# Documentación API — Integración ERP ↔ bot-palmas360 ## Índice 1. [Autenticación](#1-autenticación) 2. [Endpoints que el ERP debe exponer](#2-endpoints-que-el-erp-debe-exponer) 3. [Endpoints que el ERP debe consumir](#3-endpoints-que-el-erp-debe-consumir) 4. [Flujo completo](#4-flujo-completo) 5. [Implementación de referencia en PHP](#5-implementación-de-referencia-en-php) --- ## 1. Autenticación Todas las llamadas entre el bot y el ERP usan **API Key** via header: ``` X-API-Key: tu_api_key_secreta ``` Cada empresa tiene su propia `api_key` configurada en la tabla `companies`. Esta misma key la usa el bot para autenticarse cuando llama al ERP, y el ERP la usa para autenticarse cuando llama al bot. --- ## 2. Endpoints que el ERP debe exponer ### 2.1 Lista de empresas (sincronización) Usado por el bot para obtener la lista completa de empresas con sus números y URLs. **`GET {ERP_SYNC_API_URL}`** Autenticación: `Authorization: Bearer {ERP_SYNC_API_KEY}` (configurado en `.env`) **Respuesta exitosa (200):** ```json [ { "name": "Empresa Uno", "display_name": "Empresa Uno S.A.S.", "phone_number_id": "384710295638401", "display_phone": "573001234567", "api_base_url": "https://erp.empresauno.com/api", "api_key": "key_secreta_empresa_1", "bot_type": "hybrid", "requires_approval": true, "is_active": true, "config_json": { "commands": { "menu": "show_main", "info": "show_info", "contacto": "show_contact", "horario": "show_hours" }, "menus": { "show_main": { "type": "list", "header": "Bienvenido a Empresa Uno", "body": "Selecciona una opción:", "footer": "Empresa Uno", "button": "Ver opciones", "sections": [ { "title": "Información", "rows": [ { "id": "servicios", "title": "Servicios", "description": "Nuestros servicios disponibles" }, { "id": "contacto", "title": "Contacto", "description": "Habla con un asesor" } ] } ] } }, "flows": { "servicios": { "type": "text", "message": "Ofrecemos servicios de consultoría y desarrollo." }, "contacto": { "type": "function", "function": "forward_to_ai" } }, "ai_prompt": "Eres el asistente virtual de Empresa Uno. Responde preguntas sobre servicios.", "fallback": "No entendí. Escribe 'menu' para ver opciones.", "approval_webhook": "https://erp.empresauno.com/api/approval-notify" } } ] ``` > **Nota:** El bot almacena estos datos localmente en la tabla `companies`. El campo `config_json` define el comportamiento del bot normal, mensajes de IA, menús interactivos y webhooks de notificación. --- ### 2.2 Recepción de mensajes entrantes El bot envía aquí cada mensaje que llega por WhatsApp. **`POST {empresa.api_base_url}/webhook/incoming`** Autenticación: `X-API-Key: {empresa.api_key}` **Payload:** ```json { "company_id": 1, "from": "573001234567", "name": "Juan Pérez", "message_id": "wamid.HBgNNTczMDAxMjM0NTY3FQIAERgSN0QzQjFGQ0YyRjU3OTJGMwA=", "type": "text", "content": "Hola, quiero información sobre los precios", "media_id": null, "timestamp": 1717600000, "phone_number_id": "384710295638401", "display_phone": "573001234567", "raw_payload": "{...payload completo de Meta...}" } ``` **Respuesta esperada (200):** ```json { "status": "received" } ``` > **Importante:** El bot responderá 200 a Meta inmediatamente después de recibir el mensaje. La respuesta al usuario la maneja el bot según la configuración de la empresa (respuesta automática, IA, o cola de aprobación). --- ### 2.3 Estados de mensajes El bot notifica aquí cuando cambia el estado de un mensaje enviado. **`POST {empresa.api_base_url}/webhook/status`** Autenticación: `X-API-Key: {empresa.api_key}` **Payload:** ```json { "company_id": 1, "message_id": "wamid.HBgNNTczMDAxMjM0NTY3FQIAERgSN0QzQjFGQ0YyRjU3OTJGMwA=", "status": "sent", "recipient": "573001234567", "timestamp": 1717600000, "errors": null } ``` Posibles valores de `status`: `sent`, `delivered`, `read`, `failed` Cuando es `failed`, el campo `errors` contiene detalles del error: ```json { "status": "failed", "errors": [ { "code": 131026, "title": "Message undeliverable", "details": "Phone number has unsubscribed from the channel" } ] } ``` **Respuesta esperada (200):** ```json { "status": "received" } ``` --- ### 2.4 Health check (opcional pero recomendado) Usado por el monitor de salud del bot para verificar disponibilidad. **`GET {empresa.api_base_url}/health`** Autenticación: `X-API-Key: {empresa.api_key}` (opcional en este endpoint) No requiere payload. El bot solo verifica que responda HTTP 200. **Respuesta esperada (200):** ```json { "status": "ok", "service": "erp-empresa-uno", "timestamp": "2026-06-12T10:00:00Z" } ``` --- ## 3. Endpoints que el ERP debe consumir ### 3.1 Enviar mensaje directo **`POST https://bot.palmas360.com/api/send-direct`** Autenticación: `X-API-Key: {api_key de tu empresa}` Usa este endpoint cuando quieras enviar un mensaje a un cliente desde el ERP (ej: un asesor responde manualmente desde tu dashboard). **Payload (texto):** ```json { "to": "573001234567", "type": "text", "text": "Hola Juan, gracias por escribirnos. Te comparto nuestros precios: Plan Básico $49/mes, Plan Premium $99/mes." } ``` **Payload (imagen):** ```json { "to": "573001234567", "type": "image", "media_id": "123456789", "url": "https://tuservidor.com/imagen.jpg", "caption": "Nuestro catálogo de productos" } ``` > Envia `media_id` (ID de Meta) o `url` (URL pública). Si envías ambos, prioriza `media_id`. **Payload (template):** ```json { "to": "573001234567", "type": "template", "template_name": "bienvenida", "components": [ { "type": "body", "parameters": [ { "type": "text", "text": "Juan" } ] } ] } ``` **Respuesta exitosa (200):** ```json { "status": "queued", "id": 123 } ``` El mensaje se encola y se envía en segundo plano. El estado de envío llegará a tu `webhook/status`. --- ### 3.2 Ver mensajes pendientes de aprobación **`GET https://bot.palmas360.com/api/pending`** Autenticación: `X-API-Key: {api_key de tu empresa}` Devuelve todos los mensajes que el bot procesó pero que requieren aprobación humana antes de enviarse. **Respuesta exitosa (200):** ```json { "pending": [ { "id": 5, "company_id": 1, "phone_number": "573001234567", "contact_name": "Juan Pérez", "incoming_msg": "Quiero saber el precio del servicio premium", "bot_response": { "action": "send", "type": "text", "to": "573001234567", "payload": "{\"text\":\"El plan Premium cuesta $99/mes. ¿Te gustaría suscribirte?\"}" }, "bot_type": "ai", "status": "pending", "created_at": "2026-06-12 09:30:00" } ], "total": 1 } ``` --- ### 3.3 Aprobar o rechazar mensaje **`POST https://bot.palmas360.com/api/approval`** Autenticación: `X-API-Key: {api_key de tu empresa}` Aprueba o rechaza un mensaje pendiente. **Payload (aprobar):** ```json { "pending_id": 5, "action": "approve", "note": "Aprobado por gerencia" } ``` **Respuesta (200):** ```json { "status": "approved", "pending_id": 5, "message": "Respuesta aprobada y encolada para envío" } ``` **Payload (rechazar):** ```json { "pending_id": 5, "action": "reject", "note": "El cliente pidió información incorrecta" } ``` **Respuesta (200):** ```json { "status": "rejected", "pending_id": 5, "message": "Mensaje rechazado" } ``` > Cuando apruebas, el bot automáticamente encola la respuesta generada para enviarla al cliente por WhatsApp. --- ### 3.4 Listar empresas (opcional) Si necesitas consultar qué empresas están registradas en el bot: **`GET https://bot.palmas360.com/api/companies`** Autenticación: `X-API-Key: {api_key de tu empresa}` (solo empresas activas) **Respuesta (200):** ```json { "companies": [ { "id": 1, "name": "Empresa Uno", "display_name": "Empresa Uno S.A.S.", "phone_number_id": "384710295638401", "display_phone": "573001234567", "bot_type": "hybrid", "requires_approval": true, "is_active": true } ] } ``` --- ## 4. Flujo completo ``` Cliente WhatsApp bot-palmas360 ERP Palmas360 │ │ │ │ Mensaje de texto │ │ │ ─────────────────────────────────► │ │ │ │ │ │ │ POST /webhook/incoming │ │ │ ──────────────────────────────► │ │ │ ◄─── { status: "received" } ─── │ │ │ │ │ │ BotRouter decide: │ │ │ ├─ Respuesta directa │ │ │ │ → encola en outbound_queue │ │ │ │ → WhatsAppSender envía │ │ │ │ → POST /webhook/status │ │ │ │ ──────────────────────────► │ │ │ │ │ │ ◄─── Respuesta IA/Bot ────────── │ │ │ │ │ │ │ │ │ └─ requires_approval = true │ │ │ → guarda en pending_approval │ │ │ → notifica vía webhook (opc) │ │ │ │ │ │ GET /api/pending │ │ │ ◄──────────────────────────── │ │ │ ── lista pendientes ──────► │ │ │ │ │ │ POST /api/approval │ │ │ ◄──────────────────────────── │ │ │ ── approve/reject ─────────► │ │ │ │ │ │ (si approve) │ │ ◄─── Respuesta aprobada ──────── │ → outbound_queue │ │ │ → WhatsAppSender envía │ │ │ │ │ │ ERP inicia conversación │ │ │ POST /api/send-direct │ │ │ ◄──────────────────────────── │ │ ◄─── Mensaje del asesor ──────── │ ── mensaje outbound ────────► │ │ │ │ ``` --- ## 5. Implementación de referencia en PHP ```php baseUrl = rtrim($baseUrl, '/'); $this->apiKey = $apiKey; } /** * Enviar un mensaje de texto */ public function sendText(string $to, string $text): array { return $this->post('/api/send-direct', [ 'to' => $to, 'type' => 'text', 'text' => $text, ]); } /** * Enviar una imagen */ public function sendImage(string $to, string $url, ?string $caption = null): array { $payload = [ 'to' => $to, 'type' => 'image', 'url' => $url, ]; if ($caption !== null) { $payload['caption'] = $caption; } return $this->post('/api/send-direct', $payload); } /** * Enviar una plantilla (template) */ public function sendTemplate(string $to, string $templateName, array $components = []): array { return $this->post('/api/send-direct', [ 'to' => $to, 'type' => 'template', 'template_name' => $templateName, 'components' => $components, ]); } /** * Obtener mensajes pendientes de aprobación */ public function getPending(): array { return $this->get('/api/pending'); } /** * Aprobar un mensaje pendiente */ public function approvePending(int $pendingId, ?string $note = null): array { return $this->post('/api/approval', [ 'pending_id' => $pendingId, 'action' => 'approve', 'note' => $note, ]); } /** * Rechazar un mensaje pendiente */ public function rejectPending(int $pendingId, ?string $note = null): array { return $this->post('/api/approval', [ 'pending_id' => $pendingId, 'action' => 'reject', 'note' => $note, ]); } // ─── Helpers HTTP ─────────────────────────────────────────────────────── private function get(string $path): array { return $this->request('GET', $path); } private function post(string $path, array $data): array { return $this->request('POST', $path, $data); } private function request(string $method, string $path, ?array $data = null): array { $url = $this->baseUrl . $path; $ch = curl_init($url); $headers = [ 'X-API-Key: ' . $this->apiKey, 'Content-Type: application/json', 'Accept: application/json', ]; $options = [ CURLOPT_RETURNTRANSFER => true, CURLOPT_HTTPHEADER => $headers, CURLOPT_TIMEOUT => 15, ]; if ($method === 'POST') { $options[CURLOPT_POST] = true; $options[CURLOPT_POSTFIELDS] = json_encode($data ?? []); } curl_setopt_array($ch, $options); $response = curl_exec($ch); $httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE); $error = curl_error($ch); curl_close($ch); if ($error !== '') { return ['success' => false, 'error' => $error]; } $decoded = json_decode($response, true); return [ 'success' => $httpCode >= 200 && $httpCode < 300, 'http_code' => $httpCode, 'data' => $decoded ?? $response, ]; } } // ─── Ejemplo de uso ──────────────────────────────────────────────────────── $bot = new BotPalmasClient('https://bot.palmas360.com', 'api_key_de_mi_empresa'); // Enviar mensaje $result = $bot->sendText('573001234567', 'Hola, gracias por escribirnos'); print_r($result); // Ver pendientes $pending = $bot->getPending(); foreach ($pending['pending'] ?? [] as $item) { echo "Pendiente #{$item['id']}: {$item['incoming_msg']}\n"; } // Aprobar el primero if (!empty($pending['pending'])) { $bot->approvePending((int)$pending['pending'][0]['id'], 'Aprobado desde ERP'); } ``` --- ## Resumen rápido de endpoints | Dirección | Qué hace | |---|---| | **ERP → Bot** | | | `POST /api/send-direct` | Enviar mensaje WhatsApp | | `GET /api/pending` | Ver pendientes de aprobación | | `POST /api/approval` | Aprobar/rechazar pendiente | | **Bot → ERP** | | | `POST /webhook/incoming` | Recibir mensaje entrante | | `POST /webhook/status` | Recibir estado de envío | | `GET /health` | Health check |