@devlas/dte-sii 2.5.14 → 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 +254 -100
- package/cert/ConfigLoader.js +1 -1
- package/package.json +1 -1
- package/utils/progress.js +1 -0
package/cert/CertRunner.js
CHANGED
|
@@ -354,7 +354,7 @@ class CertRunner {
|
|
|
354
354
|
* @param {Object} [options] - { maxIntentos, intervalo, label }
|
|
355
355
|
*/
|
|
356
356
|
async _declararConReintentos(sets, debugPrefix, options = {}) {
|
|
357
|
-
const { maxIntentos = 10, intervalo = 5000, label = 'avance' } = options;
|
|
357
|
+
const { maxIntentos = 10, intervalo = 5000, label = 'avance', retryOnAllRejected = false } = options;
|
|
358
358
|
|
|
359
359
|
console.log(` Esperando 10s para que SII procese los envios...`);
|
|
360
360
|
await sleep(10000);
|
|
@@ -400,9 +400,14 @@ class CertRunner {
|
|
|
400
400
|
console.log(` [...] SII aún procesando, reintentando en ${intervalo / 1000}s...`);
|
|
401
401
|
await sleep(intervalo);
|
|
402
402
|
} else if (result.allRejected) {
|
|
403
|
-
// SII rechazó todos los sets/libros — período incorrecto
|
|
404
|
-
|
|
405
|
-
|
|
403
|
+
// SII rechazó todos los sets/libros — puede ser período incorrecto (libros) o TrackID no procesado aún (simulación)
|
|
404
|
+
if (retryOnAllRejected && intento < maxIntentos) {
|
|
405
|
+
console.log(` [!] S21 — SII aún procesando TrackID, reintentando en ${intervalo / 1000}s...`);
|
|
406
|
+
await sleep(intervalo);
|
|
407
|
+
} else {
|
|
408
|
+
console.log(` [ERR] SII rechazó todos los envíos (campos vacíos en portal) — período incorrecto. Corregir período y reenviar.`);
|
|
409
|
+
break;
|
|
410
|
+
}
|
|
406
411
|
} else if (result.verificado === false && intento < maxIntentos) {
|
|
407
412
|
// Verificación post-declaración falló: los campos quedaron vacíos en el portal
|
|
408
413
|
console.log(` [!] Verificación fallida: ${result.error}`);
|
|
@@ -777,6 +782,7 @@ class CertRunner {
|
|
|
777
782
|
this._decrementarPeriodoLibros();
|
|
778
783
|
const _nuevoPeriodo = this._getPeriodoLibros();
|
|
779
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) });
|
|
780
786
|
await _reenviarLibros(_nuevoPeriodo);
|
|
781
787
|
declaracion = await this.declararLibros({ ...resultados, ...(options.setsResultados || {}) });
|
|
782
788
|
resultados.declaracion = declaracion;
|
|
@@ -801,6 +807,7 @@ class CertRunner {
|
|
|
801
807
|
const _nuevoPeriodo = this._getPeriodoLibros();
|
|
802
808
|
const _s21Nombres = _s21Keys.map(k => _KEY_A_SII_NOMBRE[k]).filter(Boolean);
|
|
803
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) });
|
|
804
811
|
|
|
805
812
|
await _reenviarLibros(_nuevoPeriodo, new Set(_s21Keys));
|
|
806
813
|
declaracion = await this.declararLibros({ ...resultados, ...(options.setsResultados || {}) });
|
|
@@ -1476,7 +1483,7 @@ class CertRunner {
|
|
|
1476
1483
|
},
|
|
1477
1484
|
};
|
|
1478
1485
|
|
|
1479
|
-
const result = await this._declararConReintentos(sets, 'declaracion-simulacion-response', { maxIntentos, intervalo, label: 'simulación' });
|
|
1486
|
+
const result = await this._declararConReintentos(sets, 'declaracion-simulacion-response', { maxIntentos, intervalo, label: 'simulación', retryOnAllRejected: true });
|
|
1480
1487
|
if (result?.success) console.log(' [OK] Simulación declarada exitosamente');
|
|
1481
1488
|
return result;
|
|
1482
1489
|
}
|
|
@@ -1545,9 +1552,16 @@ class CertRunner {
|
|
|
1545
1552
|
const yaIntercambio = Boolean(verificacion?.etapaActual?.includes('INTERCAMBIO'));
|
|
1546
1553
|
const simConforme = Boolean(estadoSim?.esConforme || estadoSim?.estado?.toUpperCase()?.includes('REVISADO CONFORME'));
|
|
1547
1554
|
|
|
1548
|
-
if (yaIntercambio
|
|
1549
|
-
console.log('\n ¡SIMULACIÓN CONFIRMADA!
|
|
1550
|
-
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 };
|
|
1551
1565
|
}
|
|
1552
1566
|
|
|
1553
1567
|
console.log(' [!] SII aún mantiene formulario de simulación pendiente; se reintentará...');
|
|
@@ -1580,8 +1594,11 @@ class CertRunner {
|
|
|
1580
1594
|
|
|
1581
1595
|
if (esAprobado) {
|
|
1582
1596
|
console.log(` [OK] SIMULACIÓN: REVISADO CONFORME`);
|
|
1583
|
-
|
|
1584
|
-
|
|
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 };
|
|
1585
1602
|
} else if (esRechazado) {
|
|
1586
1603
|
console.log(` [ERR] SIMULACIÓN: ${simEstado.estado}`);
|
|
1587
1604
|
return { success: false, error: 'Simulación rechazada' };
|
|
@@ -2208,6 +2225,7 @@ class CertRunner {
|
|
|
2208
2225
|
browser = await puppeteer.launch({
|
|
2209
2226
|
headless: true,
|
|
2210
2227
|
ignoreHTTPSErrors: true,
|
|
2228
|
+
protocolTimeout: 300000, // 5 min — DOM.setFileInputFiles con 256 archivos supera el default de 30s
|
|
2211
2229
|
args: ['--no-sandbox', '--disable-setuid-sandbox', '--ignore-certificate-errors'],
|
|
2212
2230
|
});
|
|
2213
2231
|
const page = await browser.newPage();
|
|
@@ -2221,6 +2239,26 @@ class CertRunner {
|
|
|
2221
2239
|
await new Promise(r => setTimeout(r, 3000));
|
|
2222
2240
|
if (debugDir) await page.screenshot({ path: path.join(debugDir, 'pdfte-01-loaded.png'), fullPage: true }).catch(() => {});
|
|
2223
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
|
+
|
|
2224
2262
|
// Paso 1: RUT Empresa
|
|
2225
2263
|
const rutInputs = await page.$$('input[name="rut"]');
|
|
2226
2264
|
const dvInputs = await page.$$('input[name="dv"]');
|
|
@@ -2239,12 +2277,26 @@ class CertRunner {
|
|
|
2239
2277
|
if (debugDir) await page.screenshot({ path: path.join(debugDir, 'pdfte-02-after-rut.png'), fullPage: true }).catch(() => {});
|
|
2240
2278
|
|
|
2241
2279
|
// Paso 2: Diálogo "ya existe revisión" → click "Sí"
|
|
2280
|
+
// nuevaRevisionCreada=true cuando el usuario acepta crear una nueva revisión;
|
|
2281
|
+
// en ese caso NO se hace el early-exit por estado previo (el texto "EN REVISIÓN"
|
|
2282
|
+
// que queda visible corresponde a la revisión antigua, no a la nueva vacía).
|
|
2283
|
+
let nuevaRevisionCreada = false;
|
|
2242
2284
|
const hayDialog = await page.evaluate(() => {
|
|
2243
2285
|
const dlg = document.querySelector('.x-window');
|
|
2244
2286
|
return !!(dlg && dlg.offsetParent !== null);
|
|
2245
2287
|
});
|
|
2246
2288
|
if (hayDialog) {
|
|
2247
|
-
console.log(' → Diálogo
|
|
2289
|
+
console.log(' → Diálogo "Ya existe revisión" detectado → click "Sí"');
|
|
2290
|
+
if (debugDir) {
|
|
2291
|
+
await page.screenshot({ path: path.join(debugDir, 'pdfte-dialog-ya-existe.png'), fullPage: true }).catch(() => {});
|
|
2292
|
+
fs.writeFileSync(path.join(debugDir, 'pdfte-dialog-ya-existe.html'), await page.content().catch(() => ''), 'utf8');
|
|
2293
|
+
// Log texto del diálogo para debug
|
|
2294
|
+
const dlgText = await page.evaluate(() => {
|
|
2295
|
+
const dlg = document.querySelector('.x-window');
|
|
2296
|
+
return dlg ? dlg.textContent.trim().replace(/\s+/g, ' ') : '';
|
|
2297
|
+
}).catch(() => '');
|
|
2298
|
+
console.log(` [DEBUG] Texto del diálogo: "${dlgText}"`);
|
|
2299
|
+
}
|
|
2248
2300
|
const clicked = await page.evaluate(() => {
|
|
2249
2301
|
const si = Array.from(document.querySelectorAll('button.x-btn-text'))
|
|
2250
2302
|
.find(b => /^s[ií]$/i.test(b.textContent.trim()));
|
|
@@ -2253,6 +2305,8 @@ class CertRunner {
|
|
|
2253
2305
|
});
|
|
2254
2306
|
if (!clicked) await page.evaluate(() => { const b = document.querySelector('.x-window button'); if (b) b.click(); });
|
|
2255
2307
|
await new Promise(r => setTimeout(r, 2500));
|
|
2308
|
+
if (debugDir) await page.screenshot({ path: path.join(debugDir, 'pdfte-dialog-despues-si.png'), fullPage: true }).catch(() => {});
|
|
2309
|
+
nuevaRevisionCreada = true;
|
|
2256
2310
|
}
|
|
2257
2311
|
|
|
2258
2312
|
// Paso 3: RUT Proveedor (mismo RUT empresa)
|
|
@@ -2268,18 +2322,50 @@ class CertRunner {
|
|
|
2268
2322
|
console.log(` → Ingresando RUT proveedor: ${rutNum}-${dvChar}`);
|
|
2269
2323
|
await pRut.click({ clickCount: 3 }); await pRut.type(rutNum);
|
|
2270
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);
|
|
2271
2346
|
await clickBoton(page, 'Consultar');
|
|
2272
2347
|
await new Promise(r => setTimeout(r, 2500));
|
|
2348
|
+
page.off('response', _onPortalResponse);
|
|
2273
2349
|
if (debugDir) await page.screenshot({ path: path.join(debugDir, 'pdfte-03-after-consultar.png'), fullPage: true }).catch(() => {});
|
|
2274
2350
|
|
|
2275
2351
|
// ── Re-ejecución: detectar estado terminal antes de proceder ──
|
|
2276
|
-
|
|
2352
|
+
// Solo aplicable cuando NO se creó una nueva revisión.
|
|
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.
|
|
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
|
|
2277
2365
|
const t = (document.body.textContent || '').toUpperCase();
|
|
2278
2366
|
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';
|
|
2282
2367
|
if (t.includes('ENVIADO AL SII')) return 'ENVIADO AL SII';
|
|
2368
|
+
if (t.includes('RECHAZADO')) return 'RECHAZADO';
|
|
2283
2369
|
return null;
|
|
2284
2370
|
}).catch(() => null);
|
|
2285
2371
|
if (_estadoYaSubido) {
|
|
@@ -2343,97 +2429,121 @@ class CertRunner {
|
|
|
2343
2429
|
if (!_hayInp) { await clickBoton(page, 'Crear'); await new Promise(r => setTimeout(r, 2500)); }
|
|
2344
2430
|
};
|
|
2345
2431
|
|
|
2346
|
-
|
|
2347
|
-
|
|
2348
|
-
|
|
2349
|
-
//
|
|
2350
|
-
//
|
|
2351
|
-
|
|
2352
|
-
|
|
2353
|
-
|
|
2354
|
-
|
|
2355
|
-
|
|
2356
|
-
|
|
2357
|
-
|
|
2358
|
-
|
|
2359
|
-
|
|
2360
|
-
const
|
|
2361
|
-
|
|
2362
|
-
|
|
2363
|
-
const
|
|
2364
|
-
|
|
2365
|
-
|
|
2366
|
-
|
|
2432
|
+
const fileInput = await page.$('input.gwt-FileUpload');
|
|
2433
|
+
if (!fileInput) throw new Error('pdfdteInternet: input.gwt-FileUpload no encontrado');
|
|
2434
|
+
|
|
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
|
+
}
|
|
2367
2454
|
}
|
|
2368
|
-
|
|
2369
|
-
|
|
2370
|
-
|
|
2371
|
-
|
|
2372
|
-
|
|
2373
|
-
|
|
2374
|
-
|
|
2375
|
-
|
|
2376
|
-
|
|
2377
|
-
|
|
2378
|
-
|
|
2379
|
-
|
|
2380
|
-
|
|
2381
|
-
|
|
2382
|
-
|
|
2383
|
-
|
|
2384
|
-
|
|
2385
|
-
|
|
2455
|
+
};
|
|
2456
|
+
page.on('response', _onFirstUploadResp);
|
|
2457
|
+
|
|
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(() => {});
|
|
2386
2490
|
}
|
|
2387
|
-
|
|
2388
|
-
|
|
2389
|
-
|
|
2390
|
-
|
|
2391
|
-
|
|
2392
|
-
|
|
2393
|
-
|
|
2394
|
-
|
|
2395
|
-
|
|
2396
|
-
|
|
2397
|
-
|
|
2491
|
+
}
|
|
2492
|
+
|
|
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);
|
|
2499
|
+
|
|
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');
|
|
2398
2504
|
}
|
|
2399
|
-
|
|
2400
|
-
let estado = null;
|
|
2401
|
-
if (t.includes('APROBADO')) estado = 'APROBADO';
|
|
2402
|
-
else if (t.includes('POR REVISAR')) estado = 'POR REVISAR';
|
|
2403
|
-
else if (t.includes('EN REVISI')) estado = 'EN REVISIÓN';
|
|
2404
|
-
else if (t.includes('RECHAZADO')) estado = 'RECHAZADO';
|
|
2405
|
-
return { procesados, estado };
|
|
2406
|
-
}).catch(() => ({ procesados: 0, estado: null }));
|
|
2407
|
-
|
|
2408
|
-
if (_fase1.estado) {
|
|
2409
|
-
console.log(` [OK] Portal en estado "${_fase1.estado}" — muestras ya procesadas previamente.`);
|
|
2410
|
-
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.');
|
|
2411
2506
|
}
|
|
2412
2507
|
|
|
2413
|
-
if (
|
|
2414
|
-
|
|
2415
|
-
|
|
2416
|
-
|
|
2417
|
-
|
|
2418
|
-
|
|
2419
|
-
|
|
2420
|
-
|
|
2421
|
-
|
|
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)}`);
|
|
2422
2540
|
}
|
|
2423
|
-
|
|
2424
|
-
}, { timeout: pdfPaths.length * 15000, polling: 1000 }, pdfPaths.length).catch(async () => {
|
|
2425
|
-
const procesados = await page.evaluate(() => {
|
|
2426
|
-
for (const el of document.querySelectorAll('.x-progress-text')) {
|
|
2427
|
-
const m = el.textContent.match(/Procesados\s+(\d+)/);
|
|
2428
|
-
if (m) return +m[1];
|
|
2429
|
-
}
|
|
2430
|
-
return 0;
|
|
2431
|
-
}).catch(() => 0);
|
|
2432
|
-
console.warn(` [!] Timeout: solo se procesaron ${procesados}/${pdfPaths.length} antes del timeout`);
|
|
2433
|
-
});
|
|
2541
|
+
}
|
|
2434
2542
|
}
|
|
2435
2543
|
|
|
2436
|
-
|
|
2544
|
+
console.log(` ✓ ${pdfPaths.length} PDFs subidos al portal`);
|
|
2545
|
+
|
|
2546
|
+
// Esperar que las validaciones del portal (leeImpresoById) terminen
|
|
2437
2547
|
await page.waitForNetworkIdle({ timeout: 60000, idleTime: 2000 }).catch(() => {});
|
|
2438
2548
|
await new Promise(r => setTimeout(r, 2000));
|
|
2439
2549
|
|
|
@@ -2452,9 +2562,53 @@ class CertRunner {
|
|
|
2452
2562
|
|
|
2453
2563
|
if (debugDir) await page.screenshot({ path: path.join(debugDir, 'pdfte-05b-antes-enviar.png'), fullPage: true }).catch(() => {});
|
|
2454
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
|
+
|
|
2455
2576
|
console.log(' → Click "Enviar al SII"...');
|
|
2456
|
-
|
|
2457
|
-
|
|
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
|
+
}
|
|
2458
2612
|
|
|
2459
2613
|
await page.waitForNetworkIdle({ timeout: 30000, idleTime: 1000 }).catch(() => {});
|
|
2460
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',
|