# Canales de Comunicación en V12 — Referencia Técnica > **Audiencia:** Desarrolladores y administradores del sistema V12. > **Paquete:** `dbext` — aplicaciones `dbext/comm` y `dbext/smtp`. > **Biblioteca principal:** `v12.com.dkl` --- ## 1. Conceptos Fundamentales El sistema de comunicaciones de V12 es un mecanismo transversal diseñado para ser extensible: cualquier aplicación, servicio o tarea del sistema puede enviar notificaciones externas a destinatarios a través de diferentes medios de comunicación, sin acoplarse a un proveedor específico. Esto significa que la lógica de envío queda completamente desacoplada del programa que la invoca. La abstracción se organiza en cuatro conceptos: | Concepto | Descripción | Ejemplo | |---|---|---| | **Provider Type** | Categoría del medio de comunicación | `E-Mail`, `TEXT`, `IM` | | **Provider** | Tecnología o servicio de envío concreto | `SMTP`, `AI Agent WhatsApp QR` | | **Channel** | Instancia configurada de un proveedor | `smtp.soporte`, `waqr_notifs`, `waba_ventas` | | **Template** | Estructura del mensaje del proveedor | `_default_req_changepwd.htt` | Un mismo proveedor puede tener múltiples canales: por ejemplo, dos cuentas SMTP distintas o dos números de WhatsApp con distintas credenciales. **Representación abstracta de la relación entre entidades:** ``` provider { name: "SMTP", type: 1, channels: [{ name: "notificaciones soporte", config: { server: "", user: "", pwd: "" } }, ...], template: { "header": { "caption": "Asunto", "control": "text", "required": true }, ... } } ``` > **Instalación:** Los proveedores se insertan en la entidad `snd_provider` mediante un archivo `.csv` incluido en el paquete `dbext`. Por defecto se instalan tres registros: `SMTP` (`sys_pk: 1`), `AI Agent WhatsApp QR` (`sys_pk: 2`) y `AI Agent WhatsApp Business API` (`sys_pk: 3`). --- ## 2. Entidades de la Base de Datos Las entidades involucradas corresponden a los tipos de elementos de datos `DET040` (Proveedor de canal de comunicación) y `DET00C` (Canal de comunicación). ### `snd_provider_tpy` — Tipos de proveedor Enumeración de las categorías de canal disponibles. | `id` | `name` | |---|---| | `0` | E-Mail | | `1` | TEXT (SMS) | | `2` | IM (mensajería instantánea) | ### `snd_provider` — Catálogo de proveedores Cada registro representa una tecnología o servicio de envío (`DET040`). | Atributo | Descripción | |---|---| | `name` | Nombre descriptivo del proveedor (ej. `SMTP`, `AI Agent WhatsApp QR`) | | `tpy` | Tipo de proveedor — FK → `snd_provider_tpy` | | `lib` | Ruta a la biblioteca Devkron que implementa el envío | | `entry_point` | URL relativa de la aplicación de configuración dentro de V12 | | `manifest` | JSON con metadatos del proveedor. Usado por proveedores ligeros; vacío en proveedores pesados. Estructura: `name`, `settings` (parámetros de configuración del canal) y `template` (campos del mensaje). | **Estructura del `manifest` (proveedores ligeros):** ```json { "name": "Nombre del proveedor", "settings": { "nombre_campo": { "label": "Etiqueta visible", "control": "input", "required": true } }, "template": { "nombre_campo": { "caption": "Etiqueta visible", "control": "textarea", "required": true } } } ``` **Ejemplo — `AI Agent WhatsApp QR`:** ```json { "name": "AI Agent WhatsApp QR", "settings": { "agent_id": { "label": "Id del agente", "control": "input", "required": true }, "secret": { "label": "Clave de API de Webservice", "control": "input", "required": true } }, "template": { "message": { "caption": "Mensaje", "control": "textarea", "required": true } } } ``` **Ejemplo — `AI Agent WhatsApp Business API`:** ```json { "name": "AI Agent WhatsApp Business API", "settings": { "agent_id": { "label": "Id del agente", "control": "input", "required": true }, "secret": { "label": "Clave de API de Webservice", "control": "input", "required": true } }, "template": { "template_id": { "caption": "Id de plantilla aprobada por Meta", "control": "text", "required": true } } } ``` ### `snd_channel` — Catálogo de canales configurados Instancia concreta y operativa de un proveedor (`DET00C`). | Atributo | Descripción | |---|---| | `prov` | Proveedor al que pertenece — FK → `snd_provider` | | `name` | Nombre del canal (usado como identificador en el código) | | `config` | JSON con credenciales y parámetros específicos del canal. `null` si no se ha configurado. | **Ejemplo de `config` para SMTP:** ```json { "server": "smtp.example.com", "user": "notificaciones@example.com", "pwd": "s3cr3t" } ``` --- ## 3. Proveedores Instalados por Defecto | `name` | `tpy` | Tipo | `entry_point` | `lib` | Tipo de proveedor | |---|---|---|---|---|---| | `SMTP` | `0` | E-Mail | `/!/dbext/smtp/` | `dbext/smtp/lib.dk` | **Pesado** | | `AI Agent WhatsApp QR` | `2` | IM | `/!/dbext/comm/` | `dbext/comm/snd_lib/waqr_lib.dk` | **Ligero** | | `AI Agent WhatsApp Business API` | `2` | IM | `/!/dbext/comm/` | `dbext/comm/snd_lib/waba_lib.dk` | **Ligero** | ### Proveedores pesados vs. ligeros - **Proveedor pesado:** Tiene su propia aplicación de administración dentro de V12 (referenciada en `entry_point`). Útil para configuraciones avanzadas y desarrollo personalizado. La biblioteca del proveedor se ejecuta con `runscript` y recibe los datos a través de `@com_context`. - **Proveedor ligero:** Usa la aplicación central `dbext/comm` para la administración de sus canales. Su biblioteca reside en `dbext/comm/snd_lib/` y se carga con `include`, exponiendo una función `sendMessage` que retorna la respuesta del proveedor. --- ## 4. Biblioteca de Funciones — `v12.com.dkl` Biblioteca principal que contiene las funciones para gestionar canales y orquestar el envío de mensajes. Es utilizada por los modelos de las aplicaciones `dbext/comm` y `dbext/smtp`, así como por cualquier modelo, operación o tarea del sistema que necesite enviar mensajes. ### 4.1 Funciones de canal (`v12.channel`) #### `v12.channel.set(&db, &channel)` → `true / false` Inserta un nuevo registro en la entidad `snd_channel`. Antes de insertar verifica que no exista ya un canal con el mismo nombre. ``` Parámetros: db {object} Referencia a una base de datos abierta. channel {object} { prov: 1, // {number} PK de snd_provider — requerido name: "nombre_canal", // {string} — requerido config: '{"usr":"u","pwd":"p"}' // {string:JSON} — opcional } Entidades involucradas: snd_channel Retorno: true — canal creado exitosamente false — ya existe un canal con ese nombre ``` --- #### `v12.channel.get(&db, name)` → `{object} | null` Retorna el registro completo de un canal por su nombre, o `null` si no existe. ``` Parámetros: db {object} Referencia a una base de datos abierta. name {string} Nombre del canal. Retorno: { sys_pk: 1, ...campos sys_, prov: 1, name: "nombre_canal", config: "" } Entidades involucradas: snd_channel ``` --- #### `v12.channel.get_prov(&db, channel_name)` → `{object} | null` Retorna el registro del proveedor asociado al canal indicado. ``` Parámetros: db {object} Referencia a una base de datos abierta. channel_name {string} Nombre del canal. Retorno: { sys_pk: 1, ...campos sys_, name: "SMTP", tpy: 0, entry_point: "/!/dbext/smtp/", lib: "dbext/smtp/lib.dk", manifest: "" } Entidades involucradas: snd_provider, snd_channel ``` --- ### 4.2 Funciones de configuración de canal (`v12.channel.config`) #### `v12.channel.config.set(&db, &config, channel)` Serializa el objeto `config` como JSON y actualiza el atributo `config` del canal en `snd_channel`. ``` Parámetros: db {object} Referencia a una base de datos abierta. config {object} Parámetros requeridos por el proveedor: { server: "smtp.example.com", user: "user_example", pwd: "pwd_example" // ...todos los atributos requeridos por el proveedor } channel {string} Nombre del canal. Entidades involucradas: snd_channel Retorno: sin datos de retorno ``` --- #### `v12.channel.config.get(&db, channel)` → `{object} | null` Retorna la configuración del canal como objeto. Retorna `null` si el canal no tiene configuración establecida. ``` Parámetros: db {object} Referencia a una base de datos abierta. channel {string} Nombre del canal. Retorno: { server: "smtp.example.com", user: "user_example", pwd: "pwd_example" // ...atributos según el proveedor } null — si el canal no tiene configuración Entidades involucradas: snd_channel ``` --- ### 4.3 Funciones de envío (`v12.com`) Ambas funciones realizan los mismos pasos de validación y preparación antes de invocar la biblioteca del proveedor: 1. Valida que el campo `channel` no esté vacío; lanza error si falta. 2. Si `replace` es `null`, lo inicializa como objeto vacío. 3. Obtiene la configuración del canal con `v12.channel.config.get`. 4. Si se indica `template`, busca el archivo en la ruta de la variable `dbext_comm_msg_templates` y reemplaza el campo `message` con su contenido. 5. Verifica que el canal exista y que su `lib` esté registrado y sea un archivo accesible; lanza error en caso contrario. La diferencia entre ellas está en **cómo invocan la biblioteca del proveedor** y en **lo que retornan**. --- #### `v12.com.sendMessage(&db, &message, &replace)` Ejecuta la biblioteca del proveedor con `runscript`, pasándole los datos en el objeto global `@com_context`. No retorna datos. Usada con **proveedores pesados**. ``` Parámetros: db {object} Referencia a una base de datos abierta. message {object} { channel: "nombre_canal", // {string} — requerido message: "...", // {string} — requerido si no se indica template template: "archivo.htt", // {string} — opcional to: "destinatario", cc: "copia", cco: "copia oculta", reply: "dirección de respuesta", subject: "asunto", text_message: "versión solo texto", // ...otros atributos requeridos por el proveedor } replace {object | null} { clave: "valor de reemplazo en el template" // ... } Entidades involucradas: ninguna (usa funciones internas) Retorno: sin datos de retorno ``` **Comportamiento interno (paso 6):** - Construye el objeto global `@com_context`: ``` com_context { config: {object} // resultado de v12.channel.config.get message: {object} // el objeto &message recibido replace: {object} // el objeto &replace recibido } ``` - Ejecuta la biblioteca del proveedor: `runscript(lib, "com_context", com_context)` --- #### `v12.com.sendMessageV2(&db, &message, &replace)` → `{object}` Carga la biblioteca del proveedor con `include` e invoca directamente su función `sendMessage`, retornando su respuesta. Usada con **proveedores ligeros**. ``` Parámetros: db, message, replace — idénticos a sendMessage. Entidades involucradas: ninguna (usa funciones internas) Retorno: {object} respuesta retornada por lib.sendMessage del proveedor ``` **Comportamiento interno (paso 6):** - Carga la biblioteca del proveedor: `include lib` - Ejecuta y retorna: `return lib.sendMessage(config, message, replace)` --- > **Diferencia clave:** `sendMessage` ejecuta la biblioteca como script independiente vía `runscript` e inyecta los datos en `@com_context`. `sendMessageV2` la carga en el contexto actual vía `include` e invoca `lib.sendMessage` directamente, obteniendo su valor de retorno. > **Nota:** La convención de marcadores para sustitución de variables en el mensaje es determinada por la biblioteca de cada proveedor. --- ### 4.4 Interfaz de la biblioteca del proveedor **Proveedor pesado** — la biblioteca recibe los datos a través de `@com_context` e implementa internamente `sendMessage`: ``` sendMessage(&config, &message, &remplace_template) ``` - Los tres objetos provienen de `@com_context` construido por `v12.com.sendMessage`. - La función se ejecuta al iniciar la biblioteca. - Prepara y formatea los datos, aplica los reemplazos en el template y realiza el envío con el método nativo del proveedor (ej. `do smtp.send(send)`). **Proveedor ligero** — la biblioteca expone `sendMessage` como función pública que `v12.com.sendMessageV2` invoca tras cargarla con `include`: ``` sendMessage(config, message, replace) → {object} ``` - Prepara y formatea los datos según el proveedor. - Realiza el envío y retorna el objeto con el resultado de la solicitud. --- ## 5. Organización del Paquete `dbext` ### Aplicación `dbext/comm` — Administrador central de canales Administra los canales de todos los **proveedores ligeros**. | Archivo | Responsabilidad | |---|---| | `list.dk` | Vista que lista canales; permite crear, editar y eliminar | | `model.dk` | Modelo con operaciones CRUD apoyadas en `v12.com.dkl` | | `form.dk` | Vista de edición para proveedores ligeros | | `snd_lib/` | Directorio con las bibliotecas de proveedores ligeros | ### Aplicación `dbext/smtp` — Proveedor pesado SMTP Aplicación específica para administrar y enviar mediante SMTP. | Archivo | Responsabilidad | |---|---| | `form.dk` | Vista de edición del canal SMTP | | `model.dk` | Modelo con operaciones CRUD apoyadas en `v12.com.dkl` | | `lib.dk` | Biblioteca de envío; ejecuta `do smtp.send(send)` | **Flujo interno de `dbext/smtp/lib.dk`:** 1. Lee los objetos `config`, `message` y `replace` desde `@com_context`. 2. Prepara y formatea los datos según lo requiere el proveedor SMTP. 3. Aplica los reemplazos de variables en el template. 4. Ejecuta el envío con el método nativo: `do smtp.send(send)`. --- ## 6. Flujos de Ejecución ### Con `v12.com.sendMessage` — Proveedor pesado (SMTP) ``` programa_origen.dk (modelo de aplicación o tarea) └─ prepara {db, message, replace} └─ do v12.com.sendMessage(db, message, replace) │ └─► v12.com.dkl ├─ valida channel, inicializa replace si null ├─ obtiene config del canal ├─ carga template en message (si se indicó) ├─ valida existencia del canal y su lib ├─ construye @com_context {config, message, replace} └─ runscript("dbext/smtp/lib.dk", "com_context", com_context) │ └─► dbext/smtp/lib.dk ├─ lee @com_context ├─ prepara y formatea los datos ├─ aplica reemplazos en el template └─ do smtp.send(send) │ ▼ ✓ Envío exitoso (sin error ni excepción) ``` ### Con `v12.com.sendMessageV2` — Proveedor ligero (AI Agent WhatsApp QR) ``` programa_origen.dk (modelo de aplicación o tarea) └─ prepara {db, message, replace} └─ ref response = v12.com.sendMessageV2(db, message, replace) │ ▲ └─► v12.com.dkl │ ├─ valida channel, inicializa replace si null │ ├─ obtiene config del canal │ ├─ carga template en message (si se indicó) │ ├─ valida existencia del canal y su lib │ ├─ include "dbext/comm/snd_lib/waqr_lib.dk" │ └─ return lib.sendMessage(config, message, replace) │ │ │ └─► waqr_lib.dk │ ├─ prepara y formatea los datos │ ├─ realiza el envío │ └─ retorna objeto con resultado ─────────────┘ ``` --- ## 7. Ejemplos de Uso ### Envío de email con SMTP Desde el modelo de una aplicación o una tarea: ``` new message { @"channel": "smtp.com" // nombre del canal registrado @"to": @@(user, "email") // destinatario @"subject": email_subject // asunto @"template": "_default_req_changepwd.htt" // plantilla .htt } new replace { @"url": @url_pwd_chq_apply + "?rcode=" + @@(prm, "$rcode") } do v12.com.sendMessage(db, message, replace) show_message = "Se envió el link de recuperación a tu correo" exception { show_message = "No fue posible enviar el correo: " + last_error() } ``` --- ### Envío con AI Agent WhatsApp QR (texto libre con variable) ``` new message { @"channel": "waqr_notifs" @"to": "+5219982345678" @"message": "Notificación organización: próxima reunión @date" } new replace { @"date": date_meeting } ref response = v12.com.sendMessageV2(db, message, replace) ``` --- ### Envío con AI Agent WhatsApp Business API (plantilla Meta) ``` new message { @"channel": "waba_notifs" @"to": "+5219982345678" @"template_id": "1234567890123456" // ID de plantilla aprobada por Meta } new replace { @"client": client_name @"date": date_meeting } ref response = v12.com.sendMessageV2(db, message, replace) ``` --- ## 8. Guía para Administradores del Sistema ### Agregar un canal nuevo 1. Identificar el proveedor deseado en la entidad `snd_provider` (por `sys_pk`). 2. Navegar a la aplicación de administración correspondiente: - **Proveedor pesado (SMTP):** acceder a `dbext/smtp`. - **Proveedor ligero:** acceder a `dbext/comm`. 3. Crear el canal con un nombre único y completar los atributos de `config` según los requerimientos del proveedor. 4. Guardar — el modelo llama internamente a `v12.channel.set()`, que rechazará la operación si ya existe un canal con ese nombre. ### Actualizar la configuración de un canal Desde la aplicación del proveedor correspondiente, editar el canal. El modelo llama a `v12.channel.config.set()`. Para verificar la configuración activa desde código: ``` ref cfg = v12.channel.config.get(db, "nombre_canal") // Nota: retorna null si el canal no tiene configuración establecida ``` ### Plantillas de mensaje (archivos `.htt`) Las plantillas son archivos ubicados en la ruta definida por la variable: ``` dbr.vars: "dbext_comm_msg_templates" ``` Valor por defecto: `_templates/messages/` Se referencian por nombre en el atributo `template` del objeto `message`. Si el archivo no existe en esa ruta, el campo `template` se ignora. La convención de marcadores para sustitución de variables es definida por la biblioteca de cada proveedor. --- ## 9. Referencia Rápida | Tarea | Función | |---|---| | Crear canal | `v12.channel.set(&db, &channel)` | | Leer canal | `v12.channel.get(&db, name)` | | Leer proveedor de un canal | `v12.channel.get_prov(&db, channel_name)` | | Actualizar config de canal | `v12.channel.config.set(&db, &config, channel)` | | Leer config de canal | `v12.channel.config.get(&db, channel)` | | Enviar mensaje sin retorno (pesado) | `v12.com.sendMessage(&db, &message, &replace)` | | Enviar mensaje con retorno (ligero) | `v12.com.sendMessageV2(&db, &message, &replace)` | | Administrar canales ligeros | Aplicación `dbext/comm` | | Administrar canales SMTP | Aplicación `dbext/smtp` | ---