gufi-cli 0.1.17 → 0.1.18

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/CLAUDE.md CHANGED
@@ -1,2107 +1,206 @@
1
- # Gufi CLI - Documentación Completa para Claude
1
+ # Gufi CLI - Documentación para Claude
2
2
 
3
- ## ¿Qué es Gufi?
3
+ > **Documentación centralizada en `docs/claude/`**
4
+ >
5
+ > Este archivo es un puntero. Para documentación completa del CLI, lee:
6
+ > - [docs/claude/06-cli.md](../../docs/claude/06-cli.md) - Comandos del CLI
7
+ > - [docs/claude/00-index.md](../../docs/claude/00-index.md) - Overview de Gufi
8
+ > - [docs/claude/05-automations.md](../../docs/claude/05-automations.md) - Automations
9
+ > - [docs/claude/04-views.md](../../docs/claude/04-views.md) - Sistema de vistas
4
10
 
5
- Gufi es un **ERP multi-tenant** donde cada empresa (company) tiene su propia base de datos aislada. Los consultores pueden crear módulos personalizados sin programar - solo definiendo JSONs que describen la estructura de datos.
6
-
7
- **Filosofía**: "Simple · Professional · Elegance" - El Apple de los ERPs
8
-
9
- ### Arquitectura General
10
-
11
- ```
12
- ┌─────────────────────────────────────────────────────────────────┐
13
- │ GUFI ERP │
14
- ├─────────────────────────────────────────────────────────────────┤
15
- │ │
16
- │ Company 116 (Fitvending) Company 146 (Gufi) │
17
- │ ├── Schema: company_116 ├── Schema: company_146 │
18
- │ ├── Módulo: Nayax (308) ├── Módulo: Tareas (360) │
19
- │ │ └── Tablas: m308_t4136 │ └── Tablas: m360_t16192 │
20
- │ └── Módulo: Stock (310) └── Módulo: Incidencias (361) │
21
- │ │
22
- ├─────────────────────────────────────────────────────────────────┤
23
- │ core.modules → Definiciones JSON de módulos │
24
- │ core.entities → Metadatos (permissions, automations) │
25
- │ core.entities.automations → FUENTE DE VERDAD de automations │
26
- │ core.automation_scripts → Código JavaScript de automations │
27
- │ core.automation_meta → Índice para worker (sync auto) │
28
- ├─────────────────────────────────────────────────────────────────┤
29
- │ marketplace.packages → Paquetes publicados │
30
- │ marketplace.views → Vistas React/TypeScript │
31
- └─────────────────────────────────────────────────────────────────┘
32
- ```
33
-
34
- ### Nomenclatura de Tablas
35
-
36
- Las tablas físicas en PostgreSQL siguen el patrón:
37
- ```
38
- m{moduleId}_t{entityId}
39
- ```
40
-
41
- Ejemplo:
42
- - Módulo "Tareas" tiene ID 360
43
- - Entidad "asignaciones" tiene ID 16192
44
- - Tabla física: `m360_t16192` en schema `company_146`
45
-
46
- ---
47
-
48
- ## 💜 Core Database - Relaciones Importantes
49
-
50
- ### Diagrama de Relaciones
51
-
52
- ```
53
- ┌─────────────────────────────────────────────────────────────────────────────┐
54
- │ SCHEMA: core │
55
- ├─────────────────────────────────────────────────────────────────────────────┤
56
- │ │
57
- │ companies (1) │
58
- │ ├── id: 116 │
59
- │ ├── name: "Fitvending" │
60
- │ └── schema: "company_116" │
61
- │ │ │
62
- │ │ 1:N │
63
- │ ▼ │
64
- │ modules (N) users (N) │
65
- │ ├── id: 308 ├── id: 16 │
66
- │ ├── company_id: 116 ◄────────── ├── email: "juan@gufi.es" │
67
- │ ├── name: "nayax" └── platform_role: "admin" │
68
- │ └── json_definition: {...} │ │
69
- │ │ │ N:M │
70
- │ │ 1:N ▼ │
71
- │ ▼ company_users │
72
- │ entities (N) ├── user_id: 16 │
73
- │ ├── id: 4136 ├── company_id: 116 │
74
- │ ├── module_id: 308 ◄─────────── └── roles: ["Admin"] │
75
- │ ├── company_id: 116 │
76
- │ ├── name: "machines" │
77
- │ ├── permissions: {"Admin": "*"} ◄── Permisos por rol │
78
- │ └── automations: [{...}] ◄── JSONB array de triggers │
79
- │ │ │
80
- │ │ N:1 (via script_id en automations) │
81
- │ ▼ │
82
- │ automation_scripts (N) │
83
- │ ├── id: 15 │
84
- │ ├── company_id: 116 │
85
- │ ├── name: "sync_machines" │
86
- │ └── code: "async function..." ◄── El JavaScript │
87
- │ │ │
88
- │ │ (sync automático) │
89
- │ ▼ │
90
- │ automation_meta (índice para worker) │
91
- │ automation_executions (historial) │
92
- │ │
93
- └─────────────────────────────────────────────────────────────────────────────┘
94
-
95
- ┌─────────────────────────────────────────────────────────────────────────────┐
96
- │ SCHEMA: company_116 │
97
- ├─────────────────────────────────────────────────────────────────────────────┤
98
- │ │
99
- │ m308_t4136 (machines) m308_t4137 (products) m308_t4140 (sales) │
100
- │ ├── id ├── id ├── id │
101
- │ ├── name ├── name ├── machine_id (FK) │
102
- │ └── ... └── ... └── ... │
103
- │ │
104
- │ __audit_log__ (historial de cambios) │
105
- │ │
106
- └─────────────────────────────────────────────────────────────────────────────┘
107
- ```
108
-
109
- ### Tablas Core Principales
110
-
111
- | Tabla | Descripción | Relaciones |
112
- |-------|-------------|------------|
113
- | `companies` | Empresas/tenants | 1 company → N modules, N users |
114
- | `users` | Usuarios globales | N:M con companies via company_users |
115
- | `company_users` | Membresía usuario-empresa | Contiene roles[] del usuario en esa company |
116
- | `modules` | Definición JSON de módulos | 1 module → N entities |
117
- | `entities` | Metadatos de tablas | Contiene permissions, automations, config |
118
- | `automation_scripts` | Código JavaScript | 1 script → N entities pueden usarlo |
119
- | `automation_meta` | Índice para worker | Generado auto desde entities.automations |
120
-
121
- ### Flujo de Datos
122
-
123
- ```
124
- Usuario hace login
125
-
126
-
127
- JWT contiene: { user_id, company_id, roles[], platform_role }
128
-
129
-
130
- Backend: SET search_path TO company_116, core;
131
-
132
-
133
- Queries van automáticamente al schema correcto
134
- ```
135
-
136
- ### IDs Importantes para CLI
137
-
138
- | Quiero trabajar con... | Necesito el ID de... | Cómo obtenerlo |
139
- |------------------------|---------------------|----------------|
140
- | Datos de una tabla | entity (tabla física: m308_t4136) | `gufi schema -c 116` |
141
- | Estructura de módulo | module_id | `gufi modules 116` |
142
- | Código de automation | script_id | `gufi automations -c 116` |
143
- | Triggers de una entity | entity_id | `gufi schema -c 116` |
144
-
145
- ---
146
-
147
- ## Estructura de un Módulo (JSON)
148
-
149
- Un módulo es un JSON que define **toda la estructura de datos** de una funcionalidad. Gufi lee este JSON y automáticamente:
150
- 1. Crea las tablas en PostgreSQL
151
- 2. Genera la UI (formularios, listas, filtros)
152
- 3. Configura permisos
153
- 4. Vincula relaciones entre tablas
154
-
155
- ### JSON Completo de Ejemplo
156
-
157
- ```json
158
- {
159
- "name": "ventas",
160
- "displayName": "Gestión de Ventas",
161
- "icon": "ShoppingCart",
162
- "submodules": [
163
- {
164
- "name": "maestros",
165
- "label": "Datos Maestros",
166
- "icon": "Database",
167
- "entities": [
168
- {
169
- "kind": "table",
170
- "name": "clientes",
171
- "label": "Clientes",
172
- "fields": [
173
- {
174
- "name": "nombre",
175
- "type": "text",
176
- "label": "Nombre",
177
- "required": true,
178
- "searchable": true
179
- },
180
- {
181
- "name": "email",
182
- "type": "email",
183
- "label": "Email",
184
- "unique": true
185
- },
186
- {
187
- "name": "telefono",
188
- "type": "phone",
189
- "label": "Teléfono"
190
- },
191
- {
192
- "name": "tipo",
193
- "type": "select",
194
- "label": "Tipo de Cliente",
195
- "options": [
196
- { "value": "particular", "label": "Particular" },
197
- { "value": "empresa", "label": "Empresa" },
198
- { "value": "vip", "label": "VIP" }
199
- ],
200
- "default": "particular"
201
- },
202
- {
203
- "name": "limite_credito",
204
- "type": "currency",
205
- "label": "Límite de Crédito",
206
- "currency": "EUR"
207
- },
208
- {
209
- "name": "activo",
210
- "type": "boolean",
211
- "label": "Activo",
212
- "default": true
213
- },
214
- {
215
- "name": "notas",
216
- "type": "textarea",
217
- "label": "Notas"
218
- },
219
- {
220
- "name": "comercial_id",
221
- "type": "users",
222
- "label": "Comercial Asignado"
223
- }
224
- ],
225
- "ui": {
226
- "defaultSort": { "field": "nombre", "order": "ASC" },
227
- "searchFields": ["nombre", "email"],
228
- "listFields": ["nombre", "tipo", "comercial_id", "activo"]
229
- }
230
- },
231
- {
232
- "kind": "table",
233
- "name": "productos",
234
- "label": "Productos",
235
- "fields": [
236
- {
237
- "name": "codigo",
238
- "type": "text",
239
- "label": "Código",
240
- "required": true,
241
- "unique": true
242
- },
243
- {
244
- "name": "nombre",
245
- "type": "text",
246
- "label": "Nombre",
247
- "required": true
248
- },
249
- {
250
- "name": "precio",
251
- "type": "currency",
252
- "label": "Precio",
253
- "currency": "EUR",
254
- "required": true
255
- },
256
- {
257
- "name": "stock",
258
- "type": "number",
259
- "label": "Stock",
260
- "default": 0,
261
- "min": 0
262
- },
263
- {
264
- "name": "categoria",
265
- "type": "multiselect",
266
- "label": "Categorías",
267
- "options": [
268
- { "value": "electronica", "label": "Electrónica" },
269
- { "value": "ropa", "label": "Ropa" },
270
- { "value": "hogar", "label": "Hogar" }
271
- ]
272
- },
273
- {
274
- "name": "imagen",
275
- "type": "image",
276
- "label": "Imagen"
277
- }
278
- ]
279
- }
280
- ]
281
- },
282
- {
283
- "name": "operaciones",
284
- "label": "Operaciones",
285
- "icon": "FileText",
286
- "entities": [
287
- {
288
- "kind": "table",
289
- "name": "pedidos",
290
- "label": "Pedidos",
291
- "fields": [
292
- {
293
- "name": "numero",
294
- "type": "text",
295
- "label": "Nº Pedido",
296
- "required": true,
297
- "unique": true,
298
- "autoGenerate": "PED-{YYYY}{MM}-{SEQ:5}"
299
- },
300
- {
301
- "name": "cliente_id",
302
- "type": "relation",
303
- "label": "Cliente",
304
- "ref": "clientes",
305
- "display": "nombre",
306
- "required": true
307
- },
308
- {
309
- "name": "fecha",
310
- "type": "date",
311
- "label": "Fecha",
312
- "default": "today"
313
- },
314
- {
315
- "name": "estado",
316
- "type": "select",
317
- "label": "Estado",
318
- "options": [
319
- { "value": "borrador", "label": "Borrador", "color": "gray" },
320
- { "value": "confirmado", "label": "Confirmado", "color": "blue" },
321
- { "value": "enviado", "label": "Enviado", "color": "yellow" },
322
- { "value": "entregado", "label": "Entregado", "color": "green" },
323
- { "value": "cancelado", "label": "Cancelado", "color": "red" }
324
- ],
325
- "default": "borrador"
326
- },
327
- {
328
- "name": "total",
329
- "type": "currency",
330
- "label": "Total",
331
- "currency": "EUR",
332
- "computed": true
333
- },
334
- {
335
- "name": "responsable_id",
336
- "type": "users",
337
- "label": "Responsable"
338
- },
339
- {
340
- "name": "observaciones",
341
- "type": "textarea",
342
- "label": "Observaciones"
343
- }
344
- ],
345
- "automations": [
346
- {
347
- "trigger": "insert",
348
- "function_name": "notificar_nuevo_pedido"
349
- },
350
- {
351
- "trigger": "update",
352
- "condition": "estado = 'confirmado'",
353
- "function_name": "procesar_pedido"
354
- },
355
- {
356
- "trigger": "click",
357
- "function_name": "generar_factura",
358
- "label": "Generar Factura",
359
- "icon": "FileText",
360
- "showWhen": "estado = 'entregado'"
361
- }
362
- ]
363
- },
364
- {
365
- "kind": "table",
366
- "name": "lineas_pedido",
367
- "label": "Líneas de Pedido",
368
- "fields": [
369
- {
370
- "name": "pedido_id",
371
- "type": "relation",
372
- "label": "Pedido",
373
- "ref": "pedidos",
374
- "required": true,
375
- "cascade": true
376
- },
377
- {
378
- "name": "producto_id",
379
- "type": "relation",
380
- "label": "Producto",
381
- "ref": "productos",
382
- "display": "nombre",
383
- "required": true
384
- },
385
- {
386
- "name": "cantidad",
387
- "type": "number",
388
- "label": "Cantidad",
389
- "required": true,
390
- "min": 1,
391
- "default": 1
392
- },
393
- {
394
- "name": "precio_unitario",
395
- "type": "currency",
396
- "label": "Precio Unit.",
397
- "currency": "EUR"
398
- },
399
- {
400
- "name": "subtotal",
401
- "type": "currency",
402
- "label": "Subtotal",
403
- "currency": "EUR",
404
- "computed": "cantidad * precio_unitario"
405
- }
406
- ],
407
- "ui": {
408
- "inline": true,
409
- "parentField": "pedido_id"
410
- }
411
- }
412
- ]
413
- }
414
- ]
415
- }
416
- ```
417
-
418
- ### Tipos de Campos Disponibles
419
-
420
- | Tipo | PostgreSQL | Descripción | Opciones |
421
- |------|------------|-------------|----------|
422
- | `text` | VARCHAR(255) | Texto corto | `maxLength`, `minLength`, `pattern` |
423
- | `textarea` | TEXT | Texto largo | `rows`, `maxLength` |
424
- | `number` | NUMERIC | Número | `min`, `max`, `decimals`, `step` |
425
- | `currency` | NUMERIC(15,2) | Dinero | `currency` (EUR, USD, etc.) |
426
- | `date` | DATE | Fecha | `min`, `max`, `default: "today"` |
427
- | `datetime` | TIMESTAMP | Fecha y hora | `min`, `max` |
428
- | `time` | TIME | Solo hora | - |
429
- | `select` | VARCHAR | Selector único | `options: [{value, label, color}]` |
430
- | `multiselect` | TEXT[] | Selector múltiple | `options: [{value, label}]` |
431
- | `relation` | INTEGER (FK) | Relación a otra tabla | `ref`, `display`, `cascade` |
432
- | `users` | INTEGER/INT[] | Usuario(s) del sistema | `multiple: true` |
433
- | `boolean` | BOOLEAN | Sí/No | `default` |
434
- | `email` | VARCHAR | Email validado | - |
435
- | `phone` | VARCHAR | Teléfono | `format` |
436
- | `url` | VARCHAR | URL | - |
437
- | `image` | TEXT | URL de imagen (GCS) | `maxSize`, `allowedTypes` |
438
- | `file` | TEXT | URL de archivo (GCS) | `maxSize`, `allowedTypes` |
439
- | `json` | JSONB | Objeto JSON | `schema` |
440
- | `color` | VARCHAR(7) | Color hex | - |
441
- | `rating` | SMALLINT | Puntuación 1-5 | `max` |
442
- | `percentage` | NUMERIC | Porcentaje | `min`, `max` |
443
-
444
- ### Opciones de Campo
445
-
446
- ```json
447
- {
448
- "name": "campo",
449
- "type": "text",
450
- "label": "Etiqueta visible",
451
- "required": true, // Campo obligatorio
452
- "unique": true, // Valor único en la tabla
453
- "default": "valor", // Valor por defecto
454
- "searchable": true, // Incluir en búsqueda global
455
- "hidden": false, // Ocultar en UI
456
- "readonly": false, // Solo lectura
457
- "computed": "expr", // Calculado (no editable)
458
- "autoGenerate": "pattern", // Auto-generar valor
459
- "validation": { // Validaciones custom
460
- "pattern": "^[A-Z]{3}",
461
- "message": "Debe ser 3 letras mayúsculas"
462
- }
463
- }
464
- ```
465
-
466
- ### Permisos por Rol
467
-
468
- > **⚠️ IMPORTANTE**: Los permisos ya NO se definen en el JSON del módulo.
469
- > Se gestionan exclusivamente via la columna `permissions` de `core.entities`.
470
-
471
- Al crear una entidad nueva, el Admin obtiene `"*"` (full access) automáticamente.
472
- Los permisos se gestionan desde la UI de permisos o directamente en la base de datos:
473
-
474
- ```sql
475
- -- Ejemplo: Configurar permisos de una entidad
476
- UPDATE core.entities
477
- SET permissions = '{"Admin": "*", "Manager": "crud", "User": "read"}'::jsonb
478
- WHERE id = 6420;
479
- ```
480
-
481
- | Valor | Significado |
482
- |-------|-------------|
483
- | `"*"` | Full access |
484
- | `"crud"` | Create, Read, Update, Delete |
485
- | `"read"` | Solo lectura |
486
- | `""` | Sin acceso |
487
-
488
- ---
489
-
490
- ## Automations (Código JavaScript)
491
-
492
- Las automations son **funciones JavaScript** almacenadas en la base de datos que se ejecutan en respuesta a eventos.
493
-
494
- ### 💜 Nueva Arquitectura (2025)
495
-
496
- Las automations se almacenan en **tres lugares que trabajan juntos**:
497
-
498
- ```
499
- core.entities.automations (JSONB) ← FUENTE DE VERDAD
500
- │ (configuración de triggers)
501
-
502
- ▼ (sync automático)
503
- core.automation_meta ← ÍNDICE PARA WORKER
504
- │ (sincronizado automáticamente)
505
-
506
- ▼ (referencias)
507
- core.automation_scripts ← CÓDIGO JAVASCRIPT
508
- (la función que se ejecuta)
509
- ```
510
-
511
- ### Dónde se almacena cada cosa
512
-
513
- ```sql
514
- -- CONFIGURACIÓN: Qué automations tiene cada entity (triggers, enabled, etc.)
515
- SELECT id, name, automations FROM core.entities WHERE id = 4589;
516
-
517
- -- CÓDIGO: El JavaScript de cada automation
518
- SELECT function_name, js_code FROM core.automation_scripts WHERE company_id = 116;
519
-
520
- -- ÍNDICE: Lo que consulta el worker (sincronizado automáticamente)
521
- SELECT * FROM core.automation_meta WHERE company_id = 116;
522
- ```
523
-
524
- ### Tipos de Triggers
525
-
526
- | Trigger | Cuándo se ejecuta |
527
- |---------|-------------------|
528
- | `insert` | Al crear un registro |
529
- | `update` | Al modificar un registro |
530
- | `delete` | Al eliminar un registro |
531
- | `click` / `click` | Botón manual en la UI |
532
- | `scheduled` | Cron job (ej: "0 9 * * *") |
533
-
534
- ### Formato de entities.automations
535
-
536
- ```json
537
- [
538
- {
539
- "trigger": "insert",
540
- "function_name": "procesar_pedido",
541
- "script_id": 42,
542
- "enabled": true
543
- },
544
- {
545
- "trigger": "click",
546
- "function_name": "generar_factura",
547
- "label": "Generar Factura",
548
- "enabled": true
549
- }
550
- ]
551
- ```
552
-
553
- ### Estructura de una Automation
554
-
555
- ```javascript
556
- /**
557
- * Función: procesar_pedido
558
- * Trigger: update cuando estado = 'confirmado'
559
- *
560
- * @param {Object} context - Contexto de ejecución
561
- * @param {Object} api - API para interactuar con el sistema
562
- * @param {Object} logger - Logger para debugging
563
- */
564
- async function procesar_pedido(context, api, logger) {
565
- // ═══════════════════════════════════════════════════════════
566
- // CONTEXT - Información del evento
567
- // ═══════════════════════════════════════════════════════════
568
- const {
569
- company_id, // ID de la company (ej: 116)
570
- module_id, // ID del módulo (ej: 308)
571
- entity_id, // ID de la entidad/tabla (ej: 4136)
572
- table, // Nombre físico de tabla (ej: "m308_t4136")
573
- row, // Registro actual (después del cambio)
574
- old_row, // Registro anterior (solo en update)
575
- input, // Input del usuario (solo en click)
576
- env, // Variables de entorno de la company
577
- user // Usuario que disparó el evento
578
- } = context;
579
-
580
- logger.info('Procesando pedido', { pedido_id: row.id, estado: row.estado });
581
-
582
- // ═══════════════════════════════════════════════════════════
583
- // API.QUERY - Consultas SQL
584
- // ═══════════════════════════════════════════════════════════
585
-
586
- // Obtener líneas del pedido
587
- const { rows: lineas } = await api.query(
588
- `SELECT lp.*, p.nombre as producto_nombre, p.stock
589
- FROM ${context.schema}.m308_t4137 lp -- lineas_pedido
590
- JOIN ${context.schema}.m308_t4138 p ON p.id = lp.producto_id
591
- WHERE lp.pedido_id = $1`,
592
- [row.id]
593
- );
594
-
595
- // Actualizar stock de productos
596
- for (const linea of lineas) {
597
- await api.query(
598
- `UPDATE ${context.schema}.m308_t4138
599
- SET stock = stock - $1
600
- WHERE id = $2`,
601
- [linea.cantidad, linea.producto_id]
602
- );
603
-
604
- logger.debug('Stock actualizado', {
605
- producto: linea.producto_nombre,
606
- cantidad: linea.cantidad
607
- });
608
- }
609
-
610
- // ═══════════════════════════════════════════════════════════
611
- // API.EMAIL - Enviar emails
612
- // ═══════════════════════════════════════════════════════════
613
-
614
- // Obtener datos del cliente
615
- const { rows: [cliente] } = await api.query(
616
- `SELECT * FROM ${context.schema}.m308_t4135 WHERE id = $1`,
617
- [row.cliente_id]
618
- );
619
-
620
- await api.email({
621
- to: cliente.email,
622
- subject: `Pedido ${row.numero} confirmado`,
623
- body: `
624
- Hola ${cliente.nombre},
625
-
626
- Tu pedido ${row.numero} ha sido confirmado.
627
-
628
- Total: ${row.total}€
629
-
630
- Gracias por tu compra.
631
- `,
632
- // O usar HTML:
633
- html: `<h1>Pedido Confirmado</h1><p>...</p>`
634
- });
635
-
636
- // ═══════════════════════════════════════════════════════════
637
- // API.HTTP - Llamadas a APIs externas
638
- // ═══════════════════════════════════════════════════════════
639
-
640
- // Webhook a sistema externo
641
- if (env.WEBHOOK_URL) {
642
- const response = await api.http(env.WEBHOOK_URL, {
643
- method: 'POST',
644
- headers: {
645
- 'Content-Type': 'application/json',
646
- 'Authorization': `Bearer ${env.WEBHOOK_TOKEN}`
647
- },
648
- body: JSON.stringify({
649
- event: 'pedido_confirmado',
650
- pedido: row,
651
- cliente: cliente
652
- })
653
- });
654
-
655
- logger.info('Webhook enviado', { status: response.status });
656
- }
657
-
658
- // ═══════════════════════════════════════════════════════════
659
- // RETURN - Resultado de la automation
660
- // ═══════════════════════════════════════════════════════════
661
-
662
- return {
663
- success: true,
664
- message: `Pedido ${row.numero} procesado correctamente`,
665
- data: {
666
- lineas_procesadas: lineas.length,
667
- email_enviado: true
668
- }
669
- };
670
- }
671
- ```
672
-
673
- ### Variables de Entorno (env)
674
-
675
- Cada company puede tener variables de entorno configuradas en `core.company_env_variables`:
676
-
677
- ```javascript
678
- // Acceso en automations
679
- const apiKey = env.STRIPE_API_KEY;
680
- const webhookUrl = env.SLACK_WEBHOOK;
681
- const emailFrom = env.EMAIL_FROM || 'noreply@gufi.com';
682
- ```
683
-
684
- ### Automation con Input del Usuario (Click)
685
-
686
- ```javascript
687
- // Definición en el JSON del módulo:
688
- {
689
- "trigger": "click",
690
- "function_name": "enviar_recordatorio",
691
- "label": "Enviar Recordatorio",
692
- "icon": "Mail",
693
- "input_schema": {
694
- "mensaje": {
695
- "type": "textarea",
696
- "label": "Mensaje personalizado",
697
- "required": true
698
- },
699
- "urgente": {
700
- "type": "boolean",
701
- "label": "Marcar como urgente",
702
- "default": false
703
- }
704
- }
705
- }
706
-
707
- // La función recibe el input:
708
- async function enviar_recordatorio(context, api, logger) {
709
- const { row, input } = context;
710
-
711
- // input.mensaje contiene el texto del usuario
712
- // input.urgente contiene true/false
713
-
714
- await api.email({
715
- to: row.cliente_email,
716
- subject: input.urgente ? '🚨 URGENTE: ' + row.numero : row.numero,
717
- body: input.mensaje
718
- });
719
-
720
- return { success: true };
721
- }
722
- ```
723
-
724
- ---
725
-
726
- ## Views (Vistas del Marketplace)
727
-
728
- Las Views son **componentes React/TypeScript** que los consultores escriben en el navegador y se almacenan en PostgreSQL. Se ejecutan dinámicamente en el frontend.
729
-
730
- ### Dónde se almacenan
731
-
732
- ```sql
733
- SELECT id, name, code FROM marketplace.views WHERE package_id = 14;
734
- ```
735
-
736
- ### Estructura de Archivos de una View
737
-
738
- Cuando descargas una view con `gufi view:pull <id>`, obtienes:
739
-
740
- ```
741
- ~/gufi-dev/view_<id>/
742
- ├── index.tsx # Entry point - exporta featureConfig y default
743
- ├── types.ts # Interfaces TypeScript
744
-
745
- ├── core/
746
- │ ├── dataProvider.ts # 💜 dataSources + featureConfig (MUY IMPORTANTE)
747
- │ └── permissions.ts # 💜 Sistema de permisos dinámico
748
-
749
- ├── metadata/
750
- │ ├── inputs.ts # 💜 Inputs configurables por usuario
751
- │ ├── help.es.ts # Documentación en español
752
- │ └── help.en.ts # Documentación en inglés
753
-
754
- ├── components/
755
- │ ├── MiComponente.tsx # Componentes React
756
- │ └── DevPermissionSwitcher.tsx # 💜 Testing de permisos en dev
757
-
758
- ├── views/
759
- │ └── page.tsx # Página principal
760
-
761
- └── automations/ # Opcional: automations de la vista
762
- └── mi_automation.js
763
- ```
764
-
765
- ---
766
-
767
- ### 💜 dataSources - Declarar qué Tablas Necesita la Vista
768
-
769
- El archivo `core/dataProvider.ts` define qué tablas necesita la vista.
770
-
771
- **Arquitectura:**
772
- 1. Se declaran en código (`core/dataProvider.ts`)
773
- 2. Se editan con CLI: `gufi pull` → editar → `gufi push`
774
- 3. Developer Center los muestra como **solo lectura**
775
- 4. Al instalar la vista, el sistema mapea nombres lógicos automáticamente
776
-
777
- ```typescript
778
- // core/dataProvider.ts
779
- // 💜 Mi Vista - Data Sources & Feature Configuration
780
-
781
- /* ============================================================================
782
- 💜 Data Sources - Tablas que necesita esta vista
783
- ============================================================================ */
784
- export const dataSources = [
785
- {
786
- key: 'tareasTable', // Identificador único
787
- type: 'table', // Siempre 'table'
788
- label: { es: 'Tabla de Tareas', en: 'Tasks Table' },
789
- description: {
790
- es: 'Tabla principal de tareas',
791
- en: 'Main tasks table'
792
- },
793
- required: true, // true = obligatorio
794
- },
795
- {
796
- key: 'proyectosTable',
797
- type: 'table',
798
- label: { es: 'Proyectos', en: 'Projects' },
799
- description: {
800
- es: 'Tabla de proyectos (opcional)',
801
- en: 'Projects table (optional)'
802
- },
803
- required: false, // false = opcional
804
- },
805
- {
806
- key: 'comentariosTable',
807
- type: 'table',
808
- label: { es: 'Comentarios', en: 'Comments' },
809
- description: {
810
- es: 'Comentarios en tareas',
811
- en: 'Task comments'
812
- },
813
- required: false,
814
- },
815
- ] as const;
816
-
817
- /* ============================================================================
818
- 💜 Feature Config - EXPORTAR SIEMPRE
819
- ============================================================================ */
820
- import { featureInputs } from '../metadata/inputs';
821
-
822
- export const featureConfig = {
823
- dataSources,
824
- inputs: featureInputs,
825
- };
826
-
827
- // También exporta funciones de carga de datos...
828
- ```
829
-
830
- **En el index.tsx:**
831
- ```typescript
832
- // index.tsx
833
- export { featureConfig } from './core/dataProvider';
834
- export default function MiVista({ gufi }) { ... }
835
- ```
836
-
837
- **Cómo acceder a las tablas configuradas:**
838
- ```typescript
839
- export default function MiVista({ gufi }) {
840
- const viewSpec = gufi?.context?.viewSpec || {};
841
-
842
- // Keys de dataSources → NOMBRES LÓGICOS (nunca IDs físicos)
843
- const tareasTable = viewSpec.tareasTable; // "tareas.asignaciones"
844
- const proyectosTable = viewSpec.proyectosTable; // "tareas.proyectos" o undefined
845
-
846
- // Usar para queries
847
- const res = await gufi.dataProvider.getList({
848
- resource: tareasTable,
849
- pagination: { current: 1, pageSize: 100 },
850
- });
851
- }
852
- ```
853
-
854
- ---
855
-
856
- ### 💜 inputs (featureInputs) - Configuración del Usuario
857
-
858
- Permite que el usuario configure opciones de la vista sin tocar código.
859
-
860
- ```typescript
861
- // metadata/inputs.ts
862
- // 💜 Mi Vista - Inputs Configurables
863
-
864
- export const featureInputs = [
865
- // BOOLEAN - Checkbox
866
- {
867
- key: 'showCompleted',
868
- type: 'boolean',
869
- label: { es: 'Mostrar completadas', en: 'Show completed' },
870
- description: {
871
- es: 'Incluir tareas completadas',
872
- en: 'Include completed tasks'
873
- },
874
- default: false,
875
- },
876
-
877
- // SELECT - Dropdown estático
878
- {
879
- key: 'defaultView',
880
- type: 'select',
881
- label: { es: 'Vista por defecto', en: 'Default view' },
882
- description: {
883
- es: 'Panel inicial al abrir',
884
- en: 'Initial panel on open'
885
- },
886
- options: [
887
- { value: 'list', label: { es: 'Lista', en: 'List' } },
888
- { value: 'kanban', label: { es: 'Kanban', en: 'Kanban' } },
889
- { value: 'calendar', label: { es: 'Calendario', en: 'Calendar' } },
890
- ],
891
- default: 'list',
892
- },
893
-
894
- // NUMBER - Input numérico
895
- {
896
- key: 'lowStockThreshold',
897
- type: 'number',
898
- label: { es: 'Umbral stock bajo (%)', en: 'Low stock threshold (%)' },
899
- description: {
900
- es: 'Porcentaje para alertas',
901
- en: 'Percentage for alerts'
902
- },
903
- placeholder: '40',
904
- default: 40,
905
- },
906
-
907
- // TEXT - Input de texto
908
- {
909
- key: 'emailCc',
910
- type: 'text',
911
- label: { es: 'Email CC', en: 'CC Email' },
912
- description: {
913
- es: 'Email en copia',
914
- en: 'CC email'
915
- },
916
- placeholder: 'admin@empresa.com',
917
- },
918
-
919
- // SELECT con opciones de otra tabla (dinámico)
920
- {
921
- key: 'defaultWarehouse',
922
- type: 'select',
923
- label: { es: 'Almacén por defecto', en: 'Default warehouse' },
924
- description: {
925
- es: 'Almacén seleccionado al abrir',
926
- en: 'Warehouse on open'
927
- },
928
- optionsFrom: 'warehousesTable', // 💜 Carga opciones del dataSource
929
- default: 'all',
930
- },
931
- ] as const;
932
- ```
933
-
934
- **Acceder a inputs en runtime:**
935
- ```typescript
936
- export default function MiVista({ gufi }) {
937
- const viewSpec = gufi?.context?.viewSpec || {};
938
-
939
- // Los inputs están directamente en viewSpec
940
- const showCompleted = viewSpec.showCompleted ?? false;
941
- const defaultView = viewSpec.defaultView ?? 'list';
942
- const threshold = viewSpec.lowStockThreshold ?? 40;
943
- const emailCc = viewSpec.emailCc;
944
- }
945
- ```
946
-
947
- ---
948
-
949
- ### 💜 help - Documentación para Usuarios
950
-
951
- Crea archivos de ayuda que aparecen en el botón "?" de la vista.
952
-
953
- ```typescript
954
- // metadata/help.es.ts
955
- export const help = {
956
- // Para Admin/Consultant
957
- consultant: {
958
- title: "Mi Vista",
959
- description: "Descripción breve de la vista.",
960
- sections: [
961
- {
962
- id: "overview",
963
- title: "Descripción General",
964
- content: `
965
- **Funcionalidades:**
966
- - Lista de tareas con filtros
967
- - Vista Kanban por proyecto
968
- - Sistema de comentarios
969
-
970
- **Tablas requeridas:**
971
- | DataSource | Descripción |
972
- |------------|-------------|
973
- | tareasTable | Tabla principal de tareas |
974
- | proyectosTable | Proyectos (opcional) |
975
- `.trim(),
976
- },
977
- {
978
- id: "configuration",
979
- title: "Configuración",
980
- content: `
981
- **Campos requeridos en tareas:**
982
- - titulo (text)
983
- - estado (select: pendiente, en_progreso, completada)
984
- - asignado_a (users)
985
- - proyecto_id (relation, opcional)
986
- `.trim(),
987
- },
988
- {
989
- id: "permissions",
990
- title: "Permisos",
991
- content: `
992
- | Permiso | Efecto |
993
- |---------|--------|
994
- | * | Acceso completo |
995
- | entity:view | Solo lectura |
996
- | create_tasks | Puede crear tareas |
997
- `.trim(),
998
- },
999
- ],
1000
- },
1001
-
1002
- // Para usuarios finales
1003
- user: {
1004
- title: "Guía de Uso",
1005
- sections: [
1006
- {
1007
- id: "basics",
1008
- title: "Uso Básico",
1009
- content: `
1010
- 1. **Mis Tareas**: Tareas asignadas a ti
1011
- 2. **Proyectos**: Vista Kanban por proyecto
1012
- 3. **Gestión**: Tareas que has asignado
1013
-
1014
- Para crear tarea, pulsa el botón **+**
1015
- `.trim(),
1016
- },
1017
- ],
1018
- },
1019
- };
1020
- ```
1021
-
1022
- ---
1023
-
1024
- ### 💜 permissions - Sistema de Permisos Explícitos (Sin Wildcards)
1025
-
1026
- Las vistas usan un sistema de **permisos explícitos** - sin wildcards, sin magia. Cada permiso es un string específico que existe o no en el array de permisos del usuario.
1027
-
1028
- > ⚠️ **Importante**: Eliminamos el soporte de wildcards (`*`) porque causaba problemas de integridad entre el estado de la UI y los permisos reales. Siempre usa strings de permisos explícitos.
1029
-
1030
- **1. Declarar permisos en `metadata/permissions.ts`:**
1031
- ```typescript
1032
- // metadata/permissions.ts
1033
- export const permissions = [
1034
- // Permisos estáticos - toggles simples
1035
- {
1036
- key: 'send_orders',
1037
- label: { es: 'Enviar pedidos', en: 'Send orders' },
1038
- description: { es: 'Puede enviar pedidos a proveedores', en: 'Can send orders' },
1039
- },
1040
- {
1041
- key: 'view_costs',
1042
- label: { es: 'Ver costos', en: 'View costs' },
1043
- description: { es: 'Puede ver precios de compra', en: 'Can see purchase prices' },
1044
- },
1045
-
1046
- // Permisos dinámicos - se resuelven en runtime
1047
- {
1048
- key: 'warehouse:*', // Placeholder, se resuelve a warehouse:madrid, warehouse:barcelona, etc.
1049
- label: { es: 'Acceso a almacén', en: 'Warehouse access' },
1050
- description: { es: 'Almacenes a los que tiene acceso', en: 'Warehouses user can access' },
1051
- dynamic: true, // Marca este como dinámico
1052
- },
1053
- ] as const;
1054
- ```
1055
-
1056
- **2. Crear `core/permissions.ts` (sin wildcards):**
1057
- ```typescript
1058
- // core/permissions.ts
1059
- // SIN WILDCARDS - solo permisos explícitos
1060
-
1061
- export interface PermissionsConfig {
1062
- userPermissions: string[]; // Permisos del usuario (resueltos desde su rol)
1063
- isDevMode: boolean;
1064
- devPermissions: string[]; // Override en modo dev para testing
1065
- }
1066
-
1067
- // Obtener permisos efectivos (dev mode override)
1068
- function getEffectivePermissions(config: PermissionsConfig): string[] {
1069
- const { userPermissions, isDevMode, devPermissions } = config;
1070
- return (isDevMode && devPermissions.length > 0) ? devPermissions : userPermissions;
1071
- }
1072
-
1073
- // Check simple - ¿tiene exactamente este permiso?
1074
- export function hasPermission(config: PermissionsConfig, permission: string): boolean {
1075
- const perms = getEffectivePermissions(config);
1076
- return perms.includes(permission);
1077
- }
1078
-
1079
- // Helpers específicos de la vista
1080
- export function canSendOrders(config: PermissionsConfig): boolean {
1081
- return hasPermission(config, 'send_orders');
1082
- }
1083
-
1084
- export function canViewCosts(config: PermissionsConfig): boolean {
1085
- return hasPermission(config, 'view_costs');
1086
- }
1087
-
1088
- // Obtener warehouses permitidos (null si no hay restricciones)
1089
- export function getAllowedWarehouses(config: PermissionsConfig): string[] | null {
1090
- const perms = getEffectivePermissions(config);
1091
-
1092
- const warehouses: string[] = [];
1093
- for (const perm of perms) {
1094
- if (perm.startsWith('warehouse:')) {
1095
- warehouses.push(perm.slice('warehouse:'.length));
1096
- }
1097
- }
1098
-
1099
- return warehouses.length > 0 ? warehouses : null;
1100
- }
1101
- ```
1102
-
1103
- **3. Usar en el componente con inicialización correcta:**
1104
- ```typescript
1105
- import { useState, useEffect, useRef } from 'react';
1106
- import { DevPermissionSwitcher } from '../components/DevPermissionSwitcher';
1107
- import { canSendOrders, getAllowedWarehouses } from '../core/permissions';
1108
-
1109
- export default function MiVista({ gufi }) {
1110
- const lang = gufi?.context?.lang || 'es';
1111
- const isDevMode = gufi?.context?.isPreview || window.location.hostname === 'localhost';
1112
-
1113
- // 💜 IMPORTANTE: Inicializar vacío, poblar cuando los datos carguen
1114
- const [devPermissions, setDevPermissions] = useState<string[]>([]);
1115
- const devPermissionsInitialized = useRef(false);
1116
-
1117
- // Cargar warehouses de tu data source
1118
- const [warehouses, setWarehouses] = useState([]);
1119
-
1120
- // Inicializar permisos cuando warehouses carguen (una sola vez)
1121
- useEffect(() => {
1122
- if (isDevMode && !devPermissionsInitialized.current && warehouses.length > 0) {
1123
- devPermissionsInitialized.current = true;
1124
-
1125
- // Otorgar todos los permisos explícitos
1126
- const allPerms = [
1127
- 'send_orders',
1128
- 'view_costs',
1129
- ...warehouses.map(w => `warehouse:${w.name.toLowerCase()}`),
1130
- ];
1131
- setDevPermissions(allPerms);
1132
- }
1133
- }, [isDevMode, warehouses]);
1134
-
1135
- // Construir config de permisos
1136
- const permissionsConfig = {
1137
- userPermissions: gufi?.context?.user?.permissions || [],
1138
- isDevMode,
1139
- devPermissions,
1140
- };
1141
-
1142
- // Usar helpers de permisos
1143
- const showCosts = canViewCosts(permissionsConfig);
1144
- const allowedWarehouses = getAllowedWarehouses(permissionsConfig);
1145
-
1146
- return (
1147
- <div>
1148
- {showCosts && <CostsColumn />}
1149
-
1150
- {/* DevPermissionSwitcher - solo en dev mode */}
1151
- {isDevMode && (
1152
- <DevPermissionSwitcher
1153
- lang={lang}
1154
- activePermissions={devPermissions}
1155
- onPermissionsChange={setDevPermissions}
1156
- dynamicValues={{
1157
- warehouse: warehouses.map(w => w.name.toLowerCase())
1158
- }}
1159
- />
1160
- )}
1161
- </div>
1162
- );
1163
- }
1164
- ```
1165
-
1166
- **DevPermissionSwitcher**: Botón flotante **purple** (💜 Gufi style) que permite:
1167
- - Toggle individual de permisos on/off
1168
- - Botón "Todos" en el header para acceso completo
1169
- - Permisos dinámicos expandibles (ej: warehouses individuales)
1170
- - Integridad total: UI siempre refleja el estado real
1171
-
1172
- ### 💜 DevPermissionSwitcher AUTOMÁTICO (LivePreviewPage)
1173
-
1174
- **¡Ya no necesitas incluir DevPermissionSwitcher en tu código!** LivePreviewPage detecta automáticamente si tu vista tiene `featureConfig.permissions` y muestra el botón DEV.
1175
-
1176
- **Cómo funciona:**
1177
- 1. Declara permisos en `metadata/permissions.ts`
1178
- 2. Expórtalos en `featureConfig`:
1179
- ```typescript
1180
- // core/dataProvider.ts
1181
- import { permissions } from '../metadata/permissions';
1182
-
1183
- export const featureConfig = {
1184
- dataSources,
1185
- inputs: featureInputs,
1186
- permissions, // 💜 Solo añadir esto
1187
- };
1188
- ```
1189
- 3. LivePreviewPage lo detecta y muestra el botón DEV automáticamente
1190
-
1191
- **Usar devPermissions del contexto:**
1192
- ```typescript
1193
- export default function MiVista({ gufi }) {
1194
- // 💜 LivePreviewPage provee devPermissions automáticamente
1195
- const effectiveDevPermissions = gufi?.context?.devPermissions || [];
1196
-
1197
- const permissionsConfig = {
1198
- userPermissions: gufi?.context?.userPermissions || [],
1199
- isDevMode: gufi?.context?.isPreview || gufi?.context?.isDev,
1200
- devPermissions: effectiveDevPermissions,
1201
- };
1202
-
1203
- // Usar helpers de permisos normalmente
1204
- const canSend = hasPermission(permissionsConfig, 'send_orders');
1205
- }
1206
- ```
1207
-
1208
- **Cuándo incluir DevPermissionSwitcher manualmente:**
1209
- - Solo si la vista se ejecuta fuera de LivePreviewPage (ej: vista nativa del frontend)
1210
- - Para vistas del marketplace en Developer Center → **NO necesario**, es automático
1211
-
1212
- **Principio clave**: Inicializar con `[]` vacío, luego usar `useEffect` para otorgar todos los permisos cuando los valores dinámicos (como warehouses) terminen de cargar. Esto garantiza integridad UI.
1213
-
1214
- ---
1215
-
1216
- ### 💜 automations en Vistas
1217
-
1218
- Las vistas pueden incluir automations que se ejecutan al hacer click.
1219
-
1220
- **Archivo de automation:**
1221
- ```javascript
1222
- // automations/send_notification.js
1223
- /**
1224
- * 💜 Automation: send_notification
1225
- * Trigger: CLICK (desde la vista)
1226
- */
1227
- async function send_notification(context, api, logger) {
1228
- const { input, env } = context;
1229
- const { tarea_id, mensaje } = input;
1230
-
1231
- // Obtener tarea
1232
- const { rows } = await api.query(
1233
- 'SELECT titulo, asignado_a FROM tareas WHERE id = $1',
1234
- [tarea_id]
1235
- );
1236
-
1237
- // Obtener email del asignado
1238
- const { rows: users } = await api.query(
1239
- 'SELECT email FROM core.users WHERE id = $1',
1240
- [rows[0].asignado_a]
1241
- );
1242
-
1243
- // Enviar email
1244
- await api.email({
1245
- to: users[0].email,
1246
- subject: `Notificación: ${rows[0].titulo}`,
1247
- html: `<p>${mensaje}</p>`,
1248
- cc: env.NOTIFICATION_CC,
1249
- });
1250
-
1251
- logger.info('Notificación enviada', { tarea_id });
1252
- return { success: true };
1253
- }
1254
- ```
1255
-
1256
- **Llamar automation desde React:**
1257
- ```typescript
1258
- const handleNotify = async (tareaId: number) => {
1259
- try {
1260
- const result = await gufi.utils.runClickAutomation({
1261
- functionName: 'send_notification',
1262
- input: {
1263
- tarea_id: tareaId,
1264
- mensaje: 'Tu tarea ha sido actualizada',
1265
- },
1266
- });
1267
-
1268
- if (result.success) {
1269
- gufi.utils.toastSuccess('Notificación enviada');
1270
- }
1271
- } catch (err) {
1272
- gufi.utils.toastError(err.message);
1273
- }
1274
- };
1275
- ```
1276
-
1277
- ---
1278
-
1279
- ### 💜 El Objeto `gufi` - Props Completas
1280
-
1281
- Toda vista recibe `gufi` con todo lo necesario:
1282
-
1283
- ```typescript
1284
- interface GufiProps {
1285
- context: {
1286
- user: { id, email, name, role };
1287
- lang: 'es' | 'en';
1288
- viewSpec: {
1289
- // dataSources configurados
1290
- tareasTable: string;
1291
- proyectosTable?: string;
1292
- // inputs configurados
1293
- showCompleted: boolean;
1294
- defaultView: string;
1295
- lowStockThreshold: number;
1296
- };
1297
- companyId: number;
1298
- moduleId: number;
1299
- entityId: number;
1300
- };
1301
-
1302
- dataProvider: {
1303
- getList({ resource, pagination, filters, sorters });
1304
- getOne({ resource, id });
1305
- create({ resource, variables });
1306
- update({ resource, id, variables });
1307
- deleteOne({ resource, id });
1308
- };
1309
-
1310
- utils: {
1311
- toastSuccess(msg: string);
1312
- toastError(msg: string);
1313
- toastInfo(msg: string);
1314
- runClickAutomation({ functionName, input });
1315
- };
1316
- }
1317
- ```
1318
-
1319
- ---
1320
-
1321
- ### 💜 seedData - Datos de Ejemplo para Demo/Testing
1322
-
1323
- Las vistas pueden incluir datos de ejemplo que se cargan automáticamente al instalar. Esto es útil para demos, testing, y onboarding de nuevos usuarios.
1324
-
1325
- ```typescript
1326
- // metadata/seedData.ts
1327
- export interface SeedDataConfig {
1328
- description: { es: string; en: string };
1329
- data: { [dataSourceKey: string]: Array<Record<string, any>> };
1330
- order: string[]; // Orden de creación (importante para referencias)
1331
- }
1332
-
1333
- export const seedData: SeedDataConfig = {
1334
- description: {
1335
- es: 'Crea empresas de ejemplo (Singular, FitVending), proyectos y tareas de prueba',
1336
- en: 'Creates sample companies, projects and test tasks',
1337
- },
1338
-
1339
- // Orden de creación - tablas con referencias van después
1340
- order: ['empresasTable', 'proyectosTable', 'tareasTable'],
1341
-
1342
- data: {
1343
- // Empresas/Clientes
1344
- empresasTable: [
1345
- {
1346
- nombre: 'Singular',
1347
- contacto: 'Contacto Singular',
1348
- email: 'contacto@singular.es',
1349
- estado: 'activo'
1350
- },
1351
- {
1352
- nombre: 'FitVending',
1353
- contacto: 'Equipo FitVending',
1354
- email: 'info@fitvending.es',
1355
- estado: 'activo'
1356
- },
1357
- ],
1358
-
1359
- // Proyectos
1360
- proyectosTable: [
1361
- {
1362
- nombre: 'Gufi ERP',
1363
- descripcion: 'Desarrollo del ERP Gufi',
1364
- color: '#8B5CF6',
1365
- estado: 'activo',
1366
- },
1367
- ],
1368
-
1369
- // Tareas - con referencias a otras tablas
1370
- tareasTable: [
1371
- {
1372
- titulo: 'Revisar bug crítico',
1373
- descripcion: 'Los usuarios reportan problemas',
1374
- asignado_a: '@currentUser', // 💜 Token especial: usuario actual
1375
- asignado_por: '@currentUser',
1376
- prioridad: 'urgente',
1377
- estado: 'pendiente',
1378
- fecha_limite: '@today', // 💜 Token especial: fecha de hoy
1379
- proyecto_id: '@ref:proyectosTable.0', // 💜 Referencia: ID del primer proyecto
1380
- empresa_id: '@ref:empresasTable.0', // 💜 Referencia: ID de primera empresa
1381
- },
1382
- {
1383
- titulo: 'Preparar demo',
1384
- descripcion: 'Demo del módulo de facturación',
1385
- asignado_a: '@currentUser',
1386
- prioridad: 'alta',
1387
- fecha_limite: '@tomorrow', // 💜 Token especial: mañana
1388
- empresa_id: '@ref:empresasTable.1',
1389
- },
1390
- ],
1391
- },
1392
- };
1393
- ```
1394
-
1395
- **Tokens Especiales:**
1396
- | Token | Descripción |
1397
- |-------|-------------|
1398
- | `@currentUser` | ID del usuario actual (para campos users) |
1399
- | `@today` | Fecha de hoy (YYYY-MM-DD) |
1400
- | `@tomorrow` | Fecha de mañana |
1401
- | `@nextWeek` | Fecha dentro de 7 días |
1402
- | `@ref:tableKey.index` | ID del registro creado en otra tabla |
1403
-
1404
- **Agregar a featureConfig:**
1405
- ```typescript
1406
- // core/dataProvider.ts
1407
- import { seedData } from '../metadata/seedData';
1408
-
1409
- export const featureConfig = {
1410
- dataSources,
1411
- inputs: featureInputs,
1412
- seedData, // 💜 Agregar aquí
1413
- };
1414
- ```
1415
-
1416
- **Cargar desde Developer Center:**
1417
- En la página de edición de vista, el Developer Center muestra un botón "Load Sample Data" que ejecuta el seedData en la company seleccionada.
1418
-
1419
- ---
1420
-
1421
- ### 💜 Checklist para Nueva Vista
1422
-
1423
- 1. [ ] `core/dataProvider.ts` - dataSources + export featureConfig
1424
- 2. [ ] `metadata/inputs.ts` - featureInputs configurables
1425
- 3. [ ] `metadata/seedData.ts` - Datos de ejemplo (opcional pero recomendado)
1426
- 4. [ ] `metadata/help.es.ts` y `help.en.ts` - Documentación
1427
- 5. [ ] `index.tsx` - export featureConfig y default component
1428
- 6. [ ] Usar `gufi?.context?.viewSpec` para tablas e inputs
1429
- 7. [ ] Usar `gufi?.dataProvider` para CRUD
1430
- 8. [ ] Usar `gufi?.utils?.toast*` para notificaciones
1431
- 9. [ ] Si hay automation: carpeta `automations/` con .js
1432
-
1433
- ### View.tsx - Componente Principal
1434
-
1435
- ```tsx
1436
- import React, { useState, useEffect } from 'react';
1437
- import { useList, useUpdate, useCreate } from '@refinedev/core';
1438
-
1439
- // ═══════════════════════════════════════════════════════════════
1440
- // PROPS que recibe toda View
1441
- // ═══════════════════════════════════════════════════════════════
1442
- interface ViewProps {
1443
- // Configuración de la vista
1444
- viewSpec: {
1445
- id: number;
1446
- name: string;
1447
- config: Record<string, any>; // Config guardada por el usuario
1448
- };
1449
-
1450
- // Contexto de la company
1451
- context: {
1452
- companyId: number;
1453
- moduleId: number;
1454
- entityId: number;
1455
- };
1456
-
1457
- // Usuario actual
1458
- user: {
1459
- id: number;
1460
- email: string;
1461
- name: string;
1462
- role: string;
1463
- };
1464
-
1465
- // Notificaciones
1466
- toastSuccess: (message: string) => void;
1467
- toastError: (message: string) => void;
1468
- toastInfo: (message: string) => void;
1469
- }
1470
-
1471
- // ═══════════════════════════════════════════════════════════════
1472
- // COMPONENTE PRINCIPAL
1473
- // ═══════════════════════════════════════════════════════════════
1474
- export default function MiVista({ viewSpec, context, user, toastSuccess, toastError }: ViewProps) {
1475
- const [filtro, setFiltro] = useState('todos');
1476
-
1477
- // ─────────────────────────────────────────────────────────────
1478
- // useList - Obtener lista de registros
1479
- // ─────────────────────────────────────────────────────────────
1480
- const { data, isLoading, refetch } = useList({
1481
- resource: 'm360_t16192', // Nombre físico de la tabla
1482
- pagination: { current: 1, pageSize: 100 },
1483
- filters: filtro !== 'todos' ? [
1484
- { field: 'estado', operator: 'eq', value: filtro }
1485
- ] : [],
1486
- sorters: [
1487
- { field: 'created_at', order: 'desc' }
1488
- ]
1489
- });
1490
-
1491
- // ─────────────────────────────────────────────────────────────
1492
- // useUpdate - Actualizar registros
1493
- // ─────────────────────────────────────────────────────────────
1494
- const { mutate: updateRecord } = useUpdate();
1495
-
1496
- const handleStatusChange = (id: number, newStatus: string) => {
1497
- updateRecord(
1498
- {
1499
- resource: 'm360_t16192',
1500
- id,
1501
- values: { estado: newStatus }
1502
- },
1503
- {
1504
- onSuccess: () => {
1505
- toastSuccess('Estado actualizado');
1506
- refetch();
1507
- },
1508
- onError: (error) => {
1509
- toastError('Error al actualizar: ' + error.message);
1510
- }
1511
- }
1512
- );
1513
- };
1514
-
1515
- // ─────────────────────────────────────────────────────────────
1516
- // useCreate - Crear registros
1517
- // ─────────────────────────────────────────────────────────────
1518
- const { mutate: createRecord } = useCreate();
1519
-
1520
- const handleCreate = (data: any) => {
1521
- createRecord(
1522
- {
1523
- resource: 'm360_t16192',
1524
- values: data
1525
- },
1526
- {
1527
- onSuccess: () => {
1528
- toastSuccess('Registro creado');
1529
- refetch();
1530
- }
1531
- }
1532
- );
1533
- };
1534
-
1535
- // ─────────────────────────────────────────────────────────────
1536
- // RENDER
1537
- // ─────────────────────────────────────────────────────────────
1538
- if (isLoading) {
1539
- return (
1540
- <div className="flex items-center justify-center h-64">
1541
- <div className="animate-spin rounded-full h-8 w-8 border-b-2 border-violet-600" />
1542
- </div>
1543
- );
1544
- }
1545
-
1546
- return (
1547
- <div className="p-6 bg-gradient-to-br from-violet-50/40 via-white to-purple-50/40 min-h-screen">
1548
- {/* Header */}
1549
- <div className="flex items-center justify-between mb-6">
1550
- <h1 className="text-2xl font-bold text-gray-900">
1551
- {viewSpec.name}
1552
- </h1>
1553
- <div className="flex gap-2">
1554
- <select
1555
- value={filtro}
1556
- onChange={(e) => setFiltro(e.target.value)}
1557
- className="px-3 py-2 border rounded-lg"
1558
- >
1559
- <option value="todos">Todos</option>
1560
- <option value="pendiente">Pendientes</option>
1561
- <option value="completado">Completados</option>
1562
- </select>
1563
- </div>
1564
- </div>
1565
-
1566
- {/* Lista */}
1567
- <div className="grid gap-4">
1568
- {data?.data.map((item: any) => (
1569
- <div
1570
- key={item.id}
1571
- className="p-4 bg-white rounded-xl shadow-sm border border-gray-100 hover:shadow-md transition-shadow"
1572
- >
1573
- <div className="flex items-center justify-between">
1574
- <div>
1575
- <h3 className="font-medium text-gray-900">{item.titulo}</h3>
1576
- <p className="text-sm text-gray-500">{item.descripcion}</p>
1577
- </div>
1578
- <button
1579
- onClick={() => handleStatusChange(item.id, 'completado')}
1580
- className="px-4 py-2 bg-violet-600 text-white rounded-lg hover:bg-violet-700"
1581
- >
1582
- Completar
1583
- </button>
1584
- </div>
1585
- </div>
1586
- ))}
1587
- </div>
1588
-
1589
- {/* Empty state */}
1590
- {data?.data.length === 0 && (
1591
- <div className="text-center py-12 text-gray-500">
1592
- No hay registros que mostrar
1593
- </div>
1594
- )}
1595
- </div>
1596
- );
1597
- }
1598
- ```
1599
-
1600
- ### dataProvider.ts - Lógica de Datos Custom
1601
-
1602
- ```typescript
1603
- // core/dataProvider.ts
1604
- import { DataProvider } from '@refinedev/core';
1605
-
1606
- // Puedes extender el dataProvider por defecto
1607
- export const customDataProvider = (baseProvider: DataProvider): DataProvider => ({
1608
- ...baseProvider,
1609
-
1610
- // Override getList para transformar datos
1611
- getList: async (params) => {
1612
- const result = await baseProvider.getList(params);
1613
-
1614
- // Transformar datos antes de devolverlos
1615
- return {
1616
- ...result,
1617
- data: result.data.map(item => ({
1618
- ...item,
1619
- // Agregar campos calculados
1620
- diasPendiente: calcularDias(item.created_at),
1621
- prioridadColor: getPrioridadColor(item.prioridad)
1622
- }))
1623
- };
1624
- },
1625
-
1626
- // Custom method para operaciones especiales
1627
- custom: async ({ url, method, payload }) => {
1628
- const response = await fetch(url, {
1629
- method,
1630
- headers: { 'Content-Type': 'application/json' },
1631
- body: JSON.stringify(payload)
1632
- });
1633
- return response.json();
1634
- }
1635
- });
1636
-
1637
- function calcularDias(fecha: string): number {
1638
- const diff = Date.now() - new Date(fecha).getTime();
1639
- return Math.floor(diff / (1000 * 60 * 60 * 24));
1640
- }
1641
-
1642
- function getPrioridadColor(prioridad: string): string {
1643
- const colores: Record<string, string> = {
1644
- alta: 'red',
1645
- media: 'yellow',
1646
- baja: 'green'
1647
- };
1648
- return colores[prioridad] || 'gray';
1649
- }
1650
- ```
1651
-
1652
- ---
1653
-
1654
- ## Packages del Marketplace
1655
-
1656
- Un Package agrupa múltiples Views y módulos relacionados para distribuirlos.
1657
-
1658
- ### Estructura en Base de Datos
1659
-
1660
- ```sql
1661
- -- Package publicado
1662
- SELECT * FROM marketplace.packages WHERE id = 14;
1663
- -- { id: 14, name: "Stock Management", version: "1.2.0", published_at: ... }
1664
-
1665
- -- Vistas del package
1666
- SELECT id, name FROM marketplace.views WHERE package_id = 14;
1667
- -- { id: 5, name: "Stock Overview" }
1668
- -- { id: 6, name: "Inventory Report" }
1669
-
1670
- -- Módulos incluidos (snapshot)
1671
- SELECT * FROM marketplace.package_modules WHERE package_id = 14;
1672
- ```
1673
-
1674
- ### Flujo de Desarrollo → Publicación
1675
-
1676
- ```
1677
- 1. DESARROLLO (en tu company)
1678
- └── Creas módulo "Stock" en company 116
1679
- └── Creas views en Developer Center
1680
- └── Testas todo localmente
1681
-
1682
- 2. CREAR PACKAGE
1683
- └── Developer Center → Packages → New Package
1684
- └── Agregas módulo "Stock"
1685
- └── Agregas views "Stock Overview", "Inventory Report"
1686
-
1687
- 3. PUBLICAR
1688
- └── Click "Publish to Marketplace"
1689
- └── Sistema crea snapshot inmutable
1690
- └── Versión 1.0.0 disponible
1691
-
1692
- 4. INSTALACIÓN (otra company)
1693
- └── Company 200 instala el package
1694
- └── Sistema crea módulo "Stock" en company_200
1695
- └── Views disponibles en su menú
1696
- ```
1697
-
1698
- ---
1699
-
1700
- ## Comandos del CLI
1701
-
1702
- ### 💜 Resumen Rápido para Claude
1703
-
1704
- **El CLI gestiona companies, módulos, automations y datos. Todo es dinámico - detecta la company automáticamente.**
1705
-
1706
- | Quiero... | Comando |
1707
- |-----------|---------|
1708
- | Ver companies | `gufi companies` |
1709
- | Ver módulos de company | `gufi modules 146` |
1710
- | Ver/editar módulo | `gufi module 360` |
1711
- | Ver registros | `gufi rows m308_t4136` |
1712
- | Listar scripts | `gufi automations` |
1713
- | Ver/editar script | `gufi automation 15` |
1714
- | **Ver índice del worker** | `gufi automations:meta` |
1715
- | **Ver ejecuciones** | `gufi automations:executions` |
1716
- | **Ver/editar triggers** | `gufi entity:automations 4136` |
1717
- | **Ver mis packages** | `gufi packages` |
1718
- | **Ver package** | `gufi package 14` |
1719
- | **Crear package** | `gufi package:create "Nombre"` |
1720
- | **Desarrollar view** | `gufi pull 13` → `gufi watch` → `gufi logs` |
1721
-
1722
- **3 cosas que saber:**
1723
- 1. **Auto-login**: `gufi login` guarda credenciales → funciona para siempre (logout NO las borra)
1724
- 2. **Auto-detección**: `module`, `automation`, `rows` detectan la company automáticamente
1725
- 3. **Sin prefijos**: `gufi pull/push/watch/logs` (no `view:pull`, etc.)
1726
-
1727
- ### Instalación
11
+ ## Quick Reference
1728
12
 
