Introducción

Sector Económico: Construcción y Actividades Inmobiliarias El sector de la construcción es uno de los principales motores de la economía colombiana, no solo por su contribución al Producto Interno Bruto (PIB), sino por su capacidad para generar empleo y dinamizar otros 36 sectores productivos a través de encadenamientos hacia atrás (insumos como cemento y acero) y hacia adelante (servicios financieros e inmobiliarios). Este sector se caracteriza por ser altamente procíclico, lo que significa que su desempeño está íntimamente ligado al crecimiento económico general y a la confianza de los consumidores.

Constructora Jaramillo Mora S.A. es una de las compañías más emblemáticas y sólidas en el sector de la construcción y el desarrollo inmobiliario en el suroccidente colombiano. Con más de 50 años de trayectoria, la empresa ha transformado el paisaje urbanístico de Cali y su área de influencia, especializándose en proyectos residenciales que van desde la Vivienda de Interés Social (VIS) hasta complejos habitacionales de lujo. Su capacidad de adaptación a los ciclos económicos y su enfoque en la innovación constructiva la han posicionado como un referente de calidad y cumplimiento en el Valle del Cauca.

Metodología

El análisis del entorno macroeconómico y sectorial es vital para una constructora, ya que su operación es altamente sensible a las fluctuaciones en las tasas de interés, la disponibilidad de insumos y la confianza de los hogares. En este documento, realizaremos un análisis técnico basado en la extracción de señales de cinco variables económicas clave entre 2012 y 2024, utilizando el software estadístico R.

El enfoque metodológico se divide en dos fases fundamentales. En primer lugar, se busca desglosar las series de tiempo en sus componentes de tendencia, estacionalidad y residuo mediante la técnica STL (Seasonal and Trend decomposition using Loess). Este paso permite identificar la “dirección real” del sector en la región, eliminando el ruido estacional y los choques de corto plazo.

En segundo lugar, y como eje central de la capacidad prospectiva de este estudio, se implementa el modelado estocástico ARIMA (AutoRegressive Integrated Moving Average). A diferencia del análisis descriptivo, el modelo ARIMA permite capturar la memoria histórica y la estructura de autocorrelación de la serie de licencias de construcción en Cali. Mediante este modelo, se busca generar un pronóstico robusto para el primer trimestre de 2026, proporcionando una visión estratégica sobre la dinámica futura del mercado inmobiliario.

Este enfoque integral proporcionará a Jaramillo Mora S.A. herramientas valiosas para orientar la toma de decisiones en la planeación de nuevos lanzamientos, la gestión de inventarios y la mitigación de riesgos financieros. Al combinar la descomposición histórica con la precisión predictiva del modelo ARIMA, este documento ofrece una hoja de ruta basada en datos para navegar la volatilidad del sector y anticipar los ciclos de inversión en el suroccidente colombiano.

Instalar/Cargar librerias necesarias para el análisis

#Cargar librerías necesarias
library(readxl)  # Para leer archivos Excel
library(tseries)  # Para pruebas de estacionariedad
library(forecast)  # Para modelado ARIMA y pronósticos
library(ggplot2)  # Para visualización de datos
library(plotly)  # Para gráficos interactivos
library(timetk)   #timetk simplifica y acelera el análisis exploratorio, visualización, y preparación de datos temporales para modelado. Es ideal para quienes trabajan con series temporales en un flujo de trabajo "tidy" y buscan integrar análisis visuales, detección de patrones y forecasting en un solo paquete.

Cargar base de datos

library(readxl)
data_col <- read_excel("C:/Users/xhann/Downloads/Base Caso2 (4).xlsx", col_types = c("date", 
    "numeric", "numeric", "numeric", "numeric", 
    "numeric", "numeric", "numeric", "numeric", 
    "numeric", "numeric", "numeric", "numeric", 
    "numeric", "numeric", "numeric", "numeric", 
    "numeric", "numeric", "numeric", "numeric", 
    "numeric", "numeric", "numeric", "numeric", 
    "numeric", "numeric", "numeric", "numeric", 
    "numeric", "numeric", "numeric", "numeric", 
    "numeric", "numeric", "numeric", "numeric", 
    "numeric", "numeric", "numeric", "numeric", 
    "numeric", "numeric", "numeric", "numeric", 
    "numeric", "numeric", "numeric", "numeric", 
    "numeric", "numeric", "numeric", "numeric", 
    "numeric", "numeric", "numeric", "numeric", 
    "numeric", "numeric", "numeric", "numeric", 
    "numeric"))

Variables

Para evaluar el desempeño de la empresa y su entorno, se han seleccionado cinco variables que cubren el ciclo completo de la construcción (planeación, costos, demanda y ejecución):

  • Variable 1: Licencias de Construcción en Cali - LICC_CALI: Permite capturar señales sobre la evolución del ciclo económico del sector, ya que los permisos de construcción son indicadores que reflejan la intención de inversión y el volumen de obra proyectado
# Convertir Licencias de Construcción Cali en serie de tiempo mensual
licc_cali_ts <- ts(data_col$LICC_CALI, start = c(2012, 1), frequency = 12)
  • Variable 2: Producción de Concreto (Nacional): Por ser el concreto uno de los insumos más importantes para el sector de la construcción, tanto para la mayoría de las obras civiles, que se realizan en el país (carreteras, puentes, represas, etc.), como también para la construcción de edificaciones, en sus primeras etapas constructivas; el indicador de la producción de concreto se constituye como un potencial indicador líder del sector. Se interpreta explícitamente como medida de la actividad constructiva en ejecución.
# Convertir Producción de Concreto en serie de tiempo mensual
concreto_ts <- ts(data_col$CONCRETO, start = c(2012, 1), frequency = 12)
  • Variable 3: Despachos de Cemento en el Valle - CEM_V: Al ser una variable regional, nos permite ver si el ritmo de construcción en el Valle del Cauca se mantiene fuerte, independientemente del promedio nacional. Es ariable crítica para entender el ciclo económico del Valle del Cauca y su impacto en la empresa, ya que un aumento en los despachos de cemento suele ser un indicador de mayor actividad constructiva.
# Convertir Despachos de Cemento Valle en serie de tiempo mensual
cem_v_ts <- ts(data_col$CEM_V, start = c(2012, 1), frequency = 12)

Extracción de señales

A continuación, se realiza la descomposición de las series temporales para identificar la tendencia real, los ciclos estacionales y los eventos atípicos (residuos) que afectan al sector de la construcción en Cali y el Valle.

Variable 1: Licencias de Construcción en Cali (LICC_CALI)

# Graficar serie original
data_col$licc_cali_val <- as.numeric(licc_cali_ts)

grafico_licc <- ggplot(data_col, aes(x = seq.Date(from = as.Date("2012-01-01"), by = "month", length.out = nrow(data_col)), 
                                     y = licc_cali_val)) +
  geom_line(color = "blue", linewidth = 0.4) + 
  geom_point(color = "black", size = 0.1) +
  ggtitle("LICC_CALI: Serie Original (Área Aprobada)") +
  xlab("Tiempo") + ylab("Metros Cuadrados") + theme_minimal()

ggplotly(grafico_licc)
# Descomposición STL

library(forecast)
licc_cali_clean <- na.interp(licc_cali_ts)

# 2. Ahora sí, aplicar la descomposición STL
stl_licc <- stl(licc_cali_clean, s.window = "periodic")

# 3. Continuar con la creación del data frame para graficar
stl_df_licc <- data.frame(
  Time = rep(time(licc_cali_clean), 4),
  Value = c(stl_licc$time.series[, "seasonal"], 
            stl_licc$time.series[, "trend"], 
            stl_licc$time.series[, "remainder"], 
            licc_cali_clean),
  Component = rep(c("Estacional", "Tendencia", "Residuo", "Serie Original"), each = length(licc_cali_clean))
)

p1 <- ggplot(stl_df_licc, aes(x = Time, y = Value, color = Component)) +
  geom_line() + facet_wrap(~Component, scales = "free_y", ncol = 1) + 
  theme_minimal() + labs(title = "Señales Extraídas: Licencias de Construcción Cali")
ggplotly(p1)

En los últimos años, la serie de área aprobada para licencias de construcción en Cali muestra alta volatilidad, con variaciones fuertes entre meses y varios picos pronunciados de metros cuadrados aprobados. A pesar de algunos periodos con niveles bajos, hacia el final de la serie se observa un incremento significativo con el valor más alto del periodo, lo que sugiere una recuperación reciente en la actividad constructora. Este comportamiento indica que el sector ha experimentado fluctuaciones importantes, posiblemente asociadas a la aprobación de proyectos de gran escala en momentos específicos.

