# CLAUDE.md This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. ## Commands ```bash # Development npm run dev # Start with nodemon auto-reload npm start # Production start # Testing npm test # Run all tests once (vitest run) npm run test:watch # Watch mode npm run test:coverage # Run a single test file npx vitest run src/modules/3-turn-engine/orderModel.test.js # Database migrations (requires DATABASE_URL in .env) npm run migrate:up npm run migrate:down npm run migrate:redo npm run migrate:status npm run seed # Seed a tenant via scripts/seed-tenant.mjs ``` No lint command is configured. ## Product goal The bot must be **conversational and intelligent**, not a menu-driven flow. Customers reach out via WhatsApp **with intent to buy** — the bot's job is to: 1. **Engage in conversation** — answer questions about products, prices, availability/stock; recommend; clarify. 2. **Take orders** — build a cart through natural dialogue (multi-product turns, quantities, units). 3. **Collect delivery data** — address, delivery vs pickup, payment method. 4. **Operate within store rules** — delivery zones, days/hours, pickup windows. These config tables (`delivery_zones`, store schedule in `tenant_settings`) will be populated later; the bot has to read and respect them when present. Repetitive, hardcoded responses are a known quality problem and the focus of the active improvement plan (see `~/.claude/plans/ok-creo-que-tiene-humming-sutton.md`). The system is **not yet in production** — refactors that change behavior are acceptable. ## Architecture This is a **mono-tenant WhatsApp e-commerce chatbot** powered by Express.js. The store operator hooks the bot to a single WooCommerce shop; customers interact via WhatsApp to browse products, build carts, and place orders. The DB schema retains `tenant_id` columns (it was originally multi-tenant) but the app boots with a single tenant resolved at startup. The single id is exposed via `src/modules/shared/tenant.js` (`getTenantId()`); webhook handlers and intake routes read from there instead of looking up tenants per-request. ### Request flow ``` WhatsApp → Evolution API webhook → /webhook/evolution (or /sim/send) ↓ 1-intake: route & normalize message ↓ 2-identity/pipeline.processMessage (idempotency, history, side effects) ↓ 3-turn-engine/agent: tool-calling LLM loop ↓ Response persisted to DB + sent back via Evolution API ``` ### Turn engine: tool-calling agent (DeepSeek) `src/modules/3-turn-engine/agent/` es el único motor. Cada turno arma un **WorkingMemory** (cart, pending, last_shown_options, store, history truncado, customer_profile, preparsed quantity) y se lo pasa al LLM como user message. El LLM decide qué tools llamar: - `search_catalog`, `add_to_cart`, `set_quantity`, `select_candidate`, `remove_from_cart` - `set_shipping`, `set_address`, `confirm_order` - `pause`, `escalate_to_human` - `say` (último siempre — es el reply al usuario) El system prompt es **estático** (en `agent/systemPrompt.js` como `SYSTEM_PROMPT` const) para que DeepSeek lo cachée prefix-cache automáticamente. Cache hit ratio típico ≥70% después de 2 turnos. El parser de cantidades (`agent/quantityParser.js`) preprocesa el texto y se pasa como `working_memory.preparsed` (fracciones, "media docena", "cuarto kilo", etc.). La FSM (`fsm.js`) sigue siendo guardrail: estados `IDLE / CART / SHIPPING / PAUSED / AWAITING_HUMAN` con transiciones validadas. PAUSED tiene TTL 7d (cart preservado para "después te digo"). ### Module structure (numbered layers) - **`src/modules/0-UI/`** — Admin dashboard: REST controllers para products, conversations, settings, takeovers, recommendations, aliases. - **`src/modules/1-intake/`** — Message ingestion. Routes: `/simulator` (dev UI), `/webhook/evolution` (WhatsApp). - **`src/modules/2-identity/`** — User mapping (WhatsApp ↔ WooCommerce customer), encrypted WooCommerce credentials, pipeline orchestrator. - **`src/modules/3-turn-engine/`** — Agente tool-calling (`agent/`), FSM (`fsm.js`), order model (`orderModel.js`), catalog retrieval (`catalogRetrieval.js`), store context (`storeContext.js`). - **`src/modules/4-woo-orders/`** — WooCommerce order sync (lectura). El bot crea orders nuevas vía `wooOrders.createOrder` desde `pipeline.js` cuando emite la action `create_order`. - **`src/modules/shared/`** — DB pool, SSE, WooSnapshot, tenant resolver (`getTenantId()`), debug. ### Key integrations | System | Purpose | Config | |--------|---------|--------| | LLM (DeepSeek) | Agente tool-calling — único motor | `OPENAI_API_KEY`, `OPENAI_BASE_URL=https://api.deepseek.com/v1`, `OPENAI_MODEL=deepseek-chat` | | Evolution API | WhatsApp send/receive | `EVOLUTION_*`, `EVOLUTION_SEND_ENABLED` | | WooCommerce REST API | Products, orders, customers | `WOO_BASE_URL`, `WOO_CONSUMER_KEY`, `WOO_CONSUMER_SECRET` | | PostgreSQL | Primary database | `DATABASE_URL` | ### Database Migrations live in `db/migrations/` as timestamped SQL files managed by `dbmate`. Key tables: - `tenants`, `tenant_config`, `tenant_settings`, `tenant_ecommerce_config`, `tenant_channels` - `wa_identity_map` — WhatsApp ↔ WooCommerce customer mapping - `wa_conversation_state` — FSM state + context (cart, pending, last_shown_options, paused_until) en JSONB - `wa_messages` — Message history (idempotencia por message_id) - `woo_products_snapshot` — Cached product catalog (con índices pg_trgm en aliases) - `product_aliases`, `alias_product_mappings` — fuzzy alias resolution - `woo_orders_cache` + `woo_order_items` — orders sync para customer_profile / stats - `human_takeovers`, `audit_log`, `conversation_runs` ### Feature flags (env vars) - `AGENT_MAX_TOOL_CALLS=10` — cap de tool calls por turno - `AGENT_TURN_TIMEOUT_MS=25000` — timeout total del turno - `EVOLUTION_SEND_ENABLED=1` — enviar a WhatsApp real (off en dev) - `DEBUG_PERF`, `DEBUG_WOO_HTTP`, `DEBUG_LLM`, `DEBUG_EVOLUTION` — debug logs granular ### Métricas - `GET /api/metrics/agent` — turns, avg tool calls, fallback rate, escalations, **cache_hit_ratio** (prompt caching de DeepSeek) ### Local development Copy `env.example` to `.env` and fill in values. Use `docker-compose.override.yaml` for local overrides. Run `docker compose up` to start app + Postgres + Redis. The Dockerfile runs migrations automatically on startup (`migrate:up && seed && start`). Test files use Vitest with `globals: true` — no need to import `describe`, `it`, `expect`.