Ingeniería S&OP II: Demand Planning, de la Adivinación a la Probabilidad
Source: Dev.to
Resumen Ejecutivo
Un forecast probabilístico no te dice “venderás 100”. Te dice “con un 95 % de probabilidad, venderás entre 35 y 157”. Esa banda de incertidumbre es la base matemática para calcular tu Safety Stock sin recurrir a reglas de dedo.
Diseño de la tabla demand_forecasts
CREATE TABLE demand_forecasts (
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
execution_date DATE NOT NULL, -- Cuándo corrimos el modelo
ds DATE NOT NULL, -- Fecha futura predicha
yhat NUMERIC NOT NULL, -- Predicción central
yhat_lower NUMERIC NOT NULL, -- Límite inferior (Safety Stock)
yhat_upper NUMERIC NOT NULL, -- Límite superior (Riesgo)
model_version TEXT NOT NULL, -- Trazabilidad
UNIQUE(execution_date, ds) -- Un forecast por ejecución y fecha
);
¿Por qué execution_date?
Dentro de 6 meses, cuando quieras auditar la precisión del modelo, necesitarás saber cuándo se hizo la predicción y contra qué datos reales se comparó. Esta práctica, conocida como Snapshotting en MLOps, permite evaluar el drift del modelo a lo largo del tiempo. Sin esta columna tienes un modelo; con ella tienes un sistema auditable.
Modelo Prophet
Prophet es un motor de series temporales desarrollado por Meta, diseñado para datos de negocio irregulares: gaps, festivos y cambios de tendencia. Es ideal para una cadena de suministro real.
Clase ProphetPredictor
def train_model(self, country_code='ES'):
"""
Entrena Prophet con dos configuraciones clave para S&OP:
- interval_width: Ancho del intervalo de confianza
- country_holidays: Contexto operativo del país
"""
self.model = Prophet(
interval_width=0.95, # Intervalo de confianza del 95 %
weekly_seasonality=True,
yearly_seasonality=True
)
# Festivos que alteran patrones de carga/descarga
self.model.add_country_holidays(country_name=country_code)
self.model.fit(self.ts_df)
Decisiones de ingeniería
-
interval_width=0.95: No es decorativo. El límite superior (yhat_upper) representa la demanda máxima probable con un 95 % de confianza y sirve directamente para calcular el Safety Stock:
Safety Stock = yhat_upper - yhat. -
add_country_holidays('ES'): Los festivos son anomalías operativas (cierre de fábrica, almacén, transporte). Si el modelo ignora, por ejemplo, el 15 de agosto en España, interpretará la caída de pedidos como una tendencia a la baja, corrompiendo la predicción de septiembre.
Visualización y XAI (Explainable AI)
Los puntos negros son tu demanda histórica real. En la visualización:
- Línea azul: Predicción central (
yhat). - Banda sombreada: Intervalo de confianza al 95 %.
A mayor volatilidad histórica, más ancha es la banda → más Safety Stock necesitas → más capital inmovilizas. Esta banda es la conversación que deberías tener con tu CFO.
El modelo separa:
- Tendencia (crecimiento o decrecimiento del negocio)
- Estacionalidad (picos por época del año)
- Efecto de festivos
Esto permite al Director General aprobar el plan de operaciones con evidencia visual.
Entorno interactivo
He preparado un notebook aislado donde puedes entrenar el modelo sobre un snapshot anonimizado de los datos limpiados en el Capítulo 1. No necesitas instalar Python, Prophet ni configurar Supabase; solo un navegador.
- 📎 Abrir el Notebook interactivo en Google Colab
Ejecuta las celdas, observa cómo se genera un forecast probabilístico con bandas de incertidumbre y experimenta modificando horizonte, país e intervalo de confianza.
Próximos pasos
En el Capítulo 3 introduciremos Optimización Matemática (PuLP) y Teoría de Restricciones para transformar las predicciones en decisiones de suministro: cuánto comprar, cuándo producir y cómo distribuir recursos finitos minimizando costes.
La diferencia entre un Director de Operaciones que reacciona y uno que decide es un modelo matemático entre los datos y la acción.