gufi-cli 0.1.49 → 0.1.51
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/dist/commands/docs.js +1 -5
- package/dist/lib/docs-resolver.d.ts +8 -0
- package/dist/lib/docs-resolver.js +27 -0
- package/dist/lib/security.js +5 -0
- package/dist/mcp.js +56 -43
- package/docs/dev-guide/1-01-architecture.md +358 -0
- package/docs/dev-guide/1-02-multi-tenant.md +415 -0
- package/docs/dev-guide/1-03-column-types.md +594 -0
- package/docs/dev-guide/1-04-json-config.md +442 -0
- package/docs/dev-guide/1-05-authentication.md +427 -0
- package/docs/dev-guide/2-01-api-reference.md +564 -0
- package/docs/dev-guide/2-02-automations.md +508 -0
- package/docs/dev-guide/2-03-gufi-cli.md +568 -0
- package/docs/dev-guide/2-04-realtime.md +401 -0
- package/docs/dev-guide/2-05-permissions.md +497 -0
- package/docs/dev-guide/2-06-integrations-overview.md +104 -0
- package/docs/dev-guide/2-07-stripe.md +173 -0
- package/docs/dev-guide/2-08-nayax.md +297 -0
- package/docs/dev-guide/2-09-ourvend.md +226 -0
- package/docs/dev-guide/2-10-tns.md +177 -0
- package/docs/dev-guide/2-11-custom-http.md +268 -0
- package/docs/dev-guide/3-01-custom-views.md +555 -0
- package/docs/dev-guide/3-02-webhooks-api.md +446 -0
- package/docs/mcp/00-overview.md +329 -0
- package/docs/mcp/01-architecture.md +226 -0
- package/docs/mcp/02-modules.md +285 -0
- package/docs/mcp/03-fields.md +357 -0
- package/docs/mcp/04-views.md +613 -0
- package/docs/mcp/05-automations.md +461 -0
- package/docs/mcp/06-api.md +531 -0
- package/docs/mcp/07-packages.md +246 -0
- package/docs/mcp/08-common-errors.md +284 -0
- package/docs/mcp/09-examples.md +453 -0
- package/docs/mcp/README.md +71 -0
- package/docs/mcp/tool-descriptions.json +64 -0
- package/package.json +3 -2
|
@@ -0,0 +1,461 @@
|
|
|
1
|
+
# Automations
|
|
2
|
+
|
|
3
|
+
Las automations son **funciones JavaScript** que se ejecutan en respuesta a eventos en la base de datos.
|
|
4
|
+
|
|
5
|
+
## Arquitectura
|
|
6
|
+
|
|
7
|
+
```
|
|
8
|
+
1. Evento en tabla (INSERT/UPDATE/DELETE)
|
|
9
|
+
|
|
|
10
|
+
v
|
|
11
|
+
2. PostgreSQL NOTIFY --> pg-boss queue
|
|
12
|
+
|
|
|
13
|
+
v
|
|
14
|
+
3. Worker ejecuta runAutomation()
|
|
15
|
+
|
|
|
16
|
+
v
|
|
17
|
+
4. Resultado en __automation_executions__
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
## Tipos de triggers
|
|
21
|
+
|
|
22
|
+
| Trigger | Cuando se ejecuta | Contexto disponible |
|
|
23
|
+
|---------|-------------------|---------------------|
|
|
24
|
+
| `INSERT` | Al crear registro | `row` |
|
|
25
|
+
| `UPDATE` | Al modificar registro | `row`, `old_row` |
|
|
26
|
+
| `DELETE` | Al eliminar registro | `old_row` |
|
|
27
|
+
| `CLICK` | Botón manual en tabla | `row`, `input` |
|
|
28
|
+
| `SCHEDULED` | Cron job | Solo `context` |
|
|
29
|
+
|
|
30
|
+
---
|
|
31
|
+
|
|
32
|
+
## 💜 Nueva Firma: Un Solo Objeto `gufi`
|
|
33
|
+
|
|
34
|
+
```javascript
|
|
35
|
+
/**
|
|
36
|
+
* @param {Object} gufi - Objeto único con todo lo necesario
|
|
37
|
+
*/
|
|
38
|
+
async function mi_automation(gufi) {
|
|
39
|
+
// 💜 Contexto: row, env, input, etc.
|
|
40
|
+
const { row, old_row, input, env, event, table } = gufi.context;
|
|
41
|
+
|
|
42
|
+
// 💜 Logging
|
|
43
|
+
gufi.logger.info('Procesando...', { id: row?.id });
|
|
44
|
+
|
|
45
|
+
// 💜 Database
|
|
46
|
+
const rows = await gufi.query('SELECT * FROM ventas.productos WHERE activo = $1', [true]);
|
|
47
|
+
await gufi.insert('ventas.pedidos', { cliente_id: 123, total: 500 });
|
|
48
|
+
await gufi.update('ventas.pedidos', { id: row.id, status: 'paid' });
|
|
49
|
+
|
|
50
|
+
// 💜 Integraciones
|
|
51
|
+
await gufi.integrations.stripe.createCheckoutSession({ ... });
|
|
52
|
+
await gufi.integrations.notifications.email({ ... });
|
|
53
|
+
|
|
54
|
+
return { success: true, message: 'OK' };
|
|
55
|
+
}
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
### Qué contiene `gufi`:
|
|
59
|
+
|
|
60
|
+
| Propiedad | Descripción |
|
|
61
|
+
|-----------|-------------|
|
|
62
|
+
| `gufi.context` | row, old_row, env, input, company_id, event, table |
|
|
63
|
+
| `gufi.logger` | info, warn, error, debug (console con prefijo) |
|
|
64
|
+
| `gufi.query()` | Consultas SQL (devuelve rows directamente) |
|
|
65
|
+
| `gufi.insert()` | Insertar registro |
|
|
66
|
+
| `gufi.update()` | Actualizar registro |
|
|
67
|
+
| `gufi.remove()` | Eliminar registro |
|
|
68
|
+
| `gufi.findOne()` | Buscar un registro |
|
|
69
|
+
| `gufi.findWithFilters()` | Buscar con filtros |
|
|
70
|
+
| `gufi.bulkInsert()` | Insert masivo |
|
|
71
|
+
| `gufi.bulkUpdate()` | Update masivo |
|
|
72
|
+
| `gufi.bulkDelete()` | Delete masivo |
|
|
73
|
+
| `gufi.resolveTable()` | Resolver nombre lógico → físico |
|
|
74
|
+
| `gufi.http()` | Peticiones HTTP |
|
|
75
|
+
| `gufi.integrations.*` | Integraciones (stripe, nayax, notifications, documents) |
|
|
76
|
+
|
|
77
|
+
---
|
|
78
|
+
|
|
79
|
+
## Context (`gufi.context`)
|
|
80
|
+
|
|
81
|
+
```javascript
|
|
82
|
+
const {
|
|
83
|
+
company_id, // ID de la company
|
|
84
|
+
module_id, // ID del módulo
|
|
85
|
+
entity_id, // ID de la entidad
|
|
86
|
+
table, // Nombre físico (m308_t4136)
|
|
87
|
+
row, // Registro actual (insert/update/click)
|
|
88
|
+
old_row, // Registro anterior (update/delete)
|
|
89
|
+
input, // Input del usuario (solo click)
|
|
90
|
+
env, // Variables de entorno { EMAIL_USER: "xxx", ... }
|
|
91
|
+
event // Tipo: INSERT, UPDATE, DELETE, CLICK, SCHEDULED
|
|
92
|
+
} = gufi.context;
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
---
|
|
96
|
+
|
|
97
|
+
## ⚠️ IMPORTANTE: Usar Nombres Lógicos, NO IDs
|
|
98
|
+
|
|
99
|
+
**NUNCA uses IDs hardcodeados** - rompen entre entornos (DEV vs PROD tienen IDs diferentes).
|
|
100
|
+
|
|
101
|
+
### ❌ MAL - IDs hardcodeados
|
|
102
|
+
|
|
103
|
+
```javascript
|
|
104
|
+
if (entity_id === 7140) { ... } // ID solo válido en DEV
|
|
105
|
+
await gufi.insert('m310_t4146', { ... }); // ID físico solo válido en DEV
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
### ✅ BIEN - Nombres lógicos
|
|
109
|
+
|
|
110
|
+
```javascript
|
|
111
|
+
// Usar context.table para detectar la entidad
|
|
112
|
+
if (gufi.context.table?.includes('supplier_orders')) { ... }
|
|
113
|
+
|
|
114
|
+
// Usar nombres lógicos para operaciones
|
|
115
|
+
await gufi.insert('stock.stock_transactions', { ... });
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
---
|
|
119
|
+
|
|
120
|
+
## CRUD Básico
|
|
121
|
+
|
|
122
|
+
### gufi.query() - Consultas SQL
|
|
123
|
+
|
|
124
|
+
**Devuelve rows directamente (NO destructuring)**
|
|
125
|
+
|
|
126
|
+
```javascript
|
|
127
|
+
// ✅ CORRECTO
|
|
128
|
+
const rows = await gufi.query("SELECT * FROM ventas.productos WHERE id = $1", [id]);
|
|
129
|
+
const firstRow = rows[0];
|
|
130
|
+
|
|
131
|
+
// ❌ INCORRECTO - NO FUNCIONA
|
|
132
|
+
const { rows } = await gufi.query(...); // ERROR!
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
**Parametrización segura:**
|
|
136
|
+
```javascript
|
|
137
|
+
// ✅ CORRECTO - parámetros separados
|
|
138
|
+
const rows = await gufi.query(
|
|
139
|
+
"SELECT * FROM ventas.productos WHERE estado = $1 AND total > $2",
|
|
140
|
+
['activo', 1000]
|
|
141
|
+
);
|
|
142
|
+
|
|
143
|
+
// ❌ INCORRECTO - SQL injection!
|
|
144
|
+
const rows = await gufi.query(`SELECT * FROM tabla WHERE estado = '${estado}'`);
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
### gufi.insert() - Crear registro
|
|
148
|
+
|
|
149
|
+
```javascript
|
|
150
|
+
await gufi.insert('ventas.productos', {
|
|
151
|
+
nombre: 'Nuevo producto',
|
|
152
|
+
precio: 150,
|
|
153
|
+
estado: 'activo'
|
|
154
|
+
});
|
|
155
|
+
```
|
|
156
|
+
|
|
157
|
+
### gufi.update() - Actualizar registro
|
|
158
|
+
|
|
159
|
+
```javascript
|
|
160
|
+
await gufi.update('ventas.productos', {
|
|
161
|
+
id: row.id, // REQUERIDO
|
|
162
|
+
estado: 'procesado',
|
|
163
|
+
estado__display: 'Procesado' // Para select/relation
|
|
164
|
+
});
|
|
165
|
+
```
|
|
166
|
+
|
|
167
|
+
### gufi.remove() - Eliminar registro
|
|
168
|
+
|
|
169
|
+
```javascript
|
|
170
|
+
await gufi.remove('ventas.productos', row.id);
|
|
171
|
+
```
|
|
172
|
+
|
|
173
|
+
### gufi.findOne() - Buscar un registro
|
|
174
|
+
|
|
175
|
+
```javascript
|
|
176
|
+
const cliente = await gufi.findOne('ventas.clientes', 'email', 'juan@test.com');
|
|
177
|
+
if (cliente) {
|
|
178
|
+
gufi.logger.info('Cliente encontrado:', cliente.nombre);
|
|
179
|
+
}
|
|
180
|
+
```
|
|
181
|
+
|
|
182
|
+
### gufi.findWithFilters() - Buscar con filtros
|
|
183
|
+
|
|
184
|
+
```javascript
|
|
185
|
+
const rows = await gufi.findWithFilters('ventas.productos', [
|
|
186
|
+
{ field: 'estado', operator: 'eq', value: 'activo' },
|
|
187
|
+
{ field: 'precio', operator: '>', value: 100 }
|
|
188
|
+
], {
|
|
189
|
+
order: [{ field: 'created_at', order: 'DESC' }],
|
|
190
|
+
limit: 10
|
|
191
|
+
});
|
|
192
|
+
```
|
|
193
|
+
|
|
194
|
+
---
|
|
195
|
+
|
|
196
|
+
## Bulk Operations
|
|
197
|
+
|
|
198
|
+
Para operaciones masivas SIN disparar automations individuales:
|
|
199
|
+
|
|
200
|
+
```javascript
|
|
201
|
+
// Insert múltiple
|
|
202
|
+
const result = await gufi.bulkInsert('ventas.productos', [
|
|
203
|
+
{ nombre: 'Producto 1', precio: 100 },
|
|
204
|
+
{ nombre: 'Producto 2', precio: 200 }
|
|
205
|
+
]);
|
|
206
|
+
// Returns: { insertedIds: [1, 2], count: 2 }
|
|
207
|
+
|
|
208
|
+
// Update múltiple
|
|
209
|
+
await gufi.bulkUpdate('ventas.productos', [1, 2, 3], { estado: 'procesado' });
|
|
210
|
+
|
|
211
|
+
// Delete múltiple
|
|
212
|
+
await gufi.bulkDelete('ventas.productos', [1, 2, 3]);
|
|
213
|
+
```
|
|
214
|
+
|
|
215
|
+
---
|
|
216
|
+
|
|
217
|
+
## Integraciones (`gufi.integrations`)
|
|
218
|
+
|
|
219
|
+
Acceso a servicios externos. **Credenciales siempre desde `gufi.context.env`**.
|
|
220
|
+
|
|
221
|
+
### Email
|
|
222
|
+
|
|
223
|
+
```javascript
|
|
224
|
+
const { env } = gufi.context;
|
|
225
|
+
|
|
226
|
+
await gufi.integrations.notifications.email({
|
|
227
|
+
to: 'usuario@ejemplo.com',
|
|
228
|
+
subject: 'Asunto',
|
|
229
|
+
html: '<h1>Hola</h1>',
|
|
230
|
+
cc: 'copia@ejemplo.com',
|
|
231
|
+
attachments: [{ filename: 'doc.pdf', content: pdfBuffer }],
|
|
232
|
+
// 💜 Credenciales desde env
|
|
233
|
+
user: env.EMAIL_USER,
|
|
234
|
+
pass: env.EMAIL_PASS,
|
|
235
|
+
from: env.EMAIL_FROM
|
|
236
|
+
});
|
|
237
|
+
```
|
|
238
|
+
|
|
239
|
+
### PDF
|
|
240
|
+
|
|
241
|
+
```javascript
|
|
242
|
+
const { pdf, size } = await gufi.integrations.documents.pdf({
|
|
243
|
+
html: '<h1>Reporte</h1><p>Contenido</p>',
|
|
244
|
+
format: 'A4'
|
|
245
|
+
});
|
|
246
|
+
// pdf es un Buffer
|
|
247
|
+
```
|
|
248
|
+
|
|
249
|
+
### Stripe
|
|
250
|
+
|
|
251
|
+
```javascript
|
|
252
|
+
const result = await gufi.integrations.stripe.createCheckoutSession({
|
|
253
|
+
secretKey: env.STRIPE_SECRET_KEY,
|
|
254
|
+
lineItems: [{ name: 'Producto', amount: 1999, quantity: 1 }],
|
|
255
|
+
successUrl: `${env.FRONTEND_URL}/success`,
|
|
256
|
+
cancelUrl: `${env.FRONTEND_URL}/cancel`
|
|
257
|
+
});
|
|
258
|
+
// result.url es la URL de checkout
|
|
259
|
+
```
|
|
260
|
+
|
|
261
|
+
### Telegram
|
|
262
|
+
|
|
263
|
+
```javascript
|
|
264
|
+
await gufi.integrations.notifications.telegram({
|
|
265
|
+
botToken: env.TELEGRAM_BOT_TOKEN,
|
|
266
|
+
chatId: env.TELEGRAM_CHAT_ID,
|
|
267
|
+
message: '🚨 *Alerta*: Nueva venta',
|
|
268
|
+
parseMode: 'Markdown'
|
|
269
|
+
});
|
|
270
|
+
```
|
|
271
|
+
|
|
272
|
+
### HTTP (APIs externas)
|
|
273
|
+
|
|
274
|
+
```javascript
|
|
275
|
+
const response = await gufi.http(env.WEBHOOK_URL, {
|
|
276
|
+
method: 'POST',
|
|
277
|
+
headers: {
|
|
278
|
+
'Content-Type': 'application/json',
|
|
279
|
+
'Authorization': `Bearer ${env.API_TOKEN}`
|
|
280
|
+
},
|
|
281
|
+
body: JSON.stringify({ data: row })
|
|
282
|
+
});
|
|
283
|
+
|
|
284
|
+
const data = await response.json();
|
|
285
|
+
```
|
|
286
|
+
|
|
287
|
+
---
|
|
288
|
+
|
|
289
|
+
## Integraciones Disponibles
|
|
290
|
+
|
|
291
|
+
| Integración | Métodos | Categoría |
|
|
292
|
+
|-------------|---------|-----------|
|
|
293
|
+
| `gufi.integrations.stripe` | `createCheckoutSession` | Pagos |
|
|
294
|
+
| `gufi.integrations.notifications` | `email`, `emailWrapped`, `telegram` | Notificaciones |
|
|
295
|
+
| `gufi.integrations.documents` | `pdf`, `invoice` | Documentos |
|
|
296
|
+
| `gufi.integrations.nayax` | `sync_machines`, `sync_products`, `sync_sales`, etc. | Vending |
|
|
297
|
+
| `gufi.integrations.seur` | `createShipment`, `getTracking`, `cancelShipment` | Envíos |
|
|
298
|
+
|
|
299
|
+
---
|
|
300
|
+
|
|
301
|
+
## Variables de entorno
|
|
302
|
+
|
|
303
|
+
Cada company tiene variables privadas en `gufi.context.env`:
|
|
304
|
+
|
|
305
|
+
```javascript
|
|
306
|
+
const { env } = gufi.context;
|
|
307
|
+
|
|
308
|
+
const apiKey = env.STRIPE_API_KEY;
|
|
309
|
+
const webhookUrl = env.SLACK_WEBHOOK;
|
|
310
|
+
const emailAdmin = env.ADMIN_EMAIL;
|
|
311
|
+
```
|
|
312
|
+
|
|
313
|
+
**Gestionar con MCP:**
|
|
314
|
+
```javascript
|
|
315
|
+
gufi_env({ action: "list", company_id: "116" })
|
|
316
|
+
gufi_env({ action: "set", company_id: "116", key: "API_KEY", value: "xxx" })
|
|
317
|
+
gufi_env({ action: "delete", company_id: "116", key: "OLD_KEY" })
|
|
318
|
+
```
|
|
319
|
+
|
|
320
|
+
---
|
|
321
|
+
|
|
322
|
+
## Ejemplos Completos
|
|
323
|
+
|
|
324
|
+
### Email con PDF adjunto
|
|
325
|
+
|
|
326
|
+
```javascript
|
|
327
|
+
async function enviar_factura(gufi) {
|
|
328
|
+
const { row, env } = gufi.context;
|
|
329
|
+
|
|
330
|
+
// Generar PDF
|
|
331
|
+
const { pdf } = await gufi.integrations.documents.pdf({
|
|
332
|
+
html: `<h1>Factura #${row.id}</h1><p>Total: ${row.total} EUR</p>`
|
|
333
|
+
});
|
|
334
|
+
|
|
335
|
+
// Enviar email
|
|
336
|
+
await gufi.integrations.notifications.email({
|
|
337
|
+
to: row.cliente_email,
|
|
338
|
+
subject: `Factura #${row.id}`,
|
|
339
|
+
html: '<p>Adjuntamos su factura.</p>',
|
|
340
|
+
attachments: [{ filename: `factura-${row.id}.pdf`, content: pdf }],
|
|
341
|
+
user: env.EMAIL_USER,
|
|
342
|
+
pass: env.EMAIL_PASS,
|
|
343
|
+
from: env.EMAIL_FROM
|
|
344
|
+
});
|
|
345
|
+
|
|
346
|
+
// Marcar como enviada
|
|
347
|
+
await gufi.update(gufi.context.table, {
|
|
348
|
+
id: row.id,
|
|
349
|
+
factura_enviada: true
|
|
350
|
+
});
|
|
351
|
+
|
|
352
|
+
return { success: true };
|
|
353
|
+
}
|
|
354
|
+
```
|
|
355
|
+
|
|
356
|
+
### Sync con API externa
|
|
357
|
+
|
|
358
|
+
```javascript
|
|
359
|
+
async function sync_to_crm(gufi) {
|
|
360
|
+
const { row, old_row, event, env } = gufi.context;
|
|
361
|
+
|
|
362
|
+
// Solo sync si cambió el email
|
|
363
|
+
if (event === 'UPDATE' && row.email === old_row?.email) {
|
|
364
|
+
return { skipped: true };
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
const response = await gufi.http(`${env.CRM_URL}/api/contacts`, {
|
|
368
|
+
method: row.crm_id ? 'PUT' : 'POST',
|
|
369
|
+
headers: {
|
|
370
|
+
'Authorization': `Bearer ${env.CRM_TOKEN}`,
|
|
371
|
+
'Content-Type': 'application/json'
|
|
372
|
+
},
|
|
373
|
+
body: JSON.stringify({
|
|
374
|
+
id: row.crm_id,
|
|
375
|
+
email: row.email,
|
|
376
|
+
name: row.nombre
|
|
377
|
+
})
|
|
378
|
+
});
|
|
379
|
+
|
|
380
|
+
if (!response.ok) {
|
|
381
|
+
throw new Error(`CRM error: ${response.status}`);
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
const data = await response.json();
|
|
385
|
+
|
|
386
|
+
// Guardar ID del CRM si es nuevo
|
|
387
|
+
if (!row.crm_id && data.id) {
|
|
388
|
+
await gufi.update(gufi.context.table, {
|
|
389
|
+
id: row.id,
|
|
390
|
+
crm_id: data.id
|
|
391
|
+
});
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
return { success: true, crm_id: data.id };
|
|
395
|
+
}
|
|
396
|
+
```
|
|
397
|
+
|
|
398
|
+
---
|
|
399
|
+
|
|
400
|
+
## Operaciones con MCP
|
|
401
|
+
|
|
402
|
+
### Listar scripts
|
|
403
|
+
|
|
404
|
+
```javascript
|
|
405
|
+
gufi_automation_scripts({ action: "list", company_id: "116" })
|
|
406
|
+
```
|
|
407
|
+
|
|
408
|
+
### Ver código
|
|
409
|
+
|
|
410
|
+
```javascript
|
|
411
|
+
gufi_automation_scripts({ action: "get", company_id: "116", id: "15" })
|
|
412
|
+
```
|
|
413
|
+
|
|
414
|
+
### Crear script
|
|
415
|
+
|
|
416
|
+
```javascript
|
|
417
|
+
gufi_automation_scripts({
|
|
418
|
+
action: "create",
|
|
419
|
+
company_id: "116",
|
|
420
|
+
name: "mi_automation",
|
|
421
|
+
code: "async function mi_automation(gufi) { ... }"
|
|
422
|
+
})
|
|
423
|
+
```
|
|
424
|
+
|
|
425
|
+
### Configurar triggers
|
|
426
|
+
|
|
427
|
+
```javascript
|
|
428
|
+
gufi_automation_meta({
|
|
429
|
+
entity_id: "4136",
|
|
430
|
+
company_id: "116",
|
|
431
|
+
automations: [
|
|
432
|
+
{ trigger: "insert", function_name: "notificar_nuevo" },
|
|
433
|
+
{ trigger: "update", function_name: "sync_cambios" }
|
|
434
|
+
]
|
|
435
|
+
})
|
|
436
|
+
```
|
|
437
|
+
|
|
438
|
+
### Ver ejecuciones
|
|
439
|
+
|
|
440
|
+
```javascript
|
|
441
|
+
gufi_automation_executions({
|
|
442
|
+
company_id: "116",
|
|
443
|
+
script_name: "mi_automation",
|
|
444
|
+
limit: 20
|
|
445
|
+
})
|
|
446
|
+
```
|
|
447
|
+
|
|
448
|
+
---
|
|
449
|
+
|
|
450
|
+
## Debugging
|
|
451
|
+
|
|
452
|
+
```javascript
|
|
453
|
+
gufi.logger.info('Procesando', { id: row.id });
|
|
454
|
+
gufi.logger.warn('Advertencia', { campo: row.estado });
|
|
455
|
+
gufi.logger.error('Error', { err: error.message });
|
|
456
|
+
|
|
457
|
+
// También funciona console.*
|
|
458
|
+
console.log('Debug:', row);
|
|
459
|
+
```
|
|
460
|
+
|
|
461
|
+
Ver ejecuciones con `gufi_automation_executions()` para ver status, duración y errores.
|