Files
analiticaNucleo/nuxt4-app/docs/THEME_SYSTEM.md
josedario87 bc02bc6bde
All checks were successful
build-and-deploy / build-and-deploy (push) Successful in 52s
Docs: Crear documentación definitiva del sistema de temas
- Crear THEME_SYSTEM.md como documento único y completo
- Eliminar DEVELOPER_GUIDE.md y THEME_CUSTOMIZATION.md (desactualizados)
- Actualizar README.md con referencia al nuevo documento
- Incluye arquitectura, variables, reglas, ejemplos y troubleshooting
- Documentación clara para usuarios y desarrolladores
2025-10-30 17:18:32 -06:00

15 KiB

Sistema de Temas - Analítica Núcleo

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

Í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 de Tema (--brand-*)

Estas son las ÚNICAS variables que debes usar en tu código:

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 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

.brand-shell

Contenedor principal con gradiente radial.

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

.brand-card

Tarjetas con estilos consistentes (gradiente, borde, sombra).

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

.brand-divider

Divisor horizontal decorativo.

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

.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.

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

.brand-badge

Badge con fondo primario sólido.

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

.brand-table

Estilos para tablas.

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

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'
  }
  applyTheme()
  saveTheme()
}
</script>

<template>
  <div>
    <button @click="cambiarColorPrimario">Cambiar Primary</button>
    <button @click="aplicarTemaAzul">Aplicar Tema Azul</button>
  </div>
</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

<!-- 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)]

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

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