Pular para conteúdo

Documentação Completa — integrador-client

Status: Rascunho · Responsável: Gustavo Madruga · Atualizado em: 2026-09-21

O sistema como ele é hoje, em ordem de raciocínio: o problema, o que entra, o que se guarda, como processa, e — no fim — os contratos que o outro lado precisa cumprir. As etapas contam como chegamos aqui; esta página conta o que existe.

O sistema numa tela

flowchart TB
    subgraph NUVEM["Nuvem X-Adm (por cliente)"]
        direction TB
        PG[("Postgres — barramento<br/>tabelas-espelho + manifesto")]
        PS["PowerSync — connector"]
        INT["integrador-server<br/>POST /api/v1/powersync"]
        PG --- PS
        INT --- PG
    end
    subgraph CLIENTE["Máquina do cliente, junto ao ERP (JRE 8)"]
        direction TB
        SQLITE[("SQLite local<br/>espelho + manifesto")]
        APP["integrador-client<br/>um processador por fluxo, em série"]
        ARQ["dXpEnvio / dXpRetorno<br/>fixed-width"]
        ZIM["zimrtmu.exe — programa pAbast"]
        ERP[("X-Adm / ERP")]
        SQLITE --> APP
        APP --> ARQ
        ARQ --> ZIM
        ZIM --> ERP
        ZIM --> ARQ
        ARQ --> APP
        APP --> SQLITE
    end
    PS -->|"sync: pendentes descem"| SQLITE
    SQLITE -->|"write-back: status sobe"| INT
    APP -->|"heartbeat: sinal de vida"| INT
    INT -->|"repassa com o cliente"| CEN["central-backend<br/>alerta de silêncio"]

O dado desce da nuvem para um SQLite na máquina do ERP; o cliente monta um arquivo de texto de largura fixa, chama o runtime ZIM, lê o resultado linha a linha e devolve o status pela mesma via por onde o dado veio. Em paralelo, um heartbeat avisa pelo mesmo integrador-server que a instalação está viva.

Como acompanhar se rodou. Três lugares, nesta ordem: (1) o cod_retorno de cada linha na nuvem — 006 gravou, 1XX recusou, 9XX vai retentar; (2) o log do cliente (logs/integrador-client.log), que ecoa cada linha enviada e recebida entre |pipes|; (3) o GlitchTip (bug.xadm.biz), que recebe as falhas e as críticas do ERP, com a tag do cliente. Não há tela nem endpoint: é um daemon.

A plataforma também acompanha a instalação sem ninguém olhar: o heartbeat (na largada e a cada hora) põe a versão, os fluxos e o último sinal de cada instalação na aba Deploy do central-ui, e 6 horas úteis sem sinal viram alerta no GlitchTip do central (runbook do central).

1. Contexto e problema

Pedidos, cadastros e produtos criados na nuvem precisam entrar no ERP — e paravam na fronteira: as tabelas-espelho ficavam pendentes no banco do cliente sem ninguém que as gravasse no X-Adm. O integrador-client fecha esse trecho, rodando on-premise, junto ao ERP, no JRE 8 que já existe na máquina.

É o cliente coringa da plataforma: o mesmo programa envia dados de várias origens ao X-Adm, cada origem um fluxo, com suas tabelas e seu literal de handshake — o ZIM identifica o fluxo pelo literal, porque o nome do programa é compartilhado. Cada arquivo de envio carrega as tabelas de um fluxo só.

Fluxo Literal Estado
pied IMPORTA_PIED implementado, o único ativo hoje
abastecimento Continuar implementado e pronto; inativo até o espelho existir no servidor
encerramento de MDF-e ENCERRA_MDFE implementado; inativo até as sync rules exporem a tabela
sascar — previsto

Fora de escopo: o cliente não decide o que deve ir ao ERP (quem declara é o servidor), não transforma dado de negócio e não fala com terceiros. Ele transporta, grava e devolve status.

2. Dados de entrada

A entrada não é arquivo nem requisição: é o estado do espelho local. O que o cliente processa a cada ciclo é a linha que está pendente — cod_retorno em 000 (nova) ou 9XX (reprocessável) — nas tabelas do fluxo, na ordem dos fatos.

Duas coisas chegam pelo mesmo sync:

  • as tabelas-espelho, o dado que vai ao ERP;
  • o manifesto de entrega (integracao_remessa + integracao_remessa_item), que não vai ao ERP: ele diz quais linhas precisam viajar juntas.

A ordem dentro de um lote vem do id (UUID v7), não do updated_at — que o servidor bumpa até no write-back e por isso não serve de ordem.

3. Modelo de dados

Modelo de dados — o espelho local

