Resumen ejecutivo

Pregunta de negocio: ¿qué está pasando en el sector construcción, qué señales anticipan el futuro y cómo debería reaccionar una empresa proveedora de concreto y cemento?

  • Qué pasa: después de dos años de contracción (2023–2024), la demanda de cemento volvió a crecer en 2025 (despachos 5,4% frente a 2024). La recuperación, sin embargo, no es pareja: el concreto premezclado, más ligado a edificaciones formales, todavía cerró el año en -3,6%, y las licencias de construcción se recuperaron en el acumulado anual (6,6%) pero cayeron con fuerza en noviembre y diciembre.
  • Qué anticipa: las señales son mixtas con sesgo positivo. El ciclo de corto plazo mejora (tendencia de los despachos acelerando), pero la base de proyectos futuros (licencias) aún no confirma una expansión sostenida del segmento de edificaciones.
  • Pronóstico: el modelo SARIMA con intervenciones proyecta para el primer trimestre de 2026 despachos cercanos a 3,27 millones de toneladas, alrededor de un 10% más que en el primer trimestre de 2025, con un error de pronóstico histórico cercano al 3,5% a uno, dos y tres meses.
  • Qué hacer: prepararse para un primer semestre de mayor demanda con decisiones reversibles (turnos, alquiler de flota, compras escalonadas), priorizar el canal de obras civiles y ferreterías, y condicionar las inversiones fijas en capacidad de premezclado a que las licencias confirmen la recuperación.

1 Contexto del sector y de la empresa

1.1 El sector: construcción

La construcción es uno de los sectores más procíclicos de la economía colombiana: amplifica los ciclos porque depende de crédito (tasas de interés), de la confianza de los hogares, de los subsidios de vivienda y de la inversión pública en infraestructura. Además, mueve una cadena larga de proveedores (cemento, concreto, acero, ladrillo, transporte), por lo que sus variables funcionan como termómetro adelantado de la actividad económica.

Entre 2023 y 2024 el sector atravesó una fase de contracción asociada, entre otros factores, a tasas de interés altas (la tasa de política del Banco de la República llegó a 13,25% en 2023) y a cambios en los programas de subsidio a la vivienda. Desde finales de 2023 el Banco de la República inició un ciclo gradual de recortes de tasas, lo que típicamente favorece la reactivación con algunos meses de rezago.

1.2 La empresa (hipotética)

Concretos del Valle S.A.S. es una empresa mediana con sede en Cali que:

  • produce y despacha concreto premezclado a constructoras (vivienda y edificaciones no residenciales) desde dos plantas en el área metropolitana;
  • distribuye cemento empacado a ferreterías y contratistas de obras civiles del Valle del Cauca y el suroccidente.

Sus decisiones críticas dependen de anticipar la demanda: cuánto cemento comprar a sus proveedores, cuántos camiones mezcladores (mixers) y turnos programar, cuánto crédito otorgar a clientes y cuándo invertir en capacidad.

¿Por qué este sector y esta empresa? Porque la base de datos contiene tres indicadores mensuales que cubren toda la cadena de valor de la empresa: lo que se planea construir (licencias), lo que se produce para obras formales (concreto) y lo que efectivamente demanda el mercado (despachos de cemento). Eso permite pasar de los datos a decisiones operativas concretas.

2 Selección y justificación de las variables

Variables seleccionadas (fuente: DANE, base mensual 2012–2025)
Variable Acrónimo Unidad Rol en el análisis
Despachos de cemento DECEM Toneladas Demanda efectiva del mercado (variable a pronosticar)
Producción de concreto premezclado CONCRETO Metros cúbicos Producto núcleo de la empresa; ligado a obra formal
Licencias de construcción LICC Área aprobada (m²) Indicador de pipeline: proyectos que se construirán en los próximos meses

Relación esperada. Se espera una relación positiva entre las tres: cuando se aprueban más licencias, meses después se vierte más concreto y se despacha más cemento. Por eso las licencias deberían comportarse como un indicador adelantado o, al menos, coincidente. Se espera también que el concreto sea más sensible que el cemento al ciclo de edificaciones formales, porque el cemento empacado incluye autoconstrucción, remodelaciones y obras civiles.

Decisión empresarial que se apoya en esta información. El plan de compras de cemento y la programación de flota y turnos del primer trimestre de 2026, además de la decisión de invertir o no en capacidad adicional de premezclado durante 2026.

3 Metodología

  1. Transformación logarítmica. Se trabaja con el logaritmo de cada serie para que la estacionalidad se comporte de forma multiplicativa (en porcentajes) y para estabilizar la varianza.
  2. Descomposición STL (Seasonal and Trend decomposition using Loess). Separa cada serie en tres componentes: tendencia (la dirección de fondo), estacionalidad (el patrón que se repite cada año) e irregular (lo que queda: choques y ruido). Se usa la versión robusta, que evita que eventos extremos como la pandemia deformen la tendencia.
  3. Tasa de crecimiento interanual (YoY) de la tendencia: \(\text{YoY}_t = \left(\frac{T_t}{T_{t-12}} - 1\right)\times 100\). Es la señal principal que pide el caso: indica si la variable acelera, se desacelera o se estanca, sin el ruido de los meses individuales. Como el filtro de tendencia es menos confiable en los últimos meses de la muestra (problema de “final de muestra”), se contrasta con el crecimiento del acumulado de 12 meses.
  4. Factores estacionales promedio por mes (en % frente a un mes “normal”) e identificación de atípicos en el componente irregular (valores a más de 2,5 desviaciones estándar).
  5. Modelo SARIMA para los despachos de cemento, con selección automática (auto.arima), variables de intervención para choques identificados y efecto de Semana Santa; diagnóstico de residuos, validación cruzada con origen móvil y pronóstico de enero a marzo de 2026 con intervalos de confianza.

4 Extracción de señales

