12 KiB
Tablas y Campos
Este documento explica cómo gestionar tablas y campos en Acai usando las tools del MCP. Cubre: cómo se almacena el schema (cms/data/schema/{tabla}.ini.php), los menuType (multi, single, category, separador), el flag enlace para tablas públicas, todos los tipos de campo (textfield, textbox, wysiwyg, codigo, date, list, checkbox, upload, multitext, separator), los props comunes (isRequired, defaultValue, optionsType, etc.), la diferencia entre operaciones reversibles e irreversibles (dropData, dropColumn, rename), y el flujo correcto para crear una funcionalidad nueva. Léelo antes de usar cualquier tool del grupo tables/.
Schemas
Cada tabla tiene un schema en cms/data/schema/{tabla}.ini.php. Define:
- Nombres y tipos de campo
- Reglas de validación
- Relaciones (foreign keys)
- Configuración de display (orden, ancho, etc.)
- Bloque
[meta]conmenuName,menuType,menuOrder,controller, etc.
Antes de operar sobre una tabla, siempre consulta el schema:
list_tables— inventario rápido del proyectoget_table_schemacontableName(sincms_) — schema completoget_table_schemaconminimal=true— solo nombres + tipos + labels (ahorra tokens)get_table_schemaconfilterFields="galeria|foto|image"— filtra por palabras clave
NUNCA inventes nombres de campos o tablas. Siempre confirma con el schema.
Convenciones inmutables
| Regla | Valor correcto |
|---|---|
| Nombres de tabla en tools/Twig/CmsApi | sin prefijo cms_ |
Nombres en queryDB |
con prefijo cms_ |
| Primary key | num (siempre) |
| Foreign key | <entidad>_num (e.g. categoria_num) |
| Upload field | array [{urlPath, info1, info2, info3, info4}] |
Crear una tabla — create_table
create_table({
tableName: "vacantes", // sin cms_, lowercase + underscores
menuName: "Vacantes", // display en sidebar admin
menuType: "multi", // multi | single | category | separador
enlace: true, // ¿es tabla pública con URLs?
seoMetas: true, // añade campos SEO meta (default false)
menuOrder: 5 // opcional, orden en sidebar
})
Decisiones obligatorias antes de llamar
enlace: true|falsees una decisión de arquitectura. PREGUNTA AL USUARIO antes de llamar:true→ la tabla genera URLs públicas, automáticamente añade campoenlace+ slug. Cada registro será una página y puede tener detalle víacustom-{tableName}.false→ tabla puramente administrativa (categorías internas, configuraciones, logs).
menuType:multi→ lista plana (productos, noticias, vacantes)single→ un único registro (home, configuración, about us)category→ contenedor jerárquico que agrupa otras tablas en el menúseparador→ solo un separador visual
Después de crear la tabla
- Añade los campos necesarios con
create_field(uno por uno). - Si la tabla tiene
enlace: true, considera crear la sección generalcustom-{tableName}para el detalle (ver03-modules-and-sections.md). - Si la tabla quiere ordenar/filtrar por fechas, añade campos
fecha_publicacion,fecha_expiracion,visible(ver04-pages-and-records.md).
Crear un campo — create_field
create_field({
tableName: "vacantes",
fieldName: "salario_minimo", // identificador SQL-safe
label: "Salario Mínimo", // display en formulario admin
type: "textfield",
initialProps: { // opcional — overrides de defaults
isRequired: 1,
maxLength: 100
}
})
Tipos de campo
| Tipo | Uso |
|---|---|
textfield |
Texto de una línea |
textbox |
Texto multilínea plano |
wysiwyg |
Editor de texto enriquecido |
codigo |
Editor de código (HTML/JS/CSS snippet) |
date |
Selector de fecha o datetime |
list |
Select / radio / checkboxes (necesita listType + optionsType en initialProps) |
checkbox |
Booleano (1/0) |
upload |
Subida de archivos (imágenes, docs) |
multitext |
Repetidor de entradas de texto |
separator |
Separador visual en el formulario (sin columna en BD) |
Props comunes (initialProps)
Pasa solo los que quieres sobrescribir; el resto usa defaults.
| Prop | Aplica a | Descripción |
|---|---|---|
isRequired |
todos | 1 o 0 |
isUnique |
textfield, textbox | 1 o 0 |
defaultValue |
todos | Valor por defecto |
description |
todos | Texto de ayuda en el formulario |
minLength / maxLength |
textfield, textbox, wysiwyg | Longitud min/max |
listType |
list | select, radio, checkboxes |
optionsType |
list | text (opciones fijas) o tablename (opciones desde otra tabla) |
optionsText |
list (text) | `"opcion1,opcion2, |
optionsTablename |
list (tablename) | Tabla origen (sin cms_) |
optionsValueField |
list (tablename) | Campo del valor (típico: num) |
optionsLabelField |
list (tablename) | Campo de la etiqueta |
optionsQuery |
list (tablename) | Filtro WHERE adicional |
filterField |
list (tablename) | Filtro dinámico por valor de otro campo |
allowedExtensions |
upload | "jpg,png,webp,pdf" |
maxUploads |
upload | Número máximo de archivos |
createThumbnails |
upload | 1 o 0 |
maxThumbnailWidth / maxThumbnailHeight |
upload | px |
fieldWidth / fieldHeight |
upload | px sugeridos al builder |
adminOnly |
todos | 1 oculta el campo en formularios públicos |
charsetRule |
textfield | Restricciones de caracteres |
tipoTags |
wysiwyg | Tags HTML permitidos |
Ejemplo: lista desde tabla
create_field({
tableName: "vacantes",
fieldName: "categoria_num",
label: "Categoría",
type: "list",
initialProps: {
listType: "select",
optionsType: "tablename",
optionsTablename: "categorias",
optionsValueField: "num",
optionsLabelField: "nombre"
}
})
Actualizar un campo — update_field
update_field({
tableName: "vacantes",
fieldName: "descripcion",
newFieldName: "descripcion_corta", // OPCIONAL — renombra columna MySQL
props: {
label: "Descripción Corta",
maxLength: 200
}
})
Casos destructivos
newFieldNamerenombra la columna MySQL. Los datos se preservan, pero rompe cualquier referencia hardcodeada (Twig, hooks, JS, queryDB). Audita el código antes de renombrar.- Cambiar
typepuede coercer/truncar datos (ej.wysiwyg→textfieldelimina HTML). El backend devuelvewarningsen la respuesta — muéstralos al usuario.
Borrar un campo — delete_field
delete_field({
tableName: "vacantes",
fieldName: "campo_obsoleto",
dropColumn: false // default
})
dropColumn: false→ solo elimina del schema. Si la columna MySQL tiene datos, el backend rechaza y devuelvedataCountpara que avises al usuario.dropColumn: true→ALTER TABLE DROP COLUMN. Los datos de esa columna se pierden permanentemente.
Borrar una tabla — delete_table
delete_table({
tableName: "tabla_obsoleta",
dropData: false, // default
dryRun: true // pre-flight check
})
dryRun: true→ no borra nada, solo reportarecordCount. Úsalo siempre antes de pedir confirmación al usuario.dropData: false→ solo borra el schema (.ini.php). Si la tabla MySQL tiene registros, el backend rechaza.dropData: true→DROP TABLE+ delete schema. Datos perdidos permanentemente.
Reordenar — reorder_tables, reorder_fields
Pasa la lista completa ordenada de nombres. Solo cambia el orden visual, los datos no se tocan.
reorder_tables({ order: ["apartados", "blog", "productos", "vacantes"] })
reorder_fields({
tableName: "vacantes",
order: ["titulo", "descripcion", "salario_minimo", "fecha_publicacion", "visible"]
})
Los campos del sistema (num, creationDate, etc.) se ignoran automáticamente.
Actualizar metadata — update_table_metadata
Modifica el bloque [meta] del schema.
update_table_metadata({
tableName: "vacantes",
newTableName: "ofertas_empleo", // OPCIONAL — renombra tabla MySQL
meta: {
menuName: "Ofertas de Empleo",
menuOrder: 3,
listPageFields: "titulo,fecha_publicacion,visible",
breadcrumbField: "titulo"
}
})
Keys aceptadas en meta:
menuName, menuDesc, menuType, menuOrder, menuDisplay, menuHidden, controller, breadcrumbField, breadcrumbByLink, breadcrumbParentNum, listPageFields (csv), listPageOrder, listPageSearchFields.
newTableName renombra la tabla MySQL y rompe cualquier referencia hardcodeada (controllers custom, módulos con SQL embebido, queryDB en plantillas). Audita el código antes y avisa al usuario.
Regenerar enlaces — regenerate_enlaces
Regenera el campo enlace (slug) de todos los registros de una tabla. Cambia URLs públicas — todo lo que apunte a las antiguas dará 404 a menos que actives los aliases.
regenerate_enlaces({
tableName: "vacantes",
generateAlias: true // recomendado si la tabla ya es pública
})
generateAlias: false(default) — solo actualizaenlace. URLs antiguas → 404.generateAlias: true— escribe entradas enalias_urlspara redirigir las URLs antiguas a las nuevas. Más seguro.
Listar tablas — list_tables
Devuelve todas las tablas con su menuName, menuType, menuOrder y tableName. Sin prefijo cms_. Úsalo cuando necesites un inventario rápido.
Tipos de campo y formato al insertar/actualizar registros
Al usar create_or_update_record, cada tipo espera un formato específico:
| Tipo | Formato | Ejemplo |
|---|---|---|
textfield |
String | "Texto" |
textbox |
String multilínea | "Línea 1\nLínea 2" |
date/datetime |
YYYY-MM-DD HH:mm:ss |
"2025-12-03 10:30:00" |
wysiwyg |
String HTML | "<p>Texto</p>" |
list |
String o número | "activo" o "1" (num si es FK) |
checkbox |
Número 1/0 | 1 o 0 |
multitext |
String JSON | "[{\"item\":\"valor\"}]" |
upload |
NO enviar | Usa upload_record_image después de crear el registro |
Ver 06-hooks-and-cmsapi.md para los detalles de CmsApi::insert / update.
Flujo canónico — Funcionalidad nueva tipo "vacantes"
create_table({ tableName: "vacantes", menuType: "multi", enlace: true, seoMetas: true })— pregunta al usuario si quiereenlaceyseoMetas.create_fieldpara cada campo:titulo,descripcion(wysiwyg),salario_minimo(textfield),categoria_num(list desde tabla),fecha_publicacion(date),fecha_expiracion(date),visible(checkbox),imagen_destacada(upload).- Crear sección general
template/estandar/modulos/custom-vacantes/index-base.tplconacai-write(compila automáticamente). - (Opcional) Módulo de listado
vacantes_listado_xxxxxxque liste registros con'vacantes' | get('visible=1', 'fecha_publicacion DESC', 20). - (Opcional) Página índice
/vacantes/enapartadoscon el módulo de listado.
Reglas críticas
- Tabla sin prefijo
cms_en todas las tools. PK siemprenum. - Antes de cualquier operación:
get_table_schemapara confirmar nombres y tipos de campo. - Pregunta al usuario antes de
create_tablesobreenlaceyseoMetas(decisiones de arquitectura). dropData,dropColumn,newFieldName,newTableNameson destructivos o irreversibles — pide confirmación explícita.regenerate_enlaces: usagenerateAlias: truesi la tabla ya tiene tráfico público.- Surfacea los
warningsque el backend devuelve (cambios de tipo, renames, conteos de datos en riesgo). - Upload fields no se setean en insert/update — usa
upload_record_imagedespués.