O integrador-client não tem banco próprio: ele materializa, num SQLite local, um subconjunto das tabelas do barramento (o Postgres por cliente, cujo dono é o integrador-server). O modelo abaixo é o que o cliente enxerga — nem o schema completo do servidor, nem o schema do ERP.

Duas famílias, e a diferença governa todo o resto:

  • espelho — o dado que vai ao ERP. Cada tabela pertence a um fluxo e vira linha no arquivo de envio;
  • controle — o manifesto de entrega. Não vai ao ZIM, não tem linha no arquivo e não pertence a fluxo nenhum: só declara quais linhas do espelho precisam viajar juntas.
erDiagram
    INTEGRACAO_REMESSA ||--o{ INTEGRACAO_REMESSA_ITEM : declara
    INTEGRACAO_REMESSA_ITEM }o..o| ESPELHO : "aponta (tabela + linha_id)"
    ESPELHO {
        text id PK "UUID v7 — implícito, gerido pelo PowerSync"
        text cod_retorno "máquina de status"
        text msg_retorno
    }
    INTEGRACAO_REMESSA {
        text id PK
        text origem
        text chave_negocio
        text status
        text total_itens
    }
    INTEGRACAO_REMESSA_ITEM {
        text id PK
        text remessa_id FK
        text tabela
        text linha_id
        text bloqueado "calculado no servidor"
        text resolvido "calculado no servidor"
    }

O vínculo item→espelho é polimórfico (tabela + linha_id), sem integridade referencial: uma remessa mistura linhas de tabelas diferentes.

Regras que valem para toda tabela do espelho

  • id (UUID v7) não é declarado — o PowerSync o gerencia. É ele que dá a ordem estável do lote: updated_at do servidor bumpa até no write-back e por isso não serve de ordem.
  • Toda coluna é TEXT. Número viaja como texto e vira BigDecimal no Java — nunca double. Booleano do Postgres chega como "1"/"0" (o SDK não tem tipo booleano).
  • Só se declara o que o cliente usa: os campos de entrada (que vão ao ZIM) mais cod_retorno/msg_retorno. Coluna que o X-Adm gera (as chave_*, derivadas) e controle do servidor (deleted, updated_at, …) ficam de fora — coluna não declarada não sincroniza.
  • A ordem das tabelas dentro de um fluxo é a ordem de dependência referencial, e é a ordem em que elas entram no arquivo de envio.

Tabelas por fluxo

Fluxo pied — IMPORTA_PIED

Pedidos, cadastros e produtos vindos do int-pied. Ordem de envio (FK-safe):

# Tabela Colunas de entrada
1 propriedades cgc_cpf, cod_prop, nome_prop, fantasia, endereco, cep, bairro, cidade, estado, numero, insc_est
2 fones cgc_cpf, ddd, telefone, nome, email
3 estoque pied_codigo_alt, ean13, cod_prod_alt, nome_prod, venda, venda_pz
4 contratos pied_x_ped, cgc_cpf_cliente, chave_unid, cod_mat, dt_em, data_inc, hora_inc, vl_tot, nat_op, compl, tp_vda, class_forma, prazo
5 itensped pied_x_ped, pied_codigo_alt, qtde, valor, total, taxa_frete
6 formulas pied_formula_id, pied_x_ped, pied_cod_prod, qtde

formulas (a fórmula/BOM do kit) vai por último: referencia o produto-kit e o componente, que precisam existir no X-Adm antes. As chaves que o X-Adm gera não voltam.

Fluxo abastecimento — Continuar

Uma tabela, abastecimento, réplica da entidade do integrador do xposto: identificação do bico e do almoxarifado, encerrantes inicial e final, duração, volume, autorização, captura, usuário/motorista/produto/empresa, placa e os odômetros com os limites de média. Implementado e pronto; inativo até o espelho existir no servidor.

Fluxo encerra-mdfe — ENCERRA_MDFE

Uma tabela, mdf, com uma coluna de entrada: chave_nota (19 posições, já formatada pelo servidor). O resto do espelho é do servidor e não sincroniza. Implementado; inativo até as sync rules exporem a tabela.

Tabelas de controle — o manifesto

integracao_remessa (origem, chave_negocio, status, total_itens) e integracao_remessa_item (remessa_id, tabela, linha_id, bloqueado, resolvido).

  • Só a remessa viva desce para o cliente — as entregues viram histórico no servidor.
  • total_itens não é cache de contagem: é o que deixa o cliente perceber que a própria lista de itens chegou pela metade (o PowerSync entrega linhas da mesma transação em checkpoints diferentes).
  • bloqueado e resolvido são calculados pelo servidor; o cliente só lê. bloqueado = um pai desta linha, na mesma remessa, está em erro terminal. resolvido = nada mais a esperar por ela.

