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
@@ -0,0 +1,356 @@
1
+ import QRCode from 'qrcode';
2
+ import { extrairDadosDace } from './dace-extract.js';
3
+ import { gerarSvgCode128C } from '../../utils/code128.utils.js';
4
+ import { formatarChaveAcesso } from '../../danfe/xml-extract.js';
5
+ import { GhinfeError } from '../../errors/GhinfeError.js';
6
+
7
+ const MODALIDADE = { 0: 'CORREIOS', 1: 'PRÓPRIA', 2: 'TRANSPORTADORA' };
8
+ const EMISSOR = { Fisco: 'FISCO', Marketplace: 'MARKETPLACE', Transportadora: 'TRANSPORTADORA', ECT: 'CORREIOS (ECT)' };
9
+
10
+ const esc = (s) =>
11
+ String(s ?? '').replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;').replace(/"/g, '&quot;');
12
+
13
+ const milhar = (n) => String(n ?? '').replace(/\B(?=(\d{3})+(?!\d))/g, '.');
14
+
15
+ function moeda(valor) {
16
+ const [inteiro, centavos] = Number(valor || 0).toFixed(2).split('.');
17
+ return `R$ ${milhar(inteiro)},${centavos}`;
18
+ }
19
+
20
+ function quantidade(valor) {
21
+ const [inteiro, decimais] = String(Number(valor || 0)).split('.');
22
+ return decimais ? `${milhar(inteiro)},${decimais}` : milhar(inteiro);
23
+ }
24
+
25
+ /** "2026-10-24T15:31:12-03:00" → "24-10-2026 15:31:12" (horário local do emitente) */
26
+ function dataHora(dh) {
27
+ const m = String(dh ?? '').match(/^(\d{4})-(\d{2})-(\d{2})T(\d{2}:\d{2}:\d{2})/);
28
+ return m ? `${m[3]}-${m[2]}-${m[1]} ${m[4]}` : String(dh ?? '');
29
+ }
30
+
31
+ /** @param {{ cnpj?: string, cpf?: string, idOutros?: string }} parte */
32
+ function documento(parte, mascararCpf) {
33
+ if (!parte) return '';
34
+ if (parte.cnpj) return parte.cnpj.replace(/^(\d{2})(\d{3})(\d{3})(\d{4})(\d{2})$/, '$1.$2.$3/$4-$5');
35
+ if (parte.cpf) {
36
+ return mascararCpf
37
+ ? parte.cpf.replace(/^(\d{3})\d{6}(\d{2})$/, '$1.***.***-$2')
38
+ : parte.cpf.replace(/^(\d{3})(\d{3})(\d{3})(\d{2})$/, '$1.$2.$3-$4');
39
+ }
40
+ return parte.idOutros ?? '';
41
+ }
42
+
43
+ function cidadeUf(endereco) {
44
+ return endereco ? `${endereco.municipio}-${endereco.uf}` : '';
45
+ }
46
+
47
+ function enderecoCompleto(e) {
48
+ if (!e) return '';
49
+ const cep = e.cep ? `CEP ${e.cep.replace(/^(\d{5})(\d{3})$/, '$1-$2')}` : '';
50
+ return [
51
+ [e.logradouro, e.numero].filter(Boolean).join(', ') + (e.complemento ? ` - ${e.complemento}` : ''),
52
+ e.bairro,
53
+ cidadeUf(e),
54
+ cep,
55
+ ]
56
+ .filter(Boolean)
57
+ .join(' - ');
58
+ }
59
+
60
+ /**
61
+ * Gera a DACE (Declaração Auxiliar de Conteúdo eletrônica) em HTML imprimível,
62
+ * conforme Manual DC-e — Anexo II (modelos completo e resumido).
63
+ * PDF: imprima o HTML ou converta no sistema consumidor (puppeteer etc.).
64
+ */
65
+ export class DaceService {
66
+ /**
67
+ * @param {string} xml dceProc (recomendado) ou DCe assinada
68
+ * @param {{
69
+ * modelo?: 'completa'|'resumida',
70
+ * protocolo?: string,
71
+ * mascararCpf?: boolean,
72
+ * itensPrimeiraFolha?: number,
73
+ * itensDemaisFolhas?: number,
74
+ * }} [opcoes]
75
+ * mascararCpf: padrão true (NNN.***.***-NN, recomendação LGPD da NT 2024.001)
76
+ * @returns {Promise<{ html: string, dados: ReturnType<typeof extrairDadosDace>, tipo: 'DACE'|'DACE_RESUMIDA', folhas: number }>}
77
+ */
78
+ async gerar(xml, opcoes = {}) {
79
+ const dados = extrairDadosDace(xml);
80
+ if (opcoes.protocolo) dados.protocolo = opcoes.protocolo;
81
+
82
+ if (!dados.chaveAcesso) {
83
+ throw new GhinfeError('Chave de acesso não encontrada no XML', 'DACE_CHAVE_MISSING');
84
+ }
85
+ if (dados.tipoEmissao !== '2' && !dados.protocolo) {
86
+ throw new GhinfeError(
87
+ 'DC-e sem protocolo de autorização. Use o dceProc retornado na autorização ou informe opcoes.protocolo',
88
+ 'DACE_SEM_PROTOCOLO'
89
+ );
90
+ }
91
+ if (!dados.qrCode) {
92
+ throw new GhinfeError('XML sem infDCeSupl/qrCodDCe', 'DACE_QRCODE_MISSING');
93
+ }
94
+
95
+ const ctx = {
96
+ dados,
97
+ mascararCpf: opcoes.mascararCpf ?? true,
98
+ barras: gerarSvgCode128C(dados.chaveAcesso),
99
+ qrSvg: await QRCode.toString(dados.qrCode, { type: 'svg', margin: 0, errorCorrectionLevel: 'M' }),
100
+ homologacao: dados.ambiente === '2',
101
+ contingencia: dados.tipoEmissao === '2',
102
+ };
103
+
104
+ if (opcoes.modelo === 'resumida') {
105
+ return { html: this.#renderResumida(ctx), dados, tipo: 'DACE_RESUMIDA', folhas: 1 };
106
+ }
107
+
108
+ const primeira = opcoes.itensPrimeiraFolha ?? 12;
109
+ const demais = opcoes.itensDemaisFolhas ?? 34;
110
+ const paginas = [dados.itens.slice(0, primeira)];
111
+ for (let i = primeira; i < dados.itens.length; i += demais) {
112
+ paginas.push(dados.itens.slice(i, i + demais));
113
+ }
114
+
115
+ return { html: this.#renderCompleta(ctx, paginas), dados, tipo: 'DACE', folhas: paginas.length };
116
+ }
117
+
118
+ #protocolo(ctx) {
119
+ const { dados, contingencia } = ctx;
120
+ if (contingencia) {
121
+ return `<span class="destaque">EMITIDA EM CONTINGÊNCIA</span>${dados.protocolo ? ` ${esc(dados.protocolo)}` : ''}`;
122
+ }
123
+ const recebimento = dados.dhRecbto ? ` ${esc(dataHora(dados.dhRecbto))}` : '';
124
+ return `${esc(dados.protocolo)}${recebimento}`;
125
+ }
126
+
127
+ #campo(rotulo, valor, classe = '') {
128
+ return `<div class="campo ${classe}"><div class="rot">${esc(rotulo)}</div><div class="val">${valor}</div></div>`;
129
+ }
130
+
131
+ #cabecalho(ctx, folha, totalFolhas) {
132
+ const { dados } = ctx;
133
+ return `
134
+ <div class="quadro cab">
135
+ <div class="titulo">
136
+ <div class="dace">DACE</div>
137
+ <div class="sub">DECLARAÇÃO AUXILIAR DE CONTEÚDO ELETRÔNICA</div>
138
+ <div class="num">Nº ${esc(milhar(dados.numero))} &nbsp; SÉRIE ${esc(dados.serie)}</div>
139
+ <div class="nfolha">FOLHA ${String(folha).padStart(2, '0')}/${String(totalFolhas).padStart(2, '0')}</div>
140
+ </div>
141
+ <div class="ident">
142
+ <div class="barras">${ctx.barras}</div>
143
+ ${this.#campo('CHAVE DE ACESSO', `<span class="chave">${esc(formatarChaveAcesso(dados.chaveAcesso))}</span>`)}
144
+ <div class="linha">
145
+ ${this.#campo('DATA DE EMISSÃO', esc(dataHora(dados.dhEmi)))}
146
+ ${this.#campo('MODALIDADE DO TRANSPORTE', `${esc(dados.modalidadeTransporte)}-${esc(MODALIDADE[dados.modalidadeTransporte] ?? '')}`)}
147
+ </div>
148
+ ${this.#campo('PROTOCOLO DE AUTORIZAÇÃO', this.#protocolo(ctx))}
149
+ </div>
150
+ </div>`;
151
+ }
152
+
153
+ #quadroParte(titulo, rotulo, parte, ctx) {
154
+ return `
155
+ <div class="secao">${esc(titulo)}</div>
156
+ <div class="quadro grade">
157
+ ${this.#campo(`CNPJ/CPF ${rotulo}`, esc(documento(parte, ctx.mascararCpf)))}
158
+ ${this.#campo(`NOME ${rotulo}`, esc(parte?.nome))}
159
+ ${this.#campo(`CIDADE-UF ${rotulo}`, esc(cidadeUf(parte?.endereco)))}
160
+ ${this.#campo(`ENDEREÇO ${rotulo}`, esc(enderecoCompleto(parte?.endereco)))}
161
+ </div>`;
162
+ }
163
+
164
+ #tabelaItens(itens, ctx, comTotal) {
165
+ const linhas = itens
166
+ .map(
167
+ (i) => `<tr>
168
+ <td class="c">${esc(i.nItem)}</td>
169
+ <td>${esc(i.descricao)}${i.informacoesAdicionais ? `<div class="adic">${esc(i.informacoesAdicionais)}</div>` : ''}</td>
170
+ <td class="r">${esc(quantidade(i.quantidade))}</td>
171
+ <td class="r">${esc(moeda(i.valorTotal))}</td>
172
+ </tr>`
173
+ )
174
+ .join('');
175
+
176
+ return `
177
+ <div class="secao">IDENTIFICAÇÃO DOS BENS OU MERCADORIAS</div>
178
+ <table>
179
+ <thead><tr><th class="c">ITEM</th><th>DESCRIÇÃO</th><th class="r">QUANTIDADE</th><th class="r">VALOR</th></tr></thead>
180
+ <tbody>${linhas}</tbody>
181
+ ${comTotal ? `<tfoot><tr><td colspan="3" class="r"><b>VALOR TOTAL</b></td><td class="r"><b>${esc(moeda(ctx.dados.valorTotal))}</b></td></tr></tfoot>` : ''}
182
+ </table>`;
183
+ }
184
+
185
+ #dadosAdicionais(ctx) {
186
+ const { dados, homologacao } = ctx;
187
+ const complementares = [
188
+ homologacao ? '<span class="destaque">EMITIDA EM HOMOLOGAÇÃO — SEM VALOR FISCAL</span>' : '',
189
+ esc(dados.informacoesComplementares),
190
+ esc(dados.informacoesMarketplace),
191
+ esc(dados.informacoesECT),
192
+ ]
193
+ .filter(Boolean)
194
+ .join('<br/>');
195
+
196
+ return `
197
+ <div class="secao">DADOS ADICIONAIS</div>
198
+ <div class="quadro linha">
199
+ ${this.#campo('INFORMAÇÕES COMPLEMENTARES', complementares || '&nbsp;', 'meio')}
200
+ ${this.#campo('INFORMAÇÕES ADICIONAIS DO FISCO', esc(dados.informacoesFisco) || '&nbsp;', 'meio')}
201
+ </div>
202
+ <div class="quadro rodape">
203
+ <div class="qr">${ctx.qrSvg}</div>
204
+ <div class="obs">
205
+ <div class="rot">OBSERVAÇÕES</div>
206
+ ${dados.observacoes.map((o) => `<p>${esc(o)}</p>`).join('')}
207
+ </div>
208
+ </div>`;
209
+ }
210
+
211
+ #estilos(extra = '') {
212
+ return `
213
+ * { box-sizing: border-box; }
214
+ body { font-family: Arial, Helvetica, sans-serif; color: #000; margin: 0; }
215
+ .rot { font-size: 6.5pt; color: #333; text-transform: uppercase; }
216
+ .val { font-size: 8.5pt; font-weight: bold; word-break: break-word; }
217
+ .destaque { color: #b00000; font-weight: bold; }
218
+ .chave { letter-spacing: .5px; font-size: 9pt; }
219
+ .marca { position: absolute; top: 45%; left: 0; right: 0; text-align: center; transform: rotate(-30deg);
220
+ font-size: 40pt; font-weight: bold; color: rgba(176,0,0,.14); pointer-events: none; z-index: 2; }
221
+ ${extra}`;
222
+ }
223
+
224
+ #renderCompleta(ctx, paginas) {
225
+ const { dados } = ctx;
226
+ const total = paginas.length;
227
+ const emissor = dados.empresaEmissora;
228
+
229
+ const folhas = paginas.map((itens, idx) => {
230
+ const primeira = idx === 0;
231
+ return `
232
+ <section class="folha">
233
+ ${ctx.homologacao ? '<div class="marca">EMITIDA EM HOMOLOGAÇÃO</div>' : ''}
234
+ ${this.#cabecalho(ctx, idx + 1, total)}
235
+ ${this.#quadroParte('IDENTIFICAÇÃO DO REMETENTE (USUÁRIO EMITENTE)', 'REMETENTE', dados.emitente, ctx)}
236
+ ${primeira ? this.#quadroParte('IDENTIFICAÇÃO DO DESTINATÁRIO', 'DESTINATÁRIO', dados.destinatario, ctx) : ''}
237
+ ${primeira && emissor ? `
238
+ <div class="secao">IDENTIFICAÇÃO DO ${esc(EMISSOR[emissor.tipo])}</div>
239
+ <div class="quadro grade">
240
+ ${this.#campo(`CNPJ ${EMISSOR[emissor.tipo]}`, esc(documento({ cnpj: emissor.cnpj })))}
241
+ ${this.#campo(`NOME ${EMISSOR[emissor.tipo]}`, esc(emissor.nome))}
242
+ </div>` : ''}
243
+ ${this.#tabelaItens(itens, ctx, primeira)}
244
+ ${primeira ? this.#dadosAdicionais(ctx) : ''}
245
+ </section>`;
246
+ });
247
+
248
+ return `<!DOCTYPE html>
249
+ <html lang="pt-BR">
250
+ <head>
251
+ <meta charset="UTF-8"/>
252
+ <title>DACE ${esc(dados.chaveAcesso)}</title>
253
+ <style>${this.#estilos(`
254
+ @page { size: A4 portrait; margin: 8mm; }
255
+ .folha { width: 194mm; margin: 0 auto; position: relative; z-index: 1; }
256
+ .folha + .folha { page-break-before: always; break-before: page; margin-top: 8mm; }
257
+ .quadro { border: 1px solid #000; margin-bottom: 1.5mm; }
258
+ .campo { padding: 1mm 1.5mm; border-right: 1px solid #000; border-bottom: 1px solid #000; }
259
+ .cab { display: grid; grid-template-columns: 62mm 1fr; }
260
+ .cab .titulo { border-right: 1px solid #000; padding: 2mm; text-align: center; display: flex; flex-direction: column; justify-content: center; gap: 1.5mm; }
261
+ .dace { font-size: 22pt; font-weight: bold; letter-spacing: 2px; }
262
+ .sub { font-size: 8pt; font-weight: bold; }
263
+ .num { font-size: 10pt; font-weight: bold; }
264
+ .nfolha { font-size: 8pt; }
265
+ .ident .campo:last-child, .grade .campo:nth-child(2n), .linha .campo:last-child { border-right: none; }
266
+ .ident > .campo:last-child { border-bottom: none; }
267
+ .barras { height: 15mm; padding: 1mm 4mm; border-bottom: 1px solid #000; }
268
+ .barras svg { width: 100%; height: 100%; display: block; }
269
+ .linha { display: flex; } .linha .campo { flex: 1; }
270
+ .grade { display: grid; grid-template-columns: 52mm 1fr; }
271
+ .grade .campo:nth-last-child(-n+2) { border-bottom: none; }
272
+ .quadro.linha .campo { border-bottom: none; }
273
+ .secao { font-size: 7pt; font-weight: bold; margin: 2mm 0 .5mm; }
274
+ table { width: 100%; border-collapse: collapse; font-size: 8pt; margin-bottom: 1.5mm; }
275
+ th, td { border: 1px solid #000; padding: .8mm 1.5mm; vertical-align: top; }
276
+ th { font-size: 6.5pt; background: #eee; }
277
+ .c { text-align: center; width: 12mm; } .r { text-align: right; white-space: nowrap; }
278
+ .adic { font-size: 6.5pt; color: #333; }
279
+ .meio { min-height: 16mm; }
280
+ .rodape { display: grid; grid-template-columns: 38mm 1fr; }
281
+ .qr { padding: 2mm; border-right: 1px solid #000; } .qr svg { width: 34mm; height: 34mm; display: block; }
282
+ .obs { padding: 1.5mm 2mm; } .obs p { font-size: 6.5pt; margin: 1mm 0; text-align: justify; }
283
+ @media screen { body { background: #888; padding: 8mm 0; } .folha { background: #fff; padding: 8mm; width: 210mm; } }
284
+ `)}</style>
285
+ </head>
286
+ <body>
287
+ ${folhas.join('')}
288
+ </body>
289
+ </html>`;
290
+ }
291
+
292
+ #renderResumida(ctx) {
293
+ const { dados } = ctx;
294
+ const emissor = dados.empresaEmissora;
295
+ const linha = (rotulo, valor) => `<div><span class="rot">${esc(rotulo)}:</span> <b>${valor}</b></div>`;
296
+
297
+ return `<!DOCTYPE html>
298
+ <html lang="pt-BR">
299
+ <head>
300
+ <meta charset="UTF-8"/>
301
+ <title>DACE Resumida ${esc(dados.chaveAcesso)}</title>
302
+ <style>${this.#estilos(`
303
+ @page { size: 100mm 150mm; margin: 3mm; }
304
+ .etiqueta { width: 94mm; margin: 0 auto; border: 1px solid #000; padding: 2mm; font-size: 7.5pt; position: relative; z-index: 1; background: #fff; }
305
+ .topo { text-align: center; font-weight: bold; font-size: 8.5pt; border-bottom: 1px solid #000; padding-bottom: 1.5mm; margin-bottom: 1.5mm; }
306
+ .bloco { border-bottom: 1px solid #000; padding: 1mm 0; line-height: 1.35; }
307
+ .bloco .rot { font-size: 6.5pt; }
308
+ .obs p { font-size: 5.5pt; margin: .8mm 0; text-align: justify; }
309
+ .final { display: grid; grid-template-columns: 26mm 1fr; gap: 2mm; align-items: center; padding-top: 1.5mm; }
310
+ .final svg.qr, .final .qr svg { width: 26mm; height: 26mm; display: block; }
311
+ .barras { height: 11mm; } .barras svg { width: 100%; height: 100%; display: block; }
312
+ .chave { font-size: 7.5pt; text-align: center; margin-top: 1mm; }
313
+ .marca { font-size: 18pt; }
314
+ @media screen { body { background: #888; padding: 8mm 0; } }
315
+ `)}</style>
316
+ </head>
317
+ <body>
318
+ <div class="etiqueta">
319
+ ${ctx.homologacao ? '<div class="marca">EMITIDA EM HOMOLOGAÇÃO</div>' : ''}
320
+ <div class="topo">DACE RESUMIDA - DECLARAÇÃO AUXILIAR DE CONTEÚDO ELETRÔNICA</div>
321
+ <div class="bloco">
322
+ ${linha('Nº', `${esc(milhar(dados.numero))} &nbsp; SÉRIE ${esc(dados.serie)}`)}
323
+ ${linha('DATA DE EMISSÃO', esc(dataHora(dados.dhEmi)))}
324
+ ${linha('PROTOCOLO DE AUTORIZAÇÃO', this.#protocolo(ctx))}
325
+ ${linha('MODALIDADE DO TRANSPORTE', `${esc(dados.modalidadeTransporte)}-${esc(MODALIDADE[dados.modalidadeTransporte] ?? '')}`)}
326
+ </div>
327
+ <div class="bloco">
328
+ ${linha('CNPJ/CPF REMETENTE (USUÁRIO EMITENTE)', esc(documento(dados.emitente, ctx.mascararCpf)))}
329
+ ${linha('NOME REMETENTE', esc(dados.emitente?.nome))}
330
+ ${linha('CIDADE-UF REMETENTE', esc(cidadeUf(dados.emitente?.endereco)))}
331
+ ${linha('ENDEREÇO REMETENTE', esc(enderecoCompleto(dados.emitente?.endereco)))}
332
+ </div>
333
+ <div class="bloco">
334
+ ${linha('CNPJ/CPF DESTINATÁRIO', esc(documento(dados.destinatario, ctx.mascararCpf)))}
335
+ ${linha('NOME DESTINATÁRIO', esc(dados.destinatario?.nome))}
336
+ ${linha('CIDADE-UF DESTINATÁRIO', esc(cidadeUf(dados.destinatario?.endereco)))}
337
+ ${linha('ENDEREÇO DESTINATÁRIO', esc(enderecoCompleto(dados.destinatario?.endereco)))}
338
+ </div>
339
+ ${emissor ? `<div class="bloco">
340
+ ${linha(`CNPJ ${EMISSOR[emissor.tipo]}`, esc(documento({ cnpj: emissor.cnpj })))}
341
+ ${linha(`NOME ${EMISSOR[emissor.tipo]}`, esc(emissor.nome))}
342
+ </div>` : ''}
343
+ ${ctx.homologacao ? '<div class="bloco destaque">EMITIDA EM HOMOLOGAÇÃO — SEM VALOR FISCAL</div>' : ''}
344
+ <div class="bloco obs">${dados.observacoes.map((o) => `<p>${esc(o)}</p>`).join('')}</div>
345
+ <div class="final">
346
+ <div class="qr">${ctx.qrSvg}</div>
347
+ <div>
348
+ <div class="barras">${ctx.barras}</div>
349
+ <div class="chave"><span class="rot" style="text-transform:none">CHAVE DE ACESSO DC-e</span><br/><b>${esc(formatarChaveAcesso(dados.chaveAcesso))}</b></div>
350
+ </div>
351
+ </div>
352
+ </div>
353
+ </body>
354
+ </html>`;
355
+ }
356
+ }
@@ -0,0 +1,114 @@
1
+ import { DOMParser } from '@xmldom/xmldom';
2
+ import { GhinfeError } from '../../errors/GhinfeError.js';
3
+
4
+ /** @param {Element|null|undefined} parent @param {string} nome */
5
+ function filho(parent, nome) {
6
+ if (!parent) return null;
7
+ for (let n = parent.firstChild; n; n = n.nextSibling) {
8
+ if (n.nodeType === 1 && n.localName === nome) return /** @type {Element} */ (n);
9
+ }
10
+ return null;
11
+ }
12
+
13
+ /** @param {Element|null|undefined} parent @param {string} nome */
14
+ function filhos(parent, nome) {
15
+ const lista = [];
16
+ for (let n = parent?.firstChild; n; n = n.nextSibling) {
17
+ if (n.nodeType === 1 && n.localName === nome) lista.push(/** @type {Element} */ (n));
18
+ }
19
+ return lista;
20
+ }
21
+
22
+ /** @param {Element|null|undefined} parent @param {string} nome */
23
+ function texto(parent, nome) {
24
+ return filho(parent, nome)?.textContent?.trim() ?? '';
25
+ }
26
+
27
+ /** @param {Element|null} el */
28
+ function parte(el, tagEndereco) {
29
+ if (!el) return null;
30
+ const end = filho(el, tagEndereco);
31
+ return {
32
+ cnpj: texto(el, 'CNPJ'),
33
+ cpf: texto(el, 'CPF'),
34
+ idOutros: texto(el, 'idOutros'),
35
+ nome: texto(el, 'xNome'),
36
+ endereco: end
37
+ ? {
38
+ logradouro: texto(end, 'xLgr'),
39
+ numero: texto(end, 'nro'),
40
+ complemento: texto(end, 'xCpl'),
41
+ bairro: texto(end, 'xBairro'),
42
+ municipio: texto(end, 'xMun'),
43
+ uf: texto(end, 'UF'),
44
+ cep: texto(end, 'CEP'),
45
+ pais: texto(end, 'xPais'),
46
+ }
47
+ : null,
48
+ };
49
+ }
50
+
51
+ /**
52
+ * Extrai os dados da DC-e (dceProc ou DCe assinada) usados na DACE.
53
+ * @param {string} xml
54
+ */
55
+ export function extrairDadosDace(xml) {
56
+ const doc = new DOMParser().parseFromString(xml, 'text/xml');
57
+ const dce = doc.getElementsByTagNameNS('*', 'DCe')[0];
58
+ const inf = filho(dce, 'infDCe');
59
+ if (!inf) {
60
+ throw new GhinfeError('XML não contém DCe/infDCe', 'DACE_XML_INVALIDO');
61
+ }
62
+
63
+ const ide = filho(inf, 'ide');
64
+ const infProt = doc.getElementsByTagNameNS('*', 'infProt')[0];
65
+ const supl = filho(dce, 'infDCeSupl');
66
+ const infAdic = filho(inf, 'infAdic');
67
+ const infDec = filho(inf, 'infDec');
68
+
69
+ const grupoEmissor = ['Fisco', 'Marketplace', 'Transportadora', 'ECT']
70
+ .map((tipo) => ({ tipo, el: filho(inf, tipo) }))
71
+ .find(({ el }) => el);
72
+
73
+ return {
74
+ chaveAcesso: (inf.getAttribute('Id') ?? '').replace(/^DCe/, ''),
75
+ numero: texto(ide, 'nDC'),
76
+ serie: texto(ide, 'serie'),
77
+ dhEmi: texto(ide, 'dhEmi'),
78
+ tipoEmissao: texto(ide, 'tpEmis'),
79
+ tipoEmitente: texto(ide, 'tpEmit'),
80
+ ambiente: texto(ide, 'tpAmb'),
81
+ protocolo: texto(infProt, 'nProt'),
82
+ dhRecbto: texto(infProt, 'dhRecbto'),
83
+ emitente: parte(filho(inf, 'emit'), 'enderEmit'),
84
+ destinatario: parte(filho(inf, 'dest'), 'enderDest'),
85
+ empresaEmissora: grupoEmissor
86
+ ? {
87
+ tipo: grupoEmissor.tipo,
88
+ cnpj: texto(grupoEmissor.el, 'CNPJ'),
89
+ nome: texto(grupoEmissor.el, 'xNome') || texto(grupoEmissor.el, 'xOrgao'),
90
+ }
91
+ : null,
92
+ itens: filhos(inf, 'det').map((det) => {
93
+ const prod = filho(det, 'prod');
94
+ return {
95
+ nItem: det.getAttribute('nItem'),
96
+ descricao: texto(prod, 'xProd'),
97
+ ncm: texto(prod, 'NCM'),
98
+ quantidade: texto(prod, 'qCom'),
99
+ valorUnitario: texto(prod, 'vUnCom'),
100
+ valorTotal: texto(prod, 'vProd'),
101
+ informacoesAdicionais: texto(det, 'infAdProd'),
102
+ };
103
+ }),
104
+ valorTotal: texto(filho(inf, 'total'), 'vDC'),
105
+ modalidadeTransporte: texto(filho(inf, 'transp'), 'modTrans'),
106
+ cnpjTransportadora: texto(filho(inf, 'transp'), 'CNPJTransp'),
107
+ informacoesComplementares: texto(infAdic, 'infCpl'),
108
+ informacoesFisco: texto(infAdic, 'infAdFisco'),
109
+ informacoesMarketplace: texto(infAdic, 'infAdMarketplace'),
110
+ informacoesECT: texto(infAdic, 'infAdECT'),
111
+ observacoes: [texto(infDec, 'xObs1'), texto(infDec, 'xObs2')].filter(Boolean),
112
+ qrCode: texto(supl, 'qrCodDCe'),
113
+ };
114
+ }