Propuesta IA: correcciones encontradas probando contra la API real de MiniMax

Tres defectos que solo se veian llamando al modelo de verdad.

1. min(2) en los factores obligaba a inventar relleno. Con una dimension sin
   cifras, MiniMax produjo "herramienta_de_seguimiento_actual=0 x canal=1" solo
   para satisfacer la restriccion. Ahora los dos factores se exigen unicamente
   cuando hay montoAnualMXN.

2. El modelo OMITE montoAnualMXN en vez de mandar null explicito, que es lo
   natural para un LLM. Exigirlo presente quemaba los tres intentos del paso.
   Ahora es .default(null) y la omision se tolera.

3. Sin confianza por factor, el modelo la metia dentro del nombre
   ("tasa_conversion (por_validar)=0.1"). Ahora es un campo.

Y el hallazgo que mas importa, porque es comercial y no de formato: el modelo
puede OMITIR un factor y aun asi cuadrar la aritmetica. En una corrida calculo
40 mensajes x 0.5 sin contestar x 52 semanas x 3,000 de utilidad POR CLIENTE =
3,120,000, asumiendo que cada mensaje sin responder es un cliente perdido. R5 lo
acepto porque los factores si multiplican al monto: R5 no puede ver lo que falta.

El ratio habria dicho "subcotizado" con el denominador inflado diez veces. Como
no se puede impedir que el modelo produzca estimaciones plausibles y erroneas, lo
que se hace es que el asesor las cache de un vistazo:
- R5b avisa cuando una cifra no tiene ni un factor confirmado.
- El anexo interno desglosa cada factor con su confianza, y alerta en rojo si el
  valor descansa entero en estimaciones.

