ghinfe 0.6.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 (80) hide show
  1. package/.env.example +15 -0
  2. package/LICENSE +21 -0
  3. package/README.md +413 -0
  4. package/docs/developer-guide.html +421 -0
  5. package/docs/openapi.yaml +428 -0
  6. package/examples/emitir-nfce.js +51 -0
  7. package/examples/emitir-nfe.js +83 -0
  8. package/examples/emitir-nfse.js +46 -0
  9. package/examples/endpoints-sefaz.js +14 -0
  10. package/examples/eventos-nfe.js +49 -0
  11. package/examples/gerar-chave.js +25 -0
  12. package/examples/gerar-danfe.js +75 -0
  13. package/examples/layouts-nfse.js +51 -0
  14. package/examples/mock-sefaz.js +52 -0
  15. package/examples/nfse-eventos.js +57 -0
  16. package/examples/reforma-ibscbs.js +27 -0
  17. package/examples/reforma-nfe-xml.js +52 -0
  18. package/examples/status-sefaz.js +12 -0
  19. package/examples/validar-xsd.js +48 -0
  20. package/package.json +68 -0
  21. package/schemas/nfe/PL_009_V4/consReciNFe_v4.00.xsd +9 -0
  22. package/schemas/nfe/PL_009_V4/consSitNFe_v4.00.xsd +9 -0
  23. package/schemas/nfe/PL_009_V4/consStatServ_v4.00.xsd +9 -0
  24. package/schemas/nfe/PL_009_V4/enviNFe_v3.10.xsd +9 -0
  25. package/schemas/nfe/PL_009_V4/enviNFe_v4.00.xsd +9 -0
  26. package/schemas/nfe/PL_009_V4/inutNFe_v4.00.xsd +9 -0
  27. package/schemas/nfe/PL_009_V4/leiauteNFe_v4.00.xsd +7339 -0
  28. package/schemas/nfe/PL_009_V4/nfe_v4.00.xsd +9 -0
  29. package/schemas/nfe/PL_009_V4/tiposBasico_v4.00.xsd +598 -0
  30. package/schemas/nfe/PL_009_V4/xmldsig-core-schema_v1.01.xsd +98 -0
  31. package/scripts/download-schemas.js +39 -0
  32. package/scripts/test-xsd.js +34 -0
  33. package/src/api/server.js +316 -0
  34. package/src/constants/sefaz.js +69 -0
  35. package/src/danfe/DanfeService.js +205 -0
  36. package/src/danfe/xml-extract.js +69 -0
  37. package/src/errors/GhinfeError.js +13 -0
  38. package/src/errors/SefazError.js +25 -0
  39. package/src/errors/XsdValidationError.js +15 -0
  40. package/src/index.js +60 -0
  41. package/src/nfce/builders/NFCeXmlBuilder.js +178 -0
  42. package/src/nfce/constants/qrcode.js +26 -0
  43. package/src/nfce/services/NFCeService.js +188 -0
  44. package/src/nfce/services/NFCeXmlService.js +38 -0
  45. package/src/nfce/types/NFCeDocument.js +42 -0
  46. package/src/nfce/utils/qrcode.utils.js +92 -0
  47. package/src/nfe/builders/NFeEventoXmlBuilder.js +110 -0
  48. package/src/nfe/builders/NFeXmlBuilder.js +215 -0
  49. package/src/nfe/mocks/sefaz-responses.js +195 -0
  50. package/src/nfe/services/NFeEventoService.js +95 -0
  51. package/src/nfe/services/NFeService.js +196 -0
  52. package/src/nfe/services/NFeSignService.js +111 -0
  53. package/src/nfe/services/NFeSoapService.js +437 -0
  54. package/src/nfe/services/NFeXmlService.js +41 -0
  55. package/src/nfe/services/NFeXsdService.js +56 -0
  56. package/src/nfe/types/NFeDocument.js +92 -0
  57. package/src/nfe/types/NFeEvento.js +44 -0
  58. package/src/nfse/NFSeMunicipioResolver.js +42 -0
  59. package/src/nfse/builders/NFSeEventoXmlBuilder.js +92 -0
  60. package/src/nfse/builders/NFSeXmlBuilder.js +16 -0
  61. package/src/nfse/constants/municipios.js +82 -0
  62. package/src/nfse/layouts/adapters.js +202 -0
  63. package/src/nfse/mocks/nfse-responses.js +142 -0
  64. package/src/nfse/services/NFSeService.js +191 -0
  65. package/src/nfse/services/NFSeSoapService.js +232 -0
  66. package/src/nfse/services/NFSeXmlService.js +12 -0
  67. package/src/nfse/types/NFSeDocument.js +54 -0
  68. package/src/queue/NFCeQueue.js +63 -0
  69. package/src/queue/NFSeQueue.js +59 -0
  70. package/src/queue/NFeQueue.js +76 -0
  71. package/src/reforma/ibscbs.js +175 -0
  72. package/src/sefaz/SefazEndpointResolver.js +95 -0
  73. package/src/sefaz/endpoints.registry.js +205 -0
  74. package/src/shared/fiscal.helpers.js +53 -0
  75. package/src/utils/certificate.utils.js +47 -0
  76. package/src/utils/chave.utils.js +117 -0
  77. package/src/utils/date.utils.js +26 -0
  78. package/src/utils/xml.utils.js +44 -0
  79. package/src/validation/XsdValidator.js +114 -0
  80. package/src/validation/schemas.js +14 -0
