@bitize/bitmde 0.18.0 → 0.20.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (61) hide show
  1. package/CHANGELOG.md +64 -0
  2. package/README.md +309 -4
  3. package/dist/apis/distribuicaoDFe-api.d.ts +13 -1
  4. package/dist/apis/distribuicaoNFSe-api.d.ts +176 -0
  5. package/dist/apis/manifestacaoNFSe-api.d.ts +130 -0
  6. package/dist/apis/recepcaoEvento-api.d.ts +5 -1
  7. package/dist/controllers/distribuicaoDFe-controller.d.ts +5 -1
  8. package/dist/controllers/distribuicaoNFSe-controller.d.ts +50 -0
  9. package/dist/controllers/index.d.ts +84 -0
  10. package/dist/controllers/manifestacaoNFSe-controller.d.ts +38 -0
  11. package/dist/controllers/recepcaoEvento-controller.d.ts +5 -1
  12. package/dist/env/ca.d.ts +8 -2
  13. package/dist/env/index.d.ts +5 -1
  14. package/dist/env/nfse.d.ts +32 -0
  15. package/dist/helpers/distribuicaoDFe-helper.d.ts +4 -0
  16. package/dist/helpers/distribuicaoNFSe-helper.d.ts +107 -0
  17. package/dist/helpers/index.d.ts +158 -0
  18. package/dist/helpers/manifestacaoNFSe-helper.d.ts +80 -0
  19. package/dist/helpers/recepcaoEvento-helper.d.ts +4 -0
  20. package/dist/helpers/retorno-helper.d.ts +5 -0
  21. package/dist/helpers/retornoNfse-helper.d.ts +44 -0
  22. package/dist/index.d.ts +13 -1
  23. package/dist/schemas/index.d.ts +17 -0
  24. package/dist/schemas/manifestacaoNFSe-schema.d.ts +33 -0
  25. package/dist/services/sefaz-service.d.ts +8 -1
  26. package/dist/util/assinatura.d.ts +3 -1
  27. package/dist/util/gzip.d.ts +7 -0
  28. package/dist/util/index.d.ts +2 -1
  29. package/dist/validators/chaveNfse-validator.d.ts +14 -0
  30. package/dist/validators/cnpj-validator.d.ts +19 -0
  31. package/dist/validators/index.d.ts +50 -0
  32. package/dist/validators/motivoRejeicao-validator.d.ts +23 -0
  33. package/dist/validators/nsuNfse-validator.d.ts +20 -0
  34. package/lib/apis/distribuicaoNFSe-api.js +59 -0
  35. package/lib/apis/manifestacaoNFSe-api.js +69 -0
  36. package/lib/controllers/distribuicaoNFSe-controller.js +16 -0
  37. package/lib/controllers/index.js +3 -1
  38. package/lib/controllers/manifestacaoNFSe-controller.js +25 -0
  39. package/lib/env/ca.js +3 -2
  40. package/lib/env/index.js +12 -1
  41. package/lib/env/nfse.js +24 -0
  42. package/lib/env/version.js +1 -1
  43. package/lib/helpers/distribuicaoDFe-helper.js +2 -2
  44. package/lib/helpers/distribuicaoNFSe-helper.js +128 -0
  45. package/lib/helpers/index.js +5 -2
  46. package/lib/helpers/manifestacaoNFSe-helper.js +105 -0
  47. package/lib/helpers/recepcaoEvento-helper.js +2 -2
  48. package/lib/helpers/retorno-helper.js +16 -9
  49. package/lib/helpers/retornoNfse-helper.js +31 -0
  50. package/lib/index.js +13 -1
  51. package/lib/schemas/index.js +2 -1
  52. package/lib/schemas/manifestacaoNFSe-schema.js +19 -0
  53. package/lib/services/sefaz-service.js +15 -14
  54. package/lib/util/assinatura.js +2 -2
  55. package/lib/util/gzip.js +8 -0
  56. package/lib/validators/chaveNfse-validator.js +16 -0
  57. package/lib/validators/cnpj-validator.js +21 -0
  58. package/lib/validators/index.js +5 -1
  59. package/lib/validators/motivoRejeicao-validator.js +28 -0
  60. package/lib/validators/nsuNfse-validator.js +18 -0
  61. package/package.json +10 -4
package/CHANGELOG.md CHANGED
@@ -2,6 +2,70 @@
2
2
 
3
3
  ## [Não publicado]
4
4
 