1729
13
  ```bash
1730
- npm install -g gufi-cli
1731
- gufi login
1732
- ```
1733
-
1734
- ### 💜 Autenticación Persistente (Auto-Login)
14
+ # Contexto y diagnóstico
15
+ gufi context # Genera contexto para Claude
16
+ gufi doctor # Diagnóstico del sistema
1735
17
 
1736
- El CLI guarda las credenciales en `~/.gufi/config.json` y hace **auto-login automático** cuando es necesario:
1737
-
1738
- ```json
1739
- {
1740
- "apiUrl": "https://gogufi.com",
1741
- "email": "user@example.com",
1742
- "password": "secreto",
1743
- "token": "eyJ...",
1744
- "refreshToken": "eyJ..."
1745
- }
1746
- ```
18
+ # Entornos
19
+ gufi config # Ver entornos
20
+ gufi config:local # Cambiar a localhost
21
+ gufi config:prod # Cambiar a producción
1747
22
 
1748
- **Flujo automático:**
1749
- 1. `gufi login` pide email/password → los guarda permanentemente
1750
- 2. Cualquier comando si token expiró → auto-login con credenciales guardadas
1751
- 3. `gufi logout` → borra TODO (token + credenciales)
23
+ # Módulos
24
+ gufi modules 146 # Ver módulos
25
+ gufi module 360 --edit # Editar con $EDITOR
1752
26
 
