@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/utils/rut.js
ADDED
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
// Copyright (c) 2026 Devlas SpA — https://devlas.cl
|
|
2
|
+
// Licencia MIT. Ver archivo LICENSE para mas detalles.
|
|
3
|
+
/**
|
|
4
|
+
* Utilidades de RUT
|
|
5
|
+
*
|
|
6
|
+
* Funciones para manipulación y validación de RUT chileno
|
|
7
|
+
*
|
|
8
|
+
* @module dte-sii/utils/rut
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
// ============================================
|
|
12
|
+
// FORMATEO
|
|
13
|
+
// ============================================
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Formatear RUT chileno (quitar puntos, mayúsculas)
|
|
17
|
+
*
|
|
18
|
+
* @param {string} rut - RUT a formatear (ej: "12.345.678-K")
|
|
19
|
+
* @returns {string} - RUT formateado (ej: "12345678-K")
|
|
20
|
+
*/
|
|
21
|
+
function formatRut(rut) {
|
|
22
|
+
if (!rut) return '';
|
|
23
|
+
return rut.replace(/\./g, '').toUpperCase();
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Limpiar RUT (solo números y K)
|
|
28
|
+
*
|
|
29
|
+
* @param {string} rut - RUT con cualquier formato
|
|
30
|
+
* @returns {string} - RUT limpio (ej: "12345678K")
|
|
31
|
+
*/
|
|
32
|
+
function cleanRut(rut) {
|
|
33
|
+
if (!rut) return '';
|
|
34
|
+
return rut.replace(/[^0-9kK]/g, '').toUpperCase();
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Separar RUT en número y dígito verificador
|
|
39
|
+
*
|
|
40
|
+
* @param {string} rut - RUT en cualquier formato
|
|
41
|
+
* @returns {{ numero: string, dv: string }}
|
|
42
|
+
*/
|
|
43
|
+
function splitRut(rut) {
|
|
44
|
+
const cleaned = cleanRut(rut);
|
|
45
|
+
if (cleaned.length < 2) {
|
|
46
|
+
return { numero: cleaned, dv: '' };
|
|
47
|
+
}
|
|
48
|
+
return {
|
|
49
|
+
numero: cleaned.slice(0, -1),
|
|
50
|
+
dv: cleaned.slice(-1),
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Formatear RUT con puntos y guión
|
|
56
|
+
*
|
|
57
|
+
* @param {string} rut - RUT sin formato
|
|
58
|
+
* @returns {string} - RUT formateado (ej: "12.345.678-K")
|
|
59
|
+
*/
|
|
60
|
+
function formatRutWithDots(rut) {
|
|
61
|
+
const { numero, dv } = splitRut(rut);
|
|
62
|
+
if (!numero) return '';
|
|
63
|
+
|
|
64
|
+
// Agregar puntos cada 3 dígitos desde la derecha
|
|
65
|
+
const formatted = numero.replace(/\B(?=(\d{3})+(?!\d))/g, '.');
|
|
66
|
+
return `${formatted}-${dv}`;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Formatear RUT para uso en SII (solo con guión)
|
|
71
|
+
*
|
|
72
|
+
* @param {string} rut - RUT en cualquier formato
|
|
73
|
+
* @returns {string} - RUT formateado (ej: "12345678-K")
|
|
74
|
+
*/
|
|
75
|
+
function formatRutSii(rut) {
|
|
76
|
+
const { numero, dv } = splitRut(rut);
|
|
77
|
+
if (!numero) return '';
|
|
78
|
+
return `${numero}-${dv}`;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
// ============================================
|
|
82
|
+
// VALIDACIÓN
|
|
83
|
+
// ============================================
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* Calcular dígito verificador de RUT
|
|
87
|
+
*
|
|
88
|
+
* @param {string|number} rutSinDV - RUT sin dígito verificador
|
|
89
|
+
* @returns {string} - Dígito verificador (0-9 o K)
|
|
90
|
+
*/
|
|
91
|
+
function calcularDV(rutSinDV) {
|
|
92
|
+
const rut = parseInt(String(rutSinDV).replace(/\D/g, ''), 10);
|
|
93
|
+
if (isNaN(rut) || rut <= 0) return '';
|
|
94
|
+
|
|
95
|
+
let suma = 0;
|
|
96
|
+
let multiplicador = 2;
|
|
97
|
+
const rutStr = rut.toString();
|
|
98
|
+
|
|
99
|
+
for (let i = rutStr.length - 1; i >= 0; i--) {
|
|
100
|
+
suma += parseInt(rutStr[i]) * multiplicador;
|
|
101
|
+
multiplicador = multiplicador === 7 ? 2 : multiplicador + 1;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
const resto = suma % 11;
|
|
105
|
+
const dv = 11 - resto;
|
|
106
|
+
|
|
107
|
+
if (dv === 11) return '0';
|
|
108
|
+
if (dv === 10) return 'K';
|
|
109
|
+
return dv.toString();
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/**
|
|
113
|
+
* Validar RUT chileno
|
|
114
|
+
*
|
|
115
|
+
* @param {string} rut - RUT a validar
|
|
116
|
+
* @returns {boolean} - true si el RUT es válido
|
|
117
|
+
*/
|
|
118
|
+
function validarRut(rut) {
|
|
119
|
+
const { numero, dv } = splitRut(rut);
|
|
120
|
+
if (!numero || numero.length < 7 || numero.length > 8) return false;
|
|
121
|
+
if (!dv) return false;
|
|
122
|
+
|
|
123
|
+
const dvCalculado = calcularDV(numero);
|
|
124
|
+
return dvCalculado === dv.toUpperCase();
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
/**
|
|
128
|
+
* Validar RUT y retornar formateado si es válido
|
|
129
|
+
*
|
|
130
|
+
* @param {string} rut - RUT a validar
|
|
131
|
+
* @returns {{ valid: boolean, rut: string|null, error?: string }}
|
|
132
|
+
*/
|
|
133
|
+
function validateAndFormatRut(rut) {
|
|
134
|
+
if (!rut || typeof rut !== 'string') {
|
|
135
|
+
return { valid: false, rut: null, error: 'RUT no proporcionado' };
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
const { numero, dv } = splitRut(rut);
|
|
139
|
+
|
|
140
|
+
if (!numero || numero.length < 7) {
|
|
141
|
+
return { valid: false, rut: null, error: 'RUT muy corto' };
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
if (numero.length > 8) {
|
|
145
|
+
return { valid: false, rut: null, error: 'RUT muy largo' };
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
const dvCalculado = calcularDV(numero);
|
|
149
|
+
if (dvCalculado !== dv.toUpperCase()) {
|
|
150
|
+
return { valid: false, rut: null, error: 'Dígito verificador inválido' };
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
return { valid: true, rut: formatRutSii(rut) };
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
// ============================================
|
|
157
|
+
// EXPORTS
|
|
158
|
+
// ============================================
|
|
159
|
+
|
|
160
|
+
module.exports = {
|
|
161
|
+
formatRut,
|
|
162
|
+
cleanRut,
|
|
163
|
+
splitRut,
|
|
164
|
+
formatRutWithDots,
|
|
165
|
+
formatRutSii,
|
|
166
|
+
calcularDV,
|
|
167
|
+
validarRut,
|
|
168
|
+
validateAndFormatRut,
|
|
169
|
+
};
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
// Copyright (c) 2026 Devlas SpA — https://devlas.cl
|
|
2
|
+
// Licencia MIT. Ver archivo LICENSE para mas detalles.
|
|
3
|
+
/**
|
|
4
|
+
* Utilidades de Sanitización
|
|
5
|
+
*
|
|
6
|
+
* Funciones para sanitizar y formatear texto para DTEs del SII
|
|
7
|
+
*
|
|
8
|
+
* @module dte-sii/utils/sanitize
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
// ============================================
|
|
12
|
+
// SANITIZACIÓN DE TEXTO
|
|
13
|
+
// ============================================
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Sanitizar texto para DTE SII
|
|
17
|
+
* Elimina caracteres que causan problemas de firma XML
|
|
18
|
+
*
|
|
19
|
+
* @param {string} text - Texto a sanitizar
|
|
20
|
+
* @returns {string} - Texto sanitizado
|
|
21
|
+
*/
|
|
22
|
+
function sanitizeSiiText(text) {
|
|
23
|
+
if (text === undefined || text === null) return '';
|
|
24
|
+
return String(text)
|
|
25
|
+
.replace(/[''´`]/g, '') // Eliminar apóstrofes
|
|
26
|
+
.replace(/[""]/g, '') // Eliminar comillas tipográficas
|
|
27
|
+
.replace(/"/g, '') // Eliminar comillas dobles ASCII
|
|
28
|
+
.trim();
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Truncar texto a longitud máxima (preservando palabras completas si es posible)
|
|
33
|
+
*
|
|
34
|
+
* @param {string} text - Texto a truncar
|
|
35
|
+
* @param {number} maxLen - Longitud máxima
|
|
36
|
+
* @param {boolean} [preserveWords=false] - Si preservar palabras completas
|
|
37
|
+
* @returns {string}
|
|
38
|
+
*/
|
|
39
|
+
function truncateText(text, maxLen, preserveWords = false) {
|
|
40
|
+
const sanitized = sanitizeSiiText(text);
|
|
41
|
+
if (sanitized.length <= maxLen) return sanitized;
|
|
42
|
+
|
|
43
|
+
if (preserveWords) {
|
|
44
|
+
const truncated = sanitized.substring(0, maxLen);
|
|
45
|
+
const lastSpace = truncated.lastIndexOf(' ');
|
|
46
|
+
if (lastSpace > maxLen * 0.7) {
|
|
47
|
+
return truncated.substring(0, lastSpace).trim();
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
return sanitized.substring(0, maxLen);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Sanitizar giro para receptor (máximo 40 caracteres)
|
|
56
|
+
*
|
|
57
|
+
* @param {string} giro - Giro del receptor
|
|
58
|
+
* @returns {string}
|
|
59
|
+
*/
|
|
60
|
+
function sanitizeGiroRecep(giro) {
|
|
61
|
+
return truncateText(giro, 40);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Sanitizar razón social (máximo 100 caracteres)
|
|
66
|
+
*
|
|
67
|
+
* @param {string} razonSocial - Razón social
|
|
68
|
+
* @returns {string}
|
|
69
|
+
*/
|
|
70
|
+
function sanitizeRazonSocial(razonSocial) {
|
|
71
|
+
return truncateText(razonSocial, 100);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* Sanitizar nombre de ítem (máximo 80 caracteres)
|
|
76
|
+
*
|
|
77
|
+
* @param {string} nombre - Nombre del ítem
|
|
78
|
+
* @returns {string}
|
|
79
|
+
*/
|
|
80
|
+
function sanitizeNombreItem(nombre) {
|
|
81
|
+
return truncateText(nombre, 80);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* Sanitizar descripción de ítem (máximo 1000 caracteres)
|
|
86
|
+
*
|
|
87
|
+
* @param {string} descripcion - Descripción del ítem
|
|
88
|
+
* @returns {string}
|
|
89
|
+
*/
|
|
90
|
+
function sanitizeDescripcionItem(descripcion) {
|
|
91
|
+
return truncateText(descripcion, 1000);
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* Crear segmento seguro para nombres de archivo/directorio
|
|
96
|
+
*
|
|
97
|
+
* @param {*} value - Valor a convertir
|
|
98
|
+
* @param {string} [fallback='sin-valor'] - Valor por defecto
|
|
99
|
+
* @returns {string}
|
|
100
|
+
*/
|
|
101
|
+
function safeSegment(value, fallback = 'sin-valor') {
|
|
102
|
+
const raw = value === undefined || value === null ? '' : String(value);
|
|
103
|
+
const cleaned = raw
|
|
104
|
+
.replace(/\./g, '')
|
|
105
|
+
.replace(/[^a-zA-Z0-9._-]/g, '-')
|
|
106
|
+
.replace(/-+/g, '-')
|
|
107
|
+
.replace(/^-|-$/g, '')
|
|
108
|
+
.slice(0, 80);
|
|
109
|
+
return cleaned || fallback;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
// ============================================
|
|
113
|
+
// EXPORTS
|
|
114
|
+
// ============================================
|
|
115
|
+
|
|
116
|
+
module.exports = {
|
|
117
|
+
sanitizeSiiText,
|
|
118
|
+
truncateText,
|
|
119
|
+
sanitizeGiroRecep,
|
|
120
|
+
sanitizeRazonSocial,
|
|
121
|
+
sanitizeNombreItem,
|
|
122
|
+
sanitizeDescripcionItem,
|
|
123
|
+
safeSegment,
|
|
124
|
+
};
|
|
@@ -0,0 +1,214 @@
|
|
|
1
|
+
// Copyright (c) 2026 Devlas SpA — https://devlas.cl
|
|
2
|
+
// Licencia MIT. Ver archivo LICENSE para mas detalles.
|
|
3
|
+
/**
|
|
4
|
+
* Token Cache
|
|
5
|
+
*
|
|
6
|
+
* Cache de tokens SII para evitar solicitudes innecesarias
|
|
7
|
+
* Los tokens tienen validez de ~60 minutos
|
|
8
|
+
*
|
|
9
|
+
* @module dte-sii/utils/tokenCache
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
const { getConfigSection } = require('./config');
|
|
13
|
+
|
|
14
|
+
// ============================================
|
|
15
|
+
// ALMACENAMIENTO EN MEMORIA
|
|
16
|
+
// ============================================
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Estructura del cache:
|
|
20
|
+
* {
|
|
21
|
+
* 'certificacion:rest:78206276-K': { token: 'xxx', expiresAt: Date },
|
|
22
|
+
* 'produccion:soap:78206276-K': { token: 'yyy', expiresAt: Date },
|
|
23
|
+
* }
|
|
24
|
+
*/
|
|
25
|
+
const tokenStore = new Map();
|
|
26
|
+
|
|
27
|
+
// ============================================
|
|
28
|
+
// FUNCIONES PRINCIPALES
|
|
29
|
+
// ============================================
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Generar clave única para el cache
|
|
33
|
+
* @param {string} ambiente - 'certificacion' o 'produccion'
|
|
34
|
+
* @param {string} tipo - 'rest' o 'soap'
|
|
35
|
+
* @param {string} rutEmisor - RUT del emisor
|
|
36
|
+
* @returns {string} Clave del cache
|
|
37
|
+
*/
|
|
38
|
+
function generateCacheKey(ambiente, tipo, rutEmisor) {
|
|
39
|
+
return `${ambiente}:${tipo}:${rutEmisor}`;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Obtener token del cache si existe y no ha expirado
|
|
44
|
+
* @param {string} ambiente - 'certificacion' o 'produccion'
|
|
45
|
+
* @param {string} tipo - 'rest' o 'soap'
|
|
46
|
+
* @param {string} rutEmisor - RUT del emisor
|
|
47
|
+
* @returns {string|null} Token cacheado o null
|
|
48
|
+
*/
|
|
49
|
+
function getCachedToken(ambiente, tipo, rutEmisor) {
|
|
50
|
+
const config = getConfigSection('tokenCache');
|
|
51
|
+
|
|
52
|
+
if (!config?.enabled) {
|
|
53
|
+
return null;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
const key = generateCacheKey(ambiente, tipo, rutEmisor);
|
|
57
|
+
const cached = tokenStore.get(key);
|
|
58
|
+
|
|
59
|
+
if (!cached) {
|
|
60
|
+
return null;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
// Verificar expiración
|
|
64
|
+
if (Date.now() >= cached.expiresAt) {
|
|
65
|
+
tokenStore.delete(key);
|
|
66
|
+
return null;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
return cached.token;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Guardar token en cache
|
|
74
|
+
* @param {string} ambiente - 'certificacion' o 'produccion'
|
|
75
|
+
* @param {string} tipo - 'rest' o 'soap'
|
|
76
|
+
* @param {string} rutEmisor - RUT del emisor
|
|
77
|
+
* @param {string} token - Token a cachear
|
|
78
|
+
* @param {number} [ttlMinutes] - TTL personalizado (opcional)
|
|
79
|
+
*/
|
|
80
|
+
function setCachedToken(ambiente, tipo, rutEmisor, token, ttlMinutes) {
|
|
81
|
+
const config = getConfigSection('tokenCache');
|
|
82
|
+
|
|
83
|
+
if (!config?.enabled) {
|
|
84
|
+
return;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
const ttl = ttlMinutes ?? config.ttlMinutes ?? 55;
|
|
88
|
+
const expiresAt = Date.now() + (ttl * 60 * 1000);
|
|
89
|
+
|
|
90
|
+
const key = generateCacheKey(ambiente, tipo, rutEmisor);
|
|
91
|
+
tokenStore.set(key, {
|
|
92
|
+
token,
|
|
93
|
+
expiresAt,
|
|
94
|
+
createdAt: Date.now(),
|
|
95
|
+
ambiente,
|
|
96
|
+
tipo,
|
|
97
|
+
rutEmisor,
|
|
98
|
+
});
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* Invalidar token específico
|
|
103
|
+
* @param {string} ambiente - 'certificacion' o 'produccion'
|
|
104
|
+
* @param {string} tipo - 'rest' o 'soap'
|
|
105
|
+
* @param {string} rutEmisor - RUT del emisor
|
|
106
|
+
*/
|
|
107
|
+
function invalidateToken(ambiente, tipo, rutEmisor) {
|
|
108
|
+
const key = generateCacheKey(ambiente, tipo, rutEmisor);
|
|
109
|
+
tokenStore.delete(key);
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/**
|
|
113
|
+
* Invalidar todos los tokens de un ambiente
|
|
114
|
+
* @param {string} ambiente - 'certificacion' o 'produccion'
|
|
115
|
+
*/
|
|
116
|
+
function invalidateAmbiente(ambiente) {
|
|
117
|
+
for (const key of tokenStore.keys()) {
|
|
118
|
+
if (key.startsWith(`${ambiente}:`)) {
|
|
119
|
+
tokenStore.delete(key);
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
/**
|
|
125
|
+
* Invalidar todos los tokens de un emisor
|
|
126
|
+
* @param {string} rutEmisor - RUT del emisor
|
|
127
|
+
*/
|
|
128
|
+
function invalidateEmisor(rutEmisor) {
|
|
129
|
+
for (const key of tokenStore.keys()) {
|
|
130
|
+
if (key.endsWith(`:${rutEmisor}`)) {
|
|
131
|
+
tokenStore.delete(key);
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
/**
|
|
137
|
+
* Limpiar todo el cache
|
|
138
|
+
*/
|
|
139
|
+
function clearTokenCache() {
|
|
140
|
+
tokenStore.clear();
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
/**
|
|
144
|
+
* Obtener estadísticas del cache
|
|
145
|
+
* @returns {Object} Estadísticas
|
|
146
|
+
*/
|
|
147
|
+
function getTokenCacheStats() {
|
|
148
|
+
const now = Date.now();
|
|
149
|
+
let active = 0;
|
|
150
|
+
let expired = 0;
|
|
151
|
+
|
|
152
|
+
for (const [key, value] of tokenStore.entries()) {
|
|
153
|
+
if (now >= value.expiresAt) {
|
|
154
|
+
expired++;
|
|
155
|
+
} else {
|
|
156
|
+
active++;
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
return {
|
|
161
|
+
total: tokenStore.size,
|
|
162
|
+
active,
|
|
163
|
+
expired,
|
|
164
|
+
entries: Array.from(tokenStore.entries()).map(([key, value]) => ({
|
|
165
|
+
key,
|
|
166
|
+
ambiente: value.ambiente,
|
|
167
|
+
tipo: value.tipo,
|
|
168
|
+
rutEmisor: value.rutEmisor,
|
|
169
|
+
createdAt: new Date(value.createdAt).toISOString(),
|
|
170
|
+
expiresAt: new Date(value.expiresAt).toISOString(),
|
|
171
|
+
isExpired: now >= value.expiresAt,
|
|
172
|
+
remainingMinutes: Math.max(0, Math.round((value.expiresAt - now) / 60000)),
|
|
173
|
+
})),
|
|
174
|
+
};
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
/**
|
|
178
|
+
* Limpiar tokens expirados (limpieza manual)
|
|
179
|
+
* @returns {number} Número de tokens eliminados
|
|
180
|
+
*/
|
|
181
|
+
function pruneExpiredTokens() {
|
|
182
|
+
const now = Date.now();
|
|
183
|
+
let pruned = 0;
|
|
184
|
+
|
|
185
|
+
for (const [key, value] of tokenStore.entries()) {
|
|
186
|
+
if (now >= value.expiresAt) {
|
|
187
|
+
tokenStore.delete(key);
|
|
188
|
+
pruned++;
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
return pruned;
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
// ============================================
|
|
196
|
+
// EXPORTS
|
|
197
|
+
// ============================================
|
|
198
|
+
|
|
199
|
+
module.exports = {
|
|
200
|
+
// Operaciones principales
|
|
201
|
+
getCachedToken,
|
|
202
|
+
setCachedToken,
|
|
203
|
+
|
|
204
|
+
// Invalidación
|
|
205
|
+
invalidateToken,
|
|
206
|
+
invalidateAmbiente,
|
|
207
|
+
invalidateEmisor,
|
|
208
|
+
clearTokenCache,
|
|
209
|
+
|
|
210
|
+
// Utilidades
|
|
211
|
+
getTokenCacheStats,
|
|
212
|
+
pruneExpiredTokens,
|
|
213
|
+
generateCacheKey,
|
|
214
|
+
};
|