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
This commit is contained in:
Marco Gallegos
2026-02-02 20:45:32 -06:00
parent 5f651f2a9d
commit 423f96022a
94 changed files with 17763 additions and 50 deletions

View File

@@ -0,0 +1,796 @@
# 📋 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