@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.
- package/cert/CertRunner.js +220 -80
- package/cert/ConfigLoader.js +1 -1
- package/package.json +1 -1
- package/utils/progress.js +1 -0
package/cert/CertRunner.js
CHANGED
|
@@ -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
|
|
1554
|
-
console.log('\n ¡SIMULACIÓN CONFIRMADA!
|
|
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
|
-
|
|
1589
|
-
|
|
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
|
-
//
|
|
2300
|
-
//
|
|
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
|
-
//
|
|
2381
|
-
|
|
2382
|
-
|
|
2383
|
-
|
|
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
|
-
|
|
2387
|
-
|
|
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
|
-
//
|
|
2390
|
-
|
|
2391
|
-
|
|
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
|
-
|
|
2394
|
-
|
|
2395
|
-
|
|
2396
|
-
|
|
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
|
-
|
|
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 (
|
|
2428
|
-
|
|
2429
|
-
|
|
2430
|
-
|
|
2431
|
-
|
|
2432
|
-
|
|
2433
|
-
|
|
2434
|
-
|
|
2435
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
2471
|
-
|
|
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));
|
package/cert/ConfigLoader.js
CHANGED
|
@@ -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.
|
|
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',
|