Files
acai-scaffold/docs/builder-fields.md
2026-03-29 12:42:30 +01:00

13 KiB

Builder Fields & Acai Attributes

Nombres de variables

El atributo data-field-label se convierte a variable removiendo espacios y caracteres especiales (minúsculas).

Label Variable
Categoría Noticia categoranoticia
Color Principal colorprincipal
Título Producto ttuloproducto

Field Types (data-field-type)

Type Element Returns
textfield <p> String
headfield <h1>-<h6> String + variable _tag con la etiqueta elegida
textbox <div> String multi-línea
wysiwyg <div class="wysiwyg"> HTML string
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

textfield

<p data-field-type="textfield" data-field-label="Título">
  Elemento editable
</p>

headfield

Genera 2 variables: la estándar y otra con sufijo _tag con la etiqueta elegida por el usuario.

<{{ title_tag | default('h2') }} data-field-type="headfield" data-field-label="Título Sección" class="text-3xl font-bold">
  Título de la sección
</{{ title_tag | default('h2') }}>

textbox

<div data-field-type="textbox" data-field-label="Descripción">
  Texto largo editable
</div>

wysiwyg

<div class="wysiwyg" data-field-type="wysiwyg" data-field-label="Contenido Enriquecido">
  <p>Texto con <strong>estilos</strong> editables</p>
</div>
<a data-field-type="link" data-field-label="Enlace Principal" href="#">
  Haz clic aquí
</a>

upload

<div class="p-1/6 relative">
  <img
    class="absolute top-0 left-0 w-full h-full object-cover object-center 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="titulo": Campo de información adicional (usado como alt)

Acceso en Twig: {{ imagen[0].urlPath }}, {{ imagen[0].info1 }}

uploadMulti

Itera sobre todas las imágenes subidas:

<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 de opciones: opcion1,opcion2,|opcion3,valor3|opcion4

list (tabla)

<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 (generalmente num)
  • data-list-label: Campo a mostrar como label

multiv2 — Campos repetibles

<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 — las variables son propiedades del objeto iterado:

{% for record in productos %}
  <div class="producto">
    <h3>{{ record.nombre }}</h3>
    <p>{{ record.descripcion }}</p>
    <img src="{{ record.imagen[0].urlPath }}" alt="">
  </div>
{% endfor %}

Acai Attributes

c-if — Renderizado condicional

<!-- Verificar existencia de variable -->
<div c-if="subtitle">{{ subtitle }}</div>

<!-- Comparación de valores (usa = no ==) -->
<div c-if="layout = 'grid'">Grid layout</div>

c-else

Debe ir 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 (condición SQL), c-order (orden), c-limit (límite).

Equivalente en Twig:

{% for producto in 'productos' | get('visible=1','num desc',10) %}
  <li>{{ producto.title }}</li>
{% endfor %}

Dentro 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 expresiones Twig (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 — Elementos ocultos

Elemento que no se renderiza pero puede declarar variables builder:

<div c-hidden="true">
  <input data-field-type="textfield" data-field-label="Config" value="default" />
</div>

c-required — Campos requeridos condicionales

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

Definiendo variables con <set>

<!-- 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 %}

Incluyendo módulos

Para incluir un módulo dentro de otro módulo o sección general, usa el ID del módulo como etiqueta HTML:

<module_id :param1="value1" :param2="value2"></module_id>

Ejemplo:

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

Manejo automático de 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"
>
  <div class="mb-4">
    <label class="block mb-2">Nombre</label>
    <input name="nombre" type="text" class="w-full p-2 border rounded" required>
  </div>
  <div class="mb-4">
    <label class="block mb-2">Email</label>
    <input name="email" type="text" class="w-full p-2 border rounded" required>
  </div>
  <div class="mb-4">
    <label class="block mb-2">Mensaje</label>
    <textarea name="mensaje" class="w-full p-2 border rounded" rows="5" required></textarea>
  </div>
  <div class="mb-4">
    <label class="flex items-center">
      <input name="acepto_politica" type="checkbox" class="mr-2" required>
      <span>Acepto la política de privacidad</span>
    </label>
  </div>
  <button type="submit" class="bg-teal-500 text-white px-6 py-2 rounded hover:bg-teal-600">Enviar</button>
  <captcha/>
</c-form>

Atributos de c-form

