@devlas/dte-sii 2.5.14 → 2.5.15

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/cert/CertRunner.js +53 -39
  2. package/package.json +1 -1
@@ -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, 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;
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}`);
@@ -1476,7 +1481,7 @@ class CertRunner {
1476
1481
  },
1477
1482
  };
1478
1483
 
1479
- const result = await this._declararConReintentos(sets, 'declaracion-simulacion-response', { maxIntentos, intervalo, label: 'simulación' });
1484
+ const result = await this._declararConReintentos(sets, 'declaracion-simulacion-response', { maxIntentos, intervalo, label: 'simulación', retryOnAllRejected: true });
1480
1485
  if (result?.success) console.log(' [OK] Simulación declarada exitosamente');
1481
1486
  return result;
1482
1487
  }
@@ -2208,6 +2213,7 @@ class CertRunner {
2208
2213
  browser = await puppeteer.launch({
2209
2214
  headless: true,
2210
2215
  ignoreHTTPSErrors: true,
2216
+ protocolTimeout: 300000, // 5 min — DOM.setFileInputFiles con 256 archivos supera el default de 30s
2211
2217
  args: ['--no-sandbox', '--disable-setuid-sandbox', '--ignore-certificate-errors'],
2212
2218
  });
2213
2219
  const page = await browser.newPage();
@@ -2239,12 +2245,26 @@ class CertRunner {
2239
2245
  if (debugDir) await page.screenshot({ path: path.join(debugDir, 'pdfte-02-after-rut.png'), fullPage: true }).catch(() => {});
2240
2246
 
2241
2247
  // Paso 2: Diálogo "ya existe revisión" → click "Sí"
2248
+ // nuevaRevisionCreada=true cuando el usuario acepta crear una nueva revisión;
2249
+ // en ese caso NO se hace el early-exit por estado previo (el texto "EN REVISIÓN"
2250
+ // que queda visible corresponde a la revisión antigua, no a la nueva vacía).
2251
+ let nuevaRevisionCreada = false;
2242
2252
  const hayDialog = await page.evaluate(() => {
2243
2253
  const dlg = document.querySelector('.x-window');
2244
2254
  return !!(dlg && dlg.offsetParent !== null);
2245
2255
  });
2246
2256
  if (hayDialog) {
2247
- console.log(' → Diálogo de revisión existentehaciendo click en "Sí"');
2257
+ console.log(' → Diálogo "Ya existe revisión" detectado → click "Sí"');
2258
+ if (debugDir) {
2259
+ await page.screenshot({ path: path.join(debugDir, 'pdfte-dialog-ya-existe.png'), fullPage: true }).catch(() => {});
2260
+ fs.writeFileSync(path.join(debugDir, 'pdfte-dialog-ya-existe.html'), await page.content().catch(() => ''), 'utf8');
2261
+ // Log texto del diálogo para debug
2262
+ const dlgText = await page.evaluate(() => {
2263
+ const dlg = document.querySelector('.x-window');
2264
+ return dlg ? dlg.textContent.trim().replace(/\s+/g, ' ') : '';
2265
+ }).catch(() => '');
2266
+ console.log(` [DEBUG] Texto del diálogo: "${dlgText}"`);
2267
+ }
2248
2268
  const clicked = await page.evaluate(() => {
2249
2269
  const si = Array.from(document.querySelectorAll('button.x-btn-text'))
2250
2270
  .find(b => /^s[ií]$/i.test(b.textContent.trim()));
@@ -2253,6 +2273,8 @@ class CertRunner {
2253
2273
  });
2254
2274
  if (!clicked) await page.evaluate(() => { const b = document.querySelector('.x-window button'); if (b) b.click(); });
2255
2275
  await new Promise(r => setTimeout(r, 2500));
2276
+ if (debugDir) await page.screenshot({ path: path.join(debugDir, 'pdfte-dialog-despues-si.png'), fullPage: true }).catch(() => {});
2277
+ nuevaRevisionCreada = true;
2256
2278
  }
2257
2279
 
2258
2280
  // Paso 3: RUT Proveedor (mismo RUT empresa)
@@ -2273,7 +2295,10 @@ class CertRunner {
2273
2295
  if (debugDir) await page.screenshot({ path: path.join(debugDir, 'pdfte-03-after-consultar.png'), fullPage: true }).catch(() => {});
2274
2296
 
2275
2297
  // ── Re-ejecución: detectar estado terminal antes de proceder ──
2276
- const _estadoYaSubido = await page.evaluate(() => {
2298
+ // Solo aplicable cuando NO se creó una nueva revisión.
2299
+ // Si se creó una nueva (nuevaRevisionCreada=true), el texto "EN REVISIÓN"
2300
+ // que aparece pertenece a la revisión anterior y no es válido como early-exit.
2301
+ const _estadoYaSubido = nuevaRevisionCreada ? null : await page.evaluate(() => {
2277
2302
  const t = (document.body.textContent || '').toUpperCase();
2278
2303
  if (t.includes('APROBADO')) return 'APROBADO';
2279
2304
  if (t.includes('POR REVISAR')) return 'POR REVISAR';
@@ -2343,38 +2368,27 @@ class CertRunner {
2343
2368
  if (!_hayInp) { await clickBoton(page, 'Crear'); await new Promise(r => setTimeout(r, 2500)); }
2344
2369
  };
2345
2370
 
2346
- // Cargar todos los PDFs como base64 en Node.js y soltarlos en el drop zone de GWT
2347
- // de una sola vez via DataTransfer. GWT los procesa en secuencia internamente:
2348
- // drop por cada file: submit form al iframe → respuesta → leeImpresoById → tick verde
2349
- // Esto evita la re-navegación entre archivos y garantiza que la validación
2350
- // (Timbre/CAF/TED) ocurra antes de salir de la página.
2351
- console.log(` → Cargando ${pdfPaths.length} PDFs para drop en el portal...`);
2352
- const _fileDataList = pdfPaths.map(p => ({
2353
- name: path.basename(p),
2354
- b64: fs.readFileSync(p).toString('base64'),
2355
- }));
2356
-
2357
- if (debugDir) await page.screenshot({ path: path.join(debugDir, 'pdfte-04b-antes-drop.png'), fullPage: true }).catch(() => {});
2358
-
2359
- console.log(` → Ejecutando drop de ${pdfPaths.length} PDFs sobre el portal...`);
2360
- const _dropped = await page.evaluate((files) => {
2361
- const dt = new DataTransfer();
2362
- for (const f of files) {
2363
- const bin = atob(f.b64);
2364
- const arr = new Uint8Array(bin.length);
2365
- for (let i = 0; i < bin.length; i++) arr[i] = bin.charCodeAt(i);
2366
- dt.items.add(new File([arr], f.name, { type: 'application/pdf' }));
2367
- }
2368
- const dz = document.querySelector('.dropFilesLabel');
2369
- if (!dz) return 0;
2370
- dz.dispatchEvent(new DragEvent('dragenter', { dataTransfer: dt, bubbles: true, cancelable: true }));
2371
- dz.dispatchEvent(new DragEvent('dragover', { dataTransfer: dt, bubbles: true, cancelable: true }));
2372
- dz.dispatchEvent(new DragEvent('drop', { dataTransfer: dt, bubbles: true, cancelable: true }));
2373
- return dt.files.length;
2374
- }, _fileDataList);
2375
-
2376
- if (_dropped === 0) throw new Error('pdfdteInternet: drop zone no encontrado (.dropFilesLabel)');
2377
- console.log(` → Drop ejecutado (${_dropped} archivos). Esperando procesamiento...`);
2371
+ // Cargar todos los PDFs a la vez en el input de archivo.
2372
+ // GWT no tiene atributo "multiple" por defecto se fuerza vía DOM.
2373
+ // Puppeteer dispara el evento "change" internamente tras uploadFile(),
2374
+ // lo que dispara el handler GWT que encola todos los archivos para
2375
+ // procesarlos en batch (una POST por archivo vía iframe, sin re-navegación).
2376
+ console.log(` → Cargando ${pdfPaths.length} PDFs de una vez en el input...`);
2377
+ const fileInput = await page.$('input.gwt-FileUpload');
2378
+ if (!fileInput) throw new Error('pdfdteInternet: input.gwt-FileUpload no encontrado');
2379
+
2380
+ // Forzar múltiple selección y quitar restrict de accept (solo .PDF uppercase falla en algunos OS)
2381
+ await fileInput.evaluate(el => {
2382
+ el.setAttribute('multiple', '');
2383
+ el.removeAttribute('accept');
2384
+ });
2385
+
2386
+ await fileInput.uploadFile(...pdfPaths);
2387
+ console.log(` ${pdfPaths.length} PDFs seleccionados, esperando que GWT encole los uploads...`);
2388
+
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(() => {});
2378
2392
 
2379
2393
  // ── Fase 1: esperar hasta 45s por primera señal de progreso o estado terminal ──
2380
2394
  // Si el portal ya está en "POR REVISAR" (re-ejecución), lo detectamos aquí inmediatamente.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@devlas/dte-sii",
3
- "version": "2.5.14",
3
+ "version": "2.5.15",
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",