Files
gloria_app/SPRINT3_4_IMPLEMENTATION_PLAN.md
Marco Gallegos 423f96022a feat: Complete Sprints 3 & 4 - Email, Reschedule, OCR, Upload, Contact Forms
Sprint 3 - Crisis y Agenda (100%):
- Implement SMTP email service with nodemailer
- Create email templates (reschedule, daily agenda, course inquiry)
- Add appointment reschedule functionality with modal
- Add Google Calendar updateEvent function
- Create scheduled job for daily agenda email at 10 PM
- Add manual trigger endpoint for testing

Sprint 4 - Pagos y Roles (100%):
- Add Payment proof upload with OCR (tesseract.js, pdf-parse)
- Extract data from proofs (amount, date, reference, sender, bank)
- Create PaymentUpload component with drag & drop
- Add course contact form to /cursos page
- Update Services button to navigate to /servicios
- Add Reschedule button to Assistant and Therapist dashboards
- Add PaymentUpload component to Assistant dashboard
- Add eventId field to Appointment model
- Add OCR-extracted fields to Payment model
- Update Prisma schema and generate client
- Create API endpoints for reschedule, upload-proof, courses contact
- Create manual trigger endpoint for daily agenda job
- Initialize daily agenda job in layout.tsx

Dependencies added:
- nodemailer, node-cron, tesseract.js, sharp, pdf-parse, @types/nodemailer

