# 📋 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 ```bash 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 ```env # 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`** ```typescript 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()`: ```typescript export async function updateEvent(eventId: string, newStart: Date): Promise; ``` - Actualizar evento en Google Calendar API 4. **`src/infrastructure/email/smtp.ts`** - Agregar función `sendRescheduleConfirmation()`: ```typescript export async function sendRescheduleConfirmation( to: string, patientName: string, oldDate: Date, newDate: Date ): Promise; ``` #### 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`** ```typescript 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`** ```typescript POST /api/jobs/trigger-agenda Para testing manual del job ``` #### Archivos a Modificar 1. **`src/app/layout.tsx`** - Importar job al inicio del archivo ```typescript import "@/jobs/send-daily-agenda"; ``` 2. **`src/infrastructure/email/smtp.ts`** - Agregar función `sendDailyAgenda()`: ```typescript export async function sendDailyAgenda( to: string, appointments: AppointmentWithDetails[] ): Promise; ``` #### 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 ```tsx // Antes: ; // Después: import { useRouter } from "next/navigation"; // ... en el componente const router = useRouter(); // ... cambiar botón a: ; ``` --- ### 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`** ```typescript 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:** ```tsx import { ContactForm } from "@/components/forms/ContactForm"; ``` 2. **Agregar sección de contacto al final (después del bloque de "¿Buscas formación personalizada?"):** ```tsx

¿Tienes preguntas sobre algún curso?

``` 3. **Crear `src/components/forms/ContactForm.tsx`** (opcional) o integrar formulario directamente en cursos/page.tsx: ```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):** ```tsx // Agregar state para modal const [contactModalOpen, setContactModalOpen] = useState(false); // Cambiar botón: ; ``` #### 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` ```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:** ```bash npx prisma db push ``` #### Archivos a Crear 1. **`src/app/api/payments/upload-proof/route.ts`** ```typescript 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`** ```typescript // Procesador OCR (servidor) export async function processOCR(file: File): Promise<{ text: string; confidence: number; extractedData: ExtractedData; }>; ``` 3. **`src/lib/ocr/templates.ts`** ```typescript // 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 ```bash # 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