gufi-cli 0.1.49 → 0.1.51

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (36) hide show
  1. package/dist/commands/docs.js +1 -5
  2. package/dist/lib/docs-resolver.d.ts +8 -0
  3. package/dist/lib/docs-resolver.js +27 -0
  4. package/dist/lib/security.js +5 -0
  5. package/dist/mcp.js +56 -43
  6. package/docs/dev-guide/1-01-architecture.md +358 -0
  7. package/docs/dev-guide/1-02-multi-tenant.md +415 -0
  8. package/docs/dev-guide/1-03-column-types.md +594 -0
  9. package/docs/dev-guide/1-04-json-config.md +442 -0
  10. package/docs/dev-guide/1-05-authentication.md +427 -0
  11. package/docs/dev-guide/2-01-api-reference.md +564 -0
  12. package/docs/dev-guide/2-02-automations.md +508 -0
  13. package/docs/dev-guide/2-03-gufi-cli.md +568 -0
  14. package/docs/dev-guide/2-04-realtime.md +401 -0
  15. package/docs/dev-guide/2-05-permissions.md +497 -0
  16. package/docs/dev-guide/2-06-integrations-overview.md +104 -0
  17. package/docs/dev-guide/2-07-stripe.md +173 -0
  18. package/docs/dev-guide/2-08-nayax.md +297 -0
  19. package/docs/dev-guide/2-09-ourvend.md +226 -0
  20. package/docs/dev-guide/2-10-tns.md +177 -0
  21. package/docs/dev-guide/2-11-custom-http.md +268 -0
  22. package/docs/dev-guide/3-01-custom-views.md +555 -0
  23. package/docs/dev-guide/3-02-webhooks-api.md +446 -0
  24. package/docs/mcp/00-overview.md +329 -0
  25. package/docs/mcp/01-architecture.md +226 -0
  26. package/docs/mcp/02-modules.md +285 -0
  27. package/docs/mcp/03-fields.md +357 -0
  28. package/docs/mcp/04-views.md +613 -0
  29. package/docs/mcp/05-automations.md +461 -0
  30. package/docs/mcp/06-api.md +531 -0
  31. package/docs/mcp/07-packages.md +246 -0
  32. package/docs/mcp/08-common-errors.md +284 -0
  33. package/docs/mcp/09-examples.md +453 -0
  34. package/docs/mcp/README.md +71 -0
  35. package/docs/mcp/tool-descriptions.json +64 -0
  36. package/package.json +3 -2
@@ -0,0 +1,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`**