Commit Graph

51 Commits

Author SHA1 Message Date
Lucas Tettamanti
9c69cf8911 D1 redesign: quantityParser determinista (es-AR) + 46 tests
Primer paso del rediseño tool-calling agent. Setup:
- DeepSeek V4 confirmado vía OPENAI_BASE_URL en .env (no commiteado).
- Smoke test exitoso: tools+tool_choice nativos andan con deepseek-chat.

Nuevo: src/modules/3-turn-engine/agent/quantityParser.js
- Parser determinista que pre-procesa la query del usuario para extraer
  cantidad+unidad ANTES del LLM. Resultado va al agente como side-channel
  (working_memory.preparsed); el agente puede sobreescribirlo.
- Cubre AR-es: fracciones (1/4 kg, 3/4), frases compuestas (media docena,
  cuarto kilo, tres cuartos, medio kilo, par, docena), numéricos pegados
  (2kg, 0.5kg, 500g, 2,5 kilos), numéricos solos, palabras + unidad.
- Confidence escalonado: fraction 0.95, phrase/numeric+unit 0.9,
  word+unit 0.85, numeric solo 0.7.

Tests: 46/46 pasan, incluyen casos de WhatsApp real, casos negativos,
edge cases (división por cero, string vacío, decimales con coma).

Total suite: 238/238 (192 previos + 46 quantity).

Próximos pasos del plan: D1 workingMemory.js + runTurn skeleton, D2 tools
cart, D3 tools shipping/checkout, D4 customerProfile, D5 catalog fallback,
D6 system prompt + tuning, D7 persistencia, D8 shadow validation,
D9 cleanup legacy, D10 hardening. Plan completo en
~/.claude/plans/ok-creo-que-tiene-humming-sutton.md.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-02 12:31:59 -03:00
Lucas Tettamanti
6376739f48 Frontend: scroll fix, SSE reconnect, toast global, stale states, theming
5 bloques en una pasada:

A. Scroll fixes (síntoma reportado por el usuario):
   - run-timeline: nuevo flag _userScrolledUp con detección por rAF en
     bindScroll. Auto-scroll al final SOLO si el usuario está abajo
     (umbral 150px) o si él mismo disparó un optimistic bubble. Cuando
     el usuario manda mensaje, se resetea el flag y se scrollea.
   - conversation-inspector: mismo patrón. ui:chatScroll respeta el
     flag userScrolledUp del emisor para no perseguir al usuario que
     lee arriba.
   - .bubble: + min-width:0, overflow-wrap:anywhere para URLs/JSON
     largos sin espacios.
   - <pre>: + overflow-x:auto, max-width:100%.
   - chat-simulator: textarea con resize:vertical, min/max heights
     viewport-friendly. inputs-col con overflow-y:auto.

B. Stale state options:
   - conversation-list y conversations-crud: dropdowns ahora muestran
     IDLE / CART / SHIPPING / AWAITING_HUMAN. Quitados BROWSING,
     BUILDING_ORDER, WAITING_ADDRESS, WAITING_PAYMENT, COMPLETED.
   - main.js: simulated plan.next_state pasa a CART.

C. SSE resilience (lib/sse.js):
   - connect() con backoff exponencial (1s → 30s).
   - safeParse() helper: cada evento envuelve JSON.parse en try-catch
     para que un payload malformado no rompa otros listeners.
   - reset retryDelay al primer "hello" exitoso.
   - ops-shell: indicador con dot (verde "En vivo" / naranja pulsante
     "Reconectando…") en lugar de texto plano.

D. Toast service global (public/lib/toast.js):
   - API simple: toast({ kind, text, ms }). Apila, animación slide-in,
     auto-dismiss 4s. Click para cerrar.
   - safeFetch en api.js: wrapper que dispara toast en network error y
     non-OK. Migrados simEvolution + retryLast.
   - chat-simulator usa toast en lugar de status text efímero.

E. Theming con CSS vars:
   - public/styles/theme.css con paleta completa (panels, borders,
     text, accents, bubbles, charts, radii, shadows). Linkeado desde
     index.html.
   - Migrados a var(--*) los 5 componentes más visibles:
     run-timeline, chat-simulator, conversation-inspector,
     conversation-list, home-dashboard. Custom properties heredan a
     través del shadow DOM, así que los demás componentes pueden
     migrar gradualmente sin cambios estructurales.
   - home-dashboard ya tenía vars locales: ahora apuntan a las globales.

