@mbermeo/wa-blaster 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1060 -0
- package/bin/wa-blast.js +124 -0
- package/dist/node20/cli/index.js +2 -0
- package/dist/node20/cli/index.jsc +0 -0
- package/dist/node20/cli/lib.js +2 -0
- package/dist/node20/cli/lib.jsc +0 -0
- package/dist/node20/mcp/index.js +2 -0
- package/dist/node20/mcp/index.jsc +0 -0
- package/dist/node20/src/accountManager.js +2 -0
- package/dist/node20/src/accountManager.jsc +0 -0
- package/dist/node20/src/campaignRunner.js +2 -0
- package/dist/node20/src/campaignRunner.jsc +0 -0
- package/dist/node20/src/db.js +2 -0
- package/dist/node20/src/db.jsc +0 -0
- package/dist/node20/src/paths.js +2 -0
- package/dist/node20/src/paths.jsc +0 -0
- package/dist/node20/src/routes/accounts.js +2 -0
- package/dist/node20/src/routes/accounts.jsc +0 -0
- package/dist/node20/src/routes/campaigns.js +2 -0
- package/dist/node20/src/routes/campaigns.jsc +0 -0
- package/dist/node20/src/routes/inbound.js +2 -0
- package/dist/node20/src/routes/inbound.jsc +0 -0
- package/dist/node20/src/server.js +2 -0
- package/dist/node20/src/server.jsc +0 -0
- package/dist/node20/src/utils/csvParser.js +2 -0
- package/dist/node20/src/utils/csvParser.jsc +0 -0
- package/dist/node20/src/utils/templateEngine.js +2 -0
- package/dist/node20/src/utils/templateEngine.jsc +0 -0
- package/dist/node22/cli/index.js +2 -0
- package/dist/node22/cli/index.jsc +0 -0
- package/dist/node22/cli/lib.js +2 -0
- package/dist/node22/cli/lib.jsc +0 -0
- package/dist/node22/mcp/index.js +2 -0
- package/dist/node22/mcp/index.jsc +0 -0
- package/dist/node22/src/accountManager.js +2 -0
- package/dist/node22/src/accountManager.jsc +0 -0
- package/dist/node22/src/campaignRunner.js +2 -0
- package/dist/node22/src/campaignRunner.jsc +0 -0
- package/dist/node22/src/db.js +2 -0
- package/dist/node22/src/db.jsc +0 -0
- package/dist/node22/src/paths.js +2 -0
- package/dist/node22/src/paths.jsc +0 -0
- package/dist/node22/src/routes/accounts.js +2 -0
- package/dist/node22/src/routes/accounts.jsc +0 -0
- package/dist/node22/src/routes/campaigns.js +2 -0
- package/dist/node22/src/routes/campaigns.jsc +0 -0
- package/dist/node22/src/routes/inbound.js +2 -0
- package/dist/node22/src/routes/inbound.jsc +0 -0
- package/dist/node22/src/server.js +2 -0
- package/dist/node22/src/server.jsc +0 -0
- package/dist/node22/src/utils/csvParser.js +2 -0
- package/dist/node22/src/utils/csvParser.jsc +0 -0
- package/dist/node22/src/utils/templateEngine.js +2 -0
- package/dist/node22/src/utils/templateEngine.jsc +0 -0
- package/dist/node24/cli/index.js +2 -0
- package/dist/node24/cli/index.jsc +0 -0
- package/dist/node24/cli/lib.js +2 -0
- package/dist/node24/cli/lib.jsc +0 -0
- package/dist/node24/mcp/index.js +2 -0
- package/dist/node24/mcp/index.jsc +0 -0
- package/dist/node24/src/accountManager.js +2 -0
- package/dist/node24/src/accountManager.jsc +0 -0
- package/dist/node24/src/campaignRunner.js +2 -0
- package/dist/node24/src/campaignRunner.jsc +0 -0
- package/dist/node24/src/db.js +2 -0
- package/dist/node24/src/db.jsc +0 -0
- package/dist/node24/src/paths.js +2 -0
- package/dist/node24/src/paths.jsc +0 -0
- package/dist/node24/src/routes/accounts.js +2 -0
- package/dist/node24/src/routes/accounts.jsc +0 -0
- package/dist/node24/src/routes/campaigns.js +2 -0
- package/dist/node24/src/routes/campaigns.jsc +0 -0
- package/dist/node24/src/routes/inbound.js +2 -0
- package/dist/node24/src/routes/inbound.jsc +0 -0
- package/dist/node24/src/server.js +2 -0
- package/dist/node24/src/server.jsc +0 -0
- package/dist/node24/src/utils/csvParser.js +2 -0
- package/dist/node24/src/utils/csvParser.jsc +0 -0
- package/dist/node24/src/utils/templateEngine.js +2 -0
- package/dist/node24/src/utils/templateEngine.jsc +0 -0
- package/package.json +44 -0
- package/public/index.html +2459 -0
package/README.md
ADDED
|
@@ -0,0 +1,1060 @@
|
|
|
1
|
+
# WA Blast — Sistema de Mensajes Masivos por WhatsApp
|
|
2
|
+
|
|
3
|
+
Sistema para enviar mensajes masivos por WhatsApp con soporte multi-cuenta, plantillas con variables, imágenes con A/B testing, scheduling por hora/día, control de campañas y comportamiento anti-spam. Incluye API HTTP, interfaz web y CLI para uso por agentes de IA.
|
|
4
|
+
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## Tabla de contenidos
|
|
8
|
+
|
|
9
|
+
- [Requisitos](#requisitos)
|
|
10
|
+
- [Instalación](#instalación)
|
|
11
|
+
- [Configuración](#configuración)
|
|
12
|
+
- [Arranque](#arranque)
|
|
13
|
+
- [Despliegue con Docker](#despliegue-con-docker)
|
|
14
|
+
- [CLI global (`wa-blast`)](#cli-global-wa-blast)
|
|
15
|
+
- [Arquitectura](#arquitectura)
|
|
16
|
+
- [Flujo de uso](#flujo-de-uso)
|
|
17
|
+
- [Interfaz web](#interfaz-web)
|
|
18
|
+
- [API HTTP](#api-http)
|
|
19
|
+
- [CLI](#cli)
|
|
20
|
+
- [Comportamiento anti-spam](#comportamiento-anti-spam)
|
|
21
|
+
- [Scheduling y límites de envío](#scheduling-y-límites-de-envío)
|
|
22
|
+
- [Imágenes y A/B testing](#imágenes-y-ab-testing)
|
|
23
|
+
- [Campañas inbound](#campañas-inbound)
|
|
24
|
+
- [Formato de archivos de contactos](#formato-de-archivos-de-contactos)
|
|
25
|
+
- [Sistema de plantillas](#sistema-de-plantillas)
|
|
26
|
+
- [Base de datos](#base-de-datos)
|
|
27
|
+
- [Solución de problemas](#solución-de-problemas)
|
|
28
|
+
|
|
29
|
+
---
|
|
30
|
+
|
|
31
|
+
## Requisitos
|
|
32
|
+
|
|
33
|
+
- **Node.js v24+** (recomendado via `nvm`)
|
|
34
|
+
- **npm**
|
|
35
|
+
- Chromium (Puppeteer lo descarga automáticamente al hacer `npm install`)
|
|
36
|
+
- Un teléfono con WhatsApp para escanear el QR
|
|
37
|
+
|
|
38
|
+
---
|
|
39
|
+
|
|
40
|
+
## Instalación
|
|
41
|
+
|
|
42
|
+
```bash
|
|
43
|
+
git clone <repo>
|
|
44
|
+
cd agente_whatsapp
|
|
45
|
+
npm install
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
---
|
|
49
|
+
|
|
50
|
+
## Configuración
|
|
51
|
+
|
|
52
|
+
Crea un archivo `.env` en la raíz del proyecto:
|
|
53
|
+
|
|
54
|
+
```env
|
|
55
|
+
PORT=3000 # Puerto del servidor HTTP (default: 3000)
|
|
56
|
+
DELAY_MS=3000 # Delay por defecto entre mensajes en ms (default: 3000)
|
|
57
|
+
MAX_ACCOUNTS=3 # Máximo de cuentas WhatsApp activas simultáneas (default: 3)
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
> `DELAY_MS` es solo el default global. Cada campaña puede sobreescribirlo con su propio `delay_ms`.
|
|
61
|
+
|
|
62
|
+
---
|
|
63
|
+
|
|
64
|
+
## Arranque
|
|
65
|
+
|
|
66
|
+
```bash
|
|
67
|
+
# Modo producción
|
|
68
|
+
npm start
|
|
69
|
+
|
|
70
|
+
# Modo desarrollo (recarga automática ante cambios)
|
|
71
|
+
npm run dev
|
|
72
|
+
|
|
73
|
+
# Ejecutar tests
|
|
74
|
+
npm test
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
El servidor arranca en `http://localhost:3000`.
|
|
78
|
+
|
|
79
|
+
Al arrancar, el sistema reconecta automáticamente todas las cuentas que tienen sesión guardada en `./sessions/` — no es necesario volver a escanear el QR.
|
|
80
|
+
|
|
81
|
+
---
|
|
82
|
+
|
|
83
|
+
## Despliegue con Docker
|
|
84
|
+
|
|
85
|
+
La opción recomendada para producción. El contenedor incluye Chromium del sistema (no descarga el suyo propio), lo que hace la imagen reproducible sin sorpresas de red.
|
|
86
|
+
|
|
87
|
+
### Requisitos
|
|
88
|
+
|
|
89
|
+
- Docker + Docker Compose
|
|
90
|
+
|
|
91
|
+
### Levantar el servidor
|
|
92
|
+
|
|
93
|
+
```bash
|
|
94
|
+
# (Opcional) Copiar y ajustar variables de entorno
|
|
95
|
+
cp .env.example .env
|
|
96
|
+
|
|
97
|
+
# Primera vez — construir imagen y arrancar
|
|
98
|
+
docker compose up -d --build
|
|
99
|
+
|
|
100
|
+
# Ver logs
|
|
101
|
+
docker compose logs -f
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
El servidor queda disponible en `http://localhost:3000`.
|
|
105
|
+
|
|
106
|
+
### Verificar que funciona
|
|
107
|
+
|
|
108
|
+
```bash
|
|
109
|
+
curl http://localhost:3000/health
|
|
110
|
+
# → {"status":"ok","timestamp":"..."}
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
### Persistencia de datos
|
|
114
|
+
|
|
115
|
+
Los datos sobreviven reinicios mediante volúmenes montados en el directorio local:
|
|
116
|
+
|
|
117
|
+
| Directorio local | Montura en contenedor | Contenido |
|
|
118
|
+
|---|---|---|
|
|
119
|
+
| `sessions/` | `/app/sessions` | Sesiones de WhatsApp (~500 MB por cuenta) |
|
|
120
|
+
| `data/` | `/app/data` | Base de datos SQLite (`data.db`) |
|
|
121
|
+
| `uploads/` | `/app/uploads` | Archivos CSV temporales |
|
|
122
|
+
| `campaign_images/` | `/app/campaign_images` | Imágenes de campañas (A/B testing) |
|
|
123
|
+
|
|
124
|
+
### Comandos útiles
|
|
125
|
+
|
|
126
|
+
```bash
|
|
127
|
+
docker compose restart # Reiniciar sin reconstruir
|
|
128
|
+
docker compose up -d --build # Actualizar tras cambios de código
|
|
129
|
+
docker compose down # Detener y eliminar contenedor
|
|
130
|
+
docker compose logs -f # Seguir logs en tiempo real
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
---
|
|
134
|
+
|
|
135
|
+
## CLI global (`wa-blast`)
|
|
136
|
+
|
|
137
|
+
Tras instalar globalmente el CLI, el comando `wa-blast` queda disponible en el PATH para controlar cualquier servidor (local o remoto) desde scripts o desde otro equipo.
|
|
138
|
+
|
|
139
|
+
### Instalación
|
|
140
|
+
|
|
141
|
+
```bash
|
|
142
|
+
# En el directorio del repo clonado
|
|
143
|
+
npm install -g .
|
|
144
|
+
|
|
145
|
+
# Verificar
|
|
146
|
+
wa-blast accounts list
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
### Configurar la URL del servidor
|
|
150
|
+
|
|
151
|
+
```bash
|
|
152
|
+
# Via flag en cada comando
|
|
153
|
+
wa-blast --url http://mi-servidor:3000 accounts list
|
|
154
|
+
|
|
155
|
+
# Via variable de entorno (más cómodo para scripts)
|
|
156
|
+
export WA_BLAST_URL=http://mi-servidor:3000
|
|
157
|
+
wa-blast accounts list
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
---
|
|
161
|
+
|
|
162
|
+
## Arquitectura
|
|
163
|
+
|
|
164
|
+
```
|
|
165
|
+
agente_whatsapp/
|
|
166
|
+
├── src/
|
|
167
|
+
│ ├── server.js # Entry point: Express + arranque + auto-reconexión + scheduler
|
|
168
|
+
│ ├── db.js # SQLite (better-sqlite3) + schema + migraciones automáticas
|
|
169
|
+
│ ├── accountManager.js # Gestión de clientes whatsapp-web.js en memoria
|
|
170
|
+
│ ├── campaignRunner.js # Motor de envío: round-robin, anti-spam, scheduling, A/B
|
|
171
|
+
│ └── routes/
|
|
172
|
+
│ ├── accounts.js # Endpoints REST de cuentas
|
|
173
|
+
│ ├── campaigns.js # Endpoints REST de campañas e imágenes
|
|
174
|
+
│ └── inbound.js # Endpoints REST de campañas inbound
|
|
175
|
+
│ └── utils/
|
|
176
|
+
│ ├── csvParser.js # Parseo de CSV y XLSX
|
|
177
|
+
│ └── templateEngine.js # Reemplazo de {{variables}}
|
|
178
|
+
├── cli/
|
|
179
|
+
│ └── index.js # CLI para uso por agente (salida JSON, exit codes)
|
|
180
|
+
├── sessions/ # Sesiones de WhatsApp (LocalAuth, generado en runtime)
|
|
181
|
+
├── uploads/ # Archivos CSV temporales (se limpian tras importar)
|
|
182
|
+
├── campaign_images/ # Imágenes de campañas (persisten)
|
|
183
|
+
├── data.db # Base de datos SQLite (generada en runtime)
|
|
184
|
+
├── public/
|
|
185
|
+
│ └── index.html # Frontend web (SPA estático)
|
|
186
|
+
├── test/
|
|
187
|
+
│ └── campaigns.test.js # Tests de integración (node:test + supertest)
|
|
188
|
+
├── package.json
|
|
189
|
+
└── .env
|
|
190
|
+
```
|
|
191
|
+
|
|
192
|
+
### Componentes principales
|
|
193
|
+
|
|
194
|
+
| Componente | Responsabilidad |
|
|
195
|
+
|---|---|
|
|
196
|
+
| `server.js` | Arranca Express, monta rutas, reconecta cuentas, ejecuta scheduler de campañas |
|
|
197
|
+
| `db.js` | Conexión singleton a SQLite, schema y migraciones idempotentes |
|
|
198
|
+
| `accountManager.js` | `Map` en memoria de clientes whatsapp-web.js con sus estados y QR |
|
|
199
|
+
| `campaignRunner.js` | Distribuye contactos en round-robin, lanza un loop por cuenta, anti-spam, scheduling, A/B |
|
|
200
|
+
| `csvParser.js` | Lee CSV/XLSX, normaliza columnas, valida columna `phone` |
|
|
201
|
+
| `templateEngine.js` | Reemplaza `{{variable}}` con valores del objeto de datos del contacto |
|
|
202
|
+
| `cli/index.js` | CLI sin dependencias extra, salida JSON limpia, exit codes claros |
|
|
203
|
+
|
|
204
|
+
---
|
|
205
|
+
|
|
206
|
+
## Flujo de uso
|
|
207
|
+
|
|
208
|
+
```
|
|
209
|
+
1. Agregar cuenta WhatsApp → POST /api/accounts
|
|
210
|
+
↓
|
|
211
|
+
2. Escanear QR con el teléfono → GET /api/accounts/:id/qr
|
|
212
|
+
↓
|
|
213
|
+
3. Crear campaña (nombre + cuentas + template + opciones) → POST /api/campaigns
|
|
214
|
+
↓
|
|
215
|
+
4. (Opcional) Subir imagen(es) para A/B testing → POST /api/campaigns/:id/images
|
|
216
|
+
↓
|
|
217
|
+
5. Subir CSV/XLSX con contactos → POST /api/campaigns/:id/contacts
|
|
218
|
+
↓
|
|
219
|
+
6. Iniciar campaña → POST /api/campaigns/:id/start
|
|
220
|
+
↓
|
|
221
|
+
7. Monitorear / pausar / cancelar → GET /api/campaigns/:id
|
|
222
|
+
POST /api/campaigns/:id/pause
|
|
223
|
+
POST /api/campaigns/:id/cancel
|
|
224
|
+
```
|
|
225
|
+
|
|
226
|
+
Si la campaña tiene límites de scheduling configurados, pasará automáticamente a estado `scheduled` al alcanzar el tope diario/horario o salir de la ventana de envío, y se reanudará sola cuando las condiciones lo permitan.
|
|
227
|
+
|
|
228
|
+
---
|
|
229
|
+
|
|
230
|
+
## Interfaz web
|
|
231
|
+
|
|
232
|
+
Abre `http://localhost:3000` en el navegador.
|
|
233
|
+
|
|
234
|
+
### Panel Cuentas
|
|
235
|
+
|
|
236
|
+
- **Agregar cuenta**: ingresa un ID (ej. `micuenta`) y haz clic en AGREGAR
|
|
237
|
+
- **Escanear QR**: el botón QR aparece mientras la cuenta espera autenticación
|
|
238
|
+
- **LÍMITE/DÍA**: campo inline para configurar cuántos mensajes puede enviar esa cuenta por día (default 50)
|
|
239
|
+
- El estado cambia automáticamente: `pending` → `authenticated` → `ready`
|
|
240
|
+
|
|
241
|
+
### Panel Campañas
|
|
242
|
+
|
|
243
|
+
- **Nueva campaña**: abre el modal con todos los parámetros (anti-spam + scheduling)
|
|
244
|
+
- **UPLOAD CSV**: sube contactos (solo en estado `pending`)
|
|
245
|
+
- **IMAGEN / 📷 N IMGS**: sube imágenes para A/B testing (hasta 5 variantes)
|
|
246
|
+
- **START**: inicia el envío (requiere cuentas `ready` y contactos cargados)
|
|
247
|
+
- **PAUSE / RESUME / CANCEL / DEL**: botones según estado
|
|
248
|
+
- **EDIT**: edita cualquier parámetro de campañas `pending`
|
|
249
|
+
- **TRACKING**: modal con funnel ACK en tiempo real, respuestas recibidas y stats por variante A/B
|
|
250
|
+
|
|
251
|
+
### Estados de campaña en la UI
|
|
252
|
+
|
|
253
|
+
| Badge | Significado |
|
|
254
|
+
|---|---|
|
|
255
|
+
| `PENDING` | Lista para configurar |
|
|
256
|
+
| `RUNNING` | Enviando activamente |
|
|
257
|
+
| `PAUSED` | Pausada manualmente |
|
|
258
|
+
| `⏰ SCHEDULED` | Auto-suspendida por límite/horario; se reanuda sola |
|
|
259
|
+
| `DONE` | Completada |
|
|
260
|
+
| `CANCELLED` | Cancelada definitivamente |
|
|
261
|
+
|
|
262
|
+
---
|
|
263
|
+
|
|
264
|
+
## API HTTP
|
|
265
|
+
|
|
266
|
+
Base URL: `http://localhost:3000`
|
|
267
|
+
|
|
268
|
+
Todos los endpoints de recursos devuelven JSON. Los errores incluyen `{ "error": "..." }`.
|
|
269
|
+
|
|
270
|
+
### Health check
|
|
271
|
+
|
|
272
|
+
```
|
|
273
|
+
GET /health
|
|
274
|
+
```
|
|
275
|
+
```json
|
|
276
|
+
{ "status": "ok", "timestamp": "2026-03-31T10:00:00.000Z" }
|
|
277
|
+
```
|
|
278
|
+
|
|
279
|
+
---
|
|
280
|
+
|
|
281
|
+
### Cuentas — `/api/accounts`
|
|
282
|
+
|
|
283
|
+
#### Crear / inicializar cuenta
|
|
284
|
+
|
|
285
|
+
```
|
|
286
|
+
POST /api/accounts
|
|
287
|
+
Content-Type: application/json
|
|
288
|
+
|
|
289
|
+
{ "id": "micuenta" }
|
|
290
|
+
```
|
|
291
|
+
|
|
292
|
+
Respuesta `202`:
|
|
293
|
+
```json
|
|
294
|
+
{
|
|
295
|
+
"message": "Cuenta \"micuenta\" inicializando. Use GET /api/accounts/micuenta/qr para obtener el QR.",
|
|
296
|
+
"id": "micuenta",
|
|
297
|
+
"status": "pending"
|
|
298
|
+
}
|
|
299
|
+
```
|
|
300
|
+
|
|
301
|
+
> La inicialización es asíncrona. El QR estará disponible en ~5-10 segundos.
|
|
302
|
+
|
|
303
|
+
---
|
|
304
|
+
|
|
305
|
+
#### Listar cuentas
|
|
306
|
+
|
|
307
|
+
```
|
|
308
|
+
GET /api/accounts
|
|
309
|
+
```
|
|
310
|
+
|
|
311
|
+
```json
|
|
312
|
+
[
|
|
313
|
+
{
|
|
314
|
+
"id": "micuenta",
|
|
315
|
+
"phone": "5491112345678",
|
|
316
|
+
"status": "ready",
|
|
317
|
+
"daily_limit": 50,
|
|
318
|
+
"created_at": "2026-03-31T10:00:00",
|
|
319
|
+
"in_memory": true
|
|
320
|
+
}
|
|
321
|
+
]
|
|
322
|
+
```
|
|
323
|
+
|
|
324
|
+
**Estados de cuenta:** `pending` → `authenticated` → `ready` | `disconnected`
|
|
325
|
+
|
|
326
|
+
---
|
|
327
|
+
|
|
328
|
+
#### Obtener QR
|
|
329
|
+
|
|
330
|
+
```
|
|
331
|
+
GET /api/accounts/:id/qr
|
|
332
|
+
```
|
|
333
|
+
|
|
334
|
+
```json
|
|
335
|
+
{ "qr": "data:image/png;base64,iVBORw0KGgo..." }
|
|
336
|
+
```
|
|
337
|
+
|
|
338
|
+
---
|
|
339
|
+
|
|
340
|
+
#### Estado de una cuenta
|
|
341
|
+
|
|
342
|
+
```
|
|
343
|
+
GET /api/accounts/:id/status
|
|
344
|
+
```
|
|
345
|
+
|
|
346
|
+
---
|
|
347
|
+
|
|
348
|
+
#### Editar cuenta
|
|
349
|
+
|
|
350
|
+
```
|
|
351
|
+
PATCH /api/accounts/:id
|
|
352
|
+
Content-Type: application/json
|
|
353
|
+
|
|
354
|
+
{ "daily_limit": 30 }
|
|
355
|
+
```
|
|
356
|
+
|
|
357
|
+
Actualiza el límite diario de envío. Valor mínimo: 1.
|
|
358
|
+
|
|
359
|
+
---
|
|
360
|
+
|
|
361
|
+
#### Reset de cuenta
|
|
362
|
+
|
|
363
|
+
```
|
|
364
|
+
POST /api/accounts/:id/reset
|
|
365
|
+
```
|
|
366
|
+
|
|
367
|
+
Borra la sesión guardada y fuerza un QR nuevo. Útil si la sesión quedó corrupta.
|
|
368
|
+
|
|
369
|
+
---
|
|
370
|
+
|
|
371
|
+
#### Eliminar cuenta
|
|
372
|
+
|
|
373
|
+
```
|
|
374
|
+
DELETE /api/accounts/:id
|
|
375
|
+
```
|
|
376
|
+
|
|
377
|
+
Desconecta WhatsApp, destruye el cliente en memoria y elimina el registro de la DB.
|
|
378
|
+
|
|
379
|
+
---
|
|
380
|
+
|
|
381
|
+
### Campañas — `/api/campaigns`
|
|
382
|
+
|
|
383
|
+
#### Crear campaña
|
|
384
|
+
|
|
385
|
+
```
|
|
386
|
+
POST /api/campaigns
|
|
387
|
+
Content-Type: application/json
|
|
388
|
+
|
|
389
|
+
{
|
|
390
|
+
"name": "Promo mayo",
|
|
391
|
+
"account_ids": ["cuenta1"],
|
|
392
|
+
"template": "Hola {{nombre}}, tu código es {{codigo}}",
|
|
393
|
+
"delay_ms": 4000,
|
|
394
|
+
"jitter_ms": 2000,
|
|
395
|
+
"typing_sim": 1,
|
|
396
|
+
"burst_size": 15,
|
|
397
|
+
"burst_pause_ms": 60000,
|
|
398
|
+
"daily_cap": 40,
|
|
399
|
+
"hourly_cap": 10,
|
|
400
|
+
"send_window_start": "09:00",
|
|
401
|
+
"send_window_end": "18:00",
|
|
402
|
+
"active_days": "1,2,3,4,5"
|
|
403
|
+
}
|
|
404
|
+
```
|
|
405
|
+
|
|
406
|
+
**Campos:**
|
|
407
|
+
|
|
408
|
+
| Campo | Tipo | Req. | Default | Descripción |
|
|
409
|
+
|---|---|---|---|---|
|
|
410
|
+
| `name` | string | ✓ | — | Nombre de la campaña |
|
|
411
|
+
| `account_ids` | string[] | ✓ | — | IDs de cuentas WhatsApp asignadas |
|
|
412
|
+
| `template` | string | ✓ | — | Texto con `{{variables}}` |
|
|
413
|
+
| `delay_ms` | integer | — | env `DELAY_MS` | ms entre mensajes (mín. 1000) |
|
|
414
|
+
| `jitter_ms` | integer | — | 2000 | Variación aleatoria del delay |
|
|
415
|
+
| `typing_sim` | 0\|1 | — | 1 | Simular "escribiendo..." |
|
|
416
|
+
| `burst_size` | integer | — | 0 | Pausa cada N mensajes (0=off) |
|
|
417
|
+
| `burst_pause_ms` | integer | — | 60000 | Duración de la pausa periódica |
|
|
418
|
+
| `daily_cap` | integer | — | 0 | Máx. mensajes por día para esta campaña (0=usa límite de cuenta) |
|
|
419
|
+
| `hourly_cap` | integer | — | 0 | Máx. mensajes por hora (0=desactivado) |
|
|
420
|
+
| `send_window_start` | string | — | null | Inicio de ventana de envío (`HH:MM`) |
|
|
421
|
+
| `send_window_end` | string | — | null | Fin de ventana de envío (`HH:MM`) |
|
|
422
|
+
| `active_days` | string | — | null | Días activos: `"1,2,3,4,5"` (1=Lun … 7=Dom). `null`=todos |
|
|
423
|
+
|
|
424
|
+
> También se acepta `account_id` (string) para compatibilidad legacy con una sola cuenta.
|
|
425
|
+
|
|
426
|
+
Respuesta `201`: campaña creada con todos sus campos.
|
|
427
|
+
|
|
428
|
+
---
|
|
429
|
+
|
|
430
|
+
#### Editar campaña
|
|
431
|
+
|
|
432
|
+
Solo campañas en estado `pending`. Acepta todos los mismos campos de creación (todos opcionales).
|
|
433
|
+
|
|
434
|
+
```
|
|
435
|
+
PATCH /api/campaigns/:id
|
|
436
|
+
```
|
|
437
|
+
|
|
438
|
+
---
|
|
439
|
+
|
|
440
|
+
#### Subir contactos (CSV/XLSX)
|
|
441
|
+
|
|
442
|
+
Solo campañas en estado `pending`.
|
|
443
|
+
|
|
444
|
+
```
|
|
445
|
+
POST /api/campaigns/:id/contacts
|
|
446
|
+
Content-Type: multipart/form-data
|
|
447
|
+
|
|
448
|
+
file=<archivo.csv>
|
|
449
|
+
```
|
|
450
|
+
|
|
451
|
+
---
|
|
452
|
+
|
|
453
|
+
#### Imágenes para A/B testing
|
|
454
|
+
|
|
455
|
+
Ver sección [Imágenes y A/B testing](#imágenes-y-ab-testing).
|
|
456
|
+
|
|
457
|
+
```
|
|
458
|
+
GET /api/campaigns/:id/images
|
|
459
|
+
POST /api/campaigns/:id/images # multipart/form-data, campo: image
|
|
460
|
+
DELETE /api/campaigns/:id/images/:imgId
|
|
461
|
+
```
|
|
462
|
+
|
|
463
|
+
---
|
|
464
|
+
|
|
465
|
+
#### Iniciar / reanudar campaña
|
|
466
|
+
|
|
467
|
+
```
|
|
468
|
+
POST /api/campaigns/:id/start
|
|
469
|
+
```
|
|
470
|
+
|
|
471
|
+
Inicia o reanuda el envío. Funciona desde estados `pending`, `paused` y `scheduled`.
|
|
472
|
+
|
|
473
|
+
Los contactos se distribuyen en round-robin entre las cuentas `ready`. Si hay imágenes, también se asignan en round-robin (A/B).
|
|
474
|
+
|
|
475
|
+
---
|
|
476
|
+
|
|
477
|
+
#### Pausar campaña
|
|
478
|
+
|
|
479
|
+
```
|
|
480
|
+
POST /api/campaigns/:id/pause
|
|
481
|
+
```
|
|
482
|
+
|
|
483
|
+
Funciona desde estados `running` y `scheduled`. Para reanudar, llama a `/start`.
|
|
484
|
+
|
|
485
|
+
---
|
|
486
|
+
|
|
487
|
+
#### Cancelar campaña
|
|
488
|
+
|
|
489
|
+
```
|
|
490
|
+
POST /api/campaigns/:id/cancel
|
|
491
|
+
```
|
|
492
|
+
|
|
493
|
+
---
|
|
494
|
+
|
|
495
|
+
#### Eliminar campaña
|
|
496
|
+
|
|
497
|
+
Solo en estados `pending`, `done`, `cancelled` y `scheduled`.
|
|
498
|
+
|
|
499
|
+
```
|
|
500
|
+
DELETE /api/campaigns/:id
|
|
501
|
+
```
|
|
502
|
+
|
|
503
|
+
Elimina en cascada contactos, imágenes del disco e mensajes entrantes asociados.
|
|
504
|
+
|
|
505
|
+
---
|
|
506
|
+
|
|
507
|
+
#### Listar campañas
|
|
508
|
+
|
|
509
|
+
```
|
|
510
|
+
GET /api/campaigns
|
|
511
|
+
```
|
|
512
|
+
|
|
513
|
+
Incluye `stats`, `accounts` e `images` (variantes A/B) por campaña.
|
|
514
|
+
|
|
515
|
+
---
|
|
516
|
+
|
|
517
|
+
#### Detalle de campaña
|
|
518
|
+
|
|
519
|
+
```
|
|
520
|
+
GET /api/campaigns/:id
|
|
521
|
+
```
|
|
522
|
+
|
|
523
|
+
---
|
|
524
|
+
|
|
525
|
+
#### Tracking ACK
|
|
526
|
+
|
|
527
|
+
```
|
|
528
|
+
GET /api/campaigns/:id/tracking
|
|
529
|
+
```
|
|
530
|
+
|
|
531
|
+
```json
|
|
532
|
+
{
|
|
533
|
+
"campaign": { "id": 1, "name": "Promo mayo", "status": "scheduled" },
|
|
534
|
+
"total": 500,
|
|
535
|
+
"sent": 80,
|
|
536
|
+
"ack": [
|
|
537
|
+
{ "ack_status": "delivered", "count": 65 },
|
|
538
|
+
{ "ack_status": "read", "count": 15 }
|
|
539
|
+
],
|
|
540
|
+
"replies": 3,
|
|
541
|
+
"image_variants": [
|
|
542
|
+
{
|
|
543
|
+
"label": "A", "image_id": 1, "original_name": "promo_verano.jpg",
|
|
544
|
+
"total": 250, "sent": 40, "failed": 0, "delivered": 35, "read": 8
|
|
545
|
+
},
|
|
546
|
+
{
|
|
547
|
+
"label": "B", "image_id": 2, "original_name": "promo_invierno.jpg",
|
|
548
|
+
"total": 250, "sent": 40, "failed": 0, "delivered": 30, "read": 7
|
|
549
|
+
}
|
|
550
|
+
]
|
|
551
|
+
}
|
|
552
|
+
```
|
|
553
|
+
|
|
554
|
+
`image_variants` solo aparece si la campaña tiene imágenes configuradas.
|
|
555
|
+
|
|
556
|
+
---
|
|
557
|
+
|
|
558
|
+
#### Respuestas recibidas
|
|
559
|
+
|
|
560
|
+
```
|
|
561
|
+
GET /api/campaigns/:id/replies
|
|
562
|
+
```
|
|
563
|
+
|
|
564
|
+
---
|
|
565
|
+
|
|
566
|
+
## CLI
|
|
567
|
+
|
|
568
|
+
```bash
|
|
569
|
+
# Instalado globalmente
|
|
570
|
+
wa-blast <resource> <action> [args] [flags]
|
|
571
|
+
|
|
572
|
+
# Directo
|
|
573
|
+
node cli/index.js <resource> <action> [args] [flags]
|
|
574
|
+
```
|
|
575
|
+
|
|
576
|
+
### CLI: Cuentas
|
|
577
|
+
|
|
578
|
+
```bash
|
|
579
|
+
wa-blast accounts list
|
|
580
|
+
wa-blast accounts add micuenta
|
|
581
|
+
wa-blast accounts status micuenta
|
|
582
|
+
wa-blast accounts qr micuenta
|
|
583
|
+
wa-blast accounts edit micuenta --daily-limit 30
|
|
584
|
+
wa-blast accounts reset micuenta
|
|
585
|
+
wa-blast accounts delete micuenta
|
|
586
|
+
wa-blast accounts messages micuenta
|
|
587
|
+
```
|
|
588
|
+
|
|
589
|
+
---
|
|
590
|
+
|
|
591
|
+
### CLI: Campañas
|
|
592
|
+
|
|
593
|
+
```bash
|
|
594
|
+
# Listar y ver detalle
|
|
595
|
+
wa-blast campaigns list
|
|
596
|
+
wa-blast campaigns show 1
|
|
597
|
+
|
|
598
|
+
# Crear campaña básica
|
|
599
|
+
wa-blast campaigns create \
|
|
600
|
+
--name "Promo mayo" \
|
|
601
|
+
--accounts "cuenta1" \
|
|
602
|
+
--template "Hola {{nombre}}, tu oferta: {{oferta}}" \
|
|
603
|
+
--delay 4000
|
|
604
|
+
|
|
605
|
+
# Crear campaña con scheduling completo
|
|
606
|
+
wa-blast campaigns create \
|
|
607
|
+
--name "Campaña semana laboral" \
|
|
608
|
+
--accounts "cuenta1" \
|
|
609
|
+
--template "Hola {{nombre}}" \
|
|
610
|
+
--delay 4000 \
|
|
611
|
+
--jitter 2000 \
|
|
612
|
+
--typing 1 \
|
|
613
|
+
--burst-size 15 \
|
|
614
|
+
--burst-pause 60000 \
|
|
615
|
+
--daily-cap 40 \
|
|
616
|
+
--hourly-cap 10 \
|
|
617
|
+
--window-start 09:00 \
|
|
618
|
+
--window-end 18:00 \
|
|
619
|
+
--active-days "1,2,3,4,5"
|
|
620
|
+
|
|
621
|
+
# Subir contactos
|
|
622
|
+
wa-blast campaigns upload 1 ./contactos.csv
|
|
623
|
+
|
|
624
|
+
# Imágenes A/B testing
|
|
625
|
+
wa-blast campaigns images 1
|
|
626
|
+
wa-blast campaigns image-add 1 ./imagen_verano.jpg
|
|
627
|
+
wa-blast campaigns image-add 1 ./imagen_invierno.jpg
|
|
628
|
+
wa-blast campaigns image-delete 1 2
|
|
629
|
+
|
|
630
|
+
# Ciclo de vida
|
|
631
|
+
wa-blast campaigns start 1
|
|
632
|
+
wa-blast campaigns pause 1
|
|
633
|
+
wa-blast campaigns cancel 1
|
|
634
|
+
wa-blast campaigns delete 1
|
|
635
|
+
|
|
636
|
+
# Editar campaña pending
|
|
637
|
+
wa-blast campaigns edit 1 \
|
|
638
|
+
--daily-cap 30 \
|
|
639
|
+
--window-start 10:00 \
|
|
640
|
+
--window-end 17:00 \
|
|
641
|
+
--active-days "1,2,3,4,5"
|
|
642
|
+
|
|
643
|
+
# Esperar resultado (imprime puntos hasta done/paused/cancelled)
|
|
644
|
+
wa-blast campaigns wait 1
|
|
645
|
+
wa-blast campaigns wait 1 --interval 10
|
|
646
|
+
|
|
647
|
+
# Stats y respuestas
|
|
648
|
+
wa-blast campaigns tracking 1
|
|
649
|
+
wa-blast campaigns replies 1
|
|
650
|
+
```
|
|
651
|
+
|
|
652
|
+
---
|
|
653
|
+
|
|
654
|
+
### Flags de `campaigns create` y `campaigns edit`
|
|
655
|
+
|
|
656
|
+
**Anti-spam:**
|
|
657
|
+
|
|
658
|
+
| Flag | Descripción | Default |
|
|
659
|
+
|---|---|---|
|
|
660
|
+
| `--name <texto>` | Nombre | — |
|
|
661
|
+
| `--accounts <c1,c2>` | Cuentas separadas por coma | — |
|
|
662
|
+
| `--template <texto>` | Plantilla del mensaje | — |
|
|
663
|
+
| `--delay <ms>` | Delay base entre mensajes | 3000 |
|
|
664
|
+
| `--jitter <ms>` | Variación aleatoria del delay | 2000 |
|
|
665
|
+
| `--typing <0\|1>` | Simular "escribiendo..." | 1 |
|
|
666
|
+
| `--burst-size <n>` | Pausa cada N mensajes (0=off) | 0 |
|
|
667
|
+
| `--burst-pause <ms>` | Duración de la pausa periódica | 60000 |
|
|
668
|
+
|
|
669
|
+
**Scheduling:**
|
|
670
|
+
|
|
671
|
+
| Flag | Descripción | Default |
|
|
672
|
+
|---|---|---|
|
|
673
|
+
| `--daily-cap <n>` | Máx. mensajes/día para esta campaña (0=usa cuenta) | 0 |
|
|
674
|
+
| `--hourly-cap <n>` | Máx. mensajes/hora (0=desactivado) | 0 |
|
|
675
|
+
| `--window-start HH:MM` | Inicio de ventana de envío | null |
|
|
676
|
+
| `--window-end HH:MM` | Fin de ventana de envío | null |
|
|
677
|
+
| `--active-days <1,2,3,4,5>` | Días activos (1=Lun … 7=Dom) | null (todos) |
|
|
678
|
+
|
|
679
|
+
---
|
|
680
|
+
|
|
681
|
+
### Ejemplo completo con CLI (uso por agente)
|
|
682
|
+
|
|
683
|
+
```bash
|
|
684
|
+
export WA_BLAST_URL=http://mi-servidor:3000
|
|
685
|
+
|
|
686
|
+
# 1. Verificar cuenta lista
|
|
687
|
+
wa-blast accounts status cuenta1
|
|
688
|
+
|
|
689
|
+
# 2. Crear campaña con límite 50/día, solo días hábiles 9-18h
|
|
690
|
+
CAMPAIGN=$(wa-blast campaigns create \
|
|
691
|
+
--name "Promo 500 contactos" \
|
|
692
|
+
--accounts "cuenta1" \
|
|
693
|
+
--template "Hola {{nombre}}, tu descuento es {{descuento}}%" \
|
|
694
|
+
--delay 4000 --jitter 2000 --typing 1 \
|
|
695
|
+
--daily-cap 50 --window-start 09:00 --window-end 18:00 \
|
|
696
|
+
--active-days "1,2,3,4,5")
|
|
697
|
+
|
|
698
|
+
ID=$(echo $CAMPAIGN | jq '.id')
|
|
699
|
+
|
|
700
|
+
# 3. Agregar imagen (opcional)
|
|
701
|
+
wa-blast campaigns image-add $ID ./promo.jpg
|
|
702
|
+
|
|
703
|
+
# 4. Subir 500 contactos
|
|
704
|
+
wa-blast campaigns upload $ID ./contactos.csv
|
|
705
|
+
|
|
706
|
+
# 5. Iniciar — enviará 50 hoy, quedará en 'scheduled', se reanuda sola mañana
|
|
707
|
+
wa-blast campaigns start $ID
|
|
708
|
+
|
|
709
|
+
# 6. Monitorear
|
|
710
|
+
wa-blast campaigns show $ID | jq '{status: .status, stats: .stats}'
|
|
711
|
+
|
|
712
|
+
# 7. Ver tracking con stats por variante
|
|
713
|
+
wa-blast campaigns tracking $ID
|
|
714
|
+
```
|
|
715
|
+
|
|
716
|
+
---
|
|
717
|
+
|
|
718
|
+
## Comportamiento anti-spam
|
|
719
|
+
|
|
720
|
+
El sistema implementa cuatro capas de humanización para reducir el riesgo de ban por parte de Meta.
|
|
721
|
+
|
|
722
|
+
### 1. Jitter en el delay
|
|
723
|
+
|
|
724
|
+
```
|
|
725
|
+
delay_final = delay_ms + random(0, jitter_ms)
|
|
726
|
+
```
|
|
727
|
+
|
|
728
|
+
Ejemplo: `delay_ms=4000` + `jitter_ms=2000` → espera entre **4s y 6s** por mensaje.
|
|
729
|
+
|
|
730
|
+
### 2. Simulación de escritura (`typing_sim`)
|
|
731
|
+
|
|
732
|
+
Antes de enviar: activa "escribiendo...", espera proporcional al largo del mensaje (±30%), detiene el estado, envía.
|
|
733
|
+
|
|
734
|
+
```
|
|
735
|
+
base = clamp(largo × 60ms, 1500ms, 7000ms)
|
|
736
|
+
tiempo = base × (0.7 + random × 0.6)
|
|
737
|
+
```
|
|
738
|
+
|
|
739
|
+
### 3. Presencia online
|
|
740
|
+
|
|
741
|
+
Al inicio de cada loop de cuenta se llama `sendPresenceAvailable()`.
|
|
742
|
+
|
|
743
|
+
### 4. Pausa periódica (burst pause)
|
|
744
|
+
|
|
745
|
+
Cada `burst_size` mensajes, pausa de `burst_pause_ms`. Ejemplo: `burst_size=15` + `burst_pause_ms=60000` → pausa de 1 minuto cada 15 mensajes.
|
|
746
|
+
|
|
747
|
+
### Valores recomendados por volumen
|
|
748
|
+
|
|
749
|
+
| Volumen | `delay_ms` | `jitter_ms` | `typing_sim` | `burst_size` | `burst_pause_ms` |
|
|
750
|
+
|---|---|---|---|---|---|
|
|
751
|
+
| Bajo (<100/día) | 3000 | 1000 | 1 | 0 | — |
|
|
752
|
+
| Medio (100-500/día) | 4000 | 2000 | 1 | 20 | 60000 |
|
|
753
|
+
| Alto (500+/día) | 5000 | 3000 | 1 | 15 | 90000 |
|
|
754
|
+
|
|
755
|
+
---
|
|
756
|
+
|
|
757
|
+
## Scheduling y límites de envío
|
|
758
|
+
|
|
759
|
+
Permite que una campaña se distribuya automáticamente en varios días respetando límites de WhatsApp.
|
|
760
|
+
|
|
761
|
+
### Límite diario por cuenta
|
|
762
|
+
|
|
763
|
+
Cada cuenta tiene un `daily_limit` (default: **50**). Cuando una cuenta alcanza su límite del día, la campaña pasa automáticamente a estado `scheduled` y el scheduler la reanuda al día siguiente.
|
|
764
|
+
|
|
765
|
+
Configurar via UI (campo "LÍMITE/DÍA" en la tarjeta de cuenta) o CLI:
|
|
766
|
+
|
|
767
|
+
```bash
|
|
768
|
+
wa-blast accounts edit cuenta1 --daily-limit 40
|
|
769
|
+
```
|
|
770
|
+
|
|
771
|
+
### Parámetros de scheduling por campaña
|
|
772
|
+
|
|
773
|
+
| Parámetro | Descripción |
|
|
774
|
+
|---|---|
|
|
775
|
+
| `daily_cap` | Tope diario de la campaña. Si `0`, usa el `daily_limit` de la cuenta. Útil para usar solo una parte del cupo cuando hay varias campañas activas. |
|
|
776
|
+
| `hourly_cap` | Tope por hora. `0` = sin límite horario. |
|
|
777
|
+
| `send_window_start` / `send_window_end` | Solo envía dentro de este rango horario (formato `HH:MM`). |
|
|
778
|
+
| `active_days` | Días de la semana activos: `"1,2,3,4,5"` = lunes a viernes. `1`=Lun, `2`=Mar, … `7`=Dom. `null` o vacío = todos los días. |
|
|
779
|
+
|
|
780
|
+
### Estado `scheduled`
|
|
781
|
+
|
|
782
|
+
Cuando cualquier límite se alcanza o la campaña está fuera de ventana, el runner cambia automáticamente el estado a `scheduled`:
|
|
783
|
+
|
|
784
|
+
```
|
|
785
|
+
running → [límite alcanzado o fuera de ventana] → scheduled
|
|
786
|
+
scheduled → [condiciones ok, scheduler cada 60s] → running
|
|
787
|
+
```
|
|
788
|
+
|
|
789
|
+
Desde la UI/CLI también se puede resumir manualmente una campaña `scheduled`.
|
|
790
|
+
|
|
791
|
+
### Ejemplo: 500 contactos con cuenta de 50/día
|
|
792
|
+
|
|
793
|
+
Con `daily_cap=50`, ventana `09:00-18:00`, días `1,2,3,4,5`:
|
|
794
|
+
|
|
795
|
+
- **Día 1 (lunes)**: runner envía 50 → `scheduled`
|
|
796
|
+
- **Día 2 (martes)**: scheduler detecta nuevo día, ventana activa → `running` → envía 50 → `scheduled`
|
|
797
|
+
- …10 días hábiles después → `done`
|
|
798
|
+
|
|
799
|
+
> **Nota**: Si la campaña usa múltiples cuentas y una de ellas alcanza su `daily_limit`, la campaña completa pasa a `scheduled`. El scheduler la reanudará cuando al menos una cuenta tenga capacidad.
|
|
800
|
+
|
|
801
|
+
---
|
|
802
|
+
|
|
803
|
+
## Imágenes y A/B testing
|
|
804
|
+
|
|
805
|
+
Permite adjuntar hasta **5 imágenes** (variantes A-E) a una campaña. Se distribuyen en round-robin entre los contactos y se trackea la performance de cada variante.
|
|
806
|
+
|
|
807
|
+
### Subir imágenes
|
|
808
|
+
|
|
809
|
+
```bash
|
|
810
|
+
# Subir variante A
|
|
811
|
+
wa-blast campaigns image-add 1 ./imagen_a.jpg
|
|
812
|
+
# → { "id": 1, "label": "A", "original_name": "imagen_a.jpg", ... }
|
|
813
|
+
|
|
814
|
+
# Subir variante B
|
|
815
|
+
wa-blast campaigns image-add 1 ./imagen_b.png
|
|
816
|
+
# → { "id": 2, "label": "B", ... }
|
|
817
|
+
```
|
|
818
|
+
|
|
819
|
+
Formatos aceptados: `.jpg`, `.jpeg`, `.png`, `.gif`, `.webp`. Máximo 5MB por imagen.
|
|
820
|
+
|
|
821
|
+
### Distribución
|
|
822
|
+
|
|
823
|
+
Al iniciar la campaña, los contactos se asignan en round-robin a las variantes:
|
|
824
|
+
|
|
825
|
+
- 9 contactos + 3 variantes → 3 contactos por variante (A, B, C, A, B, C, ...)
|
|
826
|
+
- Si hay solo 1 imagen → todos los contactos reciben esa imagen (imagen única sin A/B)
|
|
827
|
+
|
|
828
|
+
### Envío
|
|
829
|
+
|
|
830
|
+
La imagen se envía con el texto del template como **caption**.
|
|
831
|
+
|
|
832
|
+
### Tracking por variante
|
|
833
|
+
|
|
834
|
+
El endpoint `GET /api/campaigns/:id/tracking` incluye `image_variants` con métricas separadas por variante: total, sent, delivered, read.
|
|
835
|
+
|
|
836
|
+
En la UI, el tab "A/B IMAGES" en el modal de tracking muestra estas métricas.
|
|
837
|
+
|
|
838
|
+
### Eliminar imagen
|
|
839
|
+
|
|
840
|
+
```bash
|
|
841
|
+
wa-blast campaigns image-delete 1 2 # elimina imagen con id=2
|
|
842
|
+
```
|
|
843
|
+
|
|
844
|
+
Al eliminar, los labels se reordenan automáticamente (A, B, C, ... consecutivos).
|
|
845
|
+
|
|
846
|
+
> Las imágenes solo se pueden agregar/eliminar en campañas en estado `pending`.
|
|
847
|
+
|
|
848
|
+
---
|
|
849
|
+
|
|
850
|
+
## Campañas inbound
|
|
851
|
+
|
|
852
|
+
Las campañas inbound son auto-respuestas que se envían automáticamente cuando un contacto escribe a una cuenta WhatsApp. Soportan distribución de cupones únicos por contacto.
|
|
853
|
+
|
|
854
|
+
### Endpoints
|
|
855
|
+
|
|
856
|
+
```
|
|
857
|
+
GET /api/inbound
|
|
858
|
+
POST /api/inbound
|
|
859
|
+
GET /api/inbound/:id
|
|
860
|
+
POST /api/inbound/:id/activate
|
|
861
|
+
POST /api/inbound/:id/pause
|
|
862
|
+
DELETE /api/inbound/:id
|
|
863
|
+
POST /api/inbound/:id/coupons # multipart, campo: file (.txt/.csv)
|
|
864
|
+
GET /api/inbound/:id/coupons
|
|
865
|
+
GET /api/inbound/:id/contacts
|
|
866
|
+
GET /api/inbound/:id/contacts/export # descarga CSV
|
|
867
|
+
POST /api/inbound/:id/to-campaign # crear campaña outbound con los contactos inbound
|
|
868
|
+
```
|
|
869
|
+
|
|
870
|
+
### Parámetros
|
|
871
|
+
|
|
872
|
+
| Campo | Descripción |
|
|
873
|
+
|---|---|
|
|
874
|
+
| `account_id` | Cuenta que recibirá mensajes entrantes |
|
|
875
|
+
| `name` | Nombre descriptivo |
|
|
876
|
+
| `template` | Mensaje de respuesta (soporta `{{nombre}}` y `{{cupon}}`) |
|
|
877
|
+
| `max_uses` | Máximo de usos (0=ilimitado) |
|
|
878
|
+
| `reply_mode` | `once_per_campaign` (default), `always`, `new_contacts_only` |
|
|
879
|
+
|
|
880
|
+
### CLI inbound
|
|
881
|
+
|
|
882
|
+
```bash
|
|
883
|
+
wa-blast inbound list
|
|
884
|
+
wa-blast inbound create --account cuenta1 --name "Promo QR" --template "Tu cupón: {{cupon}}"
|
|
885
|
+
wa-blast inbound coupons-upload 1 ./cupones.txt
|
|
886
|
+
wa-blast inbound coupons 1
|
|
887
|
+
wa-blast inbound contacts 1
|
|
888
|
+
wa-blast inbound export 1 > contactos.csv
|
|
889
|
+
wa-blast inbound to-campaign 1 --name "Follow-up" --template "Hola {{nombre}}" --accounts "cuenta1"
|
|
890
|
+
wa-blast inbound activate 1
|
|
891
|
+
wa-blast inbound pause 1
|
|
892
|
+
wa-blast inbound delete 1
|
|
893
|
+
```
|
|
894
|
+
|
|
895
|
+
---
|
|
896
|
+
|
|
897
|
+
## Formato de archivos de contactos
|
|
898
|
+
|
|
899
|
+
Se aceptan **CSV** y **Excel (.xlsx, .xls)**. Tamaño máximo: 10MB.
|
|
900
|
+
|
|
901
|
+
La columna de teléfono puede llamarse: `phone`, `telefono`, `numero`, `número`.
|
|
902
|
+
|
|
903
|
+
El resto de columnas se convierten en variables para el template.
|
|
904
|
+
|
|
905
|
+
### Ejemplo
|
|
906
|
+
|
|
907
|
+
```csv
|
|
908
|
+
phone,nombre,empresa,descuento
|
|
909
|
+
5491112345678,Juan García,Acme SA,20%
|
|
910
|
+
5491133445566,María López,Tech Corp,15%
|
|
911
|
+
5491144556677,Carlos Martínez,StartupXYZ,30%
|
|
912
|
+
```
|
|
913
|
+
|
|
914
|
+
### Formato de teléfonos
|
|
915
|
+
|
|
916
|
+
- Incluir **código de país** sin el símbolo `+`
|
|
917
|
+
- Se eliminan automáticamente: espacios, guiones, paréntesis, `+`
|
|
918
|
+
- Argentina: `54` + `9` + código de área + número → `5491112345678`
|
|
919
|
+
|
|
920
|
+
---
|
|
921
|
+
|
|
922
|
+
## Sistema de plantillas
|
|
923
|
+
|
|
924
|
+
```
|
|
925
|
+
Hola {{nombre}}, te contactamos de {{empresa}}.
|
|
926
|
+
Tu código es {{codigo}} válido hasta {{vencimiento}}.
|
|
927
|
+
```
|
|
928
|
+
|
|
929
|
+
Si una variable no existe para ese contacto, el placeholder queda tal cual.
|
|
930
|
+
|
|
931
|
+
Cuando la campaña tiene imagen, el template se envía como **caption** de la imagen.
|
|
932
|
+
|
|
933
|
+
---
|
|
934
|
+
|
|
935
|
+
## Base de datos
|
|
936
|
+
|
|
937
|
+
El archivo `data.db` (SQLite) se crea automáticamente. Las migraciones son idempotentes.
|
|
938
|
+
|
|
939
|
+
### `accounts`
|
|
940
|
+
|
|
941
|
+
| Columna | Tipo | Default | Descripción |
|
|
942
|
+
|---|---|---|---|
|
|
943
|
+
| `id` | TEXT PK | — | Identificador de cuenta |
|
|
944
|
+
| `phone` | TEXT | — | Número de teléfono (disponible tras autenticar) |
|
|
945
|
+
| `status` | TEXT | `pending` | `pending` / `authenticated` / `ready` / `disconnected` |
|
|
946
|
+
| `daily_limit` | INTEGER | 50 | Máx. mensajes que esta cuenta puede enviar por día |
|
|
947
|
+
| `created_at` | TEXT | now | |
|
|
948
|
+
|
|
949
|
+
### `campaigns`
|
|
950
|
+
|
|
951
|
+
| Columna | Tipo | Default | Descripción |
|
|
952
|
+
|---|---|---|---|
|
|
953
|
+
| `id` | INTEGER PK | autoincrement | |
|
|
954
|
+
| `name` | TEXT | — | Nombre |
|
|
955
|
+
| `template` | TEXT | — | Plantilla con `{{...}}` |
|
|
956
|
+
| `status` | TEXT | `pending` | `pending` / `running` / `paused` / `scheduled` / `done` / `cancelled` |
|
|
957
|
+
| `delay_ms` | INTEGER | 3000 | Delay base entre mensajes |
|
|
958
|
+
| `jitter_ms` | INTEGER | 2000 | Variación aleatoria del delay |
|
|
959
|
+
| `typing_sim` | INTEGER | 1 | Simular escritura (0/1) |
|
|
960
|
+
| `burst_size` | INTEGER | 0 | Pausa cada N mensajes |
|
|
961
|
+
| `burst_pause_ms` | INTEGER | 60000 | Duración de pausa periódica |
|
|
962
|
+
| `daily_cap` | INTEGER | 0 | Tope diario de campaña (0=usa daily_limit de cuenta) |
|
|
963
|
+
| `hourly_cap` | INTEGER | 0 | Tope por hora (0=desactivado) |
|
|
964
|
+
| `send_window_start` | TEXT | null | Inicio de ventana horaria (`HH:MM`) |
|
|
965
|
+
| `send_window_end` | TEXT | null | Fin de ventana horaria (`HH:MM`) |
|
|
966
|
+
| `active_days` | TEXT | null | Días activos como CSV de números 1-7 |
|
|
967
|
+
| `created_at` | TEXT | now | |
|
|
968
|
+
|
|
969
|
+
### `campaign_accounts`
|
|
970
|
+
|
|
971
|
+
Relación N:N entre campañas y cuentas.
|
|
972
|
+
|
|
973
|
+
### `campaign_images`
|
|
974
|
+
|
|
975
|
+
| Columna | Tipo | Descripción |
|
|
976
|
+
|---|---|---|
|
|
977
|
+
| `id` | INTEGER PK | |
|
|
978
|
+
| `campaign_id` | INTEGER FK | |
|
|
979
|
+
| `label` | TEXT | `A`, `B`, `C`, `D` o `E` |
|
|
980
|
+
| `file_path` | TEXT | Ruta relativa en `campaign_images/` |
|
|
981
|
+
| `original_name` | TEXT | Nombre original del archivo |
|
|
982
|
+
| `created_at` | TEXT | |
|
|
983
|
+
|
|
984
|
+
### `contacts`
|
|
985
|
+
|
|
986
|
+
| Columna | Tipo | Descripción |
|
|
987
|
+
|---|---|---|
|
|
988
|
+
| `id` | INTEGER PK | |
|
|
989
|
+
| `campaign_id` | INTEGER FK | |
|
|
990
|
+
| `phone` | TEXT | Número destino |
|
|
991
|
+
| `data` | TEXT | JSON con variables del CSV |
|
|
992
|
+
| `status` | TEXT | `pending` / `sent` / `failed` |
|
|
993
|
+
| `error` | TEXT | Mensaje de error si falló |
|
|
994
|
+
| `sent_at` | TEXT | Timestamp de envío exitoso |
|
|
995
|
+
| `assigned_account_id` | TEXT | Cuenta asignada (round-robin) |
|
|
996
|
+
| `image_id` | INTEGER FK | Variante de imagen asignada (A/B testing) |
|
|
997
|
+
| `message_id` | TEXT | ID interno del mensaje en WhatsApp |
|
|
998
|
+
| `ack_status` | TEXT | `sent` / `delivered` / `read` / `played` |
|
|
999
|
+
| `delivered_at` | TEXT | Timestamp de entrega |
|
|
1000
|
+
| `read_at` | TEXT | Timestamp de lectura |
|
|
1001
|
+
|
|
1002
|
+
### `incoming_messages`
|
|
1003
|
+
|
|
1004
|
+
Mensajes recibidos de contactos durante campañas activas.
|
|
1005
|
+
|
|
1006
|
+
### `inbound_campaigns`, `inbound_coupons`, `inbound_replies`
|
|
1007
|
+
|
|
1008
|
+
Tablas para el sistema de auto-respuesta inbound con distribución de cupones.
|
|
1009
|
+
|
|
1010
|
+
---
|
|
1011
|
+
|
|
1012
|
+
## Solución de problemas
|
|
1013
|
+
|
|
1014
|
+
### El QR no aparece
|
|
1015
|
+
|
|
1016
|
+
- Esperar ~10 segundos después de `accounts add`
|
|
1017
|
+
- Verificar que el servidor esté corriendo: `npm start`
|
|
1018
|
+
- Revisar logs: buscar `[clientId] QR generado`
|
|
1019
|
+
|
|
1020
|
+
### "Ninguna cuenta asignada está lista (ready)"
|
|
1021
|
+
|
|
1022
|
+
- La cuenta debe estar en estado `ready` antes de iniciar
|
|
1023
|
+
- Verificar: `wa-blast accounts status <id>`
|
|
1024
|
+
|
|
1025
|
+
### La campaña queda en `scheduled` y no reanuda
|
|
1026
|
+
|
|
1027
|
+
- Verificar que la hora actual esté dentro de `send_window_start` y `send_window_end`
|
|
1028
|
+
- Verificar que hoy sea un `active_day`
|
|
1029
|
+
- Verificar que la cuenta no haya alcanzado su `daily_limit`: `wa-blast campaigns tracking <id>`
|
|
1030
|
+
- El scheduler corre cada 60s; esperar o resumir manualmente con `wa-blast campaigns start <id>`
|
|
1031
|
+
|
|
1032
|
+
### La campaña queda en `running` indefinidamente
|
|
1033
|
+
|
|
1034
|
+
- Si todas las cuentas asignadas se desconectaron, el loop no termina
|
|
1035
|
+
- Pausar o cancelar manualmente, reconectar cuentas y reiniciar
|
|
1036
|
+
|
|
1037
|
+
### Error de compilación de `better-sqlite3`
|
|
1038
|
+
|
|
1039
|
+
- Requiere `better-sqlite3 v12+` con Node.js v24
|
|
1040
|
+
- Reinstalar: `npm install better-sqlite3@latest`
|
|
1041
|
+
|
|
1042
|
+
### Sesiones perdidas tras reiniciar
|
|
1043
|
+
|
|
1044
|
+
- Las sesiones se guardan en `./sessions/` via `LocalAuth`
|
|
1045
|
+
- El servidor reconecta automáticamente al arrancar
|
|
1046
|
+
- Si se borra `sessions/`, hay que escanear QR de nuevo
|
|
1047
|
+
- Usar `POST /api/accounts/:id/reset` para forzar un QR nuevo
|
|
1048
|
+
|
|
1049
|
+
### Cuenta baneada o mensajes no entregados
|
|
1050
|
+
|
|
1051
|
+
- Aumentar `delay_ms` y `jitter_ms`
|
|
1052
|
+
- Activar `typing_sim=1`
|
|
1053
|
+
- Configurar `burst_size` para pausas periódicas
|
|
1054
|
+
- Respetar el `daily_limit` de 50 mensajes/día por cuenta
|
|
1055
|
+
- Una cuenta nueva necesita warm-up gradual (no enviar 100 el primer día)
|
|
1056
|
+
|
|
1057
|
+
### Límite de cuentas activas superado
|
|
1058
|
+
|
|
1059
|
+
- Default es `MAX_ACCOUNTS=3` en `.env`
|
|
1060
|
+
- Aumentar o eliminar cuentas inactivas: `wa-blast accounts delete <id>`
|