Máquina de status (o campo cod_retorno)

flowchart TB
    P["000 — pendente"] --> E["001 — em processamento"]
    E --> S["006 — gravado no X-Adm (fim)"]
    E --> T["1XX — erro de dado (fim, não retenta)"]
    E --> R["9XX — reprocessável (volta a pendente)"]
    R --> P

O cliente processa o que está pendente (000 ou 9XX), marca 001 enquanto o lote roda e grava o código que o ZIM devolveu. Um 001 que sobrou de uma queda no meio do lote volta a 000 na inicialização. O estado terminal 1XX é o que o manifesto propaga como bloqueado para os filhos daquela linha.

4. Mapeamento — do espelho para a linha do ERP

Cada tabela do fluxo vira um bloco de linhas no arquivo de envio, na ordem de dependência referencial, e cada linha é fixed-width: contador (3) + tipo (20) + os campos daquela tabela, com largura e escala fechadas. Texto é truncado ou preenchido; número vai sem separador, na escala do campo (HALF_UP); valor monetário é BigDecimal, nunca double.

O mapeamento campo a campo — largura, escala, offset e exemplo de cada coluna — é o contrato pAbast §3, que anda junto com o código do arquivo de envio: mudou uma largura, muda o contrato no mesmo PR.

5. Fluxos e processamento

O ciclo, para cada fluxo ativo:

  1. Sincroniza — o SDK do PowerSync mantém o SQLite local em dia, offline-first, com reconexão automática. Mudança na nuvem acorda o ciclo; um poll de segurança (5 min) cobre o resto.
  2. Coleta os pendentes por duas vias que convivem (decisão 0004): a linha coberta por uma remessa viva só sai com a remessa inteira no banco local; a linha sem remessa sai por presença, após 3 ciclos de carência. Por cima, a coleta lê duas vezes e confere a passada inteira — banco que não sossega adia o lote. O corte no teto de 999 registros respeita remessas inteiras.
  3. Grava no ERP — escreve o arquivo de envio, executa o zimrtmu.exe com o programa pAbast, faz o handshake por arquivos (o ZIM escreve Iniciou Zim; o cliente responde com o literal do fluxo) e lê o retorno ao final, com timeout. Cada linha escrita e cada linha lida vão para o log.
  4. Absorve as críticas do ERP — se o X-Adm deixou avisos no dRels3 (o que, com tela, apareceria ao usuário), o cliente os registra no log e no GlitchTip e apaga o arquivo.
  5. Devolve o status — atualiza a linha local; o SDK propaga sozinho ao integrador-server. Nuvem fora: a fila local segura, e um vigia reconecta se ela ficar presa mais de 20s.

Quando o lote deixa pendência, o agendador desacelera: descarta o gatilho do próprio eco e aplica backoff exponencial (1s dobrando, teto 15 min). Dado novo fura o backoff e o zera — o item preso entra no arquivo do novo.

Com mais de um fluxo ativo, os processadores rodam em série numa thread única, no par de arquivos único (decisão 0002) — nunca concorrem.

Máquina de status

000 → 001 → 006 | 1XX | 9XX. O 001 marca o lote em voo; um 001 órfão de uma queda volta a 000 no startup. 1XX é terminal (erro de dado, não retenta) e é o que o manifesto propaga como bloqueado para os filhos. O matching envio↔retorno é por posição de linha, protegido por contagem e tipo ecoado — divergência invalida o lote inteiro, de propósito. Duas tolerâncias, ambas para defeitos do pAbast em conflito (contrato §4): os começos de tentativa abortada que ele deixa antes da rodada final, idênticos a ela (descartados; vale a rodada final), e a resposta que ele escreve para a linha de continuação no fim do retorno (descartada; valem os 999 dos registros). No fluxo de MDF-e o retorno casa por contador: pode vir fora de ordem e parcial, e o que não voltar retenta.

Modo --debug

Homologação: nada roda sozinho. O operador processa pelo console — item a item ou pedido a pedido (fechamento relacional do contrato) — vendo envio e retorno na tela, com os arquivos mantidos em disco, a máscara do layout e as versões .json. Com mais de um fluxo ativo, um seletor escolhe o fluxo antes do menu.

Heartbeat

Na largada — depois da trava de instância e antes de conectar ao PowerSync — o cliente manda um heartbeat (versão, fluxos ativos, nome da máquina) ao integrador-server do próprio cliente, e o repete a cada heartbeat.intervaloMinutos (60 por padrão), na mesma thread do laço. É só sinal de vida: nunca derruba o daemon (falha vira WARN) e não vai ao GlitchTip — o silêncio de uma instalação quem enxerga é o central. O --debug não manda. Etapa: 06 — Heartbeat.

