All checks were successful
build-and-deploy / build-and-deploy (push) Successful in 47s
Implementa funcionalidad de copia en tres secciones del Informe: 📋 Funcionalidades agregadas: 1. Lista de Ingresos - Copiar Texto: Formato WhatsApp con emojis y legible - Copiar JSON: Formato estructurado para sistemas 2. Top 10 Clientes - Copiar Texto: Ranking formateado con métricas - Copiar JSON: Array de objetos con datos completos 3. Serie Temporal Acumulada - Copiar Texto: Evolución temporal con emojis - Copiar JSON: Datos completos para análisis ✨ Características: - Botones con iconos (i-lucide-copy y i-lucide-code) - Disabled cuando no hay datos disponibles - Alertas de confirmación al copiar - Formato texto optimizado para WhatsApp - Incluye metadata: rango de fechas y timestamp Uso: - Copiar Texto → Compartir en WhatsApp/Telegram - Copiar JSON → Integración con otros sistemas
420 lines
10 KiB
Markdown
420 lines
10 KiB
Markdown
# Guía de Desarrollo - Sistema de Temas
|
|
|
|
## Introducción
|
|
|
|
Esta guía está dirigida a desarrolladores que trabajan en Analítica Núcleo y necesitan entender cómo funciona el sistema de temas y cómo mantener la consistencia visual en toda la aplicación.
|
|
|
|
## Principios Fundamentales
|
|
|
|
### 1. Siempre Usa Variables CSS
|
|
|
|
**✅ CORRECTO:**
|
|
```vue
|
|
<div class="bg-[var(--brand-surface)] border-[var(--brand-border)] text-[var(--brand-text)]">
|
|
Contenido
|
|
</div>
|
|
```
|
|
|
|
**❌ INCORRECTO:**
|
|
```vue
|
|
<div class="bg-[#1f180f] border-[#3a2a16] text-[#fef9f0]">
|
|
Contenido
|
|
</div>
|
|
```
|
|
|
|
**❌ INCORRECTO:**
|
|
```vue
|
|
<div class="bg-gray-900 border-gray-800 text-white">
|
|
Contenido
|
|
</div>
|
|
```
|
|
|
|
### 2. Usa las Clases Utilitarias de Marca
|
|
|
|
Para elementos comunes, usa las clases predefinidas en lugar de duplicar estilos:
|
|
|
|
```vue
|
|
<!-- ✅ CORRECTO -->
|
|
<div class="brand-card">
|
|
<h2 class="brand-section-title">Título</h2>
|
|
<div class="brand-divider"></div>
|
|
</div>
|
|
|
|
<!-- ❌ INCORRECTO -->
|
|
<div class="rounded-lg bg-gradient-to-br from-[#1f180f] to-[#14100b] border border-[#3a2a16]">
|
|
<h2 class="text-lg font-semibold text-[#e0c080]">Título</h2>
|
|
<div class="h-px bg-[#3a2a16] my-4"></div>
|
|
</div>
|
|
```
|
|
|
|
## Variables de Tema Disponibles
|
|
|
|
| Variable CSS | Uso | Ejemplo |
|
|
|--------------|-----|---------|
|
|
| `--brand-bg` | Fondo principal de la aplicación | `bg-[var(--brand-bg)]` |
|
|
| `--brand-surface` | Fondos de elementos elevados (cards, modales) | `bg-[var(--brand-surface)]` |
|
|
| `--brand-border` | Bordes de elementos | `border-[var(--brand-border)]` |
|
|
| `--brand-primary` | Color principal (enlaces, botones) | `text-[var(--brand-primary)]` |
|
|
| `--brand-primary-strong` | Variante más intensa del primario | `bg-[var(--brand-primary-strong)]` |
|
|
| `--brand-accent` | Color de acento para highlights | `text-[var(--brand-accent)]` |
|
|
| `--brand-text` | Texto principal | `text-[var(--brand-text)]` |
|
|
| `--brand-text-muted` | Texto secundario/menos prominente | `text-[var(--brand-text-muted)]` |
|
|
|
|
## Clases Utilitarias de Marca
|
|
|
|
### `.brand-shell`
|
|
Contenedor principal para layouts con gradiente radial.
|
|
|
|
```vue
|
|
<div class="brand-shell">
|
|
<!-- Contenido de la página -->
|
|
</div>
|
|
```
|
|
|
|
### `.brand-card`
|
|
Tarjetas con estilos consistentes.
|
|
|
|
```vue
|
|
<div class="brand-card">
|
|
<h3>Título de la Card</h3>
|
|
<p>Contenido</p>
|
|
</div>
|
|
```
|
|
|
|
### `.brand-divider`
|
|
Divisor horizontal decorativo.
|
|
|
|
```vue
|
|
<div class="brand-divider"></div>
|
|
```
|
|
|
|
### `.brand-chip` y `.brand-pill`
|
|
Para pequeños tags o badges.
|
|
|
|
```vue
|
|
<span class="brand-chip">Estado</span>
|
|
<span class="brand-pill">Badge</span>
|
|
```
|
|
|
|
### `.brand-table`
|
|
Estilos para tablas.
|
|
|
|
```vue
|
|
<table class="brand-table">
|
|
<!-- ... -->
|
|
</table>
|
|
```
|
|
|
|
## Patrones de Uso Común
|
|
|
|
### Cards con Bordes y Sombras
|
|
|
|
```vue
|
|
<UCard class="brand-card border border-transparent">
|
|
<template #header>
|
|
<div class="flex items-center gap-2">
|
|
<UIcon name="i-lucide-star" class="size-5 text-[var(--brand-accent)]" />
|
|
<h3 class="text-[var(--brand-text)]">Título</h3>
|
|
</div>
|
|
</template>
|
|
|
|
<p class="text-[var(--brand-text-muted)]">
|
|
Descripción del contenido
|
|
</p>
|
|
</UCard>
|
|
```
|
|
|
|
### Botones con Hover Personalizado
|
|
|
|
```vue
|
|
<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">
|
|
Botón
|
|
</button>
|
|
```
|
|
|
|
### Inputs y Formularios
|
|
|
|
```vue
|
|
<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)]'
|
|
}"
|
|
/>
|
|
```
|
|
|
|
### Links con Hover
|
|
|
|
```vue
|
|
<NuxtLink
|
|
to="/perfil"
|
|
class="text-[var(--brand-accent)] hover:text-[var(--brand-primary)] hover:underline transition-colors"
|
|
>
|
|
Ver perfil →
|
|
</NuxtLink>
|
|
```
|
|
|
|
### Personalización de Componentes Nuxt UI
|
|
|
|
Muchos componentes de Nuxt UI aceptan la prop `:ui` para personalización:
|
|
|
|
```vue
|
|
<UButton
|
|
:ui="{
|
|
base: 'bg-[var(--brand-surface)] hover:bg-[var(--brand-primary)]/10',
|
|
padding: { sm: 'px-2.5 py-2' }
|
|
}"
|
|
>
|
|
Botón Personalizado
|
|
</UButton>
|
|
```
|
|
|
|
## Composable `useTheme()`
|
|
|
|
Para manipular el tema programáticamente, usa el composable `useTheme()`:
|
|
|
|
```vue
|
|
<script setup lang="ts">
|
|
const { theme, applyTheme, saveTheme, resetTheme, exportTheme, importTheme } = useTheme()
|
|
|
|
// Modificar un color
|
|
theme.value.primary = '#ff5733'
|
|
|
|
// Aplicar cambios
|
|
applyTheme()
|
|
|
|
// Guardar en localStorage
|
|
saveTheme()
|
|
|
|
// Resetear a valores por defecto
|
|
resetTheme()
|
|
|
|
// Exportar tema como JSON
|
|
const jsonTheme = exportTheme()
|
|
|
|
// Importar tema desde JSON
|
|
const success = importTheme(jsonString, true) // true = guardar automáticamente
|
|
</script>
|
|
```
|
|
|
|
## Checklist Pre-Commit
|
|
|
|
Antes de hacer commit de tu código, verifica:
|
|
|
|
- [ ] **No hay colores 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** (ej: `hover:bg-[var(--brand-primary)]/10`)
|
|
- [ ] **Se usan clases utilitarias** cuando sea posible (`.brand-card`, `.brand-shell`, etc.)
|
|
|
|
## Casos Especiales
|
|
|
|
### Colores Semánticos (Success, Error, Warning)
|
|
|
|
Para colores que NO son parte del tema (éxito, error, advertencia), puedes usar colores de Tailwind:
|
|
|
|
```vue
|
|
<!-- Estados semánticos - OK usar colores fijos -->
|
|
<div class="text-green-400">Éxito</div>
|
|
<div class="text-red-400">Error</div>
|
|
<div class="text-yellow-400">Advertencia</div>
|
|
<div class="text-blue-400">Info</div>
|
|
|
|
<!-- Pero el fondo y borde deben respetar el tema -->
|
|
<div class="bg-[var(--brand-surface)] border-green-600/30 text-green-400">
|
|
Operación exitosa
|
|
</div>
|
|
```
|
|
|
|
### Iconos
|
|
|
|
Los iconos deben usar colores del tema:
|
|
|
|
```vue
|
|
<!-- ✅ CORRECTO -->
|
|
<UIcon name="i-lucide-star" class="size-5 text-[var(--brand-primary)]" />
|
|
<UIcon name="i-lucide-info" class="size-4 text-[var(--brand-accent)]" />
|
|
<UIcon name="i-lucide-chevron-right" class="size-4 text-[var(--brand-text-muted)]" />
|
|
|
|
<!-- ❌ INCORRECTO -->
|
|
<UIcon name="i-lucide-star" class="size-5 text-amber-500" />
|
|
<UIcon name="i-lucide-info" class="size-4 text-blue-400" />
|
|
```
|
|
|
|
### Imágenes y Avatares con Bordes
|
|
|
|
```vue
|
|
<img
|
|
src="/logo.png"
|
|
class="rounded-full border-2 border-[var(--brand-accent)]/40 shadow-lg shadow-[var(--brand-primary-strong)]/25"
|
|
/>
|
|
|
|
<UAvatar
|
|
src="https://example.com/avatar.jpg"
|
|
:ui="{
|
|
wrapper: 'ring-2 ring-[var(--brand-primary)]/40'
|
|
}"
|
|
/>
|
|
```
|
|
|
|
### Hover y Focus States
|
|
|
|
Siempre usa transparencias para hover states para que funcionen con cualquier tema:
|
|
|
|
```vue
|
|
<!-- ✅ CORRECTO - Usa transparencia -->
|
|
<button class="hover:bg-[var(--brand-primary)]/10 focus:ring-2 focus:ring-[var(--brand-primary)]/50">
|
|
Botón
|
|
</button>
|
|
|
|
<!-- ❌ INCORRECTO - Color sólido -->
|
|
<button class="hover:bg-[#e0c080] focus:ring-2 focus:ring-[#c08040]">
|
|
Botón
|
|
</button>
|
|
```
|
|
|
|
## Ejemplos de Migración
|
|
|
|
### Antes (Incorrecto)
|
|
|
|
```vue
|
|
<template>
|
|
<div class="bg-gray-900 border border-gray-800">
|
|
<h2 class="text-white font-bold">Título</h2>
|
|
<p class="text-gray-400 mt-2">
|
|
Descripción
|
|
</p>
|
|
<button class="mt-4 px-4 py-2 bg-blue-600 hover:bg-blue-700 text-white rounded-lg">
|
|
Acción
|
|
</button>
|
|
</div>
|
|
</template>
|
|
```
|
|
|
|
### Después (Correcto)
|
|
|
|
```vue
|
|
<template>
|
|
<div class="bg-[var(--brand-surface)] border border-[var(--brand-border)]">
|
|
<h2 class="text-[var(--brand-text)] font-bold">Título</h2>
|
|
<p class="text-[var(--brand-text-muted)] mt-2">
|
|
Descripción
|
|
</p>
|
|
<UButton
|
|
class="mt-4"
|
|
:ui="{
|
|
base: 'bg-[var(--brand-primary)] hover:bg-[var(--brand-primary-strong)]'
|
|
}"
|
|
>
|
|
Acción
|
|
</UButton>
|
|
</div>
|
|
</template>
|
|
```
|
|
|
|
## Errores Comunes y Soluciones
|
|
|
|
### Error 1: Mezclar Variables con Hardcoded Colors
|
|
|
|
**❌ No hagas esto:**
|
|
```vue
|
|
<div class="bg-[#1f180f] text-[var(--brand-text)]">
|
|
Contenido
|
|
</div>
|
|
```
|
|
|
|
**✅ Haz esto:**
|
|
```vue
|
|
<div class="bg-[var(--brand-surface)] text-[var(--brand-text)]">
|
|
Contenido
|
|
</div>
|
|
```
|
|
|
|
### Error 2: No Considerar el Contraste
|
|
|
|
Asegúrate de que el texto tenga suficiente contraste sobre su fondo:
|
|
|
|
**❌ Mal contraste:**
|
|
```vue
|
|
<div class="bg-[var(--brand-surface)]">
|
|
<span class="text-[var(--brand-text-muted)]">Texto importante</span>
|
|
</div>
|
|
```
|
|
|
|
**✅ Buen contraste:**
|
|
```vue
|
|
<div class="bg-[var(--brand-surface)]">
|
|
<span class="text-[var(--brand-text)]">Texto importante</span>
|
|
</div>
|
|
```
|
|
|
|
### Error 3: Duplicar Estilos en Lugar de Usar Clases
|
|
|
|
**❌ Duplicación:**
|
|
```vue
|
|
<div class="rounded-lg bg-gradient-to-br from-[var(--brand-surface)] to-[var(--brand-bg)] border border-[var(--brand-border)] p-6 shadow-xl">
|
|
Card 1
|
|
</div>
|
|
<div class="rounded-lg bg-gradient-to-br from-[var(--brand-surface)] to-[var(--brand-bg)] border border-[var(--brand-border)] p-6 shadow-xl">
|
|
Card 2
|
|
</div>
|
|
```
|
|
|
|
**✅ Reutilización:**
|
|
```vue
|
|
<div class="brand-card">Card 1</div>
|
|
<div class="brand-card">Card 2</div>
|
|
```
|
|
|
|
## Testing del Tema
|
|
|
|
Para probar que tu componente respeta el tema:
|
|
|
|
1. Ve a `/settings`
|
|
2. Cambia el tema a "Azul Corporativo" o "Verde Natural"
|
|
3. Navega a tu componente
|
|
4. Verifica que todos los colores cambien correctamente
|
|
5. Verifica que no haya elementos con colores hardcoded que no cambiaron
|
|
|
|
## Recursos Adicionales
|
|
|
|
- **Documentación del usuario:** `THEME_CUSTOMIZATION.md`
|
|
- **Composable de temas:** `app/composables/useTheme.ts`
|
|
- **Variables CSS:** `app/assets/css/main.css`
|
|
- **Configuración de Nuxt UI:** `app.config.ts`
|
|
|
|
## Preguntas Frecuentes
|
|
|
|
### ¿Puedo usar colores de Tailwind como `bg-blue-500`?
|
|
|
|
Solo para estados semánticos (éxito, error, advertencia) que NO forman parte del tema. Para todo lo demás, usa las variables de tema.
|
|
|
|
### ¿Cómo personalizo un componente de Nuxt UI?
|
|
|
|
Usa la prop `:ui` con las variables de tema:
|
|
|
|
```vue
|
|
<UCard :ui="{ base: 'bg-[var(--brand-surface)]' }">
|
|
Contenido
|
|
</UCard>
|
|
```
|
|
|
|
### ¿Qué hago si necesito un color que no está en el tema?
|
|
|
|
Primero pregunta: ¿realmente necesito un color nuevo, o puedo usar uno existente? Si es absolutamente necesario, propón agregar una nueva variable al sistema de temas en lugar de hardcodear el color.
|
|
|
|
### ¿Cómo manejo dark mode?
|
|
|
|
No necesitas manejarlo manualmente. Las variables CSS ya están optimizadas para dark mode. Solo usa las variables y funcionará automáticamente.
|
|
|
|
---
|
|
|
|
**Última actualización:** 2025-10-30
|
|
**Mantenedor:** Claude Code para Núcleo Río Frío
|
|
**Versión:** 1.0.0
|