UI de conexiones
src/app/features/connections/
connections/
├── connections.module.ts, connections-routing.module.ts, connections.constants.ts
├── pages/connections-page/ # breadcrumb + <connections-connections-table>
└── components/
├── connections-filter/ # nombre + canal
├── connections-table/ # grilla de tarjetas (nombre histórico "table")
├── connection-channel-modal/ # selector de canal
├── connection-web-modal/ # alta / edición / snippet del widget web
├── connection-whatsapp-modal/ # QR + formulario de atención
└── connection-instagram-modal/ # configuración tras el OAuth (nuevo)
La ruta connections (CONNECTION_READ) carga ConnectionsPageComponent, que solo muestra shared-breadcrumb y connections-connections-table. Para el backend de estos endpoints ver Connection.
ConnectionService
core/services/api/connection.service.ts. Base: environment.apiUrl + /connections.
| Método | Petición | Nota |
|---|---|---|
findConnectionsPaginated(page, limit, filter?) | GET /connections/get-all | Devuelve { results, total } |
findConnectionById(id) | GET /connections/get-one/:id | Nuevo; toma data.results[0] |
createConnection(data) | POST /connections/create | Solo web y whatsapp-qr (ver backend) |
updateConnection(id, data) | PATCH /connections/update/:id | Muestra toast de éxito |
regenerateToken(id) | PATCH /connections/regenerate-token/:id | Solo canal web |
removeConnection(id) | DELETE /connections/remove/:id | |
startWhatsappSession(id) | POST /connections/whatsapp/:id/start | Devuelve { sessionState, qr, phoneJid? } |
getWhatsappSessionStatus(id) | GET /connections/whatsapp/:id/status | |
logoutWhatsappSession(id) | DELETE /connections/whatsapp/:id/logout | |
authorizeInstagram({ connectionId? }) | POST /meta/oauth/instagram/authorize | Nuevo; devuelve data.url |
Modelo (connection.interface.ts)
type ConnectionChannelType =
'web' | 'whatsapp-qr' | 'whatsapp-cloud' | 'facebook' | 'instagram';
type ConnectionStatus = 'connected' | 'disconnected' | 'pending';
interface ConnectionMetaConfig { // nuevo
accountId: string;
accountName: string;
expiresAt?: string | null;
authErrorAt?: string | null;
}
Connection incluye webConfig, whatsappConfig y, desde esta rama, metaConfig. ConnectionUpdateForm admite name, greetingMessage, farewellMessage, departmentIds, reopenToleranceMinutes, returnToLastAgent y webConfig. El token de acceso de Meta nunca llega al frontend: metaConfig solo expone cuenta y fechas.
ConnectionsTableComponent
components/connections-table. Muestra las conexiones como tarjetas (connection-card) en una grilla responsiva (col-12 col-md-6 col-xl-4), con:
- Paginación incremental:
PAGE_SIZE = 12, botón "Cargar más" y contadorn / total. - Esqueleto de carga (
skeletonCards) en la primera carga y estado vacío. - Encabezado con degradado derivado del color del canal (
headerGradient), icono del canal, nombre y un identificador (identifierFor): teléfono (parawhatsapp-qr, derivado dephoneJid),@usuario(parainstagram, demetaConfig.accountName) o dominio / prefijo de token (paraweb). - Píldora de estado: para WhatsApp QR usa
whatsappConfig.sessionState(init,qr,connecting,connected,disconnected,logged_out, que el socket actualiza en vivo); para el resto,connection.status. - Menú de acciones (
ngbDropdown) y botón de acción principal, gobernados por permisos.
Acciones por canal
| Canal | Menú | Botón principal |
|---|---|---|
web | "Código de instalación" (snippet) UPDATE | "Editar" UPDATE |
whatsapp-qr | "Editar" y "Reconectar" UPDATE; "Cerrar sesión" DELETE (solo si hay sesión, es decir, estado distinto de init y logged_out) | "Reconectar" UPDATE |
instagram | "Editar" UPDATE; "Reautorizar" CREATE | Si status === 'connected': "Editar" UPDATE; en otro caso: "Reautorizar" CREATE |
| todos | "Eliminar" DELETE (con ConfirmDialogComponent) |
El botón "+" (CONNECTION_CREATE) abre ConnectionChannelModalComponent. Según el canal elegido: web → openWebModal(ADD), whatsapp-qr → openWhatsappModal(), instagram → connectInstagram().
Estado en tiempo real
ngOnInit se suscribe a ChatSocketService.whatsappSessionStatus$ y actualiza connection.whatsappConfig.sessionState (y phoneJid si viene) de la tarjeta correspondiente. Ver Sockets.
ConnectionChannelModalComponent
Lista CONNECTION_CHANNELS en tarjetas de dos columnas. Los canales con enabled: false se dibujan con la clase is-disabled y la insignia "Próximamente" (connections.channels.soon); onSelect ignora su clic. Cierra el modal devolviendo el type elegido. Ver Iconos de canal.
ConnectionWebModalComponent
Tres pasos (ModalStep): create, snippet y edit.
- Crear: se piden uno o más dominios permitidos (con protocolo,
https://mi-sitio.com) y el interruptorrequiresAuth(por defecto activo). Llama acreateConnection({ channelType: 'web', webConfig })y pasa al pasosnippetcon el token real. - Snippet (
showSnippetOnlyabre directo aquí): genera el código<script>conwindow.WhaticketWidgetConfig = { token, user: {...}, ... }y carga<origin>/widget-loader.js(dondeorigineswindow.location.origin). SirequiresAuthes falso omite el bloqueuser(el widget genera un visitante anónimouser-<uuid>). Incluye botón de copiar. - Editar: dos pestañas,
identification(nombre, saludo, despedida con variables) yattention(departamentos obligatorios, tolerancia de reapertura en minutos y "volver al último agente"), más los dominios yrequiresAuth. Permite cambiar el token con confirmación (regenerateToken); el widget anterior deja de funcionar.
El widget que consume este snippet es un proyecto aparte (widget-loader.js); su documentación vive en la sección de Widget del sitio.
ConnectionWhatsappModalComponent
Dos pasos (WhatsappModalStep = 'qr' | 'form'):
- QR: si no hay conexión previa, crea una (
createConnection({ channelType: 'whatsapp-qr' })) y arranca la sesión (startWhatsappSession). Muestra el QR que llega por el evento de socketwhatsapp_qr, y reacciona awhatsapp_session_statusfiltrando porconnectionId. Al llegarconnectedavanza al formulario tras 1,2 s (STEP_ADVANCE_DELAY_MS). - Formulario (
startOnFormlo abre directamente):name,greetingMessage,farewellMessage,departmentIds(obligatorio),reopenToleranceMinutes(por defecto 15) yreturnToLastAgent(por defectotrue). Las variables insertables son{{greetings}},{{username}},{{fullName}},{{phone}},{{identifier}}y{{email}}.
Si el modal creó la conexión y se cierra sin haberla guardado y sin conectar el teléfono, ngOnDestroy la elimina (removeConnection) para no dejar conexiones huérfanas.
ConnectionInstagramModalComponent
Ver OAuth de Instagram.
Filtro
ConnectionsFilterComponent emite filterApply (con name y channelType) y filterReset. El selector de canal itera CONNECTION_CHANNELS, por lo que incluye también los canales deshabilitados.