CRM de WhatsApp Open Source
Notibuzz es software libre pensado para desplegarse en tu propia infraestructura con Docker. Sin planes comerciales: levantas el stack en un VPS, conectas WhatsApp y operas con el CRM completo.
100% Open Source
Código libre. Sin suscripciones ni planes de pago. El software es tuyo.
Docker & contenedores
Despliega con Docker Compose en minutos: servicios aislados, reproducibles y escalables.
Self-hosted
Tu VPS, tu cloud o tu red local. Tus datos nunca salen de tu infraestructura.
API REST + SDKs
Integra WhatsApp con tu stack. Documentación y ejemplos incluidos.
Contenedores listos
Cada componente del stack corre en su propio contenedor. Actualiza, reinicia o escala sin tocar el host.
Infraestructura tuya
Sin vendor lock-in. Migras de proveedor cuando quieras llevando volúmenes y configuración.
Privacidad total
Mensajes, contactos y credenciales permanecen en tu entorno. Ideal para equipos con datos sensibles.
Extensible
Modifica el código, añade módulos o conecta webhooks a tu ERP, e-commerce o BI.
Todo incluido
Un solo despliegue: CRM, API, IA, workers y bandeja. Sin módulos de pago ni feature flags comerciales.
Escala con tu hardware
Más sesiones, más agentes, más volumen — limitado solo por los recursos que asignes a los contenedores.
Todo el producto, sin restricciones
- Bandeja unificada y multi-sesión WhatsApp
- Chats, contactos, plantillas y campañas
- Dashboard, workers y monitor de eventos
- RBAC: usuarios, roles y permisos
- Chatbot y Copilot (IA)
- API REST ilimitada y webhooks
- SDKs JavaScript y Python
Guía de despliegue Docker
Elige producción en VPS o prueba en local con Traefik + HTTPS (mkcert).
Stack productivo con Docker Compose: Traefik termina HTTPS, enruta por path y orquesta MongoDB, Redis, WAHA, Hub, Auth, Bridge, Media, IA y el frontend del CRM.
Mínimo recomendado
- 2 vCPU · 4 GB RAM
- 40 GB SSD
- Ubuntu 22.04 LTS (o similar con Docker)
- 80 y 443 abiertos (HTTPS vía Traefik)
Producción con IA y varias sesiones
- 4 vCPU · 8 GB RAM
- 80 GB SSD
- Ubuntu 22.04 LTS
- 80 y 443 + registro DNS apuntando al VPS
Arquitectura del stack
| Servicio | Función |
|---|---|
| Traefik | Reverse proxy, HTTPS (Let's Encrypt) y enrutamiento por path |
| noti-sender-web | Frontend CRM (Nuxt) |
| noti-sender-hub | API REST y WebSocket en tiempo real |
| noti-sender-waha | Bridge WhatsApp / WAHA (sesiones y mensajes) |
| noti-sender-auth | Autenticación JWT y dispositivos vinculados |
| noti-sender-bridge | Cola de envío, rate limiting y bulk |
| noti-sender-media | Archivos multimedia de conversaciones |
| noti-sender-ia | Chatbot, Copilot y RAG (opcional) |
| MongoDB | Base de datos principal (sesiones, contactos, config) |
| Redis | Cache, colas y pub/sub entre servicios |
Red Docker compartida
Todos los contenedores se conectan a la red bridge externa notibuzz-network. Créala una vez con docker network create notibuzz-network antes de docker compose up. Traefik descubre servicios por labels en esa misma red (--providers.docker.network=notibuzz-network).
Planes comerciales (opcional)
Si despliegas para vender o comercializar el CRM, asigna un plan por cliente con setupPlansAndSubscription.js. Self-hosted sin venta: crea admin y asigna BUSINESS o PRO a tu propio client-id.
Los precios mensual/anual son estándar preconfigurados al ejecutar setupPlansAndSubscription.js --init-plans-only. Si deseas cambiar el precio u otros valores (sesiones, tokens IA, agentes…), revisa la colección plans en MongoDB y edita el plan correspondiente. La columna de tokens IA es el cupo mensual de consumo vía DeepSeek (Chatbot/Copilot en noti-sender-ia); requiere DEEPSEEK_API_KEY en el compose y se renueva automáticamente cada mes según la fecha de inicio de la suscripción.
| Plan | Sesiones |
|---|---|
DEVELOPERAPI / developers — sin dashboard CRM completo | 8 |
STARTEREquipos pequeños — CRM + IA + campañas básicas | 8 |
PROEmpresas en crecimiento — incluye Copilot | 15 |
BUSINESSMáxima capacidad — multi-agente y bulk avanzado | 20 |
Pasos en producción
- 1
Prepara el VPS
Instala Docker Engine y Docker Compose v2. Asegura firewall con 80/443 abiertos y SSH restringido.
Proveedores habituales: Hetzner, DigitalOcean, AWS Lightsail, OVH, etc.
Reserva RAM extra si activas el servicio de IA (embeddings en CPU).
curl -fsSL https://get.docker.com | shsudo usermod -aG docker $USERdocker compose version - 2
Configura DNS
Apunta un registro A (o AAAA) de tu dominio a la IP pública del VPS. Espera propagación antes de solicitar certificados.
Ejemplo: crm.tuempresa.com → 203.0.113.10
Opcional: registro www con redirect al dominio principal.
- 3
Crea la red Docker (bridge externa)
Todos los servicios se conectan a una red bridge compartida. Debe existir antes de levantar el stack porque el compose la referencia como external.
Driver bridge: los contenedores se resuelven por nombre (redis, mongodb, noti-sender-hub…).
Traefik usa --providers.docker.network=notibuzz-network — debe coincidir con el nombre real de la red.
Si la red ya existe, omite create (no falla: docker network create || true).
MongoDB y Redis solo están en esta red; no se publican puertos al host.
docker network create notibuzz-networkdocker network inspect notibuzz-network --format '{{.Name}} · {{.Driver}} · {{.Scope}}'Bloque networks en docker-compose.yaml
networks: notibuzz-network: external: true name: notibuzz-network - 4
Crea docker-compose.yaml
En tu VPS crea un directorio (por ejemplo ~/notibuzz), guarda el archivo docker-compose.yaml con el contenido siguiente y reemplaza cada placeholder TU_* y el dominio de ejemplo.
Busca y reemplaza crm.tu-dominio.com por tu dominio real en todas las labels Traefik.
Imagen WAHA: revisa https://waha.devlike.pro/docs/overview/quick-start/ y coloca la imagen correcta (devlikeapro/waha o :arm en Apple Silicon).
Usa la misma TU_WAHA_API_KEY en waha, hub y bridge.
TU_REDIS_PASSWORD debe coincidir en redis, waha, hub, auth, bridge, ia y media (REDIS_HOST=redis).
noti-sender-media: MEDIA_PUBLIC_BASE_URL=https://TU_DOMINIO/media para que WAHA descargue archivos.
mkdir -p ~/notibuzz && cd ~/notibuzznano docker-compose.yamlReemplaza estos placeholders en el YAML:
crm.tu-dominio.com— Tu dominio público (DNS → IP del VPS)TU_MONGO_PASSWORD— Contraseña root de MongoDBTU_REDIS_PASSWORD— Contraseña Redis (misma en todos los servicios)TU_WAHA_API_KEY— Clave API WAHA — openssl rand -hex 16TU_WAHA_DASHBOARD_PASSWORD— Panel WAHA y SwaggerTU_JWT_SECRET— JWT access — openssl rand -hex 32TU_JWT_REFRESH_SECRET— JWT refresh — openssl rand -hex 32TU_DEEPSEEK_API_KEY— Opcional, solo si usas IAtu-email@ejemplo.com— Email Let's Encrypt (Traefik)devlikeapro/waha— Imagen WAHA — revisa https://waha.devlike.pro/docs/overview/quick-start/MEDIA_PUBLIC_BASE_URL— URL pública de media — https://TU_DOMINIO/media (WAHA descarga archivos desde ahí)notibuzz-network— Red Docker bridge externa — créala antes de docker compose up
docker-compose.local.yaml
# Notibuzz — docker-compose.yaml (producción / VPS) # Copia este archivo en tu servidor y reemplaza los valores TU_* y tu-dominio.com # # Antes del primer up: docker network create notibuzz-network version: '3.8' services: # MongoDB - Base de datos principal mongodb: image: mongo:7.0 container_name: notibuzz-mongodb restart: unless-stopped expose: - "27017" environment: MONGO_INITDB_ROOT_USERNAME: root MONGO_INITDB_ROOT_PASSWORD: TU_MONGO_PASSWORD MONGO_INITDB_DATABASE: notibuzz volumes: - mongodb_data:/data/db - mongodb_config:/data/configdb networks: - notibuzz-network healthcheck: test: ["CMD", "mongosh", "--eval", "db.adminCommand('ping')"] interval: 10s timeout: 5s retries: 5 start_period: 10s # Redis - Cache y colas redis: image: redis:7-alpine container_name: notibuzz-redis restart: unless-stopped expose: - "6379" command: redis-server --appendonly yes --requirepass ${REDIS_PASSWORD:-TU_REDIS_PASSWORD} volumes: - redis_data:/data networks: - notibuzz-network healthcheck: test: ["CMD", "redis-cli", "--raw", "incr", "ping"] interval: 10s timeout: 3s retries: 5 start_period: 10s # noti-sender-waha - API principal de WhatsApp (WAHA) noti-sender-waha: # Imagen WAHA: revisa https://waha.devlike.pro/docs/overview/quick-start/ # y coloca la imagen correcta (ej. devlikeapro/waha; en ARM: devlikeapro/waha:arm). image: devlikeapro/waha container_name: noti-sender-waha restart: unless-stopped expose: - "3000" #env_file: # - noti-sender-waha/.env environment: # ================== # ===== COMMON ===== # ================== WAHA_BASE_URL: http://noti-sender-waha:3000 # ==================== # ===== SECURITY ===== # ==================== WAHA_API_KEY: TU_WAHA_API_KEY WAHA_DASHBOARD_USERNAME: admin WAHA_DASHBOARD_PASSWORD: TU_WAHA_DASHBOARD_PASSWORD WHATSAPP_SWAGGER_USERNAME: admin WHATSAPP_SWAGGER_PASSWORD: TU_WAHA_DASHBOARD_PASSWORD WHATSAPP_DEFAULT_ENGINE: NOWEB # =================== # ===== LOGGING ===== # =================== WAHA_LOG_FORMAT: JSON WAHA_LOG_LEVEL: info WAHA_PRINT_QR: "false" # ========================= # ===== MEDIA STORAGE ===== # ========================= WAHA_MEDIA_STORAGE: LOCAL WHATSAPP_FILES_LIFETIME: 0 WHATSAPP_FILES_FOLDER: /tmp/whatsapp-files WHATSAPP_DOWNLOAD_MEDIA: "true" WHATSAPP_FILES_MIMETYPES: "image/jpeg,image/png,image/webp,video/mp4,image/gif,application/pdf,application/msword,application/vnd.openxmlformats-officedocument.wordprocessingml.document,application/vnd.ms-excel,application/vnd.openxmlformats-officedocument.spreadsheetml.sheet,application/vnd.ms-powerpoint,application/vnd.openxmlformats-officedocument.presentationml.presentation,audio/mpeg,audio/ogg,audio/opus,text/csv" # =========================== # ===== SESSION STORAGE ===== # =========================== WHATSAPP_SESSIONS_MONGO_URL: mongodb://root:TU_MONGO_PASSWORD@mongodb:27017?authSource=admin # ================= # ===== APPS ====== # ================= REDIS_URL: redis://:TU_REDIS_PASSWORD@redis:6379 # ================================== # ===== ADVANCED CONFIGURATION ===== # ================================== TZ: America/Lima volumes: - waha_sessions:/app/sessions # Guardar archivos multimedia en /tmp/whatsapp-files para compartir con noti-sender-media - waha_files:/tmp/whatsapp-files labels: - "traefik.enable=true" - "traefik.http.routers.waha.rule=Host(`crm.tu-dominio.com`) && PathPrefix(`/waha`)" - "traefik.http.routers.waha.entrypoints=websecure" - "traefik.http.routers.waha.tls.certresolver=letsencrypt" - "traefik.http.routers.waha.service=waha" - "traefik.http.routers.waha.middlewares=waha-stripprefix" - "traefik.http.services.waha.loadbalancer.server.port=3000" - "traefik.http.middlewares.waha-stripprefix.stripprefix.prefixes=/waha" networks: - notibuzz-network # noti-sender-hub - Hub de WebSocket y API REST noti-sender-hub: image: hilosdf/noti-sender-hub:60f414f #142bb5a container_name: noti-sender-hub restart: unless-stopped expose: - "3001" # WebSocket - "3002" # API REST #env_file: # - noti-sender-hub/.env environment: # ========================== # PUERTOS # ========================== HUB_PORT: 3001 HTTP_PORT: 3002 # ========================== # BASE DE DATOS (MongoDB) # ========================== MONGODB_URI: mongodb://root:TU_MONGO_PASSWORD@mongodb:27017/notibuzz?authSource=admin # ========================== # REDIS # ========================== REDIS_ENABLED: "true" REDIS_URL: redis://:TU_REDIS_PASSWORD@redis:6379 REDIS_HOST: redis REDIS_PORT: 6379 REDIS_PASSWORD: TU_REDIS_PASSWORD REDIS_DB: 0 REDIS_CONNECT_TIMEOUT_MS: 5000 REDIS_RECONNECT_BASE_MS: 1000 REDIS_RECONNECT_MAX_MS: 10000 PARTICIPANT_CACHE_TTL_MS: 900000 PARTICIPANT_CACHE_MAX: 5000 PARTICIPANT_REQUEST_TIMEOUT_MS: 4000 PARTICIPANT_CACHE_SWR_THRESHOLD_MS: 120000 REDIS_PREFIX: "hub:" # Pub/Sub compartido con noti-sender-auth (user.devices.changed); no usar REDIS_PREFIX aquí en código. REDIS_USER_DEVICES_CHANNEL: "notibuzz:pub:userDevices" # ========================== # API DE WAHA # ========================== API_WS_URL: ws://noti-sender-waha:3000/ws API_KEY: TU_WAHA_API_KEY API_BASE_URL: http://noti-sender-waha:3000 # ========================== # SESIONES Y EVENTOS # ========================== SESSION: "*" EVENTS: message.any,message.reaction,message.edited,message.revoked,message.waiting # ========================== # BRIDGE # ========================== BRIDGE_API_KEY: TU_WAHA_API_KEY BRIDGE_BASE_URL: http://noti-sender-bridge:3003 # ========================== # FRONTEND (CORS) # ========================== WEB_FRONTEND_HOST: https://crm.tu-dominio.com # ========================== # SERVICIO MULTIMEDIA # ========================== MEDIA_SERVICE_URL: https://crm.tu-dominio.com/media # ========================== # SERVICIO DE AUTENTICACIÓN # ========================== # Dentro de Docker Compose, usar HTTP (no HTTPS) y el nombre del servicio AUTH_SERVICE_URL: http://noti-sender-auth:3005 # ========================== # SERVICIO DE IA # ========================== # Dentro de Docker Compose, usar HTTP (no HTTPS) y el nombre del servicio IA_SERVICE_URL: http://noti-sender-ia:3006 IA_ENABLED: "true" IA_SERVICE_TIMEOUT: 60000 # ========================== # LÍMITES DE BODY / MULTIMEDIA (Express + Traefik buffering) # ========================== # MB: mismo valor en noti-sender-media / bridge. Traefik large-upload en bytes = MB * 1024². BODY_LIMIT_MB: 1024 # Multer /api/media/upload: explícito en bytes (= 1 GiB); si se omite, uploadLimits deriva de BODY_LIMIT_MB. MEDIA_UPLOAD_MAX_BYTES: 1073741824 PUSH_AUDIT: 0 LOG_TO_CONSOLE: 0 PUSH_NON_TEXT_BODY: icon PUSH_TEXT_PREVIEW_MAX: 280 volumes: - hub_uploads:/app/uploads - hub_logs:/app/logs labels: # API REST Router - /api - "traefik.enable=true" - "traefik.http.routers.hub-api.rule=Host(`crm.tu-dominio.com`) && PathPrefix(`/api`)" - "traefik.http.routers.hub-api.entrypoints=websecure" - "traefik.http.routers.hub-api.tls.certresolver=letsencrypt" - "traefik.http.routers.hub-api.service=hub-api" - "traefik.http.routers.hub-api.middlewares=hub-api-stripprefix,large-upload" - "traefik.http.services.hub-api.loadbalancer.server.port=3002" - "traefik.http.middlewares.hub-api-stripprefix.stripprefix.prefixes=/api" # Buffering: 1 GiB (1024 MiB) = 1073741824 bytes - "traefik.http.middlewares.large-upload.buffering.maxRequestBodyBytes=1073741824" - "traefik.http.middlewares.large-upload.buffering.memRequestBodyBytes=2097152" # API REST Router - /notibuzz (proxy desde frontend, reescribe a /api) # Usa un servicio separado sin stripprefix para que Express reciba /api/sessions - "traefik.http.routers.hub-notibuzz.rule=Host(`crm.tu-dominio.com`) && PathPrefix(`/notibuzz`)" - "traefik.http.routers.hub-notibuzz.priority=5" - "traefik.http.routers.hub-notibuzz.entrypoints=websecure" - "traefik.http.routers.hub-notibuzz.tls.certresolver=letsencrypt" - "traefik.http.routers.hub-notibuzz.service=hub-notibuzz-api" - "traefik.http.routers.hub-notibuzz.middlewares=hub-notibuzz-rewrite,large-upload" - "traefik.http.services.hub-notibuzz-api.loadbalancer.server.port=3002" - "traefik.http.services.hub-notibuzz-api.loadbalancer.passHostHeader=true" - "traefik.http.middlewares.hub-notibuzz-rewrite.replacepathregex.regex=^/notibuzz/(.*)" - "traefik.http.middlewares.hub-notibuzz-rewrite.replacepathregex.replacement=/api/$$1" # Router específico para /notibuzz/web -> /web (sin /api) - "traefik.http.routers.hub-notibuzz-web.rule=Host(`crm.tu-dominio.com`) && PathPrefix(`/notibuzz/web`)" - "traefik.http.routers.hub-notibuzz-web.priority=10" - "traefik.http.routers.hub-notibuzz-web.entrypoints=websecure" - "traefik.http.routers.hub-notibuzz-web.tls.certresolver=letsencrypt" - "traefik.http.routers.hub-notibuzz-web.service=hub-notibuzz-web" - "traefik.http.routers.hub-notibuzz-web.middlewares=hub-notibuzz-web-rewrite,large-upload" - "traefik.http.services.hub-notibuzz-web.loadbalancer.server.port=3002" - "traefik.http.services.hub-notibuzz-web.loadbalancer.passHostHeader=true" - "traefik.http.middlewares.hub-notibuzz-web-rewrite.replacepathregex.regex=^/notibuzz/web/?(.*)" - "traefik.http.middlewares.hub-notibuzz-web-rewrite.replacepathregex.replacement=/web/$$1" # WebSocket Router - Similar a nginx proxy_pass para WebSocket - "traefik.http.routers.hub-ws.rule=Host(`crm.tu-dominio.com`) && PathPrefix(`/ws`)" - "traefik.http.routers.hub-ws.entrypoints=websecure" - "traefik.http.routers.hub-ws.tls.certresolver=letsencrypt" - "traefik.http.routers.hub-ws.service=hub-ws" - "traefik.http.routers.hub-ws.middlewares=hub-ws-stripprefix" - "traefik.http.services.hub-ws.loadbalancer.server.port=3001" - "traefik.http.services.hub-ws.loadbalancer.passHostHeader=true" # Middleware para remover el prefijo /ws antes de enviar al backend # /ws/?session=*&events=... -> /?session=*&events=... - "traefik.http.middlewares.hub-ws-stripprefix.stripprefix.prefixes=/ws" networks: - notibuzz-network # noti-sender-auth - Servicio de autenticación con JWT noti-sender-auth: image: hilosdf/noti-sender-auth:21e7595 #68bd411 container_name: noti-sender-auth restart: unless-stopped expose: - "3005" #env_file: # - ../noti-sender-auth/.env environment: # ============================================ # PUERTO # ============================================ PORT: 3005 # ============================================ # BASE DE DATOS (MongoDB) # ============================================ MONGODB_URI: mongodb://root:TU_MONGO_PASSWORD@mongodb:27017/notibuzz?authSource=admin # ============================================ # JWT SECRETS # ============================================ # ⚠️ IMPORTANTE: Cambiar estos valores en producción # Generar con: openssl rand -hex 32 JWT_SECRET: TU_JWT_SECRET JWT_REFRESH_SECRET: TU_JWT_REFRESH_SECRET # ============================================ # TOKEN EXPIRY # ============================================ ACCESS_TOKEN_EXPIRY: 15m REFRESH_TOKEN_EXPIRY: 7d # ============================================ # CORS # ============================================ CORS_ORIGIN: https://crm.tu-dominio.com,http://localhost:8080,http://localhost:3002 # ============================================ # TRUST PROXY # ============================================ # Configurar trust proxy para que Express confíe en Traefik (1 proxy) # Esto permite que req.ip obtenga la IP real del cliente desde X-Forwarded-For TRUST_PROXY: "1" # ============================================ # REDIS (para almacenamiento compartido de challenges) # ============================================ REDIS_ENABLED: "true" REDIS_URL: redis://:TU_REDIS_PASSWORD@redis:6379 REDIS_HOST: redis REDIS_PORT: 6379 REDIS_PASSWORD: TU_REDIS_PASSWORD REDIS_DB: 0 REDIS_CONNECT_TIMEOUT_MS: 5000 REDIS_RECONNECT_BASE_MS: 1000 REDIS_RECONNECT_MAX_MS: 10000 REDIS_PREFIX: "auth:" REDIS_USER_DEVICES_CHANNEL: "notibuzz:pub:userDevices" # ============================================ # LOGGING # ============================================ LOG_LEVEL: info NODE_ENV: production LINK_DEVICE_MAX_DEVICES_GLOBAL: "5" LINK_DEVICE_MAX_DEVICES: "5" volumes: - auth_logs:/app/logs labels: - "traefik.enable=true" # Router de autenticación - Prioridad alta para asegurar que se evalúe antes que otros routers - "traefik.http.routers.auth.rule=Host(`crm.tu-dominio.com`) && PathPrefix(`/auth`)" - "traefik.http.routers.auth.priority=20" - "traefik.http.routers.auth.entrypoints=websecure" - "traefik.http.routers.auth.tls.certresolver=letsencrypt" - "traefik.http.routers.auth.service=auth" - "traefik.http.services.auth.loadbalancer.server.port=3005" - "traefik.http.services.auth.loadbalancer.passHostHeader=true" networks: - notibuzz-network depends_on: mongodb: condition: service_healthy # noti-sender-bridge - Bridge para envío de mensajes noti-sender-bridge: image: hilosdf/noti-sender-bridge:09dcd34 container_name: noti-sender-bridge restart: unless-stopped expose: - "3003" #env_file: # - noti-sender-bridge/.env environment: # Bridge Configuration BRIDGE_PORT: 3003 BRIDGE_CLUSTER_WORKERS: 0 # API Configuration SENDER_API_URL: http://noti-sender-waha:3000 # Hub Configuration HUB_BASE_URL: http://noti-sender-hub:3002 # Mongo Configuration MONGODB_URI: mongodb://root:TU_MONGO_PASSWORD@mongodb:27017/notibuzz?authSource=admin # Redis Configuration REDIS_URL: redis://:TU_REDIS_PASSWORD@redis:6379 # Queue Configuration QUEUE_NAME: noti-send QUEUE_CONCURRENCY: 20 # Rate Limit Configuration RATE_LIMIT_MAX: 200 RATE_LIMIT_WINDOW_MS: 1000 # Queue Behavior FORCE_QUEUE: "false" # Bulk Message Configuration BULK_MAX_MESSAGES: 500 BULK_MIN_INTERVAL_MS: 2500 BULK_JITTER_MS: 250 BULK_SESSION_MAX_PARALLEL: 2 # Bulk Logging Configuration BULK_LOG_BATCH_SIZE: 50 BULK_CALLBACKS_FLUSH_MS: 1500 BULK_PROGRESS_EVERY_MESSAGES: 1 BULK_PROGRESS_FLUSH_MS: 600 BULK_CANCEL_TTL_SEC: 120 # Body Limit Configuration (alineado con hub / Traefik large-upload) BODY_LIMIT_MB: 1024 # Node Environment NODE_ENV: development # Máximo por sesión SINGLE_MESSAGE_MAX_QUEUE_PER_SESSION: 50 # Máximo total SINGLE_MESSAGE_MAX_QUEUE_TOTAL: 1000 # Intervalo mínimo SINGLE_MESSAGE_MIN_INTERVAL_MS: 2000 USE_WAHA_DISPATCHER: "true" USE_DISPATCHER_FOR_BULK: "false" PUSH_NON_TEXT_BODY: icon PUSH_TEXT_PREVIEW_MAX: "280" labels: - "traefik.enable=true" - "traefik.http.routers.bridge.rule=Host(`crm.tu-dominio.com`) && PathPrefix(`/bridge`)" - "traefik.http.routers.bridge.entrypoints=websecure" - "traefik.http.routers.bridge.tls.certresolver=letsencrypt" - "traefik.http.routers.bridge.service=bridge" - "traefik.http.routers.bridge.middlewares=bridge-stripprefix,large-upload" - "traefik.http.services.bridge.loadbalancer.server.port=3003" - "traefik.http.middlewares.bridge-stripprefix.stripprefix.prefixes=/bridge" networks: - notibuzz-network # noti-sender-media - Servicio de archivos multimedia noti-sender-media: image: hilosdf/noti-sender-media:dbad5a4 container_name: noti-sender-media restart: unless-stopped expose: - "3004" #env_file: # - noti-sender-media/.env environment: # Puerto del servidor PORT: 3004 # Ruta base donde se almacenan los archivos multimedia FILES_BASE_PATH: /tmp/whatsapp-files # Sesiones autorizadas (separadas por comas) ALLOWED_SESSIONS: "" # CORS: Orígenes permitidos (separados por comas) CORS_ORIGINS: "" # Entorno: development | production NODE_ENV: development # Permitir listado de archivos (opcional) ALLOW_LISTING: "true" # Mismo tope MB que sender-hub (express + multer /process) BODY_LIMIT_MB: 1024 # Redis (BullMQ) — obligatorio para /process y workers REDIS_HOST: redis REDIS_PORT: 6379 REDIS_PASSWORD: ${REDIS_PASSWORD:-TU_REDIS_PASSWORD} REDIS_DB: 0 # URL pública para que WAHA descargue archivos al enviar medios MEDIA_PUBLIC_BASE_URL: https://crm.tu-dominio.com/media volumes: # Leer archivos multimedia desde noti-sender-waha - waha_files:/tmp/whatsapp-files:ro labels: # Media Router - /media -> /files - "traefik.enable=true" - "traefik.http.routers.media.rule=Host(`crm.tu-dominio.com`) && PathPrefix(`/media`)" - "traefik.http.routers.media.entrypoints=websecure" - "traefik.http.routers.media.tls.certresolver=letsencrypt" - "traefik.http.routers.media.service=media" - "traefik.http.routers.media.middlewares=media-replacepath,large-upload" - "traefik.http.services.media.loadbalancer.server.port=3004" - "traefik.http.middlewares.media-replacepath.replacepathregex.regex=^/media/(.*)" - "traefik.http.middlewares.media-replacepath.replacepathregex.replacement=/files/$$1" # Files Router - /files - "traefik.http.routers.files.rule=Host(`crm.tu-dominio.com`) && PathPrefix(`/files`)" - "traefik.http.routers.files.entrypoints=websecure" - "traefik.http.routers.files.tls.certresolver=letsencrypt" - "traefik.http.routers.files.service=files" - "traefik.http.routers.files.middlewares=large-upload" - "traefik.http.services.files.loadbalancer.server.port=3004" networks: - notibuzz-network depends_on: redis: condition: service_healthy noti-sender-waha: condition: service_started # noti-sender-ia - Servicio de IA con RAG noti-sender-ia: image: hilosdf/noti-sender-ia:1d0de8e container_name: noti-sender-ia restart: unless-stopped expose: - "3006" environment: # ============================================ # PUERTO # ============================================ PORT: 3006 NODE_ENV: production # ============================================ # BASE DE DATOS (MongoDB) # ============================================ MONGODB_URI: mongodb://root:TU_MONGO_PASSWORD@mongodb:27017/notibuzz?authSource=admin MONGODB_DB_NAME: notibuzz # ============================================ # REDIS # ============================================ REDIS_URL: redis://:TU_REDIS_PASSWORD@redis:6379/0 REDIS_PREFIX: "ia:" # ============================================ # DEEPSEEK API CONFIGURATION # ============================================ DEEPSEEK_API_KEY: ${DEEPSEEK_API_KEY:-TU_DEEPSEEK_API_KEY} DEEPSEEK_API_URL: https://api.deepseek.com/v1/chat/completions DEEPSEEK_MODEL: deepseek-chat # ============================================ # TOKEN LIMITS # ============================================ DEFAULT_MAX_TOKENS_PER_REQUEST: 1000 # ============================================ # CONTEXT MANAGEMENT # ============================================ MAX_CONTEXT_MESSAGES: 6 # ============================================ # CACHE CONFIGURATION # ============================================ CACHE_TTL_SECONDS: 60 ENABLE_CACHE: "true" CACHE_MAX_ENTRIES_PER_CONVERSATION: 10 # ============================================ # RAG CONFIGURATION # ============================================ MAX_RETRIEVAL_CHUNKS: 3 SIMILARITY_THRESHOLD: 0.7 CHUNK_SIZE: 800 CHUNK_OVERLAP: 160 # ============================================ # EMBEDDING CONFIGURATION # ============================================ EMBEDDING_MODEL: paraphrase-multilingual-MiniLM-L12-v2 EMBEDDING_DIMENSION: 384 # ============================================ # LOGGING # ============================================ LOG_LEVEL: info # ============================================ # SERVICIO DE AUTENTICACIÓN # ============================================ # URL del servicio de autenticación para validar límites de tokens por plan # Dentro de Docker Compose, usar HTTP (no HTTPS) y el nombre del servicio AUTH_SERVICE_URL: http://noti-sender-auth:3005 # ============================================ # TOKENIZERS CONFIGURATION # ============================================ TOKENIZERS_PARALLELISM: "false" # PRODUCTION: Shared VPS with multiple services (MongoDB, Redis, Waha, Hub, Auth, Bridge, Media, Web, Traefik) # Use 2 threads per worker (conservative to share resources with other services) # This prevents resource contention while maintaining good performance OMP_NUM_THREADS: 2 MKL_NUM_THREADS: 2 NUMEXPR_NUM_THREADS: 2 # ============================================ # KNOWLEDGE BASE LIMITS & VALIDATION # ============================================ KB_MAX_TEXT_SIZE_BYTES: 5242880 KB_MAX_NAME_LENGTH: 200 KB_MAX_DESCRIPTION_LENGTH: 1000 KB_MAX_CHUNKS_PER_KB: 10000 KB_MAX_TAGS: 20 KB_MAX_TAG_LENGTH: 50 volumes: - ia_logs:/app/logs labels: - "traefik.enable=true" - "traefik.http.routers.ia.rule=Host(`crm.tu-dominio.com`) && PathPrefix(`/ia`)" - "traefik.http.routers.ia.entrypoints=websecure" - "traefik.http.routers.ia.tls.certresolver=letsencrypt" - "traefik.http.routers.ia.service=ia" - "traefik.http.routers.ia.middlewares=ia-stripprefix" - "traefik.http.services.ia.loadbalancer.server.port=3006" - "traefik.http.services.ia.loadbalancer.passHostHeader=true" - "traefik.http.middlewares.ia-stripprefix.stripprefix.prefixes=/ia" networks: - notibuzz-network depends_on: mongodb: condition: service_healthy redis: condition: service_healthy # noti-sender-web - Aplicación web frontend (Nuxt) noti-sender-web: image: hilosdf/noti-sender-web:bf03d0f #c91c491 container_name: noti-sender-web restart: unless-stopped expose: - "8080" #env_file: # - noti-sender-web/.env environment: # ============================================ # CONFIGURACIÓN PARA DOCKER COMPOSE + TRAEFIK # ============================================ # ============================================ # API Configuration # ============================================ NUXT_PUBLIC_API_URL: https://crm.tu-dominio.com/api API_BASE_URL: https://crm.tu-dominio.com/api # ============================================ # Hub Configuration # ============================================ NUXT_PUBLIC_HUB_URL: https://crm.tu-dominio.com/api HUB_BASE_URL: https://crm.tu-dominio.com/api # ============================================ # WebSocket Configuration # ============================================ NUXT_PUBLIC_WS_URL: wss://crm.tu-dominio.com/ws # ============================================ # Media Service Configuration # ============================================ NUXT_PUBLIC_MEDIA_SERVICE_URL: https://crm.tu-dominio.com/media # ============================================ # Bridge API Configuration # ============================================ NUXT_PUBLIC_BRIDGE_BASE_URL: https://crm.tu-dominio.com/bridge # ============================================ # WAHA API Configuration # ============================================ NUXT_PUBLIC_WAHA_BASE_URL: https://crm.tu-dominio.com/waha # ============================================ # API Key # ============================================ NUXT_PUBLIC_API_KEY: "" WAHA_API_KEY: "" # ============================================ # Server Configuration # ============================================ PORT: 8080 NODE_ENV: production # ============================================ # AUTH API Configuration # ============================================ AUTH_SERVICE_URL: https://crm.tu-dominio.com/auth # ============================================ # LOGIN TUTORIAL URL # ============================================ NUXT_PUBLIC_LOGIN_TUTORIAL_URL: https://notibuzz.com/help/iniciar-sesion-qr # ============================================ # APP ENV # ============================================ NUXT_PUBLIC_APP_ENV: PROD NUXT_PUBLIC_SHOW_DEVELOPERS: "true" NUXT_PUBLIC_SHOW_BULK_WHATSAPP: "false" NUXT_PUBLIC_LINK_DEVICE_MAX_DEVICES: "5" labels: - "traefik.enable=true" # Router para redirigir / a /dashboard/ (solo cuando el path es exactamente /) - Mayor prioridad - "traefik.http.routers.web-root.rule=(Host(`crm.tu-dominio.com`) || Host(`www.crm.tu-dominio.com`)) && Path(`/`)" - "traefik.http.routers.web-root.entrypoints=websecure" - "traefik.http.routers.web-root.tls.certresolver=letsencrypt" - "traefik.http.routers.web-root.priority=10" - "traefik.http.routers.web-root.middlewares=redirect-to-dashboard" - "traefik.http.middlewares.redirect-to-dashboard.redirectregex.regex=^(https?://[^/]+)/?$$" - "traefik.http.middlewares.redirect-to-dashboard.redirectregex.replacement=$$1/dashboard/" - "traefik.http.middlewares.redirect-to-dashboard.redirectregex.permanent=true" # Router principal - maneja /dashboard y todas las rutas de la app (excluyendo API, WS, etc.) - "traefik.http.routers.web.rule=(Host(`crm.tu-dominio.com`) || Host(`www.crm.tu-dominio.com`)) && (!PathPrefix(`/api`) && !PathPrefix(`/notibuzz`) && !PathPrefix(`/ws`) && !PathPrefix(`/media`) && !PathPrefix(`/files`) && !PathPrefix(`/bridge`) && !PathPrefix(`/waha`) && !PathPrefix(`/auth`) && !PathPrefix(`/ia`) && !PathPrefix(`/containers`) && !PathPrefix(`/node-exporter`))" - "traefik.http.routers.web.entrypoints=websecure" - "traefik.http.routers.web.tls.certresolver=letsencrypt" - "traefik.http.routers.web.service=web" - "traefik.http.routers.web.priority=1" - "traefik.http.services.web.loadbalancer.server.port=8080" - "traefik.http.services.web.loadbalancer.passHostHeader=true" # Router para redirección HTTP -> HTTPS - "traefik.http.routers.web-redirect.rule=Host(`crm.tu-dominio.com`) || Host(`www.crm.tu-dominio.com`)" - "traefik.http.routers.web-redirect.entrypoints=web" - "traefik.http.routers.web-redirect.middlewares=redirect-to-https" - "traefik.http.middlewares.redirect-to-https.redirectscheme.scheme=https" - "traefik.http.middlewares.redirect-to-https.redirectscheme.permanent=true" networks: - notibuzz-network # 🌐 Traefik - Reverse Proxy y Load Balancer traefik: image: traefik:v3.6.1 container_name: notibuzz-traefik restart: unless-stopped command: # API Dashboard (solo para desarrollo, deshabilitar en producción) - "--api.insecure=true" - "--api.dashboard=true" # Docker Provider - "--providers.docker=true" - "--providers.docker.exposedbydefault=false" - "--providers.docker.network=notibuzz-network" # Entrypoints - "--entrypoints.web.address=:80" - "--entrypoints.websecure.address=:443" # Let's Encrypt Configuration (HTTP Challenge - más confiable) - "--certificatesresolvers.letsencrypt.acme.httpchallenge=true" - "--certificatesresolvers.letsencrypt.acme.httpchallenge.entrypoint=web" - "--certificatesresolvers.letsencrypt.acme.email=tu-email@ejemplo.com" - "--certificatesresolvers.letsencrypt.acme.storage=/letsencrypt/acme.json" - "--certificatesresolvers.letsencrypt.acme.caserver=https://acme-v02.api.letsencrypt.org/directory" # SSL/TLS Configuration - "--entrypoints.websecure.http.tls=true" - "--entrypoints.websecure.http.tls.certresolver=letsencrypt" # Logging - "--log.level=INFO" - "--accesslog=true" - "--accesslog.format=json" # Global Options - "--global.sendAnonymousUsage=false" # Plugin para reescribir Location en redirects (cAdvisor bajo /containers) - "--experimental.plugins.redirectlocation.modulename=github.com/SchmitzDan/traefik-plugin-redirect-location" - "--experimental.plugins.redirectlocation.version=v0.0.2" ports: - "80:80" - "443:443" - "8080:8080" # Dashboard de Traefik (solo acceso local) volumes: - /var/run/docker.sock:/var/run/docker.sock:ro - traefik_letsencrypt:/letsencrypt networks: - notibuzz-network depends_on: - noti-sender-web - noti-sender-media - noti-sender-hub - noti-sender-ia labels: # Habilitar Traefik para que pueda definir middlewares - "traefik.enable=true" # Redirigir www -> apex para evitar URLs con www - "traefik.http.routers.www-redirect.rule=Host(`www.crm.tu-dominio.com`) && PathPrefix(`/`)" - "traefik.http.routers.www-redirect.entrypoints=web,websecure" - "traefik.http.routers.www-redirect.priority=100" - "traefik.http.routers.www-redirect.tls=true" - "traefik.http.routers.www-redirect.tls.certresolver=letsencrypt" - "traefik.http.routers.www-redirect.middlewares=redirect-to-apex" - "traefik.http.middlewares.redirect-to-apex.redirectregex.regex=^https?://www\\.(.+)" - "traefik.http.middlewares.redirect-to-apex.redirectregex.replacement=https://$${1}" - "traefik.http.middlewares.redirect-to-apex.redirectregex.permanent=true" # Middleware de seguridad global - "traefik.http.middlewares.security-headers.headers.sslredirect=true" - "traefik.http.middlewares.security-headers.headers.stsSeconds=31536000" - "traefik.http.middlewares.security-headers.headers.stsIncludeSubdomains=true" - "traefik.http.middlewares.security-headers.headers.stsPreload=true" - "traefik.http.middlewares.security-headers.headers.forceSTSHeader=true" - "traefik.http.middlewares.security-headers.headers.frameDeny=true" - "traefik.http.middlewares.security-headers.headers.browserXssFilter=true" - "traefik.http.middlewares.security-headers.headers.contentTypeNosniff=true" - "traefik.http.middlewares.security-headers.headers.referrerPolicy=strict-origin-when-cross-origin" volumes: mongodb_data: driver: local mongodb_config: driver: local redis_data: driver: local waha_sessions: driver: local waha_files: driver: local hub_uploads: driver: local hub_logs: driver: local auth_logs: driver: local ia_logs: driver: local media_files: driver: local traefik_letsencrypt: driver: local networks: notibuzz-network: external: true name: notibuzz-network - 5
Levanta el stack
Traefik enruta /api, /ws, /auth, /waha, /bridge, /media e /ia. El frontend responde en /dashboard.
Si falla «network notibuzz-network not found», ejecuta el paso 3.
Verifica que todos los contenedores aparezcan en docker network inspect notibuzz-network.
docker compose pulldocker compose up -ddocker compose psdocker network inspect notibuzz-network --format '{{range .Containers}}{{.Name}} {{end}}' - 6
Verifica, crea admin y conecta WhatsApp
Tras levantar el stack: crea el usuario administrador en noti-sender-auth, inicializa planes (opcional si comercializas) y asigna una suscripción. Luego inicia sesión en el CRM y vincula WhatsApp.
createUser.js: argumentos → USERNAME PASSWORD EMAIL (admin es Super Admin por defecto).
El sistema fuerza cambio de contraseña en el primer login (mustChangePassword).
setupPlansAndSubscription.js: planes DEVELOPER, STARTER, PRO, BUSINESS — útil si vendes el CRM como servicio.
Precios mensual/anual de la tabla son estándar preconfigurados con --init-plans-only; para cambiar precio u otros valores, edita la colección plans en MongoDB.
Los tokens IA son cupo mensual vía DeepSeek (DEEPSEEK_API_KEY en noti-sender-ia) y se renuevan automáticamente cada mes.
Self-hosted sin venta: crea admin + --init-plans-only + asigna BUSINESS o PRO a tu client-id.
El usuario admin debe existir antes de asignar suscripción (--username admin).
billing-period: monthly (mensual) o annual (anual, precio con descuento).
URL típica: https://TU_DOMINIO/dashboard/
Certificado HTTPS: unos minutos tras DNS correcto.
Revisa healthchecks de MongoDB y Redis antes de escalar tráfico.
# 1 — Crear usuario administrador (Super Admin si username=admin)docker exec -it noti-sender-auth node scripts/createUser.js admin TU_PASSWORD admin@tu-dominio.com# 2 — Inicializar planes en MongoDB (siempre recomendado)docker exec -it noti-sender-auth node scripts/setupPlansAndSubscription.js --init-plans-only# 3 — Asignar plan y suscripción (comercial o self-hosted con límites)docker exec -it noti-sender-auth node scripts/setupPlansAndSubscription.js \--plan STARTER \--client-id client-001 \--client-name "Mi Empresa" \--username admin \--billing-period monthly# Otros planes: --plan DEVELOPER | PRO | BUSINESS# Facturación anual: --billing-period annual# 4 — Verificar logs y conectar WhatsApp en el CRMdocker compose logs -f noti-sender-wahadocker compose logs -f noti-sender-hub# Accede al CRM y escanea QR de WhatsApp# Producción: https://TU_DOMINIO/dashboard/createUser.js — argumentos
# node scripts/createUser.js USERNAME PASSWORD EMAIL [super-admin] USERNAME Nombre de login (admin → Super Admin automático) PASSWORD Contraseña inicial (cambio obligatorio en 1er login) EMAIL Email del administrador super-admin Opcional: true o --super-admin para otros usuarios Ejemplo: node scripts/createUser.js admin MiClaveSegura admin@tu-dominio.comsetupPlansAndSubscription.js — parámetros y ejemplos
# setupPlansAndSubscription.js — parámetros Opciones: --plan PLAN_NAME DEVELOPER | STARTER | PRO | BUSINESS --client-id CLIENT_ID ID único del cliente (ej. client-001) --client-name NAME Nombre comercial del cliente --username USERNAME Usuario admin ya creado (ej. admin) --billing-period PERIOD monthly | annual --init-plans-only Solo crea/actualiza planes en MongoDB (sin suscripción) Orden: createUser.js → --init-plans-only → asignar plan con --plan Ejemplo DEVELOPER (API / dev): --plan DEVELOPER --client-id dev-001 --client-name "Dev API" --username admin --billing-period monthly Ejemplo STARTER (equipo pequeño): --plan STARTER --client-id client-001 --client-name "Mi Empresa" --username admin --billing-period monthly Ejemplo PRO (Copilot + más capacidad): --plan PRO --client-id client-002 --client-name "Acme Corp" --username admin --billing-period annual Ejemplo BUSINESS (máxima capacidad): --plan BUSINESS --client-id client-003 --client-name "Enterprise SA" --username admin --billing-period annual Self-hosted sin venta: asigna BUSINESS o PRO a tu propio client-id. Comercial / SaaS: un client-id distinto por cada cliente. Nota: los precios en MongoDB son estándar preconfigurados. Para cambiar precio u otros límites, edita el documento en la colección plans. Los tokens IA limitan el consumo de DeepSeek en Chatbot/Copilot y se renuevan automáticamente cada mes.
Checklist antes de go-live
- Red Docker notibuzz-network creada (bridge externa)
- Contraseñas TU_* y JWT generados con openssl — nunca valores de ejemplo
- Usuario admin creado con createUser.js (Super Admin)
- Planes inicializados (--init-plans-only) y suscripción asignada si comercializas
- MongoDB y Redis no expuestos a internet (solo red interna Docker)
- noti-sender-media: REDIS_HOST=redis y MEDIA_PUBLIC_BASE_URL=https://TU_DOMINIO/media
- Traefik: --providers.docker.network=notibuzz-network alineado con la red creada
- Backups periódicos del volumen mongodb_data
- Actualización de imágenes con docker compose pull && docker compose up -d
- Monitoreo de disco (archivos multimedia en volumen compartido WAHA/media)
- DEEPSEEK_API_KEY solo si usas IA; rotar claves periódicamente
- Firewall: solo 22 (SSH), 80 y 443 públicos
Solución de problemas frecuentes
- network notibuzz-network not foundCrea la red antes del compose: docker network create notibuzz-network
- Traefik no enruta servicios / 404 en rutasVerifica --providers.docker.network=notibuzz-network coincide con docker network ls. Todos los contenedores deben estar en esa red.
- noti-sender-media: ECONNREFUSED 127.0.0.1:6379REDIS_HOST=redis en noti-sender-media; misma REDIS_PASSWORD que el servicio redis.
- WAHA falla al enviar imagen (file.url localhost)MEDIA_PUBLIC_BASE_URL=https://TU_DOMINIO/media en noti-sender-media.
- Usuario admin no encontrado al asignar planEjecuta createUser.js antes de setupPlansAndSubscription.js. El --username debe coincidir.
- Plan inválido en setupPlansAndSubscriptionUsa --plan DEVELOPER, STARTER, PRO o BUSINESS (mayúsculas). Ejecuta --init-plans-only primero.

