# Arquitectura Multi-Tenant de Integraciones ## Resumen MiChangarrito implementa una arquitectura multi-tenant donde cada cliente (tenant) puede configurar sus propias credenciales para integraciones externas (WhatsApp Business, proveedores LLM, pasarelas de pago), con fallback automático a las credenciales de plataforma si el tenant no tiene las suyas. ## Diagrama de Flujo ``` ┌─────────────────┐ ┌──────────────────────┐ ┌─────────────────┐ │ WhatsApp │ │ whatsapp-service │ │ Backend │ │ Webhook │────▶│ │────▶│ (Internal) │ └─────────────────┘ │ - Resolver tenant │ │ │ │ - Get credentials │ │ GET /internal/ │ │ - Send via correct │ │ integrations/ │ │ WhatsApp account │ └─────────────────┘ └──────────────────────┘ │ │ │ ▼ ▼ ┌──────────────────────┐ ┌─────────────────┐ │ Meta WhatsApp API │ │ PostgreSQL │ │ (Tenant o Platform)│ │ tenant_ │ └──────────────────────┘ │ integration_ │ │ credentials │ └─────────────────┘ ``` ## Componentes Principales ### 1. Backend - Módulo de Integraciones **Ubicación:** `apps/backend/src/modules/integrations/` #### Entidades - **TenantIntegrationCredential**: Almacena credenciales por tenant e integración - **TenantWhatsAppNumber**: Mapea phoneNumberId → tenantId para webhooks #### Servicios - **TenantIntegrationsService**: - `getWhatsAppCredentials(tenantId)` - Con fallback a plataforma - `getLLMConfig(tenantId)` - Con fallback a plataforma - `resolveTenantFromPhoneNumberId(phoneNumberId)` - Para webhooks #### Controladores - **IntegrationsController**: API REST para tenants (protegido con JWT) - `GET /integrations/status` - Estado de todas las integraciones - `PUT /integrations/whatsapp` - Configurar WhatsApp propio - `PUT /integrations/llm` - Configurar LLM propio - **InternalIntegrationsController**: API interna para whatsapp-service - `GET /internal/integrations/:tenantId/whatsapp` - `GET /internal/integrations/:tenantId/llm` - `GET /internal/integrations/resolve-tenant/:phoneNumberId` ### 2. WhatsApp Service **Ubicación:** `apps/whatsapp-service/src/` #### Servicios Refactorizados - **CredentialsProviderService** (`common/`): - Cache de credenciales con TTL de 5 minutos - Consulta al backend y cachea resultados - Fallback a variables de entorno - **WhatsAppService** (`whatsapp/`): - Todos los métodos aceptan `tenantId?: string` - Cache de clientes axios por tenant - Usa credenciales correctas automáticamente - **LlmService** (`llm/`): - Obtiene config LLM por tenant - Soporta múltiples proveedores (OpenAI, OpenRouter, etc.) - System prompts personalizados por tenant - **WebhookService** (`webhook/`): - Resuelve tenant desde `metadata.phone_number_id` - Pasa `tenantId` en todo el flujo de conversación ## Flujo de un Mensaje Entrante 1. **Meta envía webhook** con `metadata.phone_number_id` 2. **WebhookController** extrae phoneNumberId del payload 3. **WebhookService.processIncomingMessage()** recibe phoneNumberId 4. **CredentialsProviderService.resolveTenantFromPhoneNumberId()** - Consulta backend (`/internal/integrations/resolve-tenant/:id`) - Retorna `tenantId` o `null` (plataforma) 5. **Contexto de conversación** incluye tenantId 6. **WhatsAppService.sendTextMessage(to, text, tenantId)** - Obtiene credenciales para ese tenant (o plataforma) - Envía mensaje con las credenciales correctas 7. **LlmService.processMessage(text, context)** - Obtiene config LLM para el tenant - Usa API key y modelo del tenant (o plataforma) ## Configuración de Variables de Entorno ### Backend (.env) ```bash # Credenciales de plataforma (fallback) WHATSAPP_ACCESS_TOKEN=EAAxxxxxxx WHATSAPP_PHONE_NUMBER_ID=123456789 WHATSAPP_BUSINESS_ACCOUNT_ID=987654321 WHATSAPP_VERIFY_TOKEN=mi_token_secreto # LLM de plataforma OPENAI_API_KEY=sk-xxxxxxx LLM_PROVIDER=openai LLM_MODEL=gpt-4o-mini # API interna INTERNAL_API_KEY=clave_secreta_para_servicios_internos ``` ### WhatsApp Service (.env) ```bash # URL del backend BACKEND_URL=http://localhost:3141/api/v1 # API key para llamadas internas INTERNAL_API_KEY=clave_secreta_para_servicios_internos # Credenciales de plataforma (fallback) WHATSAPP_ACCESS_TOKEN=EAAxxxxxxx WHATSAPP_PHONE_NUMBER_ID=123456789 OPENAI_API_KEY=sk-xxxxxxx ``` ## API REST - Configuración de Integraciones ### Obtener Estado de Integraciones ```http GET /api/v1/integrations/status Authorization: Bearer ``` **Respuesta:** ```json { "whatsapp": { "configured": false, "usesPlatformNumber": true, "isVerified": false }, "llm": { "configured": false, "usesPlatformDefault": true, "provider": "openai", "model": "gpt-4o-mini" }, "payments": { "stripe": { "configured": false }, "mercadopago": { "configured": false }, "clip": { "configured": false } } } ``` ### Configurar WhatsApp Propio ```http PUT /api/v1/integrations/whatsapp Authorization: Bearer Content-Type: application/json { "credentials": { "accessToken": "EAAxxxxx", "phoneNumberId": "111222333", "businessAccountId": "444555666" }, "phoneNumber": "+525512345678", "displayName": "Mi Tiendita" } ``` ### Configurar LLM Propio ```http PUT /api/v1/integrations/llm Authorization: Bearer Content-Type: application/json { "provider": "openrouter", "credentials": { "apiKey": "sk-or-xxxxx" }, "config": { "model": "anthropic/claude-3-haiku", "maxTokens": 1500, "temperature": 0.8, "systemPrompt": "Eres el asistente de Mi Tiendita..." } } ``` ## Proveedores Soportados ### WhatsApp - **Meta Business** (único proveedor) ### LLM - **OpenAI** - gpt-4o, gpt-4o-mini, etc. - **OpenRouter** - Acceso a múltiples modelos - **Anthropic** - Claude 3 - **Azure OpenAI** - Despliegues enterprise - **Ollama** - Modelos locales ### Pagos (Futuro) - Stripe - MercadoPago - Clip ## Esquema de Base de Datos ```sql -- Tabla de credenciales de integración por tenant CREATE TABLE tenant_integration_credentials ( id UUID PRIMARY KEY, tenant_id UUID NOT NULL REFERENCES tenants(id), integration_type VARCHAR(50) NOT NULL, -- whatsapp, llm, stripe, etc. provider VARCHAR(50) NOT NULL, -- meta, openai, openrouter, etc. credentials JSONB NOT NULL DEFAULT '{}', -- Datos sensibles encriptados config JSONB DEFAULT '{}', -- Configuración no sensible is_active BOOLEAN DEFAULT true, is_verified BOOLEAN DEFAULT false, UNIQUE(tenant_id, integration_type, provider) ); -- Mapeo de números WhatsApp a tenants CREATE TABLE tenant_whatsapp_numbers ( id UUID PRIMARY KEY, tenant_id UUID NOT NULL, phone_number_id VARCHAR(50) UNIQUE NOT NULL, phone_number VARCHAR(20), display_name VARCHAR(100), is_active BOOLEAN DEFAULT true ); ``` ## Seguridad 1. **Credenciales encriptadas** en JSONB (recomendación: usar pg_crypto) 2. **API Interna protegida** con X-Internal-Key header 3. **JWT obligatorio** para endpoints de configuración 4. **No se exponen API keys** en respuestas al frontend 5. **Cache de credenciales** para reducir queries a BD ## Consideraciones de Escalabilidad - Cache de credenciales con TTL de 5 minutos - Cache de clientes axios por tenant - Invalidación de cache al actualizar credenciales - Conexión separada de BD para servicio de WhatsApp (futuro)