@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.
@@ -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
- console.log(` Tipo ${tipoDte}: ${cantidad} folios...`);
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
- console.log(` ✓ CAF tipo ${tipoDte}`);
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
- return this._ejecutarSet(SetBasico, 'setBasico', 'basico', { 33: 4, 56: 1, 61: 3 }, 'basico', casos);
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
- return this._ejecutarSet(SetGuia, 'setGuiaDespacho', 'guia',
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
- return this._ejecutarSet(SetExenta, 'setFacturaExenta', 'exenta', { 34: 3, 56: 1, 61: 4 }, 'exenta', casos);
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
- return this._ejecutarSet(SetCompra, 'setFacturaCompra', 'compra', { 46: 1, 56: 1, 61: 1 }, 'compra', casos);
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(` Esperando 10s para que SII procese los envíos...`);
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
- console.log(` 🔄 Declarando ${label} (intento ${intento}/${maxIntentos})...`);
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(` SII aún procesando, reintentando en ${intervalo / 1000}s...`);
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(` ⚠️ Verificación fallida: ${result.error}`);
386
- console.log(` 🔄 Reintentando declaración en ${intervalo / 1000}s...`);
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(` ⚠️ Error declarando ${label}: ${result.error || 'desconocido'}`);
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(' Declaración de sets enviada');
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(`📚 FASE 4: LIBROS (cada libro usará período diferente)`);
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(` ℹ️ Ya en REVISADO CONFORME (se omitirán): ${_yaConformes.join(', ')}`);
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
- console.log('\n✅ Libro de Compras ya está REVISADO CONFORME — omitiendo');
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
- console.log('\n📖 Enviando Libro de Compras...');
527
- resultados.libroCompras = await this.ejecutarLibroCompras(options);
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
- console.log('\n✅ Libro de Ventas ya está REVISADO CONFORME — omitiendo');
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
- console.log('\n📖 Enviando Libro de Ventas...');
544
- resultados.libroVentas = await this.ejecutarLibroVentas(options);
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
- console.log('\n✅ Libro de Guías ya está REVISADO CONFORME — omitiendo');
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
- console.log('\n📖 Enviando Libro de Guías...');
561
- resultados.libroGuias = await this.ejecutarLibroGuias(options);
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
- errores.push(`Libro Guías: ${resultados.libroGuias.error}`);
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
- console.log('\n✅ Libro Compras Exentos ya está REVISADO CONFORME — omitiendo');
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
- console.log('\n📖 Enviando Libro de Compras para Exentos...');
579
- resultados.libroComprasExentos = await this.ejecutarLibroComprasExentos(options);
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
- // 4. Declarar los libros (incluyendo sets para evitar que pe_avance3 los resetee)
603
- console.log('\n📝 Declarando libros...');
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
- const declaracion = await this.declararLibros({ ...resultados, ...(options.setsResultados || {}) });
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 Libros declarados — esperando revisión del SII...');
610
-
611
- // Polling real: esperar hasta REVISADO CONFORME o S25 (máx 20 intentos x 15s = 5 min)
612
- const _librosAEsperar = ['LIBRO DE VENTAS', 'LIBRO DE COMPRAS', 'LIBRO DE GUIAS'];
613
- if (this._estructuras?.libroComprasExentos) _librosAEsperar.push('LIBRO DE COMPRAS PARA EXENTOS');
614
- // Solo esperar los libros que realmente enviamos (no los que ya eran REVISADO CONFORME)
615
- const _librosEnviados = _librosAEsperar.filter(n => !_estaConforme(n));
616
-
617
- console.log(`\n⏳ Esperando aprobación del SII para: ${_librosEnviados.join(', ')}`);
618
- let _aprobados = false;
619
- for (let _i = 0; _i < 20; _i++) {
620
- await sleep(15000);
621
- const _poll = await this.siiCert.consultarEstadoSets();
622
- if (!_poll.success) continue;
623
- const _ss = _poll.estadoSets || {};
624
- const _info = Object.entries(_ss)
625
- .filter(([k]) => k.toUpperCase().includes('LIBRO'))
626
- .map(([k, v]) => `${k.trim()}: ${v}`);
627
- if (_info.length) console.log(` 🔄 Intento ${_i + 1}/20: ${_info.join(' | ')}`);
628
-
629
- const _todosOk = _librosEnviados.every(nombre => {
630
- const e = Object.entries(_ss).find(([k]) => k.toUpperCase().includes(nombre));
631
- return e && (e[1] === 'REVISADO CONFORME' || e[1] === 'S25');
632
- });
633
- const _algunError = _librosEnviados.some(nombre => {
634
- const e = Object.entries(_ss).find(([k]) => k.toUpperCase().includes(nombre));
635
- return e && (e[1] === 'LNC' || e[1] === 'LRH' || e[1].includes('RECHAZADO') || e[1].includes('ERROR'));
636
- });
637
-
638
- if (_todosOk) {
639
- console.log('\n🎉 ¡LIBROS APROBADOS POR EL SII!');
640
- _aprobados = true;
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 (_algunError) {
644
- console.log('\n❌ Hay libros rechazados. Revisar emails del SII.');
645
- break;
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
- if (!_aprobados) {
649
- console.log('\n⚠️ Timeout (5 min). El SII aún no responde. Verifica con --avance más tarde.');
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
- console.log(`\n⚠️ Libros enviados pero declaración con error: ${declaracion.error}`);
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⚠️ Error declarando libros: ${e.message}`);
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⚠️ Solo ${librosEnviados}/3 libros enviados. Errores: ${errores.join('; ')}`);
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(` Libros declarados: ${declarados.join(', ')}`);
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(` 📅 Período decrementado: ${currentPeriodo} → ${newPeriodo}`);
961
+ console.log(` Período decrementado: ${currentPeriodo} → ${newPeriodo}`);
778
962
  } catch (e) {
779
- console.warn(` ⚠️ No se pudo guardar período: ${e.message}`);
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(` 📅 Período reseteado a: ${periodo}`);
979
+ console.log(` Período reseteado a: ${periodo}`);
796
980
  } catch (e) {
797
- console.warn(` ⚠️ No se pudo guardar período: ${e.message}`);
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
- // IMPORTANTE: Decrementar período ANTES de usar para evitar "LNC - Libro Cerrado"
824
- this._decrementarPeriodoLibros();
825
- const periodo = this._getPeriodoLibros();
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(` Libro de Ventas enviado - TrackId: ${result.trackId}`);
1040
+ console.log(` [OK] Libro de Ventas enviado - TrackId: ${result.trackId}`);
859
1041
  } else {
860
- console.log(` Error enviando Libro de Ventas: ${result.error}`);
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
- // IMPORTANTE: Decrementar período ANTES de usar para evitar "LNC - Libro Cerrado"
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(` 📚 Generando Libro de Compras para período ${periodo} (${libroComprasData.detalle.length} documentos del SII)...`);
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(` Libro de Compras enviado - TrackId: ${result.trackId}`);
1091
+ console.log(` [OK] Libro de Compras enviado - TrackId: ${result.trackId}`);
913
1092
  } else {
914
- console.log(` Error enviando Libro de Compras: ${result.error}`);
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(` 📚 Generando Libro de Compras para Exentos para período ${periodo} (${libroData.detalle.length} documentos del SII)...`);
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(` Libro de Compras para Exentos enviado - TrackId: ${result.trackId}`);
1138
+ console.log(` [OK] Libro de Compras para Exentos enviado - TrackId: ${result.trackId}`);
962
1139
  } else {
963
- console.log(` Error enviando Libro de Compras para Exentos: ${result.error}`);
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
- // IMPORTANTE: Decrementar período ANTES de usar para evitar "LNC - Libro Cerrado"
984
- this._decrementarPeriodoLibros();
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(` Libro de Guías enviado - TrackId: ${result.trackId}`);
1194
+ console.log(` [OK] Libro de Guías enviado - TrackId: ${result.trackId}`);
1021
1195
  } else {
1022
- console.log(` Error enviando Libro de Guías: ${result.error}`);
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('🚀 AVANZAR SIGUIENTE PASO');
1212
+ console.log('AVANZAR SIGUIENTE PASO');
1039
1213
  console.log('═'.repeat(60) + '\n');
1040
1214
 
1041
1215
  try {
1042
- console.log(' 📋 Enviando solicitud de avance...');
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(` 📄 Respuesta guardada en: ${path.join(this.debugDir, 'avanzar-siguiente-paso-response.html')}`);
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(' Avance al siguiente paso exitoso');
1229
+ console.log(' [OK] Avance al siguiente paso exitoso');
1056
1230
  this.resultados.avanceSiguientePaso = { success: true };
1057
1231
  } else {
1058
- console.log(` Error en avance: ${result.error || 'Error desconocido'}`);
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(` Error: ${error.message}`);
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 Esperando aprobación de libros...');
1252
+ console.log('\n[...] Esperando aprobación de libros...');
1079
1253
 
1080
1254
  for (let i = 1; i <= maxIntentos; i++) {
1081
- console.log(`\n 🔄 Intento ${i}/${maxIntentos}...`);
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(` ⚠️ Error consultando avance: ${avance.error}`);
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(` ${libro}: REVISADO CONFORME`);
1280
+ console.log(` [OK] ${libro}: REVISADO CONFORME`);
1107
1281
  } else if (esRechazado) {
1108
- console.log(` ${libro}: ${estado.estado}`);
1282
+ console.log(` [ERR] ${libro}: ${estado.estado}`);
1109
1283
  hayRechazados = true;
1110
1284
  } else {
1111
- console.log(` 🔄 ${libro}: ${estado.estado || 'EN REVISION'}`);
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 Hay libros rechazados. No se puede avanzar.');
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 🎉 ¡Todos los libros aprobados!');
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 ⚠️ Timeout esperando aprobación de libros');
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('🧪 FASE 6: SIMULACIÓN');
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(' Solicitando CAFs para simulación...');
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(' Generando DTEs de simulación...');
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(` 📦 Plan de simulación: ${plan.length} documentos`);
1189
- console.log(` 📄 Tipos usados: ${tiposUsados.join(', ')}`);
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 📤 Enviando al SII...');
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 Simulación enviada - TrackId: ${result.trackId}`);
1402
+ console.log(`\n[OK] Simulación enviada - TrackId: ${result.trackId}`);
1230
1403
  } else {
1231
- console.log(`\n Error en simulación: ${result.error}`);
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(' 🔍 Verificando etapa actual...');
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(' Simulación ya aprobada - empresa en etapa INTERCAMBIO');
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(' Simulación declarada exitosamente');
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 Esperando aprobación de simulació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 🔄 Intento ${i}/${maxIntentos}...`);
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(` ⚠️ Error consultando avance: ${avance.error}`);
1505
+ console.log(` [!] Error consultando avance: ${avance.error}`);
1333
1506
  await sleep(intervalo);
1334
1507
  continue;
1335
1508
  }
1336
1509
 
1337
- // PRIMERO: Verificar si ya pasó a INTERCAMBIO (significa que simulación fue aprobada)
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(` Etapa actual: ${avance.etapaActual}`);
1340
- console.log('\n 🎉 ¡SIMULACIÓN APROBADA! Empresa pasó a etapa INTERCAMBIO.');
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
- // TAMBIÉN: Etapas que vienen DESPUÉS de INTERCAMBIO (simulación + intercambio ya completos)
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(` 📍 Etapa actual: ${avance.etapaActual}`);
1348
- console.log('\n 🎉 ¡SIMULACIÓN + INTERCAMBIO COMPLETADOS! Empresa en etapa: ' + avance.etapaActual);
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
- // SEGUNDO: Verificar indicador de formulario de confirmación (simulación aprobada pendiente confirmar)
1525
+ // [OK] SEGUNDO: Verificar indicador de formulario de confirmación (simulación aprobada pendiente confirmar)
1353
1526
  if (avance.simulacionAprobadaIndicador) {
1354
- console.log(` Formulario de confirmación detectado`);
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 📝 Confirmando revisión de simulación (TrackId: ${this.resultados.simulacion.trackId})...`);
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(' Confirmación enviada exitosamente');
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 🎉 ¡SIMULACIÓN CONFIRMADA! Certificación completa.');
1554
+ console.log('\n ¡SIMULACIÓN CONFIRMADA! Certificación completa.');
1382
1555
  return { success: true, confirmada: true };
1383
1556
  }
1384
1557
 
1385
- console.log(' ⚠️ SII aún mantiene formulario de simulación pendiente; se reintentará...');
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(` ⚠️ Error en confirmación: ${confirmResult.error}`);
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 🎉 ¡SIMULACIÓN APROBADA! Lista para confirmar revisió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(` SIMULACIÓN: REVISADO CONFORME`);
1415
- console.log('\n 🎉 ¡SIMULACIÓN APROBADA! Certificación completa.');
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(` SIMULACIÓN: ${simEstado.estado}`);
1591
+ console.log(` [ERR] SIMULACIÓN: ${simEstado.estado}`);
1419
1592
  return { success: false, error: 'Simulación rechazada' };
1420
1593
  } else {
1421
- console.log(` 🔄 SIMULACIÓN: ${simEstado.estado || 'EN REVISION'}`);
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(` 📍 Etapa actual: ${avance.etapaActual}`);
1599
+ console.log(` Etapa actual: ${avance.etapaActual}`);
1427
1600
  } else {
1428
- console.log(' Simulación aún no registrada...');
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 ⚠️ Timeout esperando aprobación de simulació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('📬 FASE 7: INTERCAMBIO DE INFORMACIÓN');
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(`\n📂 Leyendo SET desde: ${setInputPath}`);
1652
+ console.log(`\nLeyendo SET desde: ${setInputPath}`);
1480
1653
  setXml = fs.readFileSync(setInputPath, 'utf8');
1481
- console.log(` ✓ ${setXml.length} bytes`);
1654
+ console.log(` ✓ ${setXml.length} bytes`);
1482
1655
  } else if (fs.existsSync(setDownloadPath)) {
1483
- console.log(`\n📂 Leyendo SET guardado: ${setDownloadPath}`);
1656
+ console.log(`\nLeyendo SET guardado: ${setDownloadPath}`);
1484
1657
  setXml = fs.readFileSync(setDownloadPath, 'utf8');
1485
- console.log(` ✓ ${setXml.length} bytes`);
1658
+ console.log(` ✓ ${setXml.length} bytes`);
1486
1659
  } else {
1487
- console.log('\n📡 Descargando SET desde www4.sii.cl/pfeInternet...');
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(` SET descargado (${setXml.length} bytes) → ${setDownloadPath}`);
1665
+ console.log(` [OK] SET descargado (${setXml.length} bytes) → ${setDownloadPath}`);
1493
1666
  } else {
1494
- console.log(` ⚠️ No se pudo descargar: ${dl.error}`);
1667
+ console.log(` [!] No se pudo descargar: ${dl.error}`);
1495
1668
  console.log('\n' + '─'.repeat(60));
1496
- console.log('📋 DESCARGA MANUAL REQUERIDA:');
1497
- console.log(' 1. Si aparece error de sesiones: ingresa a https://www4.sii.cl/ → Cerrar Sesión');
1498
- console.log(' 2. Ir a: https://www4.sii.cl/pfeInternet/ y descargar el SET XML');
1499
- console.log(` 3. Guardarlo en: ${setDownloadPath}`);
1500
- console.log(' 4. Volver a ejecutar el runner');
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('\n📝 Generando respuestas firmadas...');
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('\n📤 Subiendo respuestas a www4.sii.cl/pfeInternet...');
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(' INTERCAMBIO COMPLETADO');
1707
+ console.log('[OK] INTERCAMBIO COMPLETADO');
1535
1708
  console.log('═'.repeat(60));
1536
- if (uploadResult.resultado) console.log(` Resultado SII: ${uploadResult.resultado}`);
1709
+ if (uploadResult.resultado) console.log(` Resultado SII: ${uploadResult.resultado}`);
1537
1710
  } else {
1538
- console.log(` ⚠️ No se pudo subir automáticamente: ${uploadResult.error}`);
1711
+ console.log(` [!] No se pudo subir automáticamente: ${uploadResult.error}`);
1539
1712
  console.log('\n' + '─'.repeat(60));
1540
- console.log('📋 SUBIDA MANUAL REQUERIDA:');
1541
- console.log(' 1. Ir a: https://www4.sii.cl/pfeInternet/ → "Subir archivos"');
1542
- console.log(` 2. Subir: ${genResult.files.recepcion}`);
1543
- console.log(` 3. Subir: ${genResult.files.aprobacion}`);
1544
- console.log(` 4. Subir: ${genResult.files.recibos}`);
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] ♻️ Reutilizando sesión SII en memoria');
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] Sesión SII activa (cookies NETSCAPE: ${nSession})`);
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(` → Descargando SET desde pfeInternet/downloadFile (RUT ${rutNum}-${dv})...`);
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(` ✓ SET descargado correctamente (${r.body.length} bytes)`);
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(` ${errMsg}`);
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', filename: 'respuesta-recepcion-envio.xml', content: recepcionXml, uploadN: 1 },
1727
- { label: 'Recibo de Mercaderias', filename: 'envio-recibos.xml', content: recibosXml, uploadN: 2 },
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(' → Cargando portal pfeInternet...');
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(' → Clickeando "Subir archivos XML de respuesta de Intercambio"...');
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(` → Ingresando RUT empresa: ${rutConDv}`);
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(' → Click "Confirmar Empresa", esperando formulario de upload...');
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(' → Formulario de upload listo');
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(` → ${archivo.filename}: ya procesado anteriormente, saltando...`);
2022
+ console.log(` → ${archivo.filename}: ya procesado anteriormente, saltando...`);
1850
2023
  continue;
1851
2024
  }
1852
2025
 
1853
- console.log(` → Subiendo ${archivo.filename}...`);
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(` ✓ ${msgText}`);
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') || t.includes('RECHAZADO');
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')) return 'APROBADO';
1974
- if (t.includes('POR REVISAR')) return 'POR REVISAR';
1975
- if (t.includes('EN REVISI')) return 'EN REVISIÓN';
1976
- if (t.includes('RECHAZADO')) return 'RECHAZADO';
1977
- if (t.includes('ENVIADO AL SII')) return '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(`📄 FASE 8: MUESTRAS IMPRESAS (${pdfPaths.length} PDFs)`);
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(' → Cargando portal pdfdteInternet...');
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(` → Ingresando RUT empresa: ${rutNum}-${dvChar}`);
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(' → Diálogo de revisión existentehaciendo click en "Sí"');
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(` → Ingresando RUT proveedor: ${rutNum}-${dvChar}`);
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
- const _estadoYaSubido = await page.evaluate(() => {
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')) return 'APROBADO';
2111
- if (t.includes('POR REVISAR')) return 'POR REVISAR';
2112
- if (t.includes('EN REVISI')) return 'EN REVISIÓN';
2113
- if (t.includes('RECHAZADO')) return '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(` Portal ya muestra estado "${_estadoYaSubido}" — muestras subidas previamente. Proceso completado.`);
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(' → Click "Crear"...');
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 como base64 en Node.js y soltarlos en el drop zone de GWT
2179
- // de una sola vez via DataTransfer. GWT los procesa en secuencia internamente:
2180
- // drop por cada file: submit form al iframe → respuesta → leeImpresoById → tick verde
2181
- // Esto evita la re-navegación entre archivos y garantiza que la validación
2182
- // (Timbre/CAF/TED) ocurra antes de salir de la página.
2183
- console.log(` → Cargando ${pdfPaths.length} PDFs para drop en el portal...`);
2184
- const _fileDataList = pdfPaths.map(p => ({
2185
- name: path.basename(p),
2186
- b64: fs.readFileSync(p).toString('base64'),
2187
- }));
2188
-
2189
- if (debugDir) await page.screenshot({ path: path.join(debugDir, 'pdfte-04b-antes-drop.png'), fullPage: true }).catch(() => {});
2190
-
2191
- console.log(` → Ejecutando drop de ${pdfPaths.length} PDFs sobre el portal...`);
2192
- const _dropped = await page.evaluate((files) => {
2193
- const dt = new DataTransfer();
2194
- for (const f of files) {
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
- if (_dropped === 0) throw new Error('pdfdteInternet: drop zone no encontrado (.dropFilesLabel)');
2209
- console.log(` → Drop ejecutado (${_dropped} archivos). Esperando procesamiento...`);
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')) estado = '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')) estado = 'EN REVISIÓN';
2236
- else if (t.includes('RECHAZADO')) estado = '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(` Portal en estado "${_fase1.estado}" — muestras ya procesadas previamente.`);
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(' Sin progreso en 45s y sin estado terminal. Continuando al paso siguiente...');
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(` Timeout: solo se procesaron ${procesados}/${pdfPaths.length} antes del timeout`);
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(' → Re-navegando para "Enviar al SII"...');
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(' → Click "Enviar al SII"...');
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(` → Resultado: ${exitoso ? ' enviado correctamente' : '⚠️ sin confirmación explícita'}`);
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(` → [dialog SET=1] ${dlg.message()}`); await dlg.accept(); });
2525
+ page.on('dialog', async dlg => { console.log(` → [dialog SET=1] ${dlg.message()}`); await dlg.accept(); });
2344
2526
 
2345
- console.log(' → Cargando portal certBolElectDteInternet (SET=1)...');
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(` → Ingresando RUT empresa: ${rutNum}-${dvChar}`);
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(' → Click "Confirmar Empresa"');
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(` → ${nCbs} checkbox(es) marcados`);
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(` → Correo proveedor: ${correoSet}`);
2575
+ console.log(` → Correo proveedor: ${correoSet}`);
2394
2576
  } else {
2395
- console.log(' ⚠️ No se encontró campo de correo — continuando sin él');
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'] || cookieJar['DV_NS'] || '';
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(' → Click "Bajar Nuevo Set" — esperando GWT facade...');
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(` → Descargando set directamente: DownloadFileServlet?rutEmpresa=${rutNum}&dvEmpresa=${dvChar}&rutRepre=${rutRepreNum}&dvRepre=${dvRepreChar}&mailProvSw=${correoSet}`);
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(` ✓ Set guardado en: ${setPath}`);
2650
+ console.log(` ✓ Set guardado en: ${setPath}`);
2469
2651
  }
2470
2652
 
2471
- console.log(` ✓ Set de pruebas obtenido (${setText.length} chars)`);
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(` → [dialog SET=2] ${dlg.message()}`); await dlg.accept(); });
2691
+ page.on('dialog', async dlg => { console.log(` → [dialog SET=2] ${dlg.message()}`); await dlg.accept(); });
2510
2692
 
2511
- console.log(' → Cargando portal certBolElectDteInternet (SET=2)...');
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(` → Ingresando RUT empresa: ${rutNum}-${dvChar}`);
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(' → Click "Confirmar Empresa"');
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(` → Ingresando TrackId: ${trackId}`);
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(' → Click "Solicitar validación" — esperando respuesta...');
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(` ✓ Validación solicitada. Respuesta: ${respuesta.substring(0, 120)}`);
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(` → [dialog] ${dialogMsg}`);
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(` → Navegando a certBolElectDteInternet (declaración) [intento ${intento}/${MAX_INTENTOS}]...`);
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(` → Ingresando RUT empresa: ${rutEmpresaRaw}`);
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(' → Click "Confirmar Empresa"...');
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(` ⚠️ Portal respondió con alerta en intento ${intento}: ${dialogMsg.substring(0, 100)}`);
2839
+ console.log(` [!] Portal respondió con alerta en intento ${intento}: ${dialogMsg.substring(0, 100)}`);
2658
2840
  if (intento < MAX_INTENTOS) {
2659
- console.log(' → Recargando y reintentando en 3s...');
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(` ⚠️ Formulario no cargó en intento ${intento}${intento < MAX_INTENTOS ? ` — reintentando...` : ''}`);
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(` → ${totalCbs} checkbox(es) marcados`);
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(' → Click "Grabar Declaración"...');
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(` ✓ Declaración completada. Respuesta: ${msgFinal.substring(0, 150)}`);
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(() => {});