@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.
Files changed (60) hide show
  1. package/BoletaService.js +109 -0
  2. package/CAF.js +173 -0
  3. package/CafSolicitor.js +380 -0
  4. package/Certificado.js +123 -0
  5. package/ConsumoFolio.js +376 -0
  6. package/DTE.js +399 -0
  7. package/EnviadorSII.js +1304 -0
  8. package/Envio.js +196 -0
  9. package/FolioRegistry.js +553 -0
  10. package/FolioService.js +703 -0
  11. package/LICENSE +27 -0
  12. package/LibroBase.js +134 -0
  13. package/LibroCompraVenta.js +205 -0
  14. package/LibroGuia.js +225 -0
  15. package/README.md +239 -0
  16. package/Signer.js +94 -0
  17. package/SiiCertificacion.js +1189 -0
  18. package/SiiPortalAuth.js +460 -0
  19. package/SiiSession.js +499 -0
  20. package/cert/BoletaCert.js +731 -0
  21. package/cert/CertFolioHelper.js +185 -0
  22. package/cert/CertRunner.js +2658 -0
  23. package/cert/ConfigLoader.js +133 -0
  24. package/cert/IntercambioCert.js +429 -0
  25. package/cert/LibroCompras.js +359 -0
  26. package/cert/LibroGuias.js +171 -0
  27. package/cert/LibroVentas.js +153 -0
  28. package/cert/MuestrasImpresas.js +676 -0
  29. package/cert/SetBase.js +321 -0
  30. package/cert/SetBasico.js +413 -0
  31. package/cert/SetCompra.js +472 -0
  32. package/cert/SetExenta.js +490 -0
  33. package/cert/SetGuia.js +283 -0
  34. package/cert/SetParser.js +1184 -0
  35. package/cert/SetsProvider.js +499 -0
  36. package/cert/Simulacion.js +521 -0
  37. package/cert/comunaOficina.js +460 -0
  38. package/cert/index.js +124 -0
  39. package/cert/types.js +330 -0
  40. package/dte-sii.d.ts +458 -0
  41. package/index.js +428 -0
  42. package/package.json +48 -0
  43. package/utils/c14n.js +275 -0
  44. package/utils/calculo.js +396 -0
  45. package/utils/config.js +276 -0
  46. package/utils/constants.js +302 -0
  47. package/utils/emisor.js +174 -0
  48. package/utils/endpoints.js +225 -0
  49. package/utils/error.js +235 -0
  50. package/utils/index.js +339 -0
  51. package/utils/logger.js +239 -0
  52. package/utils/pfx.js +203 -0
  53. package/utils/receptor.js +218 -0
  54. package/utils/referencia.js +169 -0
  55. package/utils/resolucion.js +119 -0
  56. package/utils/rut.js +169 -0
  57. package/utils/sanitize.js +124 -0
  58. package/utils/tokenCache.js +214 -0
  59. package/utils/xml.js +358 -0
  60. package/utils.js +4 -0
@@ -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;