@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.
- package/CHANGELOG.md +176 -0
- package/README.en.md +146 -0
- package/README.md +76 -576
- package/dist/agent.d.ts +46 -1
- package/dist/agent.js +320 -4
- package/dist/agent.js.map +1 -1
- package/dist/cli.d.ts +2 -0
- package/dist/cli.js +520 -0
- package/dist/cli.js.map +1 -0
- package/dist/cliBin.d.ts +2 -0
- package/dist/cliBin.js +6 -0
- package/dist/cliBin.js.map +1 -0
- package/dist/cliExample.d.ts +6 -0
- package/dist/cliExample.js +29 -0
- package/dist/cliExample.js.map +1 -0
- package/dist/client.d.ts +47 -2
- package/dist/client.js +36 -6
- package/dist/client.js.map +1 -1
- package/dist/config.d.ts +3 -0
- package/dist/config.js +8 -0
- package/dist/config.js.map +1 -1
- package/dist/consultas.d.ts +34 -0
- package/dist/consultas.js +61 -0
- package/dist/consultas.js.map +1 -0
- package/dist/dpsJson.js +1 -41
- package/dist/dpsJson.js.map +1 -1
- package/dist/emissaoNota.d.ts +8 -3
- package/dist/emissaoNota.js +6 -3
- package/dist/emissaoNota.js.map +1 -1
- package/dist/eventoJson.d.ts +76 -0
- package/dist/eventoJson.js +128 -0
- package/dist/eventoJson.js.map +1 -0
- package/dist/eventosNfse.d.ts +38 -0
- package/dist/eventosNfse.js +73 -0
- package/dist/eventosNfse.js.map +1 -0
- package/dist/gzipB64.d.ts +5 -0
- package/dist/gzipB64.js +12 -0
- package/dist/gzipB64.js.map +1 -1
- package/dist/index.d.ts +17 -7
- package/dist/index.js +9 -4
- package/dist/index.js.map +1 -1
- package/dist/mcpCli.d.ts +2 -0
- package/dist/mcpCli.js +7 -0
- package/dist/mcpCli.js.map +1 -0
- package/dist/mcpServer.d.ts +49 -0
- package/dist/mcpServer.js +159 -0
- package/dist/mcpServer.js.map +1 -0
- package/dist/nfseXml.d.ts +48 -0
- package/dist/nfseXml.js +86 -0
- package/dist/nfseXml.js.map +1 -0
- package/dist/rejeicoes.d.ts +50 -0
- package/dist/rejeicoes.js +62 -0
- package/dist/rejeicoes.js.map +1 -0
- package/dist/rejeicoesCatalog.d.ts +10 -0
- package/dist/rejeicoesCatalog.js +4449 -0
- package/dist/rejeicoesCatalog.js.map +1 -0
- package/dist/sefinClient.d.ts +100 -6
- package/dist/sefinClient.js +211 -19
- package/dist/sefinClient.js.map +1 -1
- package/dist/signXml.js +9 -1
- package/dist/signXml.js.map +1 -1
- package/dist/testing.d.ts +80 -0
- package/dist/testing.js +285 -0
- package/dist/testing.js.map +1 -0
- package/dist/xmlBuild.d.ts +11 -0
- package/dist/xmlBuild.js +44 -0
- package/dist/xmlBuild.js.map +1 -0
- package/dist/xmlInspection.d.ts +1 -1
- package/dist/xmlInspection.js.map +1 -1
- package/dist/xsdValidation.d.ts +7 -0
- package/dist/xsdValidation.js +37 -15
- package/dist/xsdValidation.js.map +1 -1
- package/docs/README.md +47 -0
- package/docs/REGRAS_NEGOCIO_EMISSAO.md +8 -8
- package/docs/conceitos/como-funciona.md +87 -0
- package/docs/guias/adn.md +51 -0
- package/docs/guias/agentes-ia.md +111 -0
- package/docs/guias/cancelar.md +70 -0
- package/docs/guias/certificado.md +88 -0
- package/docs/guias/cli.md +68 -0
- package/docs/guias/consultar.md +54 -0
- package/docs/guias/emitir.md +172 -0
- package/docs/guias/recuperar-emissao.md +33 -0
- package/docs/guias/rejeicoes.md +70 -0
- package/docs/guias/testes.md +79 -0
- package/docs/guias/timeouts.md +33 -0
- package/docs/referencia/ambientes.md +35 -0
- package/docs/referencia/api.md +137 -0
- package/docs/referencia/erros.md +55 -0
- package/docs/tutoriais/primeira-nfse.md +130 -0
- package/examples/README.md +2 -0
- package/examples/cancel-nfse.ts +29 -0
- package/examples/recover-emission.ts +29 -0
- package/llms.txt +97 -92
- package/package.json +23 -3
package/README.md
CHANGED
|
@@ -1,52 +1,35 @@
|
|
|
1
1
|
# @useinvio/nfse-sdk
|
|
2
2
|
|
|
3
3
|
[](https://www.npmjs.com/package/@useinvio/nfse-sdk)
|
|
4
|
-
[](https://github.com/useinvio/nfse-sdk/actions/workflows/ci.yml)
|
|
5
5
|
[](https://nodejs.org/)
|
|
6
|
+
[](./LICENSE)
|
|
6
7
|
|
|
7
|
-
[Website](https://sdk.useinvio.com/)
|
|
8
|
+
[Website](https://sdk.useinvio.com/) · [Playground](https://useinvio.github.io/nfse-sdk/) · [Documentação](./docs/README.md) · [English](./README.en.md)
|
|
8
9
|
|
|
9
|
-
|
|
10
|
+
<img width="959" height="296" alt="NFS-e SDK" src="https://github.com/user-attachments/assets/d6c45737-e7ca-4ea7-a06f-d93b7c16e4f0" />
|
|
10
11
|
|
|
11
|
-
|
|
12
|
+
**Emita, consulte e cancele NFS-e Nacional em TypeScript, sem escrever XML fiscal na mão.**
|
|
12
13
|
|
|
13
|
-
|
|
14
|
+
Você descreve a nota em JSON; o SDK monta a DPS, valida contra o XSD oficial,
|
|
15
|
+
assina com o certificado A1, compacta, envia por mTLS para a SEFIN e devolve a
|
|
16
|
+
resposta oficial em formato estruturado. Sem binários nativos e sem `xmllint`.
|
|
14
17
|
|
|
15
|
-
|
|
18
|
+
## O que ele faz
|
|
16
19
|
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
-
|
|
30
|
-
- [Quick start](#quick-start)
|
|
31
|
-
- [Por que usar](#por-que-usar)
|
|
32
|
-
- [Escolhendo o ponto de entrada](#escolhendo-o-ponto-de-entrada)
|
|
33
|
-
- [Cliente orientado a recursos](#cliente-orientado-a-recursos)
|
|
34
|
-
- [Certificado A1](#certificado-a1)
|
|
35
|
-
- [Emitir a partir de XML pronto](#emitir-a-partir-de-xml-pronto)
|
|
36
|
-
- [Emitir a partir de JSON declarativo](#emitir-a-partir-de-json-declarativo)
|
|
37
|
-
- [Inspecionar o XML antes de enviar](#inspecionar-o-xml-antes-de-enviar)
|
|
38
|
-
- [Tratar rejeições da SEFIN](#tratar-rejeições-da-sefin)
|
|
39
|
-
- [Consultar uma NFS-e emitida](#consultar-uma-nfs-e-emitida)
|
|
40
|
-
- [Enviar evento](#enviar-evento-ex-cancelamento)
|
|
41
|
-
- [Série e número da DPS](#série-e-número-da-dps)
|
|
42
|
-
- [Ambientes](#ambientes)
|
|
43
|
-
- [Agentes de IA e MCP](#agentes-de-ia-e-mcp)
|
|
44
|
-
- [Referência da API](#referência-da-api)
|
|
45
|
-
- [Desenvolvimento](#desenvolvimento)
|
|
46
|
-
- [Publicação no npm](#publicação-no-npm)
|
|
47
|
-
- [Exemplos](#exemplos)
|
|
48
|
-
|
|
49
|
-
---
|
|
20
|
+
| Operação | Função | No `NfseClient` |
|
|
21
|
+
|---|---|---|
|
|
22
|
+
| Emitir a partir de JSON ou XML | `emitirNfse` | `invoices.create` |
|
|
23
|
+
| Validar sem enviar (JSON + XSD) | `validateDpsJsonRequest`, `buildDpsFromJson` | `invoices.buildDpsJson` |
|
|
24
|
+
| Consultar com resultado tipado | `obterNfse` | `invoices.retrieve` |
|
|
25
|
+
| Recuperar uma emissão após timeout | `recuperarNfsePorDps` | `invoices.recover` |
|
|
26
|
+
| Cancelar / cancelar por substituição | `cancelarNfse`, `cancelarNfsePorSubstituicao` | `invoices.cancel`, `invoices.cancelBySubstitution` |
|
|
27
|
+
| Baixar o DANFSe em PDF | `baixarDanfse` | `invoices.danfse` |
|
|
28
|
+
| Distribuição por NSU e parâmetros municipais (ADN) | `distribuirDfePorNsu`, `consultarAliquota`… | `distribution`, `municipalParameters` |
|
|
29
|
+
| Explicar rejeições com o texto oficial | `explicarRejeicao`, `explicarErros`, [uma página por código](./docs/rejeicoes/README.md) | – |
|
|
30
|
+
| Usar pelo terminal | `npx nfse init`, `doctor`, `validate`, `emit`, `explain` | – |
|
|
31
|
+
| Testar sem certificado nem rede | `@useinvio/nfse-sdk/testing` | – |
|
|
32
|
+
| Servidor MCP e ferramentas para agentes de IA | `npx -y @useinvio/nfse-mcp`, `@useinvio/nfse-sdk/agent` | – |
|
|
50
33
|
|
|
51
34
|
## Instalação
|
|
52
35
|
|
|
@@ -54,22 +37,23 @@ O SDK cuida da camada protocolar: certificado A1, montagem de DPS, validações
|
|
|
54
37
|
npm install @useinvio/nfse-sdk
|
|
55
38
|
```
|
|
56
39
|
|
|
57
|
-
|
|
40
|
+
Node.js 20 ou mais recente. Nada para instalar no sistema: a validação XSD roda
|
|
41
|
+
em WebAssembly.
|
|
58
42
|
|
|
59
|
-
|
|
43
|
+
## Em 60 segundos
|
|
60
44
|
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
45
|
+
```bash
|
|
46
|
+
NFSE_CERT_PATH=./certificado.pfx
|
|
47
|
+
NFSE_CERT_PASSWORD=sua-senha
|
|
48
|
+
```
|
|
64
49
|
|
|
65
50
|
```ts
|
|
66
51
|
import { emitirNfse, EmitirNotaError, NfseCertificate } from '@useinvio/nfse-sdk';
|
|
67
52
|
|
|
68
|
-
|
|
69
|
-
const certificado = NfseCertificate.fromEnv();
|
|
53
|
+
const certificado = NfseCertificate.fromEnv(); // a chave privada nunca aparece em logs
|
|
70
54
|
|
|
71
55
|
const nota = {
|
|
72
|
-
ambiente: 'restrita' as const,
|
|
56
|
+
ambiente: 'restrita' as const, // produção restrita: o sandbox oficial
|
|
73
57
|
prestador: {
|
|
74
58
|
cnpj: '12345678000195',
|
|
75
59
|
cLocEmi: '4106902',
|
|
@@ -88,563 +72,79 @@ const nota = {
|
|
|
88
72
|
dhEmi: '2026-06-15T10:30:00-03:00',
|
|
89
73
|
dCompet: '2026-06-01',
|
|
90
74
|
valores: { vServ: '1500.00' },
|
|
91
|
-
tributacaoMunicipal: {
|
|
92
|
-
tribISSQN: '1',
|
|
93
|
-
tpRetISSQN: '1',
|
|
94
|
-
},
|
|
95
|
-
tributacaoFederal: {
|
|
96
|
-
piscofins: { CST: '07' },
|
|
97
|
-
},
|
|
98
|
-
totTrib: {
|
|
99
|
-
pTotTribFed: '0.00',
|
|
100
|
-
pTotTribEst: '0.00',
|
|
101
|
-
pTotTribMun: '5.00',
|
|
102
|
-
},
|
|
103
|
-
},
|
|
104
|
-
};
|
|
105
|
-
|
|
106
|
-
try {
|
|
107
|
-
const resultado = await emitirNfse(nota, certificado);
|
|
108
|
-
console.log(resultado.chaveAcesso); // chave de 50 dígitos
|
|
109
|
-
console.log(resultado.nfseXml); // XML autorizado pela SEFIN
|
|
110
|
-
} catch (error) {
|
|
111
|
-
if (error instanceof EmitirNotaError) {
|
|
112
|
-
for (const rejeicao of error.erros) {
|
|
113
|
-
console.error(rejeicao.Codigo, rejeicao.Descricao);
|
|
114
|
-
}
|
|
115
|
-
}
|
|
116
|
-
}
|
|
117
|
-
```
|
|
118
|
-
|
|
119
|
-
---
|
|
120
|
-
|
|
121
|
-
## Por que usar
|
|
122
|
-
|
|
123
|
-
- Entrada JSON tipada para construir DPS no layout nacional.
|
|
124
|
-
- Assinatura XMLDSIG, GZip/Base64 e mTLS encapsulados em uma API Node.js.
|
|
125
|
-
- Erros oficiais da SEFIN preservados em formato estruturado.
|
|
126
|
-
- Builder separado para inspecionar ou salvar o XML antes de transmitir.
|
|
127
|
-
- Cliente `NfseClient` para configurar certificado, ambiente e defaults uma vez.
|
|
128
|
-
- Validação explícita de campos fiscais obrigatórios, sem defaults silenciosos.
|
|
129
|
-
|
|
130
|
-
---
|
|
131
|
-
|
|
132
|
-
## Escolhendo o ponto de entrada
|
|
133
|
-
|
|
134
|
-
| Situação | O que usar |
|
|
135
|
-
|---|---|
|
|
136
|
-
| Quer uma API de cliente com `client.invoices.create(...)` | `new NfseClient(...)` |
|
|
137
|
-
| Outro sistema já gera o XML da DPS | `emitirNfse(xmlString, certificado)` |
|
|
138
|
-
| Você monta os dados e quer que a SDK construa o XML | `emitirNfse(notaJson, certificado)` |
|
|
139
|
-
| Precisa inspecionar ou salvar o XML antes de enviar | `buildDpsFromJson(nota)` |
|
|
140
|
-
| Fluxo customizado (assinar, compactar ou enviar separadamente) | funções de baixo nível |
|
|
141
|
-
|
|
142
|
-
---
|
|
143
|
-
|
|
144
|
-
## Cliente orientado a recursos
|
|
145
|
-
|
|
146
|
-
Use `NfseClient` quando preferir configurar ambiente e certificado uma vez e
|
|
147
|
-
emitir notas por recursos, como `client.invoices.create(...)`.
|
|
148
|
-
|
|
149
|
-
O cliente usa os mesmos campos do JSON declarativo (`prestador`, `servico` e
|
|
150
|
-
`emissao`). Os `defaults` seguem o mesmo formato e podem ser sobrescritos em
|
|
151
|
-
cada chamada.
|
|
152
|
-
|
|
153
|
-
```ts
|
|
154
|
-
import { NfseCertificate, NfseClient } from '@useinvio/nfse-sdk';
|
|
155
|
-
|
|
156
|
-
const client = new NfseClient({
|
|
157
|
-
environment: 'sandbox',
|
|
158
|
-
certificate: NfseCertificate.fromEnv(),
|
|
159
|
-
defaults: {
|
|
160
|
-
prestador: {
|
|
161
|
-
cnpj: '12345678000195',
|
|
162
|
-
cLocEmi: '4106902',
|
|
163
|
-
serie: '1601',
|
|
164
|
-
opSimpNac: '1',
|
|
165
|
-
regEspTrib: '0',
|
|
166
|
-
},
|
|
167
|
-
servico: {
|
|
168
|
-
cTribNac: '010201',
|
|
169
|
-
xDescServ: 'Servico ficticio de desenvolvimento de software',
|
|
170
|
-
cLocPrestacao: '4106902',
|
|
171
|
-
},
|
|
172
|
-
},
|
|
173
|
-
});
|
|
174
|
-
|
|
175
|
-
const invoice = await client.invoices.create({
|
|
176
|
-
emissao: {
|
|
177
|
-
nDPS: '1',
|
|
178
|
-
valores: { vServ: '1000.00' },
|
|
179
|
-
tomador: {
|
|
180
|
-
CPF: '00000000000',
|
|
181
|
-
xNome: 'Cliente Exemplo',
|
|
182
|
-
end: {
|
|
183
|
-
endNac: { cMun: '4106902', CEP: '80010000' },
|
|
184
|
-
xLgr: 'Rua Exemplo', nro: '100', xBairro: 'Centro',
|
|
185
|
-
},
|
|
186
|
-
},
|
|
187
|
-
tributacaoMunicipal: {
|
|
188
|
-
tribISSQN: '1',
|
|
189
|
-
tpRetISSQN: '1',
|
|
190
|
-
},
|
|
191
|
-
tributacaoFederal: {
|
|
192
|
-
piscofins: { CST: '07' },
|
|
193
|
-
},
|
|
194
|
-
totTrib: {
|
|
195
|
-
pTotTribFed: '0.00',
|
|
196
|
-
pTotTribEst: '0.00',
|
|
197
|
-
pTotTribMun: '5.00',
|
|
198
|
-
},
|
|
199
|
-
},
|
|
200
|
-
});
|
|
201
|
-
|
|
202
|
-
console.log(invoice.chaveAcesso);
|
|
203
|
-
console.log(invoice.nfseXml);
|
|
204
|
-
```
|
|
205
|
-
|
|
206
|
-
`environment: 'sandbox'` é alias de `restrita`, e `production` é alias de
|
|
207
|
-
`producao`. `certificate` aceita um `NfseCertificate` ou, por compatibilidade,
|
|
208
|
-
`{ content, password }` com o PFX em `Buffer` ou base64.
|
|
209
|
-
|
|
210
|
-
Para inspecionar a DPS gerada sem assinar nem enviar:
|
|
211
|
-
|
|
212
|
-
```ts
|
|
213
|
-
const dps = client.invoices.buildDpsJson({
|
|
214
|
-
prestador: {
|
|
215
|
-
serie: '1701',
|
|
216
|
-
},
|
|
217
|
-
emissao: {
|
|
218
|
-
nDPS: '2',
|
|
219
|
-
valores: { vServ: '2500.00' },
|
|
220
75
|
tributacaoMunicipal: { tribISSQN: '1', tpRetISSQN: '1' },
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
});
|
|
224
|
-
```
|
|
225
|
-
|
|
226
|
-
---
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
### Validacao e compatibilidade
|
|
230
|
-
|
|
231
|
-
`validateDpsJsonRequest(entrada)` retorna `valid`, `issues` (erros), `warnings` (avisos) e uma copia `normalizedPayload`. Campos opcionais devem ser omitidos ou `undefined`; strings vazias, `null` e tipos incorretos sao rejeitados. Consulte [o mapeamento JSON](JSON_MAPPING.md) para os limites de datas e valores.
|
|
232
|
-
|
|
233
|
-
Divergencias entre `vServ` e a conversao cambial geram avisos, preservando o valor explicito. O mesmo vale para o valor estrangeiro do bloco de comercio exterior. Todos os valores monetarios sao arredondados para duas casas, inclusive `comExt.vServMoeda`; valores de servico que arredondam para zero sao rejeitados. Leia `warnings` antes da emissao quando quiser apresentar esses avisos ao usuario.
|
|
234
|
-
|
|
235
|
-
`prepararNota` verifica a vigencia do certificado antes de assinar; `transmitirNotaPreparada` verifica novamente antes do envio. `assertCertificateValidity(certificado, now?)` expoe essa verificacao. A checagem de titularidade continua explicita via `assertCertificateForProvider(certificado, documento, now?)`; o SDK nao resolve representacao fiscal.
|
|
236
|
-
|
|
237
|
-
O ambiente para JSON segue `options.ambiente` → `request.ambiente` → `restrita`, tanto no XML quanto no destino. XML externo usa `tpAmb` e rejeita uma opcao divergente. Notas preparadas tambem precisam ter o mesmo ambiente no XML assinado e no envio.
|
|
238
|
-
|
|
239
|
-
`client.invoices.get(chaveAcesso, { ambiente: 'producao' })` permite consultar uma emissao feita com sobrescrita de ambiente. Sem opcao, usa `defaults.ambiente` ou o ambiente do construtor. O cliente nao memoriza o ambiente da ultima emissao.
|
|
240
|
-
|
|
241
|
-
HTTP 2xx sem XML NFS-e bem formado e chave de 50 digitos consistente gera `EmitirNotaError` com codigo `INVALID_AUTHORIZATION_RESPONSE`, preservando corpo, status e DPS. Isso nao confirma rejeicao fiscal: concilie a emissao antes de reenviar. Nao ha retentativa automatica nem verificacao da assinatura da NFS-e recebida.
|
|
242
|
-
|
|
243
|
-
Veja [as correcoes e mudancas de comportamento](docs/CORRECOES_EMISSAO.md) e os [payloads nacional e exterior testados](examples/payloads.ts). Os exemplos declaram cenarios ilustrativos; a aplicacao escolhe o enquadramento aplicavel.
|
|
244
|
-
|
|
245
|
-
> **Migracao para 3.0.0:** esta versao tem breaking changes de validacao e resposta de autorizacao. Leia o guia curto em [CORRECOES_EMISSAO.md](docs/CORRECOES_EMISSAO.md) antes de atualizar.
|
|
246
|
-
|
|
247
|
-
## Certificado A1
|
|
248
|
-
|
|
249
|
-
Use `NfseCertificate`. Ele lê o `.pfx`/`.p12` uma vez, mantém a chave privada fora do objeto e descarta a senha e os bytes do arquivo. `console.log(certificado)` e `JSON.stringify(certificado)` mostram só CNPJ/CPF, validade e fingerprint.
|
|
250
|
-
|
|
251
|
-
### Variáveis de ambiente (recomendado)
|
|
252
|
-
|
|
253
|
-
```bash
|
|
254
|
-
NFSE_CERT_PATH=./certificado.pfx # ou NFSE_CERT_BASE64=<PFX em base64>
|
|
255
|
-
NFSE_CERT_PASSWORD=sua-senha # ou NFSE_CERT_PASSWORD_FILE=/run/secrets/nfse_cert_password
|
|
256
|
-
```
|
|
257
|
-
|
|
258
|
-
```ts
|
|
259
|
-
import { NfseCertificate } from '@useinvio/nfse-sdk';
|
|
260
|
-
|
|
261
|
-
const certificado = NfseCertificate.fromEnv();
|
|
262
|
-
console.log(certificado.cnpj, certificado.notAfter, certificado.daysUntilExpiry());
|
|
263
|
-
```
|
|
264
|
-
|
|
265
|
-
`NFSE_CERT_PASSWORD_FILE` lê a senha de um arquivo, o formato de secrets do Docker e do Kubernetes. Nunca versione o `.pfx` nem a senha.
|
|
266
|
-
|
|
267
|
-
### Outras origens
|
|
268
|
-
|
|
269
|
-
```ts
|
|
270
|
-
NfseCertificate.fromFile('./certificado.pfx', senha);
|
|
271
|
-
NfseCertificate.fromBase64(pfxBase64, senha); // aceita quebras de linha e data: URLs
|
|
272
|
-
NfseCertificate.fromPfx(pfxBuffer, senha); // ex.: PFX vindo do seu cofre de segredos
|
|
273
|
-
NfseCertificate.fromPem({ key, cert, passphrase }); // par extraído com openssl
|
|
274
|
-
```
|
|
275
|
-
|
|
276
|
-
### Vários emitentes (multi-tenant)
|
|
277
|
-
|
|
278
|
-
Crie um `NfseCertificate` por emitente e reaproveite-o. Cada certificado tem o próprio agente mTLS, então conexões nunca são compartilhadas entre emitentes. Guarde o PFX cifrado (KMS, Vault, Secrets Manager) e a senha em um segredo separado, e descriptografe só ao criar o objeto.
|
|
279
|
-
|
|
280
|
-
```ts
|
|
281
|
-
import { assertCertificateForProvider, NfseCertificate } from '@useinvio/nfse-sdk';
|
|
282
|
-
|
|
283
|
-
const cache = new Map<string, NfseCertificate>();
|
|
284
|
-
|
|
285
|
-
async function certificadoDoEmitente(cnpj: string): Promise<NfseCertificate> {
|
|
286
|
-
const cached = cache.get(cnpj);
|
|
287
|
-
if (cached?.isValidAt()) return cached;
|
|
288
|
-
const { pfx, senha } = await cofre.buscarCertificado(cnpj); // seu secret manager
|
|
289
|
-
const certificado = NfseCertificate.fromPfx(pfx, senha);
|
|
290
|
-
assertCertificateForProvider(certificado, cnpj);
|
|
291
|
-
cache.set(cnpj, certificado);
|
|
292
|
-
return certificado;
|
|
293
|
-
}
|
|
294
|
-
```
|
|
295
|
-
|
|
296
|
-
### Erros de certificado
|
|
297
|
-
|
|
298
|
-
Todos lançam `CertificateValidationError` com um `code` estável:
|
|
299
|
-
|
|
300
|
-
| `code` | Quando |
|
|
301
|
-
|---|---|
|
|
302
|
-
| `CERTIFICATE_INVALID_PASSWORD` | Senha incorreta. |
|
|
303
|
-
| `CERTIFICATE_UNREADABLE` | O conteúdo não é um PFX/PEM válido. |
|
|
304
|
-
| `CERTIFICATE_MISSING`, `CERTIFICATE_PASSWORD_MISSING` | Variáveis de ambiente ausentes em `fromEnv`. |
|
|
305
|
-
| `CERTIFICATE_NOT_YET_VALID`, `CERTIFICATE_EXPIRED` | Fora da vigência. Verificado antes de assinar e antes de enviar. |
|
|
306
|
-
| `CERTIFICATE_HOLDER_MISMATCH` | `assertCertificateForProvider` com CPF/CNPJ diferente do titular. |
|
|
307
|
-
|
|
308
|
-
> `loadPfx` e `loadPfxFromBuffer` continuam funcionando, mas retornam a chave privada em PEM dentro de um objeto comum. Prefira `NfseCertificate`.
|
|
309
|
-
|
|
310
|
-
---
|
|
311
|
-
|
|
312
|
-
## Emitir a partir de XML pronto
|
|
313
|
-
|
|
314
|
-
Use quando outro sistema (ERP, sistema legado) já gera o XML da DPS no layout nacional.
|
|
315
|
-
|
|
316
|
-
```ts
|
|
317
|
-
import { emitirNfse, NfseCertificate } from '@useinvio/nfse-sdk';
|
|
318
|
-
|
|
319
|
-
const certificado = NfseCertificate.fromEnv();
|
|
320
|
-
|
|
321
|
-
const resultado = await emitirNfse(dpsXml, certificado, { ambiente: 'restrita' });
|
|
322
|
-
|
|
323
|
-
console.log(resultado.chaveAcesso);
|
|
324
|
-
console.log(resultado.nfseXml);
|
|
325
|
-
```
|
|
326
|
-
|
|
327
|
-
> O XML precisa ter `Id` valido em `<infDPS>` e `tpAmb` consistente com o destino. `options.dpsId` apenas seleciona a referencia de assinatura; nao corrige um XML incompleto nem dispensa o XSD.
|
|
328
|
-
|
|
329
|
-
---
|
|
330
|
-
|
|
331
|
-
## Emitir a partir de JSON declarativo
|
|
332
|
-
|
|
333
|
-
Use quando a aplicação mantém os perfis de prestador e serviço e quer que a SDK monte o XML.
|
|
334
|
-
|
|
335
|
-
Para a referência completa de campos aceitos no JSON, consulte [JSON_MAPPING.md](./JSON_MAPPING.md).
|
|
336
|
-
O builder valida formatos e combinações mínimas antes de gerar XML, mas não escolhe códigos fiscais por você. Campos como `tributacaoMunicipal` e `totTrib` precisam ser informados explicita[...]
|
|
337
|
-
|
|
338
|
-
```ts
|
|
339
|
-
import { emitirNfse, NfseCertificate, type DpsJsonRequest } from '@useinvio/nfse-sdk';
|
|
340
|
-
|
|
341
|
-
const nota: DpsJsonRequest = {
|
|
342
|
-
ambiente: 'restrita',
|
|
343
|
-
prestador: {
|
|
344
|
-
cnpj: '12345678000195',
|
|
345
|
-
cLocEmi: '4106902',
|
|
346
|
-
serie: '1601',
|
|
347
|
-
opSimpNac: '1',
|
|
348
|
-
regEspTrib: '0',
|
|
349
|
-
},
|
|
350
|
-
servico: {
|
|
351
|
-
cTribNac: '010201',
|
|
352
|
-
xDescServ: 'Desenvolvimento de software',
|
|
353
|
-
cLocPrestacao: '4106902',
|
|
354
|
-
cNBS: '115022000',
|
|
355
|
-
},
|
|
356
|
-
emissao: {
|
|
357
|
-
nDPS: '1',
|
|
358
|
-
dhEmi: '2026-06-15T10:30:00-03:00',
|
|
359
|
-
dCompet: '2026-06-01',
|
|
360
|
-
valores: { vServ: '1500.00' },
|
|
361
|
-
tributacaoMunicipal: {
|
|
362
|
-
tribISSQN: '1',
|
|
363
|
-
tpRetISSQN: '1',
|
|
364
|
-
},
|
|
365
|
-
tributacaoFederal: {
|
|
366
|
-
piscofins: { CST: '07' },
|
|
367
|
-
},
|
|
368
|
-
totTrib: {
|
|
369
|
-
pTotTribFed: '0.00',
|
|
370
|
-
pTotTribEst: '0.00',
|
|
371
|
-
pTotTribMun: '5.00',
|
|
372
|
-
},
|
|
76
|
+
tributacaoFederal: { piscofins: { CST: '07' } },
|
|
77
|
+
totTrib: { pTotTribFed: '0.00', pTotTribEst: '0.00', pTotTribMun: '5.00' },
|
|
373
78
|
},
|
|
374
79
|
};
|
|
375
80
|
|
|
376
|
-
const certificado = NfseCertificate.fromEnv();
|
|
377
|
-
const resultado = await emitirNfse(nota, certificado);
|
|
378
|
-
```
|
|
379
|
-
|
|
380
|
-
> `pTotTribFed`, `pTotTribEst` e `pTotTribMun` são percentuais com duas casas decimais (ex.: `"5.00"`), diferentes das alíquotas com quatro casas (`pAliq`).
|
|
381
|
-
> O shape simplificado legado de `tribNac` é rejeitado: IBS/CBS no layout nacional `v1.01` exige o bloco RTC `IBSCBS`, ainda não implementado nesta SDK.
|
|
382
|
-
|
|
383
|
-
---
|
|
384
|
-
|
|
385
|
-
## Inspecionar o XML antes de enviar
|
|
386
|
-
|
|
387
|
-
Útil para comparar com um XML de referência, salvar snapshots ou depurar rejeições de schema.
|
|
388
|
-
|
|
389
|
-
```ts
|
|
390
|
-
import { buildDpsFromJson } from '@useinvio/nfse-sdk';
|
|
391
|
-
|
|
392
|
-
const { id, xml } = buildDpsFromJson(nota);
|
|
393
|
-
|
|
394
|
-
console.log(id); // ex.: "DPS..."
|
|
395
|
-
console.log(xml); // XML não assinado da DPS
|
|
396
|
-
```
|
|
397
|
-
|
|
398
|
-
---
|
|
399
|
-
|
|
400
|
-
## Tratar rejeições da SEFIN
|
|
401
|
-
|
|
402
|
-
`emitirNfse` lança `EmitirNotaError` quando a SEFIN retorna HTTP fora de 2xx ou resposta sem XML autorizado.
|
|
403
|
-
|
|
404
|
-
```ts
|
|
405
|
-
import { EmitirNotaError, emitirNfse } from '@useinvio/nfse-sdk';
|
|
406
|
-
|
|
407
81
|
try {
|
|
408
|
-
await emitirNfse(nota, certificado);
|
|
82
|
+
const { chaveAcesso, nfseXml } = await emitirNfse(nota, certificado);
|
|
83
|
+
console.log(chaveAcesso); // chave de 50 dígitos
|
|
409
84
|
} catch (error) {
|
|
410
85
|
if (!(error instanceof EmitirNotaError)) throw error;
|
|
411
|
-
|
|
412
|
-
console.log('HTTP:', error.status);
|
|
413
|
-
console.log('DPS ID:', error.dpsId);
|
|
414
|
-
|
|
415
|
-
for (const rejeicao of error.erros) {
|
|
416
|
-
console.log(rejeicao.Codigo); // código oficial SEFIN
|
|
417
|
-
console.log(rejeicao.Descricao); // descrição da rejeição
|
|
418
|
-
console.log(rejeicao.Complemento); // detalhe adicional (quando presente)
|
|
419
|
-
}
|
|
86
|
+
for (const rejeicao of error.erros) console.error(rejeicao.Codigo, rejeicao.Descricao);
|
|
420
87
|
}
|
|
421
88
|
```
|
|
422
89
|
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
```ts
|
|
428
|
-
import { consultarNfse } from '@useinvio/nfse-sdk';
|
|
429
|
-
|
|
430
|
-
const resposta = await consultarNfse(chaveAcesso, certificado, 'restrita');
|
|
431
|
-
|
|
432
|
-
if (resposta.status === 200) {
|
|
433
|
-
console.log(resposta.body);
|
|
434
|
-
} else {
|
|
435
|
-
console.error('Falha na consulta:', resposta.status, resposta.body);
|
|
436
|
-
}
|
|
437
|
-
```
|
|
438
|
-
|
|
439
|
-
---
|
|
440
|
-
|
|
441
|
-
## Enviar evento (ex.: cancelamento)
|
|
442
|
-
|
|
443
|
-
```ts
|
|
444
|
-
import { enviarEvento, gzipBase64, signEnveloped } from '@useinvio/nfse-sdk';
|
|
445
|
-
|
|
446
|
-
const signedEventXml = signEnveloped(pedRegXml, pedRegId, 'infPedReg', certificado);
|
|
447
|
-
const pedRegXmlGZipB64 = gzipBase64(signedEventXml);
|
|
448
|
-
|
|
449
|
-
const resposta = await enviarEvento(pedRegXmlGZipB64, certificado, chaveAcesso, 'restrita');
|
|
450
|
-
```
|
|
451
|
-
|
|
452
|
-
---
|
|
453
|
-
|
|
454
|
-
## Série e número da DPS
|
|
90
|
+
Primeira vez? Siga o [tutorial](./docs/tutoriais/primeira-nfse.md): emitir,
|
|
91
|
+
consultar, baixar o PDF e cancelar na produção restrita, do zero. Ou experimente
|
|
92
|
+
sem certificado, pelo terminal:
|
|
455
93
|
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
```ts
|
|
459
|
-
// série no perfil do prestador (padrão para todas as DPS)
|
|
460
|
-
prestador: {
|
|
461
|
-
serie: '1601',
|
|
462
|
-
}
|
|
463
|
-
|
|
464
|
-
// ou por emissão (sobrescreve prestador.serie apenas para esta DPS)
|
|
465
|
-
emissao: {
|
|
466
|
-
serie: '1601',
|
|
467
|
-
nDPS: '4',
|
|
468
|
-
}
|
|
469
|
-
```
|
|
470
|
-
|
|
471
|
-
O padrão usual é manter uma `serie` fixa e incrementar `nDPS` em cada nova DPS:
|
|
472
|
-
|
|
473
|
-
```
|
|
474
|
-
serie 1601, nDPS 1
|
|
475
|
-
serie 1601, nDPS 2
|
|
476
|
-
serie 1601, nDPS 3
|
|
477
|
-
```
|
|
478
|
-
|
|
479
|
-
> O Emissor Web usa séries como `70000`. Para emissão via API, use uma série válida para API (ex.: `1601`) e controle o `nDPS` na sua aplicação.
|
|
480
|
-
|
|
481
|
-
---
|
|
482
|
-
|
|
483
|
-
## Ambientes
|
|
484
|
-
|
|
485
|
-
| Chave | URL |
|
|
486
|
-
|---|---|
|
|
487
|
-
| `restrita` | `https://sefin.producaorestrita.nfse.gov.br/SefinNacional` |
|
|
488
|
-
| `producao` | `https://sefin.nfse.gov.br/SefinNacional` |
|
|
489
|
-
|
|
490
|
-
```ts
|
|
491
|
-
import { resolveSefinBaseUrl } from '@useinvio/nfse-sdk';
|
|
492
|
-
|
|
493
|
-
const baseUrl = resolveSefinBaseUrl('restrita');
|
|
494
|
-
```
|
|
495
|
-
|
|
496
|
-
---
|
|
497
|
-
|
|
498
|
-
## Observabilidade da SEFIN
|
|
499
|
-
|
|
500
|
-
O SDK pode emitir métricas de latência das chamadas mTLS feitas contra a SEFIN quando `NFSE_SEFIN_LATENCY_METRICS=1` está configurada. Por padrão, percentis como `p50Ms`, `p95Ms` e `p99Ms` ficam desligados e precisam de `includePercentiles: true`. Essas métricas medem o round-trip externo da SEFIN, separado da latência total da sua API.
|
|
501
|
-
|
|
502
|
-
Leia o guia completo em [docs/METRICS.md](./docs/METRICS.md).
|
|
503
|
-
|
|
504
|
-
---
|
|
505
|
-
|
|
506
|
-
## Agentes de IA e MCP
|
|
507
|
-
|
|
508
|
-
O subpath `@useinvio/nfse-sdk/agent` expõe ferramentas neutras de framework para servidores MCP e agentes (OpenAI, LangChain, Vercel AI SDK). Cada ferramenta traz `name`, `description`, `inputSchema`/`outputSchema` em JSON Schema, `annotations` no formato MCP e um handler `run`.
|
|
509
|
-
|
|
510
|
-
| Ferramenta | Efeito | Certificado |
|
|
511
|
-
| --- | --- | --- |
|
|
512
|
-
| `validate_dps` | Valida a requisição; nenhum efeito colateral. | Não |
|
|
513
|
-
| `build_dps_xml` | Gera o XML não assinado e confere contra o XSD v1.01. | Não |
|
|
514
|
-
| `get_nfse` | Consulta uma NFS-e pela chave de acesso. | Sim |
|
|
515
|
-
|
|
516
|
-
```ts
|
|
517
|
-
import { nfseAgentTools } from '@useinvio/nfse-sdk/agent';
|
|
518
|
-
|
|
519
|
-
const context = { pfx: certificado, ambiente: 'restrita' as const }; // configuração do host
|
|
520
|
-
for (const tool of nfseAgentTools) {
|
|
521
|
-
// registre tool.name, tool.description, tool.inputSchema e tool.annotations no seu framework
|
|
522
|
-
// e chame tool.run(argumentos, context) quando o modelo usar a ferramenta
|
|
523
|
-
}
|
|
94
|
+
```bash
|
|
95
|
+
npx nfse init && npx nfse emit nota.json --mock
|
|
524
96
|
```
|
|
525
97
|
|
|
526
|
-
|
|
98
|
+
## Documentação
|
|
527
99
|
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
A emissão ainda não é exposta como ferramenta: ela exigirá confirmação explícita antes do envio.
|
|
531
|
-
|
|
532
|
-
---
|
|
533
|
-
|
|
534
|
-
## Referência da API
|
|
535
|
-
|
|
536
|
-
### Funções principais
|
|
537
|
-
|
|
538
|
-
| Função | Descrição |
|
|
100
|
+
| Guia | |
|
|
539
101
|
|---|---|
|
|
540
|
-
|
|
|
541
|
-
|
|
|
542
|
-
|
|
|
543
|
-
|
|
|
544
|
-
|
|
|
545
|
-
|
|
|
546
|
-
|
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
|
551
|
-
|---|---|
|
|
552
|
-
| `assertCertificateValidity(certificado, now?)` | Verifica vigencia do certificado; aplicada na preparacao e no envio. |
|
|
553
|
-
| `assertCertificateForProvider(certificado, documento, now?)` | Verifica explicitamente vigencia e correspondencia do titular. |
|
|
554
|
-
| `NfseCertificate.fromEnv()` / `fromFile()` / `fromBase64()` / `fromPfx()` / `fromPem()` | Carrega o certificado A1 sem expor a chave privada. |
|
|
555
|
-
| `loadPfx(path, password)` | Legado: retorna a chave em PEM. Prefira `NfseCertificate`. |
|
|
556
|
-
| `loadPfxFromBuffer(buffer, password)` | Legado: retorna a chave em PEM. Prefira `NfseCertificate`. |
|
|
557
|
-
|
|
558
|
-
### Funções de baixo nível
|
|
559
|
-
|
|
560
|
-
| Função | Descrição |
|
|
561
|
-
|---|---|
|
|
562
|
-
| `signDps(xml, dpsId, certificado)` | Assina XML de DPS com XMLDSIG. |
|
|
563
|
-
| `signEnveloped(xml, id, ref, certificado)` | Assina XML genérico (eventos). |
|
|
564
|
-
| `verifyDps(xml, certPem)` | Verifica assinatura de uma DPS. |
|
|
565
|
-
| `gzipBase64(xml)` | Compacta XML em GZip e codifica em Base64. |
|
|
566
|
-
| `gunzipBase64(b64)` | Descompacta resposta GZip/Base64. |
|
|
567
|
-
| `extrairErros(body)` | Normaliza erros oficiais de uma resposta da SEFIN. |
|
|
568
|
-
| `resolveSefinBaseUrl(ambiente)` | Retorna a URL base do ambiente. |
|
|
569
|
-
|
|
570
|
-
### Tipos exportados
|
|
571
|
-
|
|
572
|
-
```ts
|
|
573
|
-
import type {
|
|
574
|
-
Ambiente,
|
|
575
|
-
CreateInvoiceInput,
|
|
576
|
-
GetInvoiceOptions,
|
|
577
|
-
DpsJsonInput,
|
|
578
|
-
DpsJsonRequest,
|
|
579
|
-
EmitirNotaOptions,
|
|
580
|
-
NfseClientDpsDefaults,
|
|
581
|
-
NfseClientOptions,
|
|
582
|
-
NotaInput,
|
|
583
|
-
PfxMaterial,
|
|
584
|
-
PrestadorProfile,
|
|
585
|
-
ResultadoEmissaoNota,
|
|
586
|
-
SefinErro,
|
|
587
|
-
SefinResposta,
|
|
588
|
-
ServicoProfile,
|
|
589
|
-
} from '@useinvio/nfse-sdk';
|
|
590
|
-
```
|
|
102
|
+
| [Certificado A1](./docs/guias/certificado.md) | Variáveis de ambiente, cofres de segredo, vários emitentes. |
|
|
103
|
+
| [Emitir](./docs/guias/emitir.md) | JSON, `NfseClient`, XML pronto, série e número. |
|
|
104
|
+
| [Consultar e DANFSe](./docs/guias/consultar.md) | Resultado tipado e PDF. |
|
|
105
|
+
| [Recuperar após timeout](./docs/guias/recuperar-emissao.md) | Sem notas duplicadas quando a conexão cai. |
|
|
106
|
+
| [Cancelar](./docs/guias/cancelar.md) | e101101 e substituição (e105102). |
|
|
107
|
+
| [Erros e rejeições](./docs/guias/rejeicoes.md) | O que cada erro significa e o que fazer. |
|
|
108
|
+
| [Linha de comando](./docs/guias/cli.md) | `nfse doctor`, `emit`, `cancel`, `explain`. |
|
|
109
|
+
| [Testar sem certificado](./docs/guias/testes.md) | SEFIN simulada para o seu CI. |
|
|
110
|
+
| [Agentes de IA e MCP](./docs/guias/agentes-ia.md) | Servidor MCP, plugin do Claude Code, ferramentas para o seu agente. |
|
|
111
|
+
| [Referência da API](./docs/referencia/api.md) | Todas as funções. |
|
|
112
|
+
| [Mapeamento do JSON](./JSON_MAPPING.md) | Cada campo de `DpsJsonRequest`. |
|
|
591
113
|
|
|
592
|
-
|
|
114
|
+
Exemplos executáveis em [`examples/`](./examples).
|
|
593
115
|
|
|
594
|
-
|
|
595
|
-
import { SEFIN_BASE_URL, TP_AMB, DEFAULT_AMBIENTE } from '@useinvio/nfse-sdk';
|
|
596
|
-
```
|
|
116
|
+
## Com agentes de IA
|
|
597
117
|
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
## Desenvolvimento
|
|
118
|
+
Emita uma NFS-e conversando com o Claude. Para experimentar sem certificado, o
|
|
119
|
+
modo de teste usa uma SEFIN simulada:
|
|
601
120
|
|
|
602
121
|
```bash
|
|
603
|
-
|
|
604
|
-
npm run typecheck # verifica tipos sem compilar
|
|
605
|
-
npm run build # compila para dist/
|
|
606
|
-
npm test # build + tipos + testes unitários
|
|
122
|
+
claude mcp add nfse --env NFSE_MCP_MOCK=1 --env NFSE_MCP_ALLOW_WRITE=1 -- npx -y @useinvio/nfse-mcp
|
|
607
123
|
```
|
|
608
124
|
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
### Uso local em outro projeto
|
|
125
|
+
Ou instale o plugin do Claude Code, que inclui o servidor e uma skill com o fluxo:
|
|
612
126
|
|
|
613
|
-
```
|
|
614
|
-
|
|
615
|
-
"dependencies": {
|
|
616
|
-
"@useinvio/nfse-sdk": "file:../nfse-sdk"
|
|
617
|
-
}
|
|
618
|
-
}
|
|
619
|
-
```
|
|
620
|
-
|
|
621
|
-
---
|
|
622
|
-
|
|
623
|
-
## Publicação no npm
|
|
624
|
-
|
|
625
|
-
Pacote publicado publicamente no npm como [`@useinvio/nfse-sdk`](https://www.npmjs.com/package/@useinvio/nfse-sdk).
|
|
626
|
-
|
|
627
|
-
```bash
|
|
628
|
-
npm install
|
|
629
|
-
npm run typecheck
|
|
630
|
-
npm run build
|
|
631
|
-
npm pack --dry-run # verifica o que será publicado
|
|
632
|
-
npm publish
|
|
127
|
+
```text
|
|
128
|
+
/plugin install nfse-nacional --marketplace useinvio/nfse-sdk
|
|
633
129
|
```
|
|
634
130
|
|
|
635
|
-
|
|
131
|
+
Emitir e cancelar sempre passam por um resumo que você aprova antes do envio, e
|
|
132
|
+
o certificado nunca chega ao modelo. Veja o [guia de agentes e MCP](./docs/guias/agentes-ia.md).
|
|
133
|
+
Para assistentes de código, o pacote inclui [`llms.txt`](./llms.txt).
|
|
636
134
|
|
|
637
|
-
|
|
135
|
+
## O que fica com a sua aplicação
|
|
638
136
|
|
|
639
|
-
|
|
137
|
+
O SDK cuida do protocolo; ele não é um motor fiscal. Códigos de serviço, CST,
|
|
138
|
+
alíquotas, retenções, regime tributário e a numeração da DPS são decisões da
|
|
139
|
+
sua aplicação e de quem cuida da parte tributária. Campos fiscais obrigatórios
|
|
140
|
+
nunca recebem valores padrão silenciosos. O grupo IBS/CBS da reforma tributária
|
|
141
|
+
ainda não é suportado.
|
|
640
142
|
|
|
641
|
-
|
|
143
|
+
## Contribuir
|
|
642
144
|
|
|
643
|
-
|
|
145
|
+
Issues e PRs são bem-vindos. Veja [CONTRIBUTING.md](./CONTRIBUTING.md) e o
|
|
146
|
+
[CHANGELOG](./CHANGELOG.md).
|
|
644
147
|
|
|
645
|
-
##
|
|
148
|
+
## Licença
|
|
646
149
|
|
|
647
|
-
|
|
648
|
-
- Funções pequenas, testáveis e reutilizáveis.
|
|
649
|
-
- Erros oficiais da SEFIN são preservados na íntegra, não engolidos.
|
|
650
|
-
- O SDK transporta declarações fiscais; não valida se estão contabilmente corretas.
|
|
150
|
+
[MIT](./LICENSE) © Invio
|