@devlas/dte-sii 2.5.5 → 2.5.6

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 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="ISO-8859-1"?>
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="ISO-8859-1"?>
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="ISO-8859-1"?>\n' + xml;
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
@@ -106,8 +107,8 @@ class EnvioBOLETA extends EnvioBase {
106
107
  const timestamp = caratula.TmstFirmaEnv || this._generateTimestamp();
107
108
 
108
109
  this.caratula = {
109
- RutEmisor: caratula.RutEmisor,
110
- RutEnvia: caratula.RutEnvia,
110
+ RutEmisor: formatRutSii(caratula.RutEmisor), // sin puntos, maxLength=10
111
+ RutEnvia: formatRutSii(caratula.RutEnvia), // sin puntos
111
112
  RutReceptor: '60803000-K', // Siempre SII para boletas
112
113
  FchResol: _normDateXsd(caratula.FchResol),
113
114
  NroResol: caratula.NroResol,
@@ -126,7 +127,7 @@ class EnvioBOLETA extends EnvioBase {
126
127
  .map(s => `<SubTotDTE><TpoDTE>${s.TpoDTE}</TpoDTE><NroDTE>${s.NroDTE}</NroDTE></SubTotDTE>`)
127
128
  .join('');
128
129
 
129
- const xmlBase = `<?xml version="1.0" encoding="ISO-8859-1"?>
130
+ const xmlBase = `<?xml version="1.0" encoding="UTF-8"?>
130
131
  <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
132
 
132
133
  const dtesXml = this._extractDTEsXml();
@@ -180,7 +181,7 @@ class EnvioDTE extends EnvioBase {
180
181
  .map(s => `<SubTotDTE><TpoDTE>${s.TpoDTE}</TpoDTE><NroDTE>${s.NroDTE}</NroDTE></SubTotDTE>`)
181
182
  .join('');
182
183
 
183
- const xmlBase = `<?xml version="1.0" encoding="ISO-8859-1"?>
184
+ const xmlBase = `<?xml version="1.0" encoding="UTF-8"?>
184
185
  <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
186
 
186
187
  const dtesXml = this._extractDTEsXml();
@@ -221,10 +221,13 @@ class SiiCertificacion {
221
221
  TOTAL: totalMatch ? totalMatch[1] : '15',
222
222
  };
223
223
 
224
- // Marcar TODOS los checkboxes disponibles en la página del SII (dinámico)
225
- // Esto evita que sets nuevos que agrega el SII queden sin marcar
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
- formData[set.id] = 'S';
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
- return res.status === 200 && (res.body.includes('RUT_EMP') || res.body.includes('ad_empresa'));
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: 25 minutos (el SII expira sesiones ~30 min de inactividad)
246
- if (Date.now() - data.ts > 25 * 60 * 1000) return null;
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
- for (const row of html.matchAll(/<tr[^>]*>[\s\S]*?<\/tr>/gi)) {
417
- const celdas = [...row[0].matchAll(/<td[^>]*>([\s\S]*?)<\/td>/gi)]
418
- .map(m => m[1]
419
- .replace(/<[^>]+>/g, '')
420
- .replace(/&nbsp;/gi, '')
421
- .replace(/&oacute;/g, 'ó')
422
- .replace(/&aacute;/g, 'á')
423
- .replace(/&eacute;/g, 'é')
424
- .replace(/&iacute;/g, 'í')
425
- .replace(/&uacute;/g, 'ú')
426
- .replace(/&ntilde;/g, 'ñ')
427
- .replace(/&amp;/g, '&')
428
- .trim()
429
- );
430
- if (celdas.length === 2 && celdas[0]) datos[celdas[0]] = celdas[1];
418
+ const decode = s => s
419
+ .replace(/<[^>]+>/g, '')
420
+ .replace(/&nbsp;/gi, '')
421
+ .replace(/&oacute;/g, 'ó')
422
+ .replace(/&aacute;/g, 'á')
423
+ .replace(/&eacute;/g, 'é')
424
+ .replace(/&iacute;/g, 'í')
425
+ .replace(/&uacute;/g, 'ú')
426
+ .replace(/&ntilde;/g, 'ñ')
427
+ .replace(/&amp;/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() + (25 * 60 * 1000), // 25 minutos de validez
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
 
@@ -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
- const required = ['CERT_PATH', 'EMISOR_RUT', 'EMISOR_RAZON_SOCIAL'];
44
- const missing = required.filter(v => !process.env[v]);
45
- if (missing.length > 0) {
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.5",
3
+ "version": "2.5.6",
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: emisor.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: emisor.RUTEmisor,
111
+ RUTEmisor: rutNorm,
110
112
  RznSoc: sanitizeSiiText(emisor.RznSoc || emisor.RznSocEmisor),
111
113
  GiroEmis: sanitizeSiiText(emisor.GiroEmis || emisor.GiroEmisor),
112
114
  };