1753
- **Resultado:** Una vez haces `gufi login`, el CLI funciona para siempre sin volver a pedir credenciales. Si el token expira, hace auto-login automáticamente.
27
+ # Automations
28
+ gufi automations # Ver scripts
29
+ gufi automation 15 --edit # Editar script
30
+ gufi entity:automations 4136 # Ver triggers
1754
31
 
1755
- **Nota:** Si haces login en el navegador web, el refreshToken del CLI se invalida, pero el CLI hace auto-login automáticamente con las credenciales guardadas.
32
+ # Vistas
33
+ gufi view:pull 13 # Descargar
34
+ gufi view:push # Subir cambios
35
+ gufi view:watch # Auto-sync
1756
36
 
1757
- ### Gestión de Companies y Módulos
1758
-
1759
- ```bash
1760
- # Ver companies
1761
- gufi companies
1762
-
1763
- # Ver módulos de una company
1764
- gufi modules 146
1765
-
1766
- # Ver JSON de un módulo (auto-detecta company)
1767
- gufi module 360
1768
-
1769
- # Editar módulo con tu editor
1770
- gufi module 360 --edit
1771
-
1772
- # Guardar JSON a archivo
1773
- gufi module 360 --file modulo.json
1774
-
1775
- # Actualizar módulo desde archivo
1776
- gufi module:update 360 modulo.json -c 146
1777
-
1778
- # Validar sin aplicar cambios
1779
- gufi module:update 360 modulo.json -c 146 --dry-run
1780
-
1781
- # Crear nueva company
1782
- gufi company:create "Mi Empresa"
1783
- ```
1784
-
1785
- ### Automations (Scripts Globales)
1786
-
1787
- ```bash
1788
- # Listar automation scripts
1789
- gufi automations -c 116
1790
-
1791
- # Ver código de una automation
1792
- gufi automation calcular_stock -c 116
1793
-
1794
- # Editar con tu editor
1795
- gufi automation calcular_stock -c 116 --edit
1796
-
1797
- # Guardar a archivo
1798
- gufi automation calcular_stock -c 116 --file script.js
1799
-
1800
- # Crear/actualizar automation desde archivo JS
1801
- gufi automation:create calcular_stock script.js -c 116
1802
-
1803
- # 💜 Debugging - Ver índice del worker (auto-synced desde entities.automations)
1804
- gufi automations:meta -c 116
1805
-
1806
- # 💜 Ver historial de ejecuciones
1807
- gufi automations:executions -c 116
1808
- gufi automations:executions -c 116 --limit 50
1809
- gufi automations:executions -c 116 --script sync_nayax
37
+ # Datos
38
+ gufi rows m360_t16192 # Listar registros
39
+ gufi row:create table --data '{...}'
1810
40
  ```
