@devlas/dte-sii 2.5.5 → 2.5.7
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/ConsumoFolio.js +3 -3
- package/EnviadorSII.js +51 -2
- package/Envio.js +9 -7
- package/SiiCertificacion.js +6 -3
- package/SiiPortalAuth.js +22 -19
- package/SiiSession.js +2 -2
- package/cert/ConfigLoader.js +4 -5
- package/package.json +1 -1
- package/utils/emisor.js +4 -2
package/ConsumoFolio.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
// Copyright (c) 2026 Devlas SpA — https://devlas.cl
|
|
2
|
-
// Licencia MIT. Ver archivo LICENSE para mas detalles.
|
|
1
|
+
// Copyright (c) 2026 Devlas SpA — https://devlas.cl
|
|
2
|
+
// Licencia MIT. Ver archivo LICENSE para mas detalles.
|
|
3
3
|
/**
|
|
4
4
|
* ConsumoFolio.js
|
|
5
5
|
*
|
|
@@ -224,7 +224,7 @@ class ConsumoFolio {
|
|
|
224
224
|
|
|
225
225
|
// Construir XML SIN indentación para C14N consistente
|
|
226
226
|
const schemaLoc = 'http://www.sii.cl/SiiDte ConsumoFolio_v10.xsd';
|
|
227
|
-
const xmlSinFirma = `<?xml version="1.0" encoding="
|
|
227
|
+
const xmlSinFirma = `<?xml version="1.0" encoding="UTF-8"?>
|
|
228
228
|
<ConsumoFolios xmlns="http://www.sii.cl/SiiDte" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="${schemaLoc}" version="1.0"><DocumentoConsumoFolios ID="${this.id}"><Caratula version="1.0"><RutEmisor>${this.caratula.RutEmisor}</RutEmisor><RutEnvia>${this.caratula.RutEnvia}</RutEnvia><FchResol>${this.caratula.FchResol}</FchResol><NroResol>${this.caratula.NroResol}</NroResol><FchInicio>${this.caratula.FchInicio}</FchInicio><FchFinal>${this.caratula.FchFinal}</FchFinal><SecEnvio>${this.caratula.SecEnvio}</SecEnvio><TmstFirmaEnv>${this.caratula.TmstFirmaEnv}</TmstFirmaEnv></Caratula>${resumenXml}</DocumentoConsumoFolios></ConsumoFolios>`;
|
|
229
229
|
|
|
230
230
|
// Firmar el documento
|
package/EnviadorSII.js
CHANGED
|
@@ -396,7 +396,7 @@ class EnviadorSII {
|
|
|
396
396
|
|
|
397
397
|
log.log('SignedInfo para firmar (length):', signedInfoParaFirmar.length);
|
|
398
398
|
|
|
399
|
-
const xmlFirmado = `<?xml version="1.0" encoding="
|
|
399
|
+
const xmlFirmado = `<?xml version="1.0" encoding="UTF-8"?>
|
|
400
400
|
<getToken><item><Semilla>${semilla}</Semilla></item><Signature xmlns="http://www.w3.org/2000/09/xmldsig#"><SignedInfo xmlns="http://www.w3.org/2000/09/xmldsig#"><CanonicalizationMethod Algorithm="http://www.w3.org/TR/2001/REC-xml-c14n-20010315"/><SignatureMethod Algorithm="http://www.w3.org/2000/09/xmldsig#rsa-sha1"/><Reference URI=""><Transforms><Transform Algorithm="http://www.w3.org/2000/09/xmldsig#enveloped-signature"/></Transforms><DigestMethod Algorithm="http://www.w3.org/2000/09/xmldsig#sha1"/><DigestValue>${digestValue}</DigestValue></Reference></SignedInfo><SignatureValue>${signatureValue}</SignatureValue><KeyInfo><KeyValue><RSAKeyValue><Modulus>${modulus}</Modulus><Exponent>${exponent}</Exponent></RSAKeyValue></KeyValue><X509Data><X509Certificate>${cert}</X509Certificate></X509Data></KeyInfo></Signature></getToken>`;
|
|
401
401
|
|
|
402
402
|
log.log('DigestValue:', digestValue);
|
|
@@ -528,7 +528,7 @@ class EnviadorSII {
|
|
|
528
528
|
const url = this.urls[this.ambiente].envio;
|
|
529
529
|
|
|
530
530
|
if (!xml.startsWith('<?xml')) {
|
|
531
|
-
xml = '<?xml version="1.0" encoding="
|
|
531
|
+
xml = '<?xml version="1.0" encoding="UTF-8"?>\n' + xml;
|
|
532
532
|
}
|
|
533
533
|
|
|
534
534
|
const [rutSenderStr, dvSender] = rutEnvia.split('-');
|
|
@@ -873,6 +873,55 @@ class EnviadorSII {
|
|
|
873
873
|
mensaje = `❌ Rechazado: ${glosa || 'Error en documento'}`;
|
|
874
874
|
esRechazado = true;
|
|
875
875
|
break;
|
|
876
|
+
// Códigos numéricos del SII (QueryEstUp)
|
|
877
|
+
case '-11':
|
|
878
|
+
mensaje = `⏳ Pendiente de proceso - El sobre aún no ha sido revisado por el SII`;
|
|
879
|
+
esIntermedio = true;
|
|
880
|
+
break;
|
|
881
|
+
case '-10':
|
|
882
|
+
mensaje = `❌ Token inválido o no autorizado`;
|
|
883
|
+
esRechazado = true;
|
|
884
|
+
break;
|
|
885
|
+
case '-9':
|
|
886
|
+
mensaje = `❌ Firma del envío inválida - ${glosa || 'Verificar certificado'}`;
|
|
887
|
+
esRechazado = true;
|
|
888
|
+
break;
|
|
889
|
+
case '-8':
|
|
890
|
+
mensaje = `❌ El envío no pertenece al firmante`;
|
|
891
|
+
esRechazado = true;
|
|
892
|
+
break;
|
|
893
|
+
case '-7':
|
|
894
|
+
mensaje = `❌ Error en datos del receptor - ${glosa || 'Verificar RUT receptor'}`;
|
|
895
|
+
esRechazado = true;
|
|
896
|
+
break;
|
|
897
|
+
case '-6':
|
|
898
|
+
mensaje = `❌ Error en datos del emisor - ${glosa || 'Verificar RUT emisor'}`;
|
|
899
|
+
esRechazado = true;
|
|
900
|
+
break;
|
|
901
|
+
case '-5':
|
|
902
|
+
mensaje = `❌ Error de certificación - ${glosa || 'Verificar proceso de certificación'}`;
|
|
903
|
+
esRechazado = true;
|
|
904
|
+
break;
|
|
905
|
+
case '-4':
|
|
906
|
+
mensaje = `❌ Envío fuera de plazo - ${glosa || 'El plazo de envío ha vencido'}`;
|
|
907
|
+
esRechazado = true;
|
|
908
|
+
break;
|
|
909
|
+
case '-3':
|
|
910
|
+
mensaje = `❌ RUT emisor no coincide con el certificado`;
|
|
911
|
+
esRechazado = true;
|
|
912
|
+
break;
|
|
913
|
+
case '-2':
|
|
914
|
+
mensaje = `❌ Error de firma en el sobre - ${glosa || 'Verificar firma digital'}`;
|
|
915
|
+
esRechazado = true;
|
|
916
|
+
break;
|
|
917
|
+
case '-1':
|
|
918
|
+
mensaje = `❌ Error de schema XML - ${glosa || 'El XML no cumple el XSD del SII'}`;
|
|
919
|
+
esRechazado = true;
|
|
920
|
+
break;
|
|
921
|
+
case '0':
|
|
922
|
+
mensaje = `⏳ Enviado al SII, pendiente de validación`;
|
|
923
|
+
esIntermedio = true;
|
|
924
|
+
break;
|
|
876
925
|
default:
|
|
877
926
|
mensaje = `Estado: ${estado} - ${glosa || 'Sin descripción'}`;
|
|
878
927
|
}
|
package/Envio.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
// Copyright (c) 2026 Devlas SpA — https://devlas.cl
|
|
2
|
-
// Licencia MIT. Ver archivo LICENSE para mas detalles.
|
|
1
|
+
// Copyright (c) 2026 Devlas SpA — https://devlas.cl
|
|
2
|
+
// Licencia MIT. Ver archivo LICENSE para mas detalles.
|
|
3
3
|
/**
|
|
4
4
|
* Clases de Envío (EnvioBOLETA, EnvioDTE)
|
|
5
5
|
*
|
|
@@ -7,6 +7,7 @@
|
|
|
7
7
|
*/
|
|
8
8
|
|
|
9
9
|
const Signer = require('./Signer');
|
|
10
|
+
const { formatRutSii } = require('./utils/rut');
|
|
10
11
|
|
|
11
12
|
// ============================================
|
|
12
13
|
// CLASE BASE ENVIO
|
|
@@ -70,7 +71,8 @@ class EnvioBase {
|
|
|
70
71
|
const yyyy = now.getFullYear();
|
|
71
72
|
const hh = String(now.getHours()).padStart(2, '0');
|
|
72
73
|
const min = String(now.getMinutes()).padStart(2, '0');
|
|
73
|
-
|
|
74
|
+
const ss = String(now.getSeconds()).padStart(2, '0');
|
|
75
|
+
return `${prefix}_${dd}_${mm}_${yyyy}_${hh}_${min}_${ss}`;
|
|
74
76
|
}
|
|
75
77
|
|
|
76
78
|
/**
|
|
@@ -106,8 +108,8 @@ class EnvioBOLETA extends EnvioBase {
|
|
|
106
108
|
const timestamp = caratula.TmstFirmaEnv || this._generateTimestamp();
|
|
107
109
|
|
|
108
110
|
this.caratula = {
|
|
109
|
-
RutEmisor: caratula.RutEmisor,
|
|
110
|
-
RutEnvia: caratula.RutEnvia,
|
|
111
|
+
RutEmisor: formatRutSii(caratula.RutEmisor), // sin puntos, maxLength=10
|
|
112
|
+
RutEnvia: formatRutSii(caratula.RutEnvia), // sin puntos
|
|
111
113
|
RutReceptor: '60803000-K', // Siempre SII para boletas
|
|
112
114
|
FchResol: _normDateXsd(caratula.FchResol),
|
|
113
115
|
NroResol: caratula.NroResol,
|
|
@@ -126,7 +128,7 @@ class EnvioBOLETA extends EnvioBase {
|
|
|
126
128
|
.map(s => `<SubTotDTE><TpoDTE>${s.TpoDTE}</TpoDTE><NroDTE>${s.NroDTE}</NroDTE></SubTotDTE>`)
|
|
127
129
|
.join('');
|
|
128
130
|
|
|
129
|
-
const xmlBase = `<?xml version="1.0" encoding="
|
|
131
|
+
const xmlBase = `<?xml version="1.0" encoding="UTF-8"?>
|
|
130
132
|
<EnvioBOLETA xmlns="http://www.sii.cl/SiiDte" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.sii.cl/SiiDte EnvioBOLETA_v11.xsd" version="1.0"><SetDTE ID="${this.setId}"><Caratula version="1.0"><RutEmisor>${this.caratula.RutEmisor}</RutEmisor><RutEnvia>${this.caratula.RutEnvia}</RutEnvia><RutReceptor>${this.caratula.RutReceptor}</RutReceptor><FchResol>${this.caratula.FchResol}</FchResol><NroResol>${this.caratula.NroResol}</NroResol><TmstFirmaEnv>${this.caratula.TmstFirmaEnv}</TmstFirmaEnv>${subTotDTEs}</Caratula><!-- DTES_PLACEHOLDER --></SetDTE></EnvioBOLETA>`;
|
|
131
133
|
|
|
132
134
|
const dtesXml = this._extractDTEsXml();
|
|
@@ -180,7 +182,7 @@ class EnvioDTE extends EnvioBase {
|
|
|
180
182
|
.map(s => `<SubTotDTE><TpoDTE>${s.TpoDTE}</TpoDTE><NroDTE>${s.NroDTE}</NroDTE></SubTotDTE>`)
|
|
181
183
|
.join('');
|
|
182
184
|
|
|
183
|
-
const xmlBase = `<?xml version="1.0" encoding="
|
|
185
|
+
const xmlBase = `<?xml version="1.0" encoding="UTF-8"?>
|
|
184
186
|
<EnvioDTE xmlns="http://www.sii.cl/SiiDte" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.sii.cl/SiiDte EnvioDTE_v10.xsd" version="1.0"><SetDTE ID="${this.setId}"><Caratula version="1.0"><RutEmisor>${this.caratula.RutEmisor}</RutEmisor><RutEnvia>${this.caratula.RutEnvia}</RutEnvia><RutReceptor>${this.caratula.RutReceptor}</RutReceptor><FchResol>${this.caratula.FchResol}</FchResol><NroResol>${this.caratula.NroResol}</NroResol><TmstFirmaEnv>${this.caratula.TmstFirmaEnv}</TmstFirmaEnv>${subTotDTEs}</Caratula><!-- DTES_PLACEHOLDER --></SetDTE></EnvioDTE>`;
|
|
185
187
|
|
|
186
188
|
const dtesXml = this._extractDTEsXml();
|
package/SiiCertificacion.js
CHANGED
|
@@ -221,10 +221,13 @@ class SiiCertificacion {
|
|
|
221
221
|
TOTAL: totalMatch ? totalMatch[1] : '15',
|
|
222
222
|
};
|
|
223
223
|
|
|
224
|
-
// Marcar
|
|
225
|
-
//
|
|
224
|
+
// Marcar solo los sets opcionales configurados (DEFAULT_SETS_OPCIONALES)
|
|
225
|
+
// No marcar todos para evitar incluir Exportación, Liquidación, etc.
|
|
226
|
+
const requestedSets = options.setsOpcionales || {};
|
|
226
227
|
for (const set of setsOpcionales) {
|
|
227
|
-
|
|
228
|
+
if (requestedSets[set.id]) {
|
|
229
|
+
formData[set.id] = 'S';
|
|
230
|
+
}
|
|
228
231
|
}
|
|
229
232
|
// SET01 (básico) siempre incluido aunque no aparezca como checkbox opcional
|
|
230
233
|
formData.SET01 = 'S';
|
package/SiiPortalAuth.js
CHANGED
|
@@ -230,7 +230,10 @@ class SiiPortalAuth {
|
|
|
230
230
|
const loc = res.headers['location'] || '';
|
|
231
231
|
if (loc.includes('InicioAutenticacion') || loc.includes('IngresoRutClave')) return false;
|
|
232
232
|
// Si el body contiene formulario de empresa → válida
|
|
233
|
-
|
|
233
|
+
const valida = res.status === 200 && (res.body.includes('RUT_EMP') || res.body.includes('ad_empresa'));
|
|
234
|
+
// Refrescar timestamp del caché para extender TTL mientras la sesión se usa activamente
|
|
235
|
+
if (valida) SiiPortalAuth._guardarSesionCache(this._certHash, cookieJar);
|
|
236
|
+
return valida;
|
|
234
237
|
} catch {
|
|
235
238
|
return false;
|
|
236
239
|
}
|
|
@@ -242,8 +245,8 @@ class SiiPortalAuth {
|
|
|
242
245
|
if (!fs.existsSync(SESSION_CACHE_PATH)) return null;
|
|
243
246
|
const data = JSON.parse(fs.readFileSync(SESSION_CACHE_PATH, 'utf8'));
|
|
244
247
|
if (data.certHash !== certHash) return null;
|
|
245
|
-
// TTL:
|
|
246
|
-
if (Date.now() - data.ts >
|
|
248
|
+
// TTL: 90 minutos (SII permite ~2h de inactividad; se refresca en cada validación)
|
|
249
|
+
if (Date.now() - data.ts > 90 * 60 * 1000) return null;
|
|
247
250
|
return data.cookies;
|
|
248
251
|
} catch {
|
|
249
252
|
return null;
|
|
@@ -412,22 +415,22 @@ class SiiPortalAuth {
|
|
|
412
415
|
*/
|
|
413
416
|
static _parsearTablaEmpresa(html) {
|
|
414
417
|
const datos = {};
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
if (celdas.length
|
|
418
|
+
const decode = s => s
|
|
419
|
+
.replace(/<[^>]+>/g, '')
|
|
420
|
+
.replace(/ /gi, '')
|
|
421
|
+
.replace(/ó/g, 'ó')
|
|
422
|
+
.replace(/á/g, 'á')
|
|
423
|
+
.replace(/é/g, 'é')
|
|
424
|
+
.replace(/í/g, 'í')
|
|
425
|
+
.replace(/ú/g, 'ú')
|
|
426
|
+
.replace(/ñ/g, 'ñ')
|
|
427
|
+
.replace(/&/g, '&')
|
|
428
|
+
.trim();
|
|
429
|
+
|
|
430
|
+
// Dividir por apertura <TR> — HTML 4.01 no exige tags </TR> de cierre
|
|
431
|
+
for (const seg of html.split(/<tr[^>]*>/i)) {
|
|
432
|
+
const celdas = [...seg.matchAll(/<td[^>]*>([\s\S]*?)<\/td>/gi)].map(m => decode(m[1]));
|
|
433
|
+
if (celdas.length >= 2 && celdas[0]) datos[celdas[0]] = celdas[1];
|
|
431
434
|
}
|
|
432
435
|
|
|
433
436
|
// Buscar por regex para ser resiliente ante variaciones de encoding / tildes
|
package/SiiSession.js
CHANGED
|
@@ -355,7 +355,7 @@ class SiiSession {
|
|
|
355
355
|
cookieJar: this.cookieJar,
|
|
356
356
|
baseHost: this.baseHost,
|
|
357
357
|
savedAt: Date.now(),
|
|
358
|
-
expiresAt: Date.now() + (
|
|
358
|
+
expiresAt: Date.now() + (90 * 60 * 1000), // 90 minutos de validez
|
|
359
359
|
};
|
|
360
360
|
fs.writeFileSync(filePath, JSON.stringify(sessionData, null, 2), 'utf8');
|
|
361
361
|
}
|
|
@@ -375,7 +375,7 @@ class SiiSession {
|
|
|
375
375
|
|
|
376
376
|
// Verificar que la sesión no haya expirado
|
|
377
377
|
if (data.expiresAt && Date.now() > data.expiresAt) {
|
|
378
|
-
console.log('Sesión SII expirada, se requiere nuevo login');
|
|
378
|
+
console.log('Sesión SII (maullin) expirada, se requiere nuevo login');
|
|
379
379
|
return false;
|
|
380
380
|
}
|
|
381
381
|
|
package/cert/ConfigLoader.js
CHANGED
|
@@ -39,11 +39,10 @@ function loadConfig(options = {}) {
|
|
|
39
39
|
return path.isAbsolute(filePath) ? filePath : path.resolve(baseDir, filePath);
|
|
40
40
|
};
|
|
41
41
|
|
|
42
|
-
// Validar variables requeridas
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
throw new Error(`Variables de entorno faltantes: ${missing.join(', ')}`);
|
|
42
|
+
// Validar variables requeridas (solo CERT_PATH es estrictamente necesario;
|
|
43
|
+
// el resto puede no estar si los datos vienen del portal SII vía getEmisorFromPortal)
|
|
44
|
+
if (!process.env.CERT_PATH) {
|
|
45
|
+
throw new Error('Variable de entorno faltante: CERT_PATH');
|
|
47
46
|
}
|
|
48
47
|
|
|
49
48
|
// Construir objetos de configuración
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@devlas/dte-sii",
|
|
3
|
-
"version": "2.5.
|
|
3
|
+
"version": "2.5.7",
|
|
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",
|
package/utils/emisor.js
CHANGED
|
@@ -94,9 +94,11 @@ function buildEmisorBoleta(config) {
|
|
|
94
94
|
* @returns {Object} Emisor normalizado
|
|
95
95
|
*/
|
|
96
96
|
function normalizeEmisor(emisor, esBoleta = false) {
|
|
97
|
+
// El schema SII no permite puntos en RUTEmisor (maxLength=10). Siempre normalizar.
|
|
98
|
+
const rutNorm = formatRutSii(emisor.RUTEmisor || '');
|
|
97
99
|
if (esBoleta) {
|
|
98
100
|
return {
|
|
99
|
-
RUTEmisor:
|
|
101
|
+
RUTEmisor: rutNorm,
|
|
100
102
|
RznSocEmisor: sanitizeSiiText(emisor.RznSocEmisor || emisor.RznSoc),
|
|
101
103
|
GiroEmisor: sanitizeSiiText(emisor.GiroEmisor || emisor.GiroEmis),
|
|
102
104
|
DirOrigen: sanitizeSiiText(emisor.DirOrigen),
|
|
@@ -106,7 +108,7 @@ function normalizeEmisor(emisor, esBoleta = false) {
|
|
|
106
108
|
}
|
|
107
109
|
|
|
108
110
|
const result = {
|
|
109
|
-
RUTEmisor:
|
|
111
|
+
RUTEmisor: rutNorm,
|
|
110
112
|
RznSoc: sanitizeSiiText(emisor.RznSoc || emisor.RznSocEmisor),
|
|
111
113
|
GiroEmis: sanitizeSiiText(emisor.GiroEmis || emisor.GiroEmisor),
|
|
112
114
|
};
|