6. Conceitos transversais e configuração

  • Uma instância por pasta de dados. Uma trava impede duas execuções na mesma pasta; a segunda sai como ocupado (exit 4), sem ter feito nada — seguro repetir.
  • Autenticação: JWT auto-assinado (RS256) com a chave privada da instalação (.pem, que nunca entra no Git). Nenhum segredo é logado.
  • Configuração: um .properties ao lado do jar. Chave faltando ou caminho inválido para na largada, nomeando a chave (exit 2). zim.fluxos é obrigatória e sem default: a instalação declara quais fluxos rodam. A guarda de startup confere fluxo × schema. As chaves heartbeat.* são opcionais: o endereço sai do powersync.upload.url e o intervalo tem padrão (0 desliga); URL que não presta também para na largada.
  • Exit codes: 0 normal · 1 falha inesperada · 2 configuração/ambiente (properties inválido ou JVM 32 bits) · 4 ocupado. A taxonomia é da casa; o app não inventa código. Detalhe no runbook.
  • Logs: stderr + arquivo rolling (30 dias), UTF-8 nos dois. stdout fica livre — é o contrato de CLI da casa.
  • Observabilidade: GlitchTip com a tag do cliente (o DSN é compartilhado), para diagnosticar de longe uma instalação que roda na máquina do cliente. Todo WARN/ERROR do programa vira incidente por um caminho só — o appender do Logback —, e o INFO vira breadcrumb: contexto que viaja dentro do próximo incidente, sem gerar nada quando o ciclo corre bem. Cada incidente carrega ainda o eco do dXpEnvio/dXpRetorno do lote e a configuração de startup (mascarada). Ficam fora do GlitchTip, de propósito: erro de configuração local e JVM de 32 bits (ação do operador na ponta, não falha do aplicativo) e o conflito de concorrência no dRels3 (ruído sem ação possível — o lote retenta sozinho). O envio se desliga pela variável de ambiente SENTRY_DSN vazia, que é como o e2e e o desenvolvimento local rodam sem sujar o painel de produção (decisão 0006).
  • Runtime Java 8 (decisão 0001): a máquina do ERP só tem JRE 8. Compila-se com JDK moderno, alvo bytecode 52, e há teste que confere isso.

7. Contratos

A conclusão de tudo acima: o que o outro lado precisa cumprir.

Contrato Fluxo O que fixa
pAbast pied layout fixed-width campo a campo, ordem das tabelas, handshake, formato do retorno e a máquina de status
abastecimento abastecimento as colunas do espelho do posto e a conversão de duração
ENCERRA_MDFE encerra-mdfe a chave_nota de 19 posições e o retorno casado por contador
heartbeat todos o corpo do sinal de vida e as respostas; o dono é o integrador-server

Não há API HTTP exposta: este é um daemon on-premise. A superfície pública dele é o par de arquivos e o literal de handshake — e é por isso que os contratos acima são o capítulo final. O heartbeat é o único contrato HTTP que o próprio cliente chama (o write-back quem faz é o SDK); ele pertence ao integrador-server, e aqui é linkado, não copiado. O guia para quem vai mexer no código está em guia do código; o fio completo, provado localmente, em E2E local.

8. Apêndice — histórico

Etapas:

  • O que nasce na nuvem entra no ERP sozinho, com status por registro.
  • A mesma instalação atende várias origens de dado do ERP, não só uma.
  • Falha transitória deixa de virar martelada no ERP, e a crítica do X-Adm chega a alguém.
  • O X-Adm passa a receber os componentes do kit, não só o pedido do kit.
  • Pedido não chega mais ao ERP sem o cliente — o que depende viaja junto ou não viaja.
  • Instalação parada deixa de ser descoberta pelo cliente: a plataforma vê versão e fluxos de cada uma e alerta quando alguma some. (planejado)

Decisões:

Pendências abertas:

  • Layout formulas provisório — larguras e escala em contrato-pabast §3.6 valem até o import real de FORMULAS chegar da equipe ZIM; o golden test trava o provisório e vira o ponto de partida da reconciliação.
  • MascaraDebug sem branch formulas (deferido, com precedente do mdf): no rodapé do modo debug a linha de fórmula sai só com contador e tipo.
  • Validação do contrato pAbast pela equipe X-Adm (§8 do contrato) e implementação do lado ZIM.
  • Smoke em máquina Windows x64 real (os nativos do SDK foram validados em Linux/WSL).
  • Ativação dos fluxos abastecimento e encerra-mdfe, ambos à espera do lado servidor.

Glossário

Termos do domínio: Glossário.