@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,521 @@
|
|
|
1
|
+
// Copyright (c) 2026 Devlas SpA — https://devlas.cl
|
|
2
|
+
// Licencia MIT. Ver archivo LICENSE para mas detalles.
|
|
3
|
+
/**
|
|
4
|
+
* Simulación - Generador del Set de Simulación para certificación SII
|
|
5
|
+
*
|
|
6
|
+
* La etapa de Simulación requiere enviar múltiples DTEs en un solo envío,
|
|
7
|
+
* simulando la operación real de la empresa. Usa todos los tipos de DTE
|
|
8
|
+
* de los sets anteriores.
|
|
9
|
+
*
|
|
10
|
+
* @module dte-sii/cert/Simulacion
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
const fs = require('fs');
|
|
14
|
+
const path = require('path');
|
|
15
|
+
|
|
16
|
+
// Core
|
|
17
|
+
const { Certificado, CAF, DTE, EnvioDTE } = require('../index');
|
|
18
|
+
const { buildDetalle, buildDetalleGuia, buildDetalleCompra, calcularTotalesDesdeDetalle } = require('../index');
|
|
19
|
+
|
|
20
|
+
const normalizeText = (value) => String(value || '').trim();
|
|
21
|
+
const TASA_IVA = 19;
|
|
22
|
+
|
|
23
|
+
class Simulacion {
|
|
24
|
+
/**
|
|
25
|
+
* @param {Object} config
|
|
26
|
+
* @param {Object} config.emisor - { rut, razon_social, giro, acteco, direccion, comuna, ciudad, fch_resol, nro_resol }
|
|
27
|
+
* @param {Object} config.receptor - { rut, razon_social, giro, direccion, comuna, ciudad }
|
|
28
|
+
* @param {Object} config.certificado - Instancia de Certificado
|
|
29
|
+
* @param {Object} [config.resolucion] - { fecha, numero }
|
|
30
|
+
*/
|
|
31
|
+
constructor(config) {
|
|
32
|
+
this.emisor = config.emisor;
|
|
33
|
+
this.receptor = config.receptor;
|
|
34
|
+
this.certificado = config.certificado;
|
|
35
|
+
this.resolucion = config.resolucion || {
|
|
36
|
+
fecha: config.emisor.fch_resol,
|
|
37
|
+
numero: config.emisor.nro_resol,
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Genera el EnvioDTE de simulación a partir de las estructuras
|
|
43
|
+
* @param {Object} estructuras - Estructuras del set de pruebas
|
|
44
|
+
* @param {Object} cafs - { tipoDte: CAF } pre-cargados
|
|
45
|
+
* @param {Object} folioHelper - Helper para gestionar folios
|
|
46
|
+
* @param {Object} [options] - Opciones
|
|
47
|
+
* @param {string} [options.fechaEmision] - Fecha de emisión (default: hoy)
|
|
48
|
+
* @returns {Object} { envioDte, dtes, xmlPath, plan }
|
|
49
|
+
*/
|
|
50
|
+
generar(estructuras, cafs, folioHelper, options = {}) {
|
|
51
|
+
const fechaEmision = options.fechaEmision || this._getFechaHoy();
|
|
52
|
+
const plan = this._buildPlan(estructuras);
|
|
53
|
+
const docRefs = {};
|
|
54
|
+
|
|
55
|
+
if (plan.length < 10) {
|
|
56
|
+
console.warn('⚠️ Se recomienda mínimo 10 documentos para simulación.');
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
const envioDte = new EnvioDTE({ certificado: this.certificado });
|
|
60
|
+
const generatedDtes = [];
|
|
61
|
+
|
|
62
|
+
for (const doc of plan) {
|
|
63
|
+
const tipoDte = Number(doc.tipoDte || 33);
|
|
64
|
+
const caf = cafs[tipoDte];
|
|
65
|
+
if (!caf) {
|
|
66
|
+
throw new Error(`No hay CAF para tipo ${tipoDte}`);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
// Obtener folio desde el CAF
|
|
70
|
+
const folio = folioHelper.reserveNextFolio({
|
|
71
|
+
tipoDte,
|
|
72
|
+
folioDesde: caf.getFolioDesde(),
|
|
73
|
+
folioHasta: caf.getFolioHasta(),
|
|
74
|
+
});
|
|
75
|
+
const base = doc.referenciaCaso ? docRefs[doc.referenciaCaso] : null;
|
|
76
|
+
|
|
77
|
+
// Resolver items
|
|
78
|
+
let items = doc.items || [];
|
|
79
|
+
if (doc.kind === 'basico' || doc.kind === 'exenta') {
|
|
80
|
+
items = this._resolveItems(doc, base, doc.kind === 'exenta');
|
|
81
|
+
}
|
|
82
|
+
if (doc.kind === 'compra' && base?.items?.length) {
|
|
83
|
+
items = items.map((item) => {
|
|
84
|
+
const original = base.items.find((i) => normalizeText(i.nombre) === normalizeText(item.nombre));
|
|
85
|
+
return { ...item, precio: item.precio ?? original?.precio ?? 1 };
|
|
86
|
+
});
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
// Receptor (para compra o guía con indTraslado=5, usar emisor)
|
|
90
|
+
const receptor = (doc.kind === 'compra' || this._shouldUseEmisorAsReceptor(doc)) ? {
|
|
91
|
+
rut: this.emisor.rut,
|
|
92
|
+
razon_social: this.emisor.razon_social,
|
|
93
|
+
giro: this.emisor.giro,
|
|
94
|
+
direccion: this.emisor.direccion,
|
|
95
|
+
comuna: this.emisor.comuna,
|
|
96
|
+
ciudad: this.emisor.ciudad || this.emisor.comuna,
|
|
97
|
+
} : this.receptor;
|
|
98
|
+
|
|
99
|
+
// Construir detalle y totales según tipo
|
|
100
|
+
const { detalle, totales } = this._buildDetalleYTotales(doc, items, tipoDte);
|
|
101
|
+
|
|
102
|
+
// Construir DTE
|
|
103
|
+
const dteDatos = {
|
|
104
|
+
Encabezado: {
|
|
105
|
+
IdDoc: {
|
|
106
|
+
TipoDTE: tipoDte,
|
|
107
|
+
Folio: folio,
|
|
108
|
+
FchEmis: fechaEmision,
|
|
109
|
+
...(tipoDte === 33 ? { TpoTranVenta: 1 } : {}),
|
|
110
|
+
...(tipoDte === 52 ? {
|
|
111
|
+
IndTraslado: doc.indTraslado,
|
|
112
|
+
...(doc.tpoDespacho ? { TpoDespacho: doc.tpoDespacho } : {}),
|
|
113
|
+
} : {}),
|
|
114
|
+
},
|
|
115
|
+
Emisor: {
|
|
116
|
+
RUTEmisor: this.emisor.rut,
|
|
117
|
+
RznSoc: this.emisor.razon_social,
|
|
118
|
+
GiroEmis: this.emisor.giro,
|
|
119
|
+
Acteco: this.emisor.acteco,
|
|
120
|
+
DirOrigen: this.emisor.direccion,
|
|
121
|
+
CmnaOrigen: this.emisor.comuna,
|
|
122
|
+
CiudadOrigen: this.emisor.ciudad || this.emisor.comuna || 'Santiago',
|
|
123
|
+
},
|
|
124
|
+
Receptor: {
|
|
125
|
+
RUTRecep: receptor.rut,
|
|
126
|
+
RznSocRecep: receptor.razon_social,
|
|
127
|
+
GiroRecep: receptor.giro,
|
|
128
|
+
DirRecep: receptor.direccion,
|
|
129
|
+
CmnaRecep: receptor.comuna,
|
|
130
|
+
CiudadRecep: receptor.ciudad || receptor.comuna,
|
|
131
|
+
},
|
|
132
|
+
Totales: totales,
|
|
133
|
+
},
|
|
134
|
+
Detalle: detalle,
|
|
135
|
+
};
|
|
136
|
+
|
|
137
|
+
// Descuento global
|
|
138
|
+
if (doc.descuentoGlobalPct) {
|
|
139
|
+
dteDatos.DscRcgGlobal = [{
|
|
140
|
+
NroLinDR: 1,
|
|
141
|
+
TpoMov: 'D',
|
|
142
|
+
TpoValor: '%',
|
|
143
|
+
ValorDR: Number(doc.descuentoGlobalPct),
|
|
144
|
+
}];
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
// Referencia
|
|
148
|
+
if (doc.referenciaCaso && base) {
|
|
149
|
+
dteDatos.Referencia = [{
|
|
150
|
+
NroLinRef: 1,
|
|
151
|
+
TpoDocRef: base.tipoDte,
|
|
152
|
+
FolioRef: base.folio,
|
|
153
|
+
FchRef: base.fecha,
|
|
154
|
+
CodRef: doc.codRef,
|
|
155
|
+
RazonRef: doc.razonRef,
|
|
156
|
+
}];
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
// Generar DTE
|
|
160
|
+
const dte = new DTE(dteDatos);
|
|
161
|
+
dte.generarXML().timbrar(caf).firmar(this.certificado);
|
|
162
|
+
envioDte.agregar(dte);
|
|
163
|
+
|
|
164
|
+
generatedDtes.push({
|
|
165
|
+
tipoDte,
|
|
166
|
+
folio,
|
|
167
|
+
xml: dte.getXML(),
|
|
168
|
+
});
|
|
169
|
+
|
|
170
|
+
// Guardar referencia para NC/ND
|
|
171
|
+
if (doc.id) {
|
|
172
|
+
docRefs[doc.id] = {
|
|
173
|
+
tipoDte,
|
|
174
|
+
folio,
|
|
175
|
+
fecha: fechaEmision,
|
|
176
|
+
items,
|
|
177
|
+
};
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
// Carátula del envío
|
|
182
|
+
envioDte.setCaratula({
|
|
183
|
+
RutEmisor: this.emisor.rut,
|
|
184
|
+
RutEnvia: this.certificado.rut || this.emisor.rut,
|
|
185
|
+
RutReceptor: '60803000-K', // SII
|
|
186
|
+
FchResol: this.resolucion.fecha,
|
|
187
|
+
NroResol: this.resolucion.numero,
|
|
188
|
+
});
|
|
189
|
+
envioDte.generar();
|
|
190
|
+
|
|
191
|
+
return {
|
|
192
|
+
envioDte,
|
|
193
|
+
dtes: generatedDtes,
|
|
194
|
+
xml: envioDte.getXML(),
|
|
195
|
+
plan,
|
|
196
|
+
tiposUsados: [...new Set(plan.map(d => d.tipoDte))],
|
|
197
|
+
};
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
/**
|
|
201
|
+
* Construye el plan de documentos a generar
|
|
202
|
+
* @private
|
|
203
|
+
*/
|
|
204
|
+
_buildPlan(estructuras) {
|
|
205
|
+
const plan = [];
|
|
206
|
+
const setBasico = estructuras?.setBasico;
|
|
207
|
+
const setExenta = estructuras?.setFacturaExenta;
|
|
208
|
+
const setGuia = estructuras?.setGuiaDespacho;
|
|
209
|
+
const setCompra = estructuras?.setFacturaCompra;
|
|
210
|
+
|
|
211
|
+
// Set Básico
|
|
212
|
+
(setBasico?.casosFactura || []).forEach((caso) => plan.push({
|
|
213
|
+
kind: 'basico', tipoDte: 33, id: caso.id,
|
|
214
|
+
items: caso.items || [], descuentoGlobalPct: caso.descuentoGlobalPct,
|
|
215
|
+
}));
|
|
216
|
+
(setBasico?.casosNC || []).forEach((caso) => plan.push({
|
|
217
|
+
kind: 'basico', tipoDte: 61, id: caso.id,
|
|
218
|
+
referenciaCaso: caso.referenciaCaso, codRef: caso.codRef,
|
|
219
|
+
razonRef: caso.razonRef, items: caso.items || [], itemsFromCaso: caso.itemsFromCaso,
|
|
220
|
+
}));
|
|
221
|
+
(setBasico?.casosND || []).forEach((caso) => plan.push({
|
|
222
|
+
kind: 'basico', tipoDte: 56, id: caso.id,
|
|
223
|
+
referenciaCaso: caso.referenciaCaso, codRef: caso.codRef,
|
|
224
|
+
razonRef: caso.razonRef, items: caso.items || [],
|
|
225
|
+
}));
|
|
226
|
+
|
|
227
|
+
// Set Factura Exenta
|
|
228
|
+
(setExenta?.casosFactura || []).forEach((caso) => plan.push({
|
|
229
|
+
kind: 'exenta', tipoDte: 34, id: caso.id, items: caso.items || [],
|
|
230
|
+
}));
|
|
231
|
+
(setExenta?.casosNC || []).forEach((caso) => plan.push({
|
|
232
|
+
kind: 'exenta', tipoDte: 61, id: caso.id,
|
|
233
|
+
referenciaCaso: caso.referenciaCaso, codRef: caso.codRef,
|
|
234
|
+
razonRef: caso.razonRef, items: caso.items || [],
|
|
235
|
+
}));
|
|
236
|
+
(setExenta?.casosND || []).forEach((caso) => plan.push({
|
|
237
|
+
kind: 'exenta', tipoDte: 56, id: caso.id,
|
|
238
|
+
referenciaCaso: caso.referenciaCaso, codRef: caso.codRef,
|
|
239
|
+
razonRef: caso.razonRef, items: caso.items || [],
|
|
240
|
+
}));
|
|
241
|
+
|
|
242
|
+
// Set Guía Despacho
|
|
243
|
+
(setGuia?.casos || []).forEach((caso) => plan.push({
|
|
244
|
+
kind: 'guia', tipoDte: 52, id: caso.id,
|
|
245
|
+
indTraslado: caso.indTraslado, tpoDespacho: caso.tpoDespacho, items: caso.items || [],
|
|
246
|
+
}));
|
|
247
|
+
|
|
248
|
+
// Set Factura Compra
|
|
249
|
+
if (setCompra?.casoFactura) {
|
|
250
|
+
plan.push({
|
|
251
|
+
kind: 'compra', tipoDte: 46, id: setCompra.casoFactura.id,
|
|
252
|
+
items: setCompra.casoFactura.items || [],
|
|
253
|
+
});
|
|
254
|
+
}
|
|
255
|
+
if (setCompra?.casoNC) {
|
|
256
|
+
plan.push({
|
|
257
|
+
kind: 'compra', tipoDte: 61, id: setCompra.casoNC.id,
|
|
258
|
+
referenciaCaso: setCompra.casoNC.referenciaCaso, codRef: setCompra.casoNC.codRef,
|
|
259
|
+
razonRef: setCompra.casoNC.razonRef, items: setCompra.casoNC.items || [],
|
|
260
|
+
});
|
|
261
|
+
}
|
|
262
|
+
if (setCompra?.casoND) {
|
|
263
|
+
plan.push({
|
|
264
|
+
kind: 'compra', tipoDte: 56, id: setCompra.casoND.id,
|
|
265
|
+
referenciaCaso: setCompra.casoND.referenciaCaso, codRef: setCompra.casoND.codRef,
|
|
266
|
+
razonRef: setCompra.casoND.razonRef, items: setCompra.casoND.items || [],
|
|
267
|
+
});
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
return plan;
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
/**
|
|
274
|
+
* Resuelve items heredados del caso referenciado
|
|
275
|
+
* @private
|
|
276
|
+
*/
|
|
277
|
+
_resolveItems(caso, base, exentoDefault) {
|
|
278
|
+
let items = caso.items || [];
|
|
279
|
+
|
|
280
|
+
if (caso.itemsFromCaso && base?.items?.length) {
|
|
281
|
+
items = base.items;
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
if (!items || items.length === 0) {
|
|
285
|
+
items = [{
|
|
286
|
+
nombre: caso.razonRef || 'CORRECCION',
|
|
287
|
+
cantidad: 1,
|
|
288
|
+
precio: 0,
|
|
289
|
+
exento: !!exentoDefault,
|
|
290
|
+
}];
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
const razon = (caso.razonRef || '').toUpperCase();
|
|
294
|
+
const esDevolucion = razon.includes('DEVOLUCION') || razon.includes('DEVOLUCIÓN');
|
|
295
|
+
const esModificaMonto = razon.includes('MODIFICA MONTO');
|
|
296
|
+
|
|
297
|
+
if (caso.codRef === 3 && esDevolucion && base?.items?.length) {
|
|
298
|
+
items = base.items.map((bi) => ({ ...bi, exento: exentoDefault || bi.exento }));
|
|
299
|
+
} else if (caso.codRef === 3 && esModificaMonto && base?.items?.length && items.length > 0) {
|
|
300
|
+
items = items.map((ncItem) => {
|
|
301
|
+
const nombreNc = normalizeText(ncItem.nombre).toUpperCase();
|
|
302
|
+
const itemOriginal = base.items.find((bi) => normalizeText(bi.nombre).toUpperCase() === nombreNc);
|
|
303
|
+
if (itemOriginal?.cantidad) {
|
|
304
|
+
return { ...ncItem, cantidad: itemOriginal.cantidad, exento: exentoDefault || ncItem.exento };
|
|
305
|
+
}
|
|
306
|
+
return { ...ncItem, exento: exentoDefault || ncItem.exento };
|
|
307
|
+
});
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
if (exentoDefault) {
|
|
311
|
+
items = items.map((i) => ({ ...i, exento: true }));
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
return items;
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
/**
|
|
318
|
+
* Construye detalle y totales según tipo de documento
|
|
319
|
+
* @private
|
|
320
|
+
*/
|
|
321
|
+
_buildDetalleYTotales(doc, items, tipoDte) {
|
|
322
|
+
let detalle, totales;
|
|
323
|
+
|
|
324
|
+
if (doc.kind === 'guia') {
|
|
325
|
+
detalle = this._buildDetalleGuia(items);
|
|
326
|
+
totales = this._calcularTotalesGuia(detalle);
|
|
327
|
+
} else if (doc.kind === 'compra') {
|
|
328
|
+
detalle = this._buildDetalleCompra(items);
|
|
329
|
+
totales = this._calcularTotalesFacturaCompra(detalle);
|
|
330
|
+
} else {
|
|
331
|
+
detalle = this._buildDetalleGeneral(items, { allowIndExe: doc.kind === 'exenta' || doc.kind === 'basico' });
|
|
332
|
+
totales = this._calcularTotalesGeneral(items, doc.descuentoGlobalPct);
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
return { detalle, totales };
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
/**
|
|
339
|
+
* Construye detalle para factura/NC/ND general
|
|
340
|
+
* @private
|
|
341
|
+
*/
|
|
342
|
+
_buildDetalleGeneral(items = [], options = {}) {
|
|
343
|
+
return items.map((i, idx) => {
|
|
344
|
+
const qty = i.cantidad ?? 1;
|
|
345
|
+
const prc = i.precio ?? 0;
|
|
346
|
+
const base = Math.round(qty * prc);
|
|
347
|
+
const descuentoPct = Number(i.descuentoPct || 0);
|
|
348
|
+
const descuentoMonto = descuentoPct > 0 ? Math.round(base * (descuentoPct / 100)) : 0;
|
|
349
|
+
const monto = base - descuentoMonto;
|
|
350
|
+
const det = {
|
|
351
|
+
NroLinDet: idx + 1,
|
|
352
|
+
...(options.allowIndExe && i.exento ? { IndExe: 1 } : {}),
|
|
353
|
+
NmbItem: normalizeText(i.nombre),
|
|
354
|
+
};
|
|
355
|
+
if (prc > 0 || options.forcePriced) {
|
|
356
|
+
det.QtyItem = qty;
|
|
357
|
+
if (i.unidad) det.UnmdItem = i.unidad;
|
|
358
|
+
det.PrcItem = prc;
|
|
359
|
+
if (descuentoPct > 0) {
|
|
360
|
+
det.DescuentoPct = descuentoPct;
|
|
361
|
+
det.DescuentoMonto = descuentoMonto;
|
|
362
|
+
}
|
|
363
|
+
}
|
|
364
|
+
det.MontoItem = monto;
|
|
365
|
+
return det;
|
|
366
|
+
});
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
/**
|
|
370
|
+
* Construye detalle para Guía de Despacho
|
|
371
|
+
* @private
|
|
372
|
+
*/
|
|
373
|
+
_buildDetalleGuia(items = []) {
|
|
374
|
+
return items.map((i, idx) => {
|
|
375
|
+
const qty = i.cantidad ?? 1;
|
|
376
|
+
const prc = i.precio ?? null;
|
|
377
|
+
const monto = prc !== null ? Math.round(qty * prc) : (i.monto ?? undefined);
|
|
378
|
+
return {
|
|
379
|
+
NroLinDet: idx + 1,
|
|
380
|
+
NmbItem: normalizeText(i.nombre),
|
|
381
|
+
QtyItem: qty,
|
|
382
|
+
UnmdItem: i.unidad || 'UN',
|
|
383
|
+
...(prc !== null && prc > 0 ? { PrcItem: prc } : {}),
|
|
384
|
+
...(monto !== undefined ? { MontoItem: monto } : {}),
|
|
385
|
+
...(i.exento ? { IndExe: 1 } : {}),
|
|
386
|
+
};
|
|
387
|
+
});
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
/**
|
|
391
|
+
* Construye detalle para Factura de Compra
|
|
392
|
+
* @private
|
|
393
|
+
*/
|
|
394
|
+
_buildDetalleCompra(items = []) {
|
|
395
|
+
return items.map((i, idx) => {
|
|
396
|
+
const qty = i.cantidad ?? 1;
|
|
397
|
+
const prc = i.precio ?? 0;
|
|
398
|
+
const monto = Math.round(qty * prc);
|
|
399
|
+
// Para facturas de compra, siempre agregar CodImpAdic: 15 a items afectos
|
|
400
|
+
const codImpAdic = i.exento ? null : (i.codImpAdic ?? 15);
|
|
401
|
+
return {
|
|
402
|
+
NroLinDet: idx + 1,
|
|
403
|
+
NmbItem: normalizeText(i.nombre),
|
|
404
|
+
QtyItem: qty,
|
|
405
|
+
UnmdItem: i.unidad || 'UN',
|
|
406
|
+
PrcItem: prc,
|
|
407
|
+
...(codImpAdic ? { CodImpAdic: codImpAdic } : {}),
|
|
408
|
+
MontoItem: monto,
|
|
409
|
+
...(i.exento ? { IndExe: 1 } : {}),
|
|
410
|
+
};
|
|
411
|
+
});
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
/**
|
|
415
|
+
* Calcula totales para factura general
|
|
416
|
+
* @private
|
|
417
|
+
*/
|
|
418
|
+
_calcularTotalesGeneral(items = [], descuentoGlobalPct = 0) {
|
|
419
|
+
let mntNeto = 0;
|
|
420
|
+
let mntExe = 0;
|
|
421
|
+
|
|
422
|
+
for (const item of items) {
|
|
423
|
+
const qty = Number(item.cantidad || 1);
|
|
424
|
+
const precio = Number(item.precio || 0);
|
|
425
|
+
const descuento = Number(item.descuentoPct || 0);
|
|
426
|
+
const base = Math.round(qty * precio);
|
|
427
|
+
const descuentoMonto = descuento > 0 ? Math.round(base * (descuento / 100)) : 0;
|
|
428
|
+
const linea = base - descuentoMonto;
|
|
429
|
+
if (item.exento) mntExe += linea;
|
|
430
|
+
else mntNeto += linea;
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
const descGlobalMonto = descuentoGlobalPct ? Math.round(mntNeto * (descuentoGlobalPct / 100)) : 0;
|
|
434
|
+
mntNeto = Math.max(0, mntNeto - descGlobalMonto);
|
|
435
|
+
|
|
436
|
+
const iva = Math.round(mntNeto * (TASA_IVA / 100));
|
|
437
|
+
const total = mntNeto + iva + mntExe;
|
|
438
|
+
|
|
439
|
+
const totales = {};
|
|
440
|
+
if (mntNeto > 0) totales.MntNeto = mntNeto;
|
|
441
|
+
if (mntExe > 0) totales.MntExe = mntExe;
|
|
442
|
+
if (mntNeto > 0) {
|
|
443
|
+
totales.TasaIVA = TASA_IVA;
|
|
444
|
+
totales.IVA = iva;
|
|
445
|
+
}
|
|
446
|
+
totales.MntTotal = total;
|
|
447
|
+
return totales;
|
|
448
|
+
}
|
|
449
|
+
|
|
450
|
+
/**
|
|
451
|
+
* Calcula totales para Guía de Despacho
|
|
452
|
+
* @private
|
|
453
|
+
*/
|
|
454
|
+
_calcularTotalesGuia(detalle = []) {
|
|
455
|
+
let mntNeto = 0;
|
|
456
|
+
let mntExe = 0;
|
|
457
|
+
|
|
458
|
+
detalle.forEach((det) => {
|
|
459
|
+
if (det.IndExe === 1) mntExe += det.MontoItem || 0;
|
|
460
|
+
else mntNeto += det.MontoItem || 0;
|
|
461
|
+
});
|
|
462
|
+
|
|
463
|
+
if (mntNeto === 0 && mntExe === 0) {
|
|
464
|
+
return { TasaIVA: TASA_IVA, MntTotal: 0 };
|
|
465
|
+
}
|
|
466
|
+
|
|
467
|
+
const iva = mntNeto > 0 ? Math.round(mntNeto * (TASA_IVA / 100)) : 0;
|
|
468
|
+
const mntTotal = mntNeto + iva + mntExe;
|
|
469
|
+
|
|
470
|
+
const totales = {};
|
|
471
|
+
if (mntNeto > 0) totales.MntNeto = mntNeto;
|
|
472
|
+
if (mntExe > 0) totales.MntExe = mntExe;
|
|
473
|
+
if (mntNeto > 0) totales.TasaIVA = TASA_IVA;
|
|
474
|
+
if (mntNeto > 0) totales.IVA = iva;
|
|
475
|
+
totales.MntTotal = mntTotal;
|
|
476
|
+
return totales;
|
|
477
|
+
}
|
|
478
|
+
|
|
479
|
+
/**
|
|
480
|
+
* Calcula totales para Factura de Compra
|
|
481
|
+
* @private
|
|
482
|
+
*/
|
|
483
|
+
_calcularTotalesFacturaCompra(detalle = []) {
|
|
484
|
+
let mntNeto = 0;
|
|
485
|
+
let mntExe = 0;
|
|
486
|
+
|
|
487
|
+
detalle.forEach((det) => {
|
|
488
|
+
if (det.IndExe === 1) mntExe += det.MontoItem || 0;
|
|
489
|
+
else mntNeto += det.MontoItem || 0;
|
|
490
|
+
});
|
|
491
|
+
|
|
492
|
+
const iva = mntNeto > 0 ? Math.round(mntNeto * (TASA_IVA / 100)) : 0;
|
|
493
|
+
const mntTotal = mntNeto + mntExe;
|
|
494
|
+
|
|
495
|
+
const totales = {};
|
|
496
|
+
if (mntNeto > 0) totales.MntNeto = mntNeto;
|
|
497
|
+
if (mntExe > 0) totales.MntExe = mntExe;
|
|
498
|
+
if (mntNeto > 0) totales.TasaIVA = TASA_IVA;
|
|
499
|
+
if (mntNeto > 0) totales.IVA = iva;
|
|
500
|
+
if (iva > 0) {
|
|
501
|
+
totales.ImptoReten = [{
|
|
502
|
+
TipoImp: 15,
|
|
503
|
+
TasaImp: TASA_IVA,
|
|
504
|
+
MontoImp: iva,
|
|
505
|
+
}];
|
|
506
|
+
}
|
|
507
|
+
totales.MntTotal = mntTotal;
|
|
508
|
+
return totales;
|
|
509
|
+
}
|
|
510
|
+
|
|
511
|
+
_shouldUseEmisorAsReceptor(doc) {
|
|
512
|
+
return Number(doc.indTraslado) === 5;
|
|
513
|
+
}
|
|
514
|
+
|
|
515
|
+
_getFechaHoy() {
|
|
516
|
+
const now = new Date();
|
|
517
|
+
return `${now.getFullYear()}-${String(now.getMonth() + 1).padStart(2, '0')}-${String(now.getDate()).padStart(2, '0')}`;
|
|
518
|
+
}
|
|
519
|
+
}
|
|
520
|
+
|
|
521
|
+
module.exports = Simulacion;
|