@devlas/dte-sii 2.5.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 (60) hide show
  1. package/BoletaService.js +109 -0
  2. package/CAF.js +173 -0
  3. package/CafSolicitor.js +380 -0
  4. package/Certificado.js +123 -0
  5. package/ConsumoFolio.js +376 -0
  6. package/DTE.js +399 -0
  7. package/EnviadorSII.js +1304 -0
  8. package/Envio.js +196 -0
  9. package/FolioRegistry.js +553 -0
  10. package/FolioService.js +703 -0
  11. package/LICENSE +27 -0
  12. package/LibroBase.js +134 -0
  13. package/LibroCompraVenta.js +205 -0
  14. package/LibroGuia.js +225 -0
  15. package/README.md +239 -0
  16. package/Signer.js +94 -0
  17. package/SiiCertificacion.js +1189 -0
  18. package/SiiPortalAuth.js +460 -0
  19. package/SiiSession.js +499 -0
  20. package/cert/BoletaCert.js +731 -0
  21. package/cert/CertFolioHelper.js +185 -0
  22. package/cert/CertRunner.js +2658 -0
  23. package/cert/ConfigLoader.js +133 -0
  24. package/cert/IntercambioCert.js +429 -0
  25. package/cert/LibroCompras.js +359 -0
  26. package/cert/LibroGuias.js +171 -0
  27. package/cert/LibroVentas.js +153 -0
  28. package/cert/MuestrasImpresas.js +676 -0
  29. package/cert/SetBase.js +321 -0
  30. package/cert/SetBasico.js +413 -0
  31. package/cert/SetCompra.js +472 -0
  32. package/cert/SetExenta.js +490 -0
  33. package/cert/SetGuia.js +283 -0
  34. package/cert/SetParser.js +1184 -0
  35. package/cert/SetsProvider.js +499 -0
  36. package/cert/Simulacion.js +521 -0
  37. package/cert/comunaOficina.js +460 -0
  38. package/cert/index.js +124 -0
  39. package/cert/types.js +330 -0
  40. package/dte-sii.d.ts +458 -0
  41. package/index.js +428 -0
  42. package/package.json +48 -0
  43. package/utils/c14n.js +275 -0
  44. package/utils/calculo.js +396 -0
  45. package/utils/config.js +276 -0
  46. package/utils/constants.js +302 -0
  47. package/utils/emisor.js +174 -0
  48. package/utils/endpoints.js +225 -0
  49. package/utils/error.js +235 -0
  50. package/utils/index.js +339 -0
  51. package/utils/logger.js +239 -0
  52. package/utils/pfx.js +203 -0
  53. package/utils/receptor.js +218 -0
  54. package/utils/referencia.js +169 -0
  55. package/utils/resolucion.js +119 -0
  56. package/utils/rut.js +169 -0
  57. package/utils/sanitize.js +124 -0
  58. package/utils/tokenCache.js +214 -0
  59. package/utils/xml.js +358 -0
  60. package/utils.js +4 -0
