Files
analiticaNucleo/METABASE_QUERIES_COMPARATIVA_COSECHAS.md
josedario87 f8c53da6fc
All checks were successful
build-and-deploy / build (push) Successful in 43s
build-and-deploy / deploy (push) Successful in 4s
feat: restaurar panorama facturador con nueva arquitectura basada en Metabase
- Crear endpoint /api/metabase/panorama.post.ts que ejecuta las 9 queries en paralelo
- Restaurar y adaptar panorama.vue para usar el nuevo endpoint
- Crear componentes auxiliares: SecosVendidos, TotalesIngresoCompra, TotalesMonetarios, TotalesVerde, MetricBox, RechazosRechazoCard
- Adaptar RechazosSubproductos para recibir data directamente de Metabase
- Toda la transformación de datos ocurre en las queries SQL de Metabase
- Sin uso de stores ni composables de métricas
- Agregar documentación de queries en archivos MD
2025-10-14 10:34:27 -06:00

556 lines
19 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 📊 QUERIES DE METABASE PARA COMPARATIVA DE COSECHAS
## 🎯 FILOSOFÍA
**Metabase calcula TODO. Vue solo renderiza.**
Esta página trabaja con **`vista_resumen_ingresos`** (datos agregados por día), NO con `vista_detalle_ingresos`.
---
## 📋 PARÁMETROS GLOBALES
Todas las queries aceptan estos parámetros:
| Parámetro | Tipo | Default | Descripción |
|-----------|------|---------|-------------|
| `cosechas_ids` | Array[String] | `[]` | Array de IDs de cosechas: 'cosecha-20-21', 'cosecha-21-22', etc. |
| `incluir_anulados` | Boolean | `false` | Si `false`, excluye registros anulados |
**IMPORTANTE:** En lugar de fechas libres, se usan IDs de cosechas que se mapean a rangos predefinidos en el backend.
---
## 📅 DEFINICIÓN DE COSECHAS (HARDCODED)
Estas definiciones están hardcoded en el código pero necesitan estar también en las queries de Metabase:
```sql
-- Mapping de cosechas a rangos de fechas
CASE
WHEN 'cosecha-20-21' = ANY({{cosechas_ids}}) AND fecha >= '2020-09-08' AND fecha <= '2021-09-07' THEN 'cosecha-20-21'
WHEN 'cosecha-21-22' = ANY({{cosechas_ids}}) AND fecha >= '2021-09-08' AND fecha <= '2022-09-07' THEN 'cosecha-21-22'
WHEN 'cosecha-22-23' = ANY({{cosechas_ids}}) AND fecha >= '2022-09-08' AND fecha <= '2023-09-07' THEN 'cosecha-22-23'
WHEN 'cosecha-23-24' = ANY({{cosechas_ids}}) AND fecha >= '2023-09-08' AND fecha <= '2024-09-07' THEN 'cosecha-23-24'
WHEN 'cosecha-24-25' = ANY({{cosechas_ids}}) AND fecha >= '2024-09-08' AND fecha <= '2025-09-07' THEN 'cosecha-24-25'
WHEN 'cosecha-25-26' = ANY({{cosechas_ids}}) AND fecha >= '2025-09-08' THEN 'cosecha-25-26'
ELSE NULL
END as cosecha_id
```
---
## 🎨 QUERIES POR COMPONENTE
### Query 1: `comparativa_datos_diarios_completos`
**Componente:** `CosechasHeatmap.vue`, `CosechasEvolucion.vue`
**Devuelve:** Múltiples filas (una por día de cada cosecha seleccionada)
```sql
WITH cosechas_mapping AS (
SELECT
fecha,
-- Identificar a qué cosecha pertenece cada fecha
CASE
WHEN fecha >= '2020-09-08' AND fecha <= '2021-09-07' THEN 'cosecha-20-21'
WHEN fecha >= '2021-09-08' AND fecha <= '2022-09-07' THEN 'cosecha-21-22'
WHEN fecha >= '2022-09-08' AND fecha <= '2023-09-07' THEN 'cosecha-22-23'
WHEN fecha >= '2023-09-08' AND fecha <= '2024-09-07' THEN 'cosecha-23-24'
WHEN fecha >= '2024-09-08' AND fecha <= '2025-09-07' THEN 'cosecha-24-25'
WHEN fecha >= '2025-09-08' THEN 'cosecha-25-26'
ELSE NULL
END as cosecha_id,
-- Calcular día relativo dentro de cada cosecha (empieza en 1)
CASE
WHEN fecha >= '2020-09-08' AND fecha <= '2021-09-07'
THEN (fecha - DATE '2020-09-08') + 1
WHEN fecha >= '2021-09-08' AND fecha <= '2022-09-07'
THEN (fecha - DATE '2021-09-08') + 1
WHEN fecha >= '2022-09-08' AND fecha <= '2023-09-07'
THEN (fecha - DATE '2022-09-08') + 1
WHEN fecha >= '2023-09-08' AND fecha <= '2024-09-07'
THEN (fecha - DATE '2023-09-08') + 1
WHEN fecha >= '2024-09-08' AND fecha <= '2025-09-07'
THEN (fecha - DATE '2024-09-08') + 1
WHEN fecha >= '2025-09-08'
THEN (fecha - DATE '2025-09-08') + 1
ELSE NULL
END as dia_relativo,
-- Métricas del día
COALESCE(total_peso_seco, 0) as total_peso_seco,
COALESCE(peso_neto_uva, 0) as peso_neto_uva,
COALESCE(peso_neto_verde, 0) as peso_neto_verde,
COALESCE(sacos_total_dia, 0) as sacos_total_dia,
COALESCE(total_lempiras_uva, 0) as total_lempiras_uva,
COALESCE(total_lempiras_verde, 0) as total_lempiras_verde,
COALESCE(total_lempiras_mojado, 0) as total_lempiras_mojado,
COALESCE(total_lempiras_oreado, 0) as total_lempiras_oreado,
COALESCE(total_lempiras_mojado, 0) + COALESCE(total_lempiras_oreado, 0) as total_lempiras_mojado_oreado
FROM vista_resumen_ingresos
WHERE
({{incluir_anulados}} OR (estado != 'anulado' AND fecha_anulado IS NULL))
-- Filtrar solo las fechas que pertenecen a cosechas seleccionadas
AND (
(fecha >= '2020-09-08' AND fecha <= '2021-09-07' AND 'cosecha-20-21' = ANY({{cosechas_ids}}))
OR (fecha >= '2021-09-08' AND fecha <= '2022-09-07' AND 'cosecha-21-22' = ANY({{cosechas_ids}}))
OR (fecha >= '2022-09-08' AND fecha <= '2023-09-07' AND 'cosecha-22-23' = ANY({{cosechas_ids}}))
OR (fecha >= '2023-09-08' AND fecha <= '2024-09-07' AND 'cosecha-23-24' = ANY({{cosechas_ids}}))
OR (fecha >= '2024-09-08' AND fecha <= '2025-09-07' AND 'cosecha-24-25' = ANY({{cosechas_ids}}))
OR (fecha >= '2025-09-08' AND 'cosecha-25-26' = ANY({{cosechas_ids}}))
)
)
SELECT
fecha,
cosecha_id,
dia_relativo,
total_peso_seco,
peso_neto_uva,
peso_neto_verde,
sacos_total_dia,
total_lempiras_uva,
total_lempiras_verde,
total_lempiras_mojado,
total_lempiras_oreado,
total_lempiras_mojado_oreado
FROM cosechas_mapping
WHERE cosecha_id IS NOT NULL
ORDER BY cosecha_id, fecha;
```
**Respuesta esperada:**
```json
[
{
"fecha": "2025-01-15",
"cosecha_id": "cosecha-24-25",
"dia_relativo": 130,
"total_peso_seco": 45.50,
"peso_neto_uva": 2250.00,
"peso_neto_verde": 500.00,
"sacos_total_dia": 50,
"total_lempiras_uva": 11475.00,
"total_lempiras_verde": 5000.00,
"total_lempiras_mojado": 8000.00,
"total_lempiras_oreado": 6000.00,
"total_lempiras_mojado_oreado": 14000.00
}
]
```
---
### Query 2: `comparativa_totales_por_cosecha`
**Componente:** `CosechasTotales.vue`
**Devuelve:** Múltiples filas (una por cosecha seleccionada con totales agregados)
```sql
WITH cosechas_mapping AS (
SELECT
CASE
WHEN fecha >= '2020-09-08' AND fecha <= '2021-09-07' THEN 'cosecha-20-21'
WHEN fecha >= '2021-09-08' AND fecha <= '2022-09-07' THEN 'cosecha-21-22'
WHEN fecha >= '2022-09-08' AND fecha <= '2023-09-07' THEN 'cosecha-22-23'
WHEN fecha >= '2023-09-08' AND fecha <= '2024-09-07' THEN 'cosecha-23-24'
WHEN fecha >= '2024-09-08' AND fecha <= '2025-09-07' THEN 'cosecha-24-25'
WHEN fecha >= '2025-09-08' THEN 'cosecha-25-26'
ELSE NULL
END as cosecha_id,
total_peso_seco,
peso_neto_uva,
peso_neto_verde,
sacos_total_dia,
total_lempiras_uva,
total_lempiras_verde,
total_lempiras_mojado,
total_lempiras_oreado
FROM vista_resumen_ingresos
WHERE
({{incluir_anulados}} OR (estado != 'anulado' AND fecha_anulado IS NULL))
AND (
(fecha >= '2020-09-08' AND fecha <= '2021-09-07' AND 'cosecha-20-21' = ANY({{cosechas_ids}}))
OR (fecha >= '2021-09-08' AND fecha <= '2022-09-07' AND 'cosecha-21-22' = ANY({{cosechas_ids}}))
OR (fecha >= '2022-09-08' AND fecha <= '2023-09-07' AND 'cosecha-22-23' = ANY({{cosechas_ids}}))
OR (fecha >= '2023-09-08' AND fecha <= '2024-09-07' AND 'cosecha-23-24' = ANY({{cosechas_ids}}))
OR (fecha >= '2024-09-08' AND fecha <= '2025-09-07' AND 'cosecha-24-25' = ANY({{cosechas_ids}}))
OR (fecha >= '2025-09-08' AND 'cosecha-25-26' = ANY({{cosechas_ids}}))
)
)
SELECT
cosecha_id,
-- Totales agregados
COUNT(*) as num_dias,
COALESCE(SUM(total_peso_seco), 0) as peso_total,
COALESCE(SUM(peso_neto_uva), 0) as peso_uva_total,
COALESCE(SUM(peso_neto_verde), 0) as peso_verde_total,
COALESCE(SUM(sacos_total_dia), 0) as sacos_total,
-- Inversiones totales
COALESCE(SUM(total_lempiras_uva), 0) as inversion_uva_total,
COALESCE(SUM(total_lempiras_verde), 0) as inversion_verde_total,
COALESCE(SUM(total_lempiras_mojado), 0) as inversion_mojado_total,
COALESCE(SUM(total_lempiras_oreado), 0) as inversion_oreado_total,
COALESCE(SUM(total_lempiras_uva), 0) +
COALESCE(SUM(total_lempiras_verde), 0) +
COALESCE(SUM(total_lempiras_mojado), 0) +
COALESCE(SUM(total_lempiras_oreado), 0) as inversion_total,
-- Promedios diarios
AVG(total_peso_seco) as peso_promedio_diario,
AVG(sacos_total_dia) as sacos_promedio_diario
FROM cosechas_mapping
WHERE cosecha_id IS NOT NULL
GROUP BY cosecha_id
ORDER BY cosecha_id;
```
**Respuesta esperada:**
```json
[
{
"cosecha_id": "cosecha-23-24",
"num_dias": 365,
"peso_total": 16425.50,
"peso_uva_total": 10000.00,
"peso_verde_total": 3000.00,
"sacos_total": 18250,
"inversion_uva_total": 4500000.00,
"inversion_verde_total": 300000.00,
"inversion_mojado_total": 800000.00,
"inversion_oreado_total": 600000.00,
"inversion_total": 6200000.00,
"peso_promedio_diario": 45.00,
"sacos_promedio_diario": 50.00
},
{
"cosecha_id": "cosecha-24-25",
"num_dias": 130,
"peso_total": 5850.00,
"peso_uva_total": 3500.00,
"peso_verde_total": 1000.00,
"sacos_total": 6500,
"inversion_uva_total": 1575000.00,
"inversion_verde_total": 100000.00,
"inversion_mojado_total": 280000.00,
"inversion_oreado_total": 210000.00,
"inversion_total": 2165000.00,
"peso_promedio_diario": 45.00,
"sacos_promedio_diario": 50.00
}
]
```
---
### Query 3: `comparativa_datos_acumulados_por_dia`
**Componente:** `CosechasEvolucion.vue` (modo acumulado)
**Devuelve:** Múltiples filas (una por día con valores acumulados)
```sql
WITH cosechas_mapping AS (
SELECT
fecha,
CASE
WHEN fecha >= '2020-09-08' AND fecha <= '2021-09-07' THEN 'cosecha-20-21'
WHEN fecha >= '2021-09-08' AND fecha <= '2022-09-07' THEN 'cosecha-21-22'
WHEN fecha >= '2022-09-08' AND fecha <= '2023-09-07' THEN 'cosecha-22-23'
WHEN fecha >= '2023-09-08' AND fecha <= '2024-09-07' THEN 'cosecha-23-24'
WHEN fecha >= '2024-09-08' AND fecha <= '2025-09-07' THEN 'cosecha-24-25'
WHEN fecha >= '2025-09-08' THEN 'cosecha-25-26'
ELSE NULL
END as cosecha_id,
CASE
WHEN fecha >= '2020-09-08' AND fecha <= '2021-09-07'
THEN (fecha - DATE '2020-09-08') + 1
WHEN fecha >= '2021-09-08' AND fecha <= '2022-09-07'
THEN (fecha - DATE '2021-09-08') + 1
WHEN fecha >= '2022-09-08' AND fecha <= '2023-09-07'
THEN (fecha - DATE '2022-09-08') + 1
WHEN fecha >= '2023-09-08' AND fecha <= '2024-09-07'
THEN (fecha - DATE '2023-09-08') + 1
WHEN fecha >= '2024-09-08' AND fecha <= '2025-09-07'
THEN (fecha - DATE '2024-09-08') + 1
WHEN fecha >= '2025-09-08'
THEN (fecha - DATE '2025-09-08') + 1
ELSE NULL
END as dia_relativo,
total_peso_seco
FROM vista_resumen_ingresos
WHERE
({{incluir_anulados}} OR (estado != 'anulado' AND fecha_anulado IS NULL))
AND (
(fecha >= '2020-09-08' AND fecha <= '2021-09-07' AND 'cosecha-20-21' = ANY({{cosechas_ids}}))
OR (fecha >= '2021-09-08' AND fecha <= '2022-09-07' AND 'cosecha-21-22' = ANY({{cosechas_ids}}))
OR (fecha >= '2022-09-08' AND fecha <= '2023-09-07' AND 'cosecha-22-23' = ANY({{cosechas_ids}}))
OR (fecha >= '2023-09-08' AND fecha <= '2024-09-07' AND 'cosecha-23-24' = ANY({{cosechas_ids}}))
OR (fecha >= '2024-09-08' AND fecha <= '2025-09-07' AND 'cosecha-24-25' = ANY({{cosechas_ids}}))
OR (fecha >= '2025-09-08' AND 'cosecha-25-26' = ANY({{cosechas_ids}}))
)
)
SELECT
fecha,
cosecha_id,
dia_relativo,
total_peso_seco as peso_dia,
-- Acumulado hasta este día (dentro de cada cosecha)
SUM(total_peso_seco) OVER (
PARTITION BY cosecha_id
ORDER BY fecha
ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW
) as peso_acumulado
FROM cosechas_mapping
WHERE cosecha_id IS NOT NULL
ORDER BY cosecha_id, fecha;
```
**Respuesta esperada:**
```json
[
{
"fecha": "2024-09-08",
"cosecha_id": "cosecha-24-25",
"dia_relativo": 1,
"peso_dia": 45.50,
"peso_acumulado": 45.50
},
{
"fecha": "2024-09-09",
"cosecha_id": "cosecha-24-25",
"dia_relativo": 2,
"peso_dia": 50.00,
"peso_acumulado": 95.50
}
]
```
---
### Query 4: `comparativa_metadata_cosechas`
**Propósito:** Obtener información de disponibilidad de cada cosecha (cuántos días tiene datos)
**Devuelve:** Múltiples filas (una por cosecha)
```sql
WITH cosechas_definidas AS (
SELECT unnest(ARRAY[
'cosecha-20-21',
'cosecha-21-22',
'cosecha-22-23',
'cosecha-23-24',
'cosecha-24-25',
'cosecha-25-26'
]) as cosecha_id
),
cosechas_mapping AS (
SELECT
CASE
WHEN fecha >= '2020-09-08' AND fecha <= '2021-09-07' THEN 'cosecha-20-21'
WHEN fecha >= '2021-09-08' AND fecha <= '2022-09-07' THEN 'cosecha-21-22'
WHEN fecha >= '2022-09-08' AND fecha <= '2023-09-07' THEN 'cosecha-22-23'
WHEN fecha >= '2023-09-08' AND fecha <= '2024-09-07' THEN 'cosecha-23-24'
WHEN fecha >= '2024-09-08' AND fecha <= '2025-09-07' THEN 'cosecha-24-25'
WHEN fecha >= '2025-09-08' THEN 'cosecha-25-26'
ELSE NULL
END as cosecha_id,
fecha
FROM vista_resumen_ingresos
WHERE
({{incluir_anulados}} OR (estado != 'anulado' AND fecha_anulado IS NULL))
)
SELECT
d.cosecha_id,
COALESCE(COUNT(m.fecha), 0) as num_registros,
MIN(m.fecha) as primera_fecha,
MAX(m.fecha) as ultima_fecha,
-- Verificar si tiene datos hasta hoy
CASE
WHEN MAX(m.fecha) >= CURRENT_DATE THEN true
ELSE false
END as tiene_datos_hasta_hoy
FROM cosechas_definidas d
LEFT JOIN cosechas_mapping m ON d.cosecha_id = m.cosecha_id
GROUP BY d.cosecha_id
ORDER BY d.cosecha_id;
```
**Respuesta esperada:**
```json
[
{ "cosecha_id": "cosecha-20-21", "num_registros": 200, "primera_fecha": "2020-09-08", "ultima_fecha": "2021-09-07", "tiene_datos_hasta_hoy": false },
{ "cosecha_id": "cosecha-21-22", "num_registros": 0, "primera_fecha": null, "ultima_fecha": null, "tiene_datos_hasta_hoy": false },
{ "cosecha_id": "cosecha-22-23", "num_registros": 300, "primera_fecha": "2022-09-08", "ultima_fecha": "2023-09-07", "tiene_datos_hasta_hoy": false },
{ "cosecha_id": "cosecha-23-24", "num_registros": 365, "primera_fecha": "2023-09-08", "ultima_fecha": "2024-09-07", "tiene_datos_hasta_hoy": false },
{ "cosecha_id": "cosecha-24-25", "num_registros": 130, "primera_fecha": "2024-09-08", "ultima_fecha": "2025-01-15", "tiene_datos_hasta_hoy": true },
{ "cosecha_id": "cosecha-25-26", "num_registros": 0, "primera_fecha": null, "ultima_fecha": null, "tiene_datos_hasta_hoy": false }
]
```
---
## 📊 RESUMEN DE QUERIES
| # | Nombre | Componente | Tipo de respuesta |
|---|--------|------------|-------------------|
| 1 | `comparativa_datos_diarios_completos` | Heatmap, Evolución | Múltiples filas (días) |
| 2 | `comparativa_totales_por_cosecha` | Totales (barras) | Múltiples filas (cosechas) |
| 3 | `comparativa_datos_acumulados_por_dia` | Evolución (acumulado) | Múltiples filas (días) |
| 4 | `comparativa_metadata_cosechas` | Selector de cosechas | Múltiples filas (cosechas) |
**Total: 4 Queries**
---
## 🔧 CONFIGURACIÓN DE PARÁMETROS EN METABASE
### `cosechas_ids`
- **Tipo:** Text (se parsea como array)
- **Widget Type:** Text
- **Default:** `[]`
- **Required:** No
- **SQL Type:** `TEXT[]`
- **Valores posibles:** `['cosecha-20-21', 'cosecha-21-22', 'cosecha-22-23', 'cosecha-23-24', 'cosecha-24-25', 'cosecha-25-26']`
### `incluir_anulados`
- **Tipo:** Boolean
- **Widget Type:** Boolean
- **Default:** `false`
- **Required:** No
---
## 🚀 INTEGRACIÓN EN VUE
```typescript
// comparativa-cosechas.vue
const cosechasSeleccionadas = ref<string[]>(['cosecha-24-25', 'cosecha-25-26'])
const incluirAnulados = ref(false)
const params = computed(() => ({
cosechas_ids: cosechasSeleccionadas.value,
incluir_anulados: incluirAnulados.value
}))
async function loadAllData() {
const [
datosDiarios,
totalesCosechas,
datosAcumulados,
metadataCosechas
] = await Promise.all([
$fetch('/api/metabase/question/201', { query: params.value }),
$fetch('/api/metabase/question/202', { query: params.value }),
$fetch('/api/metabase/question/203', { query: params.value }),
$fetch('/api/metabase/question/204', { query: params.value })
])
// Asignar directamente
datos.value = {
diarios: datosDiarios.data.rows,
totales: totalesCosechas.data.rows,
acumulados: datosAcumulados.data.rows,
metadata: metadataCosechas.data.rows
}
}
// Transformar para componentes
const resumenIngresos = computed(() => {
// Convertir datos.value.diarios al formato que esperan los componentes
return datos.value.diarios.map(row => ({
fecha: row.fecha,
cosecha_id: row.cosecha_id,
dia_relativo: row.dia_relativo,
total_peso_seco: row.total_peso_seco,
peso_neto_uva: row.peso_neto_uva,
peso_neto_verde: row.peso_neto_verde,
sacos_total_dia: row.sacos_total_dia,
total_lempiras_uva: row.total_lempiras_uva,
total_lempiras_verde: row.total_lempiras_verde,
total_lempiras_mojado: row.total_lempiras_mojado,
total_lempiras_oreado: row.total_lempiras_oreado,
total_lempiras_mojado_oreado: row.total_lempiras_mojado_oreado
}))
})
```
---
## ⚠️ NOTAS IMPORTANTES
### 1. Fuente de Datos Diferente
Esta página usa **`vista_resumen_ingresos`**, NO `vista_detalle_ingresos`:
- **Granularidad:** Por DÍA (un registro = un día completo con totales)
- **Estructura:** Ya viene agregada con totales diarios
- **Performance:** Mucho más rápida porque trabaja con menos registros
### 2. Cosechas Hardcoded
Los rangos de cosechas están hardcoded:
- **Cosecha X-Y:** Del 8 de septiembre del año X al 7 de septiembre del año Y
- **Última cosecha:** Del 8 de septiembre hasta "hoy"
Si se agregan nuevas cosechas, hay que actualizar:
- Las queries de Metabase
- La definición en el código Vue (`cosechasDefiniciones`)
### 3. Día Relativo
Cada cosecha tiene su propio "día relativo" que empieza en 1:
- **Día 1:** 8 de septiembre (inicio de cosecha)
- **Día 365:** 7 de septiembre del año siguiente (fin de cosecha)
Esto permite comparar "día a día" entre cosechas diferentes.
### 4. Métricas Computadas
La métrica `total_lempiras_mojado_oreado` es computada:
```sql
COALESCE(total_lempiras_mojado, 0) + COALESCE(total_lempiras_oreado, 0)
```
### 5. Performance
- Query 1 puede devolver hasta ~2000 filas (365 días × 6 cosechas max)
- Query 3 usa window functions, requiere índice en `fecha`
- Recomendado: Crear índices en `vista_resumen_ingresos(fecha, estado)`
### 6. Heatmap y Evolución
Ambos componentes usan la **misma Query 1**, pero procesan los datos diferente:
- **Heatmap:** Muestra matriz de días × cosechas con colores
- **Evolución:** Muestra gráficos de líneas temporales
Por eso Query 1 debe devolver TODOS los campos necesarios.
---
## 📝 PRÓXIMOS PASOS
1. ✅ Crear estas 4 queries en Metabase
2. ✅ Configurar parámetros (especialmente `cosechas_ids` como array)
3. ✅ Probar con diferentes combinaciones de cosechas
4. ✅ Obtener los Question IDs
5. ✅ Adaptar `comparativa-cosechas.vue` para usar las nuevas queries
6. ✅ Crear función de transformación de datos para los componentes
7. ✅ Probar performance con datasets completos (365 días × N cosechas)
8. ✅ Verificar que el heatmap y evolución funcionen correctamente
---
**Documento creado:** 2025-10-14
**Autor:** Claude Code
**Proyecto:** Analítica Núcleo - Comparativa de Cosechas