- 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>
262 lines
8.3 KiB
Markdown
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)
|