@devlas/dte-sii 2.5.12 → 2.5.14

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
  /**
@@ -338,16 +356,27 @@ class CertRunner {
338
356
  async _declararConReintentos(sets, debugPrefix, options = {}) {
339
357
  const { maxIntentos = 10, intervalo = 5000, label = 'avance' } = 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;
350
369
 
370
+ // Guardar el form pe_avance2 (antes del POST) para debug
371
+ if (result.formHtml) {
372
+ fs.writeFileSync(
373
+ path.join(this.debugDir, `${debugPrefix}-pe_avance2-${intento}.html`),
374
+ result.formHtml,
375
+ 'utf8'
376
+ );
377
+ }
378
+
379
+ // Guardar la respuesta pe_avance3 (despues del POST) para debug
351
380
  if (result.rawHtml) {
352
381
  fs.writeFileSync(
353
382
  path.join(this.debugDir, `${debugPrefix}-${intento}.html`),
@@ -368,10 +397,19 @@ class CertRunner {
368
397
  }
369
398
 
370
399
  if (noProcessedError && intento < maxIntentos) {
371
- console.log(` SII aún procesando, reintentando en ${intervalo / 1000}s...`);
400
+ console.log(` [...] SII aún procesando, reintentando en ${intervalo / 1000}s...`);
401
+ await sleep(intervalo);
402
+ } else if (result.allRejected) {
403
+ // SII rechazó todos los sets/libros — período incorrecto, no tiene sentido reintentar
404
+ console.log(` [ERR] SII rechazó todos los envíos (campos vacíos en portal) — período incorrecto. Corregir período y reenviar.`);
405
+ break;
406
+ } else if (result.verificado === false && intento < maxIntentos) {
407
+ // Verificación post-declaración falló: los campos quedaron vacíos en el portal
408
+ console.log(` [!] Verificación fallida: ${result.error}`);
409
+ console.log(` [...] Reintentando declaración en ${intervalo / 1000}s...`);
372
410
  await sleep(intervalo);
373
411
  } else if (!result.success) {
374
- console.log(` ⚠️ Error declarando ${label}: ${result.error || 'desconocido'}`);
412
+ console.log(` [!] Error declarando ${label}: ${result.error || 'desconocido'}`);
375
413
  break;
376
414
  }
377
415
  }
@@ -419,8 +457,9 @@ class CertRunner {
419
457
  return { success: false, error: 'No hay sets para declarar' };
420
458
  }
421
459
 
460
+ emitProgress(STEPS.SETS_DECLARING);
422
461
  const result = await this._declararConReintentos(sets, 'declaracion-response', { maxIntentos, intervalo, label: 'avance de sets' });
423
- if (result?.success) console.log(' Declaración de sets enviada');
462
+ if (result?.success) { emitProgress(STEPS.SETS_DECLARED); console.log(' Declaracion de sets enviada'); }
424
463
  return result;
425
464
  }
426
465
 
@@ -460,23 +499,72 @@ class CertRunner {
460
499
  * @param {Object} [options] - Opciones
461
500
  * @param {Object} [options.setBasicoResult] - Resultado del SetBasico
462
501
  * @param {Object} [options.setGuiaResult] - Resultado del SetGuia
502
+ * @param {Object} [options.setsResultados] - Track IDs de los sets (basico/guia/exenta/compra) para incluirlos en la declaración conjunta
463
503
  * @returns {Promise<Object>} Resultado con todos los libros
464
504
  */
