- Vocabulario funnel: labels Intento de contacto/Abandono en Leads, etapas completas en Oportunidades (Cotizador, Ganado, Perdido...) - Apartados: pipeline Enkontrol (8 estados) + checkboxes CURP/RFC/Comprobante que la app de alta prende al subir documentos - Motivos No Compra: board catálogo + dropdown + endpoint /oportunidades/:id/perder - Perfil demográfico en Contactos mapeado desde el alta (estado civil, vivienda, propósito, fecha nac., rango de edad calculado) - Desarrollos: catálogo maestro sincronizado (precios, m2, tipo, entrega) - Actividades: board nuevo + creación automática en round-robin e inactividad - Cobranza: plan liquidado mueve el apartado a 'X Escriturar' en Monday - Scripts: setup:crm y sync:desarrollos (idempotentes) - Tests 119/119 verdes
177 lines
5.2 KiB
Plaintext
177 lines
5.2 KiB
Plaintext
// 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
|
|
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])
|
|
}
|
|
|
|
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])
|
|
}
|
|
|
|
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?
|
|
createdAt DateTime @default(now())
|
|
notas NotaPlan[]
|
|
pagos Pago[]
|
|
|
|
@@index([unidadCodigo])
|
|
}
|
|
|
|
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])
|
|
}
|
|
|
|
// Estado persistente de las automatizaciones de Monday (round-robin, recordatorios).
|
|
// Clave-valor genérico: 'rr:indice', 'rr:vendedores', 'recordatorio:<itemId>:<fecha>', etc.
|
|
model AutomatizacionState {
|
|
clave String @id
|
|
valor String
|
|
updatedAt DateTime @updatedAt
|
|
}
|
|
|
|
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])
|
|
}
|
|
|
|
// ---------- 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())
|
|
createdAt DateTime @default(now())
|
|
mensajes AIMensaje[]
|
|
}
|
|
|
|
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])
|
|
}
|