@devlas/dte-sii 2.12.19 → 2.12.21

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/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
- const [rutEnviaNum, dvEnvia] = this.certificado.rut.split('-');
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
 
@@ -1281,6 +1281,13 @@ class SiiCertificacion {
1281
1281
  const formAction = SiiSession.extractFormAction(body);
1282
1282
  if (formAction && formAction.includes('pe_avance9')) {
1283
1283
  const hiddenPaso3 = SiiSession.extractInputValues(body);
1284
+ // El HTML tiene DOS botones (CONFIRMAR="Confirmar Declaración" y Salir="No Confirmar",
1285
+ // este último con TYPE=button). extractInputValues lee <input> sin distinguir el type,
1286
+ // así que Salir queda en hiddenPaso3 con valor "No Confirmar". Un browser real solo
1287
+ // envía el par name/value del botón que el usuario clickeó — nunca ambos. Si se manda
1288
+ // Salir=No Confirmar junto con CONFIRMAR, el SII podría interpretar la presencia de ese
1289
+ // campo como "el usuario canceló", pisando la confirmación real.
1290
+ delete hiddenPaso3.Salir;
1284
1291
  const opcFields = {};
1285
1292
  for (const key of Object.keys(hiddenPaso3)) {
1286
1293
  if (/^OPC\d+$/.test(key)) opcFields[key] = 'S';
@@ -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.19",
3
+ "version": "2.12.21",
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 del certificado (desde serialNumber, OU o CN)
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)