cambios importantes
This commit is contained in:
+589
@@ -0,0 +1,589 @@
|
||||
# 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 |
|
||||
Reference in New Issue
Block a user