@gnpdev/rpa-tools 1.0.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/README.md ADDED
@@ -0,0 +1,93 @@
1
+ # @gnpdev/rpa-tools
2
+
3
+ Herramientas de automatización para bots RPA: logging estructurado con **Pino**, almacenamiento en **MinIO** con optimización de imágenes (WebP) y monitoreo de flags en base de datos para capturas de pantalla en tiempo real con **Playwright**.
4
+
5
+ ## Requisitos
6
+
7
+ - **Node.js** v18+
8
+ - **pnpm** (recomendado)
9
+ - Instancia de **MinIO** activa.
10
+ - Base de datos **PostgreSQL**.
11
+
12
+ ## Instalación
13
+
14
+ ```bash
15
+ pnpm add @gnpdev/rpa-tools
16
+ ```
17
+
18
+ ## Configuración Centralizada (Best Practice)
19
+
20
+ Crea un archivo en tu proyecto (ej. `src/lib/rpa.js`) para inicializar y exportar las herramientas. Esto asegura que uses la misma instancia en toda la aplicación.
21
+
22
+ ```javascript
23
+ // src/lib/rpa.js
24
+ const { createRpaTools } = require('@gnpdev/rpa-tools');
25
+ const { Pool } = require('pg');
26
+
27
+ // 1. Configura tu pool de base de datos (usualmente ya lo tienes)
28
+ const pool = new Pool({
29
+ connectionString: process.env.DATABASE_URL
30
+ });
31
+
32
+ // 2. Inicializa las herramientas
33
+ async function initRpa() {
34
+ const rpa = await createRpaTools({
35
+ botId: 'BOT-VENTAS-01',
36
+ db: pool,
37
+ minio: {
38
+ endPoint: process.env.MINIO_ENDPOINT,
39
+ port: parseInt(process.env.MINIO_PORT || '9000'),
40
+ useSSL: process.env.MINIO_USE_SSL === 'true',
41
+ accessKey: process.env.MINIO_ACCESS_KEY,
42
+ secretKey: process.env.MINIO_SECRET_KEY,
43
+ bucket: 'rpa-screenshots'
44
+ },
45
+ log: {
46
+ level: process.env.NODE_ENV === 'development' ? 'debug' : 'info',
47
+ pretty: process.env.NODE_ENV === 'development'
48
+ }
49
+ });
50
+ return rpa;
51
+ }
52
+
53
+ module.exports = initRpa;
54
+ ```
55
+
56
+ ## Uso en la Aplicación
57
+
58
+ ### 1. Logging Estructurado
59
+ Usa el logger preconfigurado que ya incluye el `botId` en cada entrada.
60
+
61
+ ```javascript
62
+ const rpa = await initRpa();
63
+ rpa.logger.info({ orderId: '123' }, 'Procesando orden');
64
+ ```
65
+
66
+ ### 2. Capturas de Pantalla bajo demanda (Playwright)
67
+ Activa el watcher pasando la instancia de `page`. El bot detectará cambios en la tabla de la DB para tomar screenshots automáticamente.
68
+
69
+ ```javascript
70
+ const { chromium } = require('playwright');
71
+ const rpa = await initRpa();
72
+
73
+ const browser = await chromium.launch();
74
+ const page = await browser.newPage();
75
+
76
+ // Inicia el monitoreo (polling cada 3s por defecto)
77
+ rpa.watchDebugFlag(page);
78
+
79
+ // Al terminar el proceso del bot
80
+ // rpa.destroy();
81
+ ```
82
+
83
+ ## Variables de Entorno Sugeridas
84
+
85
+ ```env
86
+ MINIO_ENDPOINT=localhost
87
+ MINIO_PORT=9000
88
+ MINIO_USE_SSL=false
89
+ MINIO_ACCESS_KEY=admin
90
+ MINIO_SECRET_KEY=password
91
+ MINIO_BUCKET=rpa-screenshots
92
+ DATABASE_URL=postgres://user:pass@localhost:5432/db
93
+ ```
package/package.json ADDED
@@ -0,0 +1,27 @@
1
+ {
2
+ "name": "@gnpdev/rpa-tools",
3
+ "version": "1.0.0",
4
+ "description": "Libreria para logs y screenshot de bots",
5
+ "author": "Sergio Antonio Trujillo del Valle",
6
+ "main": "src/index.js",
7
+ "types": "src/index.d.ts",
8
+ "files": [
9
+ "src"
10
+ ],
11
+ "keywords": [
12
+ "rpa",
13
+ "playwright",
14
+ "minio",
15
+ "pino",
16
+ "screenshots"
17
+ ],
18
+ "license": "MIT",
19
+ "dependencies": {
20
+ "minio": "^8.0.7",
21
+ "pino": "^10.3.1",
22
+ "sharp": "^0.34.5"
23
+ },
24
+ "devDependencies": {
25
+ "pino-pretty": "^13.1.3"
26
+ }
27
+ }
package/src/index.d.ts ADDED
@@ -0,0 +1,75 @@
1
+ import type { Pool } from 'pg';
2
+ import type { Logger } from 'pino';
3
+ import type { Page } from 'playwright';
4
+
5
+ // ── Opciones de MinIO ────────────────────────────────────────────────────────
6
+ export interface MinioConfig {
7
+ endPoint: string;
8
+ port?: number;
9
+ useSSL?: boolean;
10
+ accessKey: string | any;
11
+ secretKey: string | any;
12
+ bucket?: string | any;
13
+ }
14
+
15
+ // ── Opciones de screenshot ───────────────────────────────────────────────────
16
+ export interface ScreenshotConfig {
17
+ /** Formato final del archivo en MinIO. Default: 'webp' */
18
+ format?: 'webp' | 'jpeg';
19
+ /** Calidad de compresión final (sharp). Default: 70 */
20
+ quality?: number;
21
+ /** Calidad de captura en Playwright. Default: 90 */
22
+ captureQuality?: number;
23
+ }
24
+
25
+ // ── Opciones de Pino ─────────────────────────────────────────────────────────
26
+ export interface LogConfig {
27
+ level?: 'trace' | 'debug' | 'info' | 'warn' | 'error' | 'fatal' | 'silent' | any;
28
+ /** Activar pino-pretty (solo dev). Default: false */
29
+ pretty?: boolean;
30
+ pinoOptions?: Record<string, unknown>;
31
+ }
32
+
33
+ // ── Opciones principales de createRpaTools ───────────────────────────────────
34
+ export interface RpaToolsOptions {
35
+ /** ID único del bot. Default: process.env.RPA_BOT_ID */
36
+ botId?: string;
37
+ /** Pool de pg ya conectado */
38
+ db: Pool;
39
+ /** Config de MinIO. Si se omite, lee de variables de entorno MINIO_* */
40
+ minio?: MinioConfig;
41
+ /** Opciones del logger Pino */
42
+ log?: LogConfig;
43
+ /** Opciones de captura y compresión de screenshots */
44
+ screenshot?: ScreenshotConfig;
45
+ }
46
+
47
+ // ── Objeto retornado por createRpaTools ──────────────────────────────────────
48
+ export interface RpaTools {
49
+ /** Instancia de Pino lista para usar en el bot */
50
+ logger: Logger;
51
+
52
+ /**
53
+ * Inicia el polling que activa/desactiva screenshots
54
+ * según bot_debug_config en la BD.
55
+ * @param page - Objeto Page de Playwright
56
+ * @param pollMs - Intervalo de polling en ms. Default: 3000
57
+ */
58
+ watchDebugFlag: (page: Page, pollMs?: number) => void;
59
+
60
+ /** Detiene todos los intervalos. Llamar siempre al cerrar el bot. */
61
+ destroy: () => void;
62
+ }
63
+
64
+ // ── Export principal ─────────────────────────────────────────────────────────
65
+ /**
66
+ * Inicializa la librería rpa-tools.
67
+ * Crea el cliente MinIO, verifica el bucket y retorna
68
+ * el logger y el watcher listos para usar.
69
+ *
70
+ * @example
71
+ * const rpa = await createRpaTools({ botId: 'bot-001', db: pool });
72
+ * rpa.watchDebugFlag(page);
73
+ * rpa.logger.info('Bot iniciado');
74
+ */
75
+ export function createRpaTools(options: RpaToolsOptions): Promise<RpaTools>;
package/src/index.js ADDED
@@ -0,0 +1,86 @@
1
+ 'use strict';
2
+ const { createLogger } = require('./logger');
3
+ const { createMinioClient, ensureBucket } = require('./storage');
4
+ const { ScreenshotWatcher } = require('./watcher');
5
+
6
+ /**
7
+ * Factory principal de la librería.
8
+ *
9
+ * @param {object} opts
10
+ * @param {string} opts.botId - identificador único del bot
11
+ * @param {import('pg').Pool} opts.db - pool de pg ya conectado
12
+ * @param {object} opts.minio - config de MinIO (ver abajo)
13
+ * @param {string} opts.minio.endPoint
14
+ * @param {number} [opts.minio.port=9000]
15
+ * @param {boolean} [opts.minio.useSSL=false]
16
+ * @param {string} opts.minio.accessKey
17
+ * @param {string} opts.minio.secretKey
18
+ * @param {string} [opts.minio.bucket='rpa-screenshots']
19
+ * @param {object} [opts.log] - opciones de Pino
20
+ * @param {string} [opts.log.level='info']
21
+ * @param {boolean} [opts.log.pretty=false]
22
+ *
23
+ * @returns {{ logger, watchDebugFlag, destroy }}
24
+ */
25
+ async function createRpaTools(opts = {}) {
26
+ const {
27
+ botId = process.env.RPA_BOT_ID,
28
+ log: logCfg = {},
29
+ db,
30
+ minio: minioCfg,
31
+ } = opts;
32
+
33
+ // Leer minio desde env si no se pasó config
34
+ const minio = minioCfg ?? {
35
+ endPoint: process.env.MINIO_ENDPOINT,
36
+ port: Number(process.env.MINIO_PORT) || 9000,
37
+ useSSL: process.env.MINIO_USE_SSL === 'true',
38
+ accessKey: process.env.MINIO_ACCESS_KEY,
39
+ secretKey: process.env.MINIO_SECRET_KEY,
40
+ bucket: process.env.MINIO_BUCKET ?? 'rpa-screenshots',
41
+ };
42
+
43
+ // db se sigue pasando desde afuera (el pool ya existe en el bot)
44
+ // pero puedes leer la URL para validar
45
+ if (!db) throw new Error('[rpa-tools] db (pg.Pool) es requerido');
46
+
47
+ // Validar que las env vars críticas existan
48
+ const missing = ['endPoint', 'accessKey', 'secretKey']
49
+ .filter(k => !minio[k]);
50
+
51
+ if (missing.length) {
52
+ throw new Error(`[rpa-tools] Faltan vars de entorno: ${missing.map(k => `MINIO_${k.toUpperCase()}`).join(', ')}`);
53
+ }
54
+
55
+
56
+
57
+ if (!botId) throw new Error('[rpa-tools] botId es requerido');
58
+ if (!db) throw new Error('[rpa-tools] db (pg.Pool) es requerido');
59
+ if (!minioCfg) throw new Error('[rpa-tools] minio config es requerida');
60
+
61
+ const logger = createLogger(botId, logCfg);
62
+ const minioClient = createMinioClient(minioCfg);
63
+ const bucket = minioCfg.bucket ?? 'rpa-screenshots';
64
+
65
+ await ensureBucket(minioClient, bucket);
66
+ logger.info({ bucket }, 'MinIO listo');
67
+
68
+ const watcher = new ScreenshotWatcher({ botId, pool: db, minioClient, bucket, logger });
69
+
70
+ return {
71
+ /** Instancia de Pino lista para usar en tu bot */
72
+ logger,
73
+
74
+ /**
75
+ * Arranca el polling. Llama una vez, pasando el objeto `page` de Playwright.
76
+ * @param {import('playwright').Page} page
77
+ * @param {number} [pollMs=3000]
78
+ */
79
+ watchDebugFlag: (page, pollMs) => watcher.watch(page, pollMs),
80
+
81
+ /** Limpia intervalos al finalizar el bot */
82
+ destroy: () => watcher.destroy(),
83
+ };
84
+ }
85
+
86
+ module.exports = { createRpaTools };
package/src/logger.js ADDED
@@ -0,0 +1,26 @@
1
+ 'use strict';
2
+ const pino = require('pino');
3
+
4
+ /**
5
+ * Crea una instancia de Pino ya configurada con el botId como campo base.
6
+ * @param {string} botId
7
+ * @param {object} opts
8
+ * @param {'trace'|'debug'|'info'|'warn'|'error'} [opts.level='info']
9
+ * @param {boolean} [opts.pretty=false] - activar pino-pretty (solo en dev)
10
+ * @param {object} [opts.pinoOptions] - opciones adicionales de Pino
11
+ */
12
+ function createLogger(botId, opts = {}) {
13
+ const { level = 'info', pretty = false, pinoOptions = {} } = opts;
14
+
15
+ return pino({
16
+ level,
17
+ base: { botId },
18
+ timestamp: pino.stdTimeFunctions.isoTime,
19
+ transport: pretty
20
+ ? { target: 'pino-pretty', options: { colorize: true } }
21
+ : undefined,
22
+ ...pinoOptions,
23
+ });
24
+ }
25
+
26
+ module.exports = { createLogger };
package/src/storage.js ADDED
@@ -0,0 +1,83 @@
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
+
83
+ module.exports = { createMinioClient, ensureBucket, uploadScreenshot };
package/src/watcher.js ADDED
@@ -0,0 +1,137 @@
1
+ 'use strict';
2
+ const { uploadScreenshot } = require('./storage');
3
+
4
+ class ScreenshotWatcher {
5
+ /**
6
+ * @param {object} p
7
+ * @param {string} p.botId
8
+ * @param {import('pg').Pool} p.pool
9
+ * @param {import('minio').Client} p.minioClient
10
+ * @param {string} p.bucket
11
+ * @param {import('pino').Logger} p.logger
12
+ * @param {object} [p.screenshot]
13
+ * @param {'webp'|'jpeg'} [p.screenshot.format='webp']
14
+ * @param {number} [p.screenshot.quality=70] - calidad conversión 1-100
15
+ * @param {number} [p.screenshot.captureQuality=90] - calidad captura Playwright
16
+ */
17
+ constructor({ botId, pool, minioClient, bucket, logger, screenshot = {} }) {
18
+ this.botId = botId;
19
+ this.pool = pool;
20
+ this.minioClient = minioClient;
21
+ this.bucket = bucket;
22
+ this.logger = logger;
23
+
24
+ this.screenshotOpts = {
25
+ format: screenshot.format ?? 'webp',
26
+ quality: screenshot.quality ?? 70,
27
+ captureQuality: screenshot.captureQuality ?? 90,
28
+ };
29
+
30
+ this._watchTimer = null;
31
+ this._shotTimer = null;
32
+ }
33
+
34
+ /**
35
+ * Inicia el polling que activa/desactiva screenshots según bot_debug_config.
36
+ * Llama una sola vez al arrancar el bot.
37
+ *
38
+ * @param {import('playwright').Page} page
39
+ * @param {number} [pollMs=3000] - cada cuántos ms revisar la BD
40
+ */
41
+ watch(page, pollMs = 3000) {
42
+ if (this._watchTimer) {
43
+ this.logger.warn('ScreenshotWatcher ya está corriendo, ignorando llamada duplicada');
44
+ return;
45
+ }
46
+
47
+ this.logger.info({ pollMs }, 'Iniciando polling de debug config');
48
+
49
+ this._watchTimer = setInterval(async () => {
50
+ try {
51
+ const { rows } = await this.pool.query(
52
+ `SELECT screenshots, interval_sec
53
+ FROM bots.tb_bots
54
+ WHERE bot_id = $1`,
55
+ [this.botId]
56
+ );
57
+
58
+ const cfg = rows[0];
59
+ // console.log('DEBUG CONFIG', cfg); // <-- loguear config para debug
60
+
61
+ if (!cfg) {
62
+ this.logger.debug('Sin config en bot_debug_config para este bot');
63
+ return;
64
+ }
65
+
66
+ if (cfg.screenshots && !this._shotTimer) {
67
+ this.logger.info({ intervalSec: cfg.interval_sec }, 'Screenshots activados');
68
+ this._startScreenshots(page, cfg.interval_sec * 1000);
69
+ return;
70
+ }
71
+
72
+ if (!cfg.screenshots && this._shotTimer) {
73
+ this.logger.info('Screenshots desactivados');
74
+ this._stopScreenshots();
75
+ }
76
+ } catch (err) {
77
+ this.logger.error({ err }, 'Error leyendo bot_debug_config');
78
+ }
79
+ }, pollMs);
80
+ }
81
+
82
+ /**
83
+ * @private
84
+ */
85
+ _startScreenshots(page, intervalMs) {
86
+ this._shotTimer = setInterval(async () => {
87
+ const t0 = Date.now();
88
+
89
+ try {
90
+ // Playwright captura en jpeg (más rápido), sharp convierte a WebP después
91
+ const rawBuffer = await page.screenshot({
92
+ type: 'jpeg',
93
+ quality: this.screenshotOpts.captureQuality,
94
+ fullPage: false,
95
+ });
96
+
97
+ const key = await uploadScreenshot(
98
+ this.minioClient,
99
+ this.bucket,
100
+ this.botId,
101
+ rawBuffer,
102
+ {
103
+ format: this.screenshotOpts.format,
104
+ quality: this.screenshotOpts.quality,
105
+ }
106
+ );
107
+
108
+ this.logger.debug(
109
+ { key, ms: Date.now() - t0 },
110
+ 'Screenshot subido'
111
+ );
112
+ } catch (err) {
113
+ this.logger.error({ err }, 'Error capturando o subiendo screenshot');
114
+ }
115
+ }, intervalMs);
116
+ }
117
+
118
+ /**
119
+ * @private
120
+ */
121
+ _stopScreenshots() {
122
+ clearInterval(this._shotTimer);
123
+ this._shotTimer = null;
124
+ }
125
+
126
+ /**
127
+ * Detiene todos los intervalos. Llamar siempre al cerrar el bot.
128
+ */
129
+ destroy() {
130
+ this._stopScreenshots();
131
+ clearInterval(this._watchTimer);
132
+ this._watchTimer = null;
133
+ this.logger.info('ScreenshotWatcher destruido');
134
+ }
135
+ }
136
+
137
+ module.exports = { ScreenshotWatcher };