Ademas, el presupuesto de reintentos sube (5 en extraccion, 4 en los otros dos):
MiniMax se equivoca de array de vez en cuando con schemas anidados, y la
extraccion es el paso fundacional. Una corrida real necesito los 5.

Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
This commit is contained in:
urieljareth
2026-07-28 18:59:42 -06:00
co-authored by Claude Opus 5
parent ce3a38f89d
commit 74c0374a2a
9 changed files with 143 additions and 21 deletions
+12 -1
View File
@@ -72,7 +72,7 @@ const propuesta: PropuestaConsultiva = {
{ id: "D02", urgencia: "verde", titulo: "MARCADORVERDE", descripcion: "Ya tienen la base de clientes ordenada.", confianza: "confirmado", citas: ["C01"], hechos: ["H01"] },
],
valorProblema: {
dimensiones: [{ tipo: "tiempo", descripcion: "MARCADORVALOR horas perdidas", calculo: { factores: [{ nombre: "h", valor: 8 }, { nombre: "s", valor: 52 }, { nombre: "c", valor: 400 }], montoAnualMXN: 166400 }, confianza: "estimado", hechos: ["H01"] }],
dimensiones: [{ tipo: "tiempo", descripcion: "MARCADORVALOR horas perdidas", calculo: { factores: [{ nombre: "h", valor: 8, confianza: "confirmado" }, { nombre: "s", valor: 52, confianza: "confirmado" }, { nombre: "c", valor: 400, confianza: "estimado" }], montoAnualMXN: 166400 }, confianza: "estimado", hechos: ["H01"] }],
notaMetodologia: "MARCADORMETODO ocho horas por semana a costo cargado.",
},
resultados: [{ enunciado: "MARCADORRESULTADO cero mensajes sin respuesta", metrica: "tiempo", lineaBase: null, periodoMedicion: "mensual" }],
@@ -156,6 +156,17 @@ async function main() {
check("el anexo calcula el ratio", txtA.includes("75.3"), "125280/166400");
check("el anexo interpreta el ratio", txtA.includes("objecion probable"));
check("el anexo sugiere responder con alcance", txtA.includes("no con descuento"));
check("el anexo desglosa de donde sale el valor anual", txtA.includes("De donde sale el valor anual"));
check("el anexo muestra los factores con su confianza", txtA.includes("confirmado") && txtA.includes("estimado"));
// R5b: una cifra sostenida solo por estimaciones debe llevar alerta visible.
const soloEstimado = await generarAnexoInternoPDF({
...datos,
propuesta: { ...propuesta, diagnostico: { ...propuesta.diagnostico, valorProblema: { ...propuesta.diagnostico.valorProblema,
dimensiones: [{ ...propuesta.diagnostico.valorProblema.dimensiones[0], calculo: { factores: [{ nombre: "a", valor: 8, confianza: "estimado" as const }, { nombre: "b", valor: 20800, confianza: "estimado" as const }], montoAnualMXN: 166400 } }] } } },
});
check("el anexo alerta si la cifra descansa solo en estimaciones", textoDePdf(soloEstimado).includes("puede cuadrar y aun asi estar inflada"));
check("el anexo NO alerta si hay factores confirmados", !txtA.includes("puede cuadrar y aun asi estar inflada"));
// Robustez del branding: la API de configuracion no valida los valores.
for (const malo of ["</style><script>", "rojo", "#GGG", ""]) {
+12 -5
View File
@@ -46,7 +46,7 @@ const diagBase = {
valorProblema: {
dimensiones: [{
tipo: "tiempo", descripcion: "x".repeat(12),
calculo: { factores: [{ nombre: "a", valor: 8 }, { nombre: "b", valor: 52 }], montoAnualMXN: 416 },
calculo: { factores: [{ nombre: "a", valor: 8, confianza: "confirmado" }, { nombre: "b", valor: 52, confianza: "estimado" }], montoAnualMXN: 416 },
confianza: "estimado", hechos: [],
}],
notaMetodologia: "n",
@@ -55,10 +55,17 @@ const diagBase = {
};
check("un diagnostico bien formado valida", diagnosticoSchema.safeParse(diagBase).success,
JSON.stringify(diagnosticoSchema.safeParse(diagBase).error?.issues?.[0] ?? ""));
check("el calculo exige al menos 2 factores",
!diagnosticoSchema.safeParse({ ...diagBase, valorProblema: { ...diagBase.valorProblema, dimensiones: [{ ...diagBase.valorProblema.dimensiones[0], calculo: { factores: [{ nombre: "a", valor: 1 }], montoAnualMXN: 1 } }] } }).success);
check("montoAnualMXN acepta null (sin cifras)",
diagnosticoSchema.safeParse({ ...diagBase, valorProblema: { ...diagBase.valorProblema, dimensiones: [{ ...diagBase.valorProblema.dimensiones[0], calculo: { factores: [{ nombre: "a", valor: 1 }, { nombre: "b", valor: 2 }], montoAnualMXN: null } }] } }).success);
check("con monto, exige al menos 2 factores",
!diagnosticoSchema.safeParse({ ...diagBase, valorProblema: { ...diagBase.valorProblema, dimensiones: [{ ...diagBase.valorProblema.dimensiones[0], calculo: { factores: [{ nombre: "a", valor: 1, confianza: "estimado" }], montoAnualMXN: 1 } }] } }).success);
check("sin monto, factores vacio es lo correcto",
diagnosticoSchema.safeParse({ ...diagBase, valorProblema: { ...diagBase.valorProblema, dimensiones: [{ ...diagBase.valorProblema.dimensiones[0], calculo: { factores: [], montoAnualMXN: null } }] } }).success);
check("omitir montoAnualMXN se tolera y queda en null",
diagnosticoSchema.safeParse({ ...diagBase, valorProblema: { ...diagBase.valorProblema, dimensiones: [{ ...diagBase.valorProblema.dimensiones[0], calculo: { factores: [] } }] } }).success);
check("al omitirlo, Zod lo rellena con null",
diagnosticoSchema.parse({ ...diagBase, valorProblema: { ...diagBase.valorProblema, dimensiones: [{ ...diagBase.valorProblema.dimensiones[0], calculo: { factores: [] } }] } }).valorProblema.dimensiones[0].calculo.montoAnualMXN === null);
check("un factor sin confianza se rechaza",
!diagnosticoSchema.safeParse({ ...diagBase, valorProblema: { ...diagBase.valorProblema, dimensiones: [{ ...diagBase.valorProblema.dimensiones[0], calculo: { factores: [{ nombre: "a", valor: 1 }, { nombre: "b", valor: 2 }], montoAnualMXN: 2 } }] } }).success);
check("exige al menos un hallazgo",
!diagnosticoSchema.safeParse({ ...diagBase, hallazgos: [] }).success);
check("urgencia fuera del semaforo se rechaza",
+7 -2
View File
@@ -29,7 +29,7 @@ const base = (): PropuestaConsultiva => ({
diagnostico: {
hallazgos: [{ id: "D01", urgencia: "rojo", titulo: "Prospectos sin seguimiento", descripcion: "Los mensajes del fin de semana se pierden.", confianza: "confirmado", citas: ["C01"], hechos: ["H01"] }],
valorProblema: {
dimensiones: [{ tipo: "tiempo", descripcion: "Horas perdidas de atencion", calculo: { factores: [{ nombre: "horas por semana", valor: 8 }, { nombre: "semanas", valor: 52 }, { nombre: "costo por hora", valor: 400 }], montoAnualMXN: 166400 }, confianza: "estimado", hechos: ["H01"] }],
dimensiones: [{ tipo: "tiempo", descripcion: "Horas perdidas de atencion", calculo: { factores: [{ nombre: "horas por semana", valor: 8, confianza: "confirmado" }, { nombre: "semanas", valor: 52, confianza: "confirmado" }, { nombre: "costo por hora", valor: 400, confianza: "estimado" }], montoAnualMXN: 166400 }, confianza: "estimado", hechos: ["H01"] }],
notaMetodologia: "Ocho horas por semana a costo cargado.",
},
resultados: [{ enunciado: "Ningun mensaje sin respuesta en mas de 24 horas", metrica: "tiempo de respuesta", lineaBase: null, periodoMedicion: "mensual" }],
@@ -74,9 +74,14 @@ check("R4 degrada un confirmado sin evidencia", tiene(validarPropuesta(p, econ,
p = base(); p.diagnostico.valorProblema.dimensiones[0].calculo.montoAnualMXN = 999999;
check("R5 detecta aritmetica que no cuadra", tiene(validarPropuesta(p, econ, fuentes), "R5"));
p = base(); p.diagnostico.valorProblema.dimensiones[0].calculo.montoAnualMXN = null;
p = base(); p.diagnostico.valorProblema.dimensiones[0].calculo.montoAnualMXN = null; p.diagnostico.valorProblema.dimensiones[0].calculo.factores = [];
check("R5 no se queja si no hay cifra", !tiene(validarPropuesta(p, econ, fuentes), "R5"));
p = base();
p.diagnostico.valorProblema.dimensiones[0].calculo.factores = p.diagnostico.valorProblema.dimensiones[0].calculo.factores.map((f) => ({ ...f, confianza: "estimado" as const }));
check("R5b avisa si el valor anual no tiene ni un factor confirmado", tiene(validarPropuesta(p, econ, fuentes), "R5b"));
check("R5b no avisa si hay al menos un confirmado", !tiene(validarPropuesta(base(), econ, fuentes), "R5b"));
p = base(); p.redaccion.beneficios[0].hallazgoId = "D99";
check("R6 detecta un beneficio colgante", tiene(validarPropuesta(p, econ, fuentes), "R6"));