@devlas/dte-sii 2.5.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.
Files changed (60) hide show
  1. package/BoletaService.js +109 -0
  2. package/CAF.js +173 -0
  3. package/CafSolicitor.js +380 -0
  4. package/Certificado.js +123 -0
  5. package/ConsumoFolio.js +376 -0
  6. package/DTE.js +399 -0
  7. package/EnviadorSII.js +1304 -0
  8. package/Envio.js +196 -0
  9. package/FolioRegistry.js +553 -0
  10. package/FolioService.js +703 -0
  11. package/LICENSE +27 -0
  12. package/LibroBase.js +134 -0
  13. package/LibroCompraVenta.js +205 -0
  14. package/LibroGuia.js +225 -0
  15. package/README.md +239 -0
  16. package/Signer.js +94 -0
  17. package/SiiCertificacion.js +1189 -0
  18. package/SiiPortalAuth.js +460 -0
  19. package/SiiSession.js +499 -0
  20. package/cert/BoletaCert.js +731 -0
  21. package/cert/CertFolioHelper.js +185 -0
  22. package/cert/CertRunner.js +2658 -0
  23. package/cert/ConfigLoader.js +133 -0
  24. package/cert/IntercambioCert.js +429 -0
  25. package/cert/LibroCompras.js +359 -0
  26. package/cert/LibroGuias.js +171 -0
  27. package/cert/LibroVentas.js +153 -0
  28. package/cert/MuestrasImpresas.js +676 -0
  29. package/cert/SetBase.js +321 -0
  30. package/cert/SetBasico.js +413 -0
  31. package/cert/SetCompra.js +472 -0
  32. package/cert/SetExenta.js +490 -0
  33. package/cert/SetGuia.js +283 -0
  34. package/cert/SetParser.js +1184 -0
  35. package/cert/SetsProvider.js +499 -0
  36. package/cert/Simulacion.js +521 -0
  37. package/cert/comunaOficina.js +460 -0
  38. package/cert/index.js +124 -0
  39. package/cert/types.js +330 -0
  40. package/dte-sii.d.ts +458 -0
  41. package/index.js +428 -0
  42. package/package.json +48 -0
  43. package/utils/c14n.js +275 -0
  44. package/utils/calculo.js +396 -0
  45. package/utils/config.js +276 -0
  46. package/utils/constants.js +302 -0
  47. package/utils/emisor.js +174 -0
  48. package/utils/endpoints.js +225 -0
  49. package/utils/error.js +235 -0
  50. package/utils/index.js +339 -0
  51. package/utils/logger.js +239 -0
  52. package/utils/pfx.js +203 -0
  53. package/utils/receptor.js +218 -0
  54. package/utils/referencia.js +169 -0
  55. package/utils/resolucion.js +119 -0
  56. package/utils/rut.js +169 -0
  57. package/utils/sanitize.js +124 -0
  58. package/utils/tokenCache.js +214 -0
  59. package/utils/xml.js +358 -0
  60. package/utils.js +4 -0
