Commit Graph

23 Commits

Author SHA1 Message Date
Lucas Tettamanti
47de1efe86 Login + ABM de operadores + audit log con UI
Backend:
- 3 migrations: system_users (citext email único, password_hash, active),
  system_sessions (UUID + expires_at + revoked_at), ALTER audit_log con
  actor_user_id/actor_email/actor_ip/action_path/summary y entity_id NULL.
- src/modules/auth/: usersRepo, sessionsRepo, passwords (bcrypt cost 10),
  auth (login/logout), bootstrap (crea admin desde ADMIN_EMAIL/PASSWORD si
  la tabla está vacía). 4 tests passwords (hash distinto cada vez, verify
  rechaza, longitud mínima 8).
- middleware/requireAuth: lee cookie bot_session, busca sesión activa,
  popula req.user. Whitelist: /styles, /components, /lib, /login, /, /home
  y SPA paths (HTML carga sin auth, el JS gatea con /api/auth/me).
- middleware/auditWriter: registra cada POST/PUT/DELETE 2xx en audit_log
  con req.user, IP, body redactado (passwords/tokens/secrets). Handlers
  pueden enriquecer summary via res.locals.audit.
- routes: /api/auth/{login,logout,me} (cookie httpOnly + DB session),
  /api/system-users (ABM con guards: cant_delete_self, cant_deactivate_self,
  email único, password ≥ 8), /api/audit-log + /api/audit-log/actors.
- src/app.js: orden estricto — webhooks (sin auth) → auth routes (sin auth)
  → /login HTML → static → SPA HTML → requireAuth + auditWriter → API admin.

Bootstrap del primer admin se ejecuta en index.js antes de listen. Usa
ADMIN_EMAIL/ADMIN_PASSWORD/ADMIN_NAME del .env. Si no están seteados y la
tabla está vacía, warn y exit (nadie puede loguearse).

Frontend:
- /login.html + /login.js: form simple, POST a /api/auth/login con
  credentials:include, redirect a ?next=... o /home. Si ya hay sesión
  activa, va directo a /home.
- public/app.js gate: chequea /api/auth/me antes de initRouter; sin sesión
  redirige a /login?next=<path>. window.__USER__ disponible para shell.
- ops-shell: nav agrega "Operadores" + "Actividad". Header derecha muestra
  email del user + botón Salir (POST /api/auth/logout + redirect /login).
- system-users-crud: CRUD lista/form (estilo settings). Crear/editar/
  cambiar password/eliminar. UI muestra badge "Vos" + bloquea eliminarse
  ni desactivarse a uno mismo.
- audit-log: tabla read-only con filtros (actor, entity_type, since,
  search), paginación 50, badges por acción, modal de detalles con
  changes JSON. /api/audit-log/actors pobla el dropdown de operadores.

Smoke E2E: login OK + cookie set, /me 200; logout → /me 401; settings POST
genera fila en audit_log con actor_email + action_path; ABM crea/borra
operadores con guards; intentar borrarse devuelve 400 cant_delete_self.

161/161 tests verde (pre-existentes 157 + 4 passwords nuevos).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-02 19:02:37 -03:00
Lucas Tettamanti
c93955fa55 Limpiar legacy delivery_* + arreglar carga del mapa en shadow DOM
Backend cleanup (todo el delivery vive ahora en delivery_zones.zones[]):
- Migration drop columns delivery_enabled / delivery_days / delivery_hours_start /
  delivery_hours_end / delivery_min_order y limpieza de schedule.delivery JSONB.
- settingsRepo: SELECT/INSERT/UPDATE sólo con campos vigentes, formatScheduleHours
  trabaja sobre pickup.
- handlers/settings: defaults sin legacy, validateSchedule sólo para pickup,
  validateDeliveryZones nuevo (estructura GeoJSON + días).
- seed_piaf_settings_and_replies + tenant_settings migrations alineadas: schedule
  sólo tiene pickup, delivery_zones queda en {} para reconfigurar via UI.

Frontend cleanup:
- settings-crud: borrado el panel "Delivery (Envío a domicilio)" + minOrder,
  toggle deliveryEnabled, y el grid schedule.delivery. collectScheduleFromInputs
  ahora sólo procesa pickup. save() ya no envía delivery_enabled/min_order.