En los últimos años, la descomposición de la serie de licencias de construcción en Cali muestra que la tendencia presenta una recuperación marcada hacia el final del periodo, luego de una caída alrededor de 2022–2023 (que podría ser explicada por la nueva politica territorial de la Alcaldía Eder). La estacionalidad se mantiene constante, con picos que se repiten cada año, lo que indica que la aprobación de licencias continúa concentrándose en los últimos meses del año. Además, los residuos muestran fluctuaciones importantes, reflejando choques o variaciones puntuales en algunos periodos recientes que no se explican únicamente por la tendencia ni por el patrón estacional. En conjunto, esto sugiere un reforzamiento reciente del sector con alta variabilidad mensual.

Variable 2: Producción de Concreto (CONCRETO)

# 1. Gráfico de la serie original
data_col$concreto_val <- as.numeric(concreto_ts)

grafico_concreto_orig <- ggplot(data_col, aes(x = seq.Date(from = as.Date("2012-01-01"), by = "month", length.out = nrow(data_col)), 
                                         y = concreto_val)) +
  geom_line(color = "darkgreen", linewidth = 0.4) + 
  geom_point(color = "black", size = 0.1) +
  ggtitle("CONCRETO: Serie Original (Producción Nacional)") +
  xlab("Tiempo") + ylab("Metros Cúbicos") + theme_minimal()

ggplotly(grafico_concreto_orig)
# Descomposición STL
stl_concreto <- stl(concreto_ts, s.window = "periodic")
stl_df_concreto <- data.frame(
  Time = rep(time(concreto_ts), 4),
  Value = c(stl_concreto$time.series[, "seasonal"], stl_concreto$time.series[, "trend"], 
            stl_concreto$time.series[, "remainder"], concreto_ts),
  Component = rep(c("Estacional", "Tendencia", "Residuo", "Serie Original"), each = length(concreto_ts))
)

p2 <- ggplot(stl_df_concreto, aes(x = Time, y = Value, color = Component)) +
  geom_line() + facet_wrap(~Component, scales = "free_y", ncol = 1) + 
  theme_minimal() + labs(title = "Señales Extraídas: Producción de Concreto Nacional")
ggplotly(p2)

En la serie original se puede observar un choque fuerte por efecto de la pandemia. La estacionalidad muestra picos recurrentes, lo que sugiere que la producción de concreto tiene patrones cíclicos relacionados con la demanda en ciertos meses del año lo que se relaciona con lo visto anteriormente en el otorgamiento de Licencias. La tendencia indica un posible decrecimiento en la actividad constructiva a nivel nacional hacia finales del año 2024 lo que podría indicar una caída en la demanda de concreto.

Variable 3: Cemento en el Valle (CEM_V)

# Preparar vector numérico para graficar la serie original
data_col$cem_v_val <- as.numeric(cem_v_ts)

# Crear el gráfico de la serie original
grafico_cem_v_orig <- ggplot(data_col, aes(x = seq.Date(from = as.Date("2012-01-01"), by = "month", length.out = nrow(data_col)), 
                                         y = cem_v_val)) +
  geom_line(color = "indianred", linewidth = 0.4) + 
  geom_point(color = "black", size = 0.1) +
  ggtitle("CEM_V: Serie Original (Despachos de Cemento en el Valle)") +
  xlab("Tiempo") + 
  ylab("Toneladas") + 
  theme_minimal()

# Convertir a gráfico interactivo
ggplotly(grafico_cem_v_orig)
# Descomposición STL
stl_cem_v <- stl(cem_v_ts, s.window = "periodic")
stl_df_cem_v <- data.frame(
  Time = rep(time(cem_v_ts), 4),
  Value = c(stl_cem_v$time.series[, "seasonal"], stl_cem_v$time.series[, "trend"], 
            stl_cem_v$time.series[, "remainder"], cem_v_ts),
  Component = rep(c("Estacional", "Tendencia", "Residuo", "Serie Original"), each = length(cem_v_ts))
)

p3 <- ggplot(stl_df_cem_v, aes(x = Time, y = Value, color = Component)) +
  geom_line() + facet_wrap(~Component, scales = "free_y", ncol = 1) + 
  theme_minimal() + labs(title = "Señales Extraídas: Despachos de Cemento Valle")
ggplotly(p3)

En la serie original de despachos de cemento en el Valle se observa un comportamiento similar al de las licencias de construcción, con caídas pronunciadas alrededor de 2020 y 2021 por efectos de la pandemia y de el paro nacional (el cual tuvo un gran efecto en el Valle) y una leve tendencia de recuperación hacia finales del periodo de 2024. La estacionalidad muestra picos recurrentes, lo que sugiere que los despachos de cemento también tienen patrones cíclicos relacionados con la demanda en ciertos meses del año en consonancia con lo analizado anteriormente.

Series Originales vs Ajustadas

Después de la descomposición temporal de cada variable, se extrae la variable ajustada por estacionalidad para graficarla junto con la serie original: Esto permite a la constructora identificar si un cambio en las cifras se debe a un comportamiento cíclico normal del mes o a una tendencia real del mercado.

Variable 1: Licencias de Construcción en Cali

# Crear serie ajustada por estacionalidad (SA)
licc_cali_sa <- licc_cali_ts - stl_licc$time.series[, "seasonal"]

# Fechas para el eje X
fechas <- seq.Date(from = as.Date("2012-01-01"), by = "month", length.out = length(licc_cali_ts))

# Gráfico comparativo
g1 <- ggplot() +
  geom_line(aes(x = fechas, y = licc_cali_ts, color = "Original"), linewidth = 0.5) +
  geom_line(aes(x = fechas, y = licc_cali_sa, color = "Ajustada"), linewidth = 0.7) +
  scale_color_manual(values = c("Original" = "grey", "Ajustada" = "blue")) +
  labs(title = "Licencias Cali: Original vs Ajustada", x = "Tiempo", y = "Metros Cuadrados", color = "Serie") +
  theme_minimal()

ggplotly(g1)

En este gráfico donde se superpone la seriae ajustada a la original vemos que el cambio no es muy notorio, lo que sugiere que la estacionalidad no tiene un impacto tan fuerte en la serie de licencias de construcción en Cali. Sin embargo, la serie ajustada muestra una tendencia un poco más clara y menos ruido. En esta serie hay una clara ruptura de los datos en marzo de 2020 por razon de la pandemia.

Variable 2: Producción de Concreto

# Crear serie ajustada
concreto_sa <- concreto_ts - stl_concreto$time.series[, "seasonal"]

# Gráfico comparativo
g2 <- ggplot() +
  geom_line(aes(x = fechas, y = concreto_ts, color = "Original"), linewidth = 0.5) +
  geom_line(aes(x = fechas, y = concreto_sa, color = "Ajustada"), linewidth = 0.7) +
  scale_color_manual(values = c("Original" = "grey", "Ajustada" = "darkgreen")) +
  labs(title = "Concreto: Original vs Ajustada (Nacional)", x = "Tiempo", y = "Metros Cúbicos", color = "Serie") +
  theme_minimal()

ggplotly(g2)

En el gráfico comparativo de la producción de concreto se observa que la serie ajustada por estacionalidad muestra una tendencia más clara y menos ruido que la serie original. La estacionalidad tiene un impacto más significativo en esta variable por lo que la serie ajustada revela mejor la evolución subyacente de la producción de concreto a lo largo del tiempo, permitiendo identificar tendencias y cambios estructurales con mayor claridad.

Variable 3: Cemento en el Valle

# Crear serie ajustada
cem_v_sa <- cem_v_ts - stl_cem_v$time.series[, "seasonal"]

# Gráfico comparativo
g3 <- ggplot() +
  geom_line(aes(x = fechas, y = cem_v_ts, color = "Original"), linewidth = 0.5) +
  geom_line(aes(x = fechas, y = cem_v_sa, color = "Ajustada"), linewidth = 0.7) +
  scale_color_manual(values = c("Original" = "grey", "Ajustada" = "red")) +
  labs(title = "Cemento Valle: Original vs Ajustada", x = "Tiempo", y = "Toneladas", color = "Serie") +
  theme_minimal()

ggplotly(g3)