desc <- lapply(series, function(x) stl(log(x), s.window = 13, robust = TRUE))

tabla_comp <- do.call(rbind, lapply(vars, function(v) {
  s  <- desc[[v]]$time.series
  tr <- exp(s[, "trend"])
  data.frame(fecha = base$FECHA, variable = v,
             valor = as.numeric(series[[v]]),
             tendencia = as.numeric(tr),
             yoy_tend = c(rep(NA, 12), 100 * (tr[-(1:12)] / tr[1:(length(tr) - 12)] - 1)),
             yoy_acum = as.numeric(yoy12(series[[v]])),
             estacional = 100 * (exp(as.numeric(s[, "seasonal"])) - 1),
             irregular = as.numeric(s[, "remainder"]))
}))
tabla_comp$variable <- factor(tabla_comp$variable, levels = vars)
ggplot(tabla_comp, aes(fecha)) +
  geom_line(aes(y = valor, color = variable), alpha = .45) +
  geom_line(aes(y = tendencia, color = variable), linewidth = 1.1) +
  facet_wrap(~variable, ncol = 1, scales = "free_y") +
  scale_color_manual(values = col3, guide = "none") +
  scale_y_continuous(labels = function(x) fmt(x)) +
  labs(title = "Series originales (línea clara) y su tendencia STL (línea gruesa)",
       subtitle = "Enero 2012 – diciembre 2025", x = NULL, y = NULL) + tema

4.1 Tendencia: crecimiento interanual (YoY)

ggplot(subset(tabla_comp, !is.na(yoy_tend)), aes(fecha, yoy_tend, color = variable)) +
  annotate("rect", xmin = as.Date("2020-03-01"), xmax = as.Date("2021-06-01"),
           ymin = -Inf, ymax = Inf, fill = "grey85", alpha = .5) +
  annotate("text", x = as.Date("2020-10-15"), y = 38, label = "Pandemia y\nparo nacional", size = 3.2, color = "grey30") +
  geom_hline(yintercept = 0, linetype = "dashed") +
  geom_line(linewidth = 1) +
  scale_color_manual(values = col3) +
  labs(title = "Crecimiento anual de la tendencia (YoY, %)",
       subtitle = "Por encima de 0 la variable crece; por debajo, se contrae",
       x = NULL, y = "%", color = NULL) + tema

anual <- sapply(series, function(x) {
  a <- aggregate(x, FUN = sum)
  round(100 * (a / stats::lag(a, -1) - 1), 1)
})
anual <- data.frame(`Año` = 2013:2025, anual, check.names = FALSE)
kable(anual, caption = "Crecimiento anual del total del año (%)", align = "c")
Crecimiento anual del total del año (%)
Año DECEM CONCRETO LICC
2013 3.5 7.8 15.9
2014 10.2 7.0 2.4
2015 7.0 6.5 24.6
2016 -5.5 -8.7 -20.3
2017 -1.0 -10.2 -5.8
2018 0.2 -3.3 -5.9
2019 4.2 3.0 19.6
2020 -10.2 -27.4 -29.0
2021 16.1 17.3 35.0
2022 3.6 36.3 29.9
2023 -4.5 -0.1 -21.9
2024 -5.0 -6.9 -23.8
2025 5.4 -3.6 6.6
ult <- subset(tabla_comp, fecha >= as.Date("2025-01-01") & format(fecha, "%m") %in% c("03","06","09","12"))
rec <- reshape(ult[, c("fecha", "variable", "yoy_tend")], idvar = "fecha",
               timevar = "variable", direction = "wide")
rec2 <- reshape(ult[, c("fecha", "variable", "yoy_acum")], idvar = "fecha",
                timevar = "variable", direction = "wide")
out <- data.frame(Mes = paste0(mes_es[as.integer(format(rec$fecha, "%m"))], "-", format(rec$fecha, "%Y")),
                  `DECEM tend.` = round(rec[, 2], 1), `DECEM acum.12m` = round(rec2[, 2], 1),
                  `CONCRETO tend.` = round(rec[, 3], 1), `CONCRETO acum.12m` = round(rec2[, 3], 1),
                  `LICC tend.` = round(rec[, 4], 1), `LICC acum.12m` = round(rec2[, 4], 1),
                  check.names = FALSE)
kable(out, caption = "2025: YoY de la tendencia vs. crecimiento del acumulado 12 meses (%)", align = "c")
2025: YoY de la tendencia vs. crecimiento del acumulado 12 meses (%)
Mes DECEM tend. DECEM acum.12m CONCRETO tend. CONCRETO acum.12m LICC tend. LICC acum.12m
Mar-2025 -0.5 -2.8 -6.5 -6.5 3.4 -12.3
Jun-2025 2.9 -1.8 -4.4 -7.1 2.9 -7.3
Sep-2025 6.1 1.8 -2.0 -5.3 -3.8 -2.7
Dic-2025 8.2 5.4 0.3 -3.6 -11.8 6.6

Lectura de la tendencia.

  • Despachos de cemento (DECEM): el sector muestra ciclos claros. Auge en 2013–2015, desaceleración y contracción en 2016–2018, recuperación en 2019, desplome en 2020 (-10,2%), rebote en 2021 (16,1%) y una nueva contracción en 2023–2024. En 2025 la tendencia cambió de dirección: su YoY pasó de terreno negativo a comienzos de año a cerca de 8,2% en diciembre. Es el cambio de comportamiento más importante de la serie en los últimos dos años.
  • Concreto premezclado (CONCRETO): acompañó el ciclo, pero con más amplitud (cayó 27,4% en 2020 y rebotó hasta 36,3% en 2022). En 2025 sigue contrayéndose, aunque cada vez menos: la tendencia pasa de caídas cercanas a −8% a comienzos de año a una variación cercana a cero en diciembre. Está saliendo del fondo, pero con rezago frente al cemento.
  • Licencias (LICC): es la serie más volátil. Cayó más de 20% en 2023 y 2024 y se recuperó en 2025 (6,6% en el acumulado anual). Sin embargo, la tendencia STL de final de muestra se vuelve negativa por las fuertes caídas de noviembre y diciembre de 2025. Las dos medidas cuentan historias distintas en el cierre, y por eso esta señal debe tratarse con cautela: la recuperación de licencias existe, pero todavía no es firme.

