Ir al contenido

ADR — Migración de METAS MES (costos) a Data Warehouse (.db)

Implementada/Aceptada (2026-06-08, commit 64febc8). Propuesta original 2026-06-07; implementada mediante el change SDD costos-metas-a-dw (archivado en openspec/changes/archive/2026-06-08-costos-metas-a-dw/). Verificación PASS-WITH-WARNINGS (0 bloqueantes).

Situación actual de METAS MES en la planilla

Sección titulada «Situación actual de METAS MES en la planilla»

Las hojas METAS MES0 y METAS MES de la planilla de costos no contienen cálculos propios: son artefactos de reshape y acumulación manual que existen por una limitación del modelo de la hoja.

METAS MES0 es un transpose puro. Fórmulas:

=TRANSPONER('COSTOS OSORNO'!D49:P49) → columna id_Brigada
=TRANSPONER('COSTOS OSORNO'!D44:P44) → columna HB Día Esperado

La planilla organiza las 13 brigadas de Osorno como columnas (D:P) en la hoja COSTOS OSORNO. TRANSPONER las convierte en filas para producir una tabla [id_Brigada, HB Día Esperado] correspondiente al mes activo del selector (fila 52).

METAS MES = apilado vertical de METAS MES0 de distintos meses. El operador cambia el selector al mes deseado, copia METAS MES0 y pega los valores al final de METAS MES. Existe solo porque la planilla calcula un mes a la vez: para tener la serie histórica hay que repetir este proceso manualmente mes a mes.

Mientras que METAS MES/MES0 son artefactos manuales frágiles, la lógica de costos pesada ya vive en Python:

  • El pipeline business_costos.py computa toda la lógica por brigada-mes de forma autónoma (SCD en memoria, cross-join vectorizado, sentinels YAML, rentabilidad, divisor 20 días).
  • ResultadoCostos.costo_por_hh ya contiene la información equivalente a METAS MES en formato largo (zonal, id_Brigada, mes), cubriendo todos los meses de una corrida en un solo paso.
  • Hoy persiste solo a Excel y GSheets; no escribe a ningún .db y no está en PIPELINES_V2.

El objetivo de esta ADR es llevar costo_por_hh al Data Warehouse y hacer que METAS MES sea una vista SQL, eliminando el proceso manual de la planilla.

Opción A — Mantener METAS MES en la planilla (status quo)

Sección titulada «Opción A — Mantener METAS MES en la planilla (status quo)»

Seguir acumulando manualmente mes a mes con el selector + copiar-pegar.

Descartada: frágil (error humano, olvido); no es reproducible; no integra con el DW; no escala a más zonales.

Opción B — Volcar costo_por_hh directamente a fact_costos_hh (ELEGIDA)

Sección titulada «Opción B — Volcar costo_por_hh directamente a fact_costos_hh (ELEGIDA)»

df.to_sql("fact_costos_hh", ...) desde el resultado ya computado por el pipeline. METAS MES pasa a ser un SELECT.

Elegida porque: no requiere lógica nueva (el cálculo ya existe); grano natural del dataframe; idempotente (replace + key); METAS MES se convierte en vista trivial; integra con el epílogo --migrate del orquestador (ADR-Data-Warehouse-Epilogue).

Opción C — Tabla intermedia fact_costos_metas separada

Sección titulada «Opción C — Tabla intermedia fact_costos_metas separada»

Crear una tabla con solo [mes, zonal, id_Brigada, hb_dia_total] en lugar de la tabla completa.

Descartada: duplica información que ya está en fact_costos_hh; pierde el contexto de costos completo; complica el linaje.

Opción B. Se crea fact_costos_hh como volcado directo de costo_por_hh (grano: zonal, id_Brigada, mes). Las hojas METAS MES / METAS MES0 desaparecen como artefactos independientes: METAS MES pasa a ser la vista/SELECT:

