Files
agenticSystem/docs/01-builder-fields.md
Jordan Diaz 4835ff9467 docs: checkbox fuera de los tipos del builder y colors a las listas
El commit anterior (49b52b0) anadio un aviso de que `checkbox` no existe como
data-field-type, pero lo dejo listado como tipo valido en cuatro sitios: la
tabla de tipos de 01-builder-fields, su propio frontmatter `summary`, la tabla
de la cheat-sheet 11b y la lista del glosario. El agente consulta esas tablas
antes que el cuerpo del documento, asi que la contradiccion seguia viva:
escribe `checkbox`, el parser lo deja con type indefinido y funciones.php
descarta la variable en silencio. Era el origen del bug de tipos fantasma.

- `checkbox` fuera de las cuatro listas de data-field-type. El aviso se
  reescribe para remitir a `list` de dos opciones.
- OJO: `checkbox` SI existe como tipo de campo de TABLA del CMS
  (server/handlers/schema.py:98 y :284). Se conservan intactas sus menciones en
  05-tables-and-fields, 04-pages-and-records y la tabla de formato de datos de
  11b:88, que son otro vocabulario. El aviso lo dice explicitamente para que no
  se vuelva a borrar por error.
- `colors` entra en la tabla de 01, en la de 11b y en el glosario: tenia
  seccion propia pero no aparecia en ninguna lista, asi que quien consultaba la
  chuleta no sabia que existia. `colorpicker` se anade tambien al parrafo de
  intro de 01, que lo omitia.
- `corners` y `ratio` se dejan fuera de las tablas a proposito (decision del
  usuario: por ahora no se promocionan).
2026-08-13 20:05:21 +00:00

