@devlas/dte-sii 2.12.17 → 2.12.19
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/DTE.js +4 -0
- package/SiiCertificacion.js +105 -23
- package/SiiPortalAuth.js +262 -14
- package/package.json +1 -1
package/DTE.js
CHANGED
|
@@ -102,8 +102,12 @@ class DTE {
|
|
|
102
102
|
};
|
|
103
103
|
|
|
104
104
|
if (d.referencia) {
|
|
105
|
+
// Orden fijo exigido por el XSD del SII: NroLinRef, TpoDocRef, FolioRef, FchRef, CodRef, RazonRef.
|
|
105
106
|
resultado.Referencia = {
|
|
106
107
|
NroLinRef: d.referencia.NroLinRef || 1,
|
|
108
|
+
...(d.referencia.TpoDocRef != null ? { TpoDocRef: d.referencia.TpoDocRef } : {}),
|
|
109
|
+
...(d.referencia.FolioRef != null ? { FolioRef: d.referencia.FolioRef } : {}),
|
|
110
|
+
...(d.referencia.FchRef ? { FchRef: d.referencia.FchRef } : {}),
|
|
107
111
|
CodRef: d.referencia.CodRef || d.referencia.codigo,
|
|
108
112
|
RazonRef: d.referencia.RazonRef || d.referencia.razon,
|
|
109
113
|
};
|
package/SiiCertificacion.js
CHANGED
|
@@ -1194,17 +1194,51 @@ class SiiCertificacion {
|
|
|
1194
1194
|
}
|
|
1195
1195
|
|
|
1196
1196
|
/**
|
|
1197
|
-
*
|
|
1197
|
+
* Guarda un HTML de debug del flujo de declaración de cumplimiento.
|
|
1198
|
+
* @private
|
|
1199
|
+
*/
|
|
1200
|
+
_saveDeclaracionDebug(filename, body) {
|
|
1201
|
+
try {
|
|
1202
|
+
const fs = require('fs');
|
|
1203
|
+
const path = require('path');
|
|
1204
|
+
const debugDir = process.env.SII_DEBUG_DIR || path.join(__dirname, '../../debug/cert-v2');
|
|
1205
|
+
if (!fs.existsSync(debugDir)) fs.mkdirSync(debugDir, { recursive: true });
|
|
1206
|
+
fs.writeFileSync(path.join(debugDir, filename), body || '', 'utf8');
|
|
1207
|
+
} catch (_e) { /* debug best-effort, no bloquear el flujo real */ }
|
|
1208
|
+
}
|
|
1209
|
+
|
|
1210
|
+
/**
|
|
1211
|
+
* Declara cumplimiento de requisitos (etapa final).
|
|
1212
|
+
*
|
|
1213
|
+
* Es un wizard de 4 pasos en el portal SII, no uno solo:
|
|
1214
|
+
* 1. GET pe_avance7 → formulario "ingrese RUT de la empresa"
|
|
1215
|
+
* 2. POST pe_avance8 (RUT/DV) → pantalla intermedia con hidden OK_MSG=S
|
|
1216
|
+
* 3. POST pe_avance8 (OK_MSG=S) → formulario real: 8 checkboxes de compromisos,
|
|
1217
|
+
* action="pe_avance9"
|
|
1218
|
+
* 4. POST pe_avance9 (checkboxes) → declaración efectuada (acción legal/irreversible)
|
|
1219
|
+
*
|
|
1220
|
+
* La versión anterior de este método solo hacía el paso 2 y devolvía `success: true`
|
|
1221
|
+
* si el texto no calzaba con el regex de bloqueo — pero el resultado del paso 2 es
|
|
1222
|
+
* SIEMPRE la pantalla intermedia del paso 3, nunca una confirmación real. Eso producía
|
|
1223
|
+
* un falso positivo: la declaración jurada real (paso 4) nunca se enviaba al SII, pero
|
|
1224
|
+
* quedaba registrada como completada en la base de datos del caller.
|
|
1225
|
+
*
|
|
1198
1226
|
* @returns {Promise<Object>} Resultado de la declaración
|
|
1199
1227
|
*/
|
|
1200
1228
|
async declararCumplimiento() {
|
|
1229
|
+
const RE_BLOQUEO = /no ha finalizado su certificaci|no podr[aá] efectuar la Declaraci/i;
|
|
1230
|
+
const extraerBloqueo = (texto) => {
|
|
1231
|
+
const frases = texto.split(/(?<=\.)\s+/);
|
|
1232
|
+
return (frases.find(f => RE_BLOQUEO.test(f)) || '').trim() || null;
|
|
1233
|
+
};
|
|
1234
|
+
|
|
1201
1235
|
try {
|
|
1202
|
-
// 1
|
|
1236
|
+
// Paso 1: acceder a la página de declarar cumplimiento
|
|
1203
1237
|
let response = await this.session.ensureSession('/cvc_cgi/dte/pe_avance7');
|
|
1204
1238
|
response = await this._handleRepresentacionPage(response);
|
|
1205
1239
|
|
|
1206
|
-
// 2
|
|
1207
|
-
|
|
1240
|
+
// Paso 2: enviar RUT/DV — primer submit del wizard
|
|
1241
|
+
let formResponse = await this.session.submitForm(
|
|
1208
1242
|
'/cvc_cgi/dte/pe_avance8',
|
|
1209
1243
|
{
|
|
1210
1244
|
RUT_EMP: this.rutEmpresa,
|
|
@@ -1214,30 +1248,78 @@ class SiiCertificacion {
|
|
|
1214
1248
|
'https://maullin.sii.cl/cvc_cgi/dte/pe_avance7'
|
|
1215
1249
|
);
|
|
1216
1250
|
|
|
1217
|
-
|
|
1251
|
+
let body = formResponse.body || '';
|
|
1252
|
+
this._saveDeclaracionDebug('pe_avance8-paso2.html', body);
|
|
1218
1253
|
|
|
1219
|
-
// El chequeo
|
|
1220
|
-
//
|
|
1221
|
-
|
|
1222
|
-
|
|
1223
|
-
|
|
1224
|
-
|
|
1225
|
-
const texto = limpiarTextoPortal(body);
|
|
1254
|
+
// El chequeo de bloqueo (SII no aprobó aún las muestras impresas) puede aparecer en
|
|
1255
|
+
// cualquiera de los pasos — se revisa después de cada submit.
|
|
1256
|
+
let texto = limpiarTextoPortal(body);
|
|
1257
|
+
if (RE_BLOQUEO.test(texto)) {
|
|
1258
|
+
return { success: false, bloqueado: true, mensaje: extraerBloqueo(texto), rawHtml: body };
|
|
1259
|
+
}
|
|
1226
1260
|
|
|
1227
|
-
|
|
1228
|
-
|
|
1261
|
+
// Paso 3: la respuesta del paso 2 es una pantalla intermedia con hidden OK_MSG=S —
|
|
1262
|
+
// hay que reenviarla para llegar al formulario real de checkboxes (action=pe_avance9).
|
|
1263
|
+
const hiddenPaso2 = SiiSession.extractInputValues(body);
|
|
1264
|
+
if (hiddenPaso2.OK_MSG) {
|
|
1265
|
+
formResponse = await this.session.submitForm(
|
|
1266
|
+
'/cvc_cgi/dte/pe_avance8',
|
|
1267
|
+
{ ...hiddenPaso2, Aceptar: 'Continuar con la Declaración' },
|
|
1268
|
+
'https://maullin.sii.cl/cvc_cgi/dte/pe_avance8'
|
|
1269
|
+
);
|
|
1270
|
+
body = formResponse.body || '';
|
|
1271
|
+
this._saveDeclaracionDebug('pe_avance8-paso3.html', body);
|
|
1229
1272
|
|
|
1230
|
-
|
|
1231
|
-
|
|
1232
|
-
|
|
1233
|
-
|
|
1234
|
-
|
|
1235
|
-
|
|
1236
|
-
|
|
1273
|
+
texto = limpiarTextoPortal(body);
|
|
1274
|
+
if (RE_BLOQUEO.test(texto)) {
|
|
1275
|
+
return { success: false, bloqueado: true, mensaje: extraerBloqueo(texto), rawHtml: body };
|
|
1276
|
+
}
|
|
1277
|
+
}
|
|
1278
|
+
|
|
1279
|
+
// Paso 4: formulario final de checkboxes de compromisos (action=pe_avance9).
|
|
1280
|
+
// Es la declaración jurada real — marcar todos los OPC* y confirmar.
|
|
1281
|
+
const formAction = SiiSession.extractFormAction(body);
|
|
1282
|
+
if (formAction && formAction.includes('pe_avance9')) {
|
|
1283
|
+
const hiddenPaso3 = SiiSession.extractInputValues(body);
|
|
1284
|
+
const opcFields = {};
|
|
1285
|
+
for (const key of Object.keys(hiddenPaso3)) {
|
|
1286
|
+
if (/^OPC\d+$/.test(key)) opcFields[key] = 'S';
|
|
1287
|
+
}
|
|
1288
|
+
|
|
1289
|
+
formResponse = await this.session.submitForm(
|
|
1290
|
+
formAction,
|
|
1291
|
+
{ ...hiddenPaso3, ...opcFields, CONFIRMAR: 'Confirmar Declaración' },
|
|
1292
|
+
'https://maullin.sii.cl/cvc_cgi/dte/pe_avance8'
|
|
1293
|
+
);
|
|
1294
|
+
body = formResponse.body || '';
|
|
1295
|
+
this._saveDeclaracionDebug('pe_avance9-final.html', body);
|
|
1296
|
+
|
|
1297
|
+
texto = limpiarTextoPortal(body);
|
|
1298
|
+
if (RE_BLOQUEO.test(texto)) {
|
|
1299
|
+
return { success: false, bloqueado: true, mensaje: extraerBloqueo(texto), rawHtml: body };
|
|
1300
|
+
}
|
|
1301
|
+
}
|
|
1302
|
+
|
|
1303
|
+
// Verificación de éxito real: si la respuesta final TODAVÍA muestra el formulario de
|
|
1304
|
+
// checkboxes (OPC1) o el de ingreso de RUT inicial, el submit no se procesó como se
|
|
1305
|
+
// esperaba (ej. faltó un campo obligatorio) — no asumir éxito solo por ausencia de bloqueo.
|
|
1306
|
+
const siguePendiente = /NAME="OPC1"|name="RUT_EMP"[^>]*type=\s*text/i.test(body);
|
|
1307
|
+
if (siguePendiente) {
|
|
1308
|
+
return {
|
|
1309
|
+
success: false,
|
|
1310
|
+
bloqueado: false,
|
|
1311
|
+
mensaje: 'El SII no confirmó la declaración de cumplimiento — la respuesta final no coincide con lo esperado. Revisar el HTML de debug (pe_avance9-final.html) manualmente.',
|
|
1312
|
+
rawHtml: body,
|
|
1313
|
+
};
|
|
1314
|
+
}
|
|
1315
|
+
|
|
1316
|
+
const mensaje = (texto.split(/(?<=\.)\s+/).find(f =>
|
|
1317
|
+
/declaraci[oó]n de cumplimiento|ha finalizado|certificad[ao]|efectuada/i.test(f)
|
|
1318
|
+
) || '').trim() || null;
|
|
1237
1319
|
|
|
1238
1320
|
return {
|
|
1239
|
-
success:
|
|
1240
|
-
bloqueado,
|
|
1321
|
+
success: true,
|
|
1322
|
+
bloqueado: false,
|
|
1241
1323
|
mensaje,
|
|
1242
1324
|
rawHtml: body,
|
|
1243
1325
|
};
|
package/SiiPortalAuth.js
CHANGED
|
@@ -241,9 +241,13 @@ class SiiPortalAuth {
|
|
|
241
241
|
res.on('end', () => {
|
|
242
242
|
const buf = Buffer.concat(chunks);
|
|
243
243
|
const ct = res.headers['content-type'] || '';
|
|
244
|
-
//
|
|
244
|
+
// La mayoría de hosts *.sii.cl sirven páginas ISO-8859-1 sin declarar charset — pero
|
|
245
|
+
// algunos servicios más nuevos (ej. complementoscvui) sí declaran "charset=UTF-8"
|
|
246
|
+
// explícito, y hay que respetarlo o se corrompen tildes/ñ (RaÃces, Común, etc.).
|
|
245
247
|
const isSiiHost = url.hostname.endsWith('.sii.cl');
|
|
246
|
-
const encoding =
|
|
248
|
+
const encoding = /charset=utf-?8/i.test(ct)
|
|
249
|
+
? 'utf8'
|
|
250
|
+
: (isSiiHost || /iso-8859|latin-1|windows-1252/i.test(ct)) ? 'latin1' : 'utf8';
|
|
247
251
|
resolve({ status: res.statusCode, headers: res.headers, body: buf.toString(encoding), cookieJar });
|
|
248
252
|
});
|
|
249
253
|
});
|
|
@@ -611,15 +615,26 @@ if (!fs.existsSync(SESSION_CACHE_PATH)) {
|
|
|
611
615
|
};
|
|
612
616
|
}
|
|
613
617
|
|
|
614
|
-
// ─── Consemitidos (www4.sii.cl/consemitidosinternetui)
|
|
618
|
+
// ─── Consemitidos (www4.sii.cl/consemitidosinternetui, o www4c.sii.cl en certificación) ─────
|
|
619
|
+
|
|
620
|
+
/**
|
|
621
|
+
* Host del portal RCV/consemitidos según ambiente. `www4c.sii.cl` es el equivalente de
|
|
622
|
+
* certificación de `www4.sii.cl` — sin este mapeo, todo `_callConsemitidos` apuntaba siempre a
|
|
623
|
+
* producción sin importar el ambiente real del comercio (DTEs de certificación jamás aparecían).
|
|
624
|
+
* @private
|
|
625
|
+
*/
|
|
626
|
+
_hostConsemitidos(ambiente) {
|
|
627
|
+
return ambiente === 'certificacion' ? 'www4c.sii.cl' : 'www4.sii.cl';
|
|
628
|
+
}
|
|
615
629
|
|
|
616
630
|
/**
|
|
617
631
|
* Navega a consemitidosinternetui para obtener el TOKEN de sesión.
|
|
618
632
|
* El TOKEN es el mismo valor que CSESSIONID y va como conversationId en el body.
|
|
619
633
|
* @private
|
|
620
634
|
*/
|
|
621
|
-
async _obtenerTokenConsemitidos(cookieJar) {
|
|
622
|
-
|
|
635
|
+
async _obtenerTokenConsemitidos(cookieJar, ambiente = 'produccion') {
|
|
636
|
+
const host = this._hostConsemitidos(ambiente);
|
|
637
|
+
await this._request(`https://${host}/consemitidosinternetui/`, { cookieJar });
|
|
623
638
|
const token = cookieJar['TOKEN'] || cookieJar['CSESSIONID'];
|
|
624
639
|
if (!token) {
|
|
625
640
|
throw new Error('SiiPortalAuth: no se pudo obtener TOKEN de sesión de consemitidosinternetui');
|
|
@@ -631,7 +646,8 @@ if (!fs.existsSync(SESSION_CACHE_PATH)) {
|
|
|
631
646
|
* Llama a un endpoint JSON de la API consemitidosinternetui.
|
|
632
647
|
* @private
|
|
633
648
|
*/
|
|
634
|
-
async _callConsemitidos(method, data, token, cookieJar) {
|
|
649
|
+
async _callConsemitidos(method, data, token, cookieJar, ambiente = 'produccion') {
|
|
650
|
+
const host = this._hostConsemitidos(ambiente);
|
|
635
651
|
const body = JSON.stringify({
|
|
636
652
|
metaData: {
|
|
637
653
|
namespace: `cl.sii.sdi.lob.diii.consemitidos.data.api.interfaces.FacadeService/${method}`,
|
|
@@ -642,7 +658,7 @@ if (!fs.existsSync(SESSION_CACHE_PATH)) {
|
|
|
642
658
|
data,
|
|
643
659
|
});
|
|
644
660
|
const res = await this._request(
|
|
645
|
-
`https
|
|
661
|
+
`https://${host}/consemitidosinternetui/services/data/facadeService/${method}`,
|
|
646
662
|
{
|
|
647
663
|
method: 'POST',
|
|
648
664
|
body,
|
|
@@ -650,8 +666,8 @@ if (!fs.existsSync(SESSION_CACHE_PATH)) {
|
|
|
650
666
|
headers: {
|
|
651
667
|
'Content-Type': 'application/json',
|
|
652
668
|
'Accept': 'application/json, text/plain, */*',
|
|
653
|
-
'Origin':
|
|
654
|
-
'Referer':
|
|
669
|
+
'Origin': `https://${host}`,
|
|
670
|
+
'Referer': `https://${host}/consemitidosinternetui/`,
|
|
655
671
|
},
|
|
656
672
|
}
|
|
657
673
|
);
|
|
@@ -663,18 +679,19 @@ if (!fs.existsSync(SESSION_CACHE_PATH)) {
|
|
|
663
679
|
}
|
|
664
680
|
|
|
665
681
|
/**
|
|
666
|
-
* Obtiene el detalle de DTEs emitidos o recibidos desde
|
|
682
|
+
* Obtiene el detalle de DTEs emitidos o recibidos desde el portal RCV del SII.
|
|
667
683
|
*
|
|
668
684
|
* @param {string} rut - RUT sin DV (ej: "12345678")
|
|
669
685
|
* @param {string} dv - DV (ej: "K")
|
|
670
686
|
* @param {string} periodo - Período YYYY-MM (ej: "2026-05")
|
|
671
687
|
* @param {number} operacion - 1 = compras / recibidos, 2 = ventas / emitidos
|
|
672
688
|
* @param {Object} [cookieJar] - Sesión ya autenticada (opcional)
|
|
689
|
+
* @param {string} [ambiente] - 'produccion' (www4.sii.cl, default) o 'certificacion' (www4c.sii.cl)
|
|
673
690
|
* @returns {Promise<{ resumen: Array, detalles: Array }>}
|
|
674
691
|
*/
|
|
675
|
-
async obtenerDetalleDtes(rut, dv, periodo, operacion = 2, cookieJar = null) {
|
|
692
|
+
async obtenerDetalleDtes(rut, dv, periodo, operacion = 2, cookieJar = null, ambiente = 'produccion') {
|
|
676
693
|
const jar = cookieJar || await this.autenticar();
|
|
677
|
-
const token = await this._obtenerTokenConsemitidos(jar);
|
|
694
|
+
const token = await this._obtenerTokenConsemitidos(jar, ambiente);
|
|
678
695
|
|
|
679
696
|
// Mapeo de convención interna → convención SII:
|
|
680
697
|
// interno: 1 = compras/recibidos, 2 = ventas/emitidos
|
|
@@ -688,7 +705,7 @@ if (!fs.existsSync(SESSION_CACHE_PATH)) {
|
|
|
688
705
|
rutContribuyente: rut,
|
|
689
706
|
dvContribuyente: dv,
|
|
690
707
|
operacion: siiOperacion,
|
|
691
|
-
}, token, jar);
|
|
708
|
+
}, token, jar, ambiente);
|
|
692
709
|
|
|
693
710
|
const resumen = resumenResp.data?.resumenDte ?? [];
|
|
694
711
|
if (resumen.length === 0) return { resumen: [], detalles: [] };
|
|
@@ -708,7 +725,7 @@ if (!fs.existsSync(SESSION_CACHE_PATH)) {
|
|
|
708
725
|
operacion: siiOperacion,
|
|
709
726
|
derrCodigo: String(t.tipoDoc),
|
|
710
727
|
refNCD: '0',
|
|
711
|
-
}, token, jar);
|
|
728
|
+
}, token, jar, ambiente);
|
|
712
729
|
const items = resp.dataResp?.detalles ?? [];
|
|
713
730
|
// Completar tipoDoc y tipoDocDesc desde el resumen si no vienen en el detalle
|
|
714
731
|
return items.map((d) => ({
|
|
@@ -722,6 +739,237 @@ if (!fs.existsSync(SESSION_CACHE_PATH)) {
|
|
|
722
739
|
return { resumen, detalles: detallesArr.flat() };
|
|
723
740
|
}
|
|
724
741
|
|
|
742
|
+
// ─── Complementos CV (www4.sii.cl/complementoscvui) — reclasificación de compras ──────────
|
|
743
|
+
// Reverse-engineered desde un HAR capturado navegando el portal SII: al abrir una compra
|
|
744
|
+
// recibida en la pestaña "Registro > Compra" y usar "Cambiar tipo de compra", el browser
|
|
745
|
+
// llama a estos 3 endpoints. El conversationId que exige el servidor es el mismo TOKEN de
|
|
746
|
+
// sesión que usa consemitidos/consdcvinternetui (cookie TOKEN/CSESSIONID) — se obtiene
|
|
747
|
+
// visitando consdcvinternetui/ una vez.
|
|
748
|
+
|
|
749
|
+
/**
|
|
750
|
+
* Token de sesión para complementoscvui — mismo mecanismo que _obtenerTokenConsemitidos
|
|
751
|
+
* pero navegando consdcvinternetui/ (la SPA "Registro de Compras y Ventas"), que es donde
|
|
752
|
+
* se originó el conversationId observado en el HAR.
|
|
753
|
+
* @private
|
|
754
|
+
*/
|
|
755
|
+
async _obtenerTokenPortalCV(cookieJar, ambiente = 'produccion') {
|
|
756
|
+
const host = this._hostConsemitidos(ambiente);
|
|
757
|
+
await this._request(`https://${host}/consdcvinternetui/`, { cookieJar });
|
|
758
|
+
const token = cookieJar['TOKEN'] || cookieJar['CSESSIONID'];
|
|
759
|
+
if (!token) {
|
|
760
|
+
throw new Error('SiiPortalAuth: no se pudo obtener TOKEN de sesión de consdcvinternetui');
|
|
761
|
+
}
|
|
762
|
+
return token;
|
|
763
|
+
}
|
|
764
|
+
|
|
765
|
+
/**
|
|
766
|
+
* Llama a un endpoint JSON de la API complementoscvui (FacadeServiceCompCompra).
|
|
767
|
+
* @private
|
|
768
|
+
*/
|
|
769
|
+
async _callComplementoscvui(method, data, token, cookieJar, ambiente = 'produccion') {
|
|
770
|
+
const host = this._hostConsemitidos(ambiente);
|
|
771
|
+
const body = JSON.stringify({
|
|
772
|
+
metaData: {
|
|
773
|
+
namespace: `cl.sii.sdi.lob.diii.dcv.data.api.interfaces.compcompra.FacadeServiceCompCompra/${method}`,
|
|
774
|
+
conversationId: token,
|
|
775
|
+
transactionId: '1',
|
|
776
|
+
page: { pageSize: 1, pageIndex: 1 },
|
|
777
|
+
},
|
|
778
|
+
data,
|
|
779
|
+
});
|
|
780
|
+
const res = await this._request(
|
|
781
|
+
`https://${host}/complementoscvui/services/data/facadeServiceCompCompraService/${method}`,
|
|
782
|
+
{
|
|
783
|
+
method: 'POST',
|
|
784
|
+
body,
|
|
785
|
+
cookieJar,
|
|
786
|
+
headers: {
|
|
787
|
+
'Content-Type': 'application/json',
|
|
788
|
+
'Accept': 'application/json, text/plain, */*',
|
|
789
|
+
'Origin': `https://${host}`,
|
|
790
|
+
'Referer': `https://${host}/complementoscvui/`,
|
|
791
|
+
},
|
|
792
|
+
}
|
|
793
|
+
);
|
|
794
|
+
try {
|
|
795
|
+
return JSON.parse(res.body);
|
|
796
|
+
} catch {
|
|
797
|
+
throw new Error(`SiiPortalAuth: respuesta no-JSON de ${method}: ${res.body.slice(0, 300)}`);
|
|
798
|
+
}
|
|
799
|
+
}
|
|
800
|
+
|
|
801
|
+
/**
|
|
802
|
+
* Lista las categorías de "tipo de compra" que el SII permite declarar sobre un documento
|
|
803
|
+
* recibido (Del Giro, Supermercados, Bienes Raíces, Activo Fijo, IVA Uso Común,
|
|
804
|
+
* IVA no Recuperable, No Corresp. Incluir). `intercambiable: true` = el usuario puede
|
|
805
|
+
* cambiarla; las demás son asignadas automáticamente por el SII según el tipo de documento.
|
|
806
|
+
*
|
|
807
|
+
* @param {Object} [cookieJar] - Sesión ya autenticada (opcional)
|
|
808
|
+
* @param {string} [ambiente] - 'produccion' (default) o 'certificacion'
|
|
809
|
+
* @returns {Promise<Array<{ id: string, descripcion: string, intercambiable: boolean }>>}
|
|
810
|
+
*/
|
|
811
|
+
async obtenerTiposTransaccionCompra(cookieJar = null, ambiente = 'produccion') {
|
|
812
|
+
const jar = cookieJar || await this.autenticar();
|
|
813
|
+
const token = await this._obtenerTokenPortalCV(jar, ambiente);
|
|
814
|
+
const resp = await this._callComplementoscvui('obtieneTiposTransaccionCompra', null, token, jar, ambiente);
|
|
815
|
+
return resp.data ?? [];
|
|
816
|
+
}
|
|
817
|
+
|
|
818
|
+
/**
|
|
819
|
+
* Detalle completo de un documento de compra tal como lo tiene registrado el SII
|
|
820
|
+
* (incluye `det_tipo_transaccion`, la clasificación de tipo de compra vigente).
|
|
821
|
+
*
|
|
822
|
+
* @param {Object} params
|
|
823
|
+
* @param {string} params.rut - RUT del contribuyente (receptor), sin DV
|
|
824
|
+
* @param {string} params.dv
|
|
825
|
+
* @param {number|string} params.tipoDocumento - Código SII (33=Factura, 46=Factura de Compra, etc.)
|
|
826
|
+
* @param {number|string} params.numeroDocumento - Folio
|
|
827
|
+
* @param {string} params.periodoTributario - "AAAAMM" (ej: "202607")
|
|
828
|
+
* @param {string} params.rutContraparte - RUT del emisor, sin DV
|
|
829
|
+
* @param {string} params.dvContraparte
|
|
830
|
+
* @param {Object} [cookieJar]
|
|
831
|
+
* @param {string} [ambiente]
|
|
832
|
+
*/
|
|
833
|
+
async obtenerDetalleDocumentoCompra(params, cookieJar = null, ambiente = 'produccion') {
|
|
834
|
+
const jar = cookieJar || await this.autenticar();
|
|
835
|
+
const token = await this._obtenerTokenPortalCV(jar, ambiente);
|
|
836
|
+
const resp = await this._callComplementoscvui('obtieneDetalleDocumento', {
|
|
837
|
+
rut: params.rut, dv: params.dv, operacion: 'COMPRA',
|
|
838
|
+
tipoDocumento: String(params.tipoDocumento), numeroDocumento: String(params.numeroDocumento),
|
|
839
|
+
periodoTributario: params.periodoTributario,
|
|
840
|
+
rutContraparte: params.rutContraparte, dvContraparte: params.dvContraparte,
|
|
841
|
+
}, token, jar, ambiente);
|
|
842
|
+
return resp.data ?? null;
|
|
843
|
+
}
|
|
844
|
+
|
|
845
|
+
/**
|
|
846
|
+
* Cambia el "tipo de compra" declarado ante el SII para un documento recibido — la misma
|
|
847
|
+
* acción que hace el portal web al usar "Cambiar tipo de compra" en la pestaña Registro.
|
|
848
|
+
* Lanza si el SII no confirma con `{ data: "OK" }`.
|
|
849
|
+
*
|
|
850
|
+
* @param {Object} params - Mismos campos que obtenerDetalleDocumentoCompra más:
|
|
851
|
+
* @param {number|string} params.tipoCompra - id de obtenerTiposTransaccionCompra (ej: "4" = Activo Fijo)
|
|
852
|
+
* @param {Object} [cookieJar]
|
|
853
|
+
* @param {string} [ambiente]
|
|
854
|
+
* @returns {Promise<true>}
|
|
855
|
+
*/
|
|
856
|
+
async cambiarTipoCompra(params, cookieJar = null, ambiente = 'produccion') {
|
|
857
|
+
const jar = cookieJar || await this.autenticar();
|
|
858
|
+
const token = await this._obtenerTokenPortalCV(jar, ambiente);
|
|
859
|
+
const resp = await this._callComplementoscvui('cambiaTipoCompra', {
|
|
860
|
+
rut: params.rut, dv: params.dv, operacion: 'COMPRA',
|
|
861
|
+
tipoDocumento: String(params.tipoDocumento), numeroDocumento: String(params.numeroDocumento),
|
|
862
|
+
periodoTributario: params.periodoTributario, tipoCompra: String(params.tipoCompra),
|
|
863
|
+
rutContraparte: params.rutContraparte, dvContraparte: params.dvContraparte,
|
|
864
|
+
}, token, jar, ambiente);
|
|
865
|
+
if (resp.data !== 'OK') {
|
|
866
|
+
throw new Error(`SiiPortalAuth: cambiaTipoCompra no confirmó OK — respuesta: ${JSON.stringify(resp)}`);
|
|
867
|
+
}
|
|
868
|
+
return true;
|
|
869
|
+
}
|
|
870
|
+
|
|
871
|
+
/**
|
|
872
|
+
* Mueve parte del IVA de un documento de "Recuperable" a "Uso Común" (o el inverso).
|
|
873
|
+
* `ivaComun`/`ivaRec` son los montos finales de cada bucket tras el cambio (no un delta) —
|
|
874
|
+
* así los captura el portal: al mover todo a Uso Común, ivaRec queda en "" y ivaComun con
|
|
875
|
+
* el monto total; al revertir, ivaComun queda en "0" e ivaRec recupera el monto total.
|
|
876
|
+
*
|
|
877
|
+
* @param {Object} params - Mismos campos identificadores que cambiarTipoCompra más:
|
|
878
|
+
* @param {string|number} params.ivaComun - Monto final en el bucket "Uso Común"
|
|
879
|
+
* @param {string|number} params.ivaRec - Monto final en el bucket "Recuperable"
|
|
880
|
+
* @param {Object} [cookieJar]
|
|
881
|
+
* @param {string} [ambiente]
|
|
882
|
+
* @returns {Promise<true>}
|
|
883
|
+
*/
|
|
884
|
+
async cambiarIvaRecuperableAUsoComun(params, cookieJar = null, ambiente = 'produccion') {
|
|
885
|
+
return this._cambiarIva('cambiaIvaRec2Comun', params, cookieJar, ambiente);
|
|
886
|
+
}
|
|
887
|
+
|
|
888
|
+
/** Inverso de cambiarIvaRecuperableAUsoComun — mueve el IVA de vuelta a "Recuperable". */
|
|
889
|
+
async cambiarIvaUsoComunARecuperable(params, cookieJar = null, ambiente = 'produccion') {
|
|
890
|
+
return this._cambiarIva('cambiaIvaComun2Recuperable', params, cookieJar, ambiente);
|
|
891
|
+
}
|
|
892
|
+
|
|
893
|
+
/** @private */
|
|
894
|
+
async _cambiarIva(metodo, params, cookieJar, ambiente) {
|
|
895
|
+
const jar = cookieJar || await this.autenticar();
|
|
896
|
+
const token = await this._obtenerTokenPortalCV(jar, ambiente);
|
|
897
|
+
const resp = await this._callComplementoscvui(metodo, {
|
|
898
|
+
rut: params.rut, dv: params.dv, operacion: 'COMPRA',
|
|
899
|
+
tipoDocumento: String(params.tipoDocumento), numeroDocumento: String(params.numeroDocumento),
|
|
900
|
+
periodoTributario: params.periodoTributario,
|
|
901
|
+
ivaComun: String(params.ivaComun ?? ''), ivaRec: String(params.ivaRec ?? ''),
|
|
902
|
+
rutContraparte: params.rutContraparte, dvContraparte: params.dvContraparte,
|
|
903
|
+
}, token, jar, ambiente);
|
|
904
|
+
if (resp.data !== 'OK') {
|
|
905
|
+
throw new Error(`SiiPortalAuth: ${metodo} no confirmó OK — respuesta: ${JSON.stringify(resp)}`);
|
|
906
|
+
}
|
|
907
|
+
return true;
|
|
908
|
+
}
|
|
909
|
+
|
|
910
|
+
// ─── consdcvinternetui — resumen "oficial" del RCV, filtrado por estado contable ───────────
|
|
911
|
+
// Distinto de consemitidos: expone el estado real (Registro/No Incluir/Pendiente/Reclamado)
|
|
912
|
+
// mediante `estadoContab`, en vez de listar todo sin distinguir si el SII lo consideró
|
|
913
|
+
// parte del registro tributario oficial. getDetalleCompra/getDetalleVenta de este mismo
|
|
914
|
+
// servicio exigen reCAPTCHA v3 (tokenRecaptcha) — no se automatizan aquí. getResumen no lo
|
|
915
|
+
// exige y sí se puede llamar.
|
|
916
|
+
|
|
917
|
+
/**
|
|
918
|
+
* Resumen "oficial" del RCV (consdcvinternetui) para un período, filtrado por estado
|
|
919
|
+
* contable — a diferencia de `obtenerDetalleDtes` (consemitidos), permite saber con certeza
|
|
920
|
+
* si un documento quedó en el Registro tributario oficial o fue excluido por el SII.
|
|
921
|
+
*
|
|
922
|
+
* @param {string} rutEmisor - RUT del contribuyente autenticado (sin DV)
|
|
923
|
+
* @param {string} dvEmisor
|
|
924
|
+
* @param {string} periodo - "AAAA-MM" (se convierte a "AAAAMM")
|
|
925
|
+
* @param {'COMPRA'|'VENTA'} operacion
|
|
926
|
+
* @param {'REGISTRO'|'PENDIENTE'|'NO_INCLUIR'|'RECLAMADO'} [estadoContab] - default 'REGISTRO'
|
|
927
|
+
* @param {Object} [cookieJar]
|
|
928
|
+
* @param {string} [ambiente]
|
|
929
|
+
* @returns {Promise<{ resumen: Array, cabecera: Object|null }>}
|
|
930
|
+
*/
|
|
931
|
+
async obtenerResumenRegistro(rutEmisor, dvEmisor, periodo, operacion, estadoContab = 'REGISTRO', cookieJar = null, ambiente = 'produccion') {
|
|
932
|
+
const jar = cookieJar || await this.autenticar();
|
|
933
|
+
const token = await this._obtenerTokenPortalCV(jar, ambiente);
|
|
934
|
+
const host = this._hostConsemitidos(ambiente);
|
|
935
|
+
const body = JSON.stringify({
|
|
936
|
+
metaData: {
|
|
937
|
+
namespace: 'cl.sii.sdi.lob.diii.consdcv.data.api.interfaces.FacadeService/getResumen',
|
|
938
|
+
conversationId: token,
|
|
939
|
+
transactionId: crypto.randomUUID(),
|
|
940
|
+
page: null,
|
|
941
|
+
},
|
|
942
|
+
data: {
|
|
943
|
+
rutEmisor, dvEmisor,
|
|
944
|
+
ptributario: periodo.replace('-', ''),
|
|
945
|
+
estadoContab,
|
|
946
|
+
operacion,
|
|
947
|
+
busquedaInicial: true,
|
|
948
|
+
},
|
|
949
|
+
});
|
|
950
|
+
const res = await this._request(
|
|
951
|
+
`https://${host}/consdcvinternetui/services/data/facadeService/getResumen`,
|
|
952
|
+
{
|
|
953
|
+
method: 'POST',
|
|
954
|
+
body,
|
|
955
|
+
cookieJar: jar,
|
|
956
|
+
headers: {
|
|
957
|
+
'Content-Type': 'application/json',
|
|
958
|
+
'Accept': 'application/json, text/plain, */*',
|
|
959
|
+
'Origin': `https://${host}`,
|
|
960
|
+
'Referer': `https://${host}/consdcvinternetui/`,
|
|
961
|
+
},
|
|
962
|
+
}
|
|
963
|
+
);
|
|
964
|
+
let parsed;
|
|
965
|
+
try {
|
|
966
|
+
parsed = JSON.parse(res.body);
|
|
967
|
+
} catch {
|
|
968
|
+
throw new Error(`SiiPortalAuth: respuesta no-JSON de getResumen (consdcvinternetui): ${res.body.slice(0, 300)}`);
|
|
969
|
+
}
|
|
970
|
+
return { resumen: parsed.data ?? [], cabecera: parsed.dataCabecera ?? null };
|
|
971
|
+
}
|
|
972
|
+
|
|
725
973
|
/**
|
|
726
974
|
* Retorna las cookies de sesión activas para un PFX dado, en formato string para SiiSession.
|
|
727
975
|
* Busca primero en el registry en memoria, luego en el caché a disco.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@devlas/dte-sii",
|
|
3
|
-
"version": "2.12.
|
|
3
|
+
"version": "2.12.19",
|
|
4
4
|
"description": "Facturación y boletas electrónicas para el SII de Chile. Genera, timbra, firma y envía DTEs, libros electrónicos y automatiza la certificación.",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"types": "dte-sii.d.ts",
|