465
505
  async ejecutarFase4Libros(options = {}) {
466
506
  // NOTA: Ya NO decrementamos aquí - cada libro decrementa su propio período
507
+ emitProgress(STEPS.BOOKS_START);
467
508
  console.log('\n' + '═'.repeat(60));
468
- console.log(`📚 FASE 4: LIBROS (cada libro usará período diferente)`);
509
+ console.log('FASE 4: LIBROS (todos usan el mismo periodo)');
469
510
  console.log('═'.repeat(60) + '\n');
470
511
 
471
512
  const resultados = {};
472
513
  const errores = [];
473
514
 
515
+ // Decrementar período UNA VEZ para todos los libros (todos usan el mismo período)
516
+ this._decrementarPeriodoLibros();
517
+ const _periodoComunLibros = this._getPeriodoLibros();
518
+ console.log(` Período para todos los libros: ${_periodoComunLibros}`);
519
+
520
+ // Verificar cuáles libros ya están REVISADO CONFORME en el portal (no re-enviar)
521
+ let _estadoActual = {};
522
+ try {
523
+ const _consultaPrevia = await this.siiCert.consultarEstadoSets();
524
+ if (_consultaPrevia.success) _estadoActual = _consultaPrevia.estadoSets || {};
525
+ const _yaConformes = Object.entries(_estadoActual)
526
+ .filter(([k, v]) => k.toUpperCase().includes('LIBRO') && v === 'REVISADO CONFORME')
527
+ .map(([k]) => k);
528
+ if (_yaConformes.length) {
529
+ console.log(` Ya en REVISADO CONFORME (se omitirán): ${_yaConformes.join(', ')}`);
530
+ }
531
+ } catch (_e) { /* ignorar error de consulta previa */ }
532
+
533
+ const _estaConforme = (nombre) => {
534
+ const nombreUpper = nombre.toUpperCase();
535
+ const e = Object.entries(_estadoActual).find(([k]) => {
536
+ const ku = k.toUpperCase();
537
+ if (nombreUpper === 'LIBRO DE COMPRAS') return ku.includes('LIBRO DE COMPRAS') && !ku.includes('EXENTOS');
538
+ return ku.includes(nombreUpper);
539
+ });
540
+ return e && e[1] === 'REVISADO CONFORME';
541
+ };
542
+
543
+ // Helper: guardar resultados parciales a disco tras cada envío exitoso
544
+ const _resultadosLibrosPath = path.join(this.debugDir, 'resultados-libros.json');
545
+ const _guardarResultadosParciales = () => {
546
+ try {
547
+ fs.writeFileSync(_resultadosLibrosPath, JSON.stringify(resultados, null, 2));
548
+ } catch (_e) { /* ignorar */ }
549
+ };
550
+
474
551
  try {
475
552
  // 1. Libro de Compras (usa datos del SII)
476
- console.log('\n📖 Enviando Libro de Compras...');
477
- resultados.libroCompras = await this.ejecutarLibroCompras(options);
478
- if (!resultados.libroCompras.success) {
479
- errores.push(`Libro Compras: ${resultados.libroCompras.error}`);
553
+ if (_estaConforme('LIBRO DE COMPRAS')) {
554
+ emitProgress(STEPS.BOOK_SKIPPED, { book: 'libroCompras' });
555
+ resultados.libroCompras = { success: true, conforme: true };
556
+ console.log('\n[OK] Libro de Compras ya esta REVISADO CONFORME — omitiendo');
557
+ } else {
558
+ emitProgress(STEPS.BOOK_SENDING, { book: 'libroCompras' });
559
+ console.log('\nEnviando Libro de Compras...');
560
+ resultados.libroCompras = await this.ejecutarLibroCompras({ ...options, periodo: _periodoComunLibros });
561
+ if (!resultados.libroCompras.success) {
562
+ emitProgress(STEPS.BOOK_ERROR, { book: 'libroCompras', error: resultados.libroCompras.error });
563
+ errores.push(`Libro Compras: ${resultados.libroCompras.error}`);
564
+ } else {
565
+ emitProgress(STEPS.BOOK_OK, { book: 'libroCompras', trackId: resultados.libroCompras.trackId });
566
+ _guardarResultadosParciales();
567
+ }
480
568
  }
481
569
  } catch (e) {
482
570
  errores.push(`Libro Compras: ${e.message}`);
@@ -484,10 +572,21 @@ class CertRunner {
484
572
 
485
573
  try {
486
574
  // 2. Libro de Ventas (usa SetBasico)
487
- console.log('\n📖 Enviando Libro de Ventas...');
488
- resultados.libroVentas = await this.ejecutarLibroVentas(options);
489
- if (!resultados.libroVentas.success) {
490
- errores.push(`Libro Ventas: ${resultados.libroVentas.error}`);
575
+ if (_estaConforme('LIBRO DE VENTAS')) {
576
+ emitProgress(STEPS.BOOK_SKIPPED, { book: 'libroVentas' });
577
+ resultados.libroVentas = { success: true, conforme: true };
578
+ console.log('\n[OK] Libro de Ventas ya esta REVISADO CONFORME — omitiendo');
579
+ } else {
580
+ emitProgress(STEPS.BOOK_SENDING, { book: 'libroVentas' });
581
+ console.log('\nEnviando Libro de Ventas...');
582
+ resultados.libroVentas = await this.ejecutarLibroVentas({ ...options, periodo: _periodoComunLibros });
583
+ if (!resultados.libroVentas.success) {
584
+ emitProgress(STEPS.BOOK_ERROR, { book: 'libroVentas', error: resultados.libroVentas.error });
585
+ errores.push(`Libro Ventas: ${resultados.libroVentas.error}`);
586
+ } else {
587
+ emitProgress(STEPS.BOOK_OK, { book: 'libroVentas', trackId: resultados.libroVentas.trackId });
588
+ _guardarResultadosParciales();
589
+ }
491
590
  }
492
591
  } catch (e) {
493
592
  errores.push(`Libro Ventas: ${e.message}`);
@@ -495,22 +594,44 @@ class CertRunner {
495
594
 
496
595
  try {
497
596
  // 3. Libro de Guías (usa SetGuia)
498
- console.log('\n📖 Enviando Libro de Guías...');
499
- resultados.libroGuias = await this.ejecutarLibroGuias(options);
500
- if (!resultados.libroGuias.success) {
501
- errores.push(`Libro Guías: ${resultados.libroGuias.error}`);
597
+ if (_estaConforme('LIBRO DE GUIAS')) {
598
+ emitProgress(STEPS.BOOK_SKIPPED, { book: 'libroGuias' });
599
+ resultados.libroGuias = { success: true, conforme: true };
600
+ console.log('\n[OK] Libro de Guias ya esta REVISADO CONFORME — omitiendo');
601
+ } else {
602
+ emitProgress(STEPS.BOOK_SENDING, { book: 'libroGuias' });
603
+ console.log('\nEnviando Libro de Guias...');
604
+ resultados.libroGuias = await this.ejecutarLibroGuias({ ...options, periodo: _periodoComunLibros });
605
+ if (!resultados.libroGuias.success) {
606
+ emitProgress(STEPS.BOOK_ERROR, { book: 'libroGuias', error: resultados.libroGuias.error });
607
+ errores.push(`Libro Guias: ${resultados.libroGuias.error}`);
608
+ } else {
609
+ emitProgress(STEPS.BOOK_OK, { book: 'libroGuias', trackId: resultados.libroGuias.trackId });
610
+ _guardarResultadosParciales();
611
+ }
502
612
  }
503
613
  } catch (e) {
504
614
  errores.push(`Libro Guías: ${e.message}`);
505
615
  }
506
616
 
507
- // 4. Libro de Compras para Exentos (solo si el SII lo entregó)
617
+ // 4. Libro de Compras para Exentos (solo si el SII lo entregó y no está ya aprobado)
508
618
  if (this._estructuras?.libroComprasExentos) {
509
619
  try {
510
- console.log('\n📖 Enviando Libro de Compras para Exentos...');
511
- resultados.libroComprasExentos = await this.ejecutarLibroComprasExentos(options);
512
- if (!resultados.libroComprasExentos.success) {
513
- errores.push(`Libro Compras Exentos: ${resultados.libroComprasExentos.error}`);
620
+ if (_estaConforme('LIBRO DE COMPRAS PARA EXENTOS')) {
621
+ emitProgress(STEPS.BOOK_SKIPPED, { book: 'libroComprasExentos' });
622
+ resultados.libroComprasExentos = { success: true, conforme: true };
623
+ console.log('\n[OK] Libro Compras Exentos ya esta REVISADO CONFORME — omitiendo');
624
+ } else {
625
+ emitProgress(STEPS.BOOK_SENDING, { book: 'libroComprasExentos' });
626
+ console.log('\nEnviando Libro de Compras para Exentos...');
627
+ resultados.libroComprasExentos = await this.ejecutarLibroComprasExentos({ ...options, periodo: _periodoComunLibros });
628
+ if (!resultados.libroComprasExentos.success) {
629
+ emitProgress(STEPS.BOOK_ERROR, { book: 'libroComprasExentos', error: resultados.libroComprasExentos.error });
630
+ errores.push(`Libro Compras Exentos: ${resultados.libroComprasExentos.error}`);
631
+ } else {
632
+ emitProgress(STEPS.BOOK_OK, { book: 'libroComprasExentos', trackId: resultados.libroComprasExentos.trackId });
633
+ _guardarResultadosParciales();
634
+ }
514
635
  }
515
636
  } catch (e) {
516
637
  errores.push(`Libro Compras Exentos: ${e.message}`);
@@ -518,27 +639,203 @@ class CertRunner {
518
639
  }
519
640
 
520
641
  // Contar libros obligatorios (ventas + compras + guías)
642
+ // Un libro cuenta como OK si fue enviado exitosamente O si ya era REVISADO CONFORME (omitido)
521
643
  const librosObligatorios = ['libroVentas', 'libroCompras', 'libroGuias'];
522
- const librosEnviados = librosObligatorios.filter(k => resultados[k]?.success).length;
644
+ const librosEnviados = librosObligatorios.filter(k => {
645
+ if (resultados[k]?.success) return true;
646
+ // Mapeo clave→nombre para consultar _estaConforme
647
+ const nombreMap = { libroVentas: 'LIBRO DE VENTAS', libroCompras: 'LIBRO DE COMPRAS', libroGuias: 'LIBRO DE GUIAS' };
648
+ return _estaConforme(nombreMap[k]);
649
+ }).length;
523
650
 
524
651
  if (librosEnviados === 3) {
525
- // 4. Declarar los libros
526
- console.log('\n📝 Declarando libros...');
652
+ // Mapeo entre nombre SII y clave interna
653
+ const _SII_NOMBRE_A_KEY = {
654
+ 'LIBRO DE VENTAS': 'libroVentas',
655
+ 'LIBRO DE COMPRAS': 'libroCompras',
656
+ 'LIBRO DE GUIAS': 'libroGuias',
657
+ 'LIBRO DE COMPRAS PARA EXENTOS': 'libroComprasExentos',
658
+ };
659
+ const _KEY_A_SII_NOMBRE = Object.fromEntries(Object.entries(_SII_NOMBRE_A_KEY).map(([n, k]) => [k, n]));
660
+
661
+ // Busca el entry de _ss para un nombre SII (COMPRAS sin EXENTOS, etc.)
662
+ const _findEntry = (ss, nombre) => Object.entries(ss).find(([k]) => {
663
+ const ku = k.toUpperCase();
664
+ if (nombre === 'LIBRO DE COMPRAS') return ku.includes('LIBRO DE COMPRAS') && !ku.includes('EXENTOS');
665
+ return ku.includes(nombre);
666
+ });
667
+
668
+ // Retorna claves internas que están en S21 en ss, de entre los especificados
669
+ const _getS21Keys = (ss, nombres) =>
670
+ nombres
671
+ .filter(n => { const e = _findEntry(ss, n); return e && e[1] === 'S21'; })
672
+ .map(n => _SII_NOMBRE_A_KEY[n])
673
+ .filter(Boolean);
674
+
675
+ // Helper para re-enviar los libros no conformes con un nuevo período
676
+ // keysAReenviar: Set opcional — si se pasa, solo re-envía las claves del Set
677
+ const _reenviarLibros = async (nuevoPeriodo, keysAReenviar) => {
678
+ const _orden = [
679
+ { key: 'libroCompras', fn: (p) => this.ejecutarLibroCompras({ ...options, periodo: p }) },
680
+ { key: 'libroVentas', fn: (p) => this.ejecutarLibroVentas({ ...options, periodo: p }) },
681
+ { key: 'libroGuias', fn: (p) => this.ejecutarLibroGuias({ ...options, periodo: p }) },
682
+ { key: 'libroComprasExentos', fn: (p) => this.ejecutarLibroComprasExentos({ ...options, periodo: p }) },
683
+ ];
684
+ for (const { key, fn } of _orden) {
685
+ if (resultados[key]?.conforme) continue; // ya conforme en SII
686
+ if (keysAReenviar && !keysAReenviar.has(key)) continue; // filtro por S21
687
+ emitProgress(STEPS.BOOK_SENDING, { book: key });
688
+ try {
689
+ resultados[key] = await fn(nuevoPeriodo);
690
+ if (!resultados[key].success) {
691
+ emitProgress(STEPS.BOOK_ERROR, { book: key, error: resultados[key].error });
692
+ } else {
693
+ emitProgress(STEPS.BOOK_OK, { book: key, trackId: resultados[key].trackId });
694
+ }
695
+ } catch (e) {
696
+ resultados[key] = { success: false, error: e.message };
697
+ }
698
+ }
699
+ };
700
+
701
+ // Polling de aprobación (reutilizable)
702
+ // librosAVerificar: array de nombres SII a esperar. Si se omite, usa todos los no-conformes.
703
+ // Devuelve { ok, estadosFinal }
704
+ // Bail-out anticipado: si todos los pendientes llevan 5 polls consecutivos en S21 → período incorrecto
705
+ const _esperarAprobacion = async (librosAVerificar) => {
706
+ const _todosCandidatos = ['LIBRO DE VENTAS', 'LIBRO DE COMPRAS', 'LIBRO DE GUIAS'];
707
+ if (this._estructuras?.libroComprasExentos) _todosCandidatos.push('LIBRO DE COMPRAS PARA EXENTOS');
708
+ const _librosAVerif = librosAVerificar || _todosCandidatos.filter(n => !_estaConforme(n));
709
+ console.log(`\nEsperando aprobacion del SII para: ${_librosAVerif.join(', ')}`);
710
+ let _ss = {};
711
+ let _consecutivosS21 = 0;
712
+ for (let _i = 0; _i < 20; _i++) {
713
+ await sleep(15000);
714
+ emitProgress(STEPS.POLLING, { intento: _i + 1, max: 20, label: 'libros' });
715
+ const _poll = await this.siiCert.consultarEstadoSets();
716
+ if (!_poll.success) continue;
717
+ _ss = _poll.estadoSets || {};
718
+ const _info = Object.entries(_ss).filter(([k]) => k.toUpperCase().includes('LIBRO')).map(([k, v]) => `${k.trim()}: ${v}`);
719
+ if (_info.length) console.log(` [...] Intento ${_i + 1}/20: ${_info.join(' | ')}`);
720
+ const _librosObs = ['LIBRO DE VENTAS', 'LIBRO DE COMPRAS', 'LIBRO DE GUIAS'];
721
+ const _todosObligatoriosOk = _librosObs.every(n => {
722
+ const e = _findEntry(_ss, n);
723
+ return e && (e[1] === 'REVISADO CONFORME' || e[1] === 'S25');
724
+ });
725
+ const _todosOk = _librosAVerif.every(n => {
726
+ const e = _findEntry(_ss, n);
727
+ return e && (e[1] === 'REVISADO CONFORME' || e[1] === 'S25');
728
+ });
729
+ const _algunError = _librosAVerif.some(n => {
730
+ const e = _findEntry(_ss, n);
731
+ return e && (e[1] === 'LNC' || e[1] === 'LRH' || e[1].includes('RECHAZADO') || e[1].includes('ERROR'));
732
+ });
733
+ if (_todosOk) {
734
+ emitProgress(STEPS.BOOKS_DONE);
735
+ console.log('\n[OK] LIBROS APROBADOS POR EL SII!');
736
+ return { ok: true, estadosFinal: _ss };
737
+ }
738
+ if (_algunError) {
739
+ console.log('\n[ERR] Hay libros rechazados. Revisar emails del SII.');
740
+ return { ok: false, estadosFinal: _ss };
741
+ }
742
+ // Bail-out anticipado: todos los pendientes llevan N polls en S21 → período incorrecto
743
+ const _pendientesAun = _librosAVerif.filter(n => {
744
+ const e = _findEntry(_ss, n);
745
+ return !e || (e[1] !== 'REVISADO CONFORME' && e[1] !== 'S25');
746
+ });
747
+ const _todosS21 = _pendientesAun.length > 0 && _pendientesAun.every(n => {
748
+ const e = _findEntry(_ss, n);
749
+ return e && e[1] === 'S21';
750
+ });
751
+ if (_todosS21) {
752
+ _consecutivosS21++;
753
+ if (_consecutivosS21 >= 5) {
754
+ console.log(`\n[!] ${_pendientesAun.join(', ')} llevan ${_consecutivosS21} polls en S21 — período incorrecto.`);
755
+ return { ok: false, estadosFinal: _ss, stuckS21: true };
756
+ }
757
+ } else {
758
+ _consecutivosS21 = 0;
759
+ }
760
+ }
761
+ console.log('\n[!] Timeout (5 min). El SII aún no responde. Verifica con --avance más tarde.');
762
+ return { ok: false, estadosFinal: _ss };
763
+ };
764
+
765
+ // 4. Declarar + retry automático:
766
+ // a) si allRejected al declarar → decrementar período y re-enviar todos
767
+ // b) si libros quedan en S21 tras polling → decrementar y re-enviar solo los S21
768
+ emitProgress(STEPS.BOOKS_DECLARING);
769
+ console.log('\nDeclarando libros...');
770
+ const MAX_PERIOD_RETRIES = 120;
527
771
  try {
528
- const declaracion = await this.declararLibros();
772
+ let declaracion = await this.declararLibros({ ...resultados, ...(options.setsResultados || {}) });
529
773
  resultados.declaracion = declaracion;
530
-
774
+
775
+ // Fase a: allRejected al declarar (período rechazado en pe_avance3)
776
+ for (let _pRetry = 0; _pRetry < MAX_PERIOD_RETRIES && declaracion.allRejected; _pRetry++) {
777
+ this._decrementarPeriodoLibros();
778
+ const _nuevoPeriodo = this._getPeriodoLibros();
779
+ console.log(`\n[!] Período rechazado por SII. Reintentando con ${_nuevoPeriodo} (${_pRetry + 1}/${MAX_PERIOD_RETRIES})...`);
780
+ await _reenviarLibros(_nuevoPeriodo);
781
+ declaracion = await this.declararLibros({ ...resultados, ...(options.setsResultados || {}) });
782
+ resultados.declaracion = declaracion;
783
+ }
784
+
531
785
  if (declaracion.success) {
532
- console.log('\n FASE 4 COMPLETADA: Todos los libros enviados y declarados');
786
+ console.log('\n[OK] Libros declarados esperando revisión del SII...');
787
+
788
+ // Construir la lista inicial de libros a verificar
789
+ const _todosLibrosNombres = ['LIBRO DE VENTAS', 'LIBRO DE COMPRAS', 'LIBRO DE GUIAS'];
790
+ if (this._estructuras?.libroComprasExentos) _todosLibrosNombres.push('LIBRO DE COMPRAS PARA EXENTOS');
791
+ let _librosAVerificar = _todosLibrosNombres.filter(n => !_estaConforme(n));
792
+
793
+ let { ok, estadosFinal } = await _esperarAprobacion(_librosAVerificar);
794
+
795
+ // Fase b: algunos libros quedaron en S21 → re-enviar solo esos con período decrementado
796
+ for (let _pRetry = 0; !ok && _pRetry < MAX_PERIOD_RETRIES; _pRetry++) {
797
+ const _s21Keys = _getS21Keys(estadosFinal, _librosAVerificar);
798
+ if (_s21Keys.length === 0) break; // errores reales (LNC/LRH), no de período
799
+
800
+ this._decrementarPeriodoLibros();
801
+ const _nuevoPeriodo = this._getPeriodoLibros();
802
+ const _s21Nombres = _s21Keys.map(k => _KEY_A_SII_NOMBRE[k]).filter(Boolean);
803
+ console.log(`\n[!] ${_s21Nombres.join(', ')} bloqueados en S21. Reintentando con período ${_nuevoPeriodo} (${_pRetry + 1}/${MAX_PERIOD_RETRIES})...`);
804
+
805
+ await _reenviarLibros(_nuevoPeriodo, new Set(_s21Keys));
806
+ declaracion = await this.declararLibros({ ...resultados, ...(options.setsResultados || {}) });
807
+ resultados.declaracion = declaracion;
808
+
809
+ if (!declaracion.success && !declaracion.allRejected) {
810
+ console.log(`\n[ERR] Declaración fallida: ${declaracion.error}`);
811
+ break;
812
+ }
813
+ if (declaracion.success) {
814
+ // Solo verificar los libros que acabamos de re-enviar
815
+ _librosAVerificar = _s21Nombres;
816
+ ;({ ok, estadosFinal } = await _esperarAprobacion(_librosAVerificar));
817
+ }
818
+ // si allRejected → continuar loop (decrementar de nuevo)
819
+ }
820
+
821
+ if (!ok) {
822
+ console.log('\n[!] No se pudo obtener aprobación del SII para todos los libros.');
823
+ }
533
824
  } else {
534
- console.log(`\n⚠️ Libros enviados pero declaración con error: ${declaracion.error}`);
825
+ const _errorDeclaracion = declaracion.error || 'Declaración rechazada por SII';
826
+ console.log(`\n[ERR] Declaración de libros fallida: ${_errorDeclaracion}`);
827
+ for (const k of ['libroVentas', 'libroCompras', 'libroGuias', 'libroComprasExentos']) {
828
+ if (resultados[k]?.success && !resultados[k]?.conforme) {
829
+ resultados[k] = { ...resultados[k], success: false, error: _errorDeclaracion };
830
+ }
831
+ }
535
832
  }
536
833
  } catch (e) {
537
- console.log(`\n⚠️ Error declarando libros: ${e.message}`);
834
+ console.log(`\n[!] Error declarando libros: ${e.message}`);
538
835
  resultados.declaracion = { success: false, error: e.message };
539
836
  }
540
837
  } else {
541
- console.log(`\n⚠️ Solo ${librosEnviados}/3 libros enviados. Errores: ${errores.join('; ')}`);
838
+ console.log(`\n[!] Solo ${librosEnviados}/3 libros enviados. Errores: ${errores.join('; ')}`);
542
839
  }
543
840
 
544
841
  return {
@@ -563,6 +860,12 @@ class CertRunner {
563
860
  const sets = {};
564
861
 
565
862
  const mapping = {
863
+ // Sets — incluirlos para que pe_avance3 no los resetee a S01
864
+ basico: 'setBasico',
865
+ guia: 'setGuiaDespacho',
866
+ exenta: 'setFacturaExenta',
867
+ compra: 'setFacturaCompra',
868
+ // Libros
566
869
  libroVentas: 'libroVentas',
567
870
  libroCompras: 'libroCompras',
568
871
  libroGuias: 'libroGuias',
@@ -582,7 +885,7 @@ class CertRunner {
582
885
  const result = await this._declararConReintentos(sets, 'declaracion-libros-response', { maxIntentos, intervalo, label: 'libros' });
583
886
  if (result?.success) {
584
887
  const declarados = result.setsDeclarados || [];
585
- console.log(` Libros declarados: ${declarados.join(', ')}`);
888
+ console.log(` [OK] Libros declarados: ${declarados.join(', ')}`);
586
889
  }
587
890
  return result;
588
891
  }
@@ -650,9 +953,9 @@ class CertRunner {
650
953
 
651
954
  try {
652
955
  fs.writeFileSync(stateFile, JSON.stringify(state, null, 2));
653
- console.log(` 📅 Período decrementado: ${currentPeriodo} → ${newPeriodo}`);
956
+ console.log(` Período decrementado: ${currentPeriodo} → ${newPeriodo}`);
654
957
  } catch (e) {
655
- console.warn(` ⚠️ No se pudo guardar período: ${e.message}`);
958
+ console.warn(` [!] No se pudo guardar período: ${e.message}`);
656
959
  }
657
960
 
658
961
  return newPeriodo;
@@ -668,9 +971,9 @@ class CertRunner {
668
971
 
669
972
  try {
670
973
  fs.writeFileSync(stateFile, JSON.stringify(state, null, 2));
671
- console.log(` 📅 Período reseteado a: ${periodo}`);
974
+ console.log(` Período reseteado a: ${periodo}`);
672
975
  } catch (e) {
673
- console.warn(` ⚠️ No se pudo guardar período: ${e.message}`);
976
+ console.warn(` [!] No se pudo guardar período: ${e.message}`);
674
977
  }
675
978
  }
676
979
 
@@ -696,10 +999,9 @@ class CertRunner {
696
999
  throw new Error('No hay resultado del SetBasico. Ejecutar ejecutarSetBasico() primero.');
697
1000
  }
698
1001
 
699
- // IMPORTANTE: Decrementar período ANTES de usar para evitar "LNC - Libro Cerrado"
700
- this._decrementarPeriodoLibros();
701
- const periodo = this._getPeriodoLibros();
702
- console.log(` 📚 Generando Libro de Ventas para período ${periodo}...`);
1002
+ // Usar período pasado por opción (fase4 lo decrementa una vez para todos) o decrementar individualmente
1003
+ const periodo = options.periodo || (this._decrementarPeriodoLibros(), this._getPeriodoLibros());
1004
+ console.log(` Generando Libro de Ventas para período ${periodo}...`);
703
1005
 
704
1006
  const libroVentas = new LibroVentas({
705
1007
  emisor: this.config.emisor,
@@ -714,7 +1016,6 @@ class CertRunner {
714
1016
  // Guardar XML de debug
715
1017
  const outPath = path.join(this.debugDir, 'libro-ventas.xml');
716
1018
  fs.writeFileSync(outPath, xml, 'utf-8');
717
- console.log(` XML guardado: ${outPath}`);
718
1019
 
719
1020
  // Enviar al SII
720
1021
  const enviador = this._createLibroEnviador();
@@ -731,9 +1032,9 @@ class CertRunner {
731
1032
  this.resultados.libroVentas = result;
732
1033
 
733
1034
  if (result.success) {
734
- console.log(` Libro de Ventas enviado - TrackId: ${result.trackId}`);
1035
+ console.log(` [OK] Libro de Ventas enviado - TrackId: ${result.trackId}`);
735
1036
  } else {
736
- console.log(` Error enviando Libro de Ventas: ${result.error}`);
1037
+ console.log(` [ERR] Error enviando Libro de Ventas: ${result.error}`);
737
1038
  }
738
1039
 
739
1040
  return result;
@@ -748,9 +1049,7 @@ class CertRunner {
748
1049
  async ejecutarLibroCompras(options = {}) {
749
1050
  const libroComprasData = options.libroComprasData || this._estructuras?.libroCompras;
750
1051
 
751
- // IMPORTANTE: Decrementar período ANTES de usar para evitar "LNC - Libro Cerrado"
752
- this._decrementarPeriodoLibros();
753
- const periodo = this._getPeriodoLibros();
1052
+ const periodo = options.periodo || (this._decrementarPeriodoLibros(), this._getPeriodoLibros());
754
1053
 
755
1054
  const libroCompras = new LibroCompras({
756
1055
  emisor: this.config.emisor,
@@ -762,13 +1061,12 @@ class CertRunner {
762
1061
  throw new Error('No hay datos del libro de compras. El SII no entregó el set LIBRO_COMPRAS al obtener las estructuras.');
763
1062
  }
764
1063
 
765
- console.log(` 📚 Generando Libro de Compras para período ${periodo} (${libroComprasData.detalle.length} documentos del SII)...`);
1064
+ console.log(` Generando Libro de Compras para período ${periodo} (${libroComprasData.detalle.length} documentos del SII)...`);
766
1065
  const { libro, xml, detalle, resumen } = libroCompras.generarDesdeEstructuras(libroComprasData, periodo);
767
1066
 
768
1067
  // Guardar XML de debug
769
1068
  const outPath = path.join(this.debugDir, 'libro-compras.xml');
770
1069
  fs.writeFileSync(outPath, xml, 'utf-8');
771
- console.log(` XML guardado: ${outPath}`);
772
1070
 
773
1071
  // Enviar al SII
774
1072
  const enviador = this._createLibroEnviador();
@@ -785,9 +1083,9 @@ class CertRunner {
785
1083
  this.resultados.libroCompras = result;
786
1084
 
787
1085
  if (result.success) {
788
- console.log(` Libro de Compras enviado - TrackId: ${result.trackId}`);
1086
+ console.log(` [OK] Libro de Compras enviado - TrackId: ${result.trackId}`);
789
1087
  } else {
790
- console.log(` Error enviando Libro de Compras: ${result.error}`);
1088
+ console.log(` [ERR] Error enviando Libro de Compras: ${result.error}`);
791
1089
  }
792
1090
 
793
1091
  return result;
@@ -804,8 +1102,7 @@ class CertRunner {
804
1102
  throw new Error('No hay datos del libro de compras para exentos. El SII no entregó el set LIBRO_COMPRAS_EXENTOS.');
805
1103
  }
806
1104
 
807
- this._decrementarPeriodoLibros();
808
- const periodo = this._getPeriodoLibros();
1105
+ const periodo = options.periodo || (this._decrementarPeriodoLibros(), this._getPeriodoLibros());
809
1106
 
810
1107
  const libroCompras = new LibroCompras({
811
1108
  emisor: this.config.emisor,
@@ -813,12 +1110,11 @@ class CertRunner {
813
1110
  certificado: this.certificado,
814
1111
  });
815
1112
 
816
- console.log(` 📚 Generando Libro de Compras para Exentos para período ${periodo} (${libroData.detalle.length} documentos del SII)...`);
1113
+ console.log(` Generando Libro de Compras para Exentos para período ${periodo} (${libroData.detalle.length} documentos del SII)...`);
817
1114
  const { libro, xml, detalle } = libroCompras.generarDesdeEstructuras(libroData, periodo);
818
1115
 
819
1116
  const outPath = path.join(this.debugDir, 'libro-compras-exentos.xml');
820
1117
  fs.writeFileSync(outPath, xml, 'utf-8');
821
- console.log(` XML guardado: ${outPath}`);
822
1118
 
823
1119
  const enviador = this._createLibroEnviador();
824
1120
  const resultado = await enviador.enviarLibro(libro, 'LibroCVExentos.xml');
@@ -834,9 +1130,9 @@ class CertRunner {
834
1130
  this.resultados.libroComprasExentos = result;
835
1131
 
836
1132
  if (result.success) {
837
- console.log(` Libro de Compras para Exentos enviado - TrackId: ${result.trackId}`);
1133
+ console.log(` [OK] Libro de Compras para Exentos enviado - TrackId: ${result.trackId}`);
838
1134
  } else {
839
- console.log(` Error enviando Libro de Compras para Exentos: ${result.error}`);
1135
+ console.log(` [ERR] Error enviando Libro de Compras para Exentos: ${result.error}`);
840
1136
  }
841
1137
 
842
1138
  return result;
@@ -856,10 +1152,8 @@ class CertRunner {
856
1152
  throw new Error('No hay resultado del SetGuia. Ejecutar ejecutarSetGuia() primero.');
857
1153
  }
858
1154
 
859
- // IMPORTANTE: Decrementar período ANTES de usar para evitar "LNC - Libro Cerrado"
860
- this._decrementarPeriodoLibros();
861
- const periodo = this._getPeriodoLibros();
862
- console.log(` 📚 Generando Libro de Guías para período ${periodo}...`);
1155
+ const periodo = options.periodo || (this._decrementarPeriodoLibros(), this._getPeriodoLibros());
1156
+ console.log(` Generando Libro de Guías para período ${periodo}...`);
863
1157
 
864
1158
  const libroGuias = new LibroGuias({
865
1159
  emisor: this.config.emisor,
@@ -876,7 +1170,6 @@ class CertRunner {
876
1170
  // Guardar XML de debug
877
1171
  const outPath = path.join(this.debugDir, 'libro-guias.xml');
878
1172
  fs.writeFileSync(outPath, xml, 'utf-8');
879
- console.log(` XML guardado: ${outPath}`);
880
1173
 
881
1174
  // Enviar al SII
882
1175
  const enviador = this._createLibroEnviador();
@@ -893,9 +1186,9 @@ class CertRunner {
893
1186
  this.resultados.libroGuias = result;
894
1187
 
895
1188
  if (result.success) {
896
- console.log(` Libro de Guías enviado - TrackId: ${result.trackId}`);
1189
+ console.log(` [OK] Libro de Guías enviado - TrackId: ${result.trackId}`);
897
1190
  } else {
898
- console.log(` Error enviando Libro de Guías: ${result.error}`);
1191
+ console.log(` [ERR] Error enviando Libro de Guías: ${result.error}`);
899
1192
  }
900
1193
 
901
1194
  return result;
@@ -911,11 +1204,11 @@ class CertRunner {
911
1204
  */
912
1205
  async avanzarSiguientePaso() {
913
1206
  console.log('\n' + '═'.repeat(60));
914
- console.log('🚀 AVANZAR SIGUIENTE PASO');
1207
+ console.log('AVANZAR SIGUIENTE PASO');
915
1208
  console.log('═'.repeat(60) + '\n');
916
1209
 
917
1210
  try {
918
- console.log(' 📋 Enviando solicitud de avance...');
1211
+ console.log(' Enviando solicitud de avance...');
919
1212
  const result = await this.siiCert.avanzarSiguientePaso();
920
1213
 
921
1214
  if (result.rawHtml) {
@@ -924,20 +1217,20 @@ class CertRunner {
924
1217
  result.rawHtml,
925
1218
  'utf8'
926
1219
  );
927
- console.log(` 📄 Respuesta guardada en: ${path.join(this.debugDir, 'avanzar-siguiente-paso-response.html')}`);
1220
+ console.log(` Respuesta guardada en: ${path.join(this.debugDir, 'avanzar-siguiente-paso-response.html')}`);
928
1221
  }
929
1222
 
930
1223
  if (result.success) {
931
- console.log(' Avance al siguiente paso exitoso');
1224
+ console.log(' [OK] Avance al siguiente paso exitoso');
932
1225
  this.resultados.avanceSiguientePaso = { success: true };
933
1226
  } else {
934
- console.log(` Error en avance: ${result.error || 'Error desconocido'}`);
1227
+ console.log(` [ERR] Error en avance: ${result.error || 'Error desconocido'}`);
935
1228
  this.resultados.avanceSiguientePaso = { success: false, error: result.error };
936
1229
  }
937
1230
 
938
1231
  return result;
939
1232
  } catch (error) {
940
- console.log(` Error: ${error.message}`);
1233
+ console.log(` [ERR] Error: ${error.message}`);
941
1234
  this.resultados.avanceSiguientePaso = { success: false, error: error.message };
942
1235
  return { success: false, error: error.message };
943
1236
  }
@@ -951,15 +1244,15 @@ class CertRunner {
951
1244
  async esperarLibrosYAvanzar(options = {}) {
952
1245
  const { maxIntentos = 30, intervalo = 10000 } = options;
953
1246
 
954
- console.log('\n Esperando aprobación de libros...');
1247
+ console.log('\n[...] Esperando aprobación de libros...');
955
1248
 
956
1249
  for (let i = 1; i <= maxIntentos; i++) {
957
- console.log(`\n 🔄 Intento ${i}/${maxIntentos}...`);
1250
+ console.log(`\n [...] Intento ${i}/${maxIntentos}...`);
958
1251
 
959
1252
  const avance = await this.siiCert.verAvanceParsed();
960
1253
 
961
1254
  if (!avance.success) {
962
- console.log(` ⚠️ Error consultando avance: ${avance.error}`);
1255
+ console.log(` [!] Error consultando avance: ${avance.error}`);
963
1256
  await sleep(intervalo);
964
1257
  continue;
965
1258
  }
@@ -979,30 +1272,30 @@ class CertRunner {
979
1272
  estado.estado?.toUpperCase().includes('REPARO');
980
1273
 
981
1274
  if (esAprobado) {
982
- console.log(` ${libro}: REVISADO CONFORME`);
1275
+ console.log(` [OK] ${libro}: REVISADO CONFORME`);
983
1276
  } else if (esRechazado) {
984
- console.log(` ${libro}: ${estado.estado}`);
1277
+ console.log(` [ERR] ${libro}: ${estado.estado}`);
985
1278
  hayRechazados = true;
986
1279
  } else {
987
- console.log(` 🔄 ${libro}: ${estado.estado || 'EN REVISION'}`);
1280
+ console.log(` [...] ${libro}: ${estado.estado || 'EN REVISION'}`);
988
1281
  todosAprobados = false;
989
1282
  }
990
1283
  }
991
1284
 
992
1285
  if (hayRechazados) {
993
- console.log('\n Hay libros rechazados. No se puede avanzar.');
1286
+ console.log('\n [ERR] Hay libros rechazados. No se puede avanzar.');
994
1287
  return { success: false, error: 'Hay libros rechazados' };
995
1288
  }
996
1289
 
997
1290
  if (todosAprobados) {
998
- console.log('\n 🎉 ¡Todos los libros aprobados!');
1291
+ console.log('\n ¡Todos los libros aprobados!');
999
1292
  return await this.avanzarSiguientePaso();
1000
1293
  }
1001
1294
 
1002
1295
  await sleep(intervalo);
1003
1296
  }
1004
1297
 
1005
- console.log('\n ⚠️ Timeout esperando aprobación de libros');
1298
+ console.log('\n [!] Timeout esperando aprobación de libros');
1006
1299
  return { success: false, error: 'Timeout esperando aprobación' };
1007
1300
  }
1008
1301
 
@@ -1024,12 +1317,12 @@ class CertRunner {
1024
1317
  }
1025
1318
 
1026
1319
  console.log('\n' + '═'.repeat(60));
1027
- console.log('🧪 FASE 6: SIMULACIÓN');
1320
+ console.log('FASE 6: SIMULACIÓN');
1028
1321
  console.log('═'.repeat(60) + '\n');
1029
1322
 
1030
1323
  // Calcular CAFs necesarios
1031
1324
  const cafRequired = this._calcularCafsSimulacion(estructuras);
1032
- console.log(' Solicitando CAFs para simulación...');
1325
+ console.log(' Solicitando CAFs para simulación...');
1033
1326
 
1034
1327
  // Solicitar CAFs frescos
1035
1328
  const cafs = await this.solicitarCafs(cafRequired);
@@ -1054,22 +1347,21 @@ class CertRunner {
1054
1347
  });
1055
1348
 
1056
1349
  // Generar
1057
- console.log(' Generando DTEs de simulación...');
1350
+ console.log(' Generando DTEs de simulación...');
1058
1351
  const { envioDte, dtes, xml, plan, tiposUsados } = simulacion.generar(
1059
1352
  estructuras,
1060
1353
  cafObjects,
1061
1354
  this.folioHelper,
1062
1355
  );
1063
1356
 
1064
- console.log(` 📦 Plan de simulación: ${plan.length} documentos`);
1065
- console.log(` 📄 Tipos usados: ${tiposUsados.join(', ')}`);
1357
+ console.log(` Plan de simulación: ${plan.length} documentos`);
1358
+ console.log(` Tipos usados: ${tiposUsados.join(', ')}`);
1066
1359
 
1067
1360
  // Guardar XML de debug
1068
1361
  const runDir = path.join(this.debugDir, 'simulacion');
1069
1362
  fs.mkdirSync(runDir, { recursive: true });
1070
1363
  const outPath = path.join(runDir, 'envio-simulacion.xml');
1071
1364
  fs.writeFileSync(outPath, xml, 'utf-8');
1072
- console.log(` XML guardado: ${outPath}`);
1073
1365
 
1074
1366
  // Guardar DTEs individuales
1075
1367
  const dtesDir = path.join(runDir, 'dtes');
@@ -1080,7 +1372,7 @@ class CertRunner {
1080
1372
  });
1081
1373
 
1082
1374
  // Enviar al SII
1083
- console.log('\n 📤 Enviando al SII...');
1375
+ console.log('\n Enviando al SII...');
1084
1376
  const enviador = this._createEnviador();
1085
1377
  const resultado = await enviador.enviar(envioDte);
1086
1378
 
@@ -1102,9 +1394,9 @@ class CertRunner {
1102
1394
  }, null, 2), 'utf8');
1103
1395
 
1104
1396
  if (result.success) {
1105
- console.log(`\n Simulación enviada - TrackId: ${result.trackId}`);
1397
+ console.log(`\n[OK] Simulación enviada - TrackId: ${result.trackId}`);
1106
1398
  } else {
1107
- console.log(`\n Error en simulación: ${result.error}`);
1399
+ console.log(`\n[ERR] Error en simulación: ${result.error}`);
1108
1400
  }
1109
1401
 
1110
1402
  return result;
@@ -1169,10 +1461,10 @@ class CertRunner {
1169
1461
  }
1170
1462
 
1171
1463
  // Verificar si ya pasamos a INTERCAMBIO (simulación ya aprobada)
1172
- console.log(' 🔍 Verificando etapa actual...');
1464
+ console.log(' Verificando etapa actual...');
1173
1465
  const avance = await this.siiCert.verAvanceParsed();
1174
1466
  if (avance.rawHtml && /paso\s*<b>\s*INTERCAMBIO/i.test(avance.rawHtml)) {
1175
- console.log(' Simulación ya aprobada - empresa en etapa INTERCAMBIO');
1467
+ console.log(' [OK] Simulación ya aprobada - empresa en etapa INTERCAMBIO');
1176
1468
  return { success: true, skipped: true, message: 'Ya en etapa INTERCAMBIO' };
1177
1469
  }
1178
1470
 
@@ -1185,7 +1477,7 @@ class CertRunner {
1185
1477
  };
1186
1478
 
1187
1479
  const result = await this._declararConReintentos(sets, 'declaracion-simulacion-response', { maxIntentos, intervalo, label: 'simulación' });
1188
- if (result?.success) console.log(' Simulación declarada exitosamente');
1480
+ if (result?.success) console.log(' [OK] Simulación declarada exitosamente');
1189
1481
  return result;
1190
1482
  }
1191
1483
 
@@ -1197,41 +1489,41 @@ class CertRunner {
1197
1489
  async esperarSimulacionAprobada(options = {}) {
1198
1490
  const { maxIntentos = 30, intervalo = 10000 } = options;
1199
1491
 
1200
- console.log('\n Esperando aprobación de simulación...');
1492
+ console.log('\n[...] Esperando aprobación de simulación...');
1201
1493
 
1202
1494
  for (let i = 1; i <= maxIntentos; i++) {
1203
- console.log(`\n 🔄 Intento ${i}/${maxIntentos}...`);
1495
+ console.log(`\n [...] Intento ${i}/${maxIntentos}...`);
1204
1496
 
1205
1497
  const avance = await this.siiCert.verAvanceParsed();
1206
1498
 
1207
1499
  if (!avance.success) {
1208
- console.log(` ⚠️ Error consultando avance: ${avance.error}`);
1500
+ console.log(` [!] Error consultando avance: ${avance.error}`);
1209
1501
  await sleep(intervalo);
1210
1502
  continue;
1211
1503
  }
1212
1504
 
1213
- // PRIMERO: Verificar si ya pasó a INTERCAMBIO (significa que simulación fue aprobada)
1505
+ // [OK] PRIMERO: Verificar si ya pasó a INTERCAMBIO (significa que simulación fue aprobada)
1214
1506
  if (avance.etapaActual && avance.etapaActual.includes('INTERCAMBIO')) {
1215
- console.log(` Etapa actual: ${avance.etapaActual}`);
1216
- console.log('\n 🎉 ¡SIMULACIÓN APROBADA! Empresa pasó a etapa INTERCAMBIO.');
1507
+ console.log(` [OK] Etapa actual: ${avance.etapaActual}`);
1508
+ console.log('\n ¡SIMULACIÓN APROBADA! Empresa pasó a etapa INTERCAMBIO.');
1217
1509
  return { success: true, etapa: 'INTERCAMBIO' };
1218
1510
  }
1219
1511
 
1220
- // TAMBIÉN: Etapas que vienen DESPUÉS de INTERCAMBIO (simulación + intercambio ya completos)
1512
+ // [OK] TAMBIÉN: Etapas que vienen DESPUÉS de INTERCAMBIO (simulación + intercambio ya completos)
1221
1513
  const ETAPAS_POST_INTERCAMBIO = ['DOCUMENTOS IMPRESOS', 'MUESTRAS IMPRESAS', 'BOLETA', 'AUTORIZADO', 'COMPLETADO'];
1222
1514
  if (avance.etapaActual && ETAPAS_POST_INTERCAMBIO.some(e => avance.etapaActual.toUpperCase().includes(e))) {
1223
- console.log(` 📍 Etapa actual: ${avance.etapaActual}`);
1224
- console.log('\n 🎉 ¡SIMULACIÓN + INTERCAMBIO COMPLETADOS! Empresa en etapa: ' + avance.etapaActual);
1515
+ console.log(` Etapa actual: ${avance.etapaActual}`);
1516
+ console.log('\n ¡SIMULACIÓN + INTERCAMBIO COMPLETADOS! Empresa en etapa: ' + avance.etapaActual);
1225
1517
  return { success: true, etapa: avance.etapaActual, postIntercambio: true };
1226
1518
  }
1227
1519
 
1228
- // SEGUNDO: Verificar indicador de formulario de confirmación (simulación aprobada pendiente confirmar)
1520
+ // [OK] SEGUNDO: Verificar indicador de formulario de confirmación (simulación aprobada pendiente confirmar)
1229
1521
  if (avance.simulacionAprobadaIndicador) {
1230
- console.log(` Formulario de confirmación detectado`);
1522
+ console.log(` [OK] Formulario de confirmación detectado`);
1231
1523
 
1232
1524
  // Confirmar automáticamente la simulación
1233
1525
  if (this.resultados.simulacion?.trackId) {
1234
- console.log(`\n 📝 Confirmando revisión de simulación (TrackId: ${this.resultados.simulacion.trackId})...`);
1526
+ console.log(`\n Confirmando revisión de simulación (TrackId: ${this.resultados.simulacion.trackId})...`);
1235
1527
 
1236
1528
  const fecha = this._getFechaHoy();
1237
1529
  const confirmResult = await this.siiCert.declararAvance({
@@ -1244,7 +1536,7 @@ class CertRunner {
1244
1536
  });
1245
1537
 
1246
1538
  if (confirmResult.success) {
1247
- console.log(' Confirmación enviada exitosamente');
1539
+ console.log(' [OK] Confirmación enviada exitosamente');
1248
1540
 
1249
1541
  // Revalidar contra SII para evitar falso positivo de confirmación
1250
1542
  const verificacion = await this.siiCert.verAvanceParsed();
@@ -1254,19 +1546,19 @@ class CertRunner {
1254
1546
  const simConforme = Boolean(estadoSim?.esConforme || estadoSim?.estado?.toUpperCase()?.includes('REVISADO CONFORME'));
1255
1547
 
1256
1548
  if (yaIntercambio || simConforme || !sigueFormulario) {
1257
- console.log('\n 🎉 ¡SIMULACIÓN CONFIRMADA! Certificación completa.');
1549
+ console.log('\n ¡SIMULACIÓN CONFIRMADA! Certificación completa.');
1258
1550
  return { success: true, confirmada: true };
1259
1551
  }
1260
1552
 
1261
- console.log(' ⚠️ SII aún mantiene formulario de simulación pendiente; se reintentará...');
1553
+ console.log(' [!] SII aún mantiene formulario de simulación pendiente; se reintentará...');
1262
1554
  await sleep(intervalo);
1263
1555
  continue;
1264
1556
  } else {
1265
- console.log(` ⚠️ Error en confirmación: ${confirmResult.error}`);
1557
+ console.log(` [!] Error en confirmación: ${confirmResult.error}`);
1266
1558
  // Continuar el loop para reintentar
1267
1559
  }
1268
1560
  } else {
1269
- console.log('\n 🎉 ¡SIMULACIÓN APROBADA! Lista para confirmar revisión.');
1561
+ console.log('\n ¡SIMULACIÓN APROBADA! Lista para confirmar revisión.');
1270
1562
  return { success: true, pendienteConfirmar: true };
1271
1563
  }
1272
1564
  }
@@ -1287,28 +1579,28 @@ class CertRunner {
1287
1579
  simEstado.estado?.toUpperCase().includes('REPARO');
1288
1580
 
1289
1581
  if (esAprobado) {
1290
- console.log(` SIMULACIÓN: REVISADO CONFORME`);
1291
- console.log('\n 🎉 ¡SIMULACIÓN APROBADA! Certificación completa.');
1582
+ console.log(` [OK] SIMULACIÓN: REVISADO CONFORME`);
1583
+ console.log('\n ¡SIMULACIÓN APROBADA! Certificación completa.');
1292
1584
  return { success: true };
1293
1585
  } else if (esRechazado) {
1294
- console.log(` SIMULACIÓN: ${simEstado.estado}`);
1586
+ console.log(` [ERR] SIMULACIÓN: ${simEstado.estado}`);
1295
1587
  return { success: false, error: 'Simulación rechazada' };
1296
1588
  } else {
1297
- console.log(` 🔄 SIMULACIÓN: ${simEstado.estado || 'EN REVISION'}`);
1589
+ console.log(` [...] SIMULACIÓN: ${simEstado.estado || 'EN REVISION'}`);
1298
1590
  }
1299
1591
  } else {
1300
1592
  // No hay estado de simulación, pero verificar etapa actual
1301
1593
  if (avance.etapaActual) {
1302
- console.log(` 📍 Etapa actual: ${avance.etapaActual}`);
1594
+ console.log(` Etapa actual: ${avance.etapaActual}`);
1303
1595
  } else {
1304
- console.log(' Simulación aún no registrada...');
1596
+ console.log(' [...] Simulación aún no registrada...');
1305
1597
  }
1306
1598
  }
1307
1599
 
1308
1600
  await sleep(intervalo);
1309
1601
  }
1310
1602
 
1311
- console.log('\n ⚠️ Timeout esperando aprobación de simulación');
1603
+ console.log('\n [!] Timeout esperando aprobación de simulación');
1312
1604
  return { success: false, error: 'Timeout esperando aprobación' };
1313
1605
  }
1314
1606
 
@@ -1339,7 +1631,7 @@ class CertRunner {
1339
1631
  fs.mkdirSync(intercambioDir, { recursive: true });
1340
1632
 
1341
1633
  console.log('\n' + '═'.repeat(60));
1342
- console.log('📬 FASE 7: INTERCAMBIO DE INFORMACIÓN');
1634
+ console.log('FASE 7: INTERCAMBIO DE INFORMACIÓN');
1343
1635
  console.log('═'.repeat(60));
1344
1636
 
1345
1637
  // ── PASO 1: Obtener el SET XML ─────────────────────────────
@@ -1352,35 +1644,35 @@ class CertRunner {
1352
1644
  const setDownloadPath = path.join(intercambioDir, 'set-intercambio.xml');
1353
1645
 
1354
1646
  if (setInputPath && fs.existsSync(setInputPath)) {
1355
- console.log(`\n📂 Leyendo SET desde: ${setInputPath}`);
1647
+ console.log(`\nLeyendo SET desde: ${setInputPath}`);
1356
1648
  setXml = fs.readFileSync(setInputPath, 'utf8');
1357
- console.log(` ✓ ${setXml.length} bytes`);
1649
+ console.log(` ✓ ${setXml.length} bytes`);
1358
1650
  } else if (fs.existsSync(setDownloadPath)) {
1359
- console.log(`\n📂 Leyendo SET guardado: ${setDownloadPath}`);
1651
+ console.log(`\nLeyendo SET guardado: ${setDownloadPath}`);
1360
1652
  setXml = fs.readFileSync(setDownloadPath, 'utf8');
1361
- console.log(` ✓ ${setXml.length} bytes`);
1653
+ console.log(` ✓ ${setXml.length} bytes`);
1362
1654
  } else {
1363
- console.log('\n📡 Descargando SET desde www4.sii.cl/pfeInternet...');
1655
+ console.log('\nDescargando SET desde www4.sii.cl/pfeInternet...');
1364
1656
  const dl = await this._descargarSetPfeInternet(intercambioDir);
1365
1657
  if (dl.success) {
1366
1658
  setXml = dl.xml;
1367
1659
  fs.writeFileSync(setDownloadPath, setXml, 'utf8');
1368
- console.log(` SET descargado (${setXml.length} bytes) → ${setDownloadPath}`);
1660
+ console.log(` [OK] SET descargado (${setXml.length} bytes) → ${setDownloadPath}`);
1369
1661
  } else {
1370
- console.log(` ⚠️ No se pudo descargar: ${dl.error}`);
1662
+ console.log(` [!] No se pudo descargar: ${dl.error}`);
1371
1663
  console.log('\n' + '─'.repeat(60));
1372
- console.log('📋 DESCARGA MANUAL REQUERIDA:');
1373
- console.log(' 1. Si aparece error de sesiones: ingresa a https://www4.sii.cl/ → Cerrar Sesión');
1374
- console.log(' 2. Ir a: https://www4.sii.cl/pfeInternet/ y descargar el SET XML');
1375
- console.log(` 3. Guardarlo en: ${setDownloadPath}`);
1376
- console.log(' 4. Volver a ejecutar el runner');
1664
+ console.log('DESCARGA MANUAL REQUERIDA:');
1665
+ console.log(' 1. Si aparece error de sesiones: ingresa a https://www4.sii.cl/ → Cerrar Sesión');
1666
+ console.log(' 2. Ir a: https://www4.sii.cl/pfeInternet/ y descargar el SET XML');
1667
+ console.log(` 3. Guardarlo en: ${setDownloadPath}`);
1668
+ console.log(' 4. Volver a ejecutar el runner');
1377
1669
  console.log('─'.repeat(60));
1378
1670
  return { success: false, error: 'SET no disponible - descarga manual requerida', requiresManual: true, manualPath: setInputPath };
1379
1671
  }
1380
1672
  }
1381
1673
 
1382
1674
  // ── PASO 2: Generar XMLs de respuesta ─────────────────────
1383
- console.log('\n📝 Generando respuestas firmadas...');
1675
+ console.log('\nGenerando respuestas firmadas...');
1384
1676
  const intercambioCert = new IntercambioCert({
1385
1677
  certificado: this.certificado,
1386
1678
  emisor: {
@@ -1397,7 +1689,7 @@ class CertRunner {
1397
1689
  }
1398
1690
 
1399
1691
  // ── PASO 3: Subir respuestas ───────────────────────────────
1400
- console.log('\n📤 Subiendo respuestas a www4.sii.cl/pfeInternet...');
1692
+ console.log('\nSubiendo respuestas a www4.sii.cl/pfeInternet...');
1401
1693
  const uploadResult = await this._subirRespuestasPfeInternet({
1402
1694
  recepcionXml: fs.readFileSync(genResult.files.recepcion, 'utf8'),
1403
1695
  aprobacionXml: fs.readFileSync(genResult.files.aprobacion, 'utf8'),
@@ -1407,17 +1699,17 @@ class CertRunner {
1407
1699
 
1408
1700
  if (uploadResult.success) {
1409
1701
  console.log('\n' + '═'.repeat(60));
1410
- console.log(' INTERCAMBIO COMPLETADO');
1702
+ console.log('[OK] INTERCAMBIO COMPLETADO');
1411
1703
  console.log('═'.repeat(60));
1412
- if (uploadResult.resultado) console.log(` Resultado SII: ${uploadResult.resultado}`);
1704
+ if (uploadResult.resultado) console.log(` Resultado SII: ${uploadResult.resultado}`);
1413
1705
  } else {
1414
- console.log(` ⚠️ No se pudo subir automáticamente: ${uploadResult.error}`);
1706
+ console.log(` [!] No se pudo subir automáticamente: ${uploadResult.error}`);
1415
1707
  console.log('\n' + '─'.repeat(60));
1416
- console.log('📋 SUBIDA MANUAL REQUERIDA:');
1417
- console.log(' 1. Ir a: https://www4.sii.cl/pfeInternet/ → "Subir archivos"');
1418
- console.log(` 2. Subir: ${genResult.files.recepcion}`);
1419
- console.log(` 3. Subir: ${genResult.files.aprobacion}`);
1420
- console.log(` 4. Subir: ${genResult.files.recibos}`);
1708
+ console.log('SUBIDA MANUAL REQUERIDA:');
1709
+ console.log(' 1. Ir a: https://www4.sii.cl/pfeInternet/ → "Subir archivos"');
1710
+ console.log(` 2. Subir: ${genResult.files.recepcion}`);
1711
+ console.log(` 3. Subir: ${genResult.files.aprobacion}`);
1712
+ console.log(` 4. Subir: ${genResult.files.recibos}`);
1421
1713
  console.log('─'.repeat(60));
1422
1714
  }
1423
1715
 
@@ -1439,7 +1731,7 @@ class CertRunner {
1439
1731
  */
1440
1732
  async _obtenerCookiesSII() {
1441
1733
  if (this._siiCookieJar) {
1442
- console.log('[SII Auth] ♻️ Reutilizando sesión SII en memoria');
1734
+ console.log('[SII Auth] Reutilizando sesión SII en memoria');
1443
1735
  return this._siiCookieJar;
1444
1736
  }
1445
1737
  const SiiPortalAuth = require('../SiiPortalAuth');
@@ -1448,7 +1740,7 @@ class CertRunner {
1448
1740
  const siiAuth = new SiiPortalAuth({ pfxBuffer, pfxPassword: password });
1449
1741
  this._siiCookieJar = await siiAuth.autenticar();
1450
1742
  const nSession = Object.keys(this._siiCookieJar).filter(k => k.startsWith('NETSCAPE')).length;
1451
- console.log(`[SII Auth] Sesión SII activa (cookies NETSCAPE: ${nSession})`);
1743
+ console.log(`[SII Auth] [OK] Sesión SII activa (cookies NETSCAPE: ${nSession})`);
1452
1744
  return this._siiCookieJar;
1453
1745
  }
1454
1746
 
@@ -1547,7 +1839,7 @@ class CertRunner {
1547
1839
  const boundary = `----WebKitFormBoundary${Date.now()}`;
1548
1840
  const emptyMultipartBody = `--${boundary}--\r\n`;
1549
1841
 
1550
- console.log(` → Descargando SET desde pfeInternet/downloadFile (RUT ${rutNum}-${dv})...`);
1842
+ console.log(` → Descargando SET desde pfeInternet/downloadFile (RUT ${rutNum}-${dv})...`);
1551
1843
 
1552
1844
  const r = await makeReq(
1553
1845
  `https://www4.sii.cl/pfeInternet/downloadFile?re=${rutNum}&dve=${dv}`,
@@ -1571,12 +1863,12 @@ class CertRunner {
1571
1863
  r.body.includes('<SetDTE') ||
1572
1864
  r.body.includes('<?xml')
1573
1865
  )) {
1574
- console.log(` ✓ SET descargado correctamente (${r.body.length} bytes)`);
1866
+ console.log(` ✓ SET descargado correctamente (${r.body.length} bytes)`);
1575
1867
  return { success: true, xml: r.body };
1576
1868
  }
1577
1869
 
1578
1870
  const errMsg = `pfeInternet/downloadFile respondió HTTP ${r.status} sin XML válido`;
1579
- console.log(` ${errMsg}`);
1871
+ console.log(` [ERR] ${errMsg}`);
1580
1872
  fs.writeFileSync(path.join(debugDir, `pfe-download-error-${Date.now()}.html`), r.body, 'utf8');
1581
1873
  return { success: false, error: errMsg };
1582
1874
  } catch (err) {
@@ -1599,8 +1891,8 @@ class CertRunner {
1599
1891
  fs.mkdirSync(tmpDir, { recursive: true });
1600
1892
  // Los labels deben coincidir con el texto del portal GWT (Archivo N: ...)
1601
1893
  const archivos = [
1602
- { label: 'Respuesta de Intercambio', filename: 'respuesta-recepcion-envio.xml', content: recepcionXml, uploadN: 1 },
1603
- { label: 'Recibo de Mercaderias', filename: 'envio-recibos.xml', content: recibosXml, uploadN: 2 },
1894
+ { label: 'Respuesta de Intercambio', filename: 'respuesta-recepcion-envio.xml', content: recepcionXml, uploadN: 1 },
1895
+ { label: 'Recibo de Mercaderias', filename: 'envio-recibos.xml', content: recibosXml, uploadN: 2 },
1604
1896
  { label: 'Resultado Aprobaci\u00f3n Comercial de Documento', filename: 'respuesta-aprobacion-comercial.xml', content: aprobacionXml, uploadN: 3 },
1605
1897
  ];
1606
1898
  for (const a of archivos) {
@@ -1627,7 +1919,7 @@ class CertRunner {
1627
1919
  await page.setCookie(...puppeteerCookies);
1628
1920
 
1629
1921
  // Navegar al portal pfeInternet
1630
- console.log(' → Cargando portal pfeInternet...');
1922
+ console.log(' → Cargando portal pfeInternet...');
1631
1923
  await page.goto('https://www4.sii.cl/pfeInternet/', {
1632
1924
  waitUntil: 'networkidle2',
1633
1925
  timeout: 60000,
@@ -1638,7 +1930,7 @@ class CertRunner {
1638
1930
 
1639
1931
  // Hacer click en el enlace "Subir archivos XML de respuesta de Intercambio"
1640
1932
  // El href es javascript:openForm('opt-ingresoEmpresaUp') — necesita click real para GWT
1641
- console.log(' → Clickeando "Subir archivos XML de respuesta de Intercambio"...');
1933
+ console.log(' → Clickeando "Subir archivos XML de respuesta de Intercambio"...');
1642
1934
  const linkClicked = await page.click('a[href*="ingresoEmpresaUp"]').then(() => true).catch(() => false);
1643
1935
  if (!linkClicked) {
1644
1936
  // Fallback: evaluar click con dispatchEvent
@@ -1657,7 +1949,7 @@ class CertRunner {
1657
1949
  if (rutInput) {
1658
1950
  const [rutNum, dv] = this.config.emisor.rut.split('-');
1659
1951
  const rutConDv = `${rutNum}-${dv}`;
1660
- console.log(` → Ingresando RUT empresa: ${rutConDv}`);
1952
+ console.log(` → Ingresando RUT empresa: ${rutConDv}`);
1661
1953
  await rutInput.click({ clickCount: 3 }); // seleccionar todo
1662
1954
  await rutInput.type(rutConDv);
1663
1955
 
@@ -1668,7 +1960,7 @@ class CertRunner {
1668
1960
  });
1669
1961
  if (confirmBtn) {
1670
1962
  await confirmBtn.asElement().click();
1671
- console.log(' → Click "Confirmar Empresa", esperando formulario de upload...');
1963
+ console.log(' → Click "Confirmar Empresa", esperando formulario de upload...');
1672
1964
  await page.waitForNetworkIdle({ timeout: 15000, idleTime: 1000 }).catch(() => {});
1673
1965
  }
1674
1966
  }
@@ -1686,7 +1978,7 @@ class CertRunner {
1686
1978
  }
1687
1979
  throw new Error('pfeInternet no mostró formulario de upload tras openForm — ver pfeInternet-error.png/.html');
1688
1980
  }
1689
- console.log(' → Formulario de upload listo');
1981
+ console.log(' → Formulario de upload listo');
1690
1982
 
1691
1983
  // ── DEBUG: screenshot del formulario con los inputs listos ──
1692
1984
  if (debugDir) {
@@ -1722,11 +2014,11 @@ class CertRunner {
1722
2014
  }, archivo.label);
1723
2015
 
1724
2016
  if (yaProcessado) {
1725
- console.log(` → ${archivo.filename}: ya procesado anteriormente, saltando...`);
2017
+ console.log(` → ${archivo.filename}: ya procesado anteriormente, saltando...`);
1726
2018
  continue;
1727
2019
  }
1728
2020
 
1729
- console.log(` → Subiendo ${archivo.filename}...`);
2021
+ console.log(` → Subiendo ${archivo.filename}...`);
1730
2022
 
1731
2023
  // Cada archivo tiene su propio form con action uploadFile1/2/3
1732
2024
  // Usamos el selector específico para no confundir entre los 3 inputs que pueden
@@ -1745,7 +2037,7 @@ class CertRunner {
1745
2037
  // Esperar el diálogo GWT de confirmación
1746
2038
  await page.waitForSelector('.gwt-DialogBox .msgeDialogBox', { timeout: 30000 });
1747
2039
  const msgText = await page.$eval('.gwt-DialogBox .msgeDialogBox', el => el.textContent.trim());
1748
- console.log(` ✓ ${msgText}`);
2040
+ console.log(` ✓ ${msgText}`);
1749
2041
 
1750
2042
  if (debugDir) {
1751
2043
  fs.writeFileSync(
@@ -1841,16 +2133,16 @@ class CertRunner {
1841
2133
  const t = (document.body.textContent || '').toUpperCase();
1842
2134
  return t.includes('ESTADO DE LA REVISI') ||
1843
2135
  t.includes('POR REVISAR') || t.includes('APROBADO') ||
1844
- t.includes('EN REVISI') || t.includes('RECHAZADO');
2136
+ t.includes('EN REVISI') || t.includes('RECHAZADO');
1845
2137
  }, { timeout: 8000, polling: 500 }).catch(() => {});
1846
2138
 
1847
2139
  const estado = await page.evaluate(() => {
1848
2140
  const t = (document.body.textContent || '').toUpperCase();
1849
- if (t.includes('APROBADO')) return 'APROBADO';
1850
- if (t.includes('POR REVISAR')) return 'POR REVISAR';
1851
- if (t.includes('EN REVISI')) return 'EN REVISIÓN';
1852
- if (t.includes('RECHAZADO')) return 'RECHAZADO';
1853
- if (t.includes('ENVIADO AL SII')) return 'ENVIADO AL SII';
2141
+ if (t.includes('APROBADO')) return 'APROBADO';
2142
+ if (t.includes('POR REVISAR')) return 'POR REVISAR';
2143
+ if (t.includes('EN REVISI')) return 'EN REVISIÓN';
2144
+ if (t.includes('RECHAZADO')) return 'RECHAZADO';
2145
+ if (t.includes('ENVIADO AL SII')) return 'ENVIADO AL SII';
1854
2146
  return null;
1855
2147
  }).catch(() => null);
1856
2148
 
@@ -1881,7 +2173,7 @@ class CertRunner {
1881
2173
  if (!pdfPaths.length) throw new Error(`No se encontraron PDFs en: ${pdfDir}`);
1882
2174
 
1883
2175
  console.log('\n' + '═'.repeat(60));
1884
- console.log(`📄 FASE 8: MUESTRAS IMPRESAS (${pdfPaths.length} PDFs)`);
2176
+ console.log(`FASE 8: MUESTRAS IMPRESAS (${pdfPaths.length} PDFs)`);
1885
2177
  console.log('═'.repeat(60));
1886
2178
 
1887
2179
  return this._subirMuestrasImpresasPortal({ pdfPaths, debugDir: pdfDir });
@@ -1922,7 +2214,7 @@ class CertRunner {
1922
2214
  await page.setCookie(...puppeteerCookies);
1923
2215
 
1924
2216
  // Navegar directamente a www4.sii.cl/pdfdteInternet/ con las cookies de sesión SII
1925
- console.log(' → Cargando portal pdfdteInternet...');
2217
+ console.log(' → Cargando portal pdfdteInternet...');
1926
2218
  await page.goto('https://www4.sii.cl/pdfdteInternet/', {
1927
2219
  waitUntil: 'networkidle2', timeout: 60000,
1928
2220
  });
@@ -1939,7 +2231,7 @@ class CertRunner {
1939
2231
  }
1940
2232
  throw new Error('pdfdteInternet: no se encontraron campos de RUT (¿sesión expirada?)');
1941
2233
  }
1942
- console.log(` → Ingresando RUT empresa: ${rutNum}-${dvChar}`);
2234
+ console.log(` → Ingresando RUT empresa: ${rutNum}-${dvChar}`);
1943
2235
  await rutInputs[0].click({ clickCount: 3 }); await rutInputs[0].type(rutNum);
1944
2236
  await dvInputs[0].click({ clickCount: 3 }); await dvInputs[0].type(dvChar);
1945
2237
  await clickBoton(page, 'Rut');
@@ -1952,7 +2244,7 @@ class CertRunner {
1952
2244
  return !!(dlg && dlg.offsetParent !== null);
1953
2245
  });
1954
2246
  if (hayDialog) {
1955
- console.log(' → Diálogo de revisión existente → haciendo click en "Sí"');
2247
+ console.log(' → Diálogo de revisión existente → haciendo click en "Sí"');
1956
2248
  const clicked = await page.evaluate(() => {
1957
2249
  const si = Array.from(document.querySelectorAll('button.x-btn-text'))
1958
2250
  .find(b => /^s[ií]$/i.test(b.textContent.trim()));
@@ -1973,7 +2265,7 @@ class CertRunner {
1973
2265
  const dvNow = await page.$$('input[name="dv"]');
1974
2266
  const pRut = rutNow.length >= 2 ? rutNow[1] : rutNow[0];
1975
2267
  const pDv = dvNow.length >= 2 ? dvNow[1] : dvNow[0];
1976
- console.log(` → Ingresando RUT proveedor: ${rutNum}-${dvChar}`);
2268
+ console.log(` → Ingresando RUT proveedor: ${rutNum}-${dvChar}`);
1977
2269
  await pRut.click({ clickCount: 3 }); await pRut.type(rutNum);
1978
2270
  await pDv.click({ clickCount: 3 }); await pDv.type(dvChar);
1979
2271
  await clickBoton(page, 'Consultar');
@@ -1983,20 +2275,20 @@ class CertRunner {
1983
2275
  // ── Re-ejecución: detectar estado terminal antes de proceder ──
1984
2276
  const _estadoYaSubido = await page.evaluate(() => {
1985
2277
  const t = (document.body.textContent || '').toUpperCase();
1986
- if (t.includes('APROBADO')) return 'APROBADO';
1987
- if (t.includes('POR REVISAR')) return 'POR REVISAR';
1988
- if (t.includes('EN REVISI')) return 'EN REVISIÓN';
1989
- if (t.includes('RECHAZADO')) return 'RECHAZADO';
2278
+ if (t.includes('APROBADO')) return 'APROBADO';
2279
+ if (t.includes('POR REVISAR')) return 'POR REVISAR';
2280
+ if (t.includes('EN REVISI')) return 'EN REVISIÓN';
2281
+ if (t.includes('RECHAZADO')) return 'RECHAZADO';
1990
2282
  if (t.includes('ENVIADO AL SII')) return 'ENVIADO AL SII';
1991
2283
  return null;
1992
2284
  }).catch(() => null);
1993
2285
  if (_estadoYaSubido) {
1994
- console.log(` Portal ya muestra estado "${_estadoYaSubido}" — muestras subidas previamente. Proceso completado.`);
2286
+ console.log(` [OK] Portal ya muestra estado "${_estadoYaSubido}" — muestras subidas previamente. Proceso completado.`);
1995
2287
  return { success: true, alreadyCompleted: true, estado: _estadoYaSubido };
1996
2288
  }
1997
2289
 
1998
2290
  // Paso 4: "Crear" → habilita el input de archivo
1999
- console.log(' → Click "Crear"...');
2291
+ console.log(' → Click "Crear"...');
2000
2292
  await clickBoton(page, 'Crear');
2001
2293
  await new Promise(r => setTimeout(r, 2500));
2002
2294
  if (debugDir) await page.screenshot({ path: path.join(debugDir, 'pdfte-04-after-crear.png'), fullPage: true }).catch(() => {});
@@ -2056,7 +2348,7 @@ class CertRunner {
2056
2348
  // drop → por cada file: submit form al iframe → respuesta → leeImpresoById → tick verde
2057
2349
  // Esto evita la re-navegación entre archivos y garantiza que la validación
2058
2350
  // (Timbre/CAF/TED) ocurra antes de salir de la página.
2059
- console.log(` → Cargando ${pdfPaths.length} PDFs para drop en el portal...`);
2351
+ console.log(` → Cargando ${pdfPaths.length} PDFs para drop en el portal...`);
2060
2352
  const _fileDataList = pdfPaths.map(p => ({
2061
2353
  name: path.basename(p),
2062
2354
  b64: fs.readFileSync(p).toString('base64'),
@@ -2064,7 +2356,7 @@ class CertRunner {
2064
2356
 
2065
2357
  if (debugDir) await page.screenshot({ path: path.join(debugDir, 'pdfte-04b-antes-drop.png'), fullPage: true }).catch(() => {});
2066
2358
 
2067
- console.log(` → Ejecutando drop de ${pdfPaths.length} PDFs sobre el portal...`);
2359
+ console.log(` → Ejecutando drop de ${pdfPaths.length} PDFs sobre el portal...`);
2068
2360
  const _dropped = await page.evaluate((files) => {
2069
2361
  const dt = new DataTransfer();
2070
2362
  for (const f of files) {
@@ -2082,7 +2374,7 @@ class CertRunner {
2082
2374
  }, _fileDataList);
2083
2375
 
2084
2376
  if (_dropped === 0) throw new Error('pdfdteInternet: drop zone no encontrado (.dropFilesLabel)');
2085
- console.log(` → Drop ejecutado (${_dropped} archivos). Esperando procesamiento...`);
2377
+ console.log(` → Drop ejecutado (${_dropped} archivos). Esperando procesamiento...`);
2086
2378
 
2087
2379
  // ── Fase 1: esperar hasta 45s por primera señal de progreso o estado terminal ──
2088
2380
  // Si el portal ya está en "POR REVISAR" (re-ejecución), lo detectamos aquí inmediatamente.
@@ -2106,21 +2398,21 @@ class CertRunner {
2106
2398
  }
2107
2399
  const t = (document.body.textContent || '').toUpperCase();
2108
2400
  let estado = null;
2109
- if (t.includes('APROBADO')) estado = 'APROBADO';
2401
+ if (t.includes('APROBADO')) estado = 'APROBADO';
2110
2402
  else if (t.includes('POR REVISAR')) estado = 'POR REVISAR';
2111
- else if (t.includes('EN REVISI')) estado = 'EN REVISIÓN';
2112
- else if (t.includes('RECHAZADO')) estado = 'RECHAZADO';
2403
+ else if (t.includes('EN REVISI')) estado = 'EN REVISIÓN';
2404
+ else if (t.includes('RECHAZADO')) estado = 'RECHAZADO';
2113
2405
  return { procesados, estado };
2114
2406
  }).catch(() => ({ procesados: 0, estado: null }));
2115
2407
 
2116
2408
  if (_fase1.estado) {
2117
- console.log(` Portal en estado "${_fase1.estado}" — muestras ya procesadas previamente.`);
2409
+ console.log(` [OK] Portal en estado "${_fase1.estado}" — muestras ya procesadas previamente.`);
2118
2410
  return { success: true, alreadyCompleted: true, estado: _fase1.estado };
2119
2411
  }
2120
2412
 
2121
2413
  if (_fase1.procesados === 0) {
2122
2414
  // Sin progreso y sin estado terminal: el portal puede no estar procesando
2123
- console.warn(' Sin progreso en 45s y sin estado terminal. Continuando al paso siguiente...');
2415
+ console.warn(' [!] Sin progreso en 45s y sin estado terminal. Continuando al paso siguiente...');
2124
2416
  } else {
2125
2417
  // ── Fase 2: progreso iniciado — esperar al total ──
2126
2418
  await page.waitForFunction((total) => {
@@ -2137,7 +2429,7 @@ class CertRunner {
2137
2429
  }
2138
2430
  return 0;
2139
2431
  }).catch(() => 0);
2140
- console.warn(` Timeout: solo se procesaron ${procesados}/${pdfPaths.length} antes del timeout`);
2432
+ console.warn(` [!] Timeout: solo se procesaron ${procesados}/${pdfPaths.length} antes del timeout`);
2141
2433
  });
2142
2434
  }
2143
2435
 
@@ -2148,7 +2440,7 @@ class CertRunner {
2148
2440
  if (debugDir) await page.screenshot({ path: path.join(debugDir, 'pdfte-05-archivos-listos.png'), fullPage: true }).catch(() => {});
2149
2441
 
2150
2442
  // Paso 6: re-navegar al estado limpio y Enviar al SII
2151
- console.log(' → Re-navegando para "Enviar al SII"...');
2443
+ console.log(' → Re-navegando para "Enviar al SII"...');
2152
2444
  await navegarAlFormulario();
2153
2445
 
2154
2446
  // Esperar a que el botón esté habilitado (aria-disabled="false")
@@ -2160,7 +2452,7 @@ class CertRunner {
2160
2452
 
2161
2453
  if (debugDir) await page.screenshot({ path: path.join(debugDir, 'pdfte-05b-antes-enviar.png'), fullPage: true }).catch(() => {});
2162
2454
 
2163
- console.log(' → Click "Enviar al SII"...');
2455
+ console.log(' → Click "Enviar al SII"...');
2164
2456
  const enviado = await clickBoton(page, 'Enviar al SII');
2165
2457
  if (!enviado) throw new Error('pdfdteInternet: botón "Enviar al SII" no disponible o deshabilitado');
2166
2458
 
@@ -2173,7 +2465,7 @@ class CertRunner {
2173
2465
 
2174
2466
  const pageText = await page.$eval('body', el => el.textContent).catch(() => '');
2175
2467
  const exitoso = /revision.*creada|solicitud.*enviada|documentos.*enviados|fue.*enviado|[eé]xito/i.test(pageText);
2176
- console.log(` → Resultado: ${exitoso ? ' enviado correctamente' : '⚠️ sin confirmación explícita'}`);
2468
+ console.log(` → Resultado: ${exitoso ? '[OK] enviado correctamente' : '[!] sin confirmación explícita'}`);
2177
2469
  return { success: exitoso, pageText: pageText.substring(0, 800) };
2178
2470
 
2179
2471
  } catch (err) {
@@ -2216,9 +2508,9 @@ class CertRunner {
2216
2508
  });
2217
2509
  const page = await browser.newPage();
2218
2510
  await page.setCookie(...puppeteerCookies);
2219
- page.on('dialog', async dlg => { console.log(` → [dialog SET=1] ${dlg.message()}`); await dlg.accept(); });
2511
+ page.on('dialog', async dlg => { console.log(` → [dialog SET=1] ${dlg.message()}`); await dlg.accept(); });
2220
2512
 
2221
- console.log(' → Cargando portal certBolElectDteInternet (SET=1)...');
2513
+ console.log(' → Cargando portal certBolElectDteInternet (SET=1)...');
2222
2514
  await page.goto('https://www4.sii.cl/certBolElectDteInternet/?SET=1', {
2223
2515
  waitUntil: 'networkidle2', timeout: 60000,
2224
2516
  });
@@ -2228,7 +2520,7 @@ class CertRunner {
2228
2520
  const rutInput = await page.$('input[maxlength="8"]');
2229
2521
  const dvInput = await page.$('input[maxlength="1"]');
2230
2522
  if (!rutInput) throw new Error('certBolElectDteInternet/?SET=1: no se encontró campo RUT');
2231
- console.log(` → Ingresando RUT empresa: ${rutNum}-${dvChar}`);
2523
+ console.log(` → Ingresando RUT empresa: ${rutNum}-${dvChar}`);
2232
2524
  await rutInput.click({ clickCount: 3 }); await rutInput.type(rutNum);
2233
2525
  await dvInput.click({ clickCount: 3 }); await dvInput.type(dvChar);
2234
2526
  await page.evaluate(() => {
@@ -2236,7 +2528,7 @@ class CertRunner {
2236
2528
  .find(b => /confirmar/i.test(b.textContent));
2237
2529
  if (btn) btn.click();
2238
2530
  });
2239
- console.log(' → Click "Confirmar Empresa"');
2531
+ console.log(' → Click "Confirmar Empresa"');
2240
2532
 
2241
2533
  // Esperar checkboxes — GWT dispara ~8-10 POST /facade en paralelo
2242
2534
  await page.waitForNetworkIdle({ idleTime: 800, timeout: 40000 }).catch(() => {});
@@ -2251,7 +2543,7 @@ class CertRunner {
2251
2543
  cbs.forEach(cb => { if (!cb.checked) cb.click(); });
2252
2544
  return cbs.length;
2253
2545
  });
2254
- console.log(` → ${nCbs} checkbox(es) marcados`);
2546
+ console.log(` → ${nCbs} checkbox(es) marcados`);
2255
2547
  await new Promise(r => setTimeout(r, 300));
2256
2548
 
2257
2549
  // Paso 3: Rellenar correo proveedor
@@ -2266,9 +2558,9 @@ class CertRunner {
2266
2558
  if (emailInput) {
2267
2559
  await emailInput.click({ clickCount: 3 });
2268
2560
  await emailInput.type(correoSet);
2269
- console.log(` → Correo proveedor: ${correoSet}`);
2561
+ console.log(` → Correo proveedor: ${correoSet}`);
2270
2562
  } else {
2271
- console.log(' ⚠️ No se encontró campo de correo — continuando sin él');
2563
+ console.log(' [!] No se encontró campo de correo — continuando sin él');
2272
2564
  }
2273
2565
 
2274
2566
  // Paso 4: Click "Bajar Nuevo Set" — esperar POST /facade (GWT RPC) y luego
@@ -2278,7 +2570,7 @@ class CertRunner {
2278
2570
  // donde rutRepre/dvRepre vienen de las cookies NETSCAPE_LIVEWIRE.rut / .dv
2279
2571
 
2280
2572
  const rutRepreNum = cookieJar['NETSCAPE_LIVEWIRE.rut'] || cookieJar['RUT_NS'] || '';
2281
- const dvRepreChar = cookieJar['NETSCAPE_LIVEWIRE.dv'] || cookieJar['DV_NS'] || '';
2573
+ const dvRepreChar = cookieJar['NETSCAPE_LIVEWIRE.dv'] || cookieJar['DV_NS'] || '';
2282
2574
  if (!rutRepreNum) throw new Error('No se pudo obtener rutRepre de las cookies SII (NETSCAPE_LIVEWIRE.rut)');
2283
2575
 
2284
2576
  // Registrar listener de facade ANTES de hacer click
@@ -2287,7 +2579,7 @@ class CertRunner {
2287
2579
  { timeout: 20000 }
2288
2580
  ).catch(() => null);
2289
2581
 
2290
- console.log(' → Click "Bajar Nuevo Set" — esperando GWT facade...');
2582
+ console.log(' → Click "Bajar Nuevo Set" — esperando GWT facade...');
2291
2583
  await page.evaluate(() => {
2292
2584
  const btn = Array.from(document.querySelectorAll('button'))
2293
2585
  .find(b => /bajar/i.test(b.textContent));
@@ -2306,7 +2598,7 @@ class CertRunner {
2306
2598
  `&rutRepre=${rutRepreNum}&dvRepre=${dvRepreChar}` +
2307
2599
  `&mailProvSw=${encodeURIComponent(correoSet)}`;
2308
2600
 
2309
- console.log(` → Descargando set directamente: DownloadFileServlet?rutEmpresa=${rutNum}&dvEmpresa=${dvChar}&rutRepre=${rutRepreNum}&dvRepre=${dvRepreChar}&mailProvSw=${correoSet}`);
2601
+ console.log(` → Descargando set directamente: DownloadFileServlet?rutEmpresa=${rutNum}&dvEmpresa=${dvChar}&rutRepre=${rutRepreNum}&dvRepre=${dvRepreChar}&mailProvSw=${correoSet}`);
2310
2602
 
2311
2603
  const setText = await new Promise((resolve, reject) => {
2312
2604
  const req = https.get(downloadUrl, {
@@ -2341,10 +2633,10 @@ class CertRunner {
2341
2633
  const nodePath = require('path');
2342
2634
  fs.mkdirSync(nodePath.dirname(setPath), { recursive: true });
2343
2635
  fs.writeFileSync(setPath, setText, 'utf-8');
2344
- console.log(` ✓ Set guardado en: ${setPath}`);
2636
+ console.log(` ✓ Set guardado en: ${setPath}`);
2345
2637
  }
2346
2638
 
2347
- console.log(` ✓ Set de pruebas obtenido (${setText.length} chars)`);
2639
+ console.log(` ✓ Set de pruebas obtenido (${setText.length} chars)`);
2348
2640
  return { success: true, setText };
2349
2641
  } catch (err) {
2350
2642
  return { success: false, error: err.message };
@@ -2382,9 +2674,9 @@ class CertRunner {
2382
2674
  });
2383
2675
  const page = await browser.newPage();
2384
2676
  await page.setCookie(...puppeteerCookies);
2385
- page.on('dialog', async dlg => { console.log(` → [dialog SET=2] ${dlg.message()}`); await dlg.accept(); });
2677
+ page.on('dialog', async dlg => { console.log(` → [dialog SET=2] ${dlg.message()}`); await dlg.accept(); });
2386
2678
 
2387
- console.log(' → Cargando portal certBolElectDteInternet (SET=2)...');
2679
+ console.log(' → Cargando portal certBolElectDteInternet (SET=2)...');
2388
2680
  await page.goto('https://www4.sii.cl/certBolElectDteInternet/?SET=2', {
2389
2681
  waitUntil: 'networkidle2', timeout: 60000,
2390
2682
  });
@@ -2394,7 +2686,7 @@ class CertRunner {
2394
2686
  const rutInput = await page.$('input[maxlength="8"]');
2395
2687
  const dvInput = await page.$('input[maxlength="1"]');
2396
2688
  if (!rutInput) throw new Error('certBolElectDteInternet/?SET=2: no se encontró campo RUT');
2397
- console.log(` → Ingresando RUT empresa: ${rutNum}-${dvChar}`);
2689
+ console.log(` → Ingresando RUT empresa: ${rutNum}-${dvChar}`);
2398
2690
  await rutInput.click({ clickCount: 3 }); await rutInput.type(rutNum);
2399
2691
  await dvInput.click({ clickCount: 3 }); await dvInput.type(dvChar);
2400
2692
  await page.evaluate(() => {
@@ -2402,7 +2694,7 @@ class CertRunner {
2402
2694
  .find(b => /confirmar/i.test(b.textContent));
2403
2695
  if (btn) btn.click();
2404
2696
  });
2405
- console.log(' → Click "Confirmar Empresa"');
2697
+ console.log(' → Click "Confirmar Empresa"');
2406
2698
 
2407
2699
  // Esperar que aparezca el campo "Identificador de Envio" — GWT dispara ~8-10 POST /facade en paralelo
2408
2700
  await page.waitForNetworkIdle({ idleTime: 800, timeout: 40000 }).catch(() => {});
@@ -2414,7 +2706,7 @@ class CertRunner {
2414
2706
  // Paso 2: Ingresar TrackId
2415
2707
  const trackInput = await page.$('input[maxlength="15"]');
2416
2708
  if (!trackInput) throw new Error('No se encontró campo "Identificador de Envio" en certBolElectDteInternet/?SET=2');
2417
- console.log(` → Ingresando TrackId: ${trackId}`);
2709
+ console.log(` → Ingresando TrackId: ${trackId}`);
2418
2710
  await trackInput.click({ clickCount: 3 });
2419
2711
  await trackInput.type(String(trackId));
2420
2712
 
@@ -2424,7 +2716,7 @@ class CertRunner {
2424
2716
  .find(b => /solicitar/i.test(b.textContent));
2425
2717
  if (btn) btn.click();
2426
2718
  });
2427
- console.log(' → Click "Solicitar validación" — esperando respuesta...');
2719
+ console.log(' → Click "Solicitar validación" — esperando respuesta...');
2428
2720
 
2429
2721
  // Esperar respuesta del portal (puede ser confirmación o error)
2430
2722
  await page.waitForFunction(() => {
@@ -2434,7 +2726,7 @@ class CertRunner {
2434
2726
  }, { timeout: 15000, polling: 500 }).catch(() => {});
2435
2727
 
2436
2728
  const respuesta = await page.evaluate(() => (document.body.innerText || '').trim().substring(0, 500));
2437
- console.log(` ✓ Validación solicitada. Respuesta: ${respuesta.substring(0, 120)}`);
2729
+ console.log(` ✓ Validación solicitada. Respuesta: ${respuesta.substring(0, 120)}`);
2438
2730
 
2439
2731
  return { success: true, respuesta };
2440
2732
  } catch (err) {
@@ -2486,7 +2778,7 @@ class CertRunner {
2486
2778
  let dialogMsg = null;
2487
2779
  page.on('dialog', async dlg => {
2488
2780
  dialogMsg = dlg.message();
2489
- console.log(` → [dialog] ${dialogMsg}`);
2781
+ console.log(` → [dialog] ${dialogMsg}`);
2490
2782
  await dlg.accept();
2491
2783
  });
2492
2784
 
@@ -2499,7 +2791,7 @@ class CertRunner {
2499
2791
  for (let intento = 1; intento <= MAX_INTENTOS; intento++) {
2500
2792
  dialogMsg = null; // resetear entre intentos
2501
2793
 
2502
- console.log(` → Navegando a certBolElectDteInternet (declaración) [intento ${intento}/${MAX_INTENTOS}]...`);
2794
+ console.log(` → Navegando a certBolElectDteInternet (declaración) [intento ${intento}/${MAX_INTENTOS}]...`);
2503
2795
  await page.goto('https://www4.sii.cl/certBolElectDteInternet/', {
2504
2796
  waitUntil: 'networkidle2', timeout: 60000,
2505
2797
  });
@@ -2509,7 +2801,7 @@ class CertRunner {
2509
2801
  const rutInput = await page.$('input[maxlength="8"]');
2510
2802
  const dvInput = await page.$('input[maxlength="1"]');
2511
2803
  if (!rutInput) throw new Error('certBolElectDteInternet/: no se encontró campo RUT empresa');
2512
- console.log(` → Ingresando RUT empresa: ${rutEmpresaRaw}`);
2804
+ console.log(` → Ingresando RUT empresa: ${rutEmpresaRaw}`);
2513
2805
  await rutInput.click({ clickCount: 3 }); await rutInput.type(rutEmpNum);
2514
2806
  await dvInput.click({ clickCount: 3 }); await dvInput.type(rutEmpDv);
2515
2807
 
@@ -2518,7 +2810,7 @@ class CertRunner {
2518
2810
  .find(b => /confirmar empresa/i.test(b.textContent));
2519
2811
  if (btn) btn.click();
2520
2812
  });
2521
- console.log(' → Click "Confirmar Empresa"...');
2813
+ console.log(' → Click "Confirmar Empresa"...');
2522
2814
 
2523
2815
  // GWT dispara ~8-10 POST /facade EN PARALELO al confirmar.
2524
2816
  // Esperar red inactiva y luego DOM con checkboxes.
@@ -2530,9 +2822,9 @@ class CertRunner {
2530
2822
  if (dialogMsg) {
2531
2823
  // Portal lanzó alert — puede ser transitorio. Guardar y reintentar.
2532
2824
  lastDialogMsg = dialogMsg;
2533
- console.log(` ⚠️ Portal respondió con alerta en intento ${intento}: ${dialogMsg.substring(0, 100)}`);
2825
+ console.log(` [!] Portal respondió con alerta en intento ${intento}: ${dialogMsg.substring(0, 100)}`);
2534
2826
  if (intento < MAX_INTENTOS) {
2535
- console.log(' → Recargando y reintentando en 3s...');
2827
+ console.log(' → Recargando y reintentando en 3s...');
2536
2828
  await new Promise(r => setTimeout(r, 3000));
2537
2829
  continue;
2538
2830
  }
@@ -2545,7 +2837,7 @@ class CertRunner {
2545
2837
  break;
2546
2838
  }
2547
2839
 
2548
- console.log(` ⚠️ Formulario no cargó en intento ${intento}${intento < MAX_INTENTOS ? ` — reintentando...` : ''}`);
2840
+ console.log(` [!] Formulario no cargó en intento ${intento}${intento < MAX_INTENTOS ? ` — reintentando...` : ''}`);
2549
2841
  await new Promise(r => setTimeout(r, 2000));
2550
2842
  }
2551
2843
 
@@ -2558,7 +2850,7 @@ class CertRunner {
2558
2850
  todos.forEach(cb => { if (!cb.checked) cb.click(); });
2559
2851
  return todos.length;
2560
2852
  });
2561
- console.log(` → ${totalCbs} checkbox(es) marcados`);
2853
+ console.log(` → ${totalCbs} checkbox(es) marcados`);
2562
2854
  await new Promise(r => setTimeout(r, 500));
2563
2855
 
2564
2856
  // ── PASO 3: Rellenar campos proveedor software ────────────────
@@ -2629,7 +2921,7 @@ class CertRunner {
2629
2921
  return false;
2630
2922
  });
2631
2923
  if (!submitOk) throw new Error('No se encontró botón "Grabar Declaración" en certBolElectDteInternet/');
2632
- console.log(' → Click "Grabar Declaración"...');
2924
+ console.log(' → Click "Grabar Declaración"...');
2633
2925
 
2634
2926
  // Esperar confirmación del SII
2635
2927
  await page.waitForFunction(() => {
@@ -2639,7 +2931,7 @@ class CertRunner {
2639
2931
  }, { timeout: 20000, polling: 1000 }).catch(() => {});
2640
2932
 
2641
2933
  const msgFinal = await page.evaluate(() => (document.body.textContent || '').trim().substring(0, 300));
2642
- console.log(` ✓ Declaración completada. Respuesta: ${msgFinal.substring(0, 150)}`);
2934
+ console.log(` ✓ Declaración completada. Respuesta: ${msgFinal.substring(0, 150)}`);
2643
2935
 
2644
2936
  if (this.config.debugDir) {
2645
2937
  await page.screenshot({ path: path.join(this.config.debugDir, 'boleta-declaracion-post-submit.png'), fullPage: true }).catch(() => {});