mirror of
https://github.com/marcogll/gloria_app.git
synced 2026-03-15 10:24:43 +00:00
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
797 lines
22 KiB
Markdown
797 lines
22 KiB
Markdown
# 📋 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
|