@devlas/dte-sii 2.14.0 → 2.14.2

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 +104 -6
  2. package/package.json +1 -1
@@ -4051,7 +4051,13 @@ class CertRunner {
4051
4051
  const cookieJar = await this._obtenerCookiesSII();
4052
4052
  const cookieStr = Object.entries(cookieJar).map(([k, v]) => `${k}=${v}`).join('; ');
4053
4053
 
4054
+ // ⚠️ Este era el ÚNICO cliente HTTP de la librería que no dejaba rastro, y es
4055
+ // precisamente el que responde "¿el SII ya habilitó el timbraje?": consulta
4056
+ // of_solicita_folios_dcto en palena y mira si el tipo 39 aparece en el select.
4057
+ // Cuando la respuesta es "todavía no", sin la captura no queda forma de saber si el
4058
+ // SII contestó eso de verdad o si la consulta salió mal.
4054
4059
  const fetchHtml = (url) => new Promise((resolve, reject) => {
4060
+ const _t0 = Date.now();
4055
4061
  const req = https.get(url, {
4056
4062
  headers: {
4057
4063
  'Cookie': cookieStr,
@@ -4062,12 +4068,54 @@ class CertRunner {
4062
4068
  }, (res) => {
4063
4069
  const chunks = [];
4064
4070
  res.on('data', c => chunks.push(c));
4065
- res.on('end', () => resolve(Buffer.concat(chunks).toString('utf-8')));
4071
+ res.on('end', () => {
4072
+ const _body = Buffer.concat(chunks).toString('utf-8');
4073
+ registrarHttpDebug({
4074
+ url, method: 'GET', status: res.statusCode, headers: res.headers,
4075
+ body: _body, ms: Date.now() - _t0, cliente: 'verificarAutorizacionBoleta',
4076
+ });
4077
+ resolve(_body);
4078
+ });
4066
4079
  });
4067
4080
  req.on('error', reject);
4068
4081
  req.setTimeout(20000, () => { req.destroy(); reject(new Error('Timeout')); });
4069
4082
  });
4070
4083
 
4084
+ const [rutNum, dvChar] = String(this.config.emisor.rut).replace(/\./g, '').split('-');
4085
+
4086
+ /** POST de formulario, para el segundo paso del timbraje. Se captura igual que el GET. */
4087
+ const postForm = (url, campos) => new Promise((resolve, reject) => {
4088
+ const cuerpo = Object.entries(campos)
4089
+ .map(([k, v]) => `${encodeURIComponent(k)}=${encodeURIComponent(v ?? '')}`).join('&');
4090
+ const u = new URL(url);
4091
+ const _t0 = Date.now();
4092
+ const req = https.request({
4093
+ hostname: u.hostname, path: u.pathname + u.search, method: 'POST',
4094
+ headers: {
4095
+ 'Cookie': cookieStr,
4096
+ 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64)',
4097
+ 'Content-Type': 'application/x-www-form-urlencoded',
4098
+ 'Content-Length': Buffer.byteLength(cuerpo),
4099
+ },
4100
+ rejectUnauthorized: false,
4101
+ }, (res) => {
4102
+ const chunks = [];
4103
+ res.on('data', c => chunks.push(c));
4104
+ res.on('end', () => {
4105
+ const _body = Buffer.concat(chunks).toString('utf-8');
4106
+ registrarHttpDebug({
4107
+ url, method: 'POST', status: res.statusCode, headers: res.headers,
4108
+ body: _body, reqBody: cuerpo, ms: Date.now() - _t0,
4109
+ cliente: 'verificarAutorizacionBoleta',
4110
+ });
4111
+ resolve(_body);
4112
+ });
4113
+ });
4114
+ req.on('error', reject);
4115
+ req.setTimeout(20000, () => { req.destroy(); reject(new Error('Timeout')); });
4116
+ req.write(cuerpo); req.end();
4117
+ });
4118
+
4071
4119
  const result = {
4072
4120
  autorizadaProduccion: false,
4073
4121
  autorizadaCertificacion: false,
@@ -4077,8 +4125,37 @@ class CertRunner {
4077
4125
  };
4078
4126
 
4079
4127
  // ── 1. Verificar producción: of_solicita_folios_dcto en palena ─────────
4128
+ //
4129
+ // ⚠️ Son DOS pasos, no uno. `of_solicita_folios_dcto` es la segunda pantalla del
4130
+ // formulario de timbraje: entrar con un GET pelado, sin el POST previo que lleva
4131
+ // RUT_EMP/DV_EMP, hace que el SII responda 200 con una página de error genérica
4132
+ // ("No ha sido posible completar su solicitud... LIBRUD-OFSF-DTE-3-1-02") que no
4133
+ // trae ningún <select>.
4134
+ //
4135
+ // Como el parser buscaba <option value="39">, esa página vacía se leía como "no
4136
+ // autorizada". Es la secuencia que ya usa CafSolicitor._processMultiStepFlow() para
4137
+ // pedir folios de verdad, y por eso ESE camino sí funciona.
4138
+ //
4139
+ // Caso real (17/08/2026, RUT 78441936-3): el flujo reportaba "Boleta NO autorizada"
4140
+ // mientras el SII tenía a la empresa como FACTURADOR ELECTRONICO con BOLETA
4141
+ // ELECTRONICA autorizada hasta el folio 103009 desde el 12/07/2026, y palena le
4142
+ // entregaba folios reales de factura por el camino bien armado. Un comercio ya
4143
+ // habilitado quedaba viendo "esperando al SII" para siempre.
4080
4144
  try {
4081
- const htmlProd = await fetchHtml('https://palena.sii.cl/cvc_cgi/dte/of_solicita_folios_dcto');
4145
+ const SiiSession = require('../SiiSession');
4146
+ const htmlPaso1 = await fetchHtml('https://palena.sii.cl/cvc_cgi/dte/of_solicita_folios');
4147
+ const accion = SiiSession.extractFormAction(htmlPaso1) || '/cvc_cgi/dte/of_solicita_folios_dcto';
4148
+ const campos = {
4149
+ ...SiiSession.extractInputValues(htmlPaso1),
4150
+ RUT_EMP: rutNum,
4151
+ DV_EMP: dvChar,
4152
+ };
4153
+ // El `action` del form puede venir absoluto, con barra inicial, o relativo.
4154
+ const BASE_PALENA = 'https://palena.sii.cl';
4155
+ const urlPaso2 = accion.startsWith('http') ? accion
4156
+ : accion.startsWith('/') ? `${BASE_PALENA}${accion}`
4157
+ : `${BASE_PALENA}/cvc_cgi/dte/${accion}`;
4158
+ const htmlProd = await postForm(urlPaso2, campos);
4082
4159
  if (this.config.debugDir) {
4083
4160
  const ts = new Date().toISOString().replace(/[:.]/g, '-');
4084
4161
  fs.writeFileSync(
@@ -4086,10 +4163,31 @@ class CertRunner {
4086
4163
  htmlProd, 'utf-8'
4087
4164
  );
4088
4165
  }
4089
- const matches = [...htmlProd.matchAll(/<option[^>]+value="(\d+)"[^>]*>/gi)];
4090
- result.tiposDisponiblesProduccion = matches.map(m => parseInt(m[1], 10)).filter(n => n > 0);
4091
- result.autorizadaProduccion = result.tiposDisponiblesProduccion.includes(39);
4092
- console.log(` Producción tipos disponibles: [${result.tiposDisponiblesProduccion.join(', ')}] | Boleta 39: ${result.autorizadaProduccion ? '✅ SÍ' : '❌ NO'}`);
4166
+ // ⚠️ "No hay <option> con el 39" NO significa "no está autorizada". El SII también
4167
+ // responde 200 con una página de error que no trae ningún select, y ahí el parser
4168
+ // veía cero tipos y concluía "no autorizada" — un dato inventado, indistinguible
4169
+ // de la respuesta real.
4170
+ //
4171
+ // Visto el 17/08/2026 (RUT 78441936-3): palena devolvió 3377 bytes con
4172
+ // "No ha sido posible completar su solicitud... código LIBRUD-OFSF-DTE-3-1-02" y
4173
+ // el flujo venía reportando "certificación incompleta" desde hacía días.
4174
+ //
4175
+ // Se distingue por la ausencia de select, no por el texto del error: cualquier
4176
+ // fallo del portal que no pinte el formulario cae acá.
4177
+ const hayFormulario = /<select[^>]*>/i.test(htmlProd);
4178
+ if (!hayFormulario) {
4179
+ const codigo = (htmlProd.match(/([A-Z]{4,}-[A-Z]{3,}-[A-Z0-9-]{4,})/) || [])[1] || null;
4180
+ result.consultaFallida = true;
4181
+ result.errorConsultaProduccion = codigo
4182
+ ? `El portal del SII no entregó el formulario de timbraje (código ${codigo})`
4183
+ : 'El portal del SII no entregó el formulario de timbraje';
4184
+ console.log(` [!] Producción — ${result.errorConsultaProduccion}. NO se puede afirmar si está autorizada.`);
4185
+ } else {
4186
+ const matches = [...htmlProd.matchAll(/<option[^>]+value="(\d+)"[^>]*>/gi)];
4187
+ result.tiposDisponiblesProduccion = matches.map(m => parseInt(m[1], 10)).filter(n => n > 0);
4188
+ result.autorizadaProduccion = result.tiposDisponiblesProduccion.includes(39);
4189
+ console.log(` ✓ Producción — tipos disponibles: [${result.tiposDisponiblesProduccion.join(', ')}] | Boleta 39: ${result.autorizadaProduccion ? '✅ SÍ' : '❌ NO'}`);
4190
+ }
4093
4191
  } catch (e) {
4094
4192
  console.log(` [!] No se pudo verificar producción: ${e.message}`);
4095
4193
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@devlas/dte-sii",
3
- "version": "2.14.0",
3
+ "version": "2.14.2",
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",