1811
41
 
1812
- ### Entity Automations (Automations por Entidad)
42
+ ## Output JSON (para Claude/scripts)
1813
43
 
1814
- Las automations de entidad están vinculadas a una entidad específica (tabla) y se almacenan en `core.entities.automations`.
44
+ Todos los comandos de lectura soportan `--json` para output estructurado sin colores ni emojis:
1815
45
 
1816
46
  ```bash
1817
- # Ver automations de una entidad (auto-detecta company)
1818
- gufi entity:automations 4589
1819
-
1820
- # Editar automations con tu editor ($EDITOR)
1821
- gufi entity:automations 4589 --edit
1822
-
1823
- # Guardar automations a archivo JSON
1824
- gufi entity:automations 4589 --file automations.json
1825
-
1826
- # Actualizar automations desde archivo JSON
1827
- gufi entity:automations:update 4589 automations.json
1828
- ```
1829
-
1830
- **Formato del JSON de automations:**
1831
- ```json
1832
- [
1833
- {
1834
- "trigger": "insert",
1835
- "function_name": "notificar_nuevo",
1836
- "enabled": true
1837
- },
1838
- {
1839
- "trigger": "click",
1840
- "function_name": "generar_factura",
1841
- "label": "Generar Factura",
1842
- "enabled": true
1843
- },
1844
- {
1845
- "trigger": "scheduled",
1846
- "function_name": "sync_diario",
1847
- "interval_ms": 86400000,
1848
- "enabled": true
1849
- }
1850
- ]
1851
- ```
1852
-
1853
- **Triggers disponibles:**
1854
- | Trigger | Descripción |
1855
- |---------|-------------|
1856
- | `insert` | Al crear registro |
1857
- | `update` | Al modificar registro |
1858
- | `delete` | Al eliminar registro |
1859
- | `click` | Botón manual en UI |
1860
- | `scheduled` | Intervalo programado |
1861
-
1862
- ### Environment Variables
1863
-
1864
- ```bash
1865
- # Ver variables de entorno de la company
1866
- gufi env
1867
-
1868
- # Crear/actualizar variable
1869
- gufi env:set STRIPE_API_KEY sk-live-xxx
1870
-
1871
- # Eliminar variable
1872
- gufi env:delete STRIPE_API_KEY
1873
- ```
1874
-
1875
- ### Schema (Estructura de Tablas)
1876
-
1877
- ```bash
1878
- # Ver todas las tablas de la company
1879
- gufi schema
1880
-
1881
- # Filtrar por módulo
1882
- gufi schema -m 360
1883
- ```
1884
-
1885
- ### Row CRUD (Datos de Tablas)
1886
-
1887
- > 💜 **Auto-detección de Company**: El CLI detecta automáticamente a qué company pertenece una tabla
1888
- > analizando el nombre (ej: `m308_t4136` → módulo 308 → busca en qué company está).
1889
- > Ya no necesitas usar `-c` en la mayoría de casos.
1890
-
1891
- ```bash
1892
- # Listar registros de una tabla (auto-detecta company)
1893
- gufi rows m360_t16192 # Últimos 20 registros
1894
- gufi rows m360_t16192 -l 50 # Últimos 50 registros
1895
- gufi rows m360_t16192 -f estado=activo # Filtrar por campo
1896
-
1897
- # Ver un registro específico
1898
- gufi row m360_t16192 123
1899
-
1900
- # Crear registro
1901
- gufi row:create m360_t16192 --data '{"nombre":"Test","estado":"activo"}'
1902
- gufi row:create m360_t16192 --file nuevo.json
1903
-
1904
- # Actualizar registro
1905
- gufi row:update m360_t16192 123 --data '{"estado":"completado"}'
1906
- gufi row:update m360_t16192 123 --file cambios.json
1907
-
1908
- # Eliminar registro
1909
- gufi row:delete m360_t16192 123
1910
-
1911
- # Duplicar registro (copia sin id/timestamps)
1912
- gufi row:duplicate m360_t16192 123
1913
-
1914
- # Crear múltiples registros desde archivo JSON
1915
- gufi rows:create m360_t16192 --file datos.json
1916
- # datos.json debe ser un array: [{"nombre":"A"},{"nombre":"B"}]
1917
-
1918
- # Si necesitas forzar una company específica, usa -c
1919
- gufi rows m360_t16192 -c 146
47
+ gufi companies --json # Lista companies en JSON
48
+ gufi modules 146 --json # Lista módulos en JSON
49
+ gufi module 360 --json # JSON del módulo
50
+ gufi automations --json # Lista automations en JSON
51
+ gufi automation 15 --json # Código y metadata del automation
52
+ gufi schema --json # Estructura de tablas en JSON
53
+ gufi rows m360_t16192 --json # Registros en JSON
54
+ gufi row m360_t16192 5 --json # Registro específico en JSON
55
+ gufi packages --json # Lista packages en JSON
56
+ gufi package 14 --json # Detalles del package en JSON
1920
57
  ```