Backend: 192/192 tests pasando. Sin cambios de API.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-02 03:25:03 -03:00
Lucas Tettamanti
7b6c62b23d Mejoras: idempotency webhook, métricas rewriter, recommend en XState, seeds
7 frentes en una pasada:

1. Idempotency en pipeline.processMessage: si el message_id ya existía
   (Evolution suele reentregar webhooks), skip todo el turn y devuelve
   { duplicate: true }. Antes el ON CONFLICT DO NOTHING evitaba el insert
   pero igual procesaba NLU + side effects.

2. Limpieza de payment residual de admin/UI:
   - ordersRepo.getMonthlyStats / getTotals: out las queries cash/card
   - home-dashboard: out el donut "Efectivo vs Tarjeta"
   - orders-crud: out columna "Pago" + sección de detalle de pago
   - conversation-inspector: out 💵💳 del resumen
   - takeovers: out payment_link en run, out payment del summary
   - public/main.js: out la invariant no_checkout_without_payment_link
   - prompts-crud: out la entry "payment" del PROMPT_LABELS
   - wooOrders.parseOrder: out lectura de payment_method/is_paid (estado
     del pago lo gestiona el comercio offline, fuera del bot)
   - ordersRepo: out is_cash/is_paid del row mapping

3. Seed migration 20260501130000_seed_piaf_settings_and_replies:
   - schedule realista para piaf (delivery/pickup días+horas)
   - delivery_zones con barrios CABA reales (Palermo, Belgrano, etc)
   - 41 reply_templates con 17 keys + variantes (todo lo de DEFAULTS)
   Permite editar respuestas sin redeploy y desbloquea {{store_hours_today}},
   {{delivery_zones_summary}} reales en los templates.

4. Address validation en shipping: nuevo checkAddressInZone() en
   storeContext.js. Cuando el usuario da dirección, se valida contra
   zonas configuradas. Si está fuera, renderiza shipping.address_out_of_zone
   con sugerencia de zonas alternativas. Sin zonas configuradas → accept-by-default.

5. Métricas rewriter: getRewriterMetrics() expuesto, contadores
   ok/fallback/timeouts + avg_ms + fallback_rate. Endpoint nuevo
   /api/metrics/rewriter.

6. Shadow XState → audit_log: el shadow mode pasa de console.log a
   insertAuditLog con entity_type='xstate_shadow'. Permite review post-mortem.

7. Recommend portado a XState: nuevo recommendActor (fromPromise) wrappea
   handleRecommend; sub-state cart.recommending invoca el actor; ingestRecommendResult
   absorbe { plan, decision } en context. RECOMMEND event funciona desde idle
   y desde cart con USE_XSTATE=1.

8. tenantId opcional en renderReply/loadReplyVariants — defaultea a
   getTenantId() del módulo shared/tenant.js. Backward-compat: callers
   pueden seguir pasando tenantId o omitirlo.

E2E tests nuevos en machine/e2e.test.js: golden flow pickup, golden flow
delivery con address-in-zone, snapshot rehydrate full flow, universal
cart-on-add desde shipping. 192/192 tests pasando (188 previos + 4 E2E).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-01 21:18:29 -03:00
Lucas Tettamanti
6b7889ef4e Mono-tenant: resolver id una vez al boot, eliminar lookups por turno
El sistema nunca fue realmente multi-tenant en la práctica. El esquema
DB conserva las columnas tenant_id (queda lista para escalar más adelante
sin migración), pero la app ahora resuelve el tenant una sola vez al
arranque y todas las capas leen de un único punto.

- src/modules/shared/tenant.js: nuevo módulo. setTenant() en boot,
  getTenantId() lo lee desde cualquier lado.
- index.js: ensureTenant() → setTenant({ id, key }). Sin cambios externos.
- pipeline.resolveTenantId(): pasa de hacer 1-2 queries a DB por turno
  a un return sincrónico del id cacheado. Mantiene firma async para no
  romper callers.
- intake handlers (sim.js, evolution.js): usan getTenantId() directo,
  sin parsing de tenant_key del chat_id ni lookup por canal.
- wooWebhooks: ya no requiere ?tenant_key=... en la query string.
  El webhook va al único tenant configurado.
- repo.js: eliminados getTenantByKey() y getTenantIdByChannel() (no más
  callers).

