From 47995ea5040edfbbf9dff4ad8acb9739eb7f3838 Mon Sep 17 00:00:00 2001 From: Victor Date: Mon, 13 Jul 2026 13:38:02 +0200 Subject: [PATCH] fix(ux): fast-path tolerante a erratas de saludo + no validar necesidad sin permiso MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Detectado en logs reales. A) 'Hlla' (por 'Hola') se saltaba el fast-path y disparaba una llamada al modelo (7,4 s). detectGreeting ahora normaliza (minúsculas, sin acentos ni símbolos) y compara contra un set de saludos con erratas comunes (hlla, ola, buenass, etc.), es/en. Sigue exigiendo saludo puro (no captura frases). B) El agente intentaba auto-validar una necesidad de un usuario sin permiso (need:validate) -> 403 inútil. Instrucción: validar SOLO con permiso confirmado (isAdmin/coordinador); si no, dejarla en cola sin intentar. Tests: +3 casos de erratas de saludo. --- src/agent/agent.ts | 2 +- src/application/welcome.test.ts | 3 +++ src/application/welcome.ts | 36 ++++++++++++++++++++++++++------- 3 files changed, 33 insertions(+), 8 deletions(-) diff --git a/src/agent/agent.ts b/src/agent/agent.ts index f256729..db4cd91 100644 --- a/src/agent/agent.ts +++ b/src/agent/agent.ts @@ -30,7 +30,7 @@ Reglas de uso de herramientas: - UBICACIÓN (crítico, no negociable): NUNCA inventes ni adivines latitud/longitud, ni uses una ubicación por defecto o "aproximada" haciéndola pasar por la del usuario. Para cualquier búsqueda "cerca de mí" o cercana necesitas una ubicación REAL: (a) si el usuario ha compartido su ubicación, úsala; (b) si te da una dirección o lugar, geolocalízalo con rg_geocode; (c) si no tienes ninguna de las dos, NO llames a las tools de búsqueda cercana ni presentes resultados — pídele que comparta su ubicación actual (botón de WhatsApp/Telegram) o que escriba una dirección. Si rg_geocode falla, pídele las coordenadas o la ubicación compartida. Jamás muestres distancias ("a 500 m", "cerca de ti") calculadas sobre una ubicación que no sea la real del usuario; si no sabes dónde está, dilo y pídesela. - Es un requisito fundamental (MUST) que NUNCA dejes información en el limbo ni decidas no guardarla si el usuario ha pedido añadirla, registrarla o actualizarla. Si necesitas buscar en el catálogo o geolocalizar antes, hazlo en el mismo turno y llama inmediatamente a la tool de escritura correspondiente para persistir la información (por ejemplo, rg_record_inventory_entry o rg_create_need) tan pronto como el usuario te dé su confirmación o la acción sea clara. Evita pedir confirmaciones redundantes o encadenar esperas que hagan perder la información proporcionada. - Las necesidades creadas mediante rg_create_need entran inicialmente en una cola de validación (estado pendiente) y no son públicas hasta que se validan con rg_validate_need. -- Si el usuario autenticado es administrador, coordinador o un usuario certificado/validado (por ejemplo, ha iniciado sesión con su teléfono y posee permisos de gestión en rg_get_api_identity, o isAdmin: true), al crear una necesidad con rg_create_need debes llamar inmediatamente en ese mismo turno a la herramienta rg_validate_need (con valid = true) para auto-aprobarla y publicarla al instante, de manera que quede publicada de inmediato sin esperas ni pasar por la cola de validación. +- Auto-validación (SOLO con permiso confirmado): si tienes la CERTEZA de que el usuario tiene permiso de validación (p. ej. rg_get_api_identity muestra isAdmin: true o un rol de coordinador/validador de esta emergencia con el permiso need:validate), al crear una necesidad con rg_create_need llama en el mismo turno a rg_validate_need (valid = true) para publicarla al instante. Si NO estás seguro de que tenga ese permiso, NO llames a rg_validate_need: la necesidad quedará en la cola pendiente de que un coordinador la valide, y así se lo dices al usuario. No intentes validar "por si acaso" (un usuario normal no tiene need:validate y daría un 403 inútil). - Al consultar las necesidades de un recurso, si rg_list_public_needs no devuelve nada, consulta la cola con rg_list_need_queue para ver si hay necesidades pendientes de este recurso. Si las hay y el usuario está certificado/administrador, ofrécete a validarlas o valídalas directamente para publicarlas. Seguridad: diff --git a/src/application/welcome.test.ts b/src/application/welcome.test.ts index c7ed90a..b5622a1 100644 --- a/src/application/welcome.test.ts +++ b/src/application/welcome.test.ts @@ -10,6 +10,9 @@ test("detectGreeting", () => { assert.strictEqual(detectGreeting("Buenos días"), "es"); assert.strictEqual(detectGreeting("buenas tardes"), "es"); assert.strictEqual(detectGreeting("/start"), "es"); + assert.strictEqual(detectGreeting("Hlla"), "es"); // errata por "Hola" + assert.strictEqual(detectGreeting("ola"), "es"); + assert.strictEqual(detectGreeting("buenass"), "es"); // Saludos puros en inglés. assert.strictEqual(detectGreeting("Hi"), "en"); diff --git a/src/application/welcome.ts b/src/application/welcome.ts index fe915c9..8c50e29 100644 --- a/src/application/welcome.ts +++ b/src/application/welcome.ts @@ -6,17 +6,39 @@ export type Lang = "es" | "en"; -// Saludo puro (todo el mensaje es un saludo), tolerando puntuación y repeticiones. -const GREETING_RE = - /^[\s\p{P}]*(hola+|buenas(?:\s+(?:tardes|noches))?|buenos\s*d[ií]as|hey+|ey+|qu[eé]\s*tal|hi+|hello+|hey\s*there|good\s*(?:morning|afternoon|evening)|\/?start)[\s\p{P}]*$/iu; +// Saludos como CONJUNTO de tokens normalizados (minúsculas, sin acentos ni +// símbolos/espacios). Toleramos erratas comunes ("hlla", "ola", "buenass") que +// en logs reales se saltaban el fast-path y disparaban una llamada al modelo. +const ES_GREETINGS = new Set([ + "hola", "holaa", "holaaa", "holi", "holis", "holaptt", "holq", + "ola", "olaa", "hlla", "hloa", "hoola", "hla", "wola", "olis", + "buenas", "buenass", "buenasa", "wenas", "buenos", "bnas", + "buenosdias", "buenasdias", "buenostardes", "buenastardes", "buenasnoches", "buenanoches", + "ey", "eyy", "quetal", "holabuenas", "saludos", "holaa", +]); +const EN_GREETINGS = new Set([ + "hi", "hii", "hiii", "hy", "hey", "heyy", "heya", "heythere", + "hello", "helo", "helloo", "hellothere", "yo", + "goodmorning", "goodafternoon", "goodevening", "greetings", "gm", +]); +const START_TOKENS = new Set(["start"]); -const EN_RE = /\b(hi|hello|hey|good\s*(morning|afternoon|evening))\b/i; +function normalizeGreeting(text: string): string { + return text + .toLowerCase() + .normalize("NFD") + .replace(/[̀-ͯ]/g, "") // quita acentos + .replace(/[^a-z]/g, ""); // quita espacios, signos, dígitos +} /** Devuelve el idioma del saludo si el mensaje es SOLO un saludo; null si no. */ export function detectGreeting(text: string | undefined): Lang | null { - if (!text) return null; - if (!GREETING_RE.test(text)) return null; - return EN_RE.test(text) ? "en" : "es"; + if (!text || text.length > 40) return null; // un saludo es corto + const n = normalizeGreeting(text); + if (!n) return null; + if (EN_GREETINGS.has(n)) return "en"; + if (ES_GREETINGS.has(n) || START_TOKENS.has(n)) return "es"; + return null; } export interface Welcome {