@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/utils/pfx.js
ADDED
|
@@ -0,0 +1,203 @@
|
|
|
1
|
+
// Copyright (c) 2026 Devlas SpA — https://devlas.cl
|
|
2
|
+
// Licencia MIT. Ver archivo LICENSE para mas detalles.
|
|
3
|
+
/**
|
|
4
|
+
* pfx.js - Loader centralizado de certificados PFX/P12
|
|
5
|
+
*
|
|
6
|
+
* Centraliza la carga y extracción de certificados PFX para evitar
|
|
7
|
+
* duplicación de código entre Certificado.js y SiiSession.js.
|
|
8
|
+
*
|
|
9
|
+
* @module utils/pfx
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
const fs = require('fs');
|
|
13
|
+
const forge = require('node-forge');
|
|
14
|
+
const { certError, ERROR_CODES } = require('./error');
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Resultado de cargar un PFX
|
|
18
|
+
* @typedef {Object} PfxData
|
|
19
|
+
* @property {forge.pki.PrivateKey} privateKey - Clave privada
|
|
20
|
+
* @property {forge.pki.Certificate} certificate - Certificado
|
|
21
|
+
* @property {string} privateKeyPem - Clave privada en formato PEM
|
|
22
|
+
* @property {string} certificatePem - Certificado en formato PEM
|
|
23
|
+
* @property {Object} subject - Campos del subject del certificado
|
|
24
|
+
* @property {string} rut - RUT extraído del certificado (si existe)
|
|
25
|
+
* @property {string} cn - Common Name del certificado
|
|
26
|
+
* @property {Date} notBefore - Fecha de inicio de validez
|
|
27
|
+
* @property {Date} notAfter - Fecha de expiración
|
|
28
|
+
*/
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Cargar y parsear un archivo PFX/P12 desde buffer
|
|
32
|
+
* @param {Buffer} pfxBuffer - Buffer del archivo PFX
|
|
33
|
+
* @param {string} password - Contraseña del PFX
|
|
34
|
+
* @returns {PfxData} Datos del certificado
|
|
35
|
+
* @throws {DteSiiError} Si hay error al parsear
|
|
36
|
+
*/
|
|
37
|
+
function loadPfxFromBuffer(pfxBuffer, password) {
|
|
38
|
+
if (!pfxBuffer || !Buffer.isBuffer(pfxBuffer)) {
|
|
39
|
+
throw certError('Buffer PFX es requerido', ERROR_CODES.CERT_INVALID);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
let p12Asn1, p12;
|
|
43
|
+
|
|
44
|
+
try {
|
|
45
|
+
p12Asn1 = forge.asn1.fromDer(pfxBuffer.toString('binary'));
|
|
46
|
+
} catch (err) {
|
|
47
|
+
throw certError(`Error parseando PFX: ${err.message}`, ERROR_CODES.CERT_INVALID, { originalError: err });
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
try {
|
|
51
|
+
p12 = forge.pkcs12.pkcs12FromAsn1(p12Asn1, password);
|
|
52
|
+
} catch (err) {
|
|
53
|
+
if (err.message.includes('Invalid password')) {
|
|
54
|
+
throw certError('Contraseña del certificado incorrecta', ERROR_CODES.CERT_PASSWORD_INVALID, { originalError: err });
|
|
55
|
+
}
|
|
56
|
+
throw certError(`Error descifrando PFX: ${err.message}`, ERROR_CODES.CERT_INVALID, { originalError: err });
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
// Extraer certificado y clave privada
|
|
60
|
+
const certBags = p12.getBags({ bagType: forge.pki.oids.certBag });
|
|
61
|
+
const keyBags = p12.getBags({ bagType: forge.pki.oids.pkcs8ShroudedKeyBag });
|
|
62
|
+
|
|
63
|
+
const certBag = certBags[forge.pki.oids.certBag];
|
|
64
|
+
const keyBag = keyBags[forge.pki.oids.pkcs8ShroudedKeyBag];
|
|
65
|
+
|
|
66
|
+
if (!certBag || !certBag.length) {
|
|
67
|
+
throw certError('No se encontró certificado en el archivo PFX', ERROR_CODES.CERT_INVALID);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
if (!keyBag || !keyBag.length) {
|
|
71
|
+
throw certError('No se encontró clave privada en el archivo PFX', ERROR_CODES.CERT_INVALID);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
const certificate = certBag[0].cert;
|
|
75
|
+
const privateKey = keyBag[0].key;
|
|
76
|
+
|
|
77
|
+
// Extraer información del subject
|
|
78
|
+
const subject = extractSubjectFields(certificate);
|
|
79
|
+
const rut = extractRutFromCertificate(certificate);
|
|
80
|
+
|
|
81
|
+
return {
|
|
82
|
+
privateKey,
|
|
83
|
+
certificate,
|
|
84
|
+
privateKeyPem: forge.pki.privateKeyToPem(privateKey),
|
|
85
|
+
certificatePem: forge.pki.certificateToPem(certificate),
|
|
86
|
+
subject,
|
|
87
|
+
rut,
|
|
88
|
+
cn: subject.CN || null,
|
|
89
|
+
notBefore: certificate.validity.notBefore,
|
|
90
|
+
notAfter: certificate.validity.notAfter,
|
|
91
|
+
};
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* Cargar PFX desde archivo
|
|
96
|
+
* @param {string} pfxPath - Ruta al archivo PFX
|
|
97
|
+
* @param {string} password - Contraseña del PFX
|
|
98
|
+
* @returns {PfxData} Datos del certificado
|
|
99
|
+
*/
|
|
100
|
+
function loadPfxFromFile(pfxPath, password) {
|
|
101
|
+
if (!fs.existsSync(pfxPath)) {
|
|
102
|
+
throw certError(`Archivo PFX no encontrado: ${pfxPath}`, ERROR_CODES.CERT_NOT_FOUND, { path: pfxPath });
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
const pfxBuffer = fs.readFileSync(pfxPath);
|
|
106
|
+
return loadPfxFromBuffer(pfxBuffer, password);
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/**
|
|
110
|
+
* Extraer campos del subject del certificado
|
|
111
|
+
* @param {forge.pki.Certificate} cert - Certificado
|
|
112
|
+
* @returns {Object} Campos del subject
|
|
113
|
+
*/
|
|
114
|
+
function extractSubjectFields(cert) {
|
|
115
|
+
const fields = {};
|
|
116
|
+
|
|
117
|
+
if (cert.subject && cert.subject.attributes) {
|
|
118
|
+
cert.subject.attributes.forEach((attr) => {
|
|
119
|
+
const name = attr.shortName || attr.name;
|
|
120
|
+
if (name) {
|
|
121
|
+
fields[name] = attr.value;
|
|
122
|
+
}
|
|
123
|
+
});
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
return fields;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
/**
|
|
130
|
+
* Extraer RUT del certificado (desde serialNumber o CN)
|
|
131
|
+
* @param {forge.pki.Certificate} cert - Certificado
|
|
132
|
+
* @returns {string|null} RUT o null
|
|
133
|
+
*/
|
|
134
|
+
function extractRutFromCertificate(cert) {
|
|
135
|
+
const subject = extractSubjectFields(cert);
|
|
136
|
+
|
|
137
|
+
// Intentar desde serialNumber (más confiable)
|
|
138
|
+
if (subject.serialNumber) {
|
|
139
|
+
const clean = subject.serialNumber.replace(/\./g, '').toUpperCase();
|
|
140
|
+
if (/^\d{7,8}-[\dK]$/.test(clean)) {
|
|
141
|
+
return clean;
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
// Intentar desde CN
|
|
146
|
+
if (subject.CN) {
|
|
147
|
+
const match = subject.CN.match(/(\d{7,8}-[\dK])/i);
|
|
148
|
+
if (match) {
|
|
149
|
+
return match[1].toUpperCase();
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
return null;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
/**
|
|
157
|
+
* Verificar si un certificado está expirado
|
|
158
|
+
* @param {Date} notAfter - Fecha de expiración
|
|
159
|
+
* @param {number} [marginDays=0] - Días de margen
|
|
160
|
+
* @returns {boolean} True si está expirado
|
|
161
|
+
*/
|
|
162
|
+
function isCertificateExpired(notAfter, marginDays = 0) {
|
|
163
|
+
const now = new Date();
|
|
164
|
+
const expiry = new Date(notAfter);
|
|
165
|
+
expiry.setDate(expiry.getDate() - marginDays);
|
|
166
|
+
return now > expiry;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
/**
|
|
170
|
+
* Obtener días hasta expiración
|
|
171
|
+
* @param {Date} notAfter - Fecha de expiración
|
|
172
|
+
* @returns {number} Días hasta expiración (negativo si expirado)
|
|
173
|
+
*/
|
|
174
|
+
function getDaysUntilExpiry(notAfter) {
|
|
175
|
+
const now = new Date();
|
|
176
|
+
const expiry = new Date(notAfter);
|
|
177
|
+
const diffMs = expiry - now;
|
|
178
|
+
return Math.floor(diffMs / (1000 * 60 * 60 * 24));
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
/**
|
|
182
|
+
* Crear opciones TLS desde datos PFX
|
|
183
|
+
* @param {PfxData} pfxData - Datos del PFX
|
|
184
|
+
* @returns {Object} Opciones TLS para https/got
|
|
185
|
+
*/
|
|
186
|
+
function createTlsOptions(pfxData) {
|
|
187
|
+
return {
|
|
188
|
+
key: pfxData.privateKeyPem,
|
|
189
|
+
cert: pfxData.certificatePem,
|
|
190
|
+
certificate: pfxData.certificatePem,
|
|
191
|
+
rejectUnauthorized: false,
|
|
192
|
+
};
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
module.exports = {
|
|
196
|
+
loadPfxFromBuffer,
|
|
197
|
+
loadPfxFromFile,
|
|
198
|
+
extractSubjectFields,
|
|
199
|
+
extractRutFromCertificate,
|
|
200
|
+
isCertificateExpired,
|
|
201
|
+
getDaysUntilExpiry,
|
|
202
|
+
createTlsOptions,
|
|
203
|
+
};
|
|
@@ -0,0 +1,218 @@
|
|
|
1
|
+
// Copyright (c) 2026 Devlas SpA — https://devlas.cl
|
|
2
|
+
// Licencia MIT. Ver archivo LICENSE para mas detalles.
|
|
3
|
+
/**
|
|
4
|
+
* Utilidades de Receptor
|
|
5
|
+
*
|
|
6
|
+
* Funciones para construir y validar datos de receptor en DTEs
|
|
7
|
+
*
|
|
8
|
+
* @module dte-sii/utils/receptor
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
const { sanitizeSiiText, sanitizeGiroRecep } = require('./sanitize');
|
|
12
|
+
const { formatRutSii, validarRut } = require('./rut');
|
|
13
|
+
|
|
14
|
+
// ============================================
|
|
15
|
+
// CONSTANTES
|
|
16
|
+
// ============================================
|
|
17
|
+
|
|
18
|
+
/** RUT para consumidor final */
|
|
19
|
+
const RUT_CONSUMIDOR_FINAL = '66666666-6';
|
|
20
|
+
|
|
21
|
+
/** Receptor por defecto para consumidor final */
|
|
22
|
+
const RECEPTOR_CONSUMIDOR_FINAL = {
|
|
23
|
+
RUTRecep: RUT_CONSUMIDOR_FINAL,
|
|
24
|
+
RznSocRecep: 'Consumidor Final',
|
|
25
|
+
DirRecep: 'Sin Direccion',
|
|
26
|
+
CmnaRecep: 'Santiago',
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
// ============================================
|
|
30
|
+
// CONSTRUCCIÓN DE RECEPTOR
|
|
31
|
+
// ============================================
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Construye objeto de Receptor para DTE
|
|
35
|
+
*
|
|
36
|
+
* @param {Object} config - Configuración del receptor
|
|
37
|
+
* @param {string} config.rut - RUT del receptor
|
|
38
|
+
* @param {string} config.razonSocial - Razón social
|
|
39
|
+
* @param {string} [config.giro] - Giro (máximo 40 caracteres)
|
|
40
|
+
* @param {string} config.direccion - Dirección
|
|
41
|
+
* @param {string} config.comuna - Comuna
|
|
42
|
+
* @param {string} [config.ciudad] - Ciudad
|
|
43
|
+
* @param {string} [config.correo] - Correo electrónico
|
|
44
|
+
* @returns {Object} Receptor formateado para DTE
|
|
45
|
+
*/
|
|
46
|
+
function buildReceptor(config) {
|
|
47
|
+
const {
|
|
48
|
+
rut,
|
|
49
|
+
razonSocial,
|
|
50
|
+
giro,
|
|
51
|
+
direccion,
|
|
52
|
+
comuna,
|
|
53
|
+
ciudad,
|
|
54
|
+
correo,
|
|
55
|
+
} = config;
|
|
56
|
+
|
|
57
|
+
const receptor = {
|
|
58
|
+
RUTRecep: formatRutSii(rut),
|
|
59
|
+
RznSocRecep: sanitizeSiiText(razonSocial),
|
|
60
|
+
};
|
|
61
|
+
|
|
62
|
+
if (giro) {
|
|
63
|
+
receptor.GiroRecep = sanitizeGiroRecep(giro);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
receptor.DirRecep = sanitizeSiiText(direccion);
|
|
67
|
+
receptor.CmnaRecep = comuna;
|
|
68
|
+
|
|
69
|
+
if (ciudad) {
|
|
70
|
+
receptor.CiudadRecep = ciudad;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
if (correo) {
|
|
74
|
+
receptor.CorreoRecep = correo;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
return receptor;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Construye receptor para Boleta (campos mínimos)
|
|
82
|
+
*
|
|
83
|
+
* @param {Object} [config] - Configuración del receptor (opcional para boleta)
|
|
84
|
+
* @returns {Object} Receptor formateado para Boleta
|
|
85
|
+
*/
|
|
86
|
+
function buildReceptorBoleta(config = {}) {
|
|
87
|
+
const { rut, razonSocial, direccion, comuna } = config;
|
|
88
|
+
|
|
89
|
+
return {
|
|
90
|
+
RUTRecep: rut ? formatRutSii(rut) : '',
|
|
91
|
+
RznSocRecep: sanitizeSiiText(razonSocial || ''),
|
|
92
|
+
DirRecep: sanitizeSiiText(direccion || ''),
|
|
93
|
+
CmnaRecep: comuna || '',
|
|
94
|
+
};
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* Crea receptor para consumidor final (RUT 66666666-6)
|
|
99
|
+
*
|
|
100
|
+
* @param {Object} [overrides] - Campos a sobrescribir
|
|
101
|
+
* @returns {Object} Receptor de consumidor final
|
|
102
|
+
*/
|
|
103
|
+
function buildReceptorConsumidorFinal(overrides = {}) {
|
|
104
|
+
return {
|
|
105
|
+
...RECEPTOR_CONSUMIDOR_FINAL,
|
|
106
|
+
...overrides,
|
|
107
|
+
RUTRecep: RUT_CONSUMIDOR_FINAL, // Siempre mantener este RUT
|
|
108
|
+
};
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* Normaliza receptor desde diferentes formatos de entrada
|
|
113
|
+
*
|
|
114
|
+
* @param {Object} receptor - Receptor en cualquier formato
|
|
115
|
+
* @param {boolean} [esBoleta=false] - Si es formato boleta
|
|
116
|
+
* @returns {Object} Receptor normalizado
|
|
117
|
+
*/
|
|
118
|
+
function normalizeReceptor(receptor, esBoleta = false) {
|
|
119
|
+
if (!receptor) {
|
|
120
|
+
return esBoleta ? buildReceptorBoleta() : RECEPTOR_CONSUMIDOR_FINAL;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
if (esBoleta) {
|
|
124
|
+
return {
|
|
125
|
+
RUTRecep: receptor.RUTRecep || '',
|
|
126
|
+
RznSocRecep: sanitizeSiiText(receptor.RznSocRecep || ''),
|
|
127
|
+
DirRecep: sanitizeSiiText(receptor.DirRecep || ''),
|
|
128
|
+
CmnaRecep: receptor.CmnaRecep || '',
|
|
129
|
+
};
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
const giroRecep = receptor.GiroRecep
|
|
133
|
+
? sanitizeGiroRecep(receptor.GiroRecep)
|
|
134
|
+
: undefined;
|
|
135
|
+
|
|
136
|
+
return {
|
|
137
|
+
RUTRecep: receptor.RUTRecep,
|
|
138
|
+
RznSocRecep: sanitizeSiiText(receptor.RznSocRecep),
|
|
139
|
+
...(giroRecep ? { GiroRecep: giroRecep } : {}),
|
|
140
|
+
DirRecep: sanitizeSiiText(receptor.DirRecep),
|
|
141
|
+
CmnaRecep: receptor.CmnaRecep,
|
|
142
|
+
...(receptor.CiudadRecep ? { CiudadRecep: receptor.CiudadRecep } : {}),
|
|
143
|
+
};
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
/**
|
|
147
|
+
* Valida datos de receptor
|
|
148
|
+
*
|
|
149
|
+
* @param {Object} receptor - Datos del receptor
|
|
150
|
+
* @param {Object} [options] - Opciones de validación
|
|
151
|
+
* @param {boolean} [options.requireGiro=false] - Si el giro es requerido
|
|
152
|
+
* @param {boolean} [options.allowConsumidorFinal=true] - Si se permite RUT 66666666-6
|
|
153
|
+
* @returns {{ valid: boolean, errors: string[] }}
|
|
154
|
+
*/
|
|
155
|
+
function validarReceptor(receptor, options = {}) {
|
|
156
|
+
const { requireGiro = false, allowConsumidorFinal = true } = options;
|
|
157
|
+
const errors = [];
|
|
158
|
+
|
|
159
|
+
if (!receptor) {
|
|
160
|
+
return { valid: false, errors: ['Receptor no proporcionado'] };
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
if (!receptor.RUTRecep) {
|
|
164
|
+
errors.push('RUT de receptor requerido');
|
|
165
|
+
} else if (receptor.RUTRecep !== RUT_CONSUMIDOR_FINAL && !validarRut(receptor.RUTRecep)) {
|
|
166
|
+
errors.push('RUT de receptor inválido');
|
|
167
|
+
} else if (receptor.RUTRecep === RUT_CONSUMIDOR_FINAL && !allowConsumidorFinal) {
|
|
168
|
+
errors.push('RUT de consumidor final no permitido para este tipo de documento');
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
if (!receptor.RznSocRecep) {
|
|
172
|
+
errors.push('Razón social de receptor requerida');
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
if (requireGiro && !receptor.GiroRecep) {
|
|
176
|
+
errors.push('Giro de receptor requerido');
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
if (!receptor.DirRecep) {
|
|
180
|
+
errors.push('Dirección de receptor requerida');
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
if (!receptor.CmnaRecep) {
|
|
184
|
+
errors.push('Comuna de receptor requerida');
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
return { valid: errors.length === 0, errors };
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
/**
|
|
191
|
+
* Verifica si un receptor es consumidor final
|
|
192
|
+
*
|
|
193
|
+
* @param {Object} receptor - Datos del receptor
|
|
194
|
+
* @returns {boolean}
|
|
195
|
+
*/
|
|
196
|
+
function esConsumidorFinal(receptor) {
|
|
197
|
+
return receptor?.RUTRecep === RUT_CONSUMIDOR_FINAL;
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
// ============================================
|
|
201
|
+
// EXPORTS
|
|
202
|
+
// ============================================
|
|
203
|
+
|
|
204
|
+
module.exports = {
|
|
205
|
+
// Constantes
|
|
206
|
+
RUT_CONSUMIDOR_FINAL,
|
|
207
|
+
RECEPTOR_CONSUMIDOR_FINAL,
|
|
208
|
+
|
|
209
|
+
// Construcción
|
|
210
|
+
buildReceptor,
|
|
211
|
+
buildReceptorBoleta,
|
|
212
|
+
buildReceptorConsumidorFinal,
|
|
213
|
+
normalizeReceptor,
|
|
214
|
+
|
|
215
|
+
// Validación
|
|
216
|
+
validarReceptor,
|
|
217
|
+
esConsumidorFinal,
|
|
218
|
+
};
|
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
// Copyright (c) 2026 Devlas SpA — https://devlas.cl
|
|
2
|
+
// Licencia MIT. Ver archivo LICENSE para mas detalles.
|
|
3
|
+
/**
|
|
4
|
+
* Utilidades de Referencia
|
|
5
|
+
*
|
|
6
|
+
* Funciones para construir referencias en DTEs (SET de pruebas, documentos, anulaciones)
|
|
7
|
+
*
|
|
8
|
+
* @module dte-sii/utils/referencia
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
// ============================================
|
|
12
|
+
// REFERENCIAS PARA CERTIFICACIÓN
|
|
13
|
+
// ============================================
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Construye referencia al SET de pruebas del SII
|
|
17
|
+
* Requerida en todos los DTEs de certificación
|
|
18
|
+
*
|
|
19
|
+
* @param {string} casoId - ID del caso (ej: "4668070-1")
|
|
20
|
+
* @param {string} fecha - Fecha de emisión YYYY-MM-DD
|
|
21
|
+
* @param {number} [nroLinRef=1] - Número de línea de referencia
|
|
22
|
+
* @returns {Object} Objeto de referencia formateado
|
|
23
|
+
*/
|
|
24
|
+
function buildSetReferencia(casoId, fecha, nroLinRef = 1) {
|
|
25
|
+
return {
|
|
26
|
+
NroLinRef: nroLinRef,
|
|
27
|
+
TpoDocRef: 'SET',
|
|
28
|
+
FolioRef: 1,
|
|
29
|
+
FchRef: fecha,
|
|
30
|
+
RazonRef: `CASO ${casoId}`,
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
// ============================================
|
|
35
|
+
// REFERENCIAS A DOCUMENTOS
|
|
36
|
+
// ============================================
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Construye referencia a documento previo (para NC/ND)
|
|
40
|
+
*
|
|
41
|
+
* @param {Object} params - Parámetros de la referencia
|
|
42
|
+
* @param {number} params.tipoDte - Tipo de documento referenciado
|
|
43
|
+
* @param {number} params.folio - Folio del documento referenciado
|
|
44
|
+
* @param {string} params.fecha - Fecha del documento YYYY-MM-DD
|
|
45
|
+
* @param {number} params.codRef - Código de referencia (1=Anula, 2=Corrige texto, 3=Corrige montos)
|
|
46
|
+
* @param {string} params.razonRef - Razón de la referencia
|
|
47
|
+
* @param {number} [params.nroLinRef=1] - Número de línea
|
|
48
|
+
* @returns {Object} Objeto de referencia
|
|
49
|
+
*/
|
|
50
|
+
function buildDocReferencia({ tipoDte, folio, fecha, codRef, razonRef, nroLinRef = 1 }) {
|
|
51
|
+
return {
|
|
52
|
+
NroLinRef: nroLinRef,
|
|
53
|
+
TpoDocRef: tipoDte,
|
|
54
|
+
FolioRef: folio,
|
|
55
|
+
FchRef: fecha,
|
|
56
|
+
CodRef: codRef,
|
|
57
|
+
RazonRef: razonRef,
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Construye referencia para anulación de documento
|
|
63
|
+
*
|
|
64
|
+
* @param {Object} params - Parámetros
|
|
65
|
+
* @param {number} params.tipoDte - Tipo de documento a anular
|
|
66
|
+
* @param {number} params.folio - Folio del documento a anular
|
|
67
|
+
* @param {string} params.fecha - Fecha del documento
|
|
68
|
+
* @param {number} [params.nroLinRef=1] - Número de línea
|
|
69
|
+
* @returns {Object} Referencia de anulación
|
|
70
|
+
*/
|
|
71
|
+
function buildAnulacionReferencia({ tipoDte, folio, fecha, nroLinRef = 1 }) {
|
|
72
|
+
return buildDocReferencia({
|
|
73
|
+
tipoDte,
|
|
74
|
+
folio,
|
|
75
|
+
fecha,
|
|
76
|
+
codRef: 1, // 1 = Anula documento de referencia
|
|
77
|
+
razonRef: 'ANULA DOCUMENTO',
|
|
78
|
+
nroLinRef,
|
|
79
|
+
});
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* Construye referencia para corrección de texto
|
|
84
|
+
*
|
|
85
|
+
* @param {Object} params - Parámetros
|
|
86
|
+
* @param {number} params.tipoDte - Tipo de documento a corregir
|
|
87
|
+
* @param {number} params.folio - Folio del documento
|
|
88
|
+
* @param {string} params.fecha - Fecha del documento
|
|
89
|
+
* @param {string} params.razonRef - Descripción de la corrección
|
|
90
|
+
* @param {number} [params.nroLinRef=1] - Número de línea
|
|
91
|
+
* @returns {Object} Referencia de corrección
|
|
92
|
+
*/
|
|
93
|
+
function buildCorreccionTextoReferencia({ tipoDte, folio, fecha, razonRef, nroLinRef = 1 }) {
|
|
94
|
+
return buildDocReferencia({
|
|
95
|
+
tipoDte,
|
|
96
|
+
folio,
|
|
97
|
+
fecha,
|
|
98
|
+
codRef: 2, // 2 = Corrige texto del documento de referencia
|
|
99
|
+
razonRef,
|
|
100
|
+
nroLinRef,
|
|
101
|
+
});
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* Construye referencia para corrección de montos
|
|
106
|
+
*
|
|
107
|
+
* @param {Object} params - Parámetros
|
|
108
|
+
* @param {number} params.tipoDte - Tipo de documento a corregir
|
|
109
|
+
* @param {number} params.folio - Folio del documento
|
|
110
|
+
* @param {string} params.fecha - Fecha del documento
|
|
111
|
+
* @param {string} params.razonRef - Descripción de la corrección
|
|
112
|
+
* @param {number} [params.nroLinRef=1] - Número de línea
|
|
113
|
+
* @returns {Object} Referencia de corrección de montos
|
|
114
|
+
*/
|
|
115
|
+
function buildCorreccionMontosReferencia({ tipoDte, folio, fecha, razonRef, nroLinRef = 1 }) {
|
|
116
|
+
return buildDocReferencia({
|
|
117
|
+
tipoDte,
|
|
118
|
+
folio,
|
|
119
|
+
fecha,
|
|
120
|
+
codRef: 3, // 3 = Corrige montos
|
|
121
|
+
razonRef,
|
|
122
|
+
nroLinRef,
|
|
123
|
+
});
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
// ============================================
|
|
127
|
+
// UTILIDADES
|
|
128
|
+
// ============================================
|
|
129
|
+
|
|
130
|
+
/**
|
|
131
|
+
* Combina referencia SET con referencia a documento
|
|
132
|
+
* Para NC/ND en certificación que necesitan ambas
|
|
133
|
+
*
|
|
134
|
+
* @param {string} casoId - ID del caso de prueba
|
|
135
|
+
* @param {string} fechaEmision - Fecha de emisión del nuevo documento
|
|
136
|
+
* @param {Object} docRef - Referencia al documento (sin NroLinRef)
|
|
137
|
+
* @returns {Array} Array con ambas referencias ordenadas
|
|
138
|
+
*/
|
|
139
|
+
function buildReferenciasNcNd(casoId, fechaEmision, docRef) {
|
|
140
|
+
const setRef = buildSetReferencia(casoId, fechaEmision, 1);
|
|
141
|
+
const documentoRef = { ...docRef, NroLinRef: 2 };
|
|
142
|
+
return [setRef, documentoRef];
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
/**
|
|
146
|
+
* Códigos de referencia SII
|
|
147
|
+
*/
|
|
148
|
+
const CODIGOS_REFERENCIA = {
|
|
149
|
+
ANULA: 1,
|
|
150
|
+
CORRIGE_TEXTO: 2,
|
|
151
|
+
CORRIGE_MONTOS: 3,
|
|
152
|
+
};
|
|
153
|
+
|
|
154
|
+
// ============================================
|
|
155
|
+
// EXPORTS
|
|
156
|
+
// ============================================
|
|
157
|
+
|
|
158
|
+
module.exports = {
|
|
159
|
+
// Referencias
|
|
160
|
+
buildSetReferencia,
|
|
161
|
+
buildDocReferencia,
|
|
162
|
+
buildAnulacionReferencia,
|
|
163
|
+
buildCorreccionTextoReferencia,
|
|
164
|
+
buildCorreccionMontosReferencia,
|
|
165
|
+
buildReferenciasNcNd,
|
|
166
|
+
|
|
167
|
+
// Constantes
|
|
168
|
+
CODIGOS_REFERENCIA,
|
|
169
|
+
};
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
// Copyright (c) 2026 Devlas SpA — https://devlas.cl
|
|
2
|
+
// Licencia MIT. Ver archivo LICENSE para mas detalles.
|
|
3
|
+
/**
|
|
4
|
+
* Utilidades de Resolución SII
|
|
5
|
+
*
|
|
6
|
+
* Funciones para manejo de resoluciones SII (certificación/producción)
|
|
7
|
+
*
|
|
8
|
+
* @module dte-sii/utils/resolucion
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
// ============================================
|
|
12
|
+
// RESOLUCIÓN SII
|
|
13
|
+
// ============================================
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Normaliza fecha de resolución a formato YYYY-MM-DD
|
|
17
|
+
*
|
|
18
|
+
* @param {string} value - Fecha en formato DD-MM-YYYY o YYYY-MM-DD
|
|
19
|
+
* @returns {string} - Fecha normalizada YYYY-MM-DD
|
|
20
|
+
*/
|
|
21
|
+
function normalizeFechaResolucion(value) {
|
|
22
|
+
const raw = (value || '').trim();
|
|
23
|
+
if (!raw) return raw;
|
|
24
|
+
|
|
25
|
+
// Ya está en formato ISO
|
|
26
|
+
if (/^\d{4}-\d{2}-\d{2}$/.test(raw)) return raw;
|
|
27
|
+
|
|
28
|
+
// Formato DD-MM-YYYY
|
|
29
|
+
if (/^\d{2}-\d{2}-\d{4}$/.test(raw)) {
|
|
30
|
+
const [dd, mm, yyyy] = raw.split('-');
|
|
31
|
+
return `${yyyy}-${mm}-${dd}`;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
// Formato DD/MM/YYYY
|
|
35
|
+
if (/^\d{2}\/\d{2}\/\d{4}$/.test(raw)) {
|
|
36
|
+
const [dd, mm, yyyy] = raw.split('/');
|
|
37
|
+
return `${yyyy}-${mm}-${dd}`;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
return raw;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Crea objeto de resolución SII
|
|
45
|
+
*
|
|
46
|
+
* @param {string} fecha - Fecha de resolución (se normaliza automáticamente)
|
|
47
|
+
* @param {number} [numero=0] - Número de resolución (0 para certificación)
|
|
48
|
+
* @returns {{ fch_resol: string, nro_resol: number }}
|
|
49
|
+
*/
|
|
50
|
+
function createResolucion(fecha, numero = 0) {
|
|
51
|
+
return {
|
|
52
|
+
fch_resol: normalizeFechaResolucion(fecha),
|
|
53
|
+
nro_resol: Number(numero) || 0,
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Crear resolución para ambiente de certificación
|
|
59
|
+
* La fecha debe ser la fecha de autorización en ambiente de certificación
|
|
60
|
+
*
|
|
61
|
+
* @param {string} fecha - Fecha de autorización
|
|
62
|
+
* @returns {{ fch_resol: string, nro_resol: number }}
|
|
63
|
+
*/
|
|
64
|
+
function createResolucionCertificacion(fecha) {
|
|
65
|
+
return createResolucion(fecha, 0); // Número 0 para certificación
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Crear resolución para ambiente de producción
|
|
70
|
+
*
|
|
71
|
+
* @param {string} fecha - Fecha de la resolución
|
|
72
|
+
* @param {number} numero - Número de la resolución
|
|
73
|
+
* @returns {{ fch_resol: string, nro_resol: number }}
|
|
74
|
+
*/
|
|
75
|
+
function createResolucionProduccion(fecha, numero) {
|
|
76
|
+
if (!numero || numero <= 0) {
|
|
77
|
+
throw new Error('Número de resolución requerido para producción');
|
|
78
|
+
}
|
|
79
|
+
return createResolucion(fecha, numero);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* Validar objeto de resolución
|
|
84
|
+
*
|
|
85
|
+
* @param {Object} resolucion - Objeto de resolución
|
|
86
|
+
* @param {boolean} [requireNumero=false] - Si se requiere número > 0
|
|
87
|
+
* @returns {{ valid: boolean, error?: string }}
|
|
88
|
+
*/
|
|
89
|
+
function validarResolucion(resolucion, requireNumero = false) {
|
|
90
|
+
if (!resolucion) {
|
|
91
|
+
return { valid: false, error: 'Resolución no proporcionada' };
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
if (!resolucion.fch_resol) {
|
|
95
|
+
return { valid: false, error: 'Fecha de resolución requerida' };
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
if (!/^\d{4}-\d{2}-\d{2}$/.test(resolucion.fch_resol)) {
|
|
99
|
+
return { valid: false, error: 'Formato de fecha inválido (debe ser YYYY-MM-DD)' };
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
if (requireNumero && (!resolucion.nro_resol || resolucion.nro_resol <= 0)) {
|
|
103
|
+
return { valid: false, error: 'Número de resolución requerido para producción' };
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
return { valid: true };
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
// ============================================
|
|
110
|
+
// EXPORTS
|
|
111
|
+
// ============================================
|
|
112
|
+
|
|
113
|
+
module.exports = {
|
|
114
|
+
normalizeFechaResolucion,
|
|
115
|
+
createResolucion,
|
|
116
|
+
createResolucionCertificacion,
|
|
117
|
+
createResolucionProduccion,
|
|
118
|
+
validarResolucion,
|
|
119
|
+
};
|