acai-write/acai-line-replace rechazan custom-header-twig/custom-footer-twig con 'Forbidden path' y no habia ninguna doc que explicara por que ni el flujo real (layout.json via get_layout_field/set_layout_field). Costo una sesion de diagnostico en produccion (turismo.webserver2.plandeweb.com, Raul).
4.8 KiB
Acai Modular System
Modules
Modules are the visual building blocks of Acai websites. Each module lives in template/estandar/modulos/<module-id>/.
File Structure
<module-id>/
├── index-base.tpl # Source template (EDIT THIS)
├── index.tpl # Compiled output (auto-generated, do NOT edit)
├── index-twig.tpl # Compiled Twig output (auto-generated, do NOT edit)
├── builder.json # Compiled builder vars (auto-generated, do NOT edit)
├── style.css # Module-scoped styles
└── script.js # Module JavaScript
Template Syntax
Templates use a hybrid of Twig and Acai attributes. The source file is always index-base.tpl.
<section class="hero-section" id="{{ section_id }}">
<div class="container mx-auto px-4">
<h2 data-field-type="headfield" class="text-3xl font-bold">
Title here
</h2>
<p data-field-type="textbox" class="text-lg text-gray-600">
Description text
</p>
<img data-field-type="upload" src="placeholder.jpg" class="w-full rounded-lg" />
<a data-field-type="link" href="#" class="btn">Call to action</a>
</div>
</section>
Including Modules from Other Modules
<module_id :param1="value1" :param2="'string value'"></module_id>
Parameters are received as variables inside the included module.
Global Variables
| Variable | Description |
|---|---|
section_id |
Unique ID per module instance (use for anchors, JS scoping) |
interno |
true when viewing in CMS editor, false on public site |
server.HTTP_HOST |
Current domain |
loop.index |
1-based iteration index (inside c-for) |
loop.index is odd / loop.index is even |
For alternating layouts |
General Sections
General sections are database-backed templates used for record views, headers, footers, and reusable layouts. They use the same template engine as modules.
Key Differences from Modules
- Access record data via the
thisrecordvariable - Upload fields return arrays:
thisrecord.image[0].urlPath - Additional upload metadata:
info1(alt text),info2,info3,info4 - Foreign key fields use
_numsuffix:thisrecord.category_num - Saved via
save_general_section()(notsave_module()) - Parser type 2 = Twig (recommended), 0 = Acai legacy syntax
Example: Record Template
<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>
Variable Assignment
Use <set> tag to create variables from queries:
<set :categories="'categorias' | get()"></set>
<set :featured="'productos' | get({destacado: 1}, 'orden ASC', 3)"></set>
Header, footer, and site-wide style/JS — NOT regular files
template/estandar/modulos/custom-header-twig/index-base.tpl and the equivalent custom-footer-twig
one are not editable as files — acai-write/acai-line-replace reject them with
"Forbidden path: ... es un artefacto generado del layout global". They're special general sections
(the header/footer of the entire site). The .tpl on disk is only a generated copy for fast reading
(acai-view works fine on it), but the real source lives in layout.json and is edited with the
get_layout_field/set_layout_field MCP tools (see
docs/mcp-tools-reference.md):
field: 'header'/field: 'footer'— the header/footer templates themselves.field: 'style'— global CSS for the whole site.field: 'javascript'— global JS for the whole site (delegated listeners shared by every page live here, not in any.jsfile in the repo — agrepof the repo for "how does X work in the header" can find nothing even though X works fine in production).
Workflow for a header/footer/global-CSS/global-JS change: get_layout_field({field: '...'}) → edit the
full string (there's no line-replace for this — set_layout_field is a destructive full-content
replace) → set_layout_field({field: '...', content: ...}). The response includes compile_ok —
verify it, same as any other write-op (see the reliability warning in
docs/mcp-tools-reference.md).
Repeatable Content (multiv2)
The multiv2 builder field type creates repeatable groups of fields:
<div c-for="item in record.items">
<h3 data-field-type="textfield">{{ item.title }}</h3>
<p data-field-type="textbox">{{ item.description }}</p>
<img data-field-type="upload" src="{{ item.image }}" />
</div>
Access individual items: record.items[0].title, record.items[1].image, etc.