Files
FRASA-CAS/api-gateway/prisma/schema.prisma
Consult AS Technology de11af985e feat(auth): login JWT en API y las 4 apps
- Modelo Usuario + seed desde lista de vendedores (18 usuarios, bcrypt cost 12)
- POST /api/auth/login + /api/auth/me; middleware Bearer en todo /api
  excepto health, login y webhook Monday; flag AUTH_ENABLED
- Username = nombre completo normalizado (case/acentos insensible),
  password inicial Frasa2026, admin vía ADMIN_PASSWORD
- Apps: LoginScreen + AuthGate (StudioBlank), token en localStorage,
  header Authorization en todas las llamadas, logout en 401
- Tests: api 136, cotizador 71, alta 65, cobranza 71, agente-ia 13, landing 4
2026-08-27 08:20:25 +00:00

194 lines
5.9 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])
}
// ---------- 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?
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())
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])
}