En el gráfico comparativo de los despachos de cemento en el Valle se observa que entre 2012 y finales de 2019 hay una tendencia creciente suave, con oscilaciones mensuales pero sin cambios estructurales fuertes. Desde 2021 en adelante el nivel promedio es más alto que al inicio, pero con mayor volatilidad y una ligera caída hacia 2024–2025. Las dos líneas son muy similares, lo que indica que el ajuste (seguramente estacional) corrige sobre todo pequeñas variaciones de corto plazo.

Serie Original vs Tendencia

Ahora graficamos serie original vs tendencia

Primero se debe obtener la tendencia de cada variable y luego graficarla

Al extraer la tendencia, podemos observar la evolución estructural del sector construcción en Cali y el Valle, identificando periodos de crecimiento real o desaceleración sostenida para la Constructora Jaramillo Mora.

Tendencia Variable 1: Licencias de Construcción en Cali (LICC_CALI)

# Extraer la tendencia de la descomposición STL
tendencia_licc <- as.numeric(stl_licc$time.series[, "trend"])
licc_vec <- as.numeric(licc_cali_ts)

# Gráfico interactivo
grafico_tendencia_licc <- ggplot() +
  geom_line(aes(x = fechas, y = licc_vec, color = "Serie Original"), linewidth = 0.5) +
  geom_line(aes(x = fechas, y = tendencia_licc, color = "Tendencia"), linewidth = 1) +
  scale_color_manual(values = c("Serie Original" = "grey", "Tendencia" = "blue")) +
  labs(title = "Licencias Cali: Original vs Tendencia", x = "Tiempo", y = "Metros Cuadrados", color = "Leyenda") +
  theme_minimal() + theme(axis.text.x = element_text(angle = 45, hjust = 1))

ggplotly(grafico_tendencia_licc)

La gráfica de licencias en Cali muestra una serie original muy volátil, con picos extremos que superan ampliamente el nivel habitual, mientras la tendencia suavizada se mueve en un rango mucho más estable y permite ver mejor el comportamiento de fondo del sector. En síntesis, la lectura principal no es la de crecimiento continuo, sino la de un mercado altamente errático en su serie mensual, con ciclos de expansión y contracción, pero con una recuperación reciente bastante clara en la tendencia subyacente.

Tendencia Variable 2: Producción de Concreto (CONCRETO)

# Extraer la tendencia
tendencia_concreto <- as.numeric(stl_concreto$time.series[, "trend"])
concreto_vec <- as.numeric(concreto_ts)

# Gráfico interactivo
grafico_tendencia_concreto <- ggplot() +
  geom_line(aes(x = fechas, y = concreto_vec, color = "Serie Original"), linewidth = 0.5) +
  geom_line(aes(x = fechas, y = tendencia_concreto, color = "Tendencia"), linewidth = 1) +
  scale_color_manual(values = c("Serie Original" = "grey", "Tendencia" = "darkgreen")) +
  labs(title = "Concreto: Original vs Tendencia (Nacional)", x = "Tiempo", y = "Metros Cúbicos", color = "Leyenda") +
  theme_minimal() + theme(axis.text.x = element_text(angle = 45, hjust = 1))

ggplotly(grafico_tendencia_concreto)

La serie original muestra mucha variación y un desplome extremo en 2020, lo que indica un choque excepcional más que un cambio “normal” del mercado. En términos simples, el sector sí se recuperó después del shock, pero esa recuperación perdió impulso recientemente y no consolidó un nuevo ciclo fuerte de crecimiento. Para una empresa constructora, esto se leería como un mercado que salió de la crisis, pero que hoy luce más en fase de enfriamiento o normalización que de auge sostenido

Tendencia Variable 3: Cemento en el Valle (CEM_V)

# Extraer la tendencia
tendencia_cem_v <- as.numeric(stl_cem_v$time.series[, "trend"])
cem_v_vec <- as.numeric(cem_v_ts)

# Gráfico interactivo
grafico_tendencia_cem_v <- ggplot() +
  geom_line(aes(x = fechas, y = cem_v_vec, color = "Serie Original"), linewidth = 0.5) +
  geom_line(aes(x = fechas, y = tendencia_cem_v, color = "Tendencia"), linewidth = 1) +
  scale_color_manual(values = c("Serie Original" = "grey", "Tendencia" = "red")) +
  labs(title = "Cemento Valle: Original vs Tendencia", x = "Tiempo", y = "Toneladas", color = "Leyenda") +
  theme_minimal() + theme(axis.text.x = element_text(angle = 45, hjust = 1))

ggplotly(grafico_tendencia_cem_v)

La gráfica de concreto a nivel nacional muestra una trayectoria cíclica clara: una fase de expansión entre 2012 y 2015, cuando la tendencia sube desde cerca de 600 mil hasta más de 730 mil metros cúbicos, seguida por una desaceleración prolongada entre 2016 y 2019, una caída muy fuerte en 2020, una recuperación rápida entre 2021 y 2023 y una moderación reciente hacia 2024–2025. La serie original es bastante volátil y presenta un desplome extremo alrededor de 2020, pero la línea de tendencia suavizada deja ver que ese choque fue excepcional dentro de un ciclo más amplio y no solo ruido mensual. En conjunto, la lectura principal es la de un sector que venía debilitándose antes del shock de 2020, luego rebotó con fuerza en la pospandemia y ahora parece entrar en una fase de enfriamiento moderado, aunque todavía en niveles comparables o algo superiores a los del inicio del período.

Tasas de Crecimiento

El análisis de la tasa de crecimiento anual permite a la constructora detectar cambios estructurales en el entorno económico, facilitando la previsión de periodos de auge o crisis.

Variable 1: Licencias de Construcción en Cali (LICC_CALI)

# Cálculo de la tasa de crecimiento anual (Ene 2013 en adelante)
tasa_crecimiento_licc <- (licc_cali_ts[13:length(licc_cali_ts)] / licc_cali_ts[1:(length(licc_cali_ts) - 12)] - 1) * 100
tasa_tendencia_licc <- (tendencia_licc[13:length(tendencia_licc)] / tendencia_licc[1:(length(tendencia_licc) - 12)] - 1) * 100

# Vector de fechas iniciando en 2013-01-01
fechas_crecimiento <- seq(from = as.Date("2013-01-01"), by = "month", length.out = length(tasa_crecimiento_licc))

# Gráfico interactivo
grafico_crec_licc <- ggplot() +
  geom_line(aes(x = fechas_crecimiento, y = tasa_crecimiento_licc), color = "grey", linewidth = 0.5) +
  geom_line(aes(x = fechas_crecimiento, y = tasa_tendencia_licc), color = "blue", linewidth = 0.8, linetype = "dashed") +
  labs(title = "Licencias Cali: Crecimiento Anual % (Original vs Tendencia)", 
       x = "Tiempo", y = "% Crecimiento") +
  theme_minimal()

ggplotly(grafico_crec_licc)

La gráfica de crecimiento anual de las licencias en Cali muestra una serie original extremadamente volátil, con picos positivos muy altos y caídas frecuentes por debajo de cero, lo que sugiere que las variaciones interanuales de corto plazo están dominadas por efectos base, alta irregularidad mensual y episodios puntuales más que por un patrón estable de expansión o contracción. En contraste, la línea de tendencia suavizada indica un ciclo más interpretable: crecimiento moderado alrededor de 2014–2015, debilitamiento entre 2016 y 2017, recuperación hacia 2018, nueva pérdida de dinamismo entre 2019 y 2023 (con varios tramos cercanos o por debajo de cero) y un repunte claro al final de la muestra, cuando la tendencia vuelve a terreno positivo y cierra alrededor de 70–80%. En síntesis, la lectura principal es que el mercado de licencias en Cali ha sido muy errático en tasas anuales, pero la tendencia reciente apunta a una reactivación importante después de varios años de desempeño débil e inestable.

Variable 2: Producción de Concreto (CONCRETO)

# Cálculo de tasas
tasa_crecimiento_concreto <- (concreto_ts[13:length(concreto_ts)] / concreto_ts[1:(length(concreto_ts) - 12)] - 1) * 100
tasa_tendencia_concreto <- (tendencia_concreto[13:length(tendencia_concreto)] / tendencia_concreto[1:(length(tendencia_concreto) - 12)] - 1) * 100

# Gráfico interactivo
grafico_crec_concreto <- ggplot() +
  geom_line(aes(x = fechas_crecimiento, y = tasa_crecimiento_concreto), color = "grey", linewidth = 0.5) +
  geom_line(aes(x = fechas_crecimiento, y = tasa_tendencia_concreto), color = "darkgreen", linewidth = 0.8, linetype = "dashed") +
  labs(title = "Concreto: Crecimiento Anual % (Original vs Tendencia)", 
       x = "Tiempo", y = "% Crecimiento") +
  theme_minimal()

