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,453 @@
|
|
|
1
|
+
# Ejemplos Practicos
|
|
2
|
+
|
|
3
|
+
Ejemplos listos para copiar/adaptar.
|
|
4
|
+
|
|
5
|
+
## Agregar campo a entidad existente
|
|
6
|
+
|
|
7
|
+
```javascript
|
|
8
|
+
gufi_schema_modify({
|
|
9
|
+
company_id: "116",
|
|
10
|
+
operations: [{
|
|
11
|
+
op: "add_field",
|
|
12
|
+
entity: "ventas.pedidos", // modulo.entidad
|
|
13
|
+
field: {
|
|
14
|
+
name: "fecha_entrega",
|
|
15
|
+
type: "date",
|
|
16
|
+
label: "Fecha de Entrega",
|
|
17
|
+
required: false
|
|
18
|
+
}
|
|
19
|
+
}]
|
|
20
|
+
})
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
## Crear entidad completa
|
|
24
|
+
|
|
25
|
+
```javascript
|
|
26
|
+
gufi_schema_modify({
|
|
27
|
+
company_id: "116",
|
|
28
|
+
operations: [{
|
|
29
|
+
op: "add_entity",
|
|
30
|
+
module: "inventario",
|
|
31
|
+
entity: {
|
|
32
|
+
name: "productos",
|
|
33
|
+
label: "Productos",
|
|
34
|
+
kind: "table",
|
|
35
|
+
fields: [
|
|
36
|
+
{ name: "nombre", type: "text", label: "Nombre", required: true },
|
|
37
|
+
{ name: "sku", type: "barcode", label: "SKU" },
|
|
38
|
+
{ name: "descripcion", type: "text", label: "Descripcion" },
|
|
39
|
+
{ name: "precio", type: "currency", label: "Precio" },
|
|
40
|
+
{ name: "stock", type: "number_int", label: "Stock" },
|
|
41
|
+
{ name: "categoria", type: "select", label: "Categoria", options: [
|
|
42
|
+
{ value: "electronica", label: "Electronica", color: "blue" },
|
|
43
|
+
{ value: "hogar", label: "Hogar", color: "green" },
|
|
44
|
+
{ value: "ropa", label: "Ropa", color: "purple" }
|
|
45
|
+
]},
|
|
46
|
+
{ name: "activo", type: "boolean", label: "Activo" }
|
|
47
|
+
],
|
|
48
|
+
permissions: {
|
|
49
|
+
admin: "*",
|
|
50
|
+
almacen: ["entity:view", "entity:create", "entity:update"],
|
|
51
|
+
ventas: ["entity:view"]
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
}]
|
|
55
|
+
})
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
## Automation: Email al crear
|
|
59
|
+
|
|
60
|
+
```javascript
|
|
61
|
+
// 1. Crear el script
|
|
62
|
+
gufi_automation_scripts({
|
|
63
|
+
action: "create",
|
|
64
|
+
company_id: "116",
|
|
65
|
+
name: "notificar_pedido_nuevo",
|
|
66
|
+
description: "Envia email cuando se crea un pedido",
|
|
67
|
+
code: `
|
|
68
|
+
async function notificar_pedido_nuevo(gufi) {
|
|
69
|
+
const { row, env } = gufi.context;
|
|
70
|
+
|
|
71
|
+
gufi.logger.info('Nuevo pedido', { id: row.id });
|
|
72
|
+
|
|
73
|
+
await gufi.integrations.notifications.email({
|
|
74
|
+
to: env.VENTAS_EMAIL,
|
|
75
|
+
subject: \`Nuevo pedido #\${row.id}\`,
|
|
76
|
+
html: \`
|
|
77
|
+
<h2>Nuevo Pedido</h2>
|
|
78
|
+
<p><strong>Cliente:</strong> \${row.cliente__display || 'N/A'}</p>
|
|
79
|
+
<p><strong>Total:</strong> \${row.total || 0} EUR</p>
|
|
80
|
+
<p><strong>Estado:</strong> \${row.estado__display || row.estado}</p>
|
|
81
|
+
<p><a href="https://app.gogufi.com/pedidos/\${row.id}">Ver pedido</a></p>
|
|
82
|
+
\`,
|
|
83
|
+
user: env.EMAIL_USER,
|
|
84
|
+
pass: env.EMAIL_PASS,
|
|
85
|
+
from: env.EMAIL_FROM,
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
return { success: true, message: 'Email enviado' };
|
|
89
|
+
}
|
|
90
|
+
`
|
|
91
|
+
})
|
|
92
|
+
|
|
93
|
+
// 2. Vincular a entidad
|
|
94
|
+
gufi_automation_meta({
|
|
95
|
+
entity_id: "16192",
|
|
96
|
+
company_id: "116",
|
|
97
|
+
automations: [
|
|
98
|
+
{ trigger: "insert", function_name: "notificar_pedido_nuevo" }
|
|
99
|
+
]
|
|
100
|
+
})
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
## Automation: Webhook externo
|
|
104
|
+
|
|
105
|
+
```javascript
|
|
106
|
+
gufi_automation_scripts({
|
|
107
|
+
action: "create",
|
|
108
|
+
company_id: "116",
|
|
109
|
+
name: "sync_to_external",
|
|
110
|
+
code: `
|
|
111
|
+
async function sync_to_external(gufi) {
|
|
112
|
+
const { row, old_row, env } = gufi.context;
|
|
113
|
+
|
|
114
|
+
// Solo si cambio el estado
|
|
115
|
+
if (row.estado === old_row?.estado) {
|
|
116
|
+
return { success: true, message: 'Sin cambios' };
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
try {
|
|
120
|
+
const response = await gufi.http(env.WEBHOOK_URL, {
|
|
121
|
+
method: 'POST',
|
|
122
|
+
headers: {
|
|
123
|
+
'Authorization': \`Bearer \${env.API_TOKEN}\`,
|
|
124
|
+
'Content-Type': 'application/json'
|
|
125
|
+
},
|
|
126
|
+
body: JSON.stringify({
|
|
127
|
+
event: 'status_changed',
|
|
128
|
+
record_id: row.id,
|
|
129
|
+
old_status: old_row?.estado,
|
|
130
|
+
new_status: row.estado,
|
|
131
|
+
timestamp: new Date().toISOString()
|
|
132
|
+
})
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
gufi.logger.info('Webhook response', response);
|
|
136
|
+
return { success: true };
|
|
137
|
+
|
|
138
|
+
} catch (err) {
|
|
139
|
+
gufi.logger.error('Webhook failed', { error: err.message });
|
|
140
|
+
return { success: false, message: err.message };
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
`
|
|
144
|
+
});
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
## Vista: Lista con filtros
|
|
148
|
+
|
|
149
|
+
```typescript
|
|
150
|
+
// index.tsx
|
|
151
|
+
import React, { useState, useEffect, useMemo } from 'react';
|
|
152
|
+
import { Search, Filter } from 'lucide-react';
|
|
153
|
+
import {
|
|
154
|
+
cn,
|
|
155
|
+
useDebouncedValue,
|
|
156
|
+
formatNumber,
|
|
157
|
+
formatCurrency,
|
|
158
|
+
toastError,
|
|
159
|
+
tUI,
|
|
160
|
+
type FeatureProps
|
|
161
|
+
} from '@/sdk';
|
|
162
|
+
|
|
163
|
+
export { featureConfig } from './core/dataProvider';
|
|
164
|
+
|
|
165
|
+
export default function ListaFiltros({ id, gufi }: FeatureProps) {
|
|
166
|
+
const { dataProvider, context, seedData } = gufi;
|
|
167
|
+
const isPreview = context?.isPreview;
|
|
168
|
+
|
|
169
|
+
// 💜 Nombre lógico - dataProvider lo resuelve automáticamente
|
|
170
|
+
const itemsTable = 'ventas.items';
|
|
171
|
+
|
|
172
|
+
const [data, setData] = useState<any[]>([]);
|
|
173
|
+
const [loading, setLoading] = useState(true);
|
|
174
|
+
const [search, setSearch] = useState('');
|
|
175
|
+
const [filter, setFilter] = useState<string>('all');
|
|
176
|
+
|
|
177
|
+
const debouncedSearch = useDebouncedValue(search, 200);
|
|
178
|
+
|
|
179
|
+
// Cargar datos
|
|
180
|
+
useEffect(() => {
|
|
181
|
+
const load = async () => {
|
|
182
|
+
setLoading(true);
|
|
183
|
+
|
|
184
|
+
if (isPreview && seedData?.data) {
|
|
185
|
+
setData(seedData.data.items || []);
|
|
186
|
+
setLoading(false);
|
|
187
|
+
return;
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
if (!dataProvider) {
|
|
191
|
+
setLoading(false);
|
|
192
|
+
return;
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
try {
|
|
196
|
+
const res = await dataProvider.getList({
|
|
197
|
+
resource: itemsTable, // 💜 Nombre lógico
|
|
198
|
+
pagination: { current: 1, pageSize: 200 },
|
|
199
|
+
sort: { field: 'created_at', order: 'DESC' }
|
|
200
|
+
});
|
|
201
|
+
setData(res.data || []);
|
|
202
|
+
} catch (err) {
|
|
203
|
+
toastError('Error cargando datos');
|
|
204
|
+
}
|
|
205
|
+
setLoading(false);
|
|
206
|
+
};
|
|
207
|
+
|
|
208
|
+
load();
|
|
209
|
+
}, [dataProvider, itemsTable, seedData, isPreview]);
|
|
210
|
+
|
|
211
|
+
// Filtrar datos
|
|
212
|
+
const filtered = useMemo(() => {
|
|
213
|
+
let result = data;
|
|
214
|
+
|
|
215
|
+
// Filtro por estado
|
|
216
|
+
if (filter !== 'all') {
|
|
217
|
+
result = result.filter(item => item.estado === filter);
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
// Busqueda
|
|
221
|
+
if (debouncedSearch) {
|
|
222
|
+
const term = debouncedSearch.toLowerCase();
|
|
223
|
+
result = result.filter(item =>
|
|
224
|
+
item.nombre?.toLowerCase().includes(term) ||
|
|
225
|
+
item.cliente__display?.toLowerCase().includes(term)
|
|
226
|
+
);
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
return result;
|
|
230
|
+
}, [data, filter, debouncedSearch]);
|
|
231
|
+
|
|
232
|
+
if (loading) {
|
|
233
|
+
return (
|
|
234
|
+
<div className="p-4 space-y-2">
|
|
235
|
+
{[...Array(5)].map((_, i) => (
|
|
236
|
+
<div key={i} className="h-16 bg-gray-100 rounded animate-pulse" />
|
|
237
|
+
))}
|
|
238
|
+
</div>
|
|
239
|
+
);
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
return (
|
|
243
|
+
<div className="flex flex-col h-[100dvh] bg-gradient-to-br from-violet-50/40 via-white to-purple-50/40">
|
|
244
|
+
{/* Header con busqueda */}
|
|
245
|
+
<div className="p-4 border-b bg-white/80 backdrop-blur">
|
|
246
|
+
<div className="flex gap-2">
|
|
247
|
+
<div className="flex-1 relative">
|
|
248
|
+
<Search className="absolute left-3 top-1/2 -translate-y-1/2 w-4 h-4 text-gray-400" />
|
|
249
|
+
<input
|
|
250
|
+
type="text"
|
|
251
|
+
value={search}
|
|
252
|
+
onChange={(e) => setSearch(e.target.value)}
|
|
253
|
+
placeholder={tUI('common.search')}
|
|
254
|
+
className="w-full pl-10 pr-4 py-2 border rounded-lg focus:ring-2 focus:ring-violet-500"
|
|
255
|
+
/>
|
|
256
|
+
</div>
|
|
257
|
+
<select
|
|
258
|
+
value={filter}
|
|
259
|
+
onChange={(e) => setFilter(e.target.value)}
|
|
260
|
+
className="px-3 py-2 border rounded-lg"
|
|
261
|
+
>
|
|
262
|
+
<option value="all">Todos</option>
|
|
263
|
+
<option value="pendiente">Pendientes</option>
|
|
264
|
+
<option value="completado">Completados</option>
|
|
265
|
+
</select>
|
|
266
|
+
</div>
|
|
267
|
+
</div>
|
|
268
|
+
|
|
269
|
+
{/* Lista */}
|
|
270
|
+
<div className="flex-1 overflow-auto">
|
|
271
|
+
<div className="p-4 text-sm text-gray-500">
|
|
272
|
+
{formatNumber(filtered.length)} resultados
|
|
273
|
+
</div>
|
|
274
|
+
|
|
275
|
+
{filtered.map(item => (
|
|
276
|
+
<div
|
|
277
|
+
key={item.id}
|
|
278
|
+
className="mx-4 mb-2 p-4 bg-white rounded-lg border hover:border-violet-300 transition-colors"
|
|
279
|
+
>
|
|
280
|
+
<div className="flex justify-between items-start">
|
|
281
|
+
<div>
|
|
282
|
+
<h3 className="font-medium">{item.nombre}</h3>
|
|
283
|
+
<p className="text-sm text-gray-500">{item.cliente__display}</p>
|
|
284
|
+
</div>
|
|
285
|
+
<div className="text-right">
|
|
286
|
+
<div className="font-semibold text-violet-600">
|
|
287
|
+
{formatCurrency(item.total || 0, 'EUR')}
|
|
288
|
+
</div>
|
|
289
|
+
<span className={cn(
|
|
290
|
+
"text-xs px-2 py-0.5 rounded-full",
|
|
291
|
+
item.estado === 'completado' ? "bg-green-100 text-green-700" : "bg-yellow-100 text-yellow-700"
|
|
292
|
+
)}>
|
|
293
|
+
{item.estado__display || item.estado}
|
|
294
|
+
</span>
|
|
295
|
+
</div>
|
|
296
|
+
</div>
|
|
297
|
+
</div>
|
|
298
|
+
))}
|
|
299
|
+
</div>
|
|
300
|
+
</div>
|
|
301
|
+
);
|
|
302
|
+
}
|
|
303
|
+
```
|
|
304
|
+
|
|
305
|
+
```typescript
|
|
306
|
+
// core/dataProvider.ts
|
|
307
|
+
import type { FeatureConfig } from '@/sdk';
|
|
308
|
+
|
|
309
|
+
export const featureConfig: FeatureConfig = {
|
|
310
|
+
dataSources: {
|
|
311
|
+
mainTable: {
|
|
312
|
+
type: 'table',
|
|
313
|
+
module: 'ventas',
|
|
314
|
+
entity: 'pedidos',
|
|
315
|
+
label: 'Pedidos'
|
|
316
|
+
}
|
|
317
|
+
},
|
|
318
|
+
inputs: {},
|
|
319
|
+
seedData: {
|
|
320
|
+
description: { es: 'Lista de pedidos', en: 'Orders list' },
|
|
321
|
+
data: {
|
|
322
|
+
items: [
|
|
323
|
+
{ id: 1, nombre: 'Pedido #001', cliente__display: 'Empresa A', total: 1500, estado: 'pendiente', estado__display: 'Pendiente' },
|
|
324
|
+
{ id: 2, nombre: 'Pedido #002', cliente__display: 'Empresa B', total: 2300, estado: 'completado', estado__display: 'Completado' }
|
|
325
|
+
]
|
|
326
|
+
},
|
|
327
|
+
order: ['items']
|
|
328
|
+
}
|
|
329
|
+
};
|
|
330
|
+
```
|
|
331
|
+
|
|
332
|
+
## Crear registro con todos los tipos
|
|
333
|
+
|
|
334
|
+
```javascript
|
|
335
|
+
gufi_data({
|
|
336
|
+
action: "create",
|
|
337
|
+
table: "ventas.productos",
|
|
338
|
+
company_id: "116",
|
|
339
|
+
data: {
|
|
340
|
+
// Texto
|
|
341
|
+
nombre: "Mi Producto",
|
|
342
|
+
email: "contacto@empresa.com",
|
|
343
|
+
url: "https://ejemplo.com",
|
|
344
|
+
codigo_barras: "8414533043847",
|
|
345
|
+
|
|
346
|
+
// Numeros
|
|
347
|
+
cantidad: 100,
|
|
348
|
+
precio: 150.50,
|
|
349
|
+
descuento: 0.15, // 15%
|
|
350
|
+
|
|
351
|
+
// Fechas
|
|
352
|
+
fecha_alta: "2024-01-15",
|
|
353
|
+
hora_apertura: "09:00:00",
|
|
354
|
+
|
|
355
|
+
// Seleccion (incluir __display!)
|
|
356
|
+
estado: "activo",
|
|
357
|
+
estado__display: "Activo",
|
|
358
|
+
categorias: ["electronica", "hogar"],
|
|
359
|
+
activo: true,
|
|
360
|
+
|
|
361
|
+
// Relaciones (incluir __display!)
|
|
362
|
+
cliente_id: 45,
|
|
363
|
+
cliente_id__display: "Empresa ABC",
|
|
364
|
+
responsables: [16, 23],
|
|
365
|
+
|
|
366
|
+
// Complejos
|
|
367
|
+
telefono: { prefix: "+34", number: "612345678" },
|
|
368
|
+
direccion: {
|
|
369
|
+
street: "Gran Via",
|
|
370
|
+
number: "1",
|
|
371
|
+
city: "Madrid",
|
|
372
|
+
postal_code: "28013",
|
|
373
|
+
country: "ES"
|
|
374
|
+
}
|
|
375
|
+
}
|
|
376
|
+
})
|
|
377
|
+
```
|
|
378
|
+
|
|
379
|
+
## Consulta SQL en automation
|
|
380
|
+
|
|
381
|
+
```javascript
|
|
382
|
+
gufi_automation_scripts({
|
|
383
|
+
action: "create",
|
|
384
|
+
company_id: "116",
|
|
385
|
+
name: "reporte_ventas",
|
|
386
|
+
code: `
|
|
387
|
+
async function reporte_ventas(gufi) {
|
|
388
|
+
const { env } = gufi.context;
|
|
389
|
+
|
|
390
|
+
// Obtener resumen de ventas del mes
|
|
391
|
+
const rows = await gufi.query(\`
|
|
392
|
+
SELECT
|
|
393
|
+
DATE_TRUNC('day', created_at) as fecha,
|
|
394
|
+
COUNT(*) as num_pedidos,
|
|
395
|
+
SUM(total) as total_ventas,
|
|
396
|
+
AVG(total) as ticket_medio
|
|
397
|
+
FROM m360_t16192
|
|
398
|
+
WHERE created_at >= DATE_TRUNC('month', CURRENT_DATE)
|
|
399
|
+
GROUP BY DATE_TRUNC('day', created_at)
|
|
400
|
+
ORDER BY fecha DESC
|
|
401
|
+
\`);
|
|
402
|
+
|
|
403
|
+
// Formatear reporte
|
|
404
|
+
const reporteHtml = rows.map(r => \`
|
|
405
|
+
<tr>
|
|
406
|
+
<td>\${new Date(r.fecha).toLocaleDateString('es-ES')}</td>
|
|
407
|
+
<td>\${r.num_pedidos}</td>
|
|
408
|
+
<td>\${Number(r.total_ventas).toFixed(2)} EUR</td>
|
|
409
|
+
<td>\${Number(r.ticket_medio).toFixed(2)} EUR</td>
|
|
410
|
+
</tr>
|
|
411
|
+
\`).join('');
|
|
412
|
+
|
|
413
|
+
await gufi.integrations.notifications.email({
|
|
414
|
+
to: env.ADMIN_EMAIL,
|
|
415
|
+
subject: 'Reporte de Ventas Mensual',
|
|
416
|
+
html: \`
|
|
417
|
+
<h2>Reporte de Ventas</h2>
|
|
418
|
+
<table border="1" cellpadding="5">
|
|
419
|
+
<tr><th>Fecha</th><th>Pedidos</th><th>Total</th><th>Ticket Medio</th></tr>
|
|
420
|
+
\${reporteHtml}
|
|
421
|
+
</table>
|
|
422
|
+
\`,
|
|
423
|
+
user: env.EMAIL_USER,
|
|
424
|
+
pass: env.EMAIL_PASS,
|
|
425
|
+
from: env.EMAIL_FROM,
|
|
426
|
+
});
|
|
427
|
+
|
|
428
|
+
return { success: true, message: \`Reporte enviado con \${rows.length} dias\` };
|
|
429
|
+
}
|
|
430
|
+
`
|
|
431
|
+
});
|
|
432
|
+
```
|
|
433
|
+
|
|
434
|
+
## Editar vista localmente
|
|
435
|
+
|
|
436
|
+
```javascript
|
|
437
|
+
// 1. Descargar vista a ~/gufi-dev/view_13/
|
|
438
|
+
gufi_view_pull({ view_id: 13 })
|
|
439
|
+
|
|
440
|
+
// 2. Editar archivos con Read/Edit tools
|
|
441
|
+
// Read("~/gufi-dev/view_13/index.tsx")
|
|
442
|
+
// Edit("~/gufi-dev/view_13/index.tsx", ...)
|
|
443
|
+
|
|
444
|
+
// 3. Subir cambios
|
|
445
|
+
gufi_view_push({ view_id: 13, message: "Changed table name" })
|
|
446
|
+
|
|
447
|
+
// 4. Testear en headless browser (OBLIGATORIO antes de publicar)
|
|
448
|
+
gufi_view_test({ view_id: 13, company_id: "116" })
|
|
449
|
+
// Ver errores JS, console logs, screenshot
|
|
450
|
+
|
|
451
|
+
// 5. Si todo OK, publicar package
|
|
452
|
+
gufi_package({ action: "publish", id: "19" })
|
|
453
|
+
```
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
# Documentación Gufi para Consultores y Developers
|
|
2
|
+
|
|
3
|
+
> **Para usuarios de la plataforma Gufi**: Si estás creando ERPs personalizados con Gufi (módulos, vistas, automations), esta es tu documentación.
|
|
4
|
+
|
|
5
|
+
Esta carpeta contiene la documentación para trabajar con Gufi usando el CLI y MCP (Model Context Protocol) con Claude AI.
|
|
6
|
+
|
|
7
|
+
## 🚀 Comienza aquí
|
|
8
|
+
|
|
9
|
+
**Nuevo en Gufi?** Lee `00-overview.md` primero para entender qué es y cómo funciona.
|
|
10
|
+
|
|
11
|
+
**Ya conoces Gufi?** Salta directo al archivo que necesites según la tabla de abajo.
|
|
12
|
+
|
|
13
|
+
## 📚 Índice de Documentación
|
|
14
|
+
|
|
15
|
+
| Archivo | Qué aprenderás | Cuándo consultarlo |
|
|
16
|
+
|---------|----------------|-------------------|
|
|
17
|
+
| **[00-overview.md](./00-overview.md)** | Qué es Gufi, arquitectura básica, flujo de trabajo típico | 👉 **Empieza aquí** si eres nuevo |
|
|
18
|
+
| **[01-architecture.md](./01-architecture.md)** | Arquitectura técnica completa (backend, BD, real-time) | Necesitas entender cómo conectan los componentes |
|
|
19
|
+
| **[02-modules.md](./02-modules.md)** | Crear/modificar módulos y entidades (estructura de datos) | Vas a crear o cambiar tablas |
|
|
20
|
+
| **[03-fields.md](./03-fields.md)** | Tipos de campos y `gufi.CB.*` builders | Vas a crear/actualizar registros |
|
|
21
|
+
| **[04-views.md](./04-views.md)** | Sistema de vistas React (UI personalizada) | Vas a crear o modificar interfaces |
|
|
22
|
+
| **[05-automations.md](./05-automations.md)** | Escribir automations (lógica de negocio) | Necesitas automatizar procesos |
|
|
23
|
+
| **[06-api.md](./06-api.md)** | API endpoints y métodos disponibles | Vas a integrar con APIs externas |
|
|
24
|
+
| **[07-packages.md](./07-packages.md)** | Crear y publicar packages en Marketplace | Vas a distribuir tu solución |
|
|
25
|
+
| **[08-common-errors.md](./08-common-errors.md)** | Errores comunes y cómo solucionarlos | 🐛 Algo no funciona |
|
|
26
|
+
| **[09-examples.md](./09-examples.md)** | Ejemplos prácticos completos | Necesitas código de referencia |
|
|
27
|
+
|
|
28
|
+
## 🛠️ Cómo usar esta documentación
|
|
29
|
+
|
|
30
|
+
### Desde CLI
|
|
31
|
+
|
|
32
|
+
```bash
|
|
33
|
+
# Ver contexto completo de un package
|
|
34
|
+
gufi context --package 14
|
|
35
|
+
|
|
36
|
+
# Consultar documentación específica
|
|
37
|
+
gufi docs overview # Lee 00-overview.md
|
|
38
|
+
gufi docs fields # Lee 03-fields.md
|
|
39
|
+
gufi docs automations # Lee 05-automations.md
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
### Desde Claude AI (MCP)
|
|
43
|
+
|
|
44
|
+
Si usas Claude con el plugin Gufi MCP:
|
|
45
|
+
|
|
46
|
+
```typescript
|
|
47
|
+
// Claude puede consultar automáticamente:
|
|
48
|
+
gufi_docs({ topic: "views" }) // Lee 04-views.md
|
|
49
|
+
gufi_docs({ topic: "fields" }) // Lee 03-fields.md
|
|
50
|
+
gufi_docs({ search: "currency" }) // Busca término en todos los docs
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
## 💡 Casos de uso típicos
|
|
54
|
+
|
|
55
|
+
| Quiero... | Lee esto |
|
|
56
|
+
|-----------|----------|
|
|
57
|
+
| Crear un módulo de inventario | `02-modules.md` → `03-fields.md` |
|
|
58
|
+
| Hacer una vista personalizada de reportes | `04-views.md` → `09-examples.md` |
|
|
59
|
+
| Automatizar envío de emails al crear pedido | `05-automations.md` |
|
|
60
|
+
| Entender errores de `gufi.CB.currency()` | `03-fields.md` → `08-common-errors.md` |
|
|
61
|
+
| Publicar mi solución en Marketplace | `07-packages.md` |
|
|
62
|
+
|
|
63
|
+
## ⚠️ Nota importante
|
|
64
|
+
|
|
65
|
+
Esta documentación es para **usar Gufi** (crear ERPs), no para **desarrollar el codebase de Gufi**.
|
|
66
|
+
|
|
67
|
+
Si estás desarrollando el codebase interno, consulta `/docs/claude/` en vez de `/docs/mcp/`.
|
|
68
|
+
|
|
69
|
+
---
|
|
70
|
+
|
|
71
|
+
**Última actualización**: 2025-01-14
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
{
|
|
2
|
+
"_comment": "Gufi MCP - 15 tools. Para usuarios que construyen ERPs con Gufi.",
|
|
3
|
+
"_env_note": "El entorno se configura con 'gufi config:local' o 'gufi config:prod' + 'gufi login'. El parametro env:'dev' es un OVERRIDE que requiere tener credenciales guardadas para ese entorno.",
|
|
4
|
+
|
|
5
|
+
"gufi_context": {
|
|
6
|
+
"description": "THE ONE TOOL FOR CONTEXT. Llama esto PRIMERO.\n\nUsos:\n gufi_context({ company_id: '146' }) - Schema COMPLETO de empresa\n gufi_context({ view_id: '13' }) - Contexto de vista\n gufi_context({ package_id: '14' }) - Contexto de package\n\nSiempre devuelve contexto completo: campos, tipos, relaciones, automations, permisos."
|
|
7
|
+
},
|
|
8
|
+
|
|
9
|
+
"gufi_whoami": {
|
|
10
|
+
"description": "Ver usuario actual y entorno (dev/prod). Usa para verificar autenticación."
|
|
11
|
+
},
|
|
12
|
+
|
|
13
|
+
"gufi_schema_modify": {
|
|
14
|
+
"description": "Modificar schema. Operaciones atómicas para módulos, entities, fields y sections.\n\nMÓDULOS:\n add_module: { op: 'add_module', module: { name: 'inventario', label: 'Inventario' } }\n remove_module: { op: 'remove_module', module: 'inventario' } ⚠️ Borra TODO el módulo y sus datos\n\nSECCIONES (submodules):\n add_submodule: { op: 'add_submodule', module: 'ventas', submodule: { name: 'reportes', label: 'Reportes' } }\n remove_submodule: { op: 'remove_submodule', module: 'ventas', submodule: 'reportes' } → Mueve entities a 'general'\n\nENTIDADES:\n add_entity: { op: 'add_entity', module: 'ventas', entity: { name: 'facturas', label: 'Facturas' } }\n update_entity: { op: 'update_entity', entity: 'clientes', changes: { label: 'Clientes VIP' } }\n remove_entity: { op: 'remove_entity', entity: 'clientes' } ⚠️ Borra tabla y datos\n\n💜 CREAR VISTAS (Custom Views):\n add_entity con kind='marketplace_view': { op: 'add_entity', module: 'ventas', entity: { name: 'mi_dashboard', label: 'Mi Dashboard', kind: 'marketplace_view' } }\n Luego usa gufi_view_pull({ view_id: <id> }) para descargar el template y empezar a codear.\n\nCAMPOS:\n add_field: { op: 'add_field', entity: 'clientes', field: { name: 'phone', type: 'phone', label: 'Teléfono' } }\n update_field: { op: 'update_field', entity: 'clientes', field_name: 'phone', changes: { required: true } }\n remove_field: { op: 'remove_field', entity: 'clientes', field_name: 'old_field' } ⚠️ Borra columna y datos\n\nCAMPOS RELATION (FK):\n { op: 'add_field', entity: 'pedidos', field: { name: 'cliente_id', type: 'relation', ref: 'ventas.clientes', display: 'nombre', label: 'Cliente' } }\n ⚠️ IMPORTANTE: ref usa formato 'modulo.entidad', NO { entity, module }\n\nTipos de campo: text, email, number_int, number_float, currency, date, select, multiselect, relation, phone, location, file, boolean, json\nAliases automáticos: textarea→text, varchar→text, int→number_int, float→number_float, bool→boolean, timestamp→datetime\n\nReferencias: Usa nombre lógico ('ventas.clientes') o ID numérico.\nPreview: Usa preview: true para ver qué haría sin ejecutar.\nBatch: Usa skip_existing: true para saltar campos/entidades que ya existen (no falla)."
|
|
15
|
+
},
|
|
16
|
+
|
|
17
|
+
"gufi_docs": {
|
|
18
|
+
"description": "Leer documentación de Gufi. USA ESTO cuando no sepas cómo funciona algo.\n\nTopics: overview, fields, views, automations, errors, examples, api, modules, packages"
|
|
19
|
+
},
|
|
20
|
+
|
|
21
|
+
"gufi_automation_scripts": {
|
|
22
|
+
"description": "CRUD de automation scripts.\n\nAcciones:\n list: gufi_automation_scripts({ action: 'list', company_id: '146' })\n get: gufi_automation_scripts({ action: 'get', company_id: '146', id: '5' })\n create: gufi_automation_scripts({ action: 'create', company_id: '146', name: 'mi_script', code: '...' })\n update: gufi_automation_scripts({ action: 'update', company_id: '146', name: 'mi_script', code: '...' })\n delete: gufi_automation_scripts({ action: 'delete', company_id: '146', name: 'mi_script' })\n\nIMPORTANTE: api.query() retorna rows directamente (no { rows })."
|
|
23
|
+
},
|
|
24
|
+
|
|
25
|
+
"gufi_automation_script_test": {
|
|
26
|
+
"description": "Ejecutar/testear un automation script manualmente.\n\nParámetros:\n- entity: Nombre lógico (module.entity). Opcional para scripts standalone.\n- row: Datos de la fila (incluir id). Opcional si usas input.\n- input: Datos adicionales (van a context.input). Opcional si usas row.\n\nEjemplo con entidad:\ngufi_automation_script_test({\n company_id: '116',\n script_name: 'send_email',\n entity: 'ventas.facturas',\n trigger_event: 'on_click',\n row: { id: 1, total: 150 },\n input: { email_to: 'test@example.com' }\n})\n\nEjemplo standalone (sin entidad):\ngufi_automation_script_test({\n company_id: '116',\n script_name: 'daily_report',\n trigger_event: 'scheduled',\n input: { date: '2024-01-15' }\n})"
|
|
27
|
+
},
|
|
28
|
+
|
|
29
|
+
"gufi_automation_meta": {
|
|
30
|
+
"description": "Ver/configurar triggers de entidades.\n\nVer: gufi_automation_meta({ entity_id: '123', company_id: '146' })\nSet: gufi_automation_meta({ entity_id: '123', company_id: '146', automations: [{ trigger: 'insert', function_name: 'my_script' }] })\n\nTriggers válidos: insert, update, delete, click, scheduled, custom\n\n💜 CUSTOM: Para vistas, no configures manualmente. Usa core/automations.ts y gufi_view_push lo registra automáticamente."
|
|
31
|
+
},
|
|
32
|
+
|
|
33
|
+
"gufi_automation_integrations": {
|
|
34
|
+
"description": "List Gufi's built-in integrations (Stripe, Nayax, etc.) and their api.* methods. These are pre-built by Gufi - users can also create custom integrations directly in their automations using api.http() for any external service. Credentials are passed using env.* variables."
|
|
35
|
+
},
|
|
36
|
+
|
|
37
|
+
"gufi_automation_executions": {
|
|
38
|
+
"description": "Ver historial de ejecuciones de automations. Útil para debugging."
|
|
39
|
+
},
|
|
40
|
+
|
|
41
|
+
"gufi_data": {
|
|
42
|
+
"description": "CRUD unificado para datos.\n\nAcciones:\n list: gufi_data({ action: 'list', table: 'ventas.productos', company_id: '146' })\n get: gufi_data({ action: 'get', table: '...', id: 123 })\n create: gufi_data({ action: 'create', table: '...', data: {...} })\n update: gufi_data({ action: 'update', table: '...', id: 123, data: {...} })\n delete: gufi_data({ action: 'delete', table: '...', id: 123 })\n\nAnalytics:\n aggregate: gufi_data({ action: 'aggregate', table: 'ventas.pedidos', agg: 'sum', field: 'total' })\n - agg: 'count', 'sum', 'avg', 'min', 'max'\n - groupBy: gufi_data({ action: 'aggregate', table: '...', agg: 'sum', field: 'total', groupBy: 'cliente_id' })\n - filter: gufi_data({ action: 'aggregate', table: '...', agg: 'count', filter: 'estado=activo' })\n - dateField: gufi_data({ action: 'aggregate', table: '...', agg: 'sum', field: 'total', groupBy: 'cliente_id', dateField: 'fecha', dateFrom: '2025-12-01', dateTo: '2025-12-31' })\n\nOpciones list:\n - fields: ['id', 'nombre'] - Solo columnas específicas\n - compact: true - Respuesta mínima (solo data)"
|
|
43
|
+
},
|
|
44
|
+
|
|
45
|
+
"gufi_env": {
|
|
46
|
+
"description": "Variables de entorno. Se usan en automations como process.env.KEY.\n\nAcciones:\n list: gufi_env({ action: 'list', company_id: '146' })\n set: gufi_env({ action: 'set', company_id: '146', key: 'API_KEY', value: '...' })\n delete: gufi_env({ action: 'delete', company_id: '146', key: 'API_KEY' })"
|
|
47
|
+
},
|
|
48
|
+
|
|
49
|
+
"gufi_view_pull": {
|
|
50
|
+
"description": "📥 Descargar vista a local para editarla.\n\nDescarga a: ~/gufi-dev/view_<id>/\n\n💜 FLUJO COMPLETO (crear + desarrollar):\n1. CREAR vista con gufi_schema_modify:\n gufi_schema_modify({ company_id: '146', operations: [{ op: 'add_entity', module: 'ventas', entity: { name: 'mi_dashboard', label: 'Mi Dashboard', kind: 'marketplace_view' } }] })\n → Retorna el view_id de la nueva vista\n2. gufi_view_pull({ view_id: <id> }) - Descargar template\n3. Editar archivos con Read/Edit\n4. gufi_view_push({ view_id: <id> }) - Subir\n5. gufi_view_test({ view_id: <id>, company_id: '146' }) - Verificar\n6. gufi_package({ action: 'publish', id: '19' }) - Publicar\n\nEjemplo: gufi_view_pull({ view_id: 13 })"
|
|
51
|
+
},
|
|
52
|
+
|
|
53
|
+
"gufi_view_push": {
|
|
54
|
+
"description": "📤 Subir cambios (sync completo).\n\nSube TODOS los archivos locales. El servidor borra los que no vengan.\n\n💜 AUTOMATIONS: Si existe core/automations.ts, las automations declaradas se registran en __automation_meta__ con trigger_event='CUSTOM'. Solo automations declaradas pueden llamarse con gufi.automation().\n\n⚠️ IMPORTANTE: Después de push, SIEMPRE usar gufi_view_test para verificar que la vista carga sin errores antes de publicar.\n\nFlujo:\n1. Crear scripts: gufi_automation_scripts({ action: 'create', name: 'mi_script' })\n2. Declarar en core/automations.ts: export const automations = [{ name: 'mi_script' }]\n3. gufi_view_push({ view_id: 13 }) → Registra automations\n4. gufi_view_test({ view_id: 13, company_id: '116' }) ← OBLIGATORIO\n5. Si OK → gufi_package({ action: 'publish', id: '19' })\n\nEjemplo: gufi_view_push({ view_id: 13, message: 'Fix bug' })"
|
|
55
|
+
},
|
|
56
|
+
|
|
57
|
+
"gufi_view_test": {
|
|
58
|
+
"description": "🧪 Test view in headless browser. Returns console logs, JS errors, API calls, and screenshot.\n\n⚠️ SIEMPRE ejecutar después de gufi_view_push para verificar que todo funciona.\n\nFlujo típico:\n1. Editar código localmente\n2. gufi_view_push({ view_id: 45 }) - Subir\n3. gufi_view_test({ view_id: 45, company_id: '150' }) - Verificar\n4. Ver errores/screenshot → Arreglar si hay problemas\n5. Repetir hasta que funcione\n6. gufi_package({ action: 'publish' }) - Publicar\n\nAcciones opcionales después de cargar:\n actions: [\n { type: 'click', selector: '.my-button' },\n { type: 'fill', selector: 'input[name=search]', value: 'test' },\n { type: 'wait', selector: '.results' },\n { type: 'delay', ms: 1000 }\n ]\n\nEjemplo: gufi_view_test({ view_id: 45, company_id: '150' })"
|
|
59
|
+
},
|
|
60
|
+
|
|
61
|
+
"gufi_package": {
|
|
62
|
+
"description": "Gestión de packages.\n\nAcciones:\n list: gufi_package({ action: 'list' })\n get: gufi_package({ action: 'get', id: '19' })\n create: gufi_package({ action: 'create', name: 'Mi Package' })\n delete: gufi_package({ action: 'delete', id: '19' })\n add_module: gufi_package({ action: 'add_module', id: '19', module_id: '5', company_id: '146' })\n remove_module: gufi_package({ action: 'remove_module', id: '19', module_id: '5' })\n publish: gufi_package({ action: 'publish', id: '19' })"
|
|
63
|
+
}
|
|
64
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "gufi-cli",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.51",
|
|
4
4
|
"description": "CLI for developing Gufi Marketplace views locally with Claude Code",
|
|
5
5
|
"bin": {
|
|
6
6
|
"gufi": "./bin/gufi.js"
|
|
@@ -8,6 +8,7 @@
|
|
|
8
8
|
"files": [
|
|
9
9
|
"bin",
|
|
10
10
|
"dist",
|
|
11
|
+
"docs",
|
|
11
12
|
"CLAUDE.md"
|
|
12
13
|
],
|
|
13
14
|
"repository": {
|
|
@@ -17,7 +18,7 @@
|
|
|
17
18
|
"homepage": "https://gogufi.com",
|
|
18
19
|
"type": "module",
|
|
19
20
|
"scripts": {
|
|
20
|
-
"build": "tsc",
|
|
21
|
+
"build": "tsc && mkdir -p docs/mcp docs/dev-guide && cp -r ../../docs/mcp/* docs/mcp/ && cp -r ../../docs/dev-guide/* docs/dev-guide/",
|
|
21
22
|
"dev": "tsc -w",
|
|
22
23
|
"start": "node bin/gufi.js"
|
|
23
24
|
},
|