@devlas/dte-sii 2.5.13 → 2.5.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/BoletaService.js +1 -1
- package/CafSolicitor.js +11 -12
- package/Certificado.js +2 -2
- package/ConsumoFolio.js +3 -3
- package/EnviadorSII.js +72 -99
- package/FolioService.js +1 -1
- package/SiiCertificacion.js +43 -20
- package/SiiPortalAuth.js +33 -9
- package/SiiSession.js +4 -4
- package/cert/BoletaCert.js +44 -44
- package/cert/CertRunner.js +466 -284
- package/cert/ConfigLoader.js +1 -1
- package/cert/IntercambioCert.js +19 -19
- package/cert/MuestrasImpresas.js +18 -18
- package/cert/SetBase.js +16 -16
- package/cert/SetBasico.js +6 -6
- package/cert/SetCompra.js +6 -6
- package/cert/SetExenta.js +6 -6
- package/cert/SetGuia.js +2 -2
- package/cert/SetsProvider.js +16 -19
- package/cert/Simulacion.js +1 -1
- package/package.json +2 -1
- package/utils/progress.js +78 -0
- package/utils/xml.js +2 -2
package/cert/CertRunner.js
CHANGED
|
@@ -43,6 +43,7 @@ const Simulacion = require('./Simulacion');
|
|
|
43
43
|
const IntercambioCert = require('./IntercambioCert');
|
|
44
44
|
|
|
45
45
|
const sleep = (ms) => new Promise((r) => setTimeout(r, ms));
|
|
46
|
+
const { STEPS, emitProgress } = require('../utils/progress');
|
|
46
47
|
|
|
47
48
|
/**
|
|
48
49
|
* @typedef {Object} CertConfig
|
|
@@ -136,6 +137,7 @@ class CertRunner {
|
|
|
136
137
|
pfxPassword: this.config.certificado.password,
|
|
137
138
|
rutEmpresa: rut,
|
|
138
139
|
dvEmpresa: dv,
|
|
140
|
+
sessionPath: this.sessionPath,
|
|
139
141
|
});
|
|
140
142
|
}
|
|
141
143
|
return this._siiCert;
|
|
@@ -210,7 +212,8 @@ class CertRunner {
|
|
|
210
212
|
this.folioHelper.usedFolios.clear();
|
|
211
213
|
|
|
212
214
|
for (const [tipoDte, cantidad] of Object.entries(cafRequired)) {
|
|
213
|
-
|
|
215
|
+
emitProgress(STEPS.CAF_REQUESTING, { tipo: Number(tipoDte) });
|
|
216
|
+
console.log(` Tipo ${tipoDte}: ${cantidad} folios...`);
|
|
214
217
|
|
|
215
218
|
// Usar solicitarCafConFallback que solicita y retorna el path
|
|
216
219
|
const cafPath = await this.folioService.solicitarCafConFallback({
|
|
@@ -223,7 +226,8 @@ class CertRunner {
|
|
|
223
226
|
}
|
|
224
227
|
|
|
225
228
|
cafs[tipoDte] = cafPath;
|
|
226
|
-
|
|
229
|
+
emitProgress(STEPS.CAF_OK, { tipo: Number(tipoDte) });
|
|
230
|
+
console.log(` ✓ CAF tipo ${tipoDte}`);
|
|
227
231
|
}
|
|
228
232
|
|
|
229
233
|
return cafs;
|
|
@@ -248,8 +252,6 @@ class CertRunner {
|
|
|
248
252
|
// Guardar envío consolidado
|
|
249
253
|
const envioPath = path.join(setsDir, `envio-set-${setName}.xml`);
|
|
250
254
|
fs.writeFileSync(envioPath, envio.xml, 'utf8');
|
|
251
|
-
console.log(` 📄 XML guardado: ${envioPath}`);
|
|
252
|
-
|
|
253
255
|
// Guardar DTEs individuales
|
|
254
256
|
if (envio.dtes && envio.dtes.length > 0) {
|
|
255
257
|
const dtesDir = path.join(setsDir, 'dtes');
|
|
@@ -312,20 +314,36 @@ class CertRunner {
|
|
|
312
314
|
}
|
|
313
315
|
|
|
314
316
|
async ejecutarSetBasico(casos) {
|
|
315
|
-
|
|
317
|
+
emitProgress(STEPS.SET_START, { set: 'basico' });
|
|
318
|
+
const r = await this._ejecutarSet(SetBasico, 'setBasico', 'basico', { 33: 4, 56: 1, 61: 3 }, 'basico', casos);
|
|
319
|
+
if (r.success) emitProgress(STEPS.SET_OK, { set: 'basico', trackId: r.trackId });
|
|
320
|
+
else emitProgress(STEPS.SET_ERROR, { set: 'basico', error: r.error });
|
|
321
|
+
return r;
|
|
316
322
|
}
|
|
317
323
|
|
|
318
324
|
async ejecutarSetGuia(casos) {
|
|
319
|
-
|
|
325
|
+
emitProgress(STEPS.SET_START, { set: 'guia' });
|
|
326
|
+
const r = await this._ejecutarSet(SetGuia, 'setGuiaDespacho', 'guia',
|
|
320
327
|
(setData) => ({ 52: setData.casos?.length || 1 }), 'guia', casos);
|
|
328
|
+
if (r.success) emitProgress(STEPS.SET_OK, { set: 'guia', trackId: r.trackId });
|
|
329
|
+
else emitProgress(STEPS.SET_ERROR, { set: 'guia', error: r.error });
|
|
330
|
+
return r;
|
|
321
331
|
}
|
|
322
332
|
|
|
323
333
|
async ejecutarSetExenta(casos) {
|
|
324
|
-
|
|
334
|
+
emitProgress(STEPS.SET_START, { set: 'exenta' });
|
|
335
|
+
const r = await this._ejecutarSet(SetExenta, 'setFacturaExenta', 'exenta', { 34: 3, 56: 1, 61: 4 }, 'exenta', casos);
|
|
336
|
+
if (r.success) emitProgress(STEPS.SET_OK, { set: 'exenta', trackId: r.trackId });
|
|
337
|
+
else emitProgress(STEPS.SET_ERROR, { set: 'exenta', error: r.error });
|
|
338
|
+
return r;
|
|
325
339
|
}
|
|
326
340
|
|
|
327
341
|
async ejecutarSetCompra(casos) {
|
|
328
|
-
|
|
342
|
+
emitProgress(STEPS.SET_START, { set: 'compra' });
|
|
343
|
+
const r = await this._ejecutarSet(SetCompra, 'setFacturaCompra', 'compra', { 46: 1, 56: 1, 61: 1 }, 'compra', casos);
|
|
344
|
+
if (r.success) emitProgress(STEPS.SET_OK, { set: 'compra', trackId: r.trackId });
|
|
345
|
+
else emitProgress(STEPS.SET_ERROR, { set: 'compra', error: r.error });
|
|
346
|
+
return r;
|
|
329
347
|
}
|
|
330
348
|
|
|
331
349
|
/**
|
|
@@ -336,14 +354,15 @@ class CertRunner {
|
|
|
336
354
|
* @param {Object} [options] - { maxIntentos, intervalo, label }
|
|
337
355
|
*/
|
|
338
356
|
async _declararConReintentos(sets, debugPrefix, options = {}) {
|
|
339
|
-
const { maxIntentos = 10, intervalo = 5000, label = 'avance' } = options;
|
|
357
|
+
const { maxIntentos = 10, intervalo = 5000, label = 'avance', retryOnAllRejected = false } = options;
|
|
340
358
|
|
|
341
|
-
console.log(`
|
|
359
|
+
console.log(` Esperando 10s para que SII procese los envios...`);
|
|
342
360
|
await sleep(10000);
|
|
343
361
|
|
|
344
362
|
let lastResult = null;
|
|
345
363
|
for (let intento = 1; intento <= maxIntentos; intento++) {
|
|
346
|
-
|
|
364
|
+
emitProgress(STEPS.POLLING, { intento, max: maxIntentos, label });
|
|
365
|
+
console.log(` Declarando ${label} (intento ${intento}/${maxIntentos})...`);
|
|
347
366
|
|
|
348
367
|
const result = await this.siiCert.declararAvance({ sets });
|
|
349
368
|
lastResult = result;
|
|
@@ -378,15 +397,24 @@ class CertRunner {
|
|
|
378
397
|
}
|
|
379
398
|
|
|
380
399
|
if (noProcessedError && intento < maxIntentos) {
|
|
381
|
-
console.log(`
|
|
400
|
+
console.log(` [...] SII aún procesando, reintentando en ${intervalo / 1000}s...`);
|
|
382
401
|
await sleep(intervalo);
|
|
402
|
+
} else if (result.allRejected) {
|
|
403
|
+
// SII rechazó todos los sets/libros — puede ser período incorrecto (libros) o TrackID no procesado aún (simulación)
|
|
404
|
+
if (retryOnAllRejected && intento < maxIntentos) {
|
|
405
|
+
console.log(` [!] S21 — SII aún procesando TrackID, reintentando en ${intervalo / 1000}s...`);
|
|
406
|
+
await sleep(intervalo);
|
|
407
|
+
} else {
|
|
408
|
+
console.log(` [ERR] SII rechazó todos los envíos (campos vacíos en portal) — período incorrecto. Corregir período y reenviar.`);
|
|
409
|
+
break;
|
|
410
|
+
}
|
|
383
411
|
} else if (result.verificado === false && intento < maxIntentos) {
|
|
384
412
|
// Verificación post-declaración falló: los campos quedaron vacíos en el portal
|
|
385
|
-
console.log(`
|
|
386
|
-
console.log(`
|
|
413
|
+
console.log(` [!] Verificación fallida: ${result.error}`);
|
|
414
|
+
console.log(` [...] Reintentando declaración en ${intervalo / 1000}s...`);
|
|
387
415
|
await sleep(intervalo);
|
|
388
416
|
} else if (!result.success) {
|
|
389
|
-
console.log(`
|
|
417
|
+
console.log(` [!] Error declarando ${label}: ${result.error || 'desconocido'}`);
|
|
390
418
|
break;
|
|
391
419
|
}
|
|
392
420
|
}
|
|
@@ -434,8 +462,9 @@ class CertRunner {
|
|
|
434
462
|
return { success: false, error: 'No hay sets para declarar' };
|
|
435
463
|
}
|
|
436
464
|
|
|
465
|
+
emitProgress(STEPS.SETS_DECLARING);
|
|
437
466
|
const result = await this._declararConReintentos(sets, 'declaracion-response', { maxIntentos, intervalo, label: 'avance de sets' });
|
|
438
|
-
if (result?.success) console.log('
|
|
467
|
+
if (result?.success) { emitProgress(STEPS.SETS_DECLARED); console.log(' ✓ Declaracion de sets enviada'); }
|
|
439
468
|
return result;
|
|
440
469
|
}
|
|
441
470
|
|
|
@@ -480,13 +509,19 @@ class CertRunner {
|
|
|
480
509
|
*/
|
|
481
510
|
async ejecutarFase4Libros(options = {}) {
|
|
482
511
|
// NOTA: Ya NO decrementamos aquí - cada libro decrementa su propio período
|
|
512
|
+
emitProgress(STEPS.BOOKS_START);
|
|
483
513
|
console.log('\n' + '═'.repeat(60));
|
|
484
|
-
console.log(
|
|
514
|
+
console.log('FASE 4: LIBROS (todos usan el mismo periodo)');
|
|
485
515
|
console.log('═'.repeat(60) + '\n');
|
|
486
516
|
|
|
487
517
|
const resultados = {};
|
|
488
518
|
const errores = [];
|
|
489
519
|
|
|
520
|
+
// Decrementar período UNA VEZ para todos los libros (todos usan el mismo período)
|
|
521
|
+
this._decrementarPeriodoLibros();
|
|
522
|
+
const _periodoComunLibros = this._getPeriodoLibros();
|
|
523
|
+
console.log(` Período para todos los libros: ${_periodoComunLibros}`);
|
|
524
|
+
|
|
490
525
|
// Verificar cuáles libros ya están REVISADO CONFORME en el portal (no re-enviar)
|
|
491
526
|
let _estadoActual = {};
|
|
492
527
|
try {
|
|
@@ -496,7 +531,7 @@ class CertRunner {
|
|
|
496
531
|
.filter(([k, v]) => k.toUpperCase().includes('LIBRO') && v === 'REVISADO CONFORME')
|
|
497
532
|
.map(([k]) => k);
|
|
498
533
|
if (_yaConformes.length) {
|
|
499
|
-
console.log(`
|
|
534
|
+
console.log(` Ya en REVISADO CONFORME (se omitirán): ${_yaConformes.join(', ')}`);
|
|
500
535
|
}
|
|
501
536
|
} catch (_e) { /* ignorar error de consulta previa */ }
|
|
502
537
|
|
|
@@ -521,13 +556,18 @@ class CertRunner {
|
|
|
521
556
|
try {
|
|
522
557
|
// 1. Libro de Compras (usa datos del SII)
|
|
523
558
|
if (_estaConforme('LIBRO DE COMPRAS')) {
|
|
524
|
-
|
|
559
|
+
emitProgress(STEPS.BOOK_SKIPPED, { book: 'libroCompras' });
|
|
560
|
+
resultados.libroCompras = { success: true, conforme: true };
|
|
561
|
+
console.log('\n[OK] Libro de Compras ya esta REVISADO CONFORME — omitiendo');
|
|
525
562
|
} else {
|
|
526
|
-
|
|
527
|
-
|
|
563
|
+
emitProgress(STEPS.BOOK_SENDING, { book: 'libroCompras' });
|
|
564
|
+
console.log('\nEnviando Libro de Compras...');
|
|
565
|
+
resultados.libroCompras = await this.ejecutarLibroCompras({ ...options, periodo: _periodoComunLibros });
|
|
528
566
|
if (!resultados.libroCompras.success) {
|
|
567
|
+
emitProgress(STEPS.BOOK_ERROR, { book: 'libroCompras', error: resultados.libroCompras.error });
|
|
529
568
|
errores.push(`Libro Compras: ${resultados.libroCompras.error}`);
|
|
530
569
|
} else {
|
|
570
|
+
emitProgress(STEPS.BOOK_OK, { book: 'libroCompras', trackId: resultados.libroCompras.trackId });
|
|
531
571
|
_guardarResultadosParciales();
|
|
532
572
|
}
|
|
533
573
|
}
|
|
@@ -538,13 +578,18 @@ class CertRunner {
|
|
|
538
578
|
try {
|
|
539
579
|
// 2. Libro de Ventas (usa SetBasico)
|
|
540
580
|
if (_estaConforme('LIBRO DE VENTAS')) {
|
|
541
|
-
|
|
581
|
+
emitProgress(STEPS.BOOK_SKIPPED, { book: 'libroVentas' });
|
|
582
|
+
resultados.libroVentas = { success: true, conforme: true };
|
|
583
|
+
console.log('\n[OK] Libro de Ventas ya esta REVISADO CONFORME — omitiendo');
|
|
542
584
|
} else {
|
|
543
|
-
|
|
544
|
-
|
|
585
|
+
emitProgress(STEPS.BOOK_SENDING, { book: 'libroVentas' });
|
|
586
|
+
console.log('\nEnviando Libro de Ventas...');
|
|
587
|
+
resultados.libroVentas = await this.ejecutarLibroVentas({ ...options, periodo: _periodoComunLibros });
|
|
545
588
|
if (!resultados.libroVentas.success) {
|
|
589
|
+
emitProgress(STEPS.BOOK_ERROR, { book: 'libroVentas', error: resultados.libroVentas.error });
|
|
546
590
|
errores.push(`Libro Ventas: ${resultados.libroVentas.error}`);
|
|
547
591
|
} else {
|
|
592
|
+
emitProgress(STEPS.BOOK_OK, { book: 'libroVentas', trackId: resultados.libroVentas.trackId });
|
|
548
593
|
_guardarResultadosParciales();
|
|
549
594
|
}
|
|
550
595
|
}
|
|
@@ -555,13 +600,18 @@ class CertRunner {
|
|
|
555
600
|
try {
|
|
556
601
|
// 3. Libro de Guías (usa SetGuia)
|
|
557
602
|
if (_estaConforme('LIBRO DE GUIAS')) {
|
|
558
|
-
|
|
603
|
+
emitProgress(STEPS.BOOK_SKIPPED, { book: 'libroGuias' });
|
|
604
|
+
resultados.libroGuias = { success: true, conforme: true };
|
|
605
|
+
console.log('\n[OK] Libro de Guias ya esta REVISADO CONFORME — omitiendo');
|
|
559
606
|
} else {
|
|
560
|
-
|
|
561
|
-
|
|
607
|
+
emitProgress(STEPS.BOOK_SENDING, { book: 'libroGuias' });
|
|
608
|
+
console.log('\nEnviando Libro de Guias...');
|
|
609
|
+
resultados.libroGuias = await this.ejecutarLibroGuias({ ...options, periodo: _periodoComunLibros });
|
|
562
610
|
if (!resultados.libroGuias.success) {
|
|
563
|
-
|
|
611
|
+
emitProgress(STEPS.BOOK_ERROR, { book: 'libroGuias', error: resultados.libroGuias.error });
|
|
612
|
+
errores.push(`Libro Guias: ${resultados.libroGuias.error}`);
|
|
564
613
|
} else {
|
|
614
|
+
emitProgress(STEPS.BOOK_OK, { book: 'libroGuias', trackId: resultados.libroGuias.trackId });
|
|
565
615
|
_guardarResultadosParciales();
|
|
566
616
|
}
|
|
567
617
|
}
|
|
@@ -573,13 +623,18 @@ class CertRunner {
|
|
|
573
623
|
if (this._estructuras?.libroComprasExentos) {
|
|
574
624
|
try {
|
|
575
625
|
if (_estaConforme('LIBRO DE COMPRAS PARA EXENTOS')) {
|
|
576
|
-
|
|
626
|
+
emitProgress(STEPS.BOOK_SKIPPED, { book: 'libroComprasExentos' });
|
|
627
|
+
resultados.libroComprasExentos = { success: true, conforme: true };
|
|
628
|
+
console.log('\n[OK] Libro Compras Exentos ya esta REVISADO CONFORME — omitiendo');
|
|
577
629
|
} else {
|
|
578
|
-
|
|
579
|
-
|
|
630
|
+
emitProgress(STEPS.BOOK_SENDING, { book: 'libroComprasExentos' });
|
|
631
|
+
console.log('\nEnviando Libro de Compras para Exentos...');
|
|
632
|
+
resultados.libroComprasExentos = await this.ejecutarLibroComprasExentos({ ...options, periodo: _periodoComunLibros });
|
|
580
633
|
if (!resultados.libroComprasExentos.success) {
|
|
634
|
+
emitProgress(STEPS.BOOK_ERROR, { book: 'libroComprasExentos', error: resultados.libroComprasExentos.error });
|
|
581
635
|
errores.push(`Libro Compras Exentos: ${resultados.libroComprasExentos.error}`);
|
|
582
636
|
} else {
|
|
637
|
+
emitProgress(STEPS.BOOK_OK, { book: 'libroComprasExentos', trackId: resultados.libroComprasExentos.trackId });
|
|
583
638
|
_guardarResultadosParciales();
|
|
584
639
|
}
|
|
585
640
|
}
|
|
@@ -599,64 +654,193 @@ class CertRunner {
|
|
|
599
654
|
}).length;
|
|
600
655
|
|
|
601
656
|
if (librosEnviados === 3) {
|
|
602
|
-
//
|
|
603
|
-
|
|
657
|
+
// Mapeo entre nombre SII y clave interna
|
|
658
|
+
const _SII_NOMBRE_A_KEY = {
|
|
659
|
+
'LIBRO DE VENTAS': 'libroVentas',
|
|
660
|
+
'LIBRO DE COMPRAS': 'libroCompras',
|
|
661
|
+
'LIBRO DE GUIAS': 'libroGuias',
|
|
662
|
+
'LIBRO DE COMPRAS PARA EXENTOS': 'libroComprasExentos',
|
|
663
|
+
};
|
|
664
|
+
const _KEY_A_SII_NOMBRE = Object.fromEntries(Object.entries(_SII_NOMBRE_A_KEY).map(([n, k]) => [k, n]));
|
|
665
|
+
|
|
666
|
+
// Busca el entry de _ss para un nombre SII (COMPRAS sin EXENTOS, etc.)
|
|
667
|
+
const _findEntry = (ss, nombre) => Object.entries(ss).find(([k]) => {
|
|
668
|
+
const ku = k.toUpperCase();
|
|
669
|
+
if (nombre === 'LIBRO DE COMPRAS') return ku.includes('LIBRO DE COMPRAS') && !ku.includes('EXENTOS');
|
|
670
|
+
return ku.includes(nombre);
|
|
671
|
+
});
|
|
672
|
+
|
|
673
|
+
// Retorna claves internas que están en S21 en ss, de entre los especificados
|
|
674
|
+
const _getS21Keys = (ss, nombres) =>
|
|
675
|
+
nombres
|
|
676
|
+
.filter(n => { const e = _findEntry(ss, n); return e && e[1] === 'S21'; })
|
|
677
|
+
.map(n => _SII_NOMBRE_A_KEY[n])
|
|
678
|
+
.filter(Boolean);
|
|
679
|
+
|
|
680
|
+
// Helper para re-enviar los libros no conformes con un nuevo período
|
|
681
|
+
// keysAReenviar: Set opcional — si se pasa, solo re-envía las claves del Set
|
|
682
|
+
const _reenviarLibros = async (nuevoPeriodo, keysAReenviar) => {
|
|
683
|
+
const _orden = [
|
|
684
|
+
{ key: 'libroCompras', fn: (p) => this.ejecutarLibroCompras({ ...options, periodo: p }) },
|
|
685
|
+
{ key: 'libroVentas', fn: (p) => this.ejecutarLibroVentas({ ...options, periodo: p }) },
|
|
686
|
+
{ key: 'libroGuias', fn: (p) => this.ejecutarLibroGuias({ ...options, periodo: p }) },
|
|
687
|
+
{ key: 'libroComprasExentos', fn: (p) => this.ejecutarLibroComprasExentos({ ...options, periodo: p }) },
|
|
688
|
+
];
|
|
689
|
+
for (const { key, fn } of _orden) {
|
|
690
|
+
if (resultados[key]?.conforme) continue; // ya conforme en SII
|
|
691
|
+
if (keysAReenviar && !keysAReenviar.has(key)) continue; // filtro por S21
|
|
692
|
+
emitProgress(STEPS.BOOK_SENDING, { book: key });
|
|
693
|
+
try {
|
|
694
|
+
resultados[key] = await fn(nuevoPeriodo);
|
|
695
|
+
if (!resultados[key].success) {
|
|
696
|
+
emitProgress(STEPS.BOOK_ERROR, { book: key, error: resultados[key].error });
|
|
697
|
+
} else {
|
|
698
|
+
emitProgress(STEPS.BOOK_OK, { book: key, trackId: resultados[key].trackId });
|
|
699
|
+
}
|
|
700
|
+
} catch (e) {
|
|
701
|
+
resultados[key] = { success: false, error: e.message };
|
|
702
|
+
}
|
|
703
|
+
}
|
|
704
|
+
};
|
|
705
|
+
|
|
706
|
+
// Polling de aprobación (reutilizable)
|
|
707
|
+
// librosAVerificar: array de nombres SII a esperar. Si se omite, usa todos los no-conformes.
|
|
708
|
+
// Devuelve { ok, estadosFinal }
|
|
709
|
+
// Bail-out anticipado: si todos los pendientes llevan 5 polls consecutivos en S21 → período incorrecto
|
|
710
|
+
const _esperarAprobacion = async (librosAVerificar) => {
|
|
711
|
+
const _todosCandidatos = ['LIBRO DE VENTAS', 'LIBRO DE COMPRAS', 'LIBRO DE GUIAS'];
|
|
712
|
+
if (this._estructuras?.libroComprasExentos) _todosCandidatos.push('LIBRO DE COMPRAS PARA EXENTOS');
|
|
713
|
+
const _librosAVerif = librosAVerificar || _todosCandidatos.filter(n => !_estaConforme(n));
|
|
714
|
+
console.log(`\nEsperando aprobacion del SII para: ${_librosAVerif.join(', ')}`);
|
|
715
|
+
let _ss = {};
|
|
716
|
+
let _consecutivosS21 = 0;
|
|
717
|
+
for (let _i = 0; _i < 20; _i++) {
|
|
718
|
+
await sleep(15000);
|
|
719
|
+
emitProgress(STEPS.POLLING, { intento: _i + 1, max: 20, label: 'libros' });
|
|
720
|
+
const _poll = await this.siiCert.consultarEstadoSets();
|
|
721
|
+
if (!_poll.success) continue;
|
|
722
|
+
_ss = _poll.estadoSets || {};
|
|
723
|
+
const _info = Object.entries(_ss).filter(([k]) => k.toUpperCase().includes('LIBRO')).map(([k, v]) => `${k.trim()}: ${v}`);
|
|
724
|
+
if (_info.length) console.log(` [...] Intento ${_i + 1}/20: ${_info.join(' | ')}`);
|
|
725
|
+
const _librosObs = ['LIBRO DE VENTAS', 'LIBRO DE COMPRAS', 'LIBRO DE GUIAS'];
|
|
726
|
+
const _todosObligatoriosOk = _librosObs.every(n => {
|
|
727
|
+
const e = _findEntry(_ss, n);
|
|
728
|
+
return e && (e[1] === 'REVISADO CONFORME' || e[1] === 'S25');
|
|
729
|
+
});
|
|
730
|
+
const _todosOk = _librosAVerif.every(n => {
|
|
731
|
+
const e = _findEntry(_ss, n);
|
|
732
|
+
return e && (e[1] === 'REVISADO CONFORME' || e[1] === 'S25');
|
|
733
|
+
});
|
|
734
|
+
const _algunError = _librosAVerif.some(n => {
|
|
735
|
+
const e = _findEntry(_ss, n);
|
|
736
|
+
return e && (e[1] === 'LNC' || e[1] === 'LRH' || e[1].includes('RECHAZADO') || e[1].includes('ERROR'));
|
|
737
|
+
});
|
|
738
|
+
if (_todosOk) {
|
|
739
|
+
emitProgress(STEPS.BOOKS_DONE);
|
|
740
|
+
console.log('\n[OK] LIBROS APROBADOS POR EL SII!');
|
|
741
|
+
return { ok: true, estadosFinal: _ss };
|
|
742
|
+
}
|
|
743
|
+
if (_algunError) {
|
|
744
|
+
console.log('\n[ERR] Hay libros rechazados. Revisar emails del SII.');
|
|
745
|
+
return { ok: false, estadosFinal: _ss };
|
|
746
|
+
}
|
|
747
|
+
// Bail-out anticipado: todos los pendientes llevan N polls en S21 → período incorrecto
|
|
748
|
+
const _pendientesAun = _librosAVerif.filter(n => {
|
|
749
|
+
const e = _findEntry(_ss, n);
|
|
750
|
+
return !e || (e[1] !== 'REVISADO CONFORME' && e[1] !== 'S25');
|
|
751
|
+
});
|
|
752
|
+
const _todosS21 = _pendientesAun.length > 0 && _pendientesAun.every(n => {
|
|
753
|
+
const e = _findEntry(_ss, n);
|
|
754
|
+
return e && e[1] === 'S21';
|
|
755
|
+
});
|
|
756
|
+
if (_todosS21) {
|
|
757
|
+
_consecutivosS21++;
|
|
758
|
+
if (_consecutivosS21 >= 5) {
|
|
759
|
+
console.log(`\n[!] ${_pendientesAun.join(', ')} llevan ${_consecutivosS21} polls en S21 — período incorrecto.`);
|
|
760
|
+
return { ok: false, estadosFinal: _ss, stuckS21: true };
|
|
761
|
+
}
|
|
762
|
+
} else {
|
|
763
|
+
_consecutivosS21 = 0;
|
|
764
|
+
}
|
|
765
|
+
}
|
|
766
|
+
console.log('\n[!] Timeout (5 min). El SII aún no responde. Verifica con --avance más tarde.');
|
|
767
|
+
return { ok: false, estadosFinal: _ss };
|
|
768
|
+
};
|
|
769
|
+
|
|
770
|
+
// 4. Declarar + retry automático:
|
|
771
|
+
// a) si allRejected al declarar → decrementar período y re-enviar todos
|
|
772
|
+
// b) si libros quedan en S21 tras polling → decrementar y re-enviar solo los S21
|
|
773
|
+
emitProgress(STEPS.BOOKS_DECLARING);
|
|
774
|
+
console.log('\nDeclarando libros...');
|
|
775
|
+
const MAX_PERIOD_RETRIES = 120;
|
|
604
776
|
try {
|
|
605
|
-
|
|
777
|
+
let declaracion = await this.declararLibros({ ...resultados, ...(options.setsResultados || {}) });
|
|
606
778
|
resultados.declaracion = declaracion;
|
|
607
|
-
|
|
779
|
+
|
|
780
|
+
// Fase a: allRejected al declarar (período rechazado en pe_avance3)
|
|
781
|
+
for (let _pRetry = 0; _pRetry < MAX_PERIOD_RETRIES && declaracion.allRejected; _pRetry++) {
|
|
782
|
+
this._decrementarPeriodoLibros();
|
|
783
|
+
const _nuevoPeriodo = this._getPeriodoLibros();
|
|
784
|
+
console.log(`\n[!] Período rechazado por SII. Reintentando con ${_nuevoPeriodo} (${_pRetry + 1}/${MAX_PERIOD_RETRIES})...`);
|
|
785
|
+
await _reenviarLibros(_nuevoPeriodo);
|
|
786
|
+
declaracion = await this.declararLibros({ ...resultados, ...(options.setsResultados || {}) });
|
|
787
|
+
resultados.declaracion = declaracion;
|
|
788
|
+
}
|
|
789
|
+
|
|
608
790
|
if (declaracion.success) {
|
|
609
|
-
console.log('\n
|
|
610
|
-
|
|
611
|
-
//
|
|
612
|
-
const
|
|
613
|
-
if (this._estructuras?.libroComprasExentos)
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
for (let
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
const
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
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;
|
|
791
|
+
console.log('\n[OK] Libros declarados — esperando revisión del SII...');
|
|
792
|
+
|
|
793
|
+
// Construir la lista inicial de libros a verificar
|
|
794
|
+
const _todosLibrosNombres = ['LIBRO DE VENTAS', 'LIBRO DE COMPRAS', 'LIBRO DE GUIAS'];
|
|
795
|
+
if (this._estructuras?.libroComprasExentos) _todosLibrosNombres.push('LIBRO DE COMPRAS PARA EXENTOS');
|
|
796
|
+
let _librosAVerificar = _todosLibrosNombres.filter(n => !_estaConforme(n));
|
|
797
|
+
|
|
798
|
+
let { ok, estadosFinal } = await _esperarAprobacion(_librosAVerificar);
|
|
799
|
+
|
|
800
|
+
// Fase b: algunos libros quedaron en S21 → re-enviar solo esos con período decrementado
|
|
801
|
+
for (let _pRetry = 0; !ok && _pRetry < MAX_PERIOD_RETRIES; _pRetry++) {
|
|
802
|
+
const _s21Keys = _getS21Keys(estadosFinal, _librosAVerificar);
|
|
803
|
+
if (_s21Keys.length === 0) break; // errores reales (LNC/LRH), no de período
|
|
804
|
+
|
|
805
|
+
this._decrementarPeriodoLibros();
|
|
806
|
+
const _nuevoPeriodo = this._getPeriodoLibros();
|
|
807
|
+
const _s21Nombres = _s21Keys.map(k => _KEY_A_SII_NOMBRE[k]).filter(Boolean);
|
|
808
|
+
console.log(`\n[!] ${_s21Nombres.join(', ')} bloqueados en S21. Reintentando con período ${_nuevoPeriodo} (${_pRetry + 1}/${MAX_PERIOD_RETRIES})...`);
|
|
809
|
+
|
|
810
|
+
await _reenviarLibros(_nuevoPeriodo, new Set(_s21Keys));
|
|
811
|
+
declaracion = await this.declararLibros({ ...resultados, ...(options.setsResultados || {}) });
|
|
812
|
+
resultados.declaracion = declaracion;
|
|
813
|
+
|
|
814
|
+
if (!declaracion.success && !declaracion.allRejected) {
|
|
815
|
+
console.log(`\n[ERR] Declaración fallida: ${declaracion.error}`);
|
|
641
816
|
break;
|
|
642
817
|
}
|
|
643
|
-
if (
|
|
644
|
-
|
|
645
|
-
|
|
818
|
+
if (declaracion.success) {
|
|
819
|
+
// Solo verificar los libros que acabamos de re-enviar
|
|
820
|
+
_librosAVerificar = _s21Nombres;
|
|
821
|
+
;({ ok, estadosFinal } = await _esperarAprobacion(_librosAVerificar));
|
|
646
822
|
}
|
|
823
|
+
// si allRejected → continuar loop (decrementar de nuevo)
|
|
647
824
|
}
|
|
648
|
-
|
|
649
|
-
|
|
825
|
+
|
|
826
|
+
if (!ok) {
|
|
827
|
+
console.log('\n[!] No se pudo obtener aprobación del SII para todos los libros.');
|
|
650
828
|
}
|
|
651
829
|
} else {
|
|
652
|
-
|
|
830
|
+
const _errorDeclaracion = declaracion.error || 'Declaración rechazada por SII';
|
|
831
|
+
console.log(`\n[ERR] Declaración de libros fallida: ${_errorDeclaracion}`);
|
|
832
|
+
for (const k of ['libroVentas', 'libroCompras', 'libroGuias', 'libroComprasExentos']) {
|
|
833
|
+
if (resultados[k]?.success && !resultados[k]?.conforme) {
|
|
834
|
+
resultados[k] = { ...resultados[k], success: false, error: _errorDeclaracion };
|
|
835
|
+
}
|
|
836
|
+
}
|
|
653
837
|
}
|
|
654
838
|
} catch (e) {
|
|
655
|
-
console.log(`\n
|
|
839
|
+
console.log(`\n[!] Error declarando libros: ${e.message}`);
|
|
656
840
|
resultados.declaracion = { success: false, error: e.message };
|
|
657
841
|
}
|
|
658
842
|
} else {
|
|
659
|
-
console.log(`\n
|
|
843
|
+
console.log(`\n[!] Solo ${librosEnviados}/3 libros enviados. Errores: ${errores.join('; ')}`);
|
|
660
844
|
}
|
|
661
845
|
|
|
662
846
|
return {
|
|
@@ -706,7 +890,7 @@ class CertRunner {
|
|
|
706
890
|
const result = await this._declararConReintentos(sets, 'declaracion-libros-response', { maxIntentos, intervalo, label: 'libros' });
|
|
707
891
|
if (result?.success) {
|
|
708
892
|
const declarados = result.setsDeclarados || [];
|
|
709
|
-
console.log(`
|
|
893
|
+
console.log(` [OK] Libros declarados: ${declarados.join(', ')}`);
|
|
710
894
|
}
|
|
711
895
|
return result;
|
|
712
896
|
}
|
|
@@ -774,9 +958,9 @@ class CertRunner {
|
|
|
774
958
|
|
|
775
959
|
try {
|
|
776
960
|
fs.writeFileSync(stateFile, JSON.stringify(state, null, 2));
|
|
777
|
-
console.log(`
|
|
961
|
+
console.log(` Período decrementado: ${currentPeriodo} → ${newPeriodo}`);
|
|
778
962
|
} catch (e) {
|
|
779
|
-
console.warn(`
|
|
963
|
+
console.warn(` [!] No se pudo guardar período: ${e.message}`);
|
|
780
964
|
}
|
|
781
965
|
|
|
782
966
|
return newPeriodo;
|
|
@@ -792,9 +976,9 @@ class CertRunner {
|
|
|
792
976
|
|
|
793
977
|
try {
|
|
794
978
|
fs.writeFileSync(stateFile, JSON.stringify(state, null, 2));
|
|
795
|
-
console.log(`
|
|
979
|
+
console.log(` Período reseteado a: ${periodo}`);
|
|
796
980
|
} catch (e) {
|
|
797
|
-
console.warn(`
|
|
981
|
+
console.warn(` [!] No se pudo guardar período: ${e.message}`);
|
|
798
982
|
}
|
|
799
983
|
}
|
|
800
984
|
|
|
@@ -820,10 +1004,9 @@ class CertRunner {
|
|
|
820
1004
|
throw new Error('No hay resultado del SetBasico. Ejecutar ejecutarSetBasico() primero.');
|
|
821
1005
|
}
|
|
822
1006
|
|
|
823
|
-
//
|
|
824
|
-
this._decrementarPeriodoLibros();
|
|
825
|
-
|
|
826
|
-
console.log(` 📚 Generando Libro de Ventas para período ${periodo}...`);
|
|
1007
|
+
// Usar período pasado por opción (fase4 lo decrementa una vez para todos) o decrementar individualmente
|
|
1008
|
+
const periodo = options.periodo || (this._decrementarPeriodoLibros(), this._getPeriodoLibros());
|
|
1009
|
+
console.log(` Generando Libro de Ventas para período ${periodo}...`);
|
|
827
1010
|
|
|
828
1011
|
const libroVentas = new LibroVentas({
|
|
829
1012
|
emisor: this.config.emisor,
|
|
@@ -838,7 +1021,6 @@ class CertRunner {
|
|
|
838
1021
|
// Guardar XML de debug
|
|
839
1022
|
const outPath = path.join(this.debugDir, 'libro-ventas.xml');
|
|
840
1023
|
fs.writeFileSync(outPath, xml, 'utf-8');
|
|
841
|
-
console.log(` XML guardado: ${outPath}`);
|
|
842
1024
|
|
|
843
1025
|
// Enviar al SII
|
|
844
1026
|
const enviador = this._createLibroEnviador();
|
|
@@ -855,9 +1037,9 @@ class CertRunner {
|
|
|
855
1037
|
this.resultados.libroVentas = result;
|
|
856
1038
|
|
|
857
1039
|
if (result.success) {
|
|
858
|
-
console.log(`
|
|
1040
|
+
console.log(` [OK] Libro de Ventas enviado - TrackId: ${result.trackId}`);
|
|
859
1041
|
} else {
|
|
860
|
-
console.log(`
|
|
1042
|
+
console.log(` [ERR] Error enviando Libro de Ventas: ${result.error}`);
|
|
861
1043
|
}
|
|
862
1044
|
|
|
863
1045
|
return result;
|
|
@@ -872,9 +1054,7 @@ class CertRunner {
|
|
|
872
1054
|
async ejecutarLibroCompras(options = {}) {
|
|
873
1055
|
const libroComprasData = options.libroComprasData || this._estructuras?.libroCompras;
|
|
874
1056
|
|
|
875
|
-
|
|
876
|
-
this._decrementarPeriodoLibros();
|
|
877
|
-
const periodo = this._getPeriodoLibros();
|
|
1057
|
+
const periodo = options.periodo || (this._decrementarPeriodoLibros(), this._getPeriodoLibros());
|
|
878
1058
|
|
|
879
1059
|
const libroCompras = new LibroCompras({
|
|
880
1060
|
emisor: this.config.emisor,
|
|
@@ -886,13 +1066,12 @@ class CertRunner {
|
|
|
886
1066
|
throw new Error('No hay datos del libro de compras. El SII no entregó el set LIBRO_COMPRAS al obtener las estructuras.');
|
|
887
1067
|
}
|
|
888
1068
|
|
|
889
|
-
console.log(`
|
|
1069
|
+
console.log(` Generando Libro de Compras para período ${periodo} (${libroComprasData.detalle.length} documentos del SII)...`);
|
|
890
1070
|
const { libro, xml, detalle, resumen } = libroCompras.generarDesdeEstructuras(libroComprasData, periodo);
|
|
891
1071
|
|
|
892
1072
|
// Guardar XML de debug
|
|
893
1073
|
const outPath = path.join(this.debugDir, 'libro-compras.xml');
|
|
894
1074
|
fs.writeFileSync(outPath, xml, 'utf-8');
|
|
895
|
-
console.log(` XML guardado: ${outPath}`);
|
|
896
1075
|
|
|
897
1076
|
// Enviar al SII
|
|
898
1077
|
const enviador = this._createLibroEnviador();
|
|
@@ -909,9 +1088,9 @@ class CertRunner {
|
|
|
909
1088
|
this.resultados.libroCompras = result;
|
|
910
1089
|
|
|
911
1090
|
if (result.success) {
|
|
912
|
-
console.log(`
|
|
1091
|
+
console.log(` [OK] Libro de Compras enviado - TrackId: ${result.trackId}`);
|
|
913
1092
|
} else {
|
|
914
|
-
console.log(`
|
|
1093
|
+
console.log(` [ERR] Error enviando Libro de Compras: ${result.error}`);
|
|
915
1094
|
}
|
|
916
1095
|
|
|
917
1096
|
return result;
|
|
@@ -928,8 +1107,7 @@ class CertRunner {
|
|
|
928
1107
|
throw new Error('No hay datos del libro de compras para exentos. El SII no entregó el set LIBRO_COMPRAS_EXENTOS.');
|
|
929
1108
|
}
|
|
930
1109
|
|
|
931
|
-
this._decrementarPeriodoLibros();
|
|
932
|
-
const periodo = this._getPeriodoLibros();
|
|
1110
|
+
const periodo = options.periodo || (this._decrementarPeriodoLibros(), this._getPeriodoLibros());
|
|
933
1111
|
|
|
934
1112
|
const libroCompras = new LibroCompras({
|
|
935
1113
|
emisor: this.config.emisor,
|
|
@@ -937,12 +1115,11 @@ class CertRunner {
|
|
|
937
1115
|
certificado: this.certificado,
|
|
938
1116
|
});
|
|
939
1117
|
|
|
940
|
-
console.log(`
|
|
1118
|
+
console.log(` Generando Libro de Compras para Exentos para período ${periodo} (${libroData.detalle.length} documentos del SII)...`);
|
|
941
1119
|
const { libro, xml, detalle } = libroCompras.generarDesdeEstructuras(libroData, periodo);
|
|
942
1120
|
|
|
943
1121
|
const outPath = path.join(this.debugDir, 'libro-compras-exentos.xml');
|
|
944
1122
|
fs.writeFileSync(outPath, xml, 'utf-8');
|
|
945
|
-
console.log(` XML guardado: ${outPath}`);
|
|
946
1123
|
|
|
947
1124
|
const enviador = this._createLibroEnviador();
|
|
948
1125
|
const resultado = await enviador.enviarLibro(libro, 'LibroCVExentos.xml');
|
|
@@ -958,9 +1135,9 @@ class CertRunner {
|
|
|
958
1135
|
this.resultados.libroComprasExentos = result;
|
|
959
1136
|
|
|
960
1137
|
if (result.success) {
|
|
961
|
-
console.log(`
|
|
1138
|
+
console.log(` [OK] Libro de Compras para Exentos enviado - TrackId: ${result.trackId}`);
|
|
962
1139
|
} else {
|
|
963
|
-
console.log(`
|
|
1140
|
+
console.log(` [ERR] Error enviando Libro de Compras para Exentos: ${result.error}`);
|
|
964
1141
|
}
|
|
965
1142
|
|
|
966
1143
|
return result;
|
|
@@ -980,10 +1157,8 @@ class CertRunner {
|
|
|
980
1157
|
throw new Error('No hay resultado del SetGuia. Ejecutar ejecutarSetGuia() primero.');
|
|
981
1158
|
}
|
|
982
1159
|
|
|
983
|
-
|
|
984
|
-
|
|
985
|
-
const periodo = this._getPeriodoLibros();
|
|
986
|
-
console.log(` 📚 Generando Libro de Guías para período ${periodo}...`);
|
|
1160
|
+
const periodo = options.periodo || (this._decrementarPeriodoLibros(), this._getPeriodoLibros());
|
|
1161
|
+
console.log(` Generando Libro de Guías para período ${periodo}...`);
|
|
987
1162
|
|
|
988
1163
|
const libroGuias = new LibroGuias({
|
|
989
1164
|
emisor: this.config.emisor,
|
|
@@ -1000,7 +1175,6 @@ class CertRunner {
|
|
|
1000
1175
|
// Guardar XML de debug
|
|
1001
1176
|
const outPath = path.join(this.debugDir, 'libro-guias.xml');
|
|
1002
1177
|
fs.writeFileSync(outPath, xml, 'utf-8');
|
|
1003
|
-
console.log(` XML guardado: ${outPath}`);
|
|
1004
1178
|
|
|
1005
1179
|
// Enviar al SII
|
|
1006
1180
|
const enviador = this._createLibroEnviador();
|
|
@@ -1017,9 +1191,9 @@ class CertRunner {
|
|
|
1017
1191
|
this.resultados.libroGuias = result;
|
|
1018
1192
|
|
|
1019
1193
|
if (result.success) {
|
|
1020
|
-
console.log(`
|
|
1194
|
+
console.log(` [OK] Libro de Guías enviado - TrackId: ${result.trackId}`);
|
|
1021
1195
|
} else {
|
|
1022
|
-
console.log(`
|
|
1196
|
+
console.log(` [ERR] Error enviando Libro de Guías: ${result.error}`);
|
|
1023
1197
|
}
|
|
1024
1198
|
|
|
1025
1199
|
return result;
|
|
@@ -1035,11 +1209,11 @@ class CertRunner {
|
|
|
1035
1209
|
*/
|
|
1036
1210
|
async avanzarSiguientePaso() {
|
|
1037
1211
|
console.log('\n' + '═'.repeat(60));
|
|
1038
|
-
console.log('
|
|
1212
|
+
console.log('AVANZAR SIGUIENTE PASO');
|
|
1039
1213
|
console.log('═'.repeat(60) + '\n');
|
|
1040
1214
|
|
|
1041
1215
|
try {
|
|
1042
|
-
console.log('
|
|
1216
|
+
console.log(' Enviando solicitud de avance...');
|
|
1043
1217
|
const result = await this.siiCert.avanzarSiguientePaso();
|
|
1044
1218
|
|
|
1045
1219
|
if (result.rawHtml) {
|
|
@@ -1048,20 +1222,20 @@ class CertRunner {
|
|
|
1048
1222
|
result.rawHtml,
|
|
1049
1223
|
'utf8'
|
|
1050
1224
|
);
|
|
1051
|
-
console.log(`
|
|
1225
|
+
console.log(` Respuesta guardada en: ${path.join(this.debugDir, 'avanzar-siguiente-paso-response.html')}`);
|
|
1052
1226
|
}
|
|
1053
1227
|
|
|
1054
1228
|
if (result.success) {
|
|
1055
|
-
console.log('
|
|
1229
|
+
console.log(' [OK] Avance al siguiente paso exitoso');
|
|
1056
1230
|
this.resultados.avanceSiguientePaso = { success: true };
|
|
1057
1231
|
} else {
|
|
1058
|
-
console.log(`
|
|
1232
|
+
console.log(` [ERR] Error en avance: ${result.error || 'Error desconocido'}`);
|
|
1059
1233
|
this.resultados.avanceSiguientePaso = { success: false, error: result.error };
|
|
1060
1234
|
}
|
|
1061
1235
|
|
|
1062
1236
|
return result;
|
|
1063
1237
|
} catch (error) {
|
|
1064
|
-
console.log(`
|
|
1238
|
+
console.log(` [ERR] Error: ${error.message}`);
|
|
1065
1239
|
this.resultados.avanceSiguientePaso = { success: false, error: error.message };
|
|
1066
1240
|
return { success: false, error: error.message };
|
|
1067
1241
|
}
|
|
@@ -1075,15 +1249,15 @@ class CertRunner {
|
|
|
1075
1249
|
async esperarLibrosYAvanzar(options = {}) {
|
|
1076
1250
|
const { maxIntentos = 30, intervalo = 10000 } = options;
|
|
1077
1251
|
|
|
1078
|
-
console.log('\n
|
|
1252
|
+
console.log('\n[...] Esperando aprobación de libros...');
|
|
1079
1253
|
|
|
1080
1254
|
for (let i = 1; i <= maxIntentos; i++) {
|
|
1081
|
-
console.log(`\n
|
|
1255
|
+
console.log(`\n [...] Intento ${i}/${maxIntentos}...`);
|
|
1082
1256
|
|
|
1083
1257
|
const avance = await this.siiCert.verAvanceParsed();
|
|
1084
1258
|
|
|
1085
1259
|
if (!avance.success) {
|
|
1086
|
-
console.log(`
|
|
1260
|
+
console.log(` [!] Error consultando avance: ${avance.error}`);
|
|
1087
1261
|
await sleep(intervalo);
|
|
1088
1262
|
continue;
|
|
1089
1263
|
}
|
|
@@ -1103,30 +1277,30 @@ class CertRunner {
|
|
|
1103
1277
|
estado.estado?.toUpperCase().includes('REPARO');
|
|
1104
1278
|
|
|
1105
1279
|
if (esAprobado) {
|
|
1106
|
-
console.log(`
|
|
1280
|
+
console.log(` [OK] ${libro}: REVISADO CONFORME`);
|
|
1107
1281
|
} else if (esRechazado) {
|
|
1108
|
-
console.log(`
|
|
1282
|
+
console.log(` [ERR] ${libro}: ${estado.estado}`);
|
|
1109
1283
|
hayRechazados = true;
|
|
1110
1284
|
} else {
|
|
1111
|
-
console.log(`
|
|
1285
|
+
console.log(` [...] ${libro}: ${estado.estado || 'EN REVISION'}`);
|
|
1112
1286
|
todosAprobados = false;
|
|
1113
1287
|
}
|
|
1114
1288
|
}
|
|
1115
1289
|
|
|
1116
1290
|
if (hayRechazados) {
|
|
1117
|
-
console.log('\n
|
|
1291
|
+
console.log('\n [ERR] Hay libros rechazados. No se puede avanzar.');
|
|
1118
1292
|
return { success: false, error: 'Hay libros rechazados' };
|
|
1119
1293
|
}
|
|
1120
1294
|
|
|
1121
1295
|
if (todosAprobados) {
|
|
1122
|
-
console.log('\n
|
|
1296
|
+
console.log('\n ¡Todos los libros aprobados!');
|
|
1123
1297
|
return await this.avanzarSiguientePaso();
|
|
1124
1298
|
}
|
|
1125
1299
|
|
|
1126
1300
|
await sleep(intervalo);
|
|
1127
1301
|
}
|
|
1128
1302
|
|
|
1129
|
-
console.log('\n
|
|
1303
|
+
console.log('\n [!] Timeout esperando aprobación de libros');
|
|
1130
1304
|
return { success: false, error: 'Timeout esperando aprobación' };
|
|
1131
1305
|
}
|
|
1132
1306
|
|
|
@@ -1148,12 +1322,12 @@ class CertRunner {
|
|
|
1148
1322
|
}
|
|
1149
1323
|
|
|
1150
1324
|
console.log('\n' + '═'.repeat(60));
|
|
1151
|
-
console.log('
|
|
1325
|
+
console.log('FASE 6: SIMULACIÓN');
|
|
1152
1326
|
console.log('═'.repeat(60) + '\n');
|
|
1153
1327
|
|
|
1154
1328
|
// Calcular CAFs necesarios
|
|
1155
1329
|
const cafRequired = this._calcularCafsSimulacion(estructuras);
|
|
1156
|
-
console.log('
|
|
1330
|
+
console.log(' Solicitando CAFs para simulación...');
|
|
1157
1331
|
|
|
1158
1332
|
// Solicitar CAFs frescos
|
|
1159
1333
|
const cafs = await this.solicitarCafs(cafRequired);
|
|
@@ -1178,22 +1352,21 @@ class CertRunner {
|
|
|
1178
1352
|
});
|
|
1179
1353
|
|
|
1180
1354
|
// Generar
|
|
1181
|
-
console.log('
|
|
1355
|
+
console.log(' Generando DTEs de simulación...');
|
|
1182
1356
|
const { envioDte, dtes, xml, plan, tiposUsados } = simulacion.generar(
|
|
1183
1357
|
estructuras,
|
|
1184
1358
|
cafObjects,
|
|
1185
1359
|
this.folioHelper,
|
|
1186
1360
|
);
|
|
1187
1361
|
|
|
1188
|
-
console.log(`
|
|
1189
|
-
console.log(`
|
|
1362
|
+
console.log(` Plan de simulación: ${plan.length} documentos`);
|
|
1363
|
+
console.log(` Tipos usados: ${tiposUsados.join(', ')}`);
|
|
1190
1364
|
|
|
1191
1365
|
// Guardar XML de debug
|
|
1192
1366
|
const runDir = path.join(this.debugDir, 'simulacion');
|
|
1193
1367
|
fs.mkdirSync(runDir, { recursive: true });
|
|
1194
1368
|
const outPath = path.join(runDir, 'envio-simulacion.xml');
|
|
1195
1369
|
fs.writeFileSync(outPath, xml, 'utf-8');
|
|
1196
|
-
console.log(` XML guardado: ${outPath}`);
|
|
1197
1370
|
|
|
1198
1371
|
// Guardar DTEs individuales
|
|
1199
1372
|
const dtesDir = path.join(runDir, 'dtes');
|
|
@@ -1204,7 +1377,7 @@ class CertRunner {
|
|
|
1204
1377
|
});
|
|
1205
1378
|
|
|
1206
1379
|
// Enviar al SII
|
|
1207
|
-
console.log('\n
|
|
1380
|
+
console.log('\n Enviando al SII...');
|
|
1208
1381
|
const enviador = this._createEnviador();
|
|
1209
1382
|
const resultado = await enviador.enviar(envioDte);
|
|
1210
1383
|
|
|
@@ -1226,9 +1399,9 @@ class CertRunner {
|
|
|
1226
1399
|
}, null, 2), 'utf8');
|
|
1227
1400
|
|
|
1228
1401
|
if (result.success) {
|
|
1229
|
-
console.log(`\n
|
|
1402
|
+
console.log(`\n[OK] Simulación enviada - TrackId: ${result.trackId}`);
|
|
1230
1403
|
} else {
|
|
1231
|
-
console.log(`\n
|
|
1404
|
+
console.log(`\n[ERR] Error en simulación: ${result.error}`);
|
|
1232
1405
|
}
|
|
1233
1406
|
|
|
1234
1407
|
return result;
|
|
@@ -1293,10 +1466,10 @@ class CertRunner {
|
|
|
1293
1466
|
}
|
|
1294
1467
|
|
|
1295
1468
|
// Verificar si ya pasamos a INTERCAMBIO (simulación ya aprobada)
|
|
1296
|
-
console.log('
|
|
1469
|
+
console.log(' Verificando etapa actual...');
|
|
1297
1470
|
const avance = await this.siiCert.verAvanceParsed();
|
|
1298
1471
|
if (avance.rawHtml && /paso\s*<b>\s*INTERCAMBIO/i.test(avance.rawHtml)) {
|
|
1299
|
-
console.log('
|
|
1472
|
+
console.log(' [OK] Simulación ya aprobada - empresa en etapa INTERCAMBIO');
|
|
1300
1473
|
return { success: true, skipped: true, message: 'Ya en etapa INTERCAMBIO' };
|
|
1301
1474
|
}
|
|
1302
1475
|
|
|
@@ -1308,8 +1481,8 @@ class CertRunner {
|
|
|
1308
1481
|
},
|
|
1309
1482
|
};
|
|
1310
1483
|
|
|
1311
|
-
const result = await this._declararConReintentos(sets, 'declaracion-simulacion-response', { maxIntentos, intervalo, label: 'simulación' });
|
|
1312
|
-
if (result?.success) console.log('
|
|
1484
|
+
const result = await this._declararConReintentos(sets, 'declaracion-simulacion-response', { maxIntentos, intervalo, label: 'simulación', retryOnAllRejected: true });
|
|
1485
|
+
if (result?.success) console.log(' [OK] Simulación declarada exitosamente');
|
|
1313
1486
|
return result;
|
|
1314
1487
|
}
|
|
1315
1488
|
|
|
@@ -1321,41 +1494,41 @@ class CertRunner {
|
|
|
1321
1494
|
async esperarSimulacionAprobada(options = {}) {
|
|
1322
1495
|
const { maxIntentos = 30, intervalo = 10000 } = options;
|
|
1323
1496
|
|
|
1324
|
-
console.log('\n
|
|
1497
|
+
console.log('\n[...] Esperando aprobación de simulación...');
|
|
1325
1498
|
|
|
1326
1499
|
for (let i = 1; i <= maxIntentos; i++) {
|
|
1327
|
-
console.log(`\n
|
|
1500
|
+
console.log(`\n [...] Intento ${i}/${maxIntentos}...`);
|
|
1328
1501
|
|
|
1329
1502
|
const avance = await this.siiCert.verAvanceParsed();
|
|
1330
1503
|
|
|
1331
1504
|
if (!avance.success) {
|
|
1332
|
-
console.log(`
|
|
1505
|
+
console.log(` [!] Error consultando avance: ${avance.error}`);
|
|
1333
1506
|
await sleep(intervalo);
|
|
1334
1507
|
continue;
|
|
1335
1508
|
}
|
|
1336
1509
|
|
|
1337
|
-
//
|
|
1510
|
+
// [OK] PRIMERO: Verificar si ya pasó a INTERCAMBIO (significa que simulación fue aprobada)
|
|
1338
1511
|
if (avance.etapaActual && avance.etapaActual.includes('INTERCAMBIO')) {
|
|
1339
|
-
console.log(`
|
|
1340
|
-
console.log('\n
|
|
1512
|
+
console.log(` [OK] Etapa actual: ${avance.etapaActual}`);
|
|
1513
|
+
console.log('\n ¡SIMULACIÓN APROBADA! Empresa pasó a etapa INTERCAMBIO.');
|
|
1341
1514
|
return { success: true, etapa: 'INTERCAMBIO' };
|
|
1342
1515
|
}
|
|
1343
1516
|
|
|
1344
|
-
//
|
|
1517
|
+
// [OK] TAMBIÉN: Etapas que vienen DESPUÉS de INTERCAMBIO (simulación + intercambio ya completos)
|
|
1345
1518
|
const ETAPAS_POST_INTERCAMBIO = ['DOCUMENTOS IMPRESOS', 'MUESTRAS IMPRESAS', 'BOLETA', 'AUTORIZADO', 'COMPLETADO'];
|
|
1346
1519
|
if (avance.etapaActual && ETAPAS_POST_INTERCAMBIO.some(e => avance.etapaActual.toUpperCase().includes(e))) {
|
|
1347
|
-
console.log(`
|
|
1348
|
-
console.log('\n
|
|
1520
|
+
console.log(` Etapa actual: ${avance.etapaActual}`);
|
|
1521
|
+
console.log('\n ¡SIMULACIÓN + INTERCAMBIO COMPLETADOS! Empresa en etapa: ' + avance.etapaActual);
|
|
1349
1522
|
return { success: true, etapa: avance.etapaActual, postIntercambio: true };
|
|
1350
1523
|
}
|
|
1351
1524
|
|
|
1352
|
-
//
|
|
1525
|
+
// [OK] SEGUNDO: Verificar indicador de formulario de confirmación (simulación aprobada pendiente confirmar)
|
|
1353
1526
|
if (avance.simulacionAprobadaIndicador) {
|
|
1354
|
-
console.log(`
|
|
1527
|
+
console.log(` [OK] Formulario de confirmación detectado`);
|
|
1355
1528
|
|
|
1356
1529
|
// Confirmar automáticamente la simulación
|
|
1357
1530
|
if (this.resultados.simulacion?.trackId) {
|
|
1358
|
-
console.log(`\n
|
|
1531
|
+
console.log(`\n Confirmando revisión de simulación (TrackId: ${this.resultados.simulacion.trackId})...`);
|
|
1359
1532
|
|
|
1360
1533
|
const fecha = this._getFechaHoy();
|
|
1361
1534
|
const confirmResult = await this.siiCert.declararAvance({
|
|
@@ -1368,7 +1541,7 @@ class CertRunner {
|
|
|
1368
1541
|
});
|
|
1369
1542
|
|
|
1370
1543
|
if (confirmResult.success) {
|
|
1371
|
-
console.log('
|
|
1544
|
+
console.log(' [OK] Confirmación enviada exitosamente');
|
|
1372
1545
|
|
|
1373
1546
|
// Revalidar contra SII para evitar falso positivo de confirmación
|
|
1374
1547
|
const verificacion = await this.siiCert.verAvanceParsed();
|
|
@@ -1378,19 +1551,19 @@ class CertRunner {
|
|
|
1378
1551
|
const simConforme = Boolean(estadoSim?.esConforme || estadoSim?.estado?.toUpperCase()?.includes('REVISADO CONFORME'));
|
|
1379
1552
|
|
|
1380
1553
|
if (yaIntercambio || simConforme || !sigueFormulario) {
|
|
1381
|
-
console.log('\n
|
|
1554
|
+
console.log('\n ¡SIMULACIÓN CONFIRMADA! Certificación completa.');
|
|
1382
1555
|
return { success: true, confirmada: true };
|
|
1383
1556
|
}
|
|
1384
1557
|
|
|
1385
|
-
console.log('
|
|
1558
|
+
console.log(' [!] SII aún mantiene formulario de simulación pendiente; se reintentará...');
|
|
1386
1559
|
await sleep(intervalo);
|
|
1387
1560
|
continue;
|
|
1388
1561
|
} else {
|
|
1389
|
-
console.log(`
|
|
1562
|
+
console.log(` [!] Error en confirmación: ${confirmResult.error}`);
|
|
1390
1563
|
// Continuar el loop para reintentar
|
|
1391
1564
|
}
|
|
1392
1565
|
} else {
|
|
1393
|
-
console.log('\n
|
|
1566
|
+
console.log('\n ¡SIMULACIÓN APROBADA! Lista para confirmar revisión.');
|
|
1394
1567
|
return { success: true, pendienteConfirmar: true };
|
|
1395
1568
|
}
|
|
1396
1569
|
}
|
|
@@ -1411,28 +1584,28 @@ class CertRunner {
|
|
|
1411
1584
|
simEstado.estado?.toUpperCase().includes('REPARO');
|
|
1412
1585
|
|
|
1413
1586
|
if (esAprobado) {
|
|
1414
|
-
console.log(`
|
|
1415
|
-
console.log('\n
|
|
1587
|
+
console.log(` [OK] SIMULACIÓN: REVISADO CONFORME`);
|
|
1588
|
+
console.log('\n ¡SIMULACIÓN APROBADA! Certificación completa.');
|
|
1416
1589
|
return { success: true };
|
|
1417
1590
|
} else if (esRechazado) {
|
|
1418
|
-
console.log(`
|
|
1591
|
+
console.log(` [ERR] SIMULACIÓN: ${simEstado.estado}`);
|
|
1419
1592
|
return { success: false, error: 'Simulación rechazada' };
|
|
1420
1593
|
} else {
|
|
1421
|
-
console.log(`
|
|
1594
|
+
console.log(` [...] SIMULACIÓN: ${simEstado.estado || 'EN REVISION'}`);
|
|
1422
1595
|
}
|
|
1423
1596
|
} else {
|
|
1424
1597
|
// No hay estado de simulación, pero verificar etapa actual
|
|
1425
1598
|
if (avance.etapaActual) {
|
|
1426
|
-
console.log(`
|
|
1599
|
+
console.log(` Etapa actual: ${avance.etapaActual}`);
|
|
1427
1600
|
} else {
|
|
1428
|
-
console.log('
|
|
1601
|
+
console.log(' [...] Simulación aún no registrada...');
|
|
1429
1602
|
}
|
|
1430
1603
|
}
|
|
1431
1604
|
|
|
1432
1605
|
await sleep(intervalo);
|
|
1433
1606
|
}
|
|
1434
1607
|
|
|
1435
|
-
console.log('\n
|
|
1608
|
+
console.log('\n [!] Timeout esperando aprobación de simulación');
|
|
1436
1609
|
return { success: false, error: 'Timeout esperando aprobación' };
|
|
1437
1610
|
}
|
|
1438
1611
|
|
|
@@ -1463,7 +1636,7 @@ class CertRunner {
|
|
|
1463
1636
|
fs.mkdirSync(intercambioDir, { recursive: true });
|
|
1464
1637
|
|
|
1465
1638
|
console.log('\n' + '═'.repeat(60));
|
|
1466
|
-
console.log('
|
|
1639
|
+
console.log('FASE 7: INTERCAMBIO DE INFORMACIÓN');
|
|
1467
1640
|
console.log('═'.repeat(60));
|
|
1468
1641
|
|
|
1469
1642
|
// ── PASO 1: Obtener el SET XML ─────────────────────────────
|
|
@@ -1476,35 +1649,35 @@ class CertRunner {
|
|
|
1476
1649
|
const setDownloadPath = path.join(intercambioDir, 'set-intercambio.xml');
|
|
1477
1650
|
|
|
1478
1651
|
if (setInputPath && fs.existsSync(setInputPath)) {
|
|
1479
|
-
console.log(`\
|
|
1652
|
+
console.log(`\nLeyendo SET desde: ${setInputPath}`);
|
|
1480
1653
|
setXml = fs.readFileSync(setInputPath, 'utf8');
|
|
1481
|
-
console.log(`
|
|
1654
|
+
console.log(` ✓ ${setXml.length} bytes`);
|
|
1482
1655
|
} else if (fs.existsSync(setDownloadPath)) {
|
|
1483
|
-
console.log(`\
|
|
1656
|
+
console.log(`\nLeyendo SET guardado: ${setDownloadPath}`);
|
|
1484
1657
|
setXml = fs.readFileSync(setDownloadPath, 'utf8');
|
|
1485
|
-
console.log(`
|
|
1658
|
+
console.log(` ✓ ${setXml.length} bytes`);
|
|
1486
1659
|
} else {
|
|
1487
|
-
console.log('\
|
|
1660
|
+
console.log('\nDescargando SET desde www4.sii.cl/pfeInternet...');
|
|
1488
1661
|
const dl = await this._descargarSetPfeInternet(intercambioDir);
|
|
1489
1662
|
if (dl.success) {
|
|
1490
1663
|
setXml = dl.xml;
|
|
1491
1664
|
fs.writeFileSync(setDownloadPath, setXml, 'utf8');
|
|
1492
|
-
console.log(`
|
|
1665
|
+
console.log(` [OK] SET descargado (${setXml.length} bytes) → ${setDownloadPath}`);
|
|
1493
1666
|
} else {
|
|
1494
|
-
console.log(`
|
|
1667
|
+
console.log(` [!] No se pudo descargar: ${dl.error}`);
|
|
1495
1668
|
console.log('\n' + '─'.repeat(60));
|
|
1496
|
-
console.log('
|
|
1497
|
-
console.log('
|
|
1498
|
-
console.log('
|
|
1499
|
-
console.log(`
|
|
1500
|
-
console.log('
|
|
1669
|
+
console.log('DESCARGA MANUAL REQUERIDA:');
|
|
1670
|
+
console.log(' 1. Si aparece error de sesiones: ingresa a https://www4.sii.cl/ → Cerrar Sesión');
|
|
1671
|
+
console.log(' 2. Ir a: https://www4.sii.cl/pfeInternet/ y descargar el SET XML');
|
|
1672
|
+
console.log(` 3. Guardarlo en: ${setDownloadPath}`);
|
|
1673
|
+
console.log(' 4. Volver a ejecutar el runner');
|
|
1501
1674
|
console.log('─'.repeat(60));
|
|
1502
1675
|
return { success: false, error: 'SET no disponible - descarga manual requerida', requiresManual: true, manualPath: setInputPath };
|
|
1503
1676
|
}
|
|
1504
1677
|
}
|
|
1505
1678
|
|
|
1506
1679
|
// ── PASO 2: Generar XMLs de respuesta ─────────────────────
|
|
1507
|
-
console.log('\
|
|
1680
|
+
console.log('\nGenerando respuestas firmadas...');
|
|
1508
1681
|
const intercambioCert = new IntercambioCert({
|
|
1509
1682
|
certificado: this.certificado,
|
|
1510
1683
|
emisor: {
|
|
@@ -1521,7 +1694,7 @@ class CertRunner {
|
|
|
1521
1694
|
}
|
|
1522
1695
|
|
|
1523
1696
|
// ── PASO 3: Subir respuestas ───────────────────────────────
|
|
1524
|
-
console.log('\
|
|
1697
|
+
console.log('\nSubiendo respuestas a www4.sii.cl/pfeInternet...');
|
|
1525
1698
|
const uploadResult = await this._subirRespuestasPfeInternet({
|
|
1526
1699
|
recepcionXml: fs.readFileSync(genResult.files.recepcion, 'utf8'),
|
|
1527
1700
|
aprobacionXml: fs.readFileSync(genResult.files.aprobacion, 'utf8'),
|
|
@@ -1531,17 +1704,17 @@ class CertRunner {
|
|
|
1531
1704
|
|
|
1532
1705
|
if (uploadResult.success) {
|
|
1533
1706
|
console.log('\n' + '═'.repeat(60));
|
|
1534
|
-
console.log('
|
|
1707
|
+
console.log('[OK] INTERCAMBIO COMPLETADO');
|
|
1535
1708
|
console.log('═'.repeat(60));
|
|
1536
|
-
if (uploadResult.resultado) console.log(`
|
|
1709
|
+
if (uploadResult.resultado) console.log(` Resultado SII: ${uploadResult.resultado}`);
|
|
1537
1710
|
} else {
|
|
1538
|
-
console.log(`
|
|
1711
|
+
console.log(` [!] No se pudo subir automáticamente: ${uploadResult.error}`);
|
|
1539
1712
|
console.log('\n' + '─'.repeat(60));
|
|
1540
|
-
console.log('
|
|
1541
|
-
console.log('
|
|
1542
|
-
console.log(`
|
|
1543
|
-
console.log(`
|
|
1544
|
-
console.log(`
|
|
1713
|
+
console.log('SUBIDA MANUAL REQUERIDA:');
|
|
1714
|
+
console.log(' 1. Ir a: https://www4.sii.cl/pfeInternet/ → "Subir archivos"');
|
|
1715
|
+
console.log(` 2. Subir: ${genResult.files.recepcion}`);
|
|
1716
|
+
console.log(` 3. Subir: ${genResult.files.aprobacion}`);
|
|
1717
|
+
console.log(` 4. Subir: ${genResult.files.recibos}`);
|
|
1545
1718
|
console.log('─'.repeat(60));
|
|
1546
1719
|
}
|
|
1547
1720
|
|
|
@@ -1563,7 +1736,7 @@ class CertRunner {
|
|
|
1563
1736
|
*/
|
|
1564
1737
|
async _obtenerCookiesSII() {
|
|
1565
1738
|
if (this._siiCookieJar) {
|
|
1566
|
-
console.log('[SII Auth]
|
|
1739
|
+
console.log('[SII Auth] Reutilizando sesión SII en memoria');
|
|
1567
1740
|
return this._siiCookieJar;
|
|
1568
1741
|
}
|
|
1569
1742
|
const SiiPortalAuth = require('../SiiPortalAuth');
|
|
@@ -1572,7 +1745,7 @@ class CertRunner {
|
|
|
1572
1745
|
const siiAuth = new SiiPortalAuth({ pfxBuffer, pfxPassword: password });
|
|
1573
1746
|
this._siiCookieJar = await siiAuth.autenticar();
|
|
1574
1747
|
const nSession = Object.keys(this._siiCookieJar).filter(k => k.startsWith('NETSCAPE')).length;
|
|
1575
|
-
console.log(`[SII Auth]
|
|
1748
|
+
console.log(`[SII Auth] [OK] Sesión SII activa (cookies NETSCAPE: ${nSession})`);
|
|
1576
1749
|
return this._siiCookieJar;
|
|
1577
1750
|
}
|
|
1578
1751
|
|
|
@@ -1671,7 +1844,7 @@ class CertRunner {
|
|
|
1671
1844
|
const boundary = `----WebKitFormBoundary${Date.now()}`;
|
|
1672
1845
|
const emptyMultipartBody = `--${boundary}--\r\n`;
|
|
1673
1846
|
|
|
1674
|
-
console.log(`
|
|
1847
|
+
console.log(` → Descargando SET desde pfeInternet/downloadFile (RUT ${rutNum}-${dv})...`);
|
|
1675
1848
|
|
|
1676
1849
|
const r = await makeReq(
|
|
1677
1850
|
`https://www4.sii.cl/pfeInternet/downloadFile?re=${rutNum}&dve=${dv}`,
|
|
@@ -1695,12 +1868,12 @@ class CertRunner {
|
|
|
1695
1868
|
r.body.includes('<SetDTE') ||
|
|
1696
1869
|
r.body.includes('<?xml')
|
|
1697
1870
|
)) {
|
|
1698
|
-
console.log(`
|
|
1871
|
+
console.log(` ✓ SET descargado correctamente (${r.body.length} bytes)`);
|
|
1699
1872
|
return { success: true, xml: r.body };
|
|
1700
1873
|
}
|
|
1701
1874
|
|
|
1702
1875
|
const errMsg = `pfeInternet/downloadFile respondió HTTP ${r.status} sin XML válido`;
|
|
1703
|
-
console.log(`
|
|
1876
|
+
console.log(` [ERR] ${errMsg}`);
|
|
1704
1877
|
fs.writeFileSync(path.join(debugDir, `pfe-download-error-${Date.now()}.html`), r.body, 'utf8');
|
|
1705
1878
|
return { success: false, error: errMsg };
|
|
1706
1879
|
} catch (err) {
|
|
@@ -1723,8 +1896,8 @@ class CertRunner {
|
|
|
1723
1896
|
fs.mkdirSync(tmpDir, { recursive: true });
|
|
1724
1897
|
// Los labels deben coincidir con el texto del portal GWT (Archivo N: ...)
|
|
1725
1898
|
const archivos = [
|
|
1726
|
-
{ label: 'Respuesta de Intercambio',
|
|
1727
|
-
{ label: 'Recibo de Mercaderias',
|
|
1899
|
+
{ label: 'Respuesta de Intercambio', filename: 'respuesta-recepcion-envio.xml', content: recepcionXml, uploadN: 1 },
|
|
1900
|
+
{ label: 'Recibo de Mercaderias', filename: 'envio-recibos.xml', content: recibosXml, uploadN: 2 },
|
|
1728
1901
|
{ label: 'Resultado Aprobaci\u00f3n Comercial de Documento', filename: 'respuesta-aprobacion-comercial.xml', content: aprobacionXml, uploadN: 3 },
|
|
1729
1902
|
];
|
|
1730
1903
|
for (const a of archivos) {
|
|
@@ -1751,7 +1924,7 @@ class CertRunner {
|
|
|
1751
1924
|
await page.setCookie(...puppeteerCookies);
|
|
1752
1925
|
|
|
1753
1926
|
// Navegar al portal pfeInternet
|
|
1754
|
-
console.log('
|
|
1927
|
+
console.log(' → Cargando portal pfeInternet...');
|
|
1755
1928
|
await page.goto('https://www4.sii.cl/pfeInternet/', {
|
|
1756
1929
|
waitUntil: 'networkidle2',
|
|
1757
1930
|
timeout: 60000,
|
|
@@ -1762,7 +1935,7 @@ class CertRunner {
|
|
|
1762
1935
|
|
|
1763
1936
|
// Hacer click en el enlace "Subir archivos XML de respuesta de Intercambio"
|
|
1764
1937
|
// El href es javascript:openForm('opt-ingresoEmpresaUp') — necesita click real para GWT
|
|
1765
|
-
console.log('
|
|
1938
|
+
console.log(' → Clickeando "Subir archivos XML de respuesta de Intercambio"...');
|
|
1766
1939
|
const linkClicked = await page.click('a[href*="ingresoEmpresaUp"]').then(() => true).catch(() => false);
|
|
1767
1940
|
if (!linkClicked) {
|
|
1768
1941
|
// Fallback: evaluar click con dispatchEvent
|
|
@@ -1781,7 +1954,7 @@ class CertRunner {
|
|
|
1781
1954
|
if (rutInput) {
|
|
1782
1955
|
const [rutNum, dv] = this.config.emisor.rut.split('-');
|
|
1783
1956
|
const rutConDv = `${rutNum}-${dv}`;
|
|
1784
|
-
console.log(`
|
|
1957
|
+
console.log(` → Ingresando RUT empresa: ${rutConDv}`);
|
|
1785
1958
|
await rutInput.click({ clickCount: 3 }); // seleccionar todo
|
|
1786
1959
|
await rutInput.type(rutConDv);
|
|
1787
1960
|
|
|
@@ -1792,7 +1965,7 @@ class CertRunner {
|
|
|
1792
1965
|
});
|
|
1793
1966
|
if (confirmBtn) {
|
|
1794
1967
|
await confirmBtn.asElement().click();
|
|
1795
|
-
console.log('
|
|
1968
|
+
console.log(' → Click "Confirmar Empresa", esperando formulario de upload...');
|
|
1796
1969
|
await page.waitForNetworkIdle({ timeout: 15000, idleTime: 1000 }).catch(() => {});
|
|
1797
1970
|
}
|
|
1798
1971
|
}
|
|
@@ -1810,7 +1983,7 @@ class CertRunner {
|
|
|
1810
1983
|
}
|
|
1811
1984
|
throw new Error('pfeInternet no mostró formulario de upload tras openForm — ver pfeInternet-error.png/.html');
|
|
1812
1985
|
}
|
|
1813
|
-
console.log('
|
|
1986
|
+
console.log(' → Formulario de upload listo');
|
|
1814
1987
|
|
|
1815
1988
|
// ── DEBUG: screenshot del formulario con los inputs listos ──
|
|
1816
1989
|
if (debugDir) {
|
|
@@ -1846,11 +2019,11 @@ class CertRunner {
|
|
|
1846
2019
|
}, archivo.label);
|
|
1847
2020
|
|
|
1848
2021
|
if (yaProcessado) {
|
|
1849
|
-
console.log(`
|
|
2022
|
+
console.log(` → ${archivo.filename}: ya procesado anteriormente, saltando...`);
|
|
1850
2023
|
continue;
|
|
1851
2024
|
}
|
|
1852
2025
|
|
|
1853
|
-
console.log(`
|
|
2026
|
+
console.log(` → Subiendo ${archivo.filename}...`);
|
|
1854
2027
|
|
|
1855
2028
|
// Cada archivo tiene su propio form con action uploadFile1/2/3
|
|
1856
2029
|
// Usamos el selector específico para no confundir entre los 3 inputs que pueden
|
|
@@ -1869,7 +2042,7 @@ class CertRunner {
|
|
|
1869
2042
|
// Esperar el diálogo GWT de confirmación
|
|
1870
2043
|
await page.waitForSelector('.gwt-DialogBox .msgeDialogBox', { timeout: 30000 });
|
|
1871
2044
|
const msgText = await page.$eval('.gwt-DialogBox .msgeDialogBox', el => el.textContent.trim());
|
|
1872
|
-
console.log(`
|
|
2045
|
+
console.log(` ✓ ${msgText}`);
|
|
1873
2046
|
|
|
1874
2047
|
if (debugDir) {
|
|
1875
2048
|
fs.writeFileSync(
|
|
@@ -1965,16 +2138,16 @@ class CertRunner {
|
|
|
1965
2138
|
const t = (document.body.textContent || '').toUpperCase();
|
|
1966
2139
|
return t.includes('ESTADO DE LA REVISI') ||
|
|
1967
2140
|
t.includes('POR REVISAR') || t.includes('APROBADO') ||
|
|
1968
|
-
t.includes('EN REVISI')
|
|
2141
|
+
t.includes('EN REVISI') || t.includes('RECHAZADO');
|
|
1969
2142
|
}, { timeout: 8000, polling: 500 }).catch(() => {});
|
|
1970
2143
|
|
|
1971
2144
|
const estado = await page.evaluate(() => {
|
|
1972
2145
|
const t = (document.body.textContent || '').toUpperCase();
|
|
1973
|
-
if (t.includes('APROBADO'))
|
|
1974
|
-
if (t.includes('POR REVISAR'))
|
|
1975
|
-
if (t.includes('EN REVISI'))
|
|
1976
|
-
if (t.includes('RECHAZADO'))
|
|
1977
|
-
if (t.includes('ENVIADO AL SII'))
|
|
2146
|
+
if (t.includes('APROBADO')) return 'APROBADO';
|
|
2147
|
+
if (t.includes('POR REVISAR')) return 'POR REVISAR';
|
|
2148
|
+
if (t.includes('EN REVISI')) return 'EN REVISIÓN';
|
|
2149
|
+
if (t.includes('RECHAZADO')) return 'RECHAZADO';
|
|
2150
|
+
if (t.includes('ENVIADO AL SII')) return 'ENVIADO AL SII';
|
|
1978
2151
|
return null;
|
|
1979
2152
|
}).catch(() => null);
|
|
1980
2153
|
|
|
@@ -2005,7 +2178,7 @@ class CertRunner {
|
|
|
2005
2178
|
if (!pdfPaths.length) throw new Error(`No se encontraron PDFs en: ${pdfDir}`);
|
|
2006
2179
|
|
|
2007
2180
|
console.log('\n' + '═'.repeat(60));
|
|
2008
|
-
console.log(
|
|
2181
|
+
console.log(`FASE 8: MUESTRAS IMPRESAS (${pdfPaths.length} PDFs)`);
|
|
2009
2182
|
console.log('═'.repeat(60));
|
|
2010
2183
|
|
|
2011
2184
|
return this._subirMuestrasImpresasPortal({ pdfPaths, debugDir: pdfDir });
|
|
@@ -2040,13 +2213,14 @@ class CertRunner {
|
|
|
2040
2213
|
browser = await puppeteer.launch({
|
|
2041
2214
|
headless: true,
|
|
2042
2215
|
ignoreHTTPSErrors: true,
|
|
2216
|
+
protocolTimeout: 300000, // 5 min — DOM.setFileInputFiles con 256 archivos supera el default de 30s
|
|
2043
2217
|
args: ['--no-sandbox', '--disable-setuid-sandbox', '--ignore-certificate-errors'],
|
|
2044
2218
|
});
|
|
2045
2219
|
const page = await browser.newPage();
|
|
2046
2220
|
await page.setCookie(...puppeteerCookies);
|
|
2047
2221
|
|
|
2048
2222
|
// Navegar directamente a www4.sii.cl/pdfdteInternet/ con las cookies de sesión SII
|
|
2049
|
-
console.log('
|
|
2223
|
+
console.log(' → Cargando portal pdfdteInternet...');
|
|
2050
2224
|
await page.goto('https://www4.sii.cl/pdfdteInternet/', {
|
|
2051
2225
|
waitUntil: 'networkidle2', timeout: 60000,
|
|
2052
2226
|
});
|
|
@@ -2063,7 +2237,7 @@ class CertRunner {
|
|
|
2063
2237
|
}
|
|
2064
2238
|
throw new Error('pdfdteInternet: no se encontraron campos de RUT (¿sesión expirada?)');
|
|
2065
2239
|
}
|
|
2066
|
-
console.log(`
|
|
2240
|
+
console.log(` → Ingresando RUT empresa: ${rutNum}-${dvChar}`);
|
|
2067
2241
|
await rutInputs[0].click({ clickCount: 3 }); await rutInputs[0].type(rutNum);
|
|
2068
2242
|
await dvInputs[0].click({ clickCount: 3 }); await dvInputs[0].type(dvChar);
|
|
2069
2243
|
await clickBoton(page, 'Rut');
|
|
@@ -2071,12 +2245,26 @@ class CertRunner {
|
|
|
2071
2245
|
if (debugDir) await page.screenshot({ path: path.join(debugDir, 'pdfte-02-after-rut.png'), fullPage: true }).catch(() => {});
|
|
2072
2246
|
|
|
2073
2247
|
// Paso 2: Diálogo "ya existe revisión" → click "Sí"
|
|
2248
|
+
// nuevaRevisionCreada=true cuando el usuario acepta crear una nueva revisión;
|
|
2249
|
+
// en ese caso NO se hace el early-exit por estado previo (el texto "EN REVISIÓN"
|
|
2250
|
+
// que queda visible corresponde a la revisión antigua, no a la nueva vacía).
|
|
2251
|
+
let nuevaRevisionCreada = false;
|
|
2074
2252
|
const hayDialog = await page.evaluate(() => {
|
|
2075
2253
|
const dlg = document.querySelector('.x-window');
|
|
2076
2254
|
return !!(dlg && dlg.offsetParent !== null);
|
|
2077
2255
|
});
|
|
2078
2256
|
if (hayDialog) {
|
|
2079
|
-
console.log('
|
|
2257
|
+
console.log(' → Diálogo "Ya existe revisión" detectado → click "Sí"');
|
|
2258
|
+
if (debugDir) {
|
|
2259
|
+
await page.screenshot({ path: path.join(debugDir, 'pdfte-dialog-ya-existe.png'), fullPage: true }).catch(() => {});
|
|
2260
|
+
fs.writeFileSync(path.join(debugDir, 'pdfte-dialog-ya-existe.html'), await page.content().catch(() => ''), 'utf8');
|
|
2261
|
+
// Log texto del diálogo para debug
|
|
2262
|
+
const dlgText = await page.evaluate(() => {
|
|
2263
|
+
const dlg = document.querySelector('.x-window');
|
|
2264
|
+
return dlg ? dlg.textContent.trim().replace(/\s+/g, ' ') : '';
|
|
2265
|
+
}).catch(() => '');
|
|
2266
|
+
console.log(` [DEBUG] Texto del diálogo: "${dlgText}"`);
|
|
2267
|
+
}
|
|
2080
2268
|
const clicked = await page.evaluate(() => {
|
|
2081
2269
|
const si = Array.from(document.querySelectorAll('button.x-btn-text'))
|
|
2082
2270
|
.find(b => /^s[ií]$/i.test(b.textContent.trim()));
|
|
@@ -2085,6 +2273,8 @@ class CertRunner {
|
|
|
2085
2273
|
});
|
|
2086
2274
|
if (!clicked) await page.evaluate(() => { const b = document.querySelector('.x-window button'); if (b) b.click(); });
|
|
2087
2275
|
await new Promise(r => setTimeout(r, 2500));
|
|
2276
|
+
if (debugDir) await page.screenshot({ path: path.join(debugDir, 'pdfte-dialog-despues-si.png'), fullPage: true }).catch(() => {});
|
|
2277
|
+
nuevaRevisionCreada = true;
|
|
2088
2278
|
}
|
|
2089
2279
|
|
|
2090
2280
|
// Paso 3: RUT Proveedor (mismo RUT empresa)
|
|
@@ -2097,7 +2287,7 @@ class CertRunner {
|
|
|
2097
2287
|
const dvNow = await page.$$('input[name="dv"]');
|
|
2098
2288
|
const pRut = rutNow.length >= 2 ? rutNow[1] : rutNow[0];
|
|
2099
2289
|
const pDv = dvNow.length >= 2 ? dvNow[1] : dvNow[0];
|
|
2100
|
-
console.log(`
|
|
2290
|
+
console.log(` → Ingresando RUT proveedor: ${rutNum}-${dvChar}`);
|
|
2101
2291
|
await pRut.click({ clickCount: 3 }); await pRut.type(rutNum);
|
|
2102
2292
|
await pDv.click({ clickCount: 3 }); await pDv.type(dvChar);
|
|
2103
2293
|
await clickBoton(page, 'Consultar');
|
|
@@ -2105,22 +2295,25 @@ class CertRunner {
|
|
|
2105
2295
|
if (debugDir) await page.screenshot({ path: path.join(debugDir, 'pdfte-03-after-consultar.png'), fullPage: true }).catch(() => {});
|
|
2106
2296
|
|
|
2107
2297
|
// ── Re-ejecución: detectar estado terminal antes de proceder ──
|
|
2108
|
-
|
|
2298
|
+
// Solo aplicable cuando NO se creó una nueva revisión.
|
|
2299
|
+
// Si se creó una nueva (nuevaRevisionCreada=true), el texto "EN REVISIÓN"
|
|
2300
|
+
// que aparece pertenece a la revisión anterior y no es válido como early-exit.
|
|
2301
|
+
const _estadoYaSubido = nuevaRevisionCreada ? null : await page.evaluate(() => {
|
|
2109
2302
|
const t = (document.body.textContent || '').toUpperCase();
|
|
2110
|
-
if (t.includes('APROBADO'))
|
|
2111
|
-
if (t.includes('POR REVISAR'))
|
|
2112
|
-
if (t.includes('EN REVISI'))
|
|
2113
|
-
if (t.includes('RECHAZADO'))
|
|
2303
|
+
if (t.includes('APROBADO')) return 'APROBADO';
|
|
2304
|
+
if (t.includes('POR REVISAR')) return 'POR REVISAR';
|
|
2305
|
+
if (t.includes('EN REVISI')) return 'EN REVISIÓN';
|
|
2306
|
+
if (t.includes('RECHAZADO')) return 'RECHAZADO';
|
|
2114
2307
|
if (t.includes('ENVIADO AL SII')) return 'ENVIADO AL SII';
|
|
2115
2308
|
return null;
|
|
2116
2309
|
}).catch(() => null);
|
|
2117
2310
|
if (_estadoYaSubido) {
|
|
2118
|
-
console.log(`
|
|
2311
|
+
console.log(` [OK] Portal ya muestra estado "${_estadoYaSubido}" — muestras subidas previamente. Proceso completado.`);
|
|
2119
2312
|
return { success: true, alreadyCompleted: true, estado: _estadoYaSubido };
|
|
2120
2313
|
}
|
|
2121
2314
|
|
|
2122
2315
|
// Paso 4: "Crear" → habilita el input de archivo
|
|
2123
|
-
console.log('
|
|
2316
|
+
console.log(' → Click "Crear"...');
|
|
2124
2317
|
await clickBoton(page, 'Crear');
|
|
2125
2318
|
await new Promise(r => setTimeout(r, 2500));
|
|
2126
2319
|
if (debugDir) await page.screenshot({ path: path.join(debugDir, 'pdfte-04-after-crear.png'), fullPage: true }).catch(() => {});
|
|
@@ -2175,38 +2368,27 @@ class CertRunner {
|
|
|
2175
2368
|
if (!_hayInp) { await clickBoton(page, 'Crear'); await new Promise(r => setTimeout(r, 2500)); }
|
|
2176
2369
|
};
|
|
2177
2370
|
|
|
2178
|
-
// Cargar todos los PDFs
|
|
2179
|
-
//
|
|
2180
|
-
//
|
|
2181
|
-
//
|
|
2182
|
-
// (
|
|
2183
|
-
console.log(`
|
|
2184
|
-
const
|
|
2185
|
-
|
|
2186
|
-
|
|
2187
|
-
|
|
2188
|
-
|
|
2189
|
-
|
|
2190
|
-
|
|
2191
|
-
|
|
2192
|
-
|
|
2193
|
-
|
|
2194
|
-
|
|
2195
|
-
const bin = atob(f.b64);
|
|
2196
|
-
const arr = new Uint8Array(bin.length);
|
|
2197
|
-
for (let i = 0; i < bin.length; i++) arr[i] = bin.charCodeAt(i);
|
|
2198
|
-
dt.items.add(new File([arr], f.name, { type: 'application/pdf' }));
|
|
2199
|
-
}
|
|
2200
|
-
const dz = document.querySelector('.dropFilesLabel');
|
|
2201
|
-
if (!dz) return 0;
|
|
2202
|
-
dz.dispatchEvent(new DragEvent('dragenter', { dataTransfer: dt, bubbles: true, cancelable: true }));
|
|
2203
|
-
dz.dispatchEvent(new DragEvent('dragover', { dataTransfer: dt, bubbles: true, cancelable: true }));
|
|
2204
|
-
dz.dispatchEvent(new DragEvent('drop', { dataTransfer: dt, bubbles: true, cancelable: true }));
|
|
2205
|
-
return dt.files.length;
|
|
2206
|
-
}, _fileDataList);
|
|
2371
|
+
// Cargar todos los PDFs a la vez en el input de archivo.
|
|
2372
|
+
// GWT no tiene atributo "multiple" por defecto — se fuerza vía DOM.
|
|
2373
|
+
// Puppeteer dispara el evento "change" internamente tras uploadFile(),
|
|
2374
|
+
// lo que dispara el handler GWT que encola todos los archivos para
|
|
2375
|
+
// procesarlos en batch (una POST por archivo vía iframe, sin re-navegación).
|
|
2376
|
+
console.log(` → Cargando ${pdfPaths.length} PDFs de una vez en el input...`);
|
|
2377
|
+
const fileInput = await page.$('input.gwt-FileUpload');
|
|
2378
|
+
if (!fileInput) throw new Error('pdfdteInternet: input.gwt-FileUpload no encontrado');
|
|
2379
|
+
|
|
2380
|
+
// Forzar múltiple selección y quitar restrict de accept (solo .PDF uppercase falla en algunos OS)
|
|
2381
|
+
await fileInput.evaluate(el => {
|
|
2382
|
+
el.setAttribute('multiple', '');
|
|
2383
|
+
el.removeAttribute('accept');
|
|
2384
|
+
});
|
|
2385
|
+
|
|
2386
|
+
await fileInput.uploadFile(...pdfPaths);
|
|
2387
|
+
console.log(` → ${pdfPaths.length} PDFs seleccionados, esperando que GWT encole los uploads...`);
|
|
2207
2388
|
|
|
2208
|
-
|
|
2209
|
-
|
|
2389
|
+
// GWT necesita un pequeño tick antes de comenzar a procesar la cola
|
|
2390
|
+
await new Promise(r => setTimeout(r, 1500));
|
|
2391
|
+
if (debugDir) await page.screenshot({ path: path.join(debugDir, 'pdfte-04b-despues-upload-file.png'), fullPage: true }).catch(() => {});
|
|
2210
2392
|
|
|
2211
2393
|
// ── Fase 1: esperar hasta 45s por primera señal de progreso o estado terminal ──
|
|
2212
2394
|
// Si el portal ya está en "POR REVISAR" (re-ejecución), lo detectamos aquí inmediatamente.
|
|
@@ -2230,21 +2412,21 @@ class CertRunner {
|
|
|
2230
2412
|
}
|
|
2231
2413
|
const t = (document.body.textContent || '').toUpperCase();
|
|
2232
2414
|
let estado = null;
|
|
2233
|
-
if (t.includes('APROBADO'))
|
|
2415
|
+
if (t.includes('APROBADO')) estado = 'APROBADO';
|
|
2234
2416
|
else if (t.includes('POR REVISAR')) estado = 'POR REVISAR';
|
|
2235
|
-
else if (t.includes('EN REVISI'))
|
|
2236
|
-
else if (t.includes('RECHAZADO'))
|
|
2417
|
+
else if (t.includes('EN REVISI')) estado = 'EN REVISIÓN';
|
|
2418
|
+
else if (t.includes('RECHAZADO')) estado = 'RECHAZADO';
|
|
2237
2419
|
return { procesados, estado };
|
|
2238
2420
|
}).catch(() => ({ procesados: 0, estado: null }));
|
|
2239
2421
|
|
|
2240
2422
|
if (_fase1.estado) {
|
|
2241
|
-
console.log(`
|
|
2423
|
+
console.log(` [OK] Portal en estado "${_fase1.estado}" — muestras ya procesadas previamente.`);
|
|
2242
2424
|
return { success: true, alreadyCompleted: true, estado: _fase1.estado };
|
|
2243
2425
|
}
|
|
2244
2426
|
|
|
2245
2427
|
if (_fase1.procesados === 0) {
|
|
2246
2428
|
// Sin progreso y sin estado terminal: el portal puede no estar procesando
|
|
2247
|
-
console.warn('
|
|
2429
|
+
console.warn(' [!] Sin progreso en 45s y sin estado terminal. Continuando al paso siguiente...');
|
|
2248
2430
|
} else {
|
|
2249
2431
|
// ── Fase 2: progreso iniciado — esperar al total ──
|
|
2250
2432
|
await page.waitForFunction((total) => {
|
|
@@ -2261,7 +2443,7 @@ class CertRunner {
|
|
|
2261
2443
|
}
|
|
2262
2444
|
return 0;
|
|
2263
2445
|
}).catch(() => 0);
|
|
2264
|
-
console.warn(`
|
|
2446
|
+
console.warn(` [!] Timeout: solo se procesaron ${procesados}/${pdfPaths.length} antes del timeout`);
|
|
2265
2447
|
});
|
|
2266
2448
|
}
|
|
2267
2449
|
|
|
@@ -2272,7 +2454,7 @@ class CertRunner {
|
|
|
2272
2454
|
if (debugDir) await page.screenshot({ path: path.join(debugDir, 'pdfte-05-archivos-listos.png'), fullPage: true }).catch(() => {});
|
|
2273
2455
|
|
|
2274
2456
|
// Paso 6: re-navegar al estado limpio y Enviar al SII
|
|
2275
|
-
console.log('
|
|
2457
|
+
console.log(' → Re-navegando para "Enviar al SII"...');
|
|
2276
2458
|
await navegarAlFormulario();
|
|
2277
2459
|
|
|
2278
2460
|
// Esperar a que el botón esté habilitado (aria-disabled="false")
|
|
@@ -2284,7 +2466,7 @@ class CertRunner {
|
|
|
2284
2466
|
|
|
2285
2467
|
if (debugDir) await page.screenshot({ path: path.join(debugDir, 'pdfte-05b-antes-enviar.png'), fullPage: true }).catch(() => {});
|
|
2286
2468
|
|
|
2287
|
-
console.log('
|
|
2469
|
+
console.log(' → Click "Enviar al SII"...');
|
|
2288
2470
|
const enviado = await clickBoton(page, 'Enviar al SII');
|
|
2289
2471
|
if (!enviado) throw new Error('pdfdteInternet: botón "Enviar al SII" no disponible o deshabilitado');
|
|
2290
2472
|
|
|
@@ -2297,7 +2479,7 @@ class CertRunner {
|
|
|
2297
2479
|
|
|
2298
2480
|
const pageText = await page.$eval('body', el => el.textContent).catch(() => '');
|
|
2299
2481
|
const exitoso = /revision.*creada|solicitud.*enviada|documentos.*enviados|fue.*enviado|[eé]xito/i.test(pageText);
|
|
2300
|
-
console.log(`
|
|
2482
|
+
console.log(` → Resultado: ${exitoso ? '[OK] enviado correctamente' : '[!] sin confirmación explícita'}`);
|
|
2301
2483
|
return { success: exitoso, pageText: pageText.substring(0, 800) };
|
|
2302
2484
|
|
|
2303
2485
|
} catch (err) {
|
|
@@ -2340,9 +2522,9 @@ class CertRunner {
|
|
|
2340
2522
|
});
|
|
2341
2523
|
const page = await browser.newPage();
|
|
2342
2524
|
await page.setCookie(...puppeteerCookies);
|
|
2343
|
-
page.on('dialog', async dlg => { console.log(`
|
|
2525
|
+
page.on('dialog', async dlg => { console.log(` → [dialog SET=1] ${dlg.message()}`); await dlg.accept(); });
|
|
2344
2526
|
|
|
2345
|
-
console.log('
|
|
2527
|
+
console.log(' → Cargando portal certBolElectDteInternet (SET=1)...');
|
|
2346
2528
|
await page.goto('https://www4.sii.cl/certBolElectDteInternet/?SET=1', {
|
|
2347
2529
|
waitUntil: 'networkidle2', timeout: 60000,
|
|
2348
2530
|
});
|
|
@@ -2352,7 +2534,7 @@ class CertRunner {
|
|
|
2352
2534
|
const rutInput = await page.$('input[maxlength="8"]');
|
|
2353
2535
|
const dvInput = await page.$('input[maxlength="1"]');
|
|
2354
2536
|
if (!rutInput) throw new Error('certBolElectDteInternet/?SET=1: no se encontró campo RUT');
|
|
2355
|
-
console.log(`
|
|
2537
|
+
console.log(` → Ingresando RUT empresa: ${rutNum}-${dvChar}`);
|
|
2356
2538
|
await rutInput.click({ clickCount: 3 }); await rutInput.type(rutNum);
|
|
2357
2539
|
await dvInput.click({ clickCount: 3 }); await dvInput.type(dvChar);
|
|
2358
2540
|
await page.evaluate(() => {
|
|
@@ -2360,7 +2542,7 @@ class CertRunner {
|
|
|
2360
2542
|
.find(b => /confirmar/i.test(b.textContent));
|
|
2361
2543
|
if (btn) btn.click();
|
|
2362
2544
|
});
|
|
2363
|
-
console.log('
|
|
2545
|
+
console.log(' → Click "Confirmar Empresa"');
|
|
2364
2546
|
|
|
2365
2547
|
// Esperar checkboxes — GWT dispara ~8-10 POST /facade en paralelo
|
|
2366
2548
|
await page.waitForNetworkIdle({ idleTime: 800, timeout: 40000 }).catch(() => {});
|
|
@@ -2375,7 +2557,7 @@ class CertRunner {
|
|
|
2375
2557
|
cbs.forEach(cb => { if (!cb.checked) cb.click(); });
|
|
2376
2558
|
return cbs.length;
|
|
2377
2559
|
});
|
|
2378
|
-
console.log(`
|
|
2560
|
+
console.log(` → ${nCbs} checkbox(es) marcados`);
|
|
2379
2561
|
await new Promise(r => setTimeout(r, 300));
|
|
2380
2562
|
|
|
2381
2563
|
// Paso 3: Rellenar correo proveedor
|
|
@@ -2390,9 +2572,9 @@ class CertRunner {
|
|
|
2390
2572
|
if (emailInput) {
|
|
2391
2573
|
await emailInput.click({ clickCount: 3 });
|
|
2392
2574
|
await emailInput.type(correoSet);
|
|
2393
|
-
console.log(`
|
|
2575
|
+
console.log(` → Correo proveedor: ${correoSet}`);
|
|
2394
2576
|
} else {
|
|
2395
|
-
console.log('
|
|
2577
|
+
console.log(' [!] No se encontró campo de correo — continuando sin él');
|
|
2396
2578
|
}
|
|
2397
2579
|
|
|
2398
2580
|
// Paso 4: Click "Bajar Nuevo Set" — esperar POST /facade (GWT RPC) y luego
|
|
@@ -2402,7 +2584,7 @@ class CertRunner {
|
|
|
2402
2584
|
// donde rutRepre/dvRepre vienen de las cookies NETSCAPE_LIVEWIRE.rut / .dv
|
|
2403
2585
|
|
|
2404
2586
|
const rutRepreNum = cookieJar['NETSCAPE_LIVEWIRE.rut'] || cookieJar['RUT_NS'] || '';
|
|
2405
|
-
const dvRepreChar = cookieJar['NETSCAPE_LIVEWIRE.dv']
|
|
2587
|
+
const dvRepreChar = cookieJar['NETSCAPE_LIVEWIRE.dv'] || cookieJar['DV_NS'] || '';
|
|
2406
2588
|
if (!rutRepreNum) throw new Error('No se pudo obtener rutRepre de las cookies SII (NETSCAPE_LIVEWIRE.rut)');
|
|
2407
2589
|
|
|
2408
2590
|
// Registrar listener de facade ANTES de hacer click
|
|
@@ -2411,7 +2593,7 @@ class CertRunner {
|
|
|
2411
2593
|
{ timeout: 20000 }
|
|
2412
2594
|
).catch(() => null);
|
|
2413
2595
|
|
|
2414
|
-
console.log('
|
|
2596
|
+
console.log(' → Click "Bajar Nuevo Set" — esperando GWT facade...');
|
|
2415
2597
|
await page.evaluate(() => {
|
|
2416
2598
|
const btn = Array.from(document.querySelectorAll('button'))
|
|
2417
2599
|
.find(b => /bajar/i.test(b.textContent));
|
|
@@ -2430,7 +2612,7 @@ class CertRunner {
|
|
|
2430
2612
|
`&rutRepre=${rutRepreNum}&dvRepre=${dvRepreChar}` +
|
|
2431
2613
|
`&mailProvSw=${encodeURIComponent(correoSet)}`;
|
|
2432
2614
|
|
|
2433
|
-
console.log(`
|
|
2615
|
+
console.log(` → Descargando set directamente: DownloadFileServlet?rutEmpresa=${rutNum}&dvEmpresa=${dvChar}&rutRepre=${rutRepreNum}&dvRepre=${dvRepreChar}&mailProvSw=${correoSet}`);
|
|
2434
2616
|
|
|
2435
2617
|
const setText = await new Promise((resolve, reject) => {
|
|
2436
2618
|
const req = https.get(downloadUrl, {
|
|
@@ -2465,10 +2647,10 @@ class CertRunner {
|
|
|
2465
2647
|
const nodePath = require('path');
|
|
2466
2648
|
fs.mkdirSync(nodePath.dirname(setPath), { recursive: true });
|
|
2467
2649
|
fs.writeFileSync(setPath, setText, 'utf-8');
|
|
2468
|
-
console.log(`
|
|
2650
|
+
console.log(` ✓ Set guardado en: ${setPath}`);
|
|
2469
2651
|
}
|
|
2470
2652
|
|
|
2471
|
-
console.log(`
|
|
2653
|
+
console.log(` ✓ Set de pruebas obtenido (${setText.length} chars)`);
|
|
2472
2654
|
return { success: true, setText };
|
|
2473
2655
|
} catch (err) {
|
|
2474
2656
|
return { success: false, error: err.message };
|
|
@@ -2506,9 +2688,9 @@ class CertRunner {
|
|
|
2506
2688
|
});
|
|
2507
2689
|
const page = await browser.newPage();
|
|
2508
2690
|
await page.setCookie(...puppeteerCookies);
|
|
2509
|
-
page.on('dialog', async dlg => { console.log(`
|
|
2691
|
+
page.on('dialog', async dlg => { console.log(` → [dialog SET=2] ${dlg.message()}`); await dlg.accept(); });
|
|
2510
2692
|
|
|
2511
|
-
console.log('
|
|
2693
|
+
console.log(' → Cargando portal certBolElectDteInternet (SET=2)...');
|
|
2512
2694
|
await page.goto('https://www4.sii.cl/certBolElectDteInternet/?SET=2', {
|
|
2513
2695
|
waitUntil: 'networkidle2', timeout: 60000,
|
|
2514
2696
|
});
|
|
@@ -2518,7 +2700,7 @@ class CertRunner {
|
|
|
2518
2700
|
const rutInput = await page.$('input[maxlength="8"]');
|
|
2519
2701
|
const dvInput = await page.$('input[maxlength="1"]');
|
|
2520
2702
|
if (!rutInput) throw new Error('certBolElectDteInternet/?SET=2: no se encontró campo RUT');
|
|
2521
|
-
console.log(`
|
|
2703
|
+
console.log(` → Ingresando RUT empresa: ${rutNum}-${dvChar}`);
|
|
2522
2704
|
await rutInput.click({ clickCount: 3 }); await rutInput.type(rutNum);
|
|
2523
2705
|
await dvInput.click({ clickCount: 3 }); await dvInput.type(dvChar);
|
|
2524
2706
|
await page.evaluate(() => {
|
|
@@ -2526,7 +2708,7 @@ class CertRunner {
|
|
|
2526
2708
|
.find(b => /confirmar/i.test(b.textContent));
|
|
2527
2709
|
if (btn) btn.click();
|
|
2528
2710
|
});
|
|
2529
|
-
console.log('
|
|
2711
|
+
console.log(' → Click "Confirmar Empresa"');
|
|
2530
2712
|
|
|
2531
2713
|
// Esperar que aparezca el campo "Identificador de Envio" — GWT dispara ~8-10 POST /facade en paralelo
|
|
2532
2714
|
await page.waitForNetworkIdle({ idleTime: 800, timeout: 40000 }).catch(() => {});
|
|
@@ -2538,7 +2720,7 @@ class CertRunner {
|
|
|
2538
2720
|
// Paso 2: Ingresar TrackId
|
|
2539
2721
|
const trackInput = await page.$('input[maxlength="15"]');
|
|
2540
2722
|
if (!trackInput) throw new Error('No se encontró campo "Identificador de Envio" en certBolElectDteInternet/?SET=2');
|
|
2541
|
-
console.log(`
|
|
2723
|
+
console.log(` → Ingresando TrackId: ${trackId}`);
|
|
2542
2724
|
await trackInput.click({ clickCount: 3 });
|
|
2543
2725
|
await trackInput.type(String(trackId));
|
|
2544
2726
|
|
|
@@ -2548,7 +2730,7 @@ class CertRunner {
|
|
|
2548
2730
|
.find(b => /solicitar/i.test(b.textContent));
|
|
2549
2731
|
if (btn) btn.click();
|
|
2550
2732
|
});
|
|
2551
|
-
console.log('
|
|
2733
|
+
console.log(' → Click "Solicitar validación" — esperando respuesta...');
|
|
2552
2734
|
|
|
2553
2735
|
// Esperar respuesta del portal (puede ser confirmación o error)
|
|
2554
2736
|
await page.waitForFunction(() => {
|
|
@@ -2558,7 +2740,7 @@ class CertRunner {
|
|
|
2558
2740
|
}, { timeout: 15000, polling: 500 }).catch(() => {});
|
|
2559
2741
|
|
|
2560
2742
|
const respuesta = await page.evaluate(() => (document.body.innerText || '').trim().substring(0, 500));
|
|
2561
|
-
console.log(`
|
|
2743
|
+
console.log(` ✓ Validación solicitada. Respuesta: ${respuesta.substring(0, 120)}`);
|
|
2562
2744
|
|
|
2563
2745
|
return { success: true, respuesta };
|
|
2564
2746
|
} catch (err) {
|
|
@@ -2610,7 +2792,7 @@ class CertRunner {
|
|
|
2610
2792
|
let dialogMsg = null;
|
|
2611
2793
|
page.on('dialog', async dlg => {
|
|
2612
2794
|
dialogMsg = dlg.message();
|
|
2613
|
-
console.log(`
|
|
2795
|
+
console.log(` → [dialog] ${dialogMsg}`);
|
|
2614
2796
|
await dlg.accept();
|
|
2615
2797
|
});
|
|
2616
2798
|
|
|
@@ -2623,7 +2805,7 @@ class CertRunner {
|
|
|
2623
2805
|
for (let intento = 1; intento <= MAX_INTENTOS; intento++) {
|
|
2624
2806
|
dialogMsg = null; // resetear entre intentos
|
|
2625
2807
|
|
|
2626
|
-
console.log(`
|
|
2808
|
+
console.log(` → Navegando a certBolElectDteInternet (declaración) [intento ${intento}/${MAX_INTENTOS}]...`);
|
|
2627
2809
|
await page.goto('https://www4.sii.cl/certBolElectDteInternet/', {
|
|
2628
2810
|
waitUntil: 'networkidle2', timeout: 60000,
|
|
2629
2811
|
});
|
|
@@ -2633,7 +2815,7 @@ class CertRunner {
|
|
|
2633
2815
|
const rutInput = await page.$('input[maxlength="8"]');
|
|
2634
2816
|
const dvInput = await page.$('input[maxlength="1"]');
|
|
2635
2817
|
if (!rutInput) throw new Error('certBolElectDteInternet/: no se encontró campo RUT empresa');
|
|
2636
|
-
console.log(`
|
|
2818
|
+
console.log(` → Ingresando RUT empresa: ${rutEmpresaRaw}`);
|
|
2637
2819
|
await rutInput.click({ clickCount: 3 }); await rutInput.type(rutEmpNum);
|
|
2638
2820
|
await dvInput.click({ clickCount: 3 }); await dvInput.type(rutEmpDv);
|
|
2639
2821
|
|
|
@@ -2642,7 +2824,7 @@ class CertRunner {
|
|
|
2642
2824
|
.find(b => /confirmar empresa/i.test(b.textContent));
|
|
2643
2825
|
if (btn) btn.click();
|
|
2644
2826
|
});
|
|
2645
|
-
console.log('
|
|
2827
|
+
console.log(' → Click "Confirmar Empresa"...');
|
|
2646
2828
|
|
|
2647
2829
|
// GWT dispara ~8-10 POST /facade EN PARALELO al confirmar.
|
|
2648
2830
|
// Esperar red inactiva y luego DOM con checkboxes.
|
|
@@ -2654,9 +2836,9 @@ class CertRunner {
|
|
|
2654
2836
|
if (dialogMsg) {
|
|
2655
2837
|
// Portal lanzó alert — puede ser transitorio. Guardar y reintentar.
|
|
2656
2838
|
lastDialogMsg = dialogMsg;
|
|
2657
|
-
console.log(`
|
|
2839
|
+
console.log(` [!] Portal respondió con alerta en intento ${intento}: ${dialogMsg.substring(0, 100)}`);
|
|
2658
2840
|
if (intento < MAX_INTENTOS) {
|
|
2659
|
-
console.log('
|
|
2841
|
+
console.log(' → Recargando y reintentando en 3s...');
|
|
2660
2842
|
await new Promise(r => setTimeout(r, 3000));
|
|
2661
2843
|
continue;
|
|
2662
2844
|
}
|
|
@@ -2669,7 +2851,7 @@ class CertRunner {
|
|
|
2669
2851
|
break;
|
|
2670
2852
|
}
|
|
2671
2853
|
|
|
2672
|
-
console.log(`
|
|
2854
|
+
console.log(` [!] Formulario no cargó en intento ${intento}${intento < MAX_INTENTOS ? ` — reintentando...` : ''}`);
|
|
2673
2855
|
await new Promise(r => setTimeout(r, 2000));
|
|
2674
2856
|
}
|
|
2675
2857
|
|
|
@@ -2682,7 +2864,7 @@ class CertRunner {
|
|
|
2682
2864
|
todos.forEach(cb => { if (!cb.checked) cb.click(); });
|
|
2683
2865
|
return todos.length;
|
|
2684
2866
|
});
|
|
2685
|
-
console.log(`
|
|
2867
|
+
console.log(` → ${totalCbs} checkbox(es) marcados`);
|
|
2686
2868
|
await new Promise(r => setTimeout(r, 500));
|
|
2687
2869
|
|
|
2688
2870
|
// ── PASO 3: Rellenar campos proveedor software ────────────────
|
|
@@ -2753,7 +2935,7 @@ class CertRunner {
|
|
|
2753
2935
|
return false;
|
|
2754
2936
|
});
|
|
2755
2937
|
if (!submitOk) throw new Error('No se encontró botón "Grabar Declaración" en certBolElectDteInternet/');
|
|
2756
|
-
console.log('
|
|
2938
|
+
console.log(' → Click "Grabar Declaración"...');
|
|
2757
2939
|
|
|
2758
2940
|
// Esperar confirmación del SII
|
|
2759
2941
|
await page.waitForFunction(() => {
|
|
@@ -2763,7 +2945,7 @@ class CertRunner {
|
|
|
2763
2945
|
}, { timeout: 20000, polling: 1000 }).catch(() => {});
|
|
2764
2946
|
|
|
2765
2947
|
const msgFinal = await page.evaluate(() => (document.body.textContent || '').trim().substring(0, 300));
|
|
2766
|
-
console.log(`
|
|
2948
|
+
console.log(` ✓ Declaración completada. Respuesta: ${msgFinal.substring(0, 150)}`);
|
|
2767
2949
|
|
|
2768
2950
|
if (this.config.debugDir) {
|
|
2769
2951
|
await page.screenshot({ path: path.join(this.config.debugDir, 'boleta-declaracion-post-submit.png'), fullPage: true }).catch(() => {});
|