# XG() y la Arquitectura del Bot de WhatsApp — xGestion

## 1. La función XG()

`XG($key)` es la función central del sistema de configuración de xGestion. Vive en `intranet/includes/cfg/hardcode.php`.

```php
XG(null)         // devuelve el array completo de todos los entries
XG(1361)         // devuelve el entry cuya clave constante vale 1361
XG(machineTechTroubles)  // equivalente al anterior (constante PHP = 1361)
```

Cada entry es un array asociativo con campos variables según su propósito:
- Páginas del sistema: `ico`, `name`, `title`, `url`, `rusEnv`, `nRol`, `perfil_id`, `deploy_date`
- Tipos de orden (STX): `ico`, `name`, `title`, `category`, `description`
- Nodos del bot: todos los anteriores + campos específicos de bot (ver sección 3)

Las claves del array son **constantes PHP enteras**. El valor del define ES el identificador:
```php
define('machineTechTroubles', 1361);   // XG(1361) = XG(machineTechTroubles)
define('machinePaymentTroubles', 1362);
define('leadCapture', 4095);
define('ticketRequest', 2700);
define('favOrderLinkRequired', 4094);
define('getTasksPending', 2661);
```

---

## 2. El concepto bot_id

`bot_id` es el identificador unificado del bot. **Un solo número cumple tres roles a la vez:**

| Rol | Uso |
|---|---|
| Clave de XG | `XG(bot_id)` devuelve la configuración del nodo |
| STX key | Usado como `reason` en `CS_INSERT_CLAIM()` al crear tickets |
| `system.wax.bot_id` | Guarda el nodo activo en la sesión del usuario |

Esto elimina la necesidad de mapeos o lookups intermedios.

---

## 3. Campos de un nodo bot en hardcode.php

Para que un entry de XG forme parte del menú del bot, necesita el campo `bot_menu => true`. Los demás campos de bot son:

```php
machineTechTroubles => array(
    // Campos de display
    'ico'             => '⚠️',
    'name'            => 'La máquina no funciona',
    'title'           => '...',          // descripción larga (uso interno)
    'category'        => 'incident',     // incident | lead | bot_node

    // Campos de bot (REQUERIDOS para bot_menu=true)
    'bot_menu'        => true,           // activa el nodo en el menú
    'action'          => 'show_menu',    // qué hace n8n cuando se selecciona
    'next_step'       => 'waiting_sub_choice', // step que sigue en la sesión
    'parent_id'       => null,           // null = raíz; constante PHP = hijo de ese nodo
    'sort_order'      => 1,              // orden de aparición en el menú

    // Campos opcionales de bot
    'needs_machine'   => false,          // si true: requiere número de máquina
    'requires_reason' => ticketRequest,  // solo visible si $x[reasons][ticketRequest]=true
    'llm_hint'        => '...',          // texto para detección de intención (ver sección 5)
    'llm_system'      => '...',          // override del system prompt para este nodo (ver sección 5)
),
```

### Acciones disponibles (campo `action`)

| Action | Descripción |
|---|---|
| `show_menu` | Muestra sub-menú con los hijos de este nodo |
| `ask_machine_number` | Pide el número de máquina |
| `ask_payment_id` | Pide el ID de pago de Mercado Pago |
| `ask_description` | Pide descripción del problema |
| `capture_lead` | Inicia flujo de captura de prospecto |
| `request_ticket` | Inicia flujo de ticket xGestion |
| `request_fav_link` | Genera link de pedido de favoritos |
| `list_pending_tasks` | Lista tareas pendientes del usuario |

### Steps disponibles (campo `next_step`)

| Step | Estado de la sesión |
|---|---|
| `menu_sent` | Menú raíz mostrado, esperando selección |
| `waiting_sub_choice` | Sub-menú mostrado, esperando selección |
| `waiting_machine_number` | Esperando número de máquina |
| `confirm_machine` | Confirmando número de máquina |
| `waiting_description` | Esperando descripción del problema |
| `waiting_payment_id` | Esperando ID de pago MP |
| `waiting_ticket_info` | Esperando descripción para ticket xGestion |
| `waiting_lead_info` | Recopilando datos del prospecto |
| `done` | Flujo completado |

---

## 4. Cómo wsp_context construye el menú (api.php)

`execute=wsp_context` hace tres cosas:

