Files
cotizador/scripts/verificar-propuesta-prompts.ts
T
urieljarethandClaude Opus 5 bdd9319fd0 Propuesta IA: corregir lo que encontro la auditoria de herramientas y plantillas
La auditoria confirma lo que se pedia verificar: la IA SOLO puede usar las tres
herramientas internas. Una por llamada, ninguna se ejecuta jamas (el input del
modelo solo va a schema.safeParse, no hay despachador), y no se envia ningun
tool del proveedor, web search, ejecucion de codigo, MCP ni beta.

Lo que NO cumplia era la otra mitad, la correspondencia con las plantillas:

CRITICO - el documento cobraba un IVA que la cotizacion no cobra. Cada partida
se imprimia a precio SIN IVA y debajo un unico total CON IVA, rematado con
"Importes con IVA incluido", mientras el PDF economico del mismo correo dice
"los precios no incluyen IVA" sobre las mismas cifras. Las lineas no sumaban su
propio total. Ahora la caja de totales desglosa subtotal / IVA / total como la
plantilla autorizada, las lineas siguen sin IVA igual que el otro documento, y
la nota al pie dice lo que de verdad hacen las lineas.

CRITICO - los avisos bloqueantes no bloqueaban nada. hayBloqueantes() no se
llamaba en ningun sitio y la ruta del PDF nunca leia avisos: el documento del
cliente se descargaba igual con una fuga de notas internas dentro. Ahora
devuelve 409 con la lista de lo que hay que corregir. El anexo interno si se
permite: es justo el que el asesor necesita para arreglarlo.

ALTO - seis campos que SI se imprimen al cliente no pasaban por los filtros de
marca, moneda, garantias y PII: el texto y el autor de la cita destacada, la
metrica y el periodo de cada resultado, quien decide cada pendiente, y el
momento del backlog. textoVisible ahora los cubre y queda documentado que debe
seguir a lo que dibuja el PDF.

ALTO - el plan Bucefalo se caia del documento y de sus totales, aunque si
aparece en el PDF economico y en el Excel: cargarEconomia nunca leia la
relacion. El cliente recibia dos documentos con alcances distintos.

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

77 lines
4.6 KiB
TypeScript

