# 📋 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<void>;
     ```
   - 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<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`**

   ```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<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

```tsx
// 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`**

   ```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
   <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:

   ```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:
   <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`

```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
