Refactorizar Panorama Facturador: implementar filosofía "Metabase calcula TODO, Vue solo renderiza"
All checks were successful
build-and-deploy / build-and-deploy (push) Successful in 5m41s

Cambios principales:

- Refactorizar todos los componentes de panorama para recibir datos directos de Metabase
  * TotalesMonetarios.vue: cambiar de props.metrics a props.data
  * TotalesIngresoCompra.vue: cambiar de props.metrics a props.data
  * TotalesVerde.vue: cambiar de props.metrics a props.data
  * SecosVendidos.vue: cambiar de props.metrics a props.data

- Eliminar fechas hardcodeadas en panorama.post.ts
  * Pasar valores null directamente a Metabase para usar sus defaults

- Marcar composables obsoletos para Panorama Facturador
  * useIngresosMetrics.ts: agregar advertencia de no uso en Panorama
  * useRechazosMetrics.ts: agregar advertencia de no uso en Panorama

Resultado: Todos los cálculos (agregaciones, promedios ponderados) se hacen en Metabase mediante SQL. Los componentes Vue solo renderizan valores ya calculados.
This commit is contained in:
2025-10-27 15:09:03 -06:00
parent 9234c5832c
commit 694ee4e3bb
7 changed files with 137 additions and 52 deletions

View File