1921
58
 
1922
- ### Desarrollo de Views
1923
-
1924
- ```bash
1925
- # Ver tus views del Marketplace (muestra ID de cada vista)
1926
- gufi views
1927
- # 📦 Gestión de Tareas (ID: 14)
1928
- # └─ 13 Tasks Manager (custom)
1929
-
1930
- # Descargar view por ID (se guarda en ~/gufi-dev/view_<id>/)
1931
- gufi view:pull 13
1932
-
1933
- # Auto-sync al guardar archivos
1934
- gufi view:watch
1935
-
1936
- # Ver console.log del LivePreview
1937
- gufi view:logs
59
+ Usar `--json` cuando necesites parsear el output programáticamente.
1938
60
 
1939
- # Subir cambios manualmente
1940
- gufi view:push
61
+ ## MCP Server (Model Context Protocol)
1941
62
 
1942
- # Ver estado de sincronización
1943
- gufi view:status
1944
- ```
63
+ El CLI incluye un servidor MCP que permite a Claude interactuar **directamente** con Gufi sin ejecutar comandos manualmente.
1945
64
 
1946
- ### Packages del Marketplace
65
+ ### Configuración (una sola vez)
1947
66
 
1948
67
  ```bash
1949
- # Ver mis packages
1950
- gufi packages
1951
-
1952
- # Ver detalles de un package (módulos, views, estado)
1953
- gufi package 14
1954
-
1955
- # Crear nuevo package
1956
- gufi package:create "Mi Package" --description "Descripción opcional"
1957
-
1958
- # Añadir módulo de una company al package
1959
- gufi package:add-module 14 --company 146 --module 360
1960
- # O interactivo (muestra companies disponibles)
1961
- gufi package:add-module 14
1962
-
1963
- # Ver cambios pendientes en módulos
1964
- gufi package:check 14
1965
-
1966
- # Sincronizar versión (actualiza snapshots de módulos)
1967
- gufi package:sync 14
1968
-
1969
- # Publicar package al Marketplace
1970
- gufi package:publish 14
1971
-
1972
- # Despublicar
1973
- gufi package:unpublish 14
1974
-
1975
- # Gestionar views del package
1976
- gufi package:views 14
1977
- gufi package:add-view 14 <viewId>
1978
- gufi package:remove-view 14 <packageViewId>
1979
-
1980
- # Eliminar módulo o package
1981
- gufi package:remove-module 14 <moduleId>
1982
- gufi package:delete 14
68
+ claude mcp add --transport stdio gufi -- gufi mcp
69
+ ```
70
+
71
+ ### Tools disponibles (42 tools)
72
+
73
+ **Contexto e Info (EMPEZAR AQUÍ):**
74
+ - `gufi_context` - **USAR PRIMERO** - Genera contexto inteligente del proyecto
75
+ - `gufi_whoami` - Usuario y entorno actual
76
+ - `gufi_schema` - Estructura de tablas/campos
77
+
78
+ **Companies:**
79
+ - `gufi_companies` - Listar companies
80
+ - `gufi_company_create` - Crear company
81
+
82
+ **Módulos (estructura de datos):**
83
+ - `gufi_modules` - Listar módulos de una company
84
+ - `gufi_module` - Ver JSON completo de un módulo
85
+ - `gufi_module_update` - **Modificar módulo** (añadir campos, entidades)
86
+ - `gufi_module_create` - Crear módulo nuevo
87
+
88
+ **Automations (lógica de negocio):**
89
+ - `gufi_automations` - Listar scripts
90
+ - `gufi_automation` - Ver código JS de un script
91
+ - `gufi_automation_create` - **Crear/actualizar script**
92
+ - `gufi_entity_automations` - Ver triggers de una entidad
93
+ - `gufi_entity_automations_update` - **Configurar triggers**
94
+ - `gufi_automations_executions` - Ver historial de ejecuciones
95
+
96
+ **Datos (CRUD):**
97
+ - `gufi_rows` - Listar registros de una tabla
98
+ - `gufi_row` - Ver un registro
99
+ - `gufi_row_create` - Crear registro
100
+ - `gufi_row_update` - Actualizar registro
101
+ - `gufi_row_delete` - Eliminar registro
102
+
103
+ **Environment Variables:**
104
+ - `gufi_env` - Listar variables
105
+ - `gufi_env_set` - Crear/actualizar variable
106
+ - `gufi_env_delete` - Eliminar variable
107
+
108
+ **Vistas (React/TSX):**
109
+ - `gufi_view_files` - Ver archivos de una vista
110
+ - `gufi_view_file_update` - **Modificar archivo de vista**
111
+ - `gufi_view_files_update` - Modificar múltiples archivos
112
+
113
+ **Packages (Marketplace):**
114
+ - `gufi_packages` - Listar packages
115
+ - `gufi_package` - Ver detalles
116
+ - `gufi_package_create` - Crear package
117
+ - `gufi_package_publish` - Publicar
118
+ - `gufi_package_sync` - Sincronizar versión
119
+ - `gufi_package_add_module` / `gufi_package_remove_module`
120
+ - `gufi_package_add_view` / `gufi_package_remove_view`
121
+
122
+ **Package Migrations:**
123
+ - `gufi_package_migrations` - Listar migraciones
124
+ - `gufi_package_migration_create` - Crear migración SQL
125
+ - `gufi_package_entities` - Listar entidades para target_entity
126
+
127
+ ### Ejemplos de uso con Claude
128
+
129
+ ```
130
+ Usuario: "Agrega un campo 'fecha_entrega' tipo date al módulo de pedidos"
131
+ Claude: [usa gufi_module para leer] → [modifica JSON] → [usa gufi_module_update]
132
+ "Listo, agregué el campo fecha_entrega al módulo de pedidos"
133
+
134
+ Usuario: "Crea un automation que envíe email cuando un pedido pase a 'enviado'"
135
+ Claude: [usa gufi_automations para ver existentes]
136
+ [escribe código JS]
137
+ [usa gufi_automation_create]
138
+ [usa gufi_entity_automations_update para configurar trigger]
139
+ "Listo, creé el automation 'email_pedido_enviado' con trigger on_update"
140
+
141
+ Usuario: "¿Cuántos clientes hay en la empresa 116?"
142
+ Claude: [usa gufi_schema para encontrar tabla de clientes]
143
+ [usa gufi_rows para consultar]
144
+ "Hay 234 clientes en la tabla m360_t4136"
145
+ ```
146
+
147
+ ### Tipos de Campo (@gufi/column-types)
148
+
149
+ Al crear o actualizar datos, usa los formatos correctos:
150
+
151
+ **Simples:**
152
+ - text, textarea, email, url, barcode: `"string"`
153
+ - number_int: `42` | number_float: `3.14` | percentage: `0.75`
154
+ - boolean: `true/false`
155
+ - date: `"2024-01-15"` | datetime: `"2024-01-15T10:30:00Z"` | time: `"14:30:00"`
156
+ - select: `"value"` | relation: `123`
157
+
158
+ **Arrays:**
159
+ - multiselect: `["value1", "value2"]`
160
+ - users: `[16, 23]`
161
+
162
+ **JSONB (objetos complejos):**
163
+ - currency: `{ "currency": "EUR", "amount": 150.00 }`
164
+ - phone: `{ "prefix": "+34", "number": "612345678" }`
165
+ - location: `{ "street": "Mayor", "number": "15", "city": "Madrid", "lat": 40.41 }`
166
+ - file: `[{ "url": "company_130/doc.pdf", "name": "doc.pdf" }]`
167
+
168
+ En vistas usa `gufi.CB.*` para formatear correctamente:
169
+ ```typescript
170
+ gufi.CB.currency(150) // → { currency: 'EUR', amount: 150 }
171
+ gufi.CB.phone('612345678') // → { prefix: '+34', number: '612345678' }
172
+ gufi.CB.date() // → '2024-01-15' (hoy)
173
+ gufi.CB.multiselect('a', 'b') // → ['a', 'b']
1983
174
  ```
