erp-core/docs/04-modelado/requerimientos-funcionales/mgn-017/RF-MGN-017-001-conexion-whatsapp-business.md

9.9 KiB

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

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

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)

// 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)

// 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

// 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

// 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

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<string, number>;
    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

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

Dependencias

  • RF Requeridos: Ninguno (módulo base)
  • Bloqueante para: RF-002 (Templates), RF-003 (Notificaciones), RF-004 (Inbox)