Plumbing del parámetro tenantId en signatures de handlers/repos/machine
queda intacto — bajar eso es ruido de alto riesgo y no aporta hoy.
188 tests pasando.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-01 21:00:22 -03:00
Lucas Tettamanti
17cea4aa9e Eliminar payment + waiting (legacy): el bot toma pedidos, no cobra
El bot conversacional no maneja pagos. Su trabajo: pedidos, datos de
entrega, dejar la orden anotada en Woo (status=pending). El cobro lo
gestiona el comercio offline. Todo lo de payment_type / is_paid /
PAYMENT / WAITING_WEBHOOKS era legacy de un flow viejo que se baja.

Nuevo flow: IDLE → CART → SHIPPING → IDLE (con orden creada).
Cuando el usuario completa shipping (pickup elegido OR delivery+address),
shipping.js emite create_order y el bot cierra con order.confirmed.

- fsm.js: 4 estados (IDLE/CART/SHIPPING/AWAITING_HUMAN). hasPaymentInfo
  e isPaid eliminados. deriveNextState gira SHIPPING→IDLE en vez de
  →PAYMENT→WAITING. ALLOWED transitions actualizadas.
- orderModel.js: createEmptyOrder() sin payment_type/is_paid.
  migrateOldContext deja de leer payment_method / mp.payment_status.
- stateHandlers: payment.js y waiting.js eliminados. shipping.js gana
  finalizeOrder() que emite create_order action y vuelve a IDLE.
- replyTemplates: payment.* y waiting.* fuera. order.confirmed nuevo,
  con 3 variantes y rewriter habilitado.
- NLU openai.js + nlu/schemas.js: select_payment fuera del enum, payment_method
  fuera de entities. Prompt sin la regla de SELECCIONAR PAGO.
- nlu/router.js + nlu/index.js: dominio "payment" eliminado.
  shouldSkipRouter ya no chequea PAYMENT.
- nlu/specialists/payment.js: eliminado.
- promptsRepo.js + promptLoader.js: PROMPT_KEYS sin "payment".
- turnEngineV3.js: switch ya no dispatcha a PAYMENT/WAITING. normalizeState
  mapea estados legacy (PAYMENT/WAITING_WEBHOOKS/COMPLETED) a IDLE.
  context_patch ya no emite payment_method.
- wooOrders.createOrder: paymentMethod param eliminado. Order queda en
  status=pending sin payment_method (cobro offline).
- pipeline.js: paymentMethod fuera del create_order glue. Invariant
  "no_checkout_without_payment_link" eliminado. signal payment_selected
  reemplazado por shipping_completed.
- XState machine: top-level PAYMENT y WAITING eliminados. SELECT_PAYMENT
  event fuera. SHIPPING ahora cierra con enqueueWooCreateOrder +
  replyOrderConfirmed → IDLE. Guards hasPayment/isPaid borrados.
- Tests fsm.test.js / orderModel.test.js / machine/index.test.js
  actualizados al nuevo contrato. 188 tests pasando.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-01 20:53:19 -03:00
