Reemplaza el dispatcher en turnEngineV3.js por un statechart formal en
XState v5. La machine es pura: produce un effect log (pending_actions) +
un descriptor de reply (pending_reply) que el runner traduce afuera.
API externa intacta: runTurnV3 sigue retornando { plan, decision } con
shape compatible con pipeline.js. Snapshot persiste en
context.xstate_snapshot dentro del JSONB existente.
- machine/index.js: statechart top-level (idle/cart/shipping/payment/
waiting/awaiting_human) + cart sub-statechart con todo el flujo
multi-turno (searching/resolving/askingClarification/askingQuantity/
computingFromPersonas/added/showing/pricing/researching).
- guards.js: portados de fsm.js (hasCart, wantsToAddProduct, etc).
- actions.js: assigns para mutations + reply descriptors (pending_reply
con templateKey/vars/rawText). Las async no entran en la machine.
- actors.js: fromPromise wrappers de retrieveCandidates y getProductQtyRules.
- runner.js: boot con prev_context.xstate_snapshot o migrateOldContext.
NLU → nluToEvent → send → settle (espera invokes) → realizeReply
(renderReply real con rewriter) → getPersistedSnapshot → format.
- nluToEvent.js: adapter NLU intent → evento XState (1:1).
Feature flags: USE_XSTATE=1 reemplaza el path; XSTATE_SHADOW=1 corre
ambos en paralelo, devuelve legacy y loguea diffs estructurales para
validar antes de flippar prod.
16 unit tests para la machine cubren: arranque, regla universal cart-on-add,
flow de cart con strong/multi match, checkout completo (shipping/pickup/
payment/cash) y rehidratación de snapshot. 224 tests totales pasando.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Foco: matar repetición y adaptar respuestas. Los handlers tenían ~30 strings
hardcodeadas (3-7 lugares cada una). Aliases hacían substring exacto.
- pg_trgm + GIN indexes en product_aliases / alias_product_mappings.
Captura plurales, diminutivos, typos sin reglas. catalogRetrieval re-busca
el snapshot con normalized_alias cuando el query original no rinde
(vasio→vacio→Vacío).
- reply_templates table + replyTemplates.js. 20 keys, 2-3 variantes c/u
con DEFAULTS hardcodeados como fallback. pickVariant excluye las usadas
en context.recent_replies (FIFO cap 8). Wired en idle/cart/cartHelpers/
shipping/payment/waiting.
- failed_searches counter en context. count>=3 escala via humanFallback.
Reset en cada add_to_cart exitoso.
- storeContext.js: vars derivadas de getStoreConfig (delivery_zones, hours,
zonas) listas para inyectar en templates cuando los datos se carguen.
- replyRewriter.js: LLM call opcional (REPLY_REWRITER=1) que adapta el
template al hilo conversacional. 1.5s timeout, fallback al template puro.
Sólo activo en 8 slots semánticamente importantes.
- 12 unit tests para replyTemplates (rotation, recency, FIFO, vars).
208 tests totales pasando.
Plan completo: ~/.claude/plans/ok-creo-que-tiene-humming-sutton.md
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>