Files
urieljarethandClaude Opus 5 0c127b44c4 Propuesta IA: cerrar los hallazgos medios de la auditoria
El mas urgente lo cause yo al hacer que el bloqueo bloqueara de verdad: si un
aviso bloqueante caia en un campo que el panel no dejaba editar, el asesor
quedaba sin salida salvo regenerar. Ocho campos eran editables y bastantes mas
se imprimen al cliente. Ahora se pueden editar tambien la cita destacada y su
autor, las dimensiones del valor y su nota de metodologia, los resultados con su
metrica y periodo, los materiales, las decisiones con quien decide, y el backlog.
El boton de descarga del documento del cliente aparece deshabilitado cuando hay
bloqueantes, en vez de invitar a un clic que devuelve 409.

Doble propuesta: se rechaza generar. El documento asume UNA lista de alcance y
UNA caja de totales, asi que sumaria las dos opciones —que son alternativas
excluyentes— y mostraria un total que no existe. Soportarlas es rediseniar el
documento; mientras tanto es mejor no producir uno incorrecto. Hoy no hay
cotizaciones dobles en produccion, asi que no bloquea a nadie.

La transcripcion ahora se guarda con la propuesta (columna nueva, migracion
aditiva). Solo vivia en memoria durante la generacion, asi que al editar y
revalidar, R3 se quedaba sin fuente contra la cual comprobar que la cita
destacada siguiera siendo literal, y el aviso desaparecia solo.

Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
2026-07-28 21:15:03 -06:00

274 lines
8.7 KiB
Plaintext

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)
}