Projeto — Integração PIED → X-Adm (Caminho Robusto)¶
Status: Aprovado · Responsável: Gustavo Madruga · Atualizado em: 2026-07-11
Documento técnico da integração PIED → X-Adm da Maxsul (projeto MAXSUL2025/841). Sucede o Pré-projeto, cujo Caminho 2 (Robusto) foi aprovado pela diretoria em 2026-07-09 (Marcelo Garabeli) — ver decisão 0002.
Esta é a documentação-mãe do projeto como um todo. A solução se destila em vários repositórios (Integrador, este
maxsul-pied, PowerSync, cliente Java). Aqui está a visão completa e canônica; cada repositório carrega a sua fatia destilada e aponta de volta para cá.
1. Estrutura inicial e suas partes¶
A integração é um pipeline entre dois sistemas que não se conhecem: a PIED (onde a venda de painéis solares acontece) e o X-Adm (o ERP que emite a nota fiscal). Entre eles, quatro peças, cada uma no seu repositório, ligadas por um único banco Postgres compartilhado (o banco de integração da Maxsul) e pelo PowerSync.
flowchart TB
PIED["<b>PIED</b><br/>API REST + webhooks"]
subgraph nuvem["Nuvem X-Adm · Coolify — Postgres Maxsul compartilhado"]
direction TB
PIEDAPP["<b>maxsul-pied</b> — pied.maxsul.xadm.biz<br/>webhook + poll + transform"]
INT["<b>Integrador</b> — int.maxsul.xadm.biz<br/>tabelas espelho do X-Adm · conector PowerSync"]
PS["<b>PowerSync</b> — ps.maxsul.xadm.biz<br/>Postgres ↔ SQLite"]
end
ERP["<b>ps-java</b> — cliente Java on-premises no ERP<br/>grava no X-Adm (ZIM)"]
PIED -->|"webhook (tempo real)"| PIEDAPP
PIEDAPP -->|"poll REST"| PIED
PIEDAPP -->|"PUT / DELETE /api/v1/xadm"| INT
INT -->|"replicação lógica (WAL)"| PS
PS -->|"sync offline-first"| ERP
ERP -->|"write-back de status"| INT
Quem é dono do quê (a regra que rege todo o resto)¶
| Peça | Subdomínio | É dono de… | Papel |
|---|---|---|---|
Integrador (integracao/integrador) |
int.maxsul.xadm.biz |
tabelas espelho do X-Adm: contratos, propriedades, fones, itensped, estoque |
Plataforma central de integração. Recebe o estado desejado pelo mesmo contrato REST que o ERP já usa, persiste, e alimenta o PowerSync. |
| maxsul-pied (este repo) | pied.maxsul.xadm.biz |
tabelas PIED: pied_webhook, pied_rest (raw), pied_produto, pied_cliente, pied_pedido (normalizadas) |
Ponte com a PIED: recebe webhook e faz poll REST, valida, transforma para o modelo X-Adm e empurra ao Integrador via REST. |
maxsul-powersync (maxsul/powersync) |
ps.maxsul.xadm.biz |
— (infra) | Replica as tabelas do Integrador (Postgres) para o SQLite do cliente Java, offline-first. |
ps-java (maxsul/ps-java) |
on-premises (junto ao ERP) | — (consumidor) | Cliente Java de exemplo: observa as mudanças sincronizadas e (fase futura, pela equipe X-Adm) grava no ERP; devolve o status. |
Dois princípios invioláveis que valem em todo o pipeline:
- Persistir o raw ANTES de processar — nada é transformado sem antes existir cru no banco; auditoria e reprocessamento ficam garantidos desde o dia 1.
- Nunca escrever síncrono no ERP — a entrega é desacoplada (Integrador → PowerSync → cliente). A nuvem nunca chama o ZIM direto.
Banco compartilhado, donos separados. Integrador e
maxsul-piedconectam no mesmo Postgres, mas cada um só fazCREATE/ALTER(Flyway) no seu conjunto de tabelas. Omaxsul-piednão escreve SQL direto nas tabelas do Integrador — ele usa a API REST existente do Integrador (PUT/DELETE /api/v1/xadm), o mesmo caminho que o agente on-premises do X-Adm já usa. Assim preserva-se a serialização porReentrantLock, a auditoria emrequeste a semântica de soft-delete/revive do Integrador.
O plano é em fases (decisão 0001):
- Fase 1 — captura raw (MVP, em produção primeiro): só
pied_webhook+pied_rest. Sem transform, sem escrita no Integrador. Objetivo: acumular payloads reais da PIED para modelar a fase 2 com evidência, não com suposição. - Fase 2 — transform + entrega (implementada, 2026-07-13): normaliza
(
pied_produto/pied_cliente/pied_pedido), aplica o de-para X-Adm, faz o gate pordealStatus(transição de pagamento →received), empurra o estado desejado ao Integrador (PUT /api/v1/xadm) e reconcilia o retorno do X-Adm pela remessa do manifesto (GET /api/v1/integracao/remessas) — um pedido só ficaCONFIRMADOquando a remessa inteira fechou, não quando o contrato voltou006(decisão 0021). MODOPRODUCAO(auto) ×STAGING(fila manual). Operação e dry-run pelo console/console(§3.4). Nasce desligada por toggle (pied.integracao.habilitada, defaultfalse) — ligar é decisão de operação após confirmar de-para/gate com a Maxsul (configuração).
2. Integrador — o espelho do banco X-Adm¶
O Integrador já é um produto da casa: um servidor Micronaut/Java, uma instância por cliente,
Postgres dedicado, publicado em int.<cliente>.xadm.biz. Ele nasceu para o fluxo de saída do
X-Adm (o ERP faz push e o Integrador replica para os apps móveis via PowerSync). Para a Maxsul, ele
ganha o papel de plataforma central desta integração: é onde vive o modelo X-Adm que a PIED
precisa alimentar.
2.1 As tabelas espelho (o alvo da integração)¶
O contrato autoritativo é o documento interno MAXSUL2025_610110 (entidades, campos, tamanhos e regras de transformação). Cinco entidades:
| Tabela | Chave de negócio | O que é |
|---|---|---|
contratos |
xPed / ChaveCP |
Cabeçalho do pedido de venda (cliente, valor, natureza de operação, frete). |
itensped |
xPed + item |
Itens do pedido (produto, quantidade, valor, frete). |
propriedades |
CgcCpf + CodProp |
Cliente / endereço (razão social, endereço, cidade, estado). |
fones |
CgcCpf |
Contato do cliente (nome, DDD, telefone, e-mail). |
estoque |
CodigoAlt / ChaveEst |
Produto (código PIED, nome, valores de venda, EAN13). |
Cada tabela carrega a convenção do Integrador (chave de negócio + id UUID v7 + soft-delete); as
colunas Chave* e CodRetorno/MsgRetorno são preenchidas pelo lado X-Adm e voltam pelo write-back
(§5). Schema completo (colunas, tipos, tamanhos) e relacionamentos:
Modelagem §1. De-para e regras de transformação: Mapeamento.
O que muda no Integrador para a Maxsul — aditivo. O X-Adm tem um modelo único:
contratos/propriedades/estoque/itensped(efones) são as mesmas tabelas para todos os clientes — o Integrador as espelha. Uma mesmacontratosserve compra e venda; cada fluxo preenche o subconjunto de colunas que usa. Para a Maxsul o trabalho é aditivo ao molde compartilhado: garantir as colunas do fluxo de venda (xPed,CgcCpfCliente,NatOp…) e a tabelafones(nova). Delta de colunas a confirmar com o dono do Integrador.
2.2 Como o Integrador recebe o estado desejado¶
O maxsul-pied entrega os dados pelo mesmo contrato REST que o ERP já usa para popular o
Integrador: PUT /api/v1/xadm (upsert) e DELETE /api/v1/xadm (soft-delete), com
Authorization: Bearer <token>. O Integrador faz o parse, serializa o apply
(ReentrantLock(fair=true)), persiste e audita em request. Para JSON válido responde sempre
HTTP 200, com o resultado no corpo. É o maxsul-pied no papel que o agente on-premises ocupa
para os outros clientes — só que a fonte, aqui, é a PIED em vez do próprio ERP.
Comunicação tolerante a falhas. A entrega ao Integrador nunca se perde por indisponibilidade.
Se o Integrador estiver fora do ar, responder erro ou a chamada expirar, o maxsul-pied retenta no
futuro com espera progressiva — o estado desejado continua no banco (raw + tabelas normalizadas),
então retentar é sempre seguro (idempotente por chave natural, §6.4). Falha que persista além das
retentativas alerta a equipe de desenvolvimento via GlitchTip — decidido ter uma instância
para este projeto (habilitar features.glitchtip no app.json ao configurar). Nenhum pedido é
descartado por falha de rede.
3. maxsul-pied (este projeto) — a ponte com a PIED¶
Este repositório é o microserviço slim que conversa com a PIED. Ele capta por dois canais
complementares (nenhum cobre tudo sozinho) e é dono das tabelas pied_*.
flowchart TB
subgraph pied_app["maxsul-pied"]
WH["Webhook<br/>POST /webhook/pied"] --> RAWW[("pied_webhook")]
POLL["Poll REST<br/>@Scheduled"] --> RAWR[("pied_rest")]
RAWW --> TF["Transform (fase 2)"]
RAWR --> TF
TF --> NORM[("pied_produto<br/>pied_cliente<br/>pied_pedido")]
NORM --> PUSH["push → Integrador<br/>PUT/DELETE /api/v1/xadm"]
end
UI["UI simples<br/>(o que chegou × o que foi processado)"] -.lê.- RAWW
UI -.lê.- RAWR
UI -.lê.- NORM
3.1 Webhook (tempo real) — caminho quente¶
A PIED envia POST com corpo { "event": "order.created", "data": { ... } } (eventos budget.* e
order.*; o data traz company e products embutidos — ver o payload de exemplo no
MAXSUL2025_610110). Regras:
- Toggle desligado → 503; segredo compartilhado configurado e divergente → 401 (RFC 7807).
- Insert do corpo cru + headers (credenciais mascaradas) em
pied_webhook. - Responde 200 imediatamente — nenhum processamento pesado no request.
- Evento desconhecido não é rejeitado: é dado capturado (evita reentrega da PIED por erro nosso).
O cadastro do endpoint e do segredo é manual, no painel da PIED (Configuração).
Modo de falha do canal: a PIED desliga o webhook e não avisa. Quando uma entrega falha — 404 (app fora do ar) ou timeout (rede oscilando, app de pé) — a PIED desativa o webhook, para de enviar, não religa sozinha e não reenvia o que se perdeu. Só volta com recadastro manual no dashboard (runbook). Em 18/09/2026, cinco minutos de rede ruim custaram 2h44 de apagão no pico de uma sexta. Vigiado pelo watchdog em duas réguas — 45 min comerciais e 24h úteis (0016, 0023).
3.2 Poll REST — reconciliação e o que o webhook não traz¶
O webhook sozinho não cobre tudo: não há evento de produto, e alterações de frete/responsável não disparam evento. Por isso o poll REST é obrigatório, não rede de segurança. Contrato da API validado contra o ambiente real em 2026-06-17:
- Produtos:
GET /api/v2/equipments/{pagina}/{limite}— varredura completa (sem delta). - Clientes:
GET /api/v1/companies/{pagina}/{limite}(v1) — varredura completa. - Pedidos:
GET /api/v2/requests/order/{pagina}/{limite}[?lastUpdateAfter=AAAA-MM-DD]— incremental por cursor (?lastUpdateAftersó funciona em pedidos).
Envelope comum: { "error": null, "data": { "items": [...], "totalItems": N } }. Paginação 1-based,
limite 50; página além do fim retorna HTTP 200 com items vazio — a condição de parada. O cursor
de pedidos é persistido (pied_rest / tabela de cursor) e só avança ao fim de uma rodada
bem-sucedida; a janela do último dia é reprocessada de propósito (o filtro tem granularidade de dia —
dedup é problema do transform).
O poll também é o backstop do apagão de webhook — e por um tempo foi um backstop pela metade:
recuperava o dado do pedido perdido, mas se o evento de pagamento caiu no apagão, o pedido chega
aqui já pago, sem transição para o gate testemunhar, e encalhava em CAPTURADO. Fechado pelo
resgate da 1ª captura já paga (0023),
que usa o orderCreated do REST para separar pedido novo de backlog. Nasce desligado
(PIED_GATE_PRIMEIRA_CAPTURA_DESDE vazio).
3.3 Transform (fase 2) — do modelo PIED para o modelo X-Adm¶
Com dados reais em mãos, o transform lê o raw, normaliza para pied_produto/pied_cliente/
pied_pedido e produz o estado desejado no formato do MAXSUL2025_610110, empurrando ao Integrador
via REST (§2.2). É idempotente por content-hash: só reescreve quando o conteúdo de negócio muda de
fato — evita sync inútil no PowerSync. As regras de de-para (incluindo a lógica Kit × não-Kit, a
NatOp por estado, os valores fixos ChaveUnid/CodMat, a derivação de CodProp, a quebra
DDD/telefone) estão em Mapeamento e regras.
Regra de negócio — só pedido finalizado vai ao X-Adm. O X-Adm faz apenas INSERT de pedido — nunca UPDATE. Logo, um pedido só é empurrado ao Integrador quando está finalizado na PIED; enquanto estiver em aberto/rascunho, fica retido no landing e não é enviado. Assim que finaliza, é inserido uma única vez. (Clientes e produtos não têm essa trava: podem ser atualizados normalmente.)
Como foi definido: o gate de envio é a transição de pagamento — o
payment.statuspassa areceivednum update de um pedido que já tínhamos não-pago (Maxsul, 2026-08-07). Detectada por evento no upsert (payment_status/payment_status_anterior); é go-forward por natureza (backlog já-pago não re-transiciona) e o envio é uma vez (INSERT-único; a máquina de status não reenfileira). Pedido que aparece já-pago na 1ª captura não dispara sozinho (operador enfileira no console). OdealStatusdeixou de ser o gate (pendências P1).
3.4 Console operador (/console)¶
Superfície única de operação (aberta, como o /captura) que dirige cada pedido pela máquina de
estados real (pied_pedido.status) e absorveu o antigo /inspetor+/fila. Por pedido: o de-para
PIED × X-Adm campo a campo (dry-run, sem tocar o Integrador), o stepper de estágios e as ações
liberadas pelo estado — puxar da PIED, enfileirar (gate manual), enviar, atualizar o retorno, reenviar,
re-normalizar — mais um sync global de clientes (InscEst). Em STAGING é interativo; em PRODUCAO,
monitor. Rotas e envs em configuração.
3.5 UI de diagnóstico — home + browser do banco (/dados/*)¶
As telas são server-rendered (JTE, decisão 0025 — layout/menu no padrão do integrador) e abertas (sem auth,
atrás da rede). Além do /console e do /captura, a home (/) é um painel de cards com a contagem
por tabela, e o browser do banco (/dados/{pedidos,clientes,produtos,webhooks,rest,cursor,estado})
expõe read-only as 7 tabelas pied_* pelo browser — colunas + payload JSON copiável. Expõe PII
(clientes). Detalhe das rotas em configuração.
4. PowerSync — do Postgres ao cliente¶
O PowerSync (ps.maxsul.xadm.biz, repositório maxsul/powersync) replica, por replicação
lógica (WAL do Postgres), as tabelas do Integrador para um SQLite local ao lado do cliente
Java. É self-hosted (serviço PowerSync + MongoDB de bucket), publicação Postgres nomeada powersync,
auth por JWKS (auth.xadm.biz, audience maxsul).
As sync rules já estão escritas (bucket global):
data:
- SELECT * FROM contratos WHERE deleted = false
- SELECT * FROM propriedades WHERE deleted = false
- SELECT * FROM fones WHERE deleted = false
- SELECT * FROM itensped WHERE deleted = false
- SELECT * FROM estoque WHERE deleted = false
Com a fase 2 em produção, essas tabelas são alimentadas: o EstadoDesejado deste app mapeia
cliente→propriedades, pedido→contratos, item→itensped e produto/kit→estoque, e chega lá pelo
PUT /api/v1/xadm do Integrador (§3). O log "tabela ausente" do PowerSync era o estado da fase
1 — se voltar a aparecer, é sintoma, não o esperado. Runbook e regra de "nunca down -v em
produção" (apaga o bucket) estão no repositório do PowerSync.
5. Quais dados chegam no cliente Java (ps-java)¶
O ps-java (maxsul/ps-java) é um programa Java puro (Java 21+, sem Gradle/Maven para
rodar — só java -jar), entregue à equipe do ERP como exemplo funcional. Ele não puxa API nem
faz polling: consulta o SQLite local que o PowerSync mantém atualizado em tempo real.
Chegam ao cliente exatamente as cinco tabelas espelho: contratos, propriedades, fones,
itensped, estoque (linhas com deleted = false) — ou seja, o pedido completo pronto para o
X-Adm: cabeçalho + itens + cliente + contato + produtos.
Fluxo no cliente:
- Watch reativo no SQLite: linhas novas/alteradas (estado desejado).
- Para cada uma: marca "em processamento", grava no X-Adm — hoje um
TODO X-Adm(simulaXadm); a gravação real no ZIM é desenvolvida depois, pela equipe X-Adm. - Devolve o resultado: write-back por upload do PowerSync (o SDK envia a alteração local de volta
ao endpoint de upload, que persiste
CodRetorno/MsgRetornoe asChave*nas tabelas do Integrador). Offline-first: se a rede cair, o upload fica enfileirado e reenvia.
Máquina de status (códigos de retorno X-Adm — MAXSUL2025_610110)¶
O status de cada registro segue os códigos de retorno do X-Adm (000 novo → 001/002/003
em processamento → 006 gravado; 1XX erro terminal; 9XX erro de processamento). Tabela completa
em Mapeamento §6.
Nota — exemplo atual × modelo final. O
ps-javaatual sincroniza uma tabela únicarequisicoescom statusRECEBIDO/EM_PROCESSAMENTO/CONCLUIDO_*, para provar o mecanismo de sync/write-back. O modelo final são as cinco tabelas acima com os códigos de retorno — alinhar ops-javaa esse modelo é trabalho da fase 2.
6. Parte técnica (referência)¶
6.1 Modelo de dados¶
O modelo completo — as duas famílias (espelho do X-Adm, dono Integrador; pied_*, dono maxsul-pied),
com schema, tipos, chaves e relacionamentos — está em Modelagem. A transformação
de uma família na outra (de-para, regras de negócio, códigos de retorno) está em
Mapeamento e regras. Postgres 18 (uuidv7() nativo).
6.2 Contrato de entrada (PIED) — validado¶
Endpoints REST, envelope, paginação, incremental e webhook (payloads, eventos, lacunas, segurança) em API da PIED.
6.3 Contrato de saída (Integrador) — reaproveitado¶
PUT/DELETE /api/v1/xadm, Bearer, sempre HTTP 200 para JSON válido, resultado no corpo — o mesmo
contrato do agente on-premises do X-Adm. Endpoints, semântica DELETE→PUT, código Origem e corpo
por tabela em API do Integrador.
6.3.1 Gravação no ZIM (X-Adm)¶
O X-Adm roda sobre ZIM, cujo modelo de dados difere de um Postgres comum (Character de largura
variável, VastInt escalado, máscara ≠ valor armazenado). Os campos viajam tipados no pipeline e a
formatação para o ZIM é feita no cliente, na fronteira com o zimrtmu. Tipos, regras de
formatação por campo e armadilhas em Tratamento de dados no ZIM — é o
ponto de maior risco da integração.
6.4 Idempotência e ordem¶
- Raw persistido antes de processar; reprocessar é seguro.
- Transform idempotente por content-hash (só reescreve em mudança real → sem sync inútil).
- Apply no Integrador serializado por
ReentrantLock(ordemDELETE→PUT, sem deadlock40P01). - Overlap webhook × poll é seguro por chave natural (código do pedido/produto, documento do cliente).
6.5 Segurança¶
- Webhook: segredo compartilhado opcional (header
X-Pied-Secret, a confirmar com a PIED) + possível IP allowlist no Traefik; HTTPS obrigatório; payload com PII mascarado em log. - REST ao Integrador: Bearer estático (env do Coolify).
- PowerSync: JWKS
auth.xadm.biz, audiencemaxsul; credencial do Postgres só porPS_PG_URI.
6.6 Deploy e operação¶
Coolify por subdomínio (pied. / int. / ps. maxsul.xadm.biz), imagens multi-stage,
GET /health. Toggles e passo a passo em operacao/configuracao.md.
6.7 Qualidade¶
- Cliente REST/paginação com WireMock (mock da API PIED).
- Webhook ponta a ponta (
@MicronautTest+ Postgres via PostgresTestResource): segredo, toggles, corpo arbitrário. - Checkstyle X-Adm no gate (
./gradlew check, CI Forgejo).
6.8 Riscos e pendências¶
| Risco / lacuna | Situação |
|---|---|
| A lista decisão-a-decisão (cada item com alternativas e recomendação) está em | |
| Pendências. Resumo dos principais riscos: |
| Risco / lacuna | Situação |
|---|---|
Colunas de venda + fones no modelo X-Adm do Integrador |
Aditivo ao molde compartilhado; delta de colunas a confirmar com o Integrador (Modelagem §1). |
| Campo que marca "pedido finalizado" na PIED | Bloqueia o quando enviar (INSERT-only); definido a partir do raw da Fase 1 (§3.3). |
De-para restante (ClassForma, Prazo, EAN13, rateio de frete) |
Regra/confirmação — Pendências. |
| Header do segredo do webhook / retry da PIED | A confirmar com a PIED (API PIED §3.2). Token de produção é variável do Coolify. |
ps-java ainda no modelo de exemplo (requisicoes) |
Alinhar às 5 tabelas + códigos de retorno na fase 2. |
Crescimento do landing pied_* |
Política de retenção decidida na fase 2, com volumes medidos. |
7. Como esta doc se destila nos outros repositórios¶
A documentação completa vive aqui. Cada repositório carrega a fatia dele e aponta para cá:
| Repositório | O que documentar lá | Aponta para |
|---|---|---|
| Integrador | As colunas de venda + a tabela fones no modelo X-Adm compartilhado (P1, de Modelagem §1), a fonte via /api/v1/xadm, e o registro do origem INT-PIED. |
esta doc (§2), API Integrador, Pendências |
| maxsul-pied (aqui) | Webhook, poll, transform, tabelas pied_*, UI; modelo em Modelagem, transform em Mapeamento. |
— (é a doc-mãe) |
| maxsul-powersync | Sync rules, replicação, runbook (já existe). | esta doc (§4) |
| ps-java | Cliente de exemplo; simulaXadm; alvo = 5 tabelas + códigos de retorno; formatação para o ZIM (integracao-zim). |
esta doc (§5) |
8. Apêndice — histórico¶
Decisões:
- 0001 — MVP entra em produção só capturando raw
- 0002 — Caminho robusto (servidor always-on)
- 0003 — Valores tipados no pipeline, formatação ZIM no cliente
- 0004 — MODO staging: gate manual da entrega ao Integrador
- 0005 — Alerta por e-mail no erro terminal do pedido
- 0006 — Painel colaborador: dashboard e detalhe abertos para o usuário Maxsul
- 0007 — Saneamento de texto para o X-Adm: sem diacríticos e caixa alta
- 0008 — Adota xadm-comum-web (base transversal da casa) e desliga o security transitivo
- 0009 — Cliente do transform vem da aba Faturamento (invoice), não do Integrador (company)
- 0010 — Visibilidade de pagamento: parcial travado e cancelamento pós-import
- 0011 — int-pied native-ready (GraalVM); imagem/deploy na fábrica
- 0012 — Convergência da entrega ao outbox da casa e adoção do micronaut-data
- 0013 — Testes de integração sem SQL cru, sobre a fixture da casa
- 0014 — Guarda de cluster (advisory lock) para os @Scheduled
- 0015 — CI/CD 100% GitHub Actions (adoção local do ADR central 0027)
- 0016 — Watchdog de silêncio de webhook (alerta por inatividade)
- 0017 — Enriquecimento sob demanda do pedido preso na fila
- 0018 — Recência do evento e normalização incremental
- 0019 — Adota o kit de UI X-Adm (JTE) — ponteiro do ADR central 0025
- 0020 — Adota o smoke de produção pós-deploy — ponteiro do ADR central 0031
- 0021 — Reconciliação por remessa e re-arme na ordem certa — ponteiro do manifesto
- 0022 — Persistência por Micronaut Data: o critério lote × CRUD
- 0023 — Apagão de webhook: watchdog comercial e resgate da 1ª captura já paga