Plataforma de Integração¶
Status: Aprovado · Responsável: Gustavo Madruga · Atualizado em: 2026-09-12
Aplica-se a: perfis app e config que participam de um fluxo de integração.
A Plataforma de Integração é como os dados se sincronizam dentro do ecossistema X-Adm — trazer dados do ERP para a nuvem, devolvê-los ao ERP, ou enviar dados a terceiros.
- O ponto de integração é o banco por cliente. A Plataforma de Integração é a cópia fiel do X-Adm nesse banco (só as tabelas e colunas necessárias), servida pelo servidor de integração de cada cliente.
- O que vem de fora entra por apps específicos, donos das próprias tabelas, desenvolvidos para a necessidade de cada cliente ou terceiro.
Norma × detalhe
A norma está na constituição. Aqui, o contrato operável. O desenho concreto (inventário, roadmap, decisões D0–D13) e o que ainda está em fluxo vivem na arquitetura-alvo, no repo do integrador.
Topologia¶
Na figura e na taxonomia, Plataforma de Integração nomeia o serviço central por cliente
(int.<cliente>).
A figura é a norma em 4 grupos:
- cliente — o servidor X-Adm no cliente: ERP X-Adm e Integrador Cliente, que faz a ponte com a nuvem;
- nuvem X-Adm (Wiechert) — Plataforma de Integração, apps específicos, apps compartilhados e o banco por cliente;
- terceiros — os parceiros externos;
- usuários — os apps consumidor Web e Mobile, que rodam no dispositivo do usuário final, não na nuvem.
¹ Um banco de dados por cliente.
Nota: os apps consumidor (Web e Mobile) e o programa Integrador Cliente escrevem de volta à
Plataforma de Integração pelo write-back do PowerSync. O caminho é: campo editável →
uploadData → connector → a Plataforma grava nas tabelas que ela possui. É a aresta write-back
da figura, correndo por fora.
Fonte da figura (Mermaid-base)
A figura acima é um SVG desenhado à mão (assets/topologia-integracao.svg) derivado deste
Mermaid, que é a fonte semântica: mudou a topologia, atualize AQUI primeiro e espelhe no SVG.
block-beta
columns 3
space
block:cli
columns 1
clit["Servidor X-Adm Cliente"]
erp["ERP X-Adm"]
intc["Integrador Cliente"]
end
space
block:ter
columns 1
tert["Terceiros"]
pi["PIED"]
ec["E-commerce"]
sa["Sascar"]
end
block:nuv:2
columns 2
nuvt["Nuvem X-Adm (Wiechert)"]:2
vert["Apps Específicas<br/>pied.<cliente>.xadm.biz<br/>excel.<cliente>.xadm.biz<br/>ecommerce.<cliente>.xadm.biz"]
hub["Plataforma de Integração<br/>int.<cliente>.xadm.biz<br/>ps.<cliente>.xadm.biz"]
vs["Apps Compartilhados<br/>sascar.xadm.biz"]
pg[("PostgreSQL Cliente ¹")]
end
space
block:usr
columns 2
usrt["Usuários"]:2
appweb["App Web<br/>bi.<cliente>.xadm.biz"]
appmob["App Mobile<br/>App Android/Apple"]
end
space
erp <-- " " --> intc
intc -- "push tempo real + write-back" --> hub
intc -- "batch XLSX" --> vert
pi -- "webhook + pull" --> vert
sa -- "pull (MDF-e, odômetro)" --> vs
vert -- "push" --> ec
hub <-- "REST" --> vert
hub <-- "REST (baixa MDF-e, odômetro)" --> vs
hub <-- " " --> pg
vert <-- " " --> pg
vs <-- " " --> pg
pg -- "PowerSync" --> usr
pg -- "PowerSync" --> intc
usr -- "write-back" --> hub
Fluxos principais¶
Três fluxos contam a plataforma em movimento — cada um com como acompanhar: o mecanismo pelo qual um humano vê se o fluxo rodou ou falhou.
Fonte das figuras (Mermaid-base)
Mesma regra da topologia: mudou um fluxo, atualize AQUI primeiro e espelhe no SVG do
fluxo (assets/fluxo-saida.svg, assets/fluxo-entrada.svg, assets/fluxo-ciclo-mdfe.svg).
flowchart TB
subgraph f1["Saída — X-Adm → Usuários"]
direction LR
erp1["ERP X-Adm"] --> int1["Integrador Cliente"]
int1 -- "tempo real" --> plat1["Plataforma de Integração"]
int1 -- "batch XLSX" --> excel1["excel.<cliente>"]
plat1 --> pg1[("PostgreSQL")]
excel1 --> pg1
pg1 -- "PowerSync" --> web1["App Web"]
pg1 -- "PowerSync" --> mob1["App Mobile"]
end
subgraph f2["Entrada — Externo → X-Adm"]
direction LR
pied2["PIED (parceiro)"] -- "webhook + pull" --> app2["pied.<cliente>"]
app2 -- "REST (PUT do pedido)" --> pg2[("Plataforma de Integração<br/>int.<cliente> → PostgreSQL Cliente<br/>espelho, cod_retorno 000")]
pg2 -- "PowerSync" --> int2["Integrador Cliente"]
int2 -- "importa (zimrtmu)" --> erp2["ERP X-Adm"]
int2 -. "devolve cod_retorno (006 gravado · 1XX erro)" .-> pg2
end
subgraph f3["Ciclo completo — MDF-e"]
direction LR
erp3["ERP X-Adm"] --> int3["Integrador Cliente"]
int3 -- "push" --> plat3["Plataforma de Integração"]
plat3 -- "MDF-e aberto (chave, placa)" --> sas3["sascar.xadm.biz"]
sas3 -- "polling" --> ter3["Sascar (parceiro)"]
sas3 -. "devolve encerrado" .-> plat3
plat3 -. "espelho atualizado → PowerSync" .-> int3
int3 -. "importa no ERP" .-> erp3
end
f1 ~~~ f2
f2 ~~~ f3
Organiza-se por tipo de fluxo, não por cliente¶
A lógica é que tudo o que é espelho do X-Adm segue o padrão para todos os clientes. O que diverge
(aplicações específicas para cliente, integrações com terceiros) fica em aplicativo dedicado. Desta
forma mantemos a plataforma central (int.<cliente>) independente, sendo possível evoluir tanto a
plataforma como desenvolver aplicações específicas de maneira independente umas das outras.
Taxonomia¶
| Papel | O que é | Recebe de fora? | Dono de | Exemplos |
|---|---|---|---|---|
| Plataforma de Integração | A cópia fiel do banco do X-Adm por cliente (réplica do push + dado bruto do ERP) e o connector do PowerSync. Uma por cliente. | Só o X-Adm (push/agente) | réplica + auditoria | int.<cliente> (o integrador, estreitado a este papel) |
| App específico | Serviço específico que recebe de fora, faz o específico do cliente e escreve as suas tabelas. Pode enviar dados para um terceiro ou para o X-Adm. Um por cliente. | Sim | suas tabelas (bi_*, estado-desejado) |
excel.<cliente> (XLS), pied.<cliente> |
| App compartilhado | Serviço multi-tenant — 1 instância, N clientes; roteia por cliente na entrada. Nasce quando o parceiro é o mesmo para todos (não faz sentido replicar por cliente). Não escreve no barramento diretamente — conversa com a Plataforma de Integração, que grava no espelho do cliente certo. Detalhe em decisão 0018. | Sim (webhook do parceiro + chamadas da Plataforma de Integração de cada cliente) | próprio banco de trabalho (cadastro por cliente, credenciais, log de polling) | sascar.xadm.biz |
| App consumidor | O que o usuário final usa (Web ou Mobile); lê via PowerSync, escreve só campos editáveis (uploadData → connector da Plataforma de Integração). |
Não (não é integração) | nada / write-back de campo | bi.<cliente> (Web), Mobile App do cliente |
Os 4 tipos de fluxo¶
A Plataforma de Integração recebe só o tipo 1 (a cópia fiel). Os tipos 2 e 3 entram pelo
app específico. O tipo 4 sai de um app específico que recebe da Plataforma
de Integração via REST e faz POST no parceiro.
- Push X-Adm em tempo real — o X-Adm envia eventos (nota, BL, pedido) por push HTTP direto à Plataforma de Integração, que aplica na réplica. Genérico, igual para todos. (hoje: o integrador.)
- Batch de arquivo (XLSX/CSV) — o X-Adm exporta um relatório; o agente entrega o arquivo ao
app específico dono (
excel.<cliente>), que faz a recepção (dedupe/envelope), parseia e escreve as suas tabelas (bi_*). Divergente por cliente. (hoje: os-xls.) - Pull/webhook de API externa → formato X-Adm (entrada) — o app específico (
pied.<cliente>) recebe webhook + faz polling na API na sua porta, transforma para o formato do ERP e entrega o estado-desejado à Plataforma de Integração por REST (PUT /api/v1/xadm,Origemprópria) — a Plataforma é a dona das tabelas-espelho e grava as linhas com statuscod_retorno = 000. Um cliente Java do lado do ERP importa e devolve o status. (PIED, em produção na Maxsul.) - Push para API externa (saída) — um app específico (
ecommerce.<cliente>) recebe da Plataforma de Integração via REST (POST /sync/erpno contrato do app), transforma e fazPOSTnuma API de terceiro; é dono da sua tabela de status (idempotência por chave natural). (novo: thoms → WebStorm.)
Combinação ③+④ num app compartilhado. Quando o parceiro é global (mesmo endpoint para
todos os clientes), os dois tipos convivem num único app multi-tenant — recebe da Plataforma de
Integração (dado que vai para o parceiro) e devolve a ela o retorno do parceiro. A Plataforma de
Integração escreve no espelho do cliente certo; o app compartilhado nunca escreve no barramento
(para não virar segundo dono). (novo: Sascar — a Plataforma de Integração envia MDF-es abertos
com chave/placa; o app compartilhado faz polling na Sascar do cliente com credenciais próprias;
quando o motorista chega ao destino, devolve encerrado à Plataforma de Integração, que atualiza
o status do MDF-e; o cliente Java lê via PowerSync e importa no ERP. Ver
decisão 0018.)
Fronteiras e owner-writes¶
O Postgres por cliente é o barramento: cada peça escreve as suas tabelas e lê o resto via PowerSync. Um dono por tabela (quem roda o Flyway e escreve) — o ponto de integração é o banco, não um dispatcher na Plataforma de Integração. O invariante server-side está em Java/Micronaut §Banco.
Posse de escrita ≠ posse de migração. Um consumidor que lê a tabela compartilhada direto (não via PowerSync — o caso do outbox da mensageria) não migra em prod: o dono único cria e migra o DDL; o consumidor usa migração test-only e deploya coordenado com o dono (dono primeiro). Recipe em Java/Micronaut §Banco.
A convenção de nome de tabela é a propriedade do dado:
| Prefixo | Significa | Sincroniza p/ apps? | Dono típico |
|---|---|---|---|
| (sem prefixo) | dado do ERP (é, ou vira, dado do X-Adm) | Sim | Plataforma de Integração (réplica) · app específico (estado-desejado) |
<dominio>_ |
derivado do processamento, consumido por app | Sim | app específico (bi_*) |
<app>_ |
controle interno do app — staging, status por chave natural e tabela auxiliar de domínio | Não | app específico (xls_*, thoms_envio) |
Um app específico que envia para fora (tipo ④) nunca marca estado numa coluna da tabela-espelho
(isso daria dois donos à réplica) — tem a sua tabela de status (<app>_envio), separada.
Tabela que o app cria num schema-espelho leva o prefixo <app>_ — inclusive a de domínio. A regra
vale para toda tabela que o app cria para uso próprio, não só staging e status. A unidade de
trabalho do app — um lote de entrega, uma remessa, um agrupamento de envio — é controle interno
dele, não dado do X-Adm; e num schema que espelha um sistema de terceiro o prefixo é o que separa
"nosso" de "deles". Um app que serve vários fluxos compõe: int_lote para o que é comum,
int_pied_lote para o que é de um fluxo só.
O prefixo de tabela é sempre o app, nunca a origem. <origem>_ já significa outra coisa — coluna
do vocabulário do parceiro, só rastreabilidade (tabela abaixo) —, e o mesmo prefixo com dois sentidos
confunde exatamente quem a convenção deveria ajudar.
O prefixo resolve o schema; o substantivo resolve a conversa. Antes de nomear, confira o termo
contra o vocabulário do sistema espelhado — prefixo evita colisão de identificador, não de
significado. Exemplo: lote/lote_item como unidade de entrega, num schema em que o X-Adm já tem
loteest e itenslote e lote quer dizer lote de estoque; int_lote não colide no banco e continua
colidindo na reunião. Havendo homônimo inevitável, o repo do app registra os dois sentidos no
glossário dele (cada repo define o vocabulário do seu domínio) — é lá que a ambiguidade tem onde ser
resolvida.
Tabela sem prefixo que já existe não é exemplo; renomeia ao tocar. Bases da casa têm os dois
padrões convivendo (pied_* prefixado ao lado de request, push_enviada); quem lê a base e copia o
vizinho copia o errado. Quando a tabela é tocada, renomeia-se por expand/contract
(java-micronaut §Flyway — a release N
não dropa o que N−1 usa), nunca num big-bang. Enquanto não for renomeada, a tabela sem prefixo não vira
precedente para a próxima.
Dentro da tabela-espelho, a fidelidade é por coluna. O prefixo diz de quem é a tabela; dentro
de uma tabela-espelho (sem prefixo), o que rege cada coluna é: o espelho modela a entidade do
X-Adm, não a do parceiro. A identidade (chave de negócio, critério de join) vive sempre em
coluna nativa do X-Adm, conferida contra a spec de entidades do ERP. Um campo que só existe no
vocabulário da fonte externa (ex. codigo_alt, x_ped de um parceiro) entra prefixado pela origem
e é só rastreabilidade:
| Coluna | Origem | Papel | Vira identidade/join? |
|---|---|---|---|
| (sem prefixo, nativa) | modelo do X-Adm | identidade, join, negócio | Sim |
<origem>_<campo> |
sistema externo (ex. pied_x_ped) |
rastreabilidade / transporte | Não |
A coluna <origem>_ sincroniza como o resto da tabela (a tabela é sem prefixo), mas o consumidor a
lê para auditar, nunca para casar registros. Deixar a chave do parceiro virar identidade de 1ª
classe acopla o espelho ao vocabulário de um terceiro — o oposto de "cópia fiel do X-Adm"
(decisão 0009).
Transporte X-Adm↔nuvem (padrão único por direção)¶
Muda o processamento e o destino, nunca o transporte:
- Saída — tempo real: push HTTP direto à Plataforma de Integração (o
/api/v1/xadmde hoje). Contrato 200-sempre: responde sempre 200, erro no corpo. - Saída — arquivo/lote: o X-Adm grava numa outbox local; um agente
.jargenérico observa a pasta (~1 min) e envia à nuvem, roteando por tipo (config, não código): dado bruto do ERP → a Plataforma de Integração; relatório → o app específico dono. O X-Adm fica desacoplado do destino. (Alvo — o agente de outbox ainda não existe. Hoje o relatório vai porPOSTdireto do.zipao app específico ou, no modo antigo, por cópia SSH; ver Manual de integração — cenário 2.) - Entrada no ERP: a Plataforma de Integração grava tabelas-espelho (sem prefixo) no banco do
cliente com o status por linha
cod_retorno—000pendente,001em voo,006gravado no ERP,1XXerro de dado (terminal, só sai por re-arme),9XXerro reprocessável — e agrupa as linhas dependentes num manifesto de remessa (integracao_remessa). O cliente Java do lado do ERP (integrador-client) conecta via PowerSync, lê000/9XXpor remessa completa, importa chamando ozimrtmu.exe pAbaste escreve o status de volta pelo write-back. O mesmo contrato para qualquer fonte de entrada. Roteiro operacional no Manual de integração.
O transporte é PowerSync bidirecional (connector na Plataforma de Integração) para os apps consumidor (Web e Mobile) e para o cliente Java do lado do ERP; entre a Plataforma de Integração e apps específicos que enviam para fora, o transporte é REST (o servidor Java não consome PowerSync — ver 0018 e Os 4 tipos de fluxo, tipo ④). A tech (connector, write-back, auth, config do PowerSync) está em PowerSync.
Na direção inversa — quando um app chama outro via REST (o poke "algo mudou, vá puxar"; o
integrador ao app é o caso comum) — a rota do lado do chamado segue /api/<origem>/<recurso>, com
<origem> = o app chamador (/api/integrador/produto): auth uniforme (ROLE_API no /api/**) e
caminho previsível.
A env de auth e de URL de cada aresta se nomeia pelo destino: <APP>_URL e <APP>_API_TOKEN, um
token por app chamado, com o mesmo valor no validador e em todos os chamadores. Assim o mapa de
segredo não colide num app que é chamador e chamado ao mesmo tempo
(Segurança §Segredos ·
Java/Micronaut §Específico).
Entrega garantida — 3 mecanismos¶
Toda travessia de dado do ecossistema tem entrega garantida: o dado chega, mesmo com o destino fora do ar na hora. A casa não tem um mecanismo — tem três, um por fronteira, cada um com a garantia própria. Não são intercambiáveis: o que decide é quem fala com quem.
-
xadm-mensageria— HTTP M2M entre servers Micronaut. Chamada máquina-a-máquina entre apps da casa (o poke "algo mudou, vá puxar", watch, encerramento, xadm-push): o remetente grava o evento numa outbox local e um relay assíncrono o entrega. Garantia at-least-once — chega ≥ 1 vez, pode repetir → receptor idempotente por chave natural é precondição (decisão 0021). Quando: os dois lados são server Micronaut e o transporte é REST (Plataforma de Integração ↔ app específico/ compartilhado). -
Fila PowerSync
uploadData/write-back — ointegrador-clienton-premise. O cliente Java do lado do ERP é offline-first: grava a mutação numa fila local e o SDK do PowerSync a sobe quando há rede, garantindo a entrega sem o app orquestrar retry. Quando: a ponta é ointegrador-client(ou um app consumidor Mobile) que fala com a nuvem via PowerSync — não há server Micronaut do outro lado do fio. A confiabilidade tem uma pegadinha de core Kotlin (o write-back trava após falha do connector) coberta pelo watchdog em PowerSync §Cliente. -
ZIM/dXpEnvio
RetornoLote— ointegrador-client↔ ERP. Protocolo de arquivo entre o cliente Java e o runtime do ZIM: o lote é escrito, processado e o retorno confirmado por arquivo, com máquina de status. Quando: a fronteira é o ERP ZIM local. Aqui a seção nomeia a família de transporte; o contrato/detalhe da dXp/pAbast é da Fase 5 (integrador-client, amarrado ao ERP), fora do escopo atual — ver a nota em §Arestas concretas.
Duplicação evitável × inerente (vale para os três). Entrega garantida convive com repetição, mas
nem toda repetição é a mesma coisa. Inerente é o crash entre o efeito externo e o commit — o destino
já viu a chamada e o remetente não sabe: a reentrega é a única saída correta, e quem a absorve é o
receptor, por chave natural. Evitável é trabalho duplicado que ninguém pediu — duas réplicas do
mesmo app drenando a mesma fila ao mesmo tempo, por exemplo — e essa o transporte tem de não emitir.
Remover a evitável não promove nada a exactly-once nem dispensa a idempotência do receptor; só para de
gastar o dobro. O caso concreto (duas instâncias do mesmo app drenando a outbox do xadm-mensageria,
curado por advisory lock chaveado pelo app) está na
0021 §Decisão.
Fora por stack (não é débito): os três mecanismos são de infra Micronaut/PowerSync/ZIM. Um
app Java 8 (integrador-client no plano de código) usa (2) e (3), não (1); um app Flutter
(consumidor Web/Mobile) usa PowerSync do lado de leitura. Nenhum deles consome xadm-mensageria
(lib Micronaut) — isso é fronteira de stack, não lacuna a preencher.
Verificar o fio inteiro: o gate de cada app prova as peças; o fluxo ponta a ponta (vários
serviços + o sistema externo, atravessando estas fronteiras) se exercita numa suíte e2e-local
(e2e-<cliente>-<fluxo>) — apps reais, fake só nas bordas, um comando com exit 0/1. Norma na
constituição; padrão em
Engenharia — e2e-local.
Arestas concretas (call-graph)¶
O diagrama e os fluxos acima nomeiam as famílias de aresta; esta tabela é o call-graph concreto server-server que a auditoria levantou — caller → callee, transporte, endpoint e auth por linha. Não é a topologia (essa é a figura); é o inventário do que fala com o quê hoje.
| Caller | Callee | Transporte | Endpoint (no callee) | Auth |
|---|---|---|---|---|
| ERP X-Adm (push) | Plataforma de Integração (int.<cliente>) |
HTTP push | PUT/DELETE /api/v1/xadm |
Bearer, 1 token por instância |
| Plataforma de Integração | app compartilhado (sascar.xadm.biz) |
REST | POST /api/integrador/mdfe/watch |
Bearer M2M (<CALLEE>_API_TOKEN) |
| app compartilhado (Sascar) | Plataforma de Integração | REST | POST /api/sascar/encerramento |
Bearer M2M, token-tenant (0018) |
| App novo (bootstrap) | Central (broker de setup) | REST | POST /api/setup/provision |
Bearer, token de serviço do broker |
| Integrador Cliente | pAbast (parceiro) | dXp (dXpEnvio/dXpRetorno) |
— (protocolo do parceiro) | credencial do parceiro |
Notas:
/api/<origem>/<recurso>— o poke leva o chamador no prefixo:/api/integrador/…é rota no app compartilhado, chamada pela Plataforma de Integração;/api/sascar/…é rota na Plataforma, chamada pelo app compartilhado (§Transporte, direção inversa · Java/Micronaut §Específico).- Status por caso — a aresta de push do ERP responde sempre 200 (erro no corpo, contrato 200-sempre, que a fila exige); par app↔app novo sem PowerSync usa status HTTP real (decisão 0020).
- Mecanismo do contrato (decisão 0020) — nas arestas
house→house em Java (hoje só
Plataforma de Integração ↔ app compartilhado: watch/odômetro e encerramento) o contrato é um módulo tipado<svc>-apique o callee publica e o caller consome por versão (enforcement em compile-time). Onde o caller não é Java (a skill/xadm-setup→POST /api/setup/provisiondo Central; o push do ERP) oopenapi.yamlpublicado É o contrato. O Central é spec-only — não expõe aresta M2M house→house em Java. - O detalhe exato de cada endpoint (schema do corpo, contrato) vive no repo dono do callee (um fato, um dono) — aqui fica o mapa, não o contrato. A dXp/pAbast é da Fase 5 (integrador-client, amarrado ao ERP), fora deste escopo.
Concreto, roadmap e decisões¶
O inventário de projetos, os diagramas de ponta a ponta, o roadmap de migração e as decisões D0–D13 vivem na arquitetura-alvo do integrador — é o desenho volátil, que evolui no repo da integração sem tocar a constituição.