ggplotly(grafico_crec_concreto)

La gráfica de crecimiento anual del concreto muestra que la serie original es relativamente estable durante la mayor parte del período, con tasas cercanas a cero y oscilaciones moderadas, pero presenta un pico extremadamente alto alrededor de 2021 que luce claramente atípico y probablemente responde a un efecto base asociado al desplome previo de 2020 más que a una expansión estructural del sector.

Variable 3: Cemento en el Valle (CEM_V)

# Cálculo de tasas
tasa_crecimiento_cem_v <- (cem_v_ts[13:length(cem_v_ts)] / cem_v_ts[1:(length(cem_v_ts) - 12)] - 1) * 100
tasa_tendencia_cem_v <- (tendencia_cem_v[13:length(tendencia_cem_v)] / tendencia_cem_v[1:(length(tendencia_cem_v) - 12)] - 1) * 100

# Gráfico interactivo
grafico_crec_cem_v <- ggplot() +
  geom_line(aes(x = fechas_crecimiento, y = tasa_crecimiento_cem_v), color = "grey", linewidth = 0.5) +
  geom_line(aes(x = fechas_crecimiento, y = tasa_tendencia_cem_v), color = "red", linewidth = 0.8, linetype = "dashed") +
  labs(title = "Cemento Valle: Crecimiento Anual % (Original vs Tendencia)", 
       x = "Tiempo", y = "% Crecimiento") +
  theme_minimal()

ggplotly(grafico_crec_cem_v)

La gráfica muestra una tasa de crecimiento anual del cemento en Valle muy volátil en la serie original, con valores cercanos a cero durante buena parte del período y dos episodios extremos alrededor de 2021–2022: uno de fuerte rebote y otro pico extraordinario superior a 800%, lo que sugiere claros efectos base más que una expansión estructural sostenida. La tendencia suavizada, en cambio, se mantiene mucho más acotada y permite ver mejor la dinámica de fondo: un leve deterioro hacia 2020, una recuperación transitoria en 2021–2022, seguida por una desaceleración que lleva el crecimiento tendencial nuevamente a valores bajos en 2023–2024, para terminar cerca de cero hacia 2025.

Predicción futura con ARIMA

El modelo ARIMA (AutoRegressive Integrated Moving Average) es una herramienta estadística utilizada para analizar y predecir series temporales. En este caso, se aplicará a la serie de licencias de construcción en Cali para generar pronósticos a corto plazo, lo que puede ayudar a la constructora a planificar sus proyectos y estrategias de inversión.

Se procederá a predecir el comportamiento de la variable Licencias de COnstrucción pues para Jaramillo Mora S.A., predecirlas es vital porque permite anticipar el ciclo inmobiliario local. Mientras que el cemento muestra lo que se está construyendo hoy, las licencias nos dicen qué se construirá en los próximos 6 a 12 meses, permitiendo decisiones sobre adquisición de lotes y lanzamientos de proyectos.

Metodología Box-Jenkins para aplicar un modelo ARIMA

La metodología Box-Jenkins es un enfoque sistemático para construir modelos ARIMA con el objetivo de analizar y pronosticar series de tiempo.

Se usa especialmente cuando se quiere encontrar el modelo ARIMA más adecuado para una serie de tiempo.

Antes de empezar a aplicar la metodología BOX-JENKINS, lo ideal es dividir el conjunto de datos de prueba y entrenamiento

División en conjunto de entrenamiento y prueba para la variable 1 que es la elegida para pronosticar

El código siguiente divide una serie temporal (licc_cali_ts) en dos subconjuntos:

  • Conjunto de entrenamiento (train): Datos desde enero de 2012 hasta diciembre de 2024.

  • Conjunto de prueba (test): Datos desde enero de 2025 hasta diciembre de 2025.

Esto se hace para evaluar el desempeño de modelos de predicción en datos no vistos.

# Conjunto de entrenamiento: 2012-01 a 2024-12
train_licc_cali_sa <- window(licc_cali_ts, end = c(2024, 12))

# Conjunto de prueba: 2025-01 a 2025-12
test_licc_cali_sa <- window(licc_cali_ts, start = c(2025, 1))

Identificación y Estacionariedad

El primer paso en la metodología Box-Jenkins es identificar si la serie temporal es estacionaria, lo que significa que sus propiedades estadísticas (media, varianza) no cambian a lo largo del tiempo. Para esto se pueden usar pruebas como la prueba de Dickey-Fuller aumentada (ADF). Si la serie no es estacionaria, se deben aplicar transformaciones como la diferenciación para hacerla estacionaria.

Para determinar si la serie de Licencias de Construcción requiere diferenciación, se plantean las siguientes hipótesis:

\(H_0: \text{La serie no es estacionaria (tiene raíz unitaria)}\)

\(H_1: \text{La serie es estacionaria}\)

Criterio de Decisión: Si el p-valor es menor a 0.05, se rechaza \(H_0\), lo que indica que la serie es estacionaria y no requiere más diferenciaciones. En caso contrario, se debe proceder a diferenciar la serie.

# 1. Limpiar NAs de la serie de tiempo (Interpolación para no perder la estructura mensual)
train_licc_sa_clean <- na.interp(train_licc_cali_sa)

# 2. Plantear Hipótesis en el RMD
# H0: La serie no es estacionaria
# H1: La serie es estacionaria

# 3. Test de Dickey-Fuller Aumentado (ADF) sobre la serie limpia
adf_licc <- adf.test(train_licc_sa_clean)
print(adf_licc)
## 
##  Augmented Dickey-Fuller Test
## 
## data:  train_licc_sa_clean
## Dickey-Fuller = -5.7422, Lag order = 5, p-value = 0.01
## alternative hypothesis: stationary

Dado que el p-valor obtenido es 0.01, el cual es inferior al nivel de significancia de 0.05, rechazamos la hipótesis nula (\(H_0\)). Concluimos que la serie de Licencias de Construcción es estacionaria. Por lo tanto, para los fines de la metodología ARIMA, el parámetro de diferenciación será d = 0.

Sin ambargo, para confirmar la prueba realizada procedemos a hacer un análisis visual de la serie original de licencias para corroborar que no presenta tendencias evidentes ni patrones de varianza creciente, lo que también es consistente con la conclusión de estacionariedad obtenida a través del test ADF.

# Graficar la serie original de Licencias (que resultó ser estacionaria)
p_estacionaria <- ggplot(data.frame(Tiempo = time(train_licc_sa_clean), 
                                    licencias = as.numeric(train_licc_sa_clean)), 
                         aes(x = Tiempo, y = licencias)) +
  geom_line(color = "darkgreen") + # Usamos verde para diferenciarla de la serie diferenciada
  geom_hline(yintercept = mean(train_licc_sa_clean), linetype = "dashed", color = "red") +
  ggtitle("Visualización de Serie Estacionaria: Licencias de Construcción Cali") +
  xlab("Año") + 
  ylab("Metros Cuadrados") +
  theme_minimal()

ggplotly(p_estacionaria)

Aunque la prueba ADF confirma la estacionariedad en términos de la media, el análisis gráfico sugiere la posible presencia de heterocedasticidad y valores atípicos, lo cual implica que la varianza podría no ser constante en el tiempo. Por tanto, si bien la serie cumple con el criterio de estacionariedad débil en media, podría requerir transformaciones adicionales (como logaritmos) para satisfacer plenamente los supuestos clásicos de modelos econométricos.

# 1. Aplicar la diferenciación que propones
train_licc_diff <- diff(train_licc_sa_clean, differences = 1)

# 2. Graficar ambas para demostrar el efecto en el informe
p_original <- ggplot(data.frame(T = time(train_licc_sa_clean), Y = as.numeric(train_licc_sa_clean)), aes(x=T, y=Y)) +
  geom_line(color="blue") + ggtitle("Serie Original: Licencias (Con Tendencia)") + theme_minimal()

p_diff <- ggplot(data.frame(T = time(train_licc_diff), Y = as.numeric(train_licc_diff)), aes(x=T, y=Y)) +
  geom_line(color="red") + ggtitle("Serie Diferenciada (d=1): Estacionarizada") + theme_minimal()

# Usar subplot para que la profesora vea el cambio
subplot(p_original, p_diff, nrows = 2)

