@useinvio/nfse-sdk 2.2.0 → 3.1.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 (66) hide show
  1. package/JSON_MAPPING.md +30 -17
  2. package/README.md +145 -67
  3. package/dist/agent.d.ts +86 -0
  4. package/dist/agent.js +235 -0
  5. package/dist/agent.js.map +1 -0
  6. package/dist/certificate.d.ts +96 -0
  7. package/dist/certificate.js +218 -0
  8. package/dist/certificate.js.map +1 -0
  9. package/dist/certificateValidation.d.ts +12 -5
  10. package/dist/certificateValidation.js +25 -9
  11. package/dist/certificateValidation.js.map +1 -1
  12. package/dist/client.d.ts +8 -3
  13. package/dist/client.js +8 -7
  14. package/dist/client.js.map +1 -1
  15. package/dist/decimal.d.ts +7 -0
  16. package/dist/decimal.js +24 -0
  17. package/dist/decimal.js.map +1 -0
  18. package/dist/dpsJson.js +15 -45
  19. package/dist/dpsJson.js.map +1 -1
  20. package/dist/dpsJsonSchema.d.ts +17 -0
  21. package/dist/dpsJsonSchema.js +184 -0
  22. package/dist/dpsJsonSchema.js.map +1 -0
  23. package/dist/dpsNormalization.d.ts +10 -0
  24. package/dist/dpsNormalization.js +85 -0
  25. package/dist/dpsNormalization.js.map +1 -0
  26. package/dist/emissaoNota.d.ts +4 -4
  27. package/dist/emissaoNota.js +27 -8
  28. package/dist/emissaoNota.js.map +1 -1
  29. package/dist/fiscalDomains.d.ts +22 -0
  30. package/dist/fiscalDomains.js +99 -0
  31. package/dist/fiscalDomains.js.map +1 -0
  32. package/dist/fiscalValidation.d.ts +1 -0
  33. package/dist/fiscalValidation.js +119 -88
  34. package/dist/fiscalValidation.js.map +1 -1
  35. package/dist/index.d.ts +9 -4
  36. package/dist/index.js +5 -3
  37. package/dist/index.js.map +1 -1
  38. package/dist/jsonSchemaCheck.d.ts +12 -0
  39. package/dist/jsonSchemaCheck.js +70 -0
  40. package/dist/jsonSchemaCheck.js.map +1 -0
  41. package/dist/loadPfx.d.ts +2 -0
  42. package/dist/loadPfx.js +2 -1
  43. package/dist/loadPfx.js.map +1 -1
  44. package/dist/sefinClient.d.ts +10 -4
  45. package/dist/sefinClient.js +22 -4
  46. package/dist/sefinClient.js.map +1 -1
  47. package/dist/signXml.d.ts +3 -3
  48. package/dist/signXml.js +8 -6
  49. package/dist/signXml.js.map +1 -1
  50. package/dist/xmlInspection.d.ts +6 -0
  51. package/dist/xmlInspection.js +62 -0
  52. package/dist/xmlInspection.js.map +1 -0
  53. package/dist/xsdValidation.d.ts +17 -5
  54. package/dist/xsdValidation.js +75 -67
  55. package/dist/xsdValidation.js.map +1 -1
  56. package/docs/CORRECOES_EMISSAO.md +55 -0
  57. package/docs/FISCAL_MATRIX.md +11 -0
  58. package/docs/REGRAS_NEGOCIO_EMISSAO.md +317 -0
  59. package/examples/README.md +4 -1
  60. package/examples/client-resource.ts +8 -44
  61. package/examples/emit-homologation.ts +4 -40
  62. package/examples/generate-dps-xml.ts +3 -40
  63. package/examples/handle-errors.ts +6 -5
  64. package/examples/payloads.ts +36 -0
  65. package/llms.txt +22 -7
  66. package/package.json +9 -4
