ghinfe 0.6.0 → 0.6.2

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 (110) hide show
  1. package/.env.example +24 -15
  2. package/LICENSE +21 -21
  3. package/README.md +491 -413
  4. package/docs/developer-guide.html +421 -421
  5. package/docs/openapi.yaml +428 -428
  6. package/examples/dce-homologacao.js +141 -0
  7. package/examples/emitir-nfce.js +51 -51
  8. package/examples/emitir-nfe.js +83 -83
  9. package/examples/emitir-nfse.js +46 -46
  10. package/examples/endpoints-sefaz.js +14 -14
  11. package/examples/eventos-nfe.js +49 -49
  12. package/examples/gerar-chave.js +25 -25
  13. package/examples/gerar-dace.js +59 -0
  14. package/examples/gerar-danfe.js +75 -75
  15. package/examples/gerar-dce.js +58 -0
  16. package/examples/layouts-nfse.js +51 -51
  17. package/examples/mock-sefaz.js +52 -52
  18. package/examples/nfse-eventos.js +57 -57
  19. package/examples/reforma-ibscbs.js +27 -27
  20. package/examples/reforma-nfe-xml.js +52 -52
  21. package/examples/status-sefaz.js +12 -12
  22. package/examples/validar-xsd.js +48 -48
  23. package/package.json +59 -68
  24. package/schemas/dce/PL_DCe_100a_NT2024.001/consulta/consSitDCeTiposBasico_v1.00.xsd +123 -0
  25. package/schemas/dce/PL_DCe_100a_NT2024.001/consulta/consSitDCe_v1.00.xsd +10 -0
  26. package/schemas/dce/PL_DCe_100a_NT2024.001/consulta/consStatServDCeTiposBasico_v1.00.xsd +110 -0
  27. package/schemas/dce/PL_DCe_100a_NT2024.001/consulta/consStatServDCe_v1.00.xsd +10 -0
  28. package/schemas/dce/PL_DCe_100a_NT2024.001/consulta/dceTiposBasico_v1.00.xsd +1388 -0
  29. package/schemas/dce/PL_DCe_100a_NT2024.001/consulta/retConsSitDCe_v1.00.xsd +10 -0
  30. package/schemas/dce/PL_DCe_100a_NT2024.001/consulta/retConsStatServDCe_v1.00.xsd +10 -0
  31. package/schemas/dce/PL_DCe_100a_NT2024.001/consulta/tiposGeralDCe_v1.00.xsd +712 -0
  32. package/schemas/dce/PL_DCe_100a_NT2024.001/consulta/xmldsig-core-schema_v1.01.xsd +98 -0
  33. package/schemas/dce/PL_DCe_100a_NT2024.001/dceTiposBasico_v1.00.xsd +1410 -0
  34. package/schemas/dce/PL_DCe_100a_NT2024.001/dce_v1.00.xsd +10 -0
  35. package/schemas/dce/PL_DCe_100a_NT2024.001/evento/evCancDCe_v1.00.xsd +36 -0
  36. package/schemas/dce/PL_DCe_100a_NT2024.001/evento/eventoDCeTiposBasico_v1.00.xsd +283 -0
  37. package/schemas/dce/PL_DCe_100a_NT2024.001/evento/eventoDCe_v1.00.xsd +9 -0
  38. package/schemas/dce/PL_DCe_100a_NT2024.001/evento/procEventoDCe_v1.00.xsd +10 -0
  39. package/schemas/dce/PL_DCe_100a_NT2024.001/evento/retEventoDCe_v1.00.xsd +9 -0
  40. package/schemas/dce/PL_DCe_100a_NT2024.001/evento/tiposGeralDCe_v1.00.xsd +712 -0
  41. package/schemas/dce/PL_DCe_100a_NT2024.001/evento/xmldsig-core-schema_v1.01.xsd +98 -0
  42. package/schemas/dce/PL_DCe_100a_NT2024.001/procDCe_v1.00.xsd +37 -0
  43. package/schemas/dce/PL_DCe_100a_NT2024.001/retDCe_v1.00.xsd +9 -0
  44. package/schemas/dce/PL_DCe_100a_NT2024.001/tiposGeralDCe_v1.00.xsd +712 -0
  45. package/schemas/dce/PL_DCe_100a_NT2024.001/xmldsig-core-schema_v1.01.xsd +98 -0
  46. package/schemas/nfe/PL_009_V4/enviNFe_v3.10.xsd +8 -8
  47. package/schemas/nfe/PL_009_V4/xmldsig-core-schema_v1.01.xsd +97 -97
  48. package/scripts/download-schemas.js +39 -39
  49. package/scripts/test-xsd.js +34 -34
  50. package/src/api/server.js +316 -316
  51. package/src/constants/sefaz.js +69 -69
  52. package/src/danfe/DanfeService.js +205 -205
  53. package/src/danfe/xml-extract.js +69 -69
  54. package/src/dce/builders/DCeEventoXmlBuilder.js +86 -0
  55. package/src/dce/builders/DCeXmlBuilder.js +345 -0
  56. package/src/dce/constants/dce.js +87 -0
  57. package/src/dce/dace/DaceService.js +356 -0
  58. package/src/dce/dace/dace-extract.js +114 -0
  59. package/src/dce/services/DCeService.js +263 -0
  60. package/src/dce/services/DCeSoapService.js +235 -0
  61. package/src/dce/services/DCeXmlService.js +67 -0
  62. package/src/dce/types/DCeDocument.js +106 -0
  63. package/src/dce/utils/qrcode.utils.js +59 -0
  64. package/src/errors/GhinfeError.js +13 -13
  65. package/src/errors/SefazError.js +25 -25
  66. package/src/errors/XsdValidationError.js +15 -15
  67. package/src/index.js +84 -60
  68. package/src/nfce/builders/NFCeXmlBuilder.js +178 -178
  69. package/src/nfce/constants/qrcode.js +26 -26
  70. package/src/nfce/services/NFCeService.js +188 -188
  71. package/src/nfce/services/NFCeXmlService.js +38 -38
  72. package/src/nfce/types/NFCeDocument.js +42 -42
  73. package/src/nfce/utils/qrcode.utils.js +92 -92
  74. package/src/nfe/builders/NFeEventoXmlBuilder.js +110 -110
  75. package/src/nfe/builders/NFeXmlBuilder.js +215 -215
  76. package/src/nfe/mocks/sefaz-responses.js +195 -195
  77. package/src/nfe/services/NFeEventoService.js +95 -95
  78. package/src/nfe/services/NFeService.js +196 -196
  79. package/src/nfe/services/NFeSignService.js +127 -111
  80. package/src/nfe/services/NFeSoapService.js +437 -437
  81. package/src/nfe/services/NFeXmlService.js +41 -41
  82. package/src/nfe/services/NFeXsdService.js +56 -56
  83. package/src/nfe/types/NFeDocument.js +92 -92
  84. package/src/nfe/types/NFeEvento.js +44 -44
  85. package/src/nfse/NFSeMunicipioResolver.js +42 -42
  86. package/src/nfse/builders/NFSeEventoXmlBuilder.js +92 -92
  87. package/src/nfse/builders/NFSeXmlBuilder.js +16 -16
  88. package/src/nfse/constants/municipios.js +82 -82
  89. package/src/nfse/layouts/adapters.js +202 -202
  90. package/src/nfse/mocks/nfse-responses.js +142 -142
  91. package/src/nfse/services/NFSeService.js +191 -191
  92. package/src/nfse/services/NFSeSoapService.js +232 -232
  93. package/src/nfse/services/NFSeXmlService.js +12 -12
  94. package/src/nfse/types/NFSeDocument.js +54 -54
  95. package/src/queue/NFCeQueue.js +63 -63
  96. package/src/queue/NFSeQueue.js +59 -59
  97. package/src/queue/NFeQueue.js +76 -76
  98. package/src/reforma/ibscbs.js +175 -175
  99. package/src/sefaz/SefazEndpointResolver.js +95 -95
  100. package/src/sefaz/ca/icp-brasil-raiz-v10.pem +38 -0
  101. package/src/sefaz/endpoints.registry.js +205 -205
  102. package/src/sefaz/httpsAgent.js +33 -0
  103. package/src/shared/fiscal.helpers.js +53 -53
  104. package/src/utils/certificate.utils.js +69 -47
  105. package/src/utils/chave.utils.js +205 -117
  106. package/src/utils/code128.utils.js +67 -0
  107. package/src/utils/date.utils.js +26 -26
  108. package/src/utils/xml.utils.js +44 -44
  109. package/src/validation/XsdValidator.js +133 -114
  110. package/src/validation/schemas.js +18 -14
