Files
analiticaNucleo/nuxt4-app/docs/THEME_SYSTEM.md
josedario87 ed08fa8957
All checks were successful
build-and-deploy / build-and-deploy (push) Successful in 50s
Docs: Actualizar documentación completa del sistema de temas v2.1
- Actualizado THEME_SYSTEM.md con nuevas variables --brand-cosecha-2/3/4
- Documentadas implementaciones CSS con color-mix() en todas las clases
- Agregada sección 'Mejoras Recientes (v2.1)' en THEME_SYSTEM.md
- Actualizada descripción de Surface en settings.vue (cards grandes, tooltips)
- Mejorada sección 'Alcance del Sistema de Temas' con ejemplos de clases CSS
- Agregado tip sobre cards grandes usando color-mix()
- Versión actualizada a 2.1 | Fecha: 2025-10-31

Todos los componentes ahora responden completamente a cambios de tema
2025-10-31 11:14:20 -06:00

22 KiB

Sistema de Temas - Analítica Núcleo

Documentación definitiva del sistema de temas personalizable Versión: 2.1 | Última actualización: 2025-10-31

Índice

  1. Introducción
  2. Arquitectura del Sistema
  3. Variables CSS Disponibles
  4. Reglas de Uso Obligatorias
  5. Clases Utilitarias
  6. Composable useTheme()
  7. Ejemplos de Uso
  8. Integración con Nuxt UI
  9. Casos Especiales
  10. Troubleshooting

Introducción

Analítica Núcleo cuenta con un sistema de temas completamente dinámico que permite personalizar todos los colores de la interfaz en tiempo real. Los cambios se aplican instantáneamente a todos los componentes de la aplicación.

Características principales:

  • 8 variables de color personalizables
  • 4 temas predefinidos (Café, Azul, Verde, Carbón)
  • Persistencia en localStorage
  • Vista previa en tiempo real
  • Exportar/importar temas en JSON
  • Integración completa con Nuxt UI

Arquitectura del Sistema

Flujo de Datos

Usuario modifica tema en /settings
         ↓
   useTheme() composable
         ↓
   Actualiza variables CSS (--brand-*)
         ↓
   Variables --ui-color-neutral-* mapeadas automáticamente
         ↓
   Todos los componentes se actualizan instantáneamente

Archivos Clave

Archivo Propósito
app/assets/css/main.css Definición de variables CSS y clases utilitarias
app/composables/useTheme.ts Lógica del sistema de temas
app/pages/settings.vue Interfaz de configuración

Variables CSS Disponibles

Variables Base (--brand-*)

Estas variables controlan la apariencia general de la aplicación:

Variable Uso Ejemplo de valor
--brand-bg Fondo principal de la app #14100b
--brand-surface Fondos de cards, modales, elementos elevados #1f180f
--brand-border Bordes de todos los elementos #3a2a16
--brand-primary Color principal para enlaces, títulos destacados #e0c080
--brand-primary-strong Variante más intensa del primario #c08040
--brand-accent Highlights, elementos de énfasis #ffe0a0
--brand-text Texto principal (alta legibilidad) #fef9f0
--brand-text-muted Texto secundario, descripciones #d8c7a6

Variables Intermedias para Gráficas (--brand-cosecha-*)

Estas variables se usan en las gráficas de comparativa de cosechas para diferenciar visualmente múltiples series:

Variable Uso Ejemplo de valor
--brand-cosecha-2 Tono intermedio 1 para gráficas de cosechas #8b6f47
--brand-cosecha-3 Tono intermedio 2 para gráficas de cosechas #a0826e
--brand-cosecha-4 Tono intermedio 3 para gráficas de cosechas #b89968

Nota: Estas variables se ajustan automáticamente cuando cambias el tema, manteniendo la armonía visual con los colores primarios.

Variables de Tipos de Café (--coffee-*)

Estas variables se usan para identificar visualmente los tipos de café en gráficas y tablas:

