@devlas/dte-sii 2.14.3 → 2.16.0
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 +7 -7
- package/EnviadorSII.js +1 -1
- package/FolioService.js +2 -2
- package/README.md +146 -25
- package/SiiPortalAuth.js +394 -32
- package/SiiSession.js +2 -2
- package/cert/BoletaCert.js +5 -1
- package/cert/CertRunner.js +79 -14
- package/cert/SetBase.js +1 -1
- package/cert/SetParser.js +15 -1
- package/index.js +1 -1
- package/package.json +2 -2
- package/utils/c14n.js +1 -1
- package/utils/pfx.js +1 -1
- package/test-qdetestlibro.js +0 -174
package/CafSolicitor.js
CHANGED
|
@@ -37,7 +37,7 @@ class CafSolicitor {
|
|
|
37
37
|
/**
|
|
38
38
|
* @param {Object} options - Opciones de configuración
|
|
39
39
|
* @param {string} options.ambiente - 'certificacion' o 'produccion'
|
|
40
|
-
* @param {string} options.rutEmisor - RUT del emisor (ej:
|
|
40
|
+
* @param {string} options.rutEmisor - RUT del emisor (ej: 76543210-K)
|
|
41
41
|
* @param {string} options.pfxPath - Ruta absoluta al certificado PFX
|
|
42
42
|
* @param {string} options.pfxPassword - Contraseña del certificado
|
|
43
43
|
* @param {string} [options.baseDir] - Directorio base para guardar archivos
|
|
@@ -194,7 +194,7 @@ class CafSolicitor {
|
|
|
194
194
|
* Detecta la página de bloqueo duro de timbraje del SII.
|
|
195
195
|
*
|
|
196
196
|
* El SII usa al menos DOS redacciones para la misma página de rechazo:
|
|
197
|
-
* - "NO SE AUTORIZA TIMBRAJE ELECTRÓNICO" (
|
|
197
|
+
* - "NO SE AUTORIZA TIMBRAJE ELECTRÓNICO" (ver el CHANGELOG)
|
|
198
198
|
* - "NO AUTORIZA TIMBRAJE ELECTRÓNICA" (observada 2026-07-22 en tipo 56)
|
|
199
199
|
*
|
|
200
200
|
* Detectar solo la primera hacía que la segunda cayera al genérico
|
|
@@ -247,7 +247,7 @@ class CafSolicitor {
|
|
|
247
247
|
* Verificación de Actividades sale explícito ("no registra Verificación de
|
|
248
248
|
* Actividades..."), en producción el SII solo dice esto — mismo texto para tipo 39
|
|
249
249
|
* (boleta) y 33 (factura), sin indicar la causa real. Verificado 2026-07-24 contra
|
|
250
|
-
*
|
|
250
|
+
* 79555666-7, en ambos tipos, con el mismo request real repetido dos veces.
|
|
251
251
|
*
|
|
252
252
|
* Sin esta detección caía en el genérico `UNKNOWN: No se obtuvo CAF en la
|
|
253
253
|
* respuesta`, que no distingue este caso (probable Verificación de Actividades,
|
|
@@ -281,7 +281,7 @@ class CafSolicitor {
|
|
|
281
281
|
* usuarios ni timbrar. Sin este mensaje el usuario ve un error genérico y vuelve a
|
|
282
282
|
* intentar sin saber que lo único que corresponde es ir al SII.
|
|
283
283
|
*
|
|
284
|
-
* Caso real (12/08/2026, RUT
|
|
284
|
+
* Caso real (12/08/2026, RUT 79555666-7): Verificación de Actividades aprobada,
|
|
285
285
|
* usuario enrolado en maullin y certificación de boleta enviada — y aun así palena
|
|
286
286
|
* rechazaba todo, porque la inscripción por internet estaba bloqueada de origen.
|
|
287
287
|
*/
|
|
@@ -300,7 +300,7 @@ class CafSolicitor {
|
|
|
300
300
|
* autorizado como emisor electrónico en producción — no hay nada que arreglar del lado
|
|
301
301
|
* del enrolamiento, porque el portal ni siquiera deja abrir la mantención de usuarios.
|
|
302
302
|
*
|
|
303
|
-
* Caso real (12/08/2026, RUT
|
|
303
|
+
* Caso real (12/08/2026, RUT 79555666-7): el flujo de enrolamiento siguió de largo con
|
|
304
304
|
* páginas vacías —sin formulario ni hidden `key`— hasta reventar con un 500 en
|
|
305
305
|
* `eu_graba_usuario`. El 500 era el síntoma; esta frase, en el PRIMER paso, la causa.
|
|
306
306
|
*/
|
|
@@ -313,7 +313,7 @@ class CafSolicitor {
|
|
|
313
313
|
* ¿Este HTML es un rechazo que hace inútil seguir el flujo?
|
|
314
314
|
*
|
|
315
315
|
* ⚠️ Los rechazos del SII NO llegan siempre en la última respuesta. Este caso real
|
|
316
|
-
* (12/08/2026, RUT
|
|
316
|
+
* (12/08/2026, RUT 79555666-7, tipo 39 en palena) llegó en el PASO 2
|
|
317
317
|
* (`of_solicita_folios_dcto`), donde solo se miraba `esBloqueoTimbraje`. El flujo
|
|
318
318
|
* siguió de largo y terminó devolviendo el genérico `UNKNOWN: No se obtuvo CAF`,
|
|
319
319
|
* escondiendo un mensaje que la librería ya sabía interpretar desde julio.
|
|
@@ -381,7 +381,7 @@ class CafSolicitor {
|
|
|
381
381
|
* anulado completamente... los documentos que el Servicio reciba con dichos folios
|
|
382
382
|
* serán rechazados". Por eso hay que abrir cada rango para saber si sirve, y por eso
|
|
383
383
|
* este método hace un request por rango. Verificado en maullin el 14/08/2026 con el
|
|
384
|
-
* RUT
|
|
384
|
+
* RUT 76543210-K: de 6 rangos listados en el tipo 56, solo 2 eran usables.
|
|
385
385
|
*
|
|
386
386
|
* @param {number} tipoDte
|
|
387
387
|
* @returns {Promise<Array<{ campos: Object, folioDesde: number, folioHasta: number,
|
package/EnviadorSII.js
CHANGED
|
@@ -1195,7 +1195,7 @@ class EnviadorSII {
|
|
|
1195
1195
|
|
|
1196
1196
|
const [rutNum, dv] = rutEmisor.split('-');
|
|
1197
1197
|
// Fallback a rutEmisor si el certificado no expone RUT (ver
|
|
1198
|
-
//
|
|
1198
|
+
// ver el JSDoc de Certificado) — evita un TypeError por .split
|
|
1199
1199
|
// sobre null cuando el PFX no tiene el RUT en ningún campo conocido.
|
|
1200
1200
|
const rutEnvia = this.certificado.rut || rutEmisor;
|
|
1201
1201
|
const [rutEnviaNum, dvEnvia] = rutEnvia.split('-');
|
package/FolioService.js
CHANGED
|
@@ -167,7 +167,7 @@ class FolioService {
|
|
|
167
167
|
// tipo entraba como candidato válido.
|
|
168
168
|
//
|
|
169
169
|
// Pasó de verdad (14/08/2026): al reusar CAF previos, los tipos 56 y 61 del
|
|
170
|
-
// RUT
|
|
170
|
+
// RUT 76543210-K resolvieron a CAF de 77111222-3. El timbre quedó firmado con
|
|
171
171
|
// la llave de otro contribuyente y el SII devolvió `RFR - Rechazado por Error
|
|
172
172
|
// en Firma` para todo el envío, lo que a su vez trababa la declaración de
|
|
173
173
|
// simulación ("no debe contener documentos con reparos o rechazos").
|
|
@@ -761,7 +761,7 @@ class FolioService {
|
|
|
761
761
|
// negativas: son resultados esperados (ese rango ya estaba anulado, o sus folios ya
|
|
762
762
|
// se usaron) y aparecen mezclados con anulaciones exitosas.
|
|
763
763
|
//
|
|
764
|
-
// Contarlos rompía la limpieza (medido el 13/08/2026, RUT
|
|
764
|
+
// Contarlos rompía la limpieza (medido el 13/08/2026, RUT 76543210-K, tipo 33):
|
|
765
765
|
// 20 rangos a procesar
|
|
766
766
|
// ✓ 47-49 anulado ✓ 43-46 anulado
|
|
767
767
|
// ✗ 31-34 ya anulado ✗ 27-30 ya anulado → CORTE, 8 rangos sin intentar
|
package/README.md
CHANGED
|
@@ -25,6 +25,7 @@ npm install @devlas/dte-sii
|
|
|
25
25
|
- [Libros electrónicos y RCOF](#libros-electrónicos-y-rcof)
|
|
26
26
|
- [Gestión de folios](#gestión-de-folios)
|
|
27
27
|
- [Sesión y autenticación con el SII](#sesión-y-autenticación-con-el-sii)
|
|
28
|
+
- [Descargar el XML completo desde el portal (Respaldo MIPYME)](#descargar-el-xml-completo-desde-el-portal-respaldo-mipyme)
|
|
28
29
|
- [Aceptación y reclamo de DTE (WsReclamo)](#aceptación-y-reclamo-de-dte-wsreclamo)
|
|
29
30
|
- [Estados SII: Interpretación de respuestas](#estados-sii-interpretación-de-respuestas)
|
|
30
31
|
- [Manejo de errores](#manejo-de-errores)
|
|
@@ -69,7 +70,7 @@ const caf = new CAF(fs.readFileSync('caf_33.xml', 'utf8'))
|
|
|
69
70
|
const dte = new DTE({
|
|
70
71
|
Encabezado: {
|
|
71
72
|
IdDoc: { TipoDTE: 33, Folio: 1 },
|
|
72
|
-
Emisor: { RUTEmisor: '
|
|
73
|
+
Emisor: { RUTEmisor: '76543210-K', RznSoc: 'Mi Empresa SpA', GiroEmis: 'Software', DirOrigen: 'Av. Ejemplo 123', CmnaOrigen: 'Santiago', Acteco: 620200 },
|
|
73
74
|
Receptor: { RUTRecep: '12345678-9', RznSocRecep: 'Cliente SA', GiroRecep: 'Comercio', DirRecep: 'Calle 456', CmnaRecep: 'Providencia' },
|
|
74
75
|
},
|
|
75
76
|
Detalle: [
|
|
@@ -81,7 +82,7 @@ dte.generarXML().timbrar(caf).firmar(cert)
|
|
|
81
82
|
|
|
82
83
|
const envio = new EnvioDTE({ certificado: cert })
|
|
83
84
|
envio.agregar(dte)
|
|
84
|
-
envio.setCaratula({ RutEmisor: '
|
|
85
|
+
envio.setCaratula({ RutEmisor: '76543210-K', RutReceptor: '60803000-K', FchResol: '2024-01-15', NroResol: 123 })
|
|
85
86
|
envio.generar()
|
|
86
87
|
|
|
87
88
|
const enviador = new EnviadorSII(cert, 'produccion') // o 'certificacion'
|
|
@@ -121,7 +122,7 @@ const dte = new DTE({
|
|
|
121
122
|
tipo: 33,
|
|
122
123
|
folio: 1,
|
|
123
124
|
emisor: {
|
|
124
|
-
rut: '
|
|
125
|
+
rut: '76543210-K', razonSocial: 'Mi Empresa SpA',
|
|
125
126
|
giro: 'Desarrollo de software', direccion: 'Av. Ejemplo 123',
|
|
126
127
|
comuna: 'Santiago', actividadEconomica: 620200,
|
|
127
128
|
},
|
|
@@ -158,7 +159,7 @@ const { EnvioDTE, EnviadorSII } = require('@devlas/dte-sii')
|
|
|
158
159
|
const envio = new EnvioDTE({ certificado: cert })
|
|
159
160
|
envio.agregar(dte)
|
|
160
161
|
envio.setCaratula({
|
|
161
|
-
RutEmisor: '
|
|
162
|
+
RutEmisor: '76543210-K',
|
|
162
163
|
RutReceptor: '60803000-K', // RUT del SII para envíos propios
|
|
163
164
|
FchResol: '2024-01-15',
|
|
164
165
|
NroResol: 123,
|
|
@@ -178,14 +179,14 @@ const resultado = await enviador.enviarDteSoap(envio)
|
|
|
178
179
|
// Estado del sobre (EnvioDTE)
|
|
179
180
|
const estadoSobre = await enviador.consultarEstado({
|
|
180
181
|
trackId: resultado.trackId,
|
|
181
|
-
rutEmisor: '
|
|
182
|
+
rutEmisor: '76543210-K',
|
|
182
183
|
})
|
|
183
184
|
// estadoSobre.esExitoso / esIntermedio / esRechazado
|
|
184
185
|
// estadoSobre.codigo → 'EPR', 'RPR', 'RSC', etc.
|
|
185
186
|
|
|
186
187
|
// Estado de un DTE individual
|
|
187
188
|
const estadoDte = await enviador.consultarEstadoDte({
|
|
188
|
-
rutEmisor: '
|
|
189
|
+
rutEmisor: '76543210-K',
|
|
189
190
|
rutReceptor: '12345678-9',
|
|
190
191
|
tipoDte: 33,
|
|
191
192
|
folio: 1,
|
|
@@ -244,13 +245,13 @@ const fs = require('fs')
|
|
|
244
245
|
const cert = new Certificado(fs.readFileSync('empresa_cert.pfx'), 'clave')
|
|
245
246
|
const caf = new CAF(fs.readFileSync('caf_39_cert.xml', 'utf8'))
|
|
246
247
|
|
|
247
|
-
const dte = new DTE({ tipo: 39, folio: 1, emisor: { rut: '
|
|
248
|
+
const dte = new DTE({ tipo: 39, folio: 1, emisor: { rut: '76543210-K', ... }, items: [ ... ] })
|
|
248
249
|
dte.generarXML().timbrar(caf).firmar(cert)
|
|
249
250
|
|
|
250
251
|
const envio = new EnvioBOLETA({ certificado: cert })
|
|
251
252
|
envio.agregar(dte)
|
|
252
253
|
envio.setCaratula({
|
|
253
|
-
RutEmisor: '
|
|
254
|
+
RutEmisor: '76543210-K',
|
|
254
255
|
FchResol: '2019-10-18', // fecha de resolución de certificación (entregada por el SII)
|
|
255
256
|
NroResol: 0, // siempre 0 en certificación
|
|
256
257
|
})
|
|
@@ -271,13 +272,13 @@ const fs = require('fs')
|
|
|
271
272
|
const cert = new Certificado(fs.readFileSync('empresa_prod.pfx'), 'clave')
|
|
272
273
|
const caf = new CAF(fs.readFileSync('caf_39_prod.xml', 'utf8'))
|
|
273
274
|
|
|
274
|
-
const dte = new DTE({ tipo: 39, folio: 1, emisor: { rut: '
|
|
275
|
+
const dte = new DTE({ tipo: 39, folio: 1, emisor: { rut: '76543210-K', ... }, items: [ ... ] })
|
|
275
276
|
dte.generarXML().timbrar(caf).firmar(cert)
|
|
276
277
|
|
|
277
278
|
const envio = new EnvioBOLETA({ certificado: cert })
|
|
278
279
|
envio.agregar(dte)
|
|
279
280
|
envio.setCaratula({
|
|
280
|
-
RutEmisor: '
|
|
281
|
+
RutEmisor: '76543210-K',
|
|
281
282
|
FchResol: '2024-01-15', // fecha real de resolución SII de la empresa
|
|
282
283
|
NroResol: 123, // número real de resolución SII de la empresa
|
|
283
284
|
})
|
|
@@ -303,7 +304,7 @@ service.cargarCAF(fs.readFileSync('caf_39.xml', 'utf8'))
|
|
|
303
304
|
|
|
304
305
|
const boleta = await service.crearBoleta({
|
|
305
306
|
folio: 1,
|
|
306
|
-
emisor: { rut: '
|
|
307
|
+
emisor: { rut: '76543210-K', razonSocial: 'Mi Empresa', giro: 'Software', ... },
|
|
307
308
|
items: [{ nombre: 'Producto', cantidad: 1, precioConIva: 10000 }],
|
|
308
309
|
resolucion: {
|
|
309
310
|
fecha: process.env.SII_AMBIENTE === 'certificacion' ? '2019-10-18' : '2024-01-15',
|
|
@@ -323,8 +324,8 @@ const { LibroCompraVenta, Certificado } = require('@devlas/dte-sii')
|
|
|
323
324
|
|
|
324
325
|
const libro = new LibroCompraVenta()
|
|
325
326
|
libro.setCaratula({
|
|
326
|
-
RutEmisorLibro: '
|
|
327
|
-
RutEnvia: '
|
|
327
|
+
RutEmisorLibro: '76543210-K',
|
|
328
|
+
RutEnvia: '76543210-K',
|
|
328
329
|
PeriodoTributario: '2024-06',
|
|
329
330
|
FchResol: '2024-01-15', NroResol: 123,
|
|
330
331
|
TipoOperacion: 'VENTA', // o 'COMPRA'
|
|
@@ -349,7 +350,7 @@ const { ConsumoFolio, CAF, Certificado } = require('@devlas/dte-sii')
|
|
|
349
350
|
|
|
350
351
|
const rcof = new ConsumoFolio()
|
|
351
352
|
rcof.setCaratula({
|
|
352
|
-
RutEmisor: '
|
|
353
|
+
RutEmisor: '76543210-K',
|
|
353
354
|
FchResol: '2024-01-15',
|
|
354
355
|
NroResol: 0,
|
|
355
356
|
FchInicio: '2024-06-15',
|
|
@@ -395,7 +396,7 @@ const fingerprint = createCafFingerprint(cafXml) // hash único del CA
|
|
|
395
396
|
|
|
396
397
|
// Reservar el siguiente folio disponible del rango del CAF
|
|
397
398
|
const folio = registry.reserveNextFolio({
|
|
398
|
-
rutEmisor: '
|
|
399
|
+
rutEmisor: '76543210-K',
|
|
399
400
|
tipoDte: caf.getTipoDTE(),
|
|
400
401
|
folioDesde: caf.getFolioDesde(),
|
|
401
402
|
folioHasta: caf.getFolioHasta(),
|
|
@@ -407,7 +408,7 @@ const folio = registry.reserveNextFolio({
|
|
|
407
408
|
|
|
408
409
|
// Marcar folio como enviado al recibir trackId del SII
|
|
409
410
|
registry.markFolioSent({
|
|
410
|
-
rutEmisor: '
|
|
411
|
+
rutEmisor: '76543210-K', tipoDte: 33, folio,
|
|
411
412
|
folioDesde: caf.getFolioDesde(), folioHasta: caf.getFolioHasta(),
|
|
412
413
|
ambiente: 'produccion', cafFingerprint: fingerprint,
|
|
413
414
|
trackId: '0245283324',
|
|
@@ -421,7 +422,7 @@ const { resolveCafPath } = require('@devlas/dte-sii')
|
|
|
421
422
|
|
|
422
423
|
const cafPath = resolveCafPath({
|
|
423
424
|
tipoDte: 33,
|
|
424
|
-
rutEmisor: '
|
|
425
|
+
rutEmisor: '76543210-K',
|
|
425
426
|
requiredCount: 1, // necesito al menos 1 folio disponible
|
|
426
427
|
ambiente: 'produccion',
|
|
427
428
|
})
|
|
@@ -439,7 +440,7 @@ const { FolioService, Certificado } = require('@devlas/dte-sii')
|
|
|
439
440
|
|
|
440
441
|
const service = new FolioService({
|
|
441
442
|
ambiente: 'produccion',
|
|
442
|
-
rutEmisor: '
|
|
443
|
+
rutEmisor: '76543210-K',
|
|
443
444
|
certificado: new Certificado(fs.readFileSync('empresa.pfx'), 'clave'),
|
|
444
445
|
})
|
|
445
446
|
|
|
@@ -493,7 +494,7 @@ const fs = require('fs')
|
|
|
493
494
|
const path = require('path')
|
|
494
495
|
|
|
495
496
|
const CAF_DIR = path.join(__dirname, 'cafs')
|
|
496
|
-
const RUT = '
|
|
497
|
+
const RUT = '76543210-K'
|
|
497
498
|
const AMBIENTE = 'produccion'
|
|
498
499
|
const TIPO_DTE = 33
|
|
499
500
|
const UMBRAL = 10 // solicitar nuevo CAF cuando queden menos de N folios
|
|
@@ -568,6 +569,41 @@ const datos = await auth.obtenerDatosEmpresa()
|
|
|
568
569
|
const cookies = await SiiPortalAuth.getCookieStringForPfx(cert)
|
|
569
570
|
```
|
|
570
571
|
|
|
572
|
+
#### Caché de sesión: un mapa por certificado
|
|
573
|
+
|
|
574
|
+
Un login contra `zeusr.sii.cl` es un handshake con certificado, **caro y contado por el SII**,
|
|
575
|
+
que bloquea el RUT por *"máximo de sesiones autenticadas"*. Por eso las cookies se cachean en
|
|
576
|
+
disco, en `$DATADIR/sii_session_cache.json`, con un TTL de 90 minutos.
|
|
577
|
+
|
|
578
|
+
Desde **2.16.0 el caché es un mapa por huella de certificado**. Antes guardaba una sola sesión,
|
|
579
|
+
así que en un servidor multi-tenant cada certificado invalidaba al anterior y **todos**
|
|
580
|
+
re-autenticaban en cada pasada: el costo crecía lineal con la base de clientes.
|
|
581
|
+
|
|
582
|
+
- El formato viejo se **migra**, no se descarta.
|
|
583
|
+
- Poda automática: expiradas primero, y tope de 200 entradas.
|
|
584
|
+
- Escritura atómica y relectura previa, para que dos réplicas sobre el mismo volumen no se
|
|
585
|
+
borren las sesiones entre sí.
|
|
586
|
+
|
|
587
|
+
```javascript
|
|
588
|
+
SiiPortalAuth.limpiarSesionCache(certHash) // borra una
|
|
589
|
+
SiiPortalAuth.limpiarSesionCache() // borra todas
|
|
590
|
+
```
|
|
591
|
+
|
|
592
|
+
> ⚠️ **En un servidor, apunta `DATADIR` a un volumen persistente.** Sin eso el caché vive en el
|
|
593
|
+
> filesystem del contenedor y se pierde en cada redeploy, forzando un re-login de toda la base.
|
|
594
|
+
|
|
595
|
+
#### Reintento ante fallas de red/TLS
|
|
596
|
+
|
|
597
|
+
`autenticar()` reintenta 3 veces con espera progresiva (1s, 2s, 4s) ante errores de transporte.
|
|
598
|
+
|
|
599
|
+
> ⚠️ El SII devuelve **`EPROTO` de forma intermitente** al abrir la conexión TLS con certificado
|
|
600
|
+
> (`rsa_pss ... last octet invalid`), y reintentando con el **mismo** certificado funciona. **No
|
|
601
|
+
> es señal de certificado vencido ni no habilitado**, aunque lo parezca. Interpretarlo así marca
|
|
602
|
+
> como rotos certificados que están sanos.
|
|
603
|
+
|
|
604
|
+
El **límite de sesiones nunca se reintenta** (cada intento empeora el bloqueo) y se distingue por
|
|
605
|
+
`err.code === 'SII_LIMITE_SESIONES'`.
|
|
606
|
+
|
|
571
607
|
### SiiSession: sesiones HTTP autenticadas
|
|
572
608
|
|
|
573
609
|
```javascript
|
|
@@ -580,6 +616,91 @@ const resp = await session.request('GET', 'https://herculesr.sii.cl/...')
|
|
|
580
616
|
|
|
581
617
|
---
|
|
582
618
|
|
|
619
|
+
## Descargar el XML completo desde el portal (Respaldo MIPYME)
|
|
620
|
+
|
|
621
|
+
`descargarRespaldoMipyme()` baja el **XML firmado completo** de los DTE emitidos o recibidos
|
|
622
|
+
desde el "Respaldo de archivos MIPYME" del portal (`www1.sii.cl/cgi-bin/Portal001`).
|
|
623
|
+
|
|
624
|
+
Es la única vía que entrega el **documento entero**: detalle línea por línea, `CdgItem` del
|
|
625
|
+
proveedor, referencias y TED. `obtenerDetalleDtes()` solo trae metadatos y
|
|
626
|
+
`obtenerResumenRegistro()` solo totales mensuales.
|
|
627
|
+
|
|
628
|
+
**Requiere únicamente el certificado digital**, no estar certificado como emisor.
|
|
629
|
+
|
|
630
|
+
> 🔴 **Existe SOLO en producción: no hay ambiente de certificación.** Medido sobre
|
|
631
|
+
> `/cgi-bin/Portal001/lista_documentos.cgi`: `www1.sii.cl` responde 200, `maullin.sii.cl`
|
|
632
|
+
> redirige a `Error404` y `www4c.sii.cl` da 404 (para contrastar, una ruta real de maullin
|
|
633
|
+
> redirige al login de certificación, no a un 404).
|
|
634
|
+
>
|
|
635
|
+
> Consecuencia para cualquier consumidor: con el resto del sistema apuntando a maullin, este
|
|
636
|
+
> método **igual lee documentos reales del contribuyente**. Es de solo lectura contra el SII,
|
|
637
|
+
> pero escribe facturas reales en la base del entorno que lo llame. Un entorno de desarrollo
|
|
638
|
+
> necesita una puerta explícita; no alcanza con mirar la variable de ambiente del DTE, porque
|
|
639
|
+
> esta función no tiene ambientes.
|
|
640
|
+
|
|
641
|
+
```javascript
|
|
642
|
+
const auth = new SiiPortalAuth({ pfxBuffer, pfxPassword })
|
|
643
|
+
|
|
644
|
+
const { total, tramos } = await auth.descargarRespaldoMipyme('76543210', '6', {
|
|
645
|
+
origen: 'RCP', // 'ENV' emitidos | 'RCP' recibidos
|
|
646
|
+
desde: '2026-01-01',
|
|
647
|
+
hasta: '2026-08-18',
|
|
648
|
+
tipoDoc: '', // vacío = todos
|
|
649
|
+
reintentos: 3,
|
|
650
|
+
})
|
|
651
|
+
// tramos: [{ desde, hasta, total, xml }, ...] — un XML por tramo
|
|
652
|
+
```
|
|
653
|
+
|
|
654
|
+
### Modo streaming (`onTramo`) — obligatorio para históricos grandes
|
|
655
|
+
|
|
656
|
+
Sin `onTramo` **todos los XML quedan en memoria hasta el final**: ~6,4 KB por documento, o sea
|
|
657
|
+
unos 7 MB para 1.100 documentos, y crece lineal.
|
|
658
|
+
|
|
659
|
+
```javascript
|
|
660
|
+
await auth.descargarRespaldoMipyme(rut, dv, {
|
|
661
|
+
origen: 'RCP', desde, hasta,
|
|
662
|
+
onTramo: async (t) => { await guardar(t.xml) }, // se persiste y se suelta
|
|
663
|
+
})
|
|
664
|
+
// con onTramo, los tramos del resultado vienen SIN `xml`
|
|
665
|
+
```
|
|
666
|
+
|
|
667
|
+
### Qué hay que saber del portal
|
|
668
|
+
|
|
669
|
+
| | |
|
|
670
|
+
|---|---|
|
|
671
|
+
| **Tope de 20 por descarga** | Es del servidor, no cosmético. Con 21 devuelve **HTML de error**, no un XML recortado. El método trocea el rango solo, del **más reciente al más viejo**. |
|
|
672
|
+
| **Un día con más de 20** | No se puede partir más por fecha: se lanza error explícito. Tiene salida cortando por `TPO_DOC` (un tipo de DTE por consulta). ⚠️ **No** usar `FOLIO`/`FOLIOHASTA`: borran `FEC_HASTA` en silencio y devuelven otro conjunto. |
|
|
673
|
+
| **Encoding** | El XML viene en **ISO-8859-1**. Leerlo como utf8 rompe los acentos. |
|
|
674
|
+
| **Captcha** | Hoy va vacío, pero el SII puede encenderlo sin avisar → `RESPALDO_CAPTCHA`. |
|
|
675
|
+
| **Alcance** | Solo lo registrado en el sistema de facturación **gratuito** del SII. Un comercio que ya migró a otro sistema no encuentra ahí sus documentos nuevos. |
|
|
676
|
+
|
|
677
|
+
> 🔴 **El portal viejo comunica sus rechazos por `alert()` de JavaScript, con HTTP 200** — no en
|
|
678
|
+
> el HTML visible ni en el `<title>`, que dice otra cosa. Y la página **válida** trae además un
|
|
679
|
+
> `//alert(...)` **comentado**. Clasificar por título, limpiar los `<script>` antes de parsear, o
|
|
680
|
+
> creerle al alert comentado: las tres cosas producen diagnósticos falsos.
|
|
681
|
+
|
|
682
|
+
Los errores traen **`err.mensajePortal`** con el texto exacto del SII. **Mostrar ese texto, no
|
|
683
|
+
una traducción propia.**
|
|
684
|
+
|
|
685
|
+
⚠️ **`SIN_DATOS` no es lo mismo que `INDETERMINADO`.** El primero es un veredicto definitivo
|
|
686
|
+
("acá no hay nada") y el consumidor puede cerrar ese período para siempre. Los otros dos
|
|
687
|
+
significan "no se pudo concluir" y "no pudimos preguntar": tratarlos igual cerró en falso un
|
|
688
|
+
período que ya tenía 24 documentos bajados.
|
|
689
|
+
|
|
690
|
+
| código | qué pasó | ¿reintentar? |
|
|
691
|
+
|---|---|---|
|
|
692
|
+
| `RESPALDO_SIN_DATOS` | el RUT no tiene información en MIPYME | no |
|
|
693
|
+
| `RESPALDO_CAPTCHA` | el SII encendió el captcha | no |
|
|
694
|
+
| `RESPALDO_RECHAZADO` | rechazo con un texto que no conocemos; llega literal | no |
|
|
695
|
+
| `RESPALDO_INDETERMINADO` | **varios** mensajes del template, sin veredicto único; llegan en `err.mensajesPortal` | sí, más tarde |
|
|
696
|
+
| `RESPALDO_SIN_EMPRESA` | página de ingreso sin ningún alert; causa no determinada | no |
|
|
697
|
+
|
|
698
|
+
Contrato completo, respuestas reales y los errores que cuestan tiempo (es **POST** no GET;
|
|
699
|
+
`ORIGEN=ENV` no `EMI`; el listado es obligatorio antes de la descarga) en
|
|
700
|
+
la sección de arriba.
|
|
701
|
+
|
|
702
|
+
---
|
|
703
|
+
|
|
583
704
|
## Aceptación y reclamo de DTE (WsReclamo)
|
|
584
705
|
|
|
585
706
|
`WsReclamo` implementa el web service `WSRECLAMO` del SII (v1.2) para registrar eventos de aceptación/rechazo de DTE por parte del receptor.
|
|
@@ -594,7 +715,7 @@ const ws = new WsReclamo(new Certificado(fs.readFileSync('empresa.pfx'), 'clave'
|
|
|
594
715
|
|
|
595
716
|
// Consultar historial de eventos de un DTE
|
|
596
717
|
const eventos = await ws.listarEventosHistDoc({
|
|
597
|
-
rutEmisor: '
|
|
718
|
+
rutEmisor: '76543210-K',
|
|
598
719
|
tipoDTE: 33,
|
|
599
720
|
folio: 1,
|
|
600
721
|
rutReceptor: '12345678-9',
|
|
@@ -605,7 +726,7 @@ const estado = await ws.consultarEstadoReceptor({ ... })
|
|
|
605
726
|
|
|
606
727
|
// Registrar aceptación (ACD) o reclamo (RCD)
|
|
607
728
|
await ws.ingresarAceptacion({
|
|
608
|
-
rutEmisor: '
|
|
729
|
+
rutEmisor: '76543210-K', tipoDTE: 33, folio: 1,
|
|
609
730
|
accion: 'ACD', // ACD=Aceptado, RCD=Reclamado, ERM=Otorga Mercaderías
|
|
610
731
|
})
|
|
611
732
|
```
|
|
@@ -676,7 +797,7 @@ const { configure, configureRetry } = require('@devlas/dte-sii')
|
|
|
676
797
|
// Configuración global (aplicar al inicio de la app)
|
|
677
798
|
configure({
|
|
678
799
|
ambiente: 'produccion', // 'produccion' | 'certificacion'
|
|
679
|
-
defaultRutEmisor: '
|
|
800
|
+
defaultRutEmisor: '76543210-K',
|
|
680
801
|
tokenCacheTtlMs: 300_000, // 5 minutos (default)
|
|
681
802
|
})
|
|
682
803
|
|
|
@@ -758,7 +879,7 @@ const require = createRequire(import.meta.url)
|
|
|
758
879
|
const { Certificado, CAF, DTE, EnviadorSII } = require('@devlas/dte-sii')
|
|
759
880
|
```
|
|
760
881
|
|
|
761
|
-
**TypeScript con ESM**
|
|
882
|
+
**TypeScript con ESM** :
|
|
762
883
|
|
|
763
884
|
```typescript
|
|
764
885
|
import { createRequire } from 'module'
|
|
@@ -814,7 +935,7 @@ import type {
|
|
|
814
935
|
| `FolioService` | `FolioService.js` | Consulta, solicita y anula folios ante el SII |
|
|
815
936
|
| `CafSolicitor` | `CafSolicitor.js` | Solicitud automatizada de CAF al SII |
|
|
816
937
|
| `SiiSession` | `SiiSession.js` | Sesiones HTTP autenticadas con certificado (cookie jar) |
|
|
817
|
-
| `SiiPortalAuth` | `SiiPortalAuth.js` | Autenticación al portal SII;
|
|
938
|
+
| `SiiPortalAuth` | `SiiPortalAuth.js` | Autenticación al portal SII; datos de empresa; RCV; **respaldo MIPYME (XML completo)**; caché de sesión por certificado |
|
|
818
939
|
|
|
819
940
|
### Libros y reportes
|
|
820
941
|
|
|
@@ -880,7 +1001,7 @@ El directorio `cert/` contiene los helpers necesarios para ejecutar el proceso d
|
|
|
880
1001
|
- Generación de muestras impresas
|
|
881
1002
|
|
|
882
1003
|
```javascript
|
|
883
|
-
// Uso desde
|
|
1004
|
+
// Uso desde un proyecto ESM
|
|
884
1005
|
const { CertFolioHelper } = require('@devlas/dte-sii')
|
|
885
1006
|
```
|
|
886
1007
|
|