@devlas/dte-sii 2.5.11 → 2.5.13

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.
@@ -378,14 +378,22 @@ class SiiCertificacion {
378
378
  // Parsear estado de cada set/libro
379
379
  const estadoSets = {};
380
380
 
381
- // Buscar filas de la tabla con formato:
382
- // <td>SET/LIBRO nombre</td><td><b>ESTADO</b></td>
383
- const rowRegex = /<tr[^>]*>[\s\S]*?<td[^>]*>[\s\S]*?(SET[^<]*|LIBRO[^<]*)<\/font><\/td>[\s\S]*?<td[^>]*>[\s\S]*?<b>([^<]+)<\/b>/gi;
384
- let rowMatch;
385
- while ((rowMatch = rowRegex.exec(html)) !== null) {
386
- const nombre = rowMatch[1].trim();
387
- const estado = rowMatch[2].trim().toUpperCase();
388
- estadoSets[nombre] = estado;
381
+ // Parsear estado de cada set/libro fila por fila para evitar falsos positivos
382
+ // (la regex global cruzaba filas y asignaba REVISADO CONFORME de SET CASO GENERAL a LIBRO DE VENTAS)
383
+ const rows = html.split(/<\/tr>/i);
384
+ for (const row of rows) {
385
+ const nameMatch = /(SET[^<\n\r]*|LIBRO[^<\n\r]*)<\/font><\/td>/i.exec(row);
386
+ if (!nameMatch) continue;
387
+ const nombre = nameMatch[1].trim();
388
+ // Estado explícito en <b>...</b>
389
+ const stateMatch = /<b>([^<]+)<\/b>/i.exec(row);
390
+ if (stateMatch) {
391
+ estadoSets[nombre] = stateMatch[1].trim().toUpperCase();
392
+ } else {
393
+ // Fila con campos input: leer valor EST oculto (S01 = sin declarar, S21 = declarado)
394
+ const estMatch = /name="EST\d+"[^>]*value="([^"]+)"/i.exec(row);
395
+ estadoSets[nombre] = estMatch ? estMatch[1] : 'S01';
396
+ }
389
397
  }
390
398
 
391
399
  // Verificar si todos los sets requeridos están REVISADO CONFORME
