get_status
api.php:201Confirma que la API responde. No toca la base.
Parámetros
Ninguno.
Respuesta
{ "status": "ok", "timestamp": "2026-10-13 10:15:02", "message": "API is running smoothly" }Un solo archivo, ./api.php, recibe todos los comandos. El campo execute elige la acción.
| Entorno | URL |
|---|---|
| Producción | https://www.xgestion.com.ar/api.php |
| Producción (dominio Verena) | https://www.verena.com.ar/api.php |
| Local | http://localhost/xgestion/api.php |
Authorization: Bearer <token>. El token se busca en system.apis.api_token. Cada entorno tiene sus propios tokens.x-www-form-urlencoded crudo. Si llegan archivos, quedan en _FILES.metadata.allowed_execute es un array, el token solo puede llamar esos comandos. Sin esa clave, el token puede llamar todo.curl -s -X POST "$API" \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{"execute":"get_status"}'
pay.log como API Denied: <motivo>, con IP, comando pedido, api_id y los primeros 4 caracteres del token.pay.log dos veces: el cuerpo crudo (API Input (Raw)) y el detalle con método, IP, api_id (id del token en system.apis), headers y payload (API Input: <execute>). Los headers con credenciales (Authorization, Cookie, API keys) se guardan como [redacted] (LOG_SAFE_HEADERS).sys_id = DB_MAINHOMO_ID (1, Verena Vending). Cualquier sys_id que mande el cliente se pisa.$_SESSION[keySys]) y resuelve suc_id con la sucursal por defecto.Siempre JSON. No hay un sobre único: según el comando la respuesta trae ok, status, error o err (la constante keyErr vale 'err'). Las claves escritas con constantes salen con el valor del define: prdName → prd_name, psSerie → serie, pgId → pg_id.
| HTTP | Cuerpo | Causa |
|---|---|---|
| 403 | {"error":"API token missing"} | No vino el header Authorization. |
| 403 | {"error":"API token bad format"} | El header no tiene la forma Bearer <token>. |
| 403 | {"error":"API token invalid"} | El token no existe, o su metadata no es JSON válido. |
| 403 | {"error":"API token not allowed for this command"} | El comando no está en allowed_execute del token. |
| 400 | varía por comando | Falta un parámetro requerido. |
| 200 | {"status":"API command not recognized"} | execute vacío o inexistente. Ojo: devuelve 200, no 404. |
Estado de system.apis en la base local. Producción tiene sus propios tokens con otros ids y valores; los valores nunca se copian a este documento. Para ver o cambiar un token, consultar la tabla directamente.
| id | Uso | allowed_execute |
|---|---|---|
| 1 | Diagnóstico (Claude Code). En producción es el id 2: era el token general de n8n, rotado y restringido el 2026-10-15. En local sigue siendo el token general sin restricción. | get_status, debug_log (PRD) |
| 2 | IA externa, análisis de zonificación comercial. Solo lectura. | zonificacion_consignaciones |
| 3 | Landing pública home.html. Queda expuesto en el JS del sitio. | landing_clientes, landing_equipos |
| 4 | Bot WhatsApp desde Claude Code: sesiones, configuración, simulacro y revisión de calidad. En PRD es otro token con el mismo alcance. | bot_sessions, bot_config_get, bot_config_set, bot_dry_run, bot_qa |
| 5 | Control de drift de esquema entre bases db_* (autoconfig). Solo lectura. | schema_diff |
| 6 | Controles diarios: el agente de evaluación (rutina de Claude) lee los controles y entrega su informe. En producción con otro id. | controls, controls_report |
Para dar acceso a un tercero se crea un token nuevo con allowed_execute limitado a lo que necesita, y una note en metadata que diga para qué es:
INSERT INTO system.apis (api_token, metadata) VALUES (
SHA2(UUID(), 256),
'{"sys_id":"1","allowed_execute":["landing_clientes"],"note":"para qué y quién lo usa"}'
);
Detectados al relevar el código para este documento (2026-10-13). Los resueltos quedan anotados con su fecha.
pay.log
El log de entrada de api.php y mcp.php guardaba todos los headers, con Authorization. Desde el deploy 0da67742 (2026-10-15) se enmascaran con LOG_SAFE_HEADERS, y los registros anteriores se limpiaron en producción ese mismo día.
Rotado en producción el 2026-10-15 y limitado a get_status y debug_log. Antes de rotar se confirmó en pay.log que nadie más lo usaba en el último mes. El valor viejo también se sacó de la página de wiki "Chatbot WhatsApp — Arquitectura".
Los comandos del bot en n8n (wsp_*, create_machine_ticket, create_refund_ticket) y los bloques if (false) de los ya eliminados se retiraron el 2026-10-16. Antes se verificó en pay.log que nadie los había llamado en el último mes. Ver "Eliminados".
zonificacion_consignaciones no hace lo que dice su comentario
Ignora zona_id, devuelve un objeto con accounts/locations/kpis en vez de la lista plana documentada, incluye CUIT y KPIs financieros por cuenta, y escribe statics (STATICS_UPSERT) aunque el token sea "solo lectura".
Un execute mal escrito devuelve HTTP 200. Y como get_status no está en ningún allowed_execute, un consumidor con token restringido no tiene un ping propio.
Salud del endpoint y lectura del log de eventos.
Confirma que la API responde. No toca la base.
Ninguno.
{ "status": "ok", "timestamp": "2026-10-13 10:15:02", "message": "API is running smoothly" }Devuelve los últimos registros de pay.log (lo que escribe LOG_EVENT). Sirve para depurar producción sin entrar a la base.
| Nombre | Tipo | Descripción | |
|---|---|---|---|
| limit | int | opcional | Default 20, máximo 100. |
| name | string | opcional | Filtro parcial (LIKE) por nombre del evento. Ej: WAPI_CALL_LLM, API Input: bot_dry_run. |
| type | int | opcional | Filtro exacto por tipo. El comentario del código dice info|error|warning, pero la columna guarda el número; pasar el valor numérico. |
| since | unix ts | opcional | Solo registros posteriores. |
{ "count": 2, "rows": [ { "id": 812345, "timestamp": "2026-10-13 09:58:11", "type": 1, "stx": 0,
"name": "API Input: bot_qa", "description": "...", "caller": "..." } ] }pay.log retiene aproximadamente un mes.Consultas de solo lectura sobre consignaciones de Verena Vending, para la landing y para análisis externos. Las tres salen de SQL_CONSIGNACIONES().
Lista de clientes para mostrar en home.html: cuentas con al menos una máquina activa en consignación y el flag KACC_IS_PUBLIC ("Mostrar en ambientes públicos") encendido en la ficha de cuenta.
Ninguno.
{ "status": 1, "clientes": [ { "nombre": "…", "logo": "https://…/img.png", "website": "https://…" } ] }logo sale de FILE_IMG_GET(FILE_TYPE_ACC_IMG). Sin logo cargado devuelve el placeholder NOT_IMG; el front lo trata como "sin logo".website sale de preferences (preference = 'website') y puede ser null.home.html (el token está en el JS público).Sin pgId: catálogo de modelos de máquina con instalaciones activas hoy. Con pgId: datos del simulador de costo de ese modelo.
| Nombre | Tipo | Descripción | |
|---|---|---|---|
| pgId | int | opcional | Modelo a simular. Se envía como pgId, pero la respuesta lo devuelve como pg_id. |
{ "status": 1, "equipos": [ { "pg_id": 690, "nombre": "…", "imagen": "https://…", "instalaciones": 14, "tieneContenedores": true } ] }{ "status": 1, "pg_id": 690, "pg_name": "…", "canon": 0.0, "capacity": 0, "variance": 0, "iva": 1.21, "imagen": "https://…",
"insumos": [ { "nombre": "…", "pUnit": 0.0, "min": 0.0, "max": 0.0, "rinde": 0, "fraccion": 0, "umed": "…" } ] }pf_id IN (19, 20, 25, 38, 52). Si se crea una familia de equipo nueva, hay que agregarla en el código.tieneContenedores es verdadero solo si todos los insumos de la receta tienen rinde > 1 (separa café de snacks y bebidas). El front lo usa para decidir qué modelos ofrecen simulador.PRD() + ARRAY_PG_CONTAINER_DETAIL(), las mismas funciones que pgs/insumos.php. pgId inexistente devuelve 404.home.html.Clientes y puntos con consignaciones activas, con coordenadas y máquinas, para que una IA externa analice zonas de densificación comercial. Delega en RESUMEN_DE_CONSIGNACIONES().
Ninguno efectivo. El comentario del código menciona zona_id, pero no se pasa a la función.
{ "sys_id": 1, "soloseries": true,
"accounts": { "123": { "acc_id": 123, "acc_name": "…", "cuit": "…", "preferences": { … }, "kpis": { … } } },
"locations": { "456": { "acc_id": 123, "acc_name": "…", "loc_id": 456, "loc_name": "…", "loc_addr": "…",
"loc_lat": -34.4, "loc_lng": -58.6, "fecha_alta": "2024-03-01",
"machines": { "690": { "pg_id": 690, "pg_name": "…", "valor": 0.0, "cant": 1 } } } },
"kpis": { "<kpi>": { "name": "…", "description": "…", "unit": "…", "direction": "…", "title": "…" } } }STATICS_UPSERT(kpis_financieros), que escribe en statics.{"error": "…"}.Leer y ajustar el bot de producción sin bajar la base. Las escrituras en PRD se muestran antes y se aplican con el OK de quien opera. Las conversaciones tienen datos personales: no copiarlas al repo.
Sesiones del bot (system.wax) con sus mensajes (system.wax_messages), de la más nueva a la más vieja.
| Nombre | Tipo | Descripción | |
|---|---|---|---|
| since | unix ts | opcional | Actividad mínima. Default: últimos 7 días. |
| until | unix ts | opcional | Actividad máxima. |
| wa_id | string | opcional | Solo ese número. |
| wax_id | int | opcional | Una sesión puntual. Ignora los demás filtros. |
| limit | int | opcional | Sesiones. Default 20, máximo 100. |
| msg_limit | int | opcional | Mensajes por sesión. Default 100, máximo 500. |
| include_tests | bool | opcional | Incluir los números de prueba 5491100009900 a 5491100009999. |
{ "count": 1, "sessions": [ { "wax_id": 1369, "wa_id": "549…", "usr_name": "…", "acc_id": 0, "loc_id": 0, "usr_id": 0,
"last_activity": "2026-10-12 18:40:03", "closed": true, "wax_facts": { … },
"messages": [ { "role": "user", "content": "…", "at": "2026-10-12 18:39:51" } ] } ] }Configuración vigente del bot: prompt maestro y sus últimas 30 versiones, contextos (system.bot_contexts) y acciones (system.bot_actions cruzadas con XG()).
| Nombre | Tipo | Descripción | |
|---|---|---|---|
| prompt_id | int | opcional | Agrega el contenido de esa versión del prompt en prompt_version_content. |
{ "prompt": { "id": 3, "prompt_key": "wsp_master", "updated_at": "…", "updated_by": 0, "content": "…" },
"prompt_versions": [ { "id": 3, "prompt_key": "wsp_master", "updated_at": "…", "updated_by": 0, "length": 5120 } ],
"contexts": [ { "ctx_id": 4, "name": "Pagos y devoluciones", "scope": "global", "active": 1, "sort_order": 0, "content": "…" } ],
"actions": [ { "bot_id": 21764, "xg_key": "2710", "api_action": "human_handoff", "name": "…", "description": "…",
"params": { … }, "active": 1, "sort_order": 0, "required_rol": 0, "llm_hint": "…", "llm_system": "…" } ],
"context_scopes": [ "global", "create_machine_ticket", "…" ] }api_action: null en una acción significa que su xg_key ya no existe en XG().Cambia un elemento de la configuración del bot por llamada. Devuelve el registro anterior y el nuevo, y deja ambos en pay.log como BOT CONFIG SET para poder volver atrás.
| target | Campos | Efecto |
|---|---|---|
| prompt | content (string) o restore_id (int) | Inserta una versión nueva de wsp_master. Con restore_id copia el contenido de esa versión anterior. Nunca borra versiones. |
| context | ctx_id (sin él crea uno), name, content, scope, active, sort_order | Crea o modifica un contexto. scope es global o un api_action del bot; otro valor da error con la lista válida. Crear exige name y content. |
| action | bot_id o xg_key; llm_hint, llm_system, active, sort_order | Modifica una acción existente. No crea acciones (eso sale de XG()). |
Solo se modifican los campos enviados.
{ "ok": true, "target": "context", "before": { … }, "after": { … } }
{ "ok": false, "target": "action", "err": [ "Acción inexistente (bot_id / xg_key)." ], "before": null, "after": null } // HTTP 400Corre el pipeline del bot hasta la decisión del modelo, sin ejecutar la acción ni mandar WhatsApp. Sirve para probar un prompt, contexto o instrucción candidata antes de guardarla. Con simulate corre el pipeline completo con un número de prueba y guarda la sesión, para conversar varios turnos.
| Nombre | Tipo | Descripción | |
|---|---|---|---|
| msg_text | string | requerido | Mensaje del usuario. Para tocar un botón, mandar su id. |
| wa_id | string | requerido | Número real: usa su identidad, acciones y sesión. Un número no registrado simula un desconocido. |
| contact_name | string | opcional | Nombre de perfil de WhatsApp. |
| history | array | opcional | [{role, content}]; reemplaza el historial. Ignorado con simulate. |
| wax_facts | object | opcional | Reemplaza el estado transitorio. Ignorado con simulate. |
| prompt | string | opcional | Prompt maestro candidato (no se guarda). |
| contexts | string[] | opcional | Contextos candidatos (reemplazan a los guardados, no se guardan). |
| actions | object | opcional | { api_action: { llm_hint, llm_system } } candidatos. |
| full_prompt | bool | opcional | Incluye el system_prompt completo. |
| simulate | bool | opcional | Pipeline completo con sesión guardada. Solo números 5491100009900 a 5491100009999. Los efectos de negocio no ocurren: vuelven en simulated_effects y simulated_mails. |
| reset | bool | opcional | Con simulate: cierra la sesión activa del número antes del mensaje. |
{ "oai_http": 200, "oai_error": null, "action": "create_machine_ticket", "params": { "ps_id": 1234 },
"params_missing": null, "response_text": "…", "shown": { "buttons": { "si": "✅ Sí, registrar" } },
"simulated_effects": null, "simulated_mails": null, "interactive_payload": { … }, "note": "…",
"identity": { "usr_id": 0, "usr_name": "", "acc_id": 0, "acc_name": "", "loc_id": 0, "wax_id": null },
"available_actions": [ "…" ], "wax_facts": { … }, "key_info": { … }, "system_prompt_len": 9000, "user_msg": "…" }simulate no crea reclamos, leads ni tickets, no guarda la sesión y no toca system.users ni system.wa. Sí valida parámetros con lecturas (por ejemplo, que la máquina exista).Revisión de calidad de las conversaciones de un día: la misma que corre el cron BOT_QA_DAILY. Hace una consulta a OpenAI cada 15 conversaciones.
| Nombre | Tipo | Descripción | |
|---|---|---|---|
| fecha | Y-m-d | opcional | Día a revisar. Default: ayer. |
| send | bool | opcional | true: igual que el cron, registra el resumen en pay.log y encola el mail a BOT_QA_EMAILS. Default false: solo devuelve el análisis. |
{ "fecha": "2026-10-12", "kpis": { … }, "sessions": [ { … } ], "trend": [ … ], "mail_html": "<html>…" }El estado de la empresa ya evaluado por xGestion: máquinas, cobranza, facturación por contador, entregas y tareas. Es lo mismo que llega en el resumen diario por mail. El cálculo vive en fns/controls.php; quien consume solo muestra o prioriza.
Corre los controles diarios (CTL_RUN) y devuelve, por control, su nivel, un resumen, el criterio aplicado y los casos para revisar. No manda el mail: eso lo hace el cron CTL_DAILY_MAIL_SEND.
| Nombre | Tipo | Descripción | |
|---|---|---|---|
| check | string | array | opcional | Uno o varios controles. Sin check, todos. Un id inexistente responde 400 con la lista válida. |
| as_of | Y-m-d H:i:s | opcional | Evalúa como si fuera ese momento. Sirve para probar contra una copia de la base que no está al día. Las tareas usan siempre la fecha real. |
| html | int | opcional | 1: agrega mail_html, el cuerpo del resumen tal como se mandaría. |
| check | Qué evalúa | Criterio por defecto |
|---|---|---|
| chk_maquinas_sin_cobros | Máquinas con QR que llevan sin cobrar más de lo normal para esa máquina, según su ritmo por día y hora de las últimas 8 semanas. | Aviso si lo normal eran 3 ventas en ese lapso; crítico desde 6, o sin cobros en 8 semanas. |
| chk_reintegros_por_maquina | Reintegros de Mercado Pago por máquina, contra sus cobros QR. | 3 en 7 días; crítico con 6. |
| chk_deuda_vencida | Clientes con saldo vencido, con la misma vista que Saldos Generales. | Desde $ 10.000; crítico desde $ 1.000.000. |
| chk_ocex_contadores | Cuentas con facturación por contador cuya OCEX venció o está por vencer. | Crítico si venció y hubo tomas en 90 días; aviso a 15 días. |
| chk_entregas_pendientes | Órdenes para reparto con cantidades sin entregar, con el criterio de la pantalla de repartos. | Más de 3 días; crítico con más de 7. |
| chk_tareas | Tareas atrasadas o pendientes de aceptar, tomadas del resumen diario de tareas. | Atrasada es crítico; sin aceptar es aviso. |
Los umbrales son constantes CTL_* en constants.php.
{ "as_of": "2026-10-13 09:00:00", "status": "crit",
"checks": { "chk_ocex_contadores": { "check": "chk_ocex_contadores", "title": "Facturación por contador detenida",
"area": "Facturación", "status": "crit", "summary": "1 cuentas con la OCEX de contadores vencida o por vencer.",
"threshold": "…", "count": 1, "err": [], "seconds": 0.003,
"items": [ { "level": "crit", "title": "…", "detail": "La OCEX 131096 venció el 30/09/2026. …",
"acc_id": 0, "orden": 131096, "vence": "2026-09-30", "days_left": -13, "readings_after": 0 } ] } },
"err": [] }status de cada control y del total: ok, warn, crit o error (no se pudo calcular).acc_id, loc_id, ps_id, orden) y datos propios de su control.Recibe el informe del agente de evaluación (la rutina diaria de Claude que analiza controls) y lo manda por mail al equipo con CTL_AGENT_REPORT_SEND.
| Nombre | Tipo | Descripción | |
|---|---|---|---|
| report_html | string | requerido | Cuerpo en HTML básico: h3, h4, p, ul, ol, li, b, i, br, table. Todo lo demás se quita, incluidos atributos, links e imágenes. |
| summary | string | opcional | Una línea para el asunto; se corta en 110 caracteres. |
| as_of | Y-m-d H:i:s | opcional | Momento de los controles analizados; sale en el encabezado del mail. |
{ "ok": true, "err": [], "to": "…" }
{ "ok": false, "err": [ "Límite diario de informes alcanzado (3)." ] } // HTTP 429CTL_DAILY_EMAILS) y hay un máximo de 3 informes por día (CTL_AGENT_MAX_PER_DAY): el token no sirve para escribirle a terceros ni para inundar la casilla.pay.log como CTL AGENT REPORT, sin el contenido.Control de las bases de datos.
Compara la estructura de bases de empresa (tablas, columnas, índices, FKs) vía SCHEMA_DIFF(). Se usa después de cada cambio de esquema para confirmar que db_1 (donde en local los nodos de autoconfig se aplican a mano) quedó igual que las demás.
| Nombre | Tipo | Descripción | |
|---|---|---|---|
| source | string | opcional | Base de referencia. Default DB_NAME_HOMO (db_1). |
| target | string | array | opcional | Base o bases a comparar, o all (todas las db_* menos source). Default db_0. |
| only_diff | int | opcional | 1: omite los targets sin diferencias. |
{ "source": "db_1", "targets": [ { "db": "db_139", "total": 2, … } ], "err": [] }information_schema. Una base inexistente responde 400 con el detalle en err.Comandos del bot cuando corría en n8n. Hoy el bot corre en site/wsp/webhook.php y llama a las funciones WAPI_* directamente. Llamarlos devuelve "API command not recognized". El código está en el historial de git: el último commit que lo contiene es el anterior al retiro del 2026-10-16.
| Comando | Retirado | Qué hacía · reemplazo |
|---|---|---|
| wsp_context | antes de oct 2026 | Identificaba al usuario y armaba el menú. Hoy: WAPI_BUILD_CONTEXT. |
| wsp_send | antes de oct 2026 | Enviaba un WhatsApp. Hoy: WAPI_SEND. |
| wsp_state_get | antes de oct 2026 | Leía la sesión en system.wax. Hoy: WAPI_LOAD_SESSION. |
| wsp_state_set | antes de oct 2026 | Guardaba la sesión. Hoy: WAPI_SAVE_SESSION. |
| wsp_state_patch | antes de oct 2026 | Modificaba parte de la sesión. Ya nadie toca wax desde afuera. |
| wsp_create_sys_ticket | 2026-10-16 | Creaba un ticket de soporte en system.tkt (TKT_ADD_NEW). |
| wsp_pending_tasks | 2026-10-16 | Listaba las tareas pendientes como mensaje de WhatsApp (TASKS_LIST_FULL). |
| wsp_create_lead | 2026-10-16 | Registraba un prospecto como orden de consulta comercial. |
| wsp_fav_link | 2026-10-16 | Generaba un link de una hora a la extranet de favoritos de una cuenta (TA_CREATE). |
| wsp_check_machine | 2026-10-16 | Validaba un número de máquina y devolvía su consignación. |
| wsp_check_payment | 2026-10-16 | Validaba un pago en Mercado Pago y lo cruzaba con psr. |
| wsp_create_ticket | 2026-10-16 | Creaba un reclamo genérico (CS_INSERT_CLAIM). |
| create_machine_ticket | 2026-10-16 | Reclamo por falla de máquina. El bot actual tiene una acción con el mismo nombre, que resuelve WAPI_EXECUTE_ACTION. |
| create_refund_ticket | 2026-10-16 | Reclamo por pago sin entrega. El bot actual tiene una acción con el mismo nombre, que resuelve WAPI_EXECUTE_ACTION. |
Este documento se actualiza en el mismo cambio que toca api.php. La fuente está en el repo, en docs/api/api-reference.html, y se republica sobre el mismo link.
if ($data['execute'] == '…') { … exit; } en api.php, con el comentario de cabecera estándar (abajo).allowed_execute limitado y una note.#===============================================================================================================================
# nombre_del_comando — qué hace, en una línea.
# Consumidor: quién lo llama (n8n, home.html, IA externa, Claude Code…). Token: id / alcance.
# Efectos: lectura | escribe <tablas> | consume OpenAI | envía mail.
# Parámetros:
# param (tipo, requerido|opcional) descripción
# Output: { … }
Al modificar o retirar un comando, actualizar su tarjeta (o moverla a "Eliminados") y la fecha y commit del encabezado.