Transformadores — /transformers

Um Transformador converte o payload entre o formato de quem envia e o formato de quem recebe: JSON ↔ XML, renomear campos, achatar estruturas, calcular valores derivados, dividir uma mensagem em várias.
O mesmo Transformador é reutilizável em Definições de Entrega, em Coletas e nos encaminhamentos de uma Coleta para várias interfaces.
Onde o Transformador entra no fluxo
- Entrega — entre o recebimento e o envio ao destino.
- Coleta — entre a leitura da origem e o encaminhamento para as interfaces de destino.
- Encaminhamento — um Transformador próprio por destino, quando a mesma coleta alimenta sistemas que esperam formatos diferentes.
- Aplicação produtora — numa Entrega que recebe de vários sistemas, um Transformador próprio por produtora, que converte o formato dela para o da Entrega e roda antes do Transformador da Entrega. Ver Transformador por Aplicação produtora.
Antes de alterar um Transformador, confira o uso consolidado que a listagem mostra. Ele cobre o uso direto em Definições e o uso indireto — via encaminhamentos de Coleta e por Aplicação produtora numa Entrega. Os dois precisam ser considerados, e é fácil esquecer o segundo.
Quem usa este Transformador
A coluna Definições usando traz, em cada linha da listagem, um chip por uso, no formato
ícone da Aplicação · sigla da Aplicação — ícone de Coleta/Entrega · sigla da Definição. A cor do
chip repete a do resto do sistema: verde-água para Coleta, azul para Entrega.
Clicar num chip não leva à Definição: abre a lista de usos, e é daí que você escolhe o que abrir. A coluna é a mais larga da tabela, e um toque involuntário não deve tirar você da tela.
O chip distingue as duas naturezas do vínculo:
| Vínculo | Significa |
|---|---|
| Direto | A própria Entrega ou Coleta transforma com este Transformador |
| Encaminhamento | Só um destino daquela Coleta usa este; a Definição pode usar outro, ou nenhum |
| Por produtor | Só as mensagens de uma Aplicação produtora passam por ele, antes do Transformador da Entrega |
| Referência | A Definição está no Escopo deste Transformador: pode vir a usá-lo, mas hoje não transforma nada com ele |
A distinção importa na hora de alterar: mexer num Transformador de encaminhamento afeta um ramo da Coleta, não a Coleta inteira. A linha de encaminhamento mostra o destino depois de uma seta; a Por produtor mostra a sigla da produtora ao lado de um ícone de envio — sem ela, o chip seria igual ao do uso direto da Entrega. A busca do modal de uso também encontra pela sigla da produtora.
A Referência vem em cinza, com ícone de elo, fora do par verde-água/azul que no sistema inteiro significa Coleta e Entrega em operação. Uma Definição que está no Escopo e usando conta uma vez só, como uso real. Ver Escopo não é uso.
Quando há mais usos do que cabe na coluna, o chip +N abre a mesma lista, e o modal de uso tem busca própria — um Transformador compartilhado acumula dezenas de linhas, e rolar procurando a certa não é conferir nada.
A contagem de usos não é recortada pelas suas Interfaces; a identidade de quem usa é. Um Transformador usado só por outra área mostrava “Nenhuma” e parecia livre para apagar — hoje mostra o total, e a diferença aparece como “N em Interfaces sem seu acesso”. Saber que algo está em uso não revela de quem: nem sigla de Aplicação, nem de Definição, nem id.
Excluir um Transformador em uso
Excluir um Transformador que uma Entrega, uma Coleta ou uma Aplicação produtora ainda usa é recusado, com uma mensagem que diz quantos usos restam — tire-o de lá antes. Antes, a mesma situação chegava à tela como erro genérico do servidor.
O encaminhamento não trava a exclusão: o destino que usava o Transformador excluído passa a usar o Transformador da Entrega de destino, como já acontecia.
Escrever à mão — /transformers/:id
Editor Monaco (o mesmo do VS Code), com teste imediato contra um payload de exemplo. Use new
como :id para criar do zero.
O script é JavaScript. Além do payload, ele recebe um contexto:
| Campo | Conteúdo |
|---|---|
topico | Tópico de origem, em coletas MQTT |
uid | Identificador da mensagem |
dataRecebimento | Momento do recebimento |
vars | Variáveis disponíveis: as Globais mais as da Aplicação de contexto |
Uma variável de Aplicação sobrepõe uma Global de mesmo nome. É o que permite escrever um único
Transformador com vars.CENTRO e instalá-lo em várias plantas — ver
Variáveis.
O Resultado em tela cheia
A coluna 3. Resultado Obtido é estreita para um resultado grande. O ícone de tela cheia, ao lado do copiar, abre o resultado num popup com:
- Original / Formatado — JSON ou XML indentado para leitura. Resultado que não é JSON nem XML válido fica só no Original.
- Copiar original ou Copiar formatado — copia o que está na tela, e o rótulo diz qual.
Formatar é só para ler: o Transformação OK e o Modelo de Saída usam o resultado original, exatamente como o script o produziu.
Converter XML
Três funções ficam disponíveis no script, sem import:
| Função | O que faz |
|---|---|
xmlToJson(xml) | Converte XML em objeto, convertendo o que parece número em número |
xmlToJsonRaw(xml) | Converte XML em objeto sem coerção de tipo: todo texto sai como string |
jsonToXml(obj) | Monta o XML a partir do objeto |
Use xmlToJsonRaw sempre que o XML trouxer identificador. O xmlToJson come o zero à
esquerda: a ordem de produção <ID>000100234567</ID> chega ao script como 100234567 — um número
que não existe no ERP. Vale igual para código de material, lote, centro e CEP, e o estrago é
silencioso, porque o script recebe um número plausível.
As duas funções ignoram o prefixo de namespace e preservam atributos. xmlToJson continua existindo
e continua sendo o padrão de propósito: todo Transformador já escrito foi feito contra o
comportamento dele, e um script que compara if (x.Qtd > 10) passaria a comparar texto se a
conversão mudasse por baixo — a quebra apareceria na primeira mensagem, em produção.
O nome abre travado
Na edição, o campo Transformador vem com um cadeado. O nome não é só um rótulo: é a chave natural de importação e exportação (packs, arquivos de export, ferramentas MCP) e é por ele que a equipe reconhece o script nas Definições que o usam. Renomear faz um pack antigo criar um Transformador novo em vez de atualizar este.
Clicar no cadeado abre o aviso com os impactos; só depois de confirmar o campo libera. Na criação não há cadeado — ainda não existe nada apontando para o nome. A troca fica registrada no log de auditoria, na ação própria Alterar Nome do Transformador.
Escopo não é uso
O botão de alvo, ao lado do selo de escopo, abre a lista de Aplicação / Interface / Definição que este Transformador alcança. Essa lista é uma referência, não um vínculo: quem cria o vínculo de verdade é a própria Definição, na tela dela.
Para não deixar dúvida, cada linha do popup traz a coluna Uso real:
| Marca | Significa |
|---|---|
| Em uso | A Definição — ou um encaminhamento dela, ou uma Aplicação produtora nela — aponta para este Transformador agora |
| Só referência | Ninguém aponta ainda; a Definição está listada apenas como candidata |
Uma referência Em uso não pode ser removida daqui: desfaça o vínculo na tela da Definição primeiro — nessas linhas a lixeira aparece apagada, com o motivo no tooltip.
Remover é em duas etapas, como acrescentar. A lixeira marca a referência para sair — a linha fica destacada e o ícone vira “desfazer” —, e nada é removido até você clicar em Salvar. Fechar a janela descarta as marcações, tanto as de remoção quanto as linhas de adição preenchidas.
Se a marcação esvaziar o escopo, a tela avisa antes: sem nenhuma referência o Transformador volta a ser Global — passa a aparecer para todos e pode ser escolhido em qualquer Definição de Entrega ou de Coleta. É o único efeito de verdade de remover uma referência.
Histórico de versões
Cada alteração salva do script, dos modelos de payload ou do nome gera uma versão. No histórico:
- a mais nova é a Atual — o retrato do que está salvo hoje;
- as demais são Obsoletas: ficam para consulta e para restaurar, mas não são o que roda.
Restaurar esta versão pede confirmação e só mexe na tela — o script e os modelos passam a ser os da versão escolhida, mas nada é gravado até você clicar em Salvar Transformador. Para desistir, saia da tela sem salvar.
Uma versão obsoleta pode ser removida, também com confirmação; a remoção é definitiva e fica no log de auditoria. A versão Atual nunca sai — sem ela o histórico perderia a referência do que está no ar.
Levar um Transformador para outro ambiente
Um Transformador escrito e testado em homologação pode ir para produção — ou para a instalação de outro cliente — como um arquivo. Não é preciso passar pelo Importar/Exportar da Zona de Perigo, que move domínios inteiros de uma vez.
Exportar
Na listagem, o ícone de Exportar para arquivo na linha do Transformador baixa um .json com o
cadastro, o script, os modelos de payload de entrada e saída e a lista de Definições do Escopo.
Ficam de fora de propósito: o histórico de versões (o destino começa a contar do zero) e a autoria da origem (quem importa passa a ser o autor). Se você só enxerga parte do Escopo, o arquivo leva só essa parte.
O Escopo é gravado pelas siglas — Aplicação · Interface · Definição —, nunca pelo número interno do registro. Os números são sequenciais e locais: o mesmo id aponta para outra coisa no ambiente de destino, e um arquivo que os carregasse amarraria o script à Definição errada sem reclamar de nada.
Importar
O botão Importar, ao lado de Novo Transformador, pede o arquivo e mostra o que vai acontecer antes de gravar qualquer coisa.
Para cada Definição do Escopo, o CMS procura a mesma trinca de siglas aqui e diz o que encontrou:
| Situação | O que a tela mostra |
|---|---|
| A trinca existe neste ambiente | existe aqui |
| A Aplicação não existe aqui | Aplicação não existe aqui |
| A Aplicação existe, a Interface não | Interface não existe aqui |
| A Interface existe, a Definição não | Definição não existe aqui |
| A Definição existe, mas é de uma Interface sem o seu acesso | existe, mas é de uma Interface sem seu acesso |
E para cada linha você escolhe:
- Usar esta — só aparece quando a trinca casou; aproveita a Definição encontrada;
- Escolher outra — abre os combos de Aplicação → Interface → Definição deste ambiente, para você apontar à mão para onde aquele Escopo deve ir;
- Ignorar — descarta a linha.
Ignorar todas as linhas (ou importar um Transformador que já era global) traz o Transformador como Global: ele fica disponível para todas as Definições, e o vínculo pode ser feito depois, na tela de cada Definição.
O import nunca sobrescreve um Transformador existente. Se já houver um com o mesmo nome aqui, o importado entra como “nome (importado)” — a tela avisa antes de gravar. Cabe a você conferir os dois e apagar o antigo, se for o caso.
Escolher outra depende das Ferramentas /applications e /interfaces: sem elas os combos
ficariam vazios, e a tela mostra a opção desabilitada com o motivo. A Definição escolhida também é
reconferida no servidor contra as suas Interfaces — não dá para amarrar o script a uma área que você
não alcança.
Cada importação fica registrada no log de auditoria, na ação Importar Transformador, com o nome que vinha no arquivo, o nome com que foi criado e quantos vínculos de Escopo nasceram.
Gerar por IA
Quem não escreve código pode gerar o script descrevendo o resultado. É uma das formas de o CMS usar IA — ver Assistentes de configuração para o conjunto completo.
Informar os exemplos
Um payload de entrada (X) e um de saída (Y) — como exemplo real ou como JSON Schema.
Descrever a transformação
Um prompt em texto livre explicando as regras (“some as quantidades por lote”, “converta a data para
ISO 8601”, “quando não vier o campo turno, use 1”).
Revisar o resultado
O backend gera o script e o executa no mesmo worker pool sandboxado usado em produção, contra o payload de exemplo. Você vê o resultado comparado (X → Y obtido), não o código — a menos que abra o ícone de código, que também mostra o histórico completo de gerações e prompts.
Ajustar ou aprovar
Se o resultado não estiver certo, ajuste o prompt e gere de novo — cada tentativa fica no histórico e pode ser restaurada. Só ao clicar em Aprovar o script passa a valer para uso real.
O provedor (Anthropic, OpenAI ou um compatível com a API OpenAI, como Ollama, Groq ou OpenRouter) é escolhido em Configurações → IA. A chave de API fica cifrada e nunca é reexibida.
A chamada à IA acontece apenas nesta tela, por ação explícita do usuário. O motor que executa os transformadores durante recebimento, entrega e coleta não depende de IA — uma indisponibilidade do provedor não afeta a entrega de mensagem nenhuma.
Ligar duas Definições que já existem
O botão Integrar duas Definições, no topo desta tela, abre o assistente que gera o Transformador entre duas pontas já configuradas no CMS, a partir do que de fato trafega em cada uma. Ver Entre Aplicações.
O motor de execução
Implementação em cms-api/src/modules/transformador/.
Isolamento
Scripts rodam em worker_threads, num pool gerenciado por TransformadorWorkerPool:
- a thread principal da API nunca fica bloqueada por um script lento ou travado;
- cada worker tem limite de memória via
resourceLimits; - além do timeout do
vmdentro do worker, existe um timeout externo: um worker travado é morto e substituído no pool.
O tamanho do pool vem de TRANSFORMADOR_WORKER_POOL_SIZE (padrão 4).
Na prática: um script com laço infinito derruba a própria execução daquela mensagem — não a API, não o agendador, não as outras interfaces.
A geração é desacoplada da execução
A IA vive em modules/transformador-ia/, separada do pool. Cada geração fica registrada em
TransformadorGeracao com o prompt usado e pode ser restaurada.
Nunca aponte o motor de execução para o serviço de IA. A separação existe para que uma indisponibilidade do provedor não afete a entrega de mensagens.
Versões e escopo
TransformadorVersao— histórico de versões do script.TransformadorEscopo— onde o transformador está em uso.
O uso consolidado da listagem não vem de TransformadorEscopo: ele é contado nas cinco tabelas
de vínculo real (Entrega, Coleta, os dois encaminhamentos e DefinicaoTransformadorProdutor, o
Transformador por Aplicação produtora). TransformadorEscopo guarda só a lista de referência do
popup de escopo — a diferença entre as duas coisas é a coluna Uso real.