Files
analiticaNucleo/nuxt4-app/app/pages/metabase-debug.vue
josedario87 fcb321887c
All checks were successful
build-and-deploy / build-and-deploy (push) Successful in 48s
Fix: Reemplazar colores hardcodeados por variables del sistema de temas
- Agregadas nuevas variables CSS --brand-cosecha-2, --brand-cosecha-3, --brand-cosecha-4 para gráficas
- Reemplazado --brand-bg-secondary (no existía) por --brand-surface en todos los componentes
- Actualizados arrays de colores en componentes de comparativa de cosechas
- Eliminados colores RGB/hex hardcodeados en tooltips y badges
- Todos los componentes ahora respetan el sistema de temas dinámico
2025-10-31 10:56:01 -06:00

360 lines
12 KiB
Vue

<template>
<div class="container mx-auto px-4 py-8">
<div class="space-y-6">
<!-- Header -->
<div class="flex flex-col sm:flex-row sm:justify-between sm:items-start gap-4">
<div class="flex-1">
<h1 class="text-2xl sm:text-3xl font-bold">Metabase Debug</h1>
<p class="text-sm sm:text-base text-gray-600 dark:text-[var(--brand-text-muted)] mt-1">
Herramienta de debugging para queries de Metabase
</p>
</div>
<UButton
@click="refreshCards"
:loading="loading"
icon="i-heroicons-arrow-path"
color="primary"
variant="soft"
class="w-full sm:w-auto"
>
Actualizar
</UButton>
</div>
<!-- Stats -->
<UCard class="brand-card border border-transparent">
<template #header>
<div>
<h2 class="text-2xl font-bold brand-section-title">Estadísticas de Metabase</h2>
<p class="text-sm text-[var(--brand-text-muted)] mt-1">Vista general de queries y cards configuradas</p>
</div>
</template>
<div class="grid grid-cols-2 sm:grid-cols-3 md:grid-cols-6 gap-3 sm:gap-4">
<div class="rounded-lg border border-[var(--brand-border)] bg-[var(--brand-surface)] px-6 py-4">
<div class="text-xs text-[var(--brand-text-muted)] uppercase tracking-wide mb-1">Total Cards</div>
<div class="text-3xl font-bold text-[var(--brand-primary)]">{{ cards.length }}</div>
</div>
<div class="rounded-lg border border-[var(--brand-border)] bg-[var(--brand-surface)] px-6 py-4">
<div class="text-xs text-[var(--brand-text-muted)] uppercase tracking-wide mb-1">SQL Nativo</div>
<div class="text-3xl font-bold text-[var(--brand-primary)]">{{ nativeQueries }}</div>
</div>
<div class="rounded-lg border border-[var(--brand-border)] bg-[var(--brand-surface)] px-6 py-4">
<div class="text-xs text-[var(--brand-text-muted)] uppercase tracking-wide mb-1">Query Builder</div>
<div class="text-3xl font-bold text-green-400">{{ queryBuilderQueries }}</div>
</div>
<div class="rounded-lg border border-[var(--brand-border)] bg-[var(--brand-surface)] px-6 py-4">
<div class="text-xs text-[var(--brand-text-muted)] uppercase tracking-wide mb-1">Queries Panorama</div>
<div class="text-3xl font-bold" :class="panoramaQueries.length === 9 ? 'text-green-400' : 'text-orange-400'">{{ panoramaQueries.length }}/9</div>
</div>
<div class="rounded-lg border border-[var(--brand-border)] bg-[var(--brand-surface)] px-6 py-4">
<div class="text-xs text-[var(--brand-text-muted)] uppercase tracking-wide mb-1">Queries Informe</div>
<div class="text-3xl font-bold" :class="informeQueries.length === 8 ? 'text-green-400' : 'text-orange-400'">{{ informeQueries.length }}/8</div>
</div>
<div class="rounded-lg border border-[var(--brand-border)] bg-[var(--brand-surface)] px-6 py-4">
<div class="text-xs text-[var(--brand-text-muted)] uppercase tracking-wide mb-1">Queries Comparativa</div>
<div class="text-3xl font-bold" :class="comparativaQueries.length === 4 ? 'text-green-400' : 'text-orange-400'">{{ comparativaQueries.length }}/4</div>
</div>
</div>
</UCard>
<!-- Error Display -->
<UAlert
v-if="error"
color="error"
variant="soft"
:title="error"
:close-button="{ icon: 'i-heroicons-x-mark-20-solid', color: 'error', variant: 'link' }"
@close="error = null"
/>
<!-- Tabs -->
<UTabs
v-model="selectedTab"
:items="tabs"
:ui="{
root: 'space-y-4',
list: 'bg-[var(--brand-surface)]',
indicator: 'bg-[var(--brand-primary-strong)] z-10',
trigger: 'relative data-[state=active]:text-[var(--brand-bg)] data-[state=active]:hover:text-black data-[state=inactive]:text-[var(--brand-text-muted)] data-[state=inactive]:bg-transparent data-[state=inactive]:hover:text-[var(--brand-text)] data-[state=inactive]:hover:bg-[var(--brand-surface)]/50 focus-visible:ring-2 focus-visible:ring-[var(--brand-primary-strong)] transition-colors duration-200',
label: 'font-medium text-sm relative z-20',
leadingIcon: 'relative z-20 data-[state=active]:text-[var(--brand-bg)] data-[state=inactive]:text-[var(--brand-text-muted)] transition-colors duration-200',
content: 'py-4'
}"
>
<!-- Table View -->
<template #table>
<div class="py-4">
<MetabaseCardsTable
:cards="cards"
:loading="loading"
@select="selectCard"
/>
</div>
</template>
<!-- Cards View -->
<template #cards>
<div class="py-4">
<div class="grid grid-cols-1 lg:grid-cols-2 gap-4">
<MetabaseCardDisplay
v-for="card in cards"
:key="card.id"
:card="card"
/>
</div>
</div>
</template>
<!-- Panorama Queries -->
<template #panorama>
<div class="py-4 space-y-4">
<UAlert
color="info"
variant="soft"
title="Queries del Panorama Facturador"
description="Estas son las 9 queries documentadas en METABASE_QUERIES_PANORAMA.md y definidas en server/config/metabase-queries.ts"
/>
<div class="grid grid-cols-1 gap-4">
<MetabaseCardDisplay
v-for="card in panoramaQueries"
:key="card.id"
:card="card"
/>
</div>
<UAlert
v-if="panoramaQueries.length < 9"
color="warning"
variant="soft"
:title="`Faltan ${9 - panoramaQueries.length} queries por encontrar`"
:description="`Queries faltantes: ${missingPanoramaQueries.join(', ')}`"
/>
</div>
</template>
<!-- Informe Queries -->
<template #informe>
<div class="py-4 space-y-4">
<UAlert
color="info"
variant="soft"
title="Queries del Informe de Ingresos"
description="Estas son las 8 queries documentadas en METABASE_QUERIES_INFORME_INGRESOS.md y definidas en server/config/metabase-queries.ts"
/>
<div class="grid grid-cols-1 gap-4">
<MetabaseCardDisplay
v-for="card in informeQueries"
:key="card.id"
:card="card"
/>
</div>
<UAlert
v-if="informeQueries.length < 8"
color="warning"
variant="soft"
:title="`Faltan ${8 - informeQueries.length} queries por encontrar`"
:description="`Queries faltantes: ${missingInformeQueries.join(', ')}`"
/>
</div>
</template>
<!-- Comparativa Queries -->
<template #comparativa>
<div class="py-4 space-y-4">
<UAlert
color="info"
variant="soft"
title="Queries de Comparativa de Cosechas"
description="Estas son las 4 queries documentadas en METABASE_QUERIES_COMPARATIVA_COSECHAS.md y definidas en server/config/metabase-queries.ts"
/>
<div class="grid grid-cols-1 gap-4">
<MetabaseCardDisplay
v-for="card in comparativaQueries"
:key="card.id"
:card="card"
/>
</div>
<UAlert
v-if="comparativaQueries.length < 4"
color="warning"
variant="soft"
:title="`Faltan ${4 - comparativaQueries.length} queries por encontrar`"
:description="`Queries faltantes: ${missingComparativaQueries.join(', ')}`"
/>
</div>
</template>
<!-- Selected Card Detail -->
<template #detail>
<div v-if="selectedCard" class="py-4">
<MetabaseCardDisplay :card="selectedCard" />
</div>
</template>
</UTabs>
</div>
</div>
</template>
<script setup lang="ts">
definePageMeta({
layout: 'informe',
title: 'Metabase Debug'
})
const loading = ref(false)
const error = ref<string | null>(null)
const cards = ref<any[]>([])
const selectedCard = ref<any>(null)
const selectedTab = ref(0)
// Get expected query names from centralized config (via API)
const expectedPanoramaNames = ref<string[]>([])
const expectedInformeNames = ref<string[]>([])
const expectedComparativaNames = ref<string[]>([])
// Fetch query config on mount
async function fetchQueryConfig() {
try {
const config = await $fetch('/api/metabase/query-config')
expectedPanoramaNames.value = config.panorama
expectedInformeNames.value = config.informe
expectedComparativaNames.value = config.comparativa
} catch (e: any) {
console.error('Error fetching query config:', e)
}
}
const tabs = [
{
key: 'table',
label: 'Vista Tabla',
icon: 'i-heroicons-table-cells',
slot: 'table'
},
{
key: 'cards',
label: 'Vista Cards',
icon: 'i-heroicons-squares-2x2',
slot: 'cards'
},
{
key: 'panorama',
label: 'Queries Panorama',
icon: 'i-heroicons-chart-bar',
slot: 'panorama'
},
{
key: 'informe',
label: 'Queries Informe',
icon: 'i-heroicons-document-chart-bar',
slot: 'informe'
},
{
key: 'comparativa',
label: 'Queries Comparativa',
icon: 'i-heroicons-calendar',
slot: 'comparativa'
},
{
key: 'detail',
label: 'Detalle',
icon: 'i-heroicons-document-magnifying-glass',
disabled: !selectedCard.value,
slot: 'detail'
}
]
const nativeQueries = computed(() => {
return cards.value.filter(card => {
const type = card.query_type || card.dataset_query?.type
return type === 'native'
}).length
})
const queryBuilderQueries = computed(() => {
return cards.value.filter(card => {
const type = card.query_type || card.dataset_query?.type
return type === 'query'
}).length
})
// Find panorama queries using centralized config
const panoramaQueries = computed(() => {
return cards.value.filter(card =>
expectedPanoramaNames.value.includes(card.name)
)
})
// Find informe queries using centralized config
const informeQueries = computed(() => {
return cards.value.filter(card =>
expectedInformeNames.value.includes(card.name)
)
})
// Find comparativa queries using centralized config
const comparativaQueries = computed(() => {
return cards.value.filter(card =>
expectedComparativaNames.value.includes(card.name)
)
})
// Calculate missing queries
const missingPanoramaQueries = computed(() => {
const foundNames = panoramaQueries.value.map(q => q.name)
return expectedPanoramaNames.value.filter(name => !foundNames.includes(name))
})
const missingInformeQueries = computed(() => {
const foundNames = informeQueries.value.map(q => q.name)
return expectedInformeNames.value.filter(name => !foundNames.includes(name))
})
const missingComparativaQueries = computed(() => {
const foundNames = comparativaQueries.value.map(q => q.name)
return expectedComparativaNames.value.filter(name => !foundNames.includes(name))
})
async function fetchCards() {
loading.value = true
error.value = null
try {
const result = await $fetch('/api/metabase/cards?f=all')
cards.value = Array.isArray(result) ? result : []
} catch (e: any) {
error.value = e.message || 'Error al cargar las cards de Metabase'
console.error('Error fetching cards:', e)
} finally {
loading.value = false
}
}
function refreshCards() {
fetchCards()
}
function selectCard(card: any) {
selectedCard.value = card
selectedTab.value = 5 // Switch to detail tab (now index 5)
}
// Load data on mount
onMounted(async () => {
await fetchQueryConfig()
await fetchCards()
})
</script>