package/README.md CHANGED
@@ -1,413 +1,491 @@
1
- # GHINFE
2
-
3
- Biblioteca Node.js para emissão de documentos fiscais eletrônicos brasileiros: **NF-e**, **NFC-e** e **NFS-e**.
4
-
5
- Inspirada na filosofia do [ACBr](https://projetoacbr.com.br/) (objeto de domínio → XML → assinatura → envio SEFAZ), a GHINFE separa responsabilidades em serviços modulares, prontos para integração com **Sequelize**, filas **Bull/Redis** e ambientes de alta escala.
6
-
7
- ## Funcionalidades (v0.6)
8
-
9
- | Módulo | Status |
10
- |--------|--------|
11
- | Geração XML NF-e / NFC-e / NFS-e | ✅ |
12
- | IBS/CBS injetado no XML NF-e (item + totais) | ✅ experimental |
13
- | Cancelamento e substituição NFS-e | ✅ |
14
- | DANFE / DANFCE (HTML imprimível) | ✅ |
15
- | Assinatura A1, XSD, multi-UF, filas Bull | ✅ |
16
- | Injeção completa conforme XSD PL_010 estável | 🔜 |
17
-
18
- ## Estrutura de pastas
19
-
20
- ```
21
- ghinfe/
22
- ├── docs/
23
- │ ├── openapi.yaml # Especificação Swagger/OpenAPI
24
- │ └── developer-guide.html # Guia completo para desenvolvedores
25
- ├── examples/
26
- │ ├── emitir-nfe.js # Exemplo de geração + mock
27
- │ └── mock-sefaz.js # Cenários de retorno SEFAZ
28
- ├── src/
29
- │ ├── api/
30
- │ │ └── server.js # Servidor Express + Swagger UI
31
- │ ├── constants/
32
- │ │ └── sefaz.js # UF, cStat, URLs, versão layout
33
- │ ├── errors/
34
- │ │ ├── GhinfeError.js
35
- │ │ └── SefazError.js # Erros reais da SEFAZ (cStat)
36
- │ ├── nfe/
37
- │ │ ├── builders/
38
- │ │ │ ├── NFeXmlBuilder.js
39
- │ │ │ └── NFeEventoXmlBuilder.js # Cancelamento, CC-e
40
- │ │ ├── mocks/
41
- │ │ │ └── sefaz-responses.js
42
- │ │ ├── services/
43
- │ │ │ ├── NFeXmlService.js
44
- │ │ │ ├── NFeSignService.js
45
- │ │ │ ├── NFeSoapService.js
46
- │ │ │ ├── NFeEventoService.js # Eventos SEFAZ
47
- │ │ │ └── NFeService.js
48
- │ │ └── types/
49
- │ │ ├── NFeDocument.js
50
- │ │ └── NFeEvento.js
51
- │ ├── sefaz/
52
- │ │ ├── endpoints.registry.js # URLs por autorizador (SVRS, SP, MG...)
53
- │ │ └── SefazEndpointResolver.js
54
- │ ├── nfse/
55
- │ │ ├── builders/NFSeXmlBuilder.js
56
- │ │ ├── services/ # NFSeService, NFSeSoapService
57
- │ │ └── NFSeMunicipioResolver.js
58
- │ ├── queue/
59
- │ │ ├── NFeQueue.js
60
- │ │ ├── NFCeQueue.js
61
- │ │ └── NFSeQueue.js
62
- │ ├── utils/
63
- │ │ ├── certificate.utils.js
64
- │ │ ├── chave.utils.js # Chave de acesso + DV
65
- │ │ ├── date.utils.js
66
- │ │ └── xml.utils.js
67
- │ └── index.js # Exports públicos
68
- ├── .env.example
69
- └── package.json
70
- ```
71
-
72
- ## Instalação
73
-
74
- ```bash
75
- npm install
76
- cp .env.example .env
77
- npm run schemas:download # baixa XSDs se necessário (já inclusos em schemas/)
78
- ```
79
-
80
- ## Uso rápido
81
-
82
- ### 1. Montar o objeto e gerar XML (chave automática)
83
-
84
- ```javascript
85
- import { NFeService } from 'ghinfe';
86
-
87
- const service = new NFeService();
88
-
89
- const documento = {
90
- // chaveAcesso é opcional — gerada automaticamente
91
- ambiente: 2,
92
- ide: { uf: 'SP', codigoMunicipio: '3550308', serie: 1, numero: 1 },
93
- emitente: { cnpj: '12345678000199', /* ... */ },
94
- destinatario: { /* ... */ },
95
- itens: [{ /* ... */ }],
96
- };
97
-
98
- const xml = service.gerarXml(documento);
99
- ```
100
-
101
- ### 1b. Gerar chave de acesso manualmente
102
-
103
- ```javascript
104
- import { gerarChaveAcesso, validarChaveAcesso } from 'ghinfe';
105
-
106
- const chave = gerarChaveAcesso({
107
- uf: 'SP',
108
- cnpj: '12345678000199',
109
- serie: 1,
110
- numero: 42,
111
- });
112
- console.log(validarChaveAcesso(chave)); // true
113
- ```
114
-
115
- ### Endpoints SEFAZ automáticos (multi-UF)
116
-
117
- ```javascript
118
- import { SefazEndpointResolver, NFeService } from 'ghinfe';
119
-
120
- // Por UF — resolve autorizador (SVRS, SP, MG...) automaticamente
121
- const urls = SefazEndpointResolver.paraNFe('RJ', 2); // homologação
122
- console.log(urls.endpoint);
123
-
124
- // No serviço — basta informar UF
125
- const service = new NFeService({ mock: false, uf: 'SP', ambiente: 2 });
126
-
127
- // Ou configurar em runtime a partir do documento
128
- service.configurarUf('MG', 2);
129
- ```
130
-
131
- ### NFS-e (layouts municipais)
132
-
133
- ```javascript
134
- import { NFSeService, NFSE_LAYOUTS, NFSeMunicipioResolver } from 'ghinfe';
135
-
136
- // Layout explícito ou automático pelo município
137
- const service = new NFSeService({ mock: true, layout: NFSE_LAYOUTS.GINFES });
138
- const xml = service.gerarXml(documento);
139
-
140
- // Assinatura A1 do RPS
141
- const xmlAssinado = service.gerarXmlAssinado(documento, { pfx, senha });
142
-
143
- // Emissão com assinatura opcional
144
- const resultado = await service.emitir(documento, { pfx, senha });
145
- ```
146
-
147
- Layouts: `abrasf`, `ginfes`, `betha`, `issnet` — municípios pré-cadastrados em SP, RJ, BH, Curitiba, POA, Floripa, Salvador, Recife, Fortaleza.
148
-
149
- ### Consulta status SEFAZ
150
-
151
- ```javascript
152
- const service = new NFeService({ mock: true });
153
- const status = await service.consultarStatus('SP', 2);
154
- // { emOperacao: true, cStat: '107', xMotivo: 'Serviço em Operação', tMed: '1' }
155
- ```
156
-
157
- ### Reforma tributária (IBS/CBS no XML)
158
-
159
- ```javascript
160
- import { NFeService, calcularIBSCBS } from 'ghinfe';
161
-
162
- const item = {
163
- codigo: '001', descricao: '...', ncm: '...', cfop: '5102',
164
- unidade: 'UN', quantidade: 1, valorUnitario: 100, valorTotal: 100,
165
- reforma: calcularIBSCBS(100, { pIBSUF: 0.1, pIBSMun: 0.05, pCBS: 0.9 }),
166
- };
167
-
168
- const xml = service.gerarXml({ ...documento, itens: [item] });
169
- // XML contém <IBSCBS> no item e <IBSCBSTot> nos totais
170
- // Não use validarXsd:true com reforma até o schema PL_010 estar embutido
171
- ```
172
-
173
- ### Cancelamento / substituição NFS-e
174
-
175
- ```javascript
176
- await service.cancelar({
177
- numeroNfse: '2026000001',
178
- cnpjPrestador: '...',
179
- inscricaoMunicipal: '...',
180
- codigoMunicipio: '3550308',
181
- });
182
-
183
- await service.substituir({
184
- numeroNfseSubstituida: '2026000001',
185
- cnpjPrestador: '...',
186
- inscricaoMunicipal: '...',
187
- codigoMunicipio: '3550308',
188
- documentoSubstituto: { /* novo RPS */ },
189
- });
190
- ```
191
-
192
- ### DANFE / DANFCE
193
-
194
- ```javascript
195
- import { DanfeService } from 'ghinfe';
196
-
197
- const danfe = new DanfeService();
198
- const { html } = await danfe.gerarDanfe(xmlNfe, { protocolo: '...' });
199
- const { html: cupom } = await danfe.gerarDanfce(xmlNfce, { qrCodeUrl: '...' });
200
- // Salve o HTML e imprima / “Salvar como PDF” no navegador
201
- ```
202
-
203
- ### Consulta status SEFAZ
204
-
205
- ```javascript
206
- import { NFeService, XsdValidationError } from 'ghinfe';
207
-
208
- const service = new NFeService({ validarXsd: true });
209
-
210
- try {
211
- service.validarXsd(xml, { preAssinatura: true });
212
- } catch (error) {
213
- if (error instanceof XsdValidationError) console.error(error.erros);
214
- }
215
- ```
216
-
217
- Schemas oficiais em `schemas/nfe/PL_009_V4/`.
218
-
219
- ### NFC-e (modelo 65)
220
-
221
- ```javascript
222
- import { NFCeService } from 'ghinfe';
223
-
224
- const service = new NFCeService({ mock: true });
225
-
226
- const documento = {
227
- ambiente: 2,
228
- ide: { uf: 'SP', codigoMunicipio: '3550308', serie: 1, numero: 1 },
229
- emitente: { cnpj: '...', razaoSocial: '...', ie: '...', crt: '1', endereco: { /* ... */ } },
230
- itens: [{ codigo: '001', descricao: '...', ncm: '...', cfop: '5102', unidade: 'UN', quantidade: 1, valorUnitario: 10, valorTotal: 10 }],
231
- csc: { idCSC: '000001', codigo: 'SEU-CSC-SEFAZ' },
232
- };
233
-
234
- const xml = service.gerarXml(documento);
235
- // QR Code v3 é adicionado após assinatura:
236
- const { xml: xmlCompleto, qrCode } = service.gerarXmlAssinado(documento, { pfx, senha });
237
- ```
238
-
239
- ### 2. Assinar com certificado A1
240
-
241
- ```javascript
242
- import fs from 'fs';
243
-
244
- const pfx = fs.readFileSync('./certs/certificado.pfx');
245
- const xmlAssinado = service.gerarXmlAssinado(documento, {
246
- pfx,
247
- senha: process.env.PFX_PASSWORD,
248
- });
249
- ```
250
-
251
- ### 3. Enviar para SEFAZ (mock em desenvolvimento)
252
-
253
- ```javascript
254
- const service = new NFeService({ mock: true });
255
-
256
- const resultado = await service.emitir(documento, { pfx, senha: '...' });
257
-
258
- if (resultado.retorno.autorizado) {
259
- console.log('Protocolo:', resultado.retorno.protocolo);
260
- }
261
- ```
262
-
263
- ### 4. Persistir no banco (Sequelize)
264
-
265
- ```javascript
266
- const payload = service.toPersistencePayload(resultado);
267
-
268
- await NotaFiscal.create({
269
- chave_acesso: payload.chave_acesso,
270
- xml_nfe: payload.xml_nfe,
271
- xml_protocolo: payload.xml_protocolo,
272
- protocolo: payload.protocolo,
273
- status: payload.status,
274
- });
275
- ```
276
-
277
- ### 5. Cancelar NF-e
278
-
279
- ```javascript
280
- const resultado = await service.cancelar({
281
- chaveAcesso: '35260712345678000199550010000000011000000011',
282
- cnpj: '12345678000199',
283
- uf: 'SP',
284
- protocolo: '135260000000001',
285
- justificativa: 'Cancelamento por erro de digitacao no pedido',
286
- }, { pfx, senha });
287
-
288
- console.log(resultado.retorno.cStat); // '135' — evento registrado
289
- ```
290
-
291
- ### 6. Carta de Correção (CC-e)
292
-
293
- ```javascript
294
- const resultado = await service.cartaCorrecao({
295
- chaveAcesso: '...',
296
- cnpj: '12345678000199',
297
- uf: 'SP',
298
- sequencia: 1,
299
- correcao: 'Correcao do endereco de entrega: Rua Nova, 500',
300
- }, { pfx, senha });
301
- ```
302
-
303
- ### 7. Fila assíncrona (Bull + Redis)
304
-
305
- ```javascript
306
- import { NFeQueue } from 'ghinfe';
307
-
308
- const queue = new NFeQueue({
309
- redis: { host: '127.0.0.1', port: 6379 },
310
- nfeOptions: { mock: true },
311
- });
312
-
313
- await queue.adicionarEmissao({ documento, certificado: { pfx, senha } });
314
-
315
- queue.onCompleted(async (resultado) => {
316
- await NotaFiscal.create(service.toPersistencePayload(resultado));
317
- });
318
- ```
319
-
320
- ## Tratamento de erros SEFAZ
321
-
322
- ```javascript
323
- import { SefazError } from 'ghinfe';
324
-
325
- try {
326
- await service.enviar(xmlAssinado, chaveAcesso);
327
- } catch (error) {
328
- if (error instanceof SefazError) {
329
- console.error(`Rejeição ${error.cStat}: ${error.xMotivo}`);
330
- // 204 = duplicidade, 225 = schema, 280 = certificado...
331
- }
332
- }
333
- ```
334
-
335
- ## API de testes (Swagger)
336
-
337
- ```bash
338
- npm start
339
- ```
340
-
341
- | URL | Descrição |
342
- |-----|-----------|
343
- | http://localhost:3333/docs | Swagger UI interativo |
344
- | http://localhost:3333/guia/developer-guide.html | Guia HTML do desenvolvedor |
345
- | http://localhost:3333/health | Health check |
346
-
347
- ### Endpoints
348
-
349
- - `GET /sefaz/endpoints/:uf?ambiente=2` — URLs SEFAZ por UF
350
- - `GET /sefaz/ufs` — Lista UFs suportadas
351
- - `POST /nfse/cancelar-mock` — Cancela NFS-e (mock)
352
- - `POST /nfse/substituir-mock` — Substitui NFS-e (mock)
353
- - `POST /danfe` — Gera DANFE/DANFCE (JSON ou `?format=html`)
354
- - `GET /sefaz/status/:uf?cenario=ok` — Consulta status SEFAZ (mock)
355
- - `GET /nfse/layouts` — Layouts NFS-e suportados
356
- - `POST /reforma/calcular` — Calcula IBS/CBS + fragmento XML
357
- - `POST /nfse/xml?layout=ginfes` — Gera XML RPS NFS-e
358
- - `POST /nfse/enviar-mock` — Emite NFS-e mock
359
- - `GET /nfse/municipios` — Municípios configurados
360
- - `POST /nfe/xml?validarXsd=true` — Gera XML com validação XSD
361
- - `POST /nfe/validar-xsd` — Valida XML existente
362
- - `POST /nfce/xml` — Gera XML NFC-e (modelo 65)
363
- - `POST /nfce/enviar-mock` — Envia NFC-e mock
364
- - `POST /nfe/xml` — Gera XML a partir do JSON
365
- - `POST /nfe/enviar-mock?cenario=autorizado` — Fluxo completo mock
366
- - `POST /nfe/cancelar-mock` — Cancelamento simulado
367
- - `POST /nfe/cce-mock` — Carta de correção simulada
368
- - `GET /nfe/cenarios-mock` — Lista cenários de teste
369
-
370
- ## Exemplos CLI
371
-
372
- ```bash
373
- npm run example:nfe # Gera XML e simula retorno
374
- npm run example:mock # Testa cenários SEFAZ mock
375
- npm run example:endpoints # Endpoints SEFAZ por UF
376
- npm run example:reforma-xml # NF-e com IBSCBS no XML
377
- npm run example:nfse-eventos # Cancelamento / substituição NFS-e
378
- npm run example:danfe # Gera HTML em tmp/
379
- npm run example:status # Status SEFAZ mock
380
- npm run example:layouts # ABRASF / GINFES / Betha / ISSNet
381
- npm run example:reforma # Cálculo IBS/CBS
382
- npm run example:nfse # NFS-e mock
383
- npm run example:nfce # Gera NFC-e modelo 65
384
- npm run example:xsd # Valida XML contra XSD
385
- npm run example:eventos # Cancelamento e CC-e mock
386
- npm run schemas:download # Baixa schemas XSD da SEFAZ
387
- ```
388
-
389
- ## Arquitetura
390
-
391
- ```
392
- ┌─────────────┐ ┌──────────────┐ ┌───────────────┐ ┌────────────┐
393
- │ Seu App │────▶│ NFeService │────▶│ NFeXmlService │────▶│ XML NF-e │
394
- │ (Sequelize) │ │ (orquestrador)│ └───────────────┘ └────────────┘
395
- └─────────────┘ │ │────▶│ NFeSignService│────▶│ XML Assinado│
396
- │ │ │ └───────────────┘ └────────────┘
397
- │ │ │────▶│ NFeSoapService│────▶│ SEFAZ/Mock │
398
- ▼ └──────────────┘ └───────────────┘ └────────────┘
399
- ┌─────────────┐
400
- │ NFeQueue │ Bull/Redis — processamento assíncrono
401
- └─────────────┘
402
- ```
403
-
404
- ## Roadmap
405
-
406
- - [ ] Validação XSD PL_010 completa (quando SEFAZ publicar pacote estável)
407
- - [ ] Mais municípios / provedores NFS-e
408
- - [ ] PDF nativo (puppeteer opcional) no DanfeService
409
- - [ ] Assinatura XML de cancelamento NFS-e por layout
410
-
411
- ## Licença
412
-
413
- MIT — veja [LICENSE](LICENSE).
1
+ # GHINFE
2
+
3
+ Biblioteca Node.js para emissão de documentos fiscais eletrônicos brasileiros: **NF-e**, **NFC-e**, **NFS-e** e **DC-e**.
4
+
5
+ Inspirada na filosofia do [ACBr](https://projetoacbr.com.br/) (objeto de domínio → XML → assinatura → envio SEFAZ), a GHINFE separa responsabilidades em serviços modulares, prontos para integração com **Sequelize**, filas **Bull/Redis** e ambientes de alta escala.
6
+
7
+ ## Funcionalidades (v0.6)
8
+
9
+ | Módulo | Status |
10
+ |--------|--------|
11
+ | Geração XML NF-e / NFC-e / NFS-e | ✅ |
12
+ | DC-e (modelo 99): geração, autorização, consulta, status e cancelamento | ✅ |
13
+ | DACE completa e resumida (HTML imprimível, CODE-128C + QR Code) | ✅ |
14
+ | IBS/CBS injetado no XML NF-e (item + totais) | ✅ experimental |
15
+ | Cancelamento e substituição NFS-e | ✅ |
16
+ | DANFE / DANFCE (HTML imprimível) | ✅ |
17
+ | Assinatura A1, XSD, multi-UF, filas Bull | ✅ |
18
+ | Injeção completa conforme XSD PL_010 estável | 🔜 |
19
+
20
+ ## Estrutura de pastas
21
+
22
+ ```
23
+ ghinfe/
24
+ ├── docs/
25
+ │ ├── openapi.yaml # Especificação Swagger/OpenAPI
26
+ │ └── developer-guide.html # Guia completo para desenvolvedores
27
+ ├── examples/
28
+ │ ├── emitir-nfe.js # Exemplo de geração + mock
29
+ │ └── mock-sefaz.js # Cenários de retorno SEFAZ
30
+ ├── src/
31
+ │ ├── api/
32
+ │ │ └── server.js # Servidor Express + Swagger UI
33
+ │ ├── constants/
34
+ │ │ └── sefaz.js # UF, cStat, URLs, versão layout
35
+ │ ├── errors/
36
+ │ │ ├── GhinfeError.js
37
+ │ │ └── SefazError.js # Erros reais da SEFAZ (cStat)
38
+ │ ├── nfe/
39
+ │ │ ├── builders/
40
+ │ │ │ ├── NFeXmlBuilder.js
41
+ │ │ │ └── NFeEventoXmlBuilder.js # Cancelamento, CC-e
42
+ │ │ ├── mocks/
43
+ │ │ │ └── sefaz-responses.js
44
+ │ │ ├── services/
45
+ │ │ │ ├── NFeXmlService.js
46
+ │ │ │ ├── NFeSignService.js
47
+ │ │ │ ├── NFeSoapService.js
48
+ │ │ │ ├── NFeEventoService.js # Eventos SEFAZ
49
+ │ │ │ └── NFeService.js
50
+ │ │ └── types/
51
+ │ │ ├── NFeDocument.js
52
+ │ │ └── NFeEvento.js
53
+ │ ├── sefaz/
54
+ │ │ ├── endpoints.registry.js # URLs por autorizador (SVRS, SP, MG...)
55
+ │ │ └── SefazEndpointResolver.js
56
+ │ ├── nfse/
57
+ │ │ ├── builders/NFSeXmlBuilder.js
58
+ │ │ ├── services/ # NFSeService, NFSeSoapService
59
+ │ │ └── NFSeMunicipioResolver.js
60
+ │ ├── queue/
61
+ │ │ ├── NFeQueue.js
62
+ │ │ ├── NFCeQueue.js
63
+ │ │ └── NFSeQueue.js
64
+ │ ├── utils/
65
+ │ │ ├── certificate.utils.js
66
+ │ │ ├── chave.utils.js # Chave de acesso + DV
67
+ │ │ ├── date.utils.js
68
+ │ │ └── xml.utils.js
69
+ │ └── index.js # Exports públicos
70
+ ├── .env.example
71
+ └── package.json
72
+ ```
73
+
74
+ ## Instalação
75
+
76
+ ```bash
77
+ npm install
78
+ cp .env.example .env
79
+ npm run schemas:download # baixa XSDs se necessário (já inclusos em schemas/)
80
+ ```
81
+
82
+ ## Uso rápido
83
+
84
+ ### 1. Montar o objeto e gerar XML (chave automática)
85
+
86
+ ```javascript
87
+ import { NFeService } from 'ghinfe';
88
+
89
+ const service = new NFeService();
90
+
91
+ const documento = {
92
+ // chaveAcesso é opcional — gerada automaticamente
93
+ ambiente: 2,
94
+ ide: { uf: 'SP', codigoMunicipio: '3550308', serie: 1, numero: 1 },
95
+ emitente: { cnpj: '12345678000199', /* ... */ },
96
+ destinatario: { /* ... */ },
97
+ itens: [{ /* ... */ }],
98
+ };
99
+
100
+ const xml = service.gerarXml(documento);
101
+ ```
102
+
103
+ ### 1b. Gerar chave de acesso manualmente
104
+
105
+ ```javascript
106
+ import { gerarChaveAcesso, validarChaveAcesso } from 'ghinfe';
107
+
108
+ const chave = gerarChaveAcesso({
109
+ uf: 'SP',
110
+ cnpj: '12345678000199',
111
+ serie: 1,
112
+ numero: 42,
113
+ });
114
+ console.log(validarChaveAcesso(chave)); // true
115
+ ```
116
+
117
+ ### Endpoints SEFAZ automáticos (multi-UF)
118
+
119
+ ```javascript
120
+ import { SefazEndpointResolver, NFeService } from 'ghinfe';
121
+
122
+ // Por UF — resolve autorizador (SVRS, SP, MG...) automaticamente
123
+ const urls = SefazEndpointResolver.paraNFe('RJ', 2); // homologação
124
+ console.log(urls.endpoint);
125
+
126
+ // No serviço — basta informar UF
127
+ const service = new NFeService({ mock: false, uf: 'SP', ambiente: 2 });
128
+
129
+ // Ou configurar em runtime a partir do documento
130
+ service.configurarUf('MG', 2);
131
+ ```
132
+
133
+ ### NFS-e (layouts municipais)
134
+
135
+ ```javascript
136
+ import { NFSeService, NFSE_LAYOUTS, NFSeMunicipioResolver } from 'ghinfe';
137
+
138
+ // Layout explícito ou automático pelo município
139
+ const service = new NFSeService({ mock: true, layout: NFSE_LAYOUTS.GINFES });
140
+ const xml = service.gerarXml(documento);
141
+
142
+ // Assinatura A1 do RPS
143
+ const xmlAssinado = service.gerarXmlAssinado(documento, { pfx, senha });
144
+
145
+ // Emissão com assinatura opcional
146
+ const resultado = await service.emitir(documento, { pfx, senha });
147
+ ```
148
+
149
+ Layouts: `abrasf`, `ginfes`, `betha`, `issnet` — municípios pré-cadastrados em SP, RJ, BH, Curitiba, POA, Floripa, Salvador, Recife, Fortaleza.
150
+
151
+ ### Consulta status SEFAZ
152
+
153
+ ```javascript
154
+ const service = new NFeService({ mock: true });
155
+ const status = await service.consultarStatus('SP', 2);
156
+ // { emOperacao: true, cStat: '107', xMotivo: 'Serviço em Operação', tMed: '1' }
157
+ ```
158
+
159
+ ### Reforma tributária (IBS/CBS no XML)
160
+
161
+ ```javascript
162
+ import { NFeService, calcularIBSCBS } from 'ghinfe';
163
+
164
+ const item = {
165
+ codigo: '001', descricao: '...', ncm: '...', cfop: '5102',
166
+ unidade: 'UN', quantidade: 1, valorUnitario: 100, valorTotal: 100,
167
+ reforma: calcularIBSCBS(100, { pIBSUF: 0.1, pIBSMun: 0.05, pCBS: 0.9 }),
168
+ };
169
+
170
+ const xml = service.gerarXml({ ...documento, itens: [item] });
171
+ // XML contém <IBSCBS> no item e <IBSCBSTot> nos totais
172
+ // Não use validarXsd:true com reforma até o schema PL_010 estar embutido
173
+ ```
174
+
175
+ ### Cancelamento / substituição NFS-e
176
+
177
+ ```javascript
178
+ await service.cancelar({
179
+ numeroNfse: '2026000001',
180
+ cnpjPrestador: '...',
181
+ inscricaoMunicipal: '...',
182
+ codigoMunicipio: '3550308',
183
+ });
184
+
185
+ await service.substituir({
186
+ numeroNfseSubstituida: '2026000001',
187
+ cnpjPrestador: '...',
188
+ inscricaoMunicipal: '...',
189
+ codigoMunicipio: '3550308',
190
+ documentoSubstituto: { /* novo RPS */ },
191
+ });
192
+ ```
193
+
194
+ ### DANFE / DANFCE
195
+
196
+ ```javascript
197
+ import { DanfeService } from 'ghinfe';
198
+
199
+ const danfe = new DanfeService();
200
+ const { html } = await danfe.gerarDanfe(xmlNfe, { protocolo: '...' });
201
+ const { html: cupom } = await danfe.gerarDanfce(xmlNfce, { qrCodeUrl: '...' });
202
+ // Salve o HTML e imprima / “Salvar como PDF” no navegador
203
+ ```
204
+
205
+ ### Consulta status SEFAZ
206
+
207
+ ```javascript
208
+ import { NFeService, XsdValidationError } from 'ghinfe';
209
+
210
+ const service = new NFeService({ validarXsd: true });
211
+
212
+ try {
213
+ service.validarXsd(xml, { preAssinatura: true });
214
+ } catch (error) {
215
+ if (error instanceof XsdValidationError) console.error(error.erros);
216
+ }
217
+ ```
218
+
219
+ Schemas oficiais em `schemas/nfe/PL_009_V4/`.
220
+
221
+ ### NFC-e (modelo 65)
222
+
223
+ ```javascript
224
+ import { NFCeService } from 'ghinfe';
225
+
226
+ const service = new NFCeService({ mock: true });
227
+
228
+ const documento = {
229
+ ambiente: 2,
230
+ ide: { uf: 'SP', codigoMunicipio: '3550308', serie: 1, numero: 1 },
231
+ emitente: { cnpj: '...', razaoSocial: '...', ie: '...', crt: '1', endereco: { /* ... */ } },
232
+ itens: [{ codigo: '001', descricao: '...', ncm: '...', cfop: '5102', unidade: 'UN', quantidade: 1, valorUnitario: 10, valorTotal: 10 }],
233
+ csc: { idCSC: '000001', codigo: 'SEU-CSC-SEFAZ' },
234
+ };
235
+
236
+ const xml = service.gerarXml(documento);
237
+ // QR Code v3 é adicionado após assinatura:
238
+ const { xml: xmlCompleto, qrCode } = service.gerarXmlAssinado(documento, { pfx, senha });
239
+ ```
240
+
241
+ ### DC-e — Declaração de Conteúdo eletrônica (modelo 99)
242
+
243
+ Para remessa de bens por pessoas físicas ou não contribuintes do ICMS. Layout 1.00 (PL_DCe_v1.00a + NT 2024.001), validado contra o XSD oficial em `schemas/dce/`.
244
+
245
+ ```javascript
246
+ import { DCeService, DCE_TIPO_EMITENTE, DCE_MODALIDADE_TRANSPORTE } from 'ghinfe';
247
+
248
+ const service = new DCeService();
249
+
250
+ const documento = {
251
+ ambiente: 2,
252
+ ide: { uf: 'SP', serie: 1, numero: 42, tipoEmitente: DCE_TIPO_EMITENTE.MARKETPLACE },
253
+ emitente: { cpf: '...', razaoSocial: '...', endereco: { /* ... */ } },
254
+ marketplace: { cnpj: '...', razaoSocial: '...', site: 'www.exemplo.com.br' },
255
+ destinatario: { cpf: '...', razaoSocial: '...', endereco: { /* ... */ } },
256
+ itens: [{ descricao: 'LIVRO USADO', ncm: '49019900', quantidade: 2, valorUnitario: 35.5 }],
257
+ transporte: { modalidade: DCE_MODALIDADE_TRANSPORTE.CORREIOS },
258
+ };
259
+
260
+ const xml = service.gerarXml(documento, { validarXsd: true });
261
+ const { xml: xmlAssinado, chaveAcesso, qrCode } = service.gerarXmlAssinado(documento, { pfx, senha });
262
+ ```
263
+
264
+ - `tipoEmitente` define o grupo obrigatório: `fisco` (0), `marketplace` (1), nenhum na emissão própria (2), `transportadora` (3) ou `ect` (4). O CNPJ desse emissor compõe a chave de acesso.
265
+ - Em homologação, `dest/xNome` recebe automaticamente o literal exigido pela SEFAZ.
266
+ - Contingência off-line (`ide.tipoEmissao: 2`): o QR Code leva o parâmetro `sign`, por isso use `gerarXmlAssinado`.
267
+
268
+ #### Comunicação com a SEFAZ (autorizador SEFA-PR, atende todas as UFs)
269
+
270
+ ```javascript
271
+ const service = new DCeService({ certificado: { pfx, senha }, ambiente: 2 });
272
+
273
+ await service.consultarStatus(); // { emOperacao: true, cStat: '107', ... }
274
+
275
+ const resultado = await service.emitir(documento); // lança SefazError se rejeitada
276
+ resultado.retorno.protocolo; // nProt
277
+ resultado.retorno.xmlProc; // dceProc (DC-e + protDCe) — guarde este XML
278
+
279
+ await service.consultar(resultado.chaveAcesso); // cStat 100 (autorizada) / 101 (cancelada)
280
+
281
+ await service.cancelar({ // até 24h após a autorização
282
+ chaveAcesso: resultado.chaveAcesso,
283
+ protocolo: resultado.retorno.protocolo,
284
+ justificativa: 'Motivo com no minimo 15 caracteres',
285
+ emitente: { cpf: '...' }, // mesmo usuário emitente da DC-e
286
+ });
287
+ ```
288
+
289
+ - TLS mútuo com o certificado A1; a raiz ICP-Brasil v10 (`src/sefaz/ca/`) é adicionada às CAs do Node, sem desligar a verificação do servidor.
290
+ - `emitir(documento, { compactar: true })` usa `dceAutorizacaoZip` (GZip + Base64).
291
+ - Emissão própria (`tipoEmitente: 2`) só é aceita para não contribuintes do ICMS; contribuintes recebem a rejeição 812.
292
+ - Certificado em PEM (`cert.pem` + `key.pem`): `criarPfxDePem(certPem, keyPem, senha)` gera o PFX em memória.
293
+ - Testes em homologação: `npm run example:dce-homologacao -- status` (veja as variáveis no topo de `examples/dce-homologacao.js`).
294
+
295
+ #### DACE — Declaração Auxiliar de Conteúdo eletrônica
296
+
297
+ ```javascript
298
+ import { DaceService } from 'ghinfe';
299
+
300
+ const dace = new DaceService();
301
+ const { html, folhas } = await dace.gerar(xmlDceProc); // completa (A4, com itens)
302
+ const { html: etiqueta } = await dace.gerar(xmlDceProc, { modelo: 'resumida' }); // sem itens, 10×15 cm
303
+
304
+ // ou direto do resultado da emissão
305
+ const { html: dace2 } = await service.gerarDace(resultado);
306
+ ```
307
+
308
+ - Segue o Manual DC-e — Anexo II: chave em blocos de 4 dígitos, código de barras CODE-128C (SVG, sem dependência) e QR Code do `infDCeSupl`.
309
+ - Folhas adicionais repetem cabeçalho, código de barras, chave e remetente; ajuste com `itensPrimeiraFolha` / `itensDemaisFolhas`.
310
+ - Homologação: "EMITIDA EM HOMOLOGAÇÃO" em marca d'água e nas informações complementares. Contingência: "EMITIDA EM CONTINGÊNCIA" no protocolo.
311
+ - CPF mascarado por padrão (`123.***.***-09`, recomendação LGPD da NT 2024.001); use `mascararCpf: false` para exibir completo.
312
+ - Exige o protocolo de autorização (use o `dceProc`), exceto em contingência.
313
+
314
+ ### 2. Assinar com certificado A1
315
+
316
+ ```javascript
317
+ import fs from 'fs';
318
+
319
+ const pfx = fs.readFileSync('./certs/certificado.pfx');
320
+ const xmlAssinado = service.gerarXmlAssinado(documento, {
321
+ pfx,
322
+ senha: process.env.PFX_PASSWORD,
323
+ });
324
+ ```
325
+
326
+ ### 3. Enviar para SEFAZ (mock em desenvolvimento)
327
+
328
+ ```javascript
329
+ const service = new NFeService({ mock: true });
330
+
331
+ const resultado = await service.emitir(documento, { pfx, senha: '...' });
332
+
333
+ if (resultado.retorno.autorizado) {
334
+ console.log('Protocolo:', resultado.retorno.protocolo);
335
+ }
336
+ ```
337
+
338
+ ### 4. Persistir no banco (Sequelize)
339
+
340
+ ```javascript
341
+ const payload = service.toPersistencePayload(resultado);
342
+
343
+ await NotaFiscal.create({
344
+ chave_acesso: payload.chave_acesso,
345
+ xml_nfe: payload.xml_nfe,
346
+ xml_protocolo: payload.xml_protocolo,
347
+ protocolo: payload.protocolo,
348
+ status: payload.status,
349
+ });
350
+ ```
351
+
352
+ ### 5. Cancelar NF-e
353
+
354
+ ```javascript
355
+ const resultado = await service.cancelar({
356
+ chaveAcesso: '35260712345678000199550010000000011000000011',
357
+ cnpj: '12345678000199',
358
+ uf: 'SP',
359
+ protocolo: '135260000000001',
360
+ justificativa: 'Cancelamento por erro de digitacao no pedido',
361
+ }, { pfx, senha });
362
+
363
+ console.log(resultado.retorno.cStat); // '135' — evento registrado
364
+ ```
365
+
366
+ ### 6. Carta de Correção (CC-e)
367
+
368
+ ```javascript
369
+ const resultado = await service.cartaCorrecao({
370
+ chaveAcesso: '...',
371
+ cnpj: '12345678000199',
372
+ uf: 'SP',
373
+ sequencia: 1,
374
+ correcao: 'Correcao do endereco de entrega: Rua Nova, 500',
375
+ }, { pfx, senha });
376
+ ```
377
+
378
+ ### 7. Fila assíncrona (Bull + Redis)
379
+
380
+ ```javascript
381
+ import { NFeQueue } from 'ghinfe';
382
+
383
+ const queue = new NFeQueue({
384
+ redis: { host: '127.0.0.1', port: 6379 },
385
+ nfeOptions: { mock: true },
386
+ });
387
+
388
+ await queue.adicionarEmissao({ documento, certificado: { pfx, senha } });
389
+
390
+ queue.onCompleted(async (resultado) => {
391
+ await NotaFiscal.create(service.toPersistencePayload(resultado));
392
+ });
393
+ ```
394
+
395
+ ## Tratamento de erros SEFAZ
396
+
397
+ ```javascript
398
+ import { SefazError } from 'ghinfe';
399
+
400
+ try {
401
+ await service.enviar(xmlAssinado, chaveAcesso);
402
+ } catch (error) {
403
+ if (error instanceof SefazError) {
404
+ console.error(`Rejeição ${error.cStat}: ${error.xMotivo}`);
405
+ // 204 = duplicidade, 225 = schema, 280 = certificado...
406
+ }
407
+ }
408
+ ```
409
+
410
+ ## API de testes (Swagger)
411
+
412
+ ```bash
413
+ npm start
414
+ ```
415
+
416
+ | URL | Descrição |
417
+ |-----|-----------|
418
+ | http://localhost:3333/docs | Swagger UI interativo |
419
+ | http://localhost:3333/guia/developer-guide.html | Guia HTML do desenvolvedor |
420
+ | http://localhost:3333/health | Health check |
421
+
422
+ ### Endpoints
423
+
424
+ - `GET /sefaz/endpoints/:uf?ambiente=2` — URLs SEFAZ por UF
425
+ - `GET /sefaz/ufs` — Lista UFs suportadas
426
+ - `POST /nfse/cancelar-mock` — Cancela NFS-e (mock)
427
+ - `POST /nfse/substituir-mock` — Substitui NFS-e (mock)
428
+ - `POST /danfe` — Gera DANFE/DANFCE (JSON ou `?format=html`)
429
+ - `GET /sefaz/status/:uf?cenario=ok` — Consulta status SEFAZ (mock)
430
+ - `GET /nfse/layouts` — Layouts NFS-e suportados
431
+ - `POST /reforma/calcular` — Calcula IBS/CBS + fragmento XML
432
+ - `POST /nfse/xml?layout=ginfes` — Gera XML RPS NFS-e
433
+ - `POST /nfse/enviar-mock` — Emite NFS-e mock
434
+ - `GET /nfse/municipios` — Municípios configurados
435
+ - `POST /nfe/xml?validarXsd=true` — Gera XML com validação XSD
436
+ - `POST /nfe/validar-xsd` — Valida XML existente
437
+ - `POST /nfce/xml` — Gera XML NFC-e (modelo 65)
438
+ - `POST /nfce/enviar-mock` — Envia NFC-e mock
439
+ - `POST /nfe/xml` — Gera XML a partir do JSON
440
+ - `POST /nfe/enviar-mock?cenario=autorizado` — Fluxo completo mock
441
+ - `POST /nfe/cancelar-mock` — Cancelamento simulado
442
+ - `POST /nfe/cce-mock` — Carta de correção simulada
443
+ - `GET /nfe/cenarios-mock` — Lista cenários de teste
444
+
445
+ ## Exemplos CLI
446
+
447
+ ```bash
448
+ npm run example:nfe # Gera XML e simula retorno
449
+ npm run example:mock # Testa cenários SEFAZ mock
450
+ npm run example:endpoints # Endpoints SEFAZ por UF
451
+ npm run example:reforma-xml # NF-e com IBSCBS no XML
452
+ npm run example:nfse-eventos # Cancelamento / substituição NFS-e
453
+ npm run example:danfe # Gera HTML em tmp/
454
+ npm run example:dce # Gera DC-e (modelo 99) e valida XSD
455
+ npm run example:dce-homologacao -- status # DC-e real em homologação (requer certificado)
456
+ npm run example:dace # Gera DACE completa e resumida em tmp/
457
+ npm run example:status # Status SEFAZ mock
458
+ npm run example:layouts # ABRASF / GINFES / Betha / ISSNet
459
+ npm run example:reforma # Cálculo IBS/CBS
460
+ npm run example:nfse # NFS-e mock
461
+ npm run example:nfce # Gera NFC-e modelo 65
462
+ npm run example:xsd # Valida XML contra XSD
463
+ npm run example:eventos # Cancelamento e CC-e mock
464
+ npm run schemas:download # Baixa schemas XSD da SEFAZ
465
+ ```
466
+
467
+ ## Arquitetura
468
+
469
+ ```
470
+ ┌─────────────┐ ┌──────────────┐ ┌───────────────┐ ┌────────────┐
471
+ │ Seu App │────▶│ NFeService │────▶│ NFeXmlService │────▶│ XML NF-e │
472
+ │ (Sequelize) │ │ (orquestrador)│ └───────────────┘ └────────────┘
473
+ └─────────────┘ │ │────▶│ NFeSignService│────▶│ XML Assinado│
474
+ │ │ │ └───────────────┘ └────────────┘
475
+ │ │ │────▶│ NFeSoapService│────▶│ SEFAZ/Mock │
476
+ ▼ └──────────────┘ └───────────────┘ └────────────┘
477
+ ┌─────────────┐
478
+ │ NFeQueue │ Bull/Redis — processamento assíncrono
479
+ └─────────────┘
480
+ ```
481
+
482
+ ## Roadmap
483
+
484
+ - [ ] Validação XSD PL_010 completa (quando SEFAZ publicar pacote estável)
485
+ - [ ] Mais municípios / provedores NFS-e
486
+ - [ ] PDF nativo (puppeteer opcional) no DanfeService
487
+ - [ ] Assinatura XML de cancelamento NFS-e por layout
488
+
489
+ ## Licença
490
+
491
+ MIT — veja [LICENSE](LICENSE).