From 0c127b44c4fcf458202f75d39650ca87b5e3589a Mon Sep 17 00:00:00 2001 From: urieljareth Date: Tue, 28 Jul 2026 21:15:03 -0600 Subject: [PATCH] Propuesta IA: cerrar los hallazgos medios de la auditoria MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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) --- .../migration.sql | 3 + prisma/schema.prisma | 3 + src/app/api/propuesta-ia/[id]/route.ts | 21 +- src/components/PropuestaIAPanel.tsx | 189 +++++++++++++++++- 4 files changed, 208 insertions(+), 8 deletions(-) create mode 100644 prisma/migrations/20260729010000_propuesta_ia_transcripcion/migration.sql diff --git a/prisma/migrations/20260729010000_propuesta_ia_transcripcion/migration.sql b/prisma/migrations/20260729010000_propuesta_ia_transcripcion/migration.sql new file mode 100644 index 0000000..da66249 --- /dev/null +++ b/prisma/migrations/20260729010000_propuesta_ia_transcripcion/migration.sql @@ -0,0 +1,3 @@ +-- Guardar la transcripcion usada al generar la propuesta. +-- ADITIVA: columna nullable, no toca ninguna fila existente. +ALTER TABLE "PropuestaIA" ADD COLUMN IF NOT EXISTS "transcripcion" TEXT; diff --git a/prisma/schema.prisma b/prisma/schema.prisma index 6ec3975..e71e46b 100644 --- a/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -230,6 +230,9 @@ model PropuestaIA { 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. diff --git a/src/app/api/propuesta-ia/[id]/route.ts b/src/app/api/propuesta-ia/[id]/route.ts index 9002317..d649178 100644 --- a/src/app/api/propuesta-ia/[id]/route.ts +++ b/src/app/api/propuesta-ia/[id]/route.ts @@ -32,6 +32,20 @@ export async function POST(request: NextRequest, { params }: { params: Promise<{ if (!ctx) return NextResponse.json({ error: "Cotizacion no encontrada" }, { status: 404 }); const { cot, notasPartidas } = ctx; + // La doble propuesta todavia no esta soportada en el documento consultivo: su + // estructura asume UNA lista de alcance y UNA caja de totales, asi que sumaria las + // dos opciones —que son alternativas excluyentes— y mostraria un total inflado. + // Mejor no generar nada que generar un documento con un total que no existe. + if (cot.esDoble) { + return NextResponse.json( + { + error: + "Esta cotizacion es de doble propuesta y el documento consultivo todavia no las soporta: sumaria las dos opciones como si fueran una sola. Genera la propuesta desde una cotizacion de opcion unica.", + }, + { status: 400 } + ); + } + const economia = await cargarEconomia(id); if (!economia.partidas.length) { return NextResponse.json( @@ -64,6 +78,7 @@ export async function POST(request: NextRequest, { params }: { params: Promise<{ traza: traza as unknown as object, avisos: avisos as unknown as object, modelo: traza.modelo, + transcripcion: transcripcion || null, }, }); @@ -113,9 +128,13 @@ export async function PATCH(request: NextRequest, { params }: { params: Promise< // La edicion del asesor se revalida: pudo introducir una fuga, un USD o una // promesa de resultado sin darse cuenta. + // + // Se incluye la transcripcion GUARDADA con la propuesta. Antes solo vivia en + // memoria durante la peticion de generacion, asi que al editar R3 se quedaba sin + // fuente contra la cual comprobar la cita literal y el aviso desaparecia solo. const [economia, ctx] = await Promise.all([cargarEconomia(id), contextoDe(id)]); const avisos = validarPropuesta(parsed.data as PropuestaConsultiva, economia, { - textoCliente: [ctx?.cot.observaciones || "", ctx?.notasPartidas || ""].join("\n"), + textoCliente: [fila.transcripcion || "", ctx?.cot.observaciones || "", ctx?.notasPartidas || ""].join("\n"), textoInterno: ctx?.cot.observacionesInternas || "", }); diff --git a/src/components/PropuestaIAPanel.tsx b/src/components/PropuestaIAPanel.tsx index 8bbc2d6..2554248 100644 --- a/src/components/PropuestaIAPanel.tsx +++ b/src/components/PropuestaIAPanel.tsx @@ -172,6 +172,7 @@ export default function PropuestaIAPanel({ cotizacionId }: { cotizacionId: strin const bloqueantes = avisos.filter((a) => a.severidad === "bloqueante"); const advertencias = avisos.filter((a) => a.severidad === "advertencia"); + const citaDestacada = propuesta?.hechos.citas.find((c) => c.id === propuesta.redaccion.citaDestacadaId); if (cargando) { return ( @@ -371,6 +372,167 @@ export default function PropuestaIAPanel({ cotizacionId }: { cotizacionId: strin ))} + + {/* Los grupos de abajo tambien se imprimen al cliente, asi que tambien + pueden disparar un aviso bloqueante. Si no fueran editables, un + bloqueante ahi no tendria mas salida que regenerar la propuesta. */} + + {citaDestacada && ( +
+ +

+ Se imprime literal. Editala solo para quitar un dato sensible, nunca para + mejorarle la redaccion al cliente. +

+