Atributo Descripción
tableName="'table'" Tabla donde almacenar registros
mailRecord="['correos', 'ID']" Template de email de la tabla correos
sendTo="'email@domain.com'" Destinatarios (separados por coma)
sendToClient="'campo_email'" Campo con email del cliente para auto-reply
captcha="true" Google reCAPTCHA
honeypot="true" Campo oculto anti-spam
messageOK="'texto'" Mensaje de éxito
messageKO="'texto'" Mensaje de error
redirect="'/path/'" Redirección tras envío exitoso
attachFiles="true" Adjuntar archivos al email
showImages="true" Mostrar thumbnails en email
emailMode="'twig'" Email en formato Twig
header="'<div>...</div>'" HTML cabecera del email
footer="'<div>...</div>'" HTML footer del email
styles="'body { ... }'" CSS para el email

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

Animate On Scroll (AOS)

<div data-aos="fade-up" data-aos-delay="200">
  Animated content
</div>

Lazy Loading

<img class="lazyload" data-src="{{ image[0].urlPath }}" />
<!-- o -->
<img data-lazy="true" src="{{ image[0].urlPath }}" />

Puntos importantes

  1. Nombres de variables: data-field-label → sin espacios ni caracteres especiales, minúsculas
  2. Variables en multiv2: Son propiedades del objeto iterado (record.nombre)
  3. Campos upload: Retornan arrays, no strings (imagen[0].urlPath, no imagen)
  4. c-if usa = no ==: c-if="layout = 'grid'" (un solo igual)
  5. c-for tabla: El nombre de tabla va sin prefijo cms_
  6. Enlace: Ya incluye barras, no añadir extras
  7. Checkbox: Valores 1 o 0, no true/false

MCP Tools: Config Vars e Imágenes de Módulos

Regla importante: Siempre rellenar variables al añadir un módulo

Cuando se añade un módulo a una página (con add_module_to_record), este queda vacío y no muestra nada visible. SIEMPRE hay que llamar a set_module_config_vars inmediatamente después para rellenar las variables con contenido de ejemplo coherente con el contexto del sitio. Incluir:

  • Textos (títulos, descripciones, pretítulos) con contenido relevante al sitio
  • Valores de listas/selects con una opción válida
  • Para variables multi (records), crear al menos 2-3 items de ejemplo
  • Para variables de imagen (upload), usar generate_image o upload_record_image para que el módulo se vea completo

Un módulo sin variables configuradas es invisible en la web.

Leer variables de un módulo

Antes de modificar cualquier módulo, usar get_module_config_vars para conocer el estado actual:

  • tableName: tabla del registro padre (ej: apartados), SIN prefijo cms_
  • recordNum: campo num del registro padre (ej: 2)
  • sectionId: el section_id de la instancia del módulo (ej: 6c6d8)

Escribir variables de un módulo

Usar set_module_config_vars con los mismos tableName, recordNum y sectionId. Pasar todos los valores como strings.

La respuesta incluye configVars con el recordNum del registro builder_custom creado/actualizado y uploadFields para imágenes.

Tipos de almacenamiento (manejado automáticamente):

  • headfield, textfield, link, textbox, wysiwyg, upload → se guardan en tabla builder_custom
  • list, checkbox, colorpicker → se guardan directamente en el JSON config-vars (no en builder_custom)

No necesitas preocuparte por esto — set_module_config_vars lo maneja internamente. Solo pasa los valores como strings.

Subir imágenes a un módulo

El nombre del campo de imagen viene de builder.jsonvars.NOMBRE.relations.builder_custom (ej: "image1"). NO es el nombre de la variable (ej: NO "imagenes").

Flujo correcto:

  1. get_module_config_vars → obtener el recordNum en builder_custom de la variable de imagen
  2. upload_record_image con:
    • tableName: "builder_custom" (siempre, sin prefijo cms_)
    • recordId: el recordNum del paso 1 (ej: "778")
    • fieldName: el campo de relations del builder.json (ej: "image1")
    • imageUrl: URL completa accesible desde Docker
  3. reorder_record_uploads si es necesario — pasar array de upload IDs en el orden deseado
  4. list_record_uploads para verificar

Errores comunes a evitar:

  • NO usar el sectionId como recordId — usar el num de builder_custom
  • NO usar el nombre de la variable como fieldName — usar el campo de relations del builder.json (ej: image1, no imagenes)
  • NO poner prefijo cms_ en tableName