// FRASA API Gateway — persistencia local // Desarrollo: SQLite. Producción: Postgres 16 (cambiar provider y DATABASE_URL). generator client { provider = "prisma-client-js" } datasource db { provider = "sqlite" // prod: "postgresql" url = env("DATABASE_URL") } model Cotizacion { id String @id @default(uuid()) unidadCodigo String // Desarrollo de la unidad al momento de cotizar (DIAGONAL | ATELIER | ...) desarrollo String @default("DIAGONAL") // Usuario del ecosistema que generó la cotización (sub del JWT); null en // cotizaciones históricas o creadas sin auth (AUTH_ENABLED=false) vendedorId String? clienteNombre String clienteEmail String? clienteTelefono String? tipoPropuesta String // BANCARIO | INFONAVIT | CONTADO | SEMI_CONTADO acabados Boolean apartado Float // TODO prod: Decimal en Postgres descuento Float // fracción (0.02 = 2%) precioLista Float // TODO prod: Decimal en Postgres precioFinal Float enganche Float numMensualidades Int mensualidad Float? // null => N/A (Contado) pagoFinal Float mondayItemId String? createdAt DateTime @default(now()) @@index([unidadCodigo]) @@index([vendedorId]) } model Cliente { id String @id @default(uuid()) nombreCompleto String curp String rfc String email String? celular String? proyecto String unidadCodigo String? // Payload completo del alta como JSON serializado (SQLite no tiene Json nativo; // en Postgres puede migrarse a la columna Json sin cambiar el contrato). payloadJson String firmaPng String? // base64 opcional mondayContactoId String? mondayApartadoId String? createdAt DateTime @default(now()) documentos Documento[] @@index([curp]) } model Documento { id String @id @default(uuid()) clienteId String cliente Cliente @relation(fields: [clienteId], references: [id], onDelete: Cascade) tipo String // INE | CONSTANCIA_RFC | CURP | COMPROBANTE_DOMICILIO | ACTA_NACIMIENTO | OTRO nombreArchivo String ruta String mime String tamano Int createdAt DateTime @default(now()) @@index([clienteId]) } // Borrador del wizard de alta de clientes: el asesor guarda el avance y lo // retoma después. `datosJson` es el estado serializado del wizard (sin los // File de documentos; solo se persiste lo que cabe en JSON). model BorradorAlta { id String @id @default(uuid()) usuarioId String // sub del JWT del asesor dueño del borrador nombre String // etiqueta visible: nombre del cliente o "Sin nombre" datosJson String paso Int @default(0) createdAt DateTime @default(now()) updatedAt DateTime @updatedAt @@index([usuarioId]) } model PlanPago { id String @id @default(uuid()) ventaRef String? clienteNombre String clienteTelefono String? unidadCodigo String proyecto String fechaInicio DateTime // se normaliza a medianoche local engancheTotal Float // TODO prod: Decimal en Postgres numPagos Int mensualidad Float tasaMoraMensual Float @default(0) status String @default("ACTIVO") // ACTIVO | CANCELADO (LIQUIDADO se deriva) // Item del tablero Apartado (Monday) ligado a este plan; al liquidarse el // plan, ese item pasa a 'X Escriturar' (puente cobranza → postventa). mondayItemId String? // Vendedor dueño del plan (sub del JWT al crearlo); null en planes históricos. // Alimenta el filtro "mis cobranzas" y la ruta de recordatorios al vendedor. vendedorId String? createdAt DateTime @default(now()) notas NotaPlan[] pagos Pago[] @@index([unidadCodigo]) @@index([vendedorId]) } model Pago { id String @id @default(uuid()) planId String plan PlanPago @relation(fields: [planId], references: [id], onDelete: Cascade) numPago Int fechaVencimiento DateTime monto Float montoPagado Float @default(0) aplicaciones AplicacionPago[] @@index([planId]) @@index([fechaVencimiento]) } model AplicacionPago { id String @id @default(uuid()) pagoId String pago Pago @relation(fields: [pagoId], references: [id], onDelete: Cascade) fechaPago DateTime monto Float referencia String @@index([pagoId]) } // Vinculación lead ↔ oportunidad del funnel: cada lead capturado entra al // tablero Oportunidades en etapa 'Afluencia'; este mapping evita duplicar la // oportunidad y permite moverla de etapa cuando el lead avanza. model LeadOportunidad { id String @id @default(uuid()) leadId String @unique // item del tablero Leads (Monday) oportunidadId String @unique // item del tablero Oportunidades (Monday) createdAt DateTime @default(now()) } // Estado persistente de las automatizaciones de Monday (round-robin, recordatorios). // Clave-valor genérico: 'rr:indice', 'rr:vendedores', 'recordatorio::', etc. model AutomatizacionState { clave String @id valor String updatedAt DateTime @updatedAt } // Bitácora de recordatorios de pago enviados por el motor de cobranza // (recordatorios-cobranza.service). Sirve a la vez de dedup (no repetir el // mismo recordatorio el mismo día / respetar la ventana de re-recordatorio) y // de historial consultable por GET /api/cobranza/recordatorios. model RecordatorioLog { id String @id @default(uuid()) pagoId String tipo String // PROXIMO_VENCER | VENCIDO canal String // monday-actividad | whatsapp | log destinatario String // mondayUserId del responsable o 'sin-destinatario' mensaje String createdAt DateTime @default(now()) @@index([pagoId]) @@index([createdAt]) } model NotaPlan { id String @id @default(uuid()) planId String plan PlanPago @relation(fields: [planId], references: [id], onDelete: Cascade) fecha DateTime @default(now()) texto String @@index([planId]) } // ---------- Autenticación ---------- // Usuario del ecosistema (vendedores, coordinadores, corporativo y admin). // Sembrado por scripts/seed-usuarios.ts desde reference/vendedores.json. model Usuario { id String @id @default(uuid()) username String @unique // nombre completo normalizado (minúsculas, sin acentos) nombre String // nombre completo para mostrar passwordHash String // bcrypt (cost 12) rol String // vendedor | coordinador | corporativo | admin asignacion String? // ATELIER | RESIDENCIAL | MONTEBELLO | BROKER | ACORPORATIVO empleadoId Int? // User ID de Monday (people). null = el usuario aún no tiene cuenta Monday; // los filtros "míos" de /api/mi-dia responden vacíos con sinMapeoMonday: true mondayUserId Int? activo Boolean @default(true) createdAt DateTime @default(now()) updatedAt DateTime @updatedAt } // ---------- Agente IA interno (RAG) ---------- // Documento de la base de conocimiento; `hash` (sha256 del contenido) permite // re-indexar solo cuando cambió. model AIDocumento { id String @id @default(uuid()) titulo String @unique contenido String hash String createdAt DateTime @default(now()) chunks AIChunk[] } model AIChunk { id String @id @default(uuid()) documentoId String documento AIDocumento @relation(fields: [documentoId], references: [id], onDelete: Cascade) texto String embedding String // JSON array de floats (SQLite no tiene vector nativo) orden Int @@index([documentoId]) } model AIConversacion { id String @id @default(uuid()) // Dueño de la conversación (sub del JWT); null en anónimas/históricas. // GET /api/ai/conversations devuelve solo las del usuario (admin: todas). usuarioId String? createdAt DateTime @default(now()) mensajes AIMensaje[] @@index([usuarioId]) } model AIMensaje { id String @id @default(uuid()) conversacionId String conversacion AIConversacion @relation(fields: [conversacionId], references: [id], onDelete: Cascade) rol String // user | assistant contenido String fuentes String? // JSON [{titulo, score}] solo en mensajes del asistente createdAt DateTime @default(now()) @@index([conversacionId]) }