@useinvio/nfse-sdk 3.1.0 → 3.2.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 (95) hide show
  1. package/CHANGELOG.md +176 -0
  2. package/README.en.md +146 -0
  3. package/README.md +76 -576
  4. package/dist/agent.d.ts +46 -1
  5. package/dist/agent.js +320 -4
  6. package/dist/agent.js.map +1 -1
  7. package/dist/cli.d.ts +2 -0
  8. package/dist/cli.js +520 -0
  9. package/dist/cli.js.map +1 -0
  10. package/dist/cliBin.d.ts +2 -0
  11. package/dist/cliBin.js +6 -0
  12. package/dist/cliBin.js.map +1 -0
  13. package/dist/cliExample.d.ts +6 -0
  14. package/dist/cliExample.js +29 -0
  15. package/dist/cliExample.js.map +1 -0
  16. package/dist/client.d.ts +47 -2
  17. package/dist/client.js +36 -6
  18. package/dist/client.js.map +1 -1
  19. package/dist/config.d.ts +3 -0
  20. package/dist/config.js +8 -0
  21. package/dist/config.js.map +1 -1
  22. package/dist/consultas.d.ts +34 -0
  23. package/dist/consultas.js +61 -0
  24. package/dist/consultas.js.map +1 -0
  25. package/dist/dpsJson.js +1 -41
  26. package/dist/dpsJson.js.map +1 -1
  27. package/dist/emissaoNota.d.ts +8 -3
  28. package/dist/emissaoNota.js +6 -3
  29. package/dist/emissaoNota.js.map +1 -1
  30. package/dist/eventoJson.d.ts +76 -0
  31. package/dist/eventoJson.js +128 -0
  32. package/dist/eventoJson.js.map +1 -0
  33. package/dist/eventosNfse.d.ts +38 -0
  34. package/dist/eventosNfse.js +73 -0
  35. package/dist/eventosNfse.js.map +1 -0
  36. package/dist/gzipB64.d.ts +5 -0
  37. package/dist/gzipB64.js +12 -0
  38. package/dist/gzipB64.js.map +1 -1
  39. package/dist/index.d.ts +17 -7
  40. package/dist/index.js +9 -4
  41. package/dist/index.js.map +1 -1
  42. package/dist/mcpCli.d.ts +2 -0
  43. package/dist/mcpCli.js +7 -0
  44. package/dist/mcpCli.js.map +1 -0
  45. package/dist/mcpServer.d.ts +49 -0
  46. package/dist/mcpServer.js +159 -0
  47. package/dist/mcpServer.js.map +1 -0
  48. package/dist/nfseXml.d.ts +48 -0
  49. package/dist/nfseXml.js +86 -0
  50. package/dist/nfseXml.js.map +1 -0
  51. package/dist/rejeicoes.d.ts +50 -0
  52. package/dist/rejeicoes.js +62 -0
  53. package/dist/rejeicoes.js.map +1 -0
  54. package/dist/rejeicoesCatalog.d.ts +10 -0
  55. package/dist/rejeicoesCatalog.js +4449 -0
  56. package/dist/rejeicoesCatalog.js.map +1 -0
  57. package/dist/sefinClient.d.ts +100 -6
  58. package/dist/sefinClient.js +211 -19
  59. package/dist/sefinClient.js.map +1 -1
  60. package/dist/signXml.js +9 -1
  61. package/dist/signXml.js.map +1 -1
  62. package/dist/testing.d.ts +80 -0
  63. package/dist/testing.js +285 -0
  64. package/dist/testing.js.map +1 -0
  65. package/dist/xmlBuild.d.ts +11 -0
  66. package/dist/xmlBuild.js +44 -0
  67. package/dist/xmlBuild.js.map +1 -0
  68. package/dist/xmlInspection.d.ts +1 -1
  69. package/dist/xmlInspection.js.map +1 -1
  70. package/dist/xsdValidation.d.ts +7 -0
  71. package/dist/xsdValidation.js +37 -15
  72. package/dist/xsdValidation.js.map +1 -1
  73. package/docs/README.md +47 -0
  74. package/docs/REGRAS_NEGOCIO_EMISSAO.md +8 -8
  75. package/docs/conceitos/como-funciona.md +87 -0
  76. package/docs/guias/adn.md +51 -0
  77. package/docs/guias/agentes-ia.md +111 -0
  78. package/docs/guias/cancelar.md +70 -0
  79. package/docs/guias/certificado.md +88 -0
  80. package/docs/guias/cli.md +68 -0
  81. package/docs/guias/consultar.md +54 -0
  82. package/docs/guias/emitir.md +172 -0
  83. package/docs/guias/recuperar-emissao.md +33 -0
  84. package/docs/guias/rejeicoes.md +70 -0
  85. package/docs/guias/testes.md +79 -0
  86. package/docs/guias/timeouts.md +33 -0
  87. package/docs/referencia/ambientes.md +35 -0
  88. package/docs/referencia/api.md +137 -0
  89. package/docs/referencia/erros.md +55 -0
  90. package/docs/tutoriais/primeira-nfse.md +130 -0
  91. package/examples/README.md +2 -0
  92. package/examples/cancel-nfse.ts +29 -0
  93. package/examples/recover-emission.ts +29 -0
  94. package/llms.txt +97 -92
  95. package/package.json +23 -3
