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. +
+