@gnpdev/rpa-tools 1.0.16 → 1.0.17

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": "@gnpdev/rpa-tools",
3
- "version": "1.0.16",
3
+ "version": "1.0.17",
4
4
  "description": "Libreria para logs y screenshot de bots",
5
5
  "author": "Sergio Antonio Trujillo del Valle",
6
6
  "main": "src/index.js",
@@ -1,149 +1,155 @@
1
- 'use strict';
2
- const fs = require('fs/promises');
3
- const path = require('path');
4
-
5
- /**
6
- * Sube un buffer o archivo local a MinIO y retorna el key generado.
7
- * @param {import('minio').Client} client
8
- * @param {string} bucket
9
- * @param {string} key
10
- * @param {Buffer|string} source - Buffer o ruta de archivo local
11
- * @param {string} contentType
12
- */
13
- async function uploadToMinio(client, bucket, key, source, contentType) {
14
- const buffer = typeof source === 'string'
15
- ? await fs.readFile(source)
16
- : source;
17
-
18
- await client.putObject(bucket, key, buffer, buffer.length, {
19
- 'Content-Type': contentType,
20
- });
21
-
22
- return key;
23
- }
24
-
25
- /**
26
- * Genera el prefijo base para los archivos de error en MinIO.
27
- * Estructura: errors/{botId}/{YYYY-MM-DD}/{ISO-timestamp}
28
- */
29
- function errorPrefix(botId) {
30
- const now = new Date();
31
- const date = now.toISOString().slice(0, 10);
32
- const ts = now.toISOString().replace(/[:.]/g, '-');
33
- return `errors/${botId}/${date}/${ts}`;
34
- }
35
-
36
- /**
37
- * Captura el estado completo cuando ocurre un error:
38
- * - Screenshot WebP de la página actual
39
- * - Trace de Playwright (.zip con video, red, consola, DOM snapshots)
40
- * - Registro en bots.tb_error_bots
41
- *
42
- * @param {object} p
43
- * @param {import('playwright').Page} p.page
44
- * @param {import('playwright').BrowserContext} p.context
45
- * @param {Error} p.err
46
- * @param {string} p.botId - UUID del bot
47
- * @param {import('pg').Pool} p.pool
48
- * @param {import('minio').Client} p.minioClient
49
- * @param {string} p.bucket
50
- * @param {import('pino').Logger} p.logger
51
- * @param {string} [p.step] - paso donde falló
52
- *
53
- * @returns {Promise<{ screenshotKey: string|null, traceKey: string|null, errorId: number|null }>}
54
- */
55
- async function captureError({ page, context, err, botId, pool, minioClient, bucket, logger, step }) {
56
- const prefix = errorPrefix(botId);
57
- const stepInfo = step ? { step } : {};
58
- const result = { screenshotKey: null, traceKey: null, errorId: null };
59
-
60
- // ── 1. Screenshot ────────────────────────────────────────────────────────
61
- try {
62
- const buffer = await page.screenshot({
63
- type: 'jpeg',
64
- quality: 90,
65
- fullPage: true,
66
- });
67
-
68
- let finalBuffer = buffer;
69
- let ext = 'jpg';
70
- let contentType = 'image/jpeg';
71
-
72
- try {
73
- const sharp = require('sharp');
74
- finalBuffer = await sharp(buffer).webp({ quality: 80 }).toBuffer();
75
- ext = 'webp';
76
- contentType = 'image/webp';
77
- } catch {
78
- // sharp no disponible, se sube en jpeg
79
- }
80
-
81
- result.screenshotKey = await uploadToMinio(
82
- minioClient, bucket,
83
- `${prefix}-error.${ext}`,
84
- finalBuffer,
85
- contentType
86
- );
87
-
88
- logger.info({ ...stepInfo, screenshotKey: result.screenshotKey }, 'Screenshot de error subido');
89
- } catch (screenshotErr) {
90
- logger.error({ err: screenshotErr }, 'No se pudo capturar screenshot de error');
91
- }
92
-
93
- // ── 2. Trace ─────────────────────────────────────────────────────────────
94
- const tracePath = path.join(require('os').tmpdir(), `trace-${botId}-${Date.now()}.zip`);
95
-
96
- try {
97
- await context.tracing.stop({ path: tracePath });
98
-
99
- result.traceKey = await uploadToMinio(
100
- minioClient, bucket,
101
- `${prefix}-trace.zip`,
102
- tracePath,
103
- 'application/zip'
104
- );
105
-
106
- logger.info({ ...stepInfo, traceKey: result.traceKey }, 'Trace de error subido');
107
- } catch (traceErr) {
108
- logger.error({ err: traceErr }, 'No se pudo capturar trace de error');
109
- } finally {
110
- await fs.unlink(tracePath).catch(() => {});
111
- }
112
-
113
- // ── 3. Registro en bots.tb_error_bots ───────────────────────────────────
114
- try {
115
- const { rows } = await pool.query(
116
- `INSERT INTO bots.tb_error_bots
117
- (bot_id, step, error_message, url, screenshot_key, trace_key)
118
- VALUES ($1::uuid, $2, $3, $4, $5, $6)
119
- RETURNING id`,
120
- [
121
- botId,
122
- step ?? null,
123
- err.message,
124
- page.url() ?? null,
125
- result.screenshotKey ?? null,
126
- result.traceKey ?? null,
127
- ]
128
- );
129
-
130
- result.errorId = rows[0].id;
131
- logger.info({ ...stepInfo, errorId: result.errorId }, 'Error registrado en BD');
132
- } catch (dbErr) {
133
- logger.error({ err: dbErr }, 'No se pudo registrar el error en BD');
134
- }
135
-
136
- // ── 4. Log estructurado final ────────────────────────────────────────────
137
- logger.error({
138
- ...stepInfo,
139
- err,
140
- url: page.url(),
141
- screenshotKey: result.screenshotKey,
142
- traceKey: result.traceKey,
143
- errorId: result.errorId,
144
- }, 'Error capturado en playground');
145
-
146
- return result;
147
- }
148
-
1
+ 'use strict';
2
+ const fs = require('fs/promises');
3
+ const path = require('path');
4
+
5
+ /**
6
+ * Sube un buffer o archivo local a MinIO y retorna el key generado.
7
+ * @param {import('minio').Client} client
8
+ * @param {string} bucket
9
+ * @param {string} key
10
+ * @param {Buffer|string} source - Buffer o ruta de archivo local
11
+ * @param {string} contentType
12
+ */
13
+ async function uploadToMinio(client, bucket, key, source, contentType) {
14
+ const buffer = typeof source === 'string'
15
+ ? await fs.readFile(source)
16
+ : source;
17
+
18
+ await client.putObject(bucket, key, buffer, buffer.length, {
19
+ 'Content-Type': contentType,
20
+ });
21
+
22
+ return key;
23
+ }
24
+
25
+ /**
26
+ * Genera el prefijo base para los archivos de error en MinIO.
27
+ * Estructura: errors/{botId}/{YYYY-MM-DD}/{ISO-timestamp}
28
+ */
29
+ function errorPrefix(botId) {
30
+ const now = new Date();
31
+ const date = now.toISOString().slice(0, 10);
32
+ const ts = now.toISOString().replace(/[:.]/g, '-');
33
+ return `errors/${botId}/${date}/${ts}`;
34
+ }
35
+
36
+ /**
37
+ * Captura el estado completo cuando ocurre un error:
38
+ * - Screenshot WebP de la página actual
39
+ * - Trace de Playwright (.zip con video, red, consola, DOM snapshots)
40
+ * - Registro en bots.tb_error_bots
41
+ *
42
+ * @param {object} p
43
+ * @param {import('playwright').Page} p.page
44
+ * @param {import('playwright').BrowserContext} p.context
45
+ * @param {Error} p.err
46
+ * @param {string} p.botId - UUID del bot
47
+ * @param {import('pg').Pool} p.pool
48
+ * @param {import('minio').Client} p.minioClient
49
+ * @param {string} p.bucket
50
+ * @param {import('pino').Logger} p.logger
51
+ * @param {string} [p.step] - paso donde falló
52
+ *
53
+ * @returns {Promise<{ screenshotKey: string|null, traceKey: string|null, errorId: number|null }>}
54
+ */
55
+ async function captureError({ page, context, err, botId, pool, minioClient, bucket, logger, step }) {
56
+ const prefix = errorPrefix(botId);
57
+ const stepInfo = step ? { step } : {};
58
+ const result = { screenshotKey: null, traceKey: null, errorId: null };
59
+
60
+ // ── 1. Screenshot ────────────────────────────────────────────────────────
61
+ try {
62
+ const buffer = await page.screenshot({
63
+ type: 'jpeg',
64
+ quality: 90,
65
+ fullPage: true,
66
+ });
67
+
68
+ let finalBuffer = buffer;
69
+ let ext = 'jpg';
70
+ let contentType = 'image/jpeg';
71
+
72
+ try {
73
+ const sharp = require('sharp');
74
+ finalBuffer = await sharp(buffer).webp({ quality: 80 }).toBuffer();
75
+ ext = 'webp';
76
+ contentType = 'image/webp';
77
+ } catch {
78
+ // sharp no disponible, se sube en jpeg
79
+ }
80
+
81
+ result.screenshotKey = await uploadToMinio(
82
+ minioClient, bucket,
83
+ `${prefix}-error.${ext}`,
84
+ finalBuffer,
85
+ contentType
86
+ );
87
+
88
+ logger.info({ ...stepInfo, screenshotKey: result.screenshotKey }, 'Screenshot de error subido');
89
+ } catch (screenshotErr) {
90
+ logger.error({ err: screenshotErr }, 'No se pudo capturar screenshot de error');
91
+ }
92
+
93
+ // ── 2. Trace ─────────────────────────────────────────────────────────────
94
+ const tracePath = path.join(require('os').tmpdir(), `trace-${botId}-${Date.now()}.zip`);
95
+
96
+ try {
97
+ if (context?.tracing) {
98
+ await context.tracing.stop({ path: tracePath });
99
+
100
+ result.traceKey = await uploadToMinio(
101
+ minioClient, bucket,
102
+ `${prefix}-trace.zip`,
103
+ tracePath,
104
+ 'application/zip'
105
+ );
106
+
107
+ logger.info({ ...stepInfo, traceKey: result.traceKey }, 'Trace de error subido');
108
+ } else {
109
+ logger.debug('Tracing no disponible (Puppeteer o no activado)');
110
+ }
111
+ } catch (traceErr) {
112
+ logger.error({ err: traceErr }, 'No se pudo capturar trace de error');
113
+ } finally {
114
+ if (context?.tracing) {
115
+ await fs.unlink(tracePath).catch(() => {});
116
+ }
117
+ }
118
+
119
+ // ── 3. Registro en bots.tb_error_bots ───────────────────────────────────
120
+ try {
121
+ const { rows } = await pool.query(
122
+ `INSERT INTO bots.tb_error_bots
123
+ (bot_id, step, error_message, url, screenshot_key, trace_key)
124
+ VALUES ($1::uuid, $2, $3, $4, $5, $6)
125
+ RETURNING id`,
126
+ [
127
+ botId,
128
+ step ?? null,
129
+ err.message,
130
+ page.url() ?? null,
131
+ result.screenshotKey ?? null,
132
+ result.traceKey ?? null,
133
+ ]
134
+ );
135
+
136
+ result.errorId = rows[0].id;
137
+ logger.info({ ...stepInfo, errorId: result.errorId }, 'Error registrado en BD');
138
+ } catch (dbErr) {
139
+ logger.error({ err: dbErr }, 'No se pudo registrar el error en BD');
140
+ }
141
+
142
+ // ── 4. Log estructurado final ────────────────────────────────────────────
143
+ logger.error({
144
+ ...stepInfo,
145
+ err,
146
+ url: page.url(),
147
+ screenshotKey: result.screenshotKey,
148
+ traceKey: result.traceKey,
149
+ errorId: result.errorId,
150
+ }, 'Error capturado en playground');
151
+
152
+ return result;
153
+ }
154
+
149
155
  module.exports = { captureError };