Lucas Tettamanti
04ac33430f Tier 2: XState statechart como motor de turno (opt-in)
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>
2026-05-01 20:38:26 -03:00
Lucas Tettamanti
f784ddd62d Tier 1: chat quality — fuzzy aliases, reply templates, dedup, rewriter
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>
2026-05-01 19:29:02 -03:00
Lucas Tettamanti
525679cf8b local dev setup + OPENAI_BASE_URL support + dashboard fix
- CLAUDE.md con arquitectura y comandos del proyecto
- env.example: agregar LIMIT_CONVERSATIONS, MAX_CHARS_PER_MESSAGE, OPENAI_BASE_URL
- docker-compose.override: puerto 3001, extra_hosts para modelo local en Linux
- OpenAI clients: soporte OPENAI_BASE_URL para apuntar a modelo local compatible
- stats.js: sync de órdenes en background, dashboard no bloquea al cargar
- package-lock: dbmate movido a prod dependencies

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-01 18:32:22 -03:00
Lucas Tettamanti
b933db88df remove database_url 2026-02-04 18:36:28 -03:00
Lucas Tettamanti
d8a0677912 more work with Dockerfile and dbmate 2026-02-04 18:16:32 -03:00
Lucas Tettamanti
f838603877 Docker compose and coolify solved 2026-02-04 17:59:30 -03:00
Lucas Tettamanti
5e79f17d00 20260204 2026-02-04 16:06:51 -03:00
Lucas Tettamanti
2f8e267268 docker compose override for local without affecting coolify 2026-01-27 03:13:44 -03:00
Lucas Tettamanti
1e84d19db8 configs 2026-01-27 02:59:31 -03:00
Lucas Tettamanti
df9420b954 dashboard 2026-01-27 02:41:39 -03:00
Lucas Tettamanti
493f26af17 corregidos bugs de: ret, vs delivery, efectivo vs link, charsets, price query 2026-01-26 23:27:47 -03:00
Lucas Tettamanti
53293ce9b3 badges on the right, evolution api sender 2026-01-26 01:21:08 -03:00
Lucas Tettamanti
e85afab3e6 vitest 2026-01-26 00:13:03 -03:00
Lucas Tettamanti
b1c8a3685c refactor stateHandlers 2026-01-25 23:58:56 -03:00
Lucas Tettamanti
debad78781 borrado de articulos del carrito 2026-01-25 23:43:00 -03:00
Lucas Tettamanti
bd63d92c50 modificando el patron del sistema, orientado mas al usuario 2026-01-25 22:32:58 -03:00
Lucas Tettamanti
93e331535f correjido unidades y kgs 2026-01-25 21:08:23 -03:00
Lucas Tettamanti
bb947ea75e remove users, chats, etc 2026-01-25 21:04:54 -03:00
Lucas Tettamanti
a489ec66a2 modularizado de prompts 2026-01-25 20:51:33 -03:00
Lucas Tettamanti
b91ece867b routes updated 2026-01-18 20:28:27 -03:00
Lucas Tettamanti
9754347a36 pedidos 2026-01-18 20:07:40 -03:00
Lucas Tettamanti
23c3d44490 audit and sync 2026-01-18 19:00:49 -03:00
Lucas Tettamanti
3b39e706af categorias working well 2026-01-18 18:32:47 -03:00
Lucas Tettamanti
c7c56ddbfc productos, equivalencias, cross-sell y cantidades 2026-01-18 18:28:28 -03:00
Lucas Tettamanti
8cc4744c49 carrito semi saneado 2026-01-17 15:20:32 -03:00
Lucas Tettamanti
204403560e mejoras en el modelo de clarificacion de productos 2026-01-17 06:31:49 -03:00
Lucas Tettamanti
63b9ecef61 ux improved 2026-01-17 04:13:35 -03:00
Lucas Tettamanti
98e3d78e3d modules/0-ui 2026-01-15 22:53:37 -03:00
Lucas Tettamanti
ea62385e3d separated in modules 2026-01-15 22:45:33 -03:00
Lucas Tettamanti
eedd16afdb added Mercado Pago integration with new payment handling functions and updated app routing 2026-01-15 13:06:37 -03:00
Lucas Tettamanti
29fa2d127e added NLU v3 functionality with JSON schema validation and error handling in OpenAI service 2026-01-14 18:16:59 -03:00
Lucas Tettamanti
47ba68049f separado en routes 2026-01-14 13:03:11 -03:00
Lucas Tettamanti
2d01972619 travel to another computer 2026-01-10 12:39:32 -03:00
Lucas Tettamanti
ce96df9e30 no more debu legacy 2026-01-06 23:42:12 -03:00
Lucas Tettamanti
8bb21b4edb mejoras varias en frontend, separacion de intent y state, pick de articulos 2026-01-06 15:50:02 -03:00
Lucas Tettamanti
dab52492b4 implementando openAI y busqueda de productos en woo 2026-01-05 11:15:56 -03:00
Lucas Tettamanti
829823ac3d styling and create customer woo bug handled 2026-01-05 10:58:54 -03:00
Lucas Tettamanti
4de68dc996 resuelto el problema, el post para crear customer creaba pero no emitia respuesta, se deja en 2 segundos y se usa un get por email para traer la data 2026-01-02 19:59:03 -03:00
Lucas Tettamanti
303c3daafe woocommerce integration, controllers and handlers ready, evolution api simulator ready 2026-01-02 16:49:35 -03:00
Lucas Tettamanti
556c49e53d migrations for 2 tables 2026-01-02 15:31:10 -03:00
Lucas Tettamanti
947b0bd1ee skeleton of the core app 2026-01-02 00:21:46 -03:00
Lucas Tettamanti
a1fb46f70c tenant_channels migration 2026-01-02 00:21:27 -03:00
Lucas Tettamanti
5e9cc8fe1a openai service and basic tables with migrations 2026-01-01 23:20:13 -03:00
Lucas Tettamanti
5c67b27859 base con front 2026-01-01 22:49:44 -03:00
Lucas Tettamanti
863449e21d docker compose and .gitignore 2026-01-01 20:15:36 -03:00