@@ -9,7 +9,7 @@
<div class="flex flex-col">
<span class="text-xs uppercase tracking-wide opacity-80 mb-1">Total qq Secos por Vender</span>
<div class="flex items-baseline gap-2">
<span class="text-2xl font-bold">{{ formatNumber(metrics.totalQqSecoPorVender.value) }}</span>
<span class="text-2xl font-bold">{{ formatNumber(data.total_qq_seco_por_vender) }}</span>
<span class="text-sm font-bold opacity-70">qq</span>
</div>
</div>
@@ -18,7 +18,7 @@
<div class="flex flex-col">
<span class="text-xs uppercase tracking-wide opacity-80 mb-1">Precio de Venta Promedio por qq</span>
<div class="flex items-baseline gap-2">
<span class="text-2xl font-bold">{{ formatCurrency(metrics.precioVentaPromedioPorQq.value) }}</span>
<span class="text-2xl font-bold">{{ formatCurrency(data.precio_venta_promedio_por_qq) }}</span>
</div>
</div>
</div>
@@ -26,15 +26,15 @@
<div class="flex flex-col">
<span class="text-xs uppercase tracking-wide opacity-80 mb-1">Precio de Compra Promedio por qq</span>
<div class="flex items-baseline gap-2">
<span class="text-2xl font-bold">{{ formatCurrency(metrics.precioCompraPromedioPorQq.value) }}</span>
<span class="text-2xl font-bold">{{ formatCurrency(data.precio_compra_promedio_por_qq) }}</span>
</div>
</div>
</div>
<div class="p-4 rounded-lg border transition-all bg-[#1c140c] text-purple-600" :class="metrics.margenGananciaPorQq.value > 0 ? 'border-purple-600/40' : 'border-red-600/40'">
<div class="p-4 rounded-lg border transition-all bg-[#1c140c] text-purple-600" :class="data.margen_ganancia_por_qq > 0 ? 'border-purple-600/40' : 'border-red-600/40'">
<div class="flex flex-col">
<span class="text-xs uppercase tracking-wide opacity-80 mb-1">Margen de Ganancia por qq</span>
<div class="flex items-baseline gap-2">
<span class="text-2xl font-bold">{{ formatCurrency(metrics.margenGananciaPorQq.value) }}</span>
<span class="text-2xl font-bold">{{ formatCurrency(data.margen_ganancia_por_qq) }}</span>
</div>
</div>
</div>
@@ -43,10 +43,14 @@
</template>
<script setup lang="ts">
import type { IngresosMetrics } from '~/composables/useIngresosMetrics'
// Props reciben datos directos de Metabase - SIN composables
defineProps<{
metrics: IngresosMetrics
data: {
total_qq_seco_por_vender: number
precio_venta_promedio_por_qq: number
precio_compra_promedio_por_qq: number
margen_ganancia_por_qq: number
}
}>()
const formatNumber = (value: number) => {

View File

@@ -23,25 +23,25 @@
/>
</div>
<div>
<span class="text-2xl font-bold">{{ formatUvaValueNumber(metrics.totalLbUvaIngresada.value, metrics.totalQqSecoUvaIngresado.value) }}</span>
<span class="text-2xl font-bold">{{ formatUvaValueNumber(data.total_lb_uva_ingresada, data.total_qq_seco_uva_ingresado) }}</span>
<span class="text-sm font-bold opacity-70 ml-2">{{ formatUvaValueUnit() }}</span>
</div>
</div>
<MetricCard
label="Total qq Seco Mojado"
:value="formatNumber(metrics.totalQqSecoMojadoIngresado.value)"
:value="formatNumber(data.total_qq_seco_mojado_ingresado)"
unit="qq"
variant="info"
/>
<MetricCard
label="Total qq Seco Oreado"
:value="formatNumber(metrics.totalQqSecoOreadoIngresado.value)"
:value="formatNumber(data.total_qq_seco_oreado_ingresado)"
unit="qq"
variant="warning"
/>
<MetricCard
label="Total qq Seco Ingresado"
:value="formatNumber(metrics.totalQqSecoIngresado.value)"
:value="formatNumber(data.total_qq_seco_ingresado)"
unit="qq"
variant="primary"
/>
@@ -66,25 +66,25 @@
/>
</div>
<div>
<span class="text-2xl font-bold">{{ formatUvaValueNumber(metrics.totalLbUvaPagada.value, metrics.totalQqSecoUvaPagado.value) }}</span>
<span class="text-2xl font-bold">{{ formatUvaValueNumber(data.total_lb_uva_pagada, data.total_qq_seco_uva_pagado) }}</span>
<span class="text-sm font-bold opacity-70 ml-2">{{ formatUvaValueUnit() }}</span>
</div>
</div>
<MetricCard
label="Total qq Seco Mojado Pagado"
:value="formatNumber(metrics.totalQqSecoMojadoPagado.value)"
:value="formatNumber(data.total_qq_seco_mojado_pagado)"
unit="qq"
variant="info"
/>
<MetricCard
label="Total qq Seco Oreado Pagado"
:value="formatNumber(metrics.totalQqSecoOreadoPagado.value)"
:value="formatNumber(data.total_qq_seco_oreado_pagado)"
unit="qq"
variant="warning"
/>
<MetricCard
label="Total qq Seco Comprado"
:value="formatNumber(metrics.totalQqSecoComprado.value)"
:value="formatNumber(data.total_qq_seco_comprado)"
unit="qq"
variant="primary"
/>
@@ -109,25 +109,25 @@
/>
</div>
<div>
<span class="text-2xl font-bold">{{ formatUvaValueNumber(metrics.totalLbUvaDeposito.value, 0) }}</span>
<span class="text-2xl font-bold">{{ formatUvaValueNumber(data.total_lb_uva_deposito, 0) }}</span>
<span class="text-sm font-bold opacity-70 ml-2">{{ formatUvaValueUnit() }}</span>
</div>
</div>
<MetricCard
label="Total qq Seco Mojado en Depósito"
:value="formatNumber(metrics.totalQqMojadoDeposito.value)"
:value="formatNumber(data.total_qq_mojado_deposito)"
unit="qq"
variant="info"
/>
<MetricCard
label="Total qq Seco Oreado en Depósito"
:value="formatNumber(metrics.totalQqOreadoDeposito.value)"
:value="formatNumber(data.total_qq_oreado_deposito)"
unit="qq"
variant="warning"
/>
<MetricCard
label="Total qq Seco en Depósito"
:value="formatNumber(metrics.totalQqSecoDeposito.value)"
:value="formatNumber(data.total_qq_seco_deposito)"
unit="qq"
variant="primary"
/>
@@ -139,10 +139,25 @@
<script setup lang="ts">
import { ref, computed } from 'vue'
import type { IngresosMetrics } from '~/composables/useIngresosMetrics'
defineProps<{
metrics: IngresosMetrics
// Props reciben datos directos de Metabase - SIN composables
const props = defineProps<{
data: {
total_lb_uva_ingresada: number
total_qq_seco_uva_ingresado: number
total_qq_seco_mojado_ingresado: number
total_qq_seco_oreado_ingresado: number
total_qq_seco_ingresado: number
total_lb_uva_pagada: number
total_qq_seco_uva_pagado: number
total_qq_seco_mojado_pagado: number
total_qq_seco_oreado_pagado: number
total_qq_seco_comprado: number
total_lb_uva_deposito: number
total_qq_mojado_deposito: number
total_qq_oreado_deposito: number
total_qq_seco_deposito: number
}
}>()
type UnitDisplay = 'lb' | 'qq' | 'both'

View File

@@ -13,22 +13,22 @@
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-4">
<MetricCard
label="Inversión en Uva"
:value="formatCurrency(metrics.inversionUva.value)"
:value="formatCurrency(data.inversion_uva)"
variant="danger"
/>
<MetricCard
label="Inversión en Mojado"
:value="formatCurrency(metrics.inversionMojado.value)"
:value="formatCurrency(data.inversion_mojado)"
variant="info"
/>
<MetricCard
label="Inversión en Oreado"
:value="formatCurrency(metrics.inversionOreado.value)"
:value="formatCurrency(data.inversion_oreado)"
variant="warning"
/>
<MetricCard
label="Total Invertido"
:value="formatCurrency(metrics.totalInvertido.value)"
:value="formatCurrency(data.total_invertido)"
variant="primary"
/>
</div>
@@ -58,19 +58,19 @@
</div>
<MetricCard
label="Precio Promedio Ponderado Mojado"
:value="formatNumber(metrics.precioPromedioMojadoPorQq.value)"
:value="formatNumber(data.precio_promedio_mojado_por_qq)"
unit="L./qq"
variant="info"
/>
<MetricCard
label="Precio Promedio Ponderado Oreado"
:value="formatNumber(metrics.precioPromedioOreadoPorQq.value)"
:value="formatNumber(data.precio_promedio_oreado_por_qq)"
unit="L./qq"
variant="warning"
/>
<MetricCard
label="Precio Promedio Ponderado qq Seco"
:value="formatNumber(metrics.precioPromedioQqSeco.value)"
:value="formatNumber(data.precio_promedio_qq_seco)"
unit="L./qq"
variant="primary"
/>
@@ -85,22 +85,22 @@
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-4">
<MetricCard
label="Inversión Restante Uva"
:value="formatCurrency(metrics.inversionRestanteUva.value)"
:value="formatCurrency(data.inversion_restante_uva)"
variant="danger"
/>
<MetricCard
label="Inversión Restante Mojado"
:value="formatCurrency(metrics.inversionRestanteMojado.value)"
:value="formatCurrency(data.inversion_restante_mojado)"
variant="info"
/>
<MetricCard
label="Inversión Restante Oreado"
:value="formatCurrency(metrics.inversionRestanteOreado.value)"
:value="formatCurrency(data.inversion_restante_oreado)"
variant="warning"
/>
<MetricCard
label="Inversión Restante Esperada"
:value="formatCurrency(metrics.inversionRestanteEsperada.value)"
:value="formatCurrency(data.inversion_restante_esperada)"
variant="primary"
/>
</div>
@@ -111,10 +111,24 @@
<script setup lang="ts">
import { ref, computed } from 'vue'
import type { IngresosMetrics } from '~/composables/useIngresosMetrics'
// Props reciben datos directos de Metabase - SIN composables
const props = defineProps<{
metrics: IngresosMetrics
data: {
inversion_uva: number
inversion_mojado: number
inversion_oreado: number
total_invertido: number
precio_promedio_uva_por_lb: number
precio_promedio_uva_por_qq: number
precio_promedio_mojado_por_qq: number
precio_promedio_oreado_por_qq: number
precio_promedio_qq_seco: number
inversion_restante_uva: number
inversion_restante_mojado: number
inversion_restante_oreado: number
inversion_restante_esperada: number
}
}>()
type UnitDisplay = 'lb' | 'qq' | 'both'
@@ -158,11 +172,11 @@ const formatCurrency = (value: number) => {
function formatPrecioUvaNumber(): string {
switch (unitDisplay.value) {
case 'lb':
return formatNumber(props.metrics.precioPromedioUvaPorQqLb.value)
return formatNumber(props.data.precio_promedio_uva_por_lb)
case 'qq':
return formatNumber(props.metrics.precioPromedioUvaPorQq.value)
return formatNumber(props.data.precio_promedio_uva_por_qq)
case 'both':
return `${formatNumber(props.metrics.precioPromedioUvaPorQqLb.value)} / ${formatNumber(props.metrics.precioPromedioUvaPorQq.value)}`
return `${formatNumber(props.data.precio_promedio_uva_por_lb)} / ${formatNumber(props.data.precio_promedio_uva_por_qq)}`
}
}

View File

@@ -9,7 +9,7 @@
<div class="flex flex-col">
<span class="text-xs uppercase tracking-wide opacity-80 mb-1">Total Lb Neto de Verde</span>
<div class="flex items-baseline gap-2">
<span class="text-2xl font-bold">{{ formatNumber(metrics.totalLbNetoVerde.value) }}</span>
<span class="text-2xl font-bold">{{ formatNumber(data.total_lb_neto_verde) }}</span>
<span class="text-sm font-bold opacity-70">lb</span>
</div>
</div>
@@ -18,7 +18,7 @@
<div class="flex flex-col">
<span class="text-xs uppercase tracking-wide opacity-80 mb-1">Precio Promedio Ponderado Pagado</span>
<div class="flex items-baseline gap-2">
<span class="text-2xl font-bold">{{ formatNumber(metrics.precioPromedioVerdePagado.value) }}</span>
<span class="text-2xl font-bold">{{ formatNumber(data.precio_promedio_verde_pagado) }}</span>
<span class="text-sm font-bold opacity-70">L./lb</span>
</div>
</div>
@@ -27,7 +27,7 @@
<div class="flex flex-col">
<span class="text-xs uppercase tracking-wide opacity-80 mb-1">Total Lb Neto de Verde en Depósito</span>
<div class="flex items-baseline gap-2">
<span class="text-2xl font-bold">{{ formatNumber(metrics.totalLbNetoVerdeDeposito.value) }}</span>
<span class="text-2xl font-bold">{{ formatNumber(data.total_lb_neto_verde_deposito) }}</span>
<span class="text-sm font-bold opacity-70">lb</span>
</div>
</div>
@@ -36,7 +36,7 @@
<div class="flex flex-col">
<span class="text-xs uppercase tracking-wide opacity-80 mb-1">Inversión en Verde Hasta la Fecha</span>
<div class="flex items-baseline gap-2">
<span class="text-2xl font-bold">{{ formatCurrency(metrics.inversionVerdeHastaFecha.value) }}</span>
<span class="text-2xl font-bold">{{ formatCurrency(data.inversion_verde_hasta_fecha) }}</span>
</div>
</div>
</div>
@@ -44,7 +44,7 @@
<div class="flex flex-col">
<span class="text-xs uppercase tracking-wide opacity-80 mb-1">Inversión Restante a Realizar en Verde</span>
<div class="flex items-baseline gap-2">
<span class="text-2xl font-bold">{{ formatCurrency(metrics.inversionRestanteVerde.value) }}</span>
<span class="text-2xl font-bold">{{ formatCurrency(data.inversion_restante_verde) }}</span>
</div>
</div>
</div>
@@ -52,7 +52,7 @@
<div class="flex flex-col">
<span class="text-xs uppercase tracking-wide opacity-80 mb-1">Total Lb Neto Comprado de Verde</span>
<div class="flex items-baseline gap-2">
<span class="text-2xl font-bold">{{ formatNumber(metrics.totalLbNetoCompradoVerde.value) }}</span>
<span class="text-2xl font-bold">{{ formatNumber(data.total_lb_neto_comprado_verde) }}</span>
<span class="text-sm font-bold opacity-70">lb</span>
</div>
</div>
@@ -62,10 +62,16 @@
</template>
<script setup lang="ts">
import type { IngresosMetrics } from '~/composables/useIngresosMetrics'
// Props reciben datos directos de Metabase - SIN composables
defineProps<{
metrics: IngresosMetrics
data: {
total_lb_neto_verde: number
precio_promedio_verde_pagado: number
total_lb_neto_verde_deposito: number
inversion_verde_hasta_fecha: number
inversion_restante_verde: number
total_lb_neto_comprado_verde: number
}
}>()
const formatNumber = (value: number) => {

View File

@@ -1,3 +1,26 @@
/**
* ⚠️ ADVERTENCIA - COMPOSABLE OBSOLETO PARA PANORAMA FACTURADOR ⚠️
*
* Este composable NO debe usarse en el Panorama Facturador.
*
* FILOSOFÍA DE LA APLICACIÓN:
* "Metabase calcula TODO. Vue solo renderiza."
*
* Los cálculos de métricas, agregaciones y promedios ponderados DEBEN
* hacerse en Metabase mediante SQL. Los componentes de Vue solo deben
* recibir datos ya calculados y renderizarlos.
*
* Este composable existe para compatibilidad con otras páginas legacy,
* pero NO debe usarse en nuevas funcionalidades del Panorama.
*
* Para el Panorama Facturador, consulta:
* - METABASE_QUERIES_PANORAMA.md
* - server/api/metabase/panorama.post.ts
* - pages/panorama.vue
*
* Última actualización: 2025-10-27
*/
import { computed } from 'vue'
import type { ComputedRef } from 'vue'

View File

@@ -1,3 +1,27 @@
/**
* ⚠️ ADVERTENCIA - COMPOSABLE OBSOLETO PARA PANORAMA FACTURADOR ⚠️
*
* Este composable NO debe usarse en el Panorama Facturador.
*
* FILOSOFÍA DE LA APLICACIÓN:
* "Metabase calcula TODO. Vue solo renderiza."
*
* Los cálculos de métricas, agregaciones y promedios DEBEN hacerse
* en Metabase mediante SQL. Los componentes de Vue solo deben recibir
* datos ya calculados y renderizarlos.
*
* Este composable existe para compatibilidad con otras páginas legacy,
* pero NO debe usarse en nuevas funcionalidades del Panorama.
*
* Para el Panorama Facturador, consulta:
* - METABASE_QUERIES_PANORAMA.md (Query #6: panorama_rechazos_subproductos)
* - server/api/metabase/panorama.post.ts
* - pages/panorama.vue
* - components/rechazos/RechazosSubproductos.vue
*
* Última actualización: 2025-10-27
*/
import { computed } from 'vue'
import type { ComputedRef } from 'vue'

View File

@@ -36,18 +36,17 @@ export default defineEventHandler(async (event) => {
}
// Build parameters array for Metabase queries
// IMPORTANTE: Si los parámetros son null, usar los valores por defecto de Metabase
// En este caso, las queries usan los defaults: fecha_desde="2025-09-10", fecha_hasta="2025-10-14"
// Los valores null se pasan directamente a Metabase para que use sus propios defaults
const parameters = [
{
type: 'date/single',
target: ['variable', ['template-tag', 'fecha_desde']],
value: fecha_desde || '2025-09-10' // Usar default si es null
value: fecha_desde
},
{
type: 'date/single',
target: ['variable', ['template-tag', 'fecha_hasta']],
value: fecha_hasta || '2025-10-14' // Usar default si es null
value: fecha_hasta
},
{
type: 'category',