Cenário 1 — dados saindo do X-Adm em tempo real¶
Status: Aprovado · Responsável: Gustavo Madruga · Atualizado em: 2026-09-25
Aplica-se a: equipe X-Adm (ERP/ZIM) e quem opera a nuvem.
O que é¶
A cada gravação no X-Adm (nota, pedido, BL, cadastro), o ERP gera um arquivo .json com os registros
alterados. Um agente Java no servidor do cliente envia esse JSON ao integrador do cliente
(int.<cliente>.xadm.biz), que valida e grava no banco do cliente na nuvem. O PowerSync
(ps.<cliente>.xadm.biz) entrega o dado aos apps (celular e web) em segundos.
Clientes neste cenário hoje: Sulplata e OnPetro Trading. A Thoms usa o mesmo integrador, mas por enquanto o dado chega por CSV (ver cenário 2 §Thoms).
%% lint-mermaid: LR-ok — duas colunas verticais (cliente | nuvem), leitura de cima para baixo
flowchart LR
subgraph cliente["Servidor do cliente"]
direction TB
u["Usuário do X-Adm"]
erp["ERP X-Adm (ZIM)"]
ag["Agente Java<br/>(pasta monitorada)"]
u -- "1. grava nota / pedido / BL" --> erp
erp -- "2. gera .json" --> ag
end
subgraph nuvem["Nuvem X-Adm"]
direction TB
int["int.<cliente><br/>integrador"]
pg[("banco do cliente")]
ps["ps.<cliente><br/>PowerSync"]
app["Apps Web e Mobile"]
int -- "4. valida e grava<br/>(upsert pela chave)" --> pg
pg -- "5. replicação" --> ps
ps -- "6. sincroniza (segundos)" --> app
end
cliente -- "3. agente envia<br/>PUT /api/v1/xadm (Bearer)<br/>resposta: SUCESSO | ERRO" --> nuvem
Pré-requisitos¶
- Cliente com a base na nuvem pronta — banco, integrador e PowerSync (Cliente novo na nuvem, passos 1 a 5).
- O
INTEGRADOR_API_TOKENdo cliente, entregue à equipe X-Adm por canal seguro. - Saída HTTPS liberada do servidor do cliente para
int.<cliente>.xadm.biz(porta 443).
Passos¶
1. Definir o banco-espelho¶
O banco na nuvem é uma cópia fiel de parte do X-Adm: mesmos nomes de tabela, mesmas chaves. Ele é o mesmo para todos os clientes — o integrador cria todas as tabelas ao subir. O que muda por cliente é o que o ERP envia e o que o PowerSync distribui (passo 2).
Tabelas-espelho disponíveis hoje (identidade = a chave do X-Adm):
| Tabela (chave no JSON) | Chave do X-Adm | Conteúdo |
|---|---|---|
CONTRATOS |
ChaveCP |
cabeçalho do pedido/contrato |
ITENSPED |
ChaveItem |
itens do pedido |
ITENSPEDAMX |
SeqN |
alocação de item (BL, quantidade, retirada) |
ITPEDAMXDI |
SeqNItAmx |
datas de desembaraço/DI do item alocado |
ITENS / ITENSLOTE |
ChaveItemNF |
itens da nota e por lote |
NOTACOMPL |
ChaveNota |
nota complementar |
ESTOQUE |
ChaveEst (ou CodProd) |
cadastro de produto, saldo, preço |
LOTEEST |
ChaveLoteEst |
lote de estoque |
TBPCOEST |
ChaveEst + período |
tabela de preço por vigência |
PROPRIEDADES |
ChaveProp |
cliente/fornecedor |
VEICULOS |
SeqVeic |
veículo/navio |
MUNICIPIO |
CodMun |
municípios |
MDF, MDFCOMPL, NFEEVENTO, MDFITENS, NFMDF, NFCOMPLMDF, FRETES |
chaves do MDF-e | ciclo do MDF-e (origem PMDFE, usado com a Sascar) |
Regras do espelho:
- Cada linha ganha um
idpróprio da nuvem, mas quem identifica o registro é a chave do X-Adm. Reenviar o mesmo registro atualiza, nunca duplica. - Apagar no X-Adm (
DELETE) não apaga na nuvem: marcadeleted = true. Reenviar comPUTrevive a linha. - Tipos do ZIM:
VastInt(n)vira número decimal; dataAAAAMMDDvira data. - Colunas de nome curto (
NomeProdCurto,NomePropCurto,DescricaoCurta) são editadas no app e o X-Adm nunca sobrescreve.
Precisa de tabela ou coluna nova? A equipe X-Adm define o que falta (tabela do ZIM, campos, chave)
e passa para a nuvem. Na nuvem, o trabalho é no repo integrador-server — migration nova, entidade,
leitura do JSON e o gravador da tabela — seguido de publicação do integrador para todos os
clientes. Coluna nova que o ERP ainda não manda não quebra nada: fica vazia até o ERP enviar. O
roteiro do código está no guia do integrador;
o modelo consolidado, na modelagem.
2. Escolher o que sincroniza¶
O banco recebe tudo o que o ERP manda; os apps recebem só o que está nas sync rules do cliente, no
powersync.yaml do repo <cliente>-powersync. Sulplata e OnPetro Trading sincronizam as 13 tabelas
de negócio:
streams:
global:
auto_subscribe: true
queries:
- SELECT * FROM contratos WHERE deleted = false
- SELECT * FROM estoque WHERE deleted = false
- SELECT * FROM itens WHERE deleted = false
# … itenslote, itensped, itenspedamx, itpedamxdi, loteest,
# municipio, notacompl, propriedades, tbpcoest, veiculos
Incluir tabela: acrescente a linha SELECT, publique o repo do PowerSync e confira a publicação do
banco (Cliente novo — banco do cliente). Mudar sync rule faz todos os
aparelhos baixarem tudo de novo — agende fora do horário de pico.
Os apps desses clientes entram com login Google (projeto Firebase navarro-app); o PowerSync aceita
o token do Google direto. Detalhe em PowerSync.
3. Lado do X-Adm: gerar e enviar o JSON¶
Este trecho é da equipe X-Adm: o ERP gera o .json a cada gravação numa pasta do servidor do
cliente, e o agente X-Adm Integração Java monitora a pasta e envia cada arquivo ao integrador.
A preencher pela equipe X-Adm
Instalação e configuração do agente Java — <PLACEHOLDER>:
- pasta monitorada e padrão de nome dos arquivos:
<PLACEHOLDER>; - arquivo de configuração e chaves (URL, token):
<PLACEHOLDER>; - o que o agente faz com o arquivo depois de enviar (apaga, move para
enviados/,erros/):<PLACEHOLDER>; - política de nova tentativa (intervalo, limite):
<PLACEHOLDER>; - como ligar o agente no início do Windows e onde fica o log:
<PLACEHOLDER>.
Enquanto não for preenchido, o contrato abaixo é tudo o que a nuvem exige do agente.
O contrato que o agente tem de cumprir:
| Item | Valor |
|---|---|
| Endereço | https://int.<cliente>.xadm.biz/api/v1/xadm |
| Gravar / atualizar | PUT |
| Apagar | DELETE (mesmo corpo; apaga pelas chaves do X-Adm) |
| Cabeçalhos | Authorization: Bearer <INTEGRADOR_API_TOKEN> · Content-Type: application/json |
| Corpo | objeto com Origem (a rotina do X-Adm) e uma chave por tabela, cada uma com uma lista de registros |
{
"Origem": "PPEDAMX",
"CONTRATOS": [ { "ChaveCP": " 1 0P 242D 3451", "ChaveProp": " 1 0 242 0", "DtAprov": "20260116", "SeqVeic": " 81" } ],
"ITENSPED": [ { "ChaveItem": "…", "ChaveCP": " 1 0P 242D 3451", "…": "…" } ]
}
Exemplos reais completos, por rotina:
PUT e
DELETE de
um pedido (PPEDAMX); os demais (ex2 a ex7) ficam na mesma pasta.
Regras que o agente tem de seguir:
-
Ler o corpo da resposta, não só o status. O integrador responde
200mesmo quando falhou:{ "requestId": 1234, "resultado": "SUCESSO", "mensagem": null }Resposta Significa O que o agente faz 200+SUCESSOgravado segue para o próximo 200+ERROrecebido, mas não gravou (a mensagemdiz tabela, registro e motivo)registra o erro e não repete sem corrigir — repetir dá o mesmo erro 400JSON malformado corrigir a geração do arquivo 401token ausente ou errado conferir o token com a nuvem 500, sem resposta, tempo esgotadofalha de rede ou da nuvem tentar de novo mais tarde — é seguro -
Enviar na ordem em que os arquivos foram gerados, um de cada vez. A ordem importa (um
DELETEseguido dePUTdo mesmo registro tem de chegar nessa ordem). - Reenviar é seguro. O mesmo registro enviado duas vezes é atualizado, não duplicado.
- Usar as origens conhecidas:
PPEDAMX,PPEDIDO,PBLDI,PNOTAI,PNOTAD,PCSTPROD,PREQUIS(eINT-PIED, do cenário 3). Origem fora dessa lista (hoje, inclusive aPMDFE, do MDF-e) funciona, mas os erros dela não disparam alarme até ser cadastrada no integrador — avise a nuvem antes de usar uma origem nova. - Nome da tabela em MAIÚSCULAS (
CONTRATOS, nãoContratos). Tabela com nome errado é ignorada — se nenhuma tabela do corpo for reconhecida, a resposta éERROcom "payload sem nenhuma tabela reconhecida". - Valores como o ZIM gera, inclusive os espaços das chaves:
" 1 0P 242D 3451"e"1 0P 242D 3451"são registros diferentes.
Contrato completo, com todas as tabelas e campos: Contrato de ingestão do integrador.
4. Na nuvem: o que o integrador faz ao receber¶
- Guarda o JSON recebido, como veio, na tabela de auditoria
request(com a origem e o horário). - Lê as tabelas do corpo e grava tudo numa transação só: ou grava o arquivo inteiro, ou nada.
- Para cada registro: procura pela chave do X-Adm; se existe, atualiza; se não, cria; se estava apagado, revive.
- Marca o
requestcomoSUCESSOouERRO(com a mensagem) e responde ao agente. - Erro de origem conhecida vira alerta no GlitchTip (
bug.xadm.biz). - Nota de venda (
PNOTAI) e BL liberado (PBLDI) disparam notificação no celular, onde o cliente tem notificação ligada.
Registros descartados sem erro
Algumas linhas incompletas são ignoradas em silêncio (o arquivo volta SUCESSO):
ITENS sem DtEst, Qtde ou Valor; ITENSLOTE sem Qtde ou Saldo; TBPCOEST sem
ValIni, ValFin ou VlProd; ITPEDAMXDI sem SeqNItAmx. Se um registro "não aparece" no
app, confira esses campos primeiro.
5. Distribuição aos apps¶
O PowerSync lê as mudanças do banco e entrega a cada app conectado. O app funciona sem internet e atualiza quando reconecta.
Quando o usuário edita um campo permitido no app (por exemplo o nome curto de um produto), o app
devolve a alteração ao integrador (POST /api/v1/powersync), que grava no banco; o PowerSync então
distribui a todos.
Verificação¶
| Conferir | Como | Esperado |
|---|---|---|
| O arquivo chegou | no banco do cliente: SELECT id, origem, tipo, resultado, mensagem, criado_em FROM request ORDER BY id DESC LIMIT 20; |
a linha do envio com SUCESSO |
| Os erros recentes | SELECT * FROM request WHERE resultado = 'ERRO' ORDER BY id DESC; |
vazio; senão, a mensagem diz a tabela, o registro e o motivo |
| O registro está no espelho | SELECT * FROM contratos WHERE chave_cp = '<chave como o ZIM gera>'; |
a linha, com deleted = false |
| O banco está recebendo | GET https://int.<cliente>.xadm.biz/api/v1/powersync/lastchange |
horário da última mudança recente |
| O app recebeu | abrir o app do cliente | o registro aparece em segundos |
| Alertas | GlitchTip (bug.xadm.biz), projeto integrador-server |
sem erro novo da origem |
Problemas comuns¶
| Sintoma | Causa provável | O que fazer |
|---|---|---|
Todo envio volta 401 |
token do agente diferente do INTEGRADOR_API_TOKEN do integrador |
conferir os dois lados; trocar ao mesmo tempo |
200 mas o dado não aparece |
o agente trata 200 como sucesso e o corpo dizia ERRO |
olhar request com resultado = 'ERRO' |
SUCESSO mas o registro não aparece |
linha descartada por campo obrigatório vazio (ver o aviso do passo 4) ou tabela fora das sync rules | conferir os campos; conferir o powersync.yaml |
| Registro duplicado no app | a chave veio com espaçamento diferente em envios diferentes | padronizar a geração da chave no ZIM |
| App parado, banco recebendo | PowerSync fora do ar ou sync rule sem a tabela | Incidentes comuns |