@devlas/dte-sii 2.13.0 → 2.13.1

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/CafSolicitor.js +119 -13
  2. package/package.json +1 -1
package/CafSolicitor.js CHANGED
@@ -238,7 +238,7 @@ class CafSolicitor {
238
238
  }
239
239
 
240
240
  static esBloqueoTimbraje(html) {
241
- return /NO\s+(SE\s+)?AUTORIZA\s+TIMBRAJE/i.test(String(html || ''));
241
+ return /NO\s+(SE\s+)?AUTORIZA\s+TIMBRAJE/i.test(CafSolicitor.textoVisible(html, 4000));
242
242
  }
243
243
 
244
244
  /**
@@ -254,7 +254,8 @@ class CafSolicitor {
254
254
  * no confirmado por el mensaje) de un fallo real desconocido.
255
255
  */
256
256
  static esNoAutorizadoIngresarOpcion(html) {
257
- return /no\s+est.\s+autorizado\s+para\s+ingresar\s+a\s+esta\s+opci.n/i.test(String(html || ''));
257
+ return /no\s+est.{0,8}\s*autorizado\s+para\s+ingresar\s+a\s+esta\s+opci.{0,8}n/i
258
+ .test(CafSolicitor.textoVisible(html, 4000));
258
259
  }
259
260
 
260
261
  /**
@@ -267,9 +268,91 @@ class CafSolicitor {
267
268
  * es reenviar los permisos del usuario (Paso 5 de la inscripción) EN EL MISMO
268
269
  * AMBIENTE donde se está pidiendo el folio.
269
270
  */
