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

201 lines
9.7 KiB
Markdown

# 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.).
```html
<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
```html
<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.
```html
<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`:
```html
<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-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 registro** — `thisrecord` ya está disponible.
- **NO inventes otro nombre de módulo** para el detalle: debe ser `custom-{tableName}` exacto.
### Acceso a datos via `thisrecord`
```html
<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:
```html
<form_postular :vacante_num="thisrecord.num"></form_postular>
```
NO pongas el formulario como sección suelta del listado.
### Definir variables con `<set>`
```html
<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.
```html
<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:
```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`.