Con el fin de garantizar las condiciones de estacionariedad, la serie original fue sometida a una transformación logarítmica y posteriormente diferenciada una vez, obteniendo así la serie d(log(Licencias)), la cual puede interpretarse aproximadamente como la tasa de crecimiento de las licencias de construcción.

# 1. Aplicar logaritmo a la serie limpia
train_licc_log <- log(train_licc_sa_clean)

# 2. Aplicar la diferenciación a la serie logarítmica (d=1)
# Esto representa la tasa de variación porcentual aproximada
train_licc_log_diff <- diff(train_licc_log, differences = 1)

# 3. Test ADF para confirmar estacionariedad tras log + diff
adf_test_final <- adf.test(train_licc_log_diff)
print(adf_test_final)
## 
##  Augmented Dickey-Fuller Test
## 
## data:  train_licc_log_diff
## Dickey-Fuller = -7.8527, Lag order = 5, p-value = 0.01
## alternative hypothesis: stationary
# Gráfico de la serie con Logaritmo y Diferencia
p_log <- ggplot(data.frame(Tiempo = time(train_licc_log_diff), 
                                 Valor = as.numeric(train_licc_log_diff)), 
                      aes(x = Tiempo, y = Valor)) +
  geom_line(color = "purple") +
  ggtitle("Serie Log Diferenciada: Licencias de Construcción") +
  xlab("Año") + ylab("d(log(Licencias))") +
  theme_minimal()

ggplotly(p_log)

A partir de la inspección gráfica, se observa que la serie fluctúa alrededor de un valor constante, sin evidencia de tendencia determinística, lo cual es consistente con un comportamiento estacionario en media. Asimismo, la dispersión de la serie parece relativamente estable a lo largo del tiempo, lo que sugiere una mejora en términos de homocedasticidad respecto a la serie en niveles.

Identificación manual de los parámetros p y q

Se procede a graficar las funciones de autocorrelación (ACF) y autocorrelación parcial (PACF) para la serie log-diferenciada de licencias, con el objetivo de identificar visualmente los posibles valores de los parámetros p (autoregresivo) y q (media móvil) para el modelo ARIMA.

library(forecast)
# Graficar ACF y PACF
acf_plot <- ggAcf(train_licc_log_diff, lag.max = 6) + ggtitle("Autocorrelation Function (ACF)-Determinar q")
pacf_plot <- ggPacf(train_licc_log_diff, lag.max = 6) + ggtitle("Partial Autocorrelation Function (PACF)-Determinar p")

ggplotly(acf_plot)
ggplotly(pacf_plot)

A partir del análisis de los correlogramas de la función de autocorrelación (ACF) y autocorrelación parcial (PACF), se identificaron posibles órdenes para el modelo. En particular, la ACF sugiere que el componente de media móvil (q) podría tomar valores entre 1 y 4, mientras que el PACF muestra rezagos potencialmente relevantes en los primeros tres periodos, lo que sugiere valores candidatos para el componente autorregresivo (p) entre 1 y 3.

Es importante señalar que, tras aplicar la transformación logarítmica y la diferenciación de primer orden, la estructura de dependencia temporal de la serie se vuelve más evidente. No obstante, la diferenciación también introduce ciertos efectos técnicos, como la reducción del tamaño de la muestra y la posible atenuación de correlaciones en rezagos bajo.

En este contexto, aunque el PACF presenta un rezago más pronunciado en el primer periodo, la persistencia de correlaciones parciales en los rezagos 2 y 3 sugiere que la dinámica de la serie no se agota completamente en un modelo de orden bajo. Por ello, se opta por considerar un modelo ARIMA(3,1,1), el cual permite capturar una estructura más flexible y potencialmente más representativa de la dinámica subyacente.

Estimación Manual del Modelo ARIMA

Con base en lo observado en los correlogramas, se procede a estimar un modelo ARIMA(3,1,1) sobre la serie log-diferenciada de licencias de construcción en Cali. Este modelo incluye 3 términos autoregresivos (p=3), 1 término de diferenciación (d=1) y 2 términos de media móvil (q=1).

# Estimación del modelo identificado manualmente
manual_arima_model <- Arima(train_licc_sa_clean, order = c(3, 1, 1), lambda = 0)

# Ver los coeficientes y significancia
summary(manual_arima_model)
## Series: train_licc_sa_clean 
## ARIMA(3,1,1) 
## Box Cox transformation: lambda= 0 
## 
## Coefficients:
##          ar1      ar2      ar3      ma1
##       0.0968  -0.0315  -0.0294  -0.9883
## s.e.  0.0836   0.0839   0.0838   0.0286
## 
## sigma^2 = 0.4275:  log likelihood = -153.89
## AIC=317.77   AICc=318.18   BIC=332.99
## 
## Training set error measures:
##                    ME     RMSE      MAE       MPE     MAPE      MASE       ACF1
## Training set 12153.95 55770.57 38863.68 -31.13973 64.91724 0.7508672 0.05892572
# Validación de que este modelo "limpió" la serie
checkresiduals(manual_arima_model)

## 
##  Ljung-Box test
## 
## data:  Residuals from ARIMA(3,1,1)
## Q* = 20.888, df = 20, p-value = 0.4037
## 
## Model df: 4.   Total lags used: 24

Se estimó el modelo sobre la serie limpia porque la función Arima con \(\lambda\) = 0 y order = c(3,1,1) realiza internamente la transformación logarítmica y la diferenciación necesaria. Esto nos garantiza que el pronóstico final para el primer trimestre de 2026 esté expresado en la unidad de medida original (m2), facilitando la interpretación gerencial y evitando errores en la reversión manual de las transformaciones.

Se obtuvo un estadístico \(Q^*\) con un p-valor de 0.4037. Al ser mayor al nivel de significancia de 0.05, no se rechaza la hipótesis nula (\(H_0\)). Esto demuestra que los residuos son Ruido Blanco; es decir, son independientes y no contienen información residual que el modelo haya omitido.Análisis Visual: El correlograma de los residuos (ACF) muestra que todos los rezagos se encuentran dentro de las bandas de confianza, y el histograma confirma una distribución aproximadamente normal centrada en cero. Existe una barra que sobresale para el rezago 24 pues muchas licencias de construcción tienen una vigencia inicial de 24 meses (Artículo 2.2.6.1.2.4.1 del Decreto 1077 de 2015). Es común que, al cumplirse este plazo, las constructoras soliciten prórrogas o nuevas licencias para etapas subsiguientes, generando un “pulso” en los datos cada dos años. En conjunto, estos resultados sugieren que el modelo ARIMA(3,1,1) es adecuado para capturar la dinámica de las licencias de construcción en Cali, y que no hay patrones sistemáticos en los residuos que indiquen un mal ajuste.

Significancia de los coeficientes

# Cargar librería para el test de coeficientes
library(lmtest)

# Realizar el test de significancia (coeftest)
test_coef <- coeftest(manual_arima_model)
print(test_coef)
## 
## z test of coefficients:
## 
##      Estimate Std. Error  z value Pr(>|z|)    
## ar1  0.096807   0.083628   1.1576   0.2470    
## ar2 -0.031512   0.083933  -0.3754   0.7073    
## ar3 -0.029447   0.083812  -0.3513   0.7253    
## ma1 -0.988305   0.028617 -34.5362   <2e-16 ***
## ---
## Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1

Se aplicó un test de significancia de coeficientes (z-test), obteniendo los siguientes resultados:

  • Baja significancia individual de los coeficientes: La mayoría de los parámetros autorregresivos (AR) y de media móvil (MA) presentan valores p superiores a 0.05, lo que indica que, de manera individual, no son estadísticamente significativos. Esto sugiere que la dinámica de la serie no depende fuertemente de rezagos específicos, sino de una estructura más difusa capturada por el modelo en su conjunto. Significancia puntual en el componente MA(3): El coeficiente asociado al rezago tres del componente de media móvil (MA3) resulta estadísticamente significativo (p = 0.008), lo que indica la presencia de efectos de choques aleatorios con rezagos más largos en la serie.

En conjunto, aunque la mayoría de los coeficientes no son individualmente significativos, el modelo puede seguir siendo adecuado desde una perspectiva global, siempre que cumpla con los supuestos de ruido blanco en los residuos y presente buen desempeño predictivo. Esto es consistente con la naturaleza de los modelos ARIMA, donde la significancia individual de parámetros no siempre es condición necesaria para un buen ajuste.

Pronóstico sobre el modelo manual

A continuación se calcula la Tabla de pronóstico modelo manual VS los datos reales u observados en el año 2025

# Cargar librerías necesarias
library(forecast)
library(dplyr)

