feat: el agente puede declarar y leer las cmsTables de un modulo

Las cmsTables (tablas del CMS cuyo contenido MUESTRA un modulo) ya se
definen desde Forge y desde el CMS legacy; faltaba que el agente las
entendiera.

- update_module_metadata acepta cmsTables. El endpoint del server ya la
  validaba, asi que solo habia que declararla en el schema Zod.
- get_module_config_vars la devuelve: sale gratis porque el handler ya
  resolvia el schema del modulo para uploadFields/varsMeta.
- Docs (01, 03, 09) con el matiz que mas se puede confundir: `tables` es
  donde viven los VALORES de las vars (siempre builder_custom, lo pone el
  compilador) y `cmsTables` es que contenido MUESTRA el modulo.

Para el agente lo util no es solo escribirlas: al recibirlas sabe donde
esta de verdad el contenido visible. Si le piden cambiar lo que muestra un
listado de noticias, los registros estan en esa tabla, no en las vars del
modulo.

Se documenta ademas que la metadata del builder.json es la UNICA parte
editable del fichero (y solo con esta tool): el resto lo regenera el
compilador en cada compilacion.
This commit is contained in:
Jordan Diaz
2026-08-12 11:32:07 +00:00
parent 5c011ab7ef
commit bdee7665ec
5 changed files with 45 additions and 9 deletions

View File

@@ -500,3 +500,4 @@ Valores comunes: `fade-up`, `fade-down`, `fade-left`, `fade-right`, `zoom-in`, `
8. Checkbox guarda `1` o `0` (número), nunca `true`/`false`. 8. Checkbox guarda `1` o `0` (número), nunca `true`/`false`.
9. Evita Tailwind arbitrary-value en `index-base.tpl` — muévelos a `style.css`. 9. Evita Tailwind arbitrary-value en `index-base.tpl` — muévelos a `style.css`.
10. `script.js` y `style.css` son estáticos: NO uses sintaxis Twig dentro. Pasa valores dinámicos vía `data-*`. 10. `script.js` y `style.css` son estáticos: NO uses sintaxis Twig dentro. Pasa valores dinámicos vía `data-*`.
11. Si el módulo lista registros de una tabla del CMS (`c-for` sobre ella, o su `hook.php` la consulta), declara esa tabla con `update_module_metadata({ cmsTables: [...] })`. No es un `data-field-*`: no se deduce del HTML, hay que declararla. Ver `03-modules-and-sections.md`.

View File

@@ -28,6 +28,7 @@ Componentes visuales reutilizables. Viven en `template/estandar/modulos/<module-
Reglas duras: 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. - **Solo se edita `index-base.tpl`.** `index.tpl`, `index-twig.tpl` y `builder.json` los genera el compilador y se sobrescriben automáticamente.
- **Excepción del `builder.json`: la metadata SÍ se edita, con `update_module_metadata`** (`label`, `description`, `onlyAdminModule`, `MJMLModule`, `cmsTables`). Nunca la escribas con tools de archivo: el compilador regenera el fichero entero y perderías el cambio.
- Editar `index-base.tpl` con `acai-write` o `acai-line-replace` **dispara la compilación automática**. - 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-*`. - `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. - `index-base.tpl` solo contiene HTML/Twig. **Nunca** embebas etiquetas `<script>` con lógica del módulo, **nunca** PHP.
@@ -203,6 +204,32 @@ Acceso en Twig:
Las variables son **propiedades del objeto iterado**, no variables sueltas. Las variables son **propiedades del objeto iterado**, no variables sueltas.
## `cmsTables` — qué tablas del CMS muestra el módulo
Un módulo puede pintar contenido que NO vive en sus variables: un listado de noticias, una parrilla de productos, un carrusel del blog. Esos registros están en tablas del CMS, y `cmsTables` es donde el módulo declara cuáles.
```json
{ "label": "Listado de noticias", "cmsTables": ["noticias"] }
```
**No confundir con `tables`**, que también está en el `builder.json` y se parece demasiado:
| Clave | Qué es | Quién la pone |
|-------|--------|---------------|
| `tables` | Dónde se guardan los VALORES de las variables del módulo. Siempre `["builder_custom"]` | El compilador. No la toques |
| `cmsTables` | Qué contenido MUESTRA el módulo | Una persona desde el editor, o tú con `update_module_metadata` |
Para qué sirve: el editor pinta un acceso directo al CMS de cada tabla declarada desde cualquier página que incluya el módulo, para que quien edita esa página llegue al contenido sin buscarlo.
Cuándo declararla:
- **Sí**: el módulo hace `c-for` sobre registros de una tabla, o su `hook.php` los consulta.
- **No**: el módulo solo muestra sus propias variables (un banner con título e imagen). Deja la lista vacía.
Cómo usarla tú:
- Al crear un módulo que lista contenido, decláralas: `update_module_metadata({ module, cmsTables: ["noticias"] })`.
- Al recibirlas en `get_module_config_vars`, te dicen **dónde está de verdad el contenido visible**. Si el usuario pide cambiar lo que muestra un listado de noticias, los registros están en `noticias` — no en las variables del módulo. Ve a esa tabla con `list_table_records` / `create_or_update_record`.
- Pasar `cmsTables` reemplaza la lista entera: incluye las que quieras conservar. `[]` la vacía.
## Traducir las variables de un módulo ## Traducir las variables de un módulo
Los valores textuales de las variables de un módulo (títulos, descripciones, wysiwyg, etc.) NO se guardan en la fila de la página, sino en la tabla `builder_custom`. Por eso una traducción de módulo apunta siempre a `builder_custom`, no a `apartados` ni a la tabla de la página. Los valores textuales de las variables de un módulo (títulos, descripciones, wysiwyg, etc.) NO se guardan en la fila de la página, sino en la tabla `builder_custom`. Por eso una traducción de módulo apunta siempre a `builder_custom`, no a `apartados` ni a la tabla de la página.