4.2 Estacionalidad

est <- aggregate(estacional ~ variable + mes,
                 data = transform(tabla_comp, mes = as.integer(format(fecha, "%m"))), FUN = mean)
est$mes_lab <- factor(mes_es[est$mes], levels = mes_es)
ggplot(est, aes(mes_lab, estacional, fill = estacional > 0)) +
  geom_col() + geom_hline(yintercept = 0) +
  facet_wrap(~variable, ncol = 3) +
  scale_fill_manual(values = c("TRUE" = "#2E75B6", "FALSE" = "#C00000"), guide = "none") +
  labs(title = "Factor estacional promedio por mes",
       subtitle = "% por encima o por debajo de un mes típico (promedio 2012–2025)",
       x = NULL, y = "%") + tema +
  theme(axis.text.x = element_text(angle = 90, vjust = .5, size = 8))

tab_est <- reshape(est[, c("variable", "mes_lab", "estacional")], idvar = "mes_lab",
                   timevar = "variable", direction = "wide")
names(tab_est) <- c("Mes", vars)
tab_est[, vars] <- round(tab_est[, vars], 1)
kable(tab_est, caption = "Factores estacionales (%)", align = "c", row.names = FALSE)
Factores estacionales (%)
Mes DECEM CONCRETO LICC
Ene -10.5 -16.1 -12.9
Feb -3.0 0.3 -1.5
Mar 4.5 6.1 -7.4
Abr -2.7 -2.1 -1.7
May 0.4 4.1 2.6
Jun -4.3 -1.2 -5.5
Jul 3.1 2.1 -1.9
Ago 3.4 4.0 -1.0
Sep 3.9 5.0 7.7
Oct 5.5 5.7 -2.1
Nov 2.6 0.4 3.6
Dic -1.4 -5.7 25.9

Lectura de la estacionalidad.

  • Enero es el mes más débil en las tres series (cemento cerca de −10%, concreto cerca de −16%): vacaciones colectivas, reinicio lento de obras y presupuestos públicos que aún no se ejecutan.
  • Agosto a octubre concentra los meses fuertes para cemento y concreto: las obras corren para entregar antes de fin de año.
  • Diciembre es bajo para el concreto (cierre de obras por festividades) pero muy alto para las licencias (cerca de +26%): hay una aprobación masiva de proyectos al cierre del año, que alimenta la actividad del año siguiente.
  • Marzo y abril están afectados por Semana Santa, que cambia de mes cada año. Por eso el efecto se modela explícitamente en el pronóstico (en 2026 la Semana Santa cae en abril, lo que favorece a marzo).

Utilidad para la empresa: planear mantenimientos de plantas y vacaciones del personal en enero y diciembre, y reforzar flota, turnos e inventario de agosto a octubre. Para leer bien un dato mensual, siempre hay que compararlo con el mismo mes del año anterior (no con el mes previo), porque la estacionalidad es fuerte.

4.3 Componente irregular

irr <- do.call(rbind, lapply(vars, function(v) {
  d <- subset(tabla_comp, variable == v)
  d$z <- d$irregular / sd(d$irregular)
  d
}))
atip <- subset(irr, abs(z) > 2.5)
ggplot(irr, aes(fecha, z, color = variable)) +
  geom_hline(yintercept = c(-2.5, 2.5), linetype = "dotted", color = "grey40") +
  geom_line(show.legend = FALSE) +
  geom_point(data = atip, size = 2.3, color = "black") +
  geom_text(data = subset(atip, format(fecha, "%Y-%m") != "2020-05"),
            aes(label = format(fecha, "%Y-%m")), size = 2.8, hjust = -0.15, color = "black") +
  facet_wrap(~variable, ncol = 1, scales = "free_y") +
  scale_color_manual(values = col3) +
  labs(title = "Componente irregular estandarizado",
       subtitle = "Puntos fuera de ±2,5 desviaciones estándar = choques atípicos",
       x = NULL, y = "Desviaciones estándar") + tema

Choques atípicos identificados (STL)
Fecha Variable Desv. est. Posible explicación económica
2020-03 DECEM -3.25 Cuarentena nacional por COVID-19 (desde el 25 de marzo de 2020) y reapertura gradual de obras
2020-04 DECEM -11.37 Cuarentena nacional por COVID-19 (desde el 25 de marzo de 2020) y reapertura gradual de obras
2020-05 DECEM -3.28 Cuarentena nacional por COVID-19 (desde el 25 de marzo de 2020) y reapertura gradual de obras
2020-04 CONCRETO -12.32 Cuarentena nacional por COVID-19 (desde el 25 de marzo de 2020) y reapertura gradual de obras
2020-05 CONCRETO -2.67 Cuarentena nacional por COVID-19 (desde el 25 de marzo de 2020) y reapertura gradual de obras
2015-12 LICC 3.08 Concentración de aprobaciones al cierre del año; posibles radicaciones anticipadas a cambios normativos o tarifarios
2019-12 LICC 3.12 Concentración de aprobaciones al cierre del año; posibles radicaciones anticipadas a cambios normativos o tarifarios
2020-04 LICC -7.49 Cuarentena nacional por COVID-19 (desde el 25 de marzo de 2020) y reapertura gradual de obras
2020-05 LICC -2.67 Cuarentena nacional por COVID-19 (desde el 25 de marzo de 2020) y reapertura gradual de obras
2022-07 LICC 2.58 Aprobaciones inusualmente altas; posible efecto de proyectos grandes o del cierre de gobierno
2023-12 LICC 2.94 Concentración de aprobaciones al cierre del año; posibles radicaciones anticipadas a cambios normativos o tarifarios

