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/src/api/server.js CHANGED
@@ -1,316 +1,316 @@
1
- import 'dotenv/config';
2
- import express from 'express';
3
- import swaggerUi from 'swagger-ui-express';
4
- import YAML from 'yamljs';
5
- import path from 'path';
6
- import { fileURLToPath } from 'url';
7
- import { NFeService } from '../nfe/services/NFeService.js';
8
- import { NFCeService } from '../nfce/services/NFCeService.js';
9
- import { NFSeService } from '../nfse/services/NFSeService.js';
10
- import { SefazEndpointResolver } from '../sefaz/SefazEndpointResolver.js';
11
- import { NFSeMunicipioResolver } from '../nfse/NFSeMunicipioResolver.js';
12
- import { SefazError } from '../errors/SefazError.js';
13
- import { GhinfeError } from '../errors/GhinfeError.js';
14
- import { XsdValidationError } from '../errors/XsdValidationError.js';
15
- import { NFSeSoapError } from '../nfse/services/NFSeSoapService.js';
16
- import { NFSE_LAYOUTS } from '../nfse/constants/municipios.js';
17
- import { DanfeService } from '../danfe/DanfeService.js';
18
- import { calcularIBSCBS, buildFragmentoIBSCBS } from '../reforma/ibscbs.js';
19
- import { SEFAZ_MOCK_RESPONSES } from '../nfe/mocks/sefaz-responses.js';
20
-
21
- const __dirname = path.dirname(fileURLToPath(import.meta.url));
22
- const openapiPath = path.join(__dirname, '../../docs/openapi.yaml');
23
- const swaggerDocument = YAML.load(openapiPath);
24
-
25
- const app = express();
26
- const port = process.env.PORT ?? 3333;
27
-
28
- app.use(express.json({ limit: '2mb' }));
29
- app.use('/docs', swaggerUi.serve, swaggerUi.setup(swaggerDocument, {
30
- customSiteTitle: 'GHINFE API — Swagger',
31
- }));
32
- app.use('/guia', express.static(path.join(__dirname, '../../docs')));
33
-
34
- app.get('/health', (_req, res) => {
35
- res.json({ status: 'ok', library: 'ghinfe', version: '0.6.0' });
36
- });
37
-
38
- app.post('/nfe/xml', (req, res) => {
39
- try {
40
- const validar = req.query.validarXsd === 'true';
41
- const service = new NFeService({ validarXsd: validar });
42
- const doc = service.resolverDocumento(req.body);
43
- const xml = service.gerarXml(doc, { validarXsd: validar });
44
- res.json({ chaveAcesso: doc.chaveAcesso, xml, xsdValidado: validar });
45
- } catch (error) {
46
- handleError(res, error);
47
- }
48
- });
49
-
50
- app.post('/nfe/validar-xsd', (req, res) => {
51
- try {
52
- const service = new NFeService();
53
- const resultado = service.validarXsd(req.body.xml, { preAssinatura: !req.body.xml.includes('</Signature>') });
54
- res.json(resultado);
55
- } catch (error) {
56
- handleError(res, error);
57
- }
58
- });
59
-
60
- app.post('/nfce/xml', (req, res) => {
61
- try {
62
- const service = new NFCeService();
63
- const doc = service.resolverDocumento(req.body);
64
- const xml = service.gerarXml(doc);
65
- res.json({ chaveAcesso: doc.chaveAcesso, xml, observacao: 'QR Code v3 é adicionado após assinatura' });
66
- } catch (error) {
67
- handleError(res, error);
68
- }
69
- });
70
-
71
- app.post('/nfce/enviar-mock', async (req, res) => {
72
- try {
73
- const service = new NFCeService({ mock: true });
74
- const doc = service.resolverDocumento(req.body);
75
- const xml = service.gerarXml(doc);
76
- const retorno = await service.enviar(xml, doc.chaveAcesso);
77
- res.json({ chaveAcesso: doc.chaveAcesso, xmlGerado: xml, retorno, mock: true });
78
- } catch (error) {
79
- handleError(res, error);
80
- }
81
- });
82
-
83
- app.post('/nfe/chave', (req, res) => {
84
- try {
85
- const service = new NFeService();
86
- const chaveAcesso = service.gerarChaveAcesso(req.body);
87
- res.json({ chaveAcesso, valida: true });
88
- } catch (error) {
89
- handleError(res, error);
90
- }
91
- });
92
-
93
- app.post('/nfe/cancelar-mock', async (req, res) => {
94
- try {
95
- const service = new NFeService({ mock: true, mockEventoScenario: 'registrado' });
96
- const xml = service.gerarXmlCancelamento(req.body);
97
- const retorno = await service.enviarEventoMock(
98
- xml,
99
- req.body.chaveAcesso,
100
- '110111'
101
- );
102
- res.json({ xmlGerado: xml, retorno, mock: true });
103
- } catch (error) {
104
- handleError(res, error);
105
- }
106
- });
107
-
108
- app.post('/nfe/cce-mock', async (req, res) => {
109
- try {
110
- const service = new NFeService({ mock: true, mockEventoScenario: 'registrado' });
111
- const xml = service.gerarXmlCartaCorrecao(req.body);
112
- const retorno = await service.enviarEventoMock(
113
- xml,
114
- req.body.chaveAcesso,
115
- '110110'
116
- );
117
- res.json({ xmlGerado: xml, retorno, mock: true });
118
- } catch (error) {
119
- handleError(res, error);
120
- }
121
- });
122
-
123
- app.post('/nfe/enviar-mock', async (req, res) => {
124
- try {
125
- const cenario = req.query.cenario ?? 'autorizado';
126
- const service = new NFeService({ mock: true, mockScenario: cenario });
127
-
128
- const doc = service.resolverDocumento(req.body);
129
- const xml = service.gerarXml(doc);
130
- const retorno = await service.enviar(xml, doc.chaveAcesso);
131
-
132
- res.json({
133
- chaveAcesso: doc.chaveAcesso,
134
- xmlGerado: xml,
135
- retorno,
136
- mock: true,
137
- });
138
- } catch (error) {
139
- handleError(res, error);
140
- }
141
- });
142
-
143
- app.get('/sefaz/endpoints/:uf', (req, res) => {
144
- try {
145
- const ambiente = parseInt(req.query.ambiente ?? '2', 10);
146
- const info = SefazEndpointResolver.obterEndpoints(req.params.uf, ambiente);
147
- res.json(info);
148
- } catch (error) {
149
- handleError(res, error);
150
- }
151
- });
152
-
153
- app.get('/sefaz/ufs', (_req, res) => {
154
- res.json({ ufs: SefazEndpointResolver.listarUFs() });
155
- });
156
-
157
- app.get('/sefaz/status/:uf', async (req, res) => {
158
- try {
159
- const ambiente = parseInt(req.query.ambiente ?? '2', 10);
160
- const service = new NFeService({ mock: true, uf: req.params.uf, ambiente });
161
- if (req.query.cenario) service.soapService.setMockStatusScenario(req.query.cenario);
162
- const status = await service.consultarStatus(req.params.uf, ambiente);
163
- res.json(status);
164
- } catch (error) {
165
- handleError(res, error);
166
- }
167
- });
168
-
169
- app.get('/nfse/layouts', (_req, res) => {
170
- res.json({ layouts: Object.values(NFSE_LAYOUTS) });
171
- });
172
-
173
- app.post('/nfse/xml', (req, res) => {
174
- try {
175
- const layout = req.query.layout ?? req.body.layout;
176
- const service = new NFSeService({ layout });
177
- const xml = service.gerarXml(req.body, layout);
178
- res.json({ layout: layout ?? service.resolverLayout(req.body), xml });
179
- } catch (error) {
180
- handleError(res, error);
181
- }
182
- });
183
-
184
- app.post('/reforma/calcular', (req, res) => {
185
- try {
186
- const { baseCalculo, aliquotas } = req.body;
187
- const reforma = calcularIBSCBS(baseCalculo, aliquotas ?? {});
188
- res.json({ reforma, fragmentoXml: buildFragmentoIBSCBS(reforma) });
189
- } catch (error) {
190
- handleError(res, error);
191
- }
192
- });
193
-
194
- app.post('/nfse/enviar-mock', async (req, res) => {
195
- try {
196
- const layout = req.query.layout ?? req.body.layout;
197
- const service = new NFSeService({ mock: true, layout });
198
- const resultado = await service.emitir(req.body);
199
- res.json({ ...resultado, mock: true });
200
- } catch (error) {
201
- handleError(res, error);
202
- }
203
- });
204
-
205
- app.post('/nfse/cancelar-mock', async (req, res) => {
206
- try {
207
- const service = new NFSeService({ mock: true });
208
- const resultado = await service.cancelar(req.body);
209
- res.json({ ...resultado, mock: true });
210
- } catch (error) {
211
- handleError(res, error);
212
- }
213
- });
214
-
215
- app.post('/nfse/substituir-mock', async (req, res) => {
216
- try {
217
- const service = new NFSeService({ mock: true });
218
- const resultado = await service.substituir(req.body);
219
- res.json({ ...resultado, mock: true });
220
- } catch (error) {
221
- handleError(res, error);
222
- }
223
- });
224
-
225
- app.post('/danfe', async (req, res) => {
226
- try {
227
- const service = new DanfeService();
228
- const resultado = await service.gerar(req.body.xml, {
229
- protocolo: req.body.protocolo,
230
- qrCodeUrl: req.body.qrCodeUrl,
231
- });
232
- if (req.query.format === 'html') {
233
- res.type('html').send(resultado.html);
234
- return;
235
- }
236
- res.json(resultado);
237
- } catch (error) {
238
- handleError(res, error);
239
- }
240
- });
241
-
242
- app.get('/nfse/municipios', (_req, res) => {
243
- const municipios = NFSeMunicipioResolver.listarMunicipios()
244
- .filter((c) => c !== 'mock')
245
- .map((codigo) => {
246
- const info = NFSeMunicipioResolver.resolver(codigo, 2);
247
- return { codigo, ...info };
248
- });
249
- res.json({ municipios, layouts: Object.values(NFSE_LAYOUTS) });
250
- });
251
-
252
- app.get('/nfe/cenarios-mock', (_req, res) => {
253
- res.json({
254
- cenarios: [
255
- { id: 'autorizado', descricao: 'NF-e autorizada (cStat 100)', cStat: '100' },
256
- { id: 'duplicidade', descricao: 'Rejeição por duplicidade (cStat 204)', cStat: '204' },
257
- { id: 'schema', descricao: 'Rejeição por schema inválido (cStat 225)', cStat: '225' },
258
- { id: 'certificado', descricao: 'Certificado inválido (cStat 280)', cStat: '280' },
259
- { id: 'paralisado', descricao: 'Serviço paralisado (cStat 108)', cStat: '108' },
260
- ],
261
- responses: SEFAZ_MOCK_RESPONSES,
262
- });
263
- });
264
-
265
- /**
266
- * @param {import('express').Response} res
267
- * @param {unknown} error
268
- */
269
- function handleError(res, error) {
270
- if (error instanceof SefazError) {
271
- return res.status(422).json({
272
- error: error.name,
273
- code: error.code,
274
- cStat: error.cStat,
275
- xMotivo: error.xMotivo,
276
- details: error.details,
277
- });
278
- }
279
-
280
- if (error instanceof NFSeSoapError) {
281
- return res.status(422).json({
282
- error: error.name,
283
- code: error.code,
284
- codigo: error.codigo,
285
- message: error.message,
286
- });
287
- }
288
-
289
- if (error instanceof XsdValidationError) {
290
- return res.status(422).json({
291
- error: error.name,
292
- code: error.code,
293
- message: error.message,
294
- erros: error.erros,
295
- schema: error.schema,
296
- });
297
- }
298
-
299
- if (error instanceof GhinfeError) {
300
- return res.status(400).json({
301
- error: error.name,
302
- code: error.code,
303
- message: error.message,
304
- details: error.details,
305
- });
306
- }
307
-
308
- console.error(error);
309
- res.status(500).json({ error: 'InternalServerError', message: 'Erro interno' });
310
- }
311
-
312
- app.listen(port, () => {
313
- console.log(`GHINFE API rodando em http://localhost:${port}`);
314
- console.log(`Swagger UI: http://localhost:${port}/docs`);
315
- console.log(`Guia do desenvolvedor: http://localhost:${port}/guia/developer-guide.html`);
316
- });
1
+ import 'dotenv/config';
2
+ import express from 'express';
3
+ import swaggerUi from 'swagger-ui-express';
4
+ import YAML from 'yamljs';
5
+ import path from 'path';
6
+ import { fileURLToPath } from 'url';
7
+ import { NFeService } from '../nfe/services/NFeService.js';
8
+ import { NFCeService } from '../nfce/services/NFCeService.js';
9
+ import { NFSeService } from '../nfse/services/NFSeService.js';
10
+ import { SefazEndpointResolver } from '../sefaz/SefazEndpointResolver.js';
11
+ import { NFSeMunicipioResolver } from '../nfse/NFSeMunicipioResolver.js';
12
+ import { SefazError } from '../errors/SefazError.js';
13
+ import { GhinfeError } from '../errors/GhinfeError.js';
14
+ import { XsdValidationError } from '../errors/XsdValidationError.js';
15
+ import { NFSeSoapError } from '../nfse/services/NFSeSoapService.js';
16
+ import { NFSE_LAYOUTS } from '../nfse/constants/municipios.js';
17
+ import { DanfeService } from '../danfe/DanfeService.js';
18
+ import { calcularIBSCBS, buildFragmentoIBSCBS } from '../reforma/ibscbs.js';
19
+ import { SEFAZ_MOCK_RESPONSES } from '../nfe/mocks/sefaz-responses.js';
20
+
21
+ const __dirname = path.dirname(fileURLToPath(import.meta.url));
22
+ const openapiPath = path.join(__dirname, '../../docs/openapi.yaml');
23
+ const swaggerDocument = YAML.load(openapiPath);
24
+
25
+ const app = express();
26
+ const port = process.env.PORT ?? 3333;
27
+
28
+ app.use(express.json({ limit: '2mb' }));
29
+ app.use('/docs', swaggerUi.serve, swaggerUi.setup(swaggerDocument, {
30
+ customSiteTitle: 'GHINFE API — Swagger',
31
+ }));
32
+ app.use('/guia', express.static(path.join(__dirname, '../../docs')));
33
+
34
+ app.get('/health', (_req, res) => {
35
+ res.json({ status: 'ok', library: 'ghinfe', version: '0.6.0' });
36
+ });
37
+
38
+ app.post('/nfe/xml', (req, res) => {
39
+ try {
40
+ const validar = req.query.validarXsd === 'true';
41
+ const service = new NFeService({ validarXsd: validar });
42
+ const doc = service.resolverDocumento(req.body);
43
+ const xml = service.gerarXml(doc, { validarXsd: validar });
44
+ res.json({ chaveAcesso: doc.chaveAcesso, xml, xsdValidado: validar });
45
+ } catch (error) {
46
+ handleError(res, error);
47
+ }
48
+ });
49
+
50
+ app.post('/nfe/validar-xsd', (req, res) => {
51
+ try {
52
+ const service = new NFeService();
53
+ const resultado = service.validarXsd(req.body.xml, { preAssinatura: !req.body.xml.includes('</Signature>') });
54
+ res.json(resultado);
55
+ } catch (error) {
56
+ handleError(res, error);
57
+ }
58
+ });
59
+
60
+ app.post('/nfce/xml', (req, res) => {
61
+ try {
62
+ const service = new NFCeService();
63
+ const doc = service.resolverDocumento(req.body);
64
+ const xml = service.gerarXml(doc);
65
+ res.json({ chaveAcesso: doc.chaveAcesso, xml, observacao: 'QR Code v3 é adicionado após assinatura' });
66
+ } catch (error) {
67
+ handleError(res, error);
68
+ }
69
+ });
70
+
71
+ app.post('/nfce/enviar-mock', async (req, res) => {
72
+ try {
73
+ const service = new NFCeService({ mock: true });
74
+ const doc = service.resolverDocumento(req.body);
75
+ const xml = service.gerarXml(doc);
76
+ const retorno = await service.enviar(xml, doc.chaveAcesso);
77
+ res.json({ chaveAcesso: doc.chaveAcesso, xmlGerado: xml, retorno, mock: true });
78
+ } catch (error) {
79
+ handleError(res, error);
80
+ }
81
+ });
82
+
83
+ app.post('/nfe/chave', (req, res) => {
84
+ try {
85
+ const service = new NFeService();
86
+ const chaveAcesso = service.gerarChaveAcesso(req.body);
87
+ res.json({ chaveAcesso, valida: true });
88
+ } catch (error) {
89
+ handleError(res, error);
90
+ }
91
+ });
92
+
93
+ app.post('/nfe/cancelar-mock', async (req, res) => {
94
+ try {
95
+ const service = new NFeService({ mock: true, mockEventoScenario: 'registrado' });
96
+ const xml = service.gerarXmlCancelamento(req.body);
97
+ const retorno = await service.enviarEventoMock(
98
+ xml,
99
+ req.body.chaveAcesso,
100
+ '110111'
101
+ );
102
+ res.json({ xmlGerado: xml, retorno, mock: true });
103
+ } catch (error) {
104
+ handleError(res, error);
105
+ }
106
+ });
107
+
108
+ app.post('/nfe/cce-mock', async (req, res) => {
109
+ try {
110
+ const service = new NFeService({ mock: true, mockEventoScenario: 'registrado' });
111
+ const xml = service.gerarXmlCartaCorrecao(req.body);
112
+ const retorno = await service.enviarEventoMock(
113
+ xml,
114
+ req.body.chaveAcesso,
115
+ '110110'
116
+ );
117
+ res.json({ xmlGerado: xml, retorno, mock: true });
118
+ } catch (error) {
119
+ handleError(res, error);
120
+ }
121
+ });
122
+
123
+ app.post('/nfe/enviar-mock', async (req, res) => {
124
+ try {
125
+ const cenario = req.query.cenario ?? 'autorizado';
126
+ const service = new NFeService({ mock: true, mockScenario: cenario });
127
+
128
+ const doc = service.resolverDocumento(req.body);
129
+ const xml = service.gerarXml(doc);
130
+ const retorno = await service.enviar(xml, doc.chaveAcesso);
131
+
132
+ res.json({
133
+ chaveAcesso: doc.chaveAcesso,
134
+ xmlGerado: xml,
135
+ retorno,
136
+ mock: true,
137
+ });
138
+ } catch (error) {
139
+ handleError(res, error);
140
+ }
141
+ });
142
+
143
+ app.get('/sefaz/endpoints/:uf', (req, res) => {
144
+ try {
145
+ const ambiente = parseInt(req.query.ambiente ?? '2', 10);
146
+ const info = SefazEndpointResolver.obterEndpoints(req.params.uf, ambiente);
147
+ res.json(info);
148
+ } catch (error) {
149
+ handleError(res, error);
150
+ }
151
+ });
152
+
153
+ app.get('/sefaz/ufs', (_req, res) => {
154
+ res.json({ ufs: SefazEndpointResolver.listarUFs() });
155
+ });
156
+
157
+ app.get('/sefaz/status/:uf', async (req, res) => {
158
+ try {
159
+ const ambiente = parseInt(req.query.ambiente ?? '2', 10);
160
+ const service = new NFeService({ mock: true, uf: req.params.uf, ambiente });
161
+ if (req.query.cenario) service.soapService.setMockStatusScenario(req.query.cenario);
162
+ const status = await service.consultarStatus(req.params.uf, ambiente);
163
+ res.json(status);
164
+ } catch (error) {
165
+ handleError(res, error);
166
+ }
167
+ });
168
+
169
+ app.get('/nfse/layouts', (_req, res) => {
170
+ res.json({ layouts: Object.values(NFSE_LAYOUTS) });
171
+ });
172
+
173
+ app.post('/nfse/xml', (req, res) => {
174
+ try {
175
+ const layout = req.query.layout ?? req.body.layout;
176
+ const service = new NFSeService({ layout });
177
+ const xml = service.gerarXml(req.body, layout);
178
+ res.json({ layout: layout ?? service.resolverLayout(req.body), xml });
179
+ } catch (error) {
180
+ handleError(res, error);
181
+ }
182
+ });
183
+
184
+ app.post('/reforma/calcular', (req, res) => {
185
+ try {
186
+ const { baseCalculo, aliquotas } = req.body;
187
+ const reforma = calcularIBSCBS(baseCalculo, aliquotas ?? {});
188
+ res.json({ reforma, fragmentoXml: buildFragmentoIBSCBS(reforma) });
189
+ } catch (error) {
190
+ handleError(res, error);
191
+ }
192
+ });
193
+
194
+ app.post('/nfse/enviar-mock', async (req, res) => {
195
+ try {
196
+ const layout = req.query.layout ?? req.body.layout;
197
+ const service = new NFSeService({ mock: true, layout });
198
+ const resultado = await service.emitir(req.body);
199
+ res.json({ ...resultado, mock: true });
200
+ } catch (error) {
201
+ handleError(res, error);
202
+ }
203
+ });
204
+
205
+ app.post('/nfse/cancelar-mock', async (req, res) => {
206
+ try {
207
+ const service = new NFSeService({ mock: true });
208
+ const resultado = await service.cancelar(req.body);
209
+ res.json({ ...resultado, mock: true });
210
+ } catch (error) {
211
+ handleError(res, error);
212
+ }
213
+ });
214
+
215
+ app.post('/nfse/substituir-mock', async (req, res) => {
216
+ try {
217
+ const service = new NFSeService({ mock: true });
218
+ const resultado = await service.substituir(req.body);
219
+ res.json({ ...resultado, mock: true });
220
+ } catch (error) {
221
+ handleError(res, error);
222
+ }
223
+ });
224
+
225
+ app.post('/danfe', async (req, res) => {
226
+ try {
227
+ const service = new DanfeService();
228
+ const resultado = await service.gerar(req.body.xml, {
229
+ protocolo: req.body.protocolo,
230
+ qrCodeUrl: req.body.qrCodeUrl,
231
+ });
232
+ if (req.query.format === 'html') {
233
+ res.type('html').send(resultado.html);
234
+ return;
235
+ }
236
+ res.json(resultado);
237
+ } catch (error) {
238
+ handleError(res, error);
239
+ }
240
+ });
241
+
242
+ app.get('/nfse/municipios', (_req, res) => {
243
+ const municipios = NFSeMunicipioResolver.listarMunicipios()
244
+ .filter((c) => c !== 'mock')
245
+ .map((codigo) => {
246
+ const info = NFSeMunicipioResolver.resolver(codigo, 2);
247
+ return { codigo, ...info };
248
+ });
249
+ res.json({ municipios, layouts: Object.values(NFSE_LAYOUTS) });
250
+ });
251
+
252
+ app.get('/nfe/cenarios-mock', (_req, res) => {
253
+ res.json({
254
+ cenarios: [
255
+ { id: 'autorizado', descricao: 'NF-e autorizada (cStat 100)', cStat: '100' },
256
+ { id: 'duplicidade', descricao: 'Rejeição por duplicidade (cStat 204)', cStat: '204' },
257
+ { id: 'schema', descricao: 'Rejeição por schema inválido (cStat 225)', cStat: '225' },
258
+ { id: 'certificado', descricao: 'Certificado inválido (cStat 280)', cStat: '280' },
259
+ { id: 'paralisado', descricao: 'Serviço paralisado (cStat 108)', cStat: '108' },
260
+ ],
261
+ responses: SEFAZ_MOCK_RESPONSES,
262
+ });
263
+ });
264
+
265
+ /**
266
+ * @param {import('express').Response} res
267
+ * @param {unknown} error
268
+ */
269
+ function handleError(res, error) {
270
+ if (error instanceof SefazError) {
271
+ return res.status(422).json({
272
+ error: error.name,
273
+ code: error.code,
274
+ cStat: error.cStat,
275
+ xMotivo: error.xMotivo,
276
+ details: error.details,
277
+ });
278
+ }
279
+
280
+ if (error instanceof NFSeSoapError) {
281
+ return res.status(422).json({
282
+ error: error.name,
283
+ code: error.code,
284
+ codigo: error.codigo,
285
+ message: error.message,
286
+ });
287
+ }
288
+
289
+ if (error instanceof XsdValidationError) {
290
+ return res.status(422).json({
291
+ error: error.name,
292
+ code: error.code,
293
+ message: error.message,
294
+ erros: error.erros,
295
+ schema: error.schema,
296
+ });
297
+ }
298
+
299
+ if (error instanceof GhinfeError) {
300
+ return res.status(400).json({
301
+ error: error.name,
302
+ code: error.code,
303
+ message: error.message,
304
+ details: error.details,
305
+ });
306
+ }
307
+
308
+ console.error(error);
309
+ res.status(500).json({ error: 'InternalServerError', message: 'Erro interno' });
310
+ }
311
+
312
+ app.listen(port, () => {
313
+ console.log(`GHINFE API rodando em http://localhost:${port}`);
314
+ console.log(`Swagger UI: http://localhost:${port}/docs`);
315
+ console.log(`Guia do desenvolvedor: http://localhost:${port}/guia/developer-guide.html`);
316
+ });