@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,413 @@
|
|
|
1
|
+
// Copyright (c) 2026 Devlas SpA — https://devlas.cl
|
|
2
|
+
// Licencia MIT. Ver archivo LICENSE para mas detalles.
|
|
3
|
+
/**
|
|
4
|
+
* Set Básico - Factura, Nota de Crédito, Nota de Débito
|
|
5
|
+
*
|
|
6
|
+
* Tipos DTE: 33, 56, 61
|
|
7
|
+
* Flujo:
|
|
8
|
+
* 1. Generar facturas (casos tipo 33)
|
|
9
|
+
* 2. Generar NC (referenciando facturas)
|
|
10
|
+
* 3. Generar ND (referenciando facturas)
|
|
11
|
+
*
|
|
12
|
+
* @module dte-sii/cert/SetBasico
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
const SetBase = require('./SetBase');
|
|
16
|
+
const { SET_LABELS, SETS_DTE } = require('./types');
|
|
17
|
+
|
|
18
|
+
class SetBasico extends SetBase {
|
|
19
|
+
constructor(deps) {
|
|
20
|
+
super(deps);
|
|
21
|
+
|
|
22
|
+
this.key = 'basico';
|
|
23
|
+
this.label = SET_LABELS.basico;
|
|
24
|
+
this.tiposDte = SETS_DTE.basico; // [33, 56, 61]
|
|
25
|
+
|
|
26
|
+
// Registro de referencias entre documentos
|
|
27
|
+
// { casoId: { tipoDte, folio, fecha, detalle, totales, items } }
|
|
28
|
+
this._docRefs = {};
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* @override
|
|
33
|
+
* Valida casos específicos del set básico
|
|
34
|
+
*/
|
|
35
|
+
_validarCasos(casos) {
|
|
36
|
+
super._validarCasos(casos);
|
|
37
|
+
|
|
38
|
+
if (!casos.casosFactura?.length) {
|
|
39
|
+
throw new Error('SetBasico: casosFactura es requerido');
|
|
40
|
+
}
|
|
41
|
+
// NC y ND pueden estar vacíos en algunos escenarios
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* @override
|
|
46
|
+
* Calcula folios necesarios por tipo
|
|
47
|
+
*/
|
|
48
|
+
_calcularCantidadFolios(casos, tipoDte) {
|
|
49
|
+
// Usar cafRequired si está definido
|
|
50
|
+
if (casos.cafRequired?.[tipoDte]) {
|
|
51
|
+
return casos.cafRequired[tipoDte];
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
// Si no, contar casos
|
|
55
|
+
switch (tipoDte) {
|
|
56
|
+
case 33: return casos.casosFactura?.length || 1;
|
|
57
|
+
case 61: return casos.casosNC?.length || 1;
|
|
58
|
+
case 56: return casos.casosND?.length || 1;
|
|
59
|
+
default: return 1;
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* @override
|
|
65
|
+
* Genera DTEs del set básico
|
|
66
|
+
*
|
|
67
|
+
* @param {Object} casos - { casosFactura, casosNC, casosND, cafRequired }
|
|
68
|
+
* @param {Object} cafs - { 33: cafPath, 56: cafPath, 61: cafPath }
|
|
69
|
+
* @returns {Promise<DTE[]>}
|
|
70
|
+
*/
|
|
71
|
+
async generarDtes(casos, cafs) {
|
|
72
|
+
const dtes = [];
|
|
73
|
+
|
|
74
|
+
// 1. Generar facturas primero (las NC/ND referencian facturas)
|
|
75
|
+
this.logger.log(' 📄 Generando facturas...');
|
|
76
|
+
for (const caso of casos.casosFactura || []) {
|
|
77
|
+
const dte = await this._generarFactura(caso, cafs[33]);
|
|
78
|
+
dtes.push(dte);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
// 2. Generar notas de crédito
|
|
82
|
+
this.logger.log(' 📄 Generando notas de crédito...');
|
|
83
|
+
for (const caso of casos.casosNC || []) {
|
|
84
|
+
const dte = await this._generarNotaCredito(caso, cafs[61]);
|
|
85
|
+
dtes.push(dte);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
// 3. Generar notas de débito
|
|
89
|
+
this.logger.log(' 📄 Generando notas de débito...');
|
|
90
|
+
for (const caso of casos.casosND || []) {
|
|
91
|
+
const dte = await this._generarNotaDebito(caso, cafs[56]);
|
|
92
|
+
dtes.push(dte);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
return dtes;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* Genera una factura (tipo 33)
|
|
100
|
+
* @private
|
|
101
|
+
*/
|
|
102
|
+
async _generarFactura(caso, cafPath) {
|
|
103
|
+
const { DTE, CAF, buildDetalle, calcularTotalesDesdeItems, buildSetReferencia } = require('../index');
|
|
104
|
+
const fs = require('fs');
|
|
105
|
+
|
|
106
|
+
// Cargar CAF
|
|
107
|
+
const cafXml = fs.readFileSync(cafPath, 'utf8');
|
|
108
|
+
const caf = new CAF(cafXml);
|
|
109
|
+
|
|
110
|
+
// Reservar folio
|
|
111
|
+
const folio = this._reservarFolio(caf, cafXml);
|
|
112
|
+
|
|
113
|
+
// Construir detalle e items
|
|
114
|
+
const detalle = buildDetalle(caso.items);
|
|
115
|
+
const { totales } = calcularTotalesDesdeItems(caso.items, caso.descuentoGlobalPct);
|
|
116
|
+
|
|
117
|
+
// Descuento global si aplica
|
|
118
|
+
const dscRcgGlobal = caso.descuentoGlobalPct
|
|
119
|
+
? [{
|
|
120
|
+
NroLinDR: 1,
|
|
121
|
+
TpoMov: 'D',
|
|
122
|
+
GlosaDR: 'DESCUENTO GLOBAL ITEMES AFECTOS',
|
|
123
|
+
TpoValor: '%',
|
|
124
|
+
ValorDR: caso.descuentoGlobalPct,
|
|
125
|
+
}]
|
|
126
|
+
: null;
|
|
127
|
+
|
|
128
|
+
// Referencia del set de pruebas
|
|
129
|
+
const fechaEmision = this._getFechaEmision();
|
|
130
|
+
const setReferencia = buildSetReferencia(caso.id, fechaEmision);
|
|
131
|
+
|
|
132
|
+
// Construir DTE
|
|
133
|
+
const dteDatos = {
|
|
134
|
+
Encabezado: {
|
|
135
|
+
IdDoc: {
|
|
136
|
+
TipoDTE: 33,
|
|
137
|
+
Folio: folio,
|
|
138
|
+
FchEmis: fechaEmision,
|
|
139
|
+
TpoTranVenta: 1,
|
|
140
|
+
},
|
|
141
|
+
Emisor: this._buildEmisor(),
|
|
142
|
+
Receptor: this._buildReceptor(),
|
|
143
|
+
Totales: totales,
|
|
144
|
+
},
|
|
145
|
+
Detalle: detalle,
|
|
146
|
+
Referencia: [setReferencia],
|
|
147
|
+
...(dscRcgGlobal ? { DscRcgGlobal: dscRcgGlobal } : {}),
|
|
148
|
+
};
|
|
149
|
+
|
|
150
|
+
const dte = new DTE(dteDatos);
|
|
151
|
+
this._timbrarYFirmar(dte, caf);
|
|
152
|
+
|
|
153
|
+
// Guardar referencia para NC/ND
|
|
154
|
+
this._docRefs[caso.id] = {
|
|
155
|
+
tipoDte: 33,
|
|
156
|
+
folio,
|
|
157
|
+
fecha: fechaEmision,
|
|
158
|
+
detalle,
|
|
159
|
+
totales,
|
|
160
|
+
items: caso.items,
|
|
161
|
+
descuentoGlobalPct: caso.descuentoGlobalPct,
|
|
162
|
+
};
|
|
163
|
+
|
|
164
|
+
this.logger.log(` ✓ Factura caso ${caso.id}: folio ${folio}`);
|
|
165
|
+
return dte;
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
/**
|
|
169
|
+
* Genera una nota de crédito (tipo 61)
|
|
170
|
+
* @private
|
|
171
|
+
*/
|
|
172
|
+
async _generarNotaCredito(caso, cafPath) {
|
|
173
|
+
const { DTE, CAF, buildDetalle, calcularTotalesDesdeItems, buildSetReferencia } = require('../index');
|
|
174
|
+
const fs = require('fs');
|
|
175
|
+
|
|
176
|
+
// Obtener documento referenciado
|
|
177
|
+
const base = this._docRefs[caso.referenciaCaso];
|
|
178
|
+
if (!base) {
|
|
179
|
+
throw new Error(`SetBasico: No se encontró referencia del caso ${caso.referenciaCaso}`);
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
// Cargar CAF
|
|
183
|
+
const cafXml = fs.readFileSync(cafPath, 'utf8');
|
|
184
|
+
const caf = new CAF(cafXml);
|
|
185
|
+
|
|
186
|
+
// Reservar folio
|
|
187
|
+
const folio = this._reservarFolio(caf, cafXml);
|
|
188
|
+
|
|
189
|
+
// Determinar items a usar
|
|
190
|
+
let items = caso.itemsFromCaso
|
|
191
|
+
? (this._docRefs[caso.itemsFromCaso]?.items || [])
|
|
192
|
+
: (caso.items || []);
|
|
193
|
+
|
|
194
|
+
// Si codRef es 3 (DEVOLUCION/MODIFICACION), asegurar precios de la factura original
|
|
195
|
+
if (caso.codRef === 3 && items.length > 0 && base.items?.length > 0) {
|
|
196
|
+
const baseItemsMap = new Map(base.items.map(bi => [bi.nombre, bi]));
|
|
197
|
+
items = items.map(item => {
|
|
198
|
+
if ((!item.precio || item.precio === 0) && baseItemsMap.has(item.nombre)) {
|
|
199
|
+
const baseItem = baseItemsMap.get(item.nombre);
|
|
200
|
+
return { ...item, precio: baseItem.precio, descuentoPct: baseItem.descuentoPct };
|
|
201
|
+
}
|
|
202
|
+
return item;
|
|
203
|
+
});
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
const itemsFinal = items.length ? items : [{ nombre: 'SIN MONTO', cantidad: 1, precio: 0 }];
|
|
207
|
+
const detalle = buildDetalle(itemsFinal);
|
|
208
|
+
const { totales } = calcularTotalesDesdeItems(itemsFinal, null);
|
|
209
|
+
|
|
210
|
+
// Referencias
|
|
211
|
+
const fechaEmision = this._getFechaEmision();
|
|
212
|
+
const setReferencia = buildSetReferencia(caso.id, fechaEmision);
|
|
213
|
+
const docReferencia = {
|
|
214
|
+
NroLinRef: 2,
|
|
215
|
+
TpoDocRef: base.tipoDte,
|
|
216
|
+
FolioRef: base.folio,
|
|
217
|
+
FchRef: base.fecha,
|
|
218
|
+
CodRef: caso.codRef,
|
|
219
|
+
RazonRef: caso.razonRef,
|
|
220
|
+
};
|
|
221
|
+
|
|
222
|
+
// Construir DTE
|
|
223
|
+
const dteDatos = {
|
|
224
|
+
Encabezado: {
|
|
225
|
+
IdDoc: {
|
|
226
|
+
TipoDTE: 61,
|
|
227
|
+
Folio: folio,
|
|
228
|
+
FchEmis: fechaEmision,
|
|
229
|
+
},
|
|
230
|
+
Emisor: this._buildEmisor(),
|
|
231
|
+
Receptor: this._buildReceptor(caso.receptorOverride),
|
|
232
|
+
Totales: totales,
|
|
233
|
+
},
|
|
234
|
+
Detalle: detalle,
|
|
235
|
+
Referencia: [setReferencia, docReferencia],
|
|
236
|
+
};
|
|
237
|
+
|
|
238
|
+
const dte = new DTE(dteDatos);
|
|
239
|
+
this._timbrarYFirmar(dte, caf);
|
|
240
|
+
|
|
241
|
+
// Guardar referencia
|
|
242
|
+
this._docRefs[caso.id] = {
|
|
243
|
+
tipoDte: 61,
|
|
244
|
+
folio,
|
|
245
|
+
fecha: fechaEmision,
|
|
246
|
+
detalle,
|
|
247
|
+
totales,
|
|
248
|
+
items: itemsFinal,
|
|
249
|
+
};
|
|
250
|
+
|
|
251
|
+
this.logger.log(` ✓ NC caso ${caso.id}: folio ${folio} (ref: caso ${caso.referenciaCaso})`);
|
|
252
|
+
return dte;
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
/**
|
|
256
|
+
* Genera una nota de débito (tipo 56)
|
|
257
|
+
* @private
|
|
258
|
+
*/
|
|
259
|
+
async _generarNotaDebito(caso, cafPath) {
|
|
260
|
+
const { DTE, CAF, buildDetalle, calcularTotalesDesdeItems, buildSetReferencia } = require('../index');
|
|
261
|
+
const fs = require('fs');
|
|
262
|
+
|
|
263
|
+
// Obtener documento referenciado
|
|
264
|
+
const base = this._docRefs[caso.referenciaCaso];
|
|
265
|
+
if (!base) {
|
|
266
|
+
throw new Error(`SetBasico: No se encontró referencia del caso ${caso.referenciaCaso}`);
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
// Cargar CAF
|
|
270
|
+
const cafXml = fs.readFileSync(cafPath, 'utf8');
|
|
271
|
+
const caf = new CAF(cafXml);
|
|
272
|
+
|
|
273
|
+
// Reservar folio
|
|
274
|
+
const folio = this._reservarFolio(caf, cafXml);
|
|
275
|
+
|
|
276
|
+
// Items
|
|
277
|
+
const itemsFinal = caso.items || [{ nombre: 'SIN MONTO', cantidad: 1, precio: 0 }];
|
|
278
|
+
const detalle = buildDetalle(itemsFinal);
|
|
279
|
+
const { totales } = calcularTotalesDesdeItems(itemsFinal, null);
|
|
280
|
+
|
|
281
|
+
// Referencias
|
|
282
|
+
const fechaEmision = this._getFechaEmision();
|
|
283
|
+
const setReferencia = buildSetReferencia(caso.id, fechaEmision);
|
|
284
|
+
const docReferencia = {
|
|
285
|
+
NroLinRef: 2,
|
|
286
|
+
TpoDocRef: base.tipoDte,
|
|
287
|
+
FolioRef: base.folio,
|
|
288
|
+
FchRef: base.fecha,
|
|
289
|
+
CodRef: caso.codRef,
|
|
290
|
+
RazonRef: caso.razonRef,
|
|
291
|
+
};
|
|
292
|
+
|
|
293
|
+
// Construir DTE
|
|
294
|
+
const dteDatos = {
|
|
295
|
+
Encabezado: {
|
|
296
|
+
IdDoc: {
|
|
297
|
+
TipoDTE: 56,
|
|
298
|
+
Folio: folio,
|
|
299
|
+
FchEmis: fechaEmision,
|
|
300
|
+
},
|
|
301
|
+
Emisor: this._buildEmisor(),
|
|
302
|
+
Receptor: this._buildReceptor(),
|
|
303
|
+
Totales: totales,
|
|
304
|
+
},
|
|
305
|
+
Detalle: detalle,
|
|
306
|
+
Referencia: [setReferencia, docReferencia],
|
|
307
|
+
};
|
|
308
|
+
|
|
309
|
+
const dte = new DTE(dteDatos);
|
|
310
|
+
this._timbrarYFirmar(dte, caf);
|
|
311
|
+
|
|
312
|
+
this.logger.log(` ✓ ND caso ${caso.id}: folio ${folio} (ref: caso ${caso.referenciaCaso})`);
|
|
313
|
+
return dte;
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
// ─────────────────────────────────────────────────────────────────
|
|
317
|
+
// Helpers internos
|
|
318
|
+
// ─────────────────────────────────────────────────────────────────
|
|
319
|
+
|
|
320
|
+
/**
|
|
321
|
+
* Reserva el siguiente folio disponible
|
|
322
|
+
* Compatible con folioHelper de cert-base
|
|
323
|
+
* @private
|
|
324
|
+
*/
|
|
325
|
+
_reservarFolio(caf, cafXml) {
|
|
326
|
+
const cafFingerprint = this.folioHelper.createCafFingerprint(cafXml);
|
|
327
|
+
const folio = this.folioHelper.reserveNextFolio({
|
|
328
|
+
rutEmisor: this.config.emisor.rut,
|
|
329
|
+
tipoDte: caf.getTipoDTE(),
|
|
330
|
+
folioDesde: caf.getFolioDesde(),
|
|
331
|
+
folioHasta: caf.getFolioHasta(),
|
|
332
|
+
ambiente: this.config.ambiente || 'certificacion',
|
|
333
|
+
cafFingerprint,
|
|
334
|
+
});
|
|
335
|
+
return folio;
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
/**
|
|
339
|
+
* Obtiene la fecha de emisión en formato YYYY-MM-DD
|
|
340
|
+
* @private
|
|
341
|
+
*/
|
|
342
|
+
_getFechaEmision() {
|
|
343
|
+
if (this._fechaEmision) return this._fechaEmision;
|
|
344
|
+
const now = new Date();
|
|
345
|
+
this._fechaEmision = now.toISOString().split('T')[0];
|
|
346
|
+
return this._fechaEmision;
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
/**
|
|
350
|
+
* Construye datos del emisor
|
|
351
|
+
* @private
|
|
352
|
+
*/
|
|
353
|
+
_buildEmisor() {
|
|
354
|
+
const e = this.config.emisor;
|
|
355
|
+
return {
|
|
356
|
+
RUTEmisor: e.rut,
|
|
357
|
+
RznSoc: e.razon_social,
|
|
358
|
+
GiroEmis: e.giro,
|
|
359
|
+
Acteco: e.acteco,
|
|
360
|
+
DirOrigen: e.direccion,
|
|
361
|
+
CmnaOrigen: e.comuna,
|
|
362
|
+
CiudadOrigen: e.ciudad || e.comuna || 'Santiago',
|
|
363
|
+
};
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
/**
|
|
367
|
+
* Construye datos del receptor
|
|
368
|
+
* @private
|
|
369
|
+
*/
|
|
370
|
+
_buildReceptor(override = null) {
|
|
371
|
+
const r = this.config.receptor;
|
|
372
|
+
return {
|
|
373
|
+
RUTRecep: r.rut,
|
|
374
|
+
RznSocRecep: r.razon_social,
|
|
375
|
+
GiroRecep: r.giro,
|
|
376
|
+
DirRecep: r.direccion,
|
|
377
|
+
CmnaRecep: r.comuna,
|
|
378
|
+
CiudadRecep: r.ciudad || r.comuna,
|
|
379
|
+
...(override || {}),
|
|
380
|
+
};
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
/**
|
|
384
|
+
* Timbra y firma el DTE
|
|
385
|
+
* @private
|
|
386
|
+
*/
|
|
387
|
+
_timbrarYFirmar(dte, caf) {
|
|
388
|
+
const { Certificado } = require('../index');
|
|
389
|
+
const fs = require('fs');
|
|
390
|
+
|
|
391
|
+
// Cargar certificado
|
|
392
|
+
const pfxBuffer = fs.readFileSync(this.config.certificado.path);
|
|
393
|
+
const cert = new Certificado(pfxBuffer, this.config.certificado.password);
|
|
394
|
+
|
|
395
|
+
// Timestamp
|
|
396
|
+
const timestamp = new Date().toISOString().replace('Z', '');
|
|
397
|
+
|
|
398
|
+
// Generar, timbrar y firmar
|
|
399
|
+
dte.generarXML().timbrar(caf, timestamp);
|
|
400
|
+
dte.firmar(cert);
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
/**
|
|
404
|
+
* Obtiene las referencias de documentos generados
|
|
405
|
+
* Útil para debugging o para otros sets que necesiten referencias
|
|
406
|
+
* @returns {Object}
|
|
407
|
+
*/
|
|
408
|
+
getDocRefs() {
|
|
409
|
+
return { ...this._docRefs };
|
|
410
|
+
}
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
module.exports = SetBasico;
|