Distribuição de NF-e

Visão geral

O workflow nfe-distribution consulta documentos fiscais eletrônicos de interesse de cada cliente habilitado. Ele não acessa a SEFAZ diretamente: envia o ultNSU atual ao proxy NF-e da Toterus, interpreta a resposta retDistDFeInt e persiste tentativas, documentos e o novo cursor no NFE_DB.

A execução pertence a apps/workflows, usa a implementação de packages/nfe-distribution-workflow e roda pelo schedule 0 7 * * *. Também pode ser disparada manualmente pelo monitor. O parâmetro maxCallsPerClient limita cada drenagem entre 1 e 500 chamadas, com padrão 100.

Fluxo de execução

flowchart TD; Trigger{Schedule nativo ou ação manual}; Trigger --> Instance[Instância Cloudflare Workflow]; Instance --> Run[runInstrumented: nfe-distribution]; Run --> Load[Carregar clientes elegíveis]; Load --> Lease[Adquirir lease por cliente]; Lease --> Request[Proxy Toterus: consultar com ultNSU]; Request --> Parse[Interpretar retDistDFeInt]; Parse --> Decision{Classificar resposta}; Decision -->|138 ou 137 válidos| Persist[Persistir tentativa e documentos]; Persist --> NfeDb[(NFE_DB)]; Persist --> Cursor[Avançar current_ult_nsu]; Cursor --> More{Há mais NSUs e limite disponível?}; More -->|Sim| Request; More -->|Não| Release[Liberar lease]; Decision -->|656| Block[Registrar bloqueio sem avançar cursor]; Decision -->|Falha ou anomalia| Failure[Registrar falha sem avançar cursor]; Block --> NfeDb; Failure --> NfeDb; Block --> Release; Failure --> Release; Run -. eventos .-> Telemetry[(DB: workflow runs e eventos)]; Telemetry --> Monitor[Monitor]; Release --> Result[Concluir cliente e run];

  1. Carrega clientes habilitados, não bloqueados e disponíveis para execução.
  2. Sinaliza clientes sem resposta há 70 dias como risco de retenção.
  3. Adquire um lease de 15 minutos por cliente para impedir drenagens concorrentes.
  4. Lê current_ult_nsu e solicita uma página ao endpoint /nfe/distribuicao do proxy.
  5. Interpreta cStat, xMotivo, ultNSU, maxNSU e até 50 elementos docZip.
  6. Persiste a tentativa e os documentos antes de atualizar o cursor.
  7. Continua enquanto a resposta permitir avanço, ainda houver NSUs pendentes e o limite de chamadas não tiver sido atingido.
  8. Libera o lease mesmo quando uma chamada falha.

Cada página usa uma chave de tentativa formada pelo run, cliente e ultNSU solicitado. Se um step for repetido, uma tentativa já persistida é reutilizada em vez de consultar e gravar a mesma página novamente.

Decisões por resposta

RespostaComportamento
cStat=138Há documentos localizados. Exige ultNSU maior que o solicitado, persiste a página, avança o cursor e continua quando ultNSU < maxNSU.
cStat=137Não há novos documentos. Exige ultNSU = maxNSU, persiste a tentativa, avança o cursor e encerra o cliente.
cStat=656Consumo indevido. Registra o bloqueio por uma hora, preserva o cursor e encerra o cliente nesta execução.
Outro cStatRegistra falha, preserva o cursor e encerra o cliente.

Falhas de transporte, HTTP não 2xx, XML inválido, campos de cursor ausentes, NSU regressivo e documentos com o mesmo NSU mas conteúdo diferente também preservam current_ult_nsu. Essa regra impede que uma página seja perdida sem ter sido persistida.

O que existe dentro de docZip

Na resposta de distribuição, cada docZip possui os atributos NSU e schema. Seu conteúdo é um XML compactado com gzip e depois codificado em Base64. Portanto, schema identifica o XML obtido depois de decodificar o Base64 e descompactar o gzip.

SchemaDocumento recuperado
procEventoNFe_v1.00.xsdEvento processado da NF-e. Referencia uma nota, mas não contém o XML completo da nota fiscal.
procNFe_v4.00.xsdNF-e completa processada, formada pelo XML da nota e seu protocolo de autorização.
resNFe_v1.01.xsdResumo da NF-e, com um conjunto limitado de dados de identificação e valores.

