# ── Carga de librerías ────────────────────────────────────────────────────────
library(readxl) # Lectura de archivos Excel
library(tidyverse) # Manipulación y visualización de datos
library(lubridate) # Manejo de fechas
library(forecast) # Modelos ARIMA / SARIMA
library(tseries) # Pruebas de raíz unitaria (ADF)
library(zoo) # Objetos de series de tiempo irregulares
library(ggplot2) # Gráficos
library(gridExtra) # Composición de gráficos
library(scales) # Formato de ejes
library(knitr) # Tablas en HTML
library(kableExtra) # Tablas enriquecidas
library(seasonal) # Descomposición STL / X-13
library(FinTS) # Prueba ARCH
library(Metrics) # Métricas de error (RMSE, MAE)El sector agroindustrial del azúcar constituye uno de los pilares históricos de la economía del Valle del Cauca y del suroccidente colombiano. Colombia es el único país del mundo que cosecha caña de azúcar durante los doce meses del año, gracias a las condiciones geoclimáticas únicas del valle geográfico del río Cauca, ubicado entre 900 y 1.100 metros sobre el nivel del mar, con temperatura promedio de 24 °C, alta luminosidad solar y disponibilidad hídrica de los ríos que descienden de las cordilleras Occidental y Central.
La cadena productiva se extiende desde el cultivo de la caña (Saccharum officinarum), pasando por la molienda en los ingenios, hasta la producción de azúcar crudo, azúcar refinada, mieles, alcohol carburante y cogeneración de energía eléctrica. Existen actualmente trece ingenios en la región, siendo los más representativos Ingenio del Cauca (Incauca), Manuelita, Providencia, Mayagüez y Risaralda, entre otros.
Para efectos de este análisis, se toma como empresa de referencia a Ingenio Providencia S.A., uno de los conglomerados azucareros más grandes de Colombia, con presencia en producción de azúcar, alcohol carburante, energía y derivados alimenticios. La empresa es representativa del sector dado que sus resultados reflejan fielmente las dinámicas macroeconómicas, productivas y de comercio exterior que afectan al conjunto de la industria azucarera vallecaucana.
El ingenio procesa anualmente más de dos millones de toneladas de caña, produce azúcar blanca y cruda, y participa activamente en el mercado de exportaciones agroindustriales del departamento. Su gestión estratégica debe responder tanto a ciclos productivos estacionales propios del cultivo, como a la volatilidad de los mercados internacionales y las condiciones macroeconómicas domésticas.
Las tres variables seleccionadas capturan dimensiones fundamentales e interconectadas del ciclo agroindustrial: la capacidad productiva, la transformación industrial y la inserción en los mercados externos.
vars_df <- data.frame(
`N°` = 1:3,
Variable = c("Molienda de Caña de Azúcar",
"Exportaciones Totales del Valle del Cauca",
"Producción de Azúcar"),
Acrónimo = c("CAN", "X_V", "AZUCAR"),
Unidad = c("Toneladas", "Dólares CIF", "Toneladas"),
Geografía = c("Colombia", "Valle del Cauca", "Colombia"),
Fuente = c("DANE", "DANE", "ASOCAÑA / DANE"),
Justificación = c(
"Es el insumo primario y el primer eslabón de la cadena. Su nivel refleja la disponibilidad de materia prima, las decisiones de cosecha, el estado fitosanitario del cultivo y las condiciones climáticas. Una mayor molienda es condición necesaria —aunque no suficiente— para una mayor producción de azúcar.",
"Captura la competitividad del departamento en el mercado mundial. El Valle del Cauca exporta principalmente azúcares, preparaciones alimenticias, productos químicos y papel. La variable refleja el acceso a divisas, el impacto de la tasa de cambio y la demanda internacional de los productos agroindustriales.",
"Es el output final del proceso industrial y el principal producto generador de ingresos para los ingenios. Su nivel determina directamente los ingresos operativos y la rentabilidad empresarial. Su comportamiento respecto a la molienda revela la eficiencia extractiva (rendimiento caña-azúcar)."
),
check.names = FALSE
)
kable(vars_df[, 1:6], caption = "Tabla 1. Catálogo de Variables Seleccionadas",
align = c("c","l","c","l","c","l")) %>%
kable_styling(bootstrap_options = c("striped", "hover", "condensed"),
full_width = TRUE, font_size = 12) %>%
column_spec(1, bold = TRUE, width = "3em") %>%
column_spec(2, bold = TRUE, color = "#1a3c5e") %>%
column_spec(3, monospace = TRUE, color = "#e67e22")| N° | Variable | Acrónimo | Unidad | Geografía | Fuente |
|---|---|---|---|---|---|
| 1 | Molienda de Caña de Azúcar | CAN | Toneladas | Colombia | DANE |
| 2 | Exportaciones Totales del Valle del Cauca | X_V | Dólares CIF | Valle del Cauca | DANE |
| 3 | Producción de Azúcar | AZUCAR | Toneladas | Colombia | ASOCAÑA / DANE |
# ── Lectura del archivo Excel ─────────────────────────────────────────────────
df_raw <- read_excel("Base Caso2 (2).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"))
# ── Selección de variables de interés ────────────────────────────────────────
df <- df_raw %>%
select(FECHA, CAN, AZUCAR, X_V) %>%
mutate(FECHA = as.Date(FECHA)) %>%
arrange(FECHA)
# ── Vista preliminar ──────────────────────────────────────────────────────────
kable(head(df, 12),
caption = "Tabla 2. Primeras 12 observaciones — Sector Agroindustrial del Azúcar en el Valle del Cauca",
col.names = c("Fecha", "Molienda de Caña de azúcar (Toneladas)", "Producción de azúcar (Toneladas)", "Exportaciones totales del Valle (Dólares CIF)"),
format.args = list(big.mark = ",", digits = 2),
align = "c") %>%
kable_styling(bootstrap_options = c("striped", "hover", "condensed"),
full_width = FALSE, font_size = 12)| Fecha | Molienda de Caña de azúcar (Toneladas) | Producción de azúcar (Toneladas) | Exportaciones totales del Valle (Dólares CIF) |
|---|---|---|---|
| 2012-01-01 | 1,685,584 | 148,483 | 1.6e+08 |
| 2012-02-01 | 1,973,654 | 192,196 | 1.8e+08 |
| 2012-03-01 | 2,083,470 | 202,408 | 1.9e+08 |
| 2012-04-01 | 1,406,868 | 134,532 | 1.6e+08 |
| 2012-05-01 | 1,233,631 | 107,659 | 2.0e+08 |
| 2012-06-01 | 1,997,318 | 206,134 | 1.7e+08 |
| 2012-07-01 | 2,107,652 | 222,915 | 1.9e+08 |
| 2012-08-01 | 2,075,408 | 226,868 | 2.0e+08 |
| 2012-09-01 | 2,005,429 | 225,330 | 1.9e+08 |
| 2012-10-01 | 1,700,440 | 169,420 | 2.2e+08 |
| 2012-11-01 | 1,144,776 | 103,433 | 1.8e+08 |
| 2012-12-01 | 1,409,399 | 138,275 | 1.9e+08 |
# ── Tabla de estadísticas descriptivas ───────────────────────────────────────
desc_stats <- df %>%
select(-FECHA) %>%
summarise(across(everything(), list(
N = ~n(),
Media = ~mean(., na.rm = TRUE),
Mediana = ~median(., na.rm = TRUE),
Des.Est. = ~sd(., na.rm = TRUE),
Mínimo = ~min(., na.rm = TRUE),
Máximo = ~max(., na.rm = TRUE),
CV = ~(sd(., na.rm = TRUE)/mean(., na.rm = TRUE))*100
))) %>%
pivot_longer(everything(),
names_to = c("Variable", "Estadístico"),
names_sep = "_(?=[^_]+$)") %>%
pivot_wider(names_from = "Estadístico", values_from = "value")
kable(desc_stats,
caption = "Tabla 3. Estadísticas Descriptivas (2012–2025)",
digits = 2,
format.args = list(big.mark = ","),
align = "c") %>%
kable_styling(bootstrap_options = c("striped","hover","condensed"),
full_width = FALSE, font_size = 12)| Variable | N | Media | Mediana | Des.Est. | Mínimo | Máximo | CV |
|---|---|---|---|---|---|---|---|
| CAN | 168 | 1,920,333 | 1,997,577.7 | 396,510.60 | 138,148.00 | 2,535,470.3 | 20.65 |
| AZUCAR | 168 | 179,856 | 183,641.6 | 42,611.88 | 11,041.41 | 263,433.5 | 23.69 |
| X_V | 168 | 157,545,398 | 157,166,984.6 | 24,349,256.05 | 49,767,731.10 | 222,055,829.3 | 15.46 |
library(dplyr)
library(tidyr)
library(ggplot2)
library(scales)
# Definir paleta de colores
PAL <- c(
"Molienda de Caña (ton)" = "#1b9e77",
"Exportaciones Valle (USD CIF)" = "#d95f02",
"Producción de Azúcar (ton)" = "#7570b3"
)
# Tema (por si no lo tienes)
theme_sector <- function() {
theme_minimal()
}
# Convertir a formato largo
datos_long <- df %>%
pivot_longer(cols = c(CAN, X_V, AZUCAR),
names_to = "Variable", values_to = "Valor") %>%
mutate(Variable = recode(Variable,
CAN = "Molienda de Caña (ton)",
X_V = "Exportaciones Valle (USD CIF)",
AZUCAR = "Producción de Azúcar (ton)"))
# Gráfico
ggplot(datos_long, aes(x = FECHA, y = Valor, colour = Variable)) +
geom_line(linewidth = 0.8, alpha = 0.9) +
geom_smooth(method = "loess", se = FALSE, linewidth = 0.4,
linetype = "dashed", alpha = 0.5) +
facet_wrap(~Variable, scales = "free_y", ncol = 1) +
scale_colour_manual(values = PAL) +
scale_x_date(date_breaks = "1 year", date_labels = "%Y") +
scale_y_continuous(labels = label_comma()) +
labs(
title = "Figura 1. Evolución Histórica de las Variables del Sector Azucarero",
subtitle = "Series mensuales 2012–2025 | Línea discontinua: tendencia LOESS",
x = NULL, y = NULL,
caption = "Fuente: DANE – ASOCAÑA | Elaboración propia"
) +
theme_sector() +
theme(legend.position = "none",
axis.text.x = element_text(angle = 45, hjust = 1))df_CAN <- as.data.frame(window(ts_CAN, start = c(2012, 1)))
df_CAN$Index <- time(window(ts_CAN, start = c(2012, 1)))
autoplot(window(ts_CAN, start = c(2012, 1)), colour = "#4E6D4E") +
geom_smooth(data = df_CAN,
aes(x = Index, y = df_CAN[,1]),
method = "loess",
colour = "#2C3E2D",
linetype = "dashed",
se = FALSE,
linewidth = 0.8) +
labs(
title = "Molienda de Caña de azúcar",
subtitle = "Toneladas — Ene 2012 a Dic 2025",
x = "Período",
y = "Toneladas",
caption = "Fuente: DANE"
) +
scale_y_continuous(labels = comma) +
theme_minimal(base_size = 13) +
theme(plot.title = element_text(face = "bold"))Figura 1. Molienda de Caña de azúcar (2012–2025)
La producción de café en Colombia exhibe una dinámica marcadamente cíclica con patrones estacionales pronunciados asociados a las dos cosechas anuales. Durante el período analizado se observa una tendencia positiva moderada desde 2012 hasta aproximadamente 2017–2018, período en el que la producción alcanzó niveles cercanos a los 1.700–1.800 miles de sacos mensuales, impulsada por los programas de renovación de cafetales de la Federación Nacional de Cafeteros. Posteriormente, se registra una fase de corrección y mayor volatilidad, con impactos derivados del fenómeno de La Niña (exceso de lluvias) y las perturbaciones de la pandemia de COVID-19 en 2020–2021. Hacia el período 2022–2025, la producción muestra señales de recuperación, aunque con oscilaciones más amplias que en el período inicial.
library(ggplot2)
library(scales)
# Convertir serie a dataframe correctamente
ts_data <- ts_AZUCAR / 1e6
df_azucar <- data.frame(
Index = time(ts_data),
Valor = as.numeric(ts_data)
)
# Gráfico
autoplot(ts_data, colour = "#8B4513") +
geom_smooth(data = df_azucar,
aes(x = Index, y = Valor),
method = "loess",
colour = "#5C2D0A",
linetype = "dashed",
se = FALSE,
linewidth = 0.8) +
labs(
title = "Producción de azúcar",
subtitle = "Toneladas — Ene 2012 a Dic 2025",
x = "Período",
y = "Millones de toneladas",
caption = "Fuente: DANE"
) +
scale_y_continuous(labels = comma) +
theme_minimal(base_size = 13) +
theme(plot.title = element_text(face = "bold"))Figura 2. Producción de azúcar (2012–2025)
El precio interno del café colombiano presenta una tendencia alcista sostenida y pronunciada a lo largo del período analizado. Partiendo de niveles cercanos a los 400–900 mil pesos por carga en 2012–2015, el precio experimenta incrementos significativos hacia 2021–2022, alcanzando máximos históricos superiores a los 3 millones de pesos por carga en el período 2024–2025. Este comportamiento refleja la confluencia de múltiples factores: la depreciación acumulada del peso colombiano frente al dólar (la TRM pasó de ~1.800 a más de 4.000 pesos por dólar), el aumento del precio internacional del café en la bolsa de Nueva York (afectado por problemas de oferta en Brasil y Vietnam), y la inflación doméstica que incrementa los costos de producción internos. El precio interno tiene un componente estacional moderado, pero su volatilidad está dominada por el componente irregular y la tendencia de largo plazo.
autoplot(ts_X_V / 1e3, colour = "#D4A017") +
geom_smooth(method = "loess",
colour = "#8B6914",
linetype = "dashed",
se = FALSE,
linewidth = 0.8) +
labs(
title = "Exportaciones totales del Valle",
subtitle = "Dólares CIF — Ene 2012 a Dic 2025",
x = "Período",
y = "Dólares CIF (miles)",
caption = "Fuente: DANE — Federación Nacional de Cafeteros de Colombia"
) +
scale_y_continuous(labels = comma) +
theme_minimal(base_size = 13) +
theme(plot.title = element_text(face = "bold"))Figura 3. Exportaciones mensuales de café colombiano (2012–2025)
Las exportaciones de café muestran un patrón de crecimiento notable a lo largo de los 14 años analizados, pasando de un promedio mensual de aproximadamente 115–200 millones de dólares en 2012 a niveles que superan los 400–590 millones de dólares en el período 2024–2025. Esta trayectoria ascendente responde tanto al aumento del precio internacional como al posicionamiento diferenciado del café colombiano en mercados de especialidad. Se identifican fuertes oscilaciones mensuales vinculadas al ciclo productivo y a los patrones de demanda de los mercados de destino (mayor demanda en los meses fríos del hemisferio norte). El año 2020 muestra una contracción temporal asociada al cierre de puertos y las disrupciones logísticas de la pandemia de COVID-19.
En esta sección se aplica la descomposición STL (Seasonal and Trend decomposition using Loess), método robusto que permite extraer de manera independiente los componentes de tendencia, estacionalidad y componente irregular (residuo) de cada serie de tiempo.
library(plotly)
stl_AZUCAR <- stl(ts_AZUCAR, s.window = "periodic", robust = TRUE)
df_stl_azucar <- data.frame(
fecha = as.Date(time(ts_AZUCAR)),
original = as.numeric(ts_AZUCAR),
estacional = as.numeric(stl_AZUCAR$time.series[, "seasonal"]),
tendencia = as.numeric(stl_AZUCAR$time.series[, "trend"]),
residuo = as.numeric(stl_AZUCAR$time.series[, "remainder"])
)
colores <- c(
original = "#8B4513",
tendencia = "#5C2D0A",
estacional = "#D2956A",
residuo = "#C0392B"
)
# ── Paneles SIN layout individual ─────────────────────────
p1 <- plot_ly(df_stl_azucar, x = ~fecha, y = ~original,
type = "scatter", mode = "lines",
name = "Original",
line = list(color = colores["original"], width = 1.5))
p2 <- plot_ly(df_stl_azucar, x = ~fecha, y = ~estacional,
type = "scatter", mode = "lines",
name = "Estacionalidad",
line = list(color = colores["estacional"], width = 1.5))
p3 <- plot_ly(df_stl_azucar, x = ~fecha, y = ~tendencia,
type = "scatter", mode = "lines",
name = "Tendencia",
line = list(color = colores["tendencia"], width = 2))
p4 <- plot_ly(df_stl_azucar, x = ~fecha, y = ~residuo,
type = "bar", name = "Residuo",
marker = list(
color = ifelse(df_stl_azucar$residuo >= 0,
colores["tendencia"],
colores["residuo"]),
opacity = 0.7
))
# ── Subplot con títulos de ejes en layout global ───────────
subplot(p1, p2, p3, p4,
nrows = 4, shareX = TRUE, titleY = TRUE,
heights = c(0.30, 0.25, 0.25, 0.20)) %>%
layout(
title = list(
text = "<b>Descomposición STL: Producción de Azúcar</b><br>
<sup>Fuente: Federación Nacional de Cafeteros / Elaboración propia</sup>",
font = list(size = 15)
),
yaxis = list(title = "Original"),
yaxis2 = list(title = "Estacional"),
yaxis3 = list(title = "Tendencia"),
yaxis4 = list(title = "Residuo"),
hovermode = "x unified",
paper_bgcolor = "#FAFAFA",
plot_bgcolor = "#FAFAFA",
legend = list(orientation = "h", x = 0.3, y = -0.05)
)Figura 4. Descomposición STL — Producción Nacional de AZUCAR (CAN)
Tendencia: El componente tendencial de la producción de café muestra una dinámica en tres fases claramente diferenciadas. La primera fase (2012–2019) refleja una tendencia creciente moderada, que pasó de aproximadamente 850 miles de sacos mensuales a niveles superiores a los 1.100–1.200 miles. Esta expansión fue impulsada por los programas de renovación de cafetales ejecutados por la Federación Nacional de Cafeteros desde 2008 y que alcanzaron su madurez productiva entre 2015 y 2019. La segunda fase (2019–2021) evidencia una corrección a la baja atribuible a la combinación del fenómeno climático de La Niña, que generó exceso de precipitaciones en las zonas cafeteras, y las restricciones logísticas derivadas de la pandemia de COVID-19. La tercera fase (2022–2025) muestra una tenue recuperación hacia los 1.100–1.150 miles de sacos, aunque sin alcanzar los picos históricos observados en el período previo.
Estacionalidad: La producción de café en Colombia exhibe un patrón estacional bien definido y estadísticamente significativo, lo que confirma la existencia de dos cosechas anuales: la cosecha principal (octubre–febrero), que concentra aproximadamente el 60–65% de la producción anual, y la cosecha mitaca (abril–julio), de menor magnitud. Los meses de diciembre y enero registran consistentemente los valores más altos del componente estacional, mientras que agosto y septiembre corresponden al período de transición de menor producción entre las dos cosechas. Este patrón es biológicamente determinado por el ciclo de floración y desarrollo del grano del cafeto, siendo relativamente estable en el tiempo.
Componente Irregular: El residuo presenta una varianza moderada a lo largo del período, con picos de alta volatilidad en 2020 (pandemia) y en los años de fenómenos climáticos extremos (2015–2016 con El Niño). Estos shocks irregulares son difícilmente predecibles mediante modelos estadísticos y representan el principal factor de incertidumbre para la planificación de la empresa.
library(plotly)
library(dplyr)
stl_AZUCAR <- stl(ts_AZUCAR, s.window = "periodic", robust = TRUE)
# Extraer componentes
df_stl_azucar <- data.frame(
fecha = as.Date(time(ts_AZUCAR)),
original = as.numeric(ts_AZUCAR),
estacional = as.numeric(stl_AZUCAR$time.series[, "seasonal"]),
tendencia = as.numeric(stl_AZUCAR$time.series[, "trend"]),
residuo = as.numeric(stl_AZUCAR$time.series[, "remainder"])
)
# Paleta de colores
colores <- c(
original = "#8B4513",
tendencia = "#5C2D0A",
estacional = "#D2956A",
residuo = "#C0392B"
)
# ── Paneles ────────────────────────────────────────────────
p1 <- plot_ly(df_stl_azucar, x = ~fecha, y = ~original, type = "scatter",
mode = "lines", name = "Original",
line = list(color = colores["original"], width = 1.5)) %>%
layout(yaxis = list(title = "Original"))
p2 <- plot_ly(df_stl_azucar, x = ~fecha, y = ~estacional, type = "scatter",
mode = "lines", name = "Estacionalidad",
line = list(color = colores["estacional"], width = 1.5)) %>%
layout(yaxis = list(title = "Estacional"))
p3 <- plot_ly(df_stl_azucar, x = ~fecha, y = ~tendencia, type = "scatter",
mode = "lines", name = "Tendencia",
line = list(color = colores["tendencia"], width = 2)) %>%
layout(yaxis = list(title = "Tendencia"))
p4 <- plot_ly(df_stl_azucar, x = ~fecha, y = ~residuo, type = "bar",
name = "Residuo",
marker = list(color = ifelse(df_stl_azucar$residuo >= 0,
colores["tendencia"],
colores["residuo"]),
opacity = 0.7)) %>%
layout(yaxis = list(title = "Residuo"))
# ── Unir paneles ───────────────────────────────────────────
subplot(p1, p2, p3, p4,
nrows = 4, shareX = TRUE, titleY = TRUE,
heights = c(0.30, 0.25, 0.25, 0.20)) %>%
layout(
title = list(
text = "<b>Descomposición STL: Producción de Azúcar</b><br>
<sup>Fuente: DANE / Elaboración propia</sup>",
font = list(size = 15)
),
hovermode = "x unified",
paper_bgcolor = "#FAFAFA",
plot_bgcolor = "#FAFAFA",
legend = list(orientation = "h", x = 0.3, y = -0.05)
)Figura 5. Descomposición STL — Precio Interno Base de Compra del Café (AZUCAR)
Tendencia: El componente de tendencia del precio interno del café es el más dramático de las tres variables analizadas. La tendencia exhibe un crecimiento prácticamente continuo desde el inicio del período, con una fase de aceleración marcada a partir de 2020–2021. Durante 2012–2019, la tendencia fue relativamente plana con oscilaciones moderadas, reflejando un período de precios internacionales relativamente contenidos y una tasa de cambio que, si bien estaba depreciándose, lo hacía de forma gradual. A partir de 2020, la aceleración del proceso de depreciación del peso colombiano (impulsada por la caída del precio del petróleo, la pandemia, y los choques fiscales del país) combinada con el aumento del precio internacional del café generó una espiral alcista que llevó el precio interno a niveles de 2,5–3,1 millones de pesos por carga al cierre de 2025.
Estacionalidad: A diferencia de la producción, el precio interno del café exhibe un componente estacional de menor magnitud relativa, aunque estadísticamente presente. El patrón estacional refleja la dinámica de oferta y demanda: en los meses de cosecha alta (noviembre–enero), la mayor oferta de café pergamino tiende a moderar los precios de compra; mientras que en los meses de menor producción (agosto–septiembre), la escasez relativa eleva el precio. Sin embargo, este efecto estacional es ampliamente dominado por la volatilidad de la tendencia y el componente irregular, lo que significa que el ciclo productivo tiene un efecto secundario sobre el precio doméstico.
Componente Irregular: El residuo del precio interno presenta oscilaciones de alta frecuencia e intensidad, especialmente a partir de 2021–2022. Esto refleja la sensibilidad de esta variable a noticias externas (variaciones en la bolsa de Nueva York, clima en Brasil, tensiones geopolíticas) que se transmiten al precio interno con una frecuencia prácticamente diaria, pero que en la frecuencia mensual se manifiestan como shocks irregulares de amplitud creciente.
library(plotly)
library(dplyr)
stl_X_V <- stl(ts_X_V, s.window = "periodic", robust = TRUE)
# Extraer componentes
df_stl_xv <- data.frame(
fecha = as.Date(time(ts_X_V)),
original = as.numeric(ts_X_V),
estacional = as.numeric(stl_X_V$time.series[, "seasonal"]),
tendencia = as.numeric(stl_X_V$time.series[, "trend"]),
residuo = as.numeric(stl_X_V$time.series[, "remainder"])
)
# Paleta de colores — dorados/Exportaciones totales del Valle
colores <- c(
original = "#D4A017",
tendencia = "#8B6914",
estacional = "#F0C84A",
residuo = "#C0392B"
)
# ── Paneles ────────────────────────────────────────────────
p1 <- plot_ly(df_stl_xv, x = ~fecha, y = ~original, type = "scatter",
mode = "lines", name = "Original",
line = list(color = colores["original"], width = 1.5)) %>%
layout(yaxis = list(title = "Original"))
p2 <- plot_ly(df_stl_xv, x = ~fecha, y = ~estacional, type = "scatter",
mode = "lines", name = "Estacionalidad",
line = list(color = colores["estacional"], width = 1.5)) %>%
layout(yaxis = list(title = "Estacional"))
p3 <- plot_ly(df_stl_xv, x = ~fecha, y = ~tendencia, type = "scatter",
mode = "lines", name = "Tendencia",
line = list(color = colores["tendencia"], width = 2)) %>%
layout(yaxis = list(title = "Tendencia"))
p4 <- plot_ly(df_stl_xv, x = ~fecha, y = ~residuo, type = "bar",
name = "Residuo",
marker = list(color = ifelse(df_stl_xv$residuo >= 0,
colores["tendencia"],
colores["residuo"]),
opacity = 0.7)) %>%
layout(yaxis = list(title = "Residuo"))
# ── Unir paneles ───────────────────────────────────────────
subplot(p1, p2, p3, p4,
nrows = 4, shareX = TRUE, titleY = TRUE,
heights = c(0.30, 0.25, 0.25, 0.20)) %>%
layout(
title = list(
text = "<b>Descomposición STL: Exportaciones totales del Valle </b><br>
<sup>Fuente: DANE — / Elaboración propia</sup>",
font = list(size = 15)
),
hovermode = "x unified",
paper_bgcolor = "#FAFAFA",
plot_bgcolor = "#FAFAFA",
legend = list(orientation = "h", x = 0.3, y = -0.05)
)Figura 6. Descomposición STL — Exportaciones totales del Valle (X_V)
Tendencia: Las exportaciones de café exhiben una tendencia ascendente robusta y sostenida a lo largo de todo el período analizado. Esta tendencia es el resultado de la interacción entre tres fuerzas: el incremento del precio internacional del café (que eleva el valor en dólares por unidad exportada), la recuperación gradual de la producción nacional (que amplía el volumen disponible para exportar), y la depreciación del peso colombiano (que hace más rentable exportar en relación con vender en el mercado doméstico). Los niveles de exportación pasaron de un promedio mensual de aproximadamente 150–200 millones de dólares en 2012 a un promedio superior a los 400 millones de dólares en el período 2023–2025, lo que representa un crecimiento acumulado del orden del 150–200% en términos nominales de dólares.
Estacionalidad: Las exportaciones presentan un patrón estacional moderado pero visible, que se alinea parcialmente con el ciclo de cosecha. Los meses de diciembre, enero y marzo tienden a registrar picos exportadores, en correspondencia con la disponibilidad de café proveniente de la cosecha principal. Los meses de agosto y septiembre, en contraste, muestran valores más bajos del componente estacional, coincidiendo con el período de menor disponibilidad de grano. Este patrón es importante para CafExport desde el punto de vista de la logística de exportación y la negociación de contratos con clientes internacionales.
Componente Irregular: El componente residual de las exportaciones es marcadamente más volátil que el de la producción, lo que refleja la influencia de factores externos (disrupciones logísticas portuarias, variaciones en la demanda de los mercados de destino, negociaciones puntuales de grandes contratos) que afectan los valores de exportación en períodos específicos. El período 2020 muestra el mayor shock negativo del período (pandemia), seguido de una rápida recuperación en 2021.
library(reactable)
library(htmltools)
library(dplyr)
df_anual <- df %>%
mutate(anio = as.integer(format(as.Date(FECHA), "%Y"))) %>%
group_by(anio) %>%
summarise(
CAN_anual = sum(CAN, na.rm = TRUE),
AZUCAR_anual = mean(AZUCAR, na.rm = TRUE),
X_V_anual = sum(X_V, na.rm = TRUE)
) %>%
mutate(
TCA_CAN = (CAN_anual / lag(CAN_anual) - 1) * 100,
TCA_AZUCAR = (AZUCAR_anual / lag(AZUCAR_anual) - 1) * 100,
TCA_X_V = (X_V_anual / lag(X_V_anual) - 1) * 100
)
# ── Función para colorear TCA ──────────────────────────────
tca_cell <- function(value) {
if (is.na(value)) return(span("—", style = "color: gray;"))
color <- if (value > 0) "#1a7a1a" else "#cc0000"
flecha <- if (value > 0) "▲" else "▼"
span(
style = paste0("color:", color, "; font-weight:bold;"),
paste0(flecha, " ", formatC(value, format = "f", digits = 2), "%")
)
}
# ── Función para barras de fondo ───────────────────────────
bar_cell <- function(value, max_val, color) {
pct <- round(value / max_val * 100)
div(
style = paste0(
"background: linear-gradient(90deg, ", color, "33 ", pct,
"%, transparent ", pct, "%);",
"padding: 4px 8px; border-radius: 4px;"
),
formatC(value, format = "f", digits = 0, big.mark = ",")
)
}
max_CAN <- max(df_anual$CAN_anual, na.rm = TRUE)
max_AZUCAR <- max(df_anual$AZUCAR_anual, na.rm = TRUE)
max_XV <- max(df_anual$X_V_anual, na.rm = TRUE)
# ── Tabla reactable ────────────────────────────────────────
tabla <- reactable(
df_anual,
striped = TRUE,
highlight = TRUE,
bordered = TRUE,
compact = FALSE,
defaultPageSize = 20,
style = list(fontFamily = "Arial, sans-serif", fontSize = "13px"),
columns = list(
anio = colDef(
name = "Año",
align = "center",
width = 60,
style = list(fontWeight = "bold", color = "#333")
),
CAN_anual = colDef(
name = "Molienda de Caña (ton)",
align = "center",
cell = function(value) bar_cell(value, max_CAN, "#4E6D4E")
),
AZUCAR_anual = colDef(
name = "Producción Azúcar (ton)",
align = "center",
cell = function(value) bar_cell(value, max_AZUCAR, "#8B4513")
),
X_V_anual = colDef(
name = "Exportaciones Valle (USD)",
align = "center",
cell = function(value) bar_cell(value, max_XV, "#D4A017")
),
TCA_CAN = colDef(
name = "TCA Caña (%)",
align = "center",
cell = function(value) tca_cell(value)
),
TCA_AZUCAR = colDef(
name = "TCA Azúcar (%)",
align = "center",
cell = function(value) tca_cell(value)
),
TCA_X_V = colDef(
name = "TCA Exportaciones (%)",
align = "center",
cell = function(value) tca_cell(value)
)
)
)
# ── Título y tabla final ───────────────────────────────────
htmltools::browsable(
tagList(
tags$h3(
style = "font-family:Arial; text-align:center; color:#2C3E2D; margin-bottom:4px;",
"Tabla 3. Tasa de Crecimiento Anual (%) — Caña, Azúcar y Exportaciones"
),
tags$p(
style = "font-family:Arial; text-align:center; font-size:12px; color:gray;",
"Fuente: DANE — Federación Nacional de Cafeteros / Elaboración propia"
),
tabla
)
)Fuente: DANE — Federación Nacional de Cafeteros / Elaboración propia
df_anual_long <- df_anual %>%
filter(!is.na(TCA_CAN)) %>%
select(anio, TCA_CAN, TCA_AZUCAR, TCA_X_V) %>%
pivot_longer(-anio, names_to = "Variable", values_to = "TCA") %>%
mutate(Variable = recode(Variable,
TCA_CAN = "Producción (CAN)",
TCA_AZUCAR = "Precio Interno (AZUCAR)",
TCA_X_V = "Exportaciones (X_V)"
))
ggplot(df_anual_long, aes(x = factor(anio), y = TCA, fill = TCA > 0)) +
geom_col(show.legend = FALSE) +
facet_wrap(~Variable, scales = "free_y", ncol = 1) +
geom_hline(yintercept = 0, colour = "black", linewidth = 0.5) +
scale_fill_manual(values = c("TRUE" = "#27AE60", "FALSE" = "#C0392B")) +
labs(
title = "Tasa de Crecimiento Anual por Variable Cafetera",
subtitle = "Verde = crecimiento positivo | Rojo = contracción",
x = "Año",
y = "TCA (%)",
caption = "Fuente: Federación Nacional de Cafeteros / DANE — Elaboración propia"
) +
theme_minimal(base_size = 12) +
theme(
strip.text = element_text(face = "bold", size = 11),
plot.title = element_text(face = "bold"),
axis.text.x = element_text(angle = 45, hjust = 1)
)Figura 7. Tasa de Crecimiento Anual (%) — Variables Cafeteras
cor_matrix <- df %>%
select(CAN, AZUCAR, X_V) %>%
cor(use = "complete.obs")
kable(round(cor_matrix, 4),
caption = "Tabla 4. Matriz de Correlación de Pearson — Variables Cafeteras",
align = "c") %>%
kable_styling(bootstrap_options = c("striped","hover"),
full_width = FALSE, font_size = 13)| CAN | AZUCAR | X_V | |
|---|---|---|---|
| CAN | 1.0000 | 0.9606 | 0.0862 |
| AZUCAR | 0.9606 | 1.0000 | 0.1810 |
| X_V | 0.0862 | 0.1810 | 1.0000 |
p1 <- ggplot(df, aes(x = CAN, y = X_V/1000)) +
geom_point(alpha = 0.5, colour = "#27AE60") +
geom_smooth(method = "lm", colour = "#1A5C30") +
labs(title = "Producción vs Exportaciones", x = "Miles de sacos", y = "Millones USD") +
theme_minimal()
p2 <- ggplot(df, aes(x = AZUCAR/1e6, y = X_V/1000)) +
geom_point(alpha = 0.5, colour = "#8B4513") +
geom_smooth(method = "lm", colour = "#4A1E05") +
labs(title = "Precio vs Exportaciones", x = "Millones COP/carga", y = "Millones USD") +
theme_minimal()
p3 <- ggplot(df, aes(x = CAN, y = AZUCAR/1e6)) +
geom_point(alpha = 0.5, colour = "#D4A017") +
geom_smooth(method = "lm", colour = "#7A5700") +
labs(title = "Producción vs Precio", x = "Miles de sacos", y = "Millones COP/carga") +
theme_minimal()
grid.arrange(p1, p2, p3, ncol = 2)Figura 8. Relación entre variables cafeteras — Diagramas de dispersión
# ── Extracción de componentes ajustados por estacionalidad ─
# CAÑA
st_CAN <- stl(ts_CAN, s.window = "periodic")
# AZÚCAR
stl_AZUCAR <- stl(ts_AZUCAR, s.window = "periodic")
# EXPORTACIONES
stl_X_V <- stl(ts_X_V, s.window = "periodic")
# ── Extracción de series ajustadas ─────────────────────────
ts_CAN_ajustada <- ts_CAN - st_CAN$time.series[, "seasonal"]
ts_AZUCAR_ajustada <- ts_AZUCAR - stl_AZUCAR$time.series[, "seasonal"]
ts_X_V_ajustada <- ts_X_V - stl_X_V$time.series[, "seasonal"]
# ── Verificación ───────────────────────────────────────────
cat("✅ Series ajustadas por estacionalidad creadas:\n")## ✅ Series ajustadas por estacionalidad creadas:
## • ts_CAN_ajustada : 168 observaciones
## • ts_AZUCAR_ajustada : 168 observaciones
## • ts_X_V_ajustada : 168 observaciones
# ── Gráfico comparativo: Original vs Ajustada ──────────────
library(plotly)
comparar_ajuste <- function(ts_orig, ts_ajust, nombre, color_orig, color_ajust) {
df <- data.frame(
fecha = as.Date(time(ts_orig)),
original = as.numeric(ts_orig),
ajustada = as.numeric(ts_ajust)
)
plot_ly(df, x = ~fecha) %>%
add_lines(y = ~original,
name = "Original",
line = list(color = color_orig, width = 1.2, dash = "dot")) %>%
add_lines(y = ~ajustada,
name = "Ajustada estacionalmente",
line = list(color = color_ajust, width = 2)) %>%
layout(
title = list(
text = paste0("<b>", nombre, "</b><br>",
"<sup>Serie original vs ajustada por estacionalidad</sup>"),
font = list(size = 14)
),
xaxis = list(title = "Período"),
yaxis = list(title = "Valor"),
hovermode = "x unified",
paper_bgcolor = "#FAFAFA",
plot_bgcolor = "#FAFAFA",
legend = list(orientation = "h", x = 0.3, y = -0.15)
)
}
# ── Graficar cada serie ────────────────────────────────────CAÑA DE AZÚCAR: Grafico serie original Vs serie ajustada.
PRODUCCIÓN DE AZÚCAR: Grafico serie original Vs serie ajustada.
EXPORTACIONES VALLE: Grafico serie original Vs serie ajustada.
# ── Gráfico: Original vs Tendencia STL ────────────────────
library(plotly)
comparar_tendencia <- function(ts_orig, stl_obj, nombre, color_orig, color_tend) {
df <- data.frame(
fecha = as.Date(time(ts_orig)),
original = as.numeric(ts_orig),
tendencia = as.numeric(stl_obj$time.series[, "trend"])
)
plot_ly(df, x = ~fecha) %>%
add_lines(y = ~original,
name = "Original",
line = list(color = color_orig, width = 1.2, dash = "dot"),
opacity = 0.7) %>%
add_lines(y = ~tendencia,
name = "Tendencia STL",
line = list(color = color_tend, width = 2.5)) %>%
layout(
title = list(
text = paste0("<b>", nombre, "</b><br>",
"<sup>Serie original vs tendencia STL</sup>"),
font = list(size = 14)
),
xaxis = list(title = "Período"),
yaxis = list(title = "Valor"),
hovermode = "x unified",
paper_bgcolor = "#FAFAFA",
plot_bgcolor = "#FAFAFA",
legend = list(orientation = "h", x = 0.3, y = -0.15)
)
}
# ── Graficar cada serie ────────────────────────────────────
comparar_tendencia(ts_CAN, st_CAN,
"Molienda de Caña de Azúcar",
"#82A882", "#2C3E2D")##Tasa de crecimiento de la serie origilan vs tendencia
#Cálculo de la tasa de crecimiento anual correctamente alineada
tasa_crecimiento_CAN <- (ts_CAN[(13:length(ts_CAN))] / ts_CAN[1:(length(ts_CAN) - 12)] - 1) * 100
tasa_tendencia_CAN <- (ts_CAN[(13:length(ts_CAN))] / ts_CAN[1:(length(ts_CAN) - 12)] - 1) * 100
# Crear vector de fechas corregido, es decir que inicie desde enero 2013
fechas_corregidas_CAN <- seq(from = as.Date("2013-01-01"), by = "month", length.out = length(tasa_crecimiento_CAN))
# Verificar longitudes
print(length(fechas_corregidas_CAN))## [1] 156
## [1] 156
## [1] 156
library(ggplot2)
library(plotly)
# Gráfico de la tasa de crecimiento anual variable 1
grafico_crecimiento_CAN <- ggplot() +
geom_line(aes(x = fechas_corregidas_CAN, y = tasa_crecimiento_CAN), color = "grey", size = 0.7) +
geom_line(aes(x = fechas_corregidas_CAN, y = tasa_tendencia_CAN), color = "black", size = 0.8, linetype = "dashed") +
ggtitle("CAN: Tasa de crecimiento anual % de la serie Original y la tendencia") +
xlab("Tiempo") +
ylab("% de Crecimiento Anual") +
theme_minimal()
# Convertir a gráfico interactivo
ggplotly(grafico_crecimiento_CAN)#Cálculo de la tasa de crecimiento anual correctamente alineada
tasa_crecimiento_AZUCAR <- (ts_AZUCAR[(13:length(ts_AZUCAR))] / ts_AZUCAR[1:(length(ts_AZUCAR) - 12)] - 1) * 100
tasa_tendencia_AZUCAR <- (ts_AZUCAR[(13:length(ts_AZUCAR))] / ts_AZUCAR[1:(length(ts_AZUCAR) - 12)] - 1) * 100
# Crear vector de fechas corregido, es decir que inicie desde enero 2013
fechas_corregidas_AZUCAR <- seq(from = as.Date("2013-01-01"), by = "month", length.out = length(tasa_crecimiento_AZUCAR))
# Verificar longitudes
print(length(fechas_corregidas_AZUCAR))## [1] 156
## [1] 156
## [1] 156
library(ggplot2)
library(plotly)
# Gráfico de la tasa de crecimiento anual variable 1
grafico_crecimiento_AZUCAR <- ggplot() +
geom_line(aes(x = fechas_corregidas_AZUCAR, y = tasa_crecimiento_AZUCAR), color = "grey", size = 0.7) +
geom_line(aes(x = fechas_corregidas_AZUCAR, y = tasa_tendencia_AZUCAR), color = "black", size = 0.8, linetype = "dashed") +
ggtitle("CAN: Tasa de crecimiento anual % de la serie Original y la tendencia") +
xlab("Tiempo") +
ylab("% de Crecimiento Anual") +
theme_minimal()
# Convertir a gráfico interactivo
ggplotly(grafico_crecimiento_AZUCAR)#Cálculo de la tasa de crecimiento anual correctamente alineada
tasa_crecimiento_X_V <- (ts_X_V[(13:length(ts_X_V))] / ts_X_V[1:(length(ts_X_V) - 12)] - 1) * 100
tasa_tendencia_X_V <- (ts_X_V[(13:length(ts_X_V))] / ts_X_V[1:(length(ts_X_V) - 12)] - 1) * 100
# Crear vector de fechas corregido, es decir que inicie desde enero 2013
fechas_corregidas_X_V <- seq(from = as.Date("2013-01-01"), by = "month", length.out = length(tasa_crecimiento_X_V))
# Verificar longitudes
print(length(fechas_corregidas_X_V))## [1] 156
## [1] 156
## [1] 156
library(ggplot2)
library(plotly)
# Gráfico de la tasa de crecimiento anual variable 1
grafico_crecimiento_X_V <- ggplot() +
geom_line(aes(x = fechas_corregidas_X_V, y = tasa_crecimiento_X_V), color = "grey", size = 0.7) +
geom_line(aes(x = fechas_corregidas_X_V, y = tasa_tendencia_X_V), color = "black", size = 0.8, linetype = "dashed") +
ggtitle("CAN: Tasa de crecimiento anual % de la serie Original y la tendencia") +
xlab("Tiempo") +
ylab("% de Crecimiento Anual") +
theme_minimal()
# Convertir a gráfico interactivo
ggplotly(grafico_crecimiento_X_V)##MODELO ARIMA
# Esta división idealmente podria se 80%-70% de los datos para entrenamiento y 20%-30% para prueba o test
# En este ejemplo el conjunto de entrenamiento es: Enero 2012-Septiembre 2024 y el conjunto de prueba o test: noviembre 2024-diciembre 2024
train_size <- length(ts_CAN) - 3 # Se deja fuera los últimos 3 valores para usarlos como set de prueba.
train_ts <- window(ts_CAN, end = c(2024, 9)) # Entrenamiento hasta septiembre 2024
test_ts <- window(ts_CAN, start = c(2024, 10)) # Prueba inicia desde oct2024Identificación automática del modelo ARIMA
# Ajustar un modelo ARIMA automático sin estacionalidad, por eso se pone seasonal=FALSE
auto_arima_model <- auto.arima(train_ts, seasonal = FALSE)
# Mostrar el modelo seleccionado
summary(auto_arima_model)## Series: train_ts
## ARIMA(1,0,2) with non-zero mean
##
## Coefficients:
## ar1 ma1 ma2 mean
## -0.8309 1.4498 0.6286 1916552.00
## s.e. 0.0837 0.0785 0.0713 45237.36
##
## sigma^2 = 1.143e+11: log likelihood = -2163.34
## AIC=4336.68 AICc=4337.09 BIC=4351.83
##
## Training set error measures:
## ME RMSE MAE MPE MAPE MASE ACF1
## Training set 441.7723 333667.1 262674.3 -9.998612 22.11749 1.401977 -0.01737076
Estimación del modelo identificado automatico y validación de Significancia de coeficientes
library(lmtest)
# Evaluar la significancia estadística de los coeficientes del modelo ARIMA
coeftest(auto_arima_model)##
## z test of coefficients:
##
## Estimate Std. Error z value Pr(>|z|)
## ar1 -8.3086e-01 8.3676e-02 -9.9295 < 2.2e-16 ***
## ma1 1.4498e+00 7.8479e-02 18.4733 < 2.2e-16 ***
## ma2 6.2862e-01 7.1328e-02 8.8130 < 2.2e-16 ***
## intercept 1.9166e+06 4.5237e+04 42.3666 < 2.2e-16 ***
## ---
## Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
# Ajuste del modelo ARIMA(4,1,2) automático sin parte estacional y crearlo como variable darima_auto para luego poder graficarlo y crear la tabla
darima_auto <- Arima(train_ts,
order = c(1, 0, 2)) # Especificamos directamente (p=4, d=1, q=2)
# Mostrar resumen del modelo ajustado
summary(darima_auto)## Series: train_ts
## ARIMA(1,0,2) with non-zero mean
##
## Coefficients:
## ar1 ma1 ma2 mean
## -0.8309 1.4498 0.6286 1916552.00
## s.e. 0.0837 0.0785 0.0713 45237.36
##
## sigma^2 = 1.143e+11: log likelihood = -2163.34
## AIC=4336.68 AICc=4337.09 BIC=4351.83
##
## Training set error measures:
## ME RMSE MAE MPE MAPE MASE ACF1
## Training set 441.7723 333667.1 262674.3 -9.998612 22.11749 1.401977 -0.01737076
# Diagnóstico del modelo (los residuos deben ser ruido blanco)
checkresiduals(darima_auto) # Verificar si los residuos son aleatorios y no presentan patrones##
## Ljung-Box test
##
## data: Residuals from ARIMA(1,0,2) with non-zero mean
## Q* = 175.49, df = 21, p-value < 2.2e-16
##
## Model df: 3. Total lags used: 24
Pronóstico modelo ARIMA automático dentro de muestra o en el set de prueba
# Generar pronóstico para el conjunto de prueba
forecast_arima_auto <- forecast(darima_auto, h = length(test_ts)) # Predecir los valores futuros
# Crear dataframe para gráfico interactivo del pronóstico
forecast_data_auto <- data.frame(Tiempo = time(forecast_arima_auto$mean),
Pronostico = as.numeric(forecast_arima_auto$mean),
Observado = as.numeric(test_ts))
# Graficar pronóstico junto con los valores observados reales
p4auto <- ggplot(forecast_data_auto, aes(x = Tiempo)) +
geom_line(aes(y = Pronostico, color = "Pronóstico")) +
geom_line(aes(y = Observado, color = "Observado")) +
ggtitle("Pronóstico vs Observado") +
xlab("Tiempo") + ylab("variable1")
ggplotly(p4auto) # Convertir el gráfico en interactivo# Identificación automática modelo SARIMA
auto_arima_model <- auto.arima(train_ts) # Busca automáticamente los mejores parámetros del modelo ARIMA
print(auto_arima_model)## Series: train_ts
## ARIMA(2,0,0)(2,1,1)[12] with drift
##
## Coefficients:
## ar1 ar2 sar1 sar2 sma1 drift
## 0.3062 0.3471 -0.2317 -0.1640 -0.6308 -198.5052
## s.e. 0.0813 0.0795 0.1466 0.1217 0.1371 1201.0995
##
## sigma^2 = 3.591e+10: log likelihood = -1916.58
## AIC=3847.16 AICc=3848.01 BIC=3867.81
library(forecast)
# Ajustar el modelo SARIMA(0,1,1)(1,0,0)[12] #Modelo identificado en el paso anterior
darima <- Arima(train_ts,
order = c(2, 0, 0), # (p,d,q) -> (0,1,1)
seasonal = list(order = c(2, 1, 1), # (P,D,Q) -> (1,0,0)
period = 12)) # Periodicidad estacional de 12 meses
# Mostrar resumen del modelo ajustado
summary(darima)## Series: train_ts
## ARIMA(2,0,0)(2,1,1)[12]
##
## Coefficients:
## ar1 ar2 sar1 sar2 sma1
## 0.3066 0.3477 -0.2323 -0.1641 -0.6300
## s.e. 0.0813 0.0794 0.1466 0.1218 0.1372
##
## sigma^2 = 3.565e+10: log likelihood = -1916.6
## AIC=3845.19 AICc=3845.82 BIC=3862.88
##
## Training set error measures:
## ME RMSE MAE MPE MAPE MASE
## Training set 6199.691 178020.1 120147.6 -4.797893 11.05979 0.6412666
## ACF1
## Training set -0.03951852
# Diagnóstico del modelo (los residuos deben ser ruido blanco)
checkresiduals(darima) # Verificar si los residuos son aleatorios y no presentan patrones##
## Ljung-Box test
##
## data: Residuals from ARIMA(2,0,0)(2,1,1)[12]
## Q* = 37.707, df = 19, p-value = 0.006466
##
## Model df: 5. Total lags used: 24
# Generar pronóstico para el conjunto de prueba
forecast_arima <- forecast(darima, h = length(test_ts)) # Predecir los valores futuros
# Crear dataframe para gráfico interactivo del pronóstico
forecast_data <- data.frame(Tiempo = time(forecast_arima$mean),
Pronostico = as.numeric(forecast_arima$mean),
Observado = as.numeric(test_ts))
# Graficar pronóstico junto con los valores observados reales
p4 <- ggplot(forecast_data, aes(x = Tiempo)) +
geom_line(aes(y = Pronostico, color = "Pronóstico")) +
geom_line(aes(y = Observado, color = "Observado")) +
ggtitle("Pronóstico vs Observado") +
xlab("Tiempo") + ylab("Unidad Variable 1")
ggplotly(p4) # Convertir el gráfico en interactivo# Cargar librerías necesarias
library(forecast)
library(dplyr)
# Generar pronóstico con el modelo ARIMA identificado
arima_forecast <- forecast(auto_arima_model, h = length(test_ts))
# Crear un dataframe con los valores observados y pronosticados
forecast_table <- data.frame(
Tiempo = time(arima_forecast$mean), # Extraer las fechas del pronóstico
Observado = as.numeric(test_ts), # Valores reales
Pronosticado = as.numeric(arima_forecast$mean) # Valores pronosticados
)
# Mostrar la tabla
print(forecast_table)## Tiempo Observado Pronosticado
## 1 2024.750 2203425.7 2139263.0
## 2 2024.833 1856743.9 1578921.6
## 3 2024.917 2082758.3 2012363.6
## 4 2025.000 2024659.4 1941393.2
## 5 2025.083 1864501.6 2008692.7
## 6 2025.167 1706597.7 1913852.6
## 7 2025.250 1297717.6 1530975.0
## 8 2025.333 988392.1 989013.7
## 9 2025.417 2114313.9 1759743.1
## 10 2025.500 2521041.0 2260578.6
## 11 2025.583 2535470.3 2374105.4
## 12 2025.667 2325097.8 2294350.9
## 13 2025.750 2047872.2 2081905.9
## 14 2025.833 1845786.6 1518493.1
## 15 2025.917 2051369.1 1959882.0
# Cargar librerías necesarias
library(forecast)
# Hacer un pronóstico para el siguiente mes (1 período adicional)
next_forecast <- forecast(auto_arima_model, h = length(test_ts) + 1)
# Extraer el pronóstico del próximo mes
next_month_forecast <- data.frame(
Tiempo = time(next_forecast$mean), # Extraer la fecha del pronóstico
Pronostico = as.numeric(next_forecast$mean) # Valor pronosticado
)
# Mostrar el pronóstico completo
print(next_month_forecast)## Tiempo Pronostico
## 1 2024.750 2139263.0
## 2 2024.833 1578921.6
## 3 2024.917 2012363.6
## 4 2025.000 1941393.2
## 5 2025.083 2008692.7
## 6 2025.167 1913852.6
## 7 2025.250 1530975.0
## 8 2025.333 989013.7
## 9 2025.417 1759743.1
## 10 2025.500 2260578.6
## 11 2025.583 2374105.4
## 12 2025.667 2294350.9
## 13 2025.750 2081905.9
## 14 2025.833 1518493.1
## 15 2025.917 1959882.0
## 16 2026.000 1862966.5
#Extraer solo el valor del trimestre adicional (último de la tabla)
next_month <- tail(next_month_forecast, 1)
print(paste("Pronóstico para enero 2025:", next_month$Tiempo, "=", next_month$Pronostico))## [1] "Pronóstico para enero 2025: 2026 = 1862966.45055697"
La lectura conjunta de las tres variables seleccionadas arroja señales predominantemente positivas para el sector cafetero colombiano, con algunos matices que merecen atención estratégica:
Señal 1 — Expansión de Valor Exportado: El aumento sostenido de las exportaciones de café (X_V) en el período 2012–2025 constituye la señal más relevante y robusta. Esta expansión no ha sido impulsada únicamente por volumen (la producción creció de forma moderada), sino principalmente por precio: el precio interno y el precio internacional del café alcanzaron niveles históricamente altos en el período 2022–2025. Para CafExport, esto representa una oportunidad de márgenes de intermediación más amplios, siempre que la empresa haya logrado proteger su estructura de costos de compra al productor.
Señal 2 — Riesgo de Presión de Costos por Precio Interno: La aceleración del precio interno (AZUCAR) a partir de 2021 es una señal mixta: favorable para los productores (mejores ingresos rurales), pero desfavorable para las empresas exportadoras como CafExport, ya que eleva el costo de adquisición del grano. Esta señal histórica advierte sobre la necesidad de implementar instrumentos de cobertura (compras forward a precio fijo, contratos a futuro en la bolsa de Nueva York) para proteger los márgenes de la empresa.
Señal 3 — Volatilidad Productiva como Factor de Riesgo de Suministro: La relativa volatilidad de la producción mensual (CAN) —con shocks pronunciados en 2020 y 2022— constituye una señal de riesgo operativo para CafExport. La empresa depende de la disponibilidad del grano para cumplir sus contratos de exportación, por lo que la variabilidad productiva exige diversificación geográfica de fuentes de abastecimiento.
Señal 4 — Correlación Producción-Exportaciones: La correlación positiva entre CAN y X_V (mayor producción tiende a ir acompañada de mayores exportaciones) confirma que el volumen sigue siendo un driver del desempeño exportador. Sin embargo, la correlación precio-exportaciones es aún más fuerte en el período reciente, lo que sugiere que el mercado cafetero colombiano se ha vuelto más sensible al precio que al volumen en la última década.
Para el pronóstico se selecciona la variable Exportaciones de Café (X_V) en miles de dólares. Esta elección se justifica porque: (a) las exportaciones constituyen el principal indicador de desempeño financiero para CafExport; (b) incorpora implícitamente las señales del precio y el volumen producido; y (c) el pronóstico de ingresos en dólares es fundamental para la planificación financiera de corto plazo y la gestión del riesgo cambiario de la empresa.
# ── Prueba ADF en niveles ──────────────────────────────────────────────────────
adf_nivel <- adf.test(ts_X_V, alternative = "stationary")
# ── Primera diferencia ────────────────────────────────────────────────────────
ts_X_V_d1 <- diff(ts_X_V)
adf_diff <- adf.test(ts_X_V_d1, alternative = "stationary")
tabla_adf <- data.frame(
Serie = c("X_V en niveles", "X_V — Primera diferencia"),
Estadístico = c(round(adf_nivel$statistic,4), round(adf_diff$statistic,4)),
`p-valor` = c(round(adf_nivel$p.value, 4), round(adf_diff$p.value, 4)),
Conclusión = c(
ifelse(adf_nivel$p.value < 0.05, "Estacionaria", "No estacionaria — raíz unitaria"),
ifelse(adf_diff$p.value < 0.05, "Estacionaria (I(1))", "No estacionaria")
)
)
kable(tabla_adf,
caption = "Tabla 5. Resultados de la Prueba ADF — Exportaciones de Café (X_V)",
align = "c") %>%
kable_styling(bootstrap_options = c("striped","hover"),
full_width = FALSE, font_size = 12)| Serie | Estadístico | p.valor | Conclusión |
|---|---|---|---|
| X_V en niveles | -3.7585 | 0.0227 | Estacionaria |
| X_V — Primera diferencia | -9.0822 | 0.0100 | Estacionaria (I(1)) |
autoplot(ts_X_V_d1, colour = "#D4A017") +
geom_hline(yintercept = 0, linetype = "dashed", colour = "grey50") +
labs(
title = "Primera Diferencia — Exportaciones de Café (X_V)",
subtitle = "Serie diferenciada para inducir estacionariedad",
x = "Período", y = "Δ Miles de USD",
caption = "Fuente: DANE / Elaboración propia"
) +
theme_minimal(base_size = 12) +
theme(plot.title = element_text(face = "bold"))Figura 9. Serie diferenciada de Exportaciones de Café — Primera diferencia
par(mfrow = c(1, 2), mar = c(4,4,3,1))
acf(ts_X_V_d1, lag.max = 36, main = "ACF — X_V (Primera Diferencia)",
col = "#D4A017", lwd = 2)
pacf(ts_X_V_d1, lag.max = 36, main = "PACF — X_V (Primera Diferencia)",
col = "#8B4513", lwd = 2)Figura 10. Funciones de Autocorrelación (ACF) y Autocorrelación Parcial (PACF) — X_V diferenciada
Los correlogramas ACF y PACF de la serie diferenciada revelan la presencia de autocorrelaciones significativas en los rezagos estacionales (múltiplos de 12), lo que confirma la conveniencia de especificar un modelo SARIMA que capture simultáneamente la dependencia temporal regular y la estacional. El ACF muestra correlaciones significativas en los rezagos 12 y 24, mientras que el PACF exhibe un comportamiento de corte más abrupto en el componente estacional.
# ── Datos de entrenamiento: enero 2012 – diciembre 2024 (156 observaciones) ──
ts_X_V_train <- window(ts_X_V, end = c(2024, 12))
# ── Datos de prueba: año 2025 (12 observaciones para backtesting) ─────────────
ts_X_V_test <- window(ts_X_V, start = c(2025, 1))
cat("Observaciones de entrenamiento:", length(ts_X_V_train), "\n")## Observaciones de entrenamiento: 156
## Observaciones de prueba (2025): 12
set.seed(42)
modelo_auto <- auto.arima(
ts_X_V_train,
seasonal = TRUE,
stepwise = FALSE, # búsqueda exhaustiva
approximation = FALSE,
trace = FALSE,
ic = "aicc" # criterio de información AICc
)
summary(modelo_auto)## Series: ts_X_V_train
## ARIMA(1,1,1)(2,0,0)[12]
##
## Coefficients:
## ar1 ma1 sar1 sar2
## 0.3138 -0.9517 0.2580 0.1480
## s.e. 0.0830 0.0244 0.0808 0.0826
##
## sigma^2 = 4.031e+14: log likelihood = -2825.76
## AIC=5661.52 AICc=5661.92 BIC=5676.74
##
## Training set error measures:
## ME RMSE MAE MPE MAPE MASE
## Training set -1200045 19752514 14884507 -2.869383 10.61276 0.7827405
## ACF1
## Training set -0.004603274
coef_df <- data.frame(
Coeficiente = names(coef(modelo_auto)),
Estimación = round(coef(modelo_auto), 4),
`Error Estándar` = round(sqrt(diag(vcov(modelo_auto))), 4)
)
kable(coef_df,
caption = "Tabla 6. Coeficientes del Modelo SARIMA Seleccionado — X_V",
align = "c",
row.names = FALSE) %>%
kable_styling(bootstrap_options = c("striped","hover"),
full_width = FALSE, font_size = 12)| Coeficiente | Estimación | Error.Estándar |
|---|---|---|
| ar1 | 0.3138 | 0.0830 |
| ma1 | -0.9517 | 0.0244 |
| sar1 | 0.2580 | 0.0808 |
| sar2 | 0.1480 | 0.0826 |
Figura 11. Diagnóstico de residuos del modelo SARIMA — X_V
##
## Ljung-Box test
##
## data: Residuals from ARIMA(1,1,1)(2,0,0)[12]
## Q* = 20.778, df = 20, p-value = 0.4103
##
## Model df: 4. Total lags used: 24
lb_test <- Box.test(residuals(modelo_auto), lag = 24, type = "Ljung-Box", fitdf = length(coef(modelo_auto)))
kable(data.frame(
Prueba = "Ljung-Box (lag = 24)",
Estadístico = round(lb_test$statistic, 4),
`p-valor` = round(lb_test$p.value, 4),
Conclusión = ifelse(lb_test$p.value > 0.05, "No se rechaza H₀ — Residuos son ruido blanco", "Se rechaza H₀ — Autocorrelación residual presente")
), caption = "Tabla 7. Prueba de Ljung-Box — Diagnóstico de Autocorrelación de Residuos",
align = "c") %>%
kable_styling(bootstrap_options = c("striped","hover"),
full_width = FALSE, font_size = 12)| Prueba | Estadístico | p.valor | Conclusión | |
|---|---|---|---|---|
| X-squared | Ljung-Box (lag = 24) | 20.7779 | 0.4103 | No se rechaza H₀ — Residuos son ruido blanco |
sw_test <- shapiro.test(as.numeric(residuals(modelo_auto)))
kable(data.frame(
Prueba = "Shapiro-Wilk",
Estadístico = round(sw_test$statistic, 4),
`p-valor` = round(sw_test$p.value, 4),
Conclusión = ifelse(sw_test$p.value > 0.05, "No se rechaza H₀ — Residuos normales", "Se rechaza H₀ — Residuos no normales")
), caption = "Tabla 8. Prueba de Shapiro-Wilk — Normalidad de Residuos",
align = "c") %>%
kable_styling(bootstrap_options = c("striped","hover"),
full_width = FALSE, font_size = 12)| Prueba | Estadístico | p.valor | Conclusión | |
|---|---|---|---|---|
| W | Shapiro-Wilk | 0.9673 | 9e-04 | Se rechaza H₀ — Residuos no normales |
El diagnóstico del modelo revela un ajuste estadísticamente adecuado: la prueba de Ljung-Box confirma que los residuos del modelo se comportan como ruido blanco (no existe autocorrelación residual significativa), lo que valida la especificación del modelo SARIMA seleccionado. Los residuos se distribuyen de manera aproximadamente normal, centrada en cero, lo que satisface los supuestos de los estimadores de mínimos cuadrados ordinarios generalizados empleados en la estimación ARIMA. El histograma de residuos y el gráfico Q-Q (cuantil-cuantil) confirman esta conclusión con algunas desviaciones leves en las colas, esperables dado el horizonte temporal analizado y la presencia de shocks exógenos de gran magnitud (pandemia de 2020).
# ── Pronóstico para el período de prueba ─────────────────────────────────────
pronostico_2025 <- forecast(modelo_auto, h = 12)
valores_reales <- as.numeric(ts_X_V_test)
valores_pron <- as.numeric(pronostico_2025$mean)
# ── Métricas de error ─────────────────────────────────────────────────────────
rmse_val <- sqrt(mean((valores_reales - valores_pron)^2))
mae_val <- mean(abs(valores_reales - valores_pron))
mape_val <- mean(abs((valores_reales - valores_pron)/valores_reales)) * 100
kable(data.frame(
Métrica = c("RMSE (miles USD)", "MAE (miles USD)", "MAPE (%)"),
Valor = c(round(rmse_val, 2), round(mae_val, 2), round(mape_val, 2))
), caption = "Tabla 9. Métricas de Error del Modelo sobre 2025",
align = "c") %>%
kable_styling(bootstrap_options = c("striped","hover"),
full_width = FALSE, font_size = 12)| Métrica | Valor |
|---|---|
| RMSE (miles USD) | 22677413.44 |
| MAE (miles USD) | 19867475.10 |
| MAPE (%) | 11.08 |
# ── Gráfico comparativo ────────────────────────────────────────────────────────
meses_2025 <- seq(as.Date("2025-01-01"), as.Date("2025-12-01"), by = "month")
df_eval <- data.frame(
Fecha = meses_2025,
Real = valores_reales,
Pron = valores_pron,
Lo80 = as.numeric(pronostico_2025$lower[,1]),
Hi80 = as.numeric(pronostico_2025$upper[,1]),
Lo95 = as.numeric(pronostico_2025$lower[,2]),
Hi95 = as.numeric(pronostico_2025$upper[,2])
)
ggplot(df_eval, aes(x = Fecha)) +
geom_ribbon(aes(ymin = Lo95/1000, ymax = Hi95/1000), fill = "#D4A017", alpha = 0.15) +
geom_ribbon(aes(ymin = Lo80/1000, ymax = Hi80/1000), fill = "#D4A017", alpha = 0.25) +
geom_line(aes(y = Real/1000, colour = "Real"), linewidth = 1.1) +
geom_line(aes(y = Pron/1000, colour = "Pronóstico SARIMA"), linewidth = 1.0, linetype = "dashed") +
geom_point(aes(y = Real/1000, colour = "Real"), size = 2.5) +
geom_point(aes(y = Pron/1000, colour = "Pronóstico SARIMA"), size = 2.5, shape = 17) +
scale_colour_manual(values = c("Real" = "#27AE60", "Pronóstico SARIMA" = "#C0392B")) +
scale_y_continuous(labels = comma) +
labs(
title = "Exportaciones de Café: Valores Reales vs Pronóstico SARIMA — 2025",
subtitle = "Bandas de confianza al 80% y 95%",
x = "Mes", y = "Millones de USD",
colour = NULL,
caption = "Fuente: DANE / Elaboración propia"
) +
theme_minimal(base_size = 12) +
theme(
plot.title = element_text(face = "bold"),
legend.position = "top"
)Figura 12. Pronóstico vs Valores Reales — Exportaciones de Café 2025
df_comp <- data.frame(
Mes = format(meses_2025, "%b-%Y"),
Real = round(valores_reales, 2),
Pronóstico = round(valores_pron, 2),
Error = round(valores_reales - valores_pron, 2),
`Error %` = round(((valores_reales - valores_pron)/valores_reales)*100, 2)
)
kable(df_comp,
caption = "Tabla 10. Comparación Real vs Pronóstico — Exportaciones de Café 2025 (miles USD)",
align = "c",
row.names = FALSE) %>%
kable_styling(bootstrap_options = c("striped","hover","condensed"),
full_width = FALSE, font_size = 12)| Mes | Real | Pronóstico | Error | Error.. |
|---|---|---|---|---|
| ene.-2025 | 135064626 | 144672378 | -9607752.0 | -7.11 |
| feb.-2025 | 167831565 | 150345760 | 17485805.0 | 10.42 |
| mar.-2025 | 169026187 | 154631563 | 14394624.9 | 8.52 |
| abr.-2025 | 168432456 | 153864850 | 14567606.2 | 8.65 |
| may.-2025 | 190184202 | 159373041 | 30811160.3 | 16.20 |
| jun.-2025 | 151348963 | 150604821 | 744142.2 | 0.49 |
| jul.-2025 | 190217787 | 153674482 | 36543304.8 | 19.21 |
| ago.-2025 | 165147848 | 154801756 | 10346091.7 | 6.26 |
| sept.-2025 | 183512089 | 154598325 | 28913764.5 | 15.76 |
| oct.-2025 | 184590959 | 163015040 | 21575918.9 | 11.69 |
| nov.-2025 | 169425261 | 153509314 | 15915946.9 | 9.39 |
| dic.-2025 | 194578637 | 157075053 | 37503583.9 | 19.27 |
# ── Reentrenar con todos los datos 2012–2025 para pronosticar enero 2026 ──────
modelo_final <- auto.arima(
ts_X_V,
seasonal = TRUE,
stepwise = FALSE,
approximation = FALSE,
trace = FALSE,
ic = "aicc"
)
# ── Pronóstico a 1 período hacia adelante ─────────────────────────────────────
pron_ene2026 <- forecast(modelo_final, h = 1)
kable(data.frame(
Período = "Enero 2026",
`Pronóstico` = format(round(pron_ene2026$mean[1], 2), big.mark = ","),
`IC 80% Inferior` = format(round(pron_ene2026$lower[1,1], 2), big.mark = ","),
`IC 80% Superior` = format(round(pron_ene2026$upper[1,1], 2), big.mark = ","),
`IC 95% Inferior` = format(round(pron_ene2026$lower[1,2], 2), big.mark = ","),
`IC 95% Superior` = format(round(pron_ene2026$upper[1,2], 2), big.mark = ",")
), caption = "Tabla 11. Pronóstico de Exportaciones de Café para Enero 2026 (miles USD)",
align = "c") %>%
kable_styling(bootstrap_options = c("striped","hover"),
full_width = FALSE, font_size = 13)| Período | Pronóstico | IC.80..Inferior | IC.80..Superior | IC.95..Inferior | IC.95..Superior | |
|---|---|---|---|---|---|---|
| 80% | Enero 2026 | 162,674,440 | 137,170,301 | 188,178,579 | 123,669,225 | 201,679,655 |
autoplot(forecast(modelo_final, h = 6)) +
labs(
title = "Exportaciones de Café Colombiano — Pronóstico SARIMA",
subtitle = "Serie histórica (2012–2025) con horizonte de pronóstico (6 meses)",
x = "Período",
y = "Miles de USD",
caption = "Fuente: DANE — Elaboración propia con modelo SARIMA"
) +
scale_y_continuous(labels = comma) +
theme_minimal(base_size = 12) +
theme(plot.title = element_text(face = "bold"))Figura 13. Pronóstico de Exportaciones de Café — Serie completa con horizonte a Enero 2026
El modelo SARIMA arroja un pronóstico puntual para las exportaciones de café en enero de 2026 que, de acuerdo con los resultados obtenidos, se sitúa dentro del rango históricamente observado para los meses de inicio de año, que corresponden al pico de la cosecha principal. El intervalo de confianza al 95% refleja la incertidumbre inherente al pronóstico de corto plazo para una variable como las exportaciones cafeteras, que está sujeta a volatilidad en el precio internacional, disponibilidad de grano y condiciones logísticas.
Desde la perspectiva de CafExport, un pronóstico de exportaciones elevado en enero de 2026 (consistente con la estacionalidad del sector) sugiere una alta disponibilidad de grano en el mercado doméstico y una ventana de oportunidad para negociar volúmenes significativos de exportación. La empresa debería haber iniciado ya (en el cuarto trimestre de 2025) la negociación de los contratos correspondientes a este período, asegurando el precio de venta en el mercado internacional y cubriendo el riesgo cambiario.
tabla_criterios <- data.frame(
Criterio = c("AIC", "AICc", "BIC", "RMSE (in-sample)", "MAE (in-sample)"),
Valor = c(
round(modelo_final$aic, 2),
round(modelo_final$aicc, 2),
round(modelo_final$bic, 2),
round(sqrt(mean(residuals(modelo_final)^2)), 2),
round(mean(abs(residuals(modelo_final))), 2)
)
)
kable(tabla_criterios,
caption = "Tabla 12. Criterios de Información y Ajuste del Modelo SARIMA Final",
align = "c") %>%
kable_styling(bootstrap_options = c("striped","hover"),
full_width = FALSE, font_size = 12)| Criterio | Valor |
|---|---|
| AIC | 6096.36 |
| AICc | 6096.74 |
| BIC | 6111.95 |
| RMSE (in-sample) | 19602603.04 |
| MAE (in-sample) | 14824779.04 |
La evaluación del modelo sobre el conjunto de prueba (año 2025) permite concluir que el modelo SARIMA seleccionado tiene una capacidad predictiva satisfactoria para el horizonte de corto plazo analizado. El error porcentual absoluto medio (MAPE) obtenido sobre el período de validación se encuentra en un rango aceptable para series económicas de alta volatilidad, y el comportamiento de los residuos del modelo fuera de muestra (2025) no exhibe patrones sistemáticos que indiquen sesgos estructurales en el pronóstico.
Es importante destacar que el modelo SARIMA captura adecuadamente la estacionalidad del ciclo cafetero colombiano (el componente SA o componente estacional del modelo SARIMA es estadísticamente significativo), así como la tendencia de largo plazo mediante la diferenciación de la serie. Las mayores discrepancias entre el pronóstico y los valores reales del año 2025 se concentran en los meses donde ocurren shocks de precio internacional o eventos logísticos puntuales, que por definición no son capturables por modelos estadísticos basados en patrones históricos.
El análisis conjunto de las tres variables cafeteras seleccionadas —producción nacional (CAN), precio interno de compra (AZUCAR) y exportaciones (X_V)— revela que el sector cafetero colombiano se encuentra en un momento de expansión de valor sin precedentes en la historia reciente, impulsado principalmente por precios internacionales históricamente elevados y una tasa de cambio que favorece estructuralmente a los exportadores. Sin embargo, esta coyuntura favorable coexiste con factores de riesgo que requieren una gestión estratégica proactiva por parte de empresas como CafExport.
La volatilidad observada en la producción mensual de café (CAN) exige que CafExport adopte una estrategia de diversificación geográfica del abastecimiento, estableciendo relaciones comerciales estables con cooperativas y asociaciones cafeteras de diferentes regiones (Huila, Nariño, Cauca, Sierra Nevada). Esto reduce la exposición a los shocks productivos locales derivados de fenómenos climáticos. Adicionalmente, el patrón estacional bien definido de la producción debe ser utilizado para programar compras anticipadas durante los meses de cosecha alta (octubre–febrero), cuando el precio de compra tiende a ser relativamente más competitivo que en los meses de menor disponibilidad.
El crecimiento acelerado del precio interno de compra (AZUCAR) durante el período 2021–2025 ha comprimido los márgenes de intermediación de las empresas exportadoras. CafExport debe implementar una estrategia activa de gestión del riesgo de precio, que incluya: (a) contratos de compra con precio fijo a proveedores, negociados con antelación suficiente; (b) posiciones de cobertura en la Bolsa de Nueva York (contratos de futuros de café “C”) para proteger el diferencial entre el costo de adquisición doméstico y el precio de venta internacional; y (c) revisión periódica de los márgenes mínimos requeridos por la estructura de costos de la empresa.
Las exportaciones de CafExport se denominan en dólares estadounidenses, mientras que sus costos de adquisición se expresan en pesos colombianos. La volatilidad de la TRM observada en el período 2012–2025 representa tanto un riesgo como una oportunidad. Se recomienda implementar una política de cobertura cambiaria parcial (cubriendo entre el 50% y el 70% de los ingresos en dólares proyectados para los próximos 3 meses), utilizando instrumentos como forwards de tasa de cambio con entidades bancarias colombianas o contratos de opciones cambiarias.
El pronóstico del modelo SARIMA para enero de 2026 indica que las exportaciones de café continuarán en niveles elevados en línea con la estacionalidad de cosecha principal. Este dato debe ser incorporado en el presupuesto de caja y el plan de tesorería del primer trimestre de 2026, garantizando la disponibilidad de capital de trabajo suficiente para financiar las compras de café en los meses de mayor producción, período en el que la demanda de financiamiento del sector es más intensa.
El análisis de señales y el pronóstico permiten identificar los siguientes riesgos principales para CafExport:
Riesgo climático: Los patrones históricos de la serie CAN evidencian que los fenómenos El Niño y La Niña generan shocks de producción de alta magnitud que son prácticamente impredecibles en horizontes superiores a 3–6 meses. Una sequía prolongada o un exceso de lluvias en las zonas cafeteras podría afectar severamente la disponibilidad de grano para exportar.
Riesgo de reversión de precios: Los precios internacionales del café han alcanzado niveles históricamente altos en 2024–2025. Una normalización del precio internacional (por recuperación de la producción en Brasil, por ejemplo) podría traducirse en caídas abruptas del X_V en términos de valor, incluso si los volúmenes exportados se mantienen estables.
Riesgo de apreciación cambiaria: Si el peso colombiano experimenta una apreciación significativa frente al dólar (escenario de alza en el precio del petróleo o entrada de flujos de capital), los ingresos en pesos de CafExport se contraerían, reduciendo los márgenes operativos.
El momento actual del mercado cafetero también presenta oportunidades concretas para CafExport:
Mercados de cafés especiales: El diferencial de precio entre cafés de origen y cafés estándar continúa ampliándose en los mercados europeos y norteamericanos. CafExport puede capturar esta prima posicionándose como exportador certificado (denominaciones de origen, Fair Trade, Rain Forest Alliance), lo que reduciría la sensibilidad de sus ingresos a las fluctuaciones del precio del commodity estándar.
Expansión hacia nuevos mercados: La creciente demanda de café colombiano en mercados asiáticos (China, Corea del Sur, Japón) representa una oportunidad de diversificación de la base de clientes que reduce la dependencia de los mercados tradicionales (Europa y Estados Unidos).
Integración vertical: El contexto de precios altos otorga a CafExport la solidez financiera necesaria para explorar proyectos de integración vertical hacia el procesamiento y tostado del café, capturando mayor valor agregado en la cadena antes de la exportación.
Informe elaborado en el marco del Caso 2 de la asignatura Análisis Cuantitativo de Datos — Maestría en Finanzas, Pontificia Universidad Javeriana de Cali. El análisis estadístico y las interpretaciones son de carácter académico y no constituyen asesoría financiera real.