@devlas/dte-sii 2.9.5 → 2.9.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/CafSolicitor.js CHANGED
@@ -49,8 +49,8 @@ class CafSolicitor {
49
49
  if (!options.rutEmisor) {
50
50
  throw new Error('CafSolicitor: options.rutEmisor es obligatorio');
51
51
  }
52
- if (!options.pfxPath) {
53
- throw new Error('CafSolicitor: options.pfxPath es obligatorio');
52
+ if (!options.pfxPath && !options.pfxBuffer) {
53
+ throw new Error('CafSolicitor: options.pfxPath o options.pfxBuffer es obligatorio');
54
54
  }
55
55
  if (!options.pfxPassword) {
56
56
  throw new Error('CafSolicitor: options.pfxPassword es obligatorio');
@@ -61,8 +61,9 @@ class CafSolicitor {
61
61
  this.baseDir = options.baseDir || path.resolve(__dirname, '..', '..');
62
62
  this.runStamp = options.runStamp || new Date().toISOString().replace(/[:.]/g, '-');
63
63
 
64
- // Reutilizar sesión SII global si ya existe para este ambiente+rut.
65
- // Esto evita abrir múltiples sesiones en el portal del SII.
64
+ // pfxBuffer tiene prioridad sobre pfxPath para evitar I/O a disco
65
+ const _pfxBuffer = options.pfxBuffer || fs.readFileSync(options.pfxPath);
66
+
66
67
  const sessionKey = `${this.ambiente}::${this.rutEmisor}`;
67
68
  if (_sessionRegistry.has(sessionKey)) {
68
69
  this.session = _sessionRegistry.get(sessionKey);
@@ -70,19 +71,16 @@ class CafSolicitor {
70
71
  } else {
71
72
  this.session = new SiiSession({
72
73
  ambiente: this.ambiente,
73
- pfxPath: options.pfxPath,
74
+ pfxBuffer: _pfxBuffer,
74
75
  pfxPassword: options.pfxPassword,
75
76
  });
76
77
 
77
- // Intentar reutilizar cookies del store compartido (SiiPortalAuth o sesión previa).
78
- // También calcula certHash para escribir de vuelta al store cuando este SiiSession autentique.
79
78
  let certHash = null;
80
79
  try {
81
- const pfxBuffer = fs.readFileSync(options.pfxPath);
82
- const { certPem } = SiiPortalAuth._extractPems(pfxBuffer, options.pfxPassword);
80
+ const { certPem } = SiiPortalAuth._extractPems(_pfxBuffer, options.pfxPassword);
83
81
  certHash = crypto.createHash('sha1').update(certPem).digest('hex').slice(0, 12);
84
82
 
85
- const existingCookies = SiiPortalAuth.getCookieStringForPfx(pfxBuffer, options.pfxPassword);
83
+ const existingCookies = SiiPortalAuth.getCookieStringForPfx(_pfxBuffer, options.pfxPassword);
86
84
  if (existingCookies) {
87
85
  this.session.cookieJar = existingCookies;
88
86
  console.log('[CafSolicitor] 🔗 Sesión SII pre-cargada desde store compartido — sin auth extra');
package/README.md CHANGED
@@ -4,7 +4,7 @@
4
4
 
5
5
  Genera, timbra, firma y envía facturas electrónicas, boletas electrónicas, libros contables y automatiza el proceso de certificación ante el SII.
6
6
 
7
- > Desarrollada por [Devlas SpA](https://devlas.cl) y publicada bajo licencia MIT para la comunidad de desarrolladores chilenos.
7
+ > Desarrollada por [Devlas SpA](https://devlas.cl) · Licencia MIT · Node.js >= 18 · CommonJS
8
8
 
9
9
  ---
10
10
 
@@ -14,260 +14,875 @@ Genera, timbra, firma y envía facturas electrónicas, boletas electrónicas, li
14
14
  npm install @devlas/dte-sii
15
15
  ```
16
16
 
17
- ## Uso básico
17
+ ---
18
18
 
19
- ```javascript
20
- const {
21
- Certificado, CAF, DTE, EnvioDTE, EnvioBOLETA, EnviadorSII,
22
- ConsumoFolio, LibroCompraVenta, LibroGuia,
23
- } = require('@devlas/dte-sii')
19
+ ## Tabla de contenidos
20
+
21
+ - [Tipos de DTE soportados](#tipos-de-dte-soportados)
22
+ - [Uso rápido](#uso-rápido)
23
+ - [Flujo completo: Factura Electrónica (tipo 33)](#flujo-completo-factura-electrónica-tipo-33)
24
+ - [Boletas electrónicas](#boletas-electrónicas)
25
+ - [Libros electrónicos y RCOF](#libros-electrónicos-y-rcof)
26
+ - [Gestión de folios](#gestión-de-folios)
27
+ - [Sesión y autenticación con el SII](#sesión-y-autenticación-con-el-sii)
28
+ - [Aceptación y reclamo de DTE (WsReclamo)](#aceptación-y-reclamo-de-dte-wsreclamo)
29
+ - [Estados SII: Interpretación de respuestas](#estados-sii-interpretación-de-respuestas)
30
+ - [Manejo de errores](#manejo-de-errores)
31
+ - [Configuración global y reintentos](#configuración-global-y-reintentos)
32
+ - [Utilidades](#utilidades)
33
+ - [Uso desde proyectos ESM (interop)](#uso-desde-proyectos-esm-interop)
34
+ - [TypeScript](#typescript)
35
+ - [Referencia de clases](#referencia-de-clases)
36
+ - [Estructura de archivos](#estructura-de-archivos)
37
+ - [Certificación SII](#certificación-sii)
38
+ - [Licencia](#licencia)
24
39
 
25
- // Cargar certificado digital (.pfx)
26
- const cert = new Certificado(fs.readFileSync('certificado.pfx'), 'contraseña')
40
+ ---
41
+
42
+ ## Tipos de DTE soportados
27
43
 
28
- // Cargar CAF (folios autorizados)
29
- const caf = new CAF(fs.readFileSync('caf.xml', 'utf8'))
44
+ | Tipo | Documento |
45
+ |------|-----------|
46
+ | `33` | Factura Electrónica |
47
+ | `34` | Factura No Afecta o Exenta Electrónica |
48
+ | `39` | Boleta Electrónica Afecta |
49
+ | `41` | Boleta Electrónica Exenta |
50
+ | `43` | Liquidación Factura |
51
+ | `46` | Factura de Compra |
52
+ | `52` | Guía de Despacho |
53
+ | `56` | Nota de Débito |
54
+ | `61` | Nota de Crédito |
55
+
56
+ ---
57
+
58
+ ## Uso rápido
59
+
60
+ ```javascript
61
+ const { Certificado, CAF, DTE, EnvioDTE, EnviadorSII } = require('@devlas/dte-sii')
62
+ const fs = require('fs')
63
+
64
+ const cert = new Certificado(fs.readFileSync('empresa.pfx'), 'contraseña')
65
+ const caf = new CAF(fs.readFileSync('caf_33.xml', 'utf8'))
66
+
67
+ const dte = new DTE({
68
+ Encabezado: {
69
+ IdDoc: { TipoDTE: 33, Folio: 1 },
70
+ Emisor: { RUTEmisor: '76354771-K', RznSoc: 'Mi Empresa SpA', GiroEmis: 'Software', DirOrigen: 'Av. Ejemplo 123', CmnaOrigen: 'Santiago', Acteco: 620200 },
71
+ Receptor: { RUTRecep: '12345678-9', RznSocRecep: 'Cliente SA', GiroRecep: 'Comercio', DirRecep: 'Calle 456', CmnaRecep: 'Providencia' },
72
+ },
73
+ Detalle: [
74
+ { NmbItem: 'Servicio de desarrollo', QtyItem: 1, PrcItem: 100000 },
75
+ ],
76
+ })
30
77
 
31
- // Crear y timbrar una Factura Electrónica (tipo 33)
32
- const dte = new DTE({ Encabezado: { ... }, Detalle: [ ... ] })
33
78
  dte.generarXML().timbrar(caf).firmar(cert)
34
79
 
35
- // Enviar al SII
36
80
  const envio = new EnvioDTE({ certificado: cert })
37
81
  envio.agregar(dte)
38
- envio.setCaratula({ RutEmisor: '76354771-K', ... })
82
+ envio.setCaratula({ RutEmisor: '76354771-K', RutReceptor: '60803000-K', FchResol: '2024-01-15', NroResol: 123 })
39
83
  envio.generar()
40
84
 
41
85
  const enviador = new EnviadorSII(cert, 'produccion') // o 'certificacion'
42
86
  const resultado = await enviador.enviarDteSoap(envio)
43
- console.log(resultado.trackId)
87
+ console.log('TrackID:', resultado.trackId)
44
88
  ```
45
89
 
46
- ## Módulos
47
-
48
- | Clase | Descripción |
49
- |---|---|
50
- | `Certificado` | Carga y maneja certificados digitales PFX/P12 |
51
- | `CAF` | Código de Autorización de Folios del SII |
52
- | `DTE` | Genera, timbra y firma documentos tributarios |
53
- | `EnvioDTE` | Sobre XML para facturas, guías y notas |
54
- | `EnvioBOLETA` | Sobre XML para boletas electrónicas |
55
- | `EnviadorSII` | Comunicación SOAP/HTTP con el SII |
56
- | `ConsumoFolio` | Genera RCOF (Resumen Consumo de Folios) |
57
- | `LibroCompraVenta` | Libro electrónico de Compra/Venta |
58
- | `LibroGuia` | Libro electrónico de Guías de Despacho |
59
- | `FolioService` | Gestión y solicitud de folios (CAF) |
60
- | `SiiSession` | Sesión autenticada con el portal SII |
61
- | `SiiCertificacion` | Automatización del proceso de certificación |
90
+ ---
62
91
 
63
- ## Tipos de DTE soportados
92
+ ## Flujo completo: Factura Electrónica (tipo 33)
64
93
 
65
- | Tipo | Documento |
66
- |---|---|
67
- | 33 | Factura Electrónica |
68
- | 34 | Factura No Afecta o Exenta |
69
- | 39 | Boleta Electrónica Afecta |
70
- | 41 | Boleta Electrónica Exenta |
71
- | 43 | Liquidación Factura |
72
- | 46 | Factura de Compra |
73
- | 52 | Guía de Despacho |
74
- | 56 | Nota de Débito |
75
- | 61 | Nota de Crédito |
94
+ ### 1. Cargar certificado y CAF
76
95
 
77
- ## Ambientes
96
+ ```javascript
97
+ const { Certificado, CAF } = require('@devlas/dte-sii')
98
+ const fs = require('fs')
99
+
100
+ const cert = new Certificado(fs.readFileSync('empresa.pfx'), 'clave_pfx')
101
+ // cert.getPrivateKeyPem() → PEM de la llave privada
102
+ // cert.getCertificatePem() → PEM del certificado público
103
+
104
+ const caf = new CAF(fs.readFileSync('caf_33.xml', 'utf8'))
105
+ // caf.getRutEmisor() → RUT del emisor
106
+ // caf.getTipoDTE() → 33
107
+ // caf.getFolioDesde() → primer folio autorizado
108
+ // caf.getFolioHasta() → último folio autorizado
109
+ // caf.isFolioValido(folio) → boolean
110
+ ```
78
111
 
79
- Soporta `'certificacion'` y `'produccion'`. Pasarlo al instanciar `EnviadorSII`.
112
+ ### 2. Crear el DTE
80
113
 
81
- ## Estados SII — Interpretación de respuestas
114
+ ```javascript
115
+ const { DTE } = require('@devlas/dte-sii')
116
+
117
+ // Formato simplificado (calcula totales automáticamente)
118
+ const dte = new DTE({
119
+ tipo: 33,
120
+ folio: 1,
121
+ emisor: {
122
+ rut: '76354771-K', razonSocial: 'Mi Empresa SpA',
123
+ giro: 'Desarrollo de software', direccion: 'Av. Ejemplo 123',
124
+ comuna: 'Santiago', actividadEconomica: 620200,
125
+ },
126
+ receptor: {
127
+ rut: '12345678-9', razonSocial: 'Cliente SA',
128
+ giro: 'Comercio', direccion: 'Calle 456', comuna: 'Providencia',
129
+ },
130
+ items: [
131
+ { nombre: 'Licencia anual', cantidad: 1, precio: 100000 },
132
+ { nombre: 'Soporte técnico', cantidad: 3, precio: 15000 },
133
+ ],
134
+ resolucion: { fecha: '2024-01-15', numero: 123 },
135
+ })
136
+
137
+ // O bien formato estructurado con XML SII estándar
138
+ const dte = new DTE({
139
+ Encabezado: { IdDoc: { TipoDTE: 33, Folio: 1 }, Emisor: { ... }, Receptor: { ... }, Totales: { ... } },
140
+ Detalle: [ { NmbItem: 'Producto', QtyItem: 2, PrcItem: 50000 } ],
141
+ Referencia: [ { TpoDocRef: 61, FolioRef: 5, RazonRef: 'Anula factura' } ], // opcional
142
+ })
143
+
144
+ // Generar XML → timbrar → firmar (chainable)
145
+ dte.generarXML().timbrar(caf).firmar(cert)
82
146
 
83
- `EnviadorSII` interpreta automáticamente los códigos del SII en tres categorías:
147
+ // Obtener el XML final
148
+ console.log(dte.getXML())
149
+ ```
84
150
 
85
- ### QueryEstUp Estado del sobre de envío
151
+ ### 3. Crear sobre y enviar
86
152
 
87
- | Código | Descripción | `esExitoso` | `esIntermedio` | `esRechazado` |
88
- |--------|-------------|:-----------:|:--------------:|:-------------:|
89
- | `EPR` | Envío Procesado | ✅ | | |
90
- | `RPR` | Procesado con Reparos | ✅ | | |
91
- | `REC` / `SOK` / `FOK` / `CRT` / `PRD` / `PDR` | En proceso de validación | | ✅ | |
92
- | `RSC` | Error en Schema XML | | | ✅ |
93
- | `RFR` | Error en Firma Digital | | | ✅ |
94
- | `RCT` | Error en Carátula | | | ✅ |
153
+ ```javascript
154
+ const { EnvioDTE, EnviadorSII } = require('@devlas/dte-sii')
95
155
 
96
- ### QueryEstDte Estado del DTE individual
156
+ const envio = new EnvioDTE({ certificado: cert })
157
+ envio.agregar(dte)
158
+ envio.setCaratula({
159
+ RutEmisor: '76354771-K',
160
+ RutReceptor: '60803000-K', // RUT del SII para envíos propios
161
+ FchResol: '2024-01-15',
162
+ NroResol: 123,
163
+ })
164
+ envio.generar()
97
165
 
98
- | Código | Descripción | Clasificación |
99
- |--------|-------------|---------------|
100
- | `DOK` | Datos coinciden | ✅ Exitoso |
101
- | `DNK` | Datos no coinciden | ⏳ Intermedio |
102
- | `FAU` | Folio no autorizado | ❌ Rechazado |
103
- | `FNA` | Emisor no habilitado | ❌ Rechazado |
104
- | `FAN` / `AND` / `ANC` | Anulado | ❌ Rechazado |
105
- | `EMP` | Empresa sin autorización | ❌ Rechazado |
166
+ const enviador = new EnviadorSII(cert, 'produccion')
167
+ const resultado = await enviador.enviarDteSoap(envio)
168
+ // resultado.trackId → ID para consultar el estado
169
+ // resultado.estado → 'EPR', 'REC', etc.
170
+ // resultado.glosa → mensaje SII
171
+ ```
172
+
173
+ ### 4. Consultar estado del envío
174
+
175
+ ```javascript
176
+ // Estado del sobre (EnvioDTE)
177
+ const estadoSobre = await enviador.consultarEstado({
178
+ trackId: resultado.trackId,
179
+ rutEmisor: '76354771-K',
180
+ })
181
+ // estadoSobre.esExitoso / esIntermedio / esRechazado
182
+ // estadoSobre.codigo → 'EPR', 'RPR', 'RSC', etc.
183
+
184
+ // Estado de un DTE individual
185
+ const estadoDte = await enviador.consultarEstadoDte({
186
+ rutEmisor: '76354771-K',
187
+ rutReceptor: '12345678-9',
188
+ tipoDte: 33,
189
+ folio: 1,
190
+ fechaEmision: '2024-06-15',
191
+ montoDte: 145000,
192
+ })
193
+ // estadoDte.codigo → 'DOK', 'DNK', 'FAU', etc.
194
+ ```
106
195
 
107
- ### Códigos negativos (errores de consulta)
196
+ ---
108
197
 
109
- Los códigos `-1` a `-14` son **errores del servidor de consulta SII**, no implican rechazo del documento. Todos resultan en `esIntermedio = true` y se pueden reintentar.
198
+ ## Boletas electrónicas
110
199
 
111
- > Ver `docs/README.md` en el repositorio para la tabla completa de estados con descripción detallada.
200
+ > **Diferencia crítica por ambiente**
201
+ >
202
+ > - **Producción** → usar la API **REST** del SII (`enviarBoleta`) con los datos reales de resolución de la empresa.
203
+ > - **Certificación** → la API REST **no funciona** para el proceso de certificación SII. Usar **SOAP** (`enviarDteSoap`) con resolución `NroResol: 0` y la fecha de resolución de certificación que entrega el SII.
204
+ >
205
+ > Usar los datos de empresa incorrectos para el ambiente (por ejemplo, datos de producción en certificación) provoca rechazo inmediato del SII.
112
206
 
113
207
  ---
114
208
 
115
- ## Licencia
209
+ ### Datos de resolución por ambiente
116
210
 
117
- MIT Copyright (c) 2026 [Devlas SpA](https://devlas.cl)
211
+ | Campo | Certificación | Producción |
212
+ |-------|--------------|------------|
213
+ | `NroResol` | `0` (siempre cero en cert.) | Número real de resolución SII |
214
+ | `FchResol` | Fecha entregada por el SII al iniciar certificación | Fecha real de la resolución |
215
+ | Método de envío | `enviarDteSoap` (SOAP) | `enviarBoleta` (REST) |
118
216
 
119
- Inspirada conceptualmente en [LibreDTE de SASCO SpA](https://libredte.cl).
120
- Implementa el protocolo XML público del SII de Chile.
217
+ Para obtener la fecha y número de resolución de producción automáticamente desde el portal SII, ver [`SiiPortalAuth`](#sesión-y-autenticación-con-el-sii).
121
218
 
122
219
  ---
123
220
 
124
- ## Estructura de Archivos
221
+ ### Certificación: SOAP (obligatorio)
222
+
223
+ ```javascript
224
+ const { DTE, CAF, Certificado, EnvioBOLETA, EnviadorSII } = require('@devlas/dte-sii')
225
+ const fs = require('fs')
226
+
227
+ const cert = new Certificado(fs.readFileSync('empresa_cert.pfx'), 'clave')
228
+ const caf = new CAF(fs.readFileSync('caf_39_cert.xml', 'utf8'))
125
229
 
230
+ const dte = new DTE({ tipo: 39, folio: 1, emisor: { rut: '76354771-K', ... }, items: [ ... ] })
231
+ dte.generarXML().timbrar(caf).firmar(cert)
232
+
233
+ const envio = new EnvioBOLETA({ certificado: cert })
234
+ envio.agregar(dte)
235
+ envio.setCaratula({
236
+ RutEmisor: '76354771-K',
237
+ FchResol: '2019-10-18', // fecha de resolución de certificación (entregada por el SII)
238
+ NroResol: 0, // siempre 0 en certificación
239
+ })
240
+ envio.generar()
241
+
242
+ // SOAP - único método que funciona para certificación de boletas
243
+ const enviador = new EnviadorSII(cert, 'certificacion')
244
+ const resultado = await enviador.enviarDteSoap(envio)
245
+ console.log('TrackID:', resultado.trackId)
126
246
  ```
127
- dte-sii/
128
- ├── index.js # Punto de entrada principal
129
- ├── utils.js # Utilidades y helpers
130
- ├── Certificado.js # Manejo de certificados PFX/P12
131
- ├── CAF.js # Código de Autorización de Folios
132
- ├── DTE.js # Documento Tributario Electrónico
133
- ├── Signer.js # Firma XML-DSig
134
- ├── Envio.js # EnvioBOLETA y EnvioDTE
135
- ├── EnviadorSII.js # Comunicación con servicios SII
136
- ├── BoletaService.js # Servicio simplificado para boletas
137
- ├── LibroBase.js # Clase base para libros
138
- ├── ConsumoFolio.js # RCOF (Resumen Consumo Folios)
139
- ├── LibroCompraVenta.js # Libro Compra/Venta
140
- ├── LibroGuia.js # Libro Guías de Despacho
141
- ├── FolioService.js # Gestión integral de folios (solicitar, consultar, anular)
142
- ├── FolioRegistry.js # Registro local de folios usados + helpers
143
- └── SiiSession.js # Sesiones HTTP autenticadas con el SII
247
+
248
+ ### Producción: REST (recomendado)
249
+
250
+ ```javascript
251
+ const { DTE, CAF, Certificado, EnvioBOLETA, EnviadorSII } = require('@devlas/dte-sii')
252
+ const fs = require('fs')
253
+
254
+ const cert = new Certificado(fs.readFileSync('empresa_prod.pfx'), 'clave')
255
+ const caf = new CAF(fs.readFileSync('caf_39_prod.xml', 'utf8'))
256
+
257
+ const dte = new DTE({ tipo: 39, folio: 1, emisor: { rut: '76354771-K', ... }, items: [ ... ] })
258
+ dte.generarXML().timbrar(caf).firmar(cert)
259
+
260
+ const envio = new EnvioBOLETA({ certificado: cert })
261
+ envio.agregar(dte)
262
+ envio.setCaratula({
263
+ RutEmisor: '76354771-K',
264
+ FchResol: '2024-01-15', // fecha real de resolución SII de la empresa
265
+ NroResol: 123, // número real de resolución SII de la empresa
266
+ })
267
+ envio.generar()
268
+
269
+ // REST - método estándar para producción
270
+ const enviador = new EnviadorSII(cert, 'produccion')
271
+ const resultado = await enviador.enviarBoleta(envio)
272
+ console.log('TrackID:', resultado.trackId)
144
273
  ```
145
274
 
146
- ## Uso
275
+ ### Flujo con BoletaService
276
+
277
+ `BoletaService` simplifica la creación de boletas individuales. Aplica el mismo criterio de ambiente: usar `enviarDteSoap` para certificación y `enviarBoleta` para producción una vez que el servicio retorne el sobre.
147
278
 
148
279
  ```javascript
149
- const {
150
- DTE, CAF, Certificado, EnvioDTE, EnviadorSII,
151
- FolioRegistry, createCafFingerprint, resolveCafPath
152
- } = require('@devlas/dte-sii');
280
+ const { BoletaService } = require('@devlas/dte-sii')
281
+ const fs = require('fs')
282
+
283
+ const service = new BoletaService()
284
+ service.cargarCertificado(fs.readFileSync('empresa.pfx'), 'clave_pfx')
285
+ service.cargarCAF(fs.readFileSync('caf_39.xml', 'utf8'))
286
+
287
+ const boleta = await service.crearBoleta({
288
+ folio: 1,
289
+ emisor: { rut: '76354771-K', razonSocial: 'Mi Empresa', giro: 'Software', ... },
290
+ items: [{ nombre: 'Producto', cantidad: 1, precioConIva: 10000 }],
291
+ resolucion: {
292
+ fecha: process.env.SII_AMBIENTE === 'certificacion' ? '2019-10-18' : '2024-01-15',
293
+ numero: process.env.SII_AMBIENTE === 'certificacion' ? 0 : 123,
294
+ },
295
+ })
296
+ ```
297
+
298
+ ---
153
299
 
154
- // Instancia de registro de folios
155
- const folioRegistry = new FolioRegistry();
300
+ ## Libros electrónicos y RCOF
301
+
302
+ ### LibroCompraVenta
303
+
304
+ ```javascript
305
+ const { LibroCompraVenta, Certificado } = require('@devlas/dte-sii')
306
+
307
+ const libro = new LibroCompraVenta()
308
+ libro.setCaratula({
309
+ RutEmisorLibro: '76354771-K',
310
+ RutEnvia: '76354771-K',
311
+ PeriodoTributario: '2024-06',
312
+ FchResol: '2024-01-15', NroResol: 123,
313
+ TipoOperacion: 'VENTA', // o 'COMPRA'
314
+ TipoLibro: 'MENSUAL',
315
+ TipoEnvio: 'TOTAL',
316
+ FolioNotificacion: 0,
317
+ })
318
+ libro.setResumen({ /* datos del resumen */ })
319
+ libro.setDetalle([ /* array de documentos */ ])
320
+ libro.generar().firmar(cert)
321
+
322
+ const enviador = new EnviadorSII(cert, 'produccion')
323
+ await enviador.enviarLibroSoap(libro)
156
324
  ```
157
325
 
158
- ## Clases Principales
159
-
160
- | Clase | Responsabilidad |
161
- |-------|-----------------|
162
- | `Certificado` | Carga y manejo de certificados digitales (.pfx/.p12) |
163
- | `CAF` | Parseado y uso de Códigos de Autorización de Folios |
164
- | `DTE` | Generación, timbraje y firma de documentos |
165
- | `Signer` | Firma XML-DSig compatible con SII |
166
- | `EnvioBOLETA` | Sobre para boletas electrónicas |
167
- | `EnvioDTE` | Sobre para facturas y otros DTEs |
168
- | `EnviadorSII` | Autenticación y envío al SII (REST + SOAP) |
169
- | `BoletaService` | Servicio simplificado para crear boletas |
170
- | `ConsumoFolio` | RCOF - Resumen Consumo de Folios |
171
- | `LibroCompraVenta` | Libro electrónico de compras/ventas |
172
- | `LibroGuia` | Libro electrónico de guías de despacho |
173
-
174
- ## Gestión de Folios
175
-
176
- | Clase/Función | Responsabilidad |
177
- |---------------|-----------------|
178
- | `FolioService` | Servicio para solicitar, consultar y anular folios en el SII |
179
- | `FolioRegistry` | Registro local de folios usados, reservados y enviados |
180
- | `SiiSession` | Sesiones HTTP autenticadas con certificado digital |
181
- | `createCafFingerprint(xml)` | Genera hash único de un CAF |
182
- | `findLatestCaf(tipoDte, dir)` | Busca el CAF más reciente para un tipo de DTE |
183
- | `resolveCafPath(opts)` | Resuelve ruta de CAF con validación de folios |
184
-
185
- ### Ejemplo de uso de FolioRegistry
326
+ ### ConsumoFolio (RCOF)
327
+
328
+ El RCOF es obligatorio para boletas y debe enviarse **antes de las 08:00 del día siguiente**.
186
329
 
187
330
  ```javascript
188
- const {
189
- FolioRegistry, CAF, createCafFingerprint, resolveCafPath
190
- } = require('@devlas/dte-sii');
331
+ const { ConsumoFolio, CAF, Certificado } = require('@devlas/dte-sii')
332
+
333
+ const rcof = new ConsumoFolio()
334
+ rcof.setCaratula({
335
+ RutEmisor: '76354771-K',
336
+ FchResol: '2024-01-15',
337
+ NroResol: 0,
338
+ FchInicio: '2024-06-15',
339
+ FchFinal: '2024-06-15',
340
+ SecEnvio: 1,
341
+ TmstFirmaEnv: new Date().toISOString(),
342
+ })
343
+ rcof.agregar(dte, caf)
344
+ rcof.generar().firmar(cert)
345
+
346
+ const enviador = new EnviadorSII(cert, 'produccion')
347
+ await enviador.enviarRcofSoap(rcof)
348
+ ```
191
349
 
192
- // Instancia de registro
193
- const folioRegistry = new FolioRegistry();
350
+ ---
194
351
 
195
- // Resolver CAF automáticamente
196
- const cafPath = resolveCafPath({
197
- tipoDte: 33,
198
- rutEmisor: '78206276-K',
199
- requiredCount: 1,
200
- ambiente: 'certificacion',
201
- });
202
-
203
- // Cargar CAF
204
- const cafXml = fs.readFileSync(cafPath, 'utf8');
205
- const caf = new CAF(cafXml);
206
- const cafFingerprint = createCafFingerprint(cafXml);
207
-
208
- // Reservar próximo folio
209
- const folio = folioRegistry.reserveNextFolio({
210
- rutEmisor: '78206276-K',
211
- tipoDte: caf.getTipoDTE(),
212
- folioDesde: caf.getFolioDesde(),
213
- folioHasta: caf.getFolioHasta(),
214
- ambiente: 'certificacion',
215
- cafFingerprint,
216
- });
217
-
218
- // Marcar folio como enviado (después de envío exitoso)
219
- folioRegistry.markFolioSent({
220
- rutEmisor: '78206276-K',
221
- tipoDte: caf.getTipoDTE(),
222
- folio,
223
- folioDesde: caf.getFolioDesde(),
224
- folioHasta: caf.getFolioHasta(),
225
- ambiente: 'certificacion',
226
- cafFingerprint,
352
+ ## Gestión de folios
353
+
354
+ Los folios son el recurso más crítico del ciclo de facturación: sin folio válido no hay DTE. La librería provee tres capas que se complementan:
355
+
356
+ | Capa | Clase | Responsabilidad |
357
+ |------|-------|-----------------|
358
+ | **Local** | `FolioRegistry` | Asigna y persiste folios desde un CAF ya descargado |
359
+ | **SII** | `FolioService` | Consulta, solicita y anula rangos de folios ante el SII |
360
+ | **Automática** | `CafSolicitor` | Descarga el XML del CAF nuevo directamente desde el portal SII |
361
+
362
+ El flujo típico de producción combina las tres: `FolioRegistry` asigna folios del CAF activo; cuando el rango se agota, `CafSolicitor` solicita un CAF nuevo al SII sin intervención humana; si quedan folios sin usar de un CAF anterior, `FolioService` los anula para mantener la contabilidad en orden.
363
+
364
+ ---
365
+
366
+ ### FolioRegistry: registro local
367
+
368
+ `FolioRegistry` mantiene un JSON en disco que registra qué folios están reservados, usados o pendientes de confirmación. Previene la doble asignación incluso ante reinicios del proceso.
369
+
370
+ ```javascript
371
+ const { FolioRegistry, CAF, createCafFingerprint } = require('@devlas/dte-sii')
372
+ const fs = require('fs')
373
+
374
+ const registry = new FolioRegistry() // persiste en disco (JSON)
375
+ const cafXml = fs.readFileSync('caf_33.xml', 'utf8')
376
+ const caf = new CAF(cafXml)
377
+ const fingerprint = createCafFingerprint(cafXml) // hash único del CAF
378
+
379
+ // Reservar el siguiente folio disponible del rango del CAF
380
+ const folio = registry.reserveNextFolio({
381
+ rutEmisor: '76354771-K',
382
+ tipoDte: caf.getTipoDTE(),
383
+ folioDesde: caf.getFolioDesde(),
384
+ folioHasta: caf.getFolioHasta(),
385
+ ambiente: 'produccion',
386
+ cafFingerprint: fingerprint,
387
+ })
388
+
389
+ // ... generar y enviar el DTE ...
390
+
391
+ // Marcar folio como enviado al recibir trackId del SII
392
+ registry.markFolioSent({
393
+ rutEmisor: '76354771-K', tipoDte: 33, folio,
394
+ folioDesde: caf.getFolioDesde(), folioHasta: caf.getFolioHasta(),
395
+ ambiente: 'produccion', cafFingerprint: fingerprint,
227
396
  trackId: '0245283324',
228
- });
397
+ })
398
+ ```
399
+
400
+ `resolveCafPath` busca automáticamente el CAF más reciente disponible con folios libres, evitando la necesidad de hardcodear rutas:
401
+
402
+ ```javascript
403
+ const { resolveCafPath } = require('@devlas/dte-sii')
404
+
405
+ const cafPath = resolveCafPath({
406
+ tipoDte: 33,
407
+ rutEmisor: '76354771-K',
408
+ requiredCount: 1, // necesito al menos 1 folio disponible
409
+ ambiente: 'produccion',
410
+ })
411
+ const cafXml = fs.readFileSync(cafPath, 'utf8')
229
412
  ```
230
413
 
231
- ### Ejemplo de uso de FolioService (avanzado)
414
+ ---
415
+
416
+ ### FolioService: consulta, solicitud y anulación ante el SII
417
+
418
+ `FolioService` se comunica directamente con el SII para operar sobre folios: consultar el estado actual, solicitar un nuevo rango o anular folios no utilizados.
232
419
 
233
420
  ```javascript
234
- const { FolioService, Certificado } = require('@devlas/dte-sii');
235
-
236
- const certificado = new Certificado('certificado.pfx', 'password');
237
- const folioService = new FolioService({
238
- ambiente: 'certificacion',
239
- rutEmisor: '78206276-K',
240
- certificado: certificado,
241
- });
242
-
243
- // Consultar folios en el SII
244
- const consulta = await folioService.consultarFolios({ tipoDte: 33 });
245
- console.log('Último folio:', consulta.ultimoFolioFinal);
246
-
247
- // Anular folios no usados
248
- const resultado = await folioService.anularFolios({
249
- tipoDte: 33,
421
+ const { FolioService, Certificado } = require('@devlas/dte-sii')
422
+
423
+ const service = new FolioService({
424
+ ambiente: 'produccion',
425
+ rutEmisor: '76354771-K',
426
+ certificado: new Certificado(fs.readFileSync('empresa.pfx'), 'clave'),
427
+ })
428
+
429
+ // Consultar cuántos folios quedan y cuál fue el último emitido
430
+ const info = await service.consultarFolios({ tipoDte: 33 })
431
+ console.log('Último folio final:', info.ultimoFolioFinal)
432
+ console.log('Folios disponibles:', info.foliosDisponibles)
433
+
434
+ // Solicitar un nuevo rango de folios al SII
435
+ await service.solicitar({ tipoDte: 33, cantidad: 100 })
436
+
437
+ // Anular folios que nunca se usaron (evita descuadres en el SII)
438
+ await service.anularFolios({
439
+ tipoDte: 33,
250
440
  folioDesde: 50,
251
441
  folioHasta: 60,
252
- motivo: 'Folios no utilizados',
253
- });
442
+ motivo: 'Folios no utilizados por cambio de CAF',
443
+ })
444
+ ```
445
+
446
+ ---
447
+
448
+ ### CafSolicitor: obtención automática de CAF
449
+
450
+ `CafSolicitor` automatiza la descarga del XML del CAF desde el portal SII usando el certificado PFX, sin intervención manual. Es la pieza que cierra el ciclo de reposición automática de folios.
451
+
452
+ ```javascript
453
+ const { CafSolicitor, Certificado } = require('@devlas/dte-sii')
454
+
455
+ const solicitor = new CafSolicitor({
456
+ certificado: new Certificado(fs.readFileSync('empresa.pfx'), 'clave'),
457
+ ambiente: 'produccion',
458
+ })
459
+
460
+ const cafXml = await solicitor.solicitar({ tipoDte: 33, cantidad: 200 })
461
+ fs.writeFileSync('caf_33_nuevo.xml', cafXml)
462
+ ```
463
+
464
+ ---
465
+
466
+ ### Ciclo completo automatizado
467
+
468
+ El siguiente patrón implementa reposición y limpieza de folios sin intervención humana. Se recomienda ejecutarlo como un job periódico o al detectar que el CAF activo está por agotarse.
469
+
470
+ ```javascript
471
+ const {
472
+ FolioRegistry, FolioService, CafSolicitor, CAF,
473
+ Certificado, createCafFingerprint, resolveCafPath,
474
+ } = require('@devlas/dte-sii')
475
+ const fs = require('fs')
476
+ const path = require('path')
477
+
478
+ const CAF_DIR = path.join(__dirname, 'cafs')
479
+ const RUT = '76354771-K'
480
+ const AMBIENTE = 'produccion'
481
+ const TIPO_DTE = 33
482
+ const UMBRAL = 10 // solicitar nuevo CAF cuando queden menos de N folios
483
+ const CANTIDAD = 200 // folios a solicitar
484
+
485
+ const cert = new Certificado(fs.readFileSync('empresa.pfx'), 'clave')
486
+ const registry = new FolioRegistry()
487
+ const service = new FolioService({ ambiente: AMBIENTE, rutEmisor: RUT, certificado: cert })
488
+ const solicitor = new CafSolicitor({ certificado: cert, ambiente: AMBIENTE })
489
+
490
+ async function gestionarFolios() {
491
+ // 1. Consultar estado actual en el SII
492
+ const info = await service.consultarFolios({ tipoDte: TIPO_DTE })
493
+ console.log(`Folios disponibles: ${info.foliosDisponibles}`)
494
+
495
+ // 2. Solicitar nuevo CAF si quedan pocos folios
496
+ if (info.foliosDisponibles < UMBRAL) {
497
+ console.log('Solicitando nuevo CAF...')
498
+ const cafXml = await solicitor.solicitar({ tipoDte: TIPO_DTE, cantidad: CANTIDAD })
499
+ const archivo = path.join(CAF_DIR, `caf_${TIPO_DTE}_${Date.now()}.xml`)
500
+ fs.writeFileSync(archivo, cafXml)
501
+ console.log(`Nuevo CAF guardado en: ${archivo}`)
502
+ }
503
+
504
+ // 3. Detectar folios reservados pero nunca enviados (caídos en error)
505
+ // y anularlos en el SII para mantener la contabilidad limpia
506
+ const pendientes = registry.getFoliosPendientes({ rutEmisor: RUT, tipoDte: TIPO_DTE, ambiente: AMBIENTE })
507
+ for (const rango of pendientes) {
508
+ console.log(`Anulando folios caídos: ${rango.desde}-${rango.hasta}`)
509
+ await service.anularFolios({
510
+ tipoDte: TIPO_DTE,
511
+ folioDesde: rango.desde,
512
+ folioHasta: rango.hasta,
513
+ motivo: 'Folios reservados no emitidos por error de sistema',
514
+ })
515
+ }
516
+ }
517
+
518
+ // Ejecutar al inicio y luego cada hora
519
+ gestionarFolios().catch(console.error)
520
+ setInterval(() => gestionarFolios().catch(console.error), 60 * 60 * 1000)
254
521
  ```
255
522
 
523
+ **Puntos clave del ciclo automatizado:**
524
+
525
+ - `FolioRegistry` detecta folios que fueron reservados pero cuyo DTE nunca se envió exitosamente (por crash, timeout, etc.)
526
+ - `FolioService.anularFolios` limpia esos folios en el SII, previniendo descuadres en libros y RCOF
527
+ - `CafSolicitor` descarga el nuevo CAF directamente, sin necesidad de acceder al portal SII manualmente
528
+ - `resolveCafPath` hace que el código de emisión siempre use el CAF vigente, sin cambiar rutas hardcodeadas
529
+
530
+ ---
531
+
532
+ ## Sesión y autenticación con el SII
533
+
534
+ ### SiiPortalAuth: autenticación al portal
535
+
536
+ Obtiene datos de empresa (nro_resol, fch_resol) directamente desde el portal SII usando el certificado PFX. Usa el patrón Singleton por certificado para evitar el límite de sesiones del SII.
537
+
538
+ ```javascript
539
+ const { SiiPortalAuth, Certificado } = require('@devlas/dte-sii')
540
+
541
+ const cert = new Certificado(fs.readFileSync('empresa.pfx'), 'clave')
542
+ const auth = new SiiPortalAuth(cert)
543
+
544
+ await auth.autenticar()
545
+ const datos = await auth.obtenerDatosEmpresa()
546
+ // datos.nro_resol → número de resolución
547
+ // datos.fch_resol → fecha de resolución (AAAA-MM-DD)
548
+ // datos.razon_social, datos.giro, etc.
549
+
550
+ // Reutilizar sesión entre componentes (evita múltiples logins)
551
+ const cookies = await SiiPortalAuth.getCookieStringForPfx(cert)
552
+ ```
553
+
554
+ ### SiiSession: sesiones HTTP autenticadas
555
+
556
+ ```javascript
557
+ const { SiiSession, Certificado } = require('@devlas/dte-sii')
558
+
559
+ const session = new SiiSession(new Certificado(fs.readFileSync('empresa.pfx'), 'clave'))
560
+ await session.loginWithCertificate()
561
+ const resp = await session.request('GET', 'https://herculesr.sii.cl/...')
562
+ ```
563
+
564
+ ---
565
+
566
+ ## Aceptación y reclamo de DTE (WsReclamo)
567
+
568
+ `WsReclamo` implementa el web service `WSRECLAMO` del SII (v1.2) para registrar eventos de aceptación/rechazo de DTE por parte del receptor.
569
+
570
+ > `WsReclamo` no se re-exporta desde `index.js`. Importar directamente:
571
+
572
+ ```javascript
573
+ const WsReclamo = require('@devlas/dte-sii/WsReclamo')
574
+ const { Certificado } = require('@devlas/dte-sii')
575
+
576
+ const ws = new WsReclamo(new Certificado(fs.readFileSync('empresa.pfx'), 'clave'), 'produccion')
577
+
578
+ // Consultar historial de eventos de un DTE
579
+ const eventos = await ws.listarEventosHistDoc({
580
+ rutEmisor: '76354771-K',
581
+ tipoDTE: 33,
582
+ folio: 1,
583
+ rutReceptor: '12345678-9',
584
+ })
585
+
586
+ // Consultar estado desde la perspectiva del receptor
587
+ const estado = await ws.consultarEstadoReceptor({ ... })
588
+
589
+ // Registrar aceptación (ACD) o reclamo (RCD)
590
+ await ws.ingresarAceptacion({
591
+ rutEmisor: '76354771-K', tipoDTE: 33, folio: 1,
592
+ accion: 'ACD', // ACD=Aceptado, RCD=Reclamado, ERM=Otorga Mercaderías
593
+ })
594
+ ```
595
+
596
+ ---
597
+
598
+ ## Estados SII: Interpretación de respuestas
599
+
600
+ `EnviadorSII` clasifica automáticamente cada código en `esExitoso`, `esIntermedio` o `esRechazado`.
601
+
602
+ ### QueryEstUp - Estado del sobre de envío
603
+
604
+ | Código | Descripción | `esExitoso` | `esIntermedio` | `esRechazado` |
605
+ |--------|-------------|:-----------:|:--------------:|:-------------:|
606
+ | `EPR` | Envío Procesado | ✓ | | |
607
+ | `RPR` | Procesado con Reparos | ✓ | | |
608
+ | `REC` / `SOK` / `FOK` / `CRT` / `PRD` / `PDR` | En proceso de validación | | ✓ | |
609
+ | `RSC` | Error en Schema XML | | | ✓ |
610
+ | `RFR` | Error en Firma Digital | | | ✓ |
611
+ | `RCT` | Error en Carátula | | | ✓ |
612
+
613
+ ### QueryEstDte - Estado del DTE individual
614
+
615
+ | Código | Descripción | Clasificación |
616
+ |--------|-------------|---------------|
617
+ | `DOK` | Datos coinciden | ✓ Exitoso |
618
+ | `DNK` | Datos no coinciden | ~ Intermedio |
619
+ | `FAU` | Folio no autorizado | ✗ Rechazado |
620
+ | `FNA` | Emisor no habilitado | ✗ Rechazado |
621
+ | `FAN` / `AND` / `ANC` | Anulado | ✗ Rechazado |
622
+ | `EMP` | Empresa sin autorización | ✗ Rechazado |
623
+
624
+ ### Códigos de error de consulta (-1 a -14)
625
+
626
+ Los valores negativos son **errores del servidor de consulta del SII**, no rechazo del documento. Resultan en `esIntermedio = true` y pueden reintentarse.
627
+
628
+ ---
629
+
630
+ ## Manejo de errores
631
+
632
+ Todos los errores operacionales lanzan `DteSiiError` con las propiedades:
633
+
634
+ | Propiedad | Descripción |
635
+ |-----------|-------------|
636
+ | `code` | Código de error (`CERT_ERROR`, `DTE_ERROR`, `SII_ERROR`, etc.) |
637
+ | `message` | Mensaje descriptivo |
638
+ | `cause` | Error original (si aplica) |
639
+
640
+ ```javascript
641
+ const { DteSiiError } = require('@devlas/dte-sii')
642
+
643
+ try {
644
+ await enviador.enviarDteSoap(envio)
645
+ } catch (err) {
646
+ if (err instanceof DteSiiError) {
647
+ console.error(`[${err.code}] ${err.message}`)
648
+ }
649
+ }
650
+ ```
651
+
652
+ ---
653
+
654
+ ## Configuración global y reintentos
655
+
656
+ ```javascript
657
+ const { configure, configureRetry } = require('@devlas/dte-sii')
658
+
659
+ // Configuración global (aplicar al inicio de la app)
660
+ configure({
661
+ ambiente: 'produccion', // 'produccion' | 'certificacion'
662
+ defaultRutEmisor: '76354771-K',
663
+ tokenCacheTtlMs: 300_000, // 5 minutos (default)
664
+ })
665
+
666
+ // Lógica de reintentos para llamadas al SII
667
+ configureRetry({
668
+ maxAttempts: 3,
669
+ initialDelayMs: 1_000,
670
+ backoffFactor: 2,
671
+ retryOn: ['SII_TIMEOUT', 'SII_SERVER_ERROR'],
672
+ })
673
+ ```
674
+
675
+ ---
676
+
256
677
  ## Utilidades
257
678
 
258
- | Función | Descripción |
259
- |---------|-------------|
260
- | `sanitizeSiiText()` | Elimina caracteres problemáticos (apóstrofes, comillas) |
261
- | `formatRut()` | Formatea RUT chileno |
262
- | `calcularDV()` | Calcula dígito verificador |
263
- | `formatBase64InXml()` | Formatea base64 con saltos de línea |
679
+ Accesibles como named exports o mediante el namespace `utils`:
680
+
681
+ ```javascript
682
+ const {
683
+ // RUT
684
+ formatRut, validarRut, calcularDV, splitRut,
685
+ // Sanitización
686
+ sanitizeSiiText, sanitizeRazonSocial, sanitizeNombreItem,
687
+ // XML
688
+ parseXml, parseXmlNoNs, buildXml, formatBase64InXml,
689
+ // Cálculos monetarios
690
+ calcularTotalesDesdeItems, calcularMontoItem, buildDetalle,
691
+ // Construcción de entidades
692
+ buildEmisor, normalizeEmisor, validarEmisor,
693
+ buildReceptor, normalizeReceptor, RECEPTOR_CONSUMIDOR_FINAL,
694
+ buildDocReferencia, buildReferenciasNcNd,
695
+ createResolucion, createResolucionCertificacion,
696
+ // Constantes
697
+ TIPOS_DTE, TIPOS_BOLETA, NOMBRES_DTE, TASA_IVA,
698
+ // Folios
699
+ createCafFingerprint, findLatestCaf, resolveCafPath,
700
+ // Endpoints SII
701
+ SOAP_ENDPOINTS, REST_ENDPOINTS, getHost,
702
+ // Token cache
703
+ getCachedToken, setCachedToken, pruneExpiredTokens,
704
+ // Logging
705
+ logger, configureLogger, createScopedLogger,
706
+ } = require('@devlas/dte-sii')
707
+ ```
708
+
709
+ ### Tabla de referencia rápida
710
+
711
+ | Función / Constante | Descripción |
712
+ |---------------------|-------------|
713
+ | `formatRut(rut)` | Formatea RUT chileno con puntos y guion |
714
+ | `validarRut(rut)` | Valida dígito verificador |
715
+ | `calcularDV(rut)` | Calcula dígito verificador de un RUT |
716
+ | `sanitizeSiiText(str)` | Elimina caracteres no aceptados por el SII |
717
+ | `sanitizeRazonSocial(str)` | Sanitiza razones sociales |
718
+ | `sanitizeNombreItem(str)` | Sanitiza nombres de ítems |
719
+ | `calcularTotalesDesdeItems(items)` | Calcula MntNeto, IVA, MntTotal |
720
+ | `buildEmisor(data)` | Construye nodo `<Emisor>` |
721
+ | `buildReceptor(data)` | Construye nodo `<Receptor>` |
722
+ | `RECEPTOR_CONSUMIDOR_FINAL` | Objeto receptor para boletas sin RUT receptor |
723
+ | `TASA_IVA` | `0.19` (IVA Chile) |
724
+ | `TIPOS_DTE` | `{ FACTURA: 33, FACTURA_EXENTA: 34, ... }` |
725
+ | `NOMBRES_DTE` | Mapeo código → nombre legible |
726
+ | `createCafFingerprint(xml)` | Hash único de un CAF (para FolioRegistry) |
727
+ | `findLatestCaf(tipoDte, dir)` | Busca el CAF más reciente en un directorio |
728
+ | `resolveCafPath(opts)` | Resuelve ruta de CAF con validación de folios disponibles |
729
+ | `getHost(ambiente)` | URL base del host SII según ambiente |
730
+
731
+ ---
732
+
733
+ ## Uso desde proyectos ESM (interop)
734
+
735
+ Esta librería es **CommonJS**. Para usarla desde un proyecto ESM (Node.js nativo o `"type": "module"`):
736
+
737
+ ```javascript
738
+ // Node.js ESM puro
739
+ import { createRequire } from 'module'
740
+ const require = createRequire(import.meta.url)
741
+ const { Certificado, CAF, DTE, EnviadorSII } = require('@devlas/dte-sii')
742
+ ```
743
+
744
+ **TypeScript con ESM** (patrón usado en `devlas-cloud-api-node`):
745
+
746
+ ```typescript
747
+ import { createRequire } from 'module'
748
+ const _require = createRequire(import.meta.url)
749
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
750
+ const { Certificado, CAF, DTE } = _require('@devlas/dte-sii') as Record<string, new (...a: any[]) => any>
751
+
752
+ // WsReclamo se importa directamente (no está en index.js):
753
+ const WsReclamo = _require('@devlas/dte-sii/WsReclamo')
754
+ ```
755
+
756
+ ---
757
+
758
+ ## TypeScript
759
+
760
+ La librería incluye tipos completos en `dte-sii.d.ts`. Interfaces principales:
761
+
762
+ ```typescript
763
+ import type {
764
+ // Entidades
765
+ Emisor, Receptor, DetalleItem, Totales,
766
+ // DTEs
767
+ DteDatos, DteSimplificado,
768
+ // Configuración
769
+ GlobalConfig, RetryConfig, TokenCacheConfig,
770
+ // Errores
771
+ DteSiiError,
772
+ } from '@devlas/dte-sii'
773
+ ```
774
+
775
+ ---
776
+
777
+ ## Referencia de clases
778
+
779
+ ### Clases principales
780
+
781
+ | Clase | Archivo | Descripción |
782
+ |-------|---------|-------------|
783
+ | `Certificado` | `Certificado.js` | Carga y gestiona certificados digitales PFX/P12; valida expiración |
784
+ | `CAF` | `CAF.js` | Parsea CAF XML; valida rango de folios; firma TED |
785
+ | `DTE` | `DTE.js` | Genera, timbra y firma documentos tributarios (todos los tipos) |
786
+ | `Signer` | `Signer.js` | Firma XML-DSig compatible con SII (C14N + RSA-SHA1) |
787
+ | `EnvioDTE` | `Envio.js` | Sobre XML para facturas, guías y notas |
788
+ | `EnvioBOLETA` | `Envio.js` | Sobre XML para boletas electrónicas |
789
+ | `EnviadorSII` | `EnviadorSII.js` | Comunicación SOAP/REST con el SII; caché de tokens; reintentos |
790
+
791
+ ### Servicios y gestión de folios
792
+
793
+ | Clase | Archivo | Descripción |
794
+ |-------|---------|-------------|
795
+ | `BoletaService` | `BoletaService.js` | Flujo simplificado para crear boletas electrónicas |
796
+ | `FolioRegistry` | `FolioRegistry.js` | Registro local JSON de folios reservados/enviados |
797
+ | `FolioService` | `FolioService.js` | Consulta, solicita y anula folios ante el SII |
798
+ | `CafSolicitor` | `CafSolicitor.js` | Solicitud automatizada de CAF al SII |
799
+ | `SiiSession` | `SiiSession.js` | Sesiones HTTP autenticadas con certificado (cookie jar) |
800
+ | `SiiPortalAuth` | `SiiPortalAuth.js` | Autenticación al portal SII; obtiene datos de empresa; Singleton por cert |
801
+
802
+ ### Libros y reportes
803
+
804
+ | Clase | Archivo | Descripción |
805
+ |-------|---------|-------------|
806
+ | `ConsumoFolio` | `ConsumoFolio.js` | RCOF (Resumen Consumo de Folios) para boletas |
807
+ | `LibroCompraVenta` | `LibroCompraVenta.js` | Libro electrónico de compras/ventas |
808
+ | `LibroGuia` | `LibroGuia.js` | Libro electrónico de guías de despacho |
264
809
 
265
- ## Filosofía
810
+ ### Web services complementarios
266
811
 
267
- `@devlas/dte-sii` actúa como **facilitador transparente** entre el usuario y el SII:
812
+ | Clase | Archivo | Descripción |
813
+ |-------|---------|-------------|
814
+ | `WsReclamo` | `WsReclamo.js` | WS WSRECLAMO v1.2: aceptación, reclamo e historial de eventos de DTE |
268
815
 
269
- 1. **Sanitización automática**: El usuario no se preocupa por caracteres especiales
270
- 2. **Formatos flexibles**: Acepta datos simplificados o estructurados
271
- 3. **Encoding interno**: Maneja UTF-8/ISO-8859-1 automáticamente
272
- 4. **Firma automática**: Genera firmas XML-DSig compatibles con SII
273
- 5. **Gestión de folios**: Control automático de CAFs, folios y anulaciones
816
+ ---
817
+
818
+ ## Estructura de archivos
819
+
820
+ ```
821
+ dte-sii/
822
+ ├── index.js <- Punto de entrada; re-exporta todas las clases públicas
823
+ ├── dte-sii.d.ts <- Definiciones TypeScript (979 líneas)
824
+
825
+ ├── Certificado.js <- PFX/P12 loader
826
+ ├── CAF.js <- CAF parser
827
+ ├── DTE.js <- Document builder (XML, TED, firma)
828
+ ├── Signer.js <- XML-DSig
829
+ ├── Envio.js <- EnvioDTE + EnvioBOLETA
830
+ ├── EnviadorSII.js <- SOAP/REST con SII; caché tokens; reintentos
831
+
832
+ ├── BoletaService.js <- Flujo simplificado de boletas
833
+ ├── SiiPortalAuth.js <- Autenticación portal SII (Singleton)
834
+ ├── SiiSession.js <- Sesiones HTTP autenticadas
835
+ ├── SiiSessionStore.js <- Persistencia de sesiones
836
+ ├── CafSolicitor.js <- Solicitud automatizada de CAF
837
+
838
+ ├── FolioRegistry.js <- Registro local de folios (JSON)
839
+ ├── FolioService.js <- Gestión de folios ante el SII
840
+
841
+ ├── LibroBase.js <- Clase base para libros electrónicos
842
+ ├── LibroCompraVenta.js <- Libro compras/ventas
843
+ ├── LibroGuia.js <- Libro guías de despacho
844
+ ├── ConsumoFolio.js <- RCOF
845
+
846
+ ├── WsReclamo.js <- WS aceptación/reclamo de DTE
847
+
848
+ ├── utils/ <- 20 módulos de utilidades (RUT, XML, cálculos, etc.)
849
+ ├── cert/ <- 19 módulos para automatización de certificación SII
850
+ └── docs/ <- PDFs y XSDs oficiales del SII
851
+ ```
852
+
853
+ ---
854
+
855
+ ## Certificación SII
856
+
857
+ El directorio `cert/` contiene los helpers necesarios para ejecutar el proceso de certificación ante el SII. Orquestado por `CertRunner`, incluye:
858
+
859
+ - Generación de sets básicos, de compra, exentos y guías
860
+ - Libros de compras, ventas y guías para certificación
861
+ - Envío de boletas de certificación
862
+ - Intercambio de DTE entre contribuyentes (simulación)
863
+ - Generación de muestras impresas
864
+
865
+ ```javascript
866
+ // Uso desde devlas-cloud-api-node
867
+ const { CertFolioHelper } = require('@devlas/dte-sii')
868
+ ```
869
+
870
+ ---
871
+
872
+ ## Ambientes
873
+
874
+ | Ambiente | Constante | Descripción |
875
+ |----------|-----------|-------------|
876
+ | `'certificacion'` | - | Apunta a `maullin.sii.cl` - para pruebas y certificación |
877
+ | `'produccion'` | - | Apunta a `palena.sii.cl` - producción real |
878
+
879
+ > Siempre verifica la variable de entorno `SII_AMBIENTE` (o el parámetro `ambiente`) antes de ejecutar código DTE para evitar envíos accidentales a producción.
880
+
881
+ ---
882
+
883
+ ## Licencia
884
+
885
+ MIT - Copyright (c) 2026 [Devlas SpA](https://devlas.cl)
886
+
887
+ Implementa el protocolo XML público del SII de Chile.
888
+ Inspirada conceptualmente en [LibreDTE de SASCO SpA](https://libredte.cl).
package/SiiPortalAuth.js CHANGED
@@ -349,7 +349,7 @@ if (!fs.existsSync(SESSION_CACHE_PATH)) {
349
349
  * Obtiene datos del contribuyente desde ad_empresa2.
350
350
  * Incluye fch_resol, nro_resol, razón social, etc.
351
351
  *
352
- * @param {string} rutEmpresa - RUT sin DV (ej: "78206276")
352
+ * @param {string} rutEmpresa - RUT sin DV (ej: "12345678")
353
353
  * @param {string} dvEmpresa - DV (ej: "K")
354
354
  * @param {Object} [cookieJar] - Sesión ya autenticada (opcional; si omite, autenticará)
355
355
  * @returns {Promise<Object>} { rut, razonSocial, fch_resol, nro_resol, fecha_autorizacion }
@@ -378,7 +378,7 @@ if (!fs.existsSync(SESSION_CACHE_PATH)) {
378
378
  * - fch_resol / nro_resol (desde ad_empresa2)
379
379
  * - rut, razonSocial, giro, dirección, comuna, acteco (desde pe_construccion_dte)
380
380
  *
381
- * @param {string} rutEmpresa - RUT sin DV (ej: "78206276")
381
+ * @param {string} rutEmpresa - RUT sin DV (ej: "12345678")
382
382
  * @param {string} dvEmpresa - DV (ej: "K")
383
383
  * @returns {Promise<Object>} Datos completos del emisor
384
384
  */
@@ -398,7 +398,7 @@ if (!fs.existsSync(SESSION_CACHE_PATH)) {
398
398
  * → redirige a ce_consulta_muestra_e con la tabla de datos
399
399
  * 2. Parsea la tabla: nombre, dirección, actividades económicas, glosa
400
400
  *
401
- * @param {string} rutEmpresa - RUT sin DV (ej: "78206276")
401
+ * @param {string} rutEmpresa - RUT sin DV (ej: "12345678")
402
402
  * @param {string} dvEmpresa - DV (ej: "K")
403
403
  * @param {Object} [cookieJar] - Sesión autenticada (si omite, autenticará)
404
404
  * @returns {Promise<Object>} { rut, razonSocial, direccion, comuna, dirReg, acteco, glosa }
@@ -424,10 +424,10 @@ if (!fs.existsSync(SESSION_CACHE_PATH)) {
424
424
  * Parsea el HTML de ce_consulta_muestra_e.
425
425
  *
426
426
  * Estructura real del SII:
427
- * <td>DATOS DEL CONTRIBUYENTE RUT</td><td>78206276-K</td>
428
- * <td>NOMBRE O RAZÓN SOCIAL</td><td>DEVLAS SPA</td>
429
- * <td>DIRECCIÓN DE LA EMPRESA</td><td>AV.ESC.AGRICOLA 1710..., Comuna MACUL</td>
430
- * <td>DIRECCIÓN REGIONAL DEL CONTRIBUYENTE</td><td>NUNOA</td>
427
+ * <td>DATOS DEL CONTRIBUYENTE RUT</td><td>12345678-9</td>
428
+ * <td>NOMBRE O RAZÓN SOCIAL</td><td>EMPRESA EJEMPLO SPA</td>
429
+ * <td>DIRECCIÓN DE LA EMPRESA</td><td>AV. EJEMPLO 123, Comuna Santiago</td>
430
+ * <td>DIRECCIÓN REGIONAL DEL CONTRIBUYENTE</td><td>SANTIAGO</td>
431
431
  * + tabla actividades: <td>620100</td><td>ACTIVIDADES DE PROGRAMACION...</td><td>SI</td>
432
432
  * + tabla glosa: <td>GLOSA DESCRIPTIVA</td><td>Desarrollo de software...</td>
433
433
  * @private
@@ -599,7 +599,7 @@ if (!fs.existsSync(SESSION_CACHE_PATH)) {
599
599
  /**
600
600
  * Obtiene el detalle de DTEs emitidos o recibidos desde www4.sii.cl.
601
601
  *
602
- * @param {string} rut - RUT sin DV (ej: "78206276")
602
+ * @param {string} rut - RUT sin DV (ej: "12345678")
603
603
  * @param {string} dv - DV (ej: "K")
604
604
  * @param {string} periodo - Período YYYY-MM (ej: "2026-05")
605
605
  * @param {number} operacion - 1 = compras / recibidos, 2 = ventas / emitidos
@@ -2656,10 +2656,10 @@ class CertRunner {
2656
2656
  * 4. Click "Bajar Nuevo Set" → descarga archivo .txt
2657
2657
  * @param {Object} opts
2658
2658
  * @param {string} [opts.setPath] - Ruta donde guardar el set
2659
- * @param {string} [opts.correoSet='sii.certificacion@devlas.cl'] - Correo proveedor para el set
2659
+ * @param {string} [opts.correoSet=''] - Correo proveedor para el set
2660
2660
  * @returns {Promise<{success: boolean, setText?: string, error?: string}>}
2661
2661
  */
2662
- async obtenerSetBoletaPortal({ setPath, correoSet = 'sii.certificacion@devlas.cl' } = {}) {
2662
+ async obtenerSetBoletaPortal({ setPath, correoSet = '' } = {}) {
2663
2663
  const puppeteer = require('puppeteer');
2664
2664
  const cookieJar = await this._obtenerCookiesSII();
2665
2665
  const puppeteerCookies = Object.entries(cookieJar).map(([name, value]) => ({
@@ -2944,16 +2944,16 @@ class CertRunner {
2944
2944
  * Marca los checkboxes de requisitos y rellena el formulario de proveedor.
2945
2945
  * @param {Object} opts
2946
2946
  * @param {string} [opts.linkConsulta='www.sii.cl']
2947
- * @param {string} [opts.rutProveedor='78206276-K']
2948
- * @param {string} [opts.nombreProveedor='DEVLAS SPA']
2949
- * @param {string} [opts.correoProveedor='certificacion.sii@devlas.cl']
2947
+ * @param {string} opts.rutProveedor
2948
+ * @param {string} opts.nombreProveedor
2949
+ * @param {string} opts.correoProveedor
2950
2950
  * @returns {Promise<{success: boolean, mensaje?: string, error?: string}>}
2951
2951
  */
2952
2952
  async completarDeclaracionBoletaPortal({
2953
2953
  linkConsulta = 'www.sii.cl',
2954
- rutProveedor = '78206276-K',
2955
- nombreProveedor = 'DEVLAS SPA',
2956
- correoProveedor = 'certificacion.sii@devlas.cl',
2954
+ rutProveedor = '',
2955
+ nombreProveedor = '',
2956
+ correoProveedor = '',
2957
2957
  } = {}) {
2958
2958
  const puppeteer = require('puppeteer');
2959
2959
  const cookieJar = await this._obtenerCookiesSII();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@devlas/dte-sii",
3
- "version": "2.9.5",
3
+ "version": "2.9.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",
@@ -18,8 +18,8 @@ const { getConfigSection } = require('./config');
18
18
  /**
19
19
  * Estructura del cache:
20
20
  * {
21
- * 'certificacion:rest:78206276-K': { token: 'xxx', expiresAt: Date },
22
- * 'produccion:soap:78206276-K': { token: 'yyy', expiresAt: Date },
21
+ * 'certificacion:rest:12345678-9': { token: 'xxx', expiresAt: Date },
22
+ * 'produccion:soap:12345678-9': { token: 'yyy', expiresAt: Date },
23
23
  * }
24
24
  */
25
25
  const tokenStore = new Map();