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,285 @@
1
+ # Sistema de Modulos
2
+
3
+ Los modulos definen la estructura de datos en Gufi. Son JSON que se convierten automaticamente en tablas PostgreSQL.
4
+
5
+ ## Estructura de un modulo
6
+
7
+ ```json
8
+ {
9
+ "name": "ventas",
10
+ "displayName": "Ventas",
11
+ "icon": "ShoppingCart",
12
+ "submodules": [
13
+ {
14
+ "name": "pedidos",
15
+ "label": "Pedidos",
16
+ "entities": [
17
+ {
18
+ "id": 16192,
19
+ "name": "pedidos",
20
+ "label": "Pedidos",
21
+ "kind": "table",
22
+ "fields": [
23
+ { "name": "cliente", "type": "relation", "label": "Cliente", "required": true, "relation": { "module": "crm", "entity": "clientes" } },
24
+ { "name": "fecha", "type": "date", "label": "Fecha", "required": true },
25
+ { "name": "total", "type": "currency", "label": "Total", "currency": "EUR" },
26
+ { "name": "estado", "type": "select", "label": "Estado", "options": [
27
+ { "value": "pendiente", "label": "Pendiente", "color": "yellow" },
28
+ { "value": "enviado", "label": "Enviado", "color": "blue" },
29
+ { "value": "entregado", "label": "Entregado", "color": "green" }
30
+ ]}
31
+ ],
32
+ "permissions": {
33
+ "admin": "*",
34
+ "ventas": ["entity:view", "entity:create", "entity:update"]
35
+ },
36
+ "automations": [
37
+ { "trigger": "insert", "function_name": "notificar_pedido" }
38
+ ]
39
+ }
40
+ ]
41
+ }
42
+ ]
43
+ }
44
+ ```
45
+
46
+ ## Tabla fisica resultante
47
+
48
+ El modulo anterior crea la tabla `m{moduleId}_t16192`:
49
+
50
+ ```sql
51
+ CREATE TABLE company_116.m360_t16192 (
52
+ id SERIAL PRIMARY KEY,
53
+ created_at TIMESTAMP DEFAULT NOW(),
54
+ cliente INTEGER REFERENCES m308_t4136(id),
55
+ cliente__display TEXT, -- Cache del nombre
56
+ fecha DATE NOT NULL,
57
+ total NUMERIC,
58
+ estado TEXT,
59
+ estado__display TEXT -- Cache del label
60
+ );
61
+ ```
62
+
63
+ ## Campos __display
64
+
65
+ Para campos `relation` y `select`, Gufi mantiene un campo `__display` con el valor legible:
66
+
67
+ ```
68
+ cliente = 123 --> cliente__display = "Empresa ABC"
69
+ estado = "pendiente" --> estado__display = "Pendiente"
70
+ ```
71
+
72
+ **Al actualizar, actualiza AMBOS:**
73
+ ```javascript
74
+ gufi_data({
75
+ action: "update",
76
+ table: "ventas.pedidos",
77
+ company_id: "116",
78
+ id: 5,
79
+ data: {
80
+ estado: "enviado",
81
+ estado__display: "Enviado"
82
+ }
83
+ })
84
+ ```
85
+
86
+ ## Tipos de entidades (kind)
87
+
88
+ | Kind | Descripcion | UI |
89
+ |------|-------------|-----|
90
+ | `table` | Tabla normal con CRUD | GenericTable |
91
+ | `dashboard` | Dashboard de KPIs | DashboardView |
92
+ | `config` | Configuracion (1 registro) | FormView |
93
+ | `marketplace_view` | Vista del marketplace | MarketplaceView |
94
+ | `custom` | Vista custom hardcoded | Plugin especifico |
95
+
96
+ ## Operaciones con MCP
97
+
98
+ ### Ver contexto completo de una empresa
99
+
100
+ ```
101
+ gufi_context({ company_id: "116" })
102
+ ```
103
+
104
+ Devuelve: modulos, entidades, campos, relaciones, automations.
105
+
106
+ ### Agregar un campo
107
+
108
+ ```javascript
109
+ gufi_schema_modify({
110
+ company_id: "116",
111
+ operations: [{
112
+ op: "add_field",
113
+ entity: "ventas.pedidos", // modulo.entidad
114
+ field: {
115
+ name: "fecha_entrega",
116
+ type: "date",
117
+ label: "Fecha de Entrega",
118
+ required: false
119
+ }
120
+ }]
121
+ })
122
+ ```
123
+
124
+ ### Modificar un campo
125
+
126
+ ```javascript
127
+ gufi_schema_modify({
128
+ company_id: "116",
129
+ operations: [{
130
+ op: "update_field",
131
+ entity: "ventas.pedidos",
132
+ field_name: "fecha_entrega",
133
+ changes: { required: true, label: "Fecha Entrega (requerida)" }
134
+ }]
135
+ })
136
+ ```
137
+
138
+ ### Crear entidad nueva
139
+
140
+ ```javascript
141
+ gufi_schema_modify({
142
+ company_id: "116",
143
+ operations: [{
144
+ op: "add_entity",
145
+ module: "inventario",
146
+ entity: {
147
+ name: "productos",
148
+ label: "Productos",
149
+ kind: "table",
150
+ fields: [
151
+ { name: "nombre", type: "text", label: "Nombre", required: true },
152
+ { name: "sku", type: "barcode", label: "SKU" },
153
+ { name: "precio", type: "currency", label: "Precio" },
154
+ { name: "stock", type: "number_int", label: "Stock" }
155
+ ]
156
+ }
157
+ }]
158
+ })
159
+ ```
160
+
161
+ ### Crear Custom View (marketplace_view)
162
+
163
+ Las Custom Views son entidades de tipo `marketplace_view`. Se crean igual que cualquier entidad:
164
+
165
+ ```javascript
166
+ gufi_schema_modify({
167
+ company_id: "116",
168
+ operations: [{
169
+ op: "add_entity",
170
+ module: "ventas",
171
+ entity: {
172
+ name: "dashboard_ventas",
173
+ label: "Dashboard de Ventas",
174
+ kind: "marketplace_view" // <-- Esto crea una Custom View
175
+ }
176
+ }]
177
+ })
178
+ ```
179
+
180
+ Despues de crear la vista:
181
+ 1. Usa `gufi_view_pull({ view_id: <id> })` para descargar el template
182
+ 2. Edita los archivos locales
183
+ 3. Usa `gufi_view_push({ view_id: <id> })` para subir cambios
184
+ ```
185
+
186
+ ### Preview (dry run)
187
+
188
+ ```javascript
189
+ gufi_schema_modify({
190
+ company_id: "116",
191
+ preview: true, // No ejecuta, solo muestra que haria
192
+ operations: [...]
193
+ })
194
+ ```
195
+
196
+ ### Multiples operaciones
197
+
198
+ ```javascript
199
+ gufi_schema_modify({
200
+ company_id: "116",
201
+ operations: [
202
+ { op: "add_field", entity: "ventas.pedidos", field: { name: "urgente", type: "boolean", label: "Urgente" } },
203
+ { op: "add_field", entity: "ventas.pedidos", field: { name: "notas", type: "text", label: "Notas" } },
204
+ { op: "update_field", entity: "ventas.pedidos", field_name: "estado", changes: { required: true } }
205
+ ]
206
+ })
207
+ ```
208
+
209
+ ## Relaciones
210
+
211
+ ### relation (FK)
212
+
213
+ ```json
214
+ {
215
+ "name": "cliente",
216
+ "type": "relation",
217
+ "label": "Cliente",
218
+ "relation": {
219
+ "module": "crm",
220
+ "entity": "clientes",
221
+ "displayField": "nombre"
222
+ }
223
+ }
224
+ ```
225
+
226
+ ### reversed_relation (virtual)
227
+
228
+ No crea columna. Muestra registros que apuntan a este:
229
+
230
+ ```json
231
+ {
232
+ "name": "pedidos",
233
+ "type": "reversed_relation",
234
+ "label": "Pedidos",
235
+ "reversedRelation": {
236
+ "module": "ventas",
237
+ "entity": "pedidos",
238
+ "field": "cliente"
239
+ }
240
+ }
241
+ ```
242
+
243
+ ## Permisos por entidad
244
+
245
+ ```json
246
+ {
247
+ "permissions": {
248
+ "admin": "*",
249
+ "manager": ["entity:view", "entity:create", "entity:update", "entity:delete"],
250
+ "sales": ["entity:view", "entity:create"],
251
+ "viewer": ["entity:view"]
252
+ }
253
+ }
254
+ ```
255
+
256
+ **Permisos disponibles:**
257
+ - `entity:view` - Ver registros
258
+ - `entity:create` - Crear registros
259
+ - `entity:update` - Editar registros
260
+ - `entity:delete` - Eliminar registros
261
+ - `entity:import` - Importar desde Excel
262
+ - `entity:export` - Exportar a Excel/PDF
263
+
264
+ ## Migraciones automaticas
265
+
266
+ Cuando modificas un modulo, Gufi automaticamente:
267
+
268
+ 1. **Campos nuevos**: `ALTER TABLE ADD COLUMN`
269
+ 2. **Campos eliminados**: Marca como deprecated (no elimina datos)
270
+ 3. **Tipos cambiados**: Intenta cast, si falla mantiene el anterior
271
+ 4. **Relaciones nuevas**: Crea FK + indice
272
+
273
+ NO se hace automaticamente:
274
+ - Eliminar columnas con datos
275
+ - Cambiar tipos incompatibles
276
+ - Renombrar campos (crea nuevo)
277
+
278
+ ## Iconos disponibles
279
+
280
+ Los iconos son de Lucide React. Comunes:
281
+ - `ShoppingCart`, `Package`, `Users`, `Building`
282
+ - `FileText`, `Calendar`, `Settings`, `BarChart`
283
+ - `Truck`, `MapPin`, `Phone`, `Mail`
284
+
285
+ Ver catalogo completo: https://lucide.dev/icons/
@@ -0,0 +1,357 @@
1
+ # Tipos de Campos y CB Builders
2
+
3
+ Referencia completa de tipos de campo en Gufi. **Fuente de verdad**: `@gufi/column-types`
4
+
5
+ ## IMPORTANTE: Usar CB.* siempre
6
+
7
+ En vistas React, **SIEMPRE** usar `gufi.CB.*` para crear/actualizar datos:
8
+
9
+ ```typescript
10
+ await gufi.dataProvider.create({
11
+ resource: tableId,
12
+ variables: {
13
+ nombre: gufi.CB.text('Mi Producto'),
14
+ precio: gufi.CB.currency(150),
15
+ telefono: gufi.CB.phone('612345678'),
16
+ }
17
+ });
18
+ ```
19
+
20
+ ## Tipos de Campo
21
+
22
+ ### Texto
23
+
24
+ | Tipo | PostgreSQL | Formato | CB Builder |
25
+ |------|------------|---------|------------|
26
+ | `text` | TEXT | `"string"` | `CB.text('value')` |
27
+ | `email` | TEXT[] | `["user@example.com"]` | `CB.email('user@example.com')` |
28
+ | `url` | TEXT | `"https://..."` | `CB.url('https://...')` |
29
+ | `barcode` | TEXT | `"8414533043847"` | `CB.barcode(8414533043847)` |
30
+
31
+ **Notas:**
32
+ - `email` es un ARRAY de textos (TEXT[]), soporta múltiples emails
33
+ - `email` se normaliza a minúsculas automáticamente
34
+ - `barcode` acepta números (se convierte a string)
35
+
36
+ **Email - Formatos válidos:**
37
+ ```javascript
38
+ // Un solo email (se convierte a array)
39
+ CB.email('user@example.com') // → ['user@example.com']
40
+
41
+ // Múltiples emails (separados por coma)
42
+ CB.email('a@x.com, b@x.com') // → ['a@x.com', 'b@x.com']
43
+
44
+ // Array directo
45
+ CB.email(['a@x.com', 'b@x.com']) // → ['a@x.com', 'b@x.com']
46
+ ```
47
+
48
+ ### Numeros
49
+
50
+ | Tipo | PostgreSQL | Formato | CB Builder |
51
+ |------|------------|---------|------------|
52
+ | `number` | NUMERIC | `123.45` | `CB.number(123.45)` |
53
+ | `number_int` | INTEGER | `42` | `CB.int(42)` |
54
+ | `number_float` | DOUBLE | `3.14159` | `CB.float(3.14)` |
55
+ | `percentage` | DOUBLE | `0.75` (= 75%) | `CB.percentage(75)` |
56
+
57
+ **Notas:**
58
+ - `number_int` redondea automaticamente
59
+ - `percentage` se guarda como decimal (0.75), se muestra como 75%
60
+
61
+ ### Fecha/Hora
62
+
63
+ | Tipo | PostgreSQL | Formato | CB Builder |
64
+ |------|------------|---------|------------|
65
+ | `date` | TIMESTAMP | `"2024-01-15T00:00:00.000Z"` | `CB.date()` |
66
+ | `time` | TIME | `"14:30:00"` | `CB.time('14:30')` |
67
+
68
+ **Solo existe `date` (siempre TIMESTAMP):**
69
+ ```javascript
70
+ CB.date() // Hoy medianoche: '2024-01-15T00:00:00.000Z'
71
+ CB.date('2024-06-15') // '2024-06-15T00:00:00.000Z'
72
+ CB.date('2024-06-15T14:30:00') // '2024-06-15T14:30:00.000Z' (preserva hora)
73
+ CB.date(new Date()) // ISO completo del Date object
74
+ ```
75
+
76
+ **Display controlado por `includeTime` en schema:**
77
+ ```json
78
+ { "name": "fecha_nacimiento", "type": "date" }
79
+ { "name": "fecha_entrega", "type": "date", "includeTime": true }
80
+ ```
81
+
82
+ **`created_at`:** Campo sistema automatico, siempre muestra hora.
83
+
84
+ ### Seleccion
85
+
86
+ | Tipo | PostgreSQL | Formato | CB Builder |
87
+ |------|------------|---------|------------|
88
+ | `select` | TEXT | `"value"` | `CB.select('value')` |
89
+ | `multiselect` | TEXT[] | `["a", "b"]` | `CB.multiselect('a', 'b')` |
90
+ | `boolean` | BOOLEAN | `true/false` | `CB.bool(true)` |
91
+
92
+ **Opciones en schema:**
93
+ ```json
94
+ {
95
+ "name": "estado",
96
+ "type": "select",
97
+ "options": [
98
+ { "value": "pendiente", "label": "Pendiente", "color": "yellow" },
99
+ { "value": "activo", "label": "Activo", "color": "green" }
100
+ ]
101
+ }
102
+ ```
103
+
104
+ ### Relaciones
105
+
106
+ | Tipo | PostgreSQL | Formato | CB Builder |
107
+ |------|------------|---------|------------|
108
+ | `relation` | INTEGER | `123` (FK ID) | `CB.relation(123)` |
109
+ | `users` | INTEGER[] | `[16, 23]` | `CB.users(16, 23)` |
110
+ | `reversed_relation` | - | (virtual, read-only) | - |
111
+
112
+ **Notas:**
113
+ - `relation` es una FK a otra tabla
114
+ - `users` es array de IDs de usuarios (para asignar tareas, etc.)
115
+ - `reversed_relation` no tiene columna, es calculado
116
+
117
+ ### Currency (NUMERICO)
118
+
119
+ | Tipo | PostgreSQL | Formato | CB Builder |
120
+ |------|------------|---------|------------|
121
+ | `currency` | NUMERIC | `150.00` | `CB.currency(150)` |
122
+
123
+ **IMPORTANTE:** Currency es un numero simple. El codigo de moneda va en el schema del campo:
124
+
125
+ ```json
126
+ {
127
+ "name": "precio",
128
+ "type": "currency",
129
+ "currency": "EUR"
130
+ }
131
+ ```
132
+
133
+ **NO es JSONB, es NUMERIC:**
134
+ ```javascript
135
+ // CORRECTO
136
+ precio: 150.00
137
+
138
+ // INCORRECTO (formato legacy)
139
+ precio: { currency: 'EUR', amount: 150.00 }
140
+ ```
141
+
142
+ ### Phone (JSONB)
143
+
144
+ | Tipo | PostgreSQL | Formato | CB Builder |
145
+ |------|------------|---------|------------|
146
+ | `phone` | JSONB | `{ prefix, number }` | `CB.phone('612345678')` |
147
+
148
+ **Formato:**
149
+ ```javascript
150
+ {
151
+ prefix: "+34", // Codigo de pais
152
+ number: "612345678" // Numero sin prefijo
153
+ }
154
+ ```
155
+
156
+ **CB.phone detecta pais:**
157
+ ```javascript
158
+ CB.phone('612345678') // { prefix: '+34', number: '612345678' }
159
+ CB.phone('+1 555 123 4567') // { prefix: '+1', number: '5551234567' }
160
+ CB.phone('612345678', '+44') // { prefix: '+44', number: '612345678' }
161
+ ```
162
+
163
+ ### Location (JSONB)
164
+
165
+ | Tipo | PostgreSQL | Formato | CB Builder |
166
+ |------|------------|---------|------------|
167
+ | `location` | JSONB | `{ street, city, ... }` | `CB.location({...})` |
168
+
169
+ **Formato completo:**
170
+ ```javascript
171
+ {
172
+ lat: 40.4168, // Latitud
173
+ lng: -3.7038, // Longitud
174
+ country: "ES", // ISO 3166-1 alpha-2
175
+ city: "Madrid",
176
+ street: "Calle Mayor",
177
+ number: "15",
178
+ postal_code: "28013",
179
+ floor: "3",
180
+ letter: "B",
181
+ notes: "Puerta azul",
182
+ place_id: "...", // Google/OSM ID
183
+ timezone: "Europe/Madrid"
184
+ }
185
+ ```
186
+
187
+ **Minimo requerido:**
188
+ ```javascript
189
+ CB.location({ street: 'Mayor', number: '15', city: 'Madrid' })
190
+ ```
191
+
192
+ ### File (JSONB Array)
193
+
194
+ | Tipo | PostgreSQL | Formato | CB Builder |
195
+ |------|------------|---------|------------|
196
+ | `file` | JSONB | Array de archivos | `CB.file(url, name)` |
197
+
198
+ **Dos formatos validos:**
199
+
200
+ ```javascript
201
+ // Simple (solo path)
202
+ ["company_130/uuid_photo.jpg"]
203
+
204
+ // Completo (con metadata)
205
+ [{
206
+ url: "company_130/abc123_dni.pdf",
207
+ name: "dni_12345.pdf",
208
+ mime: "application/pdf",
209
+ size: 245678
210
+ }]
211
+ ```
212
+
213
+ **IMPORTANTE:** Los archivos deben subirse primero a GCS:
214
+ ```javascript
215
+ // 1. Subir archivo
216
+ const formData = new FormData();
217
+ formData.append('file', file);
218
+ const { url, name } = await fetch('/api/files/upload', { method: 'POST', body: formData }).then(r => r.json());
219
+
220
+ // 2. Guardar referencia
221
+ fotos: CB.file(url, name)
222
+ ```
223
+
224
+ **NO guardar base64 en campos file.**
225
+
226
+ ### Signature (TEXT base64)
227
+
228
+ | Tipo | PostgreSQL | Formato | CB Builder |
229
+ |------|------------|---------|------------|
230
+ | `signature` | TEXT | `"data:image/png;base64,..."` | `CB.signature(dataUrl)` |
231
+
232
+ **Unico tipo que acepta base64 directamente** (firmas son pequenas).
233
+
234
+ ### JSON (JSONB)
235
+
236
+ | Tipo | PostgreSQL | Formato | CB Builder |
237
+ |------|------------|---------|------------|
238
+ | `json` | JSONB | Cualquier objeto | `CB.json({...})` |
239
+
240
+ Para datos estructurados sin tipo especifico.
241
+
242
+ ## Ejemplo completo de create
243
+
244
+ ```typescript
245
+ // 💜 Nombre lógico - dataProvider lo resuelve automáticamente
246
+ const productosTable = 'ventas.productos';
247
+
248
+ await gufi.dataProvider.create({
249
+ resource: productosTable,
250
+ variables: {
251
+ // Texto
252
+ nombre: gufi.CB.text('Mi Producto'),
253
+ email_contacto: gufi.CB.email('contacto@empresa.com'),
254
+ codigo_barras: gufi.CB.barcode('8414533043847'),
255
+
256
+ // Numeros
257
+ cantidad: gufi.CB.int(100),
258
+ peso: gufi.CB.float(2.5),
259
+ descuento: gufi.CB.percentage(15), // 0.15
260
+
261
+ // Currency
262
+ precio: gufi.CB.currency(150), // 150.00
263
+
264
+ // Fecha/Hora (siempre TIMESTAMP)
265
+ fecha_alta: gufi.CB.date(), // Hoy a medianoche UTC
266
+ fecha_evento: gufi.CB.date('2024-06-15'), // Fecha especifica
267
+
268
+ // Seleccion
269
+ estado: gufi.CB.select('activo'),
270
+ categorias: gufi.CB.multiselect('electronica', 'hogar'),
271
+ activo: gufi.CB.bool(true),
272
+
273
+ // Relaciones
274
+ proveedor_id: gufi.CB.relation(45),
275
+ responsables: gufi.CB.users(16, 23),
276
+
277
+ // Complejos
278
+ telefono: gufi.CB.phone('612345678'),
279
+ direccion: gufi.CB.location({
280
+ street: 'Gran Via',
281
+ number: '1',
282
+ city: 'Madrid'
283
+ }),
284
+ }
285
+ });
286
+ ```
287
+
288
+ ## Ejemplo completo de update
289
+
290
+ ```typescript
291
+ // 💜 Nombre lógico
292
+ const productosTable = 'ventas.productos';
293
+
294
+ // IMPORTANTE: Para select/relation, actualizar AMBOS campos
295
+ await gufi.dataProvider.update({
296
+ resource: productosTable,
297
+ id: recordId,
298
+ variables: {
299
+ estado: gufi.CB.select('enviado'),
300
+ estado__display: 'Enviado',
301
+
302
+ cliente_id: gufi.CB.relation(newClientId),
303
+ cliente_id__display: 'Nuevo Cliente SA',
304
+
305
+ precio: gufi.CB.currency(200),
306
+ }
307
+ });
308
+ ```
309
+
310
+ ## Errores comunes
311
+
312
+ ### 1. Currency como objeto
313
+ ```javascript
314
+ // INCORRECTO
315
+ precio: { currency: 'EUR', amount: 150 }
316
+
317
+ // CORRECTO
318
+ precio: gufi.CB.currency(150) // Solo el numero
319
+ ```
320
+
321
+ ### 2. Phone con formato incorrecto
322
+ ```javascript
323
+ // INCORRECTO
324
+ telefono: { country: 'ES', number: '612345678' }
325
+
326
+ // CORRECTO
327
+ telefono: gufi.CB.phone('612345678') // { prefix: '+34', number: '612345678' }
328
+ ```
329
+
330
+ ### 3. Olvidar __display en updates
331
+ ```javascript
332
+ // INCORRECTO (UI no se actualiza)
333
+ estado: 'enviado'
334
+
335
+ // CORRECTO
336
+ estado: 'enviado',
337
+ estado__display: 'Enviado'
338
+ ```
339
+
340
+ ### 4. Base64 en campos file
341
+ ```javascript
342
+ // INCORRECTO
343
+ foto: 'data:image/png;base64,...'
344
+
345
+ // CORRECTO (subir primero)
346
+ const { url, name } = await uploadFile(file);
347
+ foto: gufi.CB.file(url, name)
348
+ ```
349
+
350
+ ### 5. Email como string plano
351
+ ```javascript
352
+ // FUNCIONA pero no recomendado
353
+ email: 'user@example.com'
354
+
355
+ // CORRECTO (usa CB para normalización)
356
+ email: gufi.CB.email('user@example.com') // → ['user@example.com']
357
+ ```