@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.
- package/BoletaService.js +109 -0
- package/CAF.js +173 -0
- package/CafSolicitor.js +380 -0
- package/Certificado.js +123 -0
- package/ConsumoFolio.js +376 -0
- package/DTE.js +399 -0
- package/EnviadorSII.js +1304 -0
- package/Envio.js +196 -0
- package/FolioRegistry.js +553 -0
- package/FolioService.js +703 -0
- package/LICENSE +27 -0
- package/LibroBase.js +134 -0
- package/LibroCompraVenta.js +205 -0
- package/LibroGuia.js +225 -0
- package/README.md +239 -0
- package/Signer.js +94 -0
- package/SiiCertificacion.js +1189 -0
- package/SiiPortalAuth.js +460 -0
- package/SiiSession.js +499 -0
- package/cert/BoletaCert.js +731 -0
- package/cert/CertFolioHelper.js +185 -0
- package/cert/CertRunner.js +2658 -0
- package/cert/ConfigLoader.js +133 -0
- package/cert/IntercambioCert.js +429 -0
- package/cert/LibroCompras.js +359 -0
- package/cert/LibroGuias.js +171 -0
- package/cert/LibroVentas.js +153 -0
- package/cert/MuestrasImpresas.js +676 -0
- package/cert/SetBase.js +321 -0
- package/cert/SetBasico.js +413 -0
- package/cert/SetCompra.js +472 -0
- package/cert/SetExenta.js +490 -0
- package/cert/SetGuia.js +283 -0
- package/cert/SetParser.js +1184 -0
- package/cert/SetsProvider.js +499 -0
- package/cert/Simulacion.js +521 -0
- package/cert/comunaOficina.js +460 -0
- package/cert/index.js +124 -0
- package/cert/types.js +330 -0
- package/dte-sii.d.ts +458 -0
- package/index.js +428 -0
- package/package.json +48 -0
- package/utils/c14n.js +275 -0
- package/utils/calculo.js +396 -0
- package/utils/config.js +276 -0
- package/utils/constants.js +302 -0
- package/utils/emisor.js +174 -0
- package/utils/endpoints.js +225 -0
- package/utils/error.js +235 -0
- package/utils/index.js +339 -0
- package/utils/logger.js +239 -0
- package/utils/pfx.js +203 -0
- package/utils/receptor.js +218 -0
- package/utils/referencia.js +169 -0
- package/utils/resolucion.js +119 -0
- package/utils/rut.js +169 -0
- package/utils/sanitize.js +124 -0
- package/utils/tokenCache.js +214 -0
- package/utils/xml.js +358 -0
- package/utils.js +4 -0
package/cert/SetBase.js
ADDED
|
@@ -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;
|