@@ -0,0 +1,321 @@
1
+ // Copyright (c) 2026 Devlas SpA — https://devlas.cl
2
+ // Licencia MIT. Ver archivo LICENSE para mas detalles.
3
+ /**
4
+ * Clase Base para Sets de Certificación
5
+ *
6
+ * Proporciona la estructura común para todos los sets:
7
+ * - SetBasico (33, 56, 61)
8
+ * - SetExenta (34)
9
+ * - SetGuia (52)
10
+ * - SetCompra (46)
11
+ *
12
+ * Patrón: Template Method
13
+ * - ejecutar() define el flujo general
14
+ * - Subclases implementan generarDtes()
15
+ *
16
+ * @module dte-sii/cert/SetBase
17
+ */
18
+
19
+ const { SetResult } = require('./types');
20
+
21
+ class SetBase {
22
+ /**
23
+ * @param {Object} deps - Dependencias inyectadas
24
+ * @param {Object} deps.config - Configuración (emisor, receptor, certificado, etc.)
25
+ * @param {Object} deps.cafManager - Gestor de CAFs (ensureCaf)
26
+ * @param {Object} deps.folioHelper - Helper de folios (reserveNextFolio, createCafFingerprint)
27
+ * @param {Object} deps.enviador - Función o clase para enviar al SII
28
+ * @param {Object} [deps.logger] - Logger opcional
29
+ */
30
+ constructor(deps) {
31
+ this._validateDeps(deps);
32
+
33
+ this.config = deps.config;
34
+ this.cafManager = deps.cafManager;
35
+ this.folioHelper = deps.folioHelper;
36
+ this.enviador = deps.enviador;
37
+ this.logger = deps.logger || console;
38
+
39
+ // Subclases deben definir estos
40
+ this.key = ''; // 'basico', 'exenta', 'guia', 'compra'
41
+ this.label = ''; // 'Set Básico (Facturas)'
42
+ this.tiposDte = []; // [33, 56, 61]
43
+ }
44
+
45
+ /**
46
+ * Valida que las dependencias requeridas estén presentes
47
+ * @private
48
+ */
49
+ _validateDeps(deps) {
50
+ const required = ['config', 'cafManager', 'folioHelper', 'enviador'];
51
+ for (const dep of required) {
52
+ if (!deps[dep]) {
53
+ throw new Error(`SetBase: dependencia '${dep}' es requerida`);
54
+ }
55
+ }
56
+
57
+ // Validar config mínima
58
+ const { config } = deps;
59
+ if (!config.emisor?.rut) {
60
+ throw new Error('SetBase: config.emisor.rut es requerido');
61
+ }
62
+ if (!config.certificado?.path) {
63
+ throw new Error('SetBase: config.certificado.path es requerido');
64
+ }
65
+ }
66
+
67
+ /**
68
+ * Ejecuta el set completo (Template Method)
69
+ *
70
+ * Flujo:
71
+ * 1. Validar casos de entrada
72
+ * 2. Asegurar CAFs disponibles
73
+ * 3. Generar DTEs (implementado por subclases)
74
+ * 4. Crear envío firmado
75
+ * 5. Enviar al SII
76
+ * 6. Retornar resultado
77
+ *
78
+ * @param {Object} casos - Casos del set parseados del SII
79
+ * @param {Object} [preloadedCafs] - CAFs pre-cargados { tipoDte: path } (opcional)
80
+ * @returns {Promise<SetResult>}
81
+ */
82
+ async ejecutar(casos, preloadedCafs = null) {
83
+ const startTime = Date.now();
84
+ this.logger.log(`\n${'═'.repeat(60)}`);
85
+ this.logger.log(`🚀 ${this.label}`);
86
+ this.logger.log(`${'═'.repeat(60)}\n`);
87
+
88
+ try {
89
+ // 1. Validar casos
90
+ this._validarCasos(casos);
91
+
92
+ // 2. Asegurar CAFs disponibles (usa pre-cargados si existen)
93
+ this.logger.log('📋 Verificando folios (CAFs)...');
94
+ const cafs = preloadedCafs || await this.ensureCafs(casos);
95
+
96
+ if (preloadedCafs) {
97
+ for (const [tipoDte, cafPath] of Object.entries(preloadedCafs)) {
98
+ this.logger.log(` ✓ CAF tipo ${tipoDte} (pre-cargado): ${cafPath}`);
99
+ }
100
+ }
101
+
102
+ // 3. Generar DTEs (subclases implementan)
103
+ this.logger.log('📝 Generando DTEs...');
104
+ const dtes = await this.generarDtes(casos, cafs);
105
+ this.logger.log(` ✓ ${dtes.length} DTEs generados`);
106
+
107
+ // 4. Crear envío firmado
108
+ this.logger.log('🔐 Firmando envío...');
109
+ const envio = await this.crearEnvio(dtes);
110
+
111
+ // 5. Enviar al SII
112
+ this.logger.log('📤 Enviando al SII...');
113
+ const respuesta = await this.enviarSii(envio);
114
+
115
+ // 6. Construir resultado con documentos para libros
116
+ const documentos = dtes.map(dte => {
117
+ const idDocData = dte.datos?.Encabezado?.IdDoc || {};
118
+ const totalesData = dte.datos?.Encabezado?.Totales || {};
119
+
120
+ // Extraer IVARetTotal desde ImptoReten (TipoImp=15)
121
+ let ivaRetTotal = 0;
122
+ if (totalesData.ImptoReten) {
123
+ const retenciones = Array.isArray(totalesData.ImptoReten)
124
+ ? totalesData.ImptoReten
125
+ : [totalesData.ImptoReten];
126
+ const ret15 = retenciones.find(r => r.TipoImp === 15);
127
+ if (ret15) {
128
+ ivaRetTotal = Number(ret15.MontoImp || 0);
129
+ }
130
+ }
131
+
132
+ return {
133
+ tipoDte: dte.getTipoDTE(),
134
+ folio: dte.getFolio(),
135
+ id: dte.getId(),
136
+ fecha: idDocData.FchEmis,
137
+ indTraslado: idDocData.IndTraslado, // Para guías
138
+ totales: {
139
+ MntExe: totalesData.MntExe || 0,
140
+ MntNeto: totalesData.MntNeto || 0,
141
+ IVA: totalesData.IVA || 0,
142
+ MntTotal: totalesData.MntTotal || dte.getMontoTotal(),
143
+ TasaIVA: totalesData.TasaIVA || 19,
144
+ IVARetTotal: ivaRetTotal, // Extraído de ImptoReten[TipoImp=15]
145
+ },
146
+ };
147
+ });
148
+
149
+ const result = SetResult.success({
150
+ trackId: respuesta.trackId,
151
+ documentos, // Incluye totales para libros
152
+ dtes: documentos.map(d => ({ // Compatibilidad
153
+ tipo: d.tipoDte,
154
+ folio: d.folio,
155
+ id: d.id,
156
+ montoTotal: d.totales.MntTotal,
157
+ })),
158
+ xmlPath: respuesta.xmlPath,
159
+ responsePath: respuesta.responsePath,
160
+ duration: Date.now() - startTime,
161
+ });
162
+
163
+ this.logger.log(`\n✅ ${this.label} completado`);
164
+ this.logger.log(` Track ID: ${result.trackId}`);
165
+ this.logger.log(` Duración: ${result.duration}ms\n`);
166
+
167
+ return result;
168
+
169
+ } catch (error) {
170
+ this.logger.error(`\n❌ Error en ${this.label}: ${error.message}\n`);
171
+
172
+ const result = SetResult.failure(error.message);
173
+ result.duration = Date.now() - startTime;
174
+ return result;
175
+ }
176
+ }
177
+
178
+ /**
179
+ * Valida que los casos tengan la estructura esperada
180
+ * @protected
181
+ * @param {Object} casos
182
+ */
183
+ _validarCasos(casos) {
184
+ if (!casos) {
185
+ throw new Error(`No se proporcionaron casos para ${this.label}`);
186
+ }
187
+ // Subclases pueden extender esta validación
188
+ }
189
+
190
+ /**
191
+ * Asegura que hay CAFs disponibles para todos los tipos de DTE
192
+ * @param {Object} casos - Para calcular cantidad necesaria
193
+ * @returns {Promise<Object>} - { [tipoDte]: cafPath }
194
+ */
195
+ async ensureCafs(casos) {
196
+ const cafs = {};
197
+
198
+ for (const tipoDte of this.tiposDte) {
199
+ const cantidad = this._calcularCantidadFolios(casos, tipoDte);
200
+ this.logger.log(` Tipo ${tipoDte}: ${cantidad} folios requeridos`);
201
+
202
+ const cafPath = await this.cafManager.ensureCaf({
203
+ tipoDte,
204
+ rutEmisor: this.config.emisor.rut,
205
+ requiredCount: cantidad,
206
+ forceNew: false,
207
+ preferExisting: true,
208
+ });
209
+
210
+ if (!cafPath) {
211
+ throw new Error(`No se pudo obtener CAF para tipo ${tipoDte}`);
212
+ }
213
+
214
+ cafs[tipoDte] = cafPath;
215
+ this.logger.log(` ✓ CAF tipo ${tipoDte}: ${cafPath}`);
216
+ }
217
+
218
+ return cafs;
219
+ }
220
+
221
+ /**
222
+ * Calcula cuántos folios se necesitan para un tipo de DTE
223
+ * Subclases pueden sobrescribir para lógica específica
224
+ * @protected
225
+ * @param {Object} casos
226
+ * @param {number} tipoDte
227
+ * @returns {number}
228
+ */
229
+ _calcularCantidadFolios(casos, tipoDte) {
230
+ // Implementación por defecto: cuenta los casos de ese tipo
231
+ if (casos.cafRequired && casos.cafRequired[tipoDte]) {
232
+ return casos.cafRequired[tipoDte];
233
+ }
234
+ return 1;
235
+ }
236
+
237
+ /**
238
+ * Genera los DTEs del set
239
+ *
240
+ * ⚠️ DEBE SER IMPLEMENTADO POR SUBCLASES
241
+ *
242
+ * @abstract
243
+ * @param {Object} casos - Casos parseados del SII
244
+ * @param {Object} cafs - { [tipoDte]: cafPath }
245
+ * @returns {Promise<DTE[]>}
246
+ */
247
+ async generarDtes(casos, cafs) {
248
+ throw new Error(`${this.constructor.name} debe implementar generarDtes()`);
249
+ }
250
+
251
+ /**
252
+ * Crea un EnvioDTE firmado con los DTEs generados
253
+ * @param {DTE[]} dtes
254
+ * @returns {Promise<Object>} - Envío listo para enviar
255
+ */
256
+ async crearEnvio(dtes) {
257
+ const { EnvioDTE, Certificado } = require('../index');
258
+ const fs = require('fs');
259
+
260
+ // Cargar certificado
261
+ const pfxBuffer = fs.readFileSync(this.config.certificado.path);
262
+ const cert = new Certificado(pfxBuffer, this.config.certificado.password);
263
+
264
+ // Timestamp para firma
265
+ const timestamp = new Date().toISOString().replace('Z', '');
266
+
267
+ // Crear envío
268
+ const envio = new EnvioDTE({ certificado: cert });
269
+
270
+ // Agregar DTEs
271
+ for (const dte of dtes) {
272
+ envio.agregar(dte);
273
+ }
274
+
275
+ // Configurar carátula
276
+ envio.setCaratula({
277
+ RutEmisor: this.config.emisor.rut,
278
+ RutEnvia: cert.rut || this.config.emisor.rut,
279
+ RutReceptor: '60803000-K', // SII para certificación
280
+ FchResol: this.config.resolucion?.fecha || this.config.emisor.fch_resol,
281
+ NroResol: this.config.resolucion?.numero ?? this.config.emisor.nro_resol,
282
+ TmstFirmaEnv: timestamp,
283
+ SetDTEId: 'DTE_SetDoc',
284
+ });
285
+
286
+ // Generar XML
287
+ envio.generar();
288
+
289
+ return envio;
290
+ }
291
+
292
+ /**
293
+ * Envía el envío firmado al SII
294
+ * @param {Object} envio - EnvioDTE firmado
295
+ * @returns {Promise<Object>} - { trackId, xmlPath, responsePath }
296
+ */
297
+ async enviarSii(envio) {
298
+ const resultado = await this.enviador.enviar(envio, {
299
+ ambiente: this.config.ambiente || 'certificacion',
300
+ });
301
+
302
+ if (!resultado.success) {
303
+ throw new Error(resultado.error || 'Error al enviar al SII');
304
+ }
305
+
306
+ return resultado;
307
+ }
308
+
309
+ /**
310
+ * Genera los DTEs sin enviar (para preview/debug)
311
+ * @param {Object} casos
312
+ * @returns {Promise<DTE[]>}
313
+ */
314
+ async preview(casos) {
315
+ this._validarCasos(casos);
316
+ const cafs = await this.ensureCafs(casos);
317
+ return this.generarDtes(casos, cafs);
318
+ }
319
+ }
320
+
321
+ module.exports = SetBase;