@devlas/dte-sii 2.14.1 → 2.14.3

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 +133 -10
  2. package/package.json +1 -1
@@ -3944,12 +3944,43 @@ class CertRunner {
3944
3944
  const listaParaDeclarar = estadoResp.includes('P90');
3945
3945
  const estadoMatch = estadoResp.match(/"(P\d+)"/);
3946
3946
  const listaVacia = /\/\/OK\[0,\[\]/.test(estadoResp);
3947
- const estado = listaParaDeclarar ? 'P90'
3948
- : estadoMatch ? estadoMatch[1]
3949
- : listaVacia ? 'sin_estado_sok_pendiente'
3950
- : 'desconocido';
3951
3947
 
3952
- return { success: true, inscrita: true, listaParaDeclarar, estado };
3948
+ // ⚠️ La consulta pregunta puntualmente por el estado 90, así que una lista vacía
3949
+ // significa "no está en P90" — y eso pasa en DOS situaciones opuestas:
3950
+ // a) todavía no llegó (el SII no emitió el SOK del set), o
3951
+ // b) YA lo pasó: declaró cumplimiento y avanzó a P91.
3952
+ //
3953
+ // Darlas por (a) siempre hacía que, después de una declaración exitosa, se
3954
+ // informara "esperando aprobación del SII (SOK)". La pantalla le pedía al usuario
3955
+ // esperar una etapa que ya había superado (visto el 17/08/2026, RUT 78441936-3:
3956
+ // declaró a las 14:01 con DECLARACION EFECTUADA y a las 14:5x seguía diciendo que
3957
+ // faltaba el SOK).
3958
+ //
3959
+ // Se desempata preguntando por el 91, que es el estado al que mueve
3960
+ // `autorizarEmpresaBolProd`.
3961
+ let estado = listaParaDeclarar ? 'P90'
3962
+ : estadoMatch ? estadoMatch[1]
3963
+ : listaVacia ? 'sin_estado_sok_pendiente'
3964
+ : 'desconocido';
3965
+ let yaDeclarada = false;
3966
+
3967
+ if (estado === 'sin_estado_sok_pendiente') {
3968
+ try {
3969
+ const resp91 = await gwtPost(
3970
+ `7|0|7|${CBE_BASE}|${CBE_POLICY}|${CBE_SVC}|obtenerEstadoAutorizaEmp|java.lang.Integer/3438268394|java.lang.String/2004016611|${dvUp}|1|2|3|4|4|5|6|5|6|5|${rutNum}|7|5|91|0|`
3971
+ );
3972
+ if (resp91.includes('P91')) {
3973
+ estado = 'P91';
3974
+ yaDeclarada = true;
3975
+ }
3976
+ } catch (e) {
3977
+ // Si la segunda consulta falla se conserva la lectura original: es un dato de
3978
+ // más, no una dependencia.
3979
+ console.log(` [!] No se pudo desempatar el estado con P91: ${e.message}`);
3980
+ }
3981
+ }
3982
+
3983
+ return { success: true, inscrita: true, listaParaDeclarar, estado, yaDeclarada };
3953
3984
  }
3954
3985
 
3955
3986
  async completarDeclaracionBoletaPortal({
@@ -3973,6 +4004,13 @@ class CertRunner {
3973
4004
  if (!estadoActual.inscrita) {
3974
4005
  return { success: false, error: 'No se pudo obtener representante vigente (facade CBE)' };
3975
4006
  }
4007
+ // Ya declaró y avanzó a P91: la etapa está cumplida, no pendiente. Sin este caso,
4008
+ // reintentar la declaración sobre una empresa ya declarada devolvía `pendingSok` y
4009
+ // la etapa quedaba "esperando" para siempre, esperando un SOK que ya había llegado
4010
+ // y ya se había usado.
4011
+ if (estadoActual.yaDeclarada) {
4012
+ return { success: true, yaDeclarada: true, mensaje: 'La declaración de cumplimiento ya estaba efectuada (P91)' };
4013
+ }
3976
4014
  if (!estadoActual.listaParaDeclarar) {
3977
4015
  const legible = estadoActual.estado === 'sin_estado_sok_pendiente'
3978
4016
  ? 'sin estado registrado (SOK aún no emitido)'
@@ -4081,6 +4119,41 @@ class CertRunner {
4081
4119
  req.setTimeout(20000, () => { req.destroy(); reject(new Error('Timeout')); });
4082
4120
  });
4083
4121
 
4122
+ const [rutNum, dvChar] = String(this.config.emisor.rut).replace(/\./g, '').split('-');
4123
+
4124
+ /** POST de formulario, para el segundo paso del timbraje. Se captura igual que el GET. */
4125
+ const postForm = (url, campos) => new Promise((resolve, reject) => {
4126
+ const cuerpo = Object.entries(campos)
4127
+ .map(([k, v]) => `${encodeURIComponent(k)}=${encodeURIComponent(v ?? '')}`).join('&');
4128
+ const u = new URL(url);
4129
+ const _t0 = Date.now();
4130
+ const req = https.request({
4131
+ hostname: u.hostname, path: u.pathname + u.search, method: 'POST',
4132
+ headers: {
4133
+ 'Cookie': cookieStr,
4134
+ 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64)',
4135
+ 'Content-Type': 'application/x-www-form-urlencoded',
4136
+ 'Content-Length': Buffer.byteLength(cuerpo),
4137
+ },
4138
+ rejectUnauthorized: false,
4139
+ }, (res) => {
4140
+ const chunks = [];
4141
+ res.on('data', c => chunks.push(c));
4142
+ res.on('end', () => {
4143
+ const _body = Buffer.concat(chunks).toString('utf-8');
4144
+ registrarHttpDebug({
4145
+ url, method: 'POST', status: res.statusCode, headers: res.headers,
4146
+ body: _body, reqBody: cuerpo, ms: Date.now() - _t0,
4147
+ cliente: 'verificarAutorizacionBoleta',
4148
+ });
4149
+ resolve(_body);
4150
+ });
4151
+ });
4152
+ req.on('error', reject);
4153
+ req.setTimeout(20000, () => { req.destroy(); reject(new Error('Timeout')); });
4154
+ req.write(cuerpo); req.end();
4155
+ });
4156
+
4084
4157
  const result = {
4085
4158
  autorizadaProduccion: false,
4086
4159
  autorizadaCertificacion: false,
@@ -4090,8 +4163,37 @@ class CertRunner {
4090
4163
  };
4091
4164
 
4092
4165
  // ── 1. Verificar producción: of_solicita_folios_dcto en palena ─────────
4166
+ //
4167
+ // ⚠️ Son DOS pasos, no uno. `of_solicita_folios_dcto` es la segunda pantalla del
4168
+ // formulario de timbraje: entrar con un GET pelado, sin el POST previo que lleva
4169
+ // RUT_EMP/DV_EMP, hace que el SII responda 200 con una página de error genérica
4170
+ // ("No ha sido posible completar su solicitud... LIBRUD-OFSF-DTE-3-1-02") que no
4171
+ // trae ningún <select>.
4172
+ //
4173
+ // Como el parser buscaba <option value="39">, esa página vacía se leía como "no
4174
+ // autorizada". Es la secuencia que ya usa CafSolicitor._processMultiStepFlow() para
4175
+ // pedir folios de verdad, y por eso ESE camino sí funciona.
4176
+ //
4177
+ // Caso real (17/08/2026, RUT 78441936-3): el flujo reportaba "Boleta NO autorizada"
4178
+ // mientras el SII tenía a la empresa como FACTURADOR ELECTRONICO con BOLETA
4179
+ // ELECTRONICA autorizada hasta el folio 103009 desde el 12/07/2026, y palena le
4180
+ // entregaba folios reales de factura por el camino bien armado. Un comercio ya
4181
+ // habilitado quedaba viendo "esperando al SII" para siempre.
4093
4182
  try {
4094
- const htmlProd = await fetchHtml('https://palena.sii.cl/cvc_cgi/dte/of_solicita_folios_dcto');
4183
+ const SiiSession = require('../SiiSession');
4184
+ const htmlPaso1 = await fetchHtml('https://palena.sii.cl/cvc_cgi/dte/of_solicita_folios');
4185
+ const accion = SiiSession.extractFormAction(htmlPaso1) || '/cvc_cgi/dte/of_solicita_folios_dcto';
4186
+ const campos = {
4187
+ ...SiiSession.extractInputValues(htmlPaso1),
4188
+ RUT_EMP: rutNum,
4189
+ DV_EMP: dvChar,
4190
+ };
4191
+ // El `action` del form puede venir absoluto, con barra inicial, o relativo.
4192
+ const BASE_PALENA = 'https://palena.sii.cl';
4193
+ const urlPaso2 = accion.startsWith('http') ? accion
4194
+ : accion.startsWith('/') ? `${BASE_PALENA}${accion}`
4195
+ : `${BASE_PALENA}/cvc_cgi/dte/${accion}`;
4196
+ const htmlProd = await postForm(urlPaso2, campos);
4095
4197
  if (this.config.debugDir) {
4096
4198
  const ts = new Date().toISOString().replace(/[:.]/g, '-');
4097
4199
  fs.writeFileSync(
@@ -4099,10 +4201,31 @@ class CertRunner {
4099
4201
  htmlProd, 'utf-8'
4100
4202
  );
4101
4203
  }
4102
- const matches = [...htmlProd.matchAll(/<option[^>]+value="(\d+)"[^>]*>/gi)];
4103
- result.tiposDisponiblesProduccion = matches.map(m => parseInt(m[1], 10)).filter(n => n > 0);
4104
- result.autorizadaProduccion = result.tiposDisponiblesProduccion.includes(39);
4105
- console.log(` Producción tipos disponibles: [${result.tiposDisponiblesProduccion.join(', ')}] | Boleta 39: ${result.autorizadaProduccion ? '✅ SÍ' : '❌ NO'}`);
4204
+ // ⚠️ "No hay <option> con el 39" NO significa "no está autorizada". El SII también
4205
+ // responde 200 con una página de error que no trae ningún select, y ahí el parser
4206
+ // veía cero tipos y concluía "no autorizada" — un dato inventado, indistinguible
4207
+ // de la respuesta real.
4208
+ //
4209
+ // Visto el 17/08/2026 (RUT 78441936-3): palena devolvió 3377 bytes con
4210
+ // "No ha sido posible completar su solicitud... código LIBRUD-OFSF-DTE-3-1-02" y
4211
+ // el flujo venía reportando "certificación incompleta" desde hacía días.
4212
+ //
4213
+ // Se distingue por la ausencia de select, no por el texto del error: cualquier
4214
+ // fallo del portal que no pinte el formulario cae acá.
4215
+ const hayFormulario = /<select[^>]*>/i.test(htmlProd);
4216
+ if (!hayFormulario) {
4217
+ const codigo = (htmlProd.match(/([A-Z]{4,}-[A-Z]{3,}-[A-Z0-9-]{4,})/) || [])[1] || null;
4218
+ result.consultaFallida = true;
4219
+ result.errorConsultaProduccion = codigo
4220
+ ? `El portal del SII no entregó el formulario de timbraje (código ${codigo})`
4221
+ : 'El portal del SII no entregó el formulario de timbraje';
4222
+ console.log(` [!] Producción — ${result.errorConsultaProduccion}. NO se puede afirmar si está autorizada.`);
4223
+ } else {
4224
+ const matches = [...htmlProd.matchAll(/<option[^>]+value="(\d+)"[^>]*>/gi)];
4225
+ result.tiposDisponiblesProduccion = matches.map(m => parseInt(m[1], 10)).filter(n => n > 0);
4226
+ result.autorizadaProduccion = result.tiposDisponiblesProduccion.includes(39);
4227
+ console.log(` ✓ Producción — tipos disponibles: [${result.tiposDisponiblesProduccion.join(', ')}] | Boleta 39: ${result.autorizadaProduccion ? '✅ SÍ' : '❌ NO'}`);
4228
+ }
4106
4229
  } catch (e) {
4107
4230
  console.log(` [!] No se pudo verificar producción: ${e.message}`);
4108
4231
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@devlas/dte-sii",
3
- "version": "2.14.1",
3
+ "version": "2.14.3",
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",