Transformadores — /transformers

Un Transformador convierte el payload entre el formato de quien envía y el formato de quien recibe: JSON ↔ XML, renombrar campos, aplanar estructuras, calcular valores derivados, dividir un mensaje en varios.
El mismo Transformador es reutilizable en Definiciones de Entrega, en Recolecciones y en los reenvíos de una Recolección a varias interfaces.
Dónde entra el Transformador en el flujo
- Entrega — entre la recepción y el envío al destino.
- Recolección — entre la lectura del origen y el reenvío a las interfaces de destino.
- Reenvío — un Transformador propio por destino, cuando la misma recolección alimenta sistemas que esperan formatos distintos.
- Aplicación productora — en una Entrega que recibe de varios sistemas, un Transformador propio por productora, que convierte el formato de ella al de la Entrega y se ejecuta antes del Transformador de la Entrega. Ver Transformador por Aplicación productora.
Antes de modificar un Transformador, revise el uso consolidado que muestra el listado. Cubre el uso directo en Definiciones y el uso indirecto — vía reenvíos de Recolección y por Aplicación productora en una Entrega. Hay que considerar los dos, y es fácil olvidar el segundo.
Quién usa este Transformador
La columna Definiciones usando trae, en cada fila del listado, un chip por uso, con el formato
ícono de la Aplicación · sigla de la Aplicación — ícono de Recolección/Entrega · sigla de la Definición. El color del chip repite el del resto del sistema: verde agua para Recolección, azul
para Entrega.
Hacer clic en un chip no lleva a la Definición: abre la lista de usos, y desde ahí usted elige qué abrir. Es la columna más ancha de la tabla, y un clic involuntario no debe sacarlo de la pantalla.
El chip distingue las dos naturalezas del vínculo:
| Vínculo | Significa |
|---|---|
| Directo | La propia Entrega o Recolección transforma con este Transformador |
| Reenvío | Solo un destino de esa Recolección lo usa; la Definición puede usar otro, o ninguno |
| Por productora | Solo los mensajes de una Aplicación productora pasan por él, antes del Transformador de la Entrega |
| Referencia | La Definición está en el Alcance de este Transformador: puede llegar a usarlo, pero hoy no transforma nada con él |
La distinción importa a la hora de modificar: tocar un Transformador de reenvío afecta una rama de la Recolección, no la Recolección entera. La fila de reenvío muestra el destino después de una flecha; la Por productora muestra la sigla de la productora junto a un ícono de envío — sin ella, el chip sería igual al del uso directo de la Entrega. La búsqueda del modal de uso también la encuentra por la sigla de la productora.
La Referencia viene en gris, con icono de eslabón, fuera del par verde agua/azul que en todo el sistema significa Recolección y Entrega en operación. Una Definición que está en el Alcance y usándolo cuenta una sola vez, como uso real. Ver El alcance no es uso.
Cuando hay más usos de los que caben en la columna, el chip +N abre la misma lista, y el modal de uso tiene búsqueda propia — un Transformador compartido acumula decenas de filas, y desplazarse buscando la correcta no es revisar nada.
El conteo de usos no se recorta por sus Interfaces; la identidad de quien lo usa sí. Un Transformador usado solo por otra área mostraba “Ninguna” y parecía libre para borrar — hoy muestra el total, y la diferencia aparece como “N en Interfaces sin su acceso”. Saber que algo está en uso no revela de quién: ni sigla de Aplicación, ni de Definición, ni id.
Eliminar un Transformador en uso
Eliminar un Transformador que una Entrega, una Recolección o una Aplicación productora todavía usa es rechazado, con un mensaje que dice cuántos usos quedan — quítelo de allí antes. Antes, la misma situación llegaba a la pantalla como error genérico del servidor.
El reenvío no bloquea la eliminación: el destino que usaba el Transformador eliminado pasa a usar el Transformador de la Entrega de destino, como ya ocurría.
Escribir a mano — /transformers/:id
Editor Monaco (el mismo de VS Code), con prueba inmediata contra un payload de ejemplo. Use new
como :id para crear desde cero.
El script es JavaScript. Además del payload, recibe un contexto:
| Campo | Contenido |
|---|---|
topico | Tópico de origen, en recolecciones MQTT |
uid | Identificador del mensaje |
dataRecebimento | Momento de la recepción |
vars | Variables disponibles: las Globales más las de la Aplicación de contexto |
Una variable de Aplicación sobrescribe una Global con el mismo nombre. Es lo que permite escribir
un único Transformador con vars.CENTRO e instalarlo en varias plantas — ver
Variables.
El Resultado en pantalla completa
La columna 3. Resultado Obtenido es angosta para un resultado grande. El ícono de pantalla completa, junto al de copiar, abre el resultado en un popup con:
- Original / Formateado — JSON o XML indentado para leer. Un resultado que no es JSON ni XML válido queda solo en Original.
- Copiar original o Copiar formateado — copia lo que está en pantalla, y el rótulo dice cuál.
El formato es solo para lectura: Transformación OK y el Modelo de Salida usan el resultado original, tal como lo produjo el script.
Convertir XML
Tres funciones quedan disponibles en el script, sin import:
| Función | Qué hace |
|---|---|
xmlToJson(xml) | Convierte XML en objeto, convirtiendo en número lo que parece número |
xmlToJsonRaw(xml) | Convierte XML en objeto sin coerción de tipo: todo texto sale como string |
jsonToXml(obj) | Arma el XML a partir del objeto |
Use xmlToJsonRaw siempre que el XML traiga identificador. xmlToJson se come el cero a la
izquierda: la orden de producción <ID>000100234567</ID> llega al script como 100234567 — un
número que no existe en el ERP. Vale igual para código de material, lote, centro y código postal, y
el daño es silencioso, porque el script recibe un número plausible.
Las dos funciones ignoran el prefijo de namespace y preservan atributos. xmlToJson sigue existiendo
y sigue siendo el predeterminado a propósito: todo Transformador ya escrito se hizo contra su
comportamiento, y un script que compara if (x.Qtd > 10) pasaría a comparar texto si la conversión
cambiara por debajo — la ruptura aparecería en el primer mensaje, en producción.
El nombre abre bloqueado
En la edición, el campo Transformador viene con un candado. El nombre no es solo una etiqueta: es la clave natural de importación y exportación (packs, archivos de export, herramientas MCP) y es por él que el equipo reconoce el script en las Definiciones que lo usan. Renombrar hace que un pack antiguo cree un Transformador nuevo en vez de actualizar este.
Hacer clic en el candado abre el aviso con los impactos; solo después de confirmar se libera el campo. En la creación no hay candado — todavía no existe nada apuntando al nombre. El cambio queda registrado en el log de auditoría, en la acción propia Cambiar Nombre del Transformador.
El alcance no es uso
El botón de destino, al lado del sello de alcance, abre la lista de Aplicación / Interfaz / Definición que este Transformador alcanza. Esa lista es una referencia, no un vínculo: quien crea el vínculo de verdad es la propia Definición, en su pantalla.
Para no dejar dudas, cada fila del popup trae la columna Uso real:
| Marca | Significa |
|---|---|
| En uso | La Definición — o uno de sus reenvíos, o una Aplicación productora en ella — apunta a este Transformador ahora |
| Solo referencia | Nadie apunta todavía; la Definición está listada solo como candidata |
Una referencia En uso no puede eliminarse desde aquí: deshaga el vínculo en la pantalla de la Definición primero — en esas filas la papelera aparece apagada, con el motivo en el tooltip.
Quitar tiene dos pasos, igual que agregar. La papelera marca la referencia para salir — la fila queda destacada y el ícono se vuelve “deshacer” —, y nada se elimina hasta que usted haga clic en Guardar. Cerrar la ventana descarta las marcas, tanto las de eliminación como las filas de agregado que haya completado.
Si las marcas vacían el alcance, la pantalla avisa antes: sin ninguna referencia el Transformador vuelve a ser Global — aparece para todos y puede elegirse en cualquier Definición de Entrega o de Recolección. Es el único efecto real de quitar una referencia.
Historial de versiones
Cada cambio guardado del script, de los modelos de payload o del nombre genera una versión. En el historial:
- la más nueva es la Actual — el retrato de lo que está guardado hoy;
- las demás son Obsoletas: quedan para consulta y para restaurar, pero no son lo que corre.
Restaurar esta versión pide confirmación y solo cambia lo que está en pantalla — el script y los modelos pasan a ser los de la versión elegida, pero no se guarda nada hasta que usted haga clic en Guardar Transformador. Para desistir, salga de la pantalla sin guardar.
Una versión obsoleta puede eliminarse, también con confirmación; la eliminación es definitiva y queda en el log de auditoría. La versión Actual nunca sale — sin ella el historial perdería la referencia de lo que está en el aire.
Llevar un Transformador a otro ambiente
Un Transformador escrito y probado en homologación puede ir a producción — o a la instalación de otro cliente — como un archivo. No hace falta pasar por el Importar/Exportar de la Zona de Peligro, que mueve dominios enteros de una vez.
Exportar
En el listado, el ícono de Exportar a archivo en la fila del Transformador descarga un .json con
el registro, el script, los modelos de payload de entrada y salida y la lista de Definiciones del
Alcance.
Quedan fuera a propósito: el historial de versiones (el destino empieza a contar desde cero) y la autoría del origen (quien importa pasa a ser el autor). Si usted solo ve parte del Alcance, el archivo lleva solo esa parte.
El Alcance se graba por las siglas — Aplicación · Interfaz · Definición —, nunca por el número interno del registro. Los números son secuenciales y locales: el mismo id apunta a otra cosa en el ambiente de destino, y un archivo que los cargara ataría el script a la Definición equivocada sin reclamar nada.
Importar
El botón Importar, al lado de Nuevo Transformador, pide el archivo y muestra lo que va a pasar antes de grabar cualquier cosa.
Para cada Definición del Alcance, el CMS busca la misma terna de siglas aquí y dice qué encontró:
| Situación | Lo que muestra la pantalla |
|---|---|
| La terna existe en este ambiente | existe aquí |
| La Aplicación no existe aquí | La Aplicación no existe aquí |
| La Aplicación existe, la Interfaz no | La Interfaz no existe aquí |
| La Interfaz existe, la Definición no | La Definición no existe aquí |
| La Definición existe, pero es de una Interfaz sin su acceso | existe, pero es de una Interfaz sin su acceso |
Y para cada fila usted elige:
- Usar esta — solo aparece cuando la terna coincidió; aprovecha la Definición encontrada;
- Elegir otra — abre los combos de Aplicación → Interfaz → Definición de este ambiente, para que usted apunte a mano hacia dónde debe ir ese Alcance;
- Ignorar — descarta la fila.
Ignorar todas las filas (o importar un Transformador que ya era global) trae el Transformador como Global: queda disponible para todas las Definiciones, y el vínculo puede hacerse después, en la pantalla de cada Definición.
El import nunca sobrescribe un Transformador existente. Si ya hay uno con el mismo nombre aquí, el importado entra como “nombre (importado)” — la pantalla avisa antes de grabar. Le toca a usted comparar los dos y borrar el viejo, si corresponde.
Elegir otra depende de las Herramientas /applications e /interfaces: sin ellas los combos
quedarían vacíos, y la pantalla muestra la opción deshabilitada con el motivo. La Definición elegida
también se reconfirma en el servidor contra sus Interfaces — no se puede atar el script a un área que
usted no alcanza.
Cada importación queda registrada en el log de auditoría, en la acción Importar Transformador, con el nombre que venía en el archivo, el nombre con que fue creado y cuántos vínculos de Alcance nacieron.
Generar por IA
Quien no escribe código puede generar el script describiendo el resultado. Es una de las formas en que el CMS usa IA — ver Asistentes de configuración para el conjunto completo.
Informar los ejemplos
Un payload de entrada (X) y uno de salida (Y) — como ejemplo real o como JSON Schema.
Describir la transformación
Un prompt en texto libre explicando las reglas (“sume las cantidades por lote”, “convierta la fecha a
ISO 8601”, “cuando no venga el campo turno, use 1”).
Revisar el resultado
El backend genera el script y lo ejecuta en el mismo pool de workers en sandbox usado en producción, contra el payload de ejemplo. Usted ve el resultado comparado (X → Y obtenido), no el código — a menos que abra el ícono de código, que también muestra el historial completo de generaciones y prompts.
Ajustar o aprobar
Si el resultado no está bien, ajuste el prompt y genere de nuevo — cada intento queda en el historial y puede restaurarse. Solo al hacer clic en Aprobar el script pasa a valer para uso real.
El proveedor (Anthropic, OpenAI o uno compatible con la API de OpenAI, como Ollama, Groq u OpenRouter) se elige en Configuraciones → IA. La clave de API queda cifrada y nunca se vuelve a mostrar.
La llamada a la IA ocurre únicamente en esta pantalla, por acción explícita del usuario. El motor que ejecuta los transformadores durante recepción, entrega y recolección no depende de IA — una indisponibilidad del proveedor no afecta la entrega de ningún mensaje.
Conectar dos Definiciones que ya existen
El botón Integrar dos Definiciones, arriba en esta pantalla, abre el asistente que genera el Transformador entre dos puntas ya configuradas en el CMS, a partir de lo que realmente circula por cada una. Ver Entre Aplicaciones.
El motor de ejecución
Implementación en cms-api/src/modules/transformador/.
Aislamiento
Los scripts corren en worker_threads, en un pool gestionado por TransformadorWorkerPool:
- el hilo principal de la API nunca queda bloqueado por un script lento o colgado;
- cada worker tiene límite de memoria vía
resourceLimits; - además del timeout del
vmdentro del worker, existe un timeout externo: un worker colgado se mata y se reemplaza en el pool.
El tamaño del pool viene de TRANSFORMADOR_WORKER_POOL_SIZE (por defecto 4).
En la práctica: un script con bucle infinito derriba solo la ejecución de ese mensaje — no la API, no el programador, no las demás interfaces.
La generación está desacoplada de la ejecución
La IA vive en modules/transformador-ia/, separada del pool. Cada generación queda registrada en
TransformadorGeracao con el prompt usado y puede restaurarse.
Nunca apunte el motor de ejecución al servicio de IA. La separación existe para que una indisponibilidad del proveedor no afecte la entrega de mensajes.
Versiones y alcance
TransformadorVersao— historial de versiones del script.TransformadorEscopo— dónde está en uso el transformador.
El uso consolidado del listado no sale de TransformadorEscopo: se cuenta en las cinco tablas
de vínculo real (Entrega, Recolección, los dos tipos de reenvío y DefinicaoTransformadorProdutor,
el Transformador por Aplicación productora). TransformadorEscopo guarda solo la lista de
referencia del popup de alcance — la diferencia entre ambas cosas es exactamente la columna Uso
real.