@gnpdev/rpa-tools 1.0.9 → 1.0.13

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/README.md CHANGED
@@ -68,7 +68,7 @@ Recupera de forma segura el `usuario`, `password` e `idUsuario` de una aplicaci
68
68
  import { logger, rpa } from './lib/rpa.js';
69
69
 
70
70
 
71
- logger.info('Optener creddenciales');
71
+ logger.info('Obtener credenciales');
72
72
  const credentials = await rpa.getCredentials('Portal CRM');
73
73
 
74
74
  if (credentials) {
@@ -81,7 +81,7 @@ if (credentials) {
81
81
  Activa el watcher pasando la instancia de `page`. El bot detectará cambios en la tabla de la DB para tomar screenshots automáticamente.
82
82
 
83
83
  ```javascript
84
- const { chromium } = require('playwright');
84
+ import { chromium } from 'playwright';
85
85
  import { rpa } from './lib/rpa.js';
86
86
 
87
87
  const browser = await chromium.launch();
@@ -90,8 +90,57 @@ const page = await browser.newPage();
90
90
  // Inicia el monitoreo (polling cada 3s por defecto)
91
91
  rpa.watchDebugFlag(page);
92
92
 
93
+
93
94
  // Al terminar el proceso del bot
94
- // rpa.destroy();
95
+ rpa.destroy();
96
+
97
+ puedes incluir
98
+
99
+ browser.on('disconnected', () => rpa.destroy());
100
+ ```
101
+
102
+ ### 4. Captura Automática de Errores (Screenshot + Trace)
103
+ Captura el estado completo del bot cuando ocurre una excepción: Screenshot (.webp), Trace de Playwright (.zip) y registro en la base de datos `bots.tb_error_bots`.
104
+
105
+ ```javascript
106
+ import { chromium } from 'playwright';
107
+ import { rpa, logger } from './lib/rpa.js';
108
+
109
+ const browser = await chromium.launch({ headless: true });
110
+ const context = await browser.newContext();
111
+ const page = await context.newPage();
112
+
113
+ // ── Activar tracing ANTES de que empiece la sesión ──────────────────────
114
+ // screenshots:true → captura visual en cada snapshot
115
+ // snapshots:true → guarda el DOM y estado de red en cada paso
116
+ await context.tracing.start({
117
+ screenshots: true,
118
+ snapshots: true,
119
+ });
120
+
121
+ let currentStep = 'Inicio';
122
+
123
+ try {
124
+ // Lógica de tu bot...
125
+ await page.goto('https://example.com');
126
+
127
+ currentStep = 'Login';
128
+ await login(page);
129
+
130
+ currentStep = 'Procesar Datos';
131
+ await page.click('#boton-inexistente');
132
+ } catch (err) {
133
+ // Captura automática de evidencia
134
+ const { errorId } = await rpa.captureError({
135
+ page,
136
+ context,
137
+ err,
138
+ step: currentStep
139
+ });
140
+
141
+ logger.error({ errorId, step: currentStep }, 'Fallo crítico capturado');
142
+ throw err;
143
+ }
95
144
  ```
96
145
 
97
146
  ## Variables de Entorno Sugeridas
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@gnpdev/rpa-tools",
3
- "version": "1.0.9",
3
+ "version": "1.0.13",
4
4
  "description": "Libreria para logs y screenshot de bots",
5
5
  "author": "Sergio Antonio Trujillo del Valle",
6
6
  "main": "src/index.js",
@@ -19,6 +19,7 @@
19
19
  "dependencies": {
20
20
  "minio": "^8.0.7",
21
21
  "pino": "^10.3.1",
22
+ "pino-pretty": "^13.1.3",
22
23
  "sharp": "^0.34.5"
23
24
  },
24
25
  "devDependencies": {
@@ -26,7 +27,6 @@
26
27
  "@commitlint/config-conventional": "^20.5.0",
27
28
  "@release-it/conventional-changelog": "^10.0.6",
28
29
  "husky": "^9.1.7",
29
- "pino-pretty": "^13.1.3",
30
30
  "release-it": "^19.2.4"
31
31
  },
32
32
  "scripts": {
@@ -0,0 +1,149 @@
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
+
149
+ module.exports = { captureError };
package/src/index.d.ts CHANGED
@@ -57,6 +57,22 @@ export interface RpaTools {
57
57
  */
58
58
  watchDebugFlag: (page: Page, pollMs?: number) => void;
59
59
 
60
+ /**
61
+ * Recupera credenciales de una aplicación desde la base de datos.
62
+ * @param nombre - Nombre de la aplicación (ej. 'Portal CRM')
63
+ */
64
+ getCredentials: (nombre: string) => Promise<{ usuario: string; password: string; idUsuario: string } | null>;
65
+
66
+ /**
67
+ * Captura screenshot, trace y registra error en base de datos.
68
+ */
69
+ captureError: (params: {
70
+ page: Page;
71
+ context: any;
72
+ err: Error;
73
+ step?: string;
74
+ }) => Promise<{ errorId: number | null; screenshotKey: string | null; traceKey: string | null }>;
75
+
60
76
  /** Detiene todos los intervalos. Llamar siempre al cerrar el bot. */
61
77
  destroy: () => void;
62
78
  }
package/src/index.js CHANGED
@@ -3,6 +3,7 @@ const { createLogger } = require('./logger');
3
3
  const { createMinioClient, ensureBucket } = require('./storage');
4
4
  const { ScreenshotWatcher } = require('./watcher');
5
5
  const { CredentialManager } = require('./credentials');
6
+ const { captureError } = require('./errorCapture');
6
7
 
7
8
  /**
8
9
  * Factory principal de la librería.
@@ -49,6 +50,7 @@ async function createRpaTools(opts = {}) {
49
50
  logger,
50
51
  watchDebugFlag: (page, pollMs) => watcher.watch(page, pollMs),
51
52
  getCredentials: (nombre) => credentials.getAppCredentials(nombre),
53
+ captureError: ({ page, context, err, step }) => captureError({ page, context, err, step, botId, pool: db, minioClient, bucket, logger }),
52
54
  destroy: () => watcher.destroy(),
53
55
  };
54
56
  }