package/src/index.d.ts CHANGED
@@ -63,10 +63,10 @@ export interface RpaTools {
63
63
  /**
64
64
  * Inicia el polling que activa/desactiva screenshots
65
65
  * según bot_debug_config en la BD.
66
- * @param page - Objeto Page de Playwright
66
+ * @param page - Objeto Page de Playwright o Puppeteer
67
67
  * @param pollMs - Intervalo de polling en ms. Default: 3000
68
68
  */
69
- watchDebugFlag: (page: Page, pollMs?: number) => void;
69
+ watchDebugFlag: (page: any, pollMs?: number) => void;
70
70
 
71
71
  /**
72
72
  * Recupera credenciales de una aplicación desde la base de datos.
@@ -78,8 +78,8 @@ export interface RpaTools {
78
78
  * Captura screenshot, trace y registra error en base de datos.
79
79
  */
80
80
  captureError: (params: {
81
- page: Page;
82
- context: any;
81
+ page: any;
82
+ context?: any;
83
83
  err: Error;
84
84
  step?: string;
85
85
  }) => Promise<{ errorId: number | null; screenshotKey: string | null; traceKey: string | null }>;
package/src/index.js CHANGED
@@ -53,7 +53,7 @@ async function createRpaTools(opts = {}) {
53
53
  state,
54
54
  step: (name) => {
55
55
  state.currentStep = name;
56
- logger.info({ step: name }, 'Nuevo paso');
56
+ logger.info(name + ' - Nuevo paso');
57
57
  },
58
58
  watchDebugFlag: (page, pollMs) => watcher.watch(page, pollMs),
59
59
  getCredentials: (nombre) => credentials.getAppCredentials(nombre),
package/src/storage.js CHANGED
@@ -1,83 +1,101 @@
1
- 'use strict';
2
- const Minio = require('minio');
3
- const sharp = require('sharp');
4
-
5
- /**
6
- * Crea y retorna el cliente de MinIO.
7
- * @param {object} cfg
8
- * @param {string} cfg.endPoint
9
- * @param {number} [cfg.port=9000]
10
- * @param {boolean} [cfg.useSSL=false]
11
- * @param {string} cfg.accessKey
12
- * @param {string} cfg.secretKey
13
- */
14
- function createMinioClient(cfg) {
15
- return new Minio.Client({
16
- endPoint: cfg.endPoint,
17
- port: cfg.port ?? 9000,
18
- useSSL: cfg.useSSL ?? false,
19
- accessKey: cfg.accessKey,
20
- secretKey: cfg.secretKey,
21
- });
22
- }
23
-
24
- /**
25
- * Garantiza que el bucket exista; lo crea si no.
26
- * @param {import('minio').Client} client
27
- * @param {string} bucket
28
- * @param {string} [region='us-east-1']
29
- */
30
- async function ensureBucket(client, bucket, region = 'us-east-1') {
31
- const exists = await client.bucketExists(bucket);
32
- if (!exists) await client.makeBucket(bucket, region);
33
- }
34
-
35
- /**
36
- * Convierte un buffer de imagen a WebP usando sharp.
37
- * @param {Buffer} buffer - buffer fuente (jpeg/png)
38
- * @param {number} [quality=70]
39
- * @returns {Promise<Buffer>}
40
- */
41
- async function toWebp(buffer, quality = 70) {
42
- return sharp(buffer)
43
- .webp({ quality })
44
- .toBuffer();
45
- }
46
-
47
- /**
48
- * Sube un screenshot a MinIO.
49
- * Si format='webp' convierte el buffer antes de subir.
50
- * Ruta generada: {botId}/{YYYY-MM-DD}/{ISO-timestamp}.{ext}
51
- *
52
- * @param {import('minio').Client} client
53
- * @param {string} bucket
54
- * @param {string} botId
55
- * @param {Buffer} buffer - buffer raw de Playwright (jpeg)
56
- * @param {object} [opts]
57
- * @param {'webp'|'jpeg'} [opts.format='webp']
58
- * @param {number} [opts.quality=70] - calidad 1-100
59
- * @returns {Promise<string>} - object key en MinIO
60
- */
61
- async function uploadScreenshot(client, bucket, botId, buffer, opts = {}) {
62
- const { format = 'webp', quality = 70 } = opts;
63
-
64
- const finalBuffer = format === 'webp'
65
- ? await toWebp(buffer, quality)
66
- : buffer;
67
-
68
- const contentType = format === 'webp' ? 'image/webp' : 'image/jpeg';
69
- const ext = format === 'webp' ? 'webp' : 'jpg';
70
-
71
- const now = new Date();
72
- const date = now.toISOString().slice(0, 10); // 2025-06-01
73
- const ts = now.toISOString().replace(/[:.]/g, '-'); // 2025-06-01T12-30-00-000Z
74
- const key = `${botId}.${ext}`;
75
-
76
- await client.putObject(bucket, key, finalBuffer, finalBuffer.length, {
77
- 'Content-Type': contentType,
78
- });
79
-
80
- return key;
81
- }
82
-
1
+ 'use strict';
2
+ const Minio = require('minio');
3
+ const sharp = require('sharp');
4
+
5
+ /**
6
+ * Crea y retorna el cliente de MinIO.
7
+ * @param {object} cfg
8
+ * @param {string} cfg.endPoint
9
+ * @param {number} [cfg.port=9000]
10
+ * @param {boolean} [cfg.useSSL=false]
11
+ * @param {string} cfg.accessKey
12
+ * @param {string} cfg.secretKey
13
+ */
14
+ function createMinioClient(cfg) {
15
+ return new Minio.Client({
16
+ endPoint: cfg.endPoint,
17
+ port: cfg.port ?? 9000,
18
+ useSSL: cfg.useSSL ?? false,
19
+ accessKey: cfg.accessKey,
20
+ secretKey: cfg.secretKey,
21
+ });
22
+ }
23
+
24
+ /**
25
+ * Garantiza que el bucket exista; lo crea si no.
26
+ * @param {import('minio').Client} client
27
+ * @param {string} bucket
28
+ * @param {string} [region='us-east-1']
29
+ */
30
+ async function ensureBucket(client, bucket, region = 'us-east-1') {
31
+ const exists = await client.bucketExists(bucket);
32
+ if (!exists) await client.makeBucket(bucket, region);
33
+ }
34
+
35
+ /**
36
+ * Convierte un buffer de imagen a WebP usando sharp.
37
+ * @param {Buffer} buffer - buffer fuente (jpeg/png)
38
+ * @param {number} [quality=70]
39
+ * @returns {Promise<Buffer>}
40
+ */
41
+ async function toWebp(buffer, quality = 70) {
42
+ return sharp(buffer)
43
+ .webp({ quality })
44
+ .toBuffer();
45
+ }
46
+
47
+ /**
48
+ * Sube un screenshot a MinIO.
49
+ * Si format='webp' convierte el buffer antes de subir.
50
+ * Ruta generada: {botId}/{YYYY-MM-DD}/{ISO-timestamp}.{ext}
51
+ *
52
+ * @param {import('minio').Client} client
53
+ * @param {string} bucket
54
+ * @param {string} botId
55
+ * @param {Buffer} buffer - buffer raw de Playwright (jpeg)
56
+ * @param {object} [opts]
57
+ * @param {'webp'|'jpeg'} [opts.format='webp']
58
+ * @param {number} [opts.quality=70] - calidad 1-100
59
+ * @param {object} [opts.pool] - Pool de base de datos
60
+ * @param {object} [opts.logger] - Instancia de logger
61
+ * @returns {Promise<string>} - object key en MinIO
62
+ */
63
+ async function uploadScreenshot(client, bucket, botId, buffer, opts = {}) {
64
+ const { format = 'webp', quality = 70, pool, logger } = opts;
65
+
66
+ const finalBuffer = format === 'webp'
67
+ ? await toWebp(buffer, quality)
68
+ : buffer;
69
+
70
+ const contentType = format === 'webp' ? 'image/webp' : 'image/jpeg';
71
+ const ext = format === 'webp' ? 'webp' : 'jpg';
72
+ const key = `${botId}.${ext}`;
73
+
74
+ await client.putObject(bucket, key, finalBuffer, finalBuffer.length, {
75
+ 'Content-Type': contentType,
76
+ });
77
+
78
+ // ── 3. Registro en base de datos ─────────────────────────────────────────
79
+ if (pool) {
80
+ try {
81
+ await pool.query(
82
+ `UPDATE bots.tb_bots
83
+ SET url_screenshot = $1,
84
+ updated_at = NOW()
85
+ WHERE id = $2::uuid`,
86
+ [key, botId]
87
+ );
88
+
89
+ } catch (err) {
90
+ if (logger) {
91
+ logger.error({ err, botId, key }, 'No se pudo actualizar url_screenshot en BD');
92
+ } else {
93
+ console.error(`[Storage] Error BD (${botId}):`, err.message);
94
+ }
95
+ }
96
+ }
97
+
98
+ return key;
99
+ }
100
+
83
101
  module.exports = { createMinioClient, ensureBucket, uploadScreenshot };