Lectura del irregular. El choque dominante es la pandemia: en abril de 2020 los despachos de cemento y el concreto cayeron más de 11 desviaciones estándar por debajo de lo normal, algo que ningún patrón histórico podía anticipar. Hay también choques más pequeños que aparecen al modelar los despachos (sección 6): el paro camionero de junio–julio de 2016 y el paro nacional con bloqueos viales de mayo de 2021, que interrumpieron la logística de insumos y la entrega en obra (Cali fue uno de los epicentros). La lección para la empresa es que el mayor riesgo de corto plazo no viene del ciclo sino de choques logísticos y sociales, y que estos se corrigen rápido: la serie vuelve a su tendencia en uno o dos meses.

5 Análisis integrado de las variables

yoyraw <- sapply(series, function(x) 100 * diff(log(x), 12))
cor_tab <- round(cor(yoyraw), 2)
kable(cor_tab, caption = "Correlación de las variaciones anuales mensuales (2013–2025)")
Correlación de las variaciones anuales mensuales (2013–2025)
DECEM CONCRETO LICC
DECEM 1.00 0.92 0.69
CONCRETO 0.92 1.00 0.68
LICC 0.69 0.68 1.00
g <- ts(sapply(series, yoy12), start = c(2012, 1), frequency = 12)
cc_par <- function(a, b, ini, fin, par, per) {
  cc <- ccf(window(g[, a], start = c(ini, 1), end = c(fin, 12)),
            window(g[, b], start = c(ini, 1), end = c(fin, 12)), lag.max = 12, plot = FALSE)
  data.frame(rezago = round(cc$lag * 12), r = as.numeric(cc$acf), par = par, periodo = per)
}
ccdf <- rbind(cc_par("LICC", "DECEM",    2014, 2019, "Licencias vs. cemento", "2014–2019"),
              cc_par("LICC", "DECEM",    2022, 2025, "Licencias vs. cemento", "2022–2025"),
              cc_par("LICC", "CONCRETO", 2014, 2019, "Licencias vs. concreto", "2014–2019"),
              cc_par("LICC", "CONCRETO", 2022, 2025, "Licencias vs. concreto", "2022–2025"))
ggplot(ccdf, aes(rezago, r, fill = periodo)) +
  geom_col(position = "dodge") +
  geom_vline(xintercept = 0, linetype = "dotted") +
  facet_wrap(~par, ncol = 2) +
  scale_fill_manual(values = c("#A5A5A5", "#548235")) +
  labs(title = "¿Las licencias anticipan al cemento y al concreto?",
       subtitle = "Correlación cruzada del crecimiento acumulado 12 meses (excluye pandemia)\nRezago negativo = las licencias se mueven antes",
       x = "Rezago (meses)", y = "Correlación", fill = NULL) + tema

r_lc_m6 <- with(ccdf, r[par == "Licencias vs. concreto" & periodo == "2022–2025" & rezago == -6])
r_lc_p6 <- with(ccdf, r[par == "Licencias vs. concreto" & periodo == "2022–2025" & rezago == 6])
div <- subset(tabla_comp, fecha >= as.Date("2022-01-01"))
ggplot(div, aes(fecha, yoy_acum, color = variable)) +
  geom_hline(yintercept = 0, linetype = "dashed") +
  geom_line(linewidth = 1.1) +
  scale_color_manual(values = col3) +
  labs(title = "La recuperación de 2025 es desigual",
       subtitle = "Crecimiento del acumulado de 12 meses (%)", x = NULL, y = "%", color = NULL) + tema

¿Qué muestran las tres juntas?

  1. Se mueven en la misma dirección. Las variaciones anuales de cemento y concreto tienen una correlación de 0,92, y las licencias se correlacionan cerca de 0,69 con ambas. Confirma la relación positiva esperada.
  2. Las licencias anticipan al concreto, no al cemento. Frente al cemento, la correlación cruzada es máxima en el rezago 0 y es incluso algo mayor en rezagos positivos: el cemento no espera a las licencias, porque buena parte de su demanda viene de obras civiles, remodelaciones y autoconstrucción que no pasan por una licencia de edificación. Frente al concreto, en cambio, en el ciclo pospandemia la relación es claramente adelantada: la correlación con las licencias de seis meses antes es 0,79, mientras que con las de seis meses después es solo 0,40. Es decir, lo que se licencia hoy se convierte en concreto vertido en los meses siguientes. Esto confirma la hipótesis inicial solo para el concreto, y es clave para la empresa, cuyo producto núcleo es el premezclado.
  3. 2025 muestra una divergencia. El cemento ya crece, el concreto aún cae y las licencias se recuperan pero con un final de año débil. La interpretación más probable es que la reactivación viene de segmentos que usan cemento empacado (obras civiles, remodelaciones, autoconstrucción) más que de edificaciones formales, que son las que consumen concreto premezclado.

Veredicto: señales mixtas con sesgo positivo. La historia del sector muestra que las salidas de ciclos bajistas anteriores empezaron de forma parecida: en 2018 el cemento dejó de caer (0,2%) mientras el concreto todavía caía (-3,3%), y tras la pandemia el concreto tuvo su mayor rebote en 2022, un año después que el cemento. Como las licencias se adelantan al concreto, la debilidad de noviembre y diciembre de 2025 es la principal alerta para el premezclado en el primer semestre de 2026: si las licencias se recuperan en los primeros meses del año, el concreto debería seguir al cemento; si no, la recuperación quedaría concentrada en obras civiles y en el canal de ferreterías.

6 Modelo SARIMA para los despachos de cemento

Se elige DECEM para pronosticar porque es la medida más directa de la demanda que enfrenta la empresa, es la menos ruidosa de las tres y es la variable que guía el plan de compras de cemento.