Variable Uso Ejemplo de valor
--coffee-uva Color de identificación para Café Uva #a855f7 (Purple)
--coffee-oreado Color de identificación para Café Oreado #f97316 (Orange)
--coffee-mojado Color de identificación para Café Mojado #06b6d4 (Cyan)
--coffee-verde Color de identificación para Café Verde #22c55e (Green)

Variables de Estados (--status-*)

Estas variables se usan para indicar estados de pago en tablas y badges:

Variable Uso Ejemplo de valor
--status-pendiente Estado de pago pendiente #f59e0b (Amber)
--status-pagado Estado pagado completamente #10b981 (Emerald)
--status-anulado Estado anulado o cancelado #6b7280 (Gray)

Variables de Nuxt UI (--ui-color-neutral-*)

⚠️ NO uses estas variables directamente. Están mapeadas automáticamente a --brand-* en main.css.

Mapeo actual (NO modificar):

--ui-color-neutral-50: var(--brand-text);           /* Texto más claro */
--ui-color-neutral-100: var(--brand-text-muted);    /* Texto secundario */
--ui-color-neutral-200: var(--brand-primary);       /* Primario claro */
--ui-color-neutral-300: var(--brand-primary);       /* Primario */
--ui-color-neutral-400: var(--brand-primary-strong); /* Primario fuerte */
--ui-color-neutral-500: var(--brand-border);        /* Bordes */
--ui-color-neutral-600: var(--brand-border);        /* Bordes */
--ui-color-neutral-700: var(--brand-surface);       /* Surface */
--ui-color-neutral-800: var(--brand-surface);       /* Surface */
--ui-color-neutral-900: var(--brand-bg);            /* Fondo más oscuro */
--ui-color-neutral-950: var(--brand-bg);            /* Fondo */

IMPORTANTE: Nuxt UI usa la escala neutral de forma invertida en dark mode:

  • neutral-50/100 → Colores claros (texto)
  • neutral-900/950 → Colores oscuros (fondos)

Reglas de Uso Obligatorias

SIEMPRE Hacer

  1. Usar variables CSS en lugar de colores hardcoded:

    <!--  CORRECTO -->
    <div class="bg-[var(--brand-surface)] text-[var(--brand-text)]">
      Contenido
    </div>
    
  2. Usar clases utilitarias cuando existan:

    <!--  CORRECTO -->
    <div class="brand-card">
      <h2 class="brand-section-title">Título</h2>
    </div>
    
  3. Usar transparencias para hover states:

    <!--  CORRECTO -->
    <button class="hover:bg-[var(--brand-primary)]/10">
      Botón
    </button>
    

NUNCA Hacer

  1. NO usar colores hardcoded:

    <!--  INCORRECTO -->
    <div class="bg-[#1f180f] text-[#fef9f0]">
      Contenido
    </div>
    
  2. NO usar clases gray-scale genéricas:

    <!--  INCORRECTO -->
    <div class="bg-gray-900 text-gray-400 border-gray-800">
      Contenido
    </div>
    
  3. NO mezclar variables con hardcoded:

    <!--  INCORRECTO -->
    <div class="bg-[#1f180f] text-[var(--brand-text)]">
      Contenido
    </div>
    

Clases Utilitarias

⚠️ IMPORTANTE: Todas las clases utilitarias ahora usan color-mix() y variables CSS dinámicas. Esto significa que todas responden automáticamente a los cambios de tema.

.brand-shell

Contenedor principal con gradiente radial dinámico.

<div class="brand-shell">
  <!-- Contenido de la página -->
</div>

Implementación CSS:

.brand-shell {
  background: radial-gradient(circle at 20% 20%,
      color-mix(in srgb, var(--brand-accent) 8%, transparent), transparent 55%),
    radial-gradient(circle at 80% 10%,
      color-mix(in srgb, var(--brand-primary-strong) 8%, transparent), transparent 45%),
    var(--brand-bg);
}

.brand-card

Tarjetas con estilos consistentes (gradiente, borde, sombra). Ahora completamente dinámicas.

<div class="brand-card">
  <h3>Título</h3>
  <p>Contenido</p>
</div>

Implementación CSS:

