Cambiado de 'icon' a 'leadingIcon' (propiedad correcta de Nuxt UI)
y agregados estados de color explícitos:
- Tab activa: text-[#1b1209] (negro oscuro)
- Tab inactiva: text-[var(--brand-text-muted)] (gris apagado)
- Transición suave de colores con transition-colors duration-200
Esto asegura que el icono sea visible y cambie de color
correctamente al seleccionar diferentes tabs.
Problema:
- El icono del toggle mostraba comportamiento inconsistente entre páginas
- Algunas páginas cargaban con icono correcto (hamburguesa) y otras con "X"
- Race condition entre múltiples watchers compitiendo por el estado
Causa raíz detectada:
1. DashboardSidebar.vue:58 tiene watcher con {immediate:true} que sincroniza
nuestro composable → contexto de DashboardGroup
2. DashboardSidebar.vue:60 tiene watcher que cierra sidebar en navegación
3. Nuestro composable TAMBIÉN tenía un watcher de navegación (duplicado)
4. Múltiples watchers registrándose cada vez que se llama useSidebarState()
5. Race condition: dependiendo del orden de montaje de componentes,
el toggle podía leer el estado antes o después de la sincronización
Solución:
- Eliminar completamente el watcher de navegación de nuestro composable
- Dejar que DashboardSidebar de Nuxt UI maneje el cierre en navegación
- Nuestro composable solo mantiene el estado, no lógica de ciclo de vida
- Evita múltiples registros de watchers
- Elimina la race condition
Cambios:
- Removido watcher de route.fullPath (líneas 110-116)
- Removidas importaciones innecesarias (watch, useRoute)
- Actualizado comentario del header
- Simplificado: composable ahora es puramente state management
Resultado:
✓ Icono consistente en todas las páginas
✓ No más race conditions
✓ Menos watchers = mejor performance
✓ DashboardSidebar maneja toda la lógica de navegación
Referencias:
- app/composables/useSidebarState.ts:14
- app/composables/useSidebarState.ts:29
Agregado @ts-expect-error para suprimir la advertencia sobre
la propiedad 'icon' que no está en los tipos formales pero es
una customización válida en Nuxt UI.
Agregado z-20 al icono para que herede correctamente el color
del trigger activo. Ahora el icono cambia a negro (#1b1209)
cuando la tab está seleccionada, igual que el label.
El icono del toggle mostraba "X" (cerrar) en lugar del icono hamburguesa
al cargar la página, aunque se corregía tras la primera interacción.
Causa raíz:
- UDashboardGroup inicializa sidebarOpen en false
- Nuestro composable inicializaba open en true por defecto
- El toggle lee el estado del contexto de DashboardGroup
- Desincronización causaba que el icono mostrara el estado incorrecto
Solución:
- Cambiar el default de open a false en el composable
- En desktop, open=false es correcto (no hay overlay, sidebar siempre visible)
- En mobile, open=false significa overlay cerrado (comportamiento esperado)
- Alineación total con el comportamiento de Nuxt UI
Archivos modificados:
- app/composables/useSidebarState.ts:43-46 (default open: false)
- app/composables/useSidebarState.ts:57 (fallback a false)
Referencias:
- app/composables/useSidebarState.ts:46
- app/composables/useSidebarState.ts:57
Cambios realizados:
- Tabs inactivos ahora tienen fondo transparente en lugar de #1c140c
- Indicador tiene z-10 para estar por encima del fondo
- Labels tienen z-20 para estar por encima del indicador
- Hover de tabs inactivos usa bg semi-transparente (#2a1f14/50)
- Agregada transición suave de colores (transition-colors duration-200)
Esto soluciona el problema donde el badge color cobre se ocultaba
durante la animación de deslizamiento entre tabs.
Soluciona todos los problemas identificados en la arquitectura anterior:
Cambios principales:
- Nuevo composable useSidebarState() que centraliza todo el estado
- Elimina múltiples fuentes de verdad que causaban desincronización
- Remueve watchers en cascada y hooks indirectos
- Elimina workarounds manuales de DOM y aria-hidden
- Implementa persistencia consistente en cookies
- Manejo responsive automático (mobile vs desktop)
Archivos modificados:
- app/composables/useSidebarState.ts (nuevo): Composable singleton
- app/components/app/AppSidebar.vue: Usa el nuevo composable
- app/layouts/dashboard.vue: Simplificado, sin refs locales ni workarounds
- docs/SIDEBAR_ARCHITECTURE.md (nuevo): Documentación completa
Beneficios:
✓ Estado consistente en toda la aplicación
✓ No más flickering o comportamientos anómalos
✓ Código más simple y mantenible
✓ Mejor performance (menos re-renders)
✓ Auto-close en mobile al navegar
Referencias:
- app/composables/useSidebarState.ts:1
- app/components/app/AppSidebar.vue:232
- app/layouts/dashboard.vue:40
Cambios:
- Crear nueva query "Informe Ingresos - Lista Simple de Clientes" (ID: 55)
- SELECT directo de tabla clientes sin filtros ni agregaciones
- Actualizar endpoint /api/clientes para usar la nueva query
- Eliminar parámetros innecesarios (la query no tiene filtros)
- Agregar logs para debugging
BREAKING: Violación de arquitectura corregida
- Eliminar endpoint /api/postgres/query (acceso directo a DB prohibido)
- Cambiar /api/clientes para usar query de Metabase en lugar de SQL directo
- Crear endpoint /api/metabase/opciones-filtros para obtener opciones
- Cambiar loadOpcionesFiltros para usar API en lugar de MCP directo
- Usar "Informe Ingresos - Lista de Clientes con Totales" para clientes
- Usar "Informe Ingresos - Opciones de Filtros" para opciones
- Respetar filosofía: Metabase calcula TODO, Vue solo renderiza
- La app NUNCA habla directamente con bases de datos
- Crear endpoint /api/clientes para obtener clientes desde Supabase
- Crear endpoint /api/postgres/query para ejecutar queries SQL
- Crear componente ClienteMultiSelector con búsqueda y filtro por ubicación
- Agregar filtros de clientes, ubicaciones y calidades en informe-ingresos.vue
- Cargar opciones de filtros desde Metabase (query ID 53)
- Actualizar detección de cambios pendientes con nuevos filtros
- Enviar cliente_ids, ubicaciones y calidades al endpoint de Metabase
- Componente con formato de cédula y ordenamiento por nombre
- Búsqueda por nombre, cédula o ubicación
- Contador de selección y botón limpiar todo
- Botones rápidos para seleccionar por ubicación
- Personalizar UTabs con prop :ui usando colores de marca
- Tab seleccionada: bg-[#c08040] con texto oscuro
- Tabs inactivas: text-[var(--brand-text-muted)] con hover
- Borde inferior: border-[var(--brand-border)]
- Input de búsqueda con bg, text y border del tema
- Focus ring en color café #c08040
- Reescribir informe-ingresos.vue siguiendo patrón de panorama.vue
- Implementar filosofía "Metabase calcula TODO, Vue solo renderiza"
- Agregar filtros avanzados: tipos de café, estados
- Integrar con endpoint /api/metabase/informe existente
- Usar componentes reutilizables: TotalesIngresoCompra, TotalesMonetarios, TotalesVerde
- Implementar detección de cambios pendientes con alertas visuales
- Agregar confirmación para incluir registros anulados
- Eliminar documentación redundante de queries (ya está en Metabase Debug)
- Layout informe con control de visibilidad de secciones
- Placeholders para tablas y gráficas futuras
- Envolver tabs en UCard con clase brand-card
- Aplicar estilos de variables CSS al input de búsqueda
- Configurar focus ring con color de marca #c08040
- Usar colores de texto y fondo del tema en input
- Aplicar clase brand-card a todas las cards principales
- Usar colores de variables CSS (--brand-text, --brand-text-muted, --brand-primary)
- Estilizar cards de estadísticas con border-[#3a2a16] y bg-[#1c140c]
- Actualizar estilos de tablas, modales y formularios
- Aplicar estilo consistente a botones principales con color de marca #c08040
Problema:
- Al enviar parámetros con valores vacíos (""), Metabase los expandía
- Filtros opcionales [[AND ...]] se convertían en filtros obligatorios
- Resultado: queries retornaban 0 registros en lugar de aplicar filtros opcionales
Solución:
- Solo enviar parámetros si son requeridos O tienen valor no vacío
- Verificar que el valor no sea null, "", undefined o array vacío
- Los parámetros opcionales sin valor no se incluyen en el request
Afecta: MetabaseCardDisplay.vue executeQuery() líneas 345-360
Problema:
- Metabase rechazaba queries cuando se enviaba null para parámetros text/date
- Error: "faltan los parámetros necesarios: fecha_desde, fecha_hasta"
- Los parámetros usados fuera de [[...]] son considerados requeridos
Solución:
- Cambiar || null a || '' para parámetros de tipo text y date
- Ahora envía string vacío que es compatible con NULLIF() en la query SQL
Afecta: MetabaseCardDisplay.vue líneas 333 y 342
Problema:
- Las queries de Metabase fallaban con error 500 al ejecutarse desde metabase-debug
- Metabase rechazaba los parámetros porque usaban tipos simples en lugar de tipos con operadores
Solución:
- Actualizar mapeo de tipos de parámetros en MetabaseCardDisplay.vue
- boolean → boolean/=
- date → date/single
- number → number/=
- text → string/=
Esto corrige el error "Tipo de parámetro no válido :text para el parámetro 'cliente_ids'"
Cambios realizados:
- Eliminar cards innecesarias que envolvían componentes de métricas
(TotalesMonetarios, TotalesIngresoCompra, TotalesVerde ya son cards)
- Actualizar estilo del contenedor de filtros al estilo de panorama facturador:
* Usar clase 'brand-card' y bordes transparentes
* Header con título y subtítulo estilizados
* Checkbox 'Incluir anulados' en header
* Inputs con variables CSS de brand
* Footer con botón 'Actualizar' estilizado
* Layout consistente con gap-8
Resultado: diseño más limpio, sin duplicación de cards y
estilo consistente entre páginas.
Cambios realizados:
- Alerta más compacta con variant 'soft' en lugar de 'solid'
- Eliminado botón 'Actualizar ahora' redundante de la alerta
- Eliminado emoji de advertencia del botón 'Actualizar'
- Simplificado borde del card (solo borde amarillo sutil)
- Eliminadas sombras y rings del card
- Eliminada animación pulse del botón
- Mantenido punto amarillo pulsante en la alerta
El indicador ahora es más discreto pero sigue siendo evidente.
Implementación de sistema de tracking para detectar cuando los filtros
han cambiado pero no se han aplicado a los datos mostrados.
Indicadores visuales implementados:
- Alerta amarilla prominente con animación pulse y ping
- Borde y sombra amarilla en el card de filtros
- Botón 'Actualizar' cambia a amarillo con emoji de advertencia
- Botón rápido 'Actualizar ahora' dentro de la alerta
- Animaciones llamativas para captar la atención
El sistema compara los filtros actuales con los últimos aplicados
y muestra indicadores visuales evidentes cuando hay diferencias.
Cambios implementados:
- Eliminada la llamada automática a loadData() en onMounted
- Añadido estado inicial con mensaje de bienvenida
- El usuario debe hacer clic explícito en 'Actualizar' para cargar datos
- Mejora la experiencia evitando queries automáticas pesadas a Metabase
Ahora la página muestra:
- Estado inicial: filtros + mensaje invitando a actualizar
- Estado con datos: filtros + todas las secciones de datos
La sidebar ahora detecta el tamaño de pantalla al inicializar:
- En móvil (< 1024px): cerrada por defecto
- En desktop (>= 1024px): abierta por defecto
Esto mejora la experiencia en dispositivos móviles evitando que
la sidebar tape el contenido al cargar la página.
Cambios realizados:
- Mover botón "Actualizar" al footer del card de Filtros
- Implementar guard para prevenir peticiones simultáneas
- Eliminar watcher automático de fechas
- Deshabilitar recarga automática al cambiar "incluir anulados"
- Usuario debe hacer clic explícito en "Actualizar" para cargar datos
Esto garantiza que solo una petición a Metabase esté activa a la vez
y da control total al usuario sobre cuándo recargar los datos.
Cambios en TotalesIngresoCompra:
- Mover botón toggle del header principal al header de sección 'Uva'
- Mejor posicionamiento: solo afecta la sección donde se aplica
Cambios en TotalesMonetarios:
- Agregar toggle de unidades en 'Precios Promedio Ponderados' para Uva
- 3 estados: LB, QQ, Ambos (muestra ambos precios en un contenedor)
- Unificar visualización de 'Uva por LB' y 'Uva por QQ' en un solo contenedor
- Grid cambia de 5 a 4 columnas para mejor layout
Ambos toggles funcionan independientemente y mantienen el mismo patrón
de diseño con iconos dinámicos.
- Agregar botón toggle de 3 estados: LB, QQ Seco, Ambos
- Por defecto muestra valores en LB
- Al cambiar a QQ Seco muestra: QQ Seco Ingresado, Pagado, en Depósito
- Al cambiar a Ambos muestra ambas unidades en el mismo contenedor
- Calcular QQ Seco en Depósito para Uva (ingresado - pagado)
- Iconos dinámicos según el modo seleccionado
Cambios principales:
- Cambiar tipo de parámetros de fecha de 'date/single' a 'text'
- Cambiar tipo de parámetro booleano de 'category' a 'boolean'
- Cambiar tipo de parámetros de categoría de 'category' a 'text'
Las queries SQL nativas en Metabase usan template-tags con tipos específicos
que deben coincidir exactamente con el tipo definido en el template-tag,
no con los tipos de UI de Metabase.
Archivos modificados:
- server/api/metabase/panorama.post.ts
- server/api/metabase/informe.post.ts
Resuelve el problema donde todas las queries retornaban error 500 y
respuestas vacías debido a tipos de parámetros incompatibles.
- Agregar logs para verificar que las cards se encuentren correctamente
- Agregar logs detallados de los parámetros enviados a cada query
- Agregar logs de la estructura de respuesta de Metabase
- Agregar logs de la transformación de datos (single row y multiple rows)
- Esto ayudará a diagnosticar por qué las respuestas están vacías
- Modificar panorama.post.ts e informe.post.ts para convertir fecha_desde y fecha_hasta null a strings vacíos
- Las queries de Metabase usan NULLIF(?, '') para detectar parámetros vacíos
- Esto permite que las queries usen sus defaults (fecha actual) correctamente
- Cambiar tipo de parámetros boolean de 'category' a 'boolean' para que funcione el UToggle
- Detectar campos de fecha por nombre aunque sean tipo text en Metabase
- Renderizar inputs de fecha con date picker para campos que contienen 'fecha'
- Mejorar inicialización de parámetros text que representan fechas
Resuelve el problema donde incluir_anulados no aparecía y fecha_desde/fecha_hasta
se mostraban como text inputs en lugar de date pickers
El contenedor estaba intentando conectarse a la URL pública de Metabase
(https://metabase.nucleoriofrio.com) en lugar de usar la red interna
de Docker (http://metabase:3000), causando errores 401 Unauthorized.
Esto permite que el contenedor de Nuxt se comunique directamente con
el contenedor de Metabase a través de la red Docker sin pasar por
Traefik/Authentik.