@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/cert/SetGuia.js
ADDED
|
@@ -0,0 +1,283 @@
|
|
|
1
|
+
// Copyright (c) 2026 Devlas SpA — https://devlas.cl
|
|
2
|
+
// Licencia MIT. Ver archivo LICENSE para mas detalles.
|
|
3
|
+
/**
|
|
4
|
+
* Set Guía de Despacho
|
|
5
|
+
*
|
|
6
|
+
* Tipo DTE: 52
|
|
7
|
+
*
|
|
8
|
+
* Campos específicos de Guía de Despacho:
|
|
9
|
+
* - IndTraslado: 1=Venta, 2=Consignación, 3=Entrega gratuita,
|
|
10
|
+
* 4=Comprobante, 5=Traslado interno, 6=Devolución
|
|
11
|
+
* - TpoDespacho: 1=Por cuenta del cliente, 2=Por cuenta del emisor
|
|
12
|
+
*
|
|
13
|
+
* @module dte-sii/cert/SetGuia
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
const SetBase = require('./SetBase');
|
|
17
|
+
const { SET_LABELS, SETS_DTE } = require('./types');
|
|
18
|
+
|
|
19
|
+
class SetGuia extends SetBase {
|
|
20
|
+
constructor(deps) {
|
|
21
|
+
super(deps);
|
|
22
|
+
|
|
23
|
+
this.key = 'guia';
|
|
24
|
+
this.label = SET_LABELS.guia;
|
|
25
|
+
this.tiposDte = SETS_DTE.guia; // [52]
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* @override
|
|
30
|
+
* Valida casos específicos del set guía
|
|
31
|
+
*/
|
|
32
|
+
_validarCasos(casos) {
|
|
33
|
+
super._validarCasos(casos);
|
|
34
|
+
|
|
35
|
+
if (!casos.casos?.length) {
|
|
36
|
+
throw new Error('SetGuia: casos es requerido');
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* @override
|
|
42
|
+
* Calcula folios necesarios por tipo
|
|
43
|
+
*/
|
|
44
|
+
_calcularCantidadFolios(casos, tipoDte) {
|
|
45
|
+
if (casos.cafRequired?.[tipoDte]) {
|
|
46
|
+
return casos.cafRequired[tipoDte];
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
// Solo tipo 52
|
|
50
|
+
if (tipoDte === 52) {
|
|
51
|
+
return casos.casos?.length || 1;
|
|
52
|
+
}
|
|
53
|
+
return 1;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* @override
|
|
58
|
+
* Genera DTEs del set guía de despacho
|
|
59
|
+
*
|
|
60
|
+
* @param {Object} casos - { casos, cafRequired }
|
|
61
|
+
* @param {Object} cafs - { 52: cafPath }
|
|
62
|
+
* @returns {Promise<DTE[]>}
|
|
63
|
+
*/
|
|
64
|
+
async generarDtes(casos, cafs) {
|
|
65
|
+
const dtes = [];
|
|
66
|
+
|
|
67
|
+
this.logger.log(' 📄 Generando guías de despacho...');
|
|
68
|
+
for (const caso of casos.casos || []) {
|
|
69
|
+
const dte = await this._generarGuia(caso, cafs[52]);
|
|
70
|
+
dtes.push(dte);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
return dtes;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Genera una guía de despacho (tipo 52)
|
|
78
|
+
* @private
|
|
79
|
+
*/
|
|
80
|
+
async _generarGuia(caso, cafPath) {
|
|
81
|
+
const { DTE, CAF, buildDetalleGuia, calcularTotalesDesdeDetalle, buildSetReferencia } = require('../index');
|
|
82
|
+
const fs = require('fs');
|
|
83
|
+
|
|
84
|
+
// Cargar CAF
|
|
85
|
+
const cafXml = fs.readFileSync(cafPath, 'utf8');
|
|
86
|
+
const caf = new CAF(cafXml);
|
|
87
|
+
|
|
88
|
+
// Reservar folio
|
|
89
|
+
const folio = this._reservarFolio(caf, cafXml);
|
|
90
|
+
|
|
91
|
+
// Construir detalle (buildDetalleGuia siempre incluye QtyItem)
|
|
92
|
+
const items = this._normalizarItems(caso.items);
|
|
93
|
+
const detalle = buildDetalleGuia(items);
|
|
94
|
+
|
|
95
|
+
// Calcular totales:
|
|
96
|
+
// - Para IndTraslado 5 (traslado interno sin valor): TasaIVA: 19, MntTotal: 0
|
|
97
|
+
// - Para otros casos: calcular desde el detalle
|
|
98
|
+
const totales = caso.indTraslado === 5
|
|
99
|
+
? { TasaIVA: 19, MntTotal: 0 }
|
|
100
|
+
: calcularTotalesDesdeDetalle(detalle, { preciosNetos: true });
|
|
101
|
+
|
|
102
|
+
// Referencia del set de pruebas
|
|
103
|
+
const fechaEmision = this._getFechaEmision();
|
|
104
|
+
const setReferencia = buildSetReferencia(caso.id, fechaEmision);
|
|
105
|
+
|
|
106
|
+
// Construir IdDoc con campos específicos de guía
|
|
107
|
+
// Orden XSD: TipoDTE, Folio, FchEmis, ..., TipoDespacho, IndTraslado
|
|
108
|
+
const idDoc = {
|
|
109
|
+
TipoDTE: 52,
|
|
110
|
+
Folio: folio,
|
|
111
|
+
FchEmis: fechaEmision,
|
|
112
|
+
};
|
|
113
|
+
|
|
114
|
+
// TipoDespacho va ANTES de IndTraslado según XSD
|
|
115
|
+
if (caso.tpoDespacho) {
|
|
116
|
+
idDoc.TipoDespacho = caso.tpoDespacho;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
idDoc.IndTraslado = caso.indTraslado || 1; // Default: Venta
|
|
120
|
+
|
|
121
|
+
// Para IndTraslado 5 (traslado interno), el receptor es el mismo emisor
|
|
122
|
+
const receptor = this._shouldUseEmisorAsReceptor(caso)
|
|
123
|
+
? this._buildEmisorAsReceptor()
|
|
124
|
+
: this._buildReceptor();
|
|
125
|
+
|
|
126
|
+
// Construir DTE
|
|
127
|
+
const dteDatos = {
|
|
128
|
+
Encabezado: {
|
|
129
|
+
IdDoc: idDoc,
|
|
130
|
+
Emisor: this._buildEmisor(),
|
|
131
|
+
Receptor: receptor,
|
|
132
|
+
Totales: totales,
|
|
133
|
+
},
|
|
134
|
+
Detalle: detalle,
|
|
135
|
+
Referencia: [setReferencia],
|
|
136
|
+
};
|
|
137
|
+
|
|
138
|
+
const dte = new DTE(dteDatos);
|
|
139
|
+
this._timbrarYFirmar(dte, caf);
|
|
140
|
+
|
|
141
|
+
this.logger.log(` ✓ Guía caso ${caso.id}: folio ${folio} (IndTraslado: ${idDoc.IndTraslado})`);
|
|
142
|
+
return dte;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
// ─────────────────────────────────────────────────────────────────
|
|
146
|
+
// Helpers internos
|
|
147
|
+
// ─────────────────────────────────────────────────────────────────
|
|
148
|
+
|
|
149
|
+
/**
|
|
150
|
+
* Normaliza items para guías de despacho
|
|
151
|
+
* Las guías pueden tener items con monto: 0 (traslados internos)
|
|
152
|
+
* @private
|
|
153
|
+
*/
|
|
154
|
+
_normalizarItems(items) {
|
|
155
|
+
return (items || []).map(item => {
|
|
156
|
+
// Preservar monto si existe (para traslados internos sin precio)
|
|
157
|
+
if (item.monto !== undefined && item.precio === undefined) {
|
|
158
|
+
return {
|
|
159
|
+
nombre: item.nombre,
|
|
160
|
+
cantidad: item.cantidad || 1,
|
|
161
|
+
monto: item.monto, // Preservar monto original
|
|
162
|
+
unidad: item.unidad || 'UN',
|
|
163
|
+
// No incluir precio - buildDetalleGuia usará monto directamente
|
|
164
|
+
};
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
return {
|
|
168
|
+
nombre: item.nombre,
|
|
169
|
+
cantidad: item.cantidad || 1,
|
|
170
|
+
precio: item.precio || 0,
|
|
171
|
+
unidad: item.unidad || 'UN',
|
|
172
|
+
...(item.exento ? { exento: true } : {}),
|
|
173
|
+
};
|
|
174
|
+
});
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
/**
|
|
178
|
+
* Reserva el siguiente folio disponible
|
|
179
|
+
* @private
|
|
180
|
+
*/
|
|
181
|
+
_reservarFolio(caf, cafXml) {
|
|
182
|
+
const cafFingerprint = this.folioHelper.createCafFingerprint(cafXml);
|
|
183
|
+
const folio = this.folioHelper.reserveNextFolio({
|
|
184
|
+
rutEmisor: this.config.emisor.rut,
|
|
185
|
+
tipoDte: caf.getTipoDTE(),
|
|
186
|
+
folioDesde: caf.getFolioDesde(),
|
|
187
|
+
folioHasta: caf.getFolioHasta(),
|
|
188
|
+
ambiente: this.config.ambiente || 'certificacion',
|
|
189
|
+
cafFingerprint,
|
|
190
|
+
});
|
|
191
|
+
return folio;
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
/**
|
|
195
|
+
* Obtiene la fecha de emisión en formato YYYY-MM-DD
|
|
196
|
+
* @private
|
|
197
|
+
*/
|
|
198
|
+
_getFechaEmision() {
|
|
199
|
+
if (this._fechaEmision) return this._fechaEmision;
|
|
200
|
+
const now = new Date();
|
|
201
|
+
this._fechaEmision = now.toISOString().split('T')[0];
|
|
202
|
+
return this._fechaEmision;
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
/**
|
|
206
|
+
* Construye datos del emisor
|
|
207
|
+
* @private
|
|
208
|
+
*/
|
|
209
|
+
_buildEmisor() {
|
|
210
|
+
const e = this.config.emisor;
|
|
211
|
+
return {
|
|
212
|
+
RUTEmisor: e.rut,
|
|
213
|
+
RznSoc: e.razon_social,
|
|
214
|
+
GiroEmis: e.giro,
|
|
215
|
+
Acteco: e.acteco,
|
|
216
|
+
DirOrigen: e.direccion,
|
|
217
|
+
CmnaOrigen: e.comuna,
|
|
218
|
+
CiudadOrigen: e.ciudad || e.comuna || 'Santiago',
|
|
219
|
+
};
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
/**
|
|
223
|
+
* Construye datos del receptor
|
|
224
|
+
* @private
|
|
225
|
+
*/
|
|
226
|
+
_buildReceptor() {
|
|
227
|
+
const r = this.config.receptor;
|
|
228
|
+
return {
|
|
229
|
+
RUTRecep: r.rut,
|
|
230
|
+
RznSocRecep: r.razon_social,
|
|
231
|
+
GiroRecep: r.giro,
|
|
232
|
+
DirRecep: r.direccion,
|
|
233
|
+
CmnaRecep: r.comuna,
|
|
234
|
+
CiudadRecep: r.ciudad || r.comuna,
|
|
235
|
+
};
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
/**
|
|
239
|
+
* Determina si debe usar el emisor como receptor (traslado interno)
|
|
240
|
+
* @private
|
|
241
|
+
*/
|
|
242
|
+
_shouldUseEmisorAsReceptor(caso) {
|
|
243
|
+
return Number(caso?.indTraslado) === 5;
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
/**
|
|
247
|
+
* Construye datos del emisor como receptor (para traslados internos)
|
|
248
|
+
* @private
|
|
249
|
+
*/
|
|
250
|
+
_buildEmisorAsReceptor() {
|
|
251
|
+
const e = this.config.emisor;
|
|
252
|
+
return {
|
|
253
|
+
RUTRecep: e.rut,
|
|
254
|
+
RznSocRecep: e.razon_social,
|
|
255
|
+
GiroRecep: e.giro,
|
|
256
|
+
DirRecep: e.direccion,
|
|
257
|
+
CmnaRecep: e.comuna,
|
|
258
|
+
CiudadRecep: e.ciudad || e.comuna || 'Santiago',
|
|
259
|
+
};
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
/**
|
|
263
|
+
* Timbra y firma el DTE
|
|
264
|
+
* @private
|
|
265
|
+
*/
|
|
266
|
+
_timbrarYFirmar(dte, caf) {
|
|
267
|
+
const { Certificado } = require('../index');
|
|
268
|
+
const fs = require('fs');
|
|
269
|
+
|
|
270
|
+
// Cargar certificado
|
|
271
|
+
const pfxBuffer = fs.readFileSync(this.config.certificado.path);
|
|
272
|
+
const cert = new Certificado(pfxBuffer, this.config.certificado.password);
|
|
273
|
+
|
|
274
|
+
// Timestamp
|
|
275
|
+
const timestamp = new Date().toISOString().replace('Z', '');
|
|
276
|
+
|
|
277
|
+
// Generar, timbrar y firmar
|
|
278
|
+
dte.generarXML().timbrar(caf, timestamp);
|
|
279
|
+
dte.firmar(cert);
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
module.exports = SetGuia;
|