Files
agenticSystem/docs/01-builder-fields.md
Jordan Diaz bdee7665ec feat: el agente puede declarar y leer las cmsTables de un modulo
Las cmsTables (tablas del CMS cuyo contenido MUESTRA un modulo) ya se
definen desde Forge y desde el CMS legacy; faltaba que el agente las
entendiera.

- update_module_metadata acepta cmsTables. El endpoint del server ya la
  validaba, asi que solo habia que declararla en el schema Zod.
- get_module_config_vars la devuelve: sale gratis porque el handler ya
  resolvia el schema del modulo para uploadFields/varsMeta.
- Docs (01, 03, 09) con el matiz que mas se puede confundir: `tables` es
  donde viven los VALORES de las vars (siempre builder_custom, lo pone el
  compilador) y `cmsTables` es que contenido MUESTRA el modulo.

Para el agente lo util no es solo escribirlas: al recibirlas sabe donde
esta de verdad el contenido visible. Si le piden cambiar lo que muestra un
listado de noticias, los registros estan en esa tabla, no en las vars del
modulo.

Se documenta ademas que la metadata del builder.json es la UNICA parte
editable del fichero (y solo con esta tool): el resto lo regenera el
compilador en cada compilacion.
2026-08-12 11:32:07 +00:00

16 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, checkbox), 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, checkbox, colorpicker), 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
checkbox <div> o <input> 1 o 0 (número)
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 %}

checkbox

Devuelve 1 o 0 (número), nunca true/false.

colorpicker

Devuelve un string hexadecimal (#ff0000). Almacenado en config-vars (no en builder_custom).

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.