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,613 @@
|
|
|
1
|
+
# Vistas Marketplace
|
|
2
|
+
|
|
3
|
+
## TL;DR
|
|
4
|
+
|
|
5
|
+
```
|
|
6
|
+
mi_vista/
|
|
7
|
+
├── index.tsx # OBLIGATORIO - Entry point
|
|
8
|
+
├── core/
|
|
9
|
+
│ ├── dataSources.ts # OBLIGATORIO - Define tablas
|
|
10
|
+
│ ├── automations.ts # Si usa automations - declara cuáles usa
|
|
11
|
+
│ └── index.tsx # OBLIGATORIO - featureConfig
|
|
12
|
+
└── views/
|
|
13
|
+
└── page.tsx # Recomendado - Componente principal
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
**Workflow MCP completo:**
|
|
17
|
+
```
|
|
18
|
+
# 1. Crear la vista (es una entity de tipo marketplace_view)
|
|
19
|
+
gufi_schema_modify({
|
|
20
|
+
company_id: "116",
|
|
21
|
+
operations: [{
|
|
22
|
+
op: "add_entity",
|
|
23
|
+
module: "ventas",
|
|
24
|
+
entity: { name: "mi_dashboard", label: "Mi Dashboard", kind: "marketplace_view" }
|
|
25
|
+
}]
|
|
26
|
+
})
|
|
27
|
+
|
|
28
|
+
# 2. Descargar template
|
|
29
|
+
gufi_view_pull({ view_id: <id> }) → Descarga a ~/gufi-dev/view_<id>/
|
|
30
|
+
|
|
31
|
+
# 3. Editar archivos locales
|
|
32
|
+
|
|
33
|
+
# 4. Subir cambios
|
|
34
|
+
gufi_view_push({ view_id: <id> }) → Sube a draft (valida ANTES de subir)
|
|
35
|
+
|
|
36
|
+
# 5. Publicar
|
|
37
|
+
gufi package:publish <package_id> → Publica a producción
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
---
|
|
41
|
+
|
|
42
|
+
## Archivos Obligatorios
|
|
43
|
+
|
|
44
|
+
### 1. index.tsx (Entry Point)
|
|
45
|
+
|
|
46
|
+
```typescript
|
|
47
|
+
// index.tsx - OBLIGATORIO
|
|
48
|
+
// Debe exportar featureConfig y default component
|
|
49
|
+
|
|
50
|
+
export { featureConfig } from './core';
|
|
51
|
+
|
|
52
|
+
import Page from './views/page';
|
|
53
|
+
export default Page;
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
### 2. core/dataSources.ts (Tablas)
|
|
57
|
+
|
|
58
|
+
```typescript
|
|
59
|
+
// core/dataSources.ts - OBLIGATORIO
|
|
60
|
+
// Define las tablas que la vista puede acceder
|
|
61
|
+
|
|
62
|
+
export const dataSources = [
|
|
63
|
+
{ key: 'productos', table: 'ventas.productos' },
|
|
64
|
+
{ key: 'pedidos', table: 'ventas.pedidos' },
|
|
65
|
+
] as const;
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
**El engine:**
|
|
69
|
+
1. Lee `core/dataSources.ts` al cargar la vista
|
|
70
|
+
2. Resuelve cada `table` lógico → ID físico (automático)
|
|
71
|
+
3. Crea `gufi.tables = { productos: 'm308_t4135', pedidos: '...' }`
|
|
72
|
+
|
|
73
|
+
### 3. core/index.tsx (Feature Config)
|
|
74
|
+
|
|
75
|
+
```typescript
|
|
76
|
+
// core/index.tsx - OBLIGATORIO
|
|
77
|
+
import { dataSources } from './dataSources';
|
|
78
|
+
import { featureInputs } from '../metadata/inputs';
|
|
79
|
+
import { seedData } from '../metadata/seedData';
|
|
80
|
+
|
|
81
|
+
export const featureConfig = {
|
|
82
|
+
dataSources,
|
|
83
|
+
inputs: featureInputs,
|
|
84
|
+
seedData,
|
|
85
|
+
meta: {
|
|
86
|
+
name: 'Mi Vista',
|
|
87
|
+
version: '1.0.0',
|
|
88
|
+
},
|
|
89
|
+
};
|
|
90
|
+
|
|
91
|
+
// Re-export dataSources
|
|
92
|
+
export { dataSources };
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
---
|
|
96
|
+
|
|
97
|
+
## Componente Principal
|
|
98
|
+
|
|
99
|
+
### views/page.tsx
|
|
100
|
+
|
|
101
|
+
```typescript
|
|
102
|
+
import React, { useState, useEffect } from 'react';
|
|
103
|
+
import type { FullGufiContext } from '@/sdk';
|
|
104
|
+
|
|
105
|
+
interface ViewProps {
|
|
106
|
+
id: string;
|
|
107
|
+
gufi: FullGufiContext; // REQUIRED - nunca opcional
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
export default function MiVista({ id, gufi }: ViewProps) {
|
|
111
|
+
const { tables, dataProvider, context, utils } = gufi;
|
|
112
|
+
const { toastSuccess, toastError } = utils;
|
|
113
|
+
|
|
114
|
+
const [data, setData] = useState<any[]>([]);
|
|
115
|
+
const [loading, setLoading] = useState(true);
|
|
116
|
+
|
|
117
|
+
useEffect(() => {
|
|
118
|
+
const load = async () => {
|
|
119
|
+
try {
|
|
120
|
+
// 💜 Usar gufi.tables (IDs físicos ya resueltos)
|
|
121
|
+
const res = await dataProvider.getList({
|
|
122
|
+
resource: tables.productos,
|
|
123
|
+
pagination: { current: 1, pageSize: 100 },
|
|
124
|
+
});
|
|
125
|
+
setData(res.data);
|
|
126
|
+
} catch (err) {
|
|
127
|
+
toastError('Error cargando datos');
|
|
128
|
+
} finally {
|
|
129
|
+
setLoading(false);
|
|
130
|
+
}
|
|
131
|
+
};
|
|
132
|
+
load();
|
|
133
|
+
}, []);
|
|
134
|
+
|
|
135
|
+
if (loading) return <div className="p-4">Cargando...</div>;
|
|
136
|
+
|
|
137
|
+
return (
|
|
138
|
+
<div className="flex flex-col h-[100dvh] bg-gradient-to-br from-violet-50/40 via-white to-purple-50/40">
|
|
139
|
+
<header className="p-4 border-b">
|
|
140
|
+
<h1 className="text-lg font-semibold">Mi Vista</h1>
|
|
141
|
+
</header>
|
|
142
|
+
<main className="flex-1 overflow-auto p-4">
|
|
143
|
+
{data.map(item => (
|
|
144
|
+
<div key={item.id} className="p-4 bg-white rounded-xl border mb-2">
|
|
145
|
+
{item.nombre}
|
|
146
|
+
</div>
|
|
147
|
+
))}
|
|
148
|
+
</main>
|
|
149
|
+
</div>
|
|
150
|
+
);
|
|
151
|
+
}
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
---
|
|
155
|
+
|
|
156
|
+
## gufi - Lo que recibes
|
|
157
|
+
|
|
158
|
+
```typescript
|
|
159
|
+
gufi = {
|
|
160
|
+
// 💜 TABLES - IDs físicos resueltos desde dataSources.ts
|
|
161
|
+
tables: {
|
|
162
|
+
productos: 'm308_t4135', // Resuelto automáticamente
|
|
163
|
+
pedidos: 'm308_t4136',
|
|
164
|
+
// Solo tablas declaradas en dataSources.ts
|
|
165
|
+
},
|
|
166
|
+
|
|
167
|
+
// CRUD
|
|
168
|
+
dataProvider: {
|
|
169
|
+
getList({ resource, pagination, filters, sorters }),
|
|
170
|
+
getOne({ resource, id }),
|
|
171
|
+
create({ resource, variables }),
|
|
172
|
+
update({ resource, id, variables }),
|
|
173
|
+
deleteOne({ resource, id }),
|
|
174
|
+
},
|
|
175
|
+
|
|
176
|
+
// Contexto
|
|
177
|
+
context: {
|
|
178
|
+
lang, // 'es' | 'en'
|
|
179
|
+
user, // { id, name, email }
|
|
180
|
+
activeCompany, // { id, name }
|
|
181
|
+
schema, // Módulos y entidades
|
|
182
|
+
},
|
|
183
|
+
|
|
184
|
+
// Utilidades
|
|
185
|
+
utils: {
|
|
186
|
+
toastSuccess, toastError, toastInfo,
|
|
187
|
+
formatCurrency, formatDate,
|
|
188
|
+
cn, tUI,
|
|
189
|
+
},
|
|
190
|
+
|
|
191
|
+
// Column Builders
|
|
192
|
+
CB: {
|
|
193
|
+
currency(150), // { currency: 'EUR', amount: 150 }
|
|
194
|
+
phone('612345678'), // { prefix: '+34', number: '...' }
|
|
195
|
+
date(), // Fecha de hoy
|
|
196
|
+
},
|
|
197
|
+
|
|
198
|
+
// 💜 Automations - Ejecutar lógica de negocio
|
|
199
|
+
automation(name, input), // Llama automation por nombre
|
|
200
|
+
|
|
201
|
+
// Device
|
|
202
|
+
device: { isHandheld, isDesktop },
|
|
203
|
+
}
|
|
204
|
+
```
|
|
205
|
+
|
|
206
|
+
---
|
|
207
|
+
|
|
208
|
+
## Librerías Disponibles
|
|
209
|
+
|
|
210
|
+
Las Custom Views pueden importar estas librerías (mockeadas/incluidas en el engine):
|
|
211
|
+
|
|
212
|
+
### UI & Componentes
|
|
213
|
+
|
|
214
|
+
| Librería | Import | Descripción |
|
|
215
|
+
|----------|--------|-------------|
|
|
216
|
+
| **Lucide Icons** | `from 'lucide-react'` | Iconos: `<Search />`, `<Plus />`, etc. |
|
|
217
|
+
| **Recharts** | `from 'recharts'` | Gráficos: `LineChart`, `BarChart`, `PieChart` |
|
|
218
|
+
| **UI Components** | `from '@/shared/ui'` | Button, Card, Dialog, Tabs, Select, Badge, etc. |
|
|
219
|
+
| **Radix UI** | `from '@radix-ui/*'` | Primitivos accesibles |
|
|
220
|
+
|
|
221
|
+
### Animaciones (Framer Motion)
|
|
222
|
+
|
|
223
|
+
```typescript
|
|
224
|
+
import {
|
|
225
|
+
motion, // motion.div, motion.button, etc.
|
|
226
|
+
AnimatePresence, // Animaciones de entrada/salida
|
|
227
|
+
useScroll, // Track scroll position
|
|
228
|
+
useTransform, // Mapear valores
|
|
229
|
+
useMotionValueEvent, // Escuchar cambios
|
|
230
|
+
useMotionValue, // Crear valor reactivo
|
|
231
|
+
useSpring, // Física de spring
|
|
232
|
+
useInView, // Detectar visibilidad
|
|
233
|
+
} from 'framer-motion';
|
|
234
|
+
```
|
|
235
|
+
|
|
236
|
+
**Props de animación:**
|
|
237
|
+
- `initial`, `animate`, `exit` - Estados
|
|
238
|
+
- `transition` - `{ duration, delay, ease }`
|
|
239
|
+
- `whileHover`, `whileTap` - Interacciones
|
|
240
|
+
- `whileInView`, `viewport` - Scroll reveal
|
|
241
|
+
|
|
242
|
+
**Propiedades animables:** `x`, `y`, `scale`, `rotate`, `opacity`, `filter`
|
|
243
|
+
|
|
244
|
+
**Ejemplo scroll animation:**
|
|
245
|
+
```typescript
|
|
246
|
+
const containerRef = useRef(null);
|
|
247
|
+
const { scrollYProgress } = useScroll({ target: containerRef, offset: ["start start", "end end"] });
|
|
248
|
+
const opacity = useTransform(scrollYProgress, [0, 0.5], [0, 1]);
|
|
249
|
+
|
|
250
|
+
return (
|
|
251
|
+
<div ref={containerRef} style={{ height: '300vh' }}>
|
|
252
|
+
<motion.div style={{ opacity }} className="sticky top-0">
|
|
253
|
+
Fade in on scroll
|
|
254
|
+
</motion.div>
|
|
255
|
+
</div>
|
|
256
|
+
);
|
|
257
|
+
```
|
|
258
|
+
|
|
259
|
+
**Ejemplo whileInView:**
|
|
260
|
+
```typescript
|
|
261
|
+
<motion.div
|
|
262
|
+
initial={{ opacity: 0, y: 30 }}
|
|
263
|
+
whileInView={{ opacity: 1, y: 0 }}
|
|
264
|
+
viewport={{ once: true }}
|
|
265
|
+
>
|
|
266
|
+
Aparece al scroll
|
|
267
|
+
</motion.div>
|
|
268
|
+
```
|
|
269
|
+
|
|
270
|
+
### Utilidades
|
|
271
|
+
|
|
272
|
+
| Librería | Import | Descripción |
|
|
273
|
+
|----------|--------|-------------|
|
|
274
|
+
| **date-fns** | `from 'date-fns'` | `format`, `parseISO`, `addDays`, etc. |
|
|
275
|
+
| **clsx/cn** | `from '@/shared/lib/utils'` | Merge classNames |
|
|
276
|
+
| **Theme** | `from '@/shared/styles/theme'` | `THEME`, `PATTERNS` |
|
|
277
|
+
| **Language** | `from '@/shared/language'` | `tUI()`, `getLang()` |
|
|
278
|
+
| **Toast** | `from '@/shared/notifications/toast'` | `toastSuccess()`, `toastError()` |
|
|
279
|
+
| **JsBarcode** | `from 'jsbarcode'` | Generar códigos de barras |
|
|
280
|
+
|
|
281
|
+
### Hooks Gufi
|
|
282
|
+
|
|
283
|
+
| Hook | Import | Descripción |
|
|
284
|
+
|------|--------|-------------|
|
|
285
|
+
| `useViewPreferences` | `from '@/shared/hooks'` | Persistir filtros/estado |
|
|
286
|
+
| `useDebouncedValue` | `from '@/shared/hooks'` | Debounce valores |
|
|
287
|
+
| `useDeviceMode` | `from '@/shared/adaptive'` | `{ isHandheld, isDesktop }` |
|
|
288
|
+
| `useEntityConfig` | `from '@/sdk'` | Config de entidad |
|
|
289
|
+
|
|
290
|
+
---
|
|
291
|
+
|
|
292
|
+
## Desarrollo con MCP
|
|
293
|
+
|
|
294
|
+
### Workflow Completo
|
|
295
|
+
|
|
296
|
+
```bash
|
|
297
|
+
# 1. Descargar vista a local
|
|
298
|
+
gufi_view_pull({ view_id: 25 })
|
|
299
|
+
# → ~/gufi-dev/view_25/
|
|
300
|
+
|
|
301
|
+
# 2. Editar archivos locales con Read/Edit
|
|
302
|
+
Read("~/gufi-dev/view_25/views/page.tsx")
|
|
303
|
+
Edit("~/gufi-dev/view_25/views/page.tsx", ...)
|
|
304
|
+
|
|
305
|
+
# 3. Subir cambios a draft
|
|
306
|
+
gufi_view_push({ view_id: 25 })
|
|
307
|
+
# ⚠️ VALIDA ESTRUCTURA ANTES DE SUBIR
|
|
308
|
+
# Si falta index.tsx o core/dataSources.ts → BLOQUEA el push
|
|
309
|
+
|
|
310
|
+
# 4. Publicar a producción (si tiene package)
|
|
311
|
+
gufi package:publish <package_id>
|
|
312
|
+
```
|
|
313
|
+
|
|
314
|
+
### Validación Pre-Push
|
|
315
|
+
|
|
316
|
+
El push ahora valida ANTES de subir:
|
|
317
|
+
|
|
318
|
+
**Errores bloqueantes:**
|
|
319
|
+
- Falta `index.tsx`
|
|
320
|
+
- Falta `core/dataSources.ts` (o dataSources en `core/index.tsx`)
|
|
321
|
+
- `index.tsx` no exporta `featureConfig`
|
|
322
|
+
- `index.tsx` no tiene `export default`
|
|
323
|
+
|
|
324
|
+
**Advertencias:**
|
|
325
|
+
- Falta `core/index.tsx` con featureConfig
|
|
326
|
+
- Falta componente en `views/`
|
|
327
|
+
|
|
328
|
+
### Entornos: Local vs Producción
|
|
329
|
+
|
|
330
|
+
```bash
|
|
331
|
+
# Ver entorno actual
|
|
332
|
+
gufi_whoami()
|
|
333
|
+
|
|
334
|
+
# Cambiar entorno ANTES de push
|
|
335
|
+
gufi config:local # → http://localhost:3000
|
|
336
|
+
gufi config:prod # → https://gogufi.com
|
|
337
|
+
```
|
|
338
|
+
|
|
339
|
+
**IMPORTANTE:** Verifica siempre el entorno antes de hacer push.
|
|
340
|
+
|
|
341
|
+
---
|
|
342
|
+
|
|
343
|
+
## CRUD
|
|
344
|
+
|
|
345
|
+
```typescript
|
|
346
|
+
const { tables, dataProvider, CB } = gufi;
|
|
347
|
+
|
|
348
|
+
// Leer lista
|
|
349
|
+
const { data } = await dataProvider.getList({
|
|
350
|
+
resource: tables.productos, // 💜 gufi.tables
|
|
351
|
+
pagination: { current: 1, pageSize: 50 },
|
|
352
|
+
filters: [{ field: 'activo', operator: 'eq', value: true }],
|
|
353
|
+
sorters: [{ field: 'nombre', order: 'asc' }],
|
|
354
|
+
});
|
|
355
|
+
|
|
356
|
+
// Leer uno
|
|
357
|
+
const { data: producto } = await dataProvider.getOne({
|
|
358
|
+
resource: tables.productos,
|
|
359
|
+
id: 123,
|
|
360
|
+
});
|
|
361
|
+
|
|
362
|
+
// Crear (usar CB para campos especiales)
|
|
363
|
+
await dataProvider.create({
|
|
364
|
+
resource: tables.productos,
|
|
365
|
+
variables: {
|
|
366
|
+
nombre: 'Nuevo',
|
|
367
|
+
precio: CB.currency(100),
|
|
368
|
+
},
|
|
369
|
+
});
|
|
370
|
+
|
|
371
|
+
// Actualizar (incluir __display para select/relation)
|
|
372
|
+
await dataProvider.update({
|
|
373
|
+
resource: tables.productos,
|
|
374
|
+
id: 123,
|
|
375
|
+
variables: {
|
|
376
|
+
estado: 'completado',
|
|
377
|
+
estado__display: 'Completado',
|
|
378
|
+
},
|
|
379
|
+
});
|
|
380
|
+
|
|
381
|
+
// Eliminar
|
|
382
|
+
await dataProvider.deleteOne({
|
|
383
|
+
resource: tables.productos,
|
|
384
|
+
id: 123,
|
|
385
|
+
});
|
|
386
|
+
```
|
|
387
|
+
|
|
388
|
+
---
|
|
389
|
+
|
|
390
|
+
## Automations desde Views
|
|
391
|
+
|
|
392
|
+
Las views pueden ejecutar automations, pero **DEBEN declararlas en `core/automations.ts`**.
|
|
393
|
+
|
|
394
|
+
### 1. Declarar automations (OBLIGATORIO)
|
|
395
|
+
|
|
396
|
+
```typescript
|
|
397
|
+
// core/automations.ts - OBLIGATORIO si la view usa automations
|
|
398
|
+
export const automations = [
|
|
399
|
+
{ name: 'process_payment', description: 'Crea sesión de pago en Stripe' },
|
|
400
|
+
{ name: 'send_invoice', description: 'Envía factura por email' },
|
|
401
|
+
] as const;
|
|
402
|
+
```
|
|
403
|
+
|
|
404
|
+
**Al hacer `gufi_view_push`:**
|
|
405
|
+
1. El sistema detecta `core/automations.ts`
|
|
406
|
+
2. Registra cada automation en `__automation_meta__` con `trigger_event='CUSTOM'`
|
|
407
|
+
3. Solo las automations declaradas podrán ser ejecutadas desde la view
|
|
408
|
+
|
|
409
|
+
### 2. Llamar automations desde la View
|
|
410
|
+
|
|
411
|
+
```typescript
|
|
412
|
+
// En la View
|
|
413
|
+
const handlePayment = async () => {
|
|
414
|
+
try {
|
|
415
|
+
// 💜 Solo funciona si 'process_payment' está en core/automations.ts
|
|
416
|
+
const result = await gufi.automation('process_payment', {
|
|
417
|
+
amount: 1999,
|
|
418
|
+
customer_email: 'cliente@example.com',
|
|
419
|
+
});
|
|
420
|
+
|
|
421
|
+
if (result.url) {
|
|
422
|
+
window.location.href = result.url;
|
|
423
|
+
}
|
|
424
|
+
} catch (err) {
|
|
425
|
+
gufi.utils.toastError('Error procesando pago');
|
|
426
|
+
}
|
|
427
|
+
};
|
|
428
|
+
|
|
429
|
+
return <button onClick={handlePayment}>Pagar</button>;
|
|
430
|
+
```
|
|
431
|
+
|
|
432
|
+
### 3. Crear el script de automation
|
|
433
|
+
|
|
434
|
+
```javascript
|
|
435
|
+
// En la Automation (BD: __automation_scripts__)
|
|
436
|
+
// Crear con: gufi_automation_scripts({ action: 'create', name: 'process_payment', code: '...' })
|
|
437
|
+
async function process_payment(gufi) {
|
|
438
|
+
const { input, env } = gufi.context;
|
|
439
|
+
|
|
440
|
+
const result = await gufi.integrations.stripe.createCheckoutSession({
|
|
441
|
+
secretKey: env.STRIPE_SECRET_KEY,
|
|
442
|
+
lineItems: [{ name: 'Producto', amount: input.amount, quantity: 1 }],
|
|
443
|
+
customerEmail: input.customer_email,
|
|
444
|
+
});
|
|
445
|
+
|
|
446
|
+
return { url: result.url, sessionId: result.sessionId };
|
|
447
|
+
}
|
|
448
|
+
```
|
|
449
|
+
|
|
450
|
+
### Flujo Completo
|
|
451
|
+
|
|
452
|
+
```
|
|
453
|
+
1. Crear script: gufi_automation_scripts({ action: 'create', name: 'X' })
|
|
454
|
+
2. Declarar en view: core/automations.ts → export const automations = [{ name: 'X' }]
|
|
455
|
+
3. Push view: gufi_view_push() → Registra en __automation_meta__
|
|
456
|
+
4. Llamar desde view: gufi.automation('X', input) → Validado y ejecutado
|
|
457
|
+
```
|
|
458
|
+
|
|
459
|
+
### Error si no está declarada
|
|
460
|
+
|
|
461
|
+
```
|
|
462
|
+
Error: Automation 'X' is not declared in this view's core/automations.ts.
|
|
463
|
+
Add it and push the view first.
|
|
464
|
+
```
|
|
465
|
+
|
|
466
|
+
**Solución:** Añadir la automation a `core/automations.ts` y hacer push.
|
|
467
|
+
|
|
468
|
+
### Integraciones disponibles
|
|
469
|
+
|
|
470
|
+
- `gufi.integrations.stripe.*` - Pagos
|
|
471
|
+
- `gufi.integrations.nayax.*` - Vending telemetry
|
|
472
|
+
- `gufi.integrations.ourvend.*` - RedPanda vending
|
|
473
|
+
- `gufi.integrations.tns.*` - Contabilidad Colombia
|
|
474
|
+
|
|
475
|
+
Ver integraciones: `gufi_automation_integrations()` en MCP.
|
|
476
|
+
|
|
477
|
+
---
|
|
478
|
+
|
|
479
|
+
## Reglas
|
|
480
|
+
|
|
481
|
+
### CORRECTO
|
|
482
|
+
|
|
483
|
+
```typescript
|
|
484
|
+
// ✅ core/dataSources.ts existe
|
|
485
|
+
export const dataSources = [
|
|
486
|
+
{ key: 'productos', table: 'ventas.productos' },
|
|
487
|
+
] as const;
|
|
488
|
+
|
|
489
|
+
// ✅ core/automations.ts para automations
|
|
490
|
+
export const automations = [
|
|
491
|
+
{ name: 'mi_script', description: 'Hace algo' },
|
|
492
|
+
] as const;
|
|
493
|
+
|
|
494
|
+
// ✅ gufi es required
|
|
495
|
+
gufi: FullGufiContext
|
|
496
|
+
|
|
497
|
+
// ✅ Usar gufi.tables
|
|
498
|
+
resource: gufi.tables.productos
|
|
499
|
+
|
|
500
|
+
// ✅ Pasar gufi a children
|
|
501
|
+
<Child gufi={gufi} />
|
|
502
|
+
```
|
|
503
|
+
|
|
504
|
+
### INCORRECTO
|
|
505
|
+
|
|
506
|
+
```typescript
|
|
507
|
+
// ❌ TABLES local (usar gufi.tables)
|
|
508
|
+
const TABLES = { productos: 'ventas.productos' };
|
|
509
|
+
|
|
510
|
+
// ❌ gufi opcional
|
|
511
|
+
gufi?: any
|
|
512
|
+
|
|
513
|
+
// ❌ IDs físicos hardcodeados
|
|
514
|
+
resource: 'm308_t4136'
|
|
515
|
+
|
|
516
|
+
// ❌ stores
|
|
517
|
+
import { useAuthStore }
|
|
518
|
+
|
|
519
|
+
// ❌ Llamar automation sin declararla
|
|
520
|
+
gufi.automation('script_no_declarado', {}) // Error 403
|
|
521
|
+
```
|
|
522
|
+
|
|
523
|
+
---
|
|
524
|
+
|
|
525
|
+
## CLI
|
|
526
|
+
|
|
527
|
+
```bash
|
|
528
|
+
gufi view:pull <id> # Descargar vista
|
|
529
|
+
gufi view:push # Subir cambios (valida + sube + valida servidor)
|
|
530
|
+
gufi package:publish <id> # Publicar a producción
|
|
531
|
+
```
|
|
532
|
+
|
|
533
|
+
---
|
|
534
|
+
|
|
535
|
+
## Errores Comunes
|
|
536
|
+
|
|
537
|
+
| Error | Causa | Solución |
|
|
538
|
+
|-------|-------|----------|
|
|
539
|
+
| `Falta archivo obligatorio: index.tsx` | No existe entry point | Crear index.tsx con exports |
|
|
540
|
+
| `Falta archivo obligatorio: core/dataSources.ts` | No se definieron tablas | Crear dataSources.ts |
|
|
541
|
+
| `index.tsx debe exportar featureConfig` | Falta export | Añadir `export { featureConfig } from './core'` |
|
|
542
|
+
| `gufi.tables undefined` | dataSources vacío | Añadir tablas a dataSources |
|
|
543
|
+
| `Cannot read context` | gufi no pasado | Pasar `gufi={gufi}` a children |
|
|
544
|
+
| `Table not found` | Nombre lógico mal | Verificar modulo.entidad en schema |
|
|
545
|
+
| `Push a producción en vez de local` | Entorno mal configurado | Verificar con `gufi_whoami()` |
|
|
546
|
+
| `Automation 'X' is not declared` | Falta en core/automations.ts | Añadir automation a core/automations.ts y push |
|
|
547
|
+
| `Declared automation script not found` | Script no existe | Crear script con gufi_automation_scripts |
|
|
548
|
+
|
|
549
|
+
---
|
|
550
|
+
|
|
551
|
+
## Testing de Views con gufi_view_test
|
|
552
|
+
|
|
553
|
+
### Workflow Estándar
|
|
554
|
+
|
|
555
|
+
```javascript
|
|
556
|
+
// 1️⃣ SIEMPRE explorar primero para ver elementos disponibles
|
|
557
|
+
gufi_view_test({
|
|
558
|
+
view_id: 45,
|
|
559
|
+
company_id: "116",
|
|
560
|
+
actions: [
|
|
561
|
+
{type: "closeModals"}, // Cerrar popups/newsletters
|
|
562
|
+
{type: "delay", ms: 2000}, // Esperar carga
|
|
563
|
+
{type: "explore"} // Listar botones, links, inputs
|
|
564
|
+
]
|
|
565
|
+
})
|
|
566
|
+
// → actions[2].elements = { buttons: [...], links: [...], inputs: [...] }
|
|
567
|
+
|
|
568
|
+
// 2️⃣ LUEGO interactuar usando texto visible
|
|
569
|
+
gufi_view_test({
|
|
570
|
+
view_id: 45,
|
|
571
|
+
company_id: "116",
|
|
572
|
+
actions: [
|
|
573
|
+
{type: "closeModals"},
|
|
574
|
+
{type: "click", selector: "text:Mi Botón"}, // Click por texto
|
|
575
|
+
{type: "delay", ms: 1000},
|
|
576
|
+
{type: "click", selector: "text:Siguiente"},
|
|
577
|
+
{type: "delay", ms: 1000}
|
|
578
|
+
]
|
|
579
|
+
})
|
|
580
|
+
```
|
|
581
|
+
|
|
582
|
+
### Actions Disponibles
|
|
583
|
+
|
|
584
|
+
| Action | Uso | Ejemplo |
|
|
585
|
+
|--------|-----|---------|
|
|
586
|
+
| `explore` | Lista elementos interactivos | `{type: "explore"}` → buttons, links, inputs |
|
|
587
|
+
| `closeModals` | Cierra popups/newsletters | `{type: "closeModals"}` |
|
|
588
|
+
| `click` | Click por texto o CSS | `{type: "click", selector: "text:Enviar"}` |
|
|
589
|
+
| `delay` | Esperar ms | `{type: "delay", ms: 1000}` |
|
|
590
|
+
| `fill` | Escribir en input | `{type: "fill", selector: "input[name=email]", value: "a@b.com"}` |
|
|
591
|
+
| `eval` | JavaScript custom | `{type: "eval", code: "document.title"}` |
|
|
592
|
+
| `screenshot` | Captura intermedia | `{type: "screenshot"}` |
|
|
593
|
+
|
|
594
|
+
### Respuesta
|
|
595
|
+
|
|
596
|
+
```javascript
|
|
597
|
+
{
|
|
598
|
+
success: true,
|
|
599
|
+
dom: { title, textPreview, elementCount },
|
|
600
|
+
actions: [
|
|
601
|
+
{ type: "explore", elements: { buttons: [...], links: [...] } },
|
|
602
|
+
{ type: "click", clicked: "Mi Botón" },
|
|
603
|
+
],
|
|
604
|
+
screenshot: "data:image/jpeg;base64,..."
|
|
605
|
+
}
|
|
606
|
+
```
|
|
607
|
+
|
|
608
|
+
### Reglas Clave
|
|
609
|
+
|
|
610
|
+
1. **SIEMPRE `closeModals` primero** - Las vistas suelen tener popups de newsletter
|
|
611
|
+
2. **SIEMPRE `explore` antes de interactuar** - Para saber qué elementos hay
|
|
612
|
+
3. **Usar `text:` para clicks** - Más robusto que selectores CSS
|
|
613
|
+
4. **Los resultados de eval están en `actions[n].result`**
|