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,205 @@
1
+ import QRCode from 'qrcode';
2
+ import { extrairDadosDanfe, formatarChaveAcesso } from './xml-extract.js';
3
+ import { GhinfeError } from '../errors/GhinfeError.js';
4
+
5
+ /**
6
+ * Gera DANFE (NF-e) e DANFCE (NFC-e) em HTML imprimível.
7
+ * PDF: imprima o HTML ou use um conversor no sistema consumidor (puppeteer, etc.).
8
+ */
9
+ export class DanfeService {
10
+ /**
11
+ * @param {string} xmlNFe XML autorizado (nfeProc) ou NFe assinada
12
+ * @param {{ protocolo?: string, titulo?: string }} [opcoes]
13
+ * @returns {Promise<{ html: string, dados: object, tipo: 'DANFE' }>}
14
+ */
15
+ async gerarDanfe(xmlNFe, opcoes = {}) {
16
+ const dados = extrairDadosDanfe(xmlNFe);
17
+ if (!dados.chaveAcesso) {
18
+ throw new GhinfeError('Chave de acesso não encontrada no XML', 'DANFE_CHAVE_MISSING');
19
+ }
20
+ if (opcoes.protocolo) dados.protocolo = opcoes.protocolo;
21
+
22
+ const html = this.#renderDanfe(dados, opcoes.titulo ?? 'DANFE — Documento Auxiliar da NF-e');
23
+ return { html, dados, tipo: 'DANFE' };
24
+ }
25
+
26
+ /**
27
+ * @param {string} xmlNFCe
28
+ * @param {{ protocolo?: string, qrCodeUrl?: string, titulo?: string }} [opcoes]
29
+ * @returns {Promise<{ html: string, dados: object, tipo: 'DANFCE', qrCodeDataUrl?: string }>}
30
+ */
31
+ async gerarDanfce(xmlNFCe, opcoes = {}) {
32
+ const dados = extrairDadosDanfe(xmlNFCe);
33
+ if (!dados.chaveAcesso) {
34
+ throw new GhinfeError('Chave de acesso não encontrada no XML', 'DANFCE_CHAVE_MISSING');
35
+ }
36
+ if (opcoes.protocolo) dados.protocolo = opcoes.protocolo;
37
+ if (opcoes.qrCodeUrl) dados.qrCode = opcoes.qrCodeUrl;
38
+
39
+ let qrCodeDataUrl = '';
40
+ if (dados.qrCode) {
41
+ qrCodeDataUrl = await QRCode.toDataURL(dados.qrCode, { width: 180, margin: 1 });
42
+ }
43
+
44
+ const html = this.#renderDanfce(dados, qrCodeDataUrl, opcoes.titulo ?? 'NFC-e — Cupom Fiscal Eletrônico');
45
+ return { html, dados, tipo: 'DANFCE', qrCodeDataUrl };
46
+ }
47
+
48
+ /**
49
+ * Detecta modelo e gera o documento adequado.
50
+ * @param {string} xml
51
+ * @param {object} [opcoes]
52
+ */
53
+ async gerar(xml, opcoes = {}) {
54
+ const dados = extrairDadosDanfe(xml);
55
+ if (dados.modelo === '65') return this.gerarDanfce(xml, opcoes);
56
+ return this.gerarDanfe(xml, opcoes);
57
+ }
58
+
59
+ #esc(s) {
60
+ return String(s ?? '')
61
+ .replace(/&/g, '&amp;')
62
+ .replace(/</g, '&lt;')
63
+ .replace(/>/g, '&gt;')
64
+ .replace(/"/g, '&quot;');
65
+ }
66
+
67
+ #renderDanfe(d, titulo) {
68
+ const chaveFmt = formatarChaveAcesso(d.chaveAcesso);
69
+ const linhas = d.itens.map((i, idx) => `
70
+ <tr>
71
+ <td>${idx + 1}</td>
72
+ <td>${this.#esc(i.codigo)}</td>
73
+ <td>${this.#esc(i.descricao)}</td>
74
+ <td>${this.#esc(i.ncm)}</td>
75
+ <td>${this.#esc(i.cfop)}</td>
76
+ <td>${this.#esc(i.unidade)}</td>
77
+ <td class="r">${this.#esc(i.quantidade)}</td>
78
+ <td class="r">${this.#esc(i.valorUnitario)}</td>
79
+ <td class="r">${this.#esc(i.valorTotal)}</td>
80
+ </tr>`).join('');
81
+
82
+ return `<!DOCTYPE html>
83
+ <html lang="pt-BR">
84
+ <head>
85
+ <meta charset="UTF-8"/>
86
+ <title>${this.#esc(titulo)}</title>
87
+ <style>
88
+ * { box-sizing: border-box; }
89
+ body { font-family: Arial, Helvetica, sans-serif; font-size: 11px; color: #111; margin: 16px; }
90
+ h1 { font-size: 14px; margin: 0 0 8px; text-align: center; }
91
+ .box { border: 1px solid #333; padding: 8px; margin-bottom: 8px; }
92
+ .row { display: flex; gap: 8px; }
93
+ .col { flex: 1; }
94
+ .label { color: #555; font-size: 9px; text-transform: uppercase; }
95
+ .val { font-weight: 600; }
96
+ table { width: 100%; border-collapse: collapse; margin-top: 6px; }
97
+ th, td { border: 1px solid #999; padding: 4px; }
98
+ th { background: #eee; font-size: 9px; }
99
+ .r { text-align: right; }
100
+ .chave { letter-spacing: 1px; font-size: 12px; font-weight: bold; word-break: break-all; }
101
+ .homolog { color: #b00; font-weight: bold; text-align: center; }
102
+ @media print { body { margin: 0; } }
103
+ </style>
104
+ </head>
105
+ <body>
106
+ <h1>${this.#esc(titulo)}</h1>
107
+ ${d.ambiente === '2' ? '<p class="homolog">NF-e EMITIDA EM AMBIENTE DE HOMOLOGAÇÃO — SEM VALOR FISCAL</p>' : ''}
108
+ <div class="box row">
109
+ <div class="col">
110
+ <div class="label">Emitente</div>
111
+ <div class="val">${this.#esc(d.emitente.razaoSocial)}</div>
112
+ <div>CNPJ ${this.#esc(d.emitente.cnpj)} — IE ${this.#esc(d.emitente.ie)}</div>
113
+ <div>${this.#esc(d.emitente.municipio)} / ${this.#esc(d.emitente.uf)}</div>
114
+ </div>
115
+ <div class="col">
116
+ <div class="label">Documento</div>
117
+ <div>NF-e nº <strong>${this.#esc(d.numero)}</strong> — Série ${this.#esc(d.serie)}</div>
118
+ <div>Emissão: ${this.#esc(d.dhEmi)}</div>
119
+ <div>Nat. Op.: ${this.#esc(d.naturezaOperacao)}</div>
120
+ <div>Protocolo: ${this.#esc(d.protocolo || '—')}</div>
121
+ </div>
122
+ </div>
123
+ <div class="box">
124
+ <div class="label">Destinatário</div>
125
+ <div class="val">${this.#esc(d.destinatario.razaoSocial)}</div>
126
+ <div>CPF/CNPJ ${this.#esc(d.destinatario.doc)}</div>
127
+ </div>
128
+ <div class="box">
129
+ <div class="label">Chave de acesso</div>
130
+ <div class="chave">${this.#esc(chaveFmt)}</div>
131
+ </div>
132
+ <table>
133
+ <thead>
134
+ <tr>
135
+ <th>#</th><th>Cód.</th><th>Descrição</th><th>NCM</th><th>CFOP</th>
136
+ <th>Un</th><th>Qtd</th><th>V. Unit</th><th>V. Total</th>
137
+ </tr>
138
+ </thead>
139
+ <tbody>${linhas}</tbody>
140
+ </table>
141
+ <div class="box row" style="margin-top:8px">
142
+ <div class="col"><div class="label">Produtos</div><div class="val">R$ ${this.#esc(d.totais.vProd)}</div></div>
143
+ <div class="col"><div class="label">ICMS</div><div class="val">R$ ${this.#esc(d.totais.vICMS)}</div></div>
144
+ <div class="col"><div class="label">Frete</div><div class="val">R$ ${this.#esc(d.totais.vFrete)}</div></div>
145
+ <div class="col"><div class="label">Total NF</div><div class="val">R$ ${this.#esc(d.totais.vNF)}</div></div>
146
+ </div>
147
+ ${d.informacoesComplementares ? `<div class="box"><div class="label">Informações complementares</div>${this.#esc(d.informacoesComplementares)}</div>` : ''}
148
+ <p style="text-align:center;color:#666;margin-top:12px">Documento gerado por GHINFE — representação auxiliar</p>
149
+ </body>
150
+ </html>`;
151
+ }
152
+
153
+ #renderDanfce(d, qrDataUrl, titulo) {
154
+ const chaveFmt = formatarChaveAcesso(d.chaveAcesso);
155
+ const linhas = d.itens.map((i) => `
156
+ <tr>
157
+ <td>${this.#esc(i.descricao)}</td>
158
+ <td class="r">${this.#esc(i.quantidade)}</td>
159
+ <td class="r">${this.#esc(i.valorUnitario)}</td>
160
+ <td class="r">${this.#esc(i.valorTotal)}</td>
161
+ </tr>`).join('');
162
+
163
+ return `<!DOCTYPE html>
164
+ <html lang="pt-BR">
165
+ <head>
166
+ <meta charset="UTF-8"/>
167
+ <title>${this.#esc(titulo)}</title>
168
+ <style>
169
+ body { font-family: 'Courier New', monospace; font-size: 12px; width: 302px; margin: 8px auto; color: #000; }
170
+ h1 { font-size: 13px; text-align: center; margin: 0 0 6px; }
171
+ .c { text-align: center; }
172
+ .r { text-align: right; }
173
+ hr { border: none; border-top: 1px dashed #000; margin: 8px 0; }
174
+ table { width: 100%; border-collapse: collapse; }
175
+ td { padding: 2px 0; vertical-align: top; }
176
+ img.qr { display: block; margin: 8px auto; }
177
+ .homolog { color: #b00; font-weight: bold; text-align: center; font-size: 11px; }
178
+ @media print { body { margin: 0; width: 80mm; } }
179
+ </style>
180
+ </head>
181
+ <body>
182
+ <h1>${this.#esc(d.emitente.razaoSocial)}</h1>
183
+ <div class="c">CNPJ ${this.#esc(d.emitente.cnpj)} IE ${this.#esc(d.emitente.ie)}</div>
184
+ <div class="c">${this.#esc(d.emitente.municipio)}/${this.#esc(d.emitente.uf)}</div>
185
+ <hr/>
186
+ <div class="c"><strong>${this.#esc(titulo)}</strong></div>
187
+ ${d.ambiente === '2' ? '<p class="homolog">EMITIDA EM HOMOLOGAÇÃO — SEM VALOR FISCAL</p>' : ''}
188
+ <div class="c">NFC-e nº ${this.#esc(d.numero)} Série ${this.#esc(d.serie)}</div>
189
+ <div class="c">${this.#esc(d.dhEmi)}</div>
190
+ <hr/>
191
+ <table>
192
+ <thead><tr><td>Item</td><td class="r">Qtd</td><td class="r">Vl Un</td><td class="r">Total</td></tr></thead>
193
+ <tbody>${linhas}</tbody>
194
+ </table>
195
+ <hr/>
196
+ <div class="c"><strong>TOTAL R$ ${this.#esc(d.totais.vNF)}</strong></div>
197
+ <div class="c">Protocolo: ${this.#esc(d.protocolo || '—')}</div>
198
+ <hr/>
199
+ <div class="c" style="font-size:10px;word-break:break-all">${this.#esc(chaveFmt)}</div>
200
+ ${qrDataUrl ? `<img class="qr" src="${qrDataUrl}" alt="QR Code NFC-e" width="160" height="160"/>` : '<p class="c">(QR Code indisponível — assine e adicione infNFeSupl)</p>'}
201
+ <p class="c" style="font-size:10px">Consulte pela chave de acesso no portal da SEFAZ</p>
202
+ </body>
203
+ </html>`;
204
+ }
205
+ }
@@ -0,0 +1,69 @@
1
+ /**
2
+ * Extrai campos úteis de XML NF-e/NFC-e para montagem do DANFE/DANFCE.
3
+ * @param {string} xml
4
+ */
5
+ export function extrairDadosDanfe(xml) {
6
+ const get = (tag) => xml.match(new RegExp(`<${tag}[^>]*>([^<]*)</${tag}>`))?.[1]?.trim() ?? '';
7
+ const getAll = (tag) => [...xml.matchAll(new RegExp(`<${tag}[^>]*>([^<]*)</${tag}>`, 'g'))].map((m) => m[1]);
8
+
9
+ const chave = get('chNFe') || (xml.match(/Id="NFe(\d{44})"/)?.[1] ?? '');
10
+ const protocolo = get('nProt');
11
+ const qrCode = xml.match(/<!\[CDATA\[(https?:\/\/[^\]]+)\]\]>/)?.[1]
12
+ || xml.match(/<qrCode[^>]*>([^<]+)<\/qrCode>/)?.[1]
13
+ || '';
14
+
15
+ const itens = [];
16
+ const detBlocks = xml.match(/<det[\s\S]*?<\/det>/g) ?? [];
17
+ for (const block of detBlocks) {
18
+ const g = (t) => block.match(new RegExp(`<${t}[^>]*>([^<]*)</${t}>`))?.[1] ?? '';
19
+ itens.push({
20
+ codigo: g('cProd'),
21
+ descricao: g('xProd'),
22
+ ncm: g('NCM'),
23
+ cfop: g('CFOP'),
24
+ unidade: g('uCom'),
25
+ quantidade: g('qCom'),
26
+ valorUnitario: g('vUnCom'),
27
+ valorTotal: g('vProd'),
28
+ });
29
+ }
30
+
31
+ return {
32
+ chaveAcesso: chave,
33
+ protocolo,
34
+ modelo: get('mod') || (chave.substring(20, 22) || '55'),
35
+ serie: get('serie'),
36
+ numero: get('nNF'),
37
+ dhEmi: get('dhEmi'),
38
+ naturezaOperacao: get('natOp'),
39
+ ambiente: get('tpAmb'),
40
+ emitente: {
41
+ cnpj: get('CNPJ'),
42
+ razaoSocial: get('xNome'),
43
+ ie: get('IE'),
44
+ municipio: (xml.match(/<enderEmit>[\s\S]*?<xMun>([^<]+)/)?.[1]) ?? '',
45
+ uf: (xml.match(/<enderEmit>[\s\S]*?<UF>([^<]+)/)?.[1]) ?? '',
46
+ },
47
+ destinatario: {
48
+ doc: xml.match(/<dest>[\s\S]*?<(?:CNPJ|CPF)>([^<]+)/)?.[1] ?? '',
49
+ razaoSocial: xml.match(/<dest>[\s\S]*?<xNome>([^<]+)/)?.[1] ?? '',
50
+ },
51
+ totais: {
52
+ vProd: get('vProd'),
53
+ vNF: get('vNF'),
54
+ vICMS: get('vICMS'),
55
+ vFrete: get('vFrete'),
56
+ },
57
+ itens,
58
+ qrCode,
59
+ informacoesComplementares: get('infCpl'),
60
+ };
61
+ }
62
+
63
+ /**
64
+ * Formata chave de acesso com espaços a cada 4 dígitos.
65
+ * @param {string} chave
66
+ */
67
+ export function formatarChaveAcesso(chave) {
68
+ return (chave || '').replace(/\D/g, '').replace(/(\d{4})(?=\d)/g, '$1 ');
69
+ }
@@ -0,0 +1,13 @@
1
+ export class GhinfeError extends Error {
2
+ /**
3
+ * @param {string} message
4
+ * @param {string} [code]
5
+ * @param {unknown} [details]
6
+ */
7
+ constructor(message, code = 'GHINFE_ERROR', details = null) {
8
+ super(message);
9
+ this.name = 'GhinfeError';
10
+ this.code = code;
11
+ this.details = details;
12
+ }
13
+ }
@@ -0,0 +1,25 @@
1
+ import { GhinfeError } from './GhinfeError.js';
2
+
3
+ export class SefazError extends GhinfeError {
4
+ /**
5
+ * @param {string} cStat
6
+ * @param {string} xMotivo
7
+ * @param {Record<string, unknown>} [raw]
8
+ */
9
+ constructor(cStat, xMotivo, raw = {}) {
10
+ super(`SEFAZ [${cStat}]: ${xMotivo}`, `SEFAZ_${cStat}`, { cStat, xMotivo, ...raw });
11
+ this.name = 'SefazError';
12
+ this.cStat = cStat;
13
+ this.xMotivo = xMotivo;
14
+ }
15
+
16
+ /** @returns {boolean} */
17
+ isAutorizado() {
18
+ return this.cStat === '100';
19
+ }
20
+
21
+ /** @returns {boolean} */
22
+ isRejeicao() {
23
+ return this.cStat.length === 3 && this.cStat.startsWith('2');
24
+ }
25
+ }
@@ -0,0 +1,15 @@
1
+ import { GhinfeError } from './GhinfeError.js';
2
+
3
+ export class XsdValidationError extends GhinfeError {
4
+ /**
5
+ * @param {string[]} erros
6
+ * @param {string} [schema]
7
+ */
8
+ constructor(erros, schema = 'unknown') {
9
+ const resumo = erros[0] ?? 'XML não conforme ao schema XSD';
10
+ super(`Validação XSD falhou: ${resumo}`, 'XSD_VALIDATION_FAILED', { erros, schema });
11
+ this.name = 'XsdValidationError';
12
+ this.erros = erros;
13
+ this.schema = schema;
14
+ }
15
+ }
package/src/index.js ADDED
@@ -0,0 +1,60 @@
1
+ export { NFeService } from './nfe/services/NFeService.js';
2
+ export { NFeXmlService } from './nfe/services/NFeXmlService.js';
3
+ export { NFeSignService } from './nfe/services/NFeSignService.js';
4
+ export { NFeSoapService } from './nfe/services/NFeSoapService.js';
5
+ export { NFeEventoService } from './nfe/services/NFeEventoService.js';
6
+ export { NFeXsdService } from './nfe/services/NFeXsdService.js';
7
+ export { NFeXmlBuilder } from './nfe/builders/NFeXmlBuilder.js';
8
+ export { NFeEventoXmlBuilder } from './nfe/builders/NFeEventoXmlBuilder.js';
9
+ export { NFeQueue } from './queue/NFeQueue.js';
10
+
11
+ export { NFCeService } from './nfce/services/NFCeService.js';
12
+ export { NFCeXmlService } from './nfce/services/NFCeXmlService.js';
13
+ export { NFCeXmlBuilder } from './nfce/builders/NFCeXmlBuilder.js';
14
+ export { NFCeQueue } from './queue/NFCeQueue.js';
15
+ export { gerarUrlQrCodeV3, gerarHashQrCodeV3, inserirInfNFeSupl } from './nfce/utils/qrcode.utils.js';
16
+
17
+ export { NFSeService } from './nfse/services/NFSeService.js';
18
+ export { NFSeXmlService } from './nfse/services/NFSeXmlService.js';
19
+ export { NFSeXmlBuilder } from './nfse/builders/NFSeXmlBuilder.js';
20
+ export { NFSeEventoXmlBuilder } from './nfse/builders/NFSeEventoXmlBuilder.js';
21
+ export { NFSeSoapService, NFSeSoapError } from './nfse/services/NFSeSoapService.js';
22
+ export { NFSeMunicipioResolver } from './nfse/NFSeMunicipioResolver.js';
23
+ export { NFSeQueue } from './queue/NFSeQueue.js';
24
+ export { NFSE_LAYOUTS, NFSE_MUNICIPIOS } from './nfse/constants/municipios.js';
25
+ export { obterAdapter, NFSeLayoutAdapters } from './nfse/layouts/adapters.js';
26
+
27
+ export { DanfeService } from './danfe/DanfeService.js';
28
+ export { extrairDadosDanfe, formatarChaveAcesso } from './danfe/xml-extract.js';
29
+
30
+ export { SefazEndpointResolver, SEFAZ_SERVICO } from './sefaz/SefazEndpointResolver.js';
31
+ export { UF_AUTORIZADOR, AUTORIZADOR_ENDPOINTS } from './sefaz/endpoints.registry.js';
32
+
33
+ export {
34
+ calcularIBSCBS,
35
+ buildFragmentoIBSCBS,
36
+ somarTotaisReforma,
37
+ reformaHabilitada,
38
+ appendIBSCBS,
39
+ appendTotaisIBSCBS,
40
+ CST_IBSCBS,
41
+ REFORMA_LAYOUT,
42
+ } from './reforma/ibscbs.js';
43
+
44
+ export { XsdValidator } from './validation/XsdValidator.js';
45
+ export { XSD_SCHEMAS } from './validation/schemas.js';
46
+
47
+ export { GhinfeError } from './errors/GhinfeError.js';
48
+ export { SefazError } from './errors/SefazError.js';
49
+ export { XsdValidationError } from './errors/XsdValidationError.js';
50
+
51
+ export { SEFAZ_STATUS, SEFAZ_URLS, AMBIENTE, NFE_VERSAO, UF_IBGE } from './constants/sefaz.js';
52
+ export { NFE_EVENTOS } from './nfe/types/NFeEvento.js';
53
+ export { loadPfxCertificate } from './utils/certificate.utils.js';
54
+ export {
55
+ gerarChaveAcesso,
56
+ calcularDigitoVerificador,
57
+ validarChaveAcesso,
58
+ parseChaveAcesso,
59
+ gerarCodigoNumerico,
60
+ } from './utils/chave.utils.js';
@@ -0,0 +1,178 @@
1
+ import { create } from 'xmlbuilder2';
2
+ import { NFE_VERSAO } from '../../constants/sefaz.js';
3
+ import { formatSefazDateTime } from '../../utils/date.utils.js';
4
+ import { GhinfeError } from '../../errors/GhinfeError.js';
5
+ import { codigoUf, formatDecimal, appendEndereco, appendImpostosItem } from '../../shared/fiscal.helpers.js';
6
+
7
+ /**
8
+ * Monta XML da NFC-e (modelo 65, layout 4.00).
9
+ */
10
+ export class NFCeXmlBuilder {
11
+ /**
12
+ * @param {import('../types/NFCeDocument.js').NFCeDocumento} documento
13
+ * @returns {string}
14
+ */
15
+ build(documento) {
16
+ this.#validar(documento);
17
+
18
+ const dhEmi = formatSefazDateTime();
19
+ const idInfNFe = `NFe${documento.chaveAcesso}`;
20
+ const ambiente = documento.ambiente ?? 2;
21
+
22
+ const totalProdutos = documento.itens.reduce((s, i) => s + i.valorTotal, 0);
23
+ const valorIcms = documento.itens.reduce((s, i) => s + (i.valorIcms ?? 0), 0);
24
+ const valorDesc = documento.itens.reduce((s, i) => s + (i.valorDesconto ?? 0), 0);
25
+ const valorNf = totalProdutos - valorDesc;
26
+
27
+ const root = create({ version: '1.0', encoding: 'UTF-8' })
28
+ .ele('NFe', { xmlns: 'http://www.portalfiscal.inf.br/nfe' })
29
+ .ele('infNFe', { versao: NFE_VERSAO, Id: idInfNFe });
30
+
31
+ const inf = root;
32
+
33
+ // ide — NFC-e modelo 65
34
+ inf
35
+ .ele('ide')
36
+ .ele('cUF').txt(codigoUf(documento.ide.uf)).up()
37
+ .ele('cNF').txt(documento.chaveAcesso.substring(35, 43)).up()
38
+ .ele('natOp').txt(documento.ide.naturezaOperacao ?? 'VENDA').up()
39
+ .ele('mod').txt('65').up()
40
+ .ele('serie').txt(String(documento.ide.serie ?? 1)).up()
41
+ .ele('nNF').txt(String(documento.ide.numero)).up()
42
+ .ele('dhEmi').txt(dhEmi).up()
43
+ .ele('tpNF').txt('1').up()
44
+ .ele('idDest').txt('1').up()
45
+ .ele('cMunFG').txt(documento.ide.codigoMunicipio).up()
46
+ .ele('tpImp').txt('4').up()
47
+ .ele('tpEmis').txt('1').up()
48
+ .ele('cDV').txt(documento.chaveAcesso.slice(-1)).up()
49
+ .ele('tpAmb').txt(String(ambiente)).up()
50
+ .ele('finNFe').txt('1').up()
51
+ .ele('indFinal').txt('1').up()
52
+ .ele('indPres').txt(String(documento.ide.presenca ?? 1)).up()
53
+ .ele('procEmi').txt('0').up()
54
+ .ele('verProc').txt('ghinfe-0.3.0').up()
55
+ .up();
56
+
57
+ // emit
58
+ const emit = inf.ele('emit');
59
+ emit.ele('CNPJ').txt(documento.emitente.cnpj.replace(/\D/g, '')).up();
60
+ emit.ele('xNome').txt(documento.emitente.razaoSocial).up();
61
+ if (documento.emitente.nomeFantasia) {
62
+ emit.ele('xFant').txt(documento.emitente.nomeFantasia).up();
63
+ }
64
+ appendEndereco(emit, 'enderEmit', documento.emitente.endereco);
65
+ emit.ele('IE').txt(documento.emitente.ie).up();
66
+ emit.ele('CRT').txt(documento.emitente.crt).up();
67
+ emit.up();
68
+
69
+ // dest — opcional na NFC-e (se informado CPF/CNPJ, exige indIEDest ou enderDest)
70
+ if (documento.destinatario?.cpf || documento.destinatario?.cnpj) {
71
+ const dest = inf.ele('dest');
72
+ const docDest = (documento.destinatario.cnpj ?? documento.destinatario.cpf ?? '').replace(/\D/g, '');
73
+ if (docDest.length === 14) dest.ele('CNPJ').txt(docDest).up();
74
+ else if (docDest.length === 11) dest.ele('CPF').txt(docDest).up();
75
+ if (documento.destinatario.razaoSocial) {
76
+ dest.ele('xNome').txt(documento.destinatario.razaoSocial).up();
77
+ }
78
+ if (documento.destinatario.endereco) {
79
+ appendEndereco(dest, 'enderDest', documento.destinatario.endereco);
80
+ } else {
81
+ dest.ele('indIEDest').txt('9').up();
82
+ }
83
+ dest.up();
84
+ }
85
+
86
+ // det — itens
87
+ documento.itens.forEach((item, index) => {
88
+ const det = inf.ele('det', { nItem: String(index + 1) });
89
+ const prod = det.ele('prod');
90
+ prod.ele('cProd').txt(item.codigo).up();
91
+ prod.ele('cEAN').txt(item.ean ?? 'SEM GTIN').up();
92
+ prod.ele('xProd').txt(item.descricao).up();
93
+ prod.ele('NCM').txt(item.ncm).up();
94
+ prod.ele('CFOP').txt(item.cfop).up();
95
+ prod.ele('uCom').txt(item.unidade).up();
96
+ prod.ele('qCom').txt(formatDecimal(item.quantidade, 4)).up();
97
+ prod.ele('vUnCom').txt(formatDecimal(item.valorUnitario, 10)).up();
98
+ prod.ele('vProd').txt(formatDecimal(item.valorTotal, 2)).up();
99
+ prod.ele('cEANTrib').txt(item.ean ?? 'SEM GTIN').up();
100
+ prod.ele('uTrib').txt(item.unidade).up();
101
+ prod.ele('qTrib').txt(formatDecimal(item.quantidade, 4)).up();
102
+ prod.ele('vUnTrib').txt(formatDecimal(item.valorUnitario, 10)).up();
103
+ prod.ele('indTot').txt('1').up();
104
+ prod.up();
105
+
106
+ appendImpostosItem(det.ele('imposto'), item);
107
+ det.up();
108
+ });
109
+
110
+ // total
111
+ const total = inf.ele('total').ele('ICMSTot');
112
+ total
113
+ .ele('vBC').txt(formatDecimal(totalProdutos, 2)).up()
114
+ .ele('vICMS').txt(formatDecimal(valorIcms, 2)).up()
115
+ .ele('vICMSDeson').txt('0.00').up()
116
+ .ele('vFCP').txt('0.00').up()
117
+ .ele('vBCST').txt('0.00').up()
118
+ .ele('vST').txt('0.00').up()
119
+ .ele('vFCPST').txt('0.00').up()
120
+ .ele('vFCPSTRet').txt('0.00').up()
121
+ .ele('vProd').txt(formatDecimal(totalProdutos, 2)).up()
122
+ .ele('vFrete').txt('0.00').up()
123
+ .ele('vSeg').txt('0.00').up()
124
+ .ele('vDesc').txt(formatDecimal(valorDesc, 2)).up()
125
+ .ele('vII').txt('0.00').up()
126
+ .ele('vIPI').txt('0.00').up()
127
+ .ele('vIPIDevol').txt('0.00').up()
128
+ .ele('vPIS').txt('0.00').up()
129
+ .ele('vCOFINS').txt('0.00').up()
130
+ .ele('vOutro').txt('0.00').up()
131
+ .ele('vNF').txt(formatDecimal(valorNf, 2)).up();
132
+ total.up().up();
133
+
134
+ // transp — NFC-e sem frete
135
+ inf.ele('transp').ele('modFrete').txt('9').up().up();
136
+
137
+ // pag
138
+ const pag = inf.ele('pag');
139
+ const pagamentos = documento.pagamentos ?? [{ forma: '01', valor: valorNf }];
140
+ pagamentos.forEach((p) => {
141
+ const detPag = pag.ele('detPag');
142
+ detPag.ele('tPag').txt(p.forma).up();
143
+ detPag.ele('vPag').txt(formatDecimal(p.valor, 2)).up();
144
+ detPag.up();
145
+ });
146
+ pag.up();
147
+
148
+ // infAdic
149
+ if (documento.informacoesComplementares || documento.informacoesFisco) {
150
+ const infAdic = inf.ele('infAdic');
151
+ if (documento.informacoesFisco) {
152
+ infAdic.ele('infAdFisco').txt(documento.informacoesFisco).up();
153
+ }
154
+ if (documento.informacoesComplementares) {
155
+ infAdic.ele('infCpl').txt(documento.informacoesComplementares).up();
156
+ }
157
+ infAdic.up();
158
+ }
159
+
160
+ return root.end({ prettyPrint: false, headless: false });
161
+ }
162
+
163
+ /** @param {import('../types/NFCeDocument.js').NFCeDocumento} documento */
164
+ #validar(documento) {
165
+ if (!documento.chaveAcesso || documento.chaveAcesso.length !== 44) {
166
+ throw new GhinfeError('chaveAcesso deve conter 44 dígitos', 'INVALID_CHAVE');
167
+ }
168
+ if (!documento.emitente?.cnpj) {
169
+ throw new GhinfeError('emitente.cnpj é obrigatório', 'INVALID_EMITENTE');
170
+ }
171
+ if (!documento.itens?.length) {
172
+ throw new GhinfeError('A NFC-e deve conter ao menos um item', 'INVALID_ITENS');
173
+ }
174
+ if (!documento.csc?.idCSC || !documento.csc?.codigo) {
175
+ throw new GhinfeError('csc.idCSC e csc.codigo são obrigatórios para QR Code', 'INVALID_CSC');
176
+ }
177
+ }
178
+ }
@@ -0,0 +1,26 @@
1
+ /** URLs de consulta QR Code NFC-e por UF e ambiente. */
2
+ export const NFCE_QRCODE_URLS = {
3
+ SP: {
4
+ 1: 'https://www.nfce.fazenda.sp.gov.br/NFCeConsultaPublica/Paginas/ConsultaQRCode.aspx',
5
+ 2: 'https://www.homologacao.nfce.fazenda.sp.gov.br/NFCeConsultaPublica/Paginas/ConsultaQRCode.aspx',
6
+ },
7
+ PR: {
8
+ 1: 'http://www.fazenda.pr.gov.br/nfce/qrcode',
9
+ 2: 'http://www.fazenda.pr.gov.br/nfce/qrcode',
10
+ },
11
+ RS: {
12
+ 1: 'https://www.sefaz.rs.gov.br/NFCE/NFCE-COM-QRCode',
13
+ 2: 'https://www.sefaz.rs.gov.br/NFCE/NFCE-COM-QRCode',
14
+ },
15
+ };
16
+
17
+ /** URLs de consulta por chave (portal SEFAZ). */
18
+ export const NFCE_URL_CHAVE = {
19
+ SP: {
20
+ 1: 'https://www.nfce.fazenda.sp.gov.br/consulta',
21
+ 2: 'https://www.homologacao.nfce.fazenda.sp.gov.br/consulta',
22
+ },
23
+ };
24
+
25
+ /** Versão do QR Code NFC-e (2 = atual). */
26
+ export const NFCE_QRCODE_VERSAO = '2';