Módulo Connection
src/app/modules/connection/
Una conexión representa un canal de comunicación de un tenant: chat web (widget), WhatsApp por QR, o cuentas de Meta (Instagram / Messenger). Cada ticket nace asociado a una conexión (ticket.connectionId), y esa conexión decide por qué dispatcher saliente se entrega la respuesta (ver Message).
Tipos de canal y estado
src/app/modules/connection/connection.constants.ts
channelType | Valor | Creable por API (POST /connections/create) | Configuración |
|---|---|---|---|
WEB | web | Sí | webConfig |
WHATSAPP_QR | whatsapp-qr | Sí | whatsappConfig (ver WhatsApp) |
WHATSAPP_CLOUD | whatsapp-cloud | No (definido, sin implementación) | — |
FACEBOOK | facebook | No | metaConfig |
INSTAGRAM | instagram | No: se crea por OAuth (ver Meta) | metaConfig |
ConnectionService.create rechaza (400 connection.channel_not_available) cualquier tipo fuera de web y whatsapp-qr (CREATABLE_CHANNEL_TYPES).
status: connected (por defecto), disconnected, pending. Una conexión de WhatsApp QR nace pending; una de Instagram nace pending y pasa a connected al guardar metaConfig; un error de autenticación de Meta la deja en disconnected.
Modelo — Connection
Colección connections.
| Campo | Tipo | Descripción |
|---|---|---|
tenantId | ObjectId → Tenant | Tenant propietario (indexado) |
name | string | Nombre. Por defecto Web Chat Connection / WhatsApp Connection si no se envía |
channelType | string | Ver tabla anterior. Inmutable tras crear |
status | string | connected | disconnected | pending |
token | string (UUID, único) | Token público de embebido del widget. Se regenera con PATCH regenerate-token/:id |
greetingMessage / farewellMessage | string | Mensajes automáticos de saludo/despedida (ver System Message) |
departmentIds | ObjectId[] → Department | Departamentos que reciben los tickets nuevos |
reopenToleranceMinutes | number | Por defecto 15 |
returnToLastAgent | boolean | Por defecto true |
webConfig | subdoc | null | allowedDomains: string[] (mín. 1, whitelist de orígenes del widget) y requiresAuth: boolean (por defecto true) |
whatsappConfig | subdoc | null | sessionState, phoneJid, lastQrAt, lastConnectedAt (lo escribe solo WhatsappSessionManager) |
metaConfig | subdoc | null | accountId, accountName, accessToken (cifrado, select: false), expiresAt, authErrorAt |
Índice único parcial { channelType, 'metaConfig.accountId' } (solo si accountId es string): una cuenta de Meta pertenece a una única conexión.
metaConfig.accessToken
Se guarda como EncryptedEnvelope { iv, tag, ciphertext } (AES-256-GCM con META_TOKEN_ENCRYPTION_KEY, ver Crypto). El campo tiene select: false: las lecturas normales no lo devuelven. Los métodos del repositorio que lo necesitan lo piden con .select('+metaConfig.accessToken') (findByIdWithMetaToken, findByMetaAccount, findMetaConnectionsExpiringBefore). readMetaAccessToken(connection) (helpers/meta-token.helper.ts) lo descifra y falla si el token no fue cargado.
Endpoints
Base: /api/v1/connections. Todos con @Auth() + PermissionsGuard y el permiso indicado.
| Método | Ruta | Permiso | Descripción |
|---|---|---|---|
POST | /connections/create | CONNECTION_CREATE | Crea una conexión web o whatsapp-qr |
GET | /connections/get-all | CONNECTION_READ | Lista (paginada si se envían page y limit); filtros name, channelType |
GET | /connections/get-one/:id | CONNECTION_READ | Obtiene una conexión |
PATCH | /connections/update/:id | CONNECTION_UPDATE | Actualiza campos editables |
PATCH | /connections/regenerate-token/:id | CONNECTION_UPDATE | Emite un token UUID nuevo (el anterior deja de resolver) |
DELETE | /connections/remove/:id | CONNECTION_DELETE | Elimina la conexión |
Los endpoints de sesión de WhatsApp cuelgan de /connections/whatsapp/... (ver WhatsApp). Para alta de Instagram ver Meta.
DTOs
// CreateConnectionDto
{
name?: string // opcional, no vacío
channelType: ChannelType // IsIn(CHANNEL_TYPE_VALUES)
webConfig?: ConnectionWebConfigDto // OBLIGATORIO si channelType === 'web'
}
// ConnectionWebConfigDto
{
allowedDomains: string[] // ≥1, URL con protocolo (require_tld: false)
requiresAuth?: boolean // omitido → true
}
// UpdateConnectionDto (todo opcional)
{
name, greetingMessage, farewellMessage,
departmentIds: string[] // MongoIds; [] limpia los departamentos
reopenToleranceMinutes: number // entero ≥ 1
returnToLastAgent: boolean
webConfig: ConnectionWebConfigDto
}
channelType, token, whatsappConfig y metaConfig no son editables vía PATCH update.
Gotchas
ConnectionServiceextiendeEventEmittery emitecreatedsolo parawhatsapp-qr;WhatsappModulese suscribe e inicia la sesión. AsíConnectionModuleno importaWhatsappModule(evita ciclo).- Para el canal
webno hay dispatcher saliente registrado: la entrega es el propio emit por socket. requiresAuthpuede faltar en documentos antiguos; léalo como?? true.