generator client { provider = "prisma-client" output = "../src/generated/prisma" } datasource db { provider = "postgresql" } model User { id String @id @default(cuid()) email String @unique password String name String role String @default("asesor") createdAt DateTime @default(now()) updatedAt DateTime @updatedAt cotizaciones Cotizacion[] @relation("AsesorCotizaciones") } model Cliente { id String @id @default(cuid()) nombre String empresa String? email String? telefono String? rfc String? createdAt DateTime @default(now()) updatedAt DateTime @updatedAt cotizaciones Cotizacion[] @@index([nombre, empresa]) } model Cotizacion { id String @id @default(cuid()) numero String @unique fecha DateTime @default(now()) vigencia DateTime moneda String @default("MXN") tipoCambio String @default("NA") proyecto String @default("MKT Digital") esquemaPago String @default("Pago Unico/Mensual") estado String @default("borrador") incluirBonos Boolean @default(false) incluirFinanciamiento Boolean @default(false) incluirIva Boolean @default(true) esDoble Boolean @default(false) opcionesMetadata Json? // Texto que SI ve el cliente: se imprime en el PDF y en el Excel. observaciones String? // Contexto de discovery, notas del asesor, riesgos. NO sale de la app: ningun // exportador declara este campo en su interfaz de datos (CotizacionPDFData / // ExcelData) y la herramienta MCP obtener_cotizacion usa lista blanca de // columnas. La garantia es estructural, no depende de recordar filtrarlo. observacionesInternas String? clienteId String asesorId String createdAt DateTime @default(now()) updatedAt DateTime @updatedAt cliente Cliente @relation(fields: [clienteId], references: [id]) asesor User @relation("AsesorCotizaciones", fields: [asesorId], references: [id]) servicios ServicioCotizado[] planBucefalo PlanBucefaloCotizacion? registrosHoras RegistroHoras[] propuestasIA PropuestaIA[] @@index([clienteId]) @@index([asesorId]) @@index([estado]) @@index([createdAt]) } model Categoria { id String @id @default(cuid()) nombre String @unique descripcion String? color String @default("#6b7280") activo Boolean @default(true) orden Int @default(0) createdAt DateTime @default(now()) updatedAt DateTime @updatedAt servicios ServicioCatalogo[] } model Paquete { id String @id @default(cuid()) nombre String descripcion String? activo Boolean @default(true) createdAt DateTime @default(now()) updatedAt DateTime @updatedAt fases FasePaquete[] } model FasePaquete { id String @id @default(cuid()) paqueteId String nombre String orden Int @default(0) createdAt DateTime @default(now()) updatedAt DateTime @updatedAt paquete Paquete @relation(fields: [paqueteId], references: [id], onDelete: Cascade) servicios ServicioPaquete[] @@index([paqueteId]) } model ServicioCatalogo { id String @id @default(cuid()) nombre String descripcion String? fase Int @default(1) tipoPago String @default("unico") precioBase Float tiempoEntrega String @default("4 - 10 dias") entregablesDefault Json @default("[]") categoriaId String? variante String? nivel String? activo Boolean @default(true) orden Int @default(0) createdAt DateTime @default(now()) updatedAt DateTime @updatedAt servicios ServicioCotizado[] categoriaRel Categoria? @relation(fields: [categoriaId], references: [id]) paquetes ServicioPaquete[] @@index([categoriaId]) @@index([activo, fase, orden]) } model ServicioPaquete { id String @id @default(cuid()) servicioCatalogoId String fasePaqueteId String createdAt DateTime @default(now()) updatedAt DateTime @updatedAt servicio ServicioCatalogo @relation(fields: [servicioCatalogoId], references: [id], onDelete: Cascade) fasePaquete FasePaquete @relation(fields: [fasePaqueteId], references: [id], onDelete: Cascade) @@unique([servicioCatalogoId, fasePaqueteId]) @@index([fasePaqueteId]) } model ServicioCotizado { id String @id @default(cuid()) cotizacionId String servicioCatalogoId String? nombre String? esPersonalizado Boolean @default(false) horas Float? tarifaHora Float? modeloCobro String @default("fijo") montoMinimo Float? horasIncluidas Float? opcion String? fase Int tipoPago String precio Float tiempoEntrega String entregables Json @default("[]") beneficios Json @default("[]") notas String? seleccionado Boolean @default(true) createdAt DateTime @default(now()) updatedAt DateTime @updatedAt cotizacion Cotizacion @relation(fields: [cotizacionId], references: [id], onDelete: Cascade) servicioCatalogo ServicioCatalogo? @relation(fields: [servicioCatalogoId], references: [id]) @@index([cotizacionId]) @@index([servicioCatalogoId]) } model PlanBucefaloCotizacion { id String @id @default(cuid()) cotizacionId String @unique nivel String @default("basico") precio Float seleccionado Boolean @default(true) createdAt DateTime @default(now()) updatedAt DateTime @updatedAt cotizacion Cotizacion @relation(fields: [cotizacionId], references: [id], onDelete: Cascade) } // Registro de horas trabajadas contra una cotizacion aprobada con cobro por tiempo // (modeloCobro horas/retainer/demanda). Se usa para emitir notas de pago al cliente: // cada entrada es un dia con un rango horario, una descripcion breve y una tarifa // (snapshot). `horas` se calcula del rango (calcularHorasRango) y se persiste. model RegistroHoras { id String @id @default(cuid()) cotizacionId String fecha DateTime horaInicio String horaFin String horas Float tarifaHora Float @default(0) descripcion String // Estado de cobro de la partida de horas: "por_pagar" (default) o "pagada". // Permite separar lo pendiente (nota por pagar al cliente) de lo ya cobrado // (recibo/historial) sin que los totales se mezclen. fechaPago = cuando se marco pagada. estadoPago String @default("por_pagar") fechaPago DateTime? createdAt DateTime @default(now()) updatedAt DateTime @updatedAt cotizacion Cotizacion @relation(fields: [cotizacionId], references: [id], onDelete: Cascade) @@index([cotizacionId]) @@index([fecha]) @@index([estadoPago]) } // Documento consultivo generado por IA a partir del contexto humano de la cotizacion. // Se guarda por separado lo que produjo el modelo y lo que edito el asesor, para poder // mostrar que cambio y para no perder el original al editar. model PropuestaIA { id String @id @default(cuid()) cotizacionId String estado String @default("borrador") // borrador | aprobada contenidoIA Json contenidoEditado Json? // Transcripcion sanitizada usada al generar. Sin esto, el PATCH revalida con // fuentes incompletas y R3 no puede comprobar que la cita destacada sea literal. transcripcion String? // Traza de la generacion: modelo, intentos por paso, tokens y aciertos de cache. traza Json? // Hallazgos de validacion y de contenido que el asesor debe revisar antes de enviar. avisos Json @default("[]") modelo String aprobadaPor String? aprobadaAt DateTime? createdAt DateTime @default(now()) updatedAt DateTime @updatedAt cotizacion Cotizacion @relation(fields: [cotizacionId], references: [id], onDelete: Cascade) @@index([cotizacionId]) } model Configuracion { id String @id @default(cuid()) clave String @unique valor String updatedAt DateTime @updatedAt } model Bono { id String @id @default(cuid()) numero Int titulo String descripcion String activo Boolean @default(true) } model FinanciamientoPlan { id String @id @default(cuid()) meses Int @unique tasa Float comision Float montoMinimo Float iva Float @default(0.16) }