@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/xml.js
ADDED
|
@@ -0,0 +1,358 @@
|
|
|
1
|
+
// Copyright (c) 2026 Devlas SpA — https://devlas.cl
|
|
2
|
+
// Licencia MIT. Ver archivo LICENSE para mas detalles.
|
|
3
|
+
/**
|
|
4
|
+
* Utilidades XML
|
|
5
|
+
*
|
|
6
|
+
* Funciones para manipulación de XML en DTEs del SII
|
|
7
|
+
*
|
|
8
|
+
* @module dte-sii/utils/xml
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
const { XMLParser, XMLBuilder } = require('fast-xml-parser');
|
|
12
|
+
const { safeSegment } = require('./sanitize');
|
|
13
|
+
|
|
14
|
+
// ============================================
|
|
15
|
+
// PARSERS SINGLETON (evita múltiples instancias)
|
|
16
|
+
// ============================================
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Parser XML configurado para DTEs del SII
|
|
20
|
+
* Configuración estándar con atributos
|
|
21
|
+
*/
|
|
22
|
+
const defaultParser = new XMLParser({
|
|
23
|
+
ignoreAttributes: false,
|
|
24
|
+
attributeNamePrefix: '@_',
|
|
25
|
+
trimValues: true,
|
|
26
|
+
parseTagValue: true,
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Parser XML con namespaces removidos
|
|
31
|
+
* Útil para respuestas SOAP del SII
|
|
32
|
+
*/
|
|
33
|
+
const noNsParser = new XMLParser({
|
|
34
|
+
ignoreAttributes: false,
|
|
35
|
+
attributeNamePrefix: '@_',
|
|
36
|
+
removeNSPrefix: true,
|
|
37
|
+
trimValues: true,
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Builder XML para generar DTEs
|
|
42
|
+
*/
|
|
43
|
+
const defaultBuilder = new XMLBuilder({
|
|
44
|
+
ignoreAttributes: false,
|
|
45
|
+
attributeNamePrefix: '@_',
|
|
46
|
+
format: false,
|
|
47
|
+
suppressEmptyNode: true,
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Builder XML con formato (para debug)
|
|
52
|
+
*/
|
|
53
|
+
const prettyBuilder = new XMLBuilder({
|
|
54
|
+
ignoreAttributes: false,
|
|
55
|
+
attributeNamePrefix: '@_',
|
|
56
|
+
format: true,
|
|
57
|
+
indentBy: ' ',
|
|
58
|
+
suppressEmptyNode: true,
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
// ============================================
|
|
62
|
+
// FUNCIONES DE PARSING CENTRALIZADAS
|
|
63
|
+
// ============================================
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Parsear XML con configuración estándar
|
|
67
|
+
* @param {string} xml - XML a parsear
|
|
68
|
+
* @returns {Object} Objeto parseado
|
|
69
|
+
*/
|
|
70
|
+
function parseXml(xml) {
|
|
71
|
+
return defaultParser.parse(xml);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* Parsear XML removiendo namespaces (para SOAP)
|
|
76
|
+
* @param {string} xml - XML a parsear
|
|
77
|
+
* @returns {Object} Objeto parseado
|
|
78
|
+
*/
|
|
79
|
+
function parseXmlNoNs(xml) {
|
|
80
|
+
return noNsParser.parse(xml);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* Construir XML desde objeto
|
|
85
|
+
* @param {Object} obj - Objeto a convertir
|
|
86
|
+
* @param {boolean} [pretty=false] - Si formatear con indentación
|
|
87
|
+
* @returns {string} XML generado
|
|
88
|
+
*/
|
|
89
|
+
function buildXml(obj, pretty = false) {
|
|
90
|
+
return pretty ? prettyBuilder.build(obj) : defaultBuilder.build(obj);
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* Decodificar entidades HTML en XML (común en respuestas SOAP)
|
|
95
|
+
* @param {string} xml - XML con entidades
|
|
96
|
+
* @returns {string} XML decodificado
|
|
97
|
+
*/
|
|
98
|
+
function decodeXmlEntities(xml) {
|
|
99
|
+
return xml
|
|
100
|
+
.replace(/</g, '<')
|
|
101
|
+
.replace(/>/g, '>')
|
|
102
|
+
.replace(/"/g, '"')
|
|
103
|
+
.replace(/'/g, "'")
|
|
104
|
+
.replace(/&/g, '&');
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* Extraer contenido de una etiqueta XML
|
|
109
|
+
* @param {string} xml - XML fuente
|
|
110
|
+
* @param {string} tagName - Nombre de la etiqueta
|
|
111
|
+
* @returns {string|null} Contenido o null
|
|
112
|
+
*/
|
|
113
|
+
function extractTagContent(xml, tagName) {
|
|
114
|
+
const regex = new RegExp(`<${tagName}>([^<]*)</${tagName}>`, 'i');
|
|
115
|
+
const match = xml.match(regex);
|
|
116
|
+
return match ? match[1] : null;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
/**
|
|
120
|
+
* Extraer valor de atributo XML
|
|
121
|
+
* @param {string} xml - XML fuente
|
|
122
|
+
* @param {string} tagName - Nombre de la etiqueta
|
|
123
|
+
* @param {string} attrName - Nombre del atributo
|
|
124
|
+
* @returns {string|null} Valor o null
|
|
125
|
+
*/
|
|
126
|
+
function extractAttribute(xml, tagName, attrName) {
|
|
127
|
+
const regex = new RegExp(`<${tagName}[^>]*${attrName}="([^"]*)"`, 'i');
|
|
128
|
+
const match = xml.match(regex);
|
|
129
|
+
return match ? match[1] : null;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
// ============================================
|
|
133
|
+
// FORMATEO XML
|
|
134
|
+
// ============================================
|
|
135
|
+
|
|
136
|
+
/**
|
|
137
|
+
* Formatear valores base64 en XML con saltos de línea cada 64 caracteres
|
|
138
|
+
* Requerido por el SII para certificados y firmas
|
|
139
|
+
*
|
|
140
|
+
* @param {string} xml - XML a formatear
|
|
141
|
+
* @returns {string} - XML con base64 formateado
|
|
142
|
+
*/
|
|
143
|
+
function formatBase64InXml(xml) {
|
|
144
|
+
const formatTag = (tagName) => {
|
|
145
|
+
const regex = new RegExp(`<${tagName}>([^<]+)</${tagName}>`, 'g');
|
|
146
|
+
return xml.replace(regex, (match, b64) => {
|
|
147
|
+
const clean = b64.replace(/[\r\n\s]/g, '');
|
|
148
|
+
const formatted = clean.replace(/(.{64})/g, '$1\n').trim();
|
|
149
|
+
return `<${tagName}>${formatted}</${tagName}>`;
|
|
150
|
+
});
|
|
151
|
+
};
|
|
152
|
+
|
|
153
|
+
xml = formatTag('SignatureValue');
|
|
154
|
+
xml = formatTag('Modulus');
|
|
155
|
+
xml = formatTag('X509Certificate');
|
|
156
|
+
|
|
157
|
+
return xml;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
/**
|
|
161
|
+
* Convertir elementos autocerrados a etiquetas explícitas (requerido para C14N)
|
|
162
|
+
*
|
|
163
|
+
* @param {string} xml - XML a procesar
|
|
164
|
+
* @returns {string} - XML con tags expandidos
|
|
165
|
+
*/
|
|
166
|
+
function expandSelfClosingTags(xml) {
|
|
167
|
+
const elements = ['CanonicalizationMethod', 'SignatureMethod', 'Transform', 'DigestMethod'];
|
|
168
|
+
|
|
169
|
+
for (const el of elements) {
|
|
170
|
+
const regex = new RegExp(`<${el}([^>]*?)/>`, 'g');
|
|
171
|
+
xml = xml.replace(regex, `<${el}$1></${el}>`);
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
return xml;
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
/**
|
|
178
|
+
* Normalizar valor a array
|
|
179
|
+
*
|
|
180
|
+
* @param {*} value - Valor a normalizar
|
|
181
|
+
* @returns {Array}
|
|
182
|
+
*/
|
|
183
|
+
function normalizeArray(value) {
|
|
184
|
+
if (Array.isArray(value)) return value;
|
|
185
|
+
if (value === undefined || value === null) return [];
|
|
186
|
+
return [value];
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
// ============================================
|
|
190
|
+
// METADATA EXTRACTION
|
|
191
|
+
// ============================================
|
|
192
|
+
|
|
193
|
+
/**
|
|
194
|
+
* Extraer metadata de un XML de envío DTE
|
|
195
|
+
*
|
|
196
|
+
* @param {string} xml - XML del envío
|
|
197
|
+
* @returns {Object} Metadata extraída
|
|
198
|
+
*/
|
|
199
|
+
function extractEnvioMetadata(xml) {
|
|
200
|
+
const parser = new XMLParser({
|
|
201
|
+
ignoreAttributes: false,
|
|
202
|
+
attributeNamePrefix: '@_',
|
|
203
|
+
trimValues: true,
|
|
204
|
+
parseTagValue: true,
|
|
205
|
+
});
|
|
206
|
+
|
|
207
|
+
try {
|
|
208
|
+
const data = parser.parse(xml);
|
|
209
|
+
const envio = data?.EnvioBOLETA || data?.EnvioDTE || data?.EnvioDTEB || data?.EnvioDTETraslado;
|
|
210
|
+
const setDTE = envio?.SetDTE || data?.SetDTE || {};
|
|
211
|
+
const caratula = setDTE?.Caratula || {};
|
|
212
|
+
|
|
213
|
+
const dtes = normalizeArray(setDTE?.DTE);
|
|
214
|
+
const items = dtes.map((dte) => {
|
|
215
|
+
const doc = dte?.Documento || dte?.DTE?.Documento || {};
|
|
216
|
+
const idDoc = doc?.Encabezado?.IdDoc || {};
|
|
217
|
+
return {
|
|
218
|
+
tipoDTE: idDoc?.TipoDTE?.toString?.() ?? null,
|
|
219
|
+
folio: idDoc?.Folio?.toString?.() ?? null,
|
|
220
|
+
fchEmis: idDoc?.FchEmis?.toString?.() ?? null,
|
|
221
|
+
};
|
|
222
|
+
});
|
|
223
|
+
|
|
224
|
+
return {
|
|
225
|
+
rutEmisor: caratula?.RutEmisor || null,
|
|
226
|
+
rutEnvia: caratula?.RutEnvia || null,
|
|
227
|
+
setId: setDTE?.['@_ID'] || setDTE?.ID || null,
|
|
228
|
+
items,
|
|
229
|
+
};
|
|
230
|
+
} catch (error) {
|
|
231
|
+
return {
|
|
232
|
+
rutEmisor: null,
|
|
233
|
+
rutEnvia: null,
|
|
234
|
+
setId: null,
|
|
235
|
+
items: [],
|
|
236
|
+
parseError: error.message,
|
|
237
|
+
};
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
// ============================================
|
|
242
|
+
// HISTÓRICO Y DEBUG
|
|
243
|
+
// ============================================
|
|
244
|
+
|
|
245
|
+
/**
|
|
246
|
+
* Guardar artefactos de envío para histórico y debug
|
|
247
|
+
*
|
|
248
|
+
* @param {Object} params - Parámetros
|
|
249
|
+
* @param {string} params.xml - XML del envío
|
|
250
|
+
* @param {string} [params.responseText] - Respuesta del SII
|
|
251
|
+
* @param {boolean} [params.responseOk] - Si la respuesta fue OK
|
|
252
|
+
* @param {number} [params.responseStatus] - Status HTTP de la respuesta
|
|
253
|
+
* @param {string} [params.trackId] - Track ID del envío
|
|
254
|
+
* @param {string} [params.ambiente] - Ambiente (certificacion/produccion)
|
|
255
|
+
* @param {string} [params.tipoEnvio] - Tipo de envío
|
|
256
|
+
* @param {string} [params.error] - Error si lo hubo
|
|
257
|
+
* @param {string} [params.baseDir] - Directorio base (para multi-tenant)
|
|
258
|
+
*/
|
|
259
|
+
function saveEnvioArtifacts({
|
|
260
|
+
xml,
|
|
261
|
+
responseText,
|
|
262
|
+
responseOk,
|
|
263
|
+
responseStatus,
|
|
264
|
+
trackId,
|
|
265
|
+
ambiente,
|
|
266
|
+
tipoEnvio,
|
|
267
|
+
error,
|
|
268
|
+
baseDir,
|
|
269
|
+
}) {
|
|
270
|
+
try {
|
|
271
|
+
const fs = require('fs');
|
|
272
|
+
const path = require('path');
|
|
273
|
+
|
|
274
|
+
const meta = extractEnvioMetadata(xml);
|
|
275
|
+
const fecha = meta.items[0]?.fchEmis || new Date().toISOString().slice(0, 10);
|
|
276
|
+
const tipoDte = meta.items.length === 1 ? meta.items[0]?.tipoDTE : 'multiple';
|
|
277
|
+
const folio = meta.items.length === 1 ? meta.items[0]?.folio : null;
|
|
278
|
+
|
|
279
|
+
// Usar baseDir si se proporciona, sino raíz de static/nodejs como fallback
|
|
280
|
+
const effectiveBase = baseDir || path.resolve(__dirname, '..', '..', '..');
|
|
281
|
+
const historicoBaseDir = path.join(effectiveBase, 'historicos');
|
|
282
|
+
const rutDir = safeSegment(meta.rutEmisor || 'sin-rut');
|
|
283
|
+
const tipoDir = safeSegment(`dte-${tipoDte || 'sin-tipo'}`);
|
|
284
|
+
const fechaDir = safeSegment(fecha);
|
|
285
|
+
const idDir = safeSegment(trackId || meta.setId || (folio ? `folio-${folio}` : `envio-${Date.now()}`));
|
|
286
|
+
|
|
287
|
+
const historicoDir = path.join(historicoBaseDir, rutDir, tipoDir, fechaDir, idDir);
|
|
288
|
+
fs.mkdirSync(historicoDir, { recursive: true });
|
|
289
|
+
|
|
290
|
+
fs.writeFileSync(path.join(historicoDir, 'envio.xml'), xml, 'utf-8');
|
|
291
|
+
if (responseText) {
|
|
292
|
+
fs.writeFileSync(path.join(historicoDir, 'respuesta.xml'), responseText, 'utf-8');
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
const metadata = {
|
|
296
|
+
createdAt: new Date().toISOString(),
|
|
297
|
+
ambiente,
|
|
298
|
+
tipoEnvio,
|
|
299
|
+
rutEmisor: meta.rutEmisor,
|
|
300
|
+
rutEnvia: meta.rutEnvia,
|
|
301
|
+
setId: meta.setId,
|
|
302
|
+
items: meta.items,
|
|
303
|
+
trackId: trackId || null,
|
|
304
|
+
responseOk: !!responseOk,
|
|
305
|
+
responseStatus: responseStatus ?? null,
|
|
306
|
+
error: error ? String(error) : null,
|
|
307
|
+
parseError: meta.parseError || null,
|
|
308
|
+
};
|
|
309
|
+
|
|
310
|
+
fs.writeFileSync(
|
|
311
|
+
path.join(historicoDir, 'metadata.json'),
|
|
312
|
+
JSON.stringify(metadata, null, 2),
|
|
313
|
+
'utf-8'
|
|
314
|
+
);
|
|
315
|
+
|
|
316
|
+
// Debug copy: organizar por RUT/fecha-hora de ejecución
|
|
317
|
+
const debugBaseDir = process.env.CERT_RUNNER_OUT_DIR
|
|
318
|
+
? path.resolve(process.env.CERT_RUNNER_OUT_DIR)
|
|
319
|
+
: path.join(effectiveBase, 'debug');
|
|
320
|
+
const stamp = new Date().toISOString().replace(/[:.]/g, '-');
|
|
321
|
+
const debugDir = path.join(debugBaseDir, rutDir, stamp);
|
|
322
|
+
fs.mkdirSync(debugDir, { recursive: true });
|
|
323
|
+
const debugPrefix = `${safeSegment(tipoEnvio)}-${safeSegment(tipoDte || 'sin-tipo')}`;
|
|
324
|
+
fs.writeFileSync(path.join(debugDir, `envio-${debugPrefix}.xml`), xml, 'utf-8');
|
|
325
|
+
if (responseText) {
|
|
326
|
+
fs.writeFileSync(path.join(debugDir, `respuesta-${debugPrefix}.xml`), responseText, 'utf-8');
|
|
327
|
+
}
|
|
328
|
+
} catch (saveError) {
|
|
329
|
+
console.warn('⚠️ No se pudo guardar histórico/debug:', saveError.message || saveError);
|
|
330
|
+
}
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
// ============================================
|
|
334
|
+
// EXPORTS
|
|
335
|
+
// ============================================
|
|
336
|
+
|
|
337
|
+
module.exports = {
|
|
338
|
+
// Parsers singleton (para uso directo si es necesario)
|
|
339
|
+
defaultParser,
|
|
340
|
+
noNsParser,
|
|
341
|
+
defaultBuilder,
|
|
342
|
+
prettyBuilder,
|
|
343
|
+
|
|
344
|
+
// Funciones de parsing
|
|
345
|
+
parseXml,
|
|
346
|
+
parseXmlNoNs,
|
|
347
|
+
buildXml,
|
|
348
|
+
decodeXmlEntities,
|
|
349
|
+
extractTagContent,
|
|
350
|
+
extractAttribute,
|
|
351
|
+
|
|
352
|
+
// Formateo
|
|
353
|
+
formatBase64InXml,
|
|
354
|
+
expandSelfClosingTags,
|
|
355
|
+
normalizeArray,
|
|
356
|
+
extractEnvioMetadata,
|
|
357
|
+
saveEnvioArtifacts,
|
|
358
|
+
};
|
package/utils.js
ADDED