.brand-card {
  background: linear-gradient(145deg,
    color-mix(in srgb, var(--brand-bg) 95%, transparent),
    color-mix(in srgb, var(--brand-surface) 85%, transparent));
  border: 1px solid color-mix(in srgb, var(--brand-primary) 12%, transparent);
  box-shadow: 0 12px 40px color-mix(in srgb, var(--brand-bg) 55%, transparent);
}

.brand-divider

Divisor horizontal decorativo.

<div class="brand-divider"></div>

Implementación CSS:

.brand-divider {
  border-top: 1px solid color-mix(in srgb, var(--brand-primary) 16%, transparent);
}

.brand-section-title

Títulos de sección con color primario.

<h2 class="brand-section-title">Mi Sección</h2>

.brand-chip / .brand-pill

Tags o badges pequeños con estilos dinámicos.

<span class="brand-chip">Estado</span>
<span class="brand-pill">Badge</span>

Implementación CSS:

.brand-chip {
  background: color-mix(in srgb, var(--brand-accent) 8%, transparent);
  border: 1px solid color-mix(in srgb, var(--brand-accent) 18%, transparent);
  color: var(--brand-accent);
}

.brand-pill {
  background: color-mix(in srgb, var(--brand-primary) 14%, transparent);
  border: 1px solid color-mix(in srgb, var(--brand-primary) 28%, transparent);
  color: var(--brand-primary);
}

.brand-badge

Badge con fondo primario sólido.

<span class="brand-badge">3</span>

.brand-table

Estilos para tablas con hover dinámico.

<table class="brand-table">
  <thead>...</thead>
  <tbody>...</tbody>
</table>

Implementación CSS:

.brand-table thead {
  background: linear-gradient(90deg,
    color-mix(in srgb, var(--brand-surface) 95%, transparent),
    color-mix(in srgb, var(--brand-bg) 95%, transparent));
}

.brand-table tbody tr:hover {
  background: color-mix(in srgb, var(--brand-accent) 6%, transparent);
}

Composable useTheme()

Importar

const {
  theme,
  applyTheme,
  saveTheme,
  resetTheme,
  exportTheme,
  importTheme
} = useTheme()

API

theme (Ref)

Estado reactivo del tema actual.

interface ThemeColors {
  bg: string
  surface: string
  border: string
  primary: string
  primaryStrong: string
  accent: string
  text: string
  textMuted: string
}

applyTheme(newTheme?: ThemeColors)

Aplica el tema a las variables CSS del DOM.

applyTheme() // Aplica theme.value actual
applyTheme(customTheme) // Aplica un tema personalizado

saveTheme()

Guarda el tema actual en localStorage.

const success = saveTheme()
if (success) {
  console.log('Tema guardado')
}

resetTheme()

Resetea al tema por defecto (Café).

resetTheme()

exportTheme()

Exporta el tema actual como JSON string.

const jsonTheme = exportTheme()
await navigator.clipboard.writeText(jsonTheme)

importTheme(jsonString: string, autoSave: boolean = false)

Importa un tema desde JSON string.

const success = importTheme(jsonString, true) // true = guardar automáticamente

Ejemplos de Uso

Ejemplo 1: Card Básica

<template>
  <UCard
    :ui="{
      base: 'bg-[var(--brand-surface)]',
      ring: 'ring-1 ring-[var(--brand-border)]',
      header: { base: 'border-b border-[var(--brand-border)]' }
    }"
  >
    <template #header>
      <h3 class="text-[var(--brand-text)]">Mi Card</h3>
    </template>

    <p class="text-[var(--brand-text-muted)]">
      Descripción del contenido
    </p>
  </UCard>
</template>

Ejemplo 2: Botón con Hover

<template>
  <button
    class="px-4 py-2 rounded-lg bg-[var(--brand-surface)] border border-[var(--brand-border)] text-[var(--brand-text)] hover:bg-[var(--brand-primary)]/10 hover:border-[var(--brand-primary)] transition-colors"
  >
    Mi Botón
  </button>
</template>

Ejemplo 3: Input Personalizado

<template>
  <UInput
    v-model="value"
    placeholder="Ingresa un valor"
    :ui="{
      base: 'bg-[var(--brand-surface)] border-[var(--brand-border)] text-[var(--brand-text)]',
      placeholder: 'placeholder:text-[var(--brand-text-muted)]'
    }"
  />