Um procEventoNFe_v1.00.xsd com tpEvento=210200 representa Confirmação da Operação, um evento de manifestação do destinatário. A presença de chNFe somente vincula o evento à NF-e; ela não transforma o documento de evento no conteúdo da nota.

Classifique antes de consumir

Não trate todo docZip como NF-e completa. Leia schema e, para eventos, tpEvento antes de decidir como apresentar ou processar o XML.

O workflow atual não descompacta nem interpreta o XML interno. Ele guarda NSU, schema e o docZip original em Base64 para consumo posterior.

Manifestação do destinatário

A manifestação do destinatário é um evento oficial enviado à SEFAZ pela empresa que consta como destinatária da NF-e. Uma pessoa autorizada, o sistema fiscal ou o ERP da empresa pode realizar essa ação, mas o evento precisa ser assinado com o certificado digital cujo CNPJ-base corresponde ao destinatário.

CódigoManifestaçãoSignificado
210200Confirmação da OperaçãoConfirma que a operação e, quando aplicável, o recebimento da mercadoria ocorreram. É uma declaração conclusiva e impede o emitente de cancelar a NF-e.
210210Ciência da Operação ou Ciência da EmissãoDeclara que o destinatário sabe que a NF-e existe, mas ainda não possui elementos para concluir se a operação ocorreu. É uma manifestação preliminar e deve ser seguida posteriormente por uma manifestação conclusiva.
210220Desconhecimento da OperaçãoDeclara que o destinatário não solicitou nem reconhece a operação. Essa opção não libera a NF-e completa para download.
210240Operação não RealizadaReconhece a participação do destinatário, mas declara que a operação não foi concluída, como em uma recusa de recebimento. Exige justificativa.

Antes da manifestação, o Ambiente Nacional normalmente distribui ao destinatário apenas o resNFe, sem os produtos da nota. Depois de uma manifestação aceita diferente de Desconhecimento da Operação, a SEFAZ disponibiliza a NF-e completa desta forma:

  1. O destinatário recebe um resNFe com a chave e os dados resumidos.
  2. Uma pessoa ou sistema autorizado envia a manifestação pelo serviço NFeRecepcaoEvento.
  3. O Ambiente Nacional registra o evento e gera um novo NSU referenciando a NF-e completa.
  4. Uma consulta posterior ao NFeDistribuicaoDFe recupera um novo docZip com schema procNFe.
  5. O resNFe original permanece armazenado; ele não é substituído pelo documento completo.

Não confirme apenas para liberar o XML

Confirmação da Operação possui efeito fiscal e declara que a operação realmente ocorreu. Quando a empresa ainda precisa analisar a nota ou a entrega, Ciência da Operação é a manifestação preliminar apropriada para liberar o XML, devendo depois ser seguida pela manifestação conclusiva correspondente.

O workflow atual apenas consulta e persiste a distribuição. Ele não envia manifestações. Portanto, os documentos completos encontrados no NFE_DB foram liberados porque uma pessoa ou outro sistema manifestou essas notas, ou porque o CNPJ consultado tinha outro papel autorizado na NF-e, como transportador ou terceiro informado em autXML.

Persistência e observabilidade

O banco compartilhado DB guarda workflow_runs e workflow_run_events. O banco fiscal NFE_DB guarda:

  • nfe_clients: configuração, cursor, bloqueio, lease e último resultado por cliente.
  • nfe_distribution_attempts: requisição, resposta, status e cursores de cada página.
  • nfe_documents: NSU, schema e docZip vinculados à tentativa.
  • nfe_import_daily_counts: total diário de documentos inéditos por cliente.

O monitor mostra clientes, runs, steps e diagnósticos do proxy. A API entrega os documentos persistidos por GET /nfe/distributions, com autenticação obrigatória, escopo definido por clienttoken e paginação por cursor do maior NSU para o menor. O tamanho da página permanece limitado a 200 documentos. Esse endpoint ignora a cota de taxa/janela de requisições da chave de API; porém, quando a chave possui uma cota finita de usos restantes, cada chamada ainda consome essa cota, e seu limite continua sendo aplicado, retornando 429 após o esgotamento. Cursores criados antes da ordenação decrescente devem ser descartados.

