Pular para conteúdo

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 escreve Iniciou Zim no arquivo final (marcador virou opcional — §4/§5). Falta as sync rules (010) exporem mdf ao 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 o IMPORTA_PIED do pied).
  • Tabela única mdf, um campo (chave_nota). O tipo_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 1XX dispara 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 textual OK/ERRO com 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, coluna chave_nota CHAR(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 Zim na 1ª linha do arquivo final — que o ZIM não escreve (ele limpa e regrava). O X-Adm dava baixa com 006, 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). Ver CHANGELOG (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 006 nas 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) + Processador restaura 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)

000 e 001 sã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 Zim na 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 o dXpRetorno e 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 (migration V24) criou a tabela mdf com chave_nota/situacao_baixa/cod_retorno/msg_retorno; o comando pendente é cod_retorno='000' na mdf. §2/§3 usam os nomes reais. Falta só as sync rules (010) exporem mdf ao PowerSync do cliente (ainda não no powersync.yaml da 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.