michangarrito/backups/docs-backup-2026-01-10/docs/90-transversal/ARQUITECTURA-MULTI-TENANT-INTEGRACIONES.md
rckrdmrd 928eb795e6 [SIMCO-V38] feat: Actualizar a SIMCO v3.8.0 + cambios apps
- HERENCIA-SIMCO.md actualizado con directivas v3.7 y v3.8
- Cambios en backend y frontend

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-10 08:53:05 -06:00

262 lines
8.3 KiB
Markdown

# 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 <jwt_token>
```
**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 <jwt_token>
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 <jwt_token>
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)