@devlas/dte-sii 2.9.8 → 2.12.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@devlas/dte-sii",
3
- "version": "2.9.8",
3
+ "version": "2.12.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",
@@ -29,14 +29,13 @@
29
29
  "node": ">=18.0.0"
30
30
  },
31
31
  "dependencies": {
32
- "@sparticuz/chromium": "^147.0.0",
33
32
  "@xmldom/xmldom": "^0.8.11",
34
33
  "bwip-js": "^4.8.0",
35
34
  "dotenv": "^17.3.1",
36
35
  "fast-xml-parser": "^5.3.3",
37
36
  "got": "^11.8.6",
38
37
  "node-forge": "^1.3.3",
39
- "puppeteer": "^24.12.1",
38
+ "pdf-lib": "^1.17.1",
40
39
  "soap": "^1.6.3",
41
40
  "xml-c14n": "^0.0.6",
42
41
  "xml-crypto": "^6.1.2"
@@ -0,0 +1,174 @@
1
+ 'use strict';
2
+ /**
3
+ * test-qdetestlibro.js
4
+ *
5
+ * Prueba los endpoints del portal SII para consultar libros electrónicos:
6
+ * 1. QEstLibro — lista todos los libros del año y extrae los Códigos
7
+ * 2. QDetEstLibro — detalle de cada envío (TrackId, estado, etc.)
8
+ *
9
+ * Uso:
10
+ * node test-qdetestlibro.js [year=2026] [periodo=2026-04]
11
+ *
12
+ * Requiere que la sesión exista o la crea automáticamente.
13
+ */
14
+
15
+ const path = require('path');
16
+ const fs = require('fs');
17
+ const SiiCertificacion = require('./SiiCertificacion.js');
18
+
19
+ // ─── Configuración ────────────────────────────────────────────────────────────
20
+ const PFX_PATH = path.resolve(__dirname, '../devlas-cloud-api-node/secret/19925444-8.pfx');
21
+ const PFX_PASS = 'Lsr12345';
22
+ const RUT_EMPRESA = '78206276';
23
+ const DV_EMPRESA = 'K';
24
+ const SESSION_PATH = path.resolve(__dirname, '../devlas-cloud-api-node/debug/cert-v2/session.json');
25
+
26
+ const YEAR = process.argv.find(a => a.startsWith('year='))?.split('=')[1] || '2026';
27
+ const PERIODO = process.argv.find(a => a.startsWith('periodo='))?.split('=')[1] || null; // null = todos
28
+
29
+ // ─── Main ─────────────────────────────────────────────────────────────────────
30
+ async function main() {
31
+ console.log('=== test-qdetestlibro.js ===');
32
+ console.log('PFX:', PFX_PATH);
33
+ console.log('RUT:', `${RUT_EMPRESA}-${DV_EMPRESA}`);
34
+ console.log('Year:', YEAR, '| Filtro período:', PERIODO || '(todos)');
35
+
36
+ const cert = new SiiCertificacion({
37
+ pfxPath: PFX_PATH,
38
+ pfxPassword: PFX_PASS,
39
+ rutEmpresa: RUT_EMPRESA,
40
+ dvEmpresa: DV_EMPRESA,
41
+ sessionPath: SESSION_PATH,
42
+ });
43
+
44
+ // ── 1. Asegurar sesión portal en subsistema /cgi_dte/UPL/ ─────────────────
45
+ // DTEauth?7 es la página del formulario de búsqueda de libros.
46
+ // CSESSIONID es el token de sesión del portal SII. Lo necesitamos válido.
47
+ console.log('\n[1] Autenticando en /cgi_dte/UPL/DTEauth?7...');
48
+ // Parchar request para capturar Set-Cookie crudos de DTEauth?7
49
+ const origRequest = cert.session.request.bind(cert.session);
50
+ let lastRawHeaders = null;
51
+ cert.session.request = async function(url, opts) {
52
+ const resp = await origRequest(url, opts);
53
+ if (url.includes('DTEauth')) {
54
+ lastRawHeaders = resp.headers;
55
+ }
56
+ return resp;
57
+ };
58
+ const authResp = await cert.session.ensureSession('/cgi_dte/UPL/DTEauth?7');
59
+ cert.session.request = origRequest; // restaurar
60
+ console.log(' Status DTEauth:', authResp?.status);
61
+ if (lastRawHeaders) {
62
+ const sc = lastRawHeaders['set-cookie'];
63
+ console.log(' Set-Cookie DTEauth?7:', JSON.stringify(sc));
64
+ }
65
+ console.log(' CSESSIONID en jar:', cert.session.cookieJar?.match(/CSESSIONID=[^;]+/)?.[0] || '(none)');
66
+ fs.writeFileSync(path.join(__dirname, 'test-output', 'dteauth7.html'), authResp?.body || '', 'latin1');
67
+
68
+ // ── TEST DIRECTO: llamar QDetEstLibro sin QEstLibro de por medio ──────────
69
+ console.log('\n[TEST DIRECTO] QDetEstLibro sin pasar por QEstLibro...');
70
+ const urlDetDirect = `https://maullin.sii.cl/cgi_dte/UPL/QDetEstLibro` +
71
+ `?Codigo=COMPRA-772220&rutC=78206276&dvC=K&periodo=2026-04`;
72
+ const directResp = await cert.session.request(urlDetDirect);
73
+ console.log(' Status:', directResp.status);
74
+ const directBody = directResp.body;
75
+ if (directBody.includes('SESION HA EXPIRADO')) {
76
+ console.warn(' [WARN] TAMBIÉN falla sin QEstLibro — es el CSESSIONID o la sesión');
77
+ } else if (directBody.includes('AUTORIZADO')) {
78
+ console.warn(' [WARN] Error de autorización');
79
+ console.log(directBody.slice(0, 500));
80
+ } else {
81
+ console.log(' OK! Funciona directamente');
82
+ console.log(directBody.slice(0, 1000));
83
+ }
84
+ fs.writeFileSync(path.join(__dirname, 'test-output', 'qdetestlibro-direct.html'), directBody, 'latin1');
85
+
86
+ // ── 2. Llamar QEstLibro ────────────────────────────────────────────────────
87
+ const urlLista = `https://maullin.sii.cl/cgi_dte/UPL/QEstLibro` +
88
+ `?rutCompany=${RUT_EMPRESA}&dvCompany=${DV_EMPRESA}&TrackId=&year=${YEAR}&month=00&tipo=TODOS`;
89
+
90
+ console.log('\n[2] GET', urlLista);
91
+ const listaResp = await cert.session.request(urlLista);
92
+ console.log(' Status:', listaResp.status);
93
+ console.log(' Set-Cookie headers:', listaResp.headers?.['set-cookie'] || '(none)');
94
+ console.log(' Cookies post-QEstLibro:', cert.session.cookieJar?.slice(0, 250) + '...');
95
+
96
+ if (listaResp.body.includes('SESION HA EXPIRADO')) {
97
+ console.error(' [ERR] Sesión expirada en QEstLibro — revisar ensureSession');
98
+ process.exit(1);
99
+ }
100
+
101
+ // Guardar HTML para inspección
102
+ const htmlListaPath = path.join(__dirname, 'test-output', 'qestlibro.html');
103
+ fs.mkdirSync(path.dirname(htmlListaPath), { recursive: true });
104
+ fs.writeFileSync(htmlListaPath, listaResp.body, 'latin1');
105
+ console.log(' HTML guardado en:', htmlListaPath);
106
+
107
+ // ── 3. Parsear la tabla: extraer Código → periodo → operación ──────────────
108
+ // El HTML tiene links tipo: QDetEstLibro?Codigo=VENTA-772219&rutC=...&periodo=2026-04
109
+ // href sin comillas: href=QDetEstLibro?Codigo=X&rutC=Y&dvC=Z&periodo=PPPP>Ver
110
+ // el > cierra el atributo, por eso lo excluimos del grupo de captura
111
+ const linkRegex = /QDetEstLibro\?Codigo=([^&"'\s>]+)&rutC=[^&"'\s>]+&dvC=[^&"'\s>]+&periodo=([^&"'\s>]+)/gi;
112
+ const codigos = {}; // { '2026-04': { VENTA: 'VENTA-772219', COMPRA: 'COMPRA-XXXXX' } }
113
+ let m;
114
+ while ((m = linkRegex.exec(listaResp.body)) !== null) {
115
+ const codigo = m[1];
116
+ const periodo = m[2];
117
+ if (PERIODO && periodo !== PERIODO) continue;
118
+ codigos[periodo] = codigos[periodo] || {};
119
+ const tipoMatch = /^(VENTA|COMPRA|GUIAS?)/i.exec(codigo);
120
+ const tipo = tipoMatch ? tipoMatch[1].toUpperCase() : codigo;
121
+ codigos[periodo][tipo] = codigo;
122
+ }
123
+
124
+ console.log('\n Códigos encontrados:');
125
+ if (Object.keys(codigos).length === 0) {
126
+ console.log(' (ninguno — revisar HTML en test-output/qestlibro.html)');
127
+ }
128
+ for (const [p, ops] of Object.entries(codigos)) {
129
+ for (const [op, cod] of Object.entries(ops)) {
130
+ console.log(` ${p} / ${op} → ${cod}`);
131
+ }
132
+ }
133
+
134
+ // ── 4. Llamar QDetEstLibro para cada código ────────────────────────────────
135
+ // Usamos las MISMAS cookies que obtuvimos de DTEauth?7 (paso 1).
136
+ // NO re-autenticamos: si DTEauth?7 usa tokens one-shot, un segundo call
137
+ // lo consumiría sin beneficio. Las cookies NETSCAPE_LIVEWIRE persisten.
138
+ for (const [periodo, ops] of Object.entries(codigos)) {
139
+ for (const [operacion, codigo] of Object.entries(ops)) {
140
+ const urlDet = `https://maullin.sii.cl/cgi_dte/UPL/QDetEstLibro` +
141
+ `?Codigo=${encodeURIComponent(codigo)}&rutC=${RUT_EMPRESA}&dvC=${DV_EMPRESA}&periodo=${periodo}`;
142
+
143
+ const refererQEstLibro = `https://maullin.sii.cl/cgi_dte/UPL/QEstLibro` +
144
+ `?rutCompany=${RUT_EMPRESA}&dvCompany=${DV_EMPRESA}&TrackId=&year=${YEAR}&month=00&tipo=TODOS`;
145
+
146
+ console.log(`\n[3] QDetEstLibro ${periodo} ${operacion} (${codigo})`);
147
+ console.log(' GET', urlDet);
148
+
149
+ const detResp = await cert.session.request(urlDet, {
150
+ headers: { Referer: refererQEstLibro },
151
+ });
152
+ console.log(' Status:', detResp.status);
153
+
154
+ const outPath = path.join(__dirname, 'test-output', `qdetestlibro-${periodo}-${operacion}.html`);
155
+ fs.writeFileSync(outPath, detResp.body, 'latin1');
156
+
157
+ if (detResp.body.includes('SESION HA EXPIRADO')) {
158
+ console.warn(' [WARN] Sesión expirada — revisar cookies / DTEauth');
159
+ console.log('\n--- BODY (500 chars) ---\n', detResp.body.slice(0, 500));
160
+ } else {
161
+ console.log(' HTML guardado en:', outPath);
162
+ console.log('\n--- BODY ---\n', detResp.body);
163
+ }
164
+ }
165
+ }
166
+
167
+ console.log('\n=== Fin ===');
168
+ }
169
+
170
+ main().catch(e => {
171
+ console.error('[FATAL]', e.message);
172
+ console.error(e.stack);
173
+ process.exit(1);
174
+ });
package/utils/progress.js CHANGED
@@ -56,7 +56,11 @@ const STEPS = {
56
56
  MUESTRAS_DONE: 'MUESTRAS_DONE',
57
57
  // Boleta electronica
58
58
  BOLETA_START: 'BOLETA_START',
59
+ BOLETA_SET_DOWNLOAD: 'BOLETA_SET_DOWNLOAD',
60
+ BOLETA_CAF: 'BOLETA_CAF',
59
61
  BOLETA_SENDING: 'BOLETA_SENDING',
62
+ BOLETA_SOAP_CHECK: 'BOLETA_SOAP_CHECK',
63
+ BOLETA_RCOF: 'BOLETA_RCOF',
60
64
  BOLETA_OK: 'BOLETA_OK',
61
65
  BOLETA_DECLARING: 'BOLETA_DECLARING',
62
66
  BOLETA_DONE: 'BOLETA_DONE',
package/utils/browser.js DELETED
@@ -1,79 +0,0 @@
1
- // Copyright (c) 2026 Devlas SpA — https://devlas.cl
2
- // Licencia MIT. Ver archivo LICENSE para mas detalles.
3
- /**
4
- * utils/browser.js
5
- *
6
- * Helper para lanzar Puppeteer con el Chromium correcto según el entorno:
7
- * - Producción / serverless (Railway, Lambda, etc.): usa @sparticuz/chromium
8
- * - Desarrollo local: usa el Chrome del sistema si @sparticuz/chromium no está disponible
9
- *
10
- * Uso:
11
- * const { launchBrowser } = require('./utils/browser')
12
- * const browser = await launchBrowser()
13
- */
14
-
15
- 'use strict';
16
-
17
- const puppeteer = require('puppeteer');
18
-
19
- /**
20
- * Devuelve las opciones de lanzamiento para puppeteer.launch() según el entorno.
21
- * @returns {Promise<import('puppeteer').LaunchOptions>}
22
- */
23
- async function getLaunchOptions() {
24
- // 1. @sparticuz/chromium — solo en Linux (Railway/serverless). En Windows entrega
25
- // un binario ELF que existe en disco pero no es ejecutable.
26
- if (process.platform !== 'win32') {
27
- try {
28
- const chromium = require('@sparticuz/chromium');
29
- const executablePath = await chromium.executablePath();
30
- if (executablePath) {
31
- return {
32
- args: chromium.args,
33
- defaultViewport: chromium.defaultViewport,
34
- executablePath,
35
- headless: chromium.headless ?? true,
36
- };
37
- }
38
- } catch {
39
- // No está disponible, continuar con fallback
40
- }
41
- }
42
-
43
- // 2. Ruta explícita por variable de entorno
44
- if (process.env.PUPPETEER_EXECUTABLE_PATH) {
45
- return {
46
- headless: true,
47
- args: ['--no-sandbox', '--disable-setuid-sandbox'],
48
- executablePath: process.env.PUPPETEER_EXECUTABLE_PATH,
49
- };
50
- }
51
-
52
- // 3. Chrome del sistema (rutas comunes Linux / Windows)
53
- const fs = require('fs');
54
- const SYSTEM_PATHS = [
55
- '/usr/bin/google-chrome-stable',
56
- '/usr/bin/google-chrome',
57
- '/usr/bin/chromium-browser',
58
- '/usr/bin/chromium',
59
- 'C:\\Program Files\\Google\\Chrome\\Application\\chrome.exe',
60
- 'C:\\Program Files (x86)\\Google\\Chrome\\Application\\chrome.exe',
61
- ];
62
- const executablePath = SYSTEM_PATHS.find(p => { try { return fs.existsSync(p) } catch { return false } });
63
- return {
64
- headless: true,
65
- args: ['--no-sandbox', '--disable-setuid-sandbox'],
66
- ...(executablePath ? { executablePath } : {}),
67
- };
68
- }
69
-
70
- /**
71
- * Lanza un browser Puppeteer con el Chromium correcto para el entorno actual.
72
- * @returns {Promise<import('puppeteer').Browser>}
73
- */
74
- async function launchBrowser() {
75
- const opts = await getLaunchOptions();
76
- return puppeteer.launch(opts);
77
- }
78
-
79
- module.exports = { launchBrowser, getLaunchOptions };