View File

@@ -37,6 +37,7 @@ Reglas:
| `check_module_usage` | Lista páginas que usan el módulo | **OBLIGATORIO antes de `delete_module`** | | `check_module_usage` | Lista páginas que usan el módulo | **OBLIGATORIO antes de `delete_module`** |
| `delete_module` | Elimina la carpeta del módulo | Destructivo. Si `inUse=true`, deniega — el usuario debe quitarlo de las páginas primero | | `delete_module` | Elimina la carpeta del módulo | Destructivo. Si `inUse=true`, deniega — el usuario debe quitarlo de las páginas primero |
| `set_module_example_data` | Define datos de ejemplo para preview en el editor | Pasar valores para TODAS las variables del schema | | `set_module_example_data` | Define datos de ejemplo para preview en el editor | Pasar valores para TODAS las variables del schema |
| `update_module_metadata` | Edita metadata del `builder.json` | `label`, `description`, `onlyAdminModule`, `MJMLModule`, `cmsTables`. NO renombra el módulo. Rechaza los módulos de layout (`custom-header/footer[-twig]`) |
### Registros (records) ### Registros (records)
@@ -51,7 +52,7 @@ Reglas:
| `remove_module_from_record` | Quita módulo de la página | Por `sectionId` (preferido) o `modulePosition` | | `remove_module_from_record` | Quita módulo de la página | Por `sectionId` (preferido) o `modulePosition` |
| `reorder_module` | Mueve módulo a otra posición | `fromPosition``toPosition` | | `reorder_module` | Mueve módulo a otra posición | `fromPosition``toPosition` |
| `toggle_module_visibility` | Muestra/oculta sin borrar | Por `sectionId` | | `toggle_module_visibility` | Muestra/oculta sin borrar | Por `sectionId` |
| `get_module_config_vars` | Lee valores actuales de las variables | Por `tableName` + `recordNum` + `sectionId` | | `get_module_config_vars` | Lee valores actuales de las variables | Por `tableName` + `recordNum` + `sectionId`. Devuelve además `cmsTables`: las tablas cuyo contenido MUESTRA el módulo (ahí están sus registros, no en las vars) |
| `set_module_config_vars` | Escribe variables del módulo | Devuelve `uploadFields` con `recordNum`+`fieldName` listos para subir imágenes | | `set_module_config_vars` | Escribe variables del módulo | Devuelve `uploadFields` con `recordNum`+`fieldName` listos para subir imágenes |
### Tablas y campos (schema) ### Tablas y campos (schema)

View File

