@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,174 @@
1
+ // Copyright (c) 2026 Devlas SpA — https://devlas.cl
2
+ // Licencia MIT. Ver archivo LICENSE para mas detalles.
3
+ /**
4
+ * Utilidades de Emisor
5
+ *
6
+ * Funciones para construir y validar datos de emisor en DTEs
7
+ *
8
+ * @module dte-sii/utils/emisor
9
+ */
10
+
11
+ const { sanitizeSiiText } = require('./sanitize');
12
+ const { formatRutSii, validarRut } = require('./rut');
13
+
14
+ // ============================================
15
+ // CONSTRUCCIÓN DE EMISOR
16
+ // ============================================
17
+
18
+ /**
19
+ * Construye objeto de Emisor para DTE (no boleta)
20
+ *
21
+ * @param {Object} config - Configuración del emisor
22
+ * @param {string} config.rut - RUT del emisor
23
+ * @param {string} config.razonSocial - Razón social
24
+ * @param {string} config.giro - Giro comercial
25
+ * @param {string} config.direccion - Dirección
26
+ * @param {string} config.comuna - Comuna
27
+ * @param {string} [config.ciudad] - Ciudad (default: comuna)
28
+ * @param {string} [config.telefono] - Teléfono
29
+ * @param {string} [config.correo] - Correo electrónico
30
+ * @param {number|string} [config.acteco] - Código de actividad económica
31
+ * @returns {Object} Emisor formateado para DTE
32
+ */
33
+ function buildEmisor(config) {
34
+ const {
35
+ rut,
36
+ razonSocial,
37
+ giro,
38
+ direccion,
39
+ comuna,
40
+ ciudad,
41
+ telefono,
42
+ correo,
43
+ acteco,
44
+ } = config;
45
+
46
+ const emisor = {
47
+ RUTEmisor: formatRutSii(rut),
48
+ RznSoc: sanitizeSiiText(razonSocial),
49
+ GiroEmis: sanitizeSiiText(giro),
50
+ };
51
+
52
+ if (acteco) emisor.Acteco = acteco;
53
+ if (telefono) emisor.Telefono = telefono;
54
+ if (correo) emisor.CorreoEmisor = correo;
55
+
56
+ emisor.DirOrigen = sanitizeSiiText(direccion);
57
+ emisor.CmnaOrigen = comuna;
58
+ emisor.CiudadOrigen = ciudad || comuna || '';
59
+
60
+ return emisor;
61
+ }
62
+
63
+ /**
64
+ * Construye objeto de Emisor para Boleta
65
+ *
66
+ * @param {Object} config - Configuración del emisor
67
+ * @param {string} config.rut - RUT del emisor
68
+ * @param {string} config.razonSocial - Razón social
69
+ * @param {string} config.giro - Giro comercial
70
+ * @param {string} config.direccion - Dirección
71
+ * @param {string} config.comuna - Comuna
72
+ * @param {string} [config.ciudad] - Ciudad
73
+ * @returns {Object} Emisor formateado para Boleta
74
+ */
75
+ function buildEmisorBoleta(config) {
76
+ const { rut, razonSocial, giro, direccion, comuna, ciudad } = config;
77
+
78
+ return {
79
+ RUTEmisor: formatRutSii(rut),
80
+ RznSocEmisor: sanitizeSiiText(razonSocial),
81
+ GiroEmisor: sanitizeSiiText(giro),
82
+ DirOrigen: sanitizeSiiText(direccion),
83
+ CmnaOrigen: comuna,
84
+ CiudadOrigen: ciudad || comuna || '',
85
+ };
86
+ }
87
+
88
+ /**
89
+ * Normaliza emisor desde diferentes formatos de entrada
90
+ * Detecta si viene en formato boleta o DTE y normaliza
91
+ *
92
+ * @param {Object} emisor - Emisor en cualquier formato
93
+ * @param {boolean} [esBoleta=false] - Si es formato boleta
94
+ * @returns {Object} Emisor normalizado
95
+ */
96
+ function normalizeEmisor(emisor, esBoleta = false) {
97
+ if (esBoleta) {
98
+ return {
99
+ RUTEmisor: emisor.RUTEmisor,
100
+ RznSocEmisor: sanitizeSiiText(emisor.RznSocEmisor || emisor.RznSoc),
101
+ GiroEmisor: sanitizeSiiText(emisor.GiroEmisor || emisor.GiroEmis),
102
+ DirOrigen: sanitizeSiiText(emisor.DirOrigen),
103
+ CmnaOrigen: emisor.CmnaOrigen,
104
+ CiudadOrigen: emisor.CiudadOrigen || emisor.CmnaOrigen || '',
105
+ };
106
+ }
107
+
108
+ const result = {
109
+ RUTEmisor: emisor.RUTEmisor,
110
+ RznSoc: sanitizeSiiText(emisor.RznSoc || emisor.RznSocEmisor),
111
+ GiroEmis: sanitizeSiiText(emisor.GiroEmis || emisor.GiroEmisor),
112
+ };
113
+
114
+ if (emisor.Acteco) result.Acteco = emisor.Acteco;
115
+ if (emisor.Telefono) result.Telefono = emisor.Telefono;
116
+ if (emisor.CorreoEmisor) result.CorreoEmisor = emisor.CorreoEmisor;
117
+
118
+ result.DirOrigen = sanitizeSiiText(emisor.DirOrigen);
119
+ result.CmnaOrigen = emisor.CmnaOrigen;
120
+ result.CiudadOrigen = emisor.CiudadOrigen || emisor.CmnaOrigen || '';
121
+
122
+ return result;
123
+ }
124
+
125
+ /**
126
+ * Valida datos de emisor
127
+ *
128
+ * @param {Object} emisor - Datos del emisor
129
+ * @returns {{ valid: boolean, errors: string[] }}
130
+ */
131
+ function validarEmisor(emisor) {
132
+ const errors = [];
133
+
134
+ if (!emisor) {
135
+ return { valid: false, errors: ['Emisor no proporcionado'] };
136
+ }
137
+
138
+ if (!emisor.RUTEmisor) {
139
+ errors.push('RUT de emisor requerido');
140
+ } else if (!validarRut(emisor.RUTEmisor)) {
141
+ errors.push('RUT de emisor inválido');
142
+ }
143
+
144
+ const razonSocial = emisor.RznSoc || emisor.RznSocEmisor;
145
+ if (!razonSocial) {
146
+ errors.push('Razón social requerida');
147
+ }
148
+
149
+ const giro = emisor.GiroEmis || emisor.GiroEmisor;
150
+ if (!giro) {
151
+ errors.push('Giro requerido');
152
+ }
153
+
154
+ if (!emisor.DirOrigen) {
155
+ errors.push('Dirección de origen requerida');
156
+ }
157
+
158
+ if (!emisor.CmnaOrigen) {
159
+ errors.push('Comuna de origen requerida');
160
+ }
161
+
162
+ return { valid: errors.length === 0, errors };
163
+ }
164
+
165
+ // ============================================
166
+ // EXPORTS
167
+ // ============================================
168
+
169
+ module.exports = {
170
+ buildEmisor,
171
+ buildEmisorBoleta,
172
+ normalizeEmisor,
173
+ validarEmisor,
174
+ };
@@ -0,0 +1,225 @@
1
+ // Copyright (c) 2026 Devlas SpA — https://devlas.cl
2
+ // Licencia MIT. Ver archivo LICENSE para mas detalles.
3
+ /**
4
+ * endpoints.js - URLs centralizadas del SII
5
+ *
6
+ * Centraliza todas las URLs de los servicios del SII para evitar
7
+ * duplicación y facilitar el mantenimiento.
8
+ *
9
+ * @module utils/endpoints
10
+ */
11
+
12
+ /**
13
+ * Hosts base por ambiente
14
+ */
15
+ const HOSTS = {
16
+ certificacion: 'maullin.sii.cl',
17
+ produccion: 'palena.sii.cl',
18
+ };
19
+
20
+ /**
21
+ * Hosts para API REST de Boletas
22
+ */
23
+ const REST_HOSTS = {
24
+ certificacion: {
25
+ api: 'apicert.sii.cl',
26
+ envio: 'pangal.sii.cl',
27
+ },
28
+ produccion: {
29
+ api: 'api.sii.cl',
30
+ envio: 'rahue.sii.cl',
31
+ },
32
+ };
33
+
34
+ /**
35
+ * Endpoints SOAP para DTEUpload
36
+ */
37
+ const SOAP_ENDPOINTS = {
38
+ certificacion: {
39
+ seed: 'https://maullin.sii.cl/DTEWS/CrSeed.jws',
40
+ token: 'https://maullin.sii.cl/DTEWS/GetTokenFromSeed.jws',
41
+ upload: 'https://maullin.sii.cl/cgi_dte/UPL/DTEUpload',
42
+ estado: 'https://maullin.sii.cl/DTEWS/QueryEstUp.jws',
43
+ estadoDte: 'https://maullin.sii.cl/DTEWS/QueryEstDte.jws',
44
+ },
45
+ produccion: {
46
+ seed: 'https://palena.sii.cl/DTEWS/CrSeed.jws',
47
+ token: 'https://palena.sii.cl/DTEWS/GetTokenFromSeed.jws',
48
+ upload: 'https://palena.sii.cl/cgi_dte/UPL/DTEUpload',
49
+ estado: 'https://palena.sii.cl/DTEWS/QueryEstUp.jws',
50
+ estadoDte: 'https://palena.sii.cl/DTEWS/QueryEstDte.jws',
51
+ },
52
+ };
53
+
54
+ /**
55
+ * Endpoints REST para Boletas Electrónicas
56
+ */
57
+ const REST_ENDPOINTS = {
58
+ certificacion: {
59
+ semilla: 'https://apicert.sii.cl/recursos/v1/boleta.electronica.semilla',
60
+ token: 'https://apicert.sii.cl/recursos/v1/boleta.electronica.token',
61
+ envio: 'https://pangal.sii.cl/recursos/v1/boleta.electronica.envio',
62
+ estado: 'https://apicert.sii.cl/recursos/v1/boleta.electronica.envio/',
63
+ },
64
+ produccion: {
65
+ semilla: 'https://api.sii.cl/recursos/v1/boleta.electronica.semilla',
66
+ token: 'https://api.sii.cl/recursos/v1/boleta.electronica.token',
67
+ envio: 'https://rahue.sii.cl/recursos/v1/boleta.electronica.envio',
68
+ estado: 'https://api.sii.cl/recursos/v1/boleta.electronica.envio/',
69
+ },
70
+ };
71
+
72
+ /**
73
+ * Endpoints de Certificación (portal pe_*)
74
+ */
75
+ const CERT_ENDPOINTS = {
76
+ // Generación de sets
77
+ generar: '/cvc_cgi/dte/pe_generar',
78
+ generar1: '/cvc_cgi/dte/pe_generar1',
79
+
80
+ // Avance de certificación
81
+ avance1: '/cvc_cgi/dte/pe_avance1',
82
+ avance2: '/cvc_cgi/dte/pe_avance2',
83
+ avance5: '/cvc_cgi/dte/pe_avance5',
84
+ avance7: '/cvc_cgi/dte/pe_avance7',
85
+
86
+ // CAF
87
+ solicitarFolios: '/cvc_cgi/dte/of_solicita_folios',
88
+ estadoFolios: '/cgi_dte/UPL/DTEauth?2',
89
+ anularFolios: '/cvc_cgi/dte/of_anular_caf',
90
+
91
+ // Boletas Electrónicas
92
+ validarBoletaEnvio: '/cgi_dte/UPL/DTEauth?3',
93
+
94
+ // Autenticación
95
+ ingresoRut: '/cvc_cgi/dte/pe_ingrut',
96
+ enrolaUsuarios: '/cvc_cgi/dte/eu_enrola_usuarios',
97
+ construccionDte: '/cvc_cgi/dte/pe_construccion_dte',
98
+ };
99
+
100
+ /**
101
+ * Obtener host base para un ambiente
102
+ * @param {string} ambiente - 'certificacion' o 'produccion'
103
+ * @returns {string} Host base (ej: 'maullin.sii.cl')
104
+ */
105
+ function getHost(ambiente) {
106
+ const amb = String(ambiente).toLowerCase();
107
+ if (!HOSTS[amb]) {
108
+ throw new Error(`Ambiente inválido: ${ambiente}`);
109
+ }
110
+ return HOSTS[amb];
111
+ }
112
+
113
+ /**
114
+ * Obtener URL completa para endpoint SOAP
115
+ * @param {string} ambiente - 'certificacion' o 'produccion'
116
+ * @param {string} endpoint - Nombre del endpoint (seed, token, upload, estado)
117
+ * @returns {string} URL completa
118
+ */
119
+ function getSoapUrl(ambiente, endpoint) {
120
+ const amb = String(ambiente).toLowerCase();
121
+ const urls = SOAP_ENDPOINTS[amb];
122
+ if (!urls) {
123
+ throw new Error(`Ambiente inválido: ${ambiente}`);
124
+ }
125
+ if (!urls[endpoint]) {
126
+ throw new Error(`Endpoint SOAP inválido: ${endpoint}`);
127
+ }
128
+ return urls[endpoint];
129
+ }
130
+
131
+ /**
132
+ * Obtener URL completa para endpoint REST (Boletas)
133
+ * @param {string} ambiente - 'certificacion' o 'produccion'
134
+ * @param {string} endpoint - Nombre del endpoint (semilla, token, envio, estado)
135
+ * @returns {string} URL completa
136
+ */
137
+ function getRestUrl(ambiente, endpoint) {
138
+ const amb = String(ambiente).toLowerCase();
139
+ const urls = REST_ENDPOINTS[amb];
140
+ if (!urls) {
141
+ throw new Error(`Ambiente inválido: ${ambiente}`);
142
+ }
143
+ if (!urls[endpoint]) {
144
+ throw new Error(`Endpoint REST inválido: ${endpoint}`);
145
+ }
146
+ return urls[endpoint];
147
+ }
148
+
149
+ /**
150
+ * Obtener URL completa para endpoint de certificación
151
+ * @param {string} ambiente - 'certificacion' o 'produccion'
152
+ * @param {string} endpoint - Nombre del endpoint (generar, avance1, etc)
153
+ * @returns {string} URL completa
154
+ */
155
+ function getCertUrl(ambiente, endpoint) {
156
+ const host = getHost(ambiente);
157
+ const path = CERT_ENDPOINTS[endpoint];
158
+ if (!path) {
159
+ throw new Error(`Endpoint de certificación inválido: ${endpoint}`);
160
+ }
161
+ return `https://${host}${path}`;
162
+ }
163
+
164
+ /**
165
+ * Obtener path relativo de certificación
166
+ * @param {string} endpoint - Nombre del endpoint
167
+ * @returns {string} Path relativo
168
+ */
169
+ function getCertPath(endpoint) {
170
+ const path = CERT_ENDPOINTS[endpoint];
171
+ if (!path) {
172
+ throw new Error(`Endpoint de certificación inválido: ${endpoint}`);
173
+ }
174
+ return path;
175
+ }
176
+
177
+ /**
178
+ * Obtener todas las URLs para un ambiente
179
+ * @param {string} ambiente - 'certificacion' o 'produccion'
180
+ * @returns {Object} Objeto con todas las URLs
181
+ */
182
+ function getAllUrls(ambiente) {
183
+ const amb = String(ambiente).toLowerCase();
184
+ return {
185
+ host: HOSTS[amb],
186
+ soap: SOAP_ENDPOINTS[amb],
187
+ rest: REST_ENDPOINTS[amb],
188
+ cert: Object.entries(CERT_ENDPOINTS).reduce((acc, [key, path]) => {
189
+ acc[key] = `https://${HOSTS[amb]}${path}`;
190
+ return acc;
191
+ }, {}),
192
+ };
193
+ }
194
+
195
+ /**
196
+ * Validar ambiente
197
+ * @param {string} ambiente - Ambiente a validar
198
+ * @returns {string} Ambiente normalizado ('certificacion' o 'produccion')
199
+ * @throws {Error} Si el ambiente es inválido
200
+ */
201
+ function validateAmbiente(ambiente) {
202
+ const amb = String(ambiente).toLowerCase();
203
+ if (!['certificacion', 'produccion'].includes(amb)) {
204
+ throw new Error(`Ambiente inválido: "${ambiente}", debe ser 'certificacion' o 'produccion'`);
205
+ }
206
+ return amb;
207
+ }
208
+
209
+ module.exports = {
210
+ // Constantes
211
+ HOSTS,
212
+ REST_HOSTS,
213
+ SOAP_ENDPOINTS,
214
+ REST_ENDPOINTS,
215
+ CERT_ENDPOINTS,
216
+
217
+ // Funciones
218
+ getHost,
219
+ getSoapUrl,
220
+ getRestUrl,
221
+ getCertUrl,
222
+ getCertPath,
223
+ getAllUrls,
224
+ validateAmbiente,
225
+ };
package/utils/error.js ADDED
@@ -0,0 +1,235 @@
1
+ // Copyright (c) 2026 Devlas SpA — https://devlas.cl
2
+ // Licencia MIT. Ver archivo LICENSE para mas detalles.
3
+ /**
4
+ * Clase de Error Personalizada
5
+ *
6
+ * Errores tipados para mejor manejo y debugging
7
+ *
8
+ * @module dte-sii/utils/error
9
+ */
10
+
11
+ // ============================================
12
+ // CÓDIGOS DE ERROR
13
+ // ============================================
14
+
15
+ const ERROR_CODES = {
16
+ // Configuración
17
+ CONFIG_INVALID: 'CONFIG_INVALID',
18
+ CONFIG_MISSING: 'CONFIG_MISSING',
19
+
20
+ // Certificado
21
+ CERT_NOT_FOUND: 'CERT_NOT_FOUND',
22
+ CERT_INVALID: 'CERT_INVALID',
23
+ CERT_EXPIRED: 'CERT_EXPIRED',
24
+ CERT_PASSWORD_WRONG: 'CERT_PASSWORD_WRONG',
25
+
26
+ // CAF / Folios
27
+ CAF_NOT_FOUND: 'CAF_NOT_FOUND',
28
+ CAF_INVALID: 'CAF_INVALID',
29
+ CAF_EXPIRED: 'CAF_EXPIRED',
30
+ CAF_NO_FOLIOS: 'CAF_NO_FOLIOS',
31
+ FOLIO_OUT_OF_RANGE: 'FOLIO_OUT_OF_RANGE',
32
+
33
+ // DTE
34
+ DTE_INVALID: 'DTE_INVALID',
35
+ DTE_MISSING_FIELDS: 'DTE_MISSING_FIELDS',
36
+ DTE_VALIDATION_FAILED: 'DTE_VALIDATION_FAILED',
37
+
38
+ // Firma
39
+ SIGN_FAILED: 'SIGN_FAILED',
40
+ SIGN_VERIFY_FAILED: 'SIGN_VERIFY_FAILED',
41
+
42
+ // SII
43
+ SII_CONNECTION_FAILED: 'SII_CONNECTION_FAILED',
44
+ SII_AUTH_FAILED: 'SII_AUTH_FAILED',
45
+ SII_REJECTED: 'SII_REJECTED',
46
+ SII_TIMEOUT: 'SII_TIMEOUT',
47
+ SII_INVALID_RESPONSE: 'SII_INVALID_RESPONSE',
48
+
49
+ // XML
50
+ XML_PARSE_FAILED: 'XML_PARSE_FAILED',
51
+ XML_BUILD_FAILED: 'XML_BUILD_FAILED',
52
+
53
+ // General
54
+ UNKNOWN: 'UNKNOWN',
55
+ };
56
+
57
+ // ============================================
58
+ // CLASE DteSiiError
59
+ // ============================================
60
+
61
+ /**
62
+ * Error personalizado del paquete
63
+ */
64
+ class DteSiiError extends Error {
65
+ /**
66
+ * @param {string} message - Mensaje de error
67
+ * @param {string} [code] - Código de error (de ERROR_CODES)
68
+ * @param {Object} [details] - Detalles adicionales
69
+ */
70
+ constructor(message, code = ERROR_CODES.UNKNOWN, details = {}) {
71
+ super(message);
72
+ this.name = 'DteSiiError';
73
+ this.code = code;
74
+ this.details = details;
75
+ this.timestamp = new Date().toISOString();
76
+
77
+ // Mantener stack trace correcto
78
+ if (Error.captureStackTrace) {
79
+ Error.captureStackTrace(this, DteSiiError);
80
+ }
81
+ }
82
+
83
+ /**
84
+ * Convierte el error a objeto plano (para logging/serialización)
85
+ * @returns {Object}
86
+ */
87
+ toJSON() {
88
+ return {
89
+ name: this.name,
90
+ code: this.code,
91
+ message: this.message,
92
+ details: this.details,
93
+ timestamp: this.timestamp,
94
+ stack: this.stack,
95
+ };
96
+ }
97
+
98
+ /**
99
+ * Representación string del error
100
+ * @returns {string}
101
+ */
102
+ toString() {
103
+ return `[${this.code}] ${this.message}`;
104
+ }
105
+
106
+ /**
107
+ * Verifica si es un error de SII
108
+ * @returns {boolean}
109
+ */
110
+ isSiiError() {
111
+ return this.code.startsWith('SII_');
112
+ }
113
+
114
+ /**
115
+ * Verifica si es un error recuperable (se puede reintentar)
116
+ * @returns {boolean}
117
+ */
118
+ isRetryable() {
119
+ return [
120
+ ERROR_CODES.SII_CONNECTION_FAILED,
121
+ ERROR_CODES.SII_TIMEOUT,
122
+ ].includes(this.code);
123
+ }
124
+ }
125
+
126
+ // ============================================
127
+ // FUNCIONES HELPER
128
+ // ============================================
129
+
130
+ /**
131
+ * Crea error de configuración
132
+ *
133
+ * @param {string} message - Mensaje de error
134
+ * @param {Object} [details] - Detalles
135
+ * @returns {DteSiiError}
136
+ */
137
+ function configError(message, details = {}) {
138
+ return new DteSiiError(message, ERROR_CODES.CONFIG_INVALID, details);
139
+ }
140
+
141
+ /**
142
+ * Crea error de certificado
143
+ *
144
+ * @param {string} message - Mensaje de error
145
+ * @param {string} [code] - Código específico de certificado
146
+ * @param {Object} [details] - Detalles
147
+ * @returns {DteSiiError}
148
+ */
149
+ function certError(message, code = ERROR_CODES.CERT_INVALID, details = {}) {
150
+ return new DteSiiError(message, code, details);
151
+ }
152
+
153
+ /**
154
+ * Crea error de CAF/folios
155
+ *
156
+ * @param {string} message - Mensaje de error
157
+ * @param {string} [code] - Código específico de CAF
158
+ * @param {Object} [details] - Detalles
159
+ * @returns {DteSiiError}
160
+ */
161
+ function cafError(message, code = ERROR_CODES.CAF_INVALID, details = {}) {
162
+ return new DteSiiError(message, code, details);
163
+ }
164
+
165
+ /**
166
+ * Crea error de DTE
167
+ *
168
+ * @param {string} message - Mensaje de error
169
+ * @param {Object} [details] - Detalles
170
+ * @returns {DteSiiError}
171
+ */
172
+ function dteError(message, details = {}) {
173
+ return new DteSiiError(message, ERROR_CODES.DTE_INVALID, details);
174
+ }
175
+
176
+ /**
177
+ * Crea error de SII
178
+ *
179
+ * @param {string} message - Mensaje de error
180
+ * @param {string} [code] - Código específico de SII
181
+ * @param {Object} [details] - Detalles
182
+ * @returns {DteSiiError}
183
+ */
184
+ function siiError(message, code = ERROR_CODES.SII_REJECTED, details = {}) {
185
+ return new DteSiiError(message, code, details);
186
+ }
187
+
188
+ /**
189
+ * Crea error de XML
190
+ *
191
+ * @param {string} message - Mensaje de error
192
+ * @param {Object} [details] - Detalles
193
+ * @returns {DteSiiError}
194
+ */
195
+ function xmlError(message, details = {}) {
196
+ return new DteSiiError(message, ERROR_CODES.XML_PARSE_FAILED, details);
197
+ }
198
+
199
+ /**
200
+ * Envuelve un error nativo en DteSiiError
201
+ *
202
+ * @param {Error} error - Error original
203
+ * @param {string} [code] - Código a asignar
204
+ * @param {Object} [details] - Detalles adicionales
205
+ * @returns {DteSiiError}
206
+ */
207
+ function wrapError(error, code = ERROR_CODES.UNKNOWN, details = {}) {
208
+ if (error instanceof DteSiiError) {
209
+ return error;
210
+ }
211
+
212
+ return new DteSiiError(
213
+ error.message || String(error),
214
+ code,
215
+ { ...details, originalError: error.name, stack: error.stack }
216
+ );
217
+ }
218
+
219
+ // ============================================
220
+ // EXPORTS
221
+ // ============================================
222
+
223
+ module.exports = {
224
+ DteSiiError,
225
+ ERROR_CODES,
226
+
227
+ // Helpers
228
+ configError,
229
+ certError,
230
+ cafError,
231
+ dteError,
232
+ siiError,
233
+ xmlError,
234
+ wrapError,
235
+ };