Download autenticado de XML e DANFE

Os downloads usam o identificador retornado por GET /nfe/distributions:

GET /nfe/{documentId}/xml?clienttoken={clienttoken}
GET /nfe/{documentId}/danfe?clienttoken={clienttoken}

As duas rotas exigem a autenticação principal da API e um clienttoken válido. A busca combina o identificador do documento com o cliente resolvido pelo token; por isso, um documento desconhecido e um documento pertencente a outro cliente recebem a mesma resposta 404. Assim como a listagem, os downloads ignoram apenas a cota de taxa/janela da chave de API. Uma cota finita de usos restantes continua sendo consumida e retorna 429 quando se esgota.

O download XML decodifica o Base64 e descompacta o gzip sob demanda, valida UTF-8 e XML, e devolve exatamente os bytes descompactados. Ele aceita documentos completos procNFe, resumos resNFe e eventos procEventoNFe. A resposta usa application/xml; charset=utf-8, anexo <chave-de-acesso-ou-NSU>.xml e Cache-Control: private, no-store.

O DANFE aceita somente um procNFe completo, contendo nfeProc/NFe/infNFe. Resumos, eventos e documentos procNFe incompletos retornam 422; eventos posteriores não são agregados à nota. O XML é enviado transitoriamente ao Browser Run, o PDF é validado e devolvido como application/pdf, anexo danfe-<chave-de-acesso-ou-NSU>.pdf e Cache-Control: private, no-store.

Saída transitória

XML e DANFE são gerados novamente a cada requisição. A API não grava o XML descompactado nem o PDF em D1, R2, KV ou no sistema de arquivos.

O renderer global aceita no máximo cinco DANFEs simultâneos. A sexta tentativa não entra em fila: recebe 503, Retry-After com segundos inteiros positivos e pode ser repetida pelo cliente depois desse intervalo. Cada render possui limite de 20 segundos; o XML descompactado pode ter no máximo 10 MiB e o PDF, 20 MiB. O retorno só é aceito quando começa com a assinatura %PDF-.

HTTPCorpo JSON estávelQuando ocorre
400{ "error": "Invalid clienttoken" }clienttoken ausente, vazio, desconhecido ou ambíguo.
401{ "error": "Unauthorized" }Credencial principal ausente, inválida, desabilitada ou expirada.
404{ "error": "NF-e document not found" }Documento desconhecido ou pertencente a outro cliente.
422{ "error": "DANFE unavailable for this document type" }A rota DANFE recebeu resumo, evento ou procNFe incompleto.
429{ "error": "Too many requests" }A cota finita de usos restantes da chave de API foi esgotada.
500{ "error": "Invalid stored NF-e document" }O docZip persistido não pode ser decodificado, descompactado ou validado dentro dos limites.
502{ "error": "DANFE generation failed" }O Browser Run ou o renderer falhou, rejeitou o PDF ou encerrou inesperadamente.
503{ "error": "DANFE renderer busy" }Os cinco slots globais estão ocupados ou o Browser Run informou indisponibilidade; inclui Retry-After.
504{ "error": "DANFE generation timed out" }A geração excedeu 20 segundos.

Como diagnosticar uma execução

  1. Abra o run de nfe-distribution no monitor e identifique o cliente e o requestedUltNsu do step que falhou.
  2. Consulte workflow_run_events para ver classificação, avanço de cursor e diagnóstico do proxy.
  3. Confira nfe_clients.current_ult_nsu, blocked_until, last_error_code e last_error_message.
  4. Localize a página em nfe_distribution_attempts pelo run, cliente e requested_ult_nsu.
  5. Confira os documentos relacionados em nfe_documents antes de considerar qualquer ajuste manual de cursor.

Um run com processedClients: 0 pode ser saudável quando nenhum cliente está elegível. last_error_code=656 com blocked_until no futuro indica bloqueio temporário por consumo indevido, não um workflow travado.

Referência fiscal