@@ -0,0 +1,68 @@
1
+ # Usar pelo terminal
2
+
3
+ O pacote traz o comando `nfse`. No seu projeto, depois de
4
+ `npm install @useinvio/nfse-sdk`, rode com `npx nfse`. Sem instalar:
5
+ `npx -p @useinvio/nfse-sdk nfse`.
6
+
7
+ ## Em um minuto, sem certificado
8
+
9
+ ```bash
10
+ npx nfse init # cria nota.json com valores ilustrativos
11
+ npx nfse validate nota.json # regras do SDK + XSD oficial, sem enviar
12
+ npx nfse emit nota.json --mock # emite numa SEFIN simulada, com certificado de teste
13
+ ```
14
+
15
+ Troque CNPJ, municípios, código de serviço e tributação pelos do seu prestador
16
+ antes de emitir de verdade.
17
+
18
+ ## Com o seu certificado
19
+
20
+ ```bash
21
+ export NFSE_CERT_PATH=./certificado.pfx
22
+ export NFSE_CERT_PASSWORD_FILE=./senha.txt # ou deixe que o nfse pergunte a senha
23
+
24
+ npx nfse doctor # Node.js, motor XSD, certificado e vencimento
25
+ npx nfse emit nota.json # mostra o resumo e pede confirmação
26
+ npx nfse get <chave> -o nfse.xml
27
+ npx nfse danfse <chave>
28
+ npx nfse cancel <chave> --motivo 1 --justificativa "Nota emitida com valor incorreto"
29
+ ```
30
+
31
+ O ambiente padrão é a produção restrita. `--producao` (ou `NFSE_AMBIENTE=producao`)
32
+ emite notas com validade jurídica e exige digitar `emitir` para confirmar.
33
+
34
+ ## Comandos
35
+
36
+ | Comando | O que faz |
37
+ |---|---|
38
+ | `init [nota.json]` | Cria um JSON de nota de exemplo. Nunca sobrescreve. |
39
+ | `doctor` | Confere Node.js, motor XSD, certificado e dias até o vencimento. |
40
+ | `cert [arquivo.pfx]` | Titular, CNPJ/CPF, emissor, validade e fingerprint. Nunca mostra a chave. |
41
+ | `validate <nota.json>` | Valida o JSON e o XML contra o XSD oficial. |
42
+ | `xml <nota.json>` | Gera o XML da DPS não assinado (`-o` para salvar). |
43
+ | `emit <nota.json>` | Assina, mostra o resumo, confirma e envia. Após timeout, consulta a DPS antes de qualquer reenvio. |
44
+ | `get <chave>` | Resumo da NFS-e (`-o` salva o XML). |
45
+ | `danfse <chave>` | Salva o PDF do DANFSe. |
46
+ | `cancel <chave>` | Cancela (`--motivo 1\|2\|9 --justificativa "..."`), mostrando a nota antes. |
47
+ | `recover <dpsId>` | Diz se a SEFIN gerou NFS-e para uma DPS. |
48
+ | `explain <código...>` | Mensagem, regra e campo oficiais de códigos de rejeição. |
49
+ | `mcp` | Inicia o [servidor MCP](agentes-ia.md). |
50
+
51
+ ## Opções
52
+
53
+ | Opção | |
54
+ |---|---|
55
+ | `--producao` | Ambiente de produção. |
56
+ | `--mock` | SEFIN simulada e certificado de teste. Não combina com `--producao`. |
57
+ | `--cert <arquivo>` / `--password-file <arquivo>` | Certificado e senha, no lugar das variáveis de ambiente. |
58
+ | `-o, --output <arquivo>` | Onde salvar XML ou PDF. |
59
+ | `-y, --yes` | Confirma sem perguntar (para scripts). Sem terminal interativo, `emit` e `cancel` exigem `--yes`. |
60
+ | `--json` | Saída em JSON, para scripts e CI. |
61
+
62
+ ## Códigos de saída
63
+
64
+ | Código | Significado |
65
+ |---|---|
66
+ | `0` | Sucesso (ou cancelado por você na confirmação). |
67
+ | `1` | Erro de entrada, certificado, validação ou rede. |
68
+ | `2` | A SEFIN rejeitou; a explicação oficial de cada código é impressa. |
@@ -0,0 +1,54 @@
1
+ # Consultar uma NFS-e e baixar o DANFSe
2
+
3
+ ## Pela chave de acesso
4
+
5
+ ```ts
6
+ import { ConsultaNfseError, obterNfse } from '@useinvio/nfse-sdk';
7
+
8
+ try {
9
+ const nota = await obterNfse(chaveAcesso, certificado, 'restrita');
10
+ console.log(nota.resumo.nNFSe, nota.resumo.cStat, nota.resumo.valores.vLiq);
11
+ console.log(nota.nfseXml); // XML autorizado, já descompactado
12
+ } catch (error) {
13
+ if (error instanceof ConsultaNfseError && error.notFound) console.log('NFS-e não existe neste ambiente');
14
+ else throw error;
15
+ }
16
+ ```
17
+
18
+ `resumo` (`NfseResumo`) traz:
19
+
20
+ | Campo | Conteúdo |
21
+ |---|---|
22
+ | `chaveAcesso`, `nNFSe`, `nDFSe` | Identificação da nota. |
23
+ | `cStat`, `dhProc` | Situação e data/hora de processamento. |
24
+ | `ambiente` | `restrita` ou `producao`, lido do `tpAmb` da DPS. |
25
+ | `emitente`, `tomador` | `CNPJ`/`CPF`/`NIF`, `IM`, `xNome`. |
26
+ | `dps` | `id`, `serie`, `nDPS`, `dhEmi`, `dCompet` da DPS de origem. |
27
+ | `valores` | `vServ`, `vBC`, `pAliqAplic`, `vISSQN`, `vTotalRet`, `vLiq`. |
28
+ | `xLocEmi`, `xLocPrestacao`, `cLocIncid`, `xLocIncid`, `xTribNac` | Locais e descrição da tributação. |
29
+
30
+ Os valores ficam como strings, exatamente como no XML. Para ler um XML que você
31
+ já tem, use `lerNfse(xml)`. No cliente: `client.invoices.retrieve(chave)`.
32
+
33
+ A resposta crua (`{ status, body }`) continua em `consultarNfse(...)` e
34
+ `client.invoices.get(...)`.
35
+
36
+ ## Pelo Id da DPS
37
+
38
+ `consultarDps(dpsId, certificado)` chama `GET /dps/{id}` e devolve a chave de
39
+ acesso gerada. Para o fluxo completo depois de um timeout, use
40
+ [`recuperarNfsePorDps`](recuperar-emissao.md).
41
+
42
+ ## DANFSe em PDF
43
+
44
+ ```ts
45
+ import { writeFile } from 'node:fs/promises';
46
+ import { baixarDanfse } from '@useinvio/nfse-sdk';
47
+
48
+ const danfse = await baixarDanfse(chaveAcesso, certificado, 'restrita');
49
+ if (danfse.pdf) await writeFile(`danfse-${chaveAcesso}.pdf`, danfse.pdf);
50
+ else console.error(danfse.status, danfse.body);
51
+ ```
52
+
53
+ O PDF oficial vem do ADN (`GET /danfse/{chave}`). No cliente:
54
+ `client.invoices.danfse(chave)`.
@@ -0,0 +1,172 @@
1
+ # Emitir uma NFS-e
2
+
3
+ Há três entradas. Todas assinam, compactam, enviam por mTLS e devolvem
4
+ `ResultadoEmissaoNota` (`chaveAcesso`, `dpsId`, `nfseXml`, `status`, `body`).
5
+
6
+ | Situação | Use |
7
+ |---|---|
8
+ | Você monta os dados e quer que o SDK gere o XML | `emitirNfse(notaJson, certificado)` |
9
+ | Prefere configurar certificado, ambiente e defaults uma vez | `new NfseClient(...)` e `client.invoices.create(...)` |
10
+ | Outro sistema já gera o XML da DPS | `emitirNfse(xmlString, certificado)` |
11
+
12
+ ## A partir de JSON declarativo
13
+
14
+ A referência de campos está em [JSON_MAPPING.md](../../JSON_MAPPING.md) e as
15
+ combinações fiscais aceitas em [FISCAL_MATRIX.md](../FISCAL_MATRIX.md). O
16
+ builder valida formatos e combinações mínimas antes de gerar XML, mas não
17
+ escolhe códigos fiscais por você.
18
+
19
+ ```ts
20
+ import { emitirNfse, NfseCertificate, type DpsJsonRequest } from '@useinvio/nfse-sdk';
21
+
22
+ const nota: DpsJsonRequest = {
23
+ ambiente: 'restrita',
24
+ prestador: {
25
+ cnpj: '12345678000195',
26
+ cLocEmi: '4106902',
27
+ serie: '1601',
28
+ opSimpNac: '1',
29
+ regEspTrib: '0',
30
+ },
31
+ servico: {
32
+ cTribNac: '010201',
33
+ xDescServ: 'Desenvolvimento de software',
34
+ cLocPrestacao: '4106902',
35
+ cNBS: '115022000',
36
+ },
37
+ emissao: {
38
+ nDPS: '1',
39
+ dhEmi: '2026-06-15T10:30:00-03:00',
40
+ dCompet: '2026-06-01',
41
+ valores: { vServ: '1500.00' },
42
+ tributacaoMunicipal: {
43
+ tribISSQN: '1',
44
+ tpRetISSQN: '1',
45
+ },
46
+ tributacaoFederal: {
47
+ piscofins: { CST: '07' },
48
+ },
49
+ totTrib: {
50
+ pTotTribFed: '0.00',
51
+ pTotTribEst: '0.00',
52
+ pTotTribMun: '5.00',
53
+ },
54
+ },
55
+ };
56
+
57
+ const resultado = await emitirNfse(nota, NfseCertificate.fromEnv());
58
+ ```
59
+
60
+ > `pTotTribFed`, `pTotTribEst` e `pTotTribMun` são percentuais com duas casas
61
+ > (ex.: `"5.00"`), diferentes das alíquotas com quatro casas (`pAliq`). O shape
62
+ > simplificado legado de `tribNac` é rejeitado: IBS/CBS exige o bloco RTC
63
+ > `IBSCBS`, ainda não implementado.
64
+
65
+ ## Com o cliente
66
+
67
+ O `NfseClient` usa os mesmos campos (`prestador`, `servico`, `emissao`). Os
68
+ `defaults` seguem o mesmo formato e podem ser sobrescritos em cada chamada.
69
+
70
+ ```ts
71
+ import { NfseCertificate, NfseClient } from '@useinvio/nfse-sdk';
72
+
73
+ const client = new NfseClient({
74
+ environment: 'sandbox',
75
+ certificate: NfseCertificate.fromEnv(),
76
+ defaults: {
77
+ prestador: {
78
+ cnpj: '12345678000195',
79
+ cLocEmi: '4106902',
80
+ serie: '1601',
81
+ opSimpNac: '1',
82
+ regEspTrib: '0',
83
+ },
84
+ servico: {
85
+ cTribNac: '010201',
86
+ xDescServ: 'Servico ficticio de desenvolvimento de software',
87
+ cLocPrestacao: '4106902',
88
+ },
89
+ },
90
+ });
91
+
92
+ const invoice = await client.invoices.create({
93
+ emissao: {
94
+ nDPS: '1',
95
+ valores: { vServ: '1000.00' },
96
+ tomador: {
97
+ CPF: '00000000000',
98
+ xNome: 'Cliente Exemplo',
99
+ end: {
100
+ endNac: { cMun: '4106902', CEP: '80010000' },
101
+ xLgr: 'Rua Exemplo', nro: '100', xBairro: 'Centro',
102
+ },
103
+ },
104
+ tributacaoMunicipal: { tribISSQN: '1', tpRetISSQN: '1' },
105
+ tributacaoFederal: { piscofins: { CST: '07' } },
106
+ totTrib: { pTotTribFed: '0.00', pTotTribEst: '0.00', pTotTribMun: '5.00' },
107
+ },
108
+ });
109
+ ```
110
+
111
+ `environment: 'sandbox'` é alias de `restrita`; `production`, de `producao`.
112
+ `client.invoices.buildDpsJson(...)` devolve o JSON combinado sem assinar nem enviar.
113
+
114
+ ## A partir de XML pronto
115
+
116
+ ```ts
117
+ const resultado = await emitirNfse(dpsXml, certificado, { ambiente: 'restrita' });
118
+ ```
119
+
120
+ O XML precisa ter `Id` válido em `<infDPS>` e `tpAmb` igual ao ambiente de envio.
121
+ `options.dpsId` só seleciona a referência da assinatura; não corrige um XML
122
+ incompleto nem dispensa o XSD.
123
+
124
+ ## Inspecionar o XML antes de enviar
125
+
126
+ ```ts
127
+ import { buildDpsFromJson, validateDpsXmlAgainstXsd } from '@useinvio/nfse-sdk';
128
+
129
+ const { id, xml } = buildDpsFromJson(nota); // XML não assinado
130
+ validateDpsXmlAgainstXsd(xml); // lança DpsXsdValidationError se violar o XSD
131
+ ```
132
+
133
+ Para validar o JSON sem lançar erro, use `validateDpsJsonRequest(nota)`: retorna
134
+ `valid`, `issues`, `warnings` e uma cópia `normalizedPayload`.
135
+
136
+ ## Preparar e transmitir separadamente
137
+
138
+ Salve a DPS assinada antes de enviar; assim uma queda de conexão nunca deixa
139
+ você sem saber o que foi transmitido.
140
+
141
+ ```ts
142
+ import { prepararNota, transmitirNotaPreparada } from '@useinvio/nfse-sdk';
143
+
144
+ const preparada = prepararNota(nota, certificado); // valida, assina e verifica a assinatura
145
+ await salvar(preparada.dpsId, preparada.signedXml);
146
+ const resultado = await transmitirNotaPreparada(preparada, certificado);
147
+ ```
148
+
149
+ ## Série e número da DPS
150
+
151
+ A SEFIN identifica uma DPS por prestador, município, `serie` e `nDPS`. Mantenha
152
+ uma `serie` fixa (em `prestador.serie` ou por emissão em `emissao.serie`) e
153
+ incremente `nDPS` a cada nova DPS: `1601/1`, `1601/2`, `1601/3`...
154
+
155
+ O Emissor Web usa séries como `70000`. Para emissão via API, use uma série
156
+ válida para API (ex.: `1601`) e controle o `nDPS` na sua aplicação.
157
+
158
+ ## Regras que o SDK aplica
159
+
160
+ - Campos opcionais devem ser omitidos ou `undefined`; string vazia, `null` e
161
+ tipos incorretos são rejeitados.
162
+ - Valores monetários são arredondados para duas casas; valores de serviço que
163
+ arredondam para zero são rejeitados.
164
+ - Divergências entre `vServ` e a conversão cambial viram `warnings`, preservando
165
+ o valor explícito.
166
+ - Ambiente para JSON: `options.ambiente` → `request.ambiente` → `restrita`.
167
+ - HTTP 2xx sem XML autorizado e chave de 50 dígitos consistente gera
168
+ `EmitirNotaError` com `INVALID_AUTHORIZATION_RESPONSE`. Concilie antes de
169
+ reenviar (veja [Recuperar uma emissão](recuperar-emissao.md)).
170
+
171
+ Exemplos testados: [`examples/payloads.ts`](../../examples/payloads.ts) traz um
172
+ cenário nacional e um de exportação.
@@ -0,0 +1,33 @@
1
+ # Recuperar uma emissão após timeout
2
+
3
+ `POST /nfse` não é idempotente, por isso o SDK **nunca** repete uma emissão
4
+ automaticamente. Se a conexão cair sem resposta, a nota pode ter sido
5
+ autorizada ou não. Pergunte à SEFIN antes de reenviar:
6
+
7
+ ```ts
8
+ import { prepararNota, recuperarNfsePorDps, transmitirNotaPreparada } from '@useinvio/nfse-sdk';
9
+
10
+ const nota = prepararNota(dps, certificado); // persista nota.dpsId e nota.signedXml antes de enviar
11
+ try {
12
+ return await transmitirNotaPreparada(nota, certificado, { timeoutMs: 30_000 });
13
+ } catch (error) {
14
+ if (error instanceof Error && error.name === 'EmitirNotaError') throw error; // rejeição real
15
+ const existente = await recuperarNfsePorDps(nota.dpsId, certificado, nota.ambiente, { retries: 3 });
16
+ return existente ?? transmitirNotaPreparada(nota, certificado); // null: a DPS não chegou à SEFIN
17
+ }
18
+ ```
19
+
20
+ `recuperarNfsePorDps` chama `GET /dps/{id}` e, se a DPS foi processada,
21
+ `GET /nfse/{chave}`. O resultado é o mesmo de [`obterNfse`](consultar.md):
22
+
23
+ | Resultado | Significado | O que fazer |
24
+ |---|---|---|
25
+ | `NfseConsultada` | A nota foi autorizada antes da queda. | Use o resultado; não reenvie. |
26
+ | `null` | A SEFIN não conhece a DPS. | Reenvie a **mesma** DPS assinada (mesmo `Id`). |
27
+ | `ConsultaNfseError` | A consulta falhou. | Tente de novo mais tarde; não reenvie às cegas. |
28
+
29
+ Reenvie sempre a mesma DPS assinada, nunca uma DPS nova: o `Id` (prestador,
30
+ série e número) identifica a DPS na SEFIN, e um número novo geraria outra nota.
31
+
32
+ No cliente: `client.invoices.recover(dpsId)`. Exemplo completo em
33
+ [`examples/recover-emission.ts`](../../examples/recover-emission.ts).
@@ -0,0 +1,70 @@
1
+ # Tratar rejeições e erros
2
+
3
+ Cada etapa lança um erro tipado. Use `instanceof` para decidir o que fazer.
4
+
5
+ ```ts
6
+ import {
7
+ CertificateValidationError,
8
+ DpsFiscalValidationError,
9
+ DpsXsdValidationError,
10
+ EmitirNotaError,
11
+ emitirNfse,
12
+ } from '@useinvio/nfse-sdk';
13
+
14
+ try {
15
+ await emitirNfse(nota, certificado);
16
+ } catch (error) {
17
+ if (error instanceof DpsFiscalValidationError) {
18
+ // JSON inválido antes de gerar XML: corrija a entrada
19
+ for (const issue of error.issues) console.log(issue.code, issue.path, issue.message);
20
+ } else if (error instanceof DpsXsdValidationError) {
21
+ // XML viola o XSD oficial: error.details traz o diagnóstico do libxml2
22
+ } else if (error instanceof CertificateValidationError) {
23
+ // senha, leitura ou vigência do A1: error.code
24
+ } else if (error instanceof EmitirNotaError) {
25
+ // a SEFIN respondeu com rejeição (ou com resposta 2xx incompleta)
26
+ for (const rejeicao of error.erros) {
27
+ console.log(rejeicao.Codigo, rejeicao.Descricao, rejeicao.Complemento);
28
+ }
29
+ } else {
30
+ throw error; // rede, timeout, XsdEngineUnavailableError: o resultado é incerto
31
+ }
32
+ }
33
+ ```
34
+
35
+ ## Rejeição da SEFIN ou resultado incerto?
36
+
37
+ | Erro | A nota foi emitida? | Ação |
38
+ |---|---|---|
39
+ | `DpsFiscalValidationError`, `DpsXsdValidationError`, `CertificateValidationError` | Não: nada foi enviado. | Corrija a entrada ou o certificado. |
40
+ | `EmitirNotaError` com `status` 4xx | Não. | Corrija conforme `erros` e envie de novo. |
41
+ | `EmitirNotaError` com `INVALID_AUTHORIZATION_RESPONSE` | Talvez. | [Recupere pela DPS](recuperar-emissao.md) antes de reenviar. |
42
+ | Erro de rede ou timeout | Talvez. | [Recupere pela DPS](recuperar-emissao.md) antes de reenviar. |
43
+ | `XsdEngineUnavailableError` | Não: falha local de infraestrutura. | Tente de novo; não é rejeição fiscal. |
44
+
45
+ ## Explicar um código
46
+
47
+ ```ts
48
+ import { explicarErros, explicarRejeicao } from '@useinvio/nfse-sdk';
49
+
50
+ explicarRejeicao('E0014');
51
+ // { codigo: 'E0014', conhecida: true, mensagemOficial: 'Conjunto de Série, Número, ...',
52
+ // regra: '...', campo: 'NFSe/infNFSe/DPS/infDPS/serie', nivel: 1, fonte: { documento, url } }
53
+
54
+ explicarErros(error.erros); // uma explicação por erro de EmitirNotaError ou RegistrarEventoError
55
+ ```
56
+
57
+ O catálogo tem as regras de rejeição dos anexos oficiais publicados em
58
+ gov.br/nfse (Anexo I v1.01 de 09/02/2026, DPS e NFS-e; Anexo II v1.01 de
59
+ 22/01/2026, eventos), com o texto oficial sem alterações. Um código fora do
60
+ catálogo volta com `conhecida: false`: não há causa inventada.
61
+ `buscarRejeicao(codigo)` e `listarRejeicoes()` dão acesso direto às regras.
62
+ Cada código também tem uma página: [docs/rejeicoes](../rejeicoes/README.md). No
63
+ terminal: `npx nfse explain E0014`.
64
+
65
+ ## Normalizar o corpo da resposta
66
+
67
+ `extrairErros(body)` normaliza os formatos de erro da SEFIN (`erros`, `Erros`,
68
+ `erro`, objeto único) para `{ Codigo, Descricao, Complemento? }`.
69
+
70
+ A lista completa de classes e códigos está em [Erros](../referencia/erros.md).
@@ -0,0 +1,79 @@
1
+ # Testar sem certificado e sem rede
2
+
3
+ `@useinvio/nfse-sdk/testing` traz duas peças para testar a sua integração de
4
+ ponta a ponta, no CI, sem certificado A1 real e sem chamar a SEFIN:
5
+
6
+ - `createTestCertificate()`: um certificado A1 autoassinado com o CNPJ (ou CPF)
7
+ nos mesmos campos ICP-Brasil de um certificado real;
8
+ - `installMockSefin()`: uma SEFIN e um ADN simulados dentro do processo. Toda
9
+ chamada do SDK é respondida localmente até `restore()`.
10
+
11
+ ```ts
12
+ import test from 'node:test';
13
+ import assert from 'node:assert/strict';
14
+ import { cancelarNfse, emitirNfse, obterNfse } from '@useinvio/nfse-sdk';
15
+ import { createTestCertificate, installMockSefin } from '@useinvio/nfse-sdk/testing';
16
+
17
+ const { certificate } = createTestCertificate({ cnpj: '12345678000195' });
18
+
19
+ test('emite e cancela', async (t) => {
20
+ const mock = installMockSefin();
21
+ t.after(() => mock.restore());
22
+
23
+ const { chaveAcesso } = await emitirNfse(minhaNota, certificate);
24
+ const { resumo } = await obterNfse(chaveAcesso, certificate);
25
+ assert.equal(resumo.emitente.CNPJ, '12345678000195');
26
+
27
+ await cancelarNfse({ chaveAcesso, cMotivo: '1', xMotivo: 'Teste de cancelamento' }, certificate);
28
+ assert.equal(mock.notas.get(chaveAcesso)?.cancelada, true);
29
+ });
30
+ ```
31
+
32
+ ## O que a SEFIN simulada verifica
33
+
34
+ - A DPS e o pedido de evento contra o XSD oficial (como a SEFIN, responde `E1235`).
35
+ - A assinatura XMLDSIG com o certificado da chamada (`E0714`).
36
+ - O ambiente do XML contra o ambiente de envio (`E0006`, `E1845`).
37
+ - DPS repetida (`E0014`) e cancelamento repetido (`E0840`, `E0845`).
38
+
39
+ As mensagens vêm do catálogo oficial, como na SEFIN. A simulação **não** aplica
40
+ regras fiscais nem municipais: uma nota aceita pelo mock ainda pode ser rejeitada
41
+ na produção restrita.
42
+
43
+ Rotas simuladas: emissão, consulta por chave e por DPS, registro e consulta de
44
+ eventos, DANFSe (um PDF mínimo), distribuição por NSU, eventos por chave e
45
+ parâmetros municipais (respostas genéricas).
46
+
47
+ ## Simular falhas
48
+
49
+ ```ts
50
+ mock.rejectNext('E0312'); // próxima chamada rejeitada com a mensagem oficial do código
51
+ mock.rejectNext([{ Codigo: 'E0001', Descricao: '...' }], { status: 400, path: /^\/nfse$/ });
52
+ mock.failNext(); // erro de rede antes de a SEFIN processar
53
+ mock.dropResponseNext(); // a SEFIN processa, mas a resposta se perde
54
+ mock.route('GET', /^\/parametrizacao\/.+\/aliquota$/, () => ({ status: 200, body: '{"aliquota":2}' }));
55
+ ```
56
+
57
+ `dropResponseNext` é o cenário de [recuperação após timeout](recuperar-emissao.md):
58
+ a nota existe na SEFIN, mas a sua aplicação não recebeu a chave.
59
+
60
+ ## Inspecionar
61
+
62
+ - `mock.notas`: notas emitidas, por chave (`nfseXml`, `dpsId`, `cancelada`, `eventos`).
63
+ - `mock.requests`: cada chamada recebida, com método, host, caminho e status.
64
+
65
+ ## Certificados de teste
66
+
67
+ ```ts
68
+ createTestCertificate({ cpf: '39053344705' }); // e-CPF
69
+ createTestCertificate({ validDays: -1 }); // vencido: testa CERTIFICATE_EXPIRED
70
+ const { pfx, password } = createTestCertificate(); // bytes do PFX para testar fromPfx/fromEnv
71
+ ```
72
+
73
+ O certificado é autoassinado e não é ICP-Brasil: a SEFIN real o recusa.
74
+
75
+ ## No servidor MCP
76
+
77
+ `NFSE_MCP_MOCK=1` liga a mesma SEFIN simulada dentro do servidor MCP, para
78
+ experimentar as ferramentas de emissão sem certificado. Veja
79
+ [Agentes de IA e MCP](agentes-ia.md).
@@ -0,0 +1,33 @@
1
+ # Timeouts, novas tentativas e cancelamento
2
+
3
+ Toda função de rede aceita um último argumento `SefinRequestOptions`:
4
+
5
+ | Opção | Padrão | Efeito |
6
+ |---|---|---|
7
+ | `timeoutMs` | `60000` | Tempo máximo de cada requisição. |
8
+ | `retries` | `0` | Novas tentativas com backoff exponencial (0,5 s, 1 s, 2 s, até 4 s, com jitter). |
9
+ | `signal` | – | `AbortSignal` que cancela a requisição e as tentativas pendentes. |
10
+
11
+ `retries` vale só para GETs e só para falhas transitórias: erro de rede e HTTP
12
+ 429, 502, 503 ou 504. Emissão (`POST /nfse`) e eventos nunca são repetidos,
13
+ mesmo que você passe `retries`, porque não são idempotentes. Para emissão, veja
14
+ [Recuperar uma emissão após timeout](recuperar-emissao.md).
15
+
16
+ ```ts
17
+ import { obterNfse } from '@useinvio/nfse-sdk';
18
+
19
+ const nota = await obterNfse(chaveAcesso, certificado, 'producao', {
20
+ timeoutMs: 15_000,
21
+ retries: 3,
22
+ signal: AbortSignal.timeout(60_000),
23
+ });
24
+ ```
25
+
26
+ No cliente, configure uma vez para todas as chamadas:
27
+ `new NfseClient({ ..., request: { timeoutMs: 30_000, retries: 2 } })`.
28
+
29
+ ## Métricas de latência
30
+
31
+ Com `NFSE_SEFIN_LATENCY_METRICS=1`, o SDK emite uma métrica por requisição
32
+ (operação, ambiente, status e duração) para o observador registrado em
33
+ `setSefinRequestObserver`. O guia completo está em [METRICS.md](../METRICS.md).
@@ -0,0 +1,35 @@
1
+ # Ambientes
2
+
3
+ | Chave | Alias no `NfseClient` | SEFIN (emissão, consulta, eventos) | ADN (DANFSe, distribuição, parâmetros) |
4
+ |---|---|---|---|
5
+ | `restrita` (padrão) | `sandbox` | `https://sefin.producaorestrita.nfse.gov.br/SefinNacional` | `https://adn.producaorestrita.nfse.gov.br` |
6
+ | `producao` | `production` | `https://sefin.nfse.gov.br/SefinNacional` | `https://adn.nfse.gov.br` |
7
+
8
+ ```ts
9
+ import { resolveAdnBaseUrl, resolveSefinBaseUrl, TP_AMB } from '@useinvio/nfse-sdk';
10
+
11
+ resolveSefinBaseUrl('restrita'); // URL da SEFIN
12
+ resolveAdnBaseUrl('producao'); // URL do ADN
13
+ TP_AMB.restrita; // '2', valor de <tpAmb> no XML
14
+ ```
15
+
16
+ - `restrita` é a produção restrita oficial: as notas não têm validade jurídica,
17
+ mas passam pelas mesmas validações. Use-a para testes e homologação.
18
+ - O `tpAmb` dentro do XML precisa coincidir com o ambiente de envio; o SDK
19
+ recusa a divergência antes de transmitir.
20
+ - No `NfseClient`, o ambiente segue: opção da chamada → `defaults.ambiente` →
21
+ `environment` do construtor. O cliente não lembra o ambiente da última emissão.
22
+
23
+ ## Endpoints usados
24
+
25
+ | Operação | Método e caminho | Serviço |
26
+ |---|---|---|
27
+ | Emitir | `POST /nfse` | SEFIN |
28
+ | Consultar NFS-e | `GET /nfse/{chave}` | SEFIN |
29
+ | Consultar DPS | `GET /dps/{id}` | SEFIN |
30
+ | Registrar evento | `POST /nfse/{chave}/eventos` | SEFIN |
31
+ | Consultar evento | `GET /nfse/{chave}/eventos/{tpEvento}/{nSeqEvento}` | SEFIN |
32
+ | DANFSe | `GET /danfse/{chave}` | ADN |
33
+ | Distribuição | `GET /contribuintes/DFe/{nsu}` | ADN |
34
+ | Eventos por chave | `GET /contribuintes/NFSe/{chave}/Eventos` | ADN |
35
+ | Parâmetros municipais | `GET /parametrizacao/...` | ADN |