@@ -0,0 +1,133 @@
1
+ // Copyright (c) 2026 Devlas SpA — https://devlas.cl
2
+ // Licencia MIT. Ver archivo LICENSE para mas detalles.
3
+ /**
4
+ * ConfigLoader - Carga configuración de certificación desde .env
5
+ *
6
+ * Centraliza la carga de configuración para que los runners (CLI)
7
+ * no necesiten módulos intermedios.
8
+ *
9
+ * @module dte-sii/cert/ConfigLoader
10
+ */
11
+
12
+ const path = require('path');
13
+ const fs = require('fs');
14
+
15
+ /**
16
+ * Carga configuración desde .env y retorna objetos estructurados
17
+ *
18
+ * @param {Object} options
19
+ * @param {string} [options.envPath] - Ruta al archivo .env (por defecto busca en static/nodejs/.env)
20
+ * @param {string} [options.baseDir] - Directorio base para resolver rutas relativas
21
+ * @returns {Object} Configuración estructurada { EMISOR, RECEPTOR, CERT_PATH, CERT_PASS, AMBIENTE, BASE_DIR, ... }
22
+ */
23
+ function loadConfig(options = {}) {
24
+ // Determinar baseDir
25
+ const baseDir = options.baseDir || path.resolve(__dirname, '..', '..', '..');
26
+
27
+ // Cargar .env
28
+ const envPath = options.envPath || path.join(baseDir, '.env');
29
+
30
+ if (!fs.existsSync(envPath)) {
31
+ throw new Error(`Archivo .env no encontrado: ${envPath}\nCopia .env.example a .env y configura tus valores.`);
32
+ }
33
+
34
+ // Usar dotenv para cargar
35
+ require('dotenv').config({ path: envPath });
36
+
37
+ // Resolver rutas relativas
38
+ const resolvePath = (filePath) => {
39
+ if (!filePath) return null;
40
+ return path.isAbsolute(filePath) ? filePath : path.resolve(baseDir, filePath);
41
+ };
42
+
43
+ // Validar variables requeridas
44
+ const required = ['CERT_PATH', 'EMISOR_RUT', 'EMISOR_RAZON_SOCIAL'];
45
+ const missing = required.filter(v => !process.env[v]);
46
+ if (missing.length > 0) {
47
+ throw new Error(`Variables de entorno faltantes: ${missing.join(', ')}`);
48
+ }
49
+
50
+ // Construir objetos de configuración
51
+ const AMBIENTE = process.env.SII_AMBIENTE || 'certificacion';
52
+
53
+ const fch_resol = process.env.FECHA_RESOLUCION || new Date().toISOString().slice(0, 10);
54
+ const nro_resol = parseInt(process.env.NRO_RESOLUCION || '0', 10);
55
+
56
+ console.log(`[ConfigLoader] ✅ Resolución: NroResol=${nro_resol} FchResol=${fch_resol} (AMBIENTE=${AMBIENTE})`);
57
+
58
+ const EMISOR = {
59
+ rut: process.env.EMISOR_RUT,
60
+ razon_social: process.env.EMISOR_RAZON_SOCIAL,
61
+ giro: process.env.EMISOR_GIRO || 'ACTIVIDADES DE PROGRAMACION INFORMATICA',
62
+ acteco: process.env.EMISOR_ACTECO || '620200',
63
+ direccion: process.env.EMISOR_DIRECCION || '',
64
+ comuna: process.env.EMISOR_COMUNA || 'Santiago',
65
+ ciudad: process.env.EMISOR_CIUDAD || 'Santiago',
66
+ fch_resol,
67
+ nro_resol,
68
+ };
69
+
70
+ const RECEPTOR = {
71
+ rut: process.env.RECEPTOR_RUT || '66666666-6',
72
+ razon_social: process.env.RECEPTOR_RAZON_SOCIAL || 'CLIENTE TEST',
73
+ giro: process.env.RECEPTOR_GIRO || 'ACTIVIDADES VARIAS',
74
+ direccion: process.env.RECEPTOR_DIRECCION || 'AVENIDA PRINCIPAL 123',
75
+ comuna: process.env.RECEPTOR_COMUNA || 'Santiago',
76
+ ciudad: process.env.RECEPTOR_CIUDAD || 'Santiago',
77
+ };
78
+
79
+ const CERT_PATH = resolvePath(process.env.CERT_PATH);
80
+ const CERT_PASS = process.env.CERT_PASS || '';
81
+
82
+ // Configuración SII
83
+ const SII_CONFIG = {
84
+ sendDelayMs: parseInt(process.env.SII_SEND_DELAY_MS || '8000', 10),
85
+ sendRetries: parseInt(process.env.SII_SEND_RETRIES || '6', 10),
86
+ cafRetries: parseInt(process.env.SII_CAF_RETRIES || '3', 10),
87
+ reuseFolios: process.env.SII_REUSE_FOLIOS === 'true',
88
+ };
89
+
90
+ // Enviador (quien firma/envía)
91
+ const ENVIADOR = {
92
+ rut: process.env.ENVIADOR_RUT || EMISOR.rut,
93
+ };
94
+
95
+ return {
96
+ EMISOR,
97
+ RECEPTOR,
98
+ CERT_PATH,
99
+ CERT_PASS,
100
+ AMBIENTE,
101
+ BASE_DIR: baseDir,
102
+ SII_CONFIG,
103
+ ENVIADOR,
104
+
105
+ // Helper para crear config de CertRunner
106
+ toCertRunnerConfig() {
107
+ return {
108
+ certificado: { path: CERT_PATH, password: CERT_PASS },
109
+ emisor: EMISOR,
110
+ receptor: RECEPTOR,
111
+ ambiente: AMBIENTE,
112
+ resolucion: { fecha: EMISOR.fch_resol, numero: EMISOR.nro_resol },
113
+ baseDir: baseDir,
114
+ debugDir: path.join(baseDir, 'debug', 'cert-v2'),
115
+ };
116
+ },
117
+ };
118
+ }
119
+
120
+ /**
121
+ * Imprime banner de sección para CLI
122
+ * @param {string} titulo
123
+ */
124
+ function printBanner(titulo) {
125
+ console.log('\n' + '═'.repeat(60));
126
+ console.log(` ${titulo}`);
127
+ console.log('═'.repeat(60));
128
+ }
129
+
130
+ module.exports = {
131
+ loadConfig,
132
+ printBanner,
133
+ };
@@ -0,0 +1,429 @@
1
+ // Copyright (c) 2026 Devlas SpA — https://devlas.cl
2
+ // Licencia MIT. Ver archivo LICENSE para mas detalles.
3
+ /**
4
+ * IntercambioCert.js
5
+ *
6
+ * Módulo para generar respuestas de Intercambio de Información DTE
7
+ * - Respuesta Recepción de Envío
8
+ * - Respuesta Aprobación Comercial (ResultadoDTE)
9
+ * - Envío de Recibos (Recepción de Mercaderías)
10
+ *
11
+ * @module dte-sii/cert/IntercambioCert
12
+ */
13
+
14
+ const fs = require('fs');
15
+ const path = require('path');
16
+ const { XMLParser, XMLBuilder } = require('fast-xml-parser');
17
+ const { SignedXml } = require('xml-crypto');
18
+
19
+ const DECLARACION_RECIBO = 'El acuse de recibo que se declara en este acto, de acuerdo a lo dispuesto en la letra b) del Art. 4, y la letra c) del Art. 5 de la Ley 19.983, acredita que la entrega de mercaderias o servicio(s) prestado(s) ha(n) sido recibido(s).';
20
+
21
+ class IntercambioCert {
22
+ /**
23
+ * @param {Object} options
24
+ * @param {Object} options.certificado - Instancia de Certificado
25
+ * @param {Object} options.emisor - Datos del emisor { rut, razonSocial }
26
+ * @param {Object} [options.contacto] - Datos de contacto { nombre, mail, fono }
27
+ * @param {string} [options.debugDir] - Directorio para guardar XMLs
28
+ */
29
+ constructor({ certificado, emisor, contacto = {}, debugDir }) {
30
+ this.certificado = certificado;
31
+ this.emisor = emisor;
32
+ this.contacto = contacto;
33
+ this.debugDir = debugDir;
34
+ }
35
+
36
+ /**
37
+ * Obtiene las utilidades del paquete
38
+ * @private
39
+ */
40
+ _lib() {
41
+ const { formatRut, normalizeArray, sanitizeSiiText } = require('../index');
42
+ return { formatRut, normalizeArray, sanitizeSiiText };
43
+ }
44
+
45
+ /**
46
+ * Formatea fecha/hora en formato SII
47
+ * @private
48
+ */
49
+ _formatSiiDateTime(date = new Date()) {
50
+ const pad = (n) => String(n).padStart(2, '0');
51
+ return `${date.getFullYear()}-${pad(date.getMonth() + 1)}-${pad(date.getDate())}T${pad(date.getHours())}:${pad(date.getMinutes())}:${pad(date.getSeconds())}`;
52
+ }
53
+
54
+ /**
55
+ * Firma un XML con el certificado
56
+ * @private
57
+ */
58
+ _signXml({ xml, referenceXpath, insertAfterXpath }) {
59
+ const sig = new SignedXml();
60
+ sig.privateKey = this.certificado.getPrivateKeyPem();
61
+ sig.publicCert = this.certificado.getCertificatePem();
62
+ sig.canonicalizationAlgorithm = 'http://www.w3.org/TR/2001/REC-xml-c14n-20010315';
63
+ sig.signatureAlgorithm = 'http://www.w3.org/2000/09/xmldsig#rsa-sha1';
64
+ sig.addReference({
65
+ xpath: referenceXpath,
66
+ transforms: ['http://www.w3.org/2000/09/xmldsig#enveloped-signature'],
67
+ digestAlgorithm: 'http://www.w3.org/2000/09/xmldsig#sha1',
68
+ });
69
+ sig.getKeyInfoContent = () => {
70
+ const certBase64 = this.certificado.getCertificateBase64();
71
+ const modulus = this.certificado.getModulus();
72
+ const exponent = this.certificado.getExponent();
73
+ return `<KeyValue><RSAKeyValue><Modulus>${modulus}</Modulus><Exponent>${exponent}</Exponent></RSAKeyValue></KeyValue><X509Data><X509Certificate>${certBase64}</X509Certificate></X509Data>`;
74
+ };
75
+
76
+ sig.computeSignature(xml, {
77
+ location: {
78
+ reference: insertAfterXpath || referenceXpath,
79
+ action: 'after',
80
+ },
81
+ });
82
+
83
+ return sig.getSignedXml();
84
+ }
85
+
86
+ /**
87
+ * Parsea un EnvioDTE XML y extrae los documentos
88
+ * @param {string} xml - XML del EnvioDTE
89
+ * @returns {Object} Metadatos del envío
90
+ */
91
+ parseEnvioDTE(xml) {
92
+ const { normalizeArray } = this._lib();
93
+
94
+ const parser = new XMLParser({
95
+ ignoreAttributes: false,
96
+ attributeNamePrefix: '@_',
97
+ trimValues: true,
98
+ parseTagValue: true,
99
+ });
100
+
101
+ const data = parser.parse(xml);
102
+ const envio = data?.EnvioDTE || data?.EnvioDTEB || data?.EnvioDTETraslado || data?.EnvioBOLETA || {};
103
+ const setDte = envio?.SetDTE || data?.SetDTE || {};
104
+ const caratula = setDte?.Caratula || {};
105
+ const setId = setDte?.['@_ID'] || setDte?.ID || 'SetDTE';
106
+
107
+ const rutEmisor = caratula?.RutEmisor || null;
108
+ const rutEnvia = caratula?.RutEnvia || null;
109
+ const rutReceptor = caratula?.RutReceptor || null;
110
+
111
+ const dtes = normalizeArray(setDte?.DTE);
112
+ const documentos = dtes.map((dte) => dte?.Documento || dte?.DTE?.Documento || dte?.DTE?.Documento || {}).filter(Boolean);
113
+
114
+ const items = documentos.map((doc) => {
115
+ const encabezado = doc?.Encabezado || {};
116
+ const idDoc = encabezado?.IdDoc || {};
117
+ const emisor = encabezado?.Emisor || {};
118
+ const receptor = encabezado?.Receptor || {};
119
+ const totales = encabezado?.Totales || {};
120
+
121
+ return {
122
+ tipoDTE: parseInt(idDoc?.TipoDTE ?? idDoc?.TpoDoc ?? 0, 10) || 0,
123
+ folio: parseInt(idDoc?.Folio ?? 0, 10) || 0,
124
+ fchEmis: idDoc?.FchEmis || null,
125
+ rutEmisor: emisor?.RUTEmisor || emisor?.RutEmisor || rutEmisor || null,
126
+ rutRecep: receptor?.RUTRecep || receptor?.RutRecep || rutReceptor || null,
127
+ mntTotal: parseInt(totales?.MntTotal ?? 0, 10) || 0,
128
+ };
129
+ });
130
+
131
+ return {
132
+ setId,
133
+ rutEmisor,
134
+ rutEnvia,
135
+ rutReceptor,
136
+ items,
137
+ };
138
+ }
139
+
140
+ /**
141
+ * Genera la Respuesta de Recepción de Envío
142
+ * @param {Object} meta - Metadatos del EnvioDTE parseado
143
+ * @param {string} [envioNombre] - Nombre del envío
144
+ * @returns {string} XML firmado
145
+ */
146
+ generarRespuestaRecepcionEnvio(meta, envioNombre) {
147
+ const { formatRut, sanitizeSiiText } = this._lib();
148
+ const now = this._formatSiiDateTime();
149
+ const resultId = `R_ENVIO_${Date.now()}`;
150
+ const rutEmpresa = this.emisor.rut;
151
+
152
+ const recepcionDte = meta.items.map((item) => {
153
+ const ok = formatRut(item.rutRecep || '') === formatRut(rutEmpresa);
154
+ return {
155
+ TipoDTE: item.tipoDTE,
156
+ Folio: item.folio,
157
+ FchEmis: item.fchEmis,
158
+ RUTEmisor: formatRut(item.rutEmisor || ''),
159
+ RUTRecep: formatRut(item.rutRecep || ''),
160
+ MntTotal: item.mntTotal,
161
+ EstadoRecepDTE: ok ? 0 : 3,
162
+ RecepDTEGlosa: ok ? 'DTE Recibido OK.' : 'DTE No Recibido - Error en RUT Receptor.',
163
+ };
164
+ });
165
+
166
+ const data = {
167
+ RespuestaDTE: {
168
+ '@_xmlns': 'http://www.sii.cl/SiiDte',
169
+ '@_xmlns:xsi': 'http://www.w3.org/2001/XMLSchema-instance',
170
+ '@_xsi:schemaLocation': 'http://www.sii.cl/SiiDte RespuestaEnvioDTE_v10.xsd',
171
+ '@_version': '1.0',
172
+ Resultado: {
173
+ '@_ID': resultId,
174
+ Caratula: {
175
+ '@_version': '1.0',
176
+ RutResponde: formatRut(rutEmpresa),
177
+ RutRecibe: formatRut(meta.rutEmisor || ''),
178
+ IdRespuesta: 1,
179
+ NroDetalles: 1,
180
+ ...(this.contacto?.nombre ? { NmbContacto: sanitizeSiiText(this.contacto.nombre) } : {}),
181
+ ...(this.contacto?.mail ? { MailContacto: this.contacto.mail } : {}),
182
+ TmstFirmaResp: now,
183
+ },
184
+ RecepcionEnvio: {
185
+ NmbEnvio: envioNombre || `ENVIO_DTE_${Date.now()}`,
186
+ FchRecep: now,
187
+ CodEnvio: 1,
188
+ EnvioDTEID: meta.setId,
189
+ RutEmisor: formatRut(meta.rutEmisor || ''),
190
+ RutReceptor: formatRut(rutEmpresa),
191
+ EstadoRecepEnv: 0,
192
+ RecepEnvGlosa: 'Envío recibido conforme.',
193
+ NroDTE: meta.items.length,
194
+ RecepcionDTE: recepcionDte,
195
+ },
196
+ },
197
+ },
198
+ };
199
+
200
+ const builder = new XMLBuilder({ ignoreAttributes: false, attributeNamePrefix: '@_', format: false });
201
+ let xml = builder.build(data);
202
+
203
+ xml = this._signXml({
204
+ xml,
205
+ referenceXpath: `//*[local-name()='Resultado' and @ID='${resultId}']`,
206
+ insertAfterXpath: `//*[local-name()='Resultado' and @ID='${resultId}']`,
207
+ });
208
+
209
+ return xml;
210
+ }
211
+
212
+ /**
213
+ * Genera la Respuesta de Aprobación Comercial (ResultadoDTE)
214
+ * @param {Object} meta - Metadatos del EnvioDTE parseado
215
+ * @returns {string} XML firmado
216
+ */
217
+ generarRespuestaAprobacionComercial(meta) {
218
+ const { formatRut, sanitizeSiiText } = this._lib();
219
+ const now = this._formatSiiDateTime();
220
+ const resultId = `R_DTE_${Date.now()}`;
221
+ const rutEmpresa = this.emisor.rut;
222
+
223
+ const resultados = meta.items.map((item, idx) => {
224
+ const ok = formatRut(item.rutRecep || '') === formatRut(rutEmpresa);
225
+ return {
226
+ TipoDTE: item.tipoDTE,
227
+ Folio: item.folio,
228
+ FchEmis: item.fchEmis,
229
+ RUTEmisor: formatRut(item.rutEmisor || ''),
230
+ RUTRecep: formatRut(item.rutRecep || ''),
231
+ MntTotal: item.mntTotal,
232
+ CodEnvio: idx + 1,
233
+ EstadoDTE: ok ? 0 : 2,
234
+ EstadoDTEGlosa: ok ? 'DTE Aceptado OK' : 'DTE Rechazado',
235
+ ...(ok ? {} : { CodRchDsc: -1 }),
236
+ };
237
+ });
238
+
239
+ const data = {
240
+ RespuestaDTE: {
241
+ '@_xmlns': 'http://www.sii.cl/SiiDte',
242
+ '@_xmlns:xsi': 'http://www.w3.org/2001/XMLSchema-instance',
243
+ '@_xsi:schemaLocation': 'http://www.sii.cl/SiiDte RespuestaEnvioDTE_v10.xsd',
244
+ '@_version': '1.0',
245
+ Resultado: {
246
+ '@_ID': resultId,
247
+ Caratula: {
248
+ '@_version': '1.0',
249
+ RutResponde: formatRut(rutEmpresa),
250
+ RutRecibe: formatRut(meta.rutEmisor || ''),
251
+ IdRespuesta: 1,
252
+ NroDetalles: meta.items.length,
253
+ ...(this.contacto?.nombre ? { NmbContacto: sanitizeSiiText(this.contacto.nombre) } : {}),
254
+ ...(this.contacto?.mail ? { MailContacto: this.contacto.mail } : {}),
255
+ TmstFirmaResp: now,
256
+ },
257
+ ResultadoDTE: resultados,
258
+ },
259
+ },
260
+ };
261
+
262
+ const builder = new XMLBuilder({ ignoreAttributes: false, attributeNamePrefix: '@_', format: false });
263
+ let xml = builder.build(data);
264
+
265
+ xml = this._signXml({
266
+ xml,
267
+ referenceXpath: `//*[local-name()='Resultado' and @ID='${resultId}']`,
268
+ insertAfterXpath: `//*[local-name()='Resultado' and @ID='${resultId}']`,
269
+ });
270
+
271
+ return xml;
272
+ }
273
+
274
+ /**
275
+ * Genera el Envío de Recibos (Recepción de Mercaderías)
276
+ * @param {Object} meta - Metadatos del EnvioDTE parseado
277
+ * @returns {string} XML firmado
278
+ */
279
+ generarEnvioRecibos(meta) {
280
+ const { formatRut, sanitizeSiiText } = this._lib();
281
+ const now = this._formatSiiDateTime();
282
+ const setId = `SetRecibos_${Date.now()}`;
283
+ const rutEmpresa = this.emisor.rut;
284
+ const rutFirma = this.certificado.rut || rutEmpresa;
285
+
286
+ const recibos = meta.items.map((item, idx) => {
287
+ const docId = `REC-${idx + 1}`;
288
+ return {
289
+ '@_version': '1.0',
290
+ DocumentoRecibo: {
291
+ '@_ID': docId,
292
+ TipoDoc: item.tipoDTE,
293
+ Folio: item.folio,
294
+ FchEmis: item.fchEmis,
295
+ RUTEmisor: formatRut(item.rutEmisor || ''),
296
+ RUTRecep: formatRut(item.rutRecep || ''),
297
+ MntTotal: item.mntTotal,
298
+ Recinto: 'Oficina central',
299
+ RutFirma: formatRut(rutFirma),
300
+ Declaracion: DECLARACION_RECIBO,
301
+ TmstFirmaRecibo: now,
302
+ },
303
+ };
304
+ });
305
+
306
+ const data = {
307
+ EnvioRecibos: {
308
+ '@_xmlns': 'http://www.sii.cl/SiiDte',
309
+ '@_xmlns:xsi': 'http://www.w3.org/2001/XMLSchema-instance',
310
+ '@_xsi:schemaLocation': 'http://www.sii.cl/SiiDte EnvioRecibos_v10.xsd',
311
+ '@_version': '1.0',
312
+ SetRecibos: {
313
+ '@_ID': setId,
314
+ Caratula: {
315
+ '@_version': '1.0',
316
+ RutResponde: formatRut(rutEmpresa),
317
+ RutRecibe: formatRut(meta.rutEmisor || ''),
318
+ ...(this.contacto?.nombre ? { NmbContacto: sanitizeSiiText(this.contacto.nombre) } : {}),
319
+ ...(this.contacto?.fono ? { FonoContacto: this.contacto.fono } : {}),
320
+ ...(this.contacto?.mail ? { MailContacto: this.contacto.mail } : {}),
321
+ TmstFirmaEnv: now,
322
+ },
323
+ Recibo: recibos,
324
+ },
325
+ },
326
+ };
327
+
328
+ const builder = new XMLBuilder({ ignoreAttributes: false, attributeNamePrefix: '@_', format: false });
329
+ let xml = builder.build(data);
330
+
331
+ // Firmar cada DocumentoRecibo
332
+ meta.items.forEach((_, idx) => {
333
+ const docId = `REC-${idx + 1}`;
334
+ xml = this._signXml({
335
+ xml,
336
+ referenceXpath: `//*[local-name()='DocumentoRecibo' and @ID='${docId}']`,
337
+ insertAfterXpath: `//*[local-name()='DocumentoRecibo' and @ID='${docId}']`,
338
+ });
339
+ });
340
+
341
+ // Firmar SetRecibos
342
+ xml = this._signXml({
343
+ xml,
344
+ referenceXpath: `//*[local-name()='SetRecibos' and @ID='${setId}']`,
345
+ insertAfterXpath: `//*[local-name()='SetRecibos' and @ID='${setId}']`,
346
+ });
347
+
348
+ return xml;
349
+ }
350
+
351
+ /**
352
+ * Genera todas las respuestas de intercambio
353
+ * @param {string} envioDteXml - XML del EnvioDTE de entrada
354
+ * @param {Object} [options]
355
+ * @param {string} [options.envioNombre] - Nombre del envío
356
+ * @param {string} [options.outDir] - Directorio de salida (override debugDir)
357
+ * @returns {Object} Resultado con rutas de archivos generados
358
+ */
359
+ async generarIntercambio(envioDteXml, options = {}) {
360
+ const outDir = options.outDir || this.debugDir || './debug/intercambio';
361
+ fs.mkdirSync(outDir, { recursive: true });
362
+
363
+ console.log('\n' + '═'.repeat(60));
364
+ console.log('📬 INTERCAMBIO DE INFORMACIÓN DTE');
365
+ console.log('═'.repeat(60));
366
+
367
+ // Parsear EnvioDTE
368
+ console.log('\n📋 Parseando EnvioDTE...');
369
+ const meta = this.parseEnvioDTE(envioDteXml);
370
+
371
+ if (!meta?.items?.length) {
372
+ throw new Error('No se encontraron DTEs en el EnvioDTE de entrada');
373
+ }
374
+
375
+ console.log(` ✓ ${meta.items.length} documentos encontrados`);
376
+ console.log(` ✓ Emisor: ${meta.rutEmisor}`);
377
+ console.log(` ✓ Receptor: ${meta.rutReceptor}`);
378
+
379
+ meta.items.forEach((item, idx) => {
380
+ console.log(` ${idx + 1}. Tipo ${item.tipoDTE} Folio ${item.folio} - $${item.mntTotal.toLocaleString('es-CL')}`);
381
+ });
382
+
383
+ // Generar respuestas
384
+ console.log('\n📝 Generando respuestas de intercambio...');
385
+
386
+ // 1. Respuesta Recepción de Envío
387
+ console.log(' 1. Respuesta Recepción de Envío...');
388
+ const recepcionXml = this.generarRespuestaRecepcionEnvio(meta, options.envioNombre);
389
+ const recepcionPath = path.join(outDir, 'respuesta-recepcion-envio.xml');
390
+ fs.writeFileSync(recepcionPath, recepcionXml, 'utf8');
391
+ console.log(` ✓ ${recepcionPath}`);
392
+
393
+ // 2. Respuesta Aprobación Comercial
394
+ console.log(' 2. Respuesta Aprobación Comercial...');
395
+ const aprobacionXml = this.generarRespuestaAprobacionComercial(meta);
396
+ const aprobacionPath = path.join(outDir, 'respuesta-aprobacion-comercial.xml');
397
+ fs.writeFileSync(aprobacionPath, aprobacionXml, 'utf8');
398
+ console.log(` ✓ ${aprobacionPath}`);
399
+
400
+ // 3. Envío de Recibos
401
+ console.log(' 3. Envío de Recibos (Mercaderías)...');
402
+ const recibosXml = this.generarEnvioRecibos(meta);
403
+ const recibosPath = path.join(outDir, 'envio-recibos.xml');
404
+ fs.writeFileSync(recibosPath, recibosXml, 'utf8');
405
+ console.log(` ✓ ${recibosPath}`);
406
+
407
+ console.log('\n' + '═'.repeat(60));
408
+ console.log('✅ INTERCAMBIO GENERADO');
409
+ console.log('═'.repeat(60));
410
+ console.log(` 📂 Directorio: ${outDir}`);
411
+ console.log(' 📄 Archivos generados:');
412
+ console.log(' - respuesta-recepcion-envio.xml');
413
+ console.log(' - respuesta-aprobacion-comercial.xml');
414
+ console.log(' - envio-recibos.xml');
415
+
416
+ return {
417
+ success: true,
418
+ outDir,
419
+ meta,
420
+ files: {
421
+ recepcion: recepcionPath,
422
+ aprobacion: aprobacionPath,
423
+ recibos: recibosPath,
424
+ },
425
+ };
426
+ }
427
+ }
428
+
429
+ module.exports = IntercambioCert;