1. **Identifica al usuario** por `wa_id` → popula `$x` con `usr_id`, `acc_id`, `loc_id`, etc.
2. **Calcula elegibilidad** → setea `$x[reasons][key] = true/false` según el contexto del usuario:
   - `ticketRequest` y `getTasksPending` → true si el usuario es INTRANET
   - `favOrderLinkRequired` → true si tiene productos favoritos cargados
   - `machineTechTroubles`, `leadCapture` → siempre true (sin restricción)
3. **Construye el árbol de bot_menu** desde `XG(null)`:
   - Filtra entries con `bot_menu = true`
   - Aplica `requires_reason`: oculta nodos para los que el usuario no es elegible
   - Ordena por `sort_order`
   - Arma estructura raíz + children (2 niveles)
   - Enriquece con `llm_hint` y `llm_system` desde el entry XG

### Output de wsp_context

```json
{
  "usr_id": 42,
  "usrName": "Claudio",
  "wa_id": "5491112345678",
  "acc_id": 150,
  "accName": "Empresa SA",
  "loc_id": 3,
  "checksum": 512,
  "keyErr": [],
  "bot_id": 0,
  "bot_menu": [
    {
      "bot_id": 1361,
      "name": "La máquina no funciona",
      "ico": "⚠️",
      "action": "show_menu",
      "next_step": "waiting_sub_choice",
      "llm_hint": "El usuario dice que la máquina está rota...",
      "llm_system": "",
      "category": "incident",
      "needs_machine": false,
      "children": [
        {
          "bot_id": 1362,
          "name": "Problemas con Mercado Pago o QR",
          "ico": "💳",
          "action": "ask_payment_id",
          "next_step": "waiting_payment_id",
          "llm_hint": "El usuario pagó con QR...",
          "llm_system": "",
          "category": "incident",
          "needs_machine": false
        }
      ]
    }
  ]
}
```

---

## 5. Roles de llm_hint y llm_system

### llm_hint — Detección de intención

Usado por el LLM **durante la navegación del menú** para mapear texto libre del usuario al nodo correcto. El sistema prompt de n8n instruye al modelo a comparar el mensaje del usuario contra el `llm_hint` de cada nodo raíz.

- Se activa en steps: `start`, `menu_sent` (cuando el usuario escribe texto en lugar de una letra)
- Ejemplo: usuario escribe "la maquina no funciona" → LLM detecta machineTechTroubles por su llm_hint

```php
'llm_hint' => implode(PHP_EOL, array(
    'El usuario dice que la máquina está rota, trabada, no funciona...',
    // múltiples variantes aumentan la precisión de detección
)),
```

### llm_system — Comportamiento del nodo

Override del system prompt global de n8n **para un nodo específico**. Inyectado dinámicamente por el Code node "LLM System Override" en n8n, que busca el nodo activo en `bot_menu` y agrega su `llm_system` al final del prompt base.

- Se activa cuando el nodo es el activo (`state.bot_id` coincide)
- Útil para: instrucciones de tono, reglas de negocio específicas, secuencia de preguntas, cuándo ejecutar acciones de cierre

```php
'llm_system' => implode(PHP_EOL, array(
    'Tu objetivo es recopilar los datos del prospecto.',
    'Campos a recopilar en orden: lead_name, lead_company, lead_type, lead_location.',
    'Cuando los cuatro campos estén completos → ejecutar create_lead inmediatamente.',
)),
```

**Diferencia clave:**
- `llm_hint` = *¿a qué nodo va este mensaje?* (entrada al nodo)
- `llm_system` = *¿cómo se comporta el LLM dentro de este nodo?* (comportamiento dentro del nodo)

---

## 6. Sesión de usuario: system.wax

`system.wax` mantiene el estado de la conversación activa por `wa_id`.

| Columna | Tipo | Descripción |
|---|---|---|
| `wax_id` | INT PK | Identificador de sesión |
| `wa_id` | VARCHAR | Número WhatsApp del usuario |
| `type` | VARCHAR | `'n8n'` para el flujo actual |
| `status` | VARCHAR | `stxStarted` → `stxClosed` |
| `sys_id` | INT | Sistema xGestion del usuario |
| `acc_id` | INT | Cuenta del usuario |
| `loc_id` | INT | Locación |
| `usr_id` | INT | Usuario identificado |
| `suc_id` | INT | Sucursal |
| `bot_id` | INT | Nodo bot activo (FK a XG constant) |
| `step` | VARCHAR | Step actual del flujo |
| `ps_id` | INT | Número de máquina (ps = punto de servicio) |
| `debug` | JSON | Estado completo como JSON (incluye campos adicionales como menu_order, sub_menu_order, lead_name, etc.) |
| `timestamp` | INT | Unix timestamp de última actividad |

