@devlas/dte-sii 2.5.11 → 2.5.12
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/CafSolicitor.js +378 -378
- package/ConsumoFolio.js +376 -376
- package/Envio.js +196 -196
- package/LICENSE +27 -27
- package/README.md +273 -273
- package/SiiCertificacion.js +14 -7
- package/SiiPortalAuth.js +474 -474
- package/cert/CertRunner.js +8 -0
- package/cert/ConfigLoader.js +131 -131
- package/cert/IntercambioCert.js +427 -427
- package/cert/LibroCompras.js +357 -357
- package/cert/LibroGuias.js +169 -169
- package/cert/LibroVentas.js +151 -151
- package/cert/MuestrasImpresas.js +676 -676
- package/cert/SetBase.js +319 -319
- package/cert/SetBasico.js +411 -411
- package/cert/SetCompra.js +470 -470
- package/cert/SetExenta.js +488 -488
- package/cert/SetGuia.js +281 -281
- package/cert/SetParser.js +1182 -1182
- package/cert/SetsProvider.js +497 -497
- package/cert/Simulacion.js +519 -519
- package/cert/comunaOficina.js +458 -458
- package/cert/index.js +122 -122
- package/cert/types.js +328 -328
- package/package.json +49 -49
package/cert/SetExenta.js
CHANGED
|
@@ -1,490 +1,490 @@
|
|
|
1
1
|
// Copyright (c) 2026 Devlas SpA — https://devlas.cl
|
|
2
2
|
// Licencia MIT. Ver archivo LICENSE para mas detalles.
|
|
3
|
-
/**
|
|
4
|
-
* Set Factura Exenta
|
|
5
|
-
*
|
|
6
|
-
* Tipos DTE: 34 (Factura Exenta), 61 (NC), 56 (ND)
|
|
7
|
-
*
|
|
8
|
-
* Flujo similar a SetBasico pero:
|
|
9
|
-
* - Todos los items son exentos
|
|
10
|
-
* - Totales sin IVA
|
|
11
|
-
*
|
|
12
|
-
* @module dte-sii/cert/SetExenta
|
|
13
|
-
*/
|
|
14
|
-
|
|
15
|
-
const SetBase = require('./SetBase');
|
|
16
|
-
const { SET_LABELS, SETS_DTE } = require('./types');
|
|
17
|
-
|
|
18
|
-
class SetExenta extends SetBase {
|
|
19
|
-
constructor(deps) {
|
|
20
|
-
super(deps);
|
|
21
|
-
|
|
22
|
-
this.key = 'exenta';
|
|
23
|
-
this.label = SET_LABELS.exenta;
|
|
24
|
-
this.tiposDte = SETS_DTE.exenta; // [34, 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 exenta
|
|
34
|
-
*/
|
|
35
|
-
_validarCasos(casos) {
|
|
36
|
-
super._validarCasos(casos);
|
|
37
|
-
|
|
38
|
-
if (!casos.casosFactura?.length) {
|
|
39
|
-
throw new Error('SetExenta: casosFactura es requerido');
|
|
40
|
-
}
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
/**
|
|
44
|
-
* @override
|
|
45
|
-
* Calcula folios necesarios por tipo
|
|
46
|
-
*/
|
|
47
|
-
_calcularCantidadFolios(casos, tipoDte) {
|
|
48
|
-
if (casos.cafRequired?.[tipoDte]) {
|
|
49
|
-
return casos.cafRequired[tipoDte];
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
switch (tipoDte) {
|
|
53
|
-
case 34: return casos.casosFactura?.length || 1;
|
|
54
|
-
case 61: return casos.casosNC?.length || 1;
|
|
55
|
-
case 56: return casos.casosND?.length || 1;
|
|
56
|
-
default: return 1;
|
|
57
|
-
}
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
/**
|
|
61
|
-
* @override
|
|
62
|
-
* Genera DTEs del set exenta
|
|
63
|
-
*
|
|
64
|
-
* @param {Object} casos - { casosFactura, casosNC, casosND, cafRequired }
|
|
65
|
-
* @param {Object} cafs - { 34: cafPath, 56: cafPath, 61: cafPath }
|
|
66
|
-
* @returns {Promise<DTE[]>}
|
|
67
|
-
*/
|
|
68
|
-
async generarDtes(casos, cafs) {
|
|
69
|
-
const dtes = [];
|
|
70
|
-
|
|
71
|
-
// 1. Generar facturas exentas primero
|
|
72
|
-
this.logger.log(' 📄 Generando facturas exentas...');
|
|
73
|
-
for (const caso of casos.casosFactura || []) {
|
|
74
|
-
const dte = await this._generarFacturaExenta(caso, cafs[34]);
|
|
75
|
-
dtes.push(dte);
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
// 2. Generar notas de crédito
|
|
79
|
-
this.logger.log(' 📄 Generando notas de crédito...');
|
|
80
|
-
for (const caso of casos.casosNC || []) {
|
|
81
|
-
const dte = await this._generarNotaCredito(caso, cafs[61]);
|
|
82
|
-
dtes.push(dte);
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
// 3. Generar notas de débito
|
|
86
|
-
this.logger.log(' 📄 Generando notas de débito...');
|
|
87
|
-
for (const caso of casos.casosND || []) {
|
|
88
|
-
const dte = await this._generarNotaDebito(caso, cafs[56]);
|
|
89
|
-
dtes.push(dte);
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
return dtes;
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
// ═══════════════════════════════════════════════════════════════
|
|
96
|
-
// Generadores de DTEs
|
|
97
|
-
// ═══════════════════════════════════════════════════════════════
|
|
98
|
-
|
|
99
|
-
/**
|
|
100
|
-
* Genera factura exenta (tipo 34)
|
|
101
|
-
* @private
|
|
102
|
-
*/
|
|
103
|
-
async _generarFacturaExenta(caso, cafPath) {
|
|
104
|
-
const { DTE, CAF, buildDetalle, calcularTotalesDesdeDetalle, buildSetReferencia } = require('../index');
|
|
105
|
-
const fs = require('fs');
|
|
106
|
-
|
|
107
|
-
const cafXml = fs.readFileSync(cafPath, 'utf8');
|
|
108
|
-
const caf = new CAF(cafXml);
|
|
109
|
-
const folio = this._reservarFolio(caf, cafXml);
|
|
110
|
-
|
|
111
|
-
// Marcar todos los items como exentos
|
|
112
|
-
const items = this._normalizarItemsExentos(caso.items);
|
|
113
|
-
const detalle = buildDetalle(items, { allowIndExe: true });
|
|
114
|
-
const totales = calcularTotalesDesdeDetalle(detalle, { soloExento: true });
|
|
115
|
-
|
|
116
|
-
const fechaEmision = this._getFechaEmision();
|
|
117
|
-
const setReferencia = buildSetReferencia(caso.id, fechaEmision);
|
|
118
|
-
|
|
119
|
-
const dteDatos = {
|
|
120
|
-
Encabezado: {
|
|
121
|
-
IdDoc: {
|
|
122
|
-
TipoDTE: 34,
|
|
123
|
-
Folio: folio,
|
|
124
|
-
FchEmis: fechaEmision,
|
|
125
|
-
},
|
|
126
|
-
Emisor: this._buildEmisor(),
|
|
127
|
-
Receptor: this._buildReceptor(),
|
|
128
|
-
Totales: totales,
|
|
129
|
-
},
|
|
130
|
-
Detalle: detalle,
|
|
131
|
-
Referencia: [setReferencia],
|
|
132
|
-
};
|
|
133
|
-
|
|
134
|
-
const dte = new DTE(dteDatos);
|
|
135
|
-
this._timbrarYFirmar(dte, caf);
|
|
136
|
-
|
|
137
|
-
// Guardar referencia para NC/ND
|
|
138
|
-
this._docRefs[caso.id] = {
|
|
139
|
-
tipoDte: 34,
|
|
140
|
-
folio,
|
|
141
|
-
fecha: fechaEmision,
|
|
142
|
-
detalle,
|
|
143
|
-
totales,
|
|
144
|
-
items,
|
|
145
|
-
};
|
|
146
|
-
|
|
147
|
-
this.logger.log(` ✓ Factura Exenta caso ${caso.id}: folio ${folio}`);
|
|
148
|
-
return dte;
|
|
149
|
-
}
|
|
150
|
-
|
|
151
|
-
/**
|
|
152
|
-
* Genera nota de crédito exenta (tipo 61)
|
|
153
|
-
* @private
|
|
154
|
-
*/
|
|
155
|
-
async _generarNotaCredito(caso, cafPath) {
|
|
156
|
-
const { DTE, CAF, buildDetalle, calcularTotalesDesdeDetalle, buildSetReferencia } = require('../index');
|
|
157
|
-
const fs = require('fs');
|
|
158
|
-
|
|
159
|
-
const cafXml = fs.readFileSync(cafPath, 'utf8');
|
|
160
|
-
const caf = new CAF(cafXml);
|
|
161
|
-
const folio = this._reservarFolio(caf, cafXml);
|
|
162
|
-
|
|
163
|
-
// Obtener documento referenciado
|
|
164
|
-
const docRef = this._docRefs[caso.referenciaCaso];
|
|
165
|
-
if (!docRef) {
|
|
166
|
-
throw new Error(`SetExenta: Documento referencia ${caso.referenciaCaso} no encontrado para NC ${caso.id}`);
|
|
167
|
-
}
|
|
168
|
-
|
|
169
|
-
// Determinar items según tipo de NC
|
|
170
|
-
const items = this._resolverItemsNC(caso, docRef);
|
|
171
|
-
const detalle = buildDetalle(items, { allowIndExe: true });
|
|
172
|
-
const totales = calcularTotalesDesdeDetalle(detalle, { soloExento: true });
|
|
173
|
-
|
|
174
|
-
const fechaEmision = this._getFechaEmision();
|
|
175
|
-
const setReferencia = buildSetReferencia(caso.id, fechaEmision);
|
|
176
|
-
|
|
177
|
-
// Referencia al documento original
|
|
178
|
-
const docReferencia = {
|
|
179
|
-
NroLinRef: 2,
|
|
180
|
-
TpoDocRef: docRef.tipoDte,
|
|
181
|
-
FolioRef: docRef.folio,
|
|
182
|
-
FchRef: docRef.fecha,
|
|
183
|
-
CodRef: caso.codRef,
|
|
184
|
-
RazonRef: caso.razonRef,
|
|
185
|
-
};
|
|
186
|
-
|
|
187
|
-
const dteDatos = {
|
|
188
|
-
Encabezado: {
|
|
189
|
-
IdDoc: {
|
|
190
|
-
TipoDTE: 61,
|
|
191
|
-
Folio: folio,
|
|
192
|
-
FchEmis: fechaEmision,
|
|
193
|
-
},
|
|
194
|
-
Emisor: this._buildEmisor(),
|
|
195
|
-
Receptor: this._buildReceptor(),
|
|
196
|
-
Totales: totales,
|
|
197
|
-
},
|
|
198
|
-
Detalle: detalle,
|
|
199
|
-
Referencia: [setReferencia, docReferencia],
|
|
200
|
-
};
|
|
201
|
-
|
|
202
|
-
const dte = new DTE(dteDatos);
|
|
203
|
-
this._timbrarYFirmar(dte, caf);
|
|
204
|
-
|
|
205
|
-
// Guardar referencia
|
|
206
|
-
this._docRefs[caso.id] = {
|
|
207
|
-
tipoDte: 61,
|
|
208
|
-
folio,
|
|
209
|
-
fecha: fechaEmision,
|
|
210
|
-
detalle,
|
|
211
|
-
totales,
|
|
212
|
-
items,
|
|
213
|
-
};
|
|
214
|
-
|
|
215
|
-
this.logger.log(` ✓ NC Exenta caso ${caso.id}: folio ${folio} (ref: ${caso.referenciaCaso})`);
|
|
216
|
-
return dte;
|
|
217
|
-
}
|
|
218
|
-
|
|
219
|
-
/**
|
|
220
|
-
* Genera nota de débito exenta (tipo 56)
|
|
221
|
-
* @private
|
|
222
|
-
*/
|
|
223
|
-
async _generarNotaDebito(caso, cafPath) {
|
|
224
|
-
const { DTE, CAF, buildDetalle, calcularTotalesDesdeDetalle, buildSetReferencia } = require('../index');
|
|
225
|
-
const fs = require('fs');
|
|
226
|
-
|
|
227
|
-
const cafXml = fs.readFileSync(cafPath, 'utf8');
|
|
228
|
-
const caf = new CAF(cafXml);
|
|
229
|
-
const folio = this._reservarFolio(caf, cafXml);
|
|
230
|
-
|
|
231
|
-
// Obtener documento referenciado
|
|
232
|
-
const docRef = this._docRefs[caso.referenciaCaso];
|
|
233
|
-
if (!docRef) {
|
|
234
|
-
throw new Error(`SetExenta: Documento referencia ${caso.referenciaCaso} no encontrado para ND ${caso.id}`);
|
|
235
|
-
}
|
|
236
|
-
|
|
237
|
-
// Determinar items según tipo de ND
|
|
238
|
-
const items = this._resolverItemsND(caso, docRef);
|
|
239
|
-
const detalle = buildDetalle(items, { allowIndExe: true });
|
|
240
|
-
const totales = calcularTotalesDesdeDetalle(detalle, { soloExento: true });
|
|
241
|
-
|
|
242
|
-
const fechaEmision = this._getFechaEmision();
|
|
243
|
-
const setReferencia = buildSetReferencia(caso.id, fechaEmision);
|
|
244
|
-
|
|
245
|
-
// Referencia al documento original
|
|
246
|
-
const docReferencia = {
|
|
247
|
-
NroLinRef: 2,
|
|
248
|
-
TpoDocRef: docRef.tipoDte,
|
|
249
|
-
FolioRef: docRef.folio,
|
|
250
|
-
FchRef: docRef.fecha,
|
|
251
|
-
CodRef: caso.codRef,
|
|
252
|
-
RazonRef: caso.razonRef,
|
|
253
|
-
};
|
|
254
|
-
|
|
255
|
-
const dteDatos = {
|
|
256
|
-
Encabezado: {
|
|
257
|
-
IdDoc: {
|
|
258
|
-
TipoDTE: 56,
|
|
259
|
-
Folio: folio,
|
|
260
|
-
FchEmis: fechaEmision,
|
|
261
|
-
},
|
|
262
|
-
Emisor: this._buildEmisor(),
|
|
263
|
-
Receptor: this._buildReceptor(),
|
|
264
|
-
Totales: totales,
|
|
265
|
-
},
|
|
266
|
-
Detalle: detalle,
|
|
267
|
-
Referencia: [setReferencia, docReferencia],
|
|
268
|
-
};
|
|
269
|
-
|
|
270
|
-
const dte = new DTE(dteDatos);
|
|
271
|
-
this._timbrarYFirmar(dte, caf);
|
|
272
|
-
|
|
273
|
-
this.logger.log(` ✓ ND Exenta caso ${caso.id}: folio ${folio} (ref: ${caso.referenciaCaso})`);
|
|
274
|
-
return dte;
|
|
275
|
-
}
|
|
276
|
-
|
|
277
|
-
// ═══════════════════════════════════════════════════════════════
|
|
278
|
-
// Helpers
|
|
279
|
-
// ═══════════════════════════════════════════════════════════════
|
|
280
|
-
|
|
281
|
-
/**
|
|
282
|
-
* Normaliza items marcándolos como exentos
|
|
283
|
-
* @private
|
|
284
|
-
*/
|
|
285
|
-
_normalizarItemsExentos(items) {
|
|
286
|
-
return (items || []).map(item => ({
|
|
287
|
-
nombre: item.nombre,
|
|
288
|
-
cantidad: item.cantidad || 1,
|
|
289
|
-
precio: item.precio || 0,
|
|
290
|
-
unidad: item.unidad || 'UN',
|
|
291
|
-
exento: true, // Siempre exento
|
|
292
|
-
}));
|
|
293
|
-
}
|
|
294
|
-
|
|
295
|
-
/**
|
|
296
|
-
* Resuelve items para NC según codRef
|
|
297
|
-
* @private
|
|
298
|
-
*/
|
|
299
|
-
_resolverItemsNC(caso, docRef) {
|
|
300
|
-
const razon = (caso.razonRef || '').toUpperCase();
|
|
301
|
-
|
|
302
|
-
// CodRef 2: Corrige giro/texto - usar item dummy
|
|
303
|
-
if (caso.codRef === 2) {
|
|
304
|
-
return [{
|
|
305
|
-
nombre: caso.razonRef || 'CORRECCION',
|
|
306
|
-
cantidad: 1,
|
|
307
|
-
precio: 0,
|
|
308
|
-
exento: true,
|
|
309
|
-
}];
|
|
310
|
-
}
|
|
311
|
-
|
|
312
|
-
// CodRef 3: Modifica montos
|
|
313
|
-
if (caso.codRef === 3) {
|
|
314
|
-
const esDevolucion = razon.includes('DEVOLUCION') || razon.includes('DEVOLUCIÓN');
|
|
315
|
-
const esModificaMonto = razon.includes('MODIFICA MONTO');
|
|
316
|
-
|
|
317
|
-
// DEVOLUCION: usar items de factura original completos
|
|
318
|
-
if (esDevolucion && docRef.items?.length) {
|
|
319
|
-
return docRef.items.map(item => ({
|
|
320
|
-
...item,
|
|
321
|
-
exento: true,
|
|
322
|
-
}));
|
|
323
|
-
}
|
|
324
|
-
|
|
325
|
-
// MODIFICA MONTO: usar CANTIDAD de factura original con PRECIO nuevo del SII
|
|
326
|
-
if (esModificaMonto && caso.items?.length && docRef.items?.length) {
|
|
327
|
-
return caso.items.map(ncItem => {
|
|
328
|
-
const nombreNc = (ncItem.nombre || '').toUpperCase().trim();
|
|
329
|
-
const itemOriginal = docRef.items.find(i =>
|
|
330
|
-
(i.nombre || '').toUpperCase().trim() === nombreNc
|
|
331
|
-
);
|
|
332
|
-
|
|
333
|
-
if (itemOriginal && itemOriginal.cantidad) {
|
|
334
|
-
return {
|
|
335
|
-
nombre: ncItem.nombre,
|
|
336
|
-
cantidad: itemOriginal.cantidad, // Cantidad de factura ORIGINAL
|
|
337
|
-
precio: ncItem.precio, // Precio NUEVO del SII
|
|
338
|
-
unidad: ncItem.unidad || itemOriginal.unidad || 'UN',
|
|
339
|
-
exento: true,
|
|
340
|
-
};
|
|
341
|
-
}
|
|
342
|
-
return { ...ncItem, exento: true };
|
|
343
|
-
});
|
|
344
|
-
}
|
|
345
|
-
|
|
346
|
-
// Otros casos de codRef 3: usar items del caso
|
|
347
|
-
if (caso.items?.length) {
|
|
348
|
-
return this._normalizarItemsExentos(caso.items);
|
|
349
|
-
}
|
|
350
|
-
return docRef.items || [];
|
|
351
|
-
}
|
|
352
|
-
|
|
353
|
-
// CodRef 1: Anula documento completo
|
|
354
|
-
if (caso.codRef === 1) {
|
|
355
|
-
return docRef.items || [];
|
|
356
|
-
}
|
|
357
|
-
|
|
358
|
-
// Default: usar items de la NC o del documento
|
|
359
|
-
return caso.items?.length
|
|
360
|
-
? this._normalizarItemsExentos(caso.items)
|
|
361
|
-
: docRef.items || [];
|
|
362
|
-
}
|
|
363
|
-
|
|
364
|
-
/**
|
|
365
|
-
* Resuelve items para ND según codRef
|
|
366
|
-
* @private
|
|
367
|
-
*/
|
|
368
|
-
_resolverItemsND(caso, docRef) {
|
|
369
|
-
const razon = (caso.razonRef || '').toUpperCase();
|
|
370
|
-
const esModificaMonto = razon.includes('MODIFICA MONTO');
|
|
371
|
-
|
|
372
|
-
// Si no hay items, usar item dummy con la razón
|
|
373
|
-
if (!caso.items?.length) {
|
|
374
|
-
// Para ANULA o similar, usar items del documento referenciado si existen
|
|
375
|
-
if (docRef.items?.length) {
|
|
376
|
-
return docRef.items.map(i => ({ ...i, exento: true }));
|
|
377
|
-
}
|
|
378
|
-
return [{
|
|
379
|
-
nombre: caso.razonRef || 'CORRECCION',
|
|
380
|
-
cantidad: 1,
|
|
381
|
-
precio: 0,
|
|
382
|
-
exento: true,
|
|
383
|
-
}];
|
|
384
|
-
}
|
|
385
|
-
|
|
386
|
-
// MODIFICA MONTO: usar CANTIDAD de documento original con PRECIO nuevo del SII
|
|
387
|
-
if (caso.codRef === 3 && esModificaMonto && docRef.items?.length) {
|
|
388
|
-
return caso.items.map(ndItem => {
|
|
389
|
-
const nombreNd = (ndItem.nombre || '').toUpperCase().trim();
|
|
390
|
-
const itemOriginal = docRef.items.find(i =>
|
|
391
|
-
(i.nombre || '').toUpperCase().trim() === nombreNd
|
|
392
|
-
);
|
|
393
|
-
|
|
394
|
-
if (itemOriginal && itemOriginal.cantidad) {
|
|
395
|
-
return {
|
|
396
|
-
nombre: ndItem.nombre,
|
|
397
|
-
cantidad: itemOriginal.cantidad, // Cantidad de documento ORIGINAL
|
|
398
|
-
precio: ndItem.precio, // Precio NUEVO del SII
|
|
399
|
-
unidad: ndItem.unidad || itemOriginal.unidad || 'UN',
|
|
400
|
-
exento: true,
|
|
401
|
-
};
|
|
402
|
-
}
|
|
403
|
-
return { ...ndItem, exento: true };
|
|
404
|
-
});
|
|
405
|
-
}
|
|
406
|
-
|
|
407
|
-
// Default: usar items del caso
|
|
408
|
-
return this._normalizarItemsExentos(caso.items);
|
|
409
|
-
}
|
|
410
|
-
|
|
411
|
-
/**
|
|
412
|
-
* Reserva el siguiente folio disponible
|
|
413
|
-
* @private
|
|
414
|
-
*/
|
|
415
|
-
_reservarFolio(caf, cafXml) {
|
|
416
|
-
const cafFingerprint = this.folioHelper.createCafFingerprint(cafXml);
|
|
417
|
-
const folio = this.folioHelper.reserveNextFolio({
|
|
418
|
-
rutEmisor: this.config.emisor.rut,
|
|
419
|
-
tipoDte: caf.getTipoDTE(),
|
|
420
|
-
folioDesde: caf.getFolioDesde(),
|
|
421
|
-
folioHasta: caf.getFolioHasta(),
|
|
422
|
-
ambiente: this.config.ambiente || 'certificacion',
|
|
423
|
-
cafFingerprint,
|
|
424
|
-
});
|
|
425
|
-
return folio;
|
|
426
|
-
}
|
|
427
|
-
|
|
428
|
-
/**
|
|
429
|
-
* Obtiene la fecha de emisión
|
|
430
|
-
* @private
|
|
431
|
-
*/
|
|
432
|
-
_getFechaEmision() {
|
|
433
|
-
if (this._fechaEmision) return this._fechaEmision;
|
|
434
|
-
const now = new Date();
|
|
435
|
-
this._fechaEmision = now.toISOString().split('T')[0];
|
|
436
|
-
return this._fechaEmision;
|
|
437
|
-
}
|
|
438
|
-
|
|
439
|
-
/**
|
|
440
|
-
* Construye datos del emisor
|
|
441
|
-
* @private
|
|
442
|
-
*/
|
|
443
|
-
_buildEmisor() {
|
|
444
|
-
const e = this.config.emisor;
|
|
445
|
-
return {
|
|
446
|
-
RUTEmisor: e.rut,
|
|
447
|
-
RznSoc: e.razon_social,
|
|
448
|
-
GiroEmis: e.giro,
|
|
449
|
-
Acteco: e.acteco,
|
|
450
|
-
DirOrigen: e.direccion,
|
|
451
|
-
CmnaOrigen: e.comuna,
|
|
452
|
-
CiudadOrigen: e.ciudad || e.comuna || 'Santiago',
|
|
453
|
-
};
|
|
454
|
-
}
|
|
455
|
-
|
|
456
|
-
/**
|
|
457
|
-
* Construye datos del receptor
|
|
458
|
-
* @private
|
|
459
|
-
*/
|
|
460
|
-
_buildReceptor() {
|
|
461
|
-
const r = this.config.receptor;
|
|
462
|
-
return {
|
|
463
|
-
RUTRecep: r.rut,
|
|
464
|
-
RznSocRecep: r.razon_social,
|
|
465
|
-
GiroRecep: r.giro,
|
|
466
|
-
DirRecep: r.direccion,
|
|
467
|
-
CmnaRecep: r.comuna,
|
|
468
|
-
CiudadRecep: r.ciudad || r.comuna,
|
|
469
|
-
};
|
|
470
|
-
}
|
|
471
|
-
|
|
472
|
-
/**
|
|
473
|
-
* Timbra y firma el DTE
|
|
474
|
-
* @private
|
|
475
|
-
*/
|
|
476
|
-
_timbrarYFirmar(dte, caf) {
|
|
477
|
-
const { Certificado } = require('../index');
|
|
478
|
-
const fs = require('fs');
|
|
479
|
-
|
|
480
|
-
const pfxBuffer = fs.readFileSync(this.config.certificado.path);
|
|
481
|
-
const cert = new Certificado(pfxBuffer, this.config.certificado.password);
|
|
482
|
-
|
|
483
|
-
const timestamp = new Date().toISOString().replace('Z', '');
|
|
484
|
-
|
|
485
|
-
dte.generarXML().timbrar(caf, timestamp);
|
|
486
|
-
dte.firmar(cert);
|
|
487
|
-
}
|
|
488
|
-
}
|
|
489
|
-
|
|
490
|
-
module.exports = SetExenta;
|
|
3
|
+
/**
|
|
4
|
+
* Set Factura Exenta
|
|
5
|
+
*
|
|
6
|
+
* Tipos DTE: 34 (Factura Exenta), 61 (NC), 56 (ND)
|
|
7
|
+
*
|
|
8
|
+
* Flujo similar a SetBasico pero:
|
|
9
|
+
* - Todos los items son exentos
|
|
10
|
+
* - Totales sin IVA
|
|
11
|
+
*
|
|
12
|
+
* @module dte-sii/cert/SetExenta
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
const SetBase = require('./SetBase');
|
|
16
|
+
const { SET_LABELS, SETS_DTE } = require('./types');
|
|
17
|
+
|
|
18
|
+
class SetExenta extends SetBase {
|
|
19
|
+
constructor(deps) {
|
|
20
|
+
super(deps);
|
|
21
|
+
|
|
22
|
+
this.key = 'exenta';
|
|
23
|
+
this.label = SET_LABELS.exenta;
|
|
24
|
+
this.tiposDte = SETS_DTE.exenta; // [34, 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 exenta
|
|
34
|
+
*/
|
|
35
|
+
_validarCasos(casos) {
|
|
36
|
+
super._validarCasos(casos);
|
|
37
|
+
|
|
38
|
+
if (!casos.casosFactura?.length) {
|
|
39
|
+
throw new Error('SetExenta: casosFactura es requerido');
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* @override
|
|
45
|
+
* Calcula folios necesarios por tipo
|
|
46
|
+
*/
|
|
47
|
+
_calcularCantidadFolios(casos, tipoDte) {
|
|
48
|
+
if (casos.cafRequired?.[tipoDte]) {
|
|
49
|
+
return casos.cafRequired[tipoDte];
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
switch (tipoDte) {
|
|
53
|
+
case 34: return casos.casosFactura?.length || 1;
|
|
54
|
+
case 61: return casos.casosNC?.length || 1;
|
|
55
|
+
case 56: return casos.casosND?.length || 1;
|
|
56
|
+
default: return 1;
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* @override
|
|
62
|
+
* Genera DTEs del set exenta
|
|
63
|
+
*
|
|
64
|
+
* @param {Object} casos - { casosFactura, casosNC, casosND, cafRequired }
|
|
65
|
+
* @param {Object} cafs - { 34: cafPath, 56: cafPath, 61: cafPath }
|
|
66
|
+
* @returns {Promise<DTE[]>}
|
|
67
|
+
*/
|
|
68
|
+
async generarDtes(casos, cafs) {
|
|
69
|
+
const dtes = [];
|
|
70
|
+
|
|
71
|
+
// 1. Generar facturas exentas primero
|
|
72
|
+
this.logger.log(' 📄 Generando facturas exentas...');
|
|
73
|
+
for (const caso of casos.casosFactura || []) {
|
|
74
|
+
const dte = await this._generarFacturaExenta(caso, cafs[34]);
|
|
75
|
+
dtes.push(dte);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
// 2. Generar notas de crédito
|
|
79
|
+
this.logger.log(' 📄 Generando notas de crédito...');
|
|
80
|
+
for (const caso of casos.casosNC || []) {
|
|
81
|
+
const dte = await this._generarNotaCredito(caso, cafs[61]);
|
|
82
|
+
dtes.push(dte);
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
// 3. Generar notas de débito
|
|
86
|
+
this.logger.log(' 📄 Generando notas de débito...');
|
|
87
|
+
for (const caso of casos.casosND || []) {
|
|
88
|
+
const dte = await this._generarNotaDebito(caso, cafs[56]);
|
|
89
|
+
dtes.push(dte);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
return dtes;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
// ═══════════════════════════════════════════════════════════════
|
|
96
|
+
// Generadores de DTEs
|
|
97
|
+
// ═══════════════════════════════════════════════════════════════
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* Genera factura exenta (tipo 34)
|
|
101
|
+
* @private
|
|
102
|
+
*/
|
|
103
|
+
async _generarFacturaExenta(caso, cafPath) {
|
|
104
|
+
const { DTE, CAF, buildDetalle, calcularTotalesDesdeDetalle, buildSetReferencia } = require('../index');
|
|
105
|
+
const fs = require('fs');
|
|
106
|
+
|
|
107
|
+
const cafXml = fs.readFileSync(cafPath, 'utf8');
|
|
108
|
+
const caf = new CAF(cafXml);
|
|
109
|
+
const folio = this._reservarFolio(caf, cafXml);
|
|
110
|
+
|
|
111
|
+
// Marcar todos los items como exentos
|
|
112
|
+
const items = this._normalizarItemsExentos(caso.items);
|
|
113
|
+
const detalle = buildDetalle(items, { allowIndExe: true });
|
|
114
|
+
const totales = calcularTotalesDesdeDetalle(detalle, { soloExento: true });
|
|
115
|
+
|
|
116
|
+
const fechaEmision = this._getFechaEmision();
|
|
117
|
+
const setReferencia = buildSetReferencia(caso.id, fechaEmision);
|
|
118
|
+
|
|
119
|
+
const dteDatos = {
|
|
120
|
+
Encabezado: {
|
|
121
|
+
IdDoc: {
|
|
122
|
+
TipoDTE: 34,
|
|
123
|
+
Folio: folio,
|
|
124
|
+
FchEmis: fechaEmision,
|
|
125
|
+
},
|
|
126
|
+
Emisor: this._buildEmisor(),
|
|
127
|
+
Receptor: this._buildReceptor(),
|
|
128
|
+
Totales: totales,
|
|
129
|
+
},
|
|
130
|
+
Detalle: detalle,
|
|
131
|
+
Referencia: [setReferencia],
|
|
132
|
+
};
|
|
133
|
+
|
|
134
|
+
const dte = new DTE(dteDatos);
|
|
135
|
+
this._timbrarYFirmar(dte, caf);
|
|
136
|
+
|
|
137
|
+
// Guardar referencia para NC/ND
|
|
138
|
+
this._docRefs[caso.id] = {
|
|
139
|
+
tipoDte: 34,
|
|
140
|
+
folio,
|
|
141
|
+
fecha: fechaEmision,
|
|
142
|
+
detalle,
|
|
143
|
+
totales,
|
|
144
|
+
items,
|
|
145
|
+
};
|
|
146
|
+
|
|
147
|
+
this.logger.log(` ✓ Factura Exenta caso ${caso.id}: folio ${folio}`);
|
|
148
|
+
return dte;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
/**
|
|
152
|
+
* Genera nota de crédito exenta (tipo 61)
|
|
153
|
+
* @private
|
|
154
|
+
*/
|
|
155
|
+
async _generarNotaCredito(caso, cafPath) {
|
|
156
|
+
const { DTE, CAF, buildDetalle, calcularTotalesDesdeDetalle, buildSetReferencia } = require('../index');
|
|
157
|
+
const fs = require('fs');
|
|
158
|
+
|
|
159
|
+
const cafXml = fs.readFileSync(cafPath, 'utf8');
|
|
160
|
+
const caf = new CAF(cafXml);
|
|
161
|
+
const folio = this._reservarFolio(caf, cafXml);
|
|
162
|
+
|
|
163
|
+
// Obtener documento referenciado
|
|
164
|
+
const docRef = this._docRefs[caso.referenciaCaso];
|
|
165
|
+
if (!docRef) {
|
|
166
|
+
throw new Error(`SetExenta: Documento referencia ${caso.referenciaCaso} no encontrado para NC ${caso.id}`);
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
// Determinar items según tipo de NC
|
|
170
|
+
const items = this._resolverItemsNC(caso, docRef);
|
|
171
|
+
const detalle = buildDetalle(items, { allowIndExe: true });
|
|
172
|
+
const totales = calcularTotalesDesdeDetalle(detalle, { soloExento: true });
|
|
173
|
+
|
|
174
|
+
const fechaEmision = this._getFechaEmision();
|
|
175
|
+
const setReferencia = buildSetReferencia(caso.id, fechaEmision);
|
|
176
|
+
|
|
177
|
+
// Referencia al documento original
|
|
178
|
+
const docReferencia = {
|
|
179
|
+
NroLinRef: 2,
|
|
180
|
+
TpoDocRef: docRef.tipoDte,
|
|
181
|
+
FolioRef: docRef.folio,
|
|
182
|
+
FchRef: docRef.fecha,
|
|
183
|
+
CodRef: caso.codRef,
|
|
184
|
+
RazonRef: caso.razonRef,
|
|
185
|
+
};
|
|
186
|
+
|
|
187
|
+
const dteDatos = {
|
|
188
|
+
Encabezado: {
|
|
189
|
+
IdDoc: {
|
|
190
|
+
TipoDTE: 61,
|
|
191
|
+
Folio: folio,
|
|
192
|
+
FchEmis: fechaEmision,
|
|
193
|
+
},
|
|
194
|
+
Emisor: this._buildEmisor(),
|
|
195
|
+
Receptor: this._buildReceptor(),
|
|
196
|
+
Totales: totales,
|
|
197
|
+
},
|
|
198
|
+
Detalle: detalle,
|
|
199
|
+
Referencia: [setReferencia, docReferencia],
|
|
200
|
+
};
|
|
201
|
+
|
|
202
|
+
const dte = new DTE(dteDatos);
|
|
203
|
+
this._timbrarYFirmar(dte, caf);
|
|
204
|
+
|
|
205
|
+
// Guardar referencia
|
|
206
|
+
this._docRefs[caso.id] = {
|
|
207
|
+
tipoDte: 61,
|
|
208
|
+
folio,
|
|
209
|
+
fecha: fechaEmision,
|
|
210
|
+
detalle,
|
|
211
|
+
totales,
|
|
212
|
+
items,
|
|
213
|
+
};
|
|
214
|
+
|
|
215
|
+
this.logger.log(` ✓ NC Exenta caso ${caso.id}: folio ${folio} (ref: ${caso.referenciaCaso})`);
|
|
216
|
+
return dte;
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
/**
|
|
220
|
+
* Genera nota de débito exenta (tipo 56)
|
|
221
|
+
* @private
|
|
222
|
+
*/
|
|
223
|
+
async _generarNotaDebito(caso, cafPath) {
|
|
224
|
+
const { DTE, CAF, buildDetalle, calcularTotalesDesdeDetalle, buildSetReferencia } = require('../index');
|
|
225
|
+
const fs = require('fs');
|
|
226
|
+
|
|
227
|
+
const cafXml = fs.readFileSync(cafPath, 'utf8');
|
|
228
|
+
const caf = new CAF(cafXml);
|
|
229
|
+
const folio = this._reservarFolio(caf, cafXml);
|
|
230
|
+
|
|
231
|
+
// Obtener documento referenciado
|
|
232
|
+
const docRef = this._docRefs[caso.referenciaCaso];
|
|
233
|
+
if (!docRef) {
|
|
234
|
+
throw new Error(`SetExenta: Documento referencia ${caso.referenciaCaso} no encontrado para ND ${caso.id}`);
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
// Determinar items según tipo de ND
|
|
238
|
+
const items = this._resolverItemsND(caso, docRef);
|
|
239
|
+
const detalle = buildDetalle(items, { allowIndExe: true });
|
|
240
|
+
const totales = calcularTotalesDesdeDetalle(detalle, { soloExento: true });
|
|
241
|
+
|
|
242
|
+
const fechaEmision = this._getFechaEmision();
|
|
243
|
+
const setReferencia = buildSetReferencia(caso.id, fechaEmision);
|
|
244
|
+
|
|
245
|
+
// Referencia al documento original
|
|
246
|
+
const docReferencia = {
|
|
247
|
+
NroLinRef: 2,
|
|
248
|
+
TpoDocRef: docRef.tipoDte,
|
|
249
|
+
FolioRef: docRef.folio,
|
|
250
|
+
FchRef: docRef.fecha,
|
|
251
|
+
CodRef: caso.codRef,
|
|
252
|
+
RazonRef: caso.razonRef,
|
|
253
|
+
};
|
|
254
|
+
|
|
255
|
+
const dteDatos = {
|
|
256
|
+
Encabezado: {
|
|
257
|
+
IdDoc: {
|
|
258
|
+
TipoDTE: 56,
|
|
259
|
+
Folio: folio,
|
|
260
|
+
FchEmis: fechaEmision,
|
|
261
|
+
},
|
|
262
|
+
Emisor: this._buildEmisor(),
|
|
263
|
+
Receptor: this._buildReceptor(),
|
|
264
|
+
Totales: totales,
|
|
265
|
+
},
|
|
266
|
+
Detalle: detalle,
|
|
267
|
+
Referencia: [setReferencia, docReferencia],
|
|
268
|
+
};
|
|
269
|
+
|
|
270
|
+
const dte = new DTE(dteDatos);
|
|
271
|
+
this._timbrarYFirmar(dte, caf);
|
|
272
|
+
|
|
273
|
+
this.logger.log(` ✓ ND Exenta caso ${caso.id}: folio ${folio} (ref: ${caso.referenciaCaso})`);
|
|
274
|
+
return dte;
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
// ═══════════════════════════════════════════════════════════════
|
|
278
|
+
// Helpers
|
|
279
|
+
// ═══════════════════════════════════════════════════════════════
|
|
280
|
+
|
|
281
|
+
/**
|
|
282
|
+
* Normaliza items marcándolos como exentos
|
|
283
|
+
* @private
|
|
284
|
+
*/
|
|
285
|
+
_normalizarItemsExentos(items) {
|
|
286
|
+
return (items || []).map(item => ({
|
|
287
|
+
nombre: item.nombre,
|
|
288
|
+
cantidad: item.cantidad || 1,
|
|
289
|
+
precio: item.precio || 0,
|
|
290
|
+
unidad: item.unidad || 'UN',
|
|
291
|
+
exento: true, // Siempre exento
|
|
292
|
+
}));
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
/**
|
|
296
|
+
* Resuelve items para NC según codRef
|
|
297
|
+
* @private
|
|
298
|
+
*/
|
|
299
|
+
_resolverItemsNC(caso, docRef) {
|
|
300
|
+
const razon = (caso.razonRef || '').toUpperCase();
|
|
301
|
+
|
|
302
|
+
// CodRef 2: Corrige giro/texto - usar item dummy
|
|
303
|
+
if (caso.codRef === 2) {
|
|
304
|
+
return [{
|
|
305
|
+
nombre: caso.razonRef || 'CORRECCION',
|
|
306
|
+
cantidad: 1,
|
|
307
|
+
precio: 0,
|
|
308
|
+
exento: true,
|
|
309
|
+
}];
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
// CodRef 3: Modifica montos
|
|
313
|
+
if (caso.codRef === 3) {
|
|
314
|
+
const esDevolucion = razon.includes('DEVOLUCION') || razon.includes('DEVOLUCIÓN');
|
|
315
|
+
const esModificaMonto = razon.includes('MODIFICA MONTO');
|
|
316
|
+
|
|
317
|
+
// DEVOLUCION: usar items de factura original completos
|
|
318
|
+
if (esDevolucion && docRef.items?.length) {
|
|
319
|
+
return docRef.items.map(item => ({
|
|
320
|
+
...item,
|
|
321
|
+
exento: true,
|
|
322
|
+
}));
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
// MODIFICA MONTO: usar CANTIDAD de factura original con PRECIO nuevo del SII
|
|
326
|
+
if (esModificaMonto && caso.items?.length && docRef.items?.length) {
|
|
327
|
+
return caso.items.map(ncItem => {
|
|
328
|
+
const nombreNc = (ncItem.nombre || '').toUpperCase().trim();
|
|
329
|
+
const itemOriginal = docRef.items.find(i =>
|
|
330
|
+
(i.nombre || '').toUpperCase().trim() === nombreNc
|
|
331
|
+
);
|
|
332
|
+
|
|
333
|
+
if (itemOriginal && itemOriginal.cantidad) {
|
|
334
|
+
return {
|
|
335
|
+
nombre: ncItem.nombre,
|
|
336
|
+
cantidad: itemOriginal.cantidad, // Cantidad de factura ORIGINAL
|
|
337
|
+
precio: ncItem.precio, // Precio NUEVO del SII
|
|
338
|
+
unidad: ncItem.unidad || itemOriginal.unidad || 'UN',
|
|
339
|
+
exento: true,
|
|
340
|
+
};
|
|
341
|
+
}
|
|
342
|
+
return { ...ncItem, exento: true };
|
|
343
|
+
});
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
// Otros casos de codRef 3: usar items del caso
|
|
347
|
+
if (caso.items?.length) {
|
|
348
|
+
return this._normalizarItemsExentos(caso.items);
|
|
349
|
+
}
|
|
350
|
+
return docRef.items || [];
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
// CodRef 1: Anula documento completo
|
|
354
|
+
if (caso.codRef === 1) {
|
|
355
|
+
return docRef.items || [];
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
// Default: usar items de la NC o del documento
|
|
359
|
+
return caso.items?.length
|
|
360
|
+
? this._normalizarItemsExentos(caso.items)
|
|
361
|
+
: docRef.items || [];
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
/**
|
|
365
|
+
* Resuelve items para ND según codRef
|
|
366
|
+
* @private
|
|
367
|
+
*/
|
|
368
|
+
_resolverItemsND(caso, docRef) {
|
|
369
|
+
const razon = (caso.razonRef || '').toUpperCase();
|
|
370
|
+
const esModificaMonto = razon.includes('MODIFICA MONTO');
|
|
371
|
+
|
|
372
|
+
// Si no hay items, usar item dummy con la razón
|
|
373
|
+
if (!caso.items?.length) {
|
|
374
|
+
// Para ANULA o similar, usar items del documento referenciado si existen
|
|
375
|
+
if (docRef.items?.length) {
|
|
376
|
+
return docRef.items.map(i => ({ ...i, exento: true }));
|
|
377
|
+
}
|
|
378
|
+
return [{
|
|
379
|
+
nombre: caso.razonRef || 'CORRECCION',
|
|
380
|
+
cantidad: 1,
|
|
381
|
+
precio: 0,
|
|
382
|
+
exento: true,
|
|
383
|
+
}];
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
// MODIFICA MONTO: usar CANTIDAD de documento original con PRECIO nuevo del SII
|
|
387
|
+
if (caso.codRef === 3 && esModificaMonto && docRef.items?.length) {
|
|
388
|
+
return caso.items.map(ndItem => {
|
|
389
|
+
const nombreNd = (ndItem.nombre || '').toUpperCase().trim();
|
|
390
|
+
const itemOriginal = docRef.items.find(i =>
|
|
391
|
+
(i.nombre || '').toUpperCase().trim() === nombreNd
|
|
392
|
+
);
|
|
393
|
+
|
|
394
|
+
if (itemOriginal && itemOriginal.cantidad) {
|
|
395
|
+
return {
|
|
396
|
+
nombre: ndItem.nombre,
|
|
397
|
+
cantidad: itemOriginal.cantidad, // Cantidad de documento ORIGINAL
|
|
398
|
+
precio: ndItem.precio, // Precio NUEVO del SII
|
|
399
|
+
unidad: ndItem.unidad || itemOriginal.unidad || 'UN',
|
|
400
|
+
exento: true,
|
|
401
|
+
};
|
|
402
|
+
}
|
|
403
|
+
return { ...ndItem, exento: true };
|
|
404
|
+
});
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
// Default: usar items del caso
|
|
408
|
+
return this._normalizarItemsExentos(caso.items);
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
/**
|
|
412
|
+
* Reserva el siguiente folio disponible
|
|
413
|
+
* @private
|
|
414
|
+
*/
|
|
415
|
+
_reservarFolio(caf, cafXml) {
|
|
416
|
+
const cafFingerprint = this.folioHelper.createCafFingerprint(cafXml);
|
|
417
|
+
const folio = this.folioHelper.reserveNextFolio({
|
|
418
|
+
rutEmisor: this.config.emisor.rut,
|
|
419
|
+
tipoDte: caf.getTipoDTE(),
|
|
420
|
+
folioDesde: caf.getFolioDesde(),
|
|
421
|
+
folioHasta: caf.getFolioHasta(),
|
|
422
|
+
ambiente: this.config.ambiente || 'certificacion',
|
|
423
|
+
cafFingerprint,
|
|
424
|
+
});
|
|
425
|
+
return folio;
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
/**
|
|
429
|
+
* Obtiene la fecha de emisión
|
|
430
|
+
* @private
|
|
431
|
+
*/
|
|
432
|
+
_getFechaEmision() {
|
|
433
|
+
if (this._fechaEmision) return this._fechaEmision;
|
|
434
|
+
const now = new Date();
|
|
435
|
+
this._fechaEmision = now.toISOString().split('T')[0];
|
|
436
|
+
return this._fechaEmision;
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
/**
|
|
440
|
+
* Construye datos del emisor
|
|
441
|
+
* @private
|
|
442
|
+
*/
|
|
443
|
+
_buildEmisor() {
|
|
444
|
+
const e = this.config.emisor;
|
|
445
|
+
return {
|
|
446
|
+
RUTEmisor: e.rut,
|
|
447
|
+
RznSoc: e.razon_social,
|
|
448
|
+
GiroEmis: e.giro,
|
|
449
|
+
Acteco: e.acteco,
|
|
450
|
+
DirOrigen: e.direccion,
|
|
451
|
+
CmnaOrigen: e.comuna,
|
|
452
|
+
CiudadOrigen: e.ciudad || e.comuna || 'Santiago',
|
|
453
|
+
};
|
|
454
|
+
}
|
|
455
|
+
|
|
456
|
+
/**
|
|
457
|
+
* Construye datos del receptor
|
|
458
|
+
* @private
|
|
459
|
+
*/
|
|
460
|
+
_buildReceptor() {
|
|
461
|
+
const r = this.config.receptor;
|
|
462
|
+
return {
|
|
463
|
+
RUTRecep: r.rut,
|
|
464
|
+
RznSocRecep: r.razon_social,
|
|
465
|
+
GiroRecep: r.giro,
|
|
466
|
+
DirRecep: r.direccion,
|
|
467
|
+
CmnaRecep: r.comuna,
|
|
468
|
+
CiudadRecep: r.ciudad || r.comuna,
|
|
469
|
+
};
|
|
470
|
+
}
|
|
471
|
+
|
|
472
|
+
/**
|
|
473
|
+
* Timbra y firma el DTE
|
|
474
|
+
* @private
|
|
475
|
+
*/
|
|
476
|
+
_timbrarYFirmar(dte, caf) {
|
|
477
|
+
const { Certificado } = require('../index');
|
|
478
|
+
const fs = require('fs');
|
|
479
|
+
|
|
480
|
+
const pfxBuffer = fs.readFileSync(this.config.certificado.path);
|
|
481
|
+
const cert = new Certificado(pfxBuffer, this.config.certificado.password);
|
|
482
|
+
|
|
483
|
+
const timestamp = new Date().toISOString().replace('Z', '');
|
|
484
|
+
|
|
485
|
+
dte.generarXML().timbrar(caf, timestamp);
|
|
486
|
+
dte.firmar(cert);
|
|
487
|
+
}
|
|
488
|
+
}
|
|
489
|
+
|
|
490
|
+
module.exports = SetExenta;
|