271
+ /**
272
+ * El SII exige hacer el trámite PRESENCIALMENTE en la oficina.
273
+ *
274
+ * Sale al intentar inscribir a la empresa como facturador electrónico, incluso en el
275
+ * sistema gratuito: "deberá presentarse en la oficina del SII correspondiente a su
276
+ * domicilio y solicitar la asistencia de un funcionario". Suele deberse a observaciones
277
+ * en el RUT, domicilio sin verificar o anotaciones del contribuyente.
278
+ *
279
+ * Se detecta porque es un callejón sin salida para el software: mientras esté, la
280
+ * empresa no puede inscribirse, no queda autorizada en palena, no puede enrolar
281
+ * usuarios ni timbrar. Sin este mensaje el usuario ve un error genérico y vuelve a
282
+ * intentar sin saber que lo único que corresponde es ir al SII.
283
+ *
284
+ * Caso real (12/08/2026, RUT 78441936-3): Verificación de Actividades aprobada,
285
+ * usuario enrolado en maullin y certificación de boleta enviada — y aun así palena
286
+ * rechazaba todo, porque la inscripción por internet estaba bloqueada de origen.
287
+ */
288
+ static esRequiereTramitePresencial(html) {
289
+ const t = CafSolicitor.textoVisible(html, 4000);
290
+ return /deber.{0,8}\s*presentarse\s+en\s+la\s+oficina\s+del\s+SII/i.test(t)
291
+ || (/no\s+cumple\s+con\s+los\s+requisitos/i.test(t)
292
+ && /asistencia\s+de\s+un\s+funcionario/i.test(t));
293
+ }
294
+
295
+ /**
296
+ * "la empresa no está autorizada para operar en esta modalidad"
297
+ *
298
+ * Distinto de `esUsuarioSinPermiso`: ahí el sujeto es el USUARIO del certificado, acá
299
+ * es la EMPRESA. El SII lo devuelve en palena cuando el contribuyente todavía no fue
300
+ * autorizado como emisor electrónico en producción — no hay nada que arreglar del lado
301
+ * del enrolamiento, porque el portal ni siquiera deja abrir la mantención de usuarios.
302
+ *
303
+ * Caso real (12/08/2026, RUT 78441936-3): el flujo de enrolamiento siguió de largo con
304
+ * páginas vacías —sin formulario ni hidden `key`— hasta reventar con un 500 en
305
+ * `eu_graba_usuario`. El 500 era el síntoma; esta frase, en el PRIMER paso, la causa.
306
+ */
307
+ static esEmpresaNoAutorizada(html) {
308
+ return /(la\s+)?empresa\s+no\s+est.{0,8}\s*autorizada\s+para\s+operar/i
309
+ .test(CafSolicitor.textoVisible(html, 4000));
310
+ }
311
+
312
+ /**
313
+ * ¿Este HTML es un rechazo que hace inútil seguir el flujo?
314
+ *
315
+ * ⚠️ Los rechazos del SII NO llegan siempre en la última respuesta. Este caso real
316
+ * (12/08/2026, RUT 78441936-3, tipo 39 en palena) llegó en el PASO 2
317
+ * (`of_solicita_folios_dcto`), donde solo se miraba `esBloqueoTimbraje`. El flujo
318
+ * siguió de largo y terminó devolviendo el genérico `UNKNOWN: No se obtuvo CAF`,
319
+ * escondiendo un mensaje que la librería ya sabía interpretar desde julio.
320
+ *
321
+ * Por eso el chequeo es UNO SOLO y se aplica en TODOS los pasos: agregar un patrón
322
+ * nuevo no debe obligar a acordarse de replicarlo en cada punto del flujo.
323
+ */
324
+ static esRechazoDuro(html) {
325
+ const h = String(html || '');
326
+ return CafSolicitor.esBloqueoTimbraje(h)
327
+ || CafSolicitor.esRequiereTramitePresencial(h)
328
+ || CafSolicitor.esEmpresaNoAutorizada(h)
329
+ || CafSolicitor.esNoAutorizadoIngresarOpcion(h)
330
+ || CafSolicitor.esUsuarioSinPermiso(h)
331
+ || /no\s+registra\s+Verificaci.{0,8}n\s+de\s+Actividades/i.test(CafSolicitor.textoVisible(h, 4000));
332
+ }
333
+
334
+ /**
335
+ * Texto legible de una página del SII, sin markup.
336
+ *
337
+ * Para que un rechazo desconocido no se pierda: sin esto, el motivo real queda solo
338
+ * en el HTML —que en producción no se guarda— y el operador ve `UNKNOWN` sin más.
339
+ */
340
+ static textoVisible(html, max = 400) {
341
+ return String(html || '')
342
+ .replace(/<script[\s\S]*?<\/script>/gi, ' ')
343
+ .replace(/<style[\s\S]*?<\/style>/gi, ' ')
344
+ .replace(/<[^>]+>/g, ' ')
345
+ .replace(/&nbsp;/gi, ' ').replace(/&aacute;/gi, 'a').replace(/&eacute;/gi, 'e')
346
+ .replace(/&iacute;/gi, 'i').replace(/&oacute;/gi, 'o').replace(/&uacute;/gi, 'u')
347
+ .replace(/&ntilde;/gi, 'n').replace(/&[a-z]+;/gi, ' ')
348
+ .replace(/\s+/g, ' ')
349
+ .trim()
350
+ .slice(0, max);
351
+ }
352
+
270
353
  static esUsuarioSinPermiso(html) {
271
- return /no\s+tiene\s+permiso\s+en\s+(la\s+)?empresa|usuario\s+no\s+autorizado\s+para\s+(la\s+)?empresa|no\s+est.\s+autorizado\s+para\s+operar\s+en\s+la\s+empresa/i
272
- .test(String(html || ''));
354
+ return /no\s+tiene\s+permiso\s+en\s+(la\s+)?empresa|usuario\s+no\s+autorizado\s+para\s+(la\s+)?empresa|no\s+est.{0,8}\s*autorizado\s+para\s+operar\s+en\s+la\s+empresa/i
355
+ .test(CafSolicitor.textoVisible(html, 4000));
273
356
  }
274
357
 