@@ -7,22 +7,27 @@ import { getCurrentProjectInfo } from "../files/helpers.js";
// Tool: update_module_metadata // Tool: update_module_metadata
// Edita la metadata de builder.json de un modulo (label, description, // Edita la metadata de builder.json de un modulo (label, description,
// onlyAdminModule, MJMLModule) delegando en /api/modules/update-metadata. // onlyAdminModule, MJMLModule, cmsTables) delegando en
// El id/carpeta del modulo NO se puede renombrar y el endpoint rechaza los // /api/modules/update-metadata. El id/carpeta del modulo NO se puede renombrar
// modulos generados del layout (custom-header/footer[-twig]). // y el endpoint rechaza los modulos generados del layout
// (custom-header/footer[-twig]).
// Claves editables del builder.json. Se envian solo las que llegan definidas, // Claves editables del builder.json. Se envian solo las que llegan definidas,
// de modo que el endpoint hace un merge parcial y no pisa el resto. // de modo que el endpoint hace un merge parcial y no pisa el resto.
const EDITABLE_KEYS = ["label", "description", "onlyAdminModule", "MJMLModule"]; const EDITABLE_KEYS = ["label", "description", "onlyAdminModule", "MJMLModule", "cmsTables"];
export function registerUpdateModuleMetadataTool(server) { export function registerUpdateModuleMetadataTool(server) {
server.tool( server.tool(
"update_module_metadata", "update_module_metadata",
`Edit a module's metadata in its builder.json: 'label' (display name shown in the visual builder), 'description' (short help text for editors), 'onlyAdminModule' (true = the module is only visible to admin users in the builder), 'MJMLModule' (true = the module is an email/MJML module). `Edit a module's metadata in its builder.json: 'label' (display name shown in the visual builder), 'description' (short help text for editors), 'onlyAdminModule' (true = the module is only visible to admin users in the builder), 'MJMLModule' (true = the module is an email/MJML module), 'cmsTables' (CMS tables whose content this module displays).
Use 'cmsTables' when the module renders records from a CMS table — a news list, a product grid, a blog carousel. Declare the tables it reads (e.g. ["noticias"]). The editor then shows a direct link to each of those tables from any page that includes the module, so whoever edits that page can reach the content without hunting for it. A module that only shows its own variables (a banner with a title and an image) needs no cmsTables.
Do NOT confuse 'cmsTables' with the 'tables' key of builder.json: 'tables' is where the module's VARIABLE VALUES are stored (always builder_custom) and is managed by the compiler. 'cmsTables' is what content the module DISPLAYS, and is chosen by a human or by you.
The module id (its folder name under template/estandar/modulos/) CANNOT be renamed with this tool — there is no rename option at all. Only the fields above change; everything else in builder.json is preserved. The module id (its folder name under template/estandar/modulos/) CANNOT be renamed with this tool — there is no rename option at all. Only the fields above change; everything else in builder.json is preserved.
At least one of label, description, onlyAdminModule or MJMLModule is required. Fields you omit are left untouched. At least one of label, description, onlyAdminModule, MJMLModule or cmsTables is required. Fields you omit are left untouched. Passing cmsTables replaces the whole list, so include the tables you want to keep; pass [] to clear it.
Not applicable to the generated layout modules (custom-header, custom-footer, custom-header-twig, custom-footer-twig): those are artifacts of the global layout and the request will be rejected — use set_layout_field for them.`, Not applicable to the generated layout modules (custom-header, custom-footer, custom-header-twig, custom-footer-twig): those are artifacts of the global layout and the request will be rejected — use set_layout_field for them.`,
withAuthParams({ withAuthParams({
@@ -31,11 +36,12 @@ Not applicable to the generated layout modules (custom-header, custom-footer, cu
description: z.string().optional().describe("Short description shown to editors in the builder"), description: z.string().optional().describe("Short description shown to editors in the builder"),
onlyAdminModule: z.boolean().optional().describe("If true, the module is only visible to admin users in the builder"), onlyAdminModule: z.boolean().optional().describe("If true, the module is only visible to admin users in the builder"),
MJMLModule: z.boolean().optional().describe("If true, the module is treated as an email (MJML) module"), MJMLModule: z.boolean().optional().describe("If true, the module is treated as an email (MJML) module"),
cmsTables: z.array(z.string()).optional().describe("CMS tables whose records this module displays, e.g. [\"noticias\"]. Replaces the whole list; [] clears it. Only real tables of the project — they become direct links to the CMS."),
}), }),
{ readOnlyHint: false, destructiveHint: false }, { readOnlyHint: false, destructiveHint: false },
withAuth(async ({ module, label, description, onlyAdminModule, MJMLModule }, _extra) => { withAuth(async ({ module, label, description, onlyAdminModule, MJMLModule, cmsTables }, _extra) => {
try { try {
const provided = { label, description, onlyAdminModule, MJMLModule }; const provided = { label, description, onlyAdminModule, MJMLModule, cmsTables };
// Validacion temprana: sin ninguna clave editable no tiene // Validacion temprana: sin ninguna clave editable no tiene
// sentido llamar al endpoint. Ojo con los booleanos false — // sentido llamar al endpoint. Ojo con los booleanos false —

View File

@@ -22,6 +22,7 @@ export function registerGetModuleConfigVarsTool(server) {
- vars: resolved values (text, HTML, etc.) for simple vars and arrays for multi/repeater vars - vars: resolved values (text, HTML, etc.) for simple vars and arrays for multi/repeater vars
- varsMeta: per-var physical location { tableName: 'builder_custom', recordNum, fieldName, type }. USE THIS to know exactly which row + column to update with create_or_update_record. The variable's display name (e.g. 'titulo') is NOT the same as the physical column name (e.g. 'title2'). Uploads inside a 'multi' var carry their labels in subFields.<subVar>.infoLabels. - varsMeta: per-var physical location { tableName: 'builder_custom', recordNum, fieldName, type }. USE THIS to know exactly which row + column to update with create_or_update_record. The variable's display name (e.g. 'titulo') is NOT the same as the physical column name (e.g. 'title2'). Uploads inside a 'multi' var carry their labels in subFields.<subVar>.infoLabels.
- uploadFields: per-var upload location for upload_record_image / replace_record_image / set_upload_info. Each entry includes infoLabels when the module defines them (array; position 0 = info1). - uploadFields: per-var upload location for upload_record_image / replace_record_image / set_upload_info. Each entry includes infoLabels when the module defines them (array; position 0 = info1).
- cmsTables: CMS tables whose records this module DISPLAYS (e.g. ["noticias"]), or [] if it only shows its own variables. This is where the module's visible content really lives: if the user asks to change what a news-list module shows, the records are in those tables, NOT in the module's vars. Use list_table_records / create_or_update_record against them. Empty list means nothing to look up elsewhere.
- moduleId, sectionId - moduleId, sectionId
Required params: Required params: