Files
agenticSystem/docs/03-modules-and-sections.md
Jordan Diaz 6881d64a08 ajustes
2026-04-25 10:27:51 +00:00

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.tpl y builder.json los genera el compilador y se sobrescriben automáticamente.
  • Editar index-base.tpl con acai-write o acai-line-replace dispara la compilación automática.
  • script.js y style.css son estáticos — NO uses sintaxis Twig dentro. Pasa valores dinámicos vía atributos data-*.
  • index-base.tpl solo 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 vacantestemplate/estandar/modulos/custom-vacantes/index-base.tpl
  • Tabla productostemplate/estandar/modulos/custom-productos/index-base.tpl
  • Tabla noticiastemplate/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-write sobre index-base.tpl dispara 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 apartados ni 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 registrothisrecord ya 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"

  1. Crear la tabla con enlace=true (create_table) y añadir los campos (create_field).
  2. Crear la sección general template/estandar/modulos/custom-{tableName}/index-base.tpl con el Twig que renderiza thisrecord.*. Añade style.css y script.js si hace falta.
  3. (Opcional) Crear un módulo de listado template/estandar/modulos/{tableName}_listado/ que consulte los registros y enlace a cada enlace.
  4. (Opcional) Crear la página índice /{tableName}/ como registro normal en apartados (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.json
  • template/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

  1. Solo se edita index-base.tpl. Los demás archivos .tpl y builder.json son auto-generados.
  2. Editar index-base.tpl con acai-write / acai-line-replace compila automáticamente.
  3. script.js y style.css son estáticos: nunca uses Twig ni atributos builder dentro.
  4. Detalle de registro = template/estandar/modulos/custom-{tableName}/. Nada de _detailPage, nada de páginas duplicadas.
  5. thisrecord solo existe en secciones generales — en módulos normales no aparece.
  6. Para incluir un módulo: <module_id :param="value"></module_id> o 'module_id' | module({param: value}).
  7. Layout global (header/footer) NO se edita por archivos — usa get_layout_field/set_layout_field.