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
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
-
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éricosendRescheduleConfirmation()- Enviar confirmación de reacomodaciónsendDailyAgenda()- Enviar agenda diariasendCourseInquiryNotification()- Enviar notificación de consulta de cursos
-
Actualizar
src/lib/env.ts- Agregar validación de variables de entorno SMTP
-
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
-
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 -
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 -
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
-
src/app/api/calendar/reschedule/route.tsPOST /api/calendar/reschedule Request body: { appointmentId: number, newDate: string (ISO 8601), reason?: string } Response: { success: boolean, message: string, appointment: Appointment } -
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
-
src/lib/email/templates/reschedule-confirmation.ts- Template HTML del email de confirmación (mencionado arriba)
Archivos a Modificar
-
src/app/dashboard/asistente/page.tsx- Agregar botón "Reacomodar" en cada cita de la lista
- Integrar modal de reacomodación
-
src/app/dashboard/terapeuta/page.tsx- Agregar botón "Reacomodar" en cada cita del historial
- Integrar modal de reacomodación
-
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
- Agregar función
-
src/infrastructure/email/smtp.ts- Agregar función
sendRescheduleConfirmation():export async function sendRescheduleConfirmation( to: string, patientName: string, oldDate: Date, newDate: Date ): Promise<void>;
- Agregar función
Flujo de la Funcionalidad
- Usuario hace click en "Reacomodar" en una cita
- Modal se abre mostrando fecha/hora actual
- Usuario selecciona nueva fecha/hora desde calendario
- Sistema verifica disponibilidad con Google Calendar API (
/api/calendar/availability) - 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
- Actualiza evento en Google Calendar (
- 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
-
src/jobs/send-daily-agenda.tsimport 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); } }); -
src/lib/email/templates/daily-agenda.ts- Template HTML con tabla de citas (mencionado arriba)
-
src/app/api/jobs/trigger-agenda/route.tsPOST /api/jobs/trigger-agenda Para testing manual del job
Archivos a Modificar
-
src/app/layout.tsx- Importar job al inicio del archivo
import "@/jobs/send-daily-agenda"; -
src/infrastructure/email/smtp.ts- Agregar función
sendDailyAgenda():export async function sendDailyAgenda( to: string, appointments: AppointmentWithDetails[] ): Promise<void>;
- Agregar función
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
-
src/app/api/contact/courses/route.tsPOST /api/contact/courses Request body: { name: string, email: string, course: string, message: string } Response: { success: boolean, message: string } -
src/lib/email/templates/course-inquiry.ts- Template HTML de notificación (mencionado arriba)
Archivo a Modificar
src/app/cursos/page.tsx
Cambios:
-
Importar ContactForm component:
import { ContactForm } from "@/components/forms/ContactForm"; -
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> -
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) -
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
CourseInquirynueva o modelo existente) - Enviar email de notificación a admin
- Guardar registro en SQLite (puede usar tabla
-
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
-
src/app/api/payments/upload-proof/route.tsPOST /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 } -
src/lib/ocr/processor.ts// Procesador OCR (servidor) export async function processOCR(file: File): Promise<{ text: string; confidence: number; extractedData: ExtractedData; }>; -
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, }; -
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
-
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
src/app/dashboard/asistente/page.tsx- Integrar componente PaymentUpload
- Para cada payment pendiente, mostrar componente de upload
Flujo del Proceso (Híbrido)
Cliente:
- Usuario arrastra o selecciona archivo (PDF, JPG, PNG)
- Validación de tipo y tamaño (máx 5MB)
- Pre-procesamiento opcional:
- Convertir imagen a escala de grises (mejora OCR)
- Aumentar contraste
- Redimensionar si es muy grande (max 2000px)
- Enviar archivo al servidor
Servidor:
- Recibir archivo multipart/form-data
- Validar tipo (PDF, JPG, PNG) y tamaño (5MB)
- Generar nombre único:
payment_{appointmentId}_{timestamp}.{ext} - Guardar en
/public/uploads/payments/ - Procesar con OCR:
- Si es imagen: usar tesseract.js directamente
- Si es PDF: extraer texto con pdf-parse primero
- Extraer datos usando regex patterns (monto, fecha, referencia, remitente, banco)
- Guardar datos extraídos en Payment model
- 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 uploaddragging- Resaltar área cuando se arrastra archivoprocessing- Mostrar barra de progresosuccess- Mostrar datos extraídos con campos editableserror- 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
-
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)
-
Reacomodar Citas:
- Flujo con confirmación enviada (email al paciente)
- El cambio es automático al recibir el email
-
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
-
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"
-
Datos a Extraer del Comprobante:
- Monto
- Fecha de transferencia
- Clave/Referencia de transferencia
- Nombre del remitente
- Banco remitente
-
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:
-
Configurar SMTP credentials:
- Obtener credenciales del servidor de email (Gmail, Outlook, etc.)
- Crear
app passwordsi usando Gmail con 2FA - Agregar variables de entorno al
.env
-
Configurar variables de entorno:
- Agregar SMTP variables al
.env.example - Actualizar
.envcon valores reales
- Agregar SMTP variables al
-
Actualizar base de datos:
- Ejecutar
npx prisma db pushdespués de actualizar schema.prisma
- Ejecutar
-
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