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,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
|
+
```
|