Files created/modified:
- src/infrastructure/email/smtp.ts
- src/lib/email/templates/*
- src/jobs/send-daily-agenda.ts
- src/app/api/calendar/reschedule/route.ts
- src/app/api/payments/upload-proof/route.ts
- src/app/api/contact/courses/route.ts
- src/app/api/jobs/trigger-agenda/route.ts
- src/components/dashboard/RescheduleModal.tsx
- src/components/dashboard/PaymentUpload.tsx
- src/components/forms/CourseContactForm.tsx
- src/app/dashboard/asistente/page.tsx (updated)
- src/app/dashboard/terapeuta/page.tsx (updated)
- src/app/cursos/page.tsx (updated)
- src/components/layout/Services.tsx (updated)
- src/infrastructure/external/calendar.ts (updated)
- src/app/layout.tsx (updated)
- prisma/schema.prisma (updated)
- src/lib/validations.ts (updated)
- src/lib/env.ts (updated)

Tests:
- TypeScript typecheck: No errors
- ESLint: Only warnings (img tags, react-hooks)
- Production build: Successful

Documentation:
- Updated CHANGELOG.md with Sprint 3/4 changes
- Updated PROGRESS.md with 100% completion status
- Updated TASKS.md with completed tasks
2026-02-02 20:45:32 -06:00

22 KiB

📋 Plan de Implementación - Sprints 3/4 Completación

Fecha de Planificación: 2026-02-02 Estado: 🟡 Pendiente de Implementación Responsable: Data-Agent + UI-Agent


📦 Resumen Ejecutivo

Este documento detalla el plan para completar el 10% faltante del Sprint 3 y el 15% faltante del Sprint 4 del Proyecto Gloria.

Estadísticas

  • Sprint 3: 90% completado → Faltan 3 tareas
  • Sprint 4: 85% completado → Faltan 3 tareas
  • Total estimado: 13-18 horas de desarrollo

📦 Nuevas Dependencias

pnpm add nodemailer node-cron tesseract.js sharp pdf-parse @types/nodemailer
Paquete Uso Sprint
nodemailer Enviar emails vía SMTP 3
node-cron Job programado para email diario 3
tesseract.js OCR para extraer texto de imágenes 4
sharp Pre-procesar imágenes (optimizar para OCR) 4
pdf-parse Extraer texto de PDFs 4
@types/nodemailer TypeScript definitions 3

🟡 FASE 1: Sprint 3 Completación (10% Pendiente)

Tarea 1.1: Configurar Servicio de Email SMTP

Objetivo: Configurar nodemailer para enviar emails desde el servidor

Duración estimada: 1-2 horas Prioridad: Alta

Archivos a Crear

  1. src/infrastructure/email/smtp.ts

    • Cliente SMTP con nodemailer
    • Configuración de transport TLS (STARTTLS)
    • Pool de conexiones para eficiencia
    • Manejo de errores con retry
    • Funciones:
      • sendEmail() - Enviar email genérico
      • sendRescheduleConfirmation() - Enviar confirmación de reacomodación
      • sendDailyAgenda() - Enviar agenda diaria
      • sendCourseInquiryNotification() - Enviar notificación de consulta de cursos
  2. Actualizar src/lib/env.ts

    • Agregar validación de variables de entorno SMTP
  3. Actualizar src/lib/validations.ts

    • Agregar esquema Zod para configuración SMTP

Variables de Entorno Nuevas

# SMTP Configuration
SMTP_HOST=smtp.gmail.com
SMTP_PORT=587
SMTP_USER=your-email@gmail.com
SMTP_PASS=your-app-password
SMTP_FROM_NAME=Gloria Niño - Terapia
SMTP_FROM_EMAIL=no-reply@glorianino.com

# Recipients
ADMIN_EMAIL=admin@glorianino.com

Especificaciones Técnicas

  • Transport: TLS (STARTTLS)
  • Pool: Múltiples conexiones para mejor performance
  • Retry: 3 intentos con exponential backoff
  • Templates: HTML con inline styles para compatibilidad
  • Logging: Registrar cada envío con timestamp y resultado

Email Templates

  1. Reschedule Confirmation (src/lib/email/templates/reschedule-confirmation.ts)

    Asunto: Confirmación de Cambio de Cita - Gloria Niño Terapia
    
    Contenido:
    - Saludo personalizado
    - Fecha/hora anterior (cancelada)
    - Fecha/hora nueva (confirmada)
    - Link al evento en Google Calendar
    - Información de contacto para cambios adicionales
    
  2. Daily Agenda (src/lib/email/templates/daily-agenda.ts)

    Asunto: 📅 Agenda para el día [fecha]
    
    Contenido:
    - Saludo
    - Tabla HTML con citas del día siguiente:
      * Hora
      * Nombre del paciente
      * Teléfono
      * Tipo (crisis/regular)
      * Estado de pago (aprobado/pendiente/rechazado)
    - Total de citas
    - Pagos pendientes
    
  3. Course Inquiry (src/lib/email/templates/course-inquiry.ts)

    Asunto: 🎓 Nueva Consulta sobre Cursos - [Nombre del curso]
    
    Contenido:
    - Datos del interesado
    - Curso de interés
    - Mensaje
    

Tarea 1.2: Funcionalidad de Reacomodar Citas

Objetivo: Permitir a asistente y terapeuta cambiar fecha/hora de citas con confirmación enviada

Duración estimada: 3-4 horas Prioridad: Alta

Archivos a Crear

  1. src/app/api/calendar/reschedule/route.ts

    POST /api/calendar/reschedule
    
    Request body: {
      appointmentId: number,
      newDate: string (ISO 8601),
      reason?: string
    }
    
    Response: {
      success: boolean,
      message: string,
      appointment: Appointment
    }
    
  2. src/components/dashboard/RescheduleModal.tsx

    • Modal con calendario para seleccionar nueva fecha/hora
    • Mostrar fecha/hora actual
    • Validar disponibilidad en tiempo real
    • Campos:
      • Date picker
      • Time slot selector
      • Reason textarea (opcional)
    • Botones:
      • Cancelar
      • Confirmar reacomodación
    • Loading states
  3. src/lib/email/templates/reschedule-confirmation.ts

    • Template HTML del email de confirmación (mencionado arriba)

Archivos a Modificar

  1. src/app/dashboard/asistente/page.tsx

    • Agregar botón "Reacomodar" en cada cita de la lista
    • Integrar modal de reacomodación
  2. src/app/dashboard/terapeuta/page.tsx

    • Agregar botón "Reacomodar" en cada cita del historial
    • Integrar modal de reacomodación
  3. src/infrastructure/external/calendar.ts

    • Agregar función updateEvent():
      export async function updateEvent(eventId: string, newStart: Date): Promise<void>;
      
    • Actualizar evento en Google Calendar API
  4. src/infrastructure/email/smtp.ts

    • Agregar función sendRescheduleConfirmation():
      export async function sendRescheduleConfirmation(
        to: string,
        patientName: string,
        oldDate: Date,
        newDate: Date
      ): Promise<void>;
      

Flujo de la Funcionalidad

  1. Usuario hace click en "Reacomodar" en una cita
  2. Modal se abre mostrando fecha/hora actual
  3. Usuario selecciona nueva fecha/hora desde calendario
  4. Sistema verifica disponibilidad con Google Calendar API (/api/calendar/availability)
  5. Si disponible:
    • Actualiza evento en Google Calendar (calendar.updateEvent())
    • Actualiza appointment en SQLite (nueva fecha)
    • Invalida caché de disponibilidad en Redis (deleteCachePattern("availability:*"))
    • Envía email de confirmación al paciente (smtp.sendRescheduleConfirmation())
    • Cierra modal y muestra mensaje de éxito
  6. Si no disponible:
    • Muestra mensaje de error: "Este horario ya está ocupado. Por favor selecciona otro."

Validaciones

  • La nueva fecha debe ser en el futuro
  • No debe colisionar con otras citas
  • Solo usuarios con roles ASSISTANT o THERAPIST pueden reacomodar

Tarea 1.3: Job Programado - Email Diario a las 10 PM

Objetivo: Enviar email a admin (Gloria) a las 10 PM con agenda del día siguiente

Duración estimada: 2-3 horas Prioridad: Alta

Archivos a Crear

  1. src/jobs/send-daily-agenda.ts

    import cron from "node-cron";
    import { prisma } from "@/infrastructure/db/prisma";
    import { sendDailyAgenda } from "@/infrastructure/email/smtp";
    
    // Schedule: 10 PM todos los días (0 22 * * *)
    cron.schedule("0 22 * * *", async () => {
      console.log("Running daily agenda job at 10 PM");
    
      try {
        // Calcular fecha de mañana
        const tomorrow = new Date();
        tomorrow.setDate(tomorrow.getDate() + 1);
        tomorrow.setHours(0, 0, 0, 0);
    
        const nextDay = new Date(tomorrow);
        nextDay.setDate(nextDay.getDate() + 1);
        nextDay.setHours(23, 59, 59, 999);
    
        // Consultar citas del día siguiente
        const appointments = await prisma.appointment.findMany({
          where: {
            date: {
              gte: tomorrow,
              lte: nextDay,
            },
            status: "confirmed",
          },
          include: {
            patient: true,
            payment: true,
          },
          orderBy: { date: "asc" },
        });
    
        // Enviar email
        await sendDailyAgenda(process.env.ADMIN_EMAIL!, appointments);
    
        console.log(`Daily agenda sent successfully. ${appointments.length} appointments found.`);
      } catch (error) {
        console.error("Error sending daily agenda:", error);
      }
    });
    
  2. src/lib/email/templates/daily-agenda.ts

    • Template HTML con tabla de citas (mencionado arriba)
  3. src/app/api/jobs/trigger-agenda/route.ts

    POST /api/jobs/trigger-agenda
    
    Para testing manual del job
    

Archivos a Modificar

  1. src/app/layout.tsx

    • Importar job al inicio del archivo
    import "@/jobs/send-daily-agenda";
    
  2. src/infrastructure/email/smtp.ts

    • Agregar función sendDailyAgenda():
      export async function sendDailyAgenda(
        to: string,
        appointments: AppointmentWithDetails[]
      ): Promise<void>;
      

Especificaciones del Job

  • Schedule: 0 22 * * * (10 PM todos los días)
  • Recuperar: Citas del día siguiente desde SQLite
  • Filtrar: Solo citas con status 'confirmed'
  • Ordenar: Por fecha/hora ascendente
  • Enviar email solo a ADMIN_EMAIL (Gloria) - NO al asistente
  • Formato: Tabla HTML con:
    • Hora de la cita
    • Nombre del paciente
    • Teléfono
    • Tipo (crisis/regular)
    • Estado de pago (aprobado/pendiente/rechazado)
  • Resumen: Total de citas, Pagos pendientes
  • Logging: Registrar cada ejecución con timestamp y resultado

🟠 FASE 2: Sprint 4 Completación (15% Pendiente)

Tarea 2.1: Botón "Ver Más Servicios" en Landing

Objetivo: Cambiar botón en sección de servicios del landing

Duración estimada: 30 min Prioridad: Baja

Archivo a Modificar

src/components/layout/Services.tsx - Línea 136-144

Cambios

// Antes:
<Button
  onClick={() => {
    const element = document.querySelector("#agendar");
    if (element) element.scrollIntoView({ behavior: "smooth" });
  }}
>
  Agenda tu Primera Sesión
</Button>;

// Después:
import { useRouter } from "next/navigation";

// ... en el componente
const router = useRouter();

// ... cambiar botón a:
<Button
  onClick={() => router.push("/servicios")}
  className="rounded-full bg-accent px-8 py-4 font-semibold text-primary transition-colors hover:bg-accent/90"
>
  Ver Más Servicios
</Button>;

Tarea 2.2: Contacto Específico para Cursos

Objetivo: Agregar sección de contacto específica en página de cursos

Duración estimada: 2-3 horas Prioridad: Media

Archivos a Crear

  1. src/app/api/contact/courses/route.ts

    POST /api/contact/courses
    
    Request body: {
      name: string,
      email: string,
      course: string,
      message: string
    }
    
    Response: {
      success: boolean,
      message: string
    }
    
  2. src/lib/email/templates/course-inquiry.ts

    • Template HTML de notificación (mencionado arriba)

Archivo a Modificar

src/app/cursos/page.tsx

Cambios:

  1. Importar ContactForm component:

    import { ContactForm } from "@/components/forms/ContactForm";
    
  2. Agregar sección de contacto al final (después del bloque de "¿Buscas formación personalizada?"):

    <motion.section
      className="mt-12 rounded-2xl bg-white p-8 shadow-lg"
      initial={{ opacity: 0, y: 30 }}
      animate={{ opacity: 1, y: 0 }}
      transition={{ duration: 0.6, delay: 0.6 }}
    >
      <h2 className="mb-6 font-serif text-2xl font-bold text-primary">
        ¿Tienes preguntas sobre algún curso?
      </h2>
      <ContactForm
        type="course"
        courses={[
          "Taller de Manejo de Ansiedad",
          "Duelo y Elaboración",
          "Comunicación Asertiva",
          "Mindfulness y Meditación",
        ]}
      />
    </motion.section>
    
  3. Crear src/components/forms/ContactForm.tsx (opcional) o integrar formulario directamente en cursos/page.tsx:

    // Campos:
    - Nombre (input, requerido)
    - Email (input email, requerido)
    - Curso de interés (select, requerido)
    - Mensaje (textarea, requerido)
    
  4. Actualizar botones "Más Información" (línea 143):

    // Agregar state para modal
    const [contactModalOpen, setContactModalOpen] = useState(false);
    
    // Cambiar botón:
    <Button
      onClick={() => setContactModalOpen(true)}
      className="bg-primary text-white hover:bg-primary/90"
    >
      Más Información
    </Button>;
    

Especificaciones

  • Formulario:

    • Nombre (requerido, validación: min 2 caracteres)
    • Email (requerido, validación: formato email)
    • Curso de interés (dropdown - requerido)
    • Mensaje (requerido, validación: min 10 caracteres)
  • Al enviar:

    • Guardar registro en SQLite (puede usar tabla CourseInquiry nueva o modelo existente)
    • Enviar email de notificación a admin
  • Diseño:

    • Consistente con paleta Nano Banana
    • Animaciones suaves con Framer Motion
    • Responsive design

Tarea 2.3: Upload de Comprobantes con OCR (Híbrido)

Objetivo: Subir comprobantes de pago, procesar con OCR para extraer datos

Duración estimada: 4-5 horas Prioridad: Alta

Base de Datos - Actualizar Modelo Payment

Archivo: prisma/schema.prisma

model Payment {
  id              Int         @id @default(autoincrement())
  appointmentId   Int         @unique
  userId          String?
  amount          Float
  status          String      @default("PENDING")
  proofUrl        String?
  approvedBy      String?
  approvedAt      DateTime?
  rejectedReason  String?
  rejectedAt      DateTime?

  // Datos extraídos por OCR
  extractedDate        DateTime?
  extractedAmount      Float?
  extractedReference   String? // Clave de transferencia
  extractedSenderName  String?
  extractedSenderBank  String?
  confidence           Float?   // % de confianza del OCR

  createdAt       DateTime    @default(now())
  updatedAt       DateTime    @updatedAt

  appointment Appointment @relation(fields: [appointmentId], references: [id], onDelete: Cascade)
  user         User?      @relation(fields: [userId], references: [id])
}

Aplicar cambios:

npx prisma db push

Archivos a Crear

  1. src/app/api/payments/upload-proof/route.ts

    POST /api/payments/upload-proof
    
    Request: multipart/form-data
    - file: File (PDF, JPG, PNG, max 5MB)
    - appointmentId: number
    
    Response: {
      success: boolean,
      fileUrl: string,
      extractedData: {
        amount?: number,
        date?: Date,
        reference?: string,
        senderName?: string,
        senderBank?: string
      },
      confidence: number // % de confianza del OCR
    }
    
  2. src/lib/ocr/processor.ts

    // Procesador OCR (servidor)
    export async function processOCR(file: File): Promise<{
      text: string;
      confidence: number;
      extractedData: ExtractedData;
    }>;
    
  3. src/lib/ocr/templates.ts

    // Regex patterns para extraer datos
    const patterns = {
      amount: /(?:monto|total|importe)[:\s]*[$]?\s*([\d,]+\.?\d*)/i,
      date: /(?:fecha|el)[\s:]*([\d]{1,2})[\/\-]([\d]{1,2})[\/\-]([\d]{2,4})/i,
      reference: /(?:referencia|clave|operación)[:\s]*([\w\d]+)/i,
      senderName: /(?:de|origen|remitente)[:\s]*([a-z\s]+)/i,
      senderBank: /(?:banco)[\s:]*([a-z\s]+)/i,
    };
    
  4. src/components/dashboard/PaymentUpload.tsx

    • Componente de upload con drag & drop
    • Previsualización de archivo
    • Barra de progreso de upload
    • Mostrar datos extraídos por OCR con opción de editar
    • Botón para reintentar si OCR falla
  5. src/lib/utils/ocr-client.ts (opcional)

    • Pre-procesamiento en cliente
    • Convertir a escala de grises
    • Aumentar contraste
    • Redimensionar si es muy grande

Archivos a Modificar

  1. src/app/dashboard/asistente/page.tsx
    • Integrar componente PaymentUpload
    • Para cada payment pendiente, mostrar componente de upload

Flujo del Proceso (Híbrido)

Cliente:

  1. Usuario arrastra o selecciona archivo (PDF, JPG, PNG)
  2. Validación de tipo y tamaño (máx 5MB)
  3. Pre-procesamiento opcional:
    • Convertir imagen a escala de grises (mejora OCR)
    • Aumentar contraste
    • Redimensionar si es muy grande (max 2000px)
  4. Enviar archivo al servidor

Servidor:

  1. Recibir archivo multipart/form-data
  2. Validar tipo (PDF, JPG, PNG) y tamaño (5MB)
  3. Generar nombre único: payment_{appointmentId}_{timestamp}.{ext}
  4. Guardar en /public/uploads/payments/
  5. Procesar con OCR:
    • Si es imagen: usar tesseract.js directamente
    • Si es PDF: extraer texto con pdf-parse primero
  6. Extraer datos usando regex patterns (monto, fecha, referencia, remitente, banco)
  7. Guardar datos extraídos en Payment model
  8. Retornar URL del archivo y datos extraídos con % de confianza

Validaciones

  • Tipo de archivo: PDF, JPG, PNG
  • Tamaño máximo: 5MB
  • Sanitización de nombre de archivo
  • Validar que appointmentId existe
  • Validar que payment no tiene proofUrl ya

Componente PaymentUpload

Características:

  • Área de drag & drop
  • Previsualización de archivo (imagen o icono PDF)
  • Barra de progreso de upload
  • Mostrar datos extraídos por OCR con opción de editar
  • Botón para reintentar si OCR falla

UI States:

  • idle - Mostrar área de upload
  • dragging - Resaltar área cuando se arrastra archivo
  • processing - Mostrar barra de progreso
  • success - Mostrar datos extraídos con campos editables
  • error - Mostrar mensaje de error y botón de reintentar

📊 Cronograma de Ejecución

Fase Tarea Prioridad Estimado
1.1 Configurar SMTP Alta 1-2 horas
1.2 Reacomodar citas Alta 3-4 horas
1.3 Email diario 10 PM Alta 2-3 horas
2.1 Botón servicios Baja 30 min
2.2 Contacto cursos Media 2-3 horas
2.3 Upload con OCR Alta 4-5 horas

Total estimado: 13-18 horas


🔍 Testing Checklist

Sprint 3

SMTP Configuration

  • Test de envío de email de prueba
  • Validar que emails se envían correctamente a ADMIN_EMAIL
  • Verificar templates HTML se renderizan correctamente en diferentes clientes de email

Reschedule Appointments

  • Test de reacomodación de cita (asistente)
  • Test de reacomodación de cita (terapeuta)
  • Verificar email de confirmación enviado al paciente
  • Test de colisión: intentar reacomodar a horario ocupado
  • Test de validación: intentar reacomodar al pasado

Daily Agenda Job

  • Test manual: trigger endpoint /api/jobs/trigger-agenda
  • Verificar email diario enviado con lista correcta de citas
  • Test de formato de tabla HTML
  • Test programado: esperar a las 10 PM y verificar email automático
  • Verificar logs de ejecución del job

Sprint 4

Services Button

  • Test de botón: navegar a /servicios
  • Verificar que el botón usa el diseño correcto (Nano Banana)

Courses Contact

  • Test de envío de consulta de curso
  • Verificar email de notificación enviado a admin
  • Validar campos requeridos (nombre, email, curso, mensaje)
  • Test de validación de email
  • Test de envío de mensaje corto (< 10 caracteres)

Upload with OCR

  • Test de upload de PDF: subida, OCR, extracción de datos
  • Test de upload de JPG: subida, OCR, extracción de datos
  • Test de upload de PNG: subida, OCR, extracción de datos
  • Test de validación de tipo: intentar subir archivo inválido (.doc, .txt)
  • Test de validación de tamaño: intentar subir archivo > 5MB
  • Test de drag & drop: arrastrar archivo al componente
  • Test de edición de datos extraídos: modificar monto detectado
  • Test de OCR accuracy con diferentes formatos de comprobantes

⚠️ Notas Importantes

  1. Reminders por WhatsApp:

    • Se decidió NO implementar recordatorios por WhatsApp para evitar baneos de Meta
    • Los recordatorios se manejan a través de:
      • Google Calendar (email 24h antes - ya implementado)
      • Email diario a las 10 PM al admin (pendiente)
  2. Reacomodar Citas:

    • Flujo con confirmación enviada (email al paciente)
    • El cambio es automático al recibir el email
  3. Upload de Comprobantes:

    • Enfoque híbrido (pre-procesamiento en cliente, OCR en servidor)
    • Extraer datos de cualquier banco sin plantillas específicas
    • Regex patterns genéricos para máxima compatibilidad
  4. Email Diario:

    • Se envía solo a admin (Gloria), no al asistente
    • Hora: 10 PM (configurable para timezone)
    • Si no hay citas, envía email con mensaje "No hay citas programadas para mañana"
  5. Datos a Extraer del Comprobante:

    • Monto
    • Fecha de transferencia
    • Clave/Referencia de transferencia
    • Nombre del remitente
    • Banco remitente
  6. OCR Accuracy:

    • Tesseract.js puede tener errores dependiendo de la calidad de la imagen
    • Se mostrará % de confianza y opción de editar manualmente
    • Se recomienda usar imágenes de alta calidad para mejores resultados

📝 Pre-requisitos

Antes de comenzar la implementación:

  1. Configurar SMTP credentials:

    • Obtener credenciales del servidor de email (Gmail, Outlook, etc.)
    • Crear app password si usando Gmail con 2FA
    • Agregar variables de entorno al .env
  2. Configurar variables de entorno:

    • Agregar SMTP variables al .env.example
    • Actualizar .env con valores reales
  3. Actualizar base de datos:

    • Ejecutar npx prisma db push después de actualizar schema.prisma
  4. Crear directorios:

    • /public/uploads/payments/ - Para guardar comprobantes

🚀 Comandos Útiles

# Instalar nuevas dependencias
pnpm add nodemailer node-cron tesseract.js sharp pdf-parse @types/nodemailer

# Aplicar cambios de base de datos
npx prisma db push

# Reiniciar servidor de desarrollo
pnpm dev

# Testing manual de job
curl -X POST http://localhost:3000/api/jobs/trigger-agenda

Documento de Plan de Implementación - Proyecto Gloria Fecha: 2026-02-02