ghinfe 0.6.0 → 0.6.1

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
@@ -1,47 +1,69 @@
1
- import forge from 'node-forge';
2
- import { GhinfeError } from '../errors/GhinfeError.js';
3
-
4
- /**
5
- * Carrega certificado A1 (.pfx / .p12) e retorna chave privada + certificado PEM.
6
- * @param {Buffer|string} pfxBuffer
7
- * @param {string} password
8
- * @returns {{ privateKeyPem: string, certificatePem: string, certificate: forge.pki.Certificate }}
9
- */
10
- export function loadPfxCertificate(pfxBuffer, password) {
11
- try {
12
- const buffer = Buffer.isBuffer(pfxBuffer) ? pfxBuffer : Buffer.from(pfxBuffer);
13
- const p12Asn1 = forge.asn1.fromDer(buffer.toString('binary'));
14
- const p12 = forge.pkcs12.pkcs12FromAsn1(p12Asn1, password);
15
-
16
- const certBags = p12.getBags({ bagType: forge.pki.oids.certBag });
17
- const keyBags = p12.getBags({ bagType: forge.pki.oids.pkcs8ShroudedKeyBag });
18
-
19
- const certBag = certBags[forge.pki.oids.certBag]?.[0];
20
- const keyBag =
21
- keyBags[forge.pki.oids.pkcs8ShroudedKeyBag]?.[0] ??
22
- p12.getBags({ bagType: forge.pki.oids.keyBag })[forge.pki.oids.keyBag]?.[0];
23
-
24
- if (!certBag?.cert || !keyBag?.key) {
25
- throw new GhinfeError(
26
- 'Certificado ou chave privada não encontrados no arquivo PFX',
27
- 'CERT_NOT_FOUND'
28
- );
29
- }
30
-
31
- const certificate = certBag.cert;
32
- const privateKey = keyBag.key;
33
-
34
- return {
35
- privateKeyPem: forge.pki.privateKeyToPem(privateKey),
36
- certificatePem: forge.pki.certificateToPem(certificate),
37
- certificate,
38
- };
39
- } catch (error) {
40
- if (error instanceof GhinfeError) throw error;
41
- throw new GhinfeError(
42
- 'Falha ao carregar certificado PFX. Verifique o arquivo e a senha.',
43
- 'CERT_LOAD_ERROR',
44
- error
45
- );
46
- }
47
- }
1
+ import forge from 'node-forge';
2
+ import { GhinfeError } from '../errors/GhinfeError.js';
3
+
4
+ /**
5
+ * Monta um PFX em memória a partir de certificado e chave PEM (ex.: cert.pem + key.pem),
6
+ * para uso nas APIs que recebem { pfx, senha }.
7
+ * @param {string} certificatePem
8
+ * @param {string} privateKeyPem Chave sem senha
9
+ * @param {string} senha Senha do PFX gerado
10
+ * @returns {Buffer}
11
+ */
12
+ export function criarPfxDePem(certificatePem, privateKeyPem, senha) {
13
+ try {
14
+ const p12 = forge.pkcs12.toPkcs12Asn1(
15
+ forge.pki.privateKeyFromPem(privateKeyPem),
16
+ [forge.pki.certificateFromPem(certificatePem)],
17
+ senha,
18
+ { algorithm: '3des' }
19
+ );
20
+ return Buffer.from(forge.asn1.toDer(p12).getBytes(), 'binary');
21
+ } catch (error) {
22
+ throw new GhinfeError('Falha ao converter certificado/chave PEM em PFX', 'CERT_PEM_ERROR', error);
23
+ }
24
+ }
25
+
26
+ /**
27
+ * Carrega certificado A1 (.pfx / .p12) e retorna chave privada + certificado PEM.
28
+ * @param {Buffer|string} pfxBuffer
29
+ * @param {string} password
30
+ * @returns {{ privateKeyPem: string, certificatePem: string, certificate: forge.pki.Certificate }}
31
+ */
32
+ export function loadPfxCertificate(pfxBuffer, password) {
33
+ try {
34
+ const buffer = Buffer.isBuffer(pfxBuffer) ? pfxBuffer : Buffer.from(pfxBuffer);
35
+ const p12Asn1 = forge.asn1.fromDer(buffer.toString('binary'));
36
+ const p12 = forge.pkcs12.pkcs12FromAsn1(p12Asn1, password);
37
+
38
+ const certBags = p12.getBags({ bagType: forge.pki.oids.certBag });
39
+ const keyBags = p12.getBags({ bagType: forge.pki.oids.pkcs8ShroudedKeyBag });
40
+
41
+ const certBag = certBags[forge.pki.oids.certBag]?.[0];
42
+ const keyBag =
43
+ keyBags[forge.pki.oids.pkcs8ShroudedKeyBag]?.[0] ??
44
+ p12.getBags({ bagType: forge.pki.oids.keyBag })[forge.pki.oids.keyBag]?.[0];
45
+
46
+ if (!certBag?.cert || !keyBag?.key) {
47
+ throw new GhinfeError(
48
+ 'Certificado ou chave privada não encontrados no arquivo PFX',
49
+ 'CERT_NOT_FOUND'
50
+ );
51
+ }
52
+
53
+ const certificate = certBag.cert;
54
+ const privateKey = keyBag.key;
55
+
56
+ return {
57
+ privateKeyPem: forge.pki.privateKeyToPem(privateKey),
58
+ certificatePem: forge.pki.certificateToPem(certificate),
59
+ certificate,
60
+ };
61
+ } catch (error) {
62
+ if (error instanceof GhinfeError) throw error;
63
+ throw new GhinfeError(
64
+ 'Falha ao carregar certificado PFX. Verifique o arquivo e a senha.',
65
+ 'CERT_LOAD_ERROR',
66
+ error
67
+ );
68
+ }
69
+ }
@@ -1,117 +1,205 @@
1
- import { UF_IBGE } from '../constants/sefaz.js';
2
- import { GhinfeError } from '../errors/GhinfeError.js';
3
-
4
- /**
5
- * Gera código numérico aleatório de 8 dígitos (cNF).
6
- * @returns {string}
7
- */
8
- export function gerarCodigoNumerico() {
9
- const valor = Math.floor(Math.random() * 99_999_999) + 1;
10
- return String(valor).padStart(8, '0');
11
- }
12
-
13
- /**
14
- * Calcula dígito verificador da chave NF-e (módulo 11).
15
- * @param {string} chave43 Primeiros 43 dígitos da chave (sem DV)
16
- * @returns {string} Dígito verificador (0-9)
17
- */
18
- export function calcularDigitoVerificador(chave43) {
19
- if (!/^\d{43}$/.test(chave43)) {
20
- throw new GhinfeError('Chave deve conter exatamente 43 dígitos para cálculo do DV', 'CHAVE_INVALID_LENGTH');
21
- }
22
-
23
- const pesos = [2, 3, 4, 5, 6, 7, 8, 9];
24
- let soma = 0;
25
- let pesoIndex = 0;
26
-
27
- for (let i = chave43.length - 1; i >= 0; i--) {
28
- soma += parseInt(chave43[i], 10) * pesos[pesoIndex];
29
- pesoIndex = (pesoIndex + 1) % pesos.length;
30
- }
31
-
32
- const resto = soma % 11;
33
- return String(resto < 2 ? 0 : 11 - resto);
34
- }
35
-
36
- /**
37
- * Valida se a chave de acesso possui DV correto.
38
- * @param {string} chaveAcesso 44 dígitos
39
- * @returns {boolean}
40
- */
41
- export function validarChaveAcesso(chaveAcesso) {
42
- const chave = chaveAcesso.replace(/\D/g, '');
43
- if (chave.length !== 44) return false;
44
- const dvCalculado = calcularDigitoVerificador(chave.substring(0, 43));
45
- return dvCalculado === chave[43];
46
- }
47
-
48
- /**
49
- * Gera chave de acesso NF-e/NFC-e (44 dígitos) conforme layout SEFAZ.
50
- *
51
- * Composição: cUF(2) + AAMM(4) + CNPJ(14) + mod(2) + serie(3) + nNF(9) + tpEmis(1) + cNF(8) + cDV(1)
52
- *
53
- * @param {{
54
- * uf: string,
55
- * cnpj: string,
56
- * modelo?: string|number,
57
- * serie: number,
58
- * numero: number,
59
- * tipoEmissao?: number,
60
- * codigoNumerico?: string,
61
- * dataEmissao?: Date,
62
- * }} params
63
- * @returns {string}
64
- */
65
- export function gerarChaveAcesso(params) {
66
- const uf = params.uf?.toUpperCase();
67
- const cUF = UF_IBGE[uf];
68
-
69
- if (!cUF) {
70
- throw new GhinfeError(`UF inválida: ${params.uf}`, 'UF_INVALIDA');
71
- }
72
-
73
- const data = params.dataEmissao ?? new Date();
74
- const ano = String(data.getFullYear()).slice(-2);
75
- const mes = String(data.getMonth() + 1).padStart(2, '0');
76
- const aamm = `${ano}${mes}`;
77
-
78
- const cnpj = params.cnpj.replace(/\D/g, '');
79
- if (cnpj.length !== 14) {
80
- throw new GhinfeError('CNPJ deve conter 14 dígitos', 'CNPJ_INVALIDO');
81
- }
82
-
83
- const modelo = String(params.modelo ?? '55').padStart(2, '0');
84
- const serie = String(params.serie).padStart(3, '0');
85
- const numero = String(params.numero).padStart(9, '0');
86
- const tpEmis = String(params.tipoEmissao ?? 1);
87
- const cNF = (params.codigoNumerico ?? gerarCodigoNumerico()).padStart(8, '0');
88
-
89
- const chave43 = `${cUF}${aamm}${cnpj}${modelo}${serie}${numero}${tpEmis}${cNF}`;
90
- const cDV = calcularDigitoVerificador(chave43);
91
-
92
- return chave43 + cDV;
93
- }
94
-
95
- /**
96
- * Extrai campos da chave de acesso para conferência.
97
- * @param {string} chaveAcesso
98
- */
99
- export function parseChaveAcesso(chaveAcesso) {
100
- const chave = chaveAcesso.replace(/\D/g, '');
101
- if (chave.length !== 44) {
102
- throw new GhinfeError('Chave de acesso deve ter 44 dígitos', 'CHAVE_INVALID_LENGTH');
103
- }
104
-
105
- return {
106
- cUF: chave.substring(0, 2),
107
- aamm: chave.substring(2, 6),
108
- cnpj: chave.substring(6, 20),
109
- modelo: chave.substring(20, 22),
110
- serie: parseInt(chave.substring(22, 25), 10),
111
- numero: parseInt(chave.substring(25, 34), 10),
112
- tipoEmissao: parseInt(chave[34], 10),
113
- codigoNumerico: chave.substring(35, 43),
114
- digitoVerificador: chave[43],
115
- valida: validarChaveAcesso(chave),
116
- };
117
- }
1
+ import { UF_IBGE } from '../constants/sefaz.js';
2
+ import { GhinfeError } from '../errors/GhinfeError.js';
3
+
4
+ /**
5
+ * Gera código numérico aleatório de 8 dígitos (cNF).
6
+ * @returns {string}
7
+ */
8
+ export function gerarCodigoNumerico() {
9
+ const valor = Math.floor(Math.random() * 99_999_999) + 1;
10
+ return String(valor).padStart(8, '0');
11
+ }
12
+
13
+ /**
14
+ * Calcula dígito verificador da chave NF-e (módulo 11).
15
+ * @param {string} chave43 Primeiros 43 dígitos da chave (sem DV)
16
+ * @returns {string} Dígito verificador (0-9)
17
+ */
18
+ export function calcularDigitoVerificador(chave43) {
19
+ if (!/^\d{43}$/.test(chave43)) {
20
+ throw new GhinfeError('Chave deve conter exatamente 43 dígitos para cálculo do DV', 'CHAVE_INVALID_LENGTH');
21
+ }
22
+
23
+ const pesos = [2, 3, 4, 5, 6, 7, 8, 9];
24
+ let soma = 0;
25
+ let pesoIndex = 0;
26
+
27
+ for (let i = chave43.length - 1; i >= 0; i--) {
28
+ soma += parseInt(chave43[i], 10) * pesos[pesoIndex];
29
+ pesoIndex = (pesoIndex + 1) % pesos.length;
30
+ }
31
+
32
+ const resto = soma % 11;
33
+ return String(resto < 2 ? 0 : 11 - resto);
34
+ }
35
+
36
+ /**
37
+ * Valida se a chave de acesso possui DV correto.
38
+ * @param {string} chaveAcesso 44 dígitos
39
+ * @returns {boolean}
40
+ */
41
+ export function validarChaveAcesso(chaveAcesso) {
42
+ const chave = chaveAcesso.replace(/\D/g, '');
43
+ if (chave.length !== 44) return false;
44
+ const dvCalculado = calcularDigitoVerificador(chave.substring(0, 43));
45
+ return dvCalculado === chave[43];
46
+ }
47
+
48
+ /**
49
+ * Gera chave de acesso NF-e/NFC-e (44 dígitos) conforme layout SEFAZ.
50
+ *
51
+ * Composição: cUF(2) + AAMM(4) + CNPJ(14) + mod(2) + serie(3) + nNF(9) + tpEmis(1) + cNF(8) + cDV(1)
52
+ *
53
+ * @param {{
54
+ * uf: string,
55
+ * cnpj: string,
56
+ * modelo?: string|number,
57
+ * serie: number,
58
+ * numero: number,
59
+ * tipoEmissao?: number,
60
+ * codigoNumerico?: string,
61
+ * dataEmissao?: Date,
62
+ * }} params
63
+ * @returns {string}
64
+ */
65
+ export function gerarChaveAcesso(params) {
66
+ const uf = params.uf?.toUpperCase();
67
+ const cUF = UF_IBGE[uf];
68
+
69
+ if (!cUF) {
70
+ throw new GhinfeError(`UF inválida: ${params.uf}`, 'UF_INVALIDA');
71
+ }
72
+
73
+ const data = params.dataEmissao ?? new Date();
74
+ const ano = String(data.getFullYear()).slice(-2);
75
+ const mes = String(data.getMonth() + 1).padStart(2, '0');
76
+ const aamm = `${ano}${mes}`;
77
+
78
+ const cnpj = params.cnpj.replace(/\D/g, '');
79
+ if (cnpj.length !== 14) {
80
+ throw new GhinfeError('CNPJ deve conter 14 dígitos', 'CNPJ_INVALIDO');
81
+ }
82
+
83
+ const modelo = String(params.modelo ?? '55').padStart(2, '0');
84
+ const serie = String(params.serie).padStart(3, '0');
85
+ const numero = String(params.numero).padStart(9, '0');
86
+ const tpEmis = String(params.tipoEmissao ?? 1);
87
+ const cNF = (params.codigoNumerico ?? gerarCodigoNumerico()).padStart(8, '0');
88
+
89
+ const chave43 = `${cUF}${aamm}${cnpj}${modelo}${serie}${numero}${tpEmis}${cNF}`;
90
+ const cDV = calcularDigitoVerificador(chave43);
91
+
92
+ return chave43 + cDV;
93
+ }
94
+
95
+ /**
96
+ * Gera código numérico da DC-e (cDC, 6 dígitos).
97
+ * A SEFAZ rejeita (897) sequências repetidas (000000, 111111...) e cDC igual a nDC.
98
+ * @param {number} [numero] nDC da declaração
99
+ * @returns {string}
100
+ */
101
+ export function gerarCodigoNumericoDCe(numero) {
102
+ for (;;) {
103
+ const cDC = String(Math.floor(Math.random() * 1_000_000)).padStart(6, '0');
104
+ if (/^(\d)\1{5}$/.test(cDC)) continue;
105
+ if (numero !== undefined && Number(cDC) === Number(numero)) continue;
106
+ return cDC;
107
+ }
108
+ }
109
+
110
+ /**
111
+ * Gera chave de acesso da DC-e (modelo 99, 44 dígitos).
112
+ *
113
+ * Composição: cUF(2) + AAMM(4) + CNPJ(14) + mod(2) + serie(3) + nDC(9) + tpEmis(1)
114
+ * + tpEmit(1) + nSiteAutoriz(1) + cDC(6) + cDV(1)
115
+ *
116
+ * O CNPJ é o de quem emite/assina: Fisco, Marketplace, Transportadora, ECT
117
+ * ou o próprio usuário emitente (emissão própria).
118
+ *
119
+ * @param {{
120
+ * uf: string,
121
+ * cnpj: string,
122
+ * serie: number,
123
+ * numero: number,
124
+ * tipoEmitente: number,
125
+ * tipoEmissao?: number,
126
+ * siteAutorizador?: number,
127
+ * codigoNumerico?: string,
128
+ * dataEmissao?: Date,
129
+ * }} params
130
+ * @returns {string}
131
+ */
132
+ export function gerarChaveAcessoDCe(params) {
133
+ const cUF = UF_IBGE[params.uf?.toUpperCase()];
134
+ if (!cUF) {
135
+ throw new GhinfeError(`UF inválida: ${params.uf}`, 'UF_INVALIDA');
136
+ }
137
+
138
+ const cnpj = params.cnpj.replace(/\D/g, '');
139
+ if (cnpj.length !== 14) {
140
+ throw new GhinfeError('CNPJ deve conter 14 dígitos', 'CNPJ_INVALIDO');
141
+ }
142
+
143
+ const data = params.dataEmissao ?? new Date();
144
+ const aamm = `${String(data.getFullYear()).slice(-2)}${String(data.getMonth() + 1).padStart(2, '0')}`;
145
+
146
+ const serie = String(params.serie).padStart(3, '0');
147
+ const numero = String(params.numero).padStart(9, '0');
148
+ const tpEmis = String(params.tipoEmissao ?? 1);
149
+ const tpEmit = String(params.tipoEmitente);
150
+ const nSiteAutoriz = String(params.siteAutorizador ?? 0);
151
+ const cDC = (params.codigoNumerico ?? gerarCodigoNumericoDCe(params.numero)).padStart(6, '0');
152
+
153
+ const chave43 = `${cUF}${aamm}${cnpj}99${serie}${numero}${tpEmis}${tpEmit}${nSiteAutoriz}${cDC}`;
154
+ return chave43 + calcularDigitoVerificador(chave43);
155
+ }
156
+
157
+ /**
158
+ * Extrai campos da chave de acesso da DC-e.
159
+ * @param {string} chaveAcesso
160
+ */
161
+ export function parseChaveAcessoDCe(chaveAcesso) {
162
+ const chave = chaveAcesso.replace(/\D/g, '');
163
+ if (chave.length !== 44) {
164
+ throw new GhinfeError('Chave de acesso deve ter 44 dígitos', 'CHAVE_INVALID_LENGTH');
165
+ }
166
+
167
+ return {
168
+ cUF: chave.substring(0, 2),
169
+ aamm: chave.substring(2, 6),
170
+ cnpj: chave.substring(6, 20),
171
+ modelo: chave.substring(20, 22),
172
+ serie: parseInt(chave.substring(22, 25), 10),
173
+ numero: parseInt(chave.substring(25, 34), 10),
174
+ tipoEmissao: parseInt(chave[34], 10),
175
+ tipoEmitente: parseInt(chave[35], 10),
176
+ siteAutorizador: parseInt(chave[36], 10),
177
+ codigoNumerico: chave.substring(37, 43),
178
+ digitoVerificador: chave[43],
179
+ valida: validarChaveAcesso(chave),
180
+ };
181
+ }
182
+
183
+ /**
184
+ * Extrai campos da chave de acesso para conferência.
185
+ * @param {string} chaveAcesso
186
+ */
187
+ export function parseChaveAcesso(chaveAcesso) {
188
+ const chave = chaveAcesso.replace(/\D/g, '');
189
+ if (chave.length !== 44) {
190
+ throw new GhinfeError('Chave de acesso deve ter 44 dígitos', 'CHAVE_INVALID_LENGTH');
191
+ }
192
+
193
+ return {
194
+ cUF: chave.substring(0, 2),
195
+ aamm: chave.substring(2, 6),
196
+ cnpj: chave.substring(6, 20),
197
+ modelo: chave.substring(20, 22),
198
+ serie: parseInt(chave.substring(22, 25), 10),
199
+ numero: parseInt(chave.substring(25, 34), 10),
200
+ tipoEmissao: parseInt(chave[34], 10),
201
+ codigoNumerico: chave.substring(35, 43),
202
+ digitoVerificador: chave[43],
203
+ valida: validarChaveAcesso(chave),
204
+ };
205
+ }
@@ -0,0 +1,67 @@
1
+ import { GhinfeError } from '../errors/GhinfeError.js';
2
+
3
+ /**
4
+ * Larguras (barra, espaço, barra, ...) de cada símbolo Code 128, em módulos.
5
+ * Índice = valor do símbolo; 105 = Start C, 106 = Stop.
6
+ */
7
+ const PADROES = [
8
+ '212222', '222122', '222221', '121223', '121322', '131222', '122213', '122312', '132212', '221213',
9
+ '221312', '231212', '112232', '122132', '122231', '113222', '123122', '123221', '223211', '221132',
10
+ '221231', '213212', '223112', '312131', '311222', '321122', '321221', '312212', '322112', '322211',
11
+ '212123', '212321', '232121', '111323', '131123', '131321', '112313', '132113', '132311', '211313',
12
+ '231113', '231311', '112133', '112331', '132131', '113123', '113321', '133121', '313121', '211331',
13
+ '231131', '213113', '213311', '213131', '311123', '311321', '331121', '312113', '312311', '332111',
14
+ '314111', '221411', '431111', '111224', '111422', '121124', '121421', '141122', '141221', '112214',
15
+ '112412', '122114', '122411', '142112', '142211', '241211', '221114', '413111', '241112', '134111',
16
+ '111242', '121142', '121241', '114212', '124112', '124211', '411212', '421112', '421211', '212141',
17
+ '214121', '412121', '111143', '111341', '131141', '114113', '114311', '411113', '411311', '113141',
18
+ '114131', '311141', '411131', '211412', '211214', '211232', '2331112',
19
+ ];
20
+
21
+ const START_C = 105;
22
+ const STOP = 106;
23
+
24
+ /**
25
+ * Codifica dígitos em Code 128 subconjunto C (pares numéricos), com DV módulo 103.
26
+ * @param {string} digitos Quantidade par de dígitos (ex.: chave de acesso, 44)
27
+ * @returns {{ simbolos: number[], dv: number, larguras: number[] }}
28
+ */
29
+ export function codificarCode128C(digitos) {
30
+ if (!/^\d+$/.test(digitos) || digitos.length % 2 !== 0) {
31
+ throw new GhinfeError('Code 128C exige quantidade par de dígitos', 'CODE128C_INVALIDO');
32
+ }
33
+
34
+ const dados = digitos.match(/\d{2}/g).map(Number);
35
+ const soma = dados.reduce((acc, valor, i) => acc + valor * (i + 1), START_C);
36
+ const dv = soma % 103;
37
+ const simbolos = [START_C, ...dados, dv, STOP];
38
+ const larguras = simbolos.flatMap((s) => [...PADROES[s]].map(Number));
39
+
40
+ return { simbolos, dv, larguras };
41
+ }
42
+
43
+ /**
44
+ * Gera SVG do código de barras Code 128C (barras em unidades de módulo, escalável via CSS).
45
+ * @param {string} digitos
46
+ * @param {{ altura?: number, margem?: number }} [opcoes] altura e margem clara em módulos (margem mínima: 10)
47
+ * @returns {string}
48
+ */
49
+ export function gerarSvgCode128C(digitos, opcoes = {}) {
50
+ const altura = opcoes.altura ?? 50;
51
+ const margem = Math.max(opcoes.margem ?? 10, 10);
52
+ const { larguras } = codificarCode128C(digitos);
53
+
54
+ let x = margem;
55
+ const barras = [];
56
+ larguras.forEach((largura, i) => {
57
+ if (i % 2 === 0) barras.push(`<rect x="${x}" y="0" width="${largura}" height="${altura}"/>`);
58
+ x += largura;
59
+ });
60
+
61
+ const total = x + margem;
62
+ return (
63
+ `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 ${total} ${altura}" preserveAspectRatio="none" ` +
64
+ `shape-rendering="crispEdges" role="img" aria-label="${digitos}">` +
65
+ `<rect width="${total}" height="${altura}" fill="#fff"/><g fill="#000">${barras.join('')}</g></svg>`
66
+ );
67
+ }
@@ -1,26 +1,26 @@
1
- /**
2
- * Formata data para padrão SEFAZ: AAAA-MM-DDTHH:mm:ss-03:00
3
- * @param {Date} [date]
4
- * @returns {string}
5
- */
6
- export function formatSefazDateTime(date = new Date()) {
7
- const pad = (n) => String(n).padStart(2, '0');
8
- const offset = -date.getTimezoneOffset();
9
- const sign = offset >= 0 ? '+' : '-';
10
- const abs = Math.abs(offset);
11
- const tz = `${sign}${pad(Math.floor(abs / 60))}:${pad(abs % 60)}`;
12
-
13
- return (
14
- `${date.getFullYear()}-${pad(date.getMonth() + 1)}-${pad(date.getDate())}` +
15
- `T${pad(date.getHours())}:${pad(date.getMinutes())}:${pad(date.getSeconds())}${tz}`
16
- );
17
- }
18
-
19
- /**
20
- * @param {Date} [date]
21
- * @returns {string} AAAA-MM-DD
22
- */
23
- export function formatSefazDate(date = new Date()) {
24
- const pad = (n) => String(n).padStart(2, '0');
25
- return `${date.getFullYear()}-${pad(date.getMonth() + 1)}-${pad(date.getDate())}`;
26
- }
1
+ /**
2
+ * Formata data para padrão SEFAZ: AAAA-MM-DDTHH:mm:ss-03:00
3
+ * @param {Date} [date]
4
+ * @returns {string}
5
+ */
6
+ export function formatSefazDateTime(date = new Date()) {
7
+ const pad = (n) => String(n).padStart(2, '0');
8
+ const offset = -date.getTimezoneOffset();
9
+ const sign = offset >= 0 ? '+' : '-';
10
+ const abs = Math.abs(offset);
11
+ const tz = `${sign}${pad(Math.floor(abs / 60))}:${pad(abs % 60)}`;
12
+
13
+ return (
14
+ `${date.getFullYear()}-${pad(date.getMonth() + 1)}-${pad(date.getDate())}` +
15
+ `T${pad(date.getHours())}:${pad(date.getMinutes())}:${pad(date.getSeconds())}${tz}`
16
+ );
17
+ }
18
+
19
+ /**
20
+ * @param {Date} [date]
21
+ * @returns {string} AAAA-MM-DD
22
+ */
23
+ export function formatSefazDate(date = new Date()) {
24
+ const pad = (n) => String(n).padStart(2, '0');
25
+ return `${date.getFullYear()}-${pad(date.getMonth() + 1)}-${pad(date.getDate())}`;
26
+ }
@@ -1,44 +1,44 @@
1
- import { DOMParser, XMLSerializer } from '@xmldom/xmldom';
2
-
3
- /**
4
- * @param {string} xml
5
- * @returns {string}
6
- */
7
- export function minifyXml(xml) {
8
- return xml.replace(/>\s+</g, '><').trim();
9
- }
10
-
11
- /**
12
- * @param {string} xml
13
- * @returns {Document}
14
- */
15
- export function parseXml(xml) {
16
- const parser = new DOMParser();
17
- const doc = parser.parseFromString(xml, 'text/xml');
18
- const parseError = doc.getElementsByTagName('parsererror')[0];
19
- if (parseError) {
20
- throw new Error(`XML inválido: ${parseError.textContent}`);
21
- }
22
- return doc;
23
- }
24
-
25
- /**
26
- * @param {Document|Element} node
27
- * @returns {string}
28
- */
29
- export function serializeXml(node) {
30
- return new XMLSerializer().serializeToString(node);
31
- }
32
-
33
- /**
34
- * @param {string} value
35
- * @returns {string}
36
- */
37
- export function escapeXml(value) {
38
- return String(value)
39
- .replace(/&/g, '&amp;')
40
- .replace(/</g, '&lt;')
41
- .replace(/>/g, '&gt;')
42
- .replace(/"/g, '&quot;')
43
- .replace(/'/g, '&apos;');
44
- }
1
+ import { DOMParser, XMLSerializer } from '@xmldom/xmldom';
2
+
3
+ /**
4
+ * @param {string} xml
5
+ * @returns {string}
6
+ */
7
+ export function minifyXml(xml) {
8
+ return xml.replace(/>\s+</g, '><').trim();
9
+ }
10
+
11
+ /**
12
+ * @param {string} xml
13
+ * @returns {Document}
14
+ */
15
+ export function parseXml(xml) {
16
+ const parser = new DOMParser();
17
+ const doc = parser.parseFromString(xml, 'text/xml');
18
+ const parseError = doc.getElementsByTagName('parsererror')[0];
19
+ if (parseError) {
20
+ throw new Error(`XML inválido: ${parseError.textContent}`);
21
+ }
22
+ return doc;
23
+ }
24
+
25
+ /**
26
+ * @param {Document|Element} node
27
+ * @returns {string}
28
+ */
29
+ export function serializeXml(node) {
30
+ return new XMLSerializer().serializeToString(node);
31
+ }
32
+
33
+ /**
34
+ * @param {string} value
35
+ * @returns {string}
36
+ */
37
+ export function escapeXml(value) {
38
+ return String(value)
39
+ .replace(/&/g, '&amp;')
40
+ .replace(/</g, '&lt;')
41
+ .replace(/>/g, '&gt;')
42
+ .replace(/"/g, '&quot;')
43
+ .replace(/'/g, '&apos;');
44
+ }