@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
|
@@ -0,0 +1,499 @@
|
|
|
1
|
+
// Copyright (c) 2026 Devlas SpA — https://devlas.cl
|
|
2
|
+
// Licencia MIT. Ver archivo LICENSE para mas detalles.
|
|
3
|
+
/**
|
|
4
|
+
* SetsProvider - Obtención y Parseo de Sets de Prueba del SII
|
|
5
|
+
*
|
|
6
|
+
* Responsabilidades:
|
|
7
|
+
* - Iniciar/reusar sesión SII
|
|
8
|
+
* - Obtener sets de prueba del portal
|
|
9
|
+
* - Parsear HTML a estructuras JSON
|
|
10
|
+
* - Detectar si se regeneró el set (requiere reiniciar proceso)
|
|
11
|
+
*
|
|
12
|
+
* @module dte-sii/cert/SetsProvider
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
const fs = require('fs');
|
|
16
|
+
const path = require('path');
|
|
17
|
+
const SiiCertificacion = require('../SiiCertificacion');
|
|
18
|
+
|
|
19
|
+
// Parser de sets (ahora en el core)
|
|
20
|
+
const SetParser = require('./SetParser');
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Resultado de obtener sets
|
|
24
|
+
* @typedef {Object} SetsResult
|
|
25
|
+
* @property {boolean} success
|
|
26
|
+
* @property {boolean} regenerated - Si el set fue regenerado (reiniciar proceso)
|
|
27
|
+
* @property {Object} estructuras - Casos parseados por set
|
|
28
|
+
* @property {Object} estadoSets - Estado de cada set en el SII
|
|
29
|
+
* @property {string|null} error
|
|
30
|
+
*/
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Sets opcionales por defecto para certificación
|
|
34
|
+
*/
|
|
35
|
+
const DEFAULT_SETS_OPCIONALES = {
|
|
36
|
+
SET03: 'S', // Guía Despacho
|
|
37
|
+
SET06: 'S', // Factura Exenta
|
|
38
|
+
SET72: 'S', // Factura Compra
|
|
39
|
+
// SET11: 'S', // Exportación - NO INCLUIR por defecto
|
|
40
|
+
// SET84: 'S', // Liquidación - NO INCLUIR por defecto
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
class SetsProvider {
|
|
44
|
+
/**
|
|
45
|
+
* @param {Object} options
|
|
46
|
+
* @param {string} options.pfxPath - Ruta al certificado .pfx
|
|
47
|
+
* @param {string} options.pfxPassword - Contraseña del certificado
|
|
48
|
+
* @param {string} options.rutEmpresa - RUT sin DV (ej: "76123456")
|
|
49
|
+
* @param {string} options.dvEmpresa - Dígito verificador
|
|
50
|
+
* @param {string} [options.sessionPath] - Ruta para guardar/cargar sesión
|
|
51
|
+
* @param {string} [options.debugDir] - Directorio para debug
|
|
52
|
+
* @param {Object} [options.logger] - Logger opcional
|
|
53
|
+
*/
|
|
54
|
+
constructor(options) {
|
|
55
|
+
this._validateOptions(options);
|
|
56
|
+
|
|
57
|
+
this.pfxPath = options.pfxPath;
|
|
58
|
+
this.pfxPassword = options.pfxPassword;
|
|
59
|
+
this.rutEmpresa = options.rutEmpresa;
|
|
60
|
+
this.dvEmpresa = options.dvEmpresa;
|
|
61
|
+
this.sessionPath = options.sessionPath || null;
|
|
62
|
+
this.debugDir = options.debugDir || null;
|
|
63
|
+
this.logger = options.logger || console;
|
|
64
|
+
|
|
65
|
+
// Instancia de SiiCertificacion (lazy init)
|
|
66
|
+
this._siiCert = null;
|
|
67
|
+
|
|
68
|
+
// Cache de estructuras
|
|
69
|
+
this._estructurasCache = null;
|
|
70
|
+
this._lastFetchTime = null;
|
|
71
|
+
this._lastNumeroAtencion = null; // Para detectar regeneración
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* Valida opciones requeridas
|
|
76
|
+
* @private
|
|
77
|
+
*/
|
|
78
|
+
_validateOptions(options) {
|
|
79
|
+
const required = ['pfxPath', 'pfxPassword', 'rutEmpresa', 'dvEmpresa'];
|
|
80
|
+
for (const key of required) {
|
|
81
|
+
if (!options[key]) {
|
|
82
|
+
throw new Error(`SetsProvider: '${key}' es requerido`);
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* Obtiene la instancia de SiiCertificacion (lazy init)
|
|
89
|
+
* @private
|
|
90
|
+
*/
|
|
91
|
+
_getSiiCert() {
|
|
92
|
+
if (!this._siiCert) {
|
|
93
|
+
this._siiCert = new SiiCertificacion({
|
|
94
|
+
pfxPath: this.pfxPath,
|
|
95
|
+
pfxPassword: this.pfxPassword,
|
|
96
|
+
rutEmpresa: this.rutEmpresa,
|
|
97
|
+
dvEmpresa: this.dvEmpresa,
|
|
98
|
+
});
|
|
99
|
+
}
|
|
100
|
+
return this._siiCert;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* Inicializa o reutiliza la sesión SII
|
|
105
|
+
* @returns {Promise<boolean>}
|
|
106
|
+
*/
|
|
107
|
+
async initSession() {
|
|
108
|
+
this.logger.log('[SetsProvider] 🔐 Inicializando sesión SII...');
|
|
109
|
+
|
|
110
|
+
const siiCert = this._getSiiCert();
|
|
111
|
+
|
|
112
|
+
// Intentar cargar sesión existente
|
|
113
|
+
if (this.sessionPath && SiiCertificacion.isSessionValid(this.sessionPath)) {
|
|
114
|
+
this.logger.log('[SetsProvider] ✓ Sesión existente válida');
|
|
115
|
+
const loaded = siiCert.loadSession(this.sessionPath);
|
|
116
|
+
if (loaded) {
|
|
117
|
+
return true;
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
// Crear nueva sesión
|
|
122
|
+
this.logger.log('[SetsProvider] Estableciendo nueva sesión...');
|
|
123
|
+
try {
|
|
124
|
+
// verAvance() fuerza el login
|
|
125
|
+
await siiCert.verAvance();
|
|
126
|
+
|
|
127
|
+
// Guardar sesión para reutilizar
|
|
128
|
+
if (this.sessionPath) {
|
|
129
|
+
this._ensureDir(path.dirname(this.sessionPath));
|
|
130
|
+
siiCert.saveSession(this.sessionPath);
|
|
131
|
+
this.logger.log('[SetsProvider] ✓ Sesión guardada');
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
return true;
|
|
135
|
+
} catch (error) {
|
|
136
|
+
this.logger.error(`[SetsProvider] ❌ Error: ${error.message}`);
|
|
137
|
+
return false;
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
/**
|
|
142
|
+
* Obtiene los sets de prueba del SII
|
|
143
|
+
* @param {Object} [options]
|
|
144
|
+
* @param {boolean} [options.descargar=true] - Descargar contenido del set
|
|
145
|
+
* @param {boolean} [options.forceRefresh=false] - Forzar nueva descarga
|
|
146
|
+
* @param {Object} [options.setsOpcionales] - Sets opcionales a incluir
|
|
147
|
+
* @returns {Promise<SetsResult>}
|
|
148
|
+
*/
|
|
149
|
+
async obtenerSets(options = {}) {
|
|
150
|
+
const {
|
|
151
|
+
descargar = true,
|
|
152
|
+
forceRefresh = false,
|
|
153
|
+
setsOpcionales = DEFAULT_SETS_OPCIONALES,
|
|
154
|
+
} = options;
|
|
155
|
+
|
|
156
|
+
this.logger.log('\n[SetsProvider] 📦 Obteniendo sets de prueba...');
|
|
157
|
+
|
|
158
|
+
// Si hay cache y no se fuerza refresh, retornar cache
|
|
159
|
+
if (!forceRefresh && this._estructurasCache) {
|
|
160
|
+
this.logger.log('[SetsProvider] ✓ Usando cache de estructuras');
|
|
161
|
+
return {
|
|
162
|
+
success: true,
|
|
163
|
+
regenerated: false,
|
|
164
|
+
estructuras: this._estructurasCache,
|
|
165
|
+
fromCache: true,
|
|
166
|
+
};
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
try {
|
|
170
|
+
// Asegurar sesión activa
|
|
171
|
+
const sessionOk = await this.initSession();
|
|
172
|
+
if (!sessionOk) {
|
|
173
|
+
return {
|
|
174
|
+
success: false,
|
|
175
|
+
error: 'No se pudo establecer sesión con el SII',
|
|
176
|
+
};
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
const siiCert = this._getSiiCert();
|
|
180
|
+
|
|
181
|
+
// Obtener sets del SII
|
|
182
|
+
const result = await siiCert.generarSetPruebas({
|
|
183
|
+
descargar,
|
|
184
|
+
setsOpcionales,
|
|
185
|
+
});
|
|
186
|
+
|
|
187
|
+
if (!result.success) {
|
|
188
|
+
return {
|
|
189
|
+
success: false,
|
|
190
|
+
error: result.error || 'Error al obtener sets del SII',
|
|
191
|
+
};
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
// Si después del self-reauth interno de generarSetPruebas SIGUE mostrando
|
|
195
|
+
// "no inscrito", la empresa genuinamente no está en el programa de postulación
|
|
196
|
+
if (result.noInscrito) {
|
|
197
|
+
return {
|
|
198
|
+
success: false,
|
|
199
|
+
error: 'El contribuyente no está inscrito en Postulación en el portal SII. ' +
|
|
200
|
+
'Accede a https://maullin.sii.cl y verifica el estado de certificación.',
|
|
201
|
+
};
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
// Guardar sesión después de operación exitosa
|
|
205
|
+
this.saveSession();
|
|
206
|
+
|
|
207
|
+
// Guardar HTML para debug
|
|
208
|
+
if (this.debugDir && result.rawHtml) {
|
|
209
|
+
this._saveDebug('sets-raw.html', result.rawHtml);
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
// Detectar si el set fue regenerado
|
|
213
|
+
const regenerated = this._detectRegeneration(result);
|
|
214
|
+
if (regenerated) {
|
|
215
|
+
this.logger.log('[SetsProvider] ⚠️ Set regenerado - reiniciar proceso');
|
|
216
|
+
// Invalidar cualquier cache anterior
|
|
217
|
+
this.invalidateCache();
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
// Parsear estructuras si se descargó
|
|
221
|
+
let estructuras = null;
|
|
222
|
+
let datosExtraidos = null;
|
|
223
|
+
|
|
224
|
+
if (descargar && result.setDescargado) {
|
|
225
|
+
// Verificar sesión válida
|
|
226
|
+
const htmlLower = String(result.setDescargado.rawHtml || '').toLowerCase();
|
|
227
|
+
if (htmlLower.includes('no se encuentra autenticado') ||
|
|
228
|
+
(htmlLower.includes('postulacion factura electronica') && !htmlLower.includes('caso'))) {
|
|
229
|
+
// Distinguir: ¿es "no inscrito" o expiró la sesión?
|
|
230
|
+
if (htmlLower.includes('no inscrito') || (htmlLower.includes('no est') && htmlLower.includes('inscrito'))) {
|
|
231
|
+
this.logger.log('[SetsProvider] ℹ️ pe_generar2: empresa no inscrita en Postulación (fase avanzada).');
|
|
232
|
+
return { success: false, error: 'El contribuyente no está inscrito en Postulación en el portal SII.' };
|
|
233
|
+
}
|
|
234
|
+
// Sesión expiró en el portal aunque el archivo no lo sabía → forzar re-autenticación
|
|
235
|
+
// Cookies vencidas server-side: limpiar sesión guardada y pedir a
|
|
236
|
+
// generarSetPruebas que re-autentique por sí mismo via pe_generar
|
|
237
|
+
this.logger.log('[SetsProvider] ⚠️ Sesión rechazada por portal, limpiando y reintentando...');
|
|
238
|
+
if (this.sessionPath) {
|
|
239
|
+
const SiiSession = require('../SiiSession');
|
|
240
|
+
SiiSession.clearSession(this.sessionPath);
|
|
241
|
+
}
|
|
242
|
+
this._siiCert = null;
|
|
243
|
+
|
|
244
|
+
const siiCertFresh = this._getSiiCert();
|
|
245
|
+
// sesión vacía → ensureSession dentro de generarSetPruebas hará el login redirect
|
|
246
|
+
const retryResult = await siiCertFresh.generarSetPruebas({ descargar, setsOpcionales });
|
|
247
|
+
if (!retryResult.success) {
|
|
248
|
+
return {
|
|
249
|
+
success: false,
|
|
250
|
+
error: retryResult.error || 'Error al obtener sets tras re-autenticación',
|
|
251
|
+
};
|
|
252
|
+
}
|
|
253
|
+
if (retryResult.noInscrito) {
|
|
254
|
+
return {
|
|
255
|
+
success: false,
|
|
256
|
+
error: 'El contribuyente no está inscrito en Postulación en el portal SII.',
|
|
257
|
+
};
|
|
258
|
+
}
|
|
259
|
+
// Guardar nueva sesión
|
|
260
|
+
if (this.sessionPath) {
|
|
261
|
+
this._ensureDir(path.dirname(this.sessionPath));
|
|
262
|
+
siiCertFresh.saveSession(this.sessionPath);
|
|
263
|
+
}
|
|
264
|
+
// Reemplazar result con el reintento exitoso para continuar el flujo normal
|
|
265
|
+
Object.assign(result, retryResult);
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
// Parsear usando flujoCert si está disponible
|
|
269
|
+
const parseResult = this._parseSetDescargado(result.setDescargado.rawHtml);
|
|
270
|
+
estructuras = parseResult.estructuras;
|
|
271
|
+
datosExtraidos = parseResult.datosExtraidos;
|
|
272
|
+
|
|
273
|
+
if (estructuras) {
|
|
274
|
+
this._estructurasCache = estructuras;
|
|
275
|
+
this._lastFetchTime = Date.now();
|
|
276
|
+
|
|
277
|
+
// Guardar estructuras para debug
|
|
278
|
+
if (this.debugDir) {
|
|
279
|
+
this._saveDebug('estructuras.json', JSON.stringify(estructuras, null, 2));
|
|
280
|
+
if (datosExtraidos) {
|
|
281
|
+
this._saveDebug('datos-extraidos.json', JSON.stringify(datosExtraidos, null, 2));
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
this.logger.log('[SetsProvider] ✓ Sets obtenidos correctamente');
|
|
288
|
+
|
|
289
|
+
return {
|
|
290
|
+
success: true,
|
|
291
|
+
regenerated,
|
|
292
|
+
estructuras,
|
|
293
|
+
datosExtraidos,
|
|
294
|
+
estadoSets: result.estadoSets,
|
|
295
|
+
setsOpcionales: result.setsOpcionales,
|
|
296
|
+
};
|
|
297
|
+
|
|
298
|
+
} catch (error) {
|
|
299
|
+
this.logger.error(`[SetsProvider] ❌ Error: ${error.message}`);
|
|
300
|
+
return {
|
|
301
|
+
success: false,
|
|
302
|
+
error: error.message,
|
|
303
|
+
};
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
/**
|
|
308
|
+
* Consulta el estado de avance en el SII
|
|
309
|
+
* @returns {Promise<Object>}
|
|
310
|
+
*/
|
|
311
|
+
async consultarAvance() {
|
|
312
|
+
const siiCert = this._getSiiCert();
|
|
313
|
+
return siiCert.verAvance();
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
/**
|
|
317
|
+
* Detecta si el set fue regenerado (folios nuevos)
|
|
318
|
+
* @private
|
|
319
|
+
*/
|
|
320
|
+
_detectRegeneration(result) {
|
|
321
|
+
// Si el HTML contiene indicadores de regeneración
|
|
322
|
+
const html = result.rawHtml || '';
|
|
323
|
+
if (html.includes('Set generado correctamente') ||
|
|
324
|
+
html.includes('nuevos folios asignados')) {
|
|
325
|
+
return true;
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
// Comparar número de atención con el anterior si existe
|
|
329
|
+
if (result.setDescargado?.rawHtml && this._lastNumeroAtencion) {
|
|
330
|
+
const match = result.setDescargado.rawHtml.match(/NUMERO DE ATENCI[^:]*:\s*(\d+)/i);
|
|
331
|
+
if (match && match[1] !== this._lastNumeroAtencion) {
|
|
332
|
+
this.logger.log(`[SetsProvider] Número de atención cambió: ${this._lastNumeroAtencion} → ${match[1]}`);
|
|
333
|
+
return true;
|
|
334
|
+
}
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
return false;
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
/**
|
|
341
|
+
* Parsea el set descargado usando flujoCert
|
|
342
|
+
* @private
|
|
343
|
+
* @param {string} rawHtml - HTML del set descargado
|
|
344
|
+
* @returns {{ estructuras: Object, datosExtraidos: Object }}
|
|
345
|
+
*/
|
|
346
|
+
_parseSetDescargado(rawHtml) {
|
|
347
|
+
// Limpiar HTML y extraer texto
|
|
348
|
+
const textoLimpio = this._limpiarHtml(rawHtml);
|
|
349
|
+
|
|
350
|
+
// Guardar texto limpio para debug
|
|
351
|
+
if (this.debugDir) {
|
|
352
|
+
this._saveDebug('set-texto.txt', textoLimpio);
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
// Usar SetParser del core
|
|
356
|
+
this.logger.log('[SetsProvider] Usando SetParser del core...');
|
|
357
|
+
|
|
358
|
+
const datosExtraidos = SetParser.extraerCasosDelSet(textoLimpio);
|
|
359
|
+
|
|
360
|
+
if (datosExtraidos.sets.length === 0) {
|
|
361
|
+
this.logger.log('[SetsProvider] ⚠️ No se encontraron sets en el contenido');
|
|
362
|
+
return { estructuras: null, datosExtraidos: null };
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
this.logger.log(`[SetsProvider] 📦 Sets encontrados: ${datosExtraidos.sets.length}`);
|
|
366
|
+
this.logger.log(`[SetsProvider] 📄 Total casos: ${datosExtraidos.totalCasos}`);
|
|
367
|
+
|
|
368
|
+
// Guardar número de atención para detectar regeneración
|
|
369
|
+
if (datosExtraidos.sets[0]?.numeroAtencion) {
|
|
370
|
+
this._lastNumeroAtencion = datosExtraidos.sets[0].numeroAtencion;
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
// Generar estructuras para scripts
|
|
374
|
+
const estructuras = SetParser.generarEstructurasParaScripts(datosExtraidos);
|
|
375
|
+
|
|
376
|
+
return { estructuras, datosExtraidos };
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
/**
|
|
380
|
+
* Limpia HTML y extrae texto plano
|
|
381
|
+
* @private
|
|
382
|
+
*/
|
|
383
|
+
_limpiarHtml(html) {
|
|
384
|
+
return html
|
|
385
|
+
.replace(/<script[^>]*>[\s\S]*?<\/script>/gi, '')
|
|
386
|
+
.replace(/<style[^>]*>[\s\S]*?<\/style>/gi, '')
|
|
387
|
+
.replace(/<[^>]+>/g, '\n')
|
|
388
|
+
.replace(/ /g, ' ')
|
|
389
|
+
.replace(/á/g, 'á')
|
|
390
|
+
.replace(/é/g, 'é')
|
|
391
|
+
.replace(/í/g, 'í')
|
|
392
|
+
.replace(/ó/g, 'ó')
|
|
393
|
+
.replace(/ú/g, 'ú')
|
|
394
|
+
.replace(/ñ/g, 'ñ')
|
|
395
|
+
.replace(/Á/g, 'Á')
|
|
396
|
+
.replace(/É/g, 'É')
|
|
397
|
+
.replace(/Í/g, 'Í')
|
|
398
|
+
.replace(/Ó/g, 'Ó')
|
|
399
|
+
.replace(/Ú/g, 'Ú')
|
|
400
|
+
.replace(/Ñ/g, 'Ñ')
|
|
401
|
+
.replace(/\n\s*\n/g, '\n')
|
|
402
|
+
.trim();
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
/**
|
|
406
|
+
* Parsea las estructuras del set descargado (método legacy)
|
|
407
|
+
* @private
|
|
408
|
+
* @deprecated Usar _parseSetDescargado en su lugar
|
|
409
|
+
*/
|
|
410
|
+
_parseEstructuras(setDescargado) {
|
|
411
|
+
if (setDescargado.rawHtml) {
|
|
412
|
+
const result = this._parseSetDescargado(setDescargado.rawHtml);
|
|
413
|
+
return result.estructuras;
|
|
414
|
+
}
|
|
415
|
+
return null;
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
/**
|
|
419
|
+
* Organiza los casos por tipo de set
|
|
420
|
+
* @private
|
|
421
|
+
*/
|
|
422
|
+
_organizarCasosPorSet(casos) {
|
|
423
|
+
const estructuras = {
|
|
424
|
+
setBasico: { casos: [], cafRequired: {} },
|
|
425
|
+
setExenta: { casos: [], cafRequired: {} },
|
|
426
|
+
setGuia: { casos: [], cafRequired: {} },
|
|
427
|
+
setCompra: { casos: [], cafRequired: {} },
|
|
428
|
+
};
|
|
429
|
+
|
|
430
|
+
for (const caso of casos) {
|
|
431
|
+
const tipoDte = caso.tipoDte || caso.tipo;
|
|
432
|
+
|
|
433
|
+
// Clasificar por tipo de DTE
|
|
434
|
+
if ([33, 56, 61].includes(tipoDte)) {
|
|
435
|
+
estructuras.setBasico.casos.push(caso);
|
|
436
|
+
this._incrementCafRequired(estructuras.setBasico.cafRequired, tipoDte);
|
|
437
|
+
} else if (tipoDte === 34) {
|
|
438
|
+
estructuras.setExenta.casos.push(caso);
|
|
439
|
+
this._incrementCafRequired(estructuras.setExenta.cafRequired, tipoDte);
|
|
440
|
+
} else if (tipoDte === 52) {
|
|
441
|
+
estructuras.setGuia.casos.push(caso);
|
|
442
|
+
this._incrementCafRequired(estructuras.setGuia.cafRequired, tipoDte);
|
|
443
|
+
} else if (tipoDte === 46) {
|
|
444
|
+
estructuras.setCompra.casos.push(caso);
|
|
445
|
+
this._incrementCafRequired(estructuras.setCompra.cafRequired, tipoDte);
|
|
446
|
+
}
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
return estructuras;
|
|
450
|
+
}
|
|
451
|
+
|
|
452
|
+
/**
|
|
453
|
+
* Incrementa contador de CAF requerido
|
|
454
|
+
* @private
|
|
455
|
+
*/
|
|
456
|
+
_incrementCafRequired(cafRequired, tipoDte) {
|
|
457
|
+
cafRequired[tipoDte] = (cafRequired[tipoDte] || 0) + 1;
|
|
458
|
+
}
|
|
459
|
+
|
|
460
|
+
/**
|
|
461
|
+
* Guarda la sesión actual
|
|
462
|
+
*/
|
|
463
|
+
saveSession() {
|
|
464
|
+
if (this.sessionPath && this._siiCert) {
|
|
465
|
+
this._ensureDir(path.dirname(this.sessionPath));
|
|
466
|
+
this._siiCert.saveSession(this.sessionPath);
|
|
467
|
+
}
|
|
468
|
+
}
|
|
469
|
+
|
|
470
|
+
/**
|
|
471
|
+
* Invalida el cache de estructuras
|
|
472
|
+
*/
|
|
473
|
+
invalidateCache() {
|
|
474
|
+
this._estructurasCache = null;
|
|
475
|
+
this._lastFetchTime = null;
|
|
476
|
+
}
|
|
477
|
+
|
|
478
|
+
/**
|
|
479
|
+
* Crea directorio si no existe
|
|
480
|
+
* @private
|
|
481
|
+
*/
|
|
482
|
+
_ensureDir(dirPath) {
|
|
483
|
+
if (!fs.existsSync(dirPath)) {
|
|
484
|
+
fs.mkdirSync(dirPath, { recursive: true });
|
|
485
|
+
}
|
|
486
|
+
}
|
|
487
|
+
|
|
488
|
+
/**
|
|
489
|
+
* Guarda archivo de debug
|
|
490
|
+
* @private
|
|
491
|
+
*/
|
|
492
|
+
_saveDebug(filename, content) {
|
|
493
|
+
if (!this.debugDir) return;
|
|
494
|
+
this._ensureDir(this.debugDir);
|
|
495
|
+
fs.writeFileSync(path.join(this.debugDir, filename), content);
|
|
496
|
+
}
|
|
497
|
+
}
|
|
498
|
+
|
|
499
|
+
module.exports = SetsProvider;
|