545 lines
18 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
---
title: "Campos editables del builder"
tags: [builder, twig, html, modules]
load_priority: 80
load_when: [always]
summary: "Atributos data-field-* (textfield, headfield, link, upload, list, multiv2, colors, colorpicker), agrupar campos en pestañas con data-field-group, c-if/c-for/c-class, c-form, componentes built-in del builder Acai."
---
# Builder Fields — Campos editables del index-base.tpl
Este documento define los campos editables que el usuario rellena desde el panel del builder de Acai. Cubre el atributo `data-field-type` con todos sus tipos (`textfield`, `headfield`, `textbox`, `wysiwyg`, `link`, `upload`, `uploadMulti`, `list`, `multiv2`, `colors`, `colorpicker`, `corners`, `ratio`), la regla `data-field-label` → nombre de variable, los atributos Acai (`c-if`, `c-else`, `c-for`, `c-class`, `c-hidden`, `c-required`), el tag `<set>`, la inclusión de módulos, los formularios `c-form` y los componentes built-in. Léelo antes de crear o modificar cualquier `index-base.tpl`.
## Reglas de nomenclatura de variables
El atributo `data-field-label` se convierte automáticamente en el nombre de variable Twig: se ponen minúsculas y se eliminan espacios y caracteres especiales.
| Label | Variable resultante |
|-------|---------------------|
| `Categoría Noticia` | `categoranoticia` |
| `Color Principal` | `colorprincipal` |
| `Título Producto` | `ttuloproducto` |
Reglas obligatorias:
- Todo elemento con `data-field-type` DEBE incluir también `data-field-label`.
- Sin `data-field-label`, el builder genera variables temporales o incorrectas y el módulo queda mal configurado.
- Usa labels descriptivos y estables; no dejes labels vacíos ni genéricos como "Campo" o "Texto".
- En `index-base.tpl` evita clases Tailwind con valores arbitrarios (`text-[44px]`, `font-['Cinzel']`, `leading-[1.1]`) — pueden romper el parseo. Muévelas a `style.css`.
## Tipos de campo (`data-field-type`)
| Tipo | Elemento HTML | Devuelve |
|------|---------------|----------|
| `textfield` | `<p>` | String |
| `headfield` | `<h1>``<h6>` | String + variable extra `_tag` con la etiqueta elegida |
| `textbox` | `<div>` | String multilínea |
| `wysiwyg` | `<div class="wysiwyg">` | String HTML |
| `link` | `<a>` | URL string (ya incluye barras) |
| `upload` | `<img>` | **Array** de `{urlPath, info1, info2, info3, info4}` |
| `uploadMulti` | `<li>` | Itera sobre archivos subidos |
| `list` (fijo) | `<div data-list-options="...">` | Valor seleccionado |
| `list` (tabla) | `<div data-list-table="...">` | `num` del registro |
| `multiv2` | `<li>` wrapper | Array de objetos repetibles |
| `colors` | `<div data-field-colors="fondo,titulo">` | Objeto de N colores por nombre: `{{ colores.fondo }}` |
| `colorpicker` | `<div>` | Hex color string |
### textfield
```html
<p data-field-type="textfield" data-field-label="Título">
Elemento editable
</p>
```
### headfield
Genera 2 variables: la estándar y `_tag` con la etiqueta elegida (h1…h6).
```html
<p data-field-type="headfield" data-field-label="Titulo" >
Título de la sección
</p>
```
### textbox
```html
<div data-field-type="textbox" data-field-label="Descripción">
Texto largo editable
</div>
```
### wysiwyg
Editor de texto enriquecido. Acceder con `| raw` para no escapar el HTML.
```html
<div class="wysiwyg" data-field-type="wysiwyg" data-field-label="Contenido Enriquecido">
<p>Texto con <strong>estilos</strong> editables</p>
</div>
```
### link
El campo `enlace` de Acai ya incluye las barras necesarias — nunca añadas barras extra.
Genera 2 variables: la estándar y `_anchor` con el anchor del enlace.
```html
<a data-field-type="link" data-field-label="Enlace">
Haz clic aquí
</a>
```
### upload
Devuelve un array. Acceso en Twig: `{{ imagen[0].urlPath }}`.
```html
<div class="p-1/6 relative">
<img class="absolute top-0 left-0 w-full h-full object-cover lazyload"
data-field-type="upload"
data-field-label="Imagen Principal"
data-lazy="true"
data-field-info1="titulo"
data-field-width="1400"
alt="">
</div>
```
Atributos disponibles:
- `data-lazy="true"` — carga perezosa
- `data-field-width="1400"` — ancho máximo sugerido
- `data-field-info1``data-field-info5` — labels de los campos de información por imagen
Los `data-field-infoN` (hasta 5) definen los labels que el builder muestra como campos editables **en cada imagen subida** (van a `infoLabels` en el `builder.json`). Sus valores se leen luego como `info1``info4` dentro del array del var.
```html
<img data-field-type="upload"
data-field-label="Imagen Principal"
data-field-info1="Texto alternativo"
data-field-info2="Pie de foto"
data-field-width="1400"
alt="">
```
```twig
<img src="{{ imagenprincipal[0].urlPath }}" alt="{{ imagenprincipal[0].info1 }}">
<figcaption>{{ imagenprincipal[0].info2 }}</figcaption>
```
Los `data-field-infoN` funcionan igual en `uploadMulti`.
### uploadMulti
Itera sobre todas las imágenes subidas. Variable iteradora: `uploadMulti`.
```html
<li data-field-type="uploadMulti" data-field-label="Galería" data-field-info1="titulo">
<div class="relative min-h-screen">
<img class="absolute top-0 left-0 w-full h-full object-cover lazyload"
data-src="{{ uploadMulti.urlPath | imagec(2100) }}"
alt="{{ uploadMulti.info1 }}">
</div>
</li>
```
### list (opciones fijas)
```html
<div data-field-type="list"
data-field-label="Color Producto"
data-list-options="Rojo,Azul,|Verde,3|Amarillo">
</div>
```
Formato `data-list-options`:
- `opcion1,opcion2` → la opción es etiqueta y valor a la vez
- `|valor3,etiqueta3` → separa valor de etiqueta con `|`
### list (tabla)
Selecciona un registro de otra tabla. Devuelve el `num`.
```html
<div data-field-type="list"
data-field-label="Noticia Destacada"
data-list-table="noticias"
data-list-value="num"
data-list-label="titulo">
{{ record.titulo }}
</div>
```
- `data-list-table` — nombre de tabla **sin prefijo `cms_`**
- `data-list-value` — campo a usar como valor (normalmente `num`)
- `data-list-label` — campo a mostrar como label
### multiv2 — Campos repetibles
Crea grupos de campos repetibles. La variable resultante es un array de objetos.
```html
<ul>
<li data-field-type="multiv2" data-field-label="Productos">
<div data-field-type="textfield" data-field-label="Nombre">
Nombre del producto
</div>
<div data-field-type="textbox" data-field-label="Descripción">
Descripción del producto
</div>
<div class="p-1/6 relative">
<img class="absolute top-0 left-0 w-full h-full object-cover lazyload"
data-field-type="upload"
data-field-label="Imagen"
data-lazy="true"
data-field-width="800"
alt="">
</div>
</li>
</ul>
```
Uso en Twig:
```twig
{% for record in productos %}
<div class="producto">
<h3>{{ record.nombre }}</h3>
<p>{{ record.descripcion }}</p>
<img src="{{ record.imagen[0].urlPath }}" alt="">
</div>
{% endfor %}
```
### colors — paleta de colores
Un solo campo que agrupa VARIOS colores. Los nombres de cada color se declaran en
`data-field-colors`, separados por comas:
```html
<div c-hidden="true">
<div data-field-type="colors"
data-field-label="Colores"
data-field-colors="fondo,titulo,boton"></div>
</div>
```
El valor guardado es un JSON con pares nombre/valor. Cada color puede ser sólido o un
gradiente lineal:
```json
{"fondo": "#ffffff", "titulo": "#111111", "boton": "linear-gradient(90deg, #aaa, #000)"}
```
En Twig se accede a cada color por su nombre: `{{ colores.fondo }}`.
### colorpicker — un solo color
Cuando solo necesitas UN color, no una paleta. El valor es un string hexadecimal plano:
```html
<div c-hidden="true">
<div data-field-type="colorpicker" data-field-label="Color de fondo"></div>
</div>
```
Valor guardado: `#ff0000`. En Twig: `{{ colordefondo }}`.
Usa `colorpicker` para un color suelto y `colors` cuando el módulo tenga varios: `colors`
gasta UNA sola variable para N colores, mientras que N `colorpicker` gastan N.
### corners — radios de esquina
Selector de radio de borde en escala Tailwind. Mismo patrón que `colors`.
### ratio — proporción
Selector de proporción de imagen (16/9, 4/3, 1/1...).
> Para un valor booleano usa `list` con dos opciones: el builder no tiene un tipo de casilla.
> (No lo confundas con el tipo `checkbox` de los campos de TABLA del CMS, que sí existe y se
> documenta en `05-tables-and-fields.md`. Son dos vocabularios distintos.)
## Agrupar campos en pestañas (`data-field-group`)
Los campos de un módulo se reparten en pestañas dentro del panel de configuración añadiendo `data-field-group="Nombre del grupo"` al mismo elemento que ya lleva `data-field-type`.
Mecanismo: cualquier atributo `data-field-*` extra —aparte de `data-field-type`, `data-field-label` y `data-field-value`— se recoge en el objeto `customDataField` del var, recortando el prefijo `data-field-`. Es decir, `data-field-group="Estilos"` acaba como `customDataField: { group: "Estilos" }` en el `builder.json`.
Comportamiento del panel:
- Los vars se agrupan por `customDataField.group` y se muestran en pestañas laterales.
- Los vars sin `data-field-group` caen en la pestaña **Principal**.
- El orden de las pestañas es el de primera aparición de cada grupo en el template.
- Con un solo grupo no se muestran pestañas.
- Funciona igual en vars de primer nivel y dentro de `multiv2`.
```html
<div data-field-type="textfield" data-field-label="Título" data-field-group="Contenido">
Título de la sección
</div>
<div data-field-type="textbox" data-field-label="Descripción" data-field-group="Contenido">
Texto de apoyo
</div>
<div c-hidden="true">
<div data-field-type="colorpicker" data-field-label="Color de fondo" data-field-group="Estilos"></div>
<div data-field-type="list"
data-field-label="Alineación"
data-list-options="|Izquierda,1|Centro,2|Derecha"
data-field-group="Estilos"></div>
</div>
```
Recomendación de uso:
- Agrupa cuando el módulo pase de ~6-8 vars; por debajo, una sola lista se lee mejor.
- Reparto habitual: **Contenido** (textos e imágenes), **Estilos** (colores, alineación, bordes), **Ajustes** (opciones de comportamiento, límites, enlaces).
- Mantén los nombres de grupo consistentes entre módulos y en español. Son literales: `Estilos` y `estilos` generan dos pestañas distintas.
## Atributos Acai
### `c-if` — Renderizado condicional
Usa `=` (un solo igual) para comparaciones, no `==`.
```html
<div c-if="subtitle">{{ subtitle }}</div>
<div c-if="layout = 'grid'">Grid layout</div>
```
### `c-else`
Va inmediatamente después del elemento `c-if`.
```html
<div c-if="image">
<img src="{{ image[0].urlPath }}">
</div>
<div c-else>
<p>No image available</p>
</div>
```
### `c-for` — Iteración sobre array
```html
<div c-for="item in record.features">
<h3>{{ item.title }}</h3>
</div>
```
### `c-for` — Iteración sobre tabla de BD
```html
<ul>
<li c-for="producto in productos"
c-where="'visible=1'"
c-order="'num desc'"
c-limit="10">
{{ producto.title }}
</li>
</ul>
```
Parámetros opcionales: `c-where` (string SQL), `c-order` (string de orden), `c-limit` (entero).
Equivalente Twig:
```twig
{% for producto in 'productos' | get('visible=1','num desc',10) %}
<li>{{ producto.title }}</li>
{% endfor %}
```
Variables del loop: `loop.index` (1-based), `loop.index is odd`, `loop.index is even`.
### `c-class` — Clases CSS condicionales
```html
<!-- Simple -->
<div c-class="{ 'text-center': alineacion == '1', 'text-right': alineacion == '2' }">
<!-- Múltiples condiciones -->
<div c-class="{
'flex-row-reverse': orden == '1',
'cursor-pointer click-a-child': record.enlace_anchor,
'rounded-xl': radioborde == '4'
}">
<!-- Con loop -->
<div c-class="{
'md:order-1': loop.index is odd,
'md:pl-6': loop.index is even
}">
<!-- Combinado con clases estáticas -->
<div class="flex items-center" c-class="{ 'justify-center': centrado }">
```
### `c-hidden` — Variables ocultas
Elemento que NO se renderiza pero SÍ declara variables builder. Patrón típico para colores y opciones de configuración.
```html
<div c-hidden="true">
<input data-field-type="textfield" data-field-label="Color de fondo" value="">
<div data-field-type="list"
data-field-label="Color titulo resaltado"
data-list-options="|Main color,1|Main color light,2|Main color dark"></div>
</div>
```
### `c-required` — Validación condicional
```html
<input type="text" name="telefono"
c-required="'2' not in camposquitar"
placeholder="Teléfono">
```
## Tag `<set>` — Definir variables
```html
<!-- Obtener configuración de la BD -->
<set :tienda="'configuracion_tienda' | get('num != 0')[0]"></set>
<!-- Construir URLs dinámicas -->
<set :logo="tienda.logo.0.urlPath
? 'https://' ~ server.HTTP_HOST ~ tienda.logo.0.urlPath
: 'https://' ~ server.HTTP_HOST ~ '/template/estandar/images/logo.png'">
</set>
<!-- Twig set para expresiones complejas -->
{% set gracias = 'apartados' | get('num = 20').0 %}
```
## Incluir módulos
Para incluir un módulo dentro de otro módulo o dentro de una sección general, usa el `moduleId` como etiqueta HTML:
```html
<module_id :param1="value1" :param2="'string value'"></module_id>
```
Ejemplos:
```html
<header_menu :showLogo="true" :menuItems="items"></header_menu>
<product_card :product="selectedProduct" :showPrice="true"></product_card>
```
El módulo hijo recibe los parámetros como variables en su contexto.
## Formularios — `c-form`
Maneja automáticamente validación, almacenamiento en BD y envío de emails.
```html
<c-form
class="max-w-2xl mx-auto p-6 bg-white rounded-lg shadow"
tableName="'solicitudes'"
mailRecord="['correos', 'CONTACTO']"
sendTo="'contacto@empresa.com'"
sendToClient="'email'"
captcha="true"
honeypot="true"
messageOK="'¡Gracias! Te contactaremos pronto'"
messageKO="'Por favor, completa todos los campos'"
redirect="'/gracias'"
attachFiles="true">
<input name="nombre" type="text" required class="w-full p-2 border rounded">
<input name="email" type="email" required class="w-full p-2 border rounded">
<textarea name="mensaje" required class="w-full p-2 border rounded" rows="5"></textarea>
<label class="flex items-center">
<input name="acepto_politica" type="checkbox" class="mr-2" required>
<span>Acepto la política de privacidad</span>
</label>
<button type="submit" class="bg-teal-500 text-white px-6 py-2 rounded">Enviar</button>
<captcha/>
</c-form>
```
### Atributos `c-form`
| Atributo | Descripción |
|----------|-------------|
| `tableName="'tabla'"` | Tabla destino (sin `cms_`) |
| `mailRecord="['correos', 'ID']"` | Template de email en tabla `correos` |
| `sendTo="'email@dominio.com'"` | Destinatarios (separados por coma) |
| `sendToClient="'campo_email'"` | Campo del formulario con email del cliente para auto-reply |
| `captcha="true"` | Activa Google reCAPTCHA |
| `honeypot="true"` | Campo oculto anti-spam |
| `messageOK="'texto'"` | Mensaje al enviar correctamente |
| `messageKO="'texto'"` | Mensaje al fallar validación |
| `redirect="'/ruta/'"` | Redirección tras envío correcto |
| `attachFiles="true"` | Adjuntar archivos al email |
| `showImages="true"` | Mostrar thumbnails en email |
| `emailMode="'twig'"` | Email en formato Twig |
| `header="'<div>...'"` | HTML cabecera del email |
| `footer="'<div>...'"` | HTML footer del email |
| `styles="'body { ... }'"` | CSS del email |
Para formularios estándar (contacto, postulación), prefiere `c-form` antes que crear lógica custom de POST/hook. Solo crea una tabla propia si necesitas gestionar esos registros desde el admin.
## Componentes built-in
### Carousel — `c-tns-wrapper`
```html
<div class="c-tns-wrapper"
data-responsive='{"0":1,"768":2,"1024":3}'
data-speed="400"
data-nav="true"
data-autoplay-timeout="3000">
<div c-for="slide in record.slides">
<img src="{{ slide.image[0].urlPath }}">
</div>
</div>
```
### Lightbox
```html
<a href="{{ image[0].urlPath }}" class="glightbox" data-gallery="gallery1">
<img src="{{ image[0].urlPath | imagec(400) }}">
</a>
```
### Breadcrumb
```html
<breadCrumb/>
<breadCrumb class="bg-gray-200 p-3 rounded" c-prevlinks="null"></breadCrumb>
```
### Animate On Scroll (AOS)
```html
<div data-aos="fade-up" data-aos-delay="200" data-aos-duration="800">
Contenido animado
</div>
```
Valores comunes: `fade-up`, `fade-down`, `fade-left`, `fade-right`, `zoom-in`, `zoom-in-up`, `fade-up-right`, `fade-up-left`. Tras cambios dinámicos en JS: `AOS.refresh()`.
### Lazy loading
```html
<img class="lazyload" data-src="{{ image[0].urlPath }}">
<!-- O en builder field: -->
<img data-field-type="upload" data-field-label="Imagen" data-lazy="true">
```
## Reglas críticas
1. Todo `data-field-type` exige `data-field-label`.
2. `data-field-label` se transforma a variable: minúsculas, sin espacios ni caracteres especiales.
3. Campos `upload` retornan **arrays** — usa `imagen[0].urlPath`, nunca `imagen`.
4. Variables dentro de `multiv2` son propiedades del objeto iterado (`record.nombre`).
5. `c-if` usa `=` (un igual). `{% if %}` usa `==` (doble igual).
6. `c-for` con tabla: nombre **sin prefijo `cms_`**.
7. `enlace` ya incluye las barras — no añadas slashes extra.
8. Checkbox guarda `1` o `0` (número), nunca `true`/`false`.
9. Evita Tailwind arbitrary-value en `index-base.tpl` — muévelos a `style.css`.
10. `script.js` y `style.css` son estáticos: NO uses sintaxis Twig dentro. Pasa valores dinámicos vía `data-*`.
11. Si el módulo lista registros de una tabla del CMS (`c-for` sobre ella, o su `hook.php` la consulta), declara esa tabla con `update_module_metadata({ cmsTables: [...] })`. No es un `data-field-*`: no se deduce del HTML, hay que declararla. Ver `03-modules-and-sections.md`.