Segunda tanda de rechazos observados contra datos reales (3 vueltas mas):
- Cada dimension del valor del problema tenia sus factores dentro de un objeto
`calculo`. Ese nivel no aportaba nada semantico y era justo donde el modelo se
perdia: devolvia {item: {...}, notaMetodologia} y quemaba reintentos. Ahora
`factores` y `montoAnualMXN` cuelgan de la dimension. Un nivel menos de
anidamiento en el punto exacto donde fallaba.
- subtitulo.max(300) se quedaba corto. Sube a 400.
La primera tanda de arreglos ya habia bajado los rechazos de 7 a 3 en tres
vueltas, y de 3 vueltas con reintento en la redaccion a solo 1.
Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
95 lines
5.7 KiB
TypeScript
95 lines
5.7 KiB
TypeScript
import { hechosSchema, diagnosticoSchema, redaccionSchema, aJsonSchema } 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++;
|
|
}
|
|
|
|
// ── P1 por construccion: ningun schema declara un campo de dinero de E3 ──
|
|
const todos = JSON.stringify([
|
|
aJsonSchema(hechosSchema),
|
|
aJsonSchema(diagnosticoSchema),
|
|
aJsonSchema(redaccionSchema),
|
|
]);
|
|
for (const prohibido of ["precio", "total", "subtotal", "iva", "montoMinimo", "tarifaHora", "descuento"]) {
|
|
check(`ningun schema declara la propiedad "${prohibido}"`, !todos.includes(`"${prohibido}"`));
|
|
}
|
|
// montoAnualMXN si debe existir: es el costo del problema del cliente, no un precio nuestro.
|
|
check("montoAnualMXN si existe (costo del problema del cliente)", todos.includes("montoAnualMXN"));
|
|
|
|
// ── Formato de las claves ──
|
|
const redaccionBase = {
|
|
hero: { titulo: "Titulo de prueba", subtitulo: "x".repeat(25) },
|
|
citaDestacadaId: "C01",
|
|
alcance: [{ refPartida: "P01", descripcionResultado: "x".repeat(25) }],
|
|
beneficios: [],
|
|
exclusiones: [{ texto: "x".repeat(20), razon: "porque si" }],
|
|
backlogEvolucion: [],
|
|
notasInternas: [],
|
|
};
|
|
check("una redaccion bien formada valida", redaccionSchema.safeParse(redaccionBase).success);
|
|
check("refPartida en minuscula se rechaza",
|
|
!redaccionSchema.safeParse({ ...redaccionBase, alcance: [{ refPartida: "p01", descripcionResultado: "x".repeat(25) }] }).success);
|
|
check("refPartida sin ceros se rechaza",
|
|
!redaccionSchema.safeParse({ ...redaccionBase, alcance: [{ refPartida: "P1", descripcionResultado: "x".repeat(25) }] }).success);
|
|
check("citaDestacadaId con otro prefijo se rechaza",
|
|
!redaccionSchema.safeParse({ ...redaccionBase, citaDestacadaId: "H01" }).success);
|
|
check("exige al menos una exclusion",
|
|
!redaccionSchema.safeParse({ ...redaccionBase, exclusiones: [] }).success);
|
|
check("rechaza propiedades desconocidas (strict)",
|
|
!redaccionSchema.safeParse({ ...redaccionBase, precioTotal: 99999 }).success);
|
|
|
|
// ── El calculo del valor debe venir desglosado ──
|
|
const diagBase = {
|
|
hallazgos: [{ id: "D01", urgencia: "rojo", titulo: "Titulo del hallazgo", descripcion: "x".repeat(25), confianza: "confirmado", citas: [], hechos: [] }],
|
|
valorProblema: {
|
|
dimensiones: [{
|
|
tipo: "tiempo", descripcion: "x".repeat(12),
|
|
factores: [{ nombre: "a", valor: 8, confianza: "confirmado" }, { nombre: "b", valor: 52, confianza: "estimado" }], montoAnualMXN: 416,
|
|
confianza: "estimado", hechos: [],
|
|
}],
|
|
notaMetodologia: "n",
|
|
},
|
|
resultados: [],
|
|
};
|
|
check("un diagnostico bien formado valida", diagnosticoSchema.safeParse(diagBase).success,
|
|
JSON.stringify(diagnosticoSchema.safeParse(diagBase).error?.issues?.[0] ?? ""));
|
|
check("con monto, exige al menos 2 factores",
|
|
!diagnosticoSchema.safeParse({ ...diagBase, valorProblema: { ...diagBase.valorProblema, dimensiones: [{ ...diagBase.valorProblema.dimensiones[0], 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], factores: [], montoAnualMXN: null }] } }).success);
|
|
// Una dimension SIN la clave montoAnualMXN: es lo que manda el modelo cuando no tiene
|
|
// cifras, y por eso el campo lleva .default(null) en vez de solo .nullable().
|
|
const dimSinMonto = { tipo: "tiempo", descripcion: "x".repeat(12), factores: [], confianza: "por_validar", hechos: [] };
|
|
const sinMonto = { ...diagBase, valorProblema: { ...diagBase.valorProblema, dimensiones: [dimSinMonto] } };
|
|
check("omitir montoAnualMXN se tolera", diagnosticoSchema.safeParse(sinMonto).success,
|
|
JSON.stringify(diagnosticoSchema.safeParse(sinMonto).error?.issues?.[0] ?? ""));
|
|
check("al omitirlo, Zod lo rellena con null",
|
|
diagnosticoSchema.parse(sinMonto).valorProblema.dimensiones[0].montoAnualMXN === null);
|
|
check("la dimension quedo plana: sin nivel `calculo`",
|
|
!JSON.stringify(aJsonSchema(diagnosticoSchema)).includes('"calculo"'));
|
|
check("un factor sin confianza se rechaza",
|
|
!diagnosticoSchema.safeParse({ ...diagBase, valorProblema: { ...diagBase.valorProblema, dimensiones: [{ ...diagBase.valorProblema.dimensiones[0], 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",
|
|
!diagnosticoSchema.safeParse({ ...diagBase, hallazgos: [{ ...diagBase.hallazgos[0], urgencia: "morado" }] }).success);
|
|
|
|
// ── Hechos ──
|
|
check("una cita corta se rechaza",
|
|
!hechosSchema.safeParse({ citas: [{ id: "C01", textoLiteral: "hola", quienLoDijo: "x" }], hechos: [], materialesPendientes: [], decisionesPendientes: [], mencionesFueraDeAlcance: [], redFlags: [] }).success);
|
|
check("hechos vacios son validos (material pobre)",
|
|
hechosSchema.safeParse({ citas: [], hechos: [], materialesPendientes: [], decisionesPendientes: [], mencionesFueraDeAlcance: [], redFlags: [] }).success);
|
|
|
|
// ── Conversion a JSON Schema ──
|
|
const js = aJsonSchema(redaccionSchema);
|
|
check("el JSON Schema no lleva $schema", !("$schema" in js));
|
|
check("conserva additionalProperties", JSON.stringify(js).includes("additionalProperties"));
|
|
check("conserva las descripciones para el modelo", JSON.stringify(js).includes("description"));
|
|
check("el JSON Schema es serializable", typeof JSON.stringify(js) === "string" && JSON.stringify(js).length > 200);
|
|
|
|
console.log(fallas === 0 ? "\ntodo paso\n" : `\n${fallas} fallas\n`);
|
|
process.exit(fallas === 0 ? 0 : 1);
|