Files
agenticSystem/docs/09-mcp-tools-reference.md
Jordan Diaz bdee7665ec 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.
2026-08-12 11:32:07 +00:00

346 lines
24 KiB
Markdown

---
title: "Referencia maestra de tools MCP"
tags: [tools, reference, workflows]
load_priority: 50
load_when: [ranked]
summary: "Inventario completo de tools por categoría (archivos, módulos, registros, tablas, layout, libs, hooks, media, navegación, proyecto, git, auth, docs) y workflows canónicos."
---
# MCP Tools — Referencia Completa
Este documento es el **inventario canónico** de todas las tools MCP disponibles para el agente Acai. Está agrupado por categoría (archivos, módulos, registros, tablas, layout, librerías, hooks, media, navegación, proyecto, git, autenticación, docs) y describe para cada tool su propósito, parámetros clave, qué devuelve y cuándo usarla. Incluye además los **workflows canónicos** para las operaciones más comunes (crear módulo, editar módulo, crear funcionalidad nueva con tabla + detalle, gestionar imágenes de un módulo, editar header/footer, configurar middleware de hook). Léelo antes de cualquier tarea para elegir la secuencia correcta de tools.
## Inventario por categoría
### Archivos
| Tool | Acción | Notas |
|------|--------|-------|
| `acai-view` | Lee un archivo (con rango de líneas opcional) | Ahorra tokens — usa `start_line`/`end_line` |
| `acai-glob` | Encuentra archivos por patrón glob | `template/estandar/modulos/**/index-base.tpl` |
| `acai-grep` | Busca texto/regex en archivos | Soporta filtro por glob |
| `acai-write` | Crea o reescribe un archivo completo | **Editar `index-base.tpl` compila automáticamente** |
| `acai-line-replace` | Reemplaza un bloque de líneas validado | Preferida sobre `acai-write` para edits puntuales |
| `acai-delete` | Elimina un archivo | Destructivo — confirma primero |
Reglas:
- Rutas siempre relativas al proyecto.
- Editar `index-base.tpl` → compila automáticamente. No necesitas llamar a `compile_module`.
- **NO toques** `index.tpl`, `index-twig.tpl`, `builder.json` (autogenerados) ni los archivos de layout protegidos (ver `08-layout-and-libraries.md`).
### Módulos
| Tool | Acción | Notas |
|------|--------|-------|
| `create_module` | Crea un módulo nuevo (carpeta + archivos + compile) | Alternativa a hacer `acai-write` manual del `index-base.tpl`. Genera `moduleId` único añadiendo sufijo aleatorio. |
| `compile_module` | Recompila manualmente (rescate) | Solo si los archivos generados están desincronizados. Editar `index-base.tpl` con tools de archivo ya compila. |
| `check_module` | Preview del render con datos de ejemplo | Devuelve preview (50 líneas) por defecto; `fullRender:true` para todo |
| `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 |
| `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)
| Tool | Acción | Notas |
|------|--------|-------|
| `list_table_records` | Lista registros con filtros, paginación | Usa `fields` y `truncateText` para ahorrar tokens |
| `get_record` | Obtiene un registro completo por `num` | Carga uploads + relations por defecto |
| `create_or_update_record` | Crea o actualiza registros | Sin `recordId` → crea. Con `recordId` → actualiza. Acepta array para batch |
| `delete_table_records` | Elimina registros (por IDs o `deleteAll`) | **Destructivo permanente** |
| `list_page_modules` | Lista módulos colocados en una página Builder | Devuelve sectionIds, posiciones, visibilidad, configVars |
| `add_module_to_record` | Añade módulo a una página Builder | Devuelve `sectionId` — úsalo en `set_module_config_vars` |
| `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` |
| `toggle_module_visibility` | Muestra/oculta sin borrar | Por `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 |
### Tablas y campos (schema)
Ver `05-tables-and-fields.md` para detalles. Tools:
| Tool | Acción |
|------|--------|
| `list_tables` | Inventario de tablas (sin `cms_`) |
| `get_table_schema` | Schema completo. Soporta `minimal:true` y `filterFields:"..."` para ahorrar tokens |
| `create_table` | Crea tabla nueva. PREGUNTA al usuario sobre `enlace`/`seoMetas` antes |
| `update_table_metadata` | Actualiza el `[meta]`. `newTableName` renombra MySQL (destructivo) |
| `delete_table` | Borra tabla. Usa `dryRun:true` primero. `dropData:true` borra datos |
| `reorder_tables` | Reordena sidebar admin |
| `create_field` | Añade campo a tabla |
| `update_field` | Actualiza props. `newFieldName` renombra columna (destructivo). Cambios de `type` pueden truncar datos — surfacea `warnings` |
| `delete_field` | Borra campo. `dropColumn:true` borra datos permanentemente |
| `reorder_fields` | Reordena los campos del formulario admin |
| `regenerate_enlaces` | Regenera URLs de la tabla. `generateAlias:true` para preservar redirects |
### Layout global
Ver `08-layout-and-libraries.md`.
| Tool | Acción |
|------|--------|
| `get_layout_field` | Lee `style`/`javascript`/`header`/`footer` del `layout.json` |
| `set_layout_field` | Escribe el campo (atómico: actualiza json + regenera tpl + compila). Destructivo |
### Librerías globales
| Tool | Acción |
|------|--------|
| `list_global_libraries` | Lista las URLs `top` (head) y `bottom` (antes de `</body>`) |
| `add_global_library` | Añade URL idempotente |
| `remove_global_library` | Quita URL idempotente |
| `set_global_libraries` | Reemplaza la lista de la sección. Destructivo |
### Hooks (middleware)
| Tool | Acción |
|------|--------|
| `get_hook_middleware` | Lee la config `middleWare` de un hook global |
| `set_hook_middleware` | Configura cuándo se ejecuta automáticamente: `[]`, `["allurls"]`, `["<tableName>-<num>"]` |
Ver `06-hooks-and-cmsapi.md` para uso. Crear/editar el `.php` del hook se hace con `acai-write`.
### Media
| Tool | Acción | Notas |
|------|--------|-------|
| `generate_image` | Genera imagen con IA y la guarda en `cms/uploads/generated/` | Devuelve `dockerUrl` y `uploadUrl`/`fullUrl`. **En Forge prefiere `uploadUrl`/`fullUrl`** sobre `dockerUrl` para `upload_record_image` |
| `upload_record_image` | Sube imagen a un campo `upload` de un registro | Necesita `tableName`, `recordId` (num), `fieldName` real (de `uploadFields` o `get_table_schema`). Sincroniza a producción si el proyecto está en modo prod |
| `list_record_uploads` | Lista los uploads existentes de un campo de un registro | Devuelve cada upload con su `uploadId` (necesario para `replace`/`delete`/`reorder`) |
| `replace_record_image` | Reemplaza un upload existente por uno nuevo | Necesita `uploadId` (de `list_record_uploads`). Borra el viejo + sube el nuevo, ambos con sync a producción |
| `delete_record_upload` | Borra un upload concreto del campo | Necesita `uploadId`. Sincroniza el borrado a producción |
| `reorder_record_uploads` | Cambia el orden de los uploads de un campo | Lista de `uploadIds` en el orden deseado |
| `set_upload_info` | Escribe los metadatos `info1`..`info5` de un upload YA subido | Necesita `uploadId` (de `list_record_uploads`) + al menos un `infoN`. Solo escribe las claves enviadas; `""` vacía ese info. Máx 1000 caracteres por campo |
| `upload_image_to_assets` | Sube imagen a `/images/` del template (assets globales) | Acepta base64, data URI, URL. Permite resize/quality/format |
`set_upload_info` es la única vía para rellenar `info2`..`info5` (al subir solo se escribe `info1`, vía el param `alt`). Como `uploadId` es la PK de la tabla central `uploads`, la MISMA tool sirve para los dos tipos de upload — no se pasa tabla/registro/campo, el `uploadId` ya identifica la fila:
- **Uploads de un registro normal**: el `uploadId` sale de `list_record_uploads({ tableName, recordId, fieldName })`.
- **Uploads de variables de módulo**: `get_module_config_vars` devuelve `uploadFields` (`varName``{ tableName: "builder_custom", recordNum, fieldName }`; `set_module_config_vars` devuelve lo mismo y además cubre las vars `multi`, con clave `"varName.subVarName"` → array `[{index, fieldName, recordNum}]`). Con eso llama a `list_record_uploads({ tableName: "builder_custom", recordId: recordNum, fieldName })` para sacar los `uploadId`.
`info1` es por convención el **alt text**: la misma columna que escribe el param `alt` de `upload_record_image`/`replace_record_image`, así que escribirlo aquí lo sobrescribe. El significado de `info2`..`info5` lo define **cada módulo**: los declara en su `index-base.tpl` con `data-field-info1`..`data-field-info5` y quedan guardados como `infoLabels` en su `builder.json`. No hace falta abrir el fichero: cada entrada de `uploadFields` incluye `infoLabels` cuando el módulo los define — array donde la posición 0 es `info1`, la 1 es `info2`, etc. (para uploads dentro de vars `multi`, los labels salen en `varsMeta.<varName>.subFields.<subVar>.infoLabels`). El `builder.json` (`vars.<varName>.infoLabels`) sigue siendo la fuente canónica si necesitas comprobarlo. Consulta esos labels antes de escribir para no inventarte el significado de cada info. Ver `01-builder-fields.md`.
### Navegación
| Tool | Acción |
|------|--------|
| `navigate_browser` | Navega el browser preview del usuario a un `enlace` (e.g. `/servicios/`) |
### Inspección de páginas (Playwright headless)
Tools del MCP `playwright`. El browser headless es del agente — el usuario NO ve lo que pasa aquí. Para que el USER vea algo, usa `navigate_browser`.
| Tool | Acción | Cuándo usarla |
|------|--------|---------------|
| `browser_navigate` | Carga una URL en el headless browser interno | Antes de cualquier otra `browser_*` |
| `browser_snapshot` | Devuelve el accessibility tree YAML (texto estructurado) | **Tool primaria de inspección**. La usas para leer la página. Ves DOM, roles, valores de inputs, enlaces, jerarquía |
| `browser_click`, `browser_fill_form`, `browser_press_key`, `browser_select_option` | Interacciones con elementos | Solo cuando necesitas simular interacción del usuario para reproducir un bug o validar un flow |
| `browser_console_messages` | Logs del console del browser | Para detectar errores JS |
| `browser_network_requests` | Lista de requests | Para detectar 404, fallos de fetch |
| `browser_take_screenshot` | Captura PNG | **Evítala**. El modelo NO procesa imágenes; el screenshot se descarta. Solo úsala si el usuario explícitamente pide "haz un screenshot para que lo vea yo" |
**Regla**: para auditar/inspeccionar/depurar UI, `browser_navigate``browser_snapshot`. NUNCA `browser_take_screenshot` esperando "ver" la página — el modelo es text-only y la imagen se pierde.
### Proyecto
| Tool | Acción |
|------|--------|
| `get_web_url` | URL del sitio en desarrollo. **OBLIGATORIO** antes de fetch/Playwright. Acuérdate de añadir `?pruebas=1` |
| `save_project_styles` | Guarda resumen de estilos en `docs/project-styles.md` |
### Git
| Tool | Acción |
|------|--------|
| `list_git_log` | Lista los últimos commits para que el usuario elija un id de rollback |
| `recover_previous_git` | Rollback rápido al commit anterior. Tool de rescate — pide confirmación al usuario antes de ejecutar |
| `recover_git` | Rollback a un commit específico (por id, obtenido vía `list_git_log`). Pide confirmación al usuario |
### Autenticación
| Tool | Acción |
|------|--------|
| `refresh_acai_token` | Renueva el JWT cuando expira (errores 403) |
### Idiomas y traducciones
Las webs multiidioma guardan las traducciones en la tabla central `cms_traducciones`. Ver `04-pages-and-records.md` y `11b-rules-cheat-sheet.md`.
| Tool | Acción | Notas |
|------|--------|-------|
| `list_web_languages` | Lista los idiomas activos del sitio (`settings.dat.php [idiomas]`) | Devuelve `prefix`/`urlPrefix`/`isDefault`. `prefix="www"` = idioma base (URLs sin prefijo); otro prefix (p.ej. `en`) = idioma bajo `/en/...` |
| `get_record_translations` | Lee traducciones de uno o varios registros | Por `tableName` (sin `cms_`) + `recordNums`. Opcional `fields` y `prefix`. Devuelve `{recordNum:{prefix:{fieldName:valor}}}` ya decodificado |
| `set_record_translations` | Escribe traducciones de un registro + idioma | `tableName`+`recordNum`+`prefix`+`fields`. `''` borra la traducción. `enlace`: path absoluto con prefijo (`/en/...`); CocoEnlace lo regenera si cambia el enlace base |
Además, las tools de lectura `list_table_records` y `get_record` aceptan el parámetro opcional `lang` (prefix, p.ej. `'en'`): devuelven los valores ya traducidos de los campos traducibles (el motor CocoDB aplica la traducción en lectura).
Campos traducibles por tipo: `textfield`, `textbox`, `wysiwyg`, `codigo`, `multitext` (el multitext se traduce como el JSON serializado completo en una sola fila). El valor en la DB va en Base64, pero es transparente: pasas y recibes texto plano.
### Documentación
| Tool | Acción |
|------|--------|
| `read_doc` | Lee un doc del knowledge base completo o por sección. Útil cuando un doc no fue cargado por relevancia o necesitas una sección puntual |
| `list_docs` | Lista los docs disponibles con sus títulos y summaries |
## Workflows canónicos
### 1. Crear un módulo nuevo
1. **Estilo del proyecto**: si existe `docs/project-styles.md` léelo. Si no, explora 3-4 módulos representativos (no `custom-*`) y guarda con `save_project_styles`.
2. **Lee la doc relevante** según contenido: `01-builder-fields.md` siempre; `07-css-js-conventions.md` si lleva JS; `06-hooks-and-cmsapi.md` si lleva hook PHP; `02-twig.md` si usa filtros.
3. **`acai-write`** sobre `template/estandar/modulos/<moduleId>_xxxxxx/index-base.tpl` con el HTML/Twig. Si necesita CSS/JS/PHP, escribe también `style.css`, `script.js`, `hook.php`.
4. **Compilación automática** al escribir `index-base.tpl`. Si por algún motivo necesitas forzarla sin tocar el archivo: `compile_module`.
5. **`add_module_to_record`** para colocarlo en una página. Devuelve `sectionId`.
6. **`set_module_config_vars`** para rellenar variables (textos, listas, etc.). Devuelve `uploadFields` con `recordNum`+`fieldName` por cada upload.
7. **Imágenes**: `generate_image` o `upload_record_image` usando el `recordNum` y `fieldName` del paso 6.
8. **`navigate_browser`** al `enlace` de la página para que el usuario vea el resultado.
### 2. Editar un módulo existente
1. `get_module_config_vars` — leer estado actual (vars + recordNums).
2. `acai-view` — leer el rango concreto de `index-base.tpl` que vas a tocar.
3. `acai-line-replace` — modificar el bloque (compila automáticamente). Usa `acai-write` solo si el cambio es masivo.
4. Si cambian variables: `set_module_config_vars` para actualizar valores.
### 3. Gestionar imágenes de un módulo
**Tras `set_module_config_vars`** (recomendado):
1. La respuesta incluye `uploadFields` con `{ fieldName, recordNum }` por cada variable upload.
2. Para multi vars con uploads: `uploadFields["records.imagen"]` es array `[{index, fieldName, recordNum}]`.
3. `upload_record_image` con `tableName: "builder_custom"`, `recordId` y `fieldName` de `uploadFields`.
**Sin `set_module_config_vars` previo**:
1. `get_module_config_vars` — obtiene `recordNum` de builder_custom.
2. Lee `builder.json` del módulo para encontrar el `fieldName` real (de `vars.NOMBRE.relations.builder_custom`, ej. `image1`**NO** uses el nombre de la variable).
3. `upload_record_image` con `tableName: "builder_custom"`, `recordId` (recordNum del paso 1), `fieldName` (de relations).
Generar imagen primero:
1. `generate_image` con `prompt` + `style`.
2. Usa la URL recomendada que devuelve (`uploadUrl` o `fullUrl` en Forge; `dockerUrl` solo en local).
3. `upload_record_image` con esa URL.
Para rellenar los metadatos `info1`..`info5` de una imagen ya subida (alt text y los campos que el módulo define en sus `infoLabels`): `set_upload_info` — ver su entrada en la sección Media.
### 4. Crear funcionalidad nueva con tabla + detalle
Ejemplo: implementar "Vacantes".
1. **`create_table`** con `tableName: "vacantes"`, `menuType: "multi"`, `enlace: true`, `seoMetas: true`. Pregunta al usuario antes los flags.
2. **`create_field`** para cada campo: `titulo`, `descripcion` (wysiwyg), `salario_minimo` (textfield), `categoria_num` (list desde tabla), `fecha_publicacion` (date), `fecha_expiracion` (date), `visible` (checkbox), `imagen_destacada` (upload).
3. **`acai-write`** sobre `template/estandar/modulos/custom-vacantes/index-base.tpl` con el Twig que usa `thisrecord.*` (sección general que renderiza el detalle de cada registro).
4. (Opcional) **Módulo de listado** `vacantes_listado_xxxxxx` con `'vacantes' | get('visible=1', 'fecha_publicacion DESC', 20)`.
5. (Opcional) **Página índice** `/vacantes/` en `apartados` (Builder) con el módulo de listado dentro.
6. **`navigate_browser`** a un detalle creado para verificar.
### 5. Editar header / footer del sitio
Ver `08-layout-and-libraries.md`.
1. `get_layout_field({ field: "header" })` — lee Twig actual.
2. Modifica localmente.
3. `set_layout_field({ field: "header", content: "..." })` — atómico. Sobrescribe `layout.json`, regenera `.tpl` y compila.
**NUNCA** uses `acai-write` sobre `custom-header-twig/index-base.tpl` ni `layout.json`.
### 6. Añadir librería global (jQuery, Vue CDN, Google Fonts)
1. `list_global_libraries` — comprueba si ya existe.
2. `add_global_library({ section: "bottom", url: "..." })` para JS, o `top` para CSS/fonts.
Para reordenar dependencias: `set_global_libraries` con la lista completa.
### 7. Hook con middleware (auto-ejecutar antes de páginas)
1. **`acai-write`** sobre `hooks/hooks.<id>.php` con la lógica.
2. **`get_hook_middleware`** sobre `/hooks/<id>/` para ver config actual.
3. **`set_hook_middleware`** con el nuevo `middleWare`:
- `[]` → solo cuando se llama explícitamente
- `["allurls"]` → antes de cada página
- `["cms_apartados-2"]` → solo antes del registro num=2 de apartados
### 8. Gestionar registros de una tabla
1. `list_table_records` con `where`/`order`/`limit`/`fields` (sin `cms_`).
2. `get_record` para uno completo (`tableName`+`recordNum`).
3. `create_or_update_record` para crear/actualizar. Antes consulta el schema con `get_table_schema`.
4. `delete_table_records` para borrar (destructivo permanente).
### 9. Explorar el sitio
- `list_table_records` sobre `apartados` para ver páginas.
- `list_page_modules` sobre una página para ver módulos.
- `get_module_config_vars` para ver datos de un módulo.
- `check_module` para preview con datos custom.
### 10. Consultar la documentación bajo demanda
Si el knowledge_base no cargó un doc relevante (lo verás en "Other Available Docs") o necesitas una sección puntual con detalle:
```
list_docs() // todos los docs con summary
read_doc({ name: "05-tables-and-fields" }) // doc completo
read_doc({ name: "06-hooks-and-cmsapi", section: "Hook middleware" }) // sección por heading H2
```
### 11. Gestionar uploads existentes de un registro
Cuando el usuario pide "cambia la imagen de la vacante 12", "borra la foto X de la galería", "reordena las imágenes" — **NO uses `upload_record_image` para reemplazar**: eso añade un upload nuevo encima sin tocar el viejo y deja basura. Usa el flow de gestión:
```
list_record_uploads({ tableName, recordId, fieldName })
→ array de uploads, cada uno con su uploadId
```
Según lo que pida el usuario:
- **Reemplazar** una imagen concreta: `replace_record_image({ tableName, recordId, fieldName, uploadId, imageUrl, alt? })` — borra el viejo + sube el nuevo, ambos con sync a producción.
- **Borrar** una imagen: `delete_record_upload({ uploadId, table? })` — sync de borrado a producción.
- **Reordenar**: `reorder_record_uploads({ tableName, recordId, fieldName, uploadIds: [...] })` con la lista en el orden deseado.
- **Editar metadatos** (alt text, pie de foto, crédito…) sin tocar el fichero: `set_upload_info({ uploadId, info1?..info5? })` — ver su entrada en la sección Media.
Para AÑADIR un upload nuevo (sin reemplazar nada existente), usa `upload_record_image` directamente.
Notas:
- En modo producción todas estas tools sincronizan automáticamente con el servidor real (no solo modifican local).
- Si solo tienes el `recordId` y necesitas saber qué `fieldName` tiene uploads, llama antes a `get_table_schema({ minimal: true })` y filtra los campos `type: "upload"`.
### 12. Traducir un registro a otro idioma
Ejemplo: traducir la vacante num=12 al inglés.
1. `list_web_languages` — obtén el `prefix` del idioma destino (p.ej. `en`). El prefix `www` es el idioma base y NO se traduce por aquí (se edita con los campos normales del registro).
2. (Opcional) `get_record({ tableName: "vacantes", recordNum: 12 })` para leer los textos originales, o `get_record_translations({ tableName: "vacantes", recordNums: [12], prefix: "en" })` para ver qué falta.
3. `set_record_translations({ tableName: "vacantes", recordNum: 12, prefix: "en", fields: { titulo: "...", descripcion: "..." } })`. Solo campos traducibles (`textfield`, `textbox`, `wysiwyg`, `codigo`, `multitext`). **NUNCA** incluyas `enlace`. Un `''` borra esa traducción.
4. Verifica con `get_record({ tableName: "vacantes", recordNum: 12, lang: "en" })` — devuelve los valores ya traducidos.
### 13. Traducir las variables de un módulo
Los textos de un módulo Builder viven en la tabla `builder_custom`, no en la tabla de la página.
1. `list_web_languages` — obtén el `prefix` destino.
2. `get_module_config_vars({ tableName, recordNum, sectionId })` — de `varsMeta` toma el `recordNum` (fila de `builder_custom`; en vars multi, uno por item).
3. `set_record_translations({ tableName: "builder_custom", recordNum: <de varsMeta>, prefix, fields: { <NOMBRE de la var>: "..." } })` — la clave es el nombre de la var (`titulo`, `subtitulo`, `enlace_anchor`), NUNCA la columna física (`title3`): el motor traduce por nombre de var y la columna física se rechaza con 400. No añadas `| translate` a la plantilla: las vars se traducen solas.
4. Para vars multi, repite por cada item con su `recordNum` de `varsMeta` (las claves siguen siendo los nombres de las sub-vars).
### 14. Traducir un texto general (literal de plantilla)
Los literales del filtro Twig `| translate` son registros normales de la tabla `textos_generales`; se traduce su campo `texto`.
1. `list_web_languages` — obtén el `prefix` destino.
2. `list_table_records({ tableName: "textos_generales", where: "identificador = '...'", fields: ["num", "identificador", "texto"] })` para localizar el `num`.
3. `set_record_translations({ tableName: "textos_generales", recordNum: <num>, prefix, fields: { texto: "traducción" } })`.
## Reglas globales para todas las tools
1. **`tableName` siempre SIN prefijo `cms_`** (excepto en `queryDB` Twig y en el `middleWare` de `set_hook_middleware`).
2. **PK siempre `num`**, nunca `id`. Foreign keys con sufijo `_num`.
3. **Upload fields son arrays** — accede con `[0].urlPath`.
4. **`fieldName` para imágenes** viene de `builder.json``vars.NOMBRE.relations.builder_custom` (ej. `image1`), NO del nombre de la variable.
5. **`recordId` para imágenes de módulo** es el `num` de `builder_custom`, NO el `sectionId`.
6. Tras `set_module_config_vars`, **TODAS** las variables (incluidos uploads) reciben `recordNum`+`fieldName` en `uploadFields`.
7. Si un token JWT expira (error 403): `refresh_acai_token` y reintentar.
8. Al pedir URLs del sitio: `get_web_url` SIEMPRE primero. Añade `?pruebas=1` para modo desarrollo. Nunca uses dominios de producción ni `localhost:8080`.
9. Antes de crear archivos, **lee la doc** relevante (`read_doc` si no está en el KB cargado).
10. Operaciones destructivas (`delete_*`, `dropColumn`, `dropData`, `dropTable`, `newTableName`, `newFieldName`, `regenerate_enlaces` sin alias, `set_global_libraries`, `set_layout_field`): **pide confirmación al usuario** si no es trivial.