@devlas/dte-sii 2.5.15 → 2.5.16

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.
@@ -782,6 +782,7 @@ class CertRunner {
782
782
  this._decrementarPeriodoLibros();
783
783
  const _nuevoPeriodo = this._getPeriodoLibros();
784
784
  console.log(`\n[!] Período rechazado por SII. Reintentando con ${_nuevoPeriodo} (${_pRetry + 1}/${MAX_PERIOD_RETRIES})...`);
785
+ emitProgress(STEPS.BOOK_PERIOD_RETRY, { periodo: _nuevoPeriodo, intento: String(_pRetry + 1) });
785
786
  await _reenviarLibros(_nuevoPeriodo);
786
787
  declaracion = await this.declararLibros({ ...resultados, ...(options.setsResultados || {}) });
787
788
  resultados.declaracion = declaracion;
@@ -806,6 +807,7 @@ class CertRunner {
806
807
  const _nuevoPeriodo = this._getPeriodoLibros();
807
808
  const _s21Nombres = _s21Keys.map(k => _KEY_A_SII_NOMBRE[k]).filter(Boolean);
808
809
  console.log(`\n[!] ${_s21Nombres.join(', ')} bloqueados en S21. Reintentando con período ${_nuevoPeriodo} (${_pRetry + 1}/${MAX_PERIOD_RETRIES})...`);
810
+ emitProgress(STEPS.BOOK_PERIOD_RETRY, { periodo: _nuevoPeriodo, intento: String(_pRetry + 1) });
809
811
 
810
812
  await _reenviarLibros(_nuevoPeriodo, new Set(_s21Keys));
811
813
  declaracion = await this.declararLibros({ ...resultados, ...(options.setsResultados || {}) });
@@ -1550,9 +1552,16 @@ class CertRunner {
1550
1552
  const yaIntercambio = Boolean(verificacion?.etapaActual?.includes('INTERCAMBIO'));
1551
1553
  const simConforme = Boolean(estadoSim?.esConforme || estadoSim?.estado?.toUpperCase()?.includes('REVISADO CONFORME'));
1552
1554
 
1553
- if (yaIntercambio || simConforme || !sigueFormulario) {
1554
- console.log('\n ¡SIMULACIÓN CONFIRMADA! Certificación completa.');
1555
- return { success: true, confirmada: true };
1555
+ if (yaIntercambio) {
1556
+ console.log('\n ¡SIMULACIÓN CONFIRMADA! Empresa ya en etapa INTERCAMBIO.');
1557
+ return { success: true, confirmada: true, etapa: 'INTERCAMBIO' };
1558
+ }
1559
+
1560
+ if (simConforme || !sigueFormulario) {
1561
+ // La empresa pasó a la siguiente etapa automáticamente (INTERCAMBIO → DOCUMENTOS IMPRESOS → DECLARAR CUMPLIMIENTO)
1562
+ const etapaActual = verificacion?.etapaActual || 'INTERCAMBIO';
1563
+ console.log(`\n ¡SIMULACIÓN APROBADA! Etapa actual: ${etapaActual}`);
1564
+ return { success: true, confirmada: true, etapa: etapaActual };
1556
1565
  }
1557
1566
 
1558
1567
  console.log(' [!] SII aún mantiene formulario de simulación pendiente; se reintentará...');
@@ -1585,8 +1594,11 @@ class CertRunner {
1585
1594
 
1586
1595
  if (esAprobado) {
1587
1596
  console.log(` [OK] SIMULACIÓN: REVISADO CONFORME`);
1588
- console.log('\n ¡SIMULACIÓN APROBADA! Certificación completa.');
1589
- return { success: true };
1597
+ // Consultar etapa actual (INTERCAMBIO es el siguiente paso tras simulación)
1598
+ const postSimAvance = await this.siiCert.verAvanceParsed().catch(() => null);
1599
+ const etapaActual = postSimAvance?.etapaActual || 'INTERCAMBIO';
1600
+ console.log(`\n ¡SIMULACIÓN APROBADA! Etapa actual: ${etapaActual}`);
1601
+ return { success: true, etapa: etapaActual };
1590
1602
  } else if (esRechazado) {
1591
1603
  console.log(` [ERR] SIMULACIÓN: ${simEstado.estado}`);
1592
1604
  return { success: false, error: 'Simulación rechazada' };
@@ -2227,6 +2239,26 @@ class CertRunner {
2227
2239
  await new Promise(r => setTimeout(r, 3000));
2228
2240
  if (debugDir) await page.screenshot({ path: path.join(debugDir, 'pdfte-01-loaded.png'), fullPage: true }).catch(() => {});
2229
2241
 
2242
+ // Hookear SWFUpload para capturar post_params.id cuando GWT inicialice el uploader
2243
+ await page.evaluate(() => {
2244
+ if (window.SWFUpload) {
2245
+ const _Orig = window.SWFUpload;
2246
+ window.SWFUpload = function(settings) {
2247
+ const pp = (settings && settings.post_params) || {};
2248
+ if (pp.id) window.__swfCapturedRevId = String(pp.id);
2249
+ const inst = new _Orig(settings);
2250
+ // Copiar propiedades estáticas
2251
+ Object.assign(window.SWFUpload, _Orig);
2252
+ return inst;
2253
+ };
2254
+ window.SWFUpload.prototype = _Orig.prototype;
2255
+ // Copiar constantes estáticas del prototipo original
2256
+ for (const k of Object.getOwnPropertyNames(_Orig)) {
2257
+ try { window.SWFUpload[k] = _Orig[k]; } catch { }
2258
+ }
2259
+ }
2260
+ }).catch(() => {});
2261
+
2230
2262
  // Paso 1: RUT Empresa
2231
2263
  const rutInputs = await page.$$('input[name="rut"]');
2232
2264
  const dvInputs = await page.$$('input[name="dv"]');
@@ -2290,21 +2322,50 @@ class CertRunner {
2290
2322
  console.log(` → Ingresando RUT proveedor: ${rutNum}-${dvChar}`);
2291
2323
  await pRut.click({ clickCount: 3 }); await pRut.type(rutNum);
2292
2324
  await pDv.click({ clickCount: 3 }); await pDv.type(dvChar);
2325
+
2326
+ // Interceptar respuestas del portal durante "Consultar" para capturar el ID de revisión
2327
+ let _capturedRevId = null;
2328
+ const _onPortalResponse = async (resp) => {
2329
+ const url = resp.url();
2330
+ if (!url.includes('sii.cl/pdfdteInternet')) return;
2331
+ try {
2332
+ const body = await resp.text();
2333
+ const matches = body.match(/\b(\d{5,7})\b/g);
2334
+ if (matches) {
2335
+ for (const m of matches) {
2336
+ const n = +m;
2337
+ if (n > 10000 && n < 9999999 && !_capturedRevId) {
2338
+ _capturedRevId = m;
2339
+ console.log(` [DEBUG] Posible nroRevision capturado de red: ${m} (url: ${url.split('?')[0]})`);
2340
+ }
2341
+ }
2342
+ }
2343
+ } catch { /* skip */ }
2344
+ };
2345
+ page.on('response', _onPortalResponse);
2293
2346
  await clickBoton(page, 'Consultar');
2294
2347
  await new Promise(r => setTimeout(r, 2500));
2348
+ page.off('response', _onPortalResponse);
2295
2349
  if (debugDir) await page.screenshot({ path: path.join(debugDir, 'pdfte-03-after-consultar.png'), fullPage: true }).catch(() => {});
2296
2350
 
2297
2351
  // ── Re-ejecución: detectar estado terminal antes de proceder ──
2298
2352
  // 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.
2353
+ // NOTA: el tab de navegación "Muestras Impresas en revisión" siempre contiene
2354
+ // el texto "en revisión", por lo que NO se puede usar textContent global para
2355
+ // detectar ese estado. Solo hacer early-exit si el formulario de upload
2356
+ // (input.gwt-FileUpload o botón "Crear") NO está visible, lo que indica que
2357
+ // la revisión está en un estado terminal irreversible.
2301
2358
  const _estadoYaSubido = nuevaRevisionCreada ? null : await page.evaluate(() => {
2359
+ // Si el formulario de carga está presente, proceder siempre con el upload
2360
+ const tieneFormulario = !!document.querySelector('input.gwt-FileUpload') ||
2361
+ Array.from(document.querySelectorAll('button.x-btn-text'))
2362
+ .some(b => ['Crear', 'Limpiar', 'Eliminar'].includes(b.textContent.trim()) && !b.disabled);
2363
+ if (tieneFormulario) return null;
2364
+ // Solo si NO hay formulario, verificar estado terminal
2302
2365
  const t = (document.body.textContent || '').toUpperCase();
2303
2366
  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';
2307
2367
  if (t.includes('ENVIADO AL SII')) return 'ENVIADO AL SII';
2368
+ if (t.includes('RECHAZADO')) return 'RECHAZADO';
2308
2369
  return null;
2309
2370
  }).catch(() => null);
2310
2371
  if (_estadoYaSubido) {
@@ -2368,86 +2429,121 @@ class CertRunner {
2368
2429
  if (!_hayInp) { await clickBoton(page, 'Crear'); await new Promise(r => setTimeout(r, 2500)); }
2369
2430
  };
2370
2431
 
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
2432
  const fileInput = await page.$('input.gwt-FileUpload');
2378
2433
  if (!fileInput) throw new Error('pdfdteInternet: input.gwt-FileUpload no encontrado');
2379
2434
 
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
- });
2435
+ // ── Obtener nroRevision y subir todos los archivos vía fetch() ──────────────────
2436
+ // ── Primer upload para obtener nroRevision ─────────────────────────────────────
2437
+ // Puppeteer 22+ usa waitForFileChooser() para file uploads.
2438
+ // Triggear el input via click + page.waitForFileChooser() es el API nativo.
2439
+ // GWT procesa el onChange, somete el FormPanel a /upload con id de revisión.
2440
+ // Capturamos nroRevision desde la respuesta del servidor.
2441
+ // ────────────────────────────────────────────────────────────────────────────
2442
+
2443
+ let nroRevision = null;
2444
+
2445
+ // Listener de respuesta para capturar nroRevision del primer upload GWT
2446
+ const _onFirstUploadResp = async (resp) => {
2447
+ if (resp.url().includes('/pdfdteInternet/upload')) {
2448
+ const txt = await resp.text().catch(() => '');
2449
+ const m = txt.trim().match(/^(\d+),(\d+)$/);
2450
+ if (m && !nroRevision) {
2451
+ nroRevision = m[2];
2452
+ console.log(` ✓ ID de revisión obtenido: ${nroRevision} (1/${pdfPaths.length} subido)`);
2453
+ }
2454
+ }
2455
+ };
2456
+ page.on('response', _onFirstUploadResp);
2385
2457
 
2386
- await fileInput.uploadFile(...pdfPaths);
2387
- console.log(` → ${pdfPaths.length} PDFs seleccionados, esperando que GWT encole los uploads...`);
2458
+ console.log(` → Disparando primer upload vía fileChooser para obtener ID de revisión...`);
2459
+ try {
2460
+ const _chooserPromise = page.waitForFileChooser({ timeout: 15000 });
2461
+ // Triggear el file input: click en el wrapper visible O directamente en el input
2462
+ await page.evaluate(() => {
2463
+ // a) intentar click en el botón visible (wrapper div con overflow: hidden)
2464
+ const wrapper = document.querySelector('div[style*="position: relative"][style*="overflow: hidden"] div.gwt-Label');
2465
+ if (wrapper) { wrapper.click(); return; }
2466
+ // b) click directo en el input oculto (funciona en headless Chrome)
2467
+ const inp = document.querySelector('input.gwt-FileUpload');
2468
+ if (inp) inp.click();
2469
+ });
2470
+ const _chooser = await _chooserPromise;
2471
+ await _chooser.accept([path.resolve(pdfPaths[0])]);
2472
+ console.log(` [DEBUG] fileChooser.accept → OK (${path.basename(pdfPaths[0])})`);
2473
+ } catch (e) {
2474
+ console.warn(` [!] waitForFileChooser falló (${e.message}), intentando CDP DOM.setFileInputFiles...`);
2475
+ // Fallback: CDP DOM.setFileInputFiles
2476
+ const _cdp = await page.createCDPSession();
2477
+ try {
2478
+ const { root } = await _cdp.send('DOM.getDocument', { depth: 0 });
2479
+ const { nodeId } = await _cdp.send('DOM.querySelector', {
2480
+ nodeId: root.nodeId, selector: 'input.gwt-FileUpload',
2481
+ });
2482
+ if (nodeId) {
2483
+ await _cdp.send('DOM.setFileInputFiles', { files: [path.resolve(pdfPaths[0])], nodeId });
2484
+ console.log(` [DEBUG] CDP setFileInputFiles → OK`);
2485
+ }
2486
+ } catch (cdpErr) {
2487
+ console.warn(` [!] CDP también falló: ${cdpErr.message}`);
2488
+ } finally {
2489
+ await _cdp.detach().catch(() => {});
2490
+ }
2491
+ }
2388
2492
 
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(() => {});
2493
+ // Esperar hasta 60s para que el FormPanel iframe complete el upload
2494
+ const _t0 = Date.now();
2495
+ while (!nroRevision && Date.now() - _t0 < 60000) {
2496
+ await new Promise(r => setTimeout(r, 500));
2497
+ }
2498
+ page.off('response', _onFirstUploadResp);
2392
2499
 
2393
- // ── Fase 1: esperar hasta 45s por primera señal de progreso o estado terminal ──
2394
- // Si el portal ya está en "POR REVISAR" (re-ejecución), lo detectamos aquí inmediatamente.
2395
- // Si el drop inició normalmente, "Procesados 1" aparece en pocos segundos.
2396
- await page.waitForFunction(() => {
2397
- for (const el of document.querySelectorAll('.x-progress-text')) {
2398
- const m = el.textContent.match(/Procesados\s+(\d+)/);
2399
- if (m && +m[1] > 0) return true;
2400
- }
2401
- const t = (document.body.textContent || '').toUpperCase();
2402
- if (t.includes('APROBADO') || t.includes('POR REVISAR') || t.includes('EN REVISI') || t.includes('RECHAZADO')) return true;
2403
- return false;
2404
- }, { timeout: 45000, polling: 1000 }).catch(() => {});
2405
-
2406
- // Leer estado real tras la fase 1
2407
- const _fase1 = await page.evaluate(() => {
2408
- let procesados = 0;
2409
- for (const el of document.querySelectorAll('.x-progress-text')) {
2410
- const m = el.textContent.match(/Procesados\s+(\d+)/);
2411
- if (m) { procesados = +m[1]; break; }
2500
+ if (!nroRevision) {
2501
+ if (debugDir) {
2502
+ await page.screenshot({ path: path.join(debugDir, 'pdfte-error-no-revid.png'), fullPage: true }).catch(() => {});
2503
+ fs.writeFileSync(path.join(debugDir, 'pdfte-error-no-revid.html'), await page.content(), 'utf8');
2412
2504
  }
2413
- const t = (document.body.textContent || '').toUpperCase();
2414
- let estado = null;
2415
- if (t.includes('APROBADO')) estado = 'APROBADO';
2416
- else if (t.includes('POR REVISAR')) estado = 'POR REVISAR';
2417
- else if (t.includes('EN REVISI')) estado = 'EN REVISIÓN';
2418
- else if (t.includes('RECHAZADO')) estado = 'RECHAZADO';
2419
- return { procesados, estado };
2420
- }).catch(() => ({ procesados: 0, estado: null }));
2421
-
2422
- if (_fase1.estado) {
2423
- console.log(` [OK] Portal en estado "${_fase1.estado}" — muestras ya procesadas previamente.`);
2424
- return { success: true, alreadyCompleted: true, estado: _fase1.estado };
2505
+ throw new Error('pdfdteInternet: no se pudo obtener ID de revisión. El file chooser o CDP no disparó el handler GWT o el servidor rechazó la petición.');
2425
2506
  }
2426
2507
 
2427
- if (_fase1.procesados === 0) {
2428
- // Sin progreso y sin estado terminal: el portal puede no estar procesando
2429
- console.warn(' [!] Sin progreso en 45s y sin estado terminal. Continuando al paso siguiente...');
2430
- } else {
2431
- // ── Fase 2: progreso iniciado — esperar al total ──
2432
- await page.waitForFunction((total) => {
2433
- for (const el of document.querySelectorAll('.x-progress-text')) {
2434
- const m = el.textContent.match(/Procesados\s+(\d+)/);
2435
- if (m && +m[1] >= total) return true;
2508
+ if (debugDir) await page.screenshot({ path: path.join(debugDir, 'pdfte-04b-primer-upload.png'), fullPage: true }).catch(() => {});
2509
+
2510
+ // ── Subir archivos restantes en paralelo vía fetch directo ──
2511
+ const CONCURRENCIA = 5;
2512
+ const _remaining = pdfPaths.slice(1);
2513
+ let _procesados = 1;
2514
+
2515
+ for (let i = 0; i < _remaining.length; i += CONCURRENCIA) {
2516
+ const _chunk = _remaining.slice(i, i + CONCURRENCIA);
2517
+ const _fileData = _chunk.map(p => ({
2518
+ name: path.basename(p),
2519
+ b64: fs.readFileSync(p).toString('base64'),
2520
+ }));
2521
+ const _responses = await page.evaluate(async (files, revId) => {
2522
+ return Promise.all(files.map(async ({ name, b64 }) => {
2523
+ const bytes = Uint8Array.from(atob(b64), c => c.charCodeAt(0));
2524
+ const blob = new Blob([bytes], { type: 'application/pdf' });
2525
+ const fd = new FormData();
2526
+ fd.append('Filedata', blob, name);
2527
+ fd.append('id', String(revId));
2528
+ fd.append('ambiente', 'false');
2529
+ const resp = await fetch('/pdfdteInternet/upload', { method: 'POST', body: fd });
2530
+ const text = await resp.text();
2531
+ return { name, ok: resp.ok, status: resp.status, text };
2532
+ }));
2533
+ }, _fileData, nroRevision);
2534
+
2535
+ _procesados += _chunk.length;
2536
+ console.log(` → Procesados ${_procesados}/${pdfPaths.length}`);
2537
+ for (const r of _responses) {
2538
+ if (!r.ok || !r.text.match(/^\d+,\d+$/)) {
2539
+ console.warn(` [!] ${r.name}: HTTP ${r.status} → ${r.text.substring(0, 120)}`);
2436
2540
  }
2437
- return false;
2438
- }, { timeout: pdfPaths.length * 15000, polling: 1000 }, pdfPaths.length).catch(async () => {
2439
- const procesados = await page.evaluate(() => {
2440
- for (const el of document.querySelectorAll('.x-progress-text')) {
2441
- const m = el.textContent.match(/Procesados\s+(\d+)/);
2442
- if (m) return +m[1];
2443
- }
2444
- return 0;
2445
- }).catch(() => 0);
2446
- console.warn(` [!] Timeout: solo se procesaron ${procesados}/${pdfPaths.length} antes del timeout`);
2447
- });
2541
+ }
2448
2542
  }
2449
2543
 
2450
- // Esperar que todos los requests de leeImpresoById (validación) terminen
2544
+ console.log(` ${pdfPaths.length} PDFs subidos al portal`);
2545
+
2546
+ // Esperar que las validaciones del portal (leeImpresoById) terminen
2451
2547
  await page.waitForNetworkIdle({ timeout: 60000, idleTime: 2000 }).catch(() => {});
2452
2548
  await new Promise(r => setTimeout(r, 2000));
2453
2549
 
@@ -2466,9 +2562,53 @@ class CertRunner {
2466
2562
 
2467
2563
  if (debugDir) await page.screenshot({ path: path.join(debugDir, 'pdfte-05b-antes-enviar.png'), fullPage: true }).catch(() => {});
2468
2564
 
2565
+ // Debug: listar todos los botones visibles y sus estados
2566
+ const _btnsDebug = await page.evaluate(() =>
2567
+ Array.from(document.querySelectorAll('button.x-btn-text')).map(b => ({
2568
+ text: b.textContent.trim(),
2569
+ disabled: b.disabled,
2570
+ aria: b.getAttribute('aria-disabled'),
2571
+ visible: b.offsetParent !== null,
2572
+ }))
2573
+ ).catch(() => []);
2574
+ console.log(` [DEBUG] Botones en página: ${JSON.stringify(_btnsDebug.filter(b => b.text))}`);
2575
+
2469
2576
  console.log(' → Click "Enviar al SII"...');
2470
- const enviado = await clickBoton(page, 'Enviar al SII');
2471
- if (!enviado) throw new Error('pdfdteInternet: botón "Enviar al SII" no disponible o deshabilitado');
2577
+
2578
+ // Intentar click en el botón directo (ignorar aria-disabled en esta etapa)
2579
+ const _clickedEnviar = await page.evaluate(() => {
2580
+ // a) Buscar button.x-btn-text exacto
2581
+ for (const btn of document.querySelectorAll('button.x-btn-text')) {
2582
+ if (btn.textContent.trim() === 'Enviar al SII') {
2583
+ btn.click();
2584
+ return 'direct';
2585
+ }
2586
+ }
2587
+ // b) Abrir overflow ">" y buscar en el menú desplegable
2588
+ const overflow = document.querySelector('td.x-toolbar-overflow-region, .x-toolbar-more-icon, button[class*="overflow"]');
2589
+ if (overflow) {
2590
+ overflow.click();
2591
+ return 'overflow-click';
2592
+ }
2593
+ return false;
2594
+ });
2595
+
2596
+ if (_clickedEnviar === 'overflow-click') {
2597
+ // Esperar que aparezca el menú y hacer click en "Enviar al SII"
2598
+ await new Promise(r => setTimeout(r, 500));
2599
+ const _menuClicked = await page.evaluate(() => {
2600
+ for (const el of document.querySelectorAll('.x-menu-item-text')) {
2601
+ if (el.textContent.trim() === 'Enviar al SII') {
2602
+ el.click();
2603
+ return true;
2604
+ }
2605
+ }
2606
+ return false;
2607
+ });
2608
+ if (!_menuClicked) throw new Error('pdfdteInternet: botón "Enviar al SII" no encontrado en overflow menu');
2609
+ } else if (!_clickedEnviar) {
2610
+ throw new Error('pdfdteInternet: botón "Enviar al SII" no disponible o deshabilitado');
2611
+ }
2472
2612
 
2473
2613
  await page.waitForNetworkIdle({ timeout: 30000, idleTime: 1000 }).catch(() => {});
2474
2614
  await new Promise(r => setTimeout(r, 3000));
@@ -54,7 +54,7 @@ function loadConfig(options = {}) {
54
54
  console.log(`[ConfigLoader] Resolución: NroResol=${nro_resol} FchResol=${fch_resol} (AMBIENTE=${AMBIENTE})`);
55
55
 
56
56
  const EMISOR = {
57
- rut: process.env.EMISOR_RUT,
57
+ rut: process.env.EMISOR_RUT || process.env.EMISOR_RUT_EMPRESA,
58
58
  razon_social: process.env.EMISOR_RAZON_SOCIAL,
59
59
  giro: process.env.EMISOR_GIRO || 'ACTIVIDADES DE PROGRAMACION INFORMATICA',
60
60
  acteco: process.env.EMISOR_ACTECO || '620200',
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@devlas/dte-sii",
3
- "version": "2.5.15",
3
+ "version": "2.5.16",
4
4
  "description": "Facturación y boletas electrónicas para el SII de Chile. Genera, timbra, firma y envía DTEs, libros electrónicos y automatiza la certificación.",
5
5
  "main": "index.js",
6
6
  "types": "dte-sii.d.ts",
package/utils/progress.js CHANGED
@@ -35,6 +35,7 @@ const STEPS = {
35
35
  BOOK_ERROR: 'BOOK_ERROR', // data: { book, error }
36
36
  BOOKS_DECLARING: 'BOOKS_DECLARING',
37
37
  BOOKS_DONE: 'BOOKS_DONE',
38
+ BOOK_PERIOD_RETRY: 'BOOK_PERIOD_RETRY', // data: { periodo, intento }
38
39
  // Avance (Fase 5)
39
40
  ADVANCE_WAITING: 'ADVANCE_WAITING',
40
41
  ADVANCE_DONE: 'ADVANCE_DONE',