273 lines
12 KiB
Markdown
273 lines
12 KiB
Markdown
# 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]` con `menuName`, `menuType`, `menuOrder`, `controller`, etc.
|
|
|
|
Antes de operar sobre una tabla, **siempre** consulta el schema:
|
|
- `list_tables` — inventario rápido del proyecto
|
|
- `get_table_schema` con `tableName` (sin `cms_`) — schema completo
|
|
- `get_table_schema` con `minimal=true` — solo nombres + tipos + labels (ahorra tokens)
|
|
- `get_table_schema` con `filterFields="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|false`** es una decisión de arquitectura. **PREGUNTA AL USUARIO** antes de llamar:
|
|
- `true` → la tabla genera URLs públicas, automáticamente añade campo `enlace` + slug. Cada registro será una página y puede tener detalle vía `custom-{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
|
|
|
|
1. Añade los campos necesarios con `create_field` (uno por uno).
|
|
2. Si la tabla tiene `enlace: true`, considera crear la sección general `custom-{tableName}` para el detalle (ver `03-modules-and-sections.md`).
|
|
3. Si la tabla quiere ordenar/filtrar por fechas, añade campos `fecha_publicacion`, `fecha_expiracion`, `visible` (ver `04-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,|valor3,etiqueta3"` |
|
|
| `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
|
|
|
|
- **`newFieldName`** renombra la columna MySQL. Los datos se preservan, pero **rompe cualquier referencia hardcodeada** (Twig, hooks, JS, queryDB). Audita el código antes de renombrar.
|
|
- **Cambiar `type`** puede coercer/truncar datos (ej. `wysiwyg` → `textfield` elimina HTML). El backend devuelve `warnings` en 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 devuelve `dataCount` para 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 reporta `recordCount`. **Ú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 actualiza `enlace`. URLs antiguas → 404.
|
|
- `generateAlias: true` — escribe entradas en `alias_urls` para 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"
|
|
|
|
1. `create_table({ tableName: "vacantes", menuType: "multi", enlace: true, seoMetas: true })` — pregunta al usuario si quiere `enlace` y `seoMetas`.
|
|
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. Crear sección general `template/estandar/modulos/custom-vacantes/index-base.tpl` con `acai-write` (compila automáticamente).
|
|
4. (Opcional) Módulo de listado `vacantes_listado_xxxxxx` que liste registros con `'vacantes' | get('visible=1', 'fecha_publicacion DESC', 20)`.
|
|
5. (Opcional) Página índice `/vacantes/` en `apartados` con el módulo de listado.
|
|
|
|
## Reglas críticas
|
|
|
|
1. **Tabla sin prefijo `cms_`** en todas las tools. PK siempre `num`.
|
|
2. **Antes de cualquier operación**: `get_table_schema` para confirmar nombres y tipos de campo.
|
|
3. **Pregunta al usuario antes de `create_table`** sobre `enlace` y `seoMetas` (decisiones de arquitectura).
|
|
4. **`dropData`, `dropColumn`, `newFieldName`, `newTableName`** son destructivos o irreversibles — pide confirmación explícita.
|
|
5. **`regenerate_enlaces`**: usa `generateAlias: true` si la tabla ya tiene tráfico público.
|
|
6. **Surfacea los `warnings`** que el backend devuelve (cambios de tipo, renames, conteos de datos en riesgo).
|
|
7. **Upload fields no se setean en insert/update** — usa `upload_record_image` después.
|