diff --git a/nuxt4-app/app/components/app/AppSidebar.vue b/nuxt4-app/app/components/app/AppSidebar.vue index 0d5f52c..c313a97 100644 --- a/nuxt4-app/app/components/app/AppSidebar.vue +++ b/nuxt4-app/app/components/app/AppSidebar.vue @@ -309,6 +309,12 @@ const navigationPrimary = computed(() => [ to: '/comparativa-cosechas', active: route.path === '/comparativa-cosechas' }, + { + label: 'UVA vs Carretas', + icon: 'i-lucide-scale', + to: '/comparativa-uva-carretas', + active: route.path === '/comparativa-uva-carretas' + }, { label: 'Explorador de datos', icon: 'i-lucide-table', diff --git a/nuxt4-app/app/pages/comparativa-uva-carretas.vue b/nuxt4-app/app/pages/comparativa-uva-carretas.vue new file mode 100644 index 0000000..c55ad0b --- /dev/null +++ b/nuxt4-app/app/pages/comparativa-uva-carretas.vue @@ -0,0 +1,292 @@ + + + diff --git a/nuxt4-app/server/api/metabase/comparativa-uva-carretas.post.ts b/nuxt4-app/server/api/metabase/comparativa-uva-carretas.post.ts new file mode 100644 index 0000000..ab30986 --- /dev/null +++ b/nuxt4-app/server/api/metabase/comparativa-uva-carretas.post.ts @@ -0,0 +1,107 @@ +/** + * API endpoint for Comparativa Ingreso UVA vs Salida Carretas + * Executes Card 94 from Metabase and returns processed data + */ + +const CARD_ID = 94 // Comparativa Ingreso UVA vs Salida (Nov-Dic 2025) + +export default defineEventHandler(async (event) => { + const body = await readBody(event) + + const { + fecha_desde = null, + fecha_hasta = null + } = body + + try { + console.log(`[Comparativa UVA] Executing card ${CARD_ID}`) + + // Execute the card without parameters (dates are hardcoded in the card) + const result = await executeCardQuery(CARD_ID, []) + + if (!result.data?.rows || !result.data?.cols) { + console.warn('[Comparativa UVA] No data returned from Metabase') + return { + datos: [], + totales: { + ingreso_qq: 0, + ingreso_lb: 0, + salida_lb: 0, + diferencia_lb: 0, + rendimiento_promedio: 0 + } + } + } + + // Transform rows to objects + const cols = result.data.cols + let datos = result.data.rows.map((row: any[]) => { + const obj: Record = {} + cols.forEach((col: any, index: number) => { + obj[col.name] = row[index] + }) + return obj + }) + + console.log(`[Comparativa UVA] Retrieved ${datos.length} rows`) + + // Filter by dates if provided + if (fecha_desde || fecha_hasta) { + datos = datos.filter((row: any) => { + if (!row.fecha) return false + + const rowDate = new Date(row.fecha) + + if (fecha_desde) { + const desde = new Date(fecha_desde) + if (rowDate < desde) return false + } + + if (fecha_hasta) { + const hasta = new Date(fecha_hasta) + if (rowDate > hasta) return false + } + + return true + }) + + console.log(`[Comparativa UVA] After date filter: ${datos.length} rows`) + } + + // Calculate totals + const totales = datos.reduce((acc: any, row: any) => { + acc.ingreso_qq += Number(row.ingreso_uva_qq) || 0 + acc.ingreso_lb += Number(row.ingreso_uva_lb) || 0 + acc.salida_lb += Number(row.salida_total_lb) || 0 + acc.diferencia_lb += Number(row.diferencia_lb) || 0 + return acc + }, { + ingreso_qq: 0, + ingreso_lb: 0, + salida_lb: 0, + diferencia_lb: 0 + }) + + // Calculate weighted average rendimiento + totales.rendimiento_promedio = totales.ingreso_lb > 0 + ? Math.round((totales.salida_lb / totales.ingreso_lb) * 1000) / 10 + : 0 + + // Round totals + totales.ingreso_qq = Math.round(totales.ingreso_qq * 100) / 100 + totales.ingreso_lb = Math.round(totales.ingreso_lb) + totales.salida_lb = Math.round(totales.salida_lb) + totales.diferencia_lb = Math.round(totales.diferencia_lb) + + return { + datos, + totales + } + } catch (error: any) { + console.error('[Comparativa UVA] Failed to execute query:', error) + throw createError({ + statusCode: error.statusCode || 500, + statusMessage: error.statusMessage || 'Failed to execute comparativa UVA query' + }) + } +})