6.1 Estacionariedad y diferenciación

Un modelo ARIMA requiere una serie estacionaria, es decir, sin tendencia y con media y varianza estables. Se revisa la serie en logaritmos.

y  <- series$DECEM
ly <- log(y)
adf  <- adf.test(ly)
kps  <- kpss.test(ly)
kable(data.frame(
  Prueba = c("ADF (H0: raíz unitaria)", "KPSS (H0: estacionaria)", "ndiffs (diferencias regulares sugeridas)", "nsdiffs (diferencias estacionales sugeridas)"),
  Resultado = c(paste("p =", round(adf$p.value, 3)), paste("p =", round(kps$p.value, 3)),
                ndiffs(ly), nsdiffs(ly))), caption = "Pruebas sobre log(DECEM)")
Pruebas sobre log(DECEM)
Prueba Resultado
ADF (H0: raíz unitaria) p = 0.01
KPSS (H0: estacionaria) p = 0.01
ndiffs (diferencias regulares sugeridas) 1
nsdiffs (diferencias estacionales sugeridas) 0
ggtsdisplay(diff(diff(ly), 12), main = "log(DECEM) con diferencia regular y estacional", theme = tema)

Las pruebas ADF y KPSS dan señales contradictorias (algo frecuente en series con quiebres como la pandemia), pero KPSS rechaza estacionariedad y ndiffs sugiere una diferencia regular (d = 1). Con la diferencia regular y la estacional, la serie queda centrada en cero y los rezagos significativos de la ACF en 1 y 12 indican componentes de media móvil regular y estacional.

6.2 Variables de intervención y selección automática

Antes de estimar, se incorporan como regresores externos los choques identificados en la extracción de señales. Si no se incluyen, el modelo “aprende” la pandemia como si fuera un comportamiento normal y sus pronósticos y bandas de incertidumbre se distorsionan.

tt <- time(y)
ao <- function(anio, mes) as.numeric(abs(tt - (anio + (mes - 1) / 12)) < 1e-6)
X <- ts(cbind(
  AO_2016_07   = ao(2016, 7),   # paro camionero
  AO_2020_03   = ao(2020, 3),   # inicio de cuarentena
  AO_2020_04   = ao(2020, 4),   # cierre total de obras
  AO_2020_05   = ao(2020, 5),   # reapertura parcial
  AO_2021_05   = ao(2021, 5),   # paro nacional y bloqueos
  SEMANA_SANTA = as.numeric(easter(y))  # 1 en el mes con Semana Santa
), start = c(2012, 1), frequency = 12)

modelo <- auto.arima(y, lambda = 0, xreg = X, stepwise = FALSE, approximation = FALSE)
modelo
## Series: y 
## Regression with ARIMA(0,1,1)(2,1,2)[12] errors 
## Box Cox transformation: lambda= 0 
## 
## Coefficients:
##           ma1    sar1     sar2     sma1    sma2  AO_2016_07  AO_2020_03
##       -0.6309  0.8080  -0.4803  -1.5862  0.7199     -0.1350     -0.3920
## s.e.   0.0621  0.1694   0.0939   0.2194  0.2205      0.0319      0.0316
##       AO_2020_04  AO_2020_05  AO_2021_05  SEMANA_SANTA
##          -1.4012     -0.4125     -0.3044       -0.1194
## s.e.      0.0324      0.0332      0.0321        0.0116
## 
## sigma^2 = 0.001397:  log likelihood = 282.14
## AIC=-540.28   AICc=-538.08   BIC=-503.75

auto.arima explora de forma exhaustiva (stepwise = FALSE) las combinaciones de órdenes y elige la de menor AICc (criterio que premia el buen ajuste pero castiga el exceso de parámetros). lambda = 0 indica que se modela el logaritmo y que el pronóstico se devuelve automáticamente en toneladas.

Modelo seleccionado: regresión con errores SARIMA(0,1,1)(2,1,2)[12] sobre el logaritmo de los despachos.

  • (0,1,1): una diferencia regular y un término de media móvil: el mes actual corrige parte del error del mes anterior.
  • (2,1,2)[12]: una diferencia estacional y términos autorregresivos y de media móvil estacionales: el patrón de cada mes depende de cómo fue ese mismo mes en los dos años previos.
  • Intervenciones: todos los coeficientes son significativos y con el signo esperado. Por ejemplo, abril de 2020 implicó una caída de alrededor de 75% frente a lo normal, y la Semana Santa reduce los despachos del mes en cerca de 11%.

6.3 Comparación de modelos candidatos

m_sin   <- auto.arima(y, lambda = 0, stepwise = FALSE, approximation = FALSE)
m_air   <- Arima(y, order = c(0, 1, 1), seasonal = c(0, 1, 1), xreg = X, lambda = 0)
lb <- function(m, k) Box.test(residuals(m), lag = 24, type = "Ljung-Box", fitdf = k)$p.value
cand <- data.frame(
  Modelo = c("SARIMA(0,1,1)(2,1,2) + intervenciones (auto)",
             "SARIMA(0,1,1)(0,1,1) + intervenciones (airline)",
             paste0(sub("Box Cox transformation.*", "", as.character(m_sin)), " sin intervenciones (auto)")),
  AICc = round(c(modelo$aicc, m_air$aicc, m_sin$aicc), 1),
  `Desv. est. residuos (%)` = round(100 * sqrt(c(modelo$sigma2, m_air$sigma2, m_sin$sigma2)), 1),
  `Ljung-Box p` = round(c(lb(modelo, 4), lb(m_air, 2), lb(m_sin, 3)), 3),
  `Jarque-Bera p` = round(sapply(list(modelo, m_air, m_sin), function(m) jarque.bera.test(residuals(m))$p.value), 3),
  check.names = FALSE)