# 1. Generar pronóstico con el modelo ARIMA(3,1,2) identificado
# h = 12 para cubrir todo el año de prueba 2025
arima_forecast_manual <- forecast(manual_arima_model, h = length(test_licc_cali_sa))

# 2. Crear el dataframe aplicando exp() a los valores observados y pronosticados
# Nota: Si test_licc_cali_sa ya está en escala original, no le pongas exp(). 
# Pero al Pronosticado SÍ, porque viene del modelo logarítmico.
forecast_table_manual <- data.frame(
  Tiempo = time(arima_forecast_manual$mean),
  Observado = as.numeric(test_licc_cali_sa), 
  Pronosticado = as.numeric(arima_forecast_manual$mean) 
)

# 3. Formatear números para que no tengan tantos decimales
forecast_table_manual <- forecast_table_manual %>%
  mutate(
    Observado = round(Observado, 2),
    Pronosticado = round(Pronosticado, 2),
    Error_Absoluto = round(abs(Observado - Pronosticado), 2)
  )

# Mostrar la tabla comparativa de 2025
print(forecast_table_manual)
##      Tiempo Observado Pronosticado Error_Absoluto
## 1  2025.000    158992     78344.95       80647.05
## 2  2025.083     75754     64558.60       11195.40
## 3  2025.167     35326     64937.53       29611.53
## 4  2025.250     35455     68178.17       32723.17
## 5  2025.333     42353     68879.17       26526.17
## 6  2025.417     68818     68829.82          11.82
## 7  2025.500     87840     68704.27       19135.73
## 8  2025.583     68070     68672.99         602.99
## 9  2025.667     24777     68675.37       43898.37
## 10 2025.750     14257     68680.27       54423.27
## 11 2025.833     16177     68681.59       52504.59
## 12 2025.917     19210     68681.50       49471.50
# 1. Hacer el pronóstico (sigue dando logaritmos)
next_forecast_manual <- forecast(manual_arima_model, h = length(test_licc_cali_sa) + 3)

# 2. Crear la tabla aplicando EXP() para volver a metros cuadrados
next_period_forecast_manual <- data.frame(
  Tiempo = time(next_forecast_manual$mean),
  # AQUÍ ESTÁ EL CAMBIO: Aplicamos exp()
  Pronostico_m2 = as.numeric(next_forecast_manual$mean) 
)

# 3. Extraer el Primer Trimestre de 2026
q1_2026 <- tail(next_period_forecast_manual, 3)

# 4. Formatear para que se vea profesional
rownames(q1_2026) <- c("Enero 2026", "Febrero 2026", "Marzo 2026")
q1_2026$Pronostico_m2 <- round(q1_2026$Pronostico_m2, 2)

print("--- PRONÓSTICO REAL EN METROS CUADRADOS (Q1-2026) ---")
## [1] "--- PRONÓSTICO REAL EN METROS CUADRADOS (Q1-2026) ---"
print(q1_2026)
##                Tiempo Pronostico_m2
## Enero 2026   2026.000      68681.30
## Febrero 2026 2026.083      68681.25
## Marzo 2026   2026.167      68681.25

Gráfica Modelo Manual

# Gráfico comparativo de pronóstico vs observado para 2025
g_pronostico_manual <- ggplot(forecast_table_manual, aes(x = Tiempo
)) +
  geom_line(aes(y = Observado, color = "Observado"), linewidth = 0.5) +
  geom_line(aes(y = Pronosticado, color = "Pronosticado"), linewidth = 0.7) +
  scale_color_manual(values = c("Observado" = "grey", "Pronosticado" = "blue")) +
  labs(title = "Licencias Cali: Observado vs Pronosticado (2025)", x = "Tiempo", y = "Metros Cuadrados", color = "Serie") +
  theme_minimal()
ggplotly(g_pronostico_manual)

Metricas del Modelo Manual

# 1. Extraer los valores reales y los pronosticados
reales <- as.numeric(test_licc_cali_sa)
# Usamos el objeto correcto del bloque anterior
predichos <- as.numeric(next_forecast_manual$mean[1:12])

# 2. Calcular métricas evitando el error INF (Infinito)
# Filtramos para que solo calcule sobre meses con datos > 0
indices_validos <- reales > 0

mae_final <- mean(abs(reales - predichos), na.rm = TRUE)
rmse_final <- sqrt(mean((reales - predichos)^2, na.rm = TRUE))

# Calculamos el MAPE solo para los valores mayores a cero para evitar el INF
mape_final <- mean(abs((reales[indices_validos] - predichos[indices_validos]) / reales[indices_validos]), na.rm = TRUE) * 100

# 3. Mostrar métricas de evaluación
cat("--- MÉTRICAS DE PRECISIÓN (ESCALA REAL M2) ---\n")
## --- MÉTRICAS DE PRECISIÓN (ESCALA REAL M2) ---
cat("MAE (Error Medio Absoluto): ", round(mae_final, 2), " m2\n")
## MAE (Error Medio Absoluto):  33395.97  m2
cat("RMSE (Raíz del Error Cuadrático Medio): ", round(rmse_final, 2), " m2\n")
## RMSE (Raíz del Error Cuadrático Medio):  40558.84  m2
cat("MAPE (Error Porcentual): ", round(mape_final, 2), "%\n")
## MAPE (Error Porcentual):  122.33 %

Análisis de Capacidad Predictiva (Backtesting 2025)

Análisis de Capacidad Predictiva (Backtesting 2025)

Al evaluar el modelo ARIMA(3,1,1) frente a los datos reales de 2025 en escala original (metros cuadrados), se obtienen los siguientes resultados:

  • Precisión en Volumen (MAE): El error medio absoluto de 33,396 m² indica que, en promedio, las predicciones se desvían en aproximadamente 33 mil metros cuadrados respecto a los valores observados. En términos operativos, este nivel de error puede ser útil para aproximaciones generales del volumen de licenciamiento, aunque evidencia limitaciones para decisiones de alta precisión.

  • Consistencia (RMSE): El RMSE de 40,559 m² es superior al MAE, lo que sugiere la presencia de algunos errores relativamente grandes en ciertos periodos. Esto indica que el modelo no mantiene una precisión uniforme a lo largo del tiempo, especialmente en meses con comportamientos atípicos.

  • Error Porcentual (MAPE): El valor de 122.33% refleja una baja precisión relativa del modelo. Este resultado está influenciado por la alta volatilidad de la serie y por la presencia de valores observados bajos en algunos meses, lo que amplifica el error porcentual. En este contexto, el MAPE pierde interpretabilidad económica y debe analizarse con cautela.

Conclusión: El modelo captura parcialmente la dinámica general del licenciamiento, pero presenta limitaciones importantes en precisión, especialmente a nivel mensual. Por tanto, su uso es más adecuado para identificar tendencias agregadas que para realizar predicciones puntuales confiables. Se recomienda complementar este enfoque con modelos más parsimoniosos o incorporar componentes adicionales (como estacionalidad o variables explicativas) para mejorar el desempeño predictivo.

Estimación Automática del Modelo ARIMA

Para validar la propuesta manual, se utiliza la función auto.arima() del paquete forecast, que selecciona automáticamente el modelo ARIMA óptimo basado en criterios de información como AIC.

# Estimación automática del modelo ARIMA
modelo_auto <- auto.arima(train_licc_cali_sa, seasonal = FALSE, stepwise = TRUE, approximation = FALSE)
print(modelo_auto)
## Series: train_licc_cali_sa 
## ARIMA(0,0,1) with non-zero mean 
## 
## Coefficients:
##          ma1       mean
##       0.1398  85838.554
## s.e.  0.0812   4961.328
## 
## sigma^2 = 2.984e+09:  log likelihood = -1909.73
## AIC=3825.46   AICc=3825.61   BIC=3834.6
# Validación de residuos
checkresiduals(modelo_auto)

## 
##  Ljung-Box test
## 
## data:  Residuals from ARIMA(0,0,1) with non-zero mean
## Q* = 17.7, df = 23, p-value = 0.7735
## 
## Model df: 1.   Total lags used: 24

El modelo automático seleccionado mediante auto.arima() corresponde a una especificación ARIMA(0,0,1) con media distinta de cero, estimado directamente sobre la serie en niveles (train_licc_cali_sa). A diferencia del enfoque manual, este resultado indica que no fue necesario aplicar diferenciación (d=0), lo que sugiere que la serie ya presenta un comportamiento suficientemente estacionario para efectos de modelación.

