Files
cotizador/scripts/verificar-propuesta-prompts.ts
T
urieljarethandClaude Opus 5 7f375b3564 Propuesta IA: prompts en capas, capa 1 cacheable y sin fugas de dinero
Verificado que ni el precio ni el cuid de ServicioCotizado aparecen en ningun
prompt: el modelo solo ve refPartida. P1 deja de depender de una validacion.

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

76 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",
};
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);