@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.
- package/cert/CertRunner.js +133 -10
- package/package.json +1 -1
package/cert/CertRunner.js
CHANGED
|
@@ -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
|
-
|
|
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
|
|
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
|
-
|
|
4103
|
-
|
|
4104
|
-
|
|
4105
|
-
|
|
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.
|
|
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",
|