Files
acai-scaffold/docs/vue-builder-rules.md
2026-03-23 21:34:03 +00:00

484 lines
28 KiB
Markdown

# Reglas CMS-VUE
Aplica estas reglas ÚNICAMENTE cuando el usuario incluya "cms-vue" o "CMS-VUE" (en cualquier combinación de mayúsculas/minúsculas) en su mensaje. Ejemplos válidos: "dame el cms-vue", "cms-vue personalizado", "crea el CMS-VUE", "necesito el cms-vue de este módulo". Si el mensaje NO contiene "cms-vue", ignora completamente estas instrucciones.
---
## 1. Estructura general: Tabs (`acai-vue-tabs`)
- Analiza el HTML proporcionado para determinar cuántos tabs son necesarios y cómo nombrarlos.
- Tabs base comunes: **Configuración**, **Imágenes**, **Textos**, **Bloques** (records), **Enlaces**, **Colores**.
- Añade tabs adicionales si el módulo lo requiere (ej: "Formulario", "Video", "Overlay", "Slider", etc.).
- Si un tab solo tendría 1 campo, evalúa fusionarlo con otro tab relacionado.
- Cada tab tiene su propio `id`, `label`, `color` e `icon` (SVG inline).
- SVG dentro del template usan `:style="{ color: color }"` para heredar el color del tab.
- Textos descriptivos claros y orientados al usuario final del CMS.
- Usa `storage-key` único: `'nombre-modulo-tabs-' + (section_id || 'default')`.
- Siempre añade `:apply-theme-styles="true"`.
- **IMPORTANTE:** La prop para pasar los tabs es `:tabs` (NO `:tabs-config`).
- **IMPORTANTE:** Siempre añadir `v-if="data"` en el `<acai-vue-tabs>` para evitar renderizar antes de que los datos estén listos.
### Template de cada tab:
```html
<template #idtab="{ color }">
<div class="w-full mb-6">
<p class="text-xl font-semibold text-gray-800">Título descriptivo del tab</p>
</div>
<!-- campos -->
</template>
```
---
## 2. Colorpicker según contexto
### 2.1 En tab "Colores" (campos generales de color de texto/fondo)
Siempre con SVG + título + descripción + colorpicker + textfield oculto:
```html
<div class="flex w-full items-center mt-6">
<div class="w-full">
<div class="flex items-center">
<svg class="w-10 h-10 flex-shrink-0 mr-4 stroke-current" :style="{ color: color }" ...>...</svg>
<p class="leading-snug text-gray-600"><b class="text-black">Nombre :</b> Descripción.</p>
</div>
<p class="text-xs leading-snug text-gray-500 mt-2 ml-14"><b class="text-gray-700">Nota :</b> valor por defecto.</p>
<div class="relative mt-2 ml-14">
<acai-vue-colorpicker :builder="builder" :data="data" :field="'campo'" :label="'Etiqueta'" :color="'#hex'" @save-data="saveData"></acai-vue-colorpicker>
</div>
<div style="display: none">
<acai-vue-textfield :builder="builder" :data="data" :field="'campo'" @save-data="saveData"></acai-vue-textfield>
</div>
</div>
</div>
```
### 2.2 Colorpicker en otros tabs (ej: color del overlay en tab Imágenes)
Misma estructura con SVG + título + descripción + colorpicker + textfield oculto, pero usando el `color` del tab donde se encuentre. Se coloca junto a los campos relacionados (ver regla 10.2).
### 2.3 Dentro de `<acai-vue-records>` (sin icono ni descripción)
Se coloca debajo del campo al que corresponde:
- Nota con `mt-4` si campo anterior es `textfield` o `title`.
- Nota con `mt-3` si campo anterior es `textbox` o `wysiwyg`.
- Colorpicker siempre con `mt-1`.
```html
<p class="text-xs leading-snug text-gray-500 mt-4 ml-14"><b class="text-gray-700">Nota :</b> color por defecto (#hex).</p>
<div class="relative mt-1 ml-14">
<acai-vue-colorpicker :builder="builder.vars.records" :data="record" :field="'campo'" :label="'Etiqueta'" :color="'#hex'" @save-data="saveData"></acai-vue-colorpicker>
</div>
<div style="display: none">
<acai-vue-textfield :builder="builder.vars.records" :data="record" :field="'campo'" @save-data="saveData"></acai-vue-textfield>
</div>
```
### 2.4 Campos tipo `list` para colores
Se usan como `<acai-vue-selectv2>` con icono y nota. El componente detecta automáticamente si las opciones son colores y muestra el modo color selector con swatches. No llevan colorpicker ni textfield oculto.
### 2.5 Extraer color por defecto
Del HTML: `style="color: {{ campo ? campo : '#HEX' }}"` → usar `#HEX`. Si no hay color, usar `#111827` (textos) o `transparent` (fondos).
---
## 3. Campos: estructura en tabs
### Fuera de records:
```html
<div class="flex w-full items-center mt-6">
<div class="w-full">
<div class="flex items-center">
<svg class="w-10 h-10 flex-shrink-0 mr-4 stroke-current" :style="{ color: color }" ...>...</svg>
<p class="leading-snug text-gray-600"><b class="text-black">Nombre :</b> Descripción.</p>
</div>
<p class="text-xs leading-snug text-gray-500 mt-2 ml-14"><b class="text-gray-700">Nota :</b> info adicional.</p>
<div class="relative mt-2 ml-14">
<!-- componente -->
</div>
</div>
</div>
```
### Dentro de records:
```html
<div class="w-full mt-6">
<div class="flex items-center">
<svg class="w-10 h-10 flex-shrink-0 mr-4 stroke-current" :style="{ color: color }" ...>...</svg>
<p class="leading-snug text-gray-600"><b class="text-black">Nombre :</b> Descripción.</p>
</div>
<div class="relative mt-2 ml-14">
<!-- componente con builder.vars.records y record -->
</div>
</div>
```
---
## 4. Nombres de campos (`:field`)
- Construir uniendo palabras del `data-field-label` en minúsculas sin espacios.
- Eliminar acentos: á→a, é→e, í→i, ó→o, ú→u, ñ→(eliminar).
- Ejemplos: `Color del título``colordeltitulo`, `Valoración``valoracion`, `Tamaño``tamao`.
---
## 5. Upload de imágenes
### General:
```html
<acai-vue-upload ref="upload_campo" :reference="'upload_campo'" :tablename="'builder_custom'" :fieldname="builder.vars.campo.relations.builder_custom" :recordnum="data.campo.recordNum" :field="data.campo" :builder_field="builder.vars.campo" :presavetempid="data.campo.preSaveTempId" :add_button="true" @add_button_click="$parent.openCute('campo',data,false,'upload_campo')" class="border-2 px-3 py-2 border-gray-600 rounded-lg shadow bg-gray-200"></acai-vue-upload>
```
### En records:
```html
<acai-vue-upload :ref="'upload_campo_' + builder.vars.records.vars.campo.relations.builder_custom + '_' + record.campo.recordNum" :reference="'upload_campo_' + builder.vars.records.vars.campo.relations.builder_custom + '_' + record.campo.recordNum" :tablename="'builder_custom'" :fieldname="builder.vars.records.vars.campo.relations.builder_custom" :recordnum="record.campo.recordNum" :field="record.campo" :builder_field="builder.vars.records.vars.campo" :presavetempid="record.campo.preSaveTempId" :add_button="true" @add_button_click="$parent.openCute('campo',record,true,'upload_campo_' + builder.vars.records.vars.campo.relations.builder_custom + '_' + record.campo.recordNum)" class="border-2 px-3 py-2 border-gray-600 rounded-lg shadow bg-gray-200"></acai-vue-upload>
```
---
## 6. Componentes y URLs
Solo incluir los que se usen. Los componentes personalizados (tabs, selectv2) se cargan desde impulse; los estándar desde cocosolution:
```javascript
// ── Componentes personalizados (impulse) ──
'acai-vue-tabs': httpVueLoader('https://impulse.webserver2.plandeweb.com/template/estandar/css/builder-acaivuetabsv2.vue?timestamp=' + new Date().getTime()),
'acai-vue-selectv2': httpVueLoader('https://impulse.webserver2.plandeweb.com/template/estandar/css/builder-acaivueselect-v2.vue?timestamp=' + new Date().getTime()),
// ── Componentes estándar (cocosolution) ──
'acai-vue-colorpicker': httpVueLoader('https://cms.cocosolution.com/lib/plugins/builder_saas/tpl/componentes/builder-acaivuecolorpicker.vue?timestamp=' + new Date().getTime()),
'acai-vue-upload': httpVueLoader('https://cms.cocosolution.com/lib/plugins/builder_saas/tpl/componentes/builder-acaivueupload.vue?timestamp=' + new Date().getTime()),
'acai-vue-records': httpVueLoader('https://cms.cocosolution.com/lib/plugins/builder_saas/tpl/componentes/builder-acaivuerecords.vue?timestamp=' + new Date().getTime()),
'acai-vue-title': httpVueLoader('https://cms.cocosolution.com/lib/plugins/builder_saas/tpl/componentes/builder-acaivuetitle.vue?timestamp=' + new Date().getTime()),
'acai-vue-wysiwyg': httpVueLoader('https://cms.cocosolution.com/lib/plugins/builder_saas/tpl/componentes/builder-acaivuewysiwyg.vue?timestamp=' + new Date().getTime()),
'acai-vue-linkv2': httpVueLoader('https://cms.cocosolution.com/lib/plugins/builder_saas/tpl/componentes/builder-acaivuelinkv2.vue?timestamp=' + new Date().getTime()),
'acai-vue-textbox': httpVueLoader('https://cms.cocosolution.com/lib/plugins/builder_saas/tpl/componentes/builder-acaivuetextbox.vue?timestamp=' + new Date().getTime()),
'acai-vue-textfield': httpVueLoader('https://cms.cocosolution.com/lib/plugins/builder_saas/tpl/componentes/builder-acaivuetextfield.vue?timestamp=' + new Date().getTime()),
'acai-vue-datepicker': httpVueLoader('https://cms.cocosolution.com/lib/plugins/builder_saas/tpl/componentes/builder-acaivuedatepicker.vue?timestamp=' + new Date().getTime()),
```
**IMPORTANTE:** `acai-vue-list` ha sido reemplazado por `acai-vue-selectv2` en todos los VUEs. NO usar `acai-vue-list` en nuevos VUEs.
---
## 7. Mapeo HTML → Vue
| `data-field-type` | Componente |
|---|---|
| `textfield` | `acai-vue-textfield` |
| `headfield` | `acai-vue-title` |
| `wysiwyg` | `acai-vue-wysiwyg` |
| `textbox` | `acai-vue-textbox` |
| `list` | `acai-vue-selectv2` |
| `upload` / `uploadMulti` | `acai-vue-upload` |
| `linkv2` | `acai-vue-linkv2` (siempre con `:show_text="true"`) |
| `multiv2` | `acai-vue-records` |
| `textfield` (usado como fecha) | `acai-vue-datepicker` + `acai-vue-textfield` oculto |
---
## 8. Script base
```javascript
module.exports = {
props: ["active", "section_id"],
data() {
return {
data: null,
builder: null,
idiomas: IDIOMAS,
tabsConfig: [ /* tabs */ ],
// iconos para toggles (solo si hay campos de 2 opciones con iconos)
// iconosNombreCampo: { '': '<svg>...</svg>', '1': '<svg>...</svg>' }
};
},
components: { /* solo los usados */ },
mounted() { this.$emit("child-mounted"); },
methods: { saveData() { this.$emit("save-data"); } },
};
```
---
## 9. Decisión de tabs según contenido HTML y contexto semántico
### 9.1 Organización contextual (PRIORITARIA)
**IMPORTANTE:** Primero analizar el **nombre del campo** para determinar su contexto semántico, independientemente del tipo. Un campo `list` llamado "tipo de imagen" debe ir en el tab **Imágenes**, no en Configuración.
#### Keywords para tab Imágenes:
Campos que contengan: `imagen`, `photo`, `video`, `fondo`, `background`, `logo`, `icono`, `icon`
**Ejemplos:**
- ✅ "tipo de imagen" (list) → **Imágenes**
- ✅ "video de fondo" (list) → **Imágenes**
- ✅ "logo principal" (upload) → **Imágenes**
#### Keywords para tab Enlaces:
Campos que contengan: `enlace`, `link`, `boton`, `button`, `url`, `href`
**Ejemplos:**
- ✅ "texto del botón" (textfield) → **Enlaces**
- ✅ "url externa" (textfield) → **Enlaces**
- ✅ "estilo del enlace" (list) → **Enlaces**
#### Keywords para tab Textos:
Campos que contengan: `titulo`, `title`, `texto`, `text`, `descripcion`, `description`, `contenido`, `content`, `label`, `etiqueta`
**Ejemplos:**
- ✅ "título principal" (headfield) → **Textos**
- ✅ "descripción corta" (textfield) → **Textos**
### 9.2 Organización por tipo (fallback)
Si el nombre del campo **no** coincide con ninguna keyword, usar el tipo:
| Tipo | Tab |
|---|---|
| `headfield`, `textfield`, `textbox`, `wysiwyg` | Textos |
| `upload`, `image` | Imágenes |
| `linkv2` | Enlaces |
| `list`, `select` (sin contexto) | Configuración |
| `multiv2` (records) | Bloques |
| Otros campos de configuración | Configuración |
---
## 10. Reglas especiales
### 10.1 Selector imagen/video con v-show
Cuando el HTML tenga un campo `list` con opciones tipo `"|Imagen,1|Video"`:
- El selector "Tipo de fondo" va en el tab **Imágenes** como **primer campo** (encima de los uploads).
- Se renderiza como toggle con iconos (foto/vídeo) usando `acai-vue-selectv2` con `:toggle-icons`.
- Usa el icono `icon-tabler-photo-video`:
```html
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" :style="{ color: color }" fill="none" stroke="#ffffff" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" class="w-10 h-10 flex-shrink-0 mr-4 stroke-current icon icon-tabler icons-tabler-outline icon-tabler-photo-video"><path stroke="none" d="M0 0h24v24H0z" fill="none"/><path d="M9 15h-3a3 3 0 0 1 -3 -3v-6a3 3 0 0 1 3 -3h6a3 3 0 0 1 3 3v3" /><path d="M9 12a3 3 0 0 1 3 -3h6a3 3 0 0 1 3 3v6a3 3 0 0 1 -3 3h-6a3 3 0 0 1 -3 -3l0 -6" /><path d="M3 12l2.296 -2.296a2.41 2.41 0 0 1 3.408 0l.296 .296" /><path d="M14 13.5v3l2.5 -1.5l-2.5 -1.5" /><path d="M7 6v.01" /></svg>
```
- El upload de **imágenes** lleva: `v-show="data.tipodeimagen && data.tipodeimagen.newValues.builder_custom.value == ''"` (visible cuando es imagen o vacío).
- El upload de **video** lleva: `v-show="data.tipodeimagen && data.tipodeimagen.newValues.builder_custom.value == '1'"` (visible cuando es video).
- **NUNCA quitar estos `v-show`**, son esenciales para mostrar uno u otro según la selección.
- Iconos del toggle:
```javascript
iconosTipoImagen: {
'': '<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" class="icon icon-tabler icons-tabler-outline icon-tabler-photo"><path stroke="none" d="M0 0h24v24H0z" fill="none"/><path d="M15 8h.01" /><path d="M3 6a3 3 0 0 1 3 -3h12a3 3 0 0 1 3 3v12a3 3 0 0 1 -3 3h-12a3 3 0 0 1 -3 -3v-12z" /><path d="M3 16l5 -5c.928 -.893 2.072 -.893 3 0l5 5" /><path d="M14 14l1 -1c.928 -.893 2.072 -.893 3 0l3 3" /></svg>',
'1': '<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" class="icon icon-tabler icons-tabler-outline icon-tabler-video"><path stroke="none" d="M0 0h24v24H0z" fill="none"/><path d="M15 10l4.553 -2.276a1 1 0 0 1 1.447 .894v6.764a1 1 0 0 1 -1.447 .894l-4.553 -2.276v-4z" /><path d="M3 6m0 2a2 2 0 0 1 2 -2h8a2 2 0 0 1 2 2v8a2 2 0 0 1 -2 2h-8a2 2 0 0 1 -2 -2z" /></svg>'
}
```
### 10.2 Grupo overlay (tipo + color + opacidad)
Cuando el HTML contenga campos de overlay (tipo de overlay, color del overlay, opacidad del overlay):
- Los tres campos van **juntos** en el tab **Imágenes**, **debajo** de la imagen/video sobre la que se aplica el overlay.
- El orden es: tipo de overlay → color del overlay (colorpicker) → opacidad del overlay.
- El **color del overlay NO va en el tab Colores**, va en Imágenes junto al resto del grupo overlay.
- El tipo de overlay (2 opciones: Sin degradado / Con degradado) se renderiza como toggle con iconos:
```javascript
iconosOverlay: {
'': '<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" class="icon icon-tabler icons-tabler-outline icon-tabler-square"><path stroke="none" d="M0 0h24v24H0z" fill="none"/><path d="M3 3m0 2a2 2 0 0 1 2 -2h14a2 2 0 0 1 2 2v14a2 2 0 0 1 -2 2h-14a2 2 0 0 1 -2 -2z" /></svg>',
'1': '<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" class="icon icon-tabler icons-tabler-outline icon-tabler-gradient"><path stroke="none" d="M0 0h24v24H0z" fill="none"/><path d="M3 3m0 2a2 2 0 0 1 2 -2h14a2 2 0 0 1 2 2v14a2 2 0 0 1 -2 2h-14a2 2 0 0 1 -2 -2z" /><path d="M7 3v18" /><path d="M3 14h4" /><path d="M3 10h4" /><path d="M3 6h4" /><path d="M3 18h4" /></svg>'
}
```
### 10.3 Campos que afectan al enlace
Los campos `list` que modifican propiedades del botón de enlace (radio borde, estilo, etc.) van en el tab **Enlaces**, debajo del campo `linkv2` al que afectan. NO van en Configuración ni en Imágenes.
### 10.4 Tabs base: definición fija de id, label, color e icono
Los tabs base siempre usan la siguiente definición fija. Este es el orden por defecto; solo se incluyen los tabs que el módulo necesite. Tabs adicionales (ej: "Formulario", "Video") se crean con id, label, color e icono nuevos.
```javascript
{ id: "configuracion", label: "Configuración", color: "#f59e0b", icon: '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" fill="none"><path stroke="none" d="M0 0h24v24H0z" fill="none"/><path d="M10.325 4.317c.426 -1.756 2.924 -1.756 3.35 0a1.724 1.724 0 0 0 2.573 1.066c1.543 -.94 3.31 .826 2.37 2.37a1.724 1.724 0 0 0 1.065 2.572c1.756 .426 1.756 2.924 0 3.35a1.724 1.724 0 0 0 -1.066 2.573c.94 1.543 -.826 3.31 -2.37 2.37a1.724 1.724 0 0 0 -2.572 1.065c-.426 1.756 -2.924 1.756 -3.35 0a1.724 1.724 0 0 0 -2.573 -1.066c-1.543 .94 -3.31 -.826 -2.37 -2.37a1.724 1.724 0 0 0 -1.065 -2.572c-1.756 -.426 -1.756 -2.924 0 -3.35a1.724 1.724 0 0 0 1.066 -2.573c-.94 -1.543 .826 -3.31 2.37 -2.37c1 .608 2.296 .07 2.572 -1.065z"/><path d="M9 12a3 3 0 1 0 6 0a3 3 0 0 0 -6 0"/></svg>' },
{ id: "imagenes", label: "Imágenes", color: "#10b981", icon: '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path stroke="none" d="M0 0h24v24H0z" fill="none"/><path d="M15 8h.01" /><path d="M11 20h-4a3 3 0 0 1 -3 -3v-10a3 3 0 0 1 3 -3h10a3 3 0 0 1 3 3v4" /><path d="M4 15l4 -4c.928 -.893 2.072 -.893 3 0l3 3" /><path d="M14 14l1 -1c.31 -.298 .644 -.497 .987 -.596" /><path d="M18.42 15.61a2.1 2.1 0 0 1 2.97 2.97l-3.39 3.42h-3v-3l3.42 -3.39" /></svg>' },
{ id: "textos", label: "Textos", color: "#3b82f6", icon: '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path stroke="none" d="M0 0h24v24H0z" fill="none"/><path d="M19 10h-14" /><path d="M5 6h14" /><path d="M14 14h-9" /><path d="M5 18h6" /><path d="M18 15v6" /><path d="M15 18h6" /></svg>' },
{ id: "bloques", label: "Bloques", color: "#ec4899", icon: '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path stroke="none" d="M0 0h24v24H0z" fill="none"/><path d="M12 4l-8 4l8 4l8 -4l-8 -4" /><path d="M4 12l8 4l8 -4" /><path d="M4 16l8 4l8 -4" /></svg>' },
{ id: "enlaces", label: "Enlaces", color: "#ef4444", icon: '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none"><path stroke="none" d="M0 0h24v24H0z" fill="none"/><path d="M9 15l6 -6"/><path d="M11 6l.463 -.536a5 5 0 0 1 7.071 7.072l-.534 .464"/><path d="M13 18l-.397 .534a5.068 5.068 0 0 1 -7.127 0a4.972 4.972 0 0 1 0 -7.071l.524 -.463"/></svg>' },
{ id: "colores", label: "Colores", color: "#8b5cf6", icon: '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path stroke="none" d="M0 0h24v24H0z" fill="none"/><path d="M19 3h-4a2 2 0 0 0 -2 2v12a4 4 0 0 0 8 0v-12a2 2 0 0 0 -2 -2" /><path d="M13 7.35l-2 -2a2 2 0 0 0 -2.828 0l-2.828 2.828a2 2 0 0 0 0 2.828l9 9" /><path d="M7.3 13h-2.3a2 2 0 0 0 -2 2v4a2 2 0 0 0 2 2h12" /><path d="M17 17l0 .01" /></svg>' },
```
### 10.5 Campos globales que afectan al multi van DENTRO del tab Bloques
Los campos `list` o `textfield` generales (no de records) que afectan visualmente a los elementos del multi (ej: radio de borde de los bloques, alineación del texto de los bloques, diseño del enlace de los bloques) deben colocarse **dentro del tab Bloques**, en la zona **superior**, ANTES del bloque descriptivo "Bloques del multi" y del `<acai-vue-records>`. Estos campos NO van en Configuración ni en otros tabs, ya que pertenecen conceptualmente a los bloques.
### 10.6 Bloque descriptivo "Bloques del multi" antes de acai-vue-records
Siempre añadir un bloque descriptivo con el icono `icon-tabler-stack-2` y el texto "Bloques del multi : Personaliza los bloques del multi." justo antes de `<acai-vue-records>`:
```html
<!-- Multi -->
<div class="flex w-full items-center mt-6">
<div class="w-full">
<div class="flex items-center">
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" :style="{ color: color }" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" class="w-10 h-10 flex-shrink-0 mr-4 icon icon-tabler icons-tabler-outline icon-tabler-stack-2"><path stroke="none" d="M0 0h24v24H0z" fill="none"/><path d="M12 4l-8 4l8 4l8 -4l-8 -4" /><path d="M4 12l8 4l8 -4" /><path d="M4 16l8 4l8 -4" /></svg>
<p class="leading-snug text-gray-600"><b class="text-black">Bloques del multi :</b> Personaliza los bloques del multi.</p>
</div>
</div>
</div>
```
### 10.7 Slot de acai-vue-records: NO desestructurar `color`
El slot de `<acai-vue-records>` NUNCA debe desestructurar `color`. Siempre usar:
```html
<template v-slot="{ record, index }">
```
**NUNCA** usar:
```html
<template v-slot="{ record, color, index }">
```
De esta forma, `color` dentro del multi resuelve al `color` del tab padre (`<template #bloques="{ color }">`), y los iconos SVG con `:style="{ color: color }"` siempre mostrarán el color correcto del tab.
### 10.8 Estructura del componente acai-vue-records
El componente `<acai-vue-records>` siempre debe incluir todas estas props y atributos:
```html
<acai-vue-records :data="data" :builder="builder" :active="active" :section_id="section_id" :root_builder_vue="$parent" ref="recordsNode">
<template v-slot="{ record, index }">
<!-- campos del record -->
</template>
</acai-vue-records>
```
**NUNCA** usar una versión simplificada sin `:active`, `:section_id`, `:root_builder_vue` o `ref`.
### 10.9 Orden del campo "Color título resaltado" en tab Colores
Cuando el módulo tenga un campo de **Color título resaltado** (tipo `list` con opciones Main color / Main color light / Main color dark), este campo debe colocarse **inmediatamente debajo** del campo **Color del título** en el tab **Colores**. Nunca en el tab Textos ni en otra posición del tab Colores.
---
## 11. Consistencia de iconos y textos descriptivos entre VUEs
### 11.1 Iconos
Los campos que ya tienen un icono SVG asignado en VUEs anteriores deben usar SIEMPRE ese mismo icono en todos los VUEs futuros. Solo se crean o personalizan iconos nuevos para campos que no se hayan visto antes en ningún VUE previo.
### 11.2 Textos descriptivos
Los textos descriptivos (título en negrita + descripción + nota) de campos recurrentes (pretítulo, título, subtítulo, texto largo, enlace, color de fondo, color del texto, etc.) deben ser idénticos en todos los VUEs. Solo se modifican si el HTML del módulo revela un comportamiento diferente para ese campo concreto.
### 11.3 Registro de referencia
Usar como referencia los iconos y textos del primer VUE en que apareció cada tipo de campo. Ante cualquier duda, mantener consistencia con lo ya establecido.
---
## 12. Componente acai-vue-selectv2 (reemplazo de acai-vue-list)
### 12.1 Descripción general
`acai-vue-selectv2` reemplaza completamente a `acai-vue-list`. Es un componente inteligente que detecta automáticamente cómo renderizar según el número y tipo de opciones:
- **2 opciones** → modo **toggle** (pill deslizante con animación)
- **2+ opciones con nombres de color** → modo **color selector** (dropdown con swatches)
- **3+ opciones normales** → modo **select** (dropdown estándar con vue-select)
### 12.2 Props
```html
<acai-vue-selectv2
:builder="builder"
:data="data"
:field="'nombrecampo'"
:toggle-icons="iconosObjeto" <!-- opcional, solo para toggles con iconos -->
@save-data="saveData">
</acai-vue-selectv2>
```
### 12.3 Toggle con iconos (`:toggle-icons`)
Para campos de 2 opciones donde se quieran iconos visuales en el toggle, se pasa un objeto con las claves correspondientes a los valores de las opciones:
```javascript
iconosNombreCampo: {
'': '<svg>...</svg>', // icono para la primera opción (valor vacío)
'1': '<svg>...</svg>' // icono para la segunda opción
}
```
Iconos de toggle establecidos:
- **Lado texto (2 opciones: Izquierda/Derecha):** `icon-tabler-align-box-left-middle` / `icon-tabler-align-box-right-middle`
- **Ver sombra (No/Si):** `icon-tabler-x` / `icon-tabler-check`
- **Tipo imagen (Imagen/Video):** `icon-tabler-photo` / `icon-tabler-video`
- **Tipo overlay (Sin degradado/Con degradado):** `icon-tabler-square` / `icon-tabler-gradient`
### 12.4 Modo color automático
El componente detecta automáticamente si las opciones son colores cuando al menos la mitad de las labels coinciden con:
- Nombres del mapa interno: main color, blanco, negro, gris, gris claro, gris oscuro, gris calido, rojo, azul, verde, etc. (español e inglés)
- Códigos hex (#fff, #ff0000)
- Valores rgb/rgba
- Valores hsl/hsla
Los colores main color, main color light y main color dark se resuelven consultando la configuración del CMS en tiempo real.
### 12.5 Campos de 3+ opciones sin iconos
No necesitan `:toggle-icons`. Se renderizan como dropdown estándar:
```html
<acai-vue-selectv2 :builder="builder" :data="data" :field="'container'" @save-data="saveData"></acai-vue-selectv2>
```
---
## 13. Componente acai-vue-datepicker (campos de fecha)
### 13.1 Uso
Cuando un campo `textfield` en el HTML se usa para fechas (se identifica por el label "Fecha" o similar), se usa `acai-vue-datepicker` junto con un `acai-vue-textfield` oculto:
```html
<div class="relative mt-2">
<acai-vue-datepicker :builder="builder" :data="data" :field="'fecha'" :label="'Fecha'" @save-data="saveData"></acai-vue-datepicker>
</div>
<div style="display: none">
<acai-vue-textfield :builder="builder" :data="data" :field="'fecha'" @save-data="saveData"></acai-vue-textfield>
</div>
```
### 13.2 Notas estándar para datepicker
```html
<p class="text-xs leading-snug text-gray-500 mt-2 ml-14"><b class="text-gray-700">Nota :</b> puedes elegir el formato de la fecha en el selector.</p>
<p class="text-xs leading-snug text-gray-500 mt-1 ml-14"><b class="text-gray-700">Recuerda :</b> también puedes mostrar la hora activando el botón del reloj.</p>
```
### 13.3 Icono estándar para fecha
```html
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" :style="{ color: color }" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" class="w-10 h-10 flex-shrink-0 mr-4 icon icon-tabler icons-tabler-outline icon-tabler-calendar-week"><path stroke="none" d="M0 0h24v24H0z" fill="none" /><path d="M4 7a2 2 0 0 1 2 -2h12a2 2 0 0 1 2 2v12a2 2 0 0 1 -2 2h-12a2 2 0 0 1 -2 -2v-12" /><path d="M16 3v4" /><path d="M8 3v4" /><path d="M4 11h16" /><path d="M7 14h.013" /><path d="M10.01 14h.005" /><path d="M13.01 14h.005" /><path d="M16.015 14h.005" /><path d="M13.015 17h.005" /><path d="M7.01 17h.005" /><path d="M10.01 17h.005" /></svg>
```
---
## 14. Reglas de spacing (separación entre elementos)
### 14.1 Separación entre nota/recuerda y componente
- El componente siempre lleva `mt-2` respecto a la nota o recuerda que lo precede.
- Nunca `mt-1` entre nota/recuerda y componente.
### 14.2 Nota y Recuerda juntos
Cuando un campo tiene **Nota** y **Recuerda**:
- **Nota** siempre lleva `mt-2` respecto al bloque de icono+texto anterior.
- **Recuerda** lleva `mt-1` respecto a la Nota (va justo debajo).
- El componente lleva `mt-2` respecto al Recuerda.
Ejemplo:
```html
<p class="text-xs leading-snug text-gray-500 mt-2 ml-14"><b class="text-gray-700">Nota :</b> texto de la nota.</p>
<p class="text-xs leading-snug text-gray-500 mt-1 ml-14"><b class="text-gray-700">Recuerda :</b> texto del recuerda.</p>
<div class="relative mt-2 ml-14">
<!-- componente -->
</div>
```
### 14.3 Solo Nota (sin Recuerda)
```html
<p class="text-xs leading-snug text-gray-500 mt-2 ml-14"><b class="text-gray-700">Nota :</b> texto.</p>
<div class="relative mt-2 ml-14">
<!-- componente -->
</div>
```
### 14.4 Solo Recuerda (sin Nota)
El Recuerda usa el estilo especial (sin ml-14, con font-light):
```html
<p class="text-xs leading-snug text-gray-600 font-light mt-2"><b class="text-black">Recuerda :</b> texto del recuerda.</p>
<div class="relative mt-2 ml-14">
<!-- componente -->
</div>
```
### 14.5 Sin Nota ni Recuerda
El componente lleva `mt-2` directamente:
```html
<div class="relative mt-2 ml-14">
<!-- componente -->
</div>
```
### 14.6 Separación entre campos
Siempre `mt-6` entre bloques de campo:
```html
<div class="flex w-full items-center mt-6">
```
El primer campo de cada tab NO lleva `mt-6` (no hay campo previo).