1984
175
 
1985
- ### Opciones Globales
1986
-
1987
- | Opción | Descripción |
1988
- |--------|-------------|
1989
- | `-c, --company <id>` | ID de company (solo para `modules`, `automations`, `*:create`) |
1990
- | `-e, --edit` | Abrir en editor ($EDITOR) |
1991
- | `-f, --file <path>` | Guardar a archivo |
1992
- | `--dry-run` | Validar sin aplicar |
1993
- | `-h, --help` | Ayuda del comando |
1994
-
1995
- **Auto-detección:** `module`, `module:update`, `automation`, `entity:automations`, `rows` detectan company automáticamente del ID.
1996
-
1997
- ---
1998
-
1999
- ## Tips para Claude
2000
-
2001
- ### 💜 Flujo de Trabajo Recomendado
2002
-
2003
- ```bash
2004
- # 1. Primero, oriéntate
2005
- gufi companies # Ver companies disponibles
2006
- gufi modules 146 # Ver módulos de una company
176
+ ### Seguridad
2007
177
 
2008
- # 2. Para ver/modificar datos (auto-detecta company)
2009
- gufi rows m308_t4136 # Auto-detecta company del módulo 308
2010
- gufi row m308_t4136 123 # Ver registro específico
2011
- gufi row:update m308_t4136 123 --data '{"status":"done"}'
178
+ - Usa las credenciales del CLI (`gufi login`)
179
+ - Solo accede a resources con permisos del usuario
180
+ - Todo corre localmente (stdio), no hay servidor público
2012
181
 
2013
- # 3. Para editar estructura (módulos - auto-detecta company)
2014
- gufi module 360 --edit # Abre JSON en tu $EDITOR
182
+ ## Comandos Esenciales
2015
183
 
2016
- # 4. Para editar lógica (automations - auto-detecta company)
2017
- gufi automation 15 # Busca por script_id
2018
- gufi automation 15 --edit # Editar código JS
2019
- ```
2020
-
2021
- ### Cuándo usar cada comando
2022
-
2023
- | El usuario quiere... | Comando |
2024
- |---------------------|---------|
184
+ | Quiero... | Comando |
185
+ |-----------|---------|
186
+ | **Contexto para Claude** | `gufi context` |
187
+ | **Diagnóstico** | `gufi doctor` |
2025
188
  | Ver companies | `gufi companies` |