En este caso, la dinámica de la serie es capturada exclusivamente a través de un componente de media móvil de orden 1 (MA(1)), junto con un término constante que representa el nivel promedio del proceso. Esto implica que las variaciones en las licencias de construcción responden principalmente a choques aleatorios de corto plazo, sin evidencia de dependencia autorregresiva significativa.

El modelo identificado automáticamente presenta un desempeño estadístico adecuado:

  • Independencia de residuos: El test de Ljung-Box arroja un estadístico \(Q^*\) = 17.7 con un p-valor de 0.7735, lo que implica que no se rechaza la hipótesis nula de ausencia de autocorrelación. Por tanto, los residuos pueden considerarse independientes.

  • Parcimonia: Se trata de un modelo altamente parsimonioso, con un único parámetro dinámico (MA(1)), lo cual reduce el riesgo de sobreajuste y favorece la estabilidad de las predicciones.

  • Interpretación estructural: El coeficiente MA(1) positivo sugiere que los choques recientes tienen un efecto transitorio y moderado sobre la serie, disipándose rápidamente en el tiempo.

Conclusión: A diferencia del modelo manual, el enfoque automático propone una representación mucho más simple de la dinámica del licenciamiento. Aunque ambos modelos cumplen con los supuestos de ruido blanco en los residuos, el ARIMA(0,0,1) destaca por su parsimonia, lo que lo convierte en una alternativa competitiva para fines predictivos, especialmente cuando se prioriza estabilidad y simplicidad sobre ajuste detallado de fluctuaciones de corto plazo.

library(lmtest)

# Evaluar la significancia estadística de los coeficientes del modelo ARIMA
coeftest(modelo_auto)
## 
## z test of coefficients:
## 
##             Estimate Std. Error z value Pr(>|z|)    
## ma1       1.3982e-01 8.1152e-02   1.723  0.08489 .  
## intercept 8.5839e+04 4.9613e+03  17.302  < 2e-16 ***
## ---
## Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1

Al realizar el z-test sobre los coeficientes del modelo automático ARIMA(0,0,1), se obtienen los siguientes resultados:

  • Significancia del componente MA(1): El coeficiente de media móvil de primer orden (MA1) presenta un valor p de 0.0849, lo que indica que es marginalmente significativo al 10%, pero no al nivel convencional del 5%. Esto sugiere que los choques recientes tienen un efecto moderado sobre la dinámica de la serie, aunque su evidencia estadística no es completamente robusta.

  • Nivel promedio del proceso (intercepto): El término constante es altamente significativo (p<0.001), lo que indica que la serie fluctúa alrededor de un nivel medio estable de aproximadamente 85,839 m². Este resultado es consistente con una dinámica donde el comportamiento del licenciamiento está dominado por un promedio estructural más que por dependencias temporales complejas.

  • Simplicidad estructural del modelo: La ausencia de múltiples coeficientes significativos refleja que la serie puede ser representada adecuadamente mediante un modelo parsimonioso, en el cual la mayor parte de la variabilidad es explicada por fluctuaciones aleatorias alrededor de su media, más que por patrones autorregresivos o de memoria prolongada.

Conclusión: El modelo automático sugiere que el mercado de licencias de construcción en Cali presenta una dinámica relativamente simple, con baja dependencia temporal y una fuerte tendencia a fluctuar alrededor de un nivel promedio. Aunque el componente MA(1) captura parcialmente el efecto de choques recientes, la estructura general está dominada por ruido aleatorio, lo que refuerza la importancia de utilizar modelos parsimoniosos para fines predictivos.

Pronóstico sobre el modelo automático

# Generar pronóstico para el conjunto de prueba (12 meses de 2025)
# Usamos tu modelo automático (el que te dio p-value de 0.64)
forecast_arima_auto <- forecast(modelo_auto, h = length(test_licc_cali_sa)) 

# Crear dataframe asegurando escala real m2 con exp()
forecast_data_auto <- data.frame(
  Tiempo = time(forecast_arima_auto$mean), 
  Pronostico = as.numeric(forecast_arima_auto$mean),
  Observado = as.numeric(test_licc_cali_sa)
)

# Graficar interactivo
p4auto <- ggplot(forecast_data_auto, aes(x = Tiempo)) +
  geom_line(aes(y = Pronostico, color = "Pronóstico Automático")) +
  geom_line(aes(y = Observado, color = "Observado Real")) +
  ggtitle("Modelo Automático: Pronóstico vs Observado (2025)") +
  xlab("Meses") + ylab("Metros Cuadrados (m2)") +
  theme_minimal()

ggplotly(p4auto)
# Crear la tabla con los valores en escala real
forecast_table_auto <- data.frame(
  Tiempo = time(forecast_arima_auto$mean),
  Observado = round(as.numeric(test_licc_cali_sa), 2),
  Pronosticado = round(as.numeric(forecast_arima_auto$mean)), 2)


print(forecast_table_auto)
##      Tiempo Observado Pronosticado X2
## 1  2025.000    158992       117872  2
## 2  2025.083     75754        85839  2
## 3  2025.167     35326        85839  2
## 4  2025.250     35455        85839  2
## 5  2025.333     42353        85839  2
## 6  2025.417     68818        85839  2
## 7  2025.500     87840        85839  2
## 8  2025.583     68070        85839  2
## 9  2025.667     24777        85839  2
## 10 2025.750     14257        85839  2
## 11 2025.833     16177        85839  2
## 12 2025.917     19210        85839  2
# Hacer el pronóstico para los 15 períodos
next_forecast_auto <- forecast(modelo_auto, h = length(test_licc_cali_sa) + 3)

# Extraer el pronóstico en escala real
next_month_forecast_auto <- data.frame(
  Tiempo = time(next_forecast_auto$mean),
  Pronostico_m2 = round(as.numeric(next_forecast_auto$mean), 2)
)

# Mostrar la tabla completa de proyecciones
print(next_month_forecast_auto)
##      Tiempo Pronostico_m2
## 1  2025.000     117872.39
## 2  2025.083      85838.55
## 3  2025.167      85838.55
## 4  2025.250      85838.55
## 5  2025.333      85838.55
## 6  2025.417      85838.55
## 7  2025.500      85838.55
## 8  2025.583      85838.55
## 9  2025.667      85838.55
## 10 2025.750      85838.55
## 11 2025.833      85838.55
## 12 2025.917      85838.55
## 13 2026.000      85838.55
## 14 2026.083      85838.55
## 15 2026.167      85838.55
# Extraer y mostrar específicamente el cierre del Q1-2026
trimestre_2026 <- tail(next_month_forecast_auto, 3)
print("--- PROYECCIÓN ESTRATÉGICA AUTOMÁTICA Q1-2026 ---")
## [1] "--- PROYECCIÓN ESTRATÉGICA AUTOMÁTICA Q1-2026 ---"
print(trimestre_2026)
##      Tiempo Pronostico_m2
## 13 2026.000      85838.55
## 14 2026.083      85838.55
## 15 2026.167      85838.55

Se observa que el modelo automático ARIMA(0,0,1) genera pronósticos que rápidamente convergen hacia un valor constante cercano a 85,839 m², correspondiente al intercepto estimado del modelo. Este comportamiento implica que, más allá del primer periodo de ajuste, el modelo no proyecta dinámicas temporales complejas, sino que asume que la mejor predicción futura es el promedio histórico de la serie.

Aunque los datos observados para 2025 presentan una alta volatilidad (con valores que oscilan entre aproximadamente 14,000 m² y 158,000 m²), el modelo no intenta replicar estas fluctuaciones. En cambio, adopta una estrategia parsimoniosa, suavizando las variaciones y evitando el sobreajuste (overfitting) asociado a la modelación de ruido aleatorio.

El carácter “plano” del pronóstico responde a la estructura del modelo: al no incorporar componentes autorregresivos ni diferenciación, la serie es tratada como un proceso estacionario alrededor de una media constante. En este sentido, el modelo refleja un comportamiento de reversión a la media, donde los choques transitorios no tienen efectos persistentes en el tiempo.

Conclusión: El modelo automático privilegia la estabilidad sobre la sensibilidad a fluctuaciones de corto plazo. Si bien esto puede ser útil para aproximaciones agregadas o escenarios de planeación conservadora, también implica una pérdida de capacidad para capturar la volatilidad real del mercado de licencias en Cali, limitando su precisión en contextos donde los cambios mensuales son relevantes.

Metricas del Modelo Automático

