@devlas/dte-sii 2.12.20 → 2.12.22
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/CafSolicitor.js +35 -1
- package/EnviadorSII.js +5 -1
- package/cert/BoletaCert.js +4 -4
- package/package.json +4 -1
- package/utils/pfx.js +38 -1
package/CafSolicitor.js
CHANGED
|
@@ -244,6 +244,19 @@ class CafSolicitor {
|
|
|
244
244
|
this.session.saveSession(this.sessionPath);
|
|
245
245
|
}
|
|
246
246
|
|
|
247
|
+
// Rechazo duro por falta de Verificación de Actividades: debe detectarse ANTES de
|
|
248
|
+
// _processMultiStepFlow. La página de rechazo también contiene el string
|
|
249
|
+
// "of_solicita_folios_dcto" dentro del JS changeRegregion(), lo que hace que
|
|
250
|
+
// _processMultiStepFlow la confunda con el paso 2 normal y siga de largo el flujo
|
|
251
|
+
// multi-paso en vez de detectar el rechazo aquí, terminando en errorCode UNKNOWN.
|
|
252
|
+
if (response.body && response.body.includes('no registra Verificacion de Actividades')) {
|
|
253
|
+
return {
|
|
254
|
+
success: false,
|
|
255
|
+
errorCode: 'VERIFICACION_ACTIVIDADES_PENDIENTE',
|
|
256
|
+
error: 'SII: El RUT no tiene registrada la Verificación de Actividades para este tipo de documento. Solicítala en el sitio SII → Peticiones Administrativas → Verificación de actividad, o en oficinas del SII.',
|
|
257
|
+
};
|
|
258
|
+
}
|
|
259
|
+
|
|
247
260
|
// Procesar flujo multi-paso del SII
|
|
248
261
|
response = await this._processMultiStepFlow(response, rut, dv, tipoDte, cantidad, debugDir);
|
|
249
262
|
|
|
@@ -269,6 +282,16 @@ class CafSolicitor {
|
|
|
269
282
|
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.' };
|
|
270
283
|
}
|
|
271
284
|
|
|
285
|
+
// Rechazo por Verificación de Actividades detectado en un paso posterior del flujo
|
|
286
|
+
// multi-paso (ver guarda defensiva en _processMultiStepFlow).
|
|
287
|
+
if (response.body && response.body.includes('no registra Verificacion de Actividades')) {
|
|
288
|
+
return {
|
|
289
|
+
success: false,
|
|
290
|
+
errorCode: 'VERIFICACION_ACTIVIDADES_PENDIENTE',
|
|
291
|
+
error: 'SII: El RUT no tiene registrada la Verificación de Actividades para este tipo de documento. Solicítala en el sitio SII → Peticiones Administrativas → Verificación de actividad, o en oficinas del SII.',
|
|
292
|
+
};
|
|
293
|
+
}
|
|
294
|
+
|
|
272
295
|
if (response.body && response.body.includes('Autenticaci')) {
|
|
273
296
|
return { success: false, errorCode: 'SESSION_EXPIRED', error: 'El SII devolvió página de autenticación' };
|
|
274
297
|
}
|
|
@@ -375,6 +398,13 @@ class CafSolicitor {
|
|
|
375
398
|
*/
|
|
376
399
|
async _processMultiStepFlow(response, rut, dv, tipoDte, cantidad, debugDir) {
|
|
377
400
|
let currentHtml = response.body || '';
|
|
401
|
+
|
|
402
|
+
// Defensivo: mismo rechazo por Verificación de Actividades puede aparecer si el SII
|
|
403
|
+
// lo entrega recién en un paso posterior en vez del response inicial de solicitar().
|
|
404
|
+
if (currentHtml.includes('no registra Verificacion de Actividades')) {
|
|
405
|
+
return response; // solicitar() detectará el rechazo en response.body
|
|
406
|
+
}
|
|
407
|
+
|
|
378
408
|
const realFormAction = SiiSession.extractFormAction(currentHtml);
|
|
379
409
|
|
|
380
410
|
// Si el <form> real ya apunta a of_confirma_folio, el SII preseleccionó
|
|
@@ -447,7 +477,11 @@ class CafSolicitor {
|
|
|
447
477
|
*/
|
|
448
478
|
async _processStep3(response, rut, dv, tipoDte, cantidad, debugDir) {
|
|
449
479
|
let currentHtml = response.body || '';
|
|
450
|
-
|
|
480
|
+
|
|
481
|
+
if (currentHtml.includes('no registra Verificacion de Actividades')) {
|
|
482
|
+
return response; // solicitar() detectará el rechazo en response.body
|
|
483
|
+
}
|
|
484
|
+
|
|
451
485
|
const formAction3 = SiiSession.extractFormAction(currentHtml) || '/cvc_cgi/dte/of_confirma_folio';
|
|
452
486
|
const inputs3 = SiiSession.extractInputValues(currentHtml);
|
|
453
487
|
|
package/EnviadorSII.js
CHANGED
|
@@ -1174,7 +1174,11 @@ class EnviadorSII {
|
|
|
1174
1174
|
log.log('XML Length:', xml.length, 'bytes');
|
|
1175
1175
|
|
|
1176
1176
|
const [rutNum, dv] = rutEmisor.split('-');
|
|
1177
|
-
|
|
1177
|
+
// Fallback a rutEmisor si el certificado no expone RUT (ver
|
|
1178
|
+
// docs/EXTRACCION_RUT_CERTIFICADO.md) — evita un TypeError por .split
|
|
1179
|
+
// sobre null cuando el PFX no tiene el RUT en ningún campo conocido.
|
|
1180
|
+
const rutEnvia = this.certificado.rut || rutEmisor;
|
|
1181
|
+
const [rutEnviaNum, dvEnvia] = rutEnvia.split('-');
|
|
1178
1182
|
|
|
1179
1183
|
const boundary = '----WebKitFormBoundary' + Math.random().toString(36).substring(2);
|
|
1180
1184
|
|
package/cert/BoletaCert.js
CHANGED
|
@@ -214,7 +214,7 @@ class BoletaCert {
|
|
|
214
214
|
|
|
215
215
|
const envioBoleta = new EnvioBOLETA({
|
|
216
216
|
rutEmisor: this.emisor.rut,
|
|
217
|
-
rutEnvia: this.certificado.rut,
|
|
217
|
+
rutEnvia: this.certificado.rut || this.emisor.rut,
|
|
218
218
|
fchResol: this.resolucion.fecha,
|
|
219
219
|
nroResol: this.resolucion.numero,
|
|
220
220
|
certificado: this.certificado,
|
|
@@ -228,7 +228,7 @@ class BoletaCert {
|
|
|
228
228
|
// Carátula para certificación
|
|
229
229
|
envioBoleta.setCaratula({
|
|
230
230
|
RutEmisor: this.emisor.rut,
|
|
231
|
-
RutEnvia: this.certificado.rut,
|
|
231
|
+
RutEnvia: this.certificado.rut || this.emisor.rut,
|
|
232
232
|
RutReceptor: '60803000-K', // SII en certificación
|
|
233
233
|
FchResol: this.resolucion.fecha,
|
|
234
234
|
NroResol: this.resolucion.numero,
|
|
@@ -291,7 +291,7 @@ class BoletaCert {
|
|
|
291
291
|
// Establecer carátula
|
|
292
292
|
consumoFolio.setCaratula({
|
|
293
293
|
RutEmisor: this.emisor.rut,
|
|
294
|
-
RutEnvia: this.certificado.rut,
|
|
294
|
+
RutEnvia: this.certificado.rut || this.emisor.rut,
|
|
295
295
|
FchResol: this.resolucion.fecha,
|
|
296
296
|
NroResol: this.resolucion.numero,
|
|
297
297
|
SecEnvio: options?.secEnvio || 1,
|
|
@@ -358,7 +358,7 @@ class BoletaCert {
|
|
|
358
358
|
|
|
359
359
|
consumoFolio.setCaratula({
|
|
360
360
|
RutEmisor: this.emisor.rut,
|
|
361
|
-
RutEnvia: this.certificado.rut,
|
|
361
|
+
RutEnvia: this.certificado.rut || this.emisor.rut,
|
|
362
362
|
FchResol: this.resolucion.fecha,
|
|
363
363
|
NroResol: this.resolucion.numero,
|
|
364
364
|
SecEnvio: options.secEnvio,
|
package/package.json
CHANGED
|
@@ -1,12 +1,15 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@devlas/dte-sii",
|
|
3
|
-
"version": "2.12.
|
|
3
|
+
"version": "2.12.22",
|
|
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",
|
|
7
7
|
"license": "MIT",
|
|
8
8
|
"author": "Devlas SpA <ti@devlas.cl> (https://devlas.cl)",
|
|
9
9
|
"homepage": "https://github.com/devlas-cl/dte-sii",
|
|
10
|
+
"scripts": {
|
|
11
|
+
"test": "node test/pfx.test.js"
|
|
12
|
+
},
|
|
10
13
|
"repository": {
|
|
11
14
|
"type": "git",
|
|
12
15
|
"url": "https://github.com/devlas-cl/dte-sii.git"
|
package/utils/pfx.js
CHANGED
|
@@ -179,11 +179,48 @@ function extractSubjectFields(cert) {
|
|
|
179
179
|
}
|
|
180
180
|
|
|
181
181
|
/**
|
|
182
|
-
* Extraer RUT
|
|
182
|
+
* Extraer RUT desde el Subject Alternative Name (SAN) con OID
|
|
183
|
+
* 1.3.6.1.4.1.8321.1 — reservado bajo la normativa chilena de firma
|
|
184
|
+
* electrónica (Ley 19.799). A diferencia de serialNumber/OU/CN, que son
|
|
185
|
+
* convención de cada CA, este OID es consistente entre CAs (verificado
|
|
186
|
+
* contra Acepta, IDOK y Signapis — ver docs/EXTRACCION_RUT_CERTIFICADO.md).
|
|
187
|
+
* @param {forge.pki.Certificate} cert - Certificado
|
|
188
|
+
* @returns {string|null} RUT o null
|
|
189
|
+
*/
|
|
190
|
+
function extractRutFromSan(cert) {
|
|
191
|
+
const san = cert.getExtension('subjectAltName');
|
|
192
|
+
if (!san || !san.altNames) return null;
|
|
193
|
+
|
|
194
|
+
for (const alt of san.altNames) {
|
|
195
|
+
if (alt.type !== 0) continue; // 0 = otherName
|
|
196
|
+
try {
|
|
197
|
+
const oid = forge.asn1.derToOid(alt.value[0].value);
|
|
198
|
+
if (oid !== '1.3.6.1.4.1.8321.1') continue;
|
|
199
|
+
|
|
200
|
+
const inner = alt.value[1] && alt.value[1].value && alt.value[1].value[0];
|
|
201
|
+
if (!inner || !inner.value) continue;
|
|
202
|
+
|
|
203
|
+
const clean = inner.value.replace(/\./g, '').toUpperCase();
|
|
204
|
+
if (/^\d{7,8}-[\dK]$/.test(clean)) {
|
|
205
|
+
return clean;
|
|
206
|
+
}
|
|
207
|
+
} catch {
|
|
208
|
+
// OID no parseable en este altName — seguir con el resto y caer al fallback
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
return null;
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
/**
|
|
216
|
+
* Extraer RUT del certificado (desde SAN OID, serialNumber, OU o CN)
|
|
183
217
|
* @param {forge.pki.Certificate} cert - Certificado
|
|
184
218
|
* @returns {string|null} RUT o null
|
|
185
219
|
*/
|
|
186
220
|
function extractRutFromCertificate(cert) {
|
|
221
|
+
const sanRut = extractRutFromSan(cert);
|
|
222
|
+
if (sanRut) return sanRut;
|
|
223
|
+
|
|
187
224
|
const subject = extractSubjectFields(cert);
|
|
188
225
|
|
|
189
226
|
// Intentar desde serialNumber (más confiable)
|