@@ -541,16 +549,17 @@ class SiiCertificacion {
541
549
  // El SII cambia el orden según qué sets están aprobados
542
550
  const fieldMapping = {};
543
551
 
544
- if (process.env.DEBUG_SII) {
552
+ // Guardar pe_avance2 para debug (siempre)
553
+ {
545
554
  const fs = require('fs');
546
555
  const path = require('path');
547
- const debugDir = process.env.SII_DEBUG_DIR || path.join(__dirname, '../../debug');
556
+ const debugDir = process.env.SII_DEBUG_DIR || path.join(__dirname, '../../debug/cert-v2');
548
557
  if (!fs.existsSync(debugDir)) {
549
558
  fs.mkdirSync(debugDir, { recursive: true });
550
559
  }
551
- const debugPath = path.join(debugDir, 'pe_avance2.html');
560
+ const debugPath = path.join(debugDir, 'pe_avance2_form.html');
552
561
  fs.writeFileSync(debugPath, formHtml, 'utf8');
553
- console.log(' [DEBUG] HTML guardado en:', debugPath);
562
+ console.log(' 📄 HTML pe_avance2 formulario guardado en debug/cert-v2/pe_avance2_form.html');
554
563
  }
555
564
 
556
565
  // Extraer todas las filas <tr>...</tr> del formulario
@@ -737,13 +746,112 @@ class SiiCertificacion {
737
746
  // Éxito si el form se envió sin errores de sesión/contenido.
738
747
  // El estado del envío (ERRORES O REPAROS / EN REVISION / REVISADO CONFORME)
739
748
  // NO determina el éxito de la declaración — eso se resuelve vía polling.
740
- const success = (!hasError || hasSuccess) && !sesionExpirada && !contenidoNoCorresponde;
749
+ const postOk = (!hasError || hasSuccess) && !sesionExpirada && !contenidoNoCorresponde;
750
+
751
+ if (!postOk) {
752
+ return {
753
+ success: false,
754
+ error: errorMsg || undefined,
755
+ status: declareResponse.status,
756
+ rawHtml: body,
757
+ formHtml,
758
+ setsDeclarados: Object.keys(sets),
759
+ formDataSent: formData,
760
+ };
761
+ }
762
+
763
+ // Guardar HTML de pe_avance3 (respuesta de declaración) siempre para debug
764
+ {
765
+ const fs = require('fs');
766
+ const path = require('path');
767
+ const debugDir = process.env.SII_DEBUG_DIR || path.join(__dirname, '../../debug/cert-v2');
768
+ if (!fs.existsSync(debugDir)) fs.mkdirSync(debugDir, { recursive: true });
769
+ fs.writeFileSync(path.join(debugDir, 'pe_avance3_response.html'), body, 'utf8');
770
+ console.log(' 📄 HTML pe_avance3 guardado en debug/cert-v2/pe_avance3_response.html');
771
+ }
772
+
773
+ // 7. VERIFICACIÓN POST-DECLARACIÓN: Re-leer pe_avance2 y confirmar que los TrackIDs se guardaron
774
+ // Si aparece "EN REVISION" → la declaración fue aceptada y el SII está procesando
775
+ // Si reaparecen inputs vacíos → la declaración no se guardó
776
+ let verificado = true;
777
+ let verificacionError = '';
778
+ let enRevision = false;
779
+ try {
780
+ const verifyResponse = await this.session.submitForm(
781
+ '/cvc_cgi/dte/pe_avance2',
782
+ { RUT_EMP: this.rutEmpresa, DV_EMP: this.dvEmpresa, ACEPTAR: 'Continuar' },
783
+ 'https://maullin.sii.cl/cvc_cgi/dte/pe_avance1'
784
+ );
785
+ const verifyHtml = verifyResponse.body || '';
786
+
787
+ // Guardar HTML de verificación pe_avance2 siempre
788
+ {
789
+ const fs = require('fs');
790
+ const path = require('path');
791
+ const debugDir = process.env.SII_DEBUG_DIR || path.join(__dirname, '../../debug/cert-v2');
792
+ if (!fs.existsSync(debugDir)) fs.mkdirSync(debugDir, { recursive: true });
793
+ fs.writeFileSync(path.join(debugDir, 'pe_avance2_verify.html'), verifyHtml, 'utf8');
794
+ console.log(' 📄 HTML pe_avance2 verificación guardado en debug/cert-v2/pe_avance2_verify.html');
795
+ }
796
+
797
+ // Para cada set que declaramos, verificar estado
798
+ const verifyRows = verifyHtml.split(/<\/tr>/i);
799
+ const camposVacios = [];
800
+ const camposEnRevision = [];
801
+ for (const [setName, index] of Object.entries(fieldMapping)) {
802
+ if (!sets[setName]) continue;
803
+ for (const row of verifyRows) {
804
+ const numEnvMatch = row.match(new RegExp(`NAME="NUM_ENV${index}"`, 'i'));
805
+ if (!numEnvMatch) {
806
+ // Si no hay input NUM_ENV, verificar si aparece REVISADO CONFORME o EN REVISION
807
+ const labelPattern = patterns.find(p => p.name === setName);
808
+ if (labelPattern && labelPattern.label.test(row)) {
809
+ if (/<b>[^<]*REVISADO CONFORME[^<]*<\/b>/i.test(row)) break;
810
+ if (/EN REVISION/i.test(row)) {
811
+ camposEnRevision.push(setName);
812
+ break;
813
+ }
814
+ }
815
+ continue;
816
+ }
817
+ // Tiene input — verificar si tiene valor o está REVISADO CONFORME
818
+ const tieneConforme = /<b>[^<]*REVISADO CONFORME[^<]*<\/b>/i.test(row);
819
+ if (tieneConforme) break;
820
+ if (/EN REVISION/i.test(row)) {
821
+ camposEnRevision.push(setName);
822
+ break;
823
+ }
824
+ const valueMatch = row.match(new RegExp(`NAME="NUM_ENV${index}"[^>]*value="([^"]*)"`, 'i'));
825
+ const tieneValor = valueMatch && valueMatch[1] && valueMatch[1].trim() !== '';
826
+ if (!tieneValor) {
827
+ camposVacios.push(setName);
828
+ }
829
+ break;
830
+ }
831
+ }
832
+
833
+ if (camposEnRevision.length > 0) {
834
+ enRevision = true;
835
+ console.log(` 🔄 EN REVISION: ${camposEnRevision.join(', ')} — declaración aceptada, SII procesando`);
836
+ }
837
+
838
+ if (camposVacios.length > 0 && !enRevision) {
839
+ verificado = false;
840
+ verificacionError = `Declaración NO se guardó en el portal SII. Campos vacíos para: ${camposVacios.join(', ')}. Posible error de sesión o TrackID no reconocido.`;
841
+ console.log(` ⚠️ ${verificacionError}`);
842
+ }
843
+ } catch (verifyErr) {
844
+ console.log(` ⚠️ No se pudo verificar la declaración: ${verifyErr.message}`);
845
+ }
741
846
 
742
847
  return {
743
- success,
744
- error: errorMsg || undefined,
848
+ success: verificado || enRevision,
849
+ error: verificacionError || errorMsg || undefined,
850
+ verificado,
851
+ enRevision,
745
852
  status: declareResponse.status,
746
853
  rawHtml: body,
854
+ formHtml,
747
855
  setsDeclarados: Object.keys(sets),
748
856
  formDataSent: formData,
749
857
  };
@@ -1102,13 +1210,17 @@ class SiiCertificacion {
1102
1210
  for (const [key, { nombre, regex }] of Object.entries(SiiCertificacion.ESTADO_PATTERNS)) {
1103
1211
  const match = result.rawHtml?.match(regex);
1104
1212
  if (match) {
1213
+ const estadoTexto = match[1].trim();
1214
+ const upper = estadoTexto.toUpperCase();
1105
1215
  estados[key] = {
1106
1216
  nombre,
1107
- estado: match[1].trim(),
1108
- esConforme: match[1].trim().toUpperCase().includes('REVISADO CONFORME'),
1109
- enRevision: match[1].trim().toUpperCase().includes('EN REVISION'),
1110
- esRechazado: match[1].trim().toUpperCase().includes('RECHAZADO') ||
1111
- match[1].trim().toUpperCase().includes('ERRORES'),
1217
+ estado: estadoTexto,
1218
+ esConforme: upper.includes('REVISADO CONFORME'),
1219
+ enRevision: upper.includes('EN REVISION'),
1220
+ esRechazado: upper.includes('RECHAZADO') || upper.includes('ERRORES'),
1221
+ esReparos: upper.includes('REPAROS'),
1222
+ porRealizar: upper.includes('POR REALIZAR'),
1223
+ esAnulado: upper.includes('ANULADO'),
1112
1224
  };
1113
1225
  }
1114
1226
  }
@@ -1119,9 +1231,12 @@ class SiiCertificacion {
1119
1231
  estados.setSimulacion = {
1120
1232
  nombre: 'SET SIMULACION',
1121
1233
  estado: 'PENDIENTE CONFIRMAR',
1122
- esConforme: false,
1123
- enRevision: true,
1234
+ esConforme: false,
1235
+ enRevision: true,
1124
1236
  esRechazado: false,
1237
+ esReparos: false,
1238
+ porRealizar: false,
1239
+ esAnulado: false,
1125
1240
  pendienteConfirmar: true,
1126
1241
  };
1127
1242
  }