Fase 0: cerrar el MCP, unificar totales y separar el contexto interno del cliente
Primera fase del plugin de propuesta consultiva (docs/superpowers/specs/ 2026-07-28-propuesta-consultiva-ia-fase0-fase1-design.md). Recupera el contexto humano que hoy se captura y se descarta, y cierra los bloqueadores que el analisis previo destapo. Seguridad (lo mas urgente): - POST /mcp no tenia NINGUNA autenticacion: cero Depends() en main.py, mientras el servicio recibe dominio publico en produccion (SERVICE_FQDN_API_8000). Cualquiera en internet podia leer y escribir cotizaciones. Ahora exige require_auth, que ya existia en app/auth.py y no se estaba usando ahi. - _obtener_cotizacion hacia SELECT c.* y devolvia dict(cot) al agente, asi que cualquier columna nueva se publicaba sola. Ahora usa lista blanca espejo de CotizacionResponse, excluyendo observacionesInternas. Totales: - Nueva calcularTotalesCotizacion() en calculators.ts como fuente de verdad unica. El calculo estaba duplicado a mano en siete consumidores. - conIva() sustituye a los `* 1.16` hardcodeados de pdf-generator y excel-builder, que ignoraban IVA_RATE y el flag incluirIva. Contexto humano: - Campo nuevo Cotizacion.observacionesInternas. La migracion es PURAMENTE ADITIVA: no mueve ni una fila. El movimiento de datos no hace falta porque la unica fila de produccion con observaciones ya contiene texto dirigido al cliente, y separarlo en dos despliegues mantiene el rollback limpio. - Dos textareas visualmente inconfundibles en el formulario. - observaciones se imprime por primera vez en el PDF y el Excel; el dato ya viajaba hasta las rutas de borrador y se tiraba. - observacionesInternas solo se ve en la app. La garantia es estructural: el campo no existe en CotizacionPDFData ni en ExcelData, asi que el generador no puede filtrarlo aunque alguien lo intente. Bugs vecinos: - orderBy explicito en las cuatro rutas de export: el PDF asume las partidas agrupadas por fase y sin orderBy podia diferir del Excel del mismo envio. - El PDF de borrador leia solo el branding, no los datos bancarios, e imprimia los hardcodeados del generador. - detalleModelo local en PDF y Excel omitia la rama "demanda": esa partida salia en $0 y sin explicacion. Ahora delegan en la version canonica. - Los bonos salen de la tabla Bono; la lista hardcodeada queda de respaldo y su texto ya no coincidia con el seed. - La palomita de los bonos mide 0pt en las fuentes base de PDFKit (verificado), o sea que salia como dos espacios. Sustituida por una vineta. Ademas: zod pasa a ser dependencia declarada. Se importaba en schemas.ts y resolvia transitivamente, asi que un npm ci --omit=dev reventaba. Verificado con build, lint y 22 comprobaciones funcionales sobre PDF y Excel reales (texto del cliente presente, texto interno ausente incluso inyectandolo a la fuerza, texto largo multipagina, y totales con y sin IVA). Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
This commit is contained in:
co-authored by
Claude Opus 5
parent
0ff783fd65
commit
9023384de9
@@ -0,0 +1,20 @@
|
||||
-- Fase 0-C, Despliegue A: separar el texto que ve el cliente del contexto interno.
|
||||
--
|
||||
-- ESTA MIGRACION ES PURAMENTE ADITIVA. No modifica ni una sola fila existente.
|
||||
-- El movimiento de datos (observaciones -> observacionesInternas) seria un
|
||||
-- Despliegue B posterior y NO se incluye aqui, por dos razones:
|
||||
--
|
||||
-- 1. Rollback seguro. No hay migraciones `down` en este repo ni servicio de
|
||||
-- backup en docker-compose.coolify.yml. Si un UPDATE vaciara "observaciones"
|
||||
-- y el despliegue se revirtiera, el codigo anterior leeria NULL en todas las
|
||||
-- filas y el asesor veria sus notas desaparecidas: sin error y sin log.
|
||||
--
|
||||
-- 2. El dato real no lo necesita. Al momento de escribir esto la unica fila de
|
||||
-- produccion con "observaciones" no vacias (UJ2606AG777, aprobada) contiene
|
||||
-- condiciones de pago dirigidas al cliente en segunda persona. Moverlas a
|
||||
-- "internas" ocultaria terminos ya acordados en un documento emitido.
|
||||
-- Esa fila ya esta clasificada correctamente donde esta.
|
||||
--
|
||||
-- IF NOT EXISTS hace la sentencia idempotente si alguien la aplica a mano.
|
||||
|
||||
ALTER TABLE "Cotizacion" ADD COLUMN IF NOT EXISTS "observacionesInternas" TEXT;
|
||||
@@ -49,7 +49,13 @@ model Cotizacion {
|
||||
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())
|
||||
|
||||
Reference in New Issue
Block a user