SELECT mes, zonal, id_Brigada, hb_dia_total
FROM fact_costos_hh;
CampoTipo SQLiteOrigenNota
zonalTEXTpipeline”OSORNO” / “CHILOE”
id_BrigadaTEXTfila 49Pregunta abierta: TEXT (BBPP-OS-01) vs INTEGER (1-13)
mesTEXT (YYYY-MM-01)ciclo de corridaPrimer día del mes como string ISO
subtotal_personalINTEGERdetalle agregado
subtotal_flotaINTEGERdetalle agregado
subtotal_gastos_gralesINTEGERdetalle agregado
total_sin_vehiculosINTEGERfila 37
total_valor_brigadaINTEGERfila 38CON vehículos
rentabilidadREALfila 39Factor (ej. 0.08 = 8 %)
total_sin_vhc_utilidadINTEGERfila 40
total_costos_utilidadINTEGERfila 41”META ZONAL”
hh_disponiblesREALcálculo interno
hb_mensualREALfila 47CON vehículos
hb_dia_totalREALfila 44Ex hb_dia_esperado — MANT. CORRECTIVO, CON vehículos
hb_mensual_svREALfila 47SIN vehículos
hb_dia_prodREALfila 48Ex hb_dia_esperado_sv — PROGRAMACIÓN, SIN vehículos

Alternativa opcional: fact_costos_detalle con grano brigada × mes × ítem (el nivel detalle de ResultadoCostos). Útil para auditoría; no bloquea la decisión principal.

Se renombran dos campos en toda la capa Python y DW:

Nombre actualNombre nuevoCriterio
hb_dia_esperadohb_dia_totalNombre por criterio consistente: “total” = CON vehículos (MANT. CORRECTIVO)
hb_dia_esperado_svhb_dia_prod”prod” = productividad/programación, SIN vehículos

El par hb_dia_total / hb_dia_prod es corto, simétrico y semánticamente claro.

Archivos afectados por el rename:

  • domains/costos/costos_schema.py
  • domains/costos/business_costos.py
  • domains/costos/excel_writer_costos.py
  • domains/costos/ACTUALIZAR_COSTOS.py
  • METAS MES deja de ser un proceso manual; pasa a ser una vista SQL reproducible.
  • fact_costos_hh integra con el epílogo --migrate del orquestador: un solo run_pipeline.py --all --migrate actualiza el DW y Supabase.
  • Nomenclatura más clara y consistente para consumidores (Power BI, futuros análisis).
  • Cobertura histórica sin límite: la corrida calcula todos los meses del rango de una vez.
  • El rename requiere tocar 4 archivos Python y actualizar Power BI (campos calculados que referencien los nombres viejos).
  • Las hojas COSTOS / METAS de la planilla quedan deprecadas; hay que coordinar con el equipo el momento del corte.
  • La planilla de Google Sheets puede seguir funcionando en paralelo durante la transición (no se elimina hasta que el DW esté validado).

Estado de implementación (2026-06-08, commit 64febc8)

Sección titulada «Estado de implementación (2026-06-08, commit 64febc8)»
ItemEstadoNota
export_costos() en data_warehouse.py invocada desde populate()✅ ImplementadoIntegración por populate(), NO por PIPELINES_V2 (costos no es BasePipeline)
fact_costos_hh en data_warehouse.db✅ ImplementadoGrano (zonal, id_Brigada, mes), 16 cols, replace+guard
Vista metas_mes✅ ImplementadoDROP+CREATE en cada export_costos()
Mapping Supabase (MAPPINGS_DATA_WAREHOUSE)✅ Implementadomigrate_config.py actualizado
Rename hb_dia_esperado/svhb_dia_total/prod✅ ImplementadoBREAKING CHANGE — ver nota de migración
Relaciones en Power BI⏳ PendienteConfiguración manual del modelo PBI fuera de scope
Deprecar hojas COSTOS / METAS en GSheets⏳ PendientePost-validación del DW con datos reales

Nota de integración: El pipeline costos NO es un BasePipeline (no consume correos IMAP). La integración al DW se hace directamente en populate() de data_warehouse.py, siguiendo el patrón de export_presupuesto_inicial() y export_dim_ot(). No se registra en PIPELINES_V2.

PreguntaOpcionesImpacto
id_Brigada: ¿TEXT o INTEGER?TEXT (BBPP-OS-01) vs INTEGER (1-13)Joins con dim_brigada, compatibilidad con planilla
¿Incluir fact_costos_detalle?Sí (grano ítem) / No (solo fact_costos_hh)Tamaño del DW, utilidad para auditoría
¿El rename aplica solo a capa .db o también a nombres internos de Python?Solo DW (alias en to_sql) vs rename totalRiesgo de inconsistencia entre capas