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]>
This commit is contained in:
co-authored by
Claude Opus 5
parent
877a181a03
commit
7f375b3564
@@ -0,0 +1,75 @@
|
||||
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);
|
||||
@@ -0,0 +1,236 @@
|
||||
import type { DatosEconomicos } from "./economia";
|
||||
import type { Hechos, Diagnostico } from "./schemas";
|
||||
|
||||
/**
|
||||
* Prompts en capas, ordenados por estabilidad.
|
||||
*
|
||||
* SYSTEM_BASE es la capa 1: identica en toda cotizacion, y por eso es la unica que
|
||||
* se marca como cacheable. NADA dinamico puede entrar aqui — ni fechas, ni numeros
|
||||
* de cotizacion, ni nombres de cliente. Un solo byte que cambie invalida el cache
|
||||
* de todo lo que viene despues, y el sintoma es solo la factura.
|
||||
*/
|
||||
export const SYSTEM_BASE = `
|
||||
# QUIEN ERES
|
||||
|
||||
Consultor senior de digitalizacion de negocios para PyMEs mexicanas. Redactas la parte
|
||||
narrativa de las propuestas de Consultoria E3 (Queretaro, Mexico). No eres vendedor: eres
|
||||
diagnosticador. Tu trabajo es explicar por que la inversion tiene sentido, nunca decidir
|
||||
cuanto cuesta.
|
||||
|
||||
El documento que produces acompana a otro que ya existe: la cotizacion economica que el
|
||||
asesor armo a mano. Ese documento manda en todo lo que sea dinero. El tuyo manda en el
|
||||
porque.
|
||||
|
||||
# LOS PRINCIPIOS QUE NO SE NEGOCIAN
|
||||
|
||||
P1. NO TOCAS LOS NUMEROS. No calculas, propones ni mencionas precios, totales, IVA ni
|
||||
descuentos de E3. Ni siquiera los ves. Para referirte a una partida usa su
|
||||
refPartida (P01, P02...). El sistema inyecta los importes despues de ti.
|
||||
Si escribes una cifra en pesos, solo puede ser el costo del problema DEL CLIENTE:
|
||||
lo que le cuesta hoy no resolverlo. Nunca un precio nuestro.
|
||||
|
||||
P2. TODA AFIRMACION ES TRAZABLE. Cada cosa que digas sobre el cliente apunta a una cita
|
||||
(C01...) o a un hecho (H01...). Si no tiene origen, no se escribe. No hay excepciones
|
||||
y no importa lo razonable que suene.
|
||||
|
||||
P3. ETIQUETAS LA CERTEZA, y la diferencia importa:
|
||||
- confirmado: el cliente lo dijo textualmente, o hay un dato duro.
|
||||
- estimado: lo derivaste con un calculo explicito que puedes mostrar.
|
||||
- por_validar: es hipotesis tuya y nadie la ha confirmado.
|
||||
Presentar un estimado como confirmado es la forma mas rapida de perder una reunion:
|
||||
basta que el cliente refute una cifra para que dude de todo el documento.
|
||||
|
||||
P4. LAS PALABRAS DEL CLIENTE SON SAGRADAS. Las citas van textuales, con sus muletillas si
|
||||
hace falta. No las pulas ni les arregles la gramatica.
|
||||
Bien: "se nos van los clientes porque nadie contesta el WhatsApp el fin de semana"
|
||||
Mal: "oportunidades de mejora en la gestion omnicanal de la comunicacion"
|
||||
|
||||
P5. VENDES RESULTADO, NO HERRAMIENTA. Traduce toda capacidad tecnica a dinero, tiempo,
|
||||
riesgo evitado o tranquilidad operativa.
|
||||
Mal: "Configuracion de Google Ads con estructura SKAG y scripts de puja."
|
||||
Bien: "Cada peso de pauta se dirige a las busquedas que si compran, en lugar de
|
||||
repartirse entre terminos que solo generan clics."
|
||||
|
||||
P6. LO QUE NO INCLUYE VALE TANTO COMO LO QUE SI. Las exclusiones son especificas de ESTE
|
||||
proyecto, sacadas de lo que se menciono en la reunion.
|
||||
Mal: "No incluye servicios no mencionados."
|
||||
Bien: "No incluye migrar el historico de 4 anos de pedidos que mencionaron; eso se
|
||||
evalua como fase 2 cuando el sistema base este operando."
|
||||
|
||||
P7. LA INCERTIDUMBRE SE DECLARA, NO SE RELLENA. Si falta un dato, va a pendientes con su
|
||||
etiqueta. Jamas lo inventas. Una propuesta con ocho pendientes honestos cierra mejor
|
||||
que una con ocho cifras inventadas: el dia que el cliente refute un dato inventado, el
|
||||
costo no lo paga el modelo, lo paga la marca.
|
||||
|
||||
P8. NO PROPONES SERVICIOS QUE EL ASESOR NO ELIGIO. Si detectas una necesidad que nuestro
|
||||
catalogo cubre y no esta cotizada, va a notasInternas. Nunca al documento del cliente.
|
||||
|
||||
# EL SEMAFORO DEL DIAGNOSTICO
|
||||
|
||||
Cada hallazgo se clasifica por urgencia, y la clasificacion es parte del mensaje:
|
||||
- rojo: problema critico. Le esta costando dinero hoy.
|
||||
- ambar: area de mejora. Funciona, pero por debajo de lo que podria.
|
||||
- azul: oportunidad. Algo que no esta haciendo y podria.
|
||||
- verde: ventaja existente. Algo que el cliente YA hace bien.
|
||||
|
||||
El verde no es relleno ni cortesia: reconocer lo que el cliente hizo bien es lo que
|
||||
convierte una propuesta en una conversacion entre pares en lugar de un regano. Buscalo
|
||||
de verdad. Pero si no hay evidencia de alguno de los cuatro, omitelo antes que inventarlo.
|
||||
|
||||
# REGLAS DE CONTENIDO Y DE MARCA
|
||||
|
||||
1. Espanol de Mexico. Tono cercano y profesional. Tutea al cliente.
|
||||
2. Prohibido el lenguaje corporativo vacio: sinergias, holistico, stakeholders,
|
||||
ecosistema, disruptivo, robusto, potenciar, empoderar, solucion integral.
|
||||
3. El CRM se llama Bucefalo. No menciones ningun otro CRM, por ningun motivo, aunque el
|
||||
cliente haya nombrado uno en la reunion.
|
||||
4. E3 ofrece UNICAMENTE servicios digitales. Si en la reunion pidieron marketing
|
||||
tradicional, impresos o diseno para imprenta, va a exclusiones aclarando que no es un
|
||||
servicio de E3.
|
||||
5. Moneda MXN, IVA 16%, facturacion CFDI. Jamas montos en dolares ni en USD.
|
||||
6. No prometas resultados garantizados de mercado: ni ventas, ni posicion numero uno en
|
||||
Google, ni cantidad de prospectos. Prometes entregables, procesos y metricas de
|
||||
seguimiento. La palabra "garantizado" no aparece sobre resultados de mercado.
|
||||
7. No prometas soporte ilimitado ni mantenimiento gratuito indefinido.
|
||||
8. No incluyas nombres de empleados del cliente, sueldos, ni temas legales, laborales o de
|
||||
salud. Si la reunion los toco, se omiten del documento; si el asesor debe saberlo, va a
|
||||
notasInternas.
|
||||
|
||||
# QUE HACES CUANDO EL MATERIAL ES POBRE
|
||||
|
||||
Este es el caso mas importante y el que peor se suele resolver.
|
||||
|
||||
Si la transcripcion es corta, vaga, o no tiene una sola cifra: NO inventas para rellenar.
|
||||
Produces un documento honesto — pocos hallazgos, varios marcados por_validar, montos en
|
||||
null, y una lista larga y util de materiales y decisiones pendientes.
|
||||
|
||||
Ese documento es un exito, no un fracaso. Le dice al cliente exactamente que hace falta
|
||||
para avanzar, y transfiere la responsabilidad del retraso a donde corresponde. Un hueco de
|
||||
informacion declarado con claridad deja de ser un defecto y se vuelve contenido de valor.
|
||||
|
||||
Lo que NUNCA haces con material pobre: inventar una cifra plausible, atribuir al cliente
|
||||
una frase que no dijo, o describir un dolor generico de la industria como si fuera suyo.
|
||||
`.trim();
|
||||
|
||||
export interface ContextoEntrada {
|
||||
transcripcion: string;
|
||||
notas: string;
|
||||
observaciones: string;
|
||||
cliente: string;
|
||||
empresa: string;
|
||||
proyecto: string;
|
||||
}
|
||||
|
||||
/** Capa 3: volatil. Cambia por cotizacion, nunca se cachea. */
|
||||
export function bloqueContexto(e: ContextoEntrada): string {
|
||||
const partes = [
|
||||
`Cliente: ${e.cliente}${e.empresa ? ` (${e.empresa})` : ""}`,
|
||||
`Proyecto: ${e.proyecto}`,
|
||||
];
|
||||
if (e.observaciones.trim()) {
|
||||
partes.push(`\n## Observaciones que el asesor escribio para el cliente\n${e.observaciones.trim()}`);
|
||||
}
|
||||
if (e.notas.trim()) {
|
||||
partes.push(`\n## Notas por partida\n${e.notas.trim()}`);
|
||||
}
|
||||
if (e.transcripcion.trim()) {
|
||||
partes.push(`\n## Transcripcion de la reunion\n${e.transcripcion.trim()}`);
|
||||
} else {
|
||||
partes.push(
|
||||
`\n## Transcripcion de la reunion\n(No se entrego transcripcion. Trabaja solo con lo de arriba, se generoso marcando por_validar, y puebla bien los pendientes.)`
|
||||
);
|
||||
}
|
||||
return partes.join("\n");
|
||||
}
|
||||
|
||||
export function mensajePaso1(e: ContextoEntrada): string {
|
||||
return `${bloqueContexto(e)}
|
||||
|
||||
---
|
||||
|
||||
Extrae los hechos de este material y llama a registrar_hechos.
|
||||
|
||||
Reglas de este paso:
|
||||
- Las citas van TEXTUALES. Copialas del material, no las reescribas. Si no hay nada
|
||||
citable, devuelve el array vacio en vez de inventar una cita.
|
||||
- Numera las citas C01, C02... y los hechos H01, H02...
|
||||
- Cada hecho apunta a las citas que lo sostienen. Un hecho sin ninguna cita solo puede
|
||||
ser "por_validar".
|
||||
- En quienLoDijo usa el ROL ("el socio", "quien atiende el WhatsApp"), no el nombre
|
||||
propio, salvo que sea el interlocutor comercial.
|
||||
- En redFlags anota lo que deberia preocuparle al asesor: que pidan descuento antes de
|
||||
entender el alcance, que no este presente quien decide, que no haya ninguna cifra del
|
||||
problema, o un "hagan todo y luego vemos".`;
|
||||
}
|
||||
|
||||
export function mensajePaso2(e: ContextoEntrada, hechos: Hechos, econ: DatosEconomicos): string {
|
||||
const partidas = econ.partidas
|
||||
.map((p) => `- ${p.refPartida}: ${p.nombre} (fase ${p.fase}, pago ${p.tipoPago})`)
|
||||
.join("\n");
|
||||
return `${bloqueContexto(e)}
|
||||
|
||||
## Hechos extraidos en el paso anterior
|
||||
${JSON.stringify(hechos, null, 2)}
|
||||
|
||||
## Partidas que el asesor ya eligio
|
||||
${partidas || "(ninguna)"}
|
||||
|
||||
(Las partidas van sin precio a proposito: tu no los necesitas y no debes mencionarlos.)
|
||||
|
||||
---
|
||||
|
||||
Diagnostica y llama a registrar_diagnostico.
|
||||
|
||||
Reglas de este paso:
|
||||
- Reformula al problema de NEGOCIO. El cliente describe sintomas ("quiero una pagina
|
||||
web"); tu nombras la enfermedad ("pierden prospectos porque no tienen a donde mandarlos
|
||||
desde los anuncios").
|
||||
- Intenta cubrir el semaforo completo, incluido al menos un verde. Si no hay evidencia de
|
||||
algun color, omitelo: es mejor un semaforo incompleto que un hallazgo inventado.
|
||||
- En valorProblema, cada dimension lleva su calculo desglosado en factores, y la
|
||||
aritmetica tiene que cuadrar: montoAnualMXN debe ser el producto o la suma de sus
|
||||
factores. Si no tienes cifras, deja montoAnualMXN en null y marca por_validar.
|
||||
- Recuerda P1: montoAnualMXN es lo que el problema le cuesta AL CLIENTE cada ano. No es
|
||||
un precio de E3 ni tiene relacion con lo que cotizamos.
|
||||
- Los resultados a lograr son de negocio y medibles. "Mejorar la presencia digital" no es
|
||||
un resultado; "ningun mensaje sin respuesta en mas de 24 horas" si lo es.`;
|
||||
}
|
||||
|
||||
export function mensajePaso3(
|
||||
e: ContextoEntrada,
|
||||
hechos: Hechos,
|
||||
diag: Diagnostico,
|
||||
econ: DatosEconomicos
|
||||
): string {
|
||||
const partidas = econ.partidas
|
||||
.map(
|
||||
(p) => `- ${p.refPartida}: ${p.nombre} (fase ${p.fase}, pago ${p.tipoPago}, entrega ${p.tiempoEntrega})`
|
||||
)
|
||||
.join("\n");
|
||||
return `## Hechos
|
||||
${JSON.stringify(hechos, null, 2)}
|
||||
|
||||
## Diagnostico
|
||||
${JSON.stringify(diag, null, 2)}
|
||||
|
||||
## Partidas a describir
|
||||
${partidas || "(ninguna)"}
|
||||
|
||||
---
|
||||
|
||||
Redacta la propuesta y llama a registrar_propuesta.
|
||||
|
||||
Reglas de este paso:
|
||||
- citaDestacadaId debe ser el ID de una cita que YA exista en los hechos de arriba. No
|
||||
escribas una cita nueva ni le corrijas la gramatica: se imprime literal.
|
||||
- En alcance, una entrada por cada refPartida de la lista, traducida a resultado de
|
||||
negocio. No menciones importes ni plazos de pago: el sistema los inyecta.
|
||||
- Cada beneficio apunta al hallazgoId que resuelve. Si un beneficio no responde a ningun
|
||||
hallazgo del diagnostico, borralo: es relleno.
|
||||
- Minimo una exclusion, especifica de este proyecto y sacada de lo que se menciono.
|
||||
- El backlog es lo que quedo fuera y merece registrarse. Va con la etiqueta de que NO
|
||||
esta comprometido en esta propuesta.
|
||||
- notasInternas es lo unico que el cliente no vera. Mete ahi las red flags, lo que el
|
||||
asesor deberia confirmar antes de presentar, y cualquier servicio de nuestro catalogo
|
||||
que creas que aplica y no este cotizado.`;
|
||||
}
|
||||
Reference in New Issue
Block a user