package/JSON_MAPPING.md CHANGED
@@ -33,8 +33,9 @@ type DpsJsonRequest = {
33
33
 
34
34
  | Campo | Obrigatorio | Tipo | Descricao |
35
35
  | --- | --- | --- | --- |
36
- | `cnpj` | Sim | `string` | CNPJ do prestador. O SDK remove nao digitos e completa com zeros a esquerda ao montar o `Id` da DPS. |
37
- | `tpInsc` | Nao | `'1' \| '2' \| string` | Tipo de inscricao no `Id` da DPS. `1` = CPF, `2` = CNPJ. Quando ausente, usa `2`. |
36
+ | `cnpj` | Alternativa a `cpf` | `string` | CNPJ com exatamente 14 digitos, sem pontuacao. Informe exatamente um identificador. |
37
+ | `cpf` | Alternativa a `cnpj` | `string` | CPF com exatamente 11 digitos, sem pontuacao. |
38
+ | `tpInsc` | Nao | `'1' \| '2' \| string` | Legado ignorado. O SDK deriva o tipo do documento: `1` = CPF, `2` = CNPJ. |
38
39
  | `cLocEmi` | Sim | `string` | Codigo IBGE do municipio de emissao. |
39
40
  | `serie` | Sim | `string` | Serie padrao da DPS. Pode ser sobrescrita por `emissao.serie`. |
40
41
  | `opSimpNac` | Sim | `string` | Opcao pelo Simples Nacional, serializada em `prest/regTrib/opSimpNac`. |
@@ -58,7 +59,7 @@ type DpsJsonRequest = {
58
59
  | `serie` | Nao | `string` | Serie desta DPS. Quando informado, sobrescreve `prestador.serie` apenas nesta emissao. |
59
60
  | `dhEmi` | Nao | `string` | Data e hora da emissao em ISO com offset, por exemplo `2026-06-15T10:30:00-03:00`. Quando ausente, usa a data/hora atual. |
60
61
  | `dCompet` | Nao | `string` | Data de competencia no formato `YYYY-MM-DD`. Quando ausente, usa a data de `dhEmi`. |
61
- | `valores` | Condicional | `Valores` | Valores do servico, descontos e deducoes. Para calcular `vServ`, informe `valores.vServ` ou `valores.vServMoeda` + `valores.cotacao`. |
62
+ | `valores` | Condicional | `Valores` | Valores do servico. Descontos e deducoes ainda nao sao suportados. Para calcular `vServ`, informe `valores.vServ` ou `valores.vServMoeda` + `valores.cotacao`. |
62
63
  | `vServ` | Condicional | `string` | Atalho legado para o valor do servico. Usado quando `valores.vServ` nao foi informado. |
63
64
  | `vServMoeda` | Condicional | `string` | Atalho legado para valor em moeda estrangeira. Usado com `cotacao` quando `vServ` nao foi informado. |
64
65
  | `cotacao` | Condicional | `number` | Atalho legado para cotacao. Usado com `vServMoeda` quando `vServ` nao foi informado. |
@@ -88,7 +89,11 @@ type DpsJsonRequest = {
88
89
  Regra condicional: para gerar a DPS, o SDK precisa obter `vServ`. Informe
89
90
  `valores.vServ` ou informe o par `valores.vServMoeda` + `valores.cotacao`.
90
91
  Os atalhos `emissao.vServ`, `emissao.vServMoeda` e `emissao.cotacao` tambem sao
91
- aceitos, mas o formato com `valores` e preferencial.
92
+ aceitos. A resolucao e por campo: `valores` prevalece sobre o atalho correspondente, permitindo entradas mistas. A mesma resolucao e usada na validacao e no XML.
93
+
94
+ No modelo atual, nacional exige reais e proibe moeda/cotacao. Exportacao exige comercio exterior, valor estrangeiro e cotacao, mesmo quando ha `vServ` explicito. Informar somente o valor no bloco `comercioExterior` nao satisfaz essa exigencia.
95
+
96
+ `vServ` explicito prevalece sobre a conversao. O valor estrangeiro do bloco `comercioExterior` prevalece na tag `comExt/vServMoeda`. Divergencias geram `CURRENCY_BRL_MISMATCH` e `CURRENCY_FOREIGN_MISMATCH` em `warnings`, comparadas em centavos, sem bloquear a emissao. A multiplicacao preserva a precisao original; a serializacao arredonda metade para cima em duas casas, inclusive `comExt.vServMoeda`. Valores de servico que arredondam para zero ou excedem 15 digitos inteiros sao rejeitados.
92
97
 
93
98
  ## `emissao.servico`
94
99
 
@@ -107,12 +112,12 @@ aceitos, mas o formato com `valores` e preferencial.
107
112
  | `NIF` | Nao | `string` | Numero de identificacao fiscal estrangeiro. |
108
113
  | `cNaoNIF` | Nao | `string` | Motivo/codigo de nao informacao do NIF. |
109
114
  | `xNome` | Sim, se `tomador` existir | `string` | Nome ou razao social do tomador. |
110
- | `end` | Nao | `Endereco` | Endereco. Veja `Endereco` abaixo. |
115
+ | `end` | Sim, se `tomador` existir | `Endereco` | Endereco. Veja `Endereco` abaixo. |
111
116
  | `fone` | Nao | `string` | Telefone. |
112
117
  | `email` | Nao | `string` | Email. |
113
118
 
114
119
  Quando `tomador` for informado, use exatamente um identificador entre `CNPJ`,
115
- `CPF`, `NIF` e `cNaoNIF`.
120
+ `CPF`, `NIF` e `cNaoNIF`. Nacional exige CPF/CNPJ e `endNac`; exterior exige NIF ou `cNaoNIF` (`1`/`2`) e `endExt`. Omitir todo o tomador continua permitido localmente.
116
121
 
117
122
  ### `Endereco`
118
123
 
@@ -176,7 +181,7 @@ Regras adicionais:
176
181
  | `pAliqCofins` | Nao | `string` | Aliquota de COFINS. Serializada com duas casas decimais. |
177
182
  | `vPis` | Nao | `string` | Valor de PIS. Serializado com duas casas decimais. |
178
183
  | `vCofins` | Nao | `string` | Valor de COFINS. Serializado com duas casas decimais. |
179
- | `tpRetPisCofins` | Nao | `string` | Tipo de retencao de PIS/COFINS. |
184
+ | `tpRetPisCofins` | Nao | `string` | Tipo de retencao de PIS/COFINS, codigo de `0` a `9`. |
180
185
 
181
186
  ## `tribNac`
182
187
 
@@ -203,8 +208,8 @@ ainda nao implementa esse bloco.
203
208
 
204
209
  | Campo | Obrigatorio | Tipo | Descricao |
205
210
  | --- | --- | --- | --- |
206
- | `cObra` | Nao | `string` | Codigo da obra. |
207
- | `inscImobFisc` | Nao | `string` | Inscricao imobiliaria fiscal. |
211
+ | `cObra` | Sim, se `obra` existir | `string` | Codigo da obra, 1 a 30 caracteres. |
212
+ | `inscImobFisc` | Nao | `string` | Inscricao imobiliaria fiscal, 1 a 30 caracteres; serializada antes de `cObra`. |
208
213
  | `cCM` | Nao suportado | `string` | Campo legado bloqueado por validacao; nao existe como filho de `obra` no XSD v1.01. |
209
214
 
210
215
  ## `evento`
@@ -216,8 +221,7 @@ ainda nao implementa esse bloco.
216
221
 
217
222
  ## `totTrib`
218
223
 
219
- `totTrib` e um `xs:choice` no XSD: informe exatamente **um** dos quatro grupos
220
- abaixo. A SDK rejeita zero ou mais de um grupo preenchido.
224
+ `totTrib` e um `xs:choice` no XSD. O validador JSON atual restringe a modalidade por regime: nao optante (`1`) exige os tres `pTotTribFed/Est/Mun`; MEI (`2`) exige somente `indTotTrib="0"`; ME/EPP (`3`) exige somente `pTotTribSN`. Misturas e grupos incompletos sao rejeitados.
221
225
 
222
226
  | Campo | Obrigatorio | Tipo | Descricao |
223
227
  | --- | --- | --- | --- |
@@ -227,19 +231,28 @@ abaixo. A SDK rejeita zero ou mais de um grupo preenchido.
227
231
  | `pTotTribFed` | Condicional | `string` | Percentual aproximado de tributos federais. Serializado com duas casas decimais. |
228
232
  | `pTotTribEst` | Condicional | `string` | Percentual aproximado de tributos estaduais. Serializado com duas casas decimais. |
229
233
  | `pTotTribMun` | Condicional | `string` | Percentual aproximado de tributos municipais. Serializado com duas casas decimais. |
230
- | `pTotTribSN` | Condicional | `string` | Percentual aproximado de tributos no Simples Nacional. Nao informe quando `prestador.opSimpNac = "1"`. |
231
- | `indTotTrib` | Condicional | `string` | Indicador oficial para nao informar valor estimado. Use `0` quando nao houver estimativa aproximada. |
234
+ | `pTotTribSN` | Condicional | `string` | Percentual aproximado de tributos no Simples Nacional. Use somente quando `prestador.opSimpNac = "3"`. |
235
+ | `indTotTrib` | Condicional | `string` | Indicador oficial para nao informar valor estimado. Use `0` somente para MEI no modelo JSON atual. |
232
236
 
233
- Informe ao menos uma forma de totalizacao: grupo `vTotTrib*` (Fed+Est+Mun),
234
- grupo `pTotTrib*` (Fed+Est+Mun), `pTotTribSN` ou `indTotTrib=0`. Ao escolher o
235
- grupo `vTotTrib*` ou `pTotTrib*`, os tres campos (Fed/Est/Mun) sao obrigatorios.
237
+ Os campos `vTotTrib*` permanecem tipados por compatibilidade, mas nenhum dos regimes aceitos permite essa modalidade no JSON atual.
236
238
 
237
239
  ## Regras de formatacao
238
240
 
239
241
  - Valores monetarios sao serializados com duas casas decimais.
240
242
  - Aliquotas (`pAliq`, `pAliqPis`, `pAliqCofins`) sao serializadas com duas casas decimais.
241
243
  - Percentuais de carga tributaria em `totTrib` sao serializados com duas casas decimais.
242
- - Campos vazios, `undefined` ou `null` nao geram tags XML opcionais.
244
+ - Campos opcionais ausentes ou `undefined` sao omitidos; `null`, strings vazias e tipos incorretos sao rejeitados antecipadamente.
243
245
  - Campos obrigatorios ausentes geram erro antes da DPS ser retornada.
244
246
  - `comercioExterior` tem prioridade sobre `comExt` quando os dois forem informados.
245
247
  - O XML gerado nos testes e validado contra `schemas/nfse/v1.01/Schemas/1.01/DPS_v1.01.xsd`.
248
+
249
+
250
+ ## Relatorio e limites estruturais
251
+
252
+ `validateDpsJsonRequest` retorna `issues` com erros e `warnings` com avisos; somente erros tornam `valid=false`. `normalizedPayload` e uma copia independente com os valores e o alias de comercio exterior resolvidos, mantendo os campos legados. Estruturas invalidas retornam `INVALID_STRUCTURE`, sem acessos que lancem `TypeError`.
253
+
254
+ `dhEmi` exige data de calendario valida entre 2000 e 2099, segundos sem fracao e offset de horas inteiras de -11:00 a +12:00; `Z` nao e aceito pelo leiaute local. `dCompet` exige data valida no mesmo intervalo de anos. Nao se impoe relacao fiscal entre as datas.
255
+
256
+ Limites apos arredondamento: valores monetarios, 15 digitos inteiros; ISS, 1; aliquotas PIS/COFINS e `pTotTribSN`, 2; `pTotTribFed/Est/Mun`, 3. Esses limites sao estruturais, nao autorizacao fiscal de aliquotas. Retencoes federais podem ser zero, mas nao negativas.
257
+
258
+ Dominios de comercio exterior: `mdPrestacao` 0–4; `vincPrest` 0–6 ou 9; `tpMoeda` tres digitos; `mecAFComexP` 00–08; `mecAFComexT` 00–26; `movTempBens` 0–3; `mdic` 0–1. Nao se consultam catalogos externos. Os dois aliases de comercio exterior nao sao combinados.
package/README.md CHANGED
@@ -40,11 +40,11 @@ O SDK cuida da camada protocolar: certificado A1, montagem de DPS, validações
40
40
  - [Enviar evento](#enviar-evento-ex-cancelamento)
41
41
  - [Série e número da DPS](#série-e-número-da-dps)
42
42
  - [Ambientes](#ambientes)
43
+ - [Agentes de IA e MCP](#agentes-de-ia-e-mcp)
43
44
  - [Referência da API](#referência-da-api)
44
45
  - [Desenvolvimento](#desenvolvimento)
45
46
  - [Publicação no npm](#publicação-no-npm)
46
- - [Divulgação e exemplos](#divulgação-e-exemplos)
47
- - [Assistente GPT](#assistente-gpt)
47
+ - [Exemplos](#exemplos)
48
48
 
49
49
  ---
50
50
 
@@ -54,7 +54,7 @@ O SDK cuida da camada protocolar: certificado A1, montagem de DPS, validações
54
54
  npm install @useinvio/nfse-sdk
55
55
  ```
56
56
 
57
- > Node.js ≥ 20 é obrigatório.
57
+ > Node.js ≥ 20 é obrigatório. Não precisa de `xmllint` nem de binários nativos: a validação XSD roda em WebAssembly.
58
58
 
59
59
  ---
60
60
 
@@ -63,15 +63,15 @@ npm install @useinvio/nfse-sdk
63
63
  O caminho mais comum: emitir uma nota a partir de JSON declarativo.
64
64
 
65
65
  ```ts
66
- import { emitirNfse, loadPfx, EmitirNotaError } from '@useinvio/nfse-sdk';
66
+ import { emitirNfse, EmitirNotaError, NfseCertificate } from '@useinvio/nfse-sdk';
67
67
 
68
- const pfx = loadPfx('./certificado.pfx', process.env.PFX_PASSWORD!);
68
+ // NFSE_CERT_PATH=./certificado.pfx e NFSE_CERT_PASSWORD=... (veja Certificado A1)
69
+ const certificado = NfseCertificate.fromEnv();
69
70
 
70
71
  const nota = {
71
72
  ambiente: 'restrita' as const,
72
73
  prestador: {
73
74
  cnpj: '12345678000195',
74
- tpInsc: '2',
75
75
  cLocEmi: '4106902',
76
76
  serie: '1601',
77
77
  opSimpNac: '1',
@@ -89,8 +89,7 @@ const nota = {
89
89
  dCompet: '2026-06-01',
90
90
  valores: { vServ: '1500.00' },
91
91
  tributacaoMunicipal: {
92
- tribISSQN: '3',
93
- cPaisResult: 'BR',
92
+ tribISSQN: '1',
94
93
  tpRetISSQN: '1',
95
94
  },
96
95
  tributacaoFederal: {
@@ -105,7 +104,7 @@ const nota = {
105
104
  };
106
105
 
107
106
  try {
108
- const resultado = await emitirNfse(nota, pfx);
107
+ const resultado = await emitirNfse(nota, certificado);
109
108
  console.log(resultado.chaveAcesso); // chave de 50 dígitos
110
109
  console.log(resultado.nfseXml); // XML autorizado pela SEFIN
111
110
  } catch (error) {
@@ -135,8 +134,8 @@ try {
135
134
  | Situação | O que usar |
136
135
  |---|---|
137
136
  | Quer uma API de cliente com `client.invoices.create(...)` | `new NfseClient(...)` |
138
- | Outro sistema já gera o XML da DPS | `emitirNfse(xmlString, pfx)` |
139
- | Você monta os dados e quer que a SDK construa o XML | `emitirNfse(notaJson, pfx)` |
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)` |
140
139
  | Precisa inspecionar ou salvar o XML antes de enviar | `buildDpsFromJson(nota)` |
141
140
  | Fluxo customizado (assinar, compactar ou enviar separadamente) | funções de baixo nível |
142
141
 
@@ -152,18 +151,14 @@ O cliente usa os mesmos campos do JSON declarativo (`prestador`, `servico` e
152
151
  cada chamada.
153
152
 
154
153
  ```ts
155
- import { NfseClient } from '@useinvio/nfse-sdk';
154
+ import { NfseCertificate, NfseClient } from '@useinvio/nfse-sdk';
156
155
 
157
156
  const client = new NfseClient({
158
157
  environment: 'sandbox',
159
- certificate: {
160
- content: process.env.NFSE_CERTIFICATE!, // PFX em base64
161
- password: process.env.NFSE_CERTIFICATE_PASSWORD!,
162
- },
158
+ certificate: NfseCertificate.fromEnv(),
163
159
  defaults: {
164
160
  prestador: {
165
161
  cnpj: '12345678000195',
166
- tpInsc: '2',
167
162
  cLocEmi: '4106902',
168
163
  serie: '1601',
169
164
  opSimpNac: '1',
@@ -184,9 +179,13 @@ const invoice = await client.invoices.create({
184
179
  tomador: {
185
180
  CPF: '00000000000',
186
181
  xNome: 'Cliente Exemplo',
182
+ end: {
183
+ endNac: { cMun: '4106902', CEP: '80010000' },
184
+ xLgr: 'Rua Exemplo', nro: '100', xBairro: 'Centro',
185
+ },
187
186
  },
188
187
  tributacaoMunicipal: {
189
- tribISSQN: '3',
188
+ tribISSQN: '1',
190
189
  tpRetISSQN: '1',
191
190
  },
192
191
  tributacaoFederal: {
@@ -205,9 +204,8 @@ console.log(invoice.nfseXml);
205
204
  ```
206
205
 
207
206
  `environment: 'sandbox'` é alias de `restrita`, e `production` é alias de
208
- `producao`. O campo `certificate.content` aceita o conteúdo binário do PFX em
209
- `Buffer` ou o PFX codificado em base64, formato comum para variáveis de
210
- ambiente.
207
+ `producao`. `certificate` aceita um `NfseCertificate` ou, por compatibilidade,
208
+ `{ content, password }` com o PFX em `Buffer` ou base64.
211
209
 
212
210
  Para inspecionar a DPS gerada sem assinar nem enviar:
213
211
 
@@ -219,31 +217,95 @@ const dps = client.invoices.buildDpsJson({
219
217
  emissao: {
220
218
  nDPS: '2',
221
219
  valores: { vServ: '2500.00' },
220
+ tributacaoMunicipal: { tribISSQN: '1', tpRetISSQN: '1' },
221
+ totTrib: { pTotTribFed: '0', pTotTribEst: '0', pTotTribMun: '5' },
222
222
  },
223
223
  });
224
224
  ```
225
225
 
226
226
  ---
227
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
+
228
247
  ## Certificado A1
229
248
 
230
- ### A partir de arquivo
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
+ ```
231
257
 
232
258
  ```ts
233
- import { loadPfx } from '@useinvio/nfse-sdk';
259
+ import { NfseCertificate } from '@useinvio/nfse-sdk';
234
260
 
235
- const pfx = loadPfx('./certificado.pfx', process.env.PFX_PASSWORD!);
261
+ const certificado = NfseCertificate.fromEnv();
262
+ console.log(certificado.cnpj, certificado.notAfter, certificado.daysUntilExpiry());
236
263
  ```
237
264
 
238
- ### A partir de buffer (ex.: certificado descriptografado do banco)
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
239
268
 
240
269
  ```ts
241
- import { loadPfxFromBuffer } from '@useinvio/nfse-sdk';
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)
242
277
 
243
- const pfx = loadPfxFromBuffer(pfxBuffer, password);
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
+ }
244
294
  ```
245
295
 
246
- O objeto `pfx` retornado contém chave privada e certificado em PEM, pronto para assinar e fazer mTLS.
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`.
247
309
 
248
310
  ---
249
311
 
@@ -252,21 +314,17 @@ O objeto `pfx` retornado contém chave privada e certificado em PEM, pronto para
252
314
  Use quando outro sistema (ERP, sistema legado) já gera o XML da DPS no layout nacional.
253
315
 
254
316
  ```ts
255
- import { emitirNfse, loadPfx } from '@useinvio/nfse-sdk';
317
+ import { emitirNfse, NfseCertificate } from '@useinvio/nfse-sdk';
256
318
 
257
- const pfx = loadPfx('./certificado.pfx', process.env.PFX_PASSWORD!);
319
+ const certificado = NfseCertificate.fromEnv();
258
320
 
259
- const resultado = await emitirNfse(dpsXml, pfx, { ambiente: 'restrita' });
321
+ const resultado = await emitirNfse(dpsXml, certificado, { ambiente: 'restrita' });
260
322
 
261
323
  console.log(resultado.chaveAcesso);
262
324
  console.log(resultado.nfseXml);
263
325
  ```
264
326
 
265
- > O XML precisa ter o atributo `Id` em `<infDPS>`. Se não tiver, passe `options.dpsId` manualmente.
266
-
267
- ```ts
268
- await emitirNfse(dpsXml, pfx, { dpsId: 'DPS0001', ambiente: 'restrita' });
269
- ```
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.
270
328
 
271
329
  ---
272
330
 
@@ -278,13 +336,12 @@ Para a referência completa de campos aceitos no JSON, consulte [JSON_MAPPING.md
278
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[...]
279
337
 
280
338
  ```ts
281
- import { emitirNfse, loadPfx, type DpsJsonRequest } from '@useinvio/nfse-sdk';
339
+ import { emitirNfse, NfseCertificate, type DpsJsonRequest } from '@useinvio/nfse-sdk';
282
340
 
283
341
  const nota: DpsJsonRequest = {
284
342
  ambiente: 'restrita',
285
343
  prestador: {
286
344
  cnpj: '12345678000195',
287
- tpInsc: '2',
288
345
  cLocEmi: '4106902',
289
346
  serie: '1601',
290
347
  opSimpNac: '1',
@@ -302,8 +359,7 @@ const nota: DpsJsonRequest = {
302
359
  dCompet: '2026-06-01',
303
360
  valores: { vServ: '1500.00' },
304
361
  tributacaoMunicipal: {
305
- tribISSQN: '3',
306
- cPaisResult: 'BR',
362
+ tribISSQN: '1',
307
363
  tpRetISSQN: '1',
308
364
  },
309
365
  tributacaoFederal: {
@@ -317,8 +373,8 @@ const nota: DpsJsonRequest = {
317
373
  },
318
374
  };
319
375
 
320
- const pfx = loadPfx('./certificado.pfx', process.env.PFX_PASSWORD!);
321
- const resultado = await emitirNfse(nota, pfx);
376
+ const certificado = NfseCertificate.fromEnv();
377
+ const resultado = await emitirNfse(nota, certificado);
322
378
  ```
323
379
 
324
380
  > `pTotTribFed`, `pTotTribEst` e `pTotTribMun` são percentuais com duas casas decimais (ex.: `"5.00"`), diferentes das alíquotas com quatro casas (`pAliq`).
@@ -349,7 +405,7 @@ console.log(xml); // XML não assinado da DPS
349
405
  import { EmitirNotaError, emitirNfse } from '@useinvio/nfse-sdk';
350
406
 
351
407
  try {
352
- await emitirNfse(nota, pfx);
408
+ await emitirNfse(nota, certificado);
353
409
  } catch (error) {
354
410
  if (!(error instanceof EmitirNotaError)) throw error;
355
411
 
@@ -371,7 +427,7 @@ try {
371
427
  ```ts
372
428
  import { consultarNfse } from '@useinvio/nfse-sdk';
373
429
 
374
- const resposta = await consultarNfse(chaveAcesso, pfx, 'restrita');
430
+ const resposta = await consultarNfse(chaveAcesso, certificado, 'restrita');
375
431
 
376
432
  if (resposta.status === 200) {
377
433
  console.log(resposta.body);
@@ -387,10 +443,10 @@ if (resposta.status === 200) {
387
443
  ```ts
388
444
  import { enviarEvento, gzipBase64, signEnveloped } from '@useinvio/nfse-sdk';
389
445
 
390
- const signedEventXml = signEnveloped(pedRegXml, pedRegId, 'infPedReg', pfx);
446
+ const signedEventXml = signEnveloped(pedRegXml, pedRegId, 'infPedReg', certificado);
391
447
  const pedRegXmlGZipB64 = gzipBase64(signedEventXml);
392
448
 
393
- const resposta = await enviarEvento(pedRegXmlGZipB64, pfx, chaveAcesso, 'restrita');
449
+ const resposta = await enviarEvento(pedRegXmlGZipB64, certificado, chaveAcesso, 'restrita');
394
450
  ```
395
451
 
396
452
  ---
@@ -447,6 +503,34 @@ Leia o guia completo em [docs/METRICS.md](./docs/METRICS.md).
447
503
 
448
504
  ---
449
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
+ }
524
+ ```
525
+
526
+ Certificado e ambiente ficam no `context` do host, nunca na entrada da ferramenta: o modelo não escolhe `producao` nem vê material de chave. Uma requisição com `ambiente` diferente do configurado gera `AMBIENTE_NOT_ALLOWED`. Falhas esperadas retornam `issues` com `code`, `path`, `severity`, `source` e `suggestion`; falhas de infraestrutura (rede, motor XSD) rejeitam a promise. `toAgentIssues(error)` converte erros do SDK (`DpsFiscalValidationError`, `DpsXsdValidationError`, `CertificateValidationError`, `EmitirNotaError`) para esse mesmo formato.
527
+
528
+ `DPS_JSON_REQUEST_SCHEMA`, exportado também pela raiz do pacote, descreve o `DpsJsonRequest` canônico em JSON Schema 2020-12 com um subconjunto portável de palavras-chave. Ele cobre formatos e domínios de códigos, mas não as regras entre campos (regime × `totTrib`, nacional × exportação); `validateDpsJsonRequest` continua sendo a autoridade. Atalhos legados (`emissao.vServ`, `comExt`) e campos rejeitados pela validação ficam fora do schema; `validate_dps` aponta campos desconhecidos como `UNSUPPORTED_FIELD` em vez de ignorá-los.
529
+
530
+ A emissão ainda não é exposta como ferramenta: ela exigirá confirmação explícita antes do envio.
531
+
532
+ ---
533
+
450
534
  ## Referência da API
451
535
 
452
536
  ### Funções principais
@@ -454,9 +538,9 @@ Leia o guia completo em [docs/METRICS.md](./docs/METRICS.md).
454
538
  | Função | Descrição |
455
539
  |---|---|
456
540
  | `new NfseClient(options)` | Cria cliente com ambiente, certificado e defaults no mesmo formato do JSON declarativo. |
457
- | `emitirNfse(input, pfx, options?)` | Emite uma NFS-e a partir de XML ou JSON. Retorna `ResultadoEmissaoNota`. |
458
- | `consultarNfse(chaveAcesso, pfx, ambiente?)` | Consulta uma NFS-e pela chave de acesso. |
459
- | `enviarEvento(xmlGzipB64, pfx, chaveAcesso, ambiente?)` | Envia evento fiscal (cancelamento, etc.). |
541
+ | `emitirNfse(input, certificado, options?)` | Emite uma NFS-e a partir de XML ou JSON. Retorna `ResultadoEmissaoNota`. |
542
+ | `consultarNfse(chaveAcesso, certificado, ambiente?)` | Consulta uma NFS-e pela chave de acesso. |
543
+ | `enviarEvento(xmlGzipB64, certificado, chaveAcesso, ambiente?)` | Envia evento fiscal (cancelamento, etc.). |
460
544
  | `buildDpsFromJson(nota)` | Monta o XML da DPS sem assinar nem enviar. |
461
545
  | `setSefinRequestObserver(observer)` | Registra um hook para observar latência/status das chamadas à SEFIN. |
462
546
  | `createSefinLatencyTracker(options?)` | Agrega amostras recentes e pode calcular percentis quando `includePercentiles` estiver habilitado. |
@@ -465,16 +549,19 @@ Leia o guia completo em [docs/METRICS.md](./docs/METRICS.md).
465
549
 
466
550
  | Função | Descrição |
467
551
  |---|---|
468
- | `loadPfx(path, password)` | Carrega certificado A1 a partir de arquivo. |
469
- | `loadPfxFromBuffer(buffer, password)` | Carrega certificado A1 a partir de buffer. |
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`. |
470
557
 
471
558
  ### Funções de baixo nível
472
559
 
473
560
  | Função | Descrição |
474
561
  |---|---|
475
- | `signDps(xml, dpsId, pfx)` | Assina XML de DPS com XMLDSIG. |
476
- | `signEnveloped(xml, id, ref, pfx)` | Assina XML genérico (eventos). |
477
- | `verifyDps(xml)` | Verifica assinatura de uma DPS. |
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. |
478
565
  | `gzipBase64(xml)` | Compacta XML em GZip e codifica em Base64. |
479
566
  | `gunzipBase64(b64)` | Descompacta resposta GZip/Base64. |
480
567
  | `extrairErros(body)` | Normaliza erros oficiais de uma resposta da SEFIN. |
@@ -486,6 +573,7 @@ Leia o guia completo em [docs/METRICS.md](./docs/METRICS.md).
486
573
  import type {
487
574
  Ambiente,
488
575
  CreateInvoiceInput,
576
+ GetInvoiceOptions,
489
577
  DpsJsonInput,
490
578
  DpsJsonRequest,
491
579
  EmitirNotaOptions,
@@ -518,7 +606,7 @@ npm run build # compila para dist/
518
606
  npm test # build + tipos + testes unitários
519
607
  ```
520
608
 
521
- `npm test` também valida uma DPS gerada contra os XSD oficiais em `schemas/nfse/v1.01/Schemas/1.01`. Para compatibilidade com `xmllint`, o teste corrige em uma cópia temporária a âncora regex[...]
609
+ `npm test` também valida as DPS geradas contra os XSD oficiais em `schemas/nfse/v1.01/Schemas/1.01` com o motor embutido (libxml2 em WebAssembly). Se o `xmllint` estiver instalado, como no CI, os testes também o usam como verificação cruzada.
522
610
 
523
611
  ### Uso local em outro projeto
524
612
 
@@ -548,19 +636,9 @@ O pacote usa `publishConfig.access = public`, necessário para publicar um pacot
548
636
 
549
637
  ---
550
638
 
551
- ## Divulgação e exemplos
552
-
553
- - Exemplos copiáveis ficam em [`examples/`](./examples).
554
- - Material de lançamento, mensagens para redes e prompt de landing page ficam em [`docs/launch.md`](./docs/launch.md).
555
-
556
- ---
557
-
558
- ## Assistente GPT
559
-
560
- Para criar um GPT que ajude outros devs a implementar e documentar esta SDK, use:
639
+ ## Exemplos
561
640
 
562
- - [`docs/gpt-assistant.md`](./docs/gpt-assistant.md): configuracao completa do GPT, instrucoes copiaveis, starters e testes de preview.
563
- - [`docs/gpt-knowledge.md`](./docs/gpt-knowledge.md): resumo compacto para subir como arquivo de Knowledge.
641
+ Exemplos copiáveis ficam em [`examples/`](./examples).
564
642
 
565
643
  ---
566
644
 
@@ -0,0 +1,86 @@
1
+ import { type JsonSchema } from './dpsJsonSchema.js';
2
+ import type { Ambiente } from './config.js';
3
+ import type { DpsJsonRequest } from './dpsJson.js';
4
+ import type { CertificateInput } from './certificate.js';
5
+ export { DPS_JSON_REQUEST_SCHEMA } from './dpsJsonSchema.js';
6
+ export type { JsonSchema } from './dpsJsonSchema.js';
7
+ export type NfseAgentIssueSource = 'sdk' | 'invio' | 'schema' | 'xsd' | 'sefin' | 'certificate' | 'environment';
8
+ /** One shape for validation, XSD, certificate and SEFIN problems. */
9
+ export interface NfseAgentIssue {
10
+ code: string;
11
+ /** Dotted path inside the request (`emissao.` omitted, as in `DpsValidationIssue`); `$` when global. */
12
+ path: string;
13
+ severity: 'error' | 'warning';
14
+ source: NfseAgentIssueSource;
15
+ message: string;
16
+ suggestion?: string;
17
+ }
18
+ export interface NfseAgentContext {
19
+ /** Host-owned A1 certificate (`NfseCertificate` recommended). Required only by tools that talk to SEFIN. */
20
+ pfx?: CertificateInput;
21
+ /** Host-owned environment. Defaults to `restrita`; tool input cannot override it. */
22
+ ambiente?: Ambiente;
23
+ }
24
+ /** Same hints as MCP `ToolAnnotations`. */
25
+ export interface NfseAgentToolAnnotations {
26
+ readOnlyHint: boolean;
27
+ destructiveHint: boolean;
28
+ idempotentHint: boolean;
29
+ openWorldHint: boolean;
30
+ }
31
+ export interface NfseAgentTool<Input = any, Output = any> {
32
+ name: string;
33
+ title: string;
34
+ description: string;
35
+ inputSchema: JsonSchema;
36
+ outputSchema: JsonSchema;
37
+ annotations: NfseAgentToolAnnotations;
38
+ requiresCertificate: boolean;
39
+ /**
40
+ * Expected failures (invalid input, rejection, missing certificate) resolve
41
+ * with `issues`. Infrastructure failures (XSD engine, network) reject.
42
+ */
43
+ run(input: Input, context?: NfseAgentContext): Promise<Output>;
44
+ }
45
+ export interface DpsRequestToolInput {
46
+ request: Omit<DpsJsonRequest, 'ambiente'>;
47
+ }
48
+ export interface ValidateDpsToolOutput {
49
+ valid: boolean;
50
+ schemaVersion: string;
51
+ ambiente: Ambiente;
52
+ issues: NfseAgentIssue[];
53
+ warnings: NfseAgentIssue[];
54
+ }
55
+ export interface BuildDpsXmlToolOutput {
56
+ ok: boolean;
57
+ ambiente: Ambiente;
58
+ dpsId?: string;
59
+ xml?: string;
60
+ issues: NfseAgentIssue[];
61
+ warnings: NfseAgentIssue[];
62
+ }
63
+ export interface GetNfseToolInput {
64
+ chaveAcesso: string;
65
+ }
66
+ export interface GetNfseToolOutput {
67
+ ok: boolean;
68
+ ambiente: Ambiente;
69
+ chaveAcesso: string;
70
+ status?: number;
71
+ nfseXml?: string;
72
+ body?: unknown;
73
+ issues: NfseAgentIssue[];
74
+ }
75
+ /**
76
+ * Converts known SDK errors to `NfseAgentIssue[]`. Returns `undefined` for
77
+ * anything else (transport, XSD engine, programming errors) so the caller can rethrow.
78
+ */
79
+ export declare function toAgentIssues(error: unknown): NfseAgentIssue[] | undefined;
80
+ /** `DPS_JSON_REQUEST_SCHEMA` without `ambiente`, which belongs to the host. */
81
+ export declare const DPS_AGENT_REQUEST_SCHEMA: JsonSchema;
82
+ export declare const validateDpsTool: NfseAgentTool<DpsRequestToolInput, ValidateDpsToolOutput>;
83
+ export declare const buildDpsXmlTool: NfseAgentTool<DpsRequestToolInput, BuildDpsXmlToolOutput>;
84
+ export declare const getNfseTool: NfseAgentTool<GetNfseToolInput, GetNfseToolOutput>;
85
+ /** All tools, in the order an agent typically needs them. */
86
+ export declare const nfseAgentTools: readonly NfseAgentTool[];