590 lines
17 KiB
Markdown
590 lines
17 KiB
Markdown
# 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
|
|
<?php
|
|
declare(strict_types=1);
|
|
|
|
/**
|
|
* Cliente de ejemplo para que el ERP se comunique con bot-palmas360
|
|
*/
|
|
class BotPalmasClient
|
|
{
|
|
private string $baseUrl;
|
|
private string $apiKey;
|
|
|
|
public function __construct(string $baseUrl, string $apiKey)
|
|
{
|
|
$this->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 |
|