2026
- | Ver módulos de company | `gufi modules 146` |
2027
- | Ver/editar módulo | `gufi module 360` |
2028
- | Ver/editar automation | `gufi automation 15` |
2029
- | Ver/editar datos | `gufi rows m308_t4136` |
2030
- | Desarrollar vista | `gufi pull "Mi Vista"` → `gufi watch` → `gufi logs` |
2031
-
2032
- ### Auto-detección
2033
-
2034
- Todo automático:
2035
- - `gufi module 360` → detecta company del módulo
2036
- - `gufi automation 15` → detecta company del script
2037
- - `gufi rows m308_t4136` → detecta company de la tabla
2038
-
2039
- ### Automations: 4 tablas que trabajan juntas
2040
-
2041
- ```
2042
- automation_scripts (id=15) ← CÓDIGO JavaScript
2043
-
2044
- └── entities.automations ← CONFIGURACIÓN (qué trigger, cuándo ejecutar)
2045
-
2046
- ▼ (sync automático)
2047
- automation_meta ← ÍNDICE para worker
2048
-
2049
-
2050
- automation_executions ← HISTORIAL de ejecuciones
2051
- ```
2052
-
2053
- | Tabla | Qué guarda | CLI |
2054
- |-------|------------|-----|
2055
- | `automation_scripts` | Código JS | `gufi automation 15` |
2056
- | `entities.automations` | Triggers (on_insert, on_click...) | `gufi entity:automations 4136` |
2057
- | `automation_meta` | Índice para worker (auto-sync) | `gufi automations:meta` |
2058
- | `automation_executions` | Historial de ejecuciones | `gufi automations:executions` |
2059
-
2060
- **Ejemplo:** Un script puede usarse en varias entities:
2061
- ```
2062
- script 15 "sync_nayax" → machines (on_click), products (scheduled), sales (on_insert)
2063
- ```
2064
-
2065
- **Flujo típico:**
2066
- ```bash
2067
- gufi automations # Ver scripts
2068
- gufi automation 15 --edit # Editar código
2069
- gufi entity:automations 4136 # Ver/editar triggers
2070
- gufi automations:meta # Ver qué tiene el worker indexado (debugging)
2071
- gufi automations:executions # Ver historial de ejecuciones
2072
- ```
2073
-
2074
- ### Errores comunes
2075
-
2076
- | Error | Solución |
2077
- |-------|----------|
2078
- | "No estás logueado" | `gufi login` (guarda credenciales para auto-login) |
2079
- | "Módulo no encontrado" | Verificar que el ID existe con `gufi modules <company>` |
2080
- | "Token expirado" | Automático: refresh o auto-login con credenciales guardadas |
2081
- | "JSON inválido" | Validar estructura del JSON |
2082
- | "API Error 404" | Verificar nombre de tabla (formato: `m{moduleId}_t{entityId}`) |
2083
-
2084
- ### Conceptos clave
2085
-
2086
- - **Multi-tenant**: Cada company = schema aislado en PostgreSQL
2087
- - **Tablas físicas**: `m{moduleId}_t{entityId}` (ej: `m308_t4136`)
2088
- - **Módulos**: JSON que define estructura → Gufi crea tablas/UI
2089
- - **Automations**: JS en DB, ejecutado por worker (pg-boss)
2090
- - **Views**: React/TS en DB, ejecutado en frontend dinámicamente
2091
- - **Marketplace**: Sistema de distribución de packages
2092
-
2093
- ### Companies Importantes
189
+ | Ver módulos | `gufi modules 146` |
190
+ | Editar módulo | `gufi module 360 --edit` |
191
+ | Ver automations | `gufi automations` |
192
+ | Desarrollar vista | `gufi view:pull 13` → `gufi view:push` |
2094
193
 
2095
- | ID | Nombre | Uso |
2096
- |----|--------|-----|
2097
- | 101 | Demo | Pruebas generales |
2098
- | 116 | Fitvending | Producción - cliente real |
2099
- | 146 | Gufi | Desarrollo interno |
2100
- | 147 | Apuestas Pro | Cliente real |
194
+ ## Tips
2101
195
 
2102
- ---
196
+ 1. **`gufi context`**: Úsalo al inicio de cualquier tarea
197
+ 2. **Auto-detección**: `module`, `automation`, `rows` detectan company automáticamente
198
+ 3. **--edit**: Abre en `$EDITOR`
199
+ 4. **Entornos separados**: Cada uno con sus credenciales
200
+ 5. **Auto-login**: CLI refresca tokens automáticamente
2103
201
 
2104
- ## Links
202
+ ## Documentación Completa
2105
203
 
2106
- - **Web**: https://gogufi.com
2107
- - **Docs**: https://github.com/juanbp23/gogufi/blob/main/docs/guide/05-marketplace.md
204
+ Para documentación detallada de cada comando, arquitectura, y patrones, ve a:
205
+ - `docs/claude/` - Fuente de verdad para Claude
206
+ - `docs/guide/27-gufi-cli.md` - Guía técnica del CLI