This commit is contained in:
Jordan Diaz
2026-04-09 20:46:03 +00:00
parent 4c73d848bb
commit 237dc00379
10 changed files with 1049 additions and 1216 deletions

View File

@@ -1,695 +0,0 @@
# Ejemplos de Builder Vue - Producción
Colección de ejemplos reales de archivos `builder.vue` implementados en producción. Cada ejemplo incluye el código completo y notas sobre decisiones de diseño importantes.
---
## Ejemplo 1: Banner Slideshow
### Descripción
Banner hero con slideshow de imágenes o video de fondo, overlay configurable, textos principales (pretítulo, título, subtítulo) y botón de llamada a la acción.
### Características principales
- **5 tabs organizados**: Configuración, Imágenes, Textos, Enlaces, Colores
- **Selector imagen/video**: Toggle con iconos que alterna entre imagen y video con `v-show`
- **Overlay completo**: Tipo (sin degradado/con degradado), color y opacidad agrupados en tab Imágenes
- **Colorpickers**: Para overlay y color de texto general con textfield oculto
- **Toggles con iconos**: Sombra (X/check), tipo imagen (foto/video), tipo overlay (cuadrado/degradado)
- **Logo adicional**: Upload de logo que se superpone al banner
- **Configuraciones globales**: Posición texto, sombra, container, altura banner
### Decisiones de diseño clave
1. **Selector imagen/video como primer campo del tab Imágenes**: El toggle de tipo de fondo está al inicio del tab Imágenes, antes de los uploads, según la regla 10.1
2. **v-show en uploads**:
- Upload de imágenes: `v-show="data.tipodeimagen && data.tipodeimagen.newValues.builder_custom.value == ''"`
- Upload de video: `v-show="data.tipodeimagen && data.tipodeimagen.newValues.builder_custom.value == '1'"`
- NUNCA quitar estos `v-show`, son esenciales
3. **Grupo overlay en tab Imágenes**: El grupo completo (tipo + color + opacidad) está en Imágenes, NO en Colores, porque afecta directamente al fondo visual (regla 10.2)
4. **Radio borde en tab Enlaces**: Campo que afecta al botón va en el tab del enlace, no en Configuración (regla 10.3)
5. **Recuerda con HTML escapado**: El campo título incluye un "Recuerda" con etiquetas HTML escapadas (`<span>`) para guiar al usuario
6. **Color del texto en tab Colores**: El color general del texto va en su propio tab, no mezclado con el overlay
### Tabs configurados
```javascript
tabsConfig: [
{ id: "configuracion", label: "Configuración", color: "#f59e0b", icon: '<svg>...</svg>' },
{ id: "imagenes", label: "Imágenes", color: "#10b981", icon: '<svg>...</svg>' },
{ id: "textos", label: "Textos", color: "#3b82f6", icon: '<svg>...</svg>' },
{ id: "enlaces", label: "Enlaces", color: "#ef4444", icon: '<svg>...</svg>' },
{ id: "colores", label: "Colores", color: "#8b5cf6", icon: '<svg>...</svg>' }
]
```
### Componentes utilizados
- `acai-vue-tabs` - Sistema de tabs con storage-key y apply-theme-styles
- `acai-vue-selectv2` - Selectores (algunos con `:toggle-icons`)
- `acai-vue-textfield` - Campos de texto simple (pretítulo, subtítulo)
- `acai-vue-title` - Encabezado principal con placeholder
- `acai-vue-linkv2` - Enlaces con `:show_text="true"`
- `acai-vue-upload` - Uploads de imagen/video/logo con todas las props necesarias
- `acai-vue-colorpicker` - Pickers de color con textfield oculto asociado
### Iconos con toggle
```javascript
iconosSombra: {
'': '<svg>...(icon-tabler-x)</svg>',
'1': '<svg>...(icon-tabler-check)</svg>'
},
iconosTipoImagen: {
'': '<svg>...(icon-tabler-photo)</svg>',
'1': '<svg>...(icon-tabler-video)</svg>'
},
iconosOverlay: {
'': '<svg>...(icon-tabler-square)</svg>',
'1': '<svg>...(icon-tabler-gradient)</svg>'
}
```
### Código completo
```vue
<template>
<div v-if="data">
<acai-vue-tabs v-if="data" :tabs="tabsConfig" :storage-key="'banner-slideshow-tabs-' + (section_id || 'default')" :apply-theme-styles="true">
<!-- TAB: CONFIGURACIÓN -->
<template #configuracion="{ color }">
<div class="w-full mb-6">
<p class="text-xl font-semibold text-gray-800">Ajustes generales del banner</p>
</div>
<!-- Lado texto -->
<div class="flex w-full items-center">
<div class="w-full">
<div class="flex items-center">
<svg xmlns="http://www.w3.org/2000/svg" class="w-10 h-10 flex-shrink-0 mr-4 stroke-current" :style="{ color: color }" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round"><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="M9 15h-2" /><path d="M13 12h-6" /><path d="M11 9h-4" /></svg>
<p class="leading-snug text-gray-600"><b class="text-black">Posición del texto :</b> Define la alineación del contenido dentro del banner.</p>
</div>
<div class="relative mt-2 ml-14">
<acai-vue-selectv2 :builder="builder" :data="data" :field="'ladotexto'" @save-data="saveData"></acai-vue-selectv2>
</div>
</div>
</div>
<!-- Ver sombra -->
<div class="w-full items-center mt-6">
<div class="w-full flex items-center">
<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-shadow"><path stroke="none" d="M0 0h24v24H0z" fill="none"/><path d="M12 12m-9 0a9 9 0 1 0 18 0a9 9 0 1 0 -18 0" /><path d="M13 12h5" /><path d="M13 15h4" /><path d="M13 18h1" /><path d="M13 9h4" /><path d="M13 6h1" /></svg>
</div>
<div class="relative">
<acai-vue-selectv2 :builder="builder" :data="data" :field="'versombra'" :toggle-icons="iconosSombra" @save-data="saveData"></acai-vue-selectv2>
</div>
</div>
<p class="text-xs leading-snug text-gray-500 mt-2 ml-14"><b class="text-gray-700">Sombra en textos :</b> Aplica un efecto de sombra a los textos del banner.</p>
</div>
<!-- Container -->
<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-arrow-autofit-width"><path stroke="none" d="M0 0h24v24H0z" fill="none"/><path d="M4 12v-6a2 2 0 0 1 2 -2h12a2 2 0 0 1 2 2v6" /><path d="M10 18h-7" /><path d="M21 18h-7" /><path d="M6 15l-3 3l3 3" /><path d="M18 15l3 3l-3 3" /></svg>
<p class="leading-snug text-gray-600"><b class="text-black">Ancho del contenedor :</b> Limita el ancho máximo del contenido textual.</p>
</div>
<p class="text-xs leading-snug text-gray-500 mt-2 ml-14"><b class="text-gray-700">Nota :</b> por defecto ocupa todo el ancho disponible (Full container).</p>
<div class="relative mt-2 ml-14">
<acai-vue-selectv2 :builder="builder" :data="data" :field="'container'" @save-data="saveData"></acai-vue-selectv2>
</div>
</div>
</div>
<!-- Altura banner -->
<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-arrow-autofit-height"><path stroke="none" d="M0 0h24v24H0z" fill="none"/><path d="M12 20h-6a2 2 0 0 1 -2 -2v-12a2 2 0 0 1 2 -2h6" /><path d="M18 14v7" /><path d="M18 3v7" /><path d="M15 18l3 3l3 -3" /><path d="M15 6l3 -3l3 3" /></svg>
<p class="leading-snug text-gray-600"><b class="text-black">Altura del banner :</b> Altura visible de la sección del banner.</p>
</div>
<p class="text-xs leading-snug text-gray-500 mt-2 ml-14"><b class="text-gray-700">Nota :</b> por defecto es pantalla completa (100vh).</p>
<div class="relative mt-2 ml-14">
<acai-vue-selectv2 :builder="builder" :data="data" :field="'alturadelbanner'" @save-data="saveData"></acai-vue-selectv2>
</div>
</div>
</div>
</template>
<!-- TAB: IMÁGENES -->
<template #imagenes="{ color }">
<div class="w-full mb-6">
<p class="text-xl font-semibold text-gray-800">Fondo y elementos visuales del banner</p>
</div>
<!-- Tipo de imagen (selector imagen/video) -->
<div class="w-full items-center mt-6">
<div class="w-full flex items-center">
<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="#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>
</div>
<div class="relative">
<acai-vue-selectv2 :builder="builder" :data="data" :field="'tipodeimagen'" :toggle-icons="iconosTipoImagen" @save-data="saveData"></acai-vue-selectv2>
</div>
</div>
<p class="text-xs leading-snug text-gray-500 mt-2 ml-14"><b class="text-gray-700">Tipo de fondo :</b> Selecciona si el fondo del banner será una imagen o un vídeo.</p>
</div>
<!-- Imágenes (visible cuando es imagen o vacío) -->
<div class="flex w-full items-center mt-6" v-show="data.tipodeimagen && data.tipodeimagen.newValues.builder_custom.value == ''">
<div class="w-full">
<div class="flex items-center">
<svg xmlns="http://www.w3.org/2000/svg" :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 stroke-current"><path stroke="none" d="M0 0h24v24H0z" fill="none" /><path d="M15 6l.01 0" /><path d="M3 6a3 3 0 0 1 3 -3h12a3 3 0 0 1 3 3v8a3 3 0 0 1 -3 3h-12a3 3 0 0 1 -3 -3l0 -8" /><path d="M3 13l4 -4a3 5 0 0 1 3 0l4 4" /><path d="M13 12l2 -2a3 5 0 0 1 3 0l3 3" /><path d="M8 21l.01 0" /><path d="M12 21l.01 0" /><path d="M16 21l.01 0" /></svg>
<p class="leading-snug text-gray-600"><b class="text-black">Imágenes :</b> Añade las imágenes que rotarán en el slideshow del banner.</p>
</div>
<div class="relative mt-2 ml-14">
<acai-vue-upload ref="upload_imagenes" :reference="'upload_imagenes'" :tablename="'builder_custom'" :fieldname="builder.vars.imagenes.relations.builder_custom" :recordnum="data.imagenes.recordNum" :field="data.imagenes" :builder_field="builder.vars.imagenes" :presavetempid="data.imagenes.preSaveTempId" :add_button="true" @add_button_click="$parent.openCute('imagenes',data,false,'upload_imagenes')" class="border-2 px-3 py-2 border-gray-600 rounded-lg shadow bg-gray-200"></acai-vue-upload>
</div>
</div>
</div>
<!-- Video (visible cuando es video) -->
<div class="flex w-full items-center mt-6" v-show="data.tipodeimagen && data.tipodeimagen.newValues.builder_custom.value == '1'">
<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-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>
<p class="leading-snug text-gray-600"><b class="text-black">Vídeo :</b> Sube el vídeo de fondo del banner.</p>
</div>
<div class="relative mt-2 ml-14">
<acai-vue-upload ref="upload_video" :reference="'upload_video'" :tablename="'builder_custom'" :fieldname="builder.vars.video.relations.builder_custom" :recordnum="data.video.recordNum" :field="data.video" :builder_field="builder.vars.video" :presavetempid="data.video.preSaveTempId" :add_button="true" @add_button_click="$parent.openCute('video',data,false,'upload_video')" class="border-2 px-3 py-2 border-gray-600 rounded-lg shadow bg-gray-200"></acai-vue-upload>
</div>
</div>
</div>
<!-- Logo -->
<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-icons"><path stroke="none" d="M0 0h24v24H0z" fill="none"/><path d="M6.5 6.5m-3.5 0a3.5 3.5 0 1 0 7 0a3.5 3.5 0 1 0 -7 0" /><path d="M2.5 21h8l-4 -7z" /><path d="M14 3l7 7" /><path d="M14 10l7 -7" /><path d="M14 14h7v7h-7z" /></svg>
<p class="leading-snug text-gray-600"><b class="text-black">Logo :</b> Imagen del logotipo que aparecerá sobre el banner.</p>
</div>
<div class="relative mt-2 ml-14">
<acai-vue-upload ref="upload_logo" :reference="'upload_logo'" :tablename="'builder_custom'" :fieldname="builder.vars.logo.relations.builder_custom" :recordnum="data.logo.recordNum" :field="data.logo" :builder_field="builder.vars.logo" :presavetempid="data.logo.preSaveTempId" :add_button="true" @add_button_click="$parent.openCute('logo',data,false,'upload_logo')" class="border-2 px-3 py-2 border-gray-600 rounded-lg shadow bg-gray-200"></acai-vue-upload>
</div>
</div>
</div>
<!-- Tipo de overlay -->
<div class="w-full items-center mt-6">
<div class="w-full flex items-center">
<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-background"><path stroke="none" d="M0 0h24v24H0z" fill="none"/><path d="M4 8l4 -4" /><path d="M14 4l-10 10" /><path d="M4 20l16 -16" /><path d="M20 10l-10 10" /><path d="M20 16l-4 4" /></svg>
</div>
<div class="relative">
<acai-vue-selectv2 :builder="builder" :data="data" :field="'tipodeoverlay'" :toggle-icons="iconosOverlay" @save-data="saveData"></acai-vue-selectv2>
</div>
</div>
<p class="text-xs leading-snug text-gray-500 mt-2 ml-14"><b class="text-gray-700">Tipo de overlay :</b> Elige si la capa de color se aplica de forma uniforme o con degradado.</p>
</div>
<!-- Color del overlay -->
<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-test-pipe"><path stroke="none" d="M0 0h24v24H0z" fill="none"/><path d="M20 8.04l-12.122 12.124a2.857 2.857 0 1 1 -4.041 -4.04l12.122 -12.124" /><path d="M7 13h8" /><path d="M19 15l1.5 1.6a2 2 0 1 1 -3 0l1.5 -1.6" /><path d="M15 3l6 6" /></svg>
<p class="leading-snug text-gray-600"><b class="text-black">Color del overlay :</b> Color y opacidad de la capa que se superpone sobre la imagen o vídeo.</p>
</div>
<p class="text-xs leading-snug text-gray-500 mt-2 ml-14"><b class="text-gray-700">Nota :</b> por defecto el overlay es transparente.</p>
<div class="relative mt-2 ml-14">
<acai-vue-colorpicker :builder="builder" :data="data" :field="'colordeloverlay'" :label="'Color overlay'" :color="'transparent'" @save-data="saveData"></acai-vue-colorpicker>
</div>
<div style="display: none">
<acai-vue-textfield :builder="builder" :data="data" :field="'colordeloverlay'" @save-data="saveData"></acai-vue-textfield>
</div>
</div>
</div>
</template>
<!-- TAB: TEXTOS -->
<template #textos="{ color }">
<div class="w-full mb-6">
<p class="text-xl font-semibold text-gray-800">Contenido textual del banner</p>
</div>
<!-- Pretítulo -->
<div class="flex w-full items-center">
<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-text-size"><path stroke="none" d="M0 0h24v24H0z" fill="none"/><path d="M3 7v-2h13v2" /><path d="M10 5v14" /><path d="M12 19h-4" /><path d="M15 13v-1h6v1" /><path d="M18 12v7" /><path d="M17 19h2" /></svg>
<p class="leading-snug text-gray-600"><b class="text-black">Pretítulo :</b> Texto que aparece encima del título principal.</p>
</div>
<div class="relative mt-2 ml-14">
<acai-vue-textfield :builder="builder" :data="data" :field="'pretitulo'" :placeholder="'Ej: Bienvenidos'" @save-data="saveData"></acai-vue-textfield>
</div>
</div>
</div>
<!-- Título -->
<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" class="w-10 h-10 flex-shrink-0 mr-4 stroke-current" :style="{ color: color }" viewBox="0 0 24 24" stroke-width="1.5" fill="none" stroke-linecap="round" stroke-linejoin="round"><path stroke="none" d="M0 0h24v24H0z" fill="none" /><path d="M7 12h10" /><path d="M7 4v16" /><path d="M17 4v16" /><path d="M15 20h4" /><path d="M15 4h4" /><path d="M5 20h4" /><path d="M5 4h4" /></svg>
<p class="leading-snug text-gray-600"><b class="text-black">Título :</b> Encabezado principal del banner.</p>
</div>
<p class="text-xs leading-snug text-gray-600 font-light mt-2"><b class="text-black">Recuerda :</b> utiliza las etiquetas <span class="text-black font-semibold">&lt;span&gt; &lt;/span&gt;</span> para resaltar las palabras clave.</p>
<div class="relative mt-2 ml-14">
<acai-vue-title :builder="builder" :data="data" :field="'titulo'" placeholder="Título del banner" @save-data="saveData"></acai-vue-title>
</div>
</div>
</div>
<!-- Subtítulo -->
<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-text-size"><path stroke="none" d="M0 0h24v24H0z" fill="none"/><path d="M3 7v-2h13v2" /><path d="M10 5v14" /><path d="M12 19h-4" /><path d="M15 13v-1h6v1" /><path d="M18 12v7" /><path d="M17 19h2" /></svg>
<p class="leading-snug text-gray-600"><b class="text-black">Subtítulo :</b> Texto que aparece debajo del título principal.</p>
</div>
<div class="relative mt-2 ml-14">
<acai-vue-textfield :builder="builder" :data="data" :field="'subtitulo'" :placeholder="'Ej: Tu solución ideal'" @save-data="saveData"></acai-vue-textfield>
</div>
</div>
</div>
</template>
<!-- TAB: ENLACES -->
<template #enlaces="{ color }">
<div class="w-full mb-6">
<p class="text-xl font-semibold text-gray-800">Botón de llamada a la acción</p>
</div>
<!-- Enlace -->
<div class="flex w-full items-center">
<div class="w-full">
<div class="flex items-center">
<svg xmlns="http://www.w3.org/2000/svg" class="w-10 h-10 flex-shrink-0 mr-4 stroke-current" :style="{ color: color }" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentcolor" fill="none" stroke-linecap="round" stroke-linejoin="round"><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>
<p class="leading-snug text-gray-600"><b class="text-black">Enlace :</b> Configura el botón con su texto y destino.</p>
</div>
<div class="relative mt-2 ml-14">
<acai-vue-linkv2 :builder="builder" :data="data" :field="'enlace'" @save-data="saveData" :show_text="true"></acai-vue-linkv2>
</div>
</div>
</div>
<!-- Radio borde enlace -->
<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 stroke-current icon icon-tabler icons-tabler-outline icon-tabler-border-radius"><path stroke="none" d="M0 0h24v24H0z" fill="none"/><path d="M4 12v-4a4 4 0 0 1 4 -4h4" /><path d="M16 4l0 .01" /><path d="M20 4l0 .01" /><path d="M20 8l0 .01" /><path d="M20 12l0 .01" /><path d="M4 16l0 .01" /><path d="M20 16l0 .01" /><path d="M4 20l0 .01" /><path d="M8 20l0 .01" /><path d="M12 20l0 .01" /><path d="M16 20l0 .01" /><path d="M20 20l0 .01" /></svg>
<p class="leading-snug text-gray-600"><b class="text-black">Radio del borde :</b> Redondeo de las esquinas del botón de enlace.</p>
</div>
<p class="text-xs leading-snug text-gray-500 mt-2 ml-14"><b class="text-gray-700">Nota :</b> por defecto es 'sm' (ligeramente redondeado).</p>
<div class="relative mt-2 ml-14">
<acai-vue-selectv2 :builder="builder" :data="data" :field="'radiobordeenlace'" @save-data="saveData"></acai-vue-selectv2>
</div>
</div>
</div>
</template>
<!-- TAB: COLORES -->
<template #colores="{ color }">
<div class="w-full mb-6">
<p class="text-xl font-semibold text-gray-800">Personalización de colores</p>
</div>
<!-- Color del texto -->
<div class="flex w-full items-center">
<div class="w-full">
<div class="flex items-center">
<svg xmlns="http://www.w3.org/2000/svg" class="w-10 h-10 flex-shrink-0 mr-4 stroke-current" :style="{ color: color }" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round"><path stroke="none" d="M0 0h24v24H0z" fill="none" /><path d="M12 21a9 9 0 0 1 0 -18c4.97 0 9 3.582 9 8c0 1.06 -.474 2.078 -1.318 2.828c-.844 .75 -1.989 1.172 -3.182 1.172h-2.5a2 2 0 0 0 -1 3.75a1.3 1.3 0 0 1 -1 2.25" /><path d="M8.5 10.5m-1 0a1 1 0 1 0 2 0a1 1 0 1 0 -2 0" /><path d="M12.5 7.5m-1 0a1 1 0 1 0 2 0a1 1 0 1 0 -2 0" /><path d="M16.5 10.5m-1 0a1 1 0 1 0 2 0a1 1 0 1 0 -2 0" /></svg>
<p class="leading-snug text-gray-600"><b class="text-black">Color del texto :</b> Color general de todos los textos del banner.</p>
</div>
<p class="text-xs leading-snug text-gray-500 mt-2 ml-14"><b class="text-gray-700">Nota :</b> por defecto es blanco (#ffffff).</p>
<div class="relative mt-2 ml-14">
<acai-vue-colorpicker :builder="builder" :data="data" :field="'colordeltexto'" :label="'Color del texto'" :color="'#ffffff'" @save-data="saveData"></acai-vue-colorpicker>
</div>
<div style="display: none">
<acai-vue-textfield :builder="builder" :data="data" :field="'colordeltexto'" @save-data="saveData"></acai-vue-textfield>
</div>
</div>
</div>
</template>
</acai-vue-tabs>
</div>
</template>
<style scoped></style>
<script>
module.exports = {
props: ["active", "section_id"],
data() {
return {
data: null,
builder: null,
idiomas: IDIOMAS,
tabsConfig: [
{ 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: "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>' },
],
iconosSombra: {
'': '<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-shadow-off"><path stroke="none" d="M0 0h24v24H0z" fill="none"/><path d="M5.634 5.638a9 9 0 0 0 12.728 12.727m1.68 -2.32a9 9 0 0 0 -12.086 -12.088" /><path d="M16 12h2" /><path d="M13 15h2" /><path d="M13 18h1" /><path d="M13 9h4" /><path d="M13 6h1" /><path d="M3 3l18 18" /></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-shadow"><path stroke="none" d="M0 0h24v24H0z" fill="none"/><path d="M3 12a9 9 0 1 0 18 0a9 9 0 1 0 -18 0" /><path d="M13 12h5" /><path d="M13 15h4" /><path d="M13 18h1" /><path d="M13 9h4" /><path d="M13 6h1" /></svg>'
},
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>'
},
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>'
}
};
},
components: {
'acai-vue-tabs': httpVueLoader('https://cms.cocosolution.com/lib/plugins/builder_saas/tpl/componentes/builder-acaivuetabs.vue?timestamp=' + new Date().getTime()),
"acai-vue-selectv2": httpVueLoader("https://cms.cocosolution.com/lib/plugins/builder_saas/tpl/componentes/builder-acaivueselect.vue?timestamp=" + new Date().getTime()),
"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-title": httpVueLoader("https://cms.cocosolution.com/lib/plugins/builder_saas/tpl/componentes/builder-acaivuetitle.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-textfield": httpVueLoader("https://cms.cocosolution.com/lib/plugins/builder_saas/tpl/componentes/builder-acaivuetextfield.vue?timestamp=" + new Date().getTime()),
},
mounted() { this.$emit("child-mounted"); },
methods: { saveData() { this.$emit("save-data"); } },
};
</script>
```
---
## Ejemplo 2: [Módulo de texto genérico]
```vue
<template>
<acai-vue-tabs v-if="data" :tabs="tabsConfig" :storage-key="'texto-cabecera-tabs-' + (section_id || 'default')" :apply-theme-styles="true">
<!-- Tab Configuración -->
<template #config="{ color }">
<div class="w-full mb-6">
<p class="text-xl font-semibold text-gray-800">Configuración general del módulo</p>
</div>
<!-- Formato (2 opciones = toggle) -->
<div class="w-full items-center mt-6">
<div class="w-full flex items-center">
<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-layout"><path stroke="none" d="M0 0h24v24H0z" fill="none"/><path d="M4 4m0 2a2 2 0 0 1 2 -2h2a2 2 0 0 1 2 2v1a2 2 0 0 1 -2 2h-2a2 2 0 0 1 -2 -2z" /><path d="M4 13m0 2a2 2 0 0 1 2 -2h2a2 2 0 0 1 2 2v3a2 2 0 0 1 -2 2h-2a2 2 0 0 1 -2 -2z" /><path d="M14 4m0 2a2 2 0 0 1 2 -2h2a2 2 0 0 1 2 2v12a2 2 0 0 1 -2 2h-2a2 2 0 0 1 -2 -2z" /></svg>
</div>
<div class="relative">
<acai-vue-selectv2 :builder="builder" :data="data" :field="'formato'" :toggle-icons="iconosFormato" @save-data="saveData"></acai-vue-selectv2>
</div>
</div>
<p class="text-xs leading-snug text-gray-500 mt-2 ml-14"><b class="text-gray-700">Formato :</b> Vertical (todo en una columna) u Horizontal (cabecera y texto en 2 columnas).</p>
</div>
<!-- Alineación texto -->
<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-align-justified"><path stroke="none" d="M0 0h24v24H0z" fill="none"/><path d="M4 6l16 0" /><path d="M4 12l16 0" /><path d="M4 18l12 0" /></svg>
<p class="leading-snug text-gray-600"><b class="text-black">Alineación texto :</b> Alineación del contenido.</p>
</div>
<p class="text-xs leading-snug text-gray-500 mt-2 ml-14"><b class="text-gray-700">Nota :</b> Por defecto es izquierda.</p>
<div class="relative mt-2 ml-14">
<acai-vue-selectv2 :builder="builder" :data="data" :field="'alineaciontexto'" @save-data="saveData"></acai-vue-selectv2>
</div>
</div>
</div>
<!-- Container texto -->
<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-arrow-autofit-width"><path stroke="none" d="M0 0h24v24H0z" fill="none"/><path d="M4 12v-6a2 2 0 0 1 2 -2h12a2 2 0 0 1 2 2v6" /><path d="M10 18h-7" /><path d="M21 18h-7" /><path d="M6 15l-3 3l3 3" /><path d="M18 15l3 3l-3 3" /></svg>
<p class="leading-snug text-gray-600"><b class="text-black">Container texto :</b> Ancho máximo del contenido.</p>
</div>
<p class="text-xs leading-snug text-gray-500 mt-2 ml-14"><b class="text-gray-700">Nota :</b> Por defecto ocupa el ancho completo.</p>
<div class="relative mt-2 ml-14">
<acai-vue-selectv2 :builder="builder" :data="data" :field="'containertexto'" @save-data="saveData"></acai-vue-selectv2>
</div>
</div>
</div>
<!-- Estilo enlace -->
<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-click"><path stroke="none" d="M0 0h24v24H0z" fill="none"/><path d="M3 12l3 0" /><path d="M12 3l0 3" /><path d="M7.8 7.8l-2.2 -2.2" /><path d="M16.2 7.8l2.2 -2.2" /><path d="M7.8 16.2l-2.2 2.2" /><path d="M12 12l9 3l-4 2l-2 4l-3 -9" /></svg>
<p class="leading-snug text-gray-600"><b class="text-black">Estilo enlace :</b> Estilo visual del botón.</p>
</div>
<p class="text-xs leading-snug text-gray-500 mt-2 ml-14"><b class="text-gray-700">Nota :</b> Por defecto usa el color principal (Main color).</p>
<div class="relative mt-2 ml-14">
<acai-vue-selectv2 :builder="builder" :data="data" :field="'estiloenlace'" @save-data="saveData"></acai-vue-selectv2>
</div>
</div>
</div>
<!-- Radio borde enlace -->
<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 stroke-current icon icon-tabler icons-tabler-outline icon-tabler-border-radius"><path stroke="none" d="M0 0h24v24H0z" fill="none"/><path d="M4 12v-4a4 4 0 0 1 4 -4h4" /><path d="M16 4l0 .01" /><path d="M20 4l0 .01" /><path d="M20 8l0 .01" /><path d="M20 12l0 .01" /><path d="M4 16l0 .01" /><path d="M20 16l0 .01" /><path d="M4 20l0 .01" /><path d="M8 20l0 .01" /><path d="M12 20l0 .01" /><path d="M16 20l0 .01" /><path d="M20 20l0 .01" /></svg>
<p class="leading-snug text-gray-600"><b class="text-black">Radio borde enlace :</b> Redondeo del botón.</p>
</div>
<p class="text-xs leading-snug text-gray-500 mt-2 ml-14"><b class="text-gray-700">Nota :</b> Por defecto es "sm".</p>
<div class="relative mt-2 ml-14">
<acai-vue-selectv2 :builder="builder" :data="data" :field="'radiobordeenlace'" @save-data="saveData"></acai-vue-selectv2>
</div>
</div>
</div>
</template>
<!-- Tab Textos -->
<template #textos="{ color }">
<div class="w-full mb-6">
<p class="text-xl font-semibold text-gray-800">Contenido textual del módulo</p>
</div>
<!-- Pretítulo -->
<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-text-size"><path stroke="none" d="M0 0h24v24H0z" fill="none"/><path d="M3 7v-2h13v2" /><path d="M10 5v14" /><path d="M12 19h-4" /><path d="M15 13v-1h6v1" /><path d="M18 12v7" /><path d="M17 19h2" /></svg>
<p class="leading-snug text-gray-600"><b class="text-black">Pretítulo :</b> Texto que aparece encima del título principal.</p>
</div>
<div class="relative mt-2 ml-14">
<acai-vue-textfield :builder="builder" :data="data" :field="'pretitulo'" :placeholder="'Ej: Descubre más'" @save-data="saveData"></acai-vue-textfield>
</div>
</div>
</div>
<!-- Título -->
<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 stroke-current icon icon-tabler icons-tabler-outline icon-tabler-heading"><path stroke="none" d="M0 0h24v24H0z" fill="none"/><path d="M7 12h10" /><path d="M7 5v14" /><path d="M17 5v14" /><path d="M15 19h4" /><path d="M15 5h4" /><path d="M5 19h4" /><path d="M5 5h4" /></svg>
<p class="leading-snug text-gray-600"><b class="text-black">Título :</b> Encabezado principal del módulo.</p>
</div>
<p class="text-xs leading-snug text-gray-600 font-light mt-2"><b class="text-black">Recuerda :</b> utiliza las etiquetas <span class="text-black font-semibold">&lt;span&gt; &lt;/span&gt;</span> para resaltar las palabras clave.</p>
<div class="relative mt-2 ml-14">
<acai-vue-title :builder="builder" :data="data" :field="'titulo'" placeholder="Título del módulo" @save-data="saveData"></acai-vue-title>
</div>
</div>
</div>
<!-- Subtítulo -->
<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-text-size"><path stroke="none" d="M0 0h24v24H0z" fill="none"/><path d="M3 7v-2h13v2" /><path d="M10 5v14" /><path d="M12 19h-4" /><path d="M15 13v-1h6v1" /><path d="M18 12v7" /><path d="M17 19h2" /></svg>
<p class="leading-snug text-gray-600"><b class="text-black">Subtítulo :</b> Texto que aparece debajo del título principal.</p>
</div>
<div class="relative mt-2 ml-14">
<acai-vue-textfield :builder="builder" :data="data" :field="'subtitulo'" :placeholder="'Ej: Conoce nuestros servicios'" @save-data="saveData"></acai-vue-textfield>
</div>
</div>
</div>
<!-- Texto -->
<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-file-text"><path stroke="none" d="M0 0h24v24H0z" fill="none"/><path d="M14 3v4a1 1 0 0 0 1 1h4" /><path d="M17 21h-10a2 2 0 0 1 -2 -2v-14a2 2 0 0 1 2 -2h7l5 5v11a2 2 0 0 1 -2 2" /><path d="M9 9l1 0" /><path d="M9 13l6 0" /><path d="M9 17l6 0" /></svg>
<p class="leading-snug text-gray-600"><b class="text-black">Texto :</b> Contenido descriptivo principal.</p>
</div>
<div class="relative mt-2 ml-14">
<acai-vue-wysiwyg :builder="builder" :data="data" :field="'texto'" @save-data="saveData"></acai-vue-wysiwyg>
</div>
</div>
</div>
</template>
<!-- Tab Enlaces -->
<template #enlaces="{ color }">
<div class="w-full mb-6">
<p class="text-xl font-semibold text-gray-800">Enlaces del módulo</p>
</div>
<!-- Enlace -->
<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-link"><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>
<p class="leading-snug text-gray-600"><b class="text-black">Enlace :</b> Botón de acción principal.</p>
</div>
<p class="text-xs leading-snug text-gray-500 mt-2 ml-14"><b class="text-gray-700">Nota :</b> Si no se configura, el botón no se mostrará.</p>
<div class="relative mt-2 ml-14">
<acai-vue-linkv2 :builder="builder" :data="data" :field="'enlace'" :show_text="true" @save-data="saveData"></acai-vue-linkv2>
</div>
</div>
</div>
</template>
<!-- Tab Colores -->
<template #colores="{ color }">
<div class="w-full mb-6">
<p class="text-xl font-semibold text-gray-800">Personalización de colores</p>
</div>
<!-- Color de fondo -->
<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-paint"><path stroke="none" d="M0 0h24v24H0z" fill="none"/><path d="M5 3m0 2a2 2 0 0 1 2 -2h10a2 2 0 0 1 2 2v2a2 2 0 0 1 -2 2h-10a2 2 0 0 1 -2 -2z" /><path d="M19 6h1a2 2 0 0 1 2 2a5 5 0 0 1 -5 5l-5 0v2" /><path d="M10 15m0 1a1 1 0 0 1 1 -1h2a1 1 0 0 1 1 1v4a1 1 0 0 1 -1 1h-2a1 1 0 0 1 -1 -1z" /></svg>
<p class="leading-snug text-gray-600"><b class="text-black">Color de fondo :</b> Color de fondo de toda la sección.</p>
</div>
<p class="text-xs leading-snug text-gray-500 mt-2 ml-14"><b class="text-gray-700">Nota :</b> Por defecto es transparente.</p>
<div class="relative mt-2 ml-14">
<acai-vue-colorpicker :builder="builder" :data="data" :field="'colordefondo'" :label="'Color de fondo'" :color="'transparent'" @save-data="saveData"></acai-vue-colorpicker>
</div>
<div style="display: none">
<acai-vue-textfield :builder="builder" :data="data" :field="'colordefondo'" @save-data="saveData"></acai-vue-textfield>
</div>
</div>
</div>
<!-- Color del pretítulo -->
<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-palette"><path stroke="none" d="M0 0h24v24H0z" fill="none"/><path d="M12 21a9 9 0 0 1 0 -18c4.97 0 9 3.582 9 8c0 1.06 -.474 2.078 -1.318 2.828c-.844 .75 -1.989 1.172 -3.182 1.172h-2.5a2 2 0 0 0 -1 3.75a1.3 1.3 0 0 1 -1 2.25" /><path d="M7.5 10.5m-1 0a1 1 0 1 0 2 0a1 1 0 1 0 -2 0" /><path d="M11.5 7.5m-1 0a1 1 0 1 0 2 0a1 1 0 1 0 -2 0" /><path d="M15.5 10.5m-1 0a1 1 0 1 0 2 0a1 1 0 1 0 -2 0" /></svg>
<p class="leading-snug text-gray-600"><b class="text-black">Color del pretítulo :</b> Color del texto del pretítulo.</p>
</div>
<p class="text-xs leading-snug text-gray-500 mt-2 ml-14"><b class="text-gray-700">Nota :</b> Por defecto es #111827.</p>
<div class="relative mt-2 ml-14">
<acai-vue-colorpicker :builder="builder" :data="data" :field="'colordelpretitulo'" :label="'Color pretítulo'" :color="'#111827'" @save-data="saveData"></acai-vue-colorpicker>
</div>
<div style="display: none">
<acai-vue-textfield :builder="builder" :data="data" :field="'colordelpretitulo'" @save-data="saveData"></acai-vue-textfield>
</div>
</div>
</div>
<!-- Color del título -->
<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-palette"><path stroke="none" d="M0 0h24v24H0z" fill="none"/><path d="M12 21a9 9 0 0 1 0 -18c4.97 0 9 3.582 9 8c0 1.06 -.474 2.078 -1.318 2.828c-.844 .75 -1.989 1.172 -3.182 1.172h-2.5a2 2 0 0 0 -1 3.75a1.3 1.3 0 0 1 -1 2.25" /><path d="M7.5 10.5m-1 0a1 1 0 1 0 2 0a1 1 0 1 0 -2 0" /><path d="M11.5 7.5m-1 0a1 1 0 1 0 2 0a1 1 0 1 0 -2 0" /><path d="M15.5 10.5m-1 0a1 1 0 1 0 2 0a1 1 0 1 0 -2 0" /></svg>
<p class="leading-snug text-gray-600"><b class="text-black">Color del título :</b> Color del texto del título principal.</p>
</div>
<p class="text-xs leading-snug text-gray-500 mt-2 ml-14"><b class="text-gray-700">Nota :</b> Por defecto es #111827.</p>
<div class="relative mt-2 ml-14">
<acai-vue-colorpicker :builder="builder" :data="data" :field="'colordeltitulo'" :label="'Color título'" :color="'#111827'" @save-data="saveData"></acai-vue-colorpicker>
</div>
<div style="display: none">
<acai-vue-textfield :builder="builder" :data="data" :field="'colordeltitulo'" @save-data="saveData"></acai-vue-textfield>
</div>
</div>
</div>
<!-- Color título resaltado -->
<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-color-swatch"><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>
<p class="leading-snug text-gray-600"><b class="text-black">Color título resaltado :</b> Color de las palabras resaltadas con &lt;span&gt;.</p>
</div>
<p class="text-xs leading-snug text-gray-500 mt-2 ml-14"><b class="text-gray-700">Nota :</b> Por defecto usa el color principal (Main color).</p>
<div class="relative mt-2 ml-14">
<acai-vue-selectv2 :builder="builder" :data="data" :field="'colortituloresaltado'" @save-data="saveData"></acai-vue-selectv2>
</div>
</div>
</div>
<!-- Color del subtítulo -->
<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-palette"><path stroke="none" d="M0 0h24v24H0z" fill="none"/><path d="M12 21a9 9 0 0 1 0 -18c4.97 0 9 3.582 9 8c0 1.06 -.474 2.078 -1.318 2.828c-.844 .75 -1.989 1.172 -3.182 1.172h-2.5a2 2 0 0 0 -1 3.75a1.3 1.3 0 0 1 -1 2.25" /><path d="M7.5 10.5m-1 0a1 1 0 1 0 2 0a1 1 0 1 0 -2 0" /><path d="M11.5 7.5m-1 0a1 1 0 1 0 2 0a1 1 0 1 0 -2 0" /><path d="M15.5 10.5m-1 0a1 1 0 1 0 2 0a1 1 0 1 0 -2 0" /></svg>
<p class="leading-snug text-gray-600"><b class="text-black">Color del subtítulo :</b> Color del texto del subtítulo.</p>
</div>
<p class="text-xs leading-snug text-gray-500 mt-2 ml-14"><b class="text-gray-700">Nota :</b> Por defecto es #111827.</p>
<div class="relative mt-2 ml-14">
<acai-vue-colorpicker :builder="builder" :data="data" :field="'colordelsubtitulo'" :label="'Color subtítulo'" :color="'#111827'" @save-data="saveData"></acai-vue-colorpicker>
</div>
<div style="display: none">
<acai-vue-textfield :builder="builder" :data="data" :field="'colordelsubtitulo'" @save-data="saveData"></acai-vue-textfield>
</div>
</div>
</div>
<!-- Color texto -->
<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-typography"><path stroke="none" d="M0 0h24v24H0z" fill="none"/><path d="M4 20l3 0" /><path d="M14 20l7 0" /><path d="M6.9 15l6.9 0" /><path d="M10.2 6.3l5.8 13.7" /><path d="M5 20l6 -16l2 0l7 16" /></svg>
<p class="leading-snug text-gray-600"><b class="text-black">Color texto :</b> Color del contenido descriptivo.</p>
</div>
<p class="text-xs leading-snug text-gray-500 mt-2 ml-14"><b class="text-gray-700">Nota :</b> Por defecto es #374151.</p>
<div class="relative mt-2 ml-14">
<acai-vue-colorpicker :builder="builder" :data="data" :field="'colortexto'" :label="'Color texto'" :color="'#374151'" @save-data="saveData"></acai-vue-colorpicker>
</div>
<div style="display: none">
<acai-vue-textfield :builder="builder" :data="data" :field="'colortexto'" @save-data="saveData"></acai-vue-textfield>
</div>
</div>
</div>
</template>
</acai-vue-tabs>
</template>
<script>
module.exports = {
props: ["active", "section_id"],
data() {
return {
data: null,
builder: null,
idiomas: IDIOMAS,
tabsConfig: [
{ id: "config", 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: "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: "enlaces", label: "Enlaces", color: "#ef4444", 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="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>' }
],
iconosFormato: {
'': '<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-layout-rows"><path stroke="none" d="M0 0h24v24H0z" fill="none"/><path d="M4 4m0 2a2 2 0 0 1 2 -2h12a2 2 0 0 1 2 2v12a2 2 0 0 1 -2 2h-12a2 2 0 0 1 -2 -2z" /><path d="M4 12l16 0" /></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-layout-columns"><path stroke="none" d="M0 0h24v24H0z" fill="none"/><path d="M4 4m0 2a2 2 0 0 1 2 -2h12a2 2 0 0 1 2 2v12a2 2 0 0 1 -2 2h-12a2 2 0 0 1 -2 -2z" /><path d="M12 4l0 16" /></svg>'
}
};
},
components: {
'acai-vue-tabs': httpVueLoader('https://cms.cocosolution.com/lib/plugins/builder_saas/tpl/componentes/builder-acaivuetabs.vue?timestamp=' + new Date().getTime()),
'acai-vue-selectv2': httpVueLoader('https://cms.cocosolution.com/lib/plugins/builder_saas/tpl/componentes/builder-acaivueselect.vue?timestamp=' + new Date().getTime()),
'acai-vue-colorpicker': httpVueLoader('https://cms.cocosolution.com/lib/plugins/builder_saas/tpl/componentes/builder-acaivuecolorpicker.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-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-textfield': httpVueLoader('https://cms.cocosolution.com/lib/plugins/builder_saas/tpl/componentes/builder-acaivuetextfield.vue?timestamp=' + new Date().getTime()),
},
mounted() { this.$emit("child-mounted"); },
methods: { saveData() { this.$emit("save-data"); } },
};
</script>
```

View File

@@ -1,484 +0,0 @@
# 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).