</template>
<template>
  <NuxtLink
    to="/perfil"
    class="text-[var(--brand-accent)] hover:text-[var(--brand-primary)] hover:underline transition-colors"
  >
    Ver perfil 
  </NuxtLink>
</template>

Ejemplo 5: Iconos

<template>
  <UIcon
    name="i-lucide-star"
    class="size-5 text-[var(--brand-primary)]"
  />
  <UIcon
    name="i-lucide-info"
    class="size-4 text-[var(--brand-accent)]"
  />
</template>

Ejemplo 6: Manipular Tema Programáticamente

<script setup lang="ts">
const { theme, applyTheme, saveTheme } = useTheme()

// Modificar un color
const cambiarColorPrimario = () => {
  theme.value.primary = '#60a5fa'
  applyTheme()
  saveTheme()
}

// Aplicar tema personalizado completo
const aplicarTemaAzul = () => {
  theme.value = {
    bg: '#0a0e1a',
    surface: '#151a28',
    border: '#2d3748',
    primary: '#60a5fa',
    primaryStrong: '#3b82f6',
    accent: '#93c5fd',
    text: '#f0f4f8',
    textMuted: '#cbd5e1',
    // Colores de café (mantener consistentes)
    coffeeUva: '#a855f7',
    coffeeOreado: '#f97316',
    coffeeMojado: '#06b6d4',
    coffeeVerde: '#22c55e',
    // Colores de estados (mantener consistentes)
    statusPendiente: '#f59e0b',
    statusPagado: '#10b981',
    statusAnulado: '#6b7280'
  }
  applyTheme()
  saveTheme()
}
</script>

<template>
  <div>
    <button @click="cambiarColorPrimario">Cambiar Primary</button>
    <button @click="aplicarTemaAzul">Aplicar Tema Azul</button>
  </div>
</template>

Ejemplo 7: Usar Colores de Tipos de Café

<template>
  <!-- Badge para tipo de café -->
  <UBadge
    :style="{ backgroundColor: 'var(--coffee-uva)', color: 'white' }"
    size="sm"
  >
    Café Uva
  </UBadge>

  <!-- Indicador visual de tipo -->
  <div class="flex items-center gap-2">
    <div class="w-3 h-3 rounded-full" style="background: var(--coffee-oreado)"></div>
    <span>Café Oreado</span>
  </div>

  <!-- En gráficas (ejemplo con Chart.js) -->
  <script setup>
  const chartData = {
    datasets: [{
      label: 'Uva',
      backgroundColor: 'var(--coffee-uva)',
      data: [10, 20, 30]
    }, {
      label: 'Oreado',
      backgroundColor: 'var(--coffee-oreado)',
      data: [15, 25, 35]
    }]
  }
  </script>
</template>

Ejemplo 8: Usar Colores de Estados

<template>
  <!-- Badge de estado pendiente -->
  <UBadge
    class="text-[var(--status-pendiente)] bg-[var(--status-pendiente)]/10 border border-[var(--status-pendiente)]/30"
    size="sm"
  >
    Pendiente
  </UBadge>

  <!-- Badge de estado pagado -->
  <UBadge
    class="text-[var(--status-pagado)] bg-[var(--status-pagado)]/10 border border-[var(--status-pagado)]/30"
    size="sm"
  >
    Pagado
  </UBadge>

  <!-- Badge de estado anulado -->
  <UBadge
    class="text-[var(--status-anulado)] bg-[var(--status-anulado)]/10 border border-[var(--status-anulado)]/30"
    size="sm"
  >
    Anulado
  </UBadge>

  <!-- Indicador en tabla -->
  <td>
    <div class="flex items-center gap-2">
      <div class="w-2 h-2 rounded-full" :style="{ backgroundColor: 'var(--status-pagado)' }"></div>
      <span>Pagado</span>
    </div>
  </td>
</template>

Integración con Nuxt UI

Componentes que Respetan el Tema Automáticamente

