@devlas/dte-sii 2.12.13 → 2.12.15
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/SiiCertificacion.js +14 -8
- package/cert/CertRunner.js +71 -15
- package/package.json +1 -1
package/SiiCertificacion.js
CHANGED
|
@@ -1063,17 +1063,23 @@ class SiiCertificacion {
|
|
|
1063
1063
|
}
|
|
1064
1064
|
|
|
1065
1065
|
// El botón real (avanzar()) solo cambia el action y submitea — no valida nada del lado
|
|
1066
|
-
// cliente. La respuesta del SII distingue
|
|
1067
|
-
// 200) ni por la palabra "error" (aparece en el <script> de todas las páginas
|
|
1068
|
-
//
|
|
1069
|
-
// "
|
|
1070
|
-
//
|
|
1071
|
-
//
|
|
1066
|
+
// cliente. La respuesta del SII distingue estos casos por texto, no por status HTTP
|
|
1067
|
+
// (siempre 200) ni por la palabra "error" (aparece en el <script> de todas las páginas
|
|
1068
|
+
// del portal):
|
|
1069
|
+
// "ha pasado al paso X" → avance REAL a otro paso intermedio
|
|
1070
|
+
// "ha finalizado con la certificación..." → avance REAL — llegó al paso final,
|
|
1071
|
+
// queda pendiente declarar cumplimiento
|
|
1072
|
+
// (pe_avance7→8, ver declararCumplimiento())
|
|
1073
|
+
// "ya se encuentra en el paso X" → no-op: no avanzó nada (PASO no
|
|
1074
|
+
// coincidía, o el SII aún no habilita el
|
|
1075
|
+
// siguiente paso) — NO es un error
|
|
1076
|
+
// cualquier otro texto → error real
|
|
1072
1077
|
const texto = limpiarTextoPortal(body);
|
|
1073
|
-
const
|
|
1078
|
+
const RE_AVANZO = /ha pasado al paso|ha finalizado con la certificaci/i;
|
|
1079
|
+
const avanzo = RE_AVANZO.test(texto);
|
|
1074
1080
|
const sinCambio = /ya se encuentra en el paso/i.test(texto);
|
|
1075
1081
|
const frases = texto.split(/(?<=\.)\s+/);
|
|
1076
|
-
const mensaje = (frases.find(f =>
|
|
1082
|
+
const mensaje = (frases.find(f => RE_AVANZO.test(f) || /ya se encuentra en el paso/i.test(f)) || '').trim() || null;
|
|
1077
1083
|
|
|
1078
1084
|
return {
|
|
1079
1085
|
success: avanzo,
|
package/cert/CertRunner.js
CHANGED
|
@@ -168,6 +168,20 @@ class CertRunner {
|
|
|
168
168
|
* @returns {Promise<Object>} { success, estructuras, error }
|
|
169
169
|
*/
|
|
170
170
|
async obtenerSets(options = {}) {
|
|
171
|
+
if (options.hydrateOnly) {
|
|
172
|
+
// Solo hidrata this._estructuras desde memoria o disco — NUNCA llama al SII. Necesario
|
|
173
|
+
// como modo aparte porque el resto de esta función SIEMPRE pide un set nuevo al SII
|
|
174
|
+
// (forceRefresh: true, ver abajo), lo que REINICIA la postulación real completa. Un
|
|
175
|
+
// llamado pensado solo para "popular estado" no puede arriesgarse a tocar esa rama.
|
|
176
|
+
if (!this._estructuras) {
|
|
177
|
+
const estructurasPath = path.join(this.debugDir, 'estructuras.json');
|
|
178
|
+
if (fs.existsSync(estructurasPath)) {
|
|
179
|
+
this._estructuras = JSON.parse(fs.readFileSync(estructurasPath, 'utf8'));
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
return { success: !!this._estructuras, estructuras: this._estructuras, hydrated: true };
|
|
183
|
+
}
|
|
184
|
+
|
|
171
185
|
const resultado = await this.setsProvider.obtenerSets({
|
|
172
186
|
setsOpcionales: options.setsOpcionales || {
|
|
173
187
|
SET03: 'S', // SET GUIA DE DESPACHO
|
|
@@ -199,6 +213,28 @@ class CertRunner {
|
|
|
199
213
|
return { success: true, estructuras: resultado.estructuras };
|
|
200
214
|
}
|
|
201
215
|
|
|
216
|
+
/**
|
|
217
|
+
* Inyecta estructuras ya descargadas (desde BD/caché) sin pedirlas al SII. Formaliza lo que
|
|
218
|
+
* hoy hacen los callers a mano (`runner._estructuras = JSON.parse(...)`) con acceso directo
|
|
219
|
+
* a un campo "privado". Útil para reanudar en una fase posterior (libros, simulación) sin
|
|
220
|
+
* volver a llamar obtenerSets() — que SIEMPRE resetea la postulación real (forceRefresh).
|
|
221
|
+
* @param {Object} estructurasJson
|
|
222
|
+
*/
|
|
223
|
+
setEstructuras(estructurasJson) {
|
|
224
|
+
this._estructuras = estructurasJson;
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
/**
|
|
228
|
+
* Inyecta resultados previos (trackIds de sets ya enviados) en this.resultados sin
|
|
229
|
+
* re-ejecutar los envíos. Hace merge — no borra resultados ya presentes que no vengan en
|
|
230
|
+
* resultadosJson. Formaliza `runner.resultados[k] = {...}` (acceso directo hoy en
|
|
231
|
+
* run-certificacion.js) con un método explícito.
|
|
232
|
+
* @param {Object} resultadosJson
|
|
233
|
+
*/
|
|
234
|
+
setResultados(resultadosJson) {
|
|
235
|
+
Object.assign(this.resultados, resultadosJson);
|
|
236
|
+
}
|
|
237
|
+
|
|
202
238
|
/**
|
|
203
239
|
* Solicita CAFs frescos para los tipos especificados
|
|
204
240
|
* @param {Object} cafRequired - { 33: 4, 56: 1, 61: 3 }
|
|
@@ -437,13 +473,18 @@ class CertRunner {
|
|
|
437
473
|
* Declara avance de los sets ejecutados con reintentos automáticos
|
|
438
474
|
* @param {Object} [resultadosExt] - Resultados externos (usa this.resultados si no se pasa)
|
|
439
475
|
* @param {Object} [estructurasExt] - Estructuras externas (usa this._estructuras si no se pasa)
|
|
440
|
-
* @param {Object} [options] - { maxIntentos, intervalo }
|
|
476
|
+
* @param {Object} [options] - { maxIntentos, intervalo, ignorarVacio }
|
|
477
|
+
* @param {boolean} [options.ignorarVacio] - Si this.resultados no tiene trackIds (runner
|
|
478
|
+
* recién creado, sin hidratar), en vez de devolver el error genérico "No hay sets para
|
|
479
|
+
* declarar" consulta el estado real del portal: si la etapa ya avanzó más allá de SET DE
|
|
480
|
+
* PRUEBAS, los sets YA fueron declarados y aprobados en una ejecución anterior — no hay
|
|
481
|
+
* nada que hacer y no es un error. Default false — comportamiento previo sin cambios.
|
|
441
482
|
* @returns {Promise<Object>} Resultado de la declaración
|
|
442
483
|
*/
|
|
443
484
|
async declararAvance(resultadosExt, estructurasExt, options = {}) {
|
|
444
485
|
const resultados = resultadosExt || this.resultados;
|
|
445
486
|
const estructuras = estructurasExt || this._estructuras;
|
|
446
|
-
const { maxIntentos = 10, intervalo = 5000 } = options;
|
|
487
|
+
const { maxIntentos = 10, intervalo = 5000, ignorarVacio = false } = options;
|
|
447
488
|
|
|
448
489
|
if (!estructuras) {
|
|
449
490
|
throw new Error('No hay estructuras. Ejecutar obtenerSets() primero.');
|
|
@@ -470,6 +511,13 @@ class CertRunner {
|
|
|
470
511
|
}
|
|
471
512
|
|
|
472
513
|
if (Object.keys(sets).length === 0) {
|
|
514
|
+
if (ignorarVacio) {
|
|
515
|
+
const avance = await this.consultarAvance().catch(() => null);
|
|
516
|
+
const etapa = avance?.etapaActual;
|
|
517
|
+
if (etapa && etapa !== 'SET DE PRUEBAS') {
|
|
518
|
+
return { success: true, yaDeclarado: true, etapaActual: etapa };
|
|
519
|
+
}
|
|
520
|
+
}
|
|
473
521
|
return { success: false, error: 'No hay sets para declarar' };
|
|
474
522
|
}
|
|
475
523
|
|
|
@@ -519,6 +567,11 @@ class CertRunner {
|
|
|
519
567
|
* @returns {Promise<Object>} Resultado con todos los libros
|
|
520
568
|
*/
|
|
521
569
|
async ejecutarFase4Libros(options = {}) {
|
|
570
|
+
// Permite pasar las estructuras explícitamente (ej. runner recién creado, hidratado desde
|
|
571
|
+
// BD/caché en vez de obtenerSets()) — igual que ejecutarSimulacion(). Si no vienen, cae al
|
|
572
|
+
// estado interno (comportamiento previo, sin cambios para callers existentes).
|
|
573
|
+
const estructuras = options.estructuras || this._estructuras;
|
|
574
|
+
|
|
522
575
|
// NOTA: Ya NO decrementamos aquí - cada libro decrementa su propio período
|
|
523
576
|
emitProgress(STEPS.BOOKS_START);
|
|
524
577
|
console.log('\n' + '═'.repeat(60));
|
|
@@ -580,8 +633,8 @@ class CertRunner {
|
|
|
580
633
|
// con el mes de esas fechas. Usar currentMonth-1 causa "ENVIO CON ERRORES O REPAROS".
|
|
581
634
|
let _periodoBase = null;
|
|
582
635
|
const _fchDocMuestra =
|
|
583
|
-
|
|
584
|
-
|
|
636
|
+
estructuras?.libroCompras?.detalle?.[0]?.FchDoc ||
|
|
637
|
+
estructuras?.libroComprasExentos?.detalle?.[0]?.FchDoc;
|
|
585
638
|
if (_fchDocMuestra) {
|
|
586
639
|
const _dt = new Date(_fchDocMuestra);
|
|
587
640
|
if (!isNaN(_dt.getTime())) {
|
|
@@ -729,7 +782,7 @@ class CertRunner {
|
|
|
729
782
|
}
|
|
730
783
|
|
|
731
784
|
// 4. Libro de Compras para Exentos (solo si el SII lo entregó y no está ya aprobado)
|
|
732
|
-
if (
|
|
785
|
+
if (estructuras?.libroComprasExentos) {
|
|
733
786
|
try {
|
|
734
787
|
if (_estaConforme('LIBRO DE COMPRAS PARA EXENTOS')) {
|
|
735
788
|
emitProgress(STEPS.BOOK_SKIPPED, { book: 'libroComprasExentos' });
|
|
@@ -867,7 +920,7 @@ class CertRunner {
|
|
|
867
920
|
// NO es un error de período. Solo LNC/LRH son errores reales que requieren acción.
|
|
868
921
|
const _esperarAprobacion = async (librosAVerificar) => {
|
|
869
922
|
const _todosCandidatos = ['LIBRO DE VENTAS', 'LIBRO DE COMPRAS', 'LIBRO DE GUIAS'];
|
|
870
|
-
if (
|
|
923
|
+
if (estructuras?.libroComprasExentos) _todosCandidatos.push('LIBRO DE COMPRAS PARA EXENTOS');
|
|
871
924
|
const _librosAVerif = librosAVerificar || _todosCandidatos.filter(n => !_estaConforme(n));
|
|
872
925
|
console.log(`\nEsperando aprobacion del SII para: ${_librosAVerif.join(', ')}`);
|
|
873
926
|
let _ss = {};
|
|
@@ -953,19 +1006,22 @@ class CertRunner {
|
|
|
953
1006
|
const _enviadorSoap = this._createLibroEnviador();
|
|
954
1007
|
const _rutEmisor = this.config.emisor.rut;
|
|
955
1008
|
|
|
956
|
-
// Polling
|
|
957
|
-
//
|
|
958
|
-
//
|
|
959
|
-
//
|
|
960
|
-
//
|
|
961
|
-
|
|
1009
|
+
// Polling corto (3 intentos × 15s = 45s). Se declara igual después sin importar cómo
|
|
1010
|
+
// termine este poll (ver más abajo "Declarando igual") — nunca bloquea. Evidencia real
|
|
1011
|
+
// de hoy: en NINGUNA corrida este poll llegó a ver LOK ni LNC antes de agotarse, ni con
|
|
1012
|
+
// 20 intentos (5 min) ni con 120 (30 min, el valor original) — maullín deja los libros
|
|
1013
|
+
// en LSO mucho más tiempo del que tiene sentido esperar acá. El único caso que este poll
|
|
1014
|
+
// puede capturar a tiempo es un rechazo de schema INMEDIATO (rápido por naturaleza); el
|
|
1015
|
+
// LNC por período ocupado se detecta más tarde, en el retry de _reenviarLibros (poll
|
|
1016
|
+
// aparte, sin cambios). Se deja en 3 intentos en vez de 0 por si ese caso rápido ocurre.
|
|
1017
|
+
const _MAX_SOAP_POLLS = 3;
|
|
962
1018
|
const _SOAP_INTERVAL_MS = 15000;
|
|
963
1019
|
let _soapPendientes = new Set(_librosParaConsultar.map(l => l.key));
|
|
964
1020
|
const _soapFinalStates = {}; // key → resultado SOAP terminal (LOK/LNC/LRH/etc.)
|
|
965
1021
|
const _soapErrCount = {}; // key → contador de errores SOAP consecutivos (ok:false o throw)
|
|
966
1022
|
const _MAX_SOAP_ERR = 10; // reintentos antes de desistir por errores transitorios
|
|
967
1023
|
|
|
968
|
-
console.log('\n[SOAP] Consultando estado de envíos (espera hasta
|
|
1024
|
+
console.log('\n[SOAP] Consultando estado de envíos (espera hasta 45s)...');
|
|
969
1025
|
await sleep(15000); // espera inicial — SII tarda al menos 15s en validar schema
|
|
970
1026
|
for (let _pi = 0; _pi < _MAX_SOAP_POLLS && _soapPendientes.size > 0; _pi++) {
|
|
971
1027
|
for (const { key, nombre } of _librosParaConsultar) {
|
|
@@ -1012,7 +1068,7 @@ class CertRunner {
|
|
|
1012
1068
|
}
|
|
1013
1069
|
if (_soapPendientes.size > 0) {
|
|
1014
1070
|
const _nombresTimeout = _librosParaConsultar.filter(l => _soapPendientes.has(l.key)).map(l => l.nombre);
|
|
1015
|
-
console.log(` [SOAP] ${_nombresTimeout.join(', ')} siguen en proceso tras
|
|
1071
|
+
console.log(` [SOAP] ${_nombresTimeout.join(', ')} siguen en proceso tras 45s. Declarando igual — verifica correos del SII.`);
|
|
1016
1072
|
}
|
|
1017
1073
|
|
|
1018
1074
|
// 4. Declarar + retry automático:
|
|
@@ -1069,7 +1125,7 @@ class CertRunner {
|
|
|
1069
1125
|
|
|
1070
1126
|
// Construir la lista inicial de libros a verificar
|
|
1071
1127
|
const _todosLibrosNombres = ['LIBRO DE VENTAS', 'LIBRO DE COMPRAS', 'LIBRO DE GUIAS'];
|
|
1072
|
-
if (
|
|
1128
|
+
if (estructuras?.libroComprasExentos) _todosLibrosNombres.push('LIBRO DE COMPRAS PARA EXENTOS');
|
|
1073
1129
|
let _librosAVerificar = _todosLibrosNombres.filter(n => !_estaConforme(n));
|
|
1074
1130
|
|
|
1075
1131
|
// Si SOAP detectó LNC/LRH para algún libro → excluirlos de la espera portal.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@devlas/dte-sii",
|
|
3
|
-
"version": "2.12.
|
|
3
|
+
"version": "2.12.15",
|
|
4
4
|
"description": "Facturación y boletas electrónicas para el SII de Chile. Genera, timbra, firma y envía DTEs, libros electrónicos y automatiza la certificación.",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"types": "dte-sii.d.ts",
|