Fix mapa (no cargaba):
- zone-map-editor: los <link> a leaflet.css/leaflet-geoman.css se inyectaban en
  document.head, que NO cruza el shadow DOM de settings-crud, por lo que las
  reglas de Leaflet no aplicaban al div del mapa. Ahora los <link> se anclan
  como hijos del propio web component; al estar en light DOM dentro del shadow
  root del padre, sí aplican.
- Espera explícita a que el stylesheet cargue antes de instanciar L.map.
- ResizeObserver + invalidateSize() para cuando el contenedor cambia tamaño
  (router muestra/oculta panel, tabs, etc).

Smoke E2E sin regresión: 1kg vacío + envío → location en Centro → "martes 12hs"
→ orden confirmada con $28.000 (producto + envío). 157/157 tests verde.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-02 15:37:47 -03:00
Lucas Tettamanti
aed79078de Zonas de delivery por polígono + horarios + location share por WhatsApp
Schema delivery_zones JSONB pasa a { zones: [{ id, name, polygon (GeoJSON),
delivery_cost, delivery_days, delivery_hours, min_order_amount, enabled }] }.
Tirado el modelo legacy de 48 barrios CABA hardcoded.

Backend:
- lib/geo.js: pointInPolygon + findZoneForPoint (ray casting, sin deps) + 9 tests.
- storeContext.checkAddressInZone ahora valida con lat/lng (necesita ubicación
  del cliente; no geocodifica texto). buildZonesForLLM expone zonas resumidas
  para el agente. summarizeDeliveryZones genera prosa con costo+días+horas.
- settingsRepo expone delivery_zones (bug pre-existente: nunca se devolvía).
- pipeline: inboundLocation ⇒ persistir order.pending_location; orderModel
  acepta pending_location, matched_zone, delivery_window.

Intake:
- evolutionParser detecta locationMessage/liveLocationMessage (Baileys).
- evolution + sim handlers propagan inboundLocation al pipeline.

Agent (DeepSeek tool-calling):
- workingMemory inyecta store.delivery.zones[], store.pickup.schedule,
  order.pending_location/matched_zone/delivery_window.
- setAddress: matchea zona con la ubicación pendiente; sin location devuelve
  need_location y el LLM le pide el pin al cliente.
- setShipping: para delivery, indica requires_location si faltan coords.
- confirmOrder: valida día+hora contra zone.delivery_days/hours o pickup.schedule.
- nueva tool set_delivery_window(day, time?) para registrar el slot pedido.
- systemPrompt agrega instrucciones de envío/zonas + flujo location share.

Frontend:
- zone-map-editor: web component (light DOM) que carga Leaflet 1.9 +
  leaflet-geoman lazy desde CDN y permite dibujar/editar polígonos sobre OSM.
  API zones get/set, eventos change/select, paleta tomada de --chart-*.
- settings-crud: borrada lista CABA_BARRIOS, nueva UI con mapa al lado y
  formulario por zona seleccionada (nombre, costo, días, horario start/end,
  mínimo, habilitada). Save serializa al schema nuevo.

Smoke E2E manual:
- "1kg vacío + envío" → bot pide pin → location en Centro → matched_zone
  $1.500, lun-sab 10-20h → "martes 12hs" → confirma orden con total + envío.
- Location en Palermo Test → mar/jue 11-19h respetado.
- Location fuera de zonas → "no llegamos a esa zona" + lista de zonas válidas.
- Domingo en Centro → rechazado con días disponibles.

157/157 tests verde.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-02 15:31:25 -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
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
d8a0677912 more work with Dockerfile and dbmate 2026-02-04 18:16:32 -03:00
Lucas Tettamanti
5e79f17d00 20260204 2026-02-04 16:06:51 -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
a489ec66a2 modularizado de prompts 2026-01-25 20:51:33 -03:00
Lucas Tettamanti
23c3d44490 audit and sync 2026-01-18 19:00:49 -03:00
Lucas Tettamanti
c7c56ddbfc productos, equivalencias, cross-sell y cantidades 2026-01-18 18:28:28 -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
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
dab52492b4 implementando openAI y busqueda de productos en woo 2026-01-05 11:15:56 -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