import { SYSTEM_BASE, bloqueContexto, mensajePaso1, mensajePaso2, mensajePaso3 } from "@/lib/propuesta/prompts";
import type { DatosEconomicos } from "@/lib/propuesta/economia";
import type { Hechos, Diagnostico } from "@/lib/propuesta/schemas";
let fallas = 0;
function check(n: string, ok: boolean, d = "") {
console.log(`${ok ? " OK " : " FALLA"} | ${n}${d ? " -> " + d : ""}`);
if (!ok) fallas++;
}
// ── La capa 1 tiene que ser 100% estable o el cache no sirve de nada ──
check("no contiene el anio actual", !SYSTEM_BASE.includes(String(new Date().getFullYear())));
check("no contiene una fecha ISO", !/\d{4}-\d{2}-\d{2}/.test(SYSTEM_BASE));
check("no contiene un numero de cotizacion", !/UJ\d{4}/.test(SYSTEM_BASE));
check("supera el minimo cacheable de 512 tokens", SYSTEM_BASE.length > 2500, `${SYSTEM_BASE.length} chars`);
// ── Los diez principios y el semaforo estan presentes ──
for (const p of ["P1.", "P2.", "P3.", "P4.", "P5.", "P6.", "P7.", "P8."]) {
check(`declara el principio ${p}`, SYSTEM_BASE.includes(p));
}
check("explica el semaforo completo",
["rojo:", "ambar:", "azul:", "verde:"].every((c) => SYSTEM_BASE.includes(c)));
check("justifica por que el verde importa", SYSTEM_BASE.includes("entre pares"));
// ── Reglas de marca ──
check("nombra Bucefalo", SYSTEM_BASE.includes("Bucefalo"));
check("prohibe otros CRM", SYSTEM_BASE.includes("ningun otro CRM"));
check("prohibe USD", SYSTEM_BASE.includes("USD"));
check("prohibe garantizar resultados", SYSTEM_BASE.includes("garantizado"));
check("prohibe lenguaje corporativo vacio", SYSTEM_BASE.includes("sinergias"));
check("restringe a servicios digitales", SYSTEM_BASE.includes("UNICAMENTE servicios digitales"));
check("prohibe datos de empleados", SYSTEM_BASE.includes("nombres de empleados"));
check("cubre el caso de material pobre", SYSTEM_BASE.includes("MATERIAL ES POBRE"));
check("dice que un hueco declarado es contenido de valor", SYSTEM_BASE.includes("contenido de valor"));
// ── El contexto degrada con gracia ──
const sinNada = bloqueContexto({ transcripcion: "", notas: "", observaciones: "", cliente: "Ana", empresa: "", proyecto: "P" });
check("sin transcripcion lo declara explicitamente", sinNada.includes("No se entrego transcripcion"));
check("sin empresa no imprime parentesis vacios", !sinNada.includes("()"));
const conTodo = bloqueContexto({ transcripcion: "TRANS", notas: "NOTAS", observaciones: "OBS", cliente: "Ana", empresa: "ACME", proyecto: "P" });
check("incluye la transcripcion", conTodo.includes("TRANS"));
check("incluye las notas por partida", conTodo.includes("NOTAS"));
check("incluye las observaciones", conTodo.includes("OBS"));
check("incluye la empresa entre parentesis", conTodo.includes("(ACME)"));
// ── Los tres mensajes piden su herramienta y no filtran dinero ──
const econ: DatosEconomicos = {
partidas: [{ refPartida: "P01", servicioCotizadoId: "cuid-secreto-abc123", nombre: "Sitio web", fase: 1, tipoPago: "unico", precio: 12345, tiempoEntrega: "2 semanas", modeloCobro: "fijo", horas: null, tarifaHora: null, entregables: [] }],
totales: { subtotalUnico: 12345, subtotalMensual: 0, ivaUnico: 1975.2, ivaMensual: 0, totalUnico: 14320.2, totalMensual: 0, totalPrimerAnio: 14320.2, incluyeIva: true },
moneda: "MXN",
planBucefalo: null,
};
const ctx = { transcripcion: "t", notas: "", observaciones: "", cliente: "Ana", empresa: "ACME", proyecto: "P" };
const hechos = { citas: [], hechos: [], materialesPendientes: [], decisionesPendientes: [], mencionesFueraDeAlcance: [], redFlags: [] } as Hechos;
const diag = { hallazgos: [], valorProblema: { dimensiones: [], notaMetodologia: "" }, resultados: [] } as unknown as Diagnostico;
const m1 = mensajePaso1(ctx);
const m2 = mensajePaso2(ctx, hechos, econ);
const m3 = mensajePaso3(ctx, hechos, diag, econ);
check("paso 1 pide registrar_hechos", m1.includes("registrar_hechos"));
check("paso 2 pide registrar_diagnostico", m2.includes("registrar_diagnostico"));
check("paso 3 pide registrar_propuesta", m3.includes("registrar_propuesta"));
check("paso 1 exige citas textuales", m1.includes("TEXTUALES"));
// P1 estructural: el precio y el cuid NO pueden aparecer en ningun prompt.
for (const [nombre, msg] of [["paso 2", m2], ["paso 3", m3]] as const) {
check(`${nombre} NO filtra el precio de la partida`, !msg.includes("12345"));
check(`${nombre} NO filtra el total`, !msg.includes("14320"));
check(`${nombre} NO filtra el cuid real`, !msg.includes("cuid-secreto-abc123"));
check(`${nombre} SI incluye la refPartida`, msg.includes("P01"));
}
console.log(fallas === 0 ? "\ntodo paso\n" : `\n${fallas} fallas\n`);
process.exit(fallas === 0 ? 0 : 1);