---

## 7. Endpoints de api.php para el bot

### wsp_context
Identifica al usuario y devuelve el menú filtrado por elegibilidad.
```
POST api.php { execute: "wsp_context", wa_id }
```

### wsp_state_get
Lee el estado actual de la sesión.
```
POST api.php { execute: "wsp_state_get", wa_id }
→ { found, state: { wax_id, bot_id, step, ps_id, menu_order, ... } }
```

### wsp_state_set
Reemplaza el estado completo de la sesión.
```
POST api.php { execute: "wsp_state_set", wa_id, state: {...} }
→ { ok }
```

### wsp_state_patch
Actualiza parcialmente el estado — mergea el patch sobre el estado existente.
```
POST api.php { execute: "wsp_state_patch", wa_id, patch: { bot_id: 1362, step: "waiting_payment_id" } }
→ { ok, state: {...} }  // devuelve el estado resultante completo
```

### wsp_create_ticket
Crea un ticket de reclamo usando `bot_id` como reason para `CS_INSERT_CLAIM`.
```
POST api.php { execute: "wsp_create_ticket", wa_id, bot_id, acc_id, loc_id, description, ps_id? }
→ { ok, orden }
```

---

## 8. Flujo completo de navegación

```
Usuario: "hola, quiero el menú"
  → n8n: wsp_context → recibe bot_menu filtrado
  → LLM: action=show_menu, next_step=menu_sent
         menu_order=[1361, 1362_es_hijo, 2700, 4095]  ← bot_ids de raíces
  → n8n: guarda state {step: "menu_sent", menu_order: [...]}
  → Bot: "a. ⚠️ La máquina no funciona\nb. 🎫 Ticket..."

Usuario: "b"
  → LLM: step=menu_sent, "b"=índice 1 de menu_order → bot_id=2700 (ticketRequest)
         action=request_ticket, next_step=waiting_ticket_info
  → n8n: guarda state {step: "waiting_ticket_info", bot_id: 2700}
  → Bot: "¿Qué problema estás teniendo con el sistema?"
         (llm_system de ticketRequest se inyecta aquí)

Usuario: "no puedo entrar al módulo de ventas"
  → LLM: step=waiting_ticket_info, tiene descripción → create_sys_ticket
  → n8n: POST wsp_create_ticket { bot_id: 2700, description: "..." }
  → Bot: "Tu ticket fue creado. Número: #12345"
```

---

## 9. Agregar un nuevo nodo al bot

Solo requiere editar `hardcode.php`. No se toca `api.php` ni n8n.

**Checklist:**
1. Definir la constante en `constants.php`: `define('miNuevoNodo', XXXX);`
2. Agregar el entry en `hardcode.php` con `bot_menu => true`
3. Si tiene restricción de usuario: agregar `requires_reason => miNuevoNodo` y asegurarse de que `$x[reasons][miNuevoNodo]` se setee a `true` en `wsp_context` para los usuarios elegibles
4. Si necesita instrucciones específicas del LLM: agregar `llm_system`

**No se necesita:**
- Tocar `api.php`
- Modificar el workflow de n8n
- Crear registros en base de datos

---

## 10. Nodos configurados (al 2026-03-17)

| Constante | bot_id | Padre | Action | Activo |
|---|---|---|---|---|
| `machineTechTroubles` | 1361 | raíz | `show_menu` | ✓ |
| `machinePaymentTroubles` | 1362 | 1361 | `ask_payment_id` | ✓ |
| `leadCapture` | 4095 | raíz | `capture_lead` | ✓ |
| `ticketRequest` | 2700 | raíz | `request_ticket` | pendiente `bot_menu=true` |
| `favOrderLinkRequired` | 4094 | raíz | `request_fav_link` | pendiente `bot_menu=true` |
| `getTasksPending` | 2661 | raíz | `list_pending_tasks` | pendiente `bot_menu=true` |