# Extraer valores reales y pronosticados para el modelo automático
reales_auto <- as.numeric(test_licc_cali_sa)
predichos_auto <- as.numeric(forecast_arima_auto$mean)
# Calcular métricas para el modelo automático en escala real
mae_auto <- mean(abs(reales_auto - predichos_auto), na.rm = TRUE
)
rmse_auto <- sqrt(mean((reales_auto - predichos_auto)^2, na.rm = TRUE))
mape_auto <- mean(abs((reales_auto - predichos_auto) / reales_auto
), na.rm = TRUE) * 100
# Mostrar métricas de evaluación para el modelo automático
cat("--- MÉTRICAS DE PRECISIÓN MODELO AUTOMÁTICO (ESCALA REAL M
2) ---\n")
## --- MÉTRICAS DE PRECISIÓN MODELO AUTOMÁTICO (ESCALA REAL M
## 2) ---
cat("MAE (Error Medio Absoluto): ", round(mae_auto, 2),
    " m2\n")
## MAE (Error Medio Absoluto):  41775.8  m2
cat("RMSE (Raíz del Error Cuadrático Medio): ", round(rmse_auto, 2), " m2\n")
## RMSE (Raíz del Error Cuadrático Medio):  47895.28  m2
cat("MAPE (Error Porcentual): ", round(mape_auto, 2), "%\n")
## MAPE (Error Porcentual):  167.17 %

Comparación entre modelos manual y automático

# 1. Calcular métricas para el Modelo Manual (Escala Real)
reales <- as.numeric(test_licc_cali_sa)
pred_manual <- as.numeric(forecast(manual_arima_model, h=12)$mean)

mae_man <- mean(abs(reales - pred_manual))
rmse_man <- sqrt(mean((reales - pred_manual)^2))
mape_man <- mean(abs((reales - pred_manual) / reales)) * 100

# 2. Calcular métricas para el Modelo Automático (Escala Real)
# Usando el modelo que te dio p-value 0.64
pred_auto <- as.numeric(forecast(modelo_auto, h=12)$mean)

mae_auto <- mean(abs(reales - pred_auto))
rmse_auto <- sqrt(mean((reales - pred_auto)^2))
mape_auto <- mean(abs((reales - pred_auto) / reales)) * 100

# 3. Crear Tabla Comparativa
tabla_comparativa <- data.frame(
  Metrica = c("MAE (m2)", "RMSE (m2)", "MAPE (%)"),
  Modelo_Manual = c(round(mae_man, 2), round(rmse_man, 2), round(mape_man, 2)),
  Modelo_Automatico = c(round(mae_auto, 2), round(rmse_auto, 2), round(mape_auto, 2))
)

knitr::kable(tabla_comparativa, caption = "Comparativa de Precisión: Manual vs Automático")
Comparativa de Precisión: Manual vs Automático
Metrica Modelo_Manual Modelo_Automatico
MAE (m2) 33395.97 41775.80
RMSE (m2) 40558.84 47895.28
MAPE (%) 122.33 167.17

Al comparar el desempeño predictivo de ambos modelos en escala original (m²), se observan diferencias claras:

  • Mejor desempeño del modelo manual: El modelo ARIMA(3,1,1) presenta menores errores en todas las métricas evaluadas (MAE, RMSE y MAPE), lo que indica una mayor capacidad para aproximarse a los valores observados durante el periodo de validación.

  • Limitaciones del modelo automático: Aunque el modelo ARIMA(0,0,1) destaca por su parsimonia, su desempeño predictivo es inferior. En particular, su tendencia a converger rápidamente hacia la media reduce su capacidad para capturar la alta volatilidad presente en la serie.

  • Trade-off entre parsimonia y precisión: Mientras el modelo automático privilegia la simplicidad estructural y la estabilidad de largo plazo, el modelo manual incorpora una mayor complejidad que le permite adaptarse mejor a fluctuaciones de corto plazo, lo cual se refleja en menores errores de predicción.

Conclusión: Los resultados evidencian que no existe una coincidencia entre los modelos manual y automático. Por el contrario, cada uno responde a enfoques distintos de modelación: el modelo manual logra una mejor precisión predictiva a costa de mayor complejidad, mientras que el modelo automático ofrece una representación más simple pero menos ajustada a la dinámica observada. En este caso, el modelo manual resulta más adecuado para fines predictivos, dado su mejor desempeño empírico.

Hallazgos clave para la empresa

El análisis econométrico del mercado de licencias de construcción en Cali evidencia una dinámica caracterizada por alta volatilidad y baja persistencia temporal. A diferencia de lo sugerido por modelos más complejos, el enfoque automático identifica que la serie puede representarse de manera adecuada mediante un modelo parsimonioso ARIMA(0,0,1), lo que implica una ausencia de memoria estructural significativa en el comportamiento mensual del licenciamiento.

El principal hallazgo es la presencia de un comportamiento de reversión a la media, donde el volumen de metros cuadrados tiende a estabilizarse alrededor de un nivel promedio cercano a 85,800 m². Esto indica que, aunque se presentan fluctuaciones extremas en el corto plazo, estas no generan efectos persistentes, y el mercado retorna rápidamente a su nivel estructural.

Sin embargo, se identifica como limitación crítica la alta variabilidad de los datos, reflejada en errores predictivos elevados (MAE superior a 33,000 m² y MAPE superior al 100%). Esto implica que, si bien el modelo es útil para aproximaciones generales, presenta restricciones importantes para predicciones puntuales a nivel mensual.

En este contexto, el modelo no debe interpretarse como una herramienta de precisión operativa, sino como un instrumento para orientar decisiones estratégicas bajo incertidumbre.

Recomendaciones estratégicas basadas en datos

  • Planificación basada en el nivel promedio del mercado: Dado que el modelo converge hacia un nivel cercano a 85,800 m², la constructora puede utilizar este valor como referencia para dimensionar su capacidad operativa en el corto plazo. No obstante, este valor debe entenderse como un promedio esperado y no como una meta rígida, debido a la alta volatilidad del sector.

  • Gestión activa de la incertidumbre: El nivel de error identificado en la validación sugiere la necesidad de incorporar márgenes de flexibilidad en la planeación financiera. Se recomienda diseñar escenarios (optimista, base y pesimista) en lugar de depender de un único pronóstico puntual.

  • Evitar sobreinterpretar patrones de corto plazo: Dado que el modelo no encuentra evidencia de dependencia temporal fuerte, las variaciones mensuales deben interpretarse con cautela. Decisiones basadas en aumentos o caídas puntuales pueden llevar a errores estratégicos si no se consideran dentro del contexto de alta variabilidad del mercado.

  • Priorización de estrategias resilientes: La naturaleza predominantemente aleatoria del proceso sugiere que factores externos (regulatorios, administrativos o macroeconómicos) juegan un papel relevante. En este sentido, la empresa debería fortalecer su capacidad de adaptación mediante diversificación de proyectos, flexibilidad operativa y monitoreo continuo del entorno.

Conclusión:

El análisis econométrico del sector de la construcción en Cali revela que el comportamiento del licenciamiento presenta alta volatilidad y limitada capacidad predictiva en el corto plazo. Si bien los modelos ARIMA permiten capturar ciertos patrones generales, los resultados evidencian que gran parte de la dinámica está dominada por fluctuaciones aleatorias alrededor de un nivel promedio.

En particular, el modelo automático ARIMA(0,0,1) destaca por su parsimonia y por reflejar un proceso de reversión a la media sin dependencia temporal significativa. Aunque el modelo manual logra un mejor ajuste en términos de error, ambos enfoques coinciden en una limitación fundamental: la dificultad para anticipar con precisión los movimientos mensuales del mercado.

En este sentido, el valor del análisis no radica en la exactitud puntual de las predicciones, sino en su capacidad para proporcionar un marco estructurado de toma de decisiones bajo incertidumbre. Para Jaramillo Mora S.A., esto implica transitar hacia una estrategia basada en rangos, escenarios y gestión del riesgo, en lugar de depender de proyecciones determinísticas en un entorno altamente volátil.

Referencias:

DANE (2024) Boletín de Estadísticas de Concreto Premezclado (EC)

Fedesarrollo. (2023). Encuesta de Opinión del Consumidor. Resultados mayo de 2023. Bogotá: Fedesarrollo.

Garay-Rodríguez, Seydyss, Pavel Vidal, Alejandro, & Cerón-Ordóñez, Julieth. (2023). El monitoreo del sector de la construcción en el Valle del Cauca. Apuntes del Cenes, 42(75), 237-271. Epub November 07, 2023.https://doi.org/10.19053/01203053.v42.n75.2023.1a667