# RF-MGN-017-001: Conexión WhatsApp Business API **Módulo:** MGN-017 - WhatsApp Business Integration **Prioridad:** P1 **Story Points:** 13 **Estado:** Definido **Fecha:** 2025-12-05 ## Descripción El sistema debe permitir a los tenants conectar una o más cuentas de WhatsApp Business para habilitar la mensajería automatizada y manual con sus clientes. La conexión se realiza a través de la WhatsApp Business Cloud API de Meta. ## Actores - **Actor Principal:** Tenant Admin - **Actores Secundarios:** - Meta Business Suite (autorización) - WhatsApp Business API (mensajería) - Sistema (webhooks) ## Precondiciones 1. Tenant debe tener suscripción con feature `whatsapp_enabled` 2. Tenant debe tener cuenta de Meta Business verificada 3. Número de WhatsApp Business disponible (no personal) 4. Proceso de verificación de negocio completado con Meta ## Flujo Principal - Conectar Cuenta 1. Tenant Admin accede a "Configuración > Integraciones > WhatsApp" 2. Sistema muestra cuentas conectadas (si existen) 3. Admin selecciona "Conectar nueva cuenta" 4. Sistema muestra requisitos previos 5. Admin confirma que cumple requisitos 6. Sistema redirige a Meta Business Login (OAuth) 7. Admin autoriza permisos requeridos: - `whatsapp_business_management` - `whatsapp_business_messaging` 8. Meta retorna access token y WABA ID 9. Sistema solicita selección de número de teléfono 10. Admin selecciona número a usar 11. Sistema configura webhook URL 12. Sistema verifica conexión enviando mensaje de prueba 13. Sistema confirma configuración exitosa ## Flujo Alternativo - Múltiples Números 1. Admin tiene múltiples números en su WABA 2. Sistema lista todos los números disponibles 3. Admin puede conectar varios números (según límite del plan) 4. Cada número se configura independientemente 5. Sistema asigna alias a cada número ("Ventas", "Soporte") ## Requisitos de Meta Business ### Verificación de Negocio - Documentos legales de la empresa - Verificación de dominio web - Proceso toma 2-5 días hábiles ### Permisos OAuth Requeridos ``` whatsapp_business_management - Gestionar cuenta WABA whatsapp_business_messaging - Enviar/recibir mensajes business_management - Acceso a Business Suite ``` ### Límites Iniciales | Tier | Conversaciones/día | Cómo alcanzar | |------|-------------------|---------------| | Tier 0 | 250 | Cuenta nueva | | Tier 1 | 1,000 | Número verificado | | Tier 2 | 10,000 | Buen quality rating | | Tier 3 | 100,000 | Alto volumen sostenido | | Tier 4 | Ilimitado | Enterprise | ## Reglas de Negocio - **RN-1:** Un número de WhatsApp solo puede estar conectado a un tenant - **RN-2:** Access tokens se refrescan automáticamente - **RN-3:** Webhook URL debe ser HTTPS con certificado válido - **RN-4:** Cada mensaje debe tener opt-in del destinatario - **RN-5:** Quality rating debe mantenerse en "Green" o "Yellow" - **RN-6:** Números desconectados mantienen historial por 90 días ## Criterios de Aceptación - [ ] Admin puede iniciar flujo de conexión OAuth - [ ] Sistema obtiene y almacena tokens de forma segura - [ ] Admin puede seleccionar número de múltiples disponibles - [ ] Webhook se configura automáticamente - [ ] Sistema valida conexión con mensaje de prueba - [ ] Dashboard muestra estado de cada número conectado - [ ] Admin puede desconectar número - [ ] Sistema notifica problemas de quality rating - [ ] Logs de conexión/desconexión disponibles ## Entidades Involucradas ### messaging.whatsapp_accounts ```sql CREATE TABLE messaging.whatsapp_accounts ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), tenant_id UUID NOT NULL REFERENCES core_tenants.tenants(id), -- Meta Business waba_id VARCHAR(50) NOT NULL, -- WhatsApp Business Account ID phone_number_id VARCHAR(50) NOT NULL, phone_number VARCHAR(20) NOT NULL, -- +521234567890 display_name VARCHAR(100), -- Tokens (encriptados) access_token_encrypted BYTEA NOT NULL, token_expires_at TIMESTAMPTZ, -- Estado status VARCHAR(20) NOT NULL DEFAULT 'pending', quality_rating VARCHAR(20), -- GREEN, YELLOW, RED messaging_limit INT, -- Tier actual -- Webhook webhook_verify_token VARCHAR(100), -- Configuración config JSONB DEFAULT '{}', -- { -- "alias": "Ventas", -- "auto_reply_enabled": true, -- "business_hours": {...}, -- "welcome_message_template": "welcome_v1" -- } -- Timestamps connected_at TIMESTAMPTZ, last_message_at TIMESTAMPTZ, created_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP, updated_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP, CONSTRAINT uq_phone_number UNIQUE (phone_number), CONSTRAINT chk_status CHECK (status IN ('pending', 'active', 'suspended', 'disconnected')) ); CREATE INDEX idx_wa_accounts_tenant ON messaging.whatsapp_accounts(tenant_id); CREATE INDEX idx_wa_accounts_phone ON messaging.whatsapp_accounts(phone_number); ``` ### messaging.whatsapp_webhook_logs ```sql CREATE TABLE messaging.whatsapp_webhook_logs ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), account_id UUID REFERENCES messaging.whatsapp_accounts(id), event_type VARCHAR(50) NOT NULL, payload JSONB NOT NULL, processed BOOLEAN DEFAULT false, processed_at TIMESTAMPTZ, error_message TEXT, created_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP ); CREATE INDEX idx_wa_webhooks_account ON messaging.whatsapp_webhook_logs(account_id); CREATE INDEX idx_wa_webhooks_type ON messaging.whatsapp_webhook_logs(event_type); ``` ## API WhatsApp Business Cloud ### Endpoint Base ``` https://graph.facebook.com/v18.0/ ``` ### Verificar Webhook (GET) ```typescript // GET /webhooks/whatsapp?hub.mode=subscribe&hub.verify_token=xxx&hub.challenge=yyy app.get('/webhooks/whatsapp', (req, res) => { const mode = req.query['hub.mode']; const token = req.query['hub.verify_token']; const challenge = req.query['hub.challenge']; if (mode === 'subscribe' && token === VERIFY_TOKEN) { res.status(200).send(challenge); } else { res.sendStatus(403); } }); ``` ### Recibir Webhook (POST) ```typescript // POST /webhooks/whatsapp interface WhatsAppWebhook { object: 'whatsapp_business_account'; entry: [{ id: string; // WABA ID changes: [{ value: { messaging_product: 'whatsapp'; metadata: { display_phone_number: string; phone_number_id: string; }; contacts?: [{ profile: { name: string }; wa_id: string; }]; messages?: [{ from: string; id: string; timestamp: string; type: 'text' | 'image' | 'document' | 'button' | 'interactive'; text?: { body: string }; }]; statuses?: [{ id: string; status: 'sent' | 'delivered' | 'read' | 'failed'; timestamp: string; recipient_id: string; }]; }; field: 'messages'; }]; }]; } ``` ### Enviar Mensaje de Texto ```typescript // POST /{phone_number_id}/messages const response = await fetch( `https://graph.facebook.com/v18.0/${phoneNumberId}/messages`, { method: 'POST', headers: { 'Authorization': `Bearer ${accessToken}`, 'Content-Type': 'application/json' }, body: JSON.stringify({ messaging_product: 'whatsapp', recipient_type: 'individual', to: '521234567890', type: 'text', text: { preview_url: false, body: 'Hola, este es un mensaje de prueba' } }) } ); ``` ### Enviar Template ```typescript // POST /{phone_number_id}/messages { "messaging_product": "whatsapp", "to": "521234567890", "type": "template", "template": { "name": "order_confirmation", "language": { "code": "es_MX" }, "components": [ { "type": "body", "parameters": [ { "type": "text", "text": "Juan" }, { "type": "text", "text": "ORD-12345" }, { "type": "text", "text": "$1,500.00" } ] } ] } } ``` ## Dashboard de Cuenta ```typescript interface WhatsAppAccountDashboard { account: { phone_number: string; display_name: string; status: string; quality_rating: 'GREEN' | 'YELLOW' | 'RED'; messaging_limit: number; }; stats_today: { messages_sent: number; messages_received: number; conversations_opened: number; cost_estimate: number; }; stats_month: { total_conversations: number; by_category: Record; total_cost: number; }; } ``` ## Manejo de Quality Rating | Rating | Significado | Acción | |--------|-------------|--------| | GREEN | Buena calidad | Mantener | | YELLOW | Calidad media | Revisar contenido | | RED | Mala calidad | Riesgo de suspensión | ### Factores que Afectan Quality - Tasa de bloqueos por usuarios - Tasa de reportes de spam - Tasa de respuesta a mensajes - Contenido de templates rechazados ## Seguridad 1. **Tokens:** Encriptados con AES-256 2. **Webhook:** Validar firma X-Hub-Signature-256 3. **Rate Limiting:** Respetar límites de Meta 4. **Logs:** No almacenar contenido sensible en logs 5. **Opt-in:** Verificar consentimiento antes de enviar ## Validación de Firma Webhook ```typescript import crypto from 'crypto'; function verifyWebhookSignature( payload: string, signature: string, appSecret: string ): boolean { const expectedSignature = crypto .createHmac('sha256', appSecret) .update(payload) .digest('hex'); return `sha256=${expectedSignature}` === signature; } ``` ## Referencias - [WhatsApp Business Platform](https://developers.facebook.com/docs/whatsapp) - [Cloud API Reference](https://developers.facebook.com/docs/whatsapp/cloud-api/reference) - [Webhooks](https://developers.facebook.com/docs/whatsapp/cloud-api/webhooks) - [Message Templates](https://developers.facebook.com/docs/whatsapp/message-templates) ## Dependencias - **RF Requeridos:** Ninguno (módulo base) - **Bloqueante para:** RF-002 (Templates), RF-003 (Notificaciones), RF-004 (Inbox)