mirror of
https://github.com/marcogll/gloria_app.git
synced 2026-03-15 13:24:44 +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:
BIN
prisma/prisma/dev.db
Normal file
BIN
prisma/prisma/dev.db
Normal file
Binary file not shown.
119
prisma/schema.prisma
Normal file
119
prisma/schema.prisma
Normal file
@@ -0,0 +1,119 @@
|
||||
generator client {
|
||||
provider = "prisma-client-js"
|
||||
}
|
||||
|
||||
datasource db {
|
||||
provider = "sqlite"
|
||||
url = env("DATABASE_URL")
|
||||
}
|
||||
|
||||
model User {
|
||||
id String @id @default(cuid())
|
||||
email String @unique
|
||||
phone String @unique
|
||||
name String
|
||||
role String @default("PATIENT")
|
||||
password String?
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
|
||||
payments Payment[]
|
||||
}
|
||||
|
||||
model Patient {
|
||||
phone String @id
|
||||
name String
|
||||
birthdate DateTime
|
||||
status String @default("active")
|
||||
email String?
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
|
||||
appointments Appointment[]
|
||||
clinicalNotes ClinicalNote[]
|
||||
files PatientFile[]
|
||||
}
|
||||
|
||||
model Appointment {
|
||||
id Int @id @default(autoincrement())
|
||||
patientPhone String
|
||||
date DateTime
|
||||
status String @default("pending")
|
||||
isCrisis Boolean @default(false)
|
||||
eventId String?
|
||||
paymentProofUrl String?
|
||||
payment Payment?
|
||||
paymentId Int?
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
|
||||
patient Patient @relation(fields: [patientPhone], references: [phone], onDelete: Cascade)
|
||||
|
||||
@@index([patientPhone])
|
||||
@@index([date])
|
||||
}
|
||||
|
||||
model ClinicalNote {
|
||||
id Int @id @default(autoincrement())
|
||||
patientId String
|
||||
content String
|
||||
tags String?
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
|
||||
patient Patient @relation(fields: [patientId], references: [phone], onDelete: Cascade)
|
||||
|
||||
@@index([patientId])
|
||||
}
|
||||
|
||||
model VoiceNote {
|
||||
id Int @id @default(autoincrement())
|
||||
filename String
|
||||
duration Int
|
||||
sentAt DateTime @default(now())
|
||||
expiresAt DateTime
|
||||
createdAt DateTime @default(now())
|
||||
|
||||
@@index([expiresAt])
|
||||
}
|
||||
|
||||
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?
|
||||
|
||||
extractedDate DateTime?
|
||||
extractedAmount Float?
|
||||
extractedReference String?
|
||||
extractedSenderName String?
|
||||
extractedSenderBank String?
|
||||
confidence Float?
|
||||
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
|
||||
appointment Appointment @relation(fields: [appointmentId], references: [id], onDelete: Cascade)
|
||||
user User? @relation(fields: [userId], references: [id])
|
||||
}
|
||||
|
||||
model PatientFile {
|
||||
id Int @id @default(autoincrement())
|
||||
patientId String
|
||||
type String
|
||||
filename String
|
||||
url String
|
||||
expiresAt DateTime
|
||||
createdAt DateTime @default(now())
|
||||
|
||||
patient Patient @relation(fields: [patientId], references: [phone], onDelete: Cascade)
|
||||
|
||||
@@index([patientId])
|
||||
@@index([expiresAt])
|
||||
}
|
||||
80
prisma/seed.ts
Normal file
80
prisma/seed.ts
Normal file
@@ -0,0 +1,80 @@
|
||||
import { PrismaClient } from "@prisma/client";
|
||||
|
||||
const prisma = new PrismaClient();
|
||||
|
||||
const ROLES = {
|
||||
PATIENT: "PATIENT",
|
||||
ASSISTANT: "ASSISTANT",
|
||||
THERAPIST: "THERAPIST",
|
||||
} as const;
|
||||
|
||||
async function main() {
|
||||
console.log("🌱 Seeding database...");
|
||||
|
||||
const therapist = await prisma.user.upsert({
|
||||
where: { phone: "+525512345678" },
|
||||
update: {},
|
||||
create: {
|
||||
email: "gloria@glorianino.com",
|
||||
phone: "+525512345678",
|
||||
name: "Gloria Niño",
|
||||
role: ROLES.THERAPIST,
|
||||
password: "admin123",
|
||||
},
|
||||
});
|
||||
|
||||
console.log("✅ Created therapist:", therapist.name);
|
||||
|
||||
const assistant = await prisma.user.upsert({
|
||||
where: { phone: "+525598765432" },
|
||||
update: {},
|
||||
create: {
|
||||
email: "asistente@glorianino.com",
|
||||
phone: "+525598765432",
|
||||
name: "Asistente Gloria",
|
||||
role: ROLES.ASSISTANT,
|
||||
password: "asistente123",
|
||||
},
|
||||
});
|
||||
|
||||
console.log("✅ Created assistant:", assistant.name);
|
||||
|
||||
const testPatient = await prisma.patient.upsert({
|
||||
where: { phone: "+52555555555" },
|
||||
update: {},
|
||||
create: {
|
||||
phone: "+52555555555",
|
||||
name: "Paciente Test",
|
||||
birthdate: new Date("1990-01-01"),
|
||||
email: "paciente@test.com",
|
||||
status: "active",
|
||||
},
|
||||
});
|
||||
|
||||
console.log("✅ Created patient:", testPatient.name);
|
||||
|
||||
const patientUser = await prisma.user.upsert({
|
||||
where: { phone: "+52555555555" },
|
||||
update: {},
|
||||
create: {
|
||||
email: "paciente@test.com",
|
||||
phone: "+52555555555",
|
||||
name: "Paciente Test",
|
||||
role: ROLES.PATIENT,
|
||||
password: "paciente123",
|
||||
},
|
||||
});
|
||||
|
||||
console.log("✅ Created patient user:", patientUser.name);
|
||||
|
||||
console.log("🎉 Seeding completed!");
|
||||
}
|
||||
|
||||
main()
|
||||
.catch((e) => {
|
||||
console.error("❌ Seeding error:", e);
|
||||
process.exit(1);
|
||||
})
|
||||
.finally(async () => {
|
||||
await prisma.$disconnect();
|
||||
});
|
||||
Reference in New Issue
Block a user