@devlas/dte-sii 2.14.2 → 2.15.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/cert/BoletaCert.js +5 -1
- package/cert/CertRunner.js +112 -9
- package/package.json +2 -2
package/cert/BoletaCert.js
CHANGED
|
@@ -447,7 +447,11 @@ class BoletaCert {
|
|
|
447
447
|
// 4. Enviar EnvioBOLETA
|
|
448
448
|
console.log('\nEnviando EnvioBOLETA al SII...');
|
|
449
449
|
const enviador = new EnviadorSII(this.certificado, this.ambiente);
|
|
450
|
-
const
|
|
450
|
+
// `let`, no `const`: la rama de duplicado más abajo lo reasigna. Con `const` eso lanzaba
|
|
451
|
+
// `TypeError: Assignment to constant variable` justo en el path que pretende recuperarse.
|
|
452
|
+
// Sin disparador confirmado: el duplicado real de EnvioBOLETA vuelve STATUS 99 (no 7), y
|
|
453
|
+
// los STATUS 7 observados son cvc-* de Libros, que EnviadorSII rutea a duplicado:false.
|
|
454
|
+
let resultadoBoleta = await enviador.enviarBoletaSoap(envioBoleta);
|
|
451
455
|
|
|
452
456
|
if (!resultadoBoleta.ok) {
|
|
453
457
|
if (resultadoBoleta.duplicado) {
|
package/cert/CertRunner.js
CHANGED
|
@@ -58,6 +58,32 @@ const { STEPS, emitProgress } = require('../utils/progress');
|
|
|
58
58
|
* @property {string} [sessionPath]
|
|
59
59
|
*/
|
|
60
60
|
|
|
61
|
+
/**
|
|
62
|
+
* CAF que consume cada set de certificación. **Fuente única de verdad**: la usan tanto los
|
|
63
|
+
* `ejecutarSet*` (como fallback cuando el set no trae `cafRequired`) como
|
|
64
|
+
* `precargarPlanDeCorrida()`. Antes estos números vivían inline en cada `ejecutarSet*` y los
|
|
65
|
+
* consumidores los duplicaban por su cuenta, así que un cambio acá los dejaba desincronizados
|
|
66
|
+
* y timbrando mal.
|
|
67
|
+
*
|
|
68
|
+
* El valor de `guia` es una función porque depende de cuántos casos traiga el set.
|
|
69
|
+
* El default de 3 sale de tráfico real: en 17 corridas registradas el set de guía vino
|
|
70
|
+
* siempre con `cafRequired: { 52: 3 }` y 3 casos. Antes la lib usaba 1, que timbraba de menos.
|
|
71
|
+
*/
|
|
72
|
+
const CAF_POR_SET = {
|
|
73
|
+
basico: { 33: 4, 56: 1, 61: 3 },
|
|
74
|
+
guia: (setData) => ({ 52: setData?.casos?.length || 3 }),
|
|
75
|
+
exenta: { 34: 3, 56: 1, 61: 4 },
|
|
76
|
+
compra: { 46: 1, 56: 1, 61: 1 },
|
|
77
|
+
};
|
|
78
|
+
|
|
79
|
+
/** Nombre de la estructura y su plan de CAF, en el orden en que se ejecutan los sets. */
|
|
80
|
+
const SETS_DE_CORRIDA = [
|
|
81
|
+
['basico', 'setBasico', CAF_POR_SET.basico],
|
|
82
|
+
['guia', 'setGuiaDespacho', CAF_POR_SET.guia],
|
|
83
|
+
['exenta', 'setFacturaExenta', CAF_POR_SET.exenta],
|
|
84
|
+
['compra', 'setFacturaCompra', CAF_POR_SET.compra],
|
|
85
|
+
];
|
|
86
|
+
|
|
61
87
|
class CertRunner {
|
|
62
88
|
/**
|
|
63
89
|
* @param {CertConfig} config - Configuración del runner
|
|
@@ -302,6 +328,45 @@ class CertRunner {
|
|
|
302
328
|
return this._cafsPrecargados;
|
|
303
329
|
}
|
|
304
330
|
|
|
331
|
+
/**
|
|
332
|
+
* Timbra por adelantado el total de folios de la corrida, armando el plan desde los sets ya
|
|
333
|
+
* cargados en `_estructuras`. Azúcar sobre `precargarCafsDeSets()` para que el consumidor no
|
|
334
|
+
* tenga que duplicar los `cafRequired` de cada set (ver `CAF_POR_SET`).
|
|
335
|
+
*
|
|
336
|
+
* Llamar después de `obtenerSets()` y antes del primer `ejecutarSet*`.
|
|
337
|
+
*
|
|
338
|
+
* ⚠️ **Hay que pasar los sets que realmente se van a ejecutar.** `_estructuras` puede traer
|
|
339
|
+
* los cuatro aunque la corrida ejecute uno solo, porque se rehidrata desde disco. Precargar
|
|
340
|
+
* todo en ese caso timbraría folios de sets que nadie va a emitir, y quedarían autorizados
|
|
341
|
+
* sin usar: exactamente el gatillo del racionamiento del SII que este método viene a evitar.
|
|
342
|
+
*
|
|
343
|
+
* @param {Array<'basico'|'guia'|'exenta'|'compra'>} [sets] - Sets de esta corrida.
|
|
344
|
+
* @returns {Promise<Object>} { 33: cafPath, 56: cafPath, ... }
|
|
345
|
+
*/
|
|
346
|
+
async precargarPlanDeCorrida(sets = ['basico', 'guia', 'exenta', 'compra']) {
|
|
347
|
+
if (!this._estructuras) {
|
|
348
|
+
throw new Error('Ejecutar obtenerSets() antes de precargarPlanDeCorrida().');
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
const pedidos = new Set(sets);
|
|
352
|
+
const planes = [];
|
|
353
|
+
for (const [nombre, claveEstructura, fallback] of SETS_DE_CORRIDA) {
|
|
354
|
+
if (!pedidos.has(nombre)) continue;
|
|
355
|
+
const setData = this._estructuras[claveEstructura];
|
|
356
|
+
if (!setData) continue; // set opcional ausente en esta corrida
|
|
357
|
+
planes.push(
|
|
358
|
+
setData.cafRequired || (typeof fallback === 'function' ? fallback(setData) : fallback)
|
|
359
|
+
);
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
if (!planes.length) {
|
|
363
|
+
console.log('\n Nada que precargar: ninguno de los sets pedidos está presente.');
|
|
364
|
+
return {};
|
|
365
|
+
}
|
|
366
|
+
return this.precargarCafsDeSets(planes);
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
|
|
305
370
|
/**
|
|
306
371
|
* Devuelve los CAF precargados que le tocan a un set.
|
|
307
372
|
*
|
|
@@ -803,7 +868,7 @@ class CertRunner {
|
|
|
803
868
|
|
|
804
869
|
async ejecutarSetBasico(casos) {
|
|
805
870
|
emitProgress(STEPS.SET_START, { set: 'basico' });
|
|
806
|
-
const r = await this._ejecutarSet(SetBasico, 'setBasico', 'basico',
|
|
871
|
+
const r = await this._ejecutarSet(SetBasico, 'setBasico', 'basico', CAF_POR_SET.basico, 'basico', casos);
|
|
807
872
|
if (r.success) emitProgress(STEPS.SET_OK, { set: 'basico', trackId: r.trackId });
|
|
808
873
|
else emitProgress(STEPS.SET_ERROR, { set: 'basico', error: r.error });
|
|
809
874
|
return r;
|
|
@@ -812,7 +877,7 @@ class CertRunner {
|
|
|
812
877
|
async ejecutarSetGuia(casos) {
|
|
813
878
|
emitProgress(STEPS.SET_START, { set: 'guia' });
|
|
814
879
|
const r = await this._ejecutarSet(SetGuia, 'setGuiaDespacho', 'guia',
|
|
815
|
-
|
|
880
|
+
CAF_POR_SET.guia, 'guia', casos);
|
|
816
881
|
if (r.success) emitProgress(STEPS.SET_OK, { set: 'guia', trackId: r.trackId });
|
|
817
882
|
else emitProgress(STEPS.SET_ERROR, { set: 'guia', error: r.error });
|
|
818
883
|
return r;
|
|
@@ -820,7 +885,7 @@ class CertRunner {
|
|
|
820
885
|
|
|
821
886
|
async ejecutarSetExenta(casos) {
|
|
822
887
|
emitProgress(STEPS.SET_START, { set: 'exenta' });
|
|
823
|
-
const r = await this._ejecutarSet(SetExenta, 'setFacturaExenta', 'exenta',
|
|
888
|
+
const r = await this._ejecutarSet(SetExenta, 'setFacturaExenta', 'exenta', CAF_POR_SET.exenta, 'exenta', casos);
|
|
824
889
|
if (r.success) emitProgress(STEPS.SET_OK, { set: 'exenta', trackId: r.trackId });
|
|
825
890
|
else emitProgress(STEPS.SET_ERROR, { set: 'exenta', error: r.error });
|
|
826
891
|
return r;
|
|
@@ -828,7 +893,7 @@ class CertRunner {
|
|
|
828
893
|
|
|
829
894
|
async ejecutarSetCompra(casos) {
|
|
830
895
|
emitProgress(STEPS.SET_START, { set: 'compra' });
|
|
831
|
-
const r = await this._ejecutarSet(SetCompra, 'setFacturaCompra', 'compra',
|
|
896
|
+
const r = await this._ejecutarSet(SetCompra, 'setFacturaCompra', 'compra', CAF_POR_SET.compra, 'compra', casos);
|
|
832
897
|
if (r.success) emitProgress(STEPS.SET_OK, { set: 'compra', trackId: r.trackId });
|
|
833
898
|
else emitProgress(STEPS.SET_ERROR, { set: 'compra', error: r.error });
|
|
834
899
|
return r;
|
|
@@ -3944,12 +4009,43 @@ class CertRunner {
|
|
|
3944
4009
|
const listaParaDeclarar = estadoResp.includes('P90');
|
|
3945
4010
|
const estadoMatch = estadoResp.match(/"(P\d+)"/);
|
|
3946
4011
|
const listaVacia = /\/\/OK\[0,\[\]/.test(estadoResp);
|
|
3947
|
-
const estado = listaParaDeclarar ? 'P90'
|
|
3948
|
-
: estadoMatch ? estadoMatch[1]
|
|
3949
|
-
: listaVacia ? 'sin_estado_sok_pendiente'
|
|
3950
|
-
: 'desconocido';
|
|
3951
4012
|
|
|
3952
|
-
|
|
4013
|
+
// ⚠️ La consulta pregunta puntualmente por el estado 90, así que una lista vacía
|
|
4014
|
+
// significa "no está en P90" — y eso pasa en DOS situaciones opuestas:
|
|
4015
|
+
// a) todavía no llegó (el SII no emitió el SOK del set), o
|
|
4016
|
+
// b) YA lo pasó: declaró cumplimiento y avanzó a P91.
|
|
4017
|
+
//
|
|
4018
|
+
// Darlas por (a) siempre hacía que, después de una declaración exitosa, se
|
|
4019
|
+
// informara "esperando aprobación del SII (SOK)". La pantalla le pedía al usuario
|
|
4020
|
+
// esperar una etapa que ya había superado (visto el 17/08/2026, RUT 78441936-3:
|
|
4021
|
+
// declaró a las 14:01 con DECLARACION EFECTUADA y a las 14:5x seguía diciendo que
|
|
4022
|
+
// faltaba el SOK).
|
|
4023
|
+
//
|
|
4024
|
+
// Se desempata preguntando por el 91, que es el estado al que mueve
|
|
4025
|
+
// `autorizarEmpresaBolProd`.
|
|
4026
|
+
let estado = listaParaDeclarar ? 'P90'
|
|
4027
|
+
: estadoMatch ? estadoMatch[1]
|
|
4028
|
+
: listaVacia ? 'sin_estado_sok_pendiente'
|
|
4029
|
+
: 'desconocido';
|
|
4030
|
+
let yaDeclarada = false;
|
|
4031
|
+
|
|
4032
|
+
if (estado === 'sin_estado_sok_pendiente') {
|
|
4033
|
+
try {
|
|
4034
|
+
const resp91 = await gwtPost(
|
|
4035
|
+
`7|0|7|${CBE_BASE}|${CBE_POLICY}|${CBE_SVC}|obtenerEstadoAutorizaEmp|java.lang.Integer/3438268394|java.lang.String/2004016611|${dvUp}|1|2|3|4|4|5|6|5|6|5|${rutNum}|7|5|91|0|`
|
|
4036
|
+
);
|
|
4037
|
+
if (resp91.includes('P91')) {
|
|
4038
|
+
estado = 'P91';
|
|
4039
|
+
yaDeclarada = true;
|
|
4040
|
+
}
|
|
4041
|
+
} catch (e) {
|
|
4042
|
+
// Si la segunda consulta falla se conserva la lectura original: es un dato de
|
|
4043
|
+
// más, no una dependencia.
|
|
4044
|
+
console.log(` [!] No se pudo desempatar el estado con P91: ${e.message}`);
|
|
4045
|
+
}
|
|
4046
|
+
}
|
|
4047
|
+
|
|
4048
|
+
return { success: true, inscrita: true, listaParaDeclarar, estado, yaDeclarada };
|
|
3953
4049
|
}
|
|
3954
4050
|
|
|
3955
4051
|
async completarDeclaracionBoletaPortal({
|
|
@@ -3973,6 +4069,13 @@ class CertRunner {
|
|
|
3973
4069
|
if (!estadoActual.inscrita) {
|
|
3974
4070
|
return { success: false, error: 'No se pudo obtener representante vigente (facade CBE)' };
|
|
3975
4071
|
}
|
|
4072
|
+
// Ya declaró y avanzó a P91: la etapa está cumplida, no pendiente. Sin este caso,
|
|
4073
|
+
// reintentar la declaración sobre una empresa ya declarada devolvía `pendingSok` y
|
|
4074
|
+
// la etapa quedaba "esperando" para siempre, esperando un SOK que ya había llegado
|
|
4075
|
+
// y ya se había usado.
|
|
4076
|
+
if (estadoActual.yaDeclarada) {
|
|
4077
|
+
return { success: true, yaDeclarada: true, mensaje: 'La declaración de cumplimiento ya estaba efectuada (P91)' };
|
|
4078
|
+
}
|
|
3976
4079
|
if (!estadoActual.listaParaDeclarar) {
|
|
3977
4080
|
const legible = estadoActual.estado === 'sin_estado_sok_pendiente'
|
|
3978
4081
|
? 'sin estado registrado (SOK aún no emitido)'
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@devlas/dte-sii",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.15.0",
|
|
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",
|
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
"author": "Devlas SpA <ti@devlas.cl> (https://devlas.cl)",
|
|
9
9
|
"homepage": "https://github.com/devlas-cl/dte-sii",
|
|
10
10
|
"scripts": {
|
|
11
|
-
"test": "node test/pfx.test.js && node test/folios-consolidados.test.js && node test/c14n-apostrofe.test.js"
|
|
11
|
+
"test": "node test/pfx.test.js && node test/folios-consolidados.test.js && node test/c14n-apostrofe.test.js && node test/precargar-plan-corrida.test.js"
|
|
12
12
|
},
|
|
13
13
|
"repository": {
|
|
14
14
|
"type": "git",
|