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

18 KiB
Raw Blame History

title, tags, load_priority, load_when, summary
title tags load_priority load_when summary
Campos editables del builder
builder
twig
html
modules
80
always
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

<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).

<p data-field-type="headfield" data-field-label="Titulo" >
  Título de la sección
</p>

textbox

<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.

<div class="wysiwyg" data-field-type="wysiwyg" data-field-label="Contenido Enriquecido">
  <p>Texto con <strong>estilos</strong> editables</p>
</div>

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.

<a data-field-type="link" data-field-label="Enlace">
  Haz clic aquí
</a>

upload

Devuelve un array. Acceso en Twig: {{ imagen[0].urlPath }}.

<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-info1data-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 info1info4 dentro del array del var.

<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="">
<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.

<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)

<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.

<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.

<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:

{% 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:

<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:

{"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:

<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.
<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 ==.

<div c-if="subtitle">{{ subtitle }}</div>
<div c-if="layout = 'grid'">Grid layout</div>

c-else

Va inmediatamente después del elemento c-if.

<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

<div c-for="item in record.features">
  <h3>{{ item.title }}</h3>
</div>

c-for — Iteración sobre tabla de BD

<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:

{% 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

<!-- 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.

<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

<input type="text" name="telefono"
       c-required="'2' not in camposquitar"
       placeholder="Teléfono">

Tag <set> — Definir variables

<!-- 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:

<module_id :param1="value1" :param2="'string value'"></module_id>

Ejemplos:

<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.

<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

<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

<a href="{{ image[0].urlPath }}" class="glightbox" data-gallery="gallery1">
  <img src="{{ image[0].urlPath | imagec(400) }}">
</a>

Breadcrumb

<breadCrumb/>
<breadCrumb class="bg-gray-200 p-3 rounded" c-prevlinks="null"></breadCrumb>

Animate On Scroll (AOS)

<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

<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.