Independiente de notebook-infraestructura-transporte.md (esa pregunta es cómo viaja el mensaje;
esta es qué contenido tiene sentido que exista en el catálogo humano de códigos, antes incluso de
elegir transporte). Notebook vivo, igual que los otros: se amplía con fecha según avancen los
experimentos.
Surgió al hablar de una posible V2 con "nueva filosofía de chat": si dentro de las 10.000 frases posibles del cartucho actual (código 4D: Categoría-Subcategoría-Acción-Matiz) hay muchas que con códigos distintos producen la misma frase, no tiene sentido mantener un catálogo de 10.000 — mejor reducir el vocabulario que ve el humano y usar la capacidad sobrante para que el propio dispositivo rellene automáticamente ("telemetría M2M") los matices que hoy elige la persona a mano.
Dos preguntas concretas a resolver antes de decidir nada:
Método: se replicó la lógica real de composeSmartSentence (src/services/translator.ts:905),
alimentada con los datos reales de src/config/schemas.ts y src/config/cartridgeContent.ts, para
generar las 10.000 frases posibles del código 4D completo (10 categorías × 10 subcategorías × 10
acciones × 10 matices) y comparar los textos resultantes letra por letra. Script de un solo uso,
no forma parte del código de producción.
Resultado global:
| Métrica | Valor |
|---|---|
| Códigos 4D posibles | 10.000 |
| Frases finales realmente distintas | 3.700 (37%) |
| Códigos que repiten el texto exacto de otro código | 6.300 (63%) |
| Media de frases distintas por tema (Categoría+Subcategoría), de 100 combinaciones Acción×Matiz posibles | 37 |
No hay colisiones entre temas distintos (100 temas × 37 = 3.700 exacto) — toda la redundancia está dentro de la combinación Acción×Matiz de un mismo tema, no entre temas.
Desglose por acción (tema de referencia: Categoría "Evacuación/Incendio" → Subcategoría "Incendio/Evacuación General"; el patrón se repite igual en los 100 temas):
| Acción | Distintas / 10 matices | Agrupación de matices que dan el mismo texto |
|---|---|---|
| Solicitar Informe | 1 | {0,1,2,3,4,5,6,7,8,9} — el matiz no cambia nada nunca |
| Estado Despejado / OK | 3 | {0,2,4,6,7,8,9} · {1,3} · {5} |
| Retener / Esperar | 4 | {0,2,6,7,8,9} · {1,3} · {4} · {5} |
| Cancelar Alerta | 4 | {0,2,4,6,7,8} · {1,3} · {5} · {9} |
| Trasladar / Reubicar | 4 | {0,2,6,7,8,9} · {1,3} · {4} · {5} |
| Confirmar Recibido | 4 | {0,2,4,6,7,8,9} · {1} · {3} · {5} |
| Denegar / Sin Recursos | 4 | {0,2,6,7,8,9} · {1,3} · {4} · {5} |
| Avisar Incidencia | 4 | {0,2,4,6,8,9} · {1,3} · {5} · {7} |
| Pedir Auxilio / SOS | 4 | {0,2,4,6,7,8,9} · {1} · {3} · {5} |
| Activar Protocolo | 5 | {0,2,6,7,8,9} · {1} · {3} · {4} · {5} |
Causa raíz: cartridgeContent.ts solo define 1-2 variantes de texto con matiz propio por acción
(el resto de matices cae en una plantilla "catch-all" genérica) — está marcado explícitamente en el
código como "contenido beta para la demo de inversores", no como límite estructural. Parte de la
"distinción" que sí existe es solo puntuación (¡...! para matices 1/3, ¿...? para el 5), no
contenido realmente distinto.
Implicación para el tamaño del catálogo: el contenido que ya existe hoy diferencia 3.700 mensajes, no 1.000. Recortar a un número redondo como 1.000 no sería solo "quitar el desperdicio" — también fusionaría o perdería variedad que ya funciona. Si el objetivo es eliminar solo la redundancia, el número que refleja lo que hay hoy ronda 3.700-4.000, no 1.000. Si 1.000 es una meta deliberada de simplicidad (aceptando perder variedad), decidir a mano qué mensajes sobreviven en vez de dejar que un recorte numérico lo decida.
De los 10 matices del cartucho actual (schemas.ts → nuances), se dividen en dos grupos según si
reflejan un hecho que el propio dispositivo ya puede conocer, o un juicio que debe seguir siendo
decisión humana:
Grupo A — el dispositivo ya tiene el dato, candidato a auto-rellenar:
Grupo B — juicio humano, no debe automatizarse:
Conclusión de esta pasada: la idea de "telemetría interna entre dispositivos rellena matices" es acertada solo para el Grupo A. Ahí, además, resuelve dos problemas a la vez: quita fricción al humano (no tiene que acordarse de marcarlo) y reduce exactamente la parte del catálogo que hoy es más redundante. El Grupo B debe seguir siendo elección explícita del humano aunque eso conserve algo de la redundancia actual — ahí la redundancia es el precio de dejar la decisión donde debe estar.
La pregunta: ningún operario va a aprenderse un código de 4 dígitos con su significado, por guiado que esté el proceso. ¿Cómo evitarlo?
Lo que ya existe hoy y va en la buena dirección: el flujo real de entrada (no la demo de
inversores) ya es por reconocimiento, no por recuerdo — el operario escribe o dicta en lenguaje
natural (src/components/Chat/ChatInput.tsx) y translator.encodeNaturalText() traduce a código
por detrás. Comprobado además que esa traducción ya es determinista: mismo texto → siempre el
mismo código (empates resueltos siempre a favor del primer candidato por orden fijo, matiz por
defecto siempre "Estándar" cuando el texto no da señal — translator.ts:623-660). No hace falta
arreglar nada ahí.
Lo que sí contradice el objetivo: hoy el código SÍ se enseña siempre, de forma prominente, en
cada burbuja de mensaje (badge #{rawCode} en MessageBubble.tsx:58-63), y también aparece en el
propio cuadro de texto tras traducir (el código sustituye la frase escrita). Es decir: aunque la
entrada ya es "por reconocimiento", la confirmación visual actual sigue enseñando el número.
Propuesta de diseño (solo para una V2, ver más abajo por qué no para V1 tal cual):
#{rawCode} por defecto en la burbuja del mensaje, plegado dentro del mismo
desplegable que ya existe para el desglose conceptual (showBreakdown) — no hace falta un patrón
nuevo, ya está ahí.quickChips, hoy precargada por contacto en
mockData.ts) por dos listas separadas, con el mismo modelo mental que "Favoritos" y
"Recientes" en la app de Teléfono de cualquier móvil:#1234) solo se muestra de forma visible en esas dos listas de acceso
rápido — es ahí donde tiene sentido que alguien lo memorice, porque ya lo está usando de verdad
y repetido (memorización por uso real, no por estudio de una tabla).Por qué esto NO debería aplicarse a la V1 (demo de inversores) solo para tapar el problema de duplicados: ocultar el código no arregla el fallo de la Medición 1 (63% de códigos duplicados por contenido sin escribir) — solo lo hace menos visible. Si alguien lo descubre después de todos modos (probando el teclado numérico, o en una auditoría técnica), parece que se ocultó a propósito en vez de solucionarse, lo cual es peor para la confianza que dejarlo visible con el hueco de contenido aún sin cerrar. Además, para V1 mostrar el código puede ser una decisión de producto válida por sí misma (parte del argumento de venta: "esto es un protocolo real de 4 dígitos, no magia").
Si de verdad preocupa que el fallo de duplicados se note en una demo con inversores, el arreglo
correcto es cerrar el contenido que falta (Medición 1) — probablemente solo para el puñado de
códigos que aparecen en el guion real de la demo (quickChips de mockData.ts +
timeline de SurgicalDemo.tsx), no las 10.000 combinaciones. Eso sí arregla el fallo de verdad,
y sirve para V1 y V2 a la vez — ocultar la interfaz solo serviría para V2, y ahí el problema de
fondo (Medición 1) seguiría sin resolverse igualmente y habría que abordarlo aparte.
La pregunta: ¿podría el sistema recomendar opciones de respuesta a partir del mensaje que se acaba de recibir?
Decisión de diseño clave: tabla de reglas estática y determinista
(CRITICAL_REPLY_SUGGESTIONS en cartridgeContent.ts), no un modelo estadístico/entrenado tipo
Smart Reply de Gmail — mantiene la sugerencia auditable línea por línea y no rompe la premisa de
NumChat de funcionar sin red ni servidor. La respuesta sugerida siempre conserva el mismo tema
(Categoría/Subcategoría) que el mensaje recibido, solo cambia la Acción, y el Matiz siempre se deja
en "Estándar" — el sistema no adivina la urgencia por el operario.
Tabla de primera versión (a revisar con feedback real de un vertical de emergencias, ver notebook-mvp-validacion-institucional.md):
| Acción recibida | Respuestas sugeridas |
|---|---|
| Estado Despejado/OK | Confirmar Recibido, Solicitar Informe |
| Activar Protocolo | Confirmar Recibido, Retener/Esperar, Avisar Incidencia |
| Retener/Esperar | Confirmar Recibido, Solicitar Informe |
| Cancelar Alerta | Confirmar Recibido |
| Trasladar/Reubicar | Confirmar Recibido, Solicitar Informe |
| Confirmar Recibido | Estado Despejado/OK, Solicitar Informe |
| Denegar/Sin Recursos | Solicitar Informe, Pedir Auxilio/SOS |
| Solicitar Informe | Estado Despejado/OK, Avisar Incidencia, Pedir Auxilio/SOS |
| Avisar Incidencia | Confirmar Recibido, Activar Protocolo, Pedir Auxilio/SOS |
| Pedir Auxilio/SOS | Confirmar Recibido, Activar Protocolo, Denegar/Sin Recursos |
El problema de UX que casi se cuela: el plan inicial era una fila nueva "💬 Responder:" además de "⭐ Favoritos" y "🔥 Frecuentes" — es decir, tres filas de chips apiladas. Se corrigió a tiempo: eso contradice directamente la Medición 3 (menos carga cognitiva, no más). Solución adoptada: una sola fila fusionada, con un icono por chip que indica el origen (💬 respuesta contextual, ⭐ favorito, 🔥 frecuente), en orden de prioridad fijo y sin duplicar un código que encaje en más de una categoría. La fila ya tenía scroll horizontal de fábrica, así que no hizo falta limitar el total por espacio — solo se mantiene un tope en Frecuentes (ahora 10, antes 6) para que "frecuente" siga significando algo, no por sitio en pantalla.
Implementado (2026-09-11):
CRITICAL_REPLY_SUGGESTIONS +
getSuggestedReplyActions().getReplySuggestions(): toma el primer token
del mensaje recibido, exige categoría+acción presentes, y construye los códigos de respuesta
preservando Categoría/Subcategoría, con Matiz 0.SuggestionsRow que fusiona
contextual + Favoritos + Frecuentes por orden de prioridad, deduplicando por código; cae al
listado fijo "Rápidos" solo cuando las tres fuentes están vacías.Verificado: npm test sigue en 42/42 + 8/8. Script de prueba directo sobre
translator.getReplySuggestions() confirma que, por ejemplo, recibir #9111 (Activar Protocolo,
Simulacro) sugiere #9150 (Confirmar Recibido), #9120 (Retener/Esperar) y #9180 (Avisar
Incidencia) — mismo tema, Matiz 0, tal como se diseñó. Comprobado también en el navegador que la
fila fusionada de Favoritos+Frecuentes ya renderiza correctamente con un icono por chip (la parte
💬 contextual usa el mismo mecanismo, verificado a nivel de lógica; no se forzó una recepción real
de dos dispositivos en esta sesión de pruebas).
cartridgeContent.ts (más variantes
escritas cambiaría el porcentaje de redundancia real).#{rawCode} de MessageBubble.tsx dentro del desplegable de
desglose existente, en vez de mostrarlo siempre.chatService.ts, respaldado en localStorage por cartucho, sin red)
+ hook useQuickAccess.ts. MessageBubble.tsx gana un
botón de estrella en los mensajes propios; ChatInput.tsx sustituye la fila fija
"Rápidos" por "⭐ Favoritos" y "🔥 Frecuentes" (mínimo 2 envíos para contar como frecuente,
excluye lo que ya es favorito) en cuanto hay historial real, y cae al listado fijo de
siempre mientras no lo hay. El conteo se registra en sendCode() de App.tsx, el único
punto por el que pasa cualquier envío real. Verificado a mano en el navegador: favorito y
frecuente aparecen correctamente y sobreviven a recargar la página; npm test sigue en
42/42 + 8/8.SurgicalDemo.tsx (la demo teatral con guion militar/rescate) tiene sus frases escritas a
mano por paso, completamente al margen de cartridgeContent.ts — no le afecta la Medición 1
en absoluto. Lo que sí usa el motor real es el chat funcional (mockData.ts, cartucho
critical). Como las plantillas de Acción se comparten entre los 100 temas, se completaron
las 10 variantes de Matiz para las 10 Acciones (100 frases en vez de ~18), arreglando el
catálogo completo de una vez, no solo los códigos sueltos de quickChips.
Verificado: repetido el script de conteo → 10.000/10.000 frases distintas (0%
duplicados, antes 37%/63%). npm test sigue en 42/42 + simulación acústica 8/8. Comprobado
en el chat real del navegador que #1105 y #1108 (mismo tema, distinto matiz) ya
renderizan frases distintas y correctas. Cambio puramente de contenido/semántica — no toca
acousticProtocol.ts ni el formato que viaja entre dispositivos.Script de un solo uso (Node + tsx) que importa directamente schemas.ts y cartridgeContent.ts
del código real de la app y reproduce la lógica de composeSmartSentence para generar y comparar
las 10.000 frases posibles. No se guardó como parte del repositorio — si se quiere repetir la
medición, reconstruir el script es trivial a partir de los dos ficheros de datos citados arriba.