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.
- package/.env.example +15 -0
- package/LICENSE +21 -0
- package/README.md +413 -0
- package/docs/developer-guide.html +421 -0
- package/docs/openapi.yaml +428 -0
- package/examples/emitir-nfce.js +51 -0
- package/examples/emitir-nfe.js +83 -0
- package/examples/emitir-nfse.js +46 -0
- package/examples/endpoints-sefaz.js +14 -0
- package/examples/eventos-nfe.js +49 -0
- package/examples/gerar-chave.js +25 -0
- package/examples/gerar-danfe.js +75 -0
- package/examples/layouts-nfse.js +51 -0
- package/examples/mock-sefaz.js +52 -0
- package/examples/nfse-eventos.js +57 -0
- package/examples/reforma-ibscbs.js +27 -0
- package/examples/reforma-nfe-xml.js +52 -0
- package/examples/status-sefaz.js +12 -0
- package/examples/validar-xsd.js +48 -0
- package/package.json +68 -0
- package/schemas/nfe/PL_009_V4/consReciNFe_v4.00.xsd +9 -0
- package/schemas/nfe/PL_009_V4/consSitNFe_v4.00.xsd +9 -0
- package/schemas/nfe/PL_009_V4/consStatServ_v4.00.xsd +9 -0
- package/schemas/nfe/PL_009_V4/enviNFe_v3.10.xsd +9 -0
- package/schemas/nfe/PL_009_V4/enviNFe_v4.00.xsd +9 -0
- package/schemas/nfe/PL_009_V4/inutNFe_v4.00.xsd +9 -0
- package/schemas/nfe/PL_009_V4/leiauteNFe_v4.00.xsd +7339 -0
- package/schemas/nfe/PL_009_V4/nfe_v4.00.xsd +9 -0
- package/schemas/nfe/PL_009_V4/tiposBasico_v4.00.xsd +598 -0
- package/schemas/nfe/PL_009_V4/xmldsig-core-schema_v1.01.xsd +98 -0
- package/scripts/download-schemas.js +39 -0
- package/scripts/test-xsd.js +34 -0
- package/src/api/server.js +316 -0
- package/src/constants/sefaz.js +69 -0
- package/src/danfe/DanfeService.js +205 -0
- package/src/danfe/xml-extract.js +69 -0
- package/src/errors/GhinfeError.js +13 -0
- package/src/errors/SefazError.js +25 -0
- package/src/errors/XsdValidationError.js +15 -0
- package/src/index.js +60 -0
- package/src/nfce/builders/NFCeXmlBuilder.js +178 -0
- package/src/nfce/constants/qrcode.js +26 -0
- package/src/nfce/services/NFCeService.js +188 -0
- package/src/nfce/services/NFCeXmlService.js +38 -0
- package/src/nfce/types/NFCeDocument.js +42 -0
- package/src/nfce/utils/qrcode.utils.js +92 -0
- package/src/nfe/builders/NFeEventoXmlBuilder.js +110 -0
- package/src/nfe/builders/NFeXmlBuilder.js +215 -0
- package/src/nfe/mocks/sefaz-responses.js +195 -0
- package/src/nfe/services/NFeEventoService.js +95 -0
- package/src/nfe/services/NFeService.js +196 -0
- package/src/nfe/services/NFeSignService.js +111 -0
- package/src/nfe/services/NFeSoapService.js +437 -0
- package/src/nfe/services/NFeXmlService.js +41 -0
- package/src/nfe/services/NFeXsdService.js +56 -0
- package/src/nfe/types/NFeDocument.js +92 -0
- package/src/nfe/types/NFeEvento.js +44 -0
- package/src/nfse/NFSeMunicipioResolver.js +42 -0
- package/src/nfse/builders/NFSeEventoXmlBuilder.js +92 -0
- package/src/nfse/builders/NFSeXmlBuilder.js +16 -0
- package/src/nfse/constants/municipios.js +82 -0
- package/src/nfse/layouts/adapters.js +202 -0
- package/src/nfse/mocks/nfse-responses.js +142 -0
- package/src/nfse/services/NFSeService.js +191 -0
- package/src/nfse/services/NFSeSoapService.js +232 -0
- package/src/nfse/services/NFSeXmlService.js +12 -0
- package/src/nfse/types/NFSeDocument.js +54 -0
- package/src/queue/NFCeQueue.js +63 -0
- package/src/queue/NFSeQueue.js +59 -0
- package/src/queue/NFeQueue.js +76 -0
- package/src/reforma/ibscbs.js +175 -0
- package/src/sefaz/SefazEndpointResolver.js +95 -0
- package/src/sefaz/endpoints.registry.js +205 -0
- package/src/shared/fiscal.helpers.js +53 -0
- package/src/utils/certificate.utils.js +47 -0
- package/src/utils/chave.utils.js +117 -0
- package/src/utils/date.utils.js +26 -0
- package/src/utils/xml.utils.js +44 -0
- package/src/validation/XsdValidator.js +114 -0
- 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 <repo> 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>
|