275
358
  /**
@@ -402,6 +485,22 @@ class CafSolicitor {
402
485
  return { success: false, errorCode: 'TIMBRAJE_BLOQUEADO', error: 'SII: No se autoriza timbraje. Folios acumulados excesivos o situaciones tributarias pendientes. Revisa el portal SII → Factura Electrónica → Solicitud de Timbraje.' };
403
486
  }
404
487
 
488
+ if (response.body && CafSolicitor.esRequiereTramitePresencial(response.body)) {
489
+ return {
490
+ success: false,
491
+ errorCode: 'REQUIERE_TRAMITE_PRESENCIAL',
492
+ error: 'El SII exige completar la inscripción en Factura Electrónica de forma presencial: el representante legal debe ir con su cédula a la oficina del SII del domicilio de la empresa. Hasta entonces no se pueden obtener folios. Suele deberse a observaciones en el RUT o al domicilio sin verificar.',
493
+ };
494
+ }
495
+
496
+ if (response.body && CafSolicitor.esEmpresaNoAutorizada(response.body)) {
497
+ return {
498
+ success: false,
499
+ errorCode: 'EMPRESA_NO_AUTORIZADA',
500
+ error: `SII (${this.ambiente}): la EMPRESA no está autorizada para operar como emisor electrónico en este ambiente. No es un problema del usuario ni de sus permisos: hasta que el SII autorice al contribuyente, el portal no permite ni enrolar usuarios ni solicitar folios.`,
501
+ };
502
+ }
503
+
405
504
  if (response.body && CafSolicitor.esNoAutorizadoIngresarOpcion(response.body)) {
406
505
  return {
407
506
  success: false,
@@ -524,7 +623,14 @@ class CafSolicitor {
524
623
  return { success: false, errorCode: 'SESSION_EXPIRED', error: 'Sesión SII inválida — registro limpiado, el próximo intento reautenticará.' };
525
624
  }
526
625
 
527
- return { success: false, errorCode: 'UNKNOWN', error: 'No se obtuvo CAF en la respuesta' };
626
+ // Se adjunta lo que dijo el SII: un `UNKNOWN` sin el motivo obliga a reproducir
627
+ // el fallo con el debug encendido, que es justo lo que no se puede hacer en
628
+ // producción cuando el problema es de un cliente concreto.
629
+ return {
630
+ success: false,
631
+ errorCode: 'UNKNOWN',
632
+ error: `No se obtuvo CAF en la respuesta. El SII respondió: "${CafSolicitor.textoVisible(response.body)}"`,
633
+ };
528
634
 
529
635
  } catch (err) {
530
636
  const msg = err.message || '';
@@ -557,8 +663,8 @@ class CafSolicitor {
557
663
 
558
664
  // Defensivo: mismo rechazo por Verificación de Actividades puede aparecer si el SII
559
665
  // lo entrega recién en un paso posterior en vez del response inicial de solicitar().
560
- if (currentHtml.includes('no registra Verificacion de Actividades')) {
561
- return response; // solicitar() detectará el rechazo en response.body
666
+ if (CafSolicitor.esRechazoDuro(currentHtml)) {
667
+ return response; // solicitar() traduce el motivo desde response.body
562
668
  }
563
669
 
564
670
  const realFormAction = SiiSession.extractFormAction(currentHtml);
@@ -592,8 +698,8 @@ class CafSolicitor {
592
698
 
593
699
  // Rechazo duro antes del check de COD_DOCTO: la página de rechazo también contiene
594
700
  // "COD_DOCTO" en su JavaScript, lo que causaría un POST innecesario con datos vacíos.
595
- if (CafSolicitor.esBloqueoTimbraje(currentHtml)) {
596
- return response; // solicitar() detectará el bloqueo en response.body
701
+ if (CafSolicitor.esRechazoDuro(currentHtml)) {
702
+ return response; // solicitar() traduce el motivo desde response.body
597
703
  }
598
704
 
599
705
  // Selección de tipo de documento
@@ -615,8 +721,8 @@ class CafSolicitor {
615
721
  currentHtml = response.body || '';
616
722
  this._saveDebug(debugDir, 'select.html', currentHtml);
617
723
 
618
- if (CafSolicitor.esBloqueoTimbraje(currentHtml)) {
619
- return response; // solicitar() detectará el bloqueo en response.body
724
+ if (CafSolicitor.esRechazoDuro(currentHtml)) {
725
+ return response; // solicitar() traduce el motivo desde response.body
620
726
  }
621
727
  }
622
728
 
@@ -634,8 +740,8 @@ class CafSolicitor {
634
740
  async _processStep3(response, rut, dv, tipoDte, cantidad, debugDir) {
635
741
  let currentHtml = response.body || '';
636
742
 
637
- if (currentHtml.includes('no registra Verificacion de Actividades')) {
638
- return response; // solicitar() detectará el rechazo en response.body
743
+ if (CafSolicitor.esRechazoDuro(currentHtml)) {
744
+ return response; // solicitar() traduce el motivo desde response.body
639
745
  }
640
746
 
641
747
  const formAction3 = SiiSession.extractFormAction(currentHtml) || '/cvc_cgi/dte/of_confirma_folio';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@devlas/dte-sii",
3
- "version": "2.13.0",
3
+ "version": "2.13.1",
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",