9.7 KiB
Módulos y Secciones Generales
Este documento explica el sistema modular de Acai: la diferencia entre módulos (componentes visuales reutilizables que el usuario coloca en páginas Builder) y secciones generales (plantillas ligadas a una tabla que se renderizan automáticamente al acceder al enlace de un registro). Cubre la estructura de archivos de un módulo, las reglas obligatorias sobre index-base.tpl, las variables globales (section_id, interno, server.HTTP_HOST, loop), la convención custom-{tableName} para detalles de registro, la inclusión de un módulo dentro de otro y el uso de thisrecord en secciones generales. Léelo antes de crear, mover o editar cualquier carpeta dentro de template/estandar/modulos/.
Módulos
Componentes visuales reutilizables. Viven en template/estandar/modulos/<module-id>/. El usuario los arrastra al builder de una página y rellena sus variables.
Estructura de archivos
<module-id>/
├── index-base.tpl # Plantilla source (Twig + atributos Acai) — EDITA ESTE
├── index.tpl # Compilado (auto-generado) — NO TOCAR
├── index-twig.tpl # Compilado Twig (auto-generado) — NO TOCAR
├── builder.json # Variables del builder (auto-generado) — NO TOCAR
├── style.css # CSS del módulo (estático)
├── script.js # JS del módulo (estático)
└── hook.php # Hook PHP propio del módulo (opcional)
Reglas duras:
- Solo se edita
index-base.tpl.index.tpl,index-twig.tplybuilder.jsonlos genera el compilador y se sobrescriben automáticamente. - Editar
index-base.tplconacai-writeoacai-line-replacedispara la compilación automática. script.jsystyle.cssson estáticos — NO uses sintaxis Twig dentro. Pasa valores dinámicos vía atributosdata-*.index-base.tplsolo contiene HTML/Twig. Nunca embebas etiquetas<script>con lógica del módulo, nunca PHP.
Sintaxis del template
Híbrido Twig + atributos Acai. Ver 01-builder-fields.md para el catálogo completo de campos editables (data-field-type) y atributos (c-if, c-for, etc.).
<section class="hero-section" id="{{ section_id }}">
<div class="container mx-auto px-4">
<h2 data-field-type="headfield" data-field-label="Titulo" class="text-3xl font-bold">
Title here
</h2>
<p data-field-type="textbox" data-field-label="Descripcion" class="text-lg text-gray-600">
Description text
</p>
<img data-field-type="upload" data-field-label="Imagen" src="placeholder.jpg" class="w-full rounded-lg">
<a data-field-type="link" data-field-label="Boton" href="#" class="btn">Call to action</a>
</div>
</section>
Incluir un módulo dentro de otro
<module_id :param1="value1" :param2="'string value'"></module_id>
El módulo hijo recibe los parámetros como variables en su contexto. Ejemplos típicos: incluir un formulario dentro del detalle de un registro, anidar un bloqueproducto dentro de un listado.
<bloqueproducto_i7aunn :producto="selectedProduct" :showPrice="true"></bloqueproducto_i7aunn>
<form_postular :vacante_num="thisrecord.num"></form_postular>
Variables globales
| Variable | Descripción |
|---|---|
section_id |
ID único por instancia del módulo. Úsalo para anchors, IDs HTML, scoping de JS |
interno |
true cuando se renderiza en el editor del CMS, false en el sitio público |
server.HTTP_HOST |
Dominio actual (sin protocolo) |
loop.index |
Índice 1-based dentro de un c-for o {% for %} |
loop.index is odd / is even |
Para layouts alternados (zigzag) |
thisrecord |
El registro actual (solo en secciones generales / detalle) |
Patrón canónico para section_id:
<div id="{{section_id}}"></div>
<section id="id_{{ section_id }}" class="relative">
<!-- contenido -->
</section>
Secciones Generales
Plantillas ligadas a una tabla. Cuando el usuario navega al enlace de un registro, Acai busca la sección general correspondiente y la renderiza pasándole el registro como thisrecord.
Se construyen como módulos especiales dentro de template/estandar/modulos/. La diferencia clave: NO se colocan vía drag-and-drop; el CMS las enlaza por convención de nombre.
Convención custom-{tableName} (REGLA DURA)
Toda tabla con campo enlace tiene automáticamente una sección general en:
template/estandar/modulos/custom-{tableName}/
El nombre es literalmente custom- seguido del tableName. Cuando el cliente accede a la URL de cualquier registro de esa tabla, Acai renderiza esa sección pasándole el registro como thisrecord.
Ejemplos:
- Tabla
vacantes→template/estandar/modulos/custom-vacantes/index-base.tpl - Tabla
productos→template/estandar/modulos/custom-productos/index-base.tpl - Tabla
noticias→template/estandar/modulos/custom-noticias/index-base.tpl
Reglas duras:
- El CMS lo enlaza automáticamente por convención de nombre. NO existe ni se configura
_detailPage. - Se crea/edita como cualquier otro módulo:
acai-writesobreindex-base.tpldispara el compile. - Dentro del Twig, el registro actual está en
thisrecord(thisrecord.titulo,thisrecord.descripcion,thisrecord.imagen[0].urlPath). - NO crees una página por registro en
apartadosni una página "detalle" genérica. El detalle ya lo resuelve la sección general. - NO uses ni configures
_detailPage— no existe. - NO construyas URLs con query params (
?id=5) ni hagas fetch desde JS para cargar el registro. - NO uses hooks para cargar el registro —
thisrecordya está disponible. - NO inventes otro nombre de módulo para el detalle: debe ser
custom-{tableName}exacto.
Acceso a datos via thisrecord
<article class="product-card">
<img src="{{ thisrecord.imagen[0].urlPath }}"
alt="{{ thisrecord.imagen[0].info1 }}"
class="w-full h-64 object-cover">
<h3 class="text-xl font-semibold">{{ thisrecord.nombre }}</h3>
<p class="text-gray-600">{{ thisrecord.descripcion | raw }}</p>
<span class="text-2xl font-bold">{{ thisrecord.precio }}€</span>
</article>
Particularidades:
- Upload fields retornan arrays:
thisrecord.imagen[0].urlPath - Metadatos del upload:
info1(alt típico),info2,info3,info4 - Foreign keys con sufijo
_num:thisrecord.categoria_num - Si la FK tiene relación cargada, también aparece como objeto:
thisrecord.categoria_bd[0].nombre
Embeber formularios en el detalle
Si un detalle necesita un formulario (postular, pedir info), embebe el módulo del formulario dentro de la sección general pasándole el num del registro actual:
<form_postular :vacante_num="thisrecord.num"></form_postular>
NO pongas el formulario como sección suelta del listado.
Definir variables con <set>
<set :categories="'categorias' | get()"></set>
<set :featured="'productos' | get('destacado=1', 'orden ASC', 3)"></set>
<set :tienda="'configuracion_tienda' | get('num != 0')[0]"></set>
Flujo canónico para una funcionalidad tipo "vacantes"
- Crear la tabla con
enlace=true(create_table) y añadir los campos (create_field). - Crear la sección general
template/estandar/modulos/custom-{tableName}/index-base.tplcon el Twig que renderizathisrecord.*. Añadestyle.cssyscript.jssi hace falta. - (Opcional) Crear un módulo de listado
template/estandar/modulos/{tableName}_listado/que consulte los registros y enlace a cadaenlace. - (Opcional) Crear la página índice
/{tableName}/como registro normal enapartados(tipo Builder) y añadirle el módulo de listado.
multiv2 — Contenido repetible dentro del módulo
El tipo multiv2 permite que el usuario repita un grupo de campos editables dentro de un mismo módulo. Útil para FAQs, listados de servicios, slides, etc.
<ul>
<li data-field-type="multiv2" data-field-label="Records">
<h3 data-field-type="textfield" data-field-label="Titulo">Título por defecto</h3>
<p data-field-type="textbox" data-field-label="Descripcion">Descripción</p>
<img data-field-type="upload" data-field-label="Imagen" src="">
</li>
</ul>
Acceso en Twig:
{% for record in records %}
<h3>{{ record.titulo }}</h3>
<p>{{ record.descripcion }}</p>
<img src="{{ record.imagen[0].urlPath }}">
{% endfor %}
Las variables son propiedades del objeto iterado, no variables sueltas.
Layout global vs módulos
header, footer, style global y javascript global NO son módulos normales. Viven en cms/lib/plugins/builder_saas/layout.json y se editan con tools dedicadas (get_layout_field / set_layout_field). Ver 08-layout-and-libraries.md.
NUNCA edites directamente:
cms/lib/plugins/builder_saas/layout.jsontemplate/estandar/modulos/custom-header-twig/*template/estandar/modulos/custom-footer-twig/*template/estandar/modulos/custom-header/*template/estandar/modulos/custom-footer/*
Estos son artefactos generados a partir de layout.json.
Reglas críticas
- Solo se edita
index-base.tpl. Los demás archivos.tplybuilder.jsonson auto-generados. - Editar
index-base.tplconacai-write/acai-line-replacecompila automáticamente. script.jsystyle.cssson estáticos: nunca uses Twig ni atributos builder dentro.- Detalle de registro =
template/estandar/modulos/custom-{tableName}/. Nada de_detailPage, nada de páginas duplicadas. thisrecordsolo existe en secciones generales — en módulos normales no aparece.- Para incluir un módulo:
<module_id :param="value"></module_id>o'module_id' | module({param: value}). - Layout global (header/footer) NO se edita por archivos — usa
get_layout_field/set_layout_field.