@devlas/dte-sii 2.5.11 → 2.5.13
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.
- package/CafSolicitor.js +378 -378
- package/ConsumoFolio.js +376 -376
- package/Envio.js +196 -196
- package/LICENSE +27 -27
- package/README.md +273 -273
- package/SiiCertificacion.js +137 -22
- package/SiiPortalAuth.js +474 -474
- package/cert/CertRunner.js +153 -21
- package/cert/ConfigLoader.js +131 -131
- package/cert/IntercambioCert.js +427 -427
- package/cert/LibroCompras.js +397 -357
- package/cert/LibroGuias.js +169 -169
- package/cert/LibroVentas.js +151 -151
- package/cert/MuestrasImpresas.js +676 -676
- package/cert/SetBase.js +319 -319
- package/cert/SetBasico.js +411 -411
- package/cert/SetCompra.js +470 -470
- package/cert/SetExenta.js +488 -488
- package/cert/SetGuia.js +281 -281
- package/cert/SetParser.js +1265 -1182
- package/cert/SetsProvider.js +497 -497
- package/cert/Simulacion.js +519 -519
- package/cert/comunaOficina.js +458 -458
- package/cert/index.js +122 -122
- package/cert/types.js +328 -328
- package/package.json +49 -49
package/cert/CertRunner.js
CHANGED
|
@@ -348,6 +348,16 @@ class CertRunner {
|
|
|
348
348
|
const result = await this.siiCert.declararAvance({ sets });
|
|
349
349
|
lastResult = result;
|
|
350
350
|
|
|
351
|
+
// Guardar el form pe_avance2 (antes del POST) para debug
|
|
352
|
+
if (result.formHtml) {
|
|
353
|
+
fs.writeFileSync(
|
|
354
|
+
path.join(this.debugDir, `${debugPrefix}-pe_avance2-${intento}.html`),
|
|
355
|
+
result.formHtml,
|
|
356
|
+
'utf8'
|
|
357
|
+
);
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
// Guardar la respuesta pe_avance3 (despues del POST) para debug
|
|
351
361
|
if (result.rawHtml) {
|
|
352
362
|
fs.writeFileSync(
|
|
353
363
|
path.join(this.debugDir, `${debugPrefix}-${intento}.html`),
|
|
@@ -370,6 +380,11 @@ class CertRunner {
|
|
|
370
380
|
if (noProcessedError && intento < maxIntentos) {
|
|
371
381
|
console.log(` ⏳ SII aún procesando, reintentando en ${intervalo / 1000}s...`);
|
|
372
382
|
await sleep(intervalo);
|
|
383
|
+
} else if (result.verificado === false && intento < maxIntentos) {
|
|
384
|
+
// Verificación post-declaración falló: los campos quedaron vacíos en el portal
|
|
385
|
+
console.log(` ⚠️ Verificación fallida: ${result.error}`);
|
|
386
|
+
console.log(` 🔄 Reintentando declaración en ${intervalo / 1000}s...`);
|
|
387
|
+
await sleep(intervalo);
|
|
373
388
|
} else if (!result.success) {
|
|
374
389
|
console.log(` ⚠️ Error declarando ${label}: ${result.error || 'desconocido'}`);
|
|
375
390
|
break;
|
|
@@ -460,6 +475,7 @@ class CertRunner {
|
|
|
460
475
|
* @param {Object} [options] - Opciones
|
|
461
476
|
* @param {Object} [options.setBasicoResult] - Resultado del SetBasico
|
|
462
477
|
* @param {Object} [options.setGuiaResult] - Resultado del SetGuia
|
|
478
|
+
* @param {Object} [options.setsResultados] - Track IDs de los sets (basico/guia/exenta/compra) para incluirlos en la declaración conjunta
|
|
463
479
|
* @returns {Promise<Object>} Resultado con todos los libros
|
|
464
480
|
*/
|
|
465
481
|
async ejecutarFase4Libros(options = {}) {
|
|
@@ -471,12 +487,49 @@ class CertRunner {
|
|
|
471
487
|
const resultados = {};
|
|
472
488
|
const errores = [];
|
|
473
489
|
|
|
490
|
+
// Verificar cuáles libros ya están REVISADO CONFORME en el portal (no re-enviar)
|
|
491
|
+
let _estadoActual = {};
|
|
492
|
+
try {
|
|
493
|
+
const _consultaPrevia = await this.siiCert.consultarEstadoSets();
|
|
494
|
+
if (_consultaPrevia.success) _estadoActual = _consultaPrevia.estadoSets || {};
|
|
495
|
+
const _yaConformes = Object.entries(_estadoActual)
|
|
496
|
+
.filter(([k, v]) => k.toUpperCase().includes('LIBRO') && v === 'REVISADO CONFORME')
|
|
497
|
+
.map(([k]) => k);
|
|
498
|
+
if (_yaConformes.length) {
|
|
499
|
+
console.log(` ℹ️ Ya en REVISADO CONFORME (se omitirán): ${_yaConformes.join(', ')}`);
|
|
500
|
+
}
|
|
501
|
+
} catch (_e) { /* ignorar error de consulta previa */ }
|
|
502
|
+
|
|
503
|
+
const _estaConforme = (nombre) => {
|
|
504
|
+
const nombreUpper = nombre.toUpperCase();
|
|
505
|
+
const e = Object.entries(_estadoActual).find(([k]) => {
|
|
506
|
+
const ku = k.toUpperCase();
|
|
507
|
+
if (nombreUpper === 'LIBRO DE COMPRAS') return ku.includes('LIBRO DE COMPRAS') && !ku.includes('EXENTOS');
|
|
508
|
+
return ku.includes(nombreUpper);
|
|
509
|
+
});
|
|
510
|
+
return e && e[1] === 'REVISADO CONFORME';
|
|
511
|
+
};
|
|
512
|
+
|
|
513
|
+
// Helper: guardar resultados parciales a disco tras cada envío exitoso
|
|
514
|
+
const _resultadosLibrosPath = path.join(this.debugDir, 'resultados-libros.json');
|
|
515
|
+
const _guardarResultadosParciales = () => {
|
|
516
|
+
try {
|
|
517
|
+
fs.writeFileSync(_resultadosLibrosPath, JSON.stringify(resultados, null, 2));
|
|
518
|
+
} catch (_e) { /* ignorar */ }
|
|
519
|
+
};
|
|
520
|
+
|
|
474
521
|
try {
|
|
475
522
|
// 1. Libro de Compras (usa datos del SII)
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
523
|
+
if (_estaConforme('LIBRO DE COMPRAS')) {
|
|
524
|
+
console.log('\n✅ Libro de Compras ya está REVISADO CONFORME — omitiendo');
|
|
525
|
+
} else {
|
|
526
|
+
console.log('\n📖 Enviando Libro de Compras...');
|
|
527
|
+
resultados.libroCompras = await this.ejecutarLibroCompras(options);
|
|
528
|
+
if (!resultados.libroCompras.success) {
|
|
529
|
+
errores.push(`Libro Compras: ${resultados.libroCompras.error}`);
|
|
530
|
+
} else {
|
|
531
|
+
_guardarResultadosParciales();
|
|
532
|
+
}
|
|
480
533
|
}
|
|
481
534
|
} catch (e) {
|
|
482
535
|
errores.push(`Libro Compras: ${e.message}`);
|
|
@@ -484,10 +537,16 @@ class CertRunner {
|
|
|
484
537
|
|
|
485
538
|
try {
|
|
486
539
|
// 2. Libro de Ventas (usa SetBasico)
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
540
|
+
if (_estaConforme('LIBRO DE VENTAS')) {
|
|
541
|
+
console.log('\n✅ Libro de Ventas ya está REVISADO CONFORME — omitiendo');
|
|
542
|
+
} else {
|
|
543
|
+
console.log('\n📖 Enviando Libro de Ventas...');
|
|
544
|
+
resultados.libroVentas = await this.ejecutarLibroVentas(options);
|
|
545
|
+
if (!resultados.libroVentas.success) {
|
|
546
|
+
errores.push(`Libro Ventas: ${resultados.libroVentas.error}`);
|
|
547
|
+
} else {
|
|
548
|
+
_guardarResultadosParciales();
|
|
549
|
+
}
|
|
491
550
|
}
|
|
492
551
|
} catch (e) {
|
|
493
552
|
errores.push(`Libro Ventas: ${e.message}`);
|
|
@@ -495,22 +554,34 @@ class CertRunner {
|
|
|
495
554
|
|
|
496
555
|
try {
|
|
497
556
|
// 3. Libro de Guías (usa SetGuia)
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
557
|
+
if (_estaConforme('LIBRO DE GUIAS')) {
|
|
558
|
+
console.log('\n✅ Libro de Guías ya está REVISADO CONFORME — omitiendo');
|
|
559
|
+
} else {
|
|
560
|
+
console.log('\n📖 Enviando Libro de Guías...');
|
|
561
|
+
resultados.libroGuias = await this.ejecutarLibroGuias(options);
|
|
562
|
+
if (!resultados.libroGuias.success) {
|
|
563
|
+
errores.push(`Libro Guías: ${resultados.libroGuias.error}`);
|
|
564
|
+
} else {
|
|
565
|
+
_guardarResultadosParciales();
|
|
566
|
+
}
|
|
502
567
|
}
|
|
503
568
|
} catch (e) {
|
|
504
569
|
errores.push(`Libro Guías: ${e.message}`);
|
|
505
570
|
}
|
|
506
571
|
|
|
507
|
-
// 4. Libro de Compras para Exentos (solo si el SII lo entregó)
|
|
572
|
+
// 4. Libro de Compras para Exentos (solo si el SII lo entregó y no está ya aprobado)
|
|
508
573
|
if (this._estructuras?.libroComprasExentos) {
|
|
509
574
|
try {
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
575
|
+
if (_estaConforme('LIBRO DE COMPRAS PARA EXENTOS')) {
|
|
576
|
+
console.log('\n✅ Libro Compras Exentos ya está REVISADO CONFORME — omitiendo');
|
|
577
|
+
} else {
|
|
578
|
+
console.log('\n📖 Enviando Libro de Compras para Exentos...');
|
|
579
|
+
resultados.libroComprasExentos = await this.ejecutarLibroComprasExentos(options);
|
|
580
|
+
if (!resultados.libroComprasExentos.success) {
|
|
581
|
+
errores.push(`Libro Compras Exentos: ${resultados.libroComprasExentos.error}`);
|
|
582
|
+
} else {
|
|
583
|
+
_guardarResultadosParciales();
|
|
584
|
+
}
|
|
514
585
|
}
|
|
515
586
|
} catch (e) {
|
|
516
587
|
errores.push(`Libro Compras Exentos: ${e.message}`);
|
|
@@ -518,18 +589,65 @@ class CertRunner {
|
|
|
518
589
|
}
|
|
519
590
|
|
|
520
591
|
// Contar libros obligatorios (ventas + compras + guías)
|
|
592
|
+
// Un libro cuenta como OK si fue enviado exitosamente O si ya era REVISADO CONFORME (omitido)
|
|
521
593
|
const librosObligatorios = ['libroVentas', 'libroCompras', 'libroGuias'];
|
|
522
|
-
const librosEnviados = librosObligatorios.filter(k =>
|
|
594
|
+
const librosEnviados = librosObligatorios.filter(k => {
|
|
595
|
+
if (resultados[k]?.success) return true;
|
|
596
|
+
// Mapeo clave→nombre para consultar _estaConforme
|
|
597
|
+
const nombreMap = { libroVentas: 'LIBRO DE VENTAS', libroCompras: 'LIBRO DE COMPRAS', libroGuias: 'LIBRO DE GUIAS' };
|
|
598
|
+
return _estaConforme(nombreMap[k]);
|
|
599
|
+
}).length;
|
|
523
600
|
|
|
524
601
|
if (librosEnviados === 3) {
|
|
525
|
-
// 4. Declarar los libros
|
|
602
|
+
// 4. Declarar los libros (incluyendo sets para evitar que pe_avance3 los resetee)
|
|
526
603
|
console.log('\n📝 Declarando libros...');
|
|
527
604
|
try {
|
|
528
|
-
const declaracion = await this.declararLibros();
|
|
605
|
+
const declaracion = await this.declararLibros({ ...resultados, ...(options.setsResultados || {}) });
|
|
529
606
|
resultados.declaracion = declaracion;
|
|
530
607
|
|
|
531
608
|
if (declaracion.success) {
|
|
532
|
-
console.log('\n✅
|
|
609
|
+
console.log('\n✅ Libros declarados — esperando revisión del SII...');
|
|
610
|
+
|
|
611
|
+
// Polling real: esperar hasta REVISADO CONFORME o S25 (máx 20 intentos x 15s = 5 min)
|
|
612
|
+
const _librosAEsperar = ['LIBRO DE VENTAS', 'LIBRO DE COMPRAS', 'LIBRO DE GUIAS'];
|
|
613
|
+
if (this._estructuras?.libroComprasExentos) _librosAEsperar.push('LIBRO DE COMPRAS PARA EXENTOS');
|
|
614
|
+
// Solo esperar los libros que realmente enviamos (no los que ya eran REVISADO CONFORME)
|
|
615
|
+
const _librosEnviados = _librosAEsperar.filter(n => !_estaConforme(n));
|
|
616
|
+
|
|
617
|
+
console.log(`\n⏳ Esperando aprobación del SII para: ${_librosEnviados.join(', ')}`);
|
|
618
|
+
let _aprobados = false;
|
|
619
|
+
for (let _i = 0; _i < 20; _i++) {
|
|
620
|
+
await sleep(15000);
|
|
621
|
+
const _poll = await this.siiCert.consultarEstadoSets();
|
|
622
|
+
if (!_poll.success) continue;
|
|
623
|
+
const _ss = _poll.estadoSets || {};
|
|
624
|
+
const _info = Object.entries(_ss)
|
|
625
|
+
.filter(([k]) => k.toUpperCase().includes('LIBRO'))
|
|
626
|
+
.map(([k, v]) => `${k.trim()}: ${v}`);
|
|
627
|
+
if (_info.length) console.log(` 🔄 Intento ${_i + 1}/20: ${_info.join(' | ')}`);
|
|
628
|
+
|
|
629
|
+
const _todosOk = _librosEnviados.every(nombre => {
|
|
630
|
+
const e = Object.entries(_ss).find(([k]) => k.toUpperCase().includes(nombre));
|
|
631
|
+
return e && (e[1] === 'REVISADO CONFORME' || e[1] === 'S25');
|
|
632
|
+
});
|
|
633
|
+
const _algunError = _librosEnviados.some(nombre => {
|
|
634
|
+
const e = Object.entries(_ss).find(([k]) => k.toUpperCase().includes(nombre));
|
|
635
|
+
return e && (e[1] === 'LNC' || e[1] === 'LRH' || e[1].includes('RECHAZADO') || e[1].includes('ERROR'));
|
|
636
|
+
});
|
|
637
|
+
|
|
638
|
+
if (_todosOk) {
|
|
639
|
+
console.log('\n🎉 ¡LIBROS APROBADOS POR EL SII!');
|
|
640
|
+
_aprobados = true;
|
|
641
|
+
break;
|
|
642
|
+
}
|
|
643
|
+
if (_algunError) {
|
|
644
|
+
console.log('\n❌ Hay libros rechazados. Revisar emails del SII.');
|
|
645
|
+
break;
|
|
646
|
+
}
|
|
647
|
+
}
|
|
648
|
+
if (!_aprobados) {
|
|
649
|
+
console.log('\n⚠️ Timeout (5 min). El SII aún no responde. Verifica con --avance más tarde.');
|
|
650
|
+
}
|
|
533
651
|
} else {
|
|
534
652
|
console.log(`\n⚠️ Libros enviados pero declaración con error: ${declaracion.error}`);
|
|
535
653
|
}
|
|
@@ -563,6 +681,12 @@ class CertRunner {
|
|
|
563
681
|
const sets = {};
|
|
564
682
|
|
|
565
683
|
const mapping = {
|
|
684
|
+
// Sets — incluirlos para que pe_avance3 no los resetee a S01
|
|
685
|
+
basico: 'setBasico',
|
|
686
|
+
guia: 'setGuiaDespacho',
|
|
687
|
+
exenta: 'setFacturaExenta',
|
|
688
|
+
compra: 'setFacturaCompra',
|
|
689
|
+
// Libros
|
|
566
690
|
libroVentas: 'libroVentas',
|
|
567
691
|
libroCompras: 'libroCompras',
|
|
568
692
|
libroGuias: 'libroGuias',
|
|
@@ -1217,6 +1341,14 @@ class CertRunner {
|
|
|
1217
1341
|
return { success: true, etapa: 'INTERCAMBIO' };
|
|
1218
1342
|
}
|
|
1219
1343
|
|
|
1344
|
+
// ✅ TAMBIÉN: Etapas que vienen DESPUÉS de INTERCAMBIO (simulación + intercambio ya completos)
|
|
1345
|
+
const ETAPAS_POST_INTERCAMBIO = ['DOCUMENTOS IMPRESOS', 'MUESTRAS IMPRESAS', 'BOLETA', 'AUTORIZADO', 'COMPLETADO'];
|
|
1346
|
+
if (avance.etapaActual && ETAPAS_POST_INTERCAMBIO.some(e => avance.etapaActual.toUpperCase().includes(e))) {
|
|
1347
|
+
console.log(` 📍 Etapa actual: ${avance.etapaActual}`);
|
|
1348
|
+
console.log('\n 🎉 ¡SIMULACIÓN + INTERCAMBIO COMPLETADOS! Empresa en etapa: ' + avance.etapaActual);
|
|
1349
|
+
return { success: true, etapa: avance.etapaActual, postIntercambio: true };
|
|
1350
|
+
}
|
|
1351
|
+
|
|
1220
1352
|
// ✅ SEGUNDO: Verificar indicador de formulario de confirmación (simulación aprobada pendiente confirmar)
|
|
1221
1353
|
if (avance.simulacionAprobadaIndicador) {
|
|
1222
1354
|
console.log(` ✅ Formulario de confirmación detectado`);
|
package/cert/ConfigLoader.js
CHANGED
|
@@ -1,131 +1,131 @@
|
|
|
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 (opcional — las vars pueden venir de process.env inyectadas por el proceso padre)
|
|
28
|
-
const envPath = options.envPath || path.join(baseDir, '.env');
|
|
29
|
-
|
|
30
|
-
if (fs.existsSync(envPath)) {
|
|
31
|
-
require('dotenv').config({ path: envPath });
|
|
32
|
-
} else {
|
|
33
|
-
console.warn(`[ConfigLoader] .env no encontrado en ${envPath} — usando variables de entorno del proceso.`);
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
// Resolver rutas relativas
|
|
37
|
-
const resolvePath = (filePath) => {
|
|
38
|
-
if (!filePath) return null;
|
|
39
|
-
return path.isAbsolute(filePath) ? filePath : path.resolve(baseDir, filePath);
|
|
40
|
-
};
|
|
41
|
-
|
|
42
|
-
// Validar variables requeridas (solo CERT_PATH es estrictamente necesario;
|
|
43
|
-
// el resto puede no estar si los datos vienen del portal SII vía getEmisorFromPortal)
|
|
44
|
-
if (!process.env.CERT_PATH) {
|
|
45
|
-
throw new Error('Variable de entorno faltante: CERT_PATH');
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
// Construir objetos de configuración
|
|
49
|
-
const AMBIENTE = process.env.SII_AMBIENTE || 'certificacion';
|
|
50
|
-
|
|
51
|
-
const fch_resol = process.env.FECHA_RESOLUCION || new Date().toISOString().slice(0, 10);
|
|
52
|
-
const nro_resol = parseInt(process.env.NRO_RESOLUCION || '0', 10);
|
|
53
|
-
|
|
54
|
-
console.log(`[ConfigLoader] ✅ Resolución: NroResol=${nro_resol} FchResol=${fch_resol} (AMBIENTE=${AMBIENTE})`);
|
|
55
|
-
|
|
56
|
-
const EMISOR = {
|
|
57
|
-
rut: process.env.EMISOR_RUT,
|
|
58
|
-
razon_social: process.env.EMISOR_RAZON_SOCIAL,
|
|
59
|
-
giro: process.env.EMISOR_GIRO || 'ACTIVIDADES DE PROGRAMACION INFORMATICA',
|
|
60
|
-
acteco: process.env.EMISOR_ACTECO || '620200',
|
|
61
|
-
direccion: process.env.EMISOR_DIRECCION || '',
|
|
62
|
-
comuna: process.env.EMISOR_COMUNA || 'Santiago',
|
|
63
|
-
ciudad: process.env.EMISOR_CIUDAD || 'Santiago',
|
|
64
|
-
fch_resol,
|
|
65
|
-
nro_resol,
|
|
66
|
-
};
|
|
67
|
-
|
|
68
|
-
const RECEPTOR = {
|
|
69
|
-
rut: process.env.RECEPTOR_RUT || '66666666-6',
|
|
70
|
-
razon_social: process.env.RECEPTOR_RAZON_SOCIAL || 'CLIENTE TEST',
|
|
71
|
-
giro: process.env.RECEPTOR_GIRO || 'ACTIVIDADES VARIAS',
|
|
72
|
-
direccion: process.env.RECEPTOR_DIRECCION || 'AVENIDA PRINCIPAL 123',
|
|
73
|
-
comuna: process.env.RECEPTOR_COMUNA || 'Santiago',
|
|
74
|
-
ciudad: process.env.RECEPTOR_CIUDAD || 'Santiago',
|
|
75
|
-
};
|
|
76
|
-
|
|
77
|
-
const CERT_PATH = resolvePath(process.env.CERT_PATH);
|
|
78
|
-
const CERT_PASS = process.env.CERT_PASS || '';
|
|
79
|
-
|
|
80
|
-
// Configuración SII
|
|
81
|
-
const SII_CONFIG = {
|
|
82
|
-
sendDelayMs: parseInt(process.env.SII_SEND_DELAY_MS || '8000', 10),
|
|
83
|
-
sendRetries: parseInt(process.env.SII_SEND_RETRIES || '6', 10),
|
|
84
|
-
cafRetries: parseInt(process.env.SII_CAF_RETRIES || '3', 10),
|
|
85
|
-
reuseFolios: process.env.SII_REUSE_FOLIOS === 'true',
|
|
86
|
-
};
|
|
87
|
-
|
|
88
|
-
// Enviador (quien firma/envía)
|
|
89
|
-
const ENVIADOR = {
|
|
90
|
-
rut: process.env.ENVIADOR_RUT || EMISOR.rut,
|
|
91
|
-
};
|
|
92
|
-
|
|
93
|
-
return {
|
|
94
|
-
EMISOR,
|
|
95
|
-
RECEPTOR,
|
|
96
|
-
CERT_PATH,
|
|
97
|
-
CERT_PASS,
|
|
98
|
-
AMBIENTE,
|
|
99
|
-
BASE_DIR: baseDir,
|
|
100
|
-
SII_CONFIG,
|
|
101
|
-
ENVIADOR,
|
|
102
|
-
|
|
103
|
-
// Helper para crear config de CertRunner
|
|
104
|
-
toCertRunnerConfig() {
|
|
105
|
-
return {
|
|
106
|
-
certificado: { path: CERT_PATH, password: CERT_PASS },
|
|
107
|
-
emisor: EMISOR,
|
|
108
|
-
receptor: RECEPTOR,
|
|
109
|
-
ambiente: AMBIENTE,
|
|
110
|
-
resolucion: { fecha: EMISOR.fch_resol, numero: EMISOR.nro_resol },
|
|
111
|
-
baseDir: baseDir,
|
|
112
|
-
debugDir: path.join(baseDir, 'debug', 'cert-v2'),
|
|
113
|
-
};
|
|
114
|
-
},
|
|
115
|
-
};
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
/**
|
|
119
|
-
* Imprime banner de sección para CLI
|
|
120
|
-
* @param {string} titulo
|
|
121
|
-
*/
|
|
122
|
-
function printBanner(titulo) {
|
|
123
|
-
console.log('\n' + '═'.repeat(60));
|
|
124
|
-
console.log(` ${titulo}`);
|
|
125
|
-
console.log('═'.repeat(60));
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
module.exports = {
|
|
129
|
-
loadConfig,
|
|
130
|
-
printBanner,
|
|
131
|
-
};
|
|
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 (opcional — las vars pueden venir de process.env inyectadas por el proceso padre)
|
|
28
|
+
const envPath = options.envPath || path.join(baseDir, '.env');
|
|
29
|
+
|
|
30
|
+
if (fs.existsSync(envPath)) {
|
|
31
|
+
require('dotenv').config({ path: envPath });
|
|
32
|
+
} else {
|
|
33
|
+
console.warn(`[ConfigLoader] .env no encontrado en ${envPath} — usando variables de entorno del proceso.`);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
// Resolver rutas relativas
|
|
37
|
+
const resolvePath = (filePath) => {
|
|
38
|
+
if (!filePath) return null;
|
|
39
|
+
return path.isAbsolute(filePath) ? filePath : path.resolve(baseDir, filePath);
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
// Validar variables requeridas (solo CERT_PATH es estrictamente necesario;
|
|
43
|
+
// el resto puede no estar si los datos vienen del portal SII vía getEmisorFromPortal)
|
|
44
|
+
if (!process.env.CERT_PATH) {
|
|
45
|
+
throw new Error('Variable de entorno faltante: CERT_PATH');
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
// Construir objetos de configuración
|
|
49
|
+
const AMBIENTE = process.env.SII_AMBIENTE || 'certificacion';
|
|
50
|
+
|
|
51
|
+
const fch_resol = process.env.FECHA_RESOLUCION || new Date().toISOString().slice(0, 10);
|
|
52
|
+
const nro_resol = parseInt(process.env.NRO_RESOLUCION || '0', 10);
|
|
53
|
+
|
|
54
|
+
console.log(`[ConfigLoader] ✅ Resolución: NroResol=${nro_resol} FchResol=${fch_resol} (AMBIENTE=${AMBIENTE})`);
|
|
55
|
+
|
|
56
|
+
const EMISOR = {
|
|
57
|
+
rut: process.env.EMISOR_RUT,
|
|
58
|
+
razon_social: process.env.EMISOR_RAZON_SOCIAL,
|
|
59
|
+
giro: process.env.EMISOR_GIRO || 'ACTIVIDADES DE PROGRAMACION INFORMATICA',
|
|
60
|
+
acteco: process.env.EMISOR_ACTECO || '620200',
|
|
61
|
+
direccion: process.env.EMISOR_DIRECCION || '',
|
|
62
|
+
comuna: process.env.EMISOR_COMUNA || 'Santiago',
|
|
63
|
+
ciudad: process.env.EMISOR_CIUDAD || 'Santiago',
|
|
64
|
+
fch_resol,
|
|
65
|
+
nro_resol,
|
|
66
|
+
};
|
|
67
|
+
|
|
68
|
+
const RECEPTOR = {
|
|
69
|
+
rut: process.env.RECEPTOR_RUT || '66666666-6',
|
|
70
|
+
razon_social: process.env.RECEPTOR_RAZON_SOCIAL || 'CLIENTE TEST',
|
|
71
|
+
giro: process.env.RECEPTOR_GIRO || 'ACTIVIDADES VARIAS',
|
|
72
|
+
direccion: process.env.RECEPTOR_DIRECCION || 'AVENIDA PRINCIPAL 123',
|
|
73
|
+
comuna: process.env.RECEPTOR_COMUNA || 'Santiago',
|
|
74
|
+
ciudad: process.env.RECEPTOR_CIUDAD || 'Santiago',
|
|
75
|
+
};
|
|
76
|
+
|
|
77
|
+
const CERT_PATH = resolvePath(process.env.CERT_PATH);
|
|
78
|
+
const CERT_PASS = process.env.CERT_PASS || '';
|
|
79
|
+
|
|
80
|
+
// Configuración SII
|
|
81
|
+
const SII_CONFIG = {
|
|
82
|
+
sendDelayMs: parseInt(process.env.SII_SEND_DELAY_MS || '8000', 10),
|
|
83
|
+
sendRetries: parseInt(process.env.SII_SEND_RETRIES || '6', 10),
|
|
84
|
+
cafRetries: parseInt(process.env.SII_CAF_RETRIES || '3', 10),
|
|
85
|
+
reuseFolios: process.env.SII_REUSE_FOLIOS === 'true',
|
|
86
|
+
};
|
|
87
|
+
|
|
88
|
+
// Enviador (quien firma/envía)
|
|
89
|
+
const ENVIADOR = {
|
|
90
|
+
rut: process.env.ENVIADOR_RUT || EMISOR.rut,
|
|
91
|
+
};
|
|
92
|
+
|
|
93
|
+
return {
|
|
94
|
+
EMISOR,
|
|
95
|
+
RECEPTOR,
|
|
96
|
+
CERT_PATH,
|
|
97
|
+
CERT_PASS,
|
|
98
|
+
AMBIENTE,
|
|
99
|
+
BASE_DIR: baseDir,
|
|
100
|
+
SII_CONFIG,
|
|
101
|
+
ENVIADOR,
|
|
102
|
+
|
|
103
|
+
// Helper para crear config de CertRunner
|
|
104
|
+
toCertRunnerConfig() {
|
|
105
|
+
return {
|
|
106
|
+
certificado: { path: CERT_PATH, password: CERT_PASS },
|
|
107
|
+
emisor: EMISOR,
|
|
108
|
+
receptor: RECEPTOR,
|
|
109
|
+
ambiente: AMBIENTE,
|
|
110
|
+
resolucion: { fecha: EMISOR.fch_resol, numero: EMISOR.nro_resol },
|
|
111
|
+
baseDir: baseDir,
|
|
112
|
+
debugDir: path.join(baseDir, 'debug', 'cert-v2'),
|
|
113
|
+
};
|
|
114
|
+
},
|
|
115
|
+
};
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
/**
|
|
119
|
+
* Imprime banner de sección para CLI
|
|
120
|
+
* @param {string} titulo
|
|
121
|
+
*/
|
|
122
|
+
function printBanner(titulo) {
|
|
123
|
+
console.log('\n' + '═'.repeat(60));
|
|
124
|
+
console.log(` ${titulo}`);
|
|
125
|
+
console.log('═'.repeat(60));
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
module.exports = {
|
|
129
|
+
loadConfig,
|
|
130
|
+
printBanner,
|
|
131
|
+
};
|