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.
Files changed (36) hide show
  1. package/dist/commands/docs.js +1 -5
  2. package/dist/lib/docs-resolver.d.ts +8 -0
  3. package/dist/lib/docs-resolver.js +27 -0
  4. package/dist/lib/security.js +5 -0
  5. package/dist/mcp.js +56 -43
  6. package/docs/dev-guide/1-01-architecture.md +358 -0
  7. package/docs/dev-guide/1-02-multi-tenant.md +415 -0
  8. package/docs/dev-guide/1-03-column-types.md +594 -0
  9. package/docs/dev-guide/1-04-json-config.md +442 -0
  10. package/docs/dev-guide/1-05-authentication.md +427 -0
  11. package/docs/dev-guide/2-01-api-reference.md +564 -0
  12. package/docs/dev-guide/2-02-automations.md +508 -0
  13. package/docs/dev-guide/2-03-gufi-cli.md +568 -0
  14. package/docs/dev-guide/2-04-realtime.md +401 -0
  15. package/docs/dev-guide/2-05-permissions.md +497 -0
  16. package/docs/dev-guide/2-06-integrations-overview.md +104 -0
  17. package/docs/dev-guide/2-07-stripe.md +173 -0
  18. package/docs/dev-guide/2-08-nayax.md +297 -0
  19. package/docs/dev-guide/2-09-ourvend.md +226 -0
  20. package/docs/dev-guide/2-10-tns.md +177 -0
  21. package/docs/dev-guide/2-11-custom-http.md +268 -0
  22. package/docs/dev-guide/3-01-custom-views.md +555 -0
  23. package/docs/dev-guide/3-02-webhooks-api.md +446 -0
  24. package/docs/mcp/00-overview.md +329 -0
  25. package/docs/mcp/01-architecture.md +226 -0
  26. package/docs/mcp/02-modules.md +285 -0
  27. package/docs/mcp/03-fields.md +357 -0
  28. package/docs/mcp/04-views.md +613 -0
  29. package/docs/mcp/05-automations.md +461 -0
  30. package/docs/mcp/06-api.md +531 -0
  31. package/docs/mcp/07-packages.md +246 -0
  32. package/docs/mcp/08-common-errors.md +284 -0
  33. package/docs/mcp/09-examples.md +453 -0
  34. package/docs/mcp/README.md +71 -0
  35. package/docs/mcp/tool-descriptions.json +64 -0
  36. 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.