mirror of
https://github.com/marcogll/gloria_app.git
synced 2026-03-15 19:24:50 +00:00
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:
796
SPRINT3_4_IMPLEMENTATION_PLAN.md
Normal file
796
SPRINT3_4_IMPLEMENTATION_PLAN.md
Normal 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
|
||||
Reference in New Issue
Block a user