0015 — Baixa de MDF-e por macro Sascar: espelho + watch + callback¶
Status: Aprovado · Responsável: Gustavo Madruga · Atualizado em: 2026-07-30 · Decidido em: 2026-07-30
Contexto. O X-Adm passou a emitir MDF-e e o negócio quer encerrá-la sozinha quando o motorista fecha uma macro na Sascar. Quem detecta a macro é o int-sascar; quem encerra a MDF-e no ERP é o integrador-client (ZIM). Faltava a ponte no Integrador: espelhar a MDF-e, dizer ao int-sascar quais vigiar, receber o encerramento e virar um comando que o client execute. O como está em baixa-mdfe-sascar; aqui ficam as escolhas e o preterido.
Decisão. Quatro capacidades aditivas ao espelho, sem quebrar o fluxo PIED.
- Reusar
PUT /api/v1/xadm(não rota nova) para oMDFE_PUT(Origem:PMDFE). Mesmo contrato de resposta (200 comresultado). Ingest tipado por tabela (como o resto): 7 tabelas novas (mdf/mdfcompl/nfeevento/mdfitens/nfmdf/nfcomplmdf/fretes),municipio/propriedadesreusadas. Preterido:/api/v1/mdfededicado — duplicaria auth/erro/request sem ganho. - Comando de baixa em coluna própria na
mdf, não tabela nova:situacao_baixa(ATIVO/BAIXA/ERRO) +cod_retorno/msg_retorno(contrato do V18) + proveniência (id_pacote/data_pacote/motivo). Preterido: tabelamdfe_comando(mais junção, sem ganho) e sobrecarregarNFEEVENTO(que fica fiel ao X-Adm, só leitura). Ocod_retornoé o sinal que oENCERRA_MDFEobserva; a coluna própria dá visibilidade humana. - Watch de saída =
@Clientdeclarativo +@Retryable(padrão do poke 008), best-effort em virtual thread, idempotente. Corpo destilado (o int-sascar nunca vê JSON cru do ZIM): status já interpretado, destino resolvido por discriminante NFe(55)×CTe(57).status=encerrado, nãoDELETE: achave_notatem$/+e não cabe em path. Oencerradosó dispara na transição (marcadorencerrado_em), independente dasituacao_baixa— o watchabertovigia a placa de toda MDF-e aberta, então todo encerramento precisa mandá-lo parar. Revisão 2026-07-31: o gatesituacao=BAIXAcaiu — a baixa comum é encerrar no X-Adm primeiro (ficaATIVO, sem macro Sascar), e aí basta parar de vigiar; receberencerradosem baixa pendente não alarma o int-sascar (só remove domdfe_vigiado). - Callback
POST /api/sascar/encerramento(path literal, não/api/v1/). Sempre 2xx (o int-sascar é dono do retry): chave inexistente = no-op, não 404 (404 giraria o retry infinito); reenvio = no-op idempotente (só a transiçãoATIVO→BAIXAgrava). - Auth do callback = Bearer único da casa. O
outbound_tokenque o int-sascar envia é oINTEGRADOR_API_TOKEN(o Bearer de/api/**, viaApiBearerSecurityRule). Preterido: um 2ºTokenValidatorpara um token separado — dariaROLE_APIglobal ao token do int-sascar (rebaixaria a segurança de todas as rotas/api). Um Bearer, um validador. - Entrega PowerSync = recorte mínimo
mdfno bucketglobal(sem bucket novo). Amdfcarrega tudo que oENCERRA_MDFElê; oSchemaTabelasdo client segue o espelho. Decisão do rollout (2026-07-30): não editar osconfig.yamlde cliente neste PR — só documentar o recorte; a edição por cliente é operação de rollout.
Consequências. MDF-e ativa aparece ATIVO, é vigiada, e — fechada a macro — vira BAIXA/000;
o client executa e escreve 006; o re-PUT com NFEEVENTO(110112,A) dispara o watch encerrado.
Chaves empacotadas do ZIM: literal na gravação, TRIM só na consulta (matching do callback e das
junções depende disso). Segredos (SASCAR_INBOUND_TOKEN) só por ENV (§1.9).
Fora de escopo: detector de macro/registry (int-sascar); ENCERRA_MDFE no ZIM
(integrador-client); geo/shadow; tela de baixas; espelhar MOTORISTAS/FRETESMOT.