@@ -0,0 +1,421 @@
1
+ <!DOCTYPE html>
2
+ <html lang="pt-BR">
3
+ <head>
4
+ <meta charset="UTF-8" />
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0" />
6
+ <title>GHINFE — Guia do Desenvolvedor</title>
7
+ <style>
8
+ :root {
9
+ --bg: #0f1419;
10
+ --surface: #1a2332;
11
+ --border: #2d3a4f;
12
+ --text: #e7ecf3;
13
+ --muted: #8b9cb3;
14
+ --accent: #3b82f6;
15
+ --accent-hover: #2563eb;
16
+ --success: #22c55e;
17
+ --warning: #f59e0b;
18
+ --code-bg: #0d1117;
19
+ }
20
+ * { box-sizing: border-box; margin: 0; padding: 0; }
21
+ body {
22
+ font-family: 'Segoe UI', system-ui, -apple-system, sans-serif;
23
+ background: var(--bg);
24
+ color: var(--text);
25
+ line-height: 1.7;
26
+ }
27
+ .layout {
28
+ display: grid;
29
+ grid-template-columns: 260px 1fr;
30
+ min-height: 100vh;
31
+ }
32
+ nav {
33
+ background: var(--surface);
34
+ border-right: 1px solid var(--border);
35
+ padding: 1.5rem 1rem;
36
+ position: sticky;
37
+ top: 0;
38
+ height: 100vh;
39
+ overflow-y: auto;
40
+ }
41
+ nav h2 {
42
+ font-size: 1.1rem;
43
+ color: var(--accent);
44
+ margin-bottom: 1rem;
45
+ padding: 0 0.5rem;
46
+ }
47
+ nav a {
48
+ display: block;
49
+ color: var(--muted);
50
+ text-decoration: none;
51
+ padding: 0.4rem 0.75rem;
52
+ border-radius: 6px;
53
+ font-size: 0.9rem;
54
+ margin-bottom: 2px;
55
+ }
56
+ nav a:hover { background: var(--border); color: var(--text); }
57
+ main {
58
+ padding: 2.5rem 3rem;
59
+ max-width: 900px;
60
+ }
61
+ h1 { font-size: 2rem; margin-bottom: 0.5rem; }
62
+ .subtitle { color: var(--muted); margin-bottom: 2rem; font-size: 1.05rem; }
63
+ h2 { font-size: 1.5rem; margin: 2.5rem 0 1rem; border-bottom: 1px solid var(--border); padding-bottom: 0.5rem; }
64
+ h3 { font-size: 1.15rem; margin: 1.5rem 0 0.75rem; color: #c5d0e0; }
65
+ p { margin-bottom: 1rem; }
66
+ ul, ol { margin: 0 0 1rem 1.5rem; }
67
+ li { margin-bottom: 0.4rem; }
68
+ code {
69
+ background: var(--code-bg);
70
+ padding: 0.15rem 0.4rem;
71
+ border-radius: 4px;
72
+ font-size: 0.88em;
73
+ font-family: 'Cascadia Code', 'Fira Code', Consolas, monospace;
74
+ }
75
+ pre {
76
+ background: var(--code-bg);
77
+ border: 1px solid var(--border);
78
+ border-radius: 8px;
79
+ padding: 1rem 1.25rem;
80
+ overflow-x: auto;
81
+ margin: 1rem 0 1.5rem;
82
+ font-size: 0.85rem;
83
+ line-height: 1.5;
84
+ }
85
+ pre code { background: none; padding: 0; }
86
+ .badge {
87
+ display: inline-block;
88
+ background: var(--accent);
89
+ color: white;
90
+ font-size: 0.75rem;
91
+ padding: 0.2rem 0.6rem;
92
+ border-radius: 999px;
93
+ margin-left: 0.5rem;
94
+ vertical-align: middle;
95
+ }
96
+ .card {
97
+ background: var(--surface);
98
+ border: 1px solid var(--border);
99
+ border-radius: 10px;
100
+ padding: 1.25rem;
101
+ margin: 1rem 0;
102
+ }
103
+ .card h4 { margin-bottom: 0.5rem; color: var(--accent); }
104
+ table {
105
+ width: 100%;
106
+ border-collapse: collapse;
107
+ margin: 1rem 0;
108
+ font-size: 0.9rem;
109
+ }
110
+ th, td {
111
+ border: 1px solid var(--border);
112
+ padding: 0.6rem 0.8rem;
113
+ text-align: left;
114
+ }
115
+ th { background: var(--surface); color: var(--muted); }
116
+ .btn {
117
+ display: inline-block;
118
+ background: var(--accent);
119
+ color: white;
120
+ padding: 0.6rem 1.2rem;
121
+ border-radius: 8px;
122
+ text-decoration: none;
123
+ font-weight: 500;
124
+ margin: 0.5rem 0.5rem 0.5rem 0;
125
+ }
126
+ .btn:hover { background: var(--accent-hover); }
127
+ .flow {
128
+ display: flex;
129
+ flex-wrap: wrap;
130
+ gap: 0.5rem;
131
+ align-items: center;
132
+ margin: 1rem 0;
133
+ }
134
+ .flow span {
135
+ background: var(--surface);
136
+ border: 1px solid var(--border);
137
+ padding: 0.4rem 0.8rem;
138
+ border-radius: 6px;
139
+ font-size: 0.85rem;
140
+ }
141
+ .flow .arrow { color: var(--muted); border: none; background: none; }
142
+ @media (max-width: 768px) {
143
+ .layout { grid-template-columns: 1fr; }
144
+ nav { position: static; height: auto; }
145
+ main { padding: 1.5rem; }
146
+ }
147
+ </style>
148
+ </head>
149
+ <body>
150
+ <div class="layout">
151
+ <nav>
152
+ <h2>GHINFE</h2>
153
+ <a href="#intro">Introdução</a>
154
+ <a href="#instalacao">Instalação</a>
155
+ <a href="#arquitetura">Arquitetura</a>
156
+ <a href="#objeto">Objeto de domínio</a>
157
+ <a href="#xml">Geração XML</a>
158
+ <a href="#assinatura">Assinatura A1</a>
159
+ <a href="#sefaz">Envio SEFAZ</a>
160
+ <a href="#erros">Erros SEFAZ</a>
161
+ <a href="#fila">Fila Bull/Redis</a>
162
+ <a href="#sequelize">Integração Sequelize</a>
163
+ <a href="#swagger">Swagger / API</a>
164
+ <a href="#roadmap">Roadmap</a>
165
+ </nav>
166
+
167
+ <main>
168
+ <h1>GHINFE <span class="badge">v0.1</span></h1>
169
+ <p class="subtitle">Guia do desenvolvedor — biblioteca Node.js para NF-e, NFC-e e NFS-e</p>
170
+
171
+ <a class="btn" href="/docs" target="_blank">Abrir Swagger UI</a>
172
+ <a class="btn" href="https://github.com" style="background:#333">Repositório</a>
173
+
174
+ <section id="intro">
175
+ <h2>Introdução</h2>
176
+ <p>
177
+ A <strong>GHINFE</strong> é uma biblioteca modular para emissão de documentos fiscais eletrônicos no Brasil.
178
+ A API foi pensada de forma semelhante ao <strong>ACBr</strong>: você monta um objeto JavaScript com
179
+ cabeçalho, itens, frete e pagamentos; a biblioteca gera o XML oficial, assina com certificado A1,
180
+ envia via SOAP para a SEFAZ e trata o retorno.
181
+ </p>
182
+ <div class="flow">
183
+ <span>Objeto JS</span>
184
+ <span class="arrow">→</span>
185
+ <span>XML NF-e</span>
186
+ <span class="arrow">→</span>
187
+ <span>Assinatura A1</span>
188
+ <span class="arrow">→</span>
189
+ <span>SOAP SEFAZ</span>
190
+ <span class="arrow">→</span>
191
+ <span>Protocolo / Rejeição</span>
192
+ </div>
193
+ </section>
194
+
195
+ <section id="instalacao">
196
+ <h2>Instalação</h2>
197
+ <pre><code>git clone &lt;repo&gt; ghinfe
198
+ cd ghinfe
199
+ npm install
200
+ cp .env.example .env
201
+ npm start # API + Swagger em http://localhost:3333
202
+ npm run example:nfe</code></pre>
203
+ <p><strong>Requisitos:</strong> Node.js 18+, Redis (para filas Bull).</p>
204
+ </section>
205
+
206
+ <section id="arquitetura">
207
+ <h2>Arquitetura</h2>
208
+ <p>Cada responsabilidade está em um serviço isolado:</p>
209
+ <table>
210
+ <thead>
211
+ <tr><th>Serviço</th><th>Responsabilidade</th></tr>
212
+ </thead>
213
+ <tbody>
214
+ <tr><td><code>NFeXmlService</code></td><td>Gera XML layout SEFAZ 4.00</td></tr>
215
+ <tr><td><code>NFeSignService</code></td><td>Assina <code>infNFe</code> com certificado .pfx</td></tr>
216
+ <tr><td><code>NFeSoapService</code></td><td>Envia SOAP e interpreta cStat</td></tr>
217
+ <tr><td><code>NFeService</code></td><td>Orquestra o fluxo completo</td></tr>
218
+ <tr><td><code>NFeQueue</code></td><td>Processamento assíncrono via Bull/Redis</td></tr>
219
+ </tbody>
220
+ </table>
221
+ </section>
222
+
223
+ <section id="objeto">
224
+ <h2>Objeto de domínio</h2>
225
+ <p>Monte o documento fiscal como um objeto JavaScript plano:</p>
226
+ <pre><code>const documento = {
227
+ chaveAcesso: '35260712345678000199550010000000011000000011', // 44 dígitos
228
+ ambiente: 2, // 1=Produção, 2=Homologação
229
+ ide: {
230
+ uf: 'SP',
231
+ codigoMunicipio: '3550308',
232
+ serie: 1,
233
+ numero: 1,
234
+ naturezaOperacao: 'VENDA',
235
+ },
236
+ emitente: {
237
+ cnpj: '12345678000199',
238
+ razaoSocial: 'EMPRESA EXEMPLO LTDA',
239
+ ie: '123456789012',
240
+ crt: '3',
241
+ endereco: { /* logradouro, numero, bairro, codigoMunicipio, municipio, uf, cep */ },
242
+ },
243
+ destinatario: { /* cpf ou cnpj, razaoSocial, endereco */ },
244
+ itens: [{
245
+ codigo: '001',
246
+ descricao: 'PRODUTO TESTE',
247
+ ncm: '84713012',
248
+ cfop: '5102',
249
+ unidade: 'UN',
250
+ quantidade: 1,
251
+ valorUnitario: 100,
252
+ valorTotal: 100,
253
+ cst: '00',
254
+ aliquotaIcms: 18,
255
+ valorIcms: 18,
256
+ }],
257
+ frete: { modalidade: 9 }, // 9 = sem frete
258
+ pagamentos: [{ forma: '01', valor: 100 }],
259
+ };</code></pre>
260
+ </section>
261
+
262
+ <section id="xml">
263
+ <h2>Geração de XML</h2>
264
+ <pre><code>import { NFeService } from 'ghinfe';
265
+
266
+ const service = new NFeService();
267
+ const xml = service.gerarXml(documento);
268
+ // Retorna string XML UTF-8, layout NF-e 4.00, sem assinatura</code></pre>
269
+ <div class="card">
270
+ <h4>NFeXmlBuilder</h4>
271
+ <p>O builder monta as seções <code>ide</code>, <code>emit</code>, <code>dest</code>, <code>det</code>, <code>total</code>, <code>transp</code> e <code>pag</code> conforme o Manual de Orientação do Contribuinte.</p>
272
+ </div>
273
+ </section>
274
+
275
+ <section id="assinatura">
276
+ <h2>Assinatura digital (A1)</h2>
277
+ <pre><code>import fs from 'fs';
278
+ import { NFeService } from 'ghinfe';
279
+
280
+ const service = new NFeService();
281
+ const pfx = fs.readFileSync('./certs/certificado.pfx');
282
+
283
+ const xmlAssinado = service.gerarXmlAssinado(documento, {
284
+ pfx,
285
+ senha: 'senha-do-certificado',
286
+ });</code></pre>
287
+ <p>A assinatura usa XML-DSig enveloped no elemento <code>infNFe</code>, com RSA-SHA1 e C14N, conforme exigido pela SEFAZ.</p>
288
+ <pre><code>import { NFeSignService } from 'ghinfe';
289
+
290
+ const signService = new NFeSignService();
291
+ const info = signService.obterInfoCertificado(pfx, senha);
292
+ console.log(info.validoAte); // data de expiração</code></pre>
293
+ </section>
294
+
295
+ <section id="sefaz">
296
+ <h2>Envio SEFAZ</h2>
297
+ <h3>Modo mock (desenvolvimento)</h3>
298
+ <pre><code>const service = new NFeService({ mock: true, mockScenario: 'autorizado' });
299
+ const resultado = await service.emitir(documento, { pfx, senha });
300
+
301
+ console.log(resultado.retorno.cStat); // '100'
302
+ console.log(resultado.retorno.protocolo); // número do protocolo</code></pre>
303
+ <p>Cenários mock disponíveis: <code>autorizado</code>, <code>duplicidade</code>, <code>schema</code>, <code>certificado</code>, <code>paralisado</code>.</p>
304
+
305
+ <h3>Modo produção/homologação real</h3>
306
+ <pre><code>import { SEFAZ_URLS } from 'ghinfe';
307
+
308
+ const service = new NFeService({
309
+ mock: false,
310
+ sefazEndpoint: SEFAZ_URLS.SP.autorizacao,
311
+ });</code></pre>
312
+ </section>
313
+
314
+ <section id="erros">
315
+ <h2>Tratamento de erros SEFAZ</h2>
316
+ <pre><code>import { SefazError } from 'ghinfe';
317
+
318
+ try {
319
+ await service.enviar(xmlAssinado, chaveAcesso);
320
+ } catch (error) {
321
+ if (error instanceof SefazError) {
322
+ console.log(error.cStat); // ex: '204'
323
+ console.log(error.xMotivo); // ex: 'Rejeição: Duplicidade de NF-e'
324
+ console.log(error.isRejeicao()); // true para cStat 2xx
325
+ }
326
+ }</code></pre>
327
+ <table>
328
+ <thead><tr><th>cStat</th><th>Significado</th></tr></thead>
329
+ <tbody>
330
+ <tr><td>100</td><td>Autorizado o uso da NF-e</td></tr>
331
+ <tr><td>103</td><td>Lote recebido com sucesso</td></tr>
332
+ <tr><td>104</td><td>Lote processado</td></tr>
333
+ <tr><td>204</td><td>Rejeição: Duplicidade de NF-e</td></tr>
334
+ <tr><td>225</td><td>Rejeição: Falha no Schema XML</td></tr>
335
+ <tr><td>280</td><td>Rejeição: Certificado Transmissor inválido</td></tr>
336
+ </tbody>
337
+ </table>
338
+ </section>
339
+
340
+ <section id="fila">
341
+ <h2>Fila Bull / Redis</h2>
342
+ <pre><code>import { NFeQueue } from 'ghinfe';
343
+
344
+ const queue = new NFeQueue({
345
+ redis: { host: '127.0.0.1', port: 6379 },
346
+ nfeOptions: { mock: true },
347
+ });
348
+
349
+ await queue.adicionarEmissao({
350
+ documento,
351
+ certificado: { pfx, senha },
352
+ });
353
+
354
+ queue.onCompleted(async (resultado) => {
355
+ // persistir no banco
356
+ });
357
+
358
+ queue.onFailed(async (error, job) => {
359
+ console.error('Job falhou:', job.id, error.message);
360
+ });</code></pre>
361
+ <p>A fila usa retry exponencial (3 tentativas) e é ideal para picos de emissão sem bloquear a API do seu sistema.</p>
362
+ </section>
363
+
364
+ <section id="sequelize">
365
+ <h2>Integração com Sequelize</h2>
366
+ <pre><code>// models/nota-fiscal.js
367
+ export default (sequelize, DataTypes) => {
368
+ return sequelize.define('NotaFiscal', {
369
+ chave_acesso: { type: DataTypes.STRING(44), unique: true },
370
+ xml_nfe: DataTypes.TEXT,
371
+ xml_protocolo: DataTypes.TEXT,
372
+ protocolo: DataTypes.STRING(20),
373
+ status: DataTypes.STRING(3),
374
+ motivo: DataTypes.STRING(255),
375
+ autorizado_em: DataTypes.DATE,
376
+ });
377
+ };
378
+
379
+ // worker
380
+ queue.onCompleted(async (resultado) => {
381
+ const payload = service.toPersistencePayload(resultado);
382
+ await NotaFiscal.create(payload);
383
+ });</code></pre>
384
+ </section>
385
+
386
+ <section id="swagger">
387
+ <h2>Swagger / API de testes</h2>
388
+ <p>Com <code>npm start</code>, acesse:</p>
389
+ <ul>
390
+ <li><a href="/docs">/docs</a> — Swagger UI interativo</li>
391
+ <li><code>POST /nfe/xml</code> — gera XML a partir de JSON</li>
392
+ <li><code>POST /nfe/enviar-mock?cenario=duplicidade</code> — simula envio</li>
393
+ <li><code>GET /nfe/cenarios-mock</code> — lista cenários</li>
394
+ </ul>
395
+ <div class="card">
396
+ <h4>Teste rápido via curl</h4>
397
+ <pre><code>curl -X POST http://localhost:3333/nfe/xml \
398
+ -H "Content-Type: application/json" \
399
+ -d @examples/payload-nfe.json</code></pre>
400
+ </div>
401
+ </section>
402
+
403
+ <section id="roadmap">
404
+ <h2>Roadmap</h2>
405
+ <ul>
406
+ <li>Geração automática de chave de acesso (DV)</li>
407
+ <li>Cancelamento de NF-e (evento 110111)</li>
408
+ <li>Carta de Correção Eletrônica (CC-e)</li>
409
+ <li>NFC-e (modelo 65) e NFS-e</li>
410
+ <li>Validação XSD antes do envio</li>
411
+ <li>Endpoints SEFAZ por UF automáticos</li>
412
+ </ul>
413
+ </section>
414
+
415
+ <p style="margin-top:3rem;color:var(--muted);font-size:0.85rem;">
416
+ GHINFE — Biblioteca fiscal open source. Documentação gerada para v0.1.0.
417
+ </p>
418
+ </main>
419
+ </div>
420
+ </body>
421
+ </html>