Los siguientes componentes de Nuxt UI NO necesitan customización porque usan --ui-color-neutral-* internamente:

  • UButton (con color="neutral")
  • UBadge (con color="neutral")
  • UInput (parcialmente)
  • UTextarea (parcialmente)
  • USelect (parcialmente)
  • Componentes de navegación (sidebar, navbar)

Componentes que Necesitan :ui Prop

Para estos componentes, debes especificar colores explícitamente:

UCard

<UCard
  :ui="{
    base: 'bg-[var(--brand-surface)]',
    ring: 'ring-1 ring-[var(--brand-border)]',
    body: { padding: 'p-6' },
    header: { base: 'border-b border-[var(--brand-border)]' }
  }"
>
  <!-- Contenido -->
</UCard>

UModal

<UModal
  v-model="isOpen"
  :ui="{
    background: 'bg-[var(--brand-surface)]',
    ring: 'ring-1 ring-[var(--brand-border)]',
    header: { base: 'border-b border-[var(--brand-border)]' }
  }"
>
  <!-- Contenido -->
</UModal>

UDropdown

<UDropdown
  :items="items"
  :ui="{
    background: 'bg-[var(--brand-surface)]',
    ring: 'ring-1 ring-[var(--brand-border)]',
    item: {
      base: 'hover:bg-[var(--brand-primary)]/10'
    }
  }"
>
  <!-- Contenido -->
</UDropdown>

Casos Especiales

Colores Semánticos (Success, Error, Warning)

Para estados semánticos, SÍ puedes usar colores fijos de Tailwind porque no forman parte del tema personalizable:

<template>
  <!--  CORRECTO - Estados semánticos -->
  <div class="text-green-400">Operación exitosa</div>
  <div class="text-red-400">Error al procesar</div>
  <div class="text-yellow-400">Advertencia</div>
  <div class="text-blue-400">Información</div>

  <!-- Pero el fondo y borde deben respetar el tema -->
  <div class="bg-[var(--brand-surface)] border-green-600/30 text-green-400">
    Éxito con tema
  </div>
</template>

Imágenes y Avatares

<template>
  <!-- Avatar con ring temático -->
  <UAvatar
    src="/avatar.jpg"
    :ui="{
      wrapper: 'ring-2 ring-[var(--brand-primary)]/40'
    }"
  />

  <!-- Imagen con borde -->
  <img
    src="/logo.png"
    class="rounded-full border-2 border-[var(--brand-accent)]/40 shadow-lg shadow-[var(--brand-primary-strong)]/25"
  />
</template>

Gradientes

<template>
  <!-- Gradiente usando variables -->
  <div
    class="bg-gradient-to-br"
    :style="{
      '--tw-gradient-from': 'var(--brand-primary)',
      '--tw-gradient-to': 'var(--brand-accent)'
    }"
  >
    Contenido con gradiente
  </div>
</template>

Troubleshooting

Problema 1: Texto Invisible

Síntoma: El texto no se ve en algunos componentes.

Causa: Estás usando un color de fondo donde debería ir un color de texto.

Solución:

<!--  INCORRECTO -->
<div class="bg-[var(--brand-text)] text-[var(--brand-bg)]">

<!--  CORRECTO -->
<div class="bg-[var(--brand-surface)] text-[var(--brand-text)]">

Problema 2: Componentes No Cambian con el Tema

Síntoma: Algunos componentes mantienen colores fijos cuando cambias de tema.

Causa: Estás usando colores hardcoded o clases gray-scale.

Solución: Reemplaza todos los colores fijos con variables --brand-*.

Problema 3: UCard con Fondo Incorrecto

Síntoma: UCard tiene un fondo que no corresponde al tema.

Causa: Falta especificar la prop :ui.

Solución:

<UCard
  :ui="{
    base: 'bg-[var(--brand-surface)]',
    ring: 'ring-1 ring-[var(--brand-border)]'
  }"
>
  <!-- Contenido -->
</UCard>

Problema 4: Hover No Se Ve

Síntoma: Los hover effects no son visibles.

Causa: Estás usando colores sólidos en lugar de transparencias.

Solución:

