Contrato do fluxo ENCERRA_MDFE¶
Status: Rascunho · Responsável: Gustavo Madruga · Atualizado em: 2026-07-30
Contrato do fluxo encerramento de MDF-e do integrador-client (cliente coringa): o laço de
baixa automática de MDF-e por macro Sascar termina no ERP aqui — o int-sascar detecta o fim da
viagem, o integrador-server grava um comando de baixa pendente no espelho, o cliente manda a
ChaveNota do MDF-e ao ZIM via pAbast (com o literal ENCERRA_MDFE) e o ZIM encerra o
MDF-e. A responsabilidade do cliente termina no sucesso (006); a confirmação I→A
(NFEEvento.SitEvento=A) é feita no servidor (fora do cliente).
Status: rascunho — a definição do dXpEnvio/dXpRetorno chegou da equipe X-Adm (2026-07-30, arquivos em
docs/anexos/privado/mdfe/+ msgs do Diego); este documento a formaliza no modelo do pied (divergências no retorno: casa por contador — fora de ordem/parcial — e alerta no erro terminal, §4). O retorno já rodou em produção (2026-08-04): o ZIM não escreveIniciou Zimno arquivo final (marcador virou opcional — §4/§5). Falta as sync rules (010) exporemmdfao cliente.
1. O que vale daqui e o que vem do contrato base¶
Este fluxo segue o MODELO COMUM (pied); o formato dos arquivos (fixed-width, windows-1252,
CRLF, contador(3) + tipo_registro(20)), handshake Iniciou Zim, retorno com eco +
cod_retorno, códigos 006/1XX/9XX e exit codes vêm do contrato base
§§1-2 e 4-6. Muda o quê vai na linha (tabela mdf, único campo ChaveNota) e o retorno
(§4).
- Linha de continuação (handshake §1 passo 5):
ENCERRA_MDFE— é como o ZIM sabe que o lote é de encerramento de MDF-e (anexada pelo cliente ao final do envio, como oIMPORTA_PIEDdo pied). - Tabela única
mdf, um campo (chave_nota). Otipo_registroémdf; o único campo de dados é a ChaveNota. Envio idêntico ao modelo do pied. - Divergências no retorno (§4): casa pelo valor do contador — pode vir fora de ordem
(o X-Adm reordena por filial) e parcial (o ZIM aborta num conflito; as linhas que não
voltarem retentam). E um
1XXdispara alerta (GlitchTip). O pied é estrito e sem alerta.
Decisão 2026-07-30: a amostra crua do ZIM (
docs/anexos/privado/mdfe/) vinha diferente (sem contador, sem tipo, retorno textualOK/ERROcom a chave ecoada e um cabeçalho de lote). Aceitou-se seguir o modelo do pied — o ZIM alinha o lado dele; os arquivos crus ficam como referência ao lado dos exemplos no padrão.
2. Arquivo de envio — tabela mdf (largura total da linha: 42)¶
Linha = contador(3) + tipo_registro(20) = mdf + chave(19). Offsets a partir de 0 (o campo
1 de dados começa no offset 23), como no contrato base §3.
| # | Campo | Tipo | Largura | Offset | Observação |
|---|---|---|---|---|---|
| 1 | chave_nota | texto | 19 | 23 | ChaveNota já formatada pelo espelho — o cliente copia os 19 caracteres verbatim (ex. 1 0R 34501$ U 1); espaços à esquerda/embutidos preservados byte a byte |
ChaveNota já formatada (decisão 2026-07-30): o espelho (integrador-server, tabela
mdf, colunachave_notaCHAR(19)sem trim na gravação) entrega a string de 19 posições pronta; o cliente não monta a chave a partir de campos — só a copia.
3. Espelho (schema PowerSync)¶
Tabela mdf do espelho (integrador-server 009, migration V24__mdfe_espelho.sql). O
fluxo declara em SchemaTabelas só o que usa: chave_nota (a ChaveNota que vai ao ZIM) +
cod_retorno/msg_retorno (status). As demais colunas do espelho (situacao_baixa — ATIVO/
BAIXA/ERRO, própria do Integrador — e o contexto do encerramento) ficam fora (padrão 001-R1) —
coluna não declarada não sincroniza. O comando pendente é cod_retorno='000' (009 item 12).
4. Arquivo de retorno¶
Mesmo layout do pied (contrato base §4): o marcador Iniciou Zim é
opcional (o ZIM o escreve no handshake e depois LIMPA o dXpRetorno, regravando o retorno
final SEM ele — o normal é o arquivo começar direto nos registros); se aparecer, é descartado.
Cada linha processada = contador(3) (eco) + tipo_registro(20) (eco = mdf) + cod_retorno(3)
+ msg_retorno (resto). Casamento por contador (§4 abaixo). Sem cabeçalho de lote e sem chave
ecoada — a chave_nota de cada linha vem do envio na posição do contador.
Incidente 2026-08-04: a 1ª entrega do encerra-mdfe rejeitava TODO retorno porque exigia
Iniciou Zimna 1ª linha do arquivo final — que o ZIM não escreve (ele limpa e regrava). O X-Adm dava baixa com006, mas o cliente marcava lote inválido e reenviava sem parar (loop apertado, sem backoff). Corrigido: marcador opcional na leitura + o {@code Agendador} desacelera quando um lote deixa pendência (não reprocessa no eco das próprias write-backs). VerCHANGELOG(Não lançado).
| # | Campo | Tipo | Largura | Offset | Conteúdo |
|---|---|---|---|---|---|
| 1 | contador | numérico 1..999 | 3 | 0 | ECO do contador da linha de envio (à direita) |
| 2 | tipo_registro | texto | 20 | 3 | ECO do tipo (mdf) |
| 3 | cod_retorno | texto | 3 | 23 | código de retorno (§5 abaixo) |
| 4 | msg_retorno | texto | resto | 26 | mensagem (motivo do sucesso ou do erro) |
Retorno CASA-POR-CONTADOR: fora de ordem e parcial (Diego/Gustavo, 2026-07-30)¶
Diferente do pied (uma linha por linha, na MESMA ordem), o retorno do MDF-e casa pelo valor do contador ecoado — cada linha aplica na posição do envio dada pelo seu contador, então pode vir:
- Fora de ordem — o X-Adm às vezes reordena (ex. por filial): os contadores chegam embaralhados
(
…13, 15, 16, 14, 17). O contador diz a posição; a ordem no arquivo não importa. - Parcial — num conflito o ZIM aborta o resto: só um subconjunto dos contadores volta. As linhas cujo contador NÃO voltou (ausência) são consideradas não processadas e voltam a pendente para retentar.
Exemplo do Diego: envio 10 linhas; o ZIM devolve
006nas 3 primeiras,999(conflito) na 4ª e para. Resultado: 1–3 encerram (006), a 4ª retenta (999), 5–10 sem resposta retentam. Inválido: sobrar linha, contador fora de 1..N, contador repetido, tipo ecoado ≠ o do envio naquela posição. Implementação:Fluxo.getRetornoCasaPorContador()(só o MDF-e) +Processadorrestaura as posições não cobertas. Retry por LOTE segue para falha geral (timeout/exit≠0).
Alerta no erro terminal (Diego/Gustavo, 2026-07-30)¶
Um 1XX (erro terminal) no encerramento dispara um evento GlitchTip/Sentry (nível ERROR) com a
empresa (tag cliente, já global) e o contexto (a chave_nota + a mensagem do ZIM) — o
alerta do GlitchTip manda o email para avaliação humana. Só o MDF-e
(Fluxo.getAlertaNoErroTerminal()); nos demais fluxos o 1XX fica só no cod_retorno/log.
Códigos de retorno (os mesmos do pied — contrato base §5)¶
O cod_retorno é numérico: o ZIM mapeia o resultado do encerramento (o OK/ERRO da
amostra crua) para o código, e o cliente aplica pela máquina de status, sem interpretar texto.
| Código | Significado | Ação do cliente |
|---|---|---|
000 |
pendente (comando de baixa gravado pelo servidor na mdf) |
entra no próximo lote |
001 |
em processamento (lote em voo neste cliente) | — (estado transitório) |
006 |
encerrado com sucesso | encerra o registro; o servidor marca NFEEvento.SitEvento=A (fora do cliente [L4]) |
1XX |
erro terminal (ex. MDF-e não autorizado) | encerra com erro; não retenta ("quando não deve mais nem tentar" — Diego) |
9XX |
reprocessável (transitório) | retenta no próximo ciclo ("quando não voltar nada daquela chave" — Diego) |
000e001são estados do espelho na nuvem — nunca aparecem no arquivo de retorno. Retry de falha GERAL (timeout/exit≠0/retorno malformado) é por LOTE (todos voltam a pendente); retry por AUSÊNCIA (linha não devolvida no retorno parcial) é por LINHA (só as que faltaram) — ver §4.
5. Pontos em aberto (validar com a equipe X-Adm / ZIM)¶
A equipe X-Adm segue o modelo do pied no lado ZIM. A amostra crua vinha diferente (sem
contador, sem tipo, retorno textual OK/ERRO com a chave ecoada e um cabeçalho de lote); tudo
isso foi alinhado ao padrão — a amostra é referência, não o formato-alvo. O ZIM escreve o
retorno como o pied: contador + tipo(mdf) + cod_retorno numérico + msg, sem cabeçalho de lote.
~~Marcador
Iniciou Zimna 1ª linha do retorno final~~ RESOLVIDA (Diego/X-Adm, 2026-08-04): o marcador é só o sinal de progresso do handshake — o ZIM o escreve ao subir e depois limpa odXpRetornoe regrava o final SEM ele, em toda a família ZIM (xposto/pied/ mdfe). O cliente não o exige mais no retorno final (é opcional): ver §4 e o incidente 2026-08-04.
- ~~Forma do comando/espelho~~ RESOLVIDA: o
009(migrationV24) criou a tabelamdfcomchave_nota/situacao_baixa/cod_retorno/msg_retorno; o comando pendente écod_retorno='000'namdf. §2/§3 usam os nomes reais. Falta só as sync rules (010) exporemmdfao PowerSync do cliente (ainda não nopowersync.yamlda maxsul). - ~~Granularidade do retry / ordem do retorno~~ RESOLVIDA (Diego/Gustavo): retorno casa
por contador — fora de ordem e parcial; as linhas que não voltarem retentam por linha (§4). Só o
MDF-e (
getRetornoCasaPorContador); pied/abastecimento seguem estritos.
6. Exemplo¶
Envio (3 registros — a linha ENCERRA_MDFE é anexada pelo cliente no handshake; · = espaço):
··1mdf·················1·0R·34501$·U····1
··2mdf·················1·0R·34525$·U····1
··3mdf·················1·4R··7808$·U····1
ENCERRA_MDFE
Retorno final correspondente (o ZIM já limpou o marcador Iniciou Zim, então começa direto nos
registros) — igual ao pied: contador + tipo(mdf) + cod_retorno + msg (a chave de cada linha
vem do envio na posição do contador):
··1mdf 006MDF-e 34501 já foi encerrado anteriormente. Processo Cancelado.
··2mdf 006Encerramento do MDFe realizado com sucesso.
··3mdf 106MDF-e não autorizado. Não permite ser encerrado
Exemplos completos (14 registros) em docs/anexos/privado/mdfe/:
dxpenvio-exemplo/dXpRetorno-exemplo— o padrão canônico (retorno na ordem do envio);dxpenvio/dXPRetorno— demo da equipe X-Adm com o retorno REORDENADO (os contadores chegam…13, 15, 16, 14, 17— a linha 14 depois da 15/16), provando o casamento por contador;codigos.txt— a tabela de códigos (§5).
O padrão-alvo é o que a equipe X-Adm segue; o cliente casa por contador (§4), então a ordem do arquivo não importa.