5
+ ## [0.20.0] / 2026-10-16
6
+
7
+ > Acrescenta a NFS-e Nacional: distribuição pelo ADN e manifestação do tomador na SEFIN. Nada muda nas APIs de NF-e.
8
+
9
+ ### Adicionado
10
+
11
+ - **`DistribuicaoNFSe`: distribuição de NFS-e Nacional pelo Ambiente de Dados Nacional (ADN)**, exportada nas três formas (`module.exports`, `.default`, `.mde`). Usa o mesmo certificado A1 da NF-e e consulta as NFS-e e os eventos em que o CNPJ figura:
12
+ - `consultaUltNSU(ultNSU)` — até 50 documentos depois do NSU informado, começando em `'0'`; fila vazia é HTTP 404 sem `error`
13
+ - `consultaNSU(nsu)` — o documento daquele NSU
14
+ - `consultaChNFSe(chNFSe)` — a NFS-e da chave de 50 dígitos e os eventos dela
15
+ - Campo `papel` em cada NFS-e distribuída: `'tomador'`, `'prestador'` ou `'intermediario'`, conforme o CNPJ consultado apareça como `toma`, `prest` ou `interm` da DPS, nessa ordem; `''` se não aparecer e em eventos. A biblioteca marca, não filtra
16
+ - Retorno próprio da NFS-e, `{ data, resBody, status, error?, transportError? }`: `resBody` é o corpo cru do ADN, e `status`, `error` e `transportError` têm o mesmo significado das APIs de NF-e. Ver [ADR 0016](https://github.com/bitize/bitmde/blob/main/.docs/arquitetura/decisoes/0016-retorno-proprio-da-nfse.md)
17
+ - Hosts do ADN, de produção e produção restrita, no `test/tls-sefaz.test.js` e com isso no workflow semanal `cadeia-tls.yml`
18
+ - **`ManifestacaoNFSe`: manifestação do tomador da NFS-e Nacional na SEFIN Nacional**, exportada nas três formas. Com o mesmo certificado A1, registra um evento por chamada:
19
+ - `confirmar(chNFSe)` — Confirmação do Tomador (`e203202`)
20
+ - `rejeitar(chNFSe, { cMotivo, xMotivo? })` — Rejeição do Tomador (`e203206`), com `cMotivo` `'1'` a `'5'` ou `'9'`; `xMotivo` de 15 a 255 caracteres, obrigatório no motivo `'9'`
21
+ - Retorno da manifestação `{ data, reqXml, resBody, status, error?, transportError? }`, no formato da ADR 0016: `reqXml` é o `pedRegEvento` assinado e `data.evento` traz o evento registrado, em `xml` e `json`
22
+ - Hosts da SEFIN Nacional, de produção e produção restrita, no `test/tls-sefaz.test.js` e no `cadeia-tls.yml`
23
+
24
+ Nada muda em `DistribuicaoDFe` nem em `RecepcaoEvento`: o XML assinado da recepção de evento sai byte a byte igual, conferido por teste.
25
+
26
+ ### Modificado
27
+
28
+ - Workflow `testes.yml` deixa de rodar em push e PR que só mexem em documentação (`**/*.md`, `.docs/**`, `.claude/**`, `LICENSE`). O `qualidade.yml`, com o Prettier que valida o Markdown, continua rodando sempre, e o `publicar.yml` roda a suíte antes de todo release
29
+
30
+ ## [0.19.0] / 2026-10-03
31
+
32
+ > Muda o comportamento padrão de transporte e deixa de suportar o Node 20; leia [Compatibilidade](#compatibilidade) antes de atualizar.
33
+
34
+ ### Modificado
35
+
36
+ - **O certificado do servidor da SEFAZ passa a ser validado por padrão** (`rejectUnauthorized: true`). Até a 0.18.0 o padrão era `false`: a biblioteca aceitava qualquer servidor, inclusive um intermediário malicioso, e apresentava a ele o certificado A1 no mTLS
37
+ - **A cadeia padrão passa a somar as raízes do Node à ICP-Brasil, em vez de substituí-las.** A cadeia embarcada (AC Raiz Brasileira v10 e AC SERPRO SSLv1) não valida mais o Ambiente Nacional, que hoje serve `AC SERPRO AR46 OV TLS CA 2025 ← GlobalSign Root R46`, e a opção `ca` do Node descarta as raízes padrão. Quem ligava `rejectUnauthorized: true`, como esta documentação recomendava, recebia sempre `UNABLE_TO_GET_ISSUER_CERT_LOCALLY`. As raízes respeitam `NODE_EXTRA_CA_CERTS` e `--use-system-ca` quando o Node oferece `tls.getCACertificates`
38
+ - **Erro de transporte deixa de se disfarçar de status HTTP.** Sem resposta da SEFAZ, o retorno vinha com `status` 500, 502 ou 504 sintético e `resXml: '<error>mensagem</error>'`, e o código do erro se perdia. Agora vem com `status: 0`, `resXml: ''`, `transportError: { code, message }` e `error: 'Falha de transporte (<code>): <message>'`. Resposta HTTP real da SEFAZ, inclusive 5xx, não muda. Ver [ADR 0014](https://github.com/bitize/bitmde/blob/main/.docs/arquitetura/decisoes/0014-tls-validado-por-padrao-e-erro-de-transporte-explicito.md)
39
+
40
+ ### Adicionado
41
+
42
+ - `CA_PADRAO` e `CA_ICP_BRASIL` exportados na raiz do pacote, nas três formas (`module.exports`, `.default`, `.mde`): a cadeia que a biblioteca usa por padrão e só a ICP-Brasil embarcada. São arrays de PEM congelados, para compor como `httpsOptions: { ca: [...CA_PADRAO, minhaCa] }`
43
+ - Campo `transportError?: { code, message }` no retorno dos três métodos de consulta e do `enviarEvento`
44
+ - `engines: { node: ">=22" }` no `package.json`
45
+ - Workflow `cadeia-tls.yml`, que confere toda semana o handshake TLS com `www1` e `hom1` usando `CA_PADRAO`, para a próxima troca de cadeia do Ambiente Nacional aparecer antes de chegar a quem consome
46
+
47
+ ### Removido
48
+
49
+ - **Node 20 deixa de ser suportado**: sem suporte do projeto Node.js desde abril de 2026, sai da matriz de testes da CI. A biblioteca passa a exigir Node 22 ou posterior
50
+
51
+ ### Compatibilidade
52
+
53
+ Nenhuma assinatura muda, mas dois comportamentos padrão mudam, daí o bump de minor (`^0.18.0` não resolve para a 0.19.0):
54
+
55
+ 1. **Servidor cuja cadeia não fecha em `CA_PADRAO` passa a ser recusado.** Para os endpoints do Ambiente Nacional, que são os únicos que a biblioteca chama, nada muda. Atrás de um proxy que reassina TLS, acrescente a CA dele com `NODE_EXTRA_CA_CERTS` ou com `httpsOptions: { ca: [...CA_PADRAO, caDoProxy] }`. Quem já passava `ca` próprio continua no controle, porque `httpsOptions` sobrescreve o padrão.
56
+ 2. **Erro de transporte muda de forma.** Quem testa só `if (retorno.error)` não precisa mudar nada. Quem lia `status` 500/502/504 ou procurava `<error>` em `resXml` passa a usar `transportError`:
57
+
58
+ ```diff
59
+ -if (retorno.status === 502 && /^<error>/.test(retorno.resXml)) {
60
+ +if (retorno.transportError) {
61
+ + log(retorno.transportError.code, retorno.transportError.message)
62
+ }
63
+ ```
64
+
65
+ Voltar ao comportamento antigo de TLS ainda é possível com `httpsOptions: { rejectUnauthorized: false }`, e não é recomendado.
66
+
67
+ Quem ainda roda Node 20 recebe um aviso de `engines` na instalação e deve atualizar para o Node 22 ou o 24, ambos LTS.
68
+
5
69
  ## [0.18.0] / 2026-10-01
6
70
 
7
71
  ### Corrigido
package/README.md CHANGED
@@ -5,9 +5,9 @@
5
5
  [![MIT License](https://img.shields.io/badge/license-MIT-blue.svg)](https://github.com/bitize/bitmde/blob/main/LICENSE)
6
6
  [![Mantido pelo BitERP](https://img.shields.io/badge/mantido%20por-BitERP-0b5fff.svg)](https://www.biterp.ai)
7
7
 
8
- Biblioteca para consumo dos Web Services da Sefaz de Distribuição de DF-e e Evento de Manifestação do Destinatário.
8
+ Biblioteca para consumo dos Web Services da Sefaz de Distribuição de DF-e e Evento de Manifestação do Destinatário, e da NFS-e Nacional: distribuição pelo Ambiente de Dados Nacional (ADN) e manifestação do tomador na SEFIN Nacional.
9
9
 
10
- Essa biblioteca permite consultar a relação das notas fiscais emitidas contra um determinado CNPJ/CPF e realizar o envio do evento de manifestação, podendo assim, baixar o XML da NF-e.
10
+ Essa biblioteca permite consultar a relação das notas fiscais emitidas contra um determinado CNPJ/CPF e realizar o envio do evento de manifestação, podendo assim, baixar o XML da NF-e. Com o mesmo certificado, consulta as NFS-e em que o CNPJ é tomador, prestador ou intermediário, e registra a Confirmação ou a Rejeição do tomador.
11
11
 
12
12
  Desenvolvida e mantida pela equipe do **[BitERP](https://www.biterp.ai)**, onde é usada em produção para a automação da manifestação do destinatário e do download de XML de NF-e de entrada.
13
13
 
@@ -25,6 +25,7 @@ npm i @bitize/bitmde
25
25
 
26
26
  - Possuir um **Certificado A1** válido emitido por uma Autoridade Certificadora credenciada pela Infraestrutura de Chaves Públicas Brasileira – **ICP-Brasil**.
27
27
  - O certificado pode ser usando no formato **PFX** e **Senha** _OU_ **cert.pem** e **key.pem**
28
+ - **Node.js 22 ou posterior.** O Node 20, sem suporte desde abril de 2026, deixou de ser testado na 0.19.0.
28
29
 
29
30
  ## Funcionalidades
30
31
 
@@ -36,6 +37,10 @@ npm i @bitize/bitmde
36
37
  - Retorna o Documento Fiscal referente ao NSU informado, podendo ser um `Resumo`, uma `NF-e` ou um `Evento`
37
38
  - Envio de evento
38
39
  - Registra o evento de manifestação na nota informada (`Confirmação da Operação`, `Ciência da Operação`, `Desconhecimento da Operação` ou `Operação não Realizada`)
40
+ - Distribuição de NFS-e Nacional
41
+ - Consulta por último NSU, por NSU e por chave de acesso no ADN, com o papel do CNPJ (`tomador`, `prestador` ou `intermediario`) em cada NFS-e
42
+ - Manifestação do tomador da NFS-e Nacional
43
+ - Registra a `Confirmação do Tomador` ou a `Rejeição do Tomador` na SEFIN Nacional
39
44
 
40
45
  ## Distribuição de DF-e
41
46
 
@@ -56,7 +61,7 @@ new DistribuicaoDFe(config)
56
61
  - `tpAmb` `<String>` - [OBRIGATÓRIO] - Identificação de Ambiente. Informar `'1'` para **Produção** ou `'2'` para **Homologação**.
57
62
  - `options` `<Object>` - [OPCIONAL]
58
63
  - `requestOptions` `<AxiosRequestConfig>` - [OPCIONAL]
59
- - `httpsOptions` `<AgentOptions>` - [OPCIONAL]
64
+ - `httpsOptions` `<AgentOptions>` - [OPCIONAL] - Opções do `https.Agent`, mescladas por cima do padrão `{ ca: CA_PADRAO, rejectUnauthorized: true }`. Ver [Conexão com a SEFAZ](#conexão-com-a-sefaz).
60
65
 
61
66
  ### Consulta por ultNSU
62
67
 
@@ -255,7 +260,7 @@ new RecepcaoEvento(config)
255
260
  - `timezone` `<String>` - [OPCIONAL] - Fuso horário do autor. É utilizado `'America/Sao_Paulo'` como valor padrão. Consulte a tabela [lista de timezones](#lista-de-timezones) válidos para o Brasil.
256
261
  - `options` `<Object>` - [OPCIONAL]
257
262
  - `requestOptions` `<AxiosRequestConfig>` - [OPCIONAL]
258
- - `httpsOptions` `<AgentOptions>` - [OPCIONAL]
263
+ - `httpsOptions` `<AgentOptions>` - [OPCIONAL] - Opções do `https.Agent`, mescladas por cima do padrão `{ ca: CA_PADRAO, rejectUnauthorized: true }`. Ver [Conexão com a SEFAZ](#conexão-com-a-sefaz).
259
264
 
260
265
  ### Enviar Lote de Eventos
261
266
 
@@ -347,6 +352,306 @@ console.log(manifestacao)
347
352
  // }
348
353
  ```
349
354
 
355
+ ## Distribuição de NFS-e Nacional
356
+
357
+ Consulta, no Ambiente de Dados Nacional (ADN), as NFS-e e os eventos em que o CNPJ figura. A fila traz o histórico inteiro do CNPJ desde a primeira nota no ADN e mistura notas recebidas, notas emitidas e eventos de terceiros sobre umas e outras. A biblioteca **não filtra**: cada NFS-e vem com o `papel` do CNPJ consultado.
358
+
359
+ > **Cobertura.** Só aparece a NFS-e que chegou ao ADN: as emitidas no Emissor Nacional e as de município cujo sistema próprio compartilha com o ADN. Nota de município que ainda não compartilha não está na distribuição, mesmo com a obrigatoriedade da LC 214/2025.
360
+
361
+ ### Construtor
362
+
363
+ ```js
364
+ new DistribuicaoNFSe(config)
365
+ ```
366
+
367
+ - `config` `<Object>`
368
+ - `pfx` `<Buffer>` - [OPCIONAL] - Arquivo **.pfx**. Se o `pfx` não for informado, as propriedades `cert` e `key` passam a ser obrigatórias.
369
+ - `passphrase` `<String>` - [OPCIONAL] - Senha do arquivo **.pfx**.
370
+ - `cert` `<Buffer | String>` - [OPCIONAL] - Conteúdo do _cert.pem_. Essa propriedade fica obrigatória se o `pfx` não for informado.
371
+ - `key` `<Buffer | String>` - [OPCIONAL] - Conteúdo do _key.pem_. Essa propriedade fica obrigatória se o `pfx` não for informado.
372
+ - `cnpj` `<String>` - [OBRIGATÓRIO] - CNPJ consultado, com 14 caracteres e sem pontuação (aceita o alfanumérico). Pode ser o do certificado ou o de outro estabelecimento com a mesma raiz.
373
+ - `tpAmb` `<String>` - [OBRIGATÓRIO] - Informar `'1'` para **Produção** ou `'2'` para **Produção restrita**.
374
+ - `options` `<Object>` - [OPCIONAL]
375
+ - `requestOptions` `<AxiosRequestConfig>` - [OPCIONAL]
376
+ - `httpsOptions` `<AgentOptions>` - [OPCIONAL] - Opções do `https.Agent`, mescladas por cima do padrão `{ ca: CA_PADRAO, rejectUnauthorized: true }`. Ver [Conexão com a SEFAZ](#conexão-com-a-sefaz).
377
+
378
+ ### Consultas
379
+
380
+ | Método | Argumento | Devolve |
381
+ | :----------------------- | :--------------------------------- | :--------------------------------------------- |
382
+ | `consultaUltNSU(ultNSU)` | NSU, 1-15 dígitos; começa em `'0'` | Até 50 documentos **depois** do NSU informado |
383
+ | `consultaNSU(nsu)` | NSU, 1-15 dígitos, maior que zero | O documento daquele NSU |
384
+ | `consultaChNFSe(chNFSe)` | Chave de acesso, 50 dígitos | A NFS-e da chave e os eventos vinculados a ela |
385
+
386
+ No ADN o NSU é um inteiro, sem zeros à esquerda: `'00051'` e `51` viram `'51'`.
387
+
388
+ #### Exemplo: drenar a fila
389
+
390
+ ```js
391
+ const { DistribuicaoNFSe } = require('@bitize/bitmde')
392
+ const fs = require('fs')
393
+
394
+ const distribuicao = new DistribuicaoNFSe({
395
+ pfx: fs.readFileSync('./certificado.pfx'),
396
+ passphrase: 'senha',
397
+ cnpj: '12345678000190',
398
+ tpAmb: '1',
399
+ })
400
+
401
+ let ultNSU = '0' // ou o último NSU guardado
402
+
403
+ while (true) {
404
+ const consulta = await distribuicao.consultaUltNSU(ultNSU)
405
+
406
+ if (consulta.error) {
407
+ throw new Error(consulta.error)
408
+ }
409
+
410
+ const { documentos, documentosErros } = consulta.data
411
+
412
+ // Fim da fila: HTTP 404, NENHUM_DOCUMENTO_LOCALIZADO, sem error.
413
+ if (documentos.length === 0 && documentosErros.length === 0) {
414
+ break
415
+ }
416
+
417
+ for (const doc of documentos) {
418
+ if (doc.tipoDocumento === 'NFSE' && doc.papel === 'tomador') {
419
+ // NFS-e recebida: doc.xml e doc.json
420
+ }
421
+ }
422
+
423
+ ultNSU = [...documentos, ...documentosErros]
424
+ .map((doc) => doc.nsu)
425
+ .sort((a, b) => Number(a) - Number(b))
426
+ .pop()
427
+ }
428
+ ```
429
+
430
+ O próximo pedido usa o **último NSU recebido**, sem somar 1. O ADN não bloqueia quem volta a um NSU anterior, como faz a distribuição de NF-e, mas guardar o ponteiro e escolher o intervalo entre as consultas continua sendo de quem consome.
431
+
432
+ #### Retorno
433
+
434
+ ```js
435
+ console.log(consulta)
436
+ // {
437
+ // data: {
438
+ // statusProcessamento: 'DOCUMENTOS_LOCALIZADOS',
439
+ // tipoAmbiente: 'PRODUCAO',
440
+ // versaoAplicativo: '1.0.0.0',
441
+ // dataHoraProcessamento: '2026-10-15T10:35:14.5687887-03:00',
442
+ // alertas: [],
443
+ // erros: [],
444
+ // documentos: [
445
+ // {
446
+ // nsu: '1',
447
+ // chaveAcesso: '41000000000000000000000000000000000000000000000001',
448
+ // tipoDocumento: 'NFSE',
449
+ // tipoEvento: '',
450
+ // dataHoraGeracao: '2026-10-01T09:12:00',
451
+ // papel: 'tomador',
452
+ // xml: '<?xml version="1.0" encoding="utf-8"?><NFSe versao="1.01" ... </NFSe>',
453
+ // json: { NFSe: { ... } },
454
+ // },
455
+ // {
456
+ // nsu: '2',
457
+ // chaveAcesso: '41000000000000000000000000000000000000000000000001',
458
+ // tipoDocumento: 'EVENTO',
459
+ // tipoEvento: 'CANCELAMENTO',
460
+ // dataHoraGeracao: '2026-10-02T15:40:00',
461
+ // papel: '',
462
+ // xml: '<?xml version="1.0" encoding="utf-8"?><evento versao="1.01" ... </evento>',
463
+ // json: { evento: { ... } },
464
+ // },
465
+ // ],
466
+ // documentosErros: [],
467
+ // },
468
+ // resBody: '{"StatusProcessamento":"DOCUMENTOS_LOCALIZADOS", ... }',
469
+ // status: 200,
470
+ // }
471
+ ```
472
+
473
+ - `papel` vale `'tomador'`, `'prestador'` ou `'intermediario'` quando o `cnpj` da config é o `toma`, o `prest` ou o `interm` da DPS, testados nessa ordem; `''` quando não é nenhum, e sempre `''` em eventos. Para ficar só com as recebidas, filtre por `tipoDocumento === 'NFSE' && papel === 'tomador'`.
474
+ - `resBody` é o corpo cru da resposta do ADN, como veio. Não há `reqXml` nem `resXml`: a consulta é um `GET` sem corpo e a resposta é JSON.
475
+ - `documentosErros` traz, com `nsu`, `chaveAcesso`, `tipoDocumento` e `error`, os itens cujo XML não abriu: `ArquivoXml sem conteúdo.`, `Falha ao descompactar o ArquivoXml: <motivo>` ou `Falha ao interpretar o XML do ArquivoXml: <motivo>`. Eles não derrubam o lote nem preenchem o `error` de topo.
476
+
477
+ #### Quando há `error`
478
+
479
+ | Situação | `status` | `data` | `error` |
480
+ | :------------------------------------------------ | :------: | :--------------------------- | :------------------------------------------------- |
481
+ | Fila vazia, chave ou NSU sem documento | 404 | preenchido, `documentos: []` | — |
482
+ | Rejeição do ADN (ex.: `cnpj` de outra raiz) | 400 | preenchido, com `erros` | `E2243: O CNPJ informado na consulta deve ter ...` |
483
+ | Resposta que não é do ADN (ex.: HTML de um proxy) | real | `{}` | `Resposta inesperada do ADN (HTTP 502).` |
484
+ | Sem resposta HTTP | 0 | `{}` | `Falha de transporte (<code>): <message>` |
485
+
486
+ Na rejeição, `data` **não** é esvaziado: `data.erros` traz o diagnóstico em `{ codigo, descricao, complemento }`. `error` e `transportError` têm o mesmo significado da distribuição de DF-e — ver [Erro de transporte](#erro-de-transporte).
487
+
488
+ ## Manifestação do tomador da NFS-e Nacional
489
+
490
+ Registra, na SEFIN Nacional, a Confirmação (`e203202`) ou a Rejeição (`e203206`) do tomador sobre uma NFS-e que exista no ADN. Cada chamada é **um** pedido de registro de evento, assinado com o A1 da config — não há lote.
491
+
492
+ > **Evento fiscal não se desfaz.** Em produção (`tpAmb: '1'`), cada chamada aceita registra um evento vinculado à nota. Depois da Confirmação, o prestador não consegue mais cancelar a NFS-e, só substituí-la; a Rejeição só é anulada pelo município de incidência. Teste em produção restrita (`tpAmb: '2'`).
493
+
494
+ ### Construtor
495
+
496
+ ```js
497
+ new ManifestacaoNFSe(config)
498
+ ```
499
+
500
+ - `config` `<Object>`
501
+ - `pfx` `<Buffer>` - [OPCIONAL] - Arquivo **.pfx**. Se o `pfx` não for informado, as propriedades `cert` e `key` passam a ser obrigatórias.
502
+ - `passphrase` `<String>` - [OPCIONAL] - Senha do arquivo **.pfx**.
503
+ - `cert` `<Buffer | String>` - [OPCIONAL] - Conteúdo do _cert.pem_. Essa propriedade fica obrigatória se o `pfx` não for informado.
504
+ - `key` `<Buffer | String>` - [OPCIONAL] - Conteúdo do _key.pem_. Essa propriedade fica obrigatória se o `pfx` não for informado.
505
+ - `cnpj` `<String>` - [OBRIGATÓRIO] - CNPJ do tomador, enviado como `CNPJAutor`, com 14 caracteres e sem pontuação. Precisa ter a mesma raiz do CNPJ do certificado.
506
+ - `tpAmb` `<String>` - [OBRIGATÓRIO] - Informar `'1'` para **Produção** ou `'2'` para **Produção restrita**.
507
+ - `timezone` `<String>` - [OPCIONAL] - Fuso do `dhEvento`. Padrão `'America/Sao_Paulo'`. Ver [Lista de Timezones](#lista-de-timezones).
508
+ - `options` `<Object>` - [OPCIONAL]
509
+ - `requestOptions` `<AxiosRequestConfig>` - [OPCIONAL]
510
+ - `httpsOptions` `<AgentOptions>` - [OPCIONAL] - Opções do `https.Agent`, mescladas por cima do padrão `{ ca: CA_PADRAO, rejectUnauthorized: true }`. Ver [Conexão com a SEFAZ](#conexão-com-a-sefaz).
511
+
512
+ ### Métodos
513
+
514
+ | Método | Argumentos | Evento |
515
+ | :------------------------- | :------------------------------------------------- | :--------------------------------- |
516
+ | `confirmar(chNFSe)` | Chave de acesso, 50 dígitos | `e203202` — Confirmação do Tomador |
517
+ | `rejeitar(chNFSe, motivo)` | Chave de acesso e `motivo` `{ cMotivo, xMotivo? }` | `e203206` — Rejeição do Tomador |
518
+
519
+ Códigos de `cMotivo`:
520
+
521
+ | `cMotivo` | Motivo |
522
+ | :-------: | :-------------------------------------------------------------------------------------------- |
523
+ | `'1'` | NFS-e em duplicidade |
524
+ | `'2'` | NFS-e já emitida pelo tomador |
525
+ | `'3'` | Não ocorrência do fato gerador |
526
+ | `'4'` | Erro quanto à responsabilidade tributária |
527
+ | `'5'` | Erro quanto ao valor do serviço, valor das deduções, serviço prestado ou data do fato gerador |
528
+ | `'9'` | Outros — **exige `xMotivo`** |
529
+
530
+ `xMotivo` é opcional nos demais motivos e, quando informado, tem de 15 a 255 caracteres.
531
+
532
+ Cada NFS-e aceita uma Confirmação e uma Rejeição do tomador. A Rejeição é aceita depois da Confirmação, e a Confirmação depois da Rejeição; repetir o mesmo evento é rejeitado (`E0861` e `E0866`).
533
+
534
+ #### Exemplo
535
+
536
+ ```js
537
+ const { ManifestacaoNFSe } = require('@bitize/bitmde')
538
+ const fs = require('fs')
539
+
540
+ const manifestacao = new ManifestacaoNFSe({
541
+ pfx: fs.readFileSync('./certificado.pfx'),
542
+ passphrase: 'senha',
543
+ cnpj: '12345678000190',
544
+ tpAmb: '2',
545
+ })
546
+
547
+ const confirmacao = await manifestacao.confirmar(
548
+ '41000000000000000000000000000000000000000000000001'
549
+ )
550
+
551
+ const rejeicao = await manifestacao.rejeitar(
552
+ '41000000000000000000000000000000000000000000000002',
553
+ { cMotivo: '9', xMotivo: 'Serviço não foi prestado.' }
554
+ )
555
+
556
+ if (rejeicao.error) {
557
+ console.error(rejeicao.error) // ex.: 'E0866: ...'
558
+ }
559
+ ```
560
+
561
+ #### Retorno
562
+
563
+ ```js
564
+ console.log(confirmacao)
565
+ // {
566
+ // data: {
567
+ // tipoAmbiente: '2',
568
+ // versaoAplicativo: 'SefinNacional_1.6.0',
569
+ // dataHoraProcessamento: '2026-10-16T09:26:06-03:00',
570
+ // alertas: [],
571
+ // erros: [],
572
+ // evento: {
573
+ // xml: '<evento versao="1.01" ... </evento>',
574
+ // json: { evento: { ... } },
575
+ // },
576
+ // },
577
+ // reqXml: '<?xml version="1.0" encoding="UTF-8"?><pedRegEvento xmlns="http://www.sped.fazenda.gov.br/nfse" versao="1.01"> ... </pedRegEvento>',
578
+ // resBody: '{"tipoAmbiente":2, ... }',
579
+ // status: 201,
580
+ // }
581
+ ```
582
+
583
+ - `reqXml` é o `pedRegEvento` assinado, antes do gzip — é o que se audita numa rejeição.
584
+ - `data.evento` traz o evento registrado pela SEFIN, em `xml` e `json`.
585
+ - `status`, `error` e `transportError` têm o mesmo significado das outras APIs.
586
+
587
+ #### Quando há `error`
588
+
589
+ | Situação | `status` | `data` | `error` |
590
+ | :-------------------------------------------------- | :------: | :----------------------- | :----------------------------------------------------- |
591
+ | Evento registrado e devolvido | 201 | preenchido, com `evento` | — |
592
+ | Evento registrado, mas o retorno não abre | 201 | preenchido, `evento: {}` | `Falha ao abrir o evento retornado: <motivo>` |
593
+ | Rejeição da SEFIN (ex.: nota fora do ADN) | 400 | preenchido, com `erros` | `E1831: O evento não pode ser recepcionado pois a ...` |
594
+ | Resposta que não é da SEFIN (ex.: HTML de um proxy) | real | `{}` | `Resposta inesperada da SEFIN (HTTP 502).` |
595
+ | Sem resposta HTTP | 0 | `{}` | `Falha de transporte (<code>): <message>` |
596
+
597
+ Na segunda linha o evento **foi registrado**: não repita o pedido; consulte a nota pela [Distribuição de NFS-e Nacional](#distribuição-de-nfs-e-nacional). `reqXml` vem em todos os casos.
598
+
599
+ ## Conexão com a SEFAZ
600
+
601
+ ### Validação do certificado do servidor
602
+
603
+ O certificado do servidor da SEFAZ é **validado por padrão**. Sem `httpsOptions`, o agente HTTPS usa:
604
+
605
+ ```js
606
+ { ca: CA_PADRAO, rejectUnauthorized: true }
607
+ ```
608
+
609
+ A biblioteca exporta as duas cadeias que usa:
610
+
611
+ - `CA_PADRAO` — as raízes que o Node usaria por padrão, somadas à cadeia ICP-Brasil. Inclui `NODE_EXTRA_CA_CERTS` e `--use-system-ca` quando o Node oferece `tls.getCACertificates` (22.15 ou posterior); nas versões do Node 22 anteriores a ela, usa o bundle da Mozilla (`tls.rootCertificates`). É montada uma vez, quando o pacote é carregado.
612
+ - `CA_ICP_BRASIL` — só a cadeia ICP-Brasil embarcada (AC Raiz Brasileira v10 e AC SERPRO SSLv1).
613
+
614
+ As duas são arrays de PEM congelados. Informar `ca` em `httpsOptions` **substitui** a cadeia inteira. Para acrescentar uma AC, por exemplo a de um proxy corporativo, some à cadeia padrão:
615
+
616
+ ```js
617
+ const { CA_PADRAO, DistribuicaoDFe } = require('@bitize/bitmde')
618
+
619
+ const distribuicao = new DistribuicaoDFe({
620
+ // ...
621
+ options: {
622
+ httpsOptions: {
623
+ ca: [...CA_PADRAO, fs.readFileSync('./minha-ca.pem', 'utf8')],
624
+ },
625
+ },
626
+ })
627
+ ```
628
+
629
+ Desligar a validação ainda é possível com `httpsOptions: { rejectUnauthorized: false }`, mas **não é recomendado**: assim a biblioteca aceita qualquer servidor e apresenta a ele o certificado A1 no mTLS.
630
+
631
+ ### Erro de transporte
632
+
633
+ Quando não há resposta HTTP (falha na cadeia TLS, DNS, conexão recusada, timeout), a biblioteca não lança. O retorno vem com `status: 0` e o motivo em `transportError`:
634
+
635
+ ```js
636
+ // {
637
+ // data: {},
638
+ // reqXml: '<?xml version="1.0" encoding="utf-8"?> ... </soap12:Body></soap12:Envelope>',
639
+ // resXml: '',
640
+ // status: 0,
641
+ // error: 'Falha de transporte (UNABLE_TO_GET_ISSUER_CERT_LOCALLY): unable to get local issuer certificate',
642
+ // transportError: {
643
+ // code: 'UNABLE_TO_GET_ISSUER_CERT_LOCALLY',
644
+ // message: 'unable to get local issuer certificate',
645
+ // },
646
+ // }
647
+ ```
648
+
649
+ `transportError.code` é o código do Node ou do axios, sem tradução: `ECONNREFUSED`, `ENOTFOUND`, `ECONNRESET`, `ECONNABORTED` (timeout), `UNABLE_TO_GET_ISSUER_CERT_LOCALLY` (cadeia), entre outros. Sem código, vem `ERR_DESCONHECIDO`. `error` continua preenchido, então `if (retorno.error)` cobre esse caso como antes.
650
+
651
+ Uma resposta HTTP de erro da SEFAZ, como um `502` ou um `503`, chega com o status real, o corpo em `resXml` e **sem** `transportError`. Então `status: 0` significa sempre "a SEFAZ não respondeu".
652
+
653
+ Na `DistribuicaoNFSe` o erro de transporte tem a mesma forma, com `resBody: ''` no lugar de `reqXml` e `resXml`. As mesmas regras de `CA_PADRAO` e `httpsOptions` valem para os hosts do ADN, que hoje servem certificado Let's Encrypt.
654
+
350
655
  ## Sobre o BitERP
351
656
 
352
657
  O `@bitize/bitmde` é mantido pela equipe do **[BitERP](https://www.biterp.ai)**, um ERP online que automatiza a rotina fiscal, financeira e comercial de empresas brasileiras.
@@ -12,7 +12,7 @@ declare class DistribuicaoDFe {
12
12
  * @param {'1' | '2'} config.tpAmb
13
13
  * @param {Object} [config.options]
14
14
  * @param {import('axios').AxiosRequestConfig} [config.options.requestOptions]
15
- * @param {import('https').AgentOptions} [config.options.httpsOptions]
15
+ * @param {import('https').AgentOptions} [config.options.httpsOptions] Mesclado por cima do padrão `{ ca: CA_PADRAO, rejectUnauthorized: true }`. Informar `ca` substitui a cadeia inteira; para somar uma AC, use `[...CA_PADRAO, minhaCa]`.
16
16
  */
17
17
  constructor(config: {
18
18
  pfx?: Buffer;
@@ -63,6 +63,10 @@ declare class DistribuicaoDFe {
63
63
  }];
64
64
  };
65
65
  error: string;
66
+ transportError?: {
67
+ code: string;
68
+ message: string;
69
+ };
66
70
  reqXml: string;
67
71
  resXml: string;
68
72
  status: number;
@@ -92,6 +96,10 @@ declare class DistribuicaoDFe {
92
96
  }];
93
97
  };
94
98
  error: string;
99
+ transportError?: {
100
+ code: string;
101
+ message: string;
102
+ };
95
103
  reqXml: string;
96
104
  resXml: string;
97
105
  status: number;
@@ -121,6 +129,10 @@ declare class DistribuicaoDFe {
121
129
  }];
122
130
  };
123
131
  error: string;
132
+ transportError?: {
133
+ code: string;
134
+ message: string;
135
+ };
124
136
  reqXml: string;
125
137
  resXml: string;
126
138
  status: number;
@@ -0,0 +1,176 @@
1
+ export = DistribuicaoNFSe;
2
+ declare class DistribuicaoNFSe {
3
+ /**
4
+ * Distribuição de NFS-e Nacional pelo Ambiente de Dados Nacional (ADN).
5
+ *
6
+ * @param {Object} config
7
+ * @param {Buffer} [config.pfx]
8
+ * @param {string} [config.passphrase]
9
+ * @param {Buffer | string} [config.cert]
10
+ * @param {Buffer | string} [config.key]
11
+ * @param {string} config.cnpj CNPJ consultado, enviado como `cnpjConsulta`. Precisa ter a mesma raiz do CNPJ do certificado.
12
+ * @param {'1' | '2'} config.tpAmb `'1'` produção, `'2'` produção restrita.
13
+ * @param {Object} [config.options]
14
+ * @param {import('axios').AxiosRequestConfig} [config.options.requestOptions]
15
+ * @param {import('https').AgentOptions} [config.options.httpsOptions] Mesclado por cima do padrão `{ ca: CA_PADRAO, rejectUnauthorized: true }`. Informar `ca` substitui a cadeia inteira; para somar uma AC, use `[...CA_PADRAO, minhaCa]`.
16
+ */
17
+ constructor(config: {
18
+ pfx?: Buffer;
19
+ passphrase?: string;
20
+ cert?: Buffer | string;
21
+ key?: Buffer | string;
22
+ cnpj: string;
23
+ tpAmb: "1" | "2";
24
+ options?: {
25
+ requestOptions?: import("axios").AxiosRequestConfig;
26
+ httpsOptions?: import("https").AgentOptions;
27
+ };
28
+ });
29
+ config: Readonly<{
30
+ cnpj: string;
31
+ tpAmb: "1" | "2";
32
+ cert: string;
33
+ key: string;
34
+ requestOptions: Readonly<import("axios").AxiosRequestConfig<any, any>>;
35
+ httpsOptions: Readonly<import("node:https").AgentOptions>;
36
+ }>;
37
+ /**
38
+ * A NFS-e da chave e os eventos vinculados a ela.
39
+ *
40
+ * @param {string} chNFSe Chave de acesso de 50 dígitos.
41
+ */
42
+ consultaChNFSe(chNFSe: string): Promise<{
43
+ data: {
44
+ statusProcessamento: string;
45
+ tipoAmbiente: string;
46
+ versaoAplicativo: string;
47
+ dataHoraProcessamento: string;
48
+ alertas: Array<{
49
+ codigo: string;
50
+ descricao: string;
51
+ complemento: string;
52
+ }>;
53
+ erros: Array<{
54
+ codigo: string;
55
+ descricao: string;
56
+ complemento: string;
57
+ }>;
58
+ documentos: Array<{
59
+ nsu: string;
60
+ chaveAcesso: string;
61
+ tipoDocumento: string;
62
+ tipoEvento: string;
63
+ dataHoraGeracao: string;
64
+ papel: "" | "tomador" | "prestador" | "intermediario";
65
+ xml: string;
66
+ json: any;
67
+ }>;
68
+ documentosErros: Array<{
69
+ nsu: string;
70
+ chaveAcesso: string;
71
+ tipoDocumento: string;
72
+ error: string;
73
+ }>;
74
+ };
75
+ error?: string;
76
+ transportError?: {
77
+ code: string;
78
+ message: string;
79
+ };
80
+ resBody: string;
81
+ status: number;
82
+ }>;
83
+ /**
84
+ * O documento daquele NSU.
85
+ *
86
+ * @param {string | number} nsu Maior que zero.
87
+ */
88
+ consultaNSU(nsu: string | number): Promise<{
89
+ data: {
90
+ statusProcessamento: string;
91
+ tipoAmbiente: string;
92
+ versaoAplicativo: string;
93
+ dataHoraProcessamento: string;
94
+ alertas: Array<{
95
+ codigo: string;
96
+ descricao: string;
97
+ complemento: string;
98
+ }>;
99
+ erros: Array<{
100
+ codigo: string;
101
+ descricao: string;
102
+ complemento: string;
103
+ }>;
104
+ documentos: Array<{
105
+ nsu: string;
106
+ chaveAcesso: string;
107
+ tipoDocumento: string;
108
+ tipoEvento: string;
109
+ dataHoraGeracao: string;
110
+ papel: "" | "tomador" | "prestador" | "intermediario";
111
+ xml: string;
112
+ json: any;
113
+ }>;
114
+ documentosErros: Array<{
115
+ nsu: string;
116
+ chaveAcesso: string;
117
+ tipoDocumento: string;
118
+ error: string;
119
+ }>;
120
+ };
121
+ error?: string;
122
+ transportError?: {
123
+ code: string;
124
+ message: string;
125
+ };
126
+ resBody: string;
127
+ status: number;
128
+ }>;
129
+ /**
130
+ * Até 50 documentos depois do NSU informado. Começa em `'0'`; o pedido
131
+ * seguinte usa o último NSU recebido. Fila vazia é `status` 404, sem `error`.
132
+ *
133
+ * @param {string | number} ultNSU
134
+ */
135
+ consultaUltNSU(ultNSU: string | number): Promise<{
136
+ data: {
137
+ statusProcessamento: string;
138
+ tipoAmbiente: string;
139
+ versaoAplicativo: string;
140
+ dataHoraProcessamento: string;
141
+ alertas: Array<{
142
+ codigo: string;
143
+ descricao: string;
144
+ complemento: string;
145
+ }>;
146
+ erros: Array<{
147
+ codigo: string;
148
+ descricao: string;
149
+ complemento: string;
150
+ }>;
151
+ documentos: Array<{
152
+ nsu: string;
153
+ chaveAcesso: string;
154
+ tipoDocumento: string;
155
+ tipoEvento: string;
156
+ dataHoraGeracao: string;
157
+ papel: "" | "tomador" | "prestador" | "intermediario";
158
+ xml: string;
159
+ json: any;
160
+ }>;
161
+ documentosErros: Array<{
162
+ nsu: string;
163
+ chaveAcesso: string;
164
+ tipoDocumento: string;
165
+ error: string;
166
+ }>;
167
+ };
168
+ error?: string;
169
+ transportError?: {
170
+ code: string;
171
+ message: string;
172
+ };
173
+ resBody: string;
174
+ status: number;
175
+ }>;
176
+ }