<!--  INCORRECTO -->
<button class="hover:bg-[var(--brand-primary)]">

<!--  CORRECTO -->
<button class="hover:bg-[var(--brand-primary)]/10">

Checklist Pre-Commit

Antes de hacer commit, verifica:

  • No hay colores hexadecimales hardcoded (#ffffff, #000000, etc.)
  • No hay clases gray-scale genéricas (bg-gray-900, text-gray-500, etc.)
  • Todos los fondos usan --brand-bg o --brand-surface
  • Todos los bordes usan --brand-border
  • Todo el texto usa --brand-text o --brand-text-muted
  • Los elementos interactivos usan --brand-primary o --brand-accent
  • Los hover states usan transparencias (/10, /20, etc.)
  • Se usan clases utilitarias cuando sea posible

Resumen de Comandos Rápidos

<!-- VARIABLES BASE -->

<!-- Fondos -->
bg-[var(--brand-bg)]        <!-- Fondo principal -->
bg-[var(--brand-surface)]   <!-- Cards, modales -->

<!-- Bordes -->
border-[var(--brand-border)]

<!-- Texto -->
text-[var(--brand-text)]        <!-- Texto principal -->
text-[var(--brand-text-muted)]  <!-- Texto secundario -->

<!-- Colores de marca -->
text-[var(--brand-primary)]         <!-- Links, títulos -->
text-[var(--brand-accent)]          <!-- Highlights -->
bg-[var(--brand-primary-strong)]    <!-- Backgrounds intensos -->

<!-- Hover (siempre con transparencia) -->
hover:bg-[var(--brand-primary)]/10
hover:border-[var(--brand-primary)]
hover:text-[var(--brand-accent)]

<!-- VARIABLES DE TIPOS DE CAFÉ -->

<!-- Colores de café -->
bg-[var(--coffee-uva)]      <!-- Purple - Café Uva -->
bg-[var(--coffee-oreado)]   <!-- Orange - Café Oreado -->
bg-[var(--coffee-mojado)]   <!-- Cyan - Café Mojado -->
bg-[var(--coffee-verde)]    <!-- Green - Café Verde -->

<!-- Ejemplo en badges -->
<UBadge :style="{ backgroundColor: 'var(--coffee-uva)', color: 'white' }">
  Café Uva
</UBadge>

<!-- VARIABLES DE ESTADOS -->

<!-- Colores de estados -->
text-[var(--status-pendiente)]  <!-- Amber - Pendiente -->
text-[var(--status-pagado)]     <!-- Green - Pagado -->
text-[var(--status-anulado)]    <!-- Gray - Anulado -->

<!-- Ejemplo en badges con fondo suave -->
<UBadge class="text-[var(--status-pagado)] bg-[var(--status-pagado)]/10">
  Pagado
</UBadge>

Notas Finales

  • Nunca modifiques el mapeo de --ui-color-neutral-* en main.css sin entender el sistema completamente
  • Siempre usa variables CSS para mantener la consistencia del tema
  • Prueba todos los temas después de hacer cambios visuales (Café, Azul, Verde, Carbón)
  • Usa el composable useTheme() para manipulación programática
  • Consulta este documento ante cualquier duda sobre colores

Mejoras Recientes (v2.1)

Todas las clases CSS ahora son completamente dinámicas

  • Las cards grandes (.brand-card) responden a cambios de tema
  • Los gradientes de fondo (.brand-shell) se ajustan automáticamente
  • Todas las clases utilitarias usan color-mix() con variables CSS

Nuevas variables para gráficas

  • --brand-cosecha-2, --brand-cosecha-3, --brand-cosecha-4
  • Colores intermedios que mantienen armonía con el tema activo
  • Usadas en gráficas de comparativa de cosechas

Eliminación de colores hardcodeados

  • Ya no existe --brand-bg-secondary (ahora usa --brand-surface)
  • Todos los colores RGB/hex hardcodeados fueron reemplazados
  • El background de html/body ahora usa var(--brand-bg)

Mantenido por: Claude Code para Núcleo Río Frío Versión del sistema: 2.1 Última revisión: 2025-10-31