kable(cand, caption = "Candidatos (AICc solo es comparable entre modelos con las mismas diferencias)")
Candidatos (AICc solo es comparable entre modelos con las mismas diferencias)
Modelo AICc Desv. est. residuos (%) Ljung-Box p Jarque-Bera p
SARIMA(0,1,1)(2,1,2) + intervenciones (auto) -538.1 3.7 0.743 0.638
SARIMA(0,1,1)(0,1,1) + intervenciones (airline) -525.4 3.9 0.006 0.560
ARIMA(1,1,1)(1,0,0)[12] sin intervenciones (auto) -199.1 13.1 0.995 0.000

El modelo sin intervenciones no deja autocorrelación, pero sus residuos tienen una desviación estándar más de tres veces mayor y no son normales (Jarque-Bera con p < 0,05): la pandemia “contamina” toda la estimación y sus intervalos de confianza serían poco confiables. El modelo airline, más sencillo, deja autocorrelación en los residuos (Ljung-Box con p < 0,05), es decir, deja información sin aprovechar. El modelo automático con intervenciones es el único que cumple los dos supuestos a la vez y tiene el menor AICc, y la validación cruzada (sección 7) confirma que su precisión es igual o mejor.

6.4 Diagnóstico de los residuos

Un buen modelo debe dejar residuos que se comporten como ruido blanco: sin autocorrelación, con media cero y varianza constante, idealmente con distribución normal para que los intervalos de confianza sean confiables.

checkresiduals(modelo, test = FALSE, theme = tema)

res <- residuals(modelo)
diag <- data.frame(
  Prueba = c("Ljung-Box, 24 rezagos (H0: no autocorrelación)",
             "Jarque-Bera (H0: normalidad)", "Shapiro-Wilk (H0: normalidad)",
             "Media de los residuos"),
  Resultado = c(paste("p =", round(Box.test(res, 24, "Ljung-Box", fitdf = 4)$p.value, 3)),
                paste("p =", round(jarque.bera.test(res)$p.value, 3)),
                paste("p =", round(shapiro.test(res)$p.value, 3)),
                round(mean(res), 4)),
  `Conclusión` = c("No hay autocorrelación: el modelo capturó la dinámica",
                 "No se rechaza normalidad", "No se rechaza normalidad",
                 "Prácticamente cero: sin sesgo"))
kable(diag, caption = "Pruebas sobre los residuos")
Pruebas sobre los residuos
Prueba Resultado Conclusión
Ljung-Box, 24 rezagos (H0: no autocorrelación) p = 0.743 No hay autocorrelación: el modelo capturó la dinámica
Jarque-Bera (H0: normalidad) p = 0.638 No se rechaza normalidad
Shapiro-Wilk (H0: normalidad) p = 0.558 No se rechaza normalidad
Media de los residuos -0.0008 Prácticamente cero: sin sesgo
autoplot(modelo) + tema + labs(title = "Raíces inversas: todas dentro del círculo unitario")

Todas las raíces inversas están dentro del círculo unitario, por lo que el modelo es estacionario e invertible (estable). Los residuos no muestran autocorrelación ni se alejan de la normalidad: el modelo es válido para pronosticar.

7 Validación del modelo: validación cruzada con origen móvil

Para medir la capacidad predictiva real se simula lo que habría pasado si se hubiera usado el modelo en el pasado: desde diciembre de 2021 hasta noviembre de 2025 (48 orígenes), en cada mes se estima el modelo solo con los datos disponibles hasta ese momento y se pronostican los siguientes 1, 2 y 3 meses. Se compara con modelos de referencia: suavizamiento exponencial (ETS), un ARIMA sin intervenciones y el naive estacional (el mismo mes del año anterior), que es la regla mínima que un modelo debe superar. Se evalúa sobre el periodo pospandemia porque se parece más a las condiciones de 2026.

especif <- list(
  "SARIMA auto + interv." = function(yy, xx) Arima(yy, order = c(0,1,1), seasonal = c(2,1,2), xreg = xx, lambda = 0),
  "SARIMA airline + interv." = function(yy, xx) Arima(yy, order = c(0,1,1), seasonal = c(0,1,1), xreg = xx, lambda = 0),
  "ARIMA sin interv." = function(yy, xx) Arima(yy, order = arimaorder(m_sin)[1:3], seasonal = arimaorder(m_sin)[4:6], lambda = 0))
H <- 3; n <- length(y); origenes <- which(time(y) >= 2021 + 11/12 - 1e-6 & time(y) <= 2025 + 10/12 + 1e-6)
resultados <- list()
for (k in origenes) {
  yy <- ts(y[1:k], start = c(2012, 1), frequency = 12)
  xx <- X[1:k, , drop = FALSE]
  hh <- min(H, n - k); xf <- X[(k + 1):(k + hh), , drop = FALSE]; real <- y[(k + 1):(k + hh)]
  pr <- list()
  for (s in names(especif)) {
    m <- especif[[s]](yy, xx)
    pr[[s]] <- if (grepl("sin interv", s)) forecast(m, h = hh)$mean else forecast(m, xreg = xf, h = hh)$mean
  }
  pr[["ETS"]] <- forecast(ets(yy, model = "ANA"), h = hh)$mean
  pr[["Naive estacional"]] <- snaive(yy, h = hh)$mean
  for (s in names(pr)) resultados[[length(resultados) + 1]] <-
    data.frame(modelo = s, h = 1:hh, real = real, pron = as.numeric(pr[[s]]))
}
R <- do.call(rbind, resultados); R$e <- R$real - R$pron
met <- do.call(rbind, lapply(split(R, list(R$modelo, R$h)), function(d)
  data.frame(Modelo = d$modelo[1], h = d$h[1], RMSE = sqrt(mean(d$e^2)),
             MAE = mean(abs(d$e)), MAPE = 100 * mean(abs(d$e / d$real)))))
met <- met[order(met$h, met$MAPE), ]
kable(transform(met, RMSE = fmt(RMSE), MAE = fmt(MAE), MAPE = round(MAPE, 2)), row.names = FALSE,
      caption = "Métricas de error por horizonte (toneladas y %)")
Métricas de error por horizonte (toneladas y %)
Modelo h RMSE MAE MAPE
SARIMA auto + interv. 1 45.423 37.284 3.48
SARIMA airline + interv. 1 46.197 37.265 3.49
ETS 1 64.364 50.077 4.68
Naive estacional 1 87.110 67.137 6.25
ARIMA sin interv. 1 81.648 66.608 6.26
SARIMA airline + interv. 2 45.141 35.709 3.32
SARIMA auto + interv. 2 46.361 37.832 3.51
ETS 2 62.740 49.065 4.55
ARIMA sin interv. 2 82.044 65.725 6.09
Naive estacional 2 87.955 68.029 6.33
SARIMA airline + interv. 3 48.283 37.315 3.48
SARIMA auto + interv. 3 49.888 39.660 3.70
ETS 3 64.287 49.459 4.57
Naive estacional 3 88.812 68.906 6.42
ARIMA sin interv. 3 85.594 69.751 6.47
ggplot(met, aes(factor(h), MAPE, fill = reorder(Modelo, MAPE))) +
  geom_col(position = "dodge") +
  scale_fill_manual(values = c("#1F4E79", "#5B9BD5", "#A5A5A5", "#F4B183", "#C00000")) +
  labs(title = "Error porcentual medio (MAPE) en validación cruzada",
       subtitle = "Pronósticos a 1, 2 y 3 meses; 48 orígenes (dic-2021 a nov-2025)",
       x = "Horizonte (meses adelante)", y = "MAPE (%)", fill = NULL) + tema

mape_sel <- met$MAPE[met$Modelo == "SARIMA auto + interv."]
mape_nv  <- met$MAPE[met$Modelo == "Naive estacional"]
mae_sel  <- met$MAE[met$Modelo == "SARIMA auto + interv."]

Cómo leer las métricas:

  • MAE (error absoluto medio): en promedio, el pronóstico se equivoca en unas 38.259 toneladas.
  • RMSE (raíz del error cuadrático medio): parecido al MAE pero castiga más los errores grandes.
  • MAPE (error porcentual absoluto medio): el error típico es de 3,5% a 3,7% según el horizonte.

Conclusión de la validación. El modelo seleccionado reduce el error del naive estacional casi a la mitad (de cerca de 6,3% a 3,6%) y supera al ETS y al ARIMA sin intervenciones. Está prácticamente empatado con el modelo airline, pero ese modelo no pasa el diagnóstico de residuos; ante igual precisión, se prefiere el que cumple los supuestos.

8 Pronóstico enero–marzo 2026

Xf <- matrix(0, 3, ncol(X), dimnames = list(NULL, colnames(X)))
Xf[, "SEMANA_SANTA"] <- 0  # Semana Santa 2026 cae en abril (Jueves y Viernes Santo: 2 y 3 de abril)
pron <- forecast(modelo, xreg = Xf, h = 3, level = c(80, 95))
prev <- as.numeric(window(y, start = c(2025, 1), end = c(2025, 3)))
tabp <- data.frame(
  Mes = c("Enero 2026", "Febrero 2026", "Marzo 2026"),
  `Pronóstico (t)` = fmt(pron$mean),
  `IC 80%` = paste(fmt(pron$lower[, 1]), "–", fmt(pron$upper[, 1])),
  `IC 95%` = paste(fmt(pron$lower[, 2]), "–", fmt(pron$upper[, 2])),
  `Mismo mes 2025 (t)` = fmt(prev),
  `Var. anual (%)` = round(100 * (as.numeric(pron$mean) / prev - 1), 1),
  check.names = FALSE)
kable(tabp, caption = "Pronóstico puntual e intervalos de confianza", align = "lrccrc")
Pronóstico puntual e intervalos de confianza
Mes Pronóstico (t) IC 80% IC 95% Mismo mes 2025 (t) Var. anual (%)
Enero 2026 1.007.350 960.037 – 1.056.995 935.897 – 1.084.258 889.614 13.2
Febrero 2026 1.076.511 1.022.706 – 1.133.147 995.321 – 1.164.325 981.553 9.7
Marzo 2026 1.186.487 1.123.829 – 1.252.639 1.092.010 – 1.289.138 1.084.450 9.4
q1_26 <- sum(pron$mean); q1_25 <- sum(prev)
hist_df <- data.frame(fecha = base$FECHA, y = as.numeric(y))
hist_df <- subset(hist_df, fecha >= as.Date("2022-01-01"))
fut <- data.frame(fecha = seq(as.Date("2026-01-01"), by = "month", length.out = 3),
                  y = as.numeric(pron$mean),
                  lo80 = pron$lower[, 1], hi80 = pron$upper[, 1],
                  lo95 = pron$lower[, 2], hi95 = pron$upper[, 2])
ajuste <- data.frame(fecha = base$FECHA, f = as.numeric(fitted(modelo)))
ajuste <- subset(ajuste, fecha >= as.Date("2022-01-01"))
ggplot() +
  geom_ribbon(data = fut, aes(fecha, ymin = lo95, ymax = hi95), fill = "#9DC3E6", alpha = .5) +
  geom_ribbon(data = fut, aes(fecha, ymin = lo80, ymax = hi80), fill = "#2E75B6", alpha = .5) +
  geom_line(data = ajuste, aes(fecha, f), color = "grey55", linetype = "dashed") +
  geom_line(data = hist_df, aes(fecha, y), color = "#1F4E79", linewidth = 1) +
  geom_line(data = rbind(tail(hist_df, 1), fut[, c("fecha", "y")]), aes(fecha, y),
            color = "#C00000", linewidth = 1.1) +
  geom_point(data = fut, aes(fecha, y), color = "#C00000", size = 2.5) +
  scale_y_continuous(labels = function(x) fmt(x)) +
  labs(title = "Despachos de cemento: histórico y pronóstico enero–marzo 2026",
       subtitle = "Rojo: pronóstico puntual · bandas: intervalos de 80% y 95% · gris: ajuste del modelo",
       x = NULL, y = "Toneladas") + tema

Interpretación económica.

  • El modelo proyecta un primer trimestre de 2026 con 3.270.349 toneladas, 10,6% más que el mismo trimestre de 2025. Es la continuación de la tendencia de recuperación que se observó en la segunda mitad de 2025, más el patrón estacional habitual (enero bajo, marzo alto).
  • Marzo es el mes más alto del trimestre por estacionalidad (cierre del primer trimestre de obra). En 2026, igual que en 2025, la Semana Santa cae en abril, así que la comparación anual de marzo no está distorsionada por el calendario. Si se hubiera comparado contra un año con Semana Santa en marzo (como 2024), el crecimiento aparente habría sido mucho mayor sin que la demanda realmente cambiara: por eso el efecto se modela.
  • Incertidumbre. Con 95% de confianza, enero de 2026 estaría entre 935.897 y 1.084.258 toneladas. Incluso el extremo inferior del intervalo es superior a enero de 2025 (889.614), lo que sugiere que un crecimiento anual es el escenario más probable. Sin embargo, el crecimiento proyectado de enero (13,2%) está inflado porque enero de 2025 fue particularmente débil. Hay que recordar además que el modelo no anticipa choques (paros, bloqueos, cambios de política) y que su error típico es cercano al 3,6%.
  • Supuesto clave: el modelo es univariado, es decir, extrapola la dinámica de la propia serie. No “sabe” que las licencias cayeron a final de 2025. Por eso el pronóstico debe leerse junto con las señales de las otras dos variables.

9 Conclusiones

  1. El sector construcción salió de la contracción de 2023–2024: la demanda de cemento creció en 2025 y su tendencia terminó el año acelerando.
  2. La recuperación es desigual. El cemento lidera, el concreto premezclado todavía no crece y las licencias, que en el ciclo reciente se adelantan al concreto, cerraron 2025 con dos meses de caídas fuertes. En conjunto, las señales son mixtas con sesgo positivo.
  3. La estacionalidad es fuerte y predecible (enero débil, agosto–octubre fuerte, Semana Santa relevante), lo que permite planear la operación con anticipación.
  4. Los mayores choques históricos fueron exógenos (pandemia, paro camionero, paro nacional) y de corta duración.
  5. El pronóstico SARIMA anticipa un primer trimestre de 2026 con un crecimiento cercano al 10% anual y un error esperado de alrededor del 3,5%. El modelo cumple los supuestos y supera a los modelos de referencia en la validación cruzada.

10 Recomendaciones estratégicas

Riesgos del sector

  • Que la recuperación no se extienda a edificaciones formales si las licencias siguen débiles (riesgo directo para el concreto premezclado).
  • Choques logísticos o sociales (paros, bloqueos) con impacto fuerte pero transitorio, especialmente en el suroccidente.
  • Una pausa en la reducción de tasas de interés o cambios en los subsidios de vivienda que frenen las ventas de vivienda nueva.

Oportunidades

  • Mayor demanda de cemento en el primer trimestre de 2026 en los canales de ferreterías y obras civiles.
  • Si las licencias confirman la recuperación, el concreto premezclado debería seguir al cemento con algunos meses de rezago, porque en el ciclo reciente las licencias se adelantan al concreto.

Decisiones concretas para Concretos del Valle S.A.S.

Área Decisión Sustento en el análisis
Compras Escalonar compras de cemento para el primer trimestre de 2026 en torno a un crecimiento del ~10% anual, con cupos flexibles hasta el límite superior del intervalo de 80% Pronóstico SARIMA e intervalos
Comercial Reforzar el canal de ferreterías y contratistas de obras civiles; ofrecer paquetes a constructoras con proyectos licenciados en 2025 Divergencia cemento vs. concreto
Operaciones Programar mantenimientos en enero; asegurar flota adicional por alquiler (no compra) para marzo y el segundo semestre Estacionalidad y efecto de Semana Santa
Finanzas Condicionar la inversión en una nueva planta o en mixers propios a dos trimestres seguidos de crecimiento positivo del concreto y de las licencias Señales mixtas; decisiones reversibles primero
Riesgo Plan de contingencia logística (inventario de seguridad, rutas alternas) ante paros Componente irregular

Indicadores a monitorear mensualmente

  • Licencias de construcción (nacional y LICC_CALI): confirmación del pipeline.
  • Producción de concreto (CONCRETO): señal de que la recuperación llega a la obra formal.
  • Despachos de cemento en el Valle (CEM_V): mercado regional directo de la empresa.
  • Error real vs. pronóstico de DECEM: si el dato observado cae fuera del intervalo de 80% dos meses seguidos, hay que reestimar el modelo.
  • Tasas de interés, cartera de vivienda y confianza del consumidor (ICC) como determinantes de la demanda de vivienda.

¿Quién usa esta información y para qué? El gerente de compras, para el volumen de cemento contratado; el director de operaciones, para la flota, los turnos y los mantenimientos; el director comercial, para priorizar canales; y el director financiero y la junta directiva, para decidir si se aprueba o se aplaza la inversión en capacidad.

Anexo: entorno de ejecución

sessionInfo()$R.version$version.string
## [1] "R version 4.6.1 (2026-06-24)"
sapply(c("forecast", "tseries", "ggplot2", "readxl"), function(p) as.character(packageVersion(p)))
##  forecast   tseries   ggplot2    readxl 
##   "9.0.2" "0.10.63"   "4.0.3"   "1.5.0"