@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,133 @@
|
|
|
1
|
+
// Copyright (c) 2026 Devlas SpA — https://devlas.cl
|
|
2
|
+
// Licencia MIT. Ver archivo LICENSE para mas detalles.
|
|
3
|
+
/**
|
|
4
|
+
* ConfigLoader - Carga configuración de certificación desde .env
|
|
5
|
+
*
|
|
6
|
+
* Centraliza la carga de configuración para que los runners (CLI)
|
|
7
|
+
* no necesiten módulos intermedios.
|
|
8
|
+
*
|
|
9
|
+
* @module dte-sii/cert/ConfigLoader
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
const path = require('path');
|
|
13
|
+
const fs = require('fs');
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Carga configuración desde .env y retorna objetos estructurados
|
|
17
|
+
*
|
|
18
|
+
* @param {Object} options
|
|
19
|
+
* @param {string} [options.envPath] - Ruta al archivo .env (por defecto busca en static/nodejs/.env)
|
|
20
|
+
* @param {string} [options.baseDir] - Directorio base para resolver rutas relativas
|
|
21
|
+
* @returns {Object} Configuración estructurada { EMISOR, RECEPTOR, CERT_PATH, CERT_PASS, AMBIENTE, BASE_DIR, ... }
|
|
22
|
+
*/
|
|
23
|
+
function loadConfig(options = {}) {
|
|
24
|
+
// Determinar baseDir
|
|
25
|
+
const baseDir = options.baseDir || path.resolve(__dirname, '..', '..', '..');
|
|
26
|
+
|
|
27
|
+
// Cargar .env
|
|
28
|
+
const envPath = options.envPath || path.join(baseDir, '.env');
|
|
29
|
+
|
|
30
|
+
if (!fs.existsSync(envPath)) {
|
|
31
|
+
throw new Error(`Archivo .env no encontrado: ${envPath}\nCopia .env.example a .env y configura tus valores.`);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
// Usar dotenv para cargar
|
|
35
|
+
require('dotenv').config({ path: envPath });
|
|
36
|
+
|
|
37
|
+
// Resolver rutas relativas
|
|
38
|
+
const resolvePath = (filePath) => {
|
|
39
|
+
if (!filePath) return null;
|
|
40
|
+
return path.isAbsolute(filePath) ? filePath : path.resolve(baseDir, filePath);
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
// Validar variables requeridas
|
|
44
|
+
const required = ['CERT_PATH', 'EMISOR_RUT', 'EMISOR_RAZON_SOCIAL'];
|
|
45
|
+
const missing = required.filter(v => !process.env[v]);
|
|
46
|
+
if (missing.length > 0) {
|
|
47
|
+
throw new Error(`Variables de entorno faltantes: ${missing.join(', ')}`);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
// Construir objetos de configuración
|
|
51
|
+
const AMBIENTE = process.env.SII_AMBIENTE || 'certificacion';
|
|
52
|
+
|
|
53
|
+
const fch_resol = process.env.FECHA_RESOLUCION || new Date().toISOString().slice(0, 10);
|
|
54
|
+
const nro_resol = parseInt(process.env.NRO_RESOLUCION || '0', 10);
|
|
55
|
+
|
|
56
|
+
console.log(`[ConfigLoader] ✅ Resolución: NroResol=${nro_resol} FchResol=${fch_resol} (AMBIENTE=${AMBIENTE})`);
|
|
57
|
+
|
|
58
|
+
const EMISOR = {
|
|
59
|
+
rut: process.env.EMISOR_RUT,
|
|
60
|
+
razon_social: process.env.EMISOR_RAZON_SOCIAL,
|
|
61
|
+
giro: process.env.EMISOR_GIRO || 'ACTIVIDADES DE PROGRAMACION INFORMATICA',
|
|
62
|
+
acteco: process.env.EMISOR_ACTECO || '620200',
|
|
63
|
+
direccion: process.env.EMISOR_DIRECCION || '',
|
|
64
|
+
comuna: process.env.EMISOR_COMUNA || 'Santiago',
|
|
65
|
+
ciudad: process.env.EMISOR_CIUDAD || 'Santiago',
|
|
66
|
+
fch_resol,
|
|
67
|
+
nro_resol,
|
|
68
|
+
};
|
|
69
|
+
|
|
70
|
+
const RECEPTOR = {
|
|
71
|
+
rut: process.env.RECEPTOR_RUT || '66666666-6',
|
|
72
|
+
razon_social: process.env.RECEPTOR_RAZON_SOCIAL || 'CLIENTE TEST',
|
|
73
|
+
giro: process.env.RECEPTOR_GIRO || 'ACTIVIDADES VARIAS',
|
|
74
|
+
direccion: process.env.RECEPTOR_DIRECCION || 'AVENIDA PRINCIPAL 123',
|
|
75
|
+
comuna: process.env.RECEPTOR_COMUNA || 'Santiago',
|
|
76
|
+
ciudad: process.env.RECEPTOR_CIUDAD || 'Santiago',
|
|
77
|
+
};
|
|
78
|
+
|
|
79
|
+
const CERT_PATH = resolvePath(process.env.CERT_PATH);
|
|
80
|
+
const CERT_PASS = process.env.CERT_PASS || '';
|
|
81
|
+
|
|
82
|
+
// Configuración SII
|
|
83
|
+
const SII_CONFIG = {
|
|
84
|
+
sendDelayMs: parseInt(process.env.SII_SEND_DELAY_MS || '8000', 10),
|
|
85
|
+
sendRetries: parseInt(process.env.SII_SEND_RETRIES || '6', 10),
|
|
86
|
+
cafRetries: parseInt(process.env.SII_CAF_RETRIES || '3', 10),
|
|
87
|
+
reuseFolios: process.env.SII_REUSE_FOLIOS === 'true',
|
|
88
|
+
};
|
|
89
|
+
|
|
90
|
+
// Enviador (quien firma/envía)
|
|
91
|
+
const ENVIADOR = {
|
|
92
|
+
rut: process.env.ENVIADOR_RUT || EMISOR.rut,
|
|
93
|
+
};
|
|
94
|
+
|
|
95
|
+
return {
|
|
96
|
+
EMISOR,
|
|
97
|
+
RECEPTOR,
|
|
98
|
+
CERT_PATH,
|
|
99
|
+
CERT_PASS,
|
|
100
|
+
AMBIENTE,
|
|
101
|
+
BASE_DIR: baseDir,
|
|
102
|
+
SII_CONFIG,
|
|
103
|
+
ENVIADOR,
|
|
104
|
+
|
|
105
|
+
// Helper para crear config de CertRunner
|
|
106
|
+
toCertRunnerConfig() {
|
|
107
|
+
return {
|
|
108
|
+
certificado: { path: CERT_PATH, password: CERT_PASS },
|
|
109
|
+
emisor: EMISOR,
|
|
110
|
+
receptor: RECEPTOR,
|
|
111
|
+
ambiente: AMBIENTE,
|
|
112
|
+
resolucion: { fecha: EMISOR.fch_resol, numero: EMISOR.nro_resol },
|
|
113
|
+
baseDir: baseDir,
|
|
114
|
+
debugDir: path.join(baseDir, 'debug', 'cert-v2'),
|
|
115
|
+
};
|
|
116
|
+
},
|
|
117
|
+
};
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
/**
|
|
121
|
+
* Imprime banner de sección para CLI
|
|
122
|
+
* @param {string} titulo
|
|
123
|
+
*/
|
|
124
|
+
function printBanner(titulo) {
|
|
125
|
+
console.log('\n' + '═'.repeat(60));
|
|
126
|
+
console.log(` ${titulo}`);
|
|
127
|
+
console.log('═'.repeat(60));
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
module.exports = {
|
|
131
|
+
loadConfig,
|
|
132
|
+
printBanner,
|
|
133
|
+
};
|
|
@@ -0,0 +1,429 @@
|
|
|
1
|
+
// Copyright (c) 2026 Devlas SpA — https://devlas.cl
|
|
2
|
+
// Licencia MIT. Ver archivo LICENSE para mas detalles.
|
|
3
|
+
/**
|
|
4
|
+
* IntercambioCert.js
|
|
5
|
+
*
|
|
6
|
+
* Módulo para generar respuestas de Intercambio de Información DTE
|
|
7
|
+
* - Respuesta Recepción de Envío
|
|
8
|
+
* - Respuesta Aprobación Comercial (ResultadoDTE)
|
|
9
|
+
* - Envío de Recibos (Recepción de Mercaderías)
|
|
10
|
+
*
|
|
11
|
+
* @module dte-sii/cert/IntercambioCert
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
const fs = require('fs');
|
|
15
|
+
const path = require('path');
|
|
16
|
+
const { XMLParser, XMLBuilder } = require('fast-xml-parser');
|
|
17
|
+
const { SignedXml } = require('xml-crypto');
|
|
18
|
+
|
|
19
|
+
const DECLARACION_RECIBO = 'El acuse de recibo que se declara en este acto, de acuerdo a lo dispuesto en la letra b) del Art. 4, y la letra c) del Art. 5 de la Ley 19.983, acredita que la entrega de mercaderias o servicio(s) prestado(s) ha(n) sido recibido(s).';
|
|
20
|
+
|
|
21
|
+
class IntercambioCert {
|
|
22
|
+
/**
|
|
23
|
+
* @param {Object} options
|
|
24
|
+
* @param {Object} options.certificado - Instancia de Certificado
|
|
25
|
+
* @param {Object} options.emisor - Datos del emisor { rut, razonSocial }
|
|
26
|
+
* @param {Object} [options.contacto] - Datos de contacto { nombre, mail, fono }
|
|
27
|
+
* @param {string} [options.debugDir] - Directorio para guardar XMLs
|
|
28
|
+
*/
|
|
29
|
+
constructor({ certificado, emisor, contacto = {}, debugDir }) {
|
|
30
|
+
this.certificado = certificado;
|
|
31
|
+
this.emisor = emisor;
|
|
32
|
+
this.contacto = contacto;
|
|
33
|
+
this.debugDir = debugDir;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Obtiene las utilidades del paquete
|
|
38
|
+
* @private
|
|
39
|
+
*/
|
|
40
|
+
_lib() {
|
|
41
|
+
const { formatRut, normalizeArray, sanitizeSiiText } = require('../index');
|
|
42
|
+
return { formatRut, normalizeArray, sanitizeSiiText };
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Formatea fecha/hora en formato SII
|
|
47
|
+
* @private
|
|
48
|
+
*/
|
|
49
|
+
_formatSiiDateTime(date = new Date()) {
|
|
50
|
+
const pad = (n) => String(n).padStart(2, '0');
|
|
51
|
+
return `${date.getFullYear()}-${pad(date.getMonth() + 1)}-${pad(date.getDate())}T${pad(date.getHours())}:${pad(date.getMinutes())}:${pad(date.getSeconds())}`;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Firma un XML con el certificado
|
|
56
|
+
* @private
|
|
57
|
+
*/
|
|
58
|
+
_signXml({ xml, referenceXpath, insertAfterXpath }) {
|
|
59
|
+
const sig = new SignedXml();
|
|
60
|
+
sig.privateKey = this.certificado.getPrivateKeyPem();
|
|
61
|
+
sig.publicCert = this.certificado.getCertificatePem();
|
|
62
|
+
sig.canonicalizationAlgorithm = 'http://www.w3.org/TR/2001/REC-xml-c14n-20010315';
|
|
63
|
+
sig.signatureAlgorithm = 'http://www.w3.org/2000/09/xmldsig#rsa-sha1';
|
|
64
|
+
sig.addReference({
|
|
65
|
+
xpath: referenceXpath,
|
|
66
|
+
transforms: ['http://www.w3.org/2000/09/xmldsig#enveloped-signature'],
|
|
67
|
+
digestAlgorithm: 'http://www.w3.org/2000/09/xmldsig#sha1',
|
|
68
|
+
});
|
|
69
|
+
sig.getKeyInfoContent = () => {
|
|
70
|
+
const certBase64 = this.certificado.getCertificateBase64();
|
|
71
|
+
const modulus = this.certificado.getModulus();
|
|
72
|
+
const exponent = this.certificado.getExponent();
|
|
73
|
+
return `<KeyValue><RSAKeyValue><Modulus>${modulus}</Modulus><Exponent>${exponent}</Exponent></RSAKeyValue></KeyValue><X509Data><X509Certificate>${certBase64}</X509Certificate></X509Data>`;
|
|
74
|
+
};
|
|
75
|
+
|
|
76
|
+
sig.computeSignature(xml, {
|
|
77
|
+
location: {
|
|
78
|
+
reference: insertAfterXpath || referenceXpath,
|
|
79
|
+
action: 'after',
|
|
80
|
+
},
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
return sig.getSignedXml();
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* Parsea un EnvioDTE XML y extrae los documentos
|
|
88
|
+
* @param {string} xml - XML del EnvioDTE
|
|
89
|
+
* @returns {Object} Metadatos del envío
|
|
90
|
+
*/
|
|
91
|
+
parseEnvioDTE(xml) {
|
|
92
|
+
const { normalizeArray } = this._lib();
|
|
93
|
+
|
|
94
|
+
const parser = new XMLParser({
|
|
95
|
+
ignoreAttributes: false,
|
|
96
|
+
attributeNamePrefix: '@_',
|
|
97
|
+
trimValues: true,
|
|
98
|
+
parseTagValue: true,
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
const data = parser.parse(xml);
|
|
102
|
+
const envio = data?.EnvioDTE || data?.EnvioDTEB || data?.EnvioDTETraslado || data?.EnvioBOLETA || {};
|
|
103
|
+
const setDte = envio?.SetDTE || data?.SetDTE || {};
|
|
104
|
+
const caratula = setDte?.Caratula || {};
|
|
105
|
+
const setId = setDte?.['@_ID'] || setDte?.ID || 'SetDTE';
|
|
106
|
+
|
|
107
|
+
const rutEmisor = caratula?.RutEmisor || null;
|
|
108
|
+
const rutEnvia = caratula?.RutEnvia || null;
|
|
109
|
+
const rutReceptor = caratula?.RutReceptor || null;
|
|
110
|
+
|
|
111
|
+
const dtes = normalizeArray(setDte?.DTE);
|
|
112
|
+
const documentos = dtes.map((dte) => dte?.Documento || dte?.DTE?.Documento || dte?.DTE?.Documento || {}).filter(Boolean);
|
|
113
|
+
|
|
114
|
+
const items = documentos.map((doc) => {
|
|
115
|
+
const encabezado = doc?.Encabezado || {};
|
|
116
|
+
const idDoc = encabezado?.IdDoc || {};
|
|
117
|
+
const emisor = encabezado?.Emisor || {};
|
|
118
|
+
const receptor = encabezado?.Receptor || {};
|
|
119
|
+
const totales = encabezado?.Totales || {};
|
|
120
|
+
|
|
121
|
+
return {
|
|
122
|
+
tipoDTE: parseInt(idDoc?.TipoDTE ?? idDoc?.TpoDoc ?? 0, 10) || 0,
|
|
123
|
+
folio: parseInt(idDoc?.Folio ?? 0, 10) || 0,
|
|
124
|
+
fchEmis: idDoc?.FchEmis || null,
|
|
125
|
+
rutEmisor: emisor?.RUTEmisor || emisor?.RutEmisor || rutEmisor || null,
|
|
126
|
+
rutRecep: receptor?.RUTRecep || receptor?.RutRecep || rutReceptor || null,
|
|
127
|
+
mntTotal: parseInt(totales?.MntTotal ?? 0, 10) || 0,
|
|
128
|
+
};
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
return {
|
|
132
|
+
setId,
|
|
133
|
+
rutEmisor,
|
|
134
|
+
rutEnvia,
|
|
135
|
+
rutReceptor,
|
|
136
|
+
items,
|
|
137
|
+
};
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
/**
|
|
141
|
+
* Genera la Respuesta de Recepción de Envío
|
|
142
|
+
* @param {Object} meta - Metadatos del EnvioDTE parseado
|
|
143
|
+
* @param {string} [envioNombre] - Nombre del envío
|
|
144
|
+
* @returns {string} XML firmado
|
|
145
|
+
*/
|
|
146
|
+
generarRespuestaRecepcionEnvio(meta, envioNombre) {
|
|
147
|
+
const { formatRut, sanitizeSiiText } = this._lib();
|
|
148
|
+
const now = this._formatSiiDateTime();
|
|
149
|
+
const resultId = `R_ENVIO_${Date.now()}`;
|
|
150
|
+
const rutEmpresa = this.emisor.rut;
|
|
151
|
+
|
|
152
|
+
const recepcionDte = meta.items.map((item) => {
|
|
153
|
+
const ok = formatRut(item.rutRecep || '') === formatRut(rutEmpresa);
|
|
154
|
+
return {
|
|
155
|
+
TipoDTE: item.tipoDTE,
|
|
156
|
+
Folio: item.folio,
|
|
157
|
+
FchEmis: item.fchEmis,
|
|
158
|
+
RUTEmisor: formatRut(item.rutEmisor || ''),
|
|
159
|
+
RUTRecep: formatRut(item.rutRecep || ''),
|
|
160
|
+
MntTotal: item.mntTotal,
|
|
161
|
+
EstadoRecepDTE: ok ? 0 : 3,
|
|
162
|
+
RecepDTEGlosa: ok ? 'DTE Recibido OK.' : 'DTE No Recibido - Error en RUT Receptor.',
|
|
163
|
+
};
|
|
164
|
+
});
|
|
165
|
+
|
|
166
|
+
const data = {
|
|
167
|
+
RespuestaDTE: {
|
|
168
|
+
'@_xmlns': 'http://www.sii.cl/SiiDte',
|
|
169
|
+
'@_xmlns:xsi': 'http://www.w3.org/2001/XMLSchema-instance',
|
|
170
|
+
'@_xsi:schemaLocation': 'http://www.sii.cl/SiiDte RespuestaEnvioDTE_v10.xsd',
|
|
171
|
+
'@_version': '1.0',
|
|
172
|
+
Resultado: {
|
|
173
|
+
'@_ID': resultId,
|
|
174
|
+
Caratula: {
|
|
175
|
+
'@_version': '1.0',
|
|
176
|
+
RutResponde: formatRut(rutEmpresa),
|
|
177
|
+
RutRecibe: formatRut(meta.rutEmisor || ''),
|
|
178
|
+
IdRespuesta: 1,
|
|
179
|
+
NroDetalles: 1,
|
|
180
|
+
...(this.contacto?.nombre ? { NmbContacto: sanitizeSiiText(this.contacto.nombre) } : {}),
|
|
181
|
+
...(this.contacto?.mail ? { MailContacto: this.contacto.mail } : {}),
|
|
182
|
+
TmstFirmaResp: now,
|
|
183
|
+
},
|
|
184
|
+
RecepcionEnvio: {
|
|
185
|
+
NmbEnvio: envioNombre || `ENVIO_DTE_${Date.now()}`,
|
|
186
|
+
FchRecep: now,
|
|
187
|
+
CodEnvio: 1,
|
|
188
|
+
EnvioDTEID: meta.setId,
|
|
189
|
+
RutEmisor: formatRut(meta.rutEmisor || ''),
|
|
190
|
+
RutReceptor: formatRut(rutEmpresa),
|
|
191
|
+
EstadoRecepEnv: 0,
|
|
192
|
+
RecepEnvGlosa: 'Envío recibido conforme.',
|
|
193
|
+
NroDTE: meta.items.length,
|
|
194
|
+
RecepcionDTE: recepcionDte,
|
|
195
|
+
},
|
|
196
|
+
},
|
|
197
|
+
},
|
|
198
|
+
};
|
|
199
|
+
|
|
200
|
+
const builder = new XMLBuilder({ ignoreAttributes: false, attributeNamePrefix: '@_', format: false });
|
|
201
|
+
let xml = builder.build(data);
|
|
202
|
+
|
|
203
|
+
xml = this._signXml({
|
|
204
|
+
xml,
|
|
205
|
+
referenceXpath: `//*[local-name()='Resultado' and @ID='${resultId}']`,
|
|
206
|
+
insertAfterXpath: `//*[local-name()='Resultado' and @ID='${resultId}']`,
|
|
207
|
+
});
|
|
208
|
+
|
|
209
|
+
return xml;
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
/**
|
|
213
|
+
* Genera la Respuesta de Aprobación Comercial (ResultadoDTE)
|
|
214
|
+
* @param {Object} meta - Metadatos del EnvioDTE parseado
|
|
215
|
+
* @returns {string} XML firmado
|
|
216
|
+
*/
|
|
217
|
+
generarRespuestaAprobacionComercial(meta) {
|
|
218
|
+
const { formatRut, sanitizeSiiText } = this._lib();
|
|
219
|
+
const now = this._formatSiiDateTime();
|
|
220
|
+
const resultId = `R_DTE_${Date.now()}`;
|
|
221
|
+
const rutEmpresa = this.emisor.rut;
|
|
222
|
+
|
|
223
|
+
const resultados = meta.items.map((item, idx) => {
|
|
224
|
+
const ok = formatRut(item.rutRecep || '') === formatRut(rutEmpresa);
|
|
225
|
+
return {
|
|
226
|
+
TipoDTE: item.tipoDTE,
|
|
227
|
+
Folio: item.folio,
|
|
228
|
+
FchEmis: item.fchEmis,
|
|
229
|
+
RUTEmisor: formatRut(item.rutEmisor || ''),
|
|
230
|
+
RUTRecep: formatRut(item.rutRecep || ''),
|
|
231
|
+
MntTotal: item.mntTotal,
|
|
232
|
+
CodEnvio: idx + 1,
|
|
233
|
+
EstadoDTE: ok ? 0 : 2,
|
|
234
|
+
EstadoDTEGlosa: ok ? 'DTE Aceptado OK' : 'DTE Rechazado',
|
|
235
|
+
...(ok ? {} : { CodRchDsc: -1 }),
|
|
236
|
+
};
|
|
237
|
+
});
|
|
238
|
+
|
|
239
|
+
const data = {
|
|
240
|
+
RespuestaDTE: {
|
|
241
|
+
'@_xmlns': 'http://www.sii.cl/SiiDte',
|
|
242
|
+
'@_xmlns:xsi': 'http://www.w3.org/2001/XMLSchema-instance',
|
|
243
|
+
'@_xsi:schemaLocation': 'http://www.sii.cl/SiiDte RespuestaEnvioDTE_v10.xsd',
|
|
244
|
+
'@_version': '1.0',
|
|
245
|
+
Resultado: {
|
|
246
|
+
'@_ID': resultId,
|
|
247
|
+
Caratula: {
|
|
248
|
+
'@_version': '1.0',
|
|
249
|
+
RutResponde: formatRut(rutEmpresa),
|
|
250
|
+
RutRecibe: formatRut(meta.rutEmisor || ''),
|
|
251
|
+
IdRespuesta: 1,
|
|
252
|
+
NroDetalles: meta.items.length,
|
|
253
|
+
...(this.contacto?.nombre ? { NmbContacto: sanitizeSiiText(this.contacto.nombre) } : {}),
|
|
254
|
+
...(this.contacto?.mail ? { MailContacto: this.contacto.mail } : {}),
|
|
255
|
+
TmstFirmaResp: now,
|
|
256
|
+
},
|
|
257
|
+
ResultadoDTE: resultados,
|
|
258
|
+
},
|
|
259
|
+
},
|
|
260
|
+
};
|
|
261
|
+
|
|
262
|
+
const builder = new XMLBuilder({ ignoreAttributes: false, attributeNamePrefix: '@_', format: false });
|
|
263
|
+
let xml = builder.build(data);
|
|
264
|
+
|
|
265
|
+
xml = this._signXml({
|
|
266
|
+
xml,
|
|
267
|
+
referenceXpath: `//*[local-name()='Resultado' and @ID='${resultId}']`,
|
|
268
|
+
insertAfterXpath: `//*[local-name()='Resultado' and @ID='${resultId}']`,
|
|
269
|
+
});
|
|
270
|
+
|
|
271
|
+
return xml;
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
/**
|
|
275
|
+
* Genera el Envío de Recibos (Recepción de Mercaderías)
|
|
276
|
+
* @param {Object} meta - Metadatos del EnvioDTE parseado
|
|
277
|
+
* @returns {string} XML firmado
|
|
278
|
+
*/
|
|
279
|
+
generarEnvioRecibos(meta) {
|
|
280
|
+
const { formatRut, sanitizeSiiText } = this._lib();
|
|
281
|
+
const now = this._formatSiiDateTime();
|
|
282
|
+
const setId = `SetRecibos_${Date.now()}`;
|
|
283
|
+
const rutEmpresa = this.emisor.rut;
|
|
284
|
+
const rutFirma = this.certificado.rut || rutEmpresa;
|
|
285
|
+
|
|
286
|
+
const recibos = meta.items.map((item, idx) => {
|
|
287
|
+
const docId = `REC-${idx + 1}`;
|
|
288
|
+
return {
|
|
289
|
+
'@_version': '1.0',
|
|
290
|
+
DocumentoRecibo: {
|
|
291
|
+
'@_ID': docId,
|
|
292
|
+
TipoDoc: item.tipoDTE,
|
|
293
|
+
Folio: item.folio,
|
|
294
|
+
FchEmis: item.fchEmis,
|
|
295
|
+
RUTEmisor: formatRut(item.rutEmisor || ''),
|
|
296
|
+
RUTRecep: formatRut(item.rutRecep || ''),
|
|
297
|
+
MntTotal: item.mntTotal,
|
|
298
|
+
Recinto: 'Oficina central',
|
|
299
|
+
RutFirma: formatRut(rutFirma),
|
|
300
|
+
Declaracion: DECLARACION_RECIBO,
|
|
301
|
+
TmstFirmaRecibo: now,
|
|
302
|
+
},
|
|
303
|
+
};
|
|
304
|
+
});
|
|
305
|
+
|
|
306
|
+
const data = {
|
|
307
|
+
EnvioRecibos: {
|
|
308
|
+
'@_xmlns': 'http://www.sii.cl/SiiDte',
|
|
309
|
+
'@_xmlns:xsi': 'http://www.w3.org/2001/XMLSchema-instance',
|
|
310
|
+
'@_xsi:schemaLocation': 'http://www.sii.cl/SiiDte EnvioRecibos_v10.xsd',
|
|
311
|
+
'@_version': '1.0',
|
|
312
|
+
SetRecibos: {
|
|
313
|
+
'@_ID': setId,
|
|
314
|
+
Caratula: {
|
|
315
|
+
'@_version': '1.0',
|
|
316
|
+
RutResponde: formatRut(rutEmpresa),
|
|
317
|
+
RutRecibe: formatRut(meta.rutEmisor || ''),
|
|
318
|
+
...(this.contacto?.nombre ? { NmbContacto: sanitizeSiiText(this.contacto.nombre) } : {}),
|
|
319
|
+
...(this.contacto?.fono ? { FonoContacto: this.contacto.fono } : {}),
|
|
320
|
+
...(this.contacto?.mail ? { MailContacto: this.contacto.mail } : {}),
|
|
321
|
+
TmstFirmaEnv: now,
|
|
322
|
+
},
|
|
323
|
+
Recibo: recibos,
|
|
324
|
+
},
|
|
325
|
+
},
|
|
326
|
+
};
|
|
327
|
+
|
|
328
|
+
const builder = new XMLBuilder({ ignoreAttributes: false, attributeNamePrefix: '@_', format: false });
|
|
329
|
+
let xml = builder.build(data);
|
|
330
|
+
|
|
331
|
+
// Firmar cada DocumentoRecibo
|
|
332
|
+
meta.items.forEach((_, idx) => {
|
|
333
|
+
const docId = `REC-${idx + 1}`;
|
|
334
|
+
xml = this._signXml({
|
|
335
|
+
xml,
|
|
336
|
+
referenceXpath: `//*[local-name()='DocumentoRecibo' and @ID='${docId}']`,
|
|
337
|
+
insertAfterXpath: `//*[local-name()='DocumentoRecibo' and @ID='${docId}']`,
|
|
338
|
+
});
|
|
339
|
+
});
|
|
340
|
+
|
|
341
|
+
// Firmar SetRecibos
|
|
342
|
+
xml = this._signXml({
|
|
343
|
+
xml,
|
|
344
|
+
referenceXpath: `//*[local-name()='SetRecibos' and @ID='${setId}']`,
|
|
345
|
+
insertAfterXpath: `//*[local-name()='SetRecibos' and @ID='${setId}']`,
|
|
346
|
+
});
|
|
347
|
+
|
|
348
|
+
return xml;
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
/**
|
|
352
|
+
* Genera todas las respuestas de intercambio
|
|
353
|
+
* @param {string} envioDteXml - XML del EnvioDTE de entrada
|
|
354
|
+
* @param {Object} [options]
|
|
355
|
+
* @param {string} [options.envioNombre] - Nombre del envío
|
|
356
|
+
* @param {string} [options.outDir] - Directorio de salida (override debugDir)
|
|
357
|
+
* @returns {Object} Resultado con rutas de archivos generados
|
|
358
|
+
*/
|
|
359
|
+
async generarIntercambio(envioDteXml, options = {}) {
|
|
360
|
+
const outDir = options.outDir || this.debugDir || './debug/intercambio';
|
|
361
|
+
fs.mkdirSync(outDir, { recursive: true });
|
|
362
|
+
|
|
363
|
+
console.log('\n' + '═'.repeat(60));
|
|
364
|
+
console.log('📬 INTERCAMBIO DE INFORMACIÓN DTE');
|
|
365
|
+
console.log('═'.repeat(60));
|
|
366
|
+
|
|
367
|
+
// Parsear EnvioDTE
|
|
368
|
+
console.log('\n📋 Parseando EnvioDTE...');
|
|
369
|
+
const meta = this.parseEnvioDTE(envioDteXml);
|
|
370
|
+
|
|
371
|
+
if (!meta?.items?.length) {
|
|
372
|
+
throw new Error('No se encontraron DTEs en el EnvioDTE de entrada');
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
console.log(` ✓ ${meta.items.length} documentos encontrados`);
|
|
376
|
+
console.log(` ✓ Emisor: ${meta.rutEmisor}`);
|
|
377
|
+
console.log(` ✓ Receptor: ${meta.rutReceptor}`);
|
|
378
|
+
|
|
379
|
+
meta.items.forEach((item, idx) => {
|
|
380
|
+
console.log(` ${idx + 1}. Tipo ${item.tipoDTE} Folio ${item.folio} - $${item.mntTotal.toLocaleString('es-CL')}`);
|
|
381
|
+
});
|
|
382
|
+
|
|
383
|
+
// Generar respuestas
|
|
384
|
+
console.log('\n📝 Generando respuestas de intercambio...');
|
|
385
|
+
|
|
386
|
+
// 1. Respuesta Recepción de Envío
|
|
387
|
+
console.log(' 1. Respuesta Recepción de Envío...');
|
|
388
|
+
const recepcionXml = this.generarRespuestaRecepcionEnvio(meta, options.envioNombre);
|
|
389
|
+
const recepcionPath = path.join(outDir, 'respuesta-recepcion-envio.xml');
|
|
390
|
+
fs.writeFileSync(recepcionPath, recepcionXml, 'utf8');
|
|
391
|
+
console.log(` ✓ ${recepcionPath}`);
|
|
392
|
+
|
|
393
|
+
// 2. Respuesta Aprobación Comercial
|
|
394
|
+
console.log(' 2. Respuesta Aprobación Comercial...');
|
|
395
|
+
const aprobacionXml = this.generarRespuestaAprobacionComercial(meta);
|
|
396
|
+
const aprobacionPath = path.join(outDir, 'respuesta-aprobacion-comercial.xml');
|
|
397
|
+
fs.writeFileSync(aprobacionPath, aprobacionXml, 'utf8');
|
|
398
|
+
console.log(` ✓ ${aprobacionPath}`);
|
|
399
|
+
|
|
400
|
+
// 3. Envío de Recibos
|
|
401
|
+
console.log(' 3. Envío de Recibos (Mercaderías)...');
|
|
402
|
+
const recibosXml = this.generarEnvioRecibos(meta);
|
|
403
|
+
const recibosPath = path.join(outDir, 'envio-recibos.xml');
|
|
404
|
+
fs.writeFileSync(recibosPath, recibosXml, 'utf8');
|
|
405
|
+
console.log(` ✓ ${recibosPath}`);
|
|
406
|
+
|
|
407
|
+
console.log('\n' + '═'.repeat(60));
|
|
408
|
+
console.log('✅ INTERCAMBIO GENERADO');
|
|
409
|
+
console.log('═'.repeat(60));
|
|
410
|
+
console.log(` 📂 Directorio: ${outDir}`);
|
|
411
|
+
console.log(' 📄 Archivos generados:');
|
|
412
|
+
console.log(' - respuesta-recepcion-envio.xml');
|
|
413
|
+
console.log(' - respuesta-aprobacion-comercial.xml');
|
|
414
|
+
console.log(' - envio-recibos.xml');
|
|
415
|
+
|
|
416
|
+
return {
|
|
417
|
+
success: true,
|
|
418
|
+
outDir,
|
|
419
|
+
meta,
|
|
420
|
+
files: {
|
|
421
|
+
recepcion: recepcionPath,
|
|
422
|
+
aprobacion: aprobacionPath,
|
|
423
|
+
recibos: recibosPath,
|
|
424
|
+
},
|
|
425
|
+
};
|
|
426
|
+
}
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
module.exports = IntercambioCert;
|