library(quantmod)
library(ggplot2)
library(dplyr)
library(tidyr)
library(scales)
library(moments)
library(MASS)
library(lubridate)
library(knitr)
library(kableExtra)
library(plotly)

Introducción

El presente informe tiene como objetivo evaluar la viabilidad financiera de la adquisición de maquinaria amarilla financiada mediante un crédito en dólares, incorporando el uso de instrumentos derivados para la gestión del riesgo cambiario.

Dado que los flujos del crédito se encuentran denominados en USD, mientras que los ingresos del proyecto se generan en pesos colombianos, el principal riesgo identificado corresponde a la volatilidad de la tasa de cambio (TRM). En este contexto, se propone la utilización de contratos forward de divisas como mecanismo de cobertura parcial.

El análisis combina herramientas cuantitativas, como simulaciones estocásticas del tipo de cambio con criterios financieros aplicados, con el fin de evaluar no solo el costo esperado del financiamiento, sino también la estabilidad de los flujos y la exposición al riesgo.

1. Parámetros Globales del Ejercicio

El análisis se desarrolla con base en condiciones de mercado observables a marzo de 2026, con el fin de garantizar la coherencia y reproducibilidad de los resultados.

Se toma como referencia una TRM de $3,692.48 COP/USD, correspondiente al 20 de marzo de 2026 (Banco de la República). Dado que el tipo de cambio varía diariamente, fijar este valor permite establecer un punto de partida consistente y verificable para el desarrollo del modelo.

Se asume una inversión total de $350 millones de pesos, financiada mediante un crédito en dólares a 10 años con pagos trimestrales bajo el sistema francés.

La tasa del crédito se fija en 8.5% EA, consistente con condiciones de financiamiento corporativo. Esta tasa se construye a partir de la Prime Rate en Estados Unidos (aproximadamente 6.75% EA según FRED), más un spread cercano a 1.75%, que refleja el riesgo crediticio asociado al financiamiento. Este tipo de estructura: tasa base más un margen, es habitual en créditos empresariales, en línea con las condiciones observadas en entidades como Wells Fargo.

Para la construcción de la curva forward, se emplean tasas de referencia en ambas economías. En Colombia, se utiliza el IBR a 1 año (10.1% EA) como aproximación de tasa de mercado en COP.

En Estados Unidos, se utiliza la Prime Rate (6.75% EA, FRED) como referencia de tasa comercial. En la selección de esta tasa se consideraron distintas alternativas. Por un lado, tasas interbancarias de corto plazo (en torno a 3.5%–3.75% EA) fueron descartadas, ya que corresponden a condiciones cercanas a tasas libres de riesgo y no reflejan el costo real de financiamiento para un agente corporativo. Por otro lado, la tasa del crédito (8.5% EA) tampoco resulta adecuada para la construcción de la curva forward, dado que incorpora un spread crediticio específico del deudor y no representa una tasa de mercado transable para operaciones de cobertura.

En este contexto, la Prime Rate permite aproximar condiciones comerciales de financiamiento en dólares, manteniendo consistencia entre el financiamiento y la estimación de la cobertura cambiaria.

set.seed(42)
options(scipen = 999)

TRM_spot      <- 3692.48 
inversion_COP <- 350000000
inversion_USD <- inversion_COP / TRM_spot
pago_ini_pct  <- 0.10
cuota_ini_USD <- inversion_USD * pago_ini_pct
monto_USD     <- inversion_USD * (1 - pago_ini_pct)

tasa_anual_USD <- 0.085
tasa_trim_USD  <- (1 + tasa_anual_USD)^(1/4) - 1
n_trim         <- 40

i_COP <- 0.101  
i_USD <- 0.0675 

resumen <- data.frame(
  CONCEPTO = c(
    "TRM Spot",
    "Inversión total (COP)",
    "Inversión total (USD)",
    "Monto financiado (90%)",
    "Tasa crédito USD — Wells Fargo (ref. comercial, EA)",
    "Tasa comercial COP — IBR (proxy PCI, EA)",
    "Tasa comercial USD — Prime Rate (EA)"
  ),
  VALOR = c(
    paste0("$", format(TRM_spot, big.mark = ","), " COP/USD"),
    paste0("$", format(inversion_COP, big.mark = ","), " COP"),
    paste0("$", formatC(inversion_USD, big.mark = ",", format = "f", digits = 2), " USD"),
    paste0("$", formatC(monto_USD, big.mark = ",", format = "f", digits = 2), " USD"),
    paste0(round(tasa_anual_USD * 100, 2), "%"),
    paste0(round(i_COP * 100, 2), "%"),
    paste0(round(i_USD * 100, 2), "%")
  ),
  stringsAsFactors = FALSE
)

print(resumen)
##                                              CONCEPTO             VALOR
## 1                                            TRM Spot $3,692.48 COP/USD
## 2                               Inversión total (COP)  $350,000,000 COP
## 3                               Inversión total (USD)    $94,787.24 USD
## 4                              Monto financiado (90%)    $85,308.52 USD
## 5 Tasa crédito USD — Wells Fargo (ref. comercial, EA)              8.5%
## 6            Tasa comercial COP — IBR (proxy PCI, EA)             10.1%
## 7                Tasa comercial USD — Prime Rate (EA)             6.75%

Los parámetros anteriores constituyen la base para la construcción del modelo y el análisis del riesgo cambiario del proyecto.


2. Análisis Fundamental TRM

tryCatch({
  getSymbols("USDCOP=X", src = "yahoo", from = "2018-01-01",
             auto.assign = TRUE, warnings = FALSE)
  trm_xts <- na.omit(`USDCOP=X`[, 6])
  trm_df  <- data.frame(fecha = index(trm_xts), TRM = as.numeric(trm_xts))
},
error = function(e) {
  stop("Error obteniendo datos de Yahoo Finance.\n")
})

# ── Tema APA para todos los gráficos ──────────────────────────────────────
theme_apa <- function(base_size = 12) {
  theme_classic(base_size = base_size) +
    theme(
      text             = element_text(family = "serif", size = base_size),
      plot.title       = element_text(face = "bold", size = base_size,
                                      hjust = 0.5, margin = margin(b = 2)),
      plot.subtitle    = element_text(size = base_size - 1, hjust = 0.5,
                                      face = "italic", margin = margin(b = 8)),
      axis.title       = element_text(face = "bold", size = base_size),
      axis.text        = element_text(size = base_size - 1),
      legend.title     = element_text(face = "bold", size = base_size - 1),
      legend.text      = element_text(size = base_size - 1),
      legend.position  = "bottom",
      panel.grid.major = element_line(color = "grey85", linewidth = 0.4),
      panel.grid.minor = element_blank(),
      plot.background  = element_rect(fill = "white", color = NA),
      panel.background = element_rect(fill = "white", color = NA)
    )
}

p_trm_hist <- ggplot(trm_df, aes(x = fecha, y = TRM)) +
  geom_line(color = "#1565C0", linewidth = 0.8) +
  geom_hline(yintercept = TRM_spot, linetype = "dashed", color = "#B71C1C") +
  annotate("text", x = max(trm_df$fecha), y = TRM_spot + 100,
           label = paste0("Spot referencia\n$", TRM_spot), color = "#B71C1C",
           hjust = 1, size = 3.5, family = "serif") +
  scale_y_continuous(labels = dollar_format(prefix = "$", suffix = " COP", big.mark = ",")) +
  labs(title    = "Figura 1",
       subtitle = "TRM Histórica USD/COP (2018–2026). Fuente: Yahoo Finance.",
       x = "Fecha", y = "COP por USD") +
  theme_apa()

print(p_trm_hist)

El comportamiento de la TRM desde 2018 hasta la actualidad evidencia una dinámica caracterizada por episodios de alta volatilidad, con una tendencia de depreciación del peso colombiano en el largo plazo, interrumpida por correcciones significativas en periodos recientes. En el periodo analizado, la TRM ha oscilado entre valores cercanos a $2,900 y niveles superiores a $4,400 COP/USD, reflejando la sensibilidad del tipo de cambio a factores externos como la política monetaria de Estados Unidos, el precio del petróleo y el apetito global por riesgo, así como a condiciones internas de la economía colombiana.

La TRM utilizada como punto de referencia para los cálculos del modelo corresponde al valor observado el 20 de marzo de 2026 ($3,692.48 COP/USD, Banco de la República), con el fin de garantizar la reproducibilidad del análisis. Aunque el tipo de cambio varía diariamente, fijar este valor permite evaluar de manera consistente los resultados bajo un punto de referencia verificable.

Desde una perspectiva prospectiva, es relevante complementar el dato observado con las expectativas del mercado. En este sentido, Bancolombia (2025) proyecta que:

El tipo de cambio USDCOP seguiría una trayectoria de apreciación, con un promedio proyectado de $3.880 en 2026. Esta tendencia estará influenciada por la debilidad global del dólar y el apetito de inversionistas extranjeros por activos locales, gracias al atractivo diferencial de tasas de interés y las expectativas de cara al proceso de elecciones legislativas y presidenciales. Sin embargo, los riesgos fiscales seguirán siendo determinantes para la evolución futura de los activos locales. (párr. 7)

Esta proyección sugiere que, si bien existe una expectativa de estabilidad o apreciación moderada en el corto plazo, el comportamiento del tipo de cambio sigue siendo incierto y altamente dependiente de factores macroeconómicos. En este contexto, la simulación del tipo de cambio no busca predecir un valor puntual, sino capturar la incertidumbre alrededor de estas expectativas, evaluando cómo distintos escenarios pueden impactar los flujos del proyecto.


3. Variación y Estadísticas TRM

trm_men <- trm_df %>%
  mutate(mes = format(fecha, "%Y-%m")) %>%
  group_by(mes) %>%
  summarise(TRM_m = mean(TRM, na.rm = TRUE), .groups = "drop") %>%
  mutate(r_log = log(TRM_m / lag(TRM_m))) %>%
  na.omit()

mu_m    <- mean(trm_men$r_log)
sigma_m <- sd(trm_men$r_log)
skew_v  <- skewness(trm_men$r_log)
kurt_v  <- kurtosis(trm_men$r_log)
nu_est  <- as.numeric(MASS::fitdistr(trm_men$r_log, "t")$estimate["df"])

estadisticas <- data.frame(
  Estadístico = c("Variación mensual promedio (μ)",
                  "Desviación estándar mensual (σ)",
                  "Asimetría (Skewness)",
                  "Curtosis",
                  "Grados de libertad t-Student (ν)"),
  Valor = c(sprintf("%.5f (%.4f%%)", mu_m, mu_m*100),
            sprintf("%.5f (%.4f%%)", sigma_m, sigma_m*100),
            sprintf("%.3f", skew_v),
            sprintf("%.3f  (colas pesadas, >3)", kurt_v),
            sprintf("%.2f", nu_est))
)

kable(estadisticas,
      caption  = "Tabla 1. Estadísticas de retornos logarítmicos mensuales TRM",
      align    = c("l","r"),
      booktabs = TRUE) %>%
  kable_styling(bootstrap_options = c("basic"),
                full_width = TRUE, font_size = 12, position = "center") %>%
  row_spec(0, bold = TRUE,
           extra_css = "border-top: 2px solid black; border-bottom: 1px solid black;") %>%
  row_spec(nrow(estadisticas), extra_css = "border-bottom: 2px solid black;")
Tabla 1. Estadísticas de retornos logarítmicos mensuales TRM
Estadístico Valor
Variación mensual promedio (μ) 0.00270 (0.2700%)
Desviación estándar mensual (σ) 0.03135 (3.1350%)
Asimetría (Skewness) 0.941
Curtosis 5.739 (colas pesadas, >3)
Grados de libertad t-Student (ν) 4.71
r_seq  <- seq(min(trm_men$r_log), max(trm_men$r_log), length.out = 200)
dens_N <- dnorm(r_seq, mu_m, sigma_m)
dens_T <- dt((r_seq - mu_m) / (sigma_m / sqrt(nu_est/(nu_est-2))), df = nu_est) /
           (sigma_m / sqrt(nu_est/(nu_est-2)))

p_hist <- ggplot(trm_men, aes(x = r_log)) +
  geom_histogram(aes(y = after_stat(density)), bins = 35,
                 fill = "#90CAF9", color = "white", alpha = 0.8) +
  geom_line(data = data.frame(x = r_seq, y = dens_N),
            aes(x = x, y = y, color = "Normal"), linewidth = 1.2) +
  geom_line(data = data.frame(x = r_seq, y = dens_T),
            aes(x = x, y = y, color = "t-Student"), linewidth = 1.2, linetype = "dashed") +
  scale_color_manual(values = c("Normal" = "#1565C0", "t-Student" = "#B71C1C")) +
  labs(title    = "Figura 2",
       subtitle = sprintf("Distribución de retornos logarítmicos mensuales de la TRM. μ = %.4f%% | σ = %.4f%% | Curtosis = %.2f.",
                          mu_m*100, sigma_m*100, kurt_v),
       x = "Variación logarítmica mensual", y = "Densidad", color = "Distribución") +
  theme_apa()

print(p_hist)

El análisis de los retornos muestra que la TRM no se ajusta adecuadamente a una distribución normal, ya que presenta colas más pesadas, es decir, una mayor frecuencia de variaciones extremas del tipo de cambio.

Esto se evidencia en una curtosis de 5.739 (superior a 3) y una asimetría de 0.941, lo que indica que los datos no son simétricos y que existen movimientos extremos más frecuentes de lo esperado bajo supuestos normales. Adicionalmente, los bajos grados de libertad estimados (ν ≈ 4.71) refuerzan este comportamiento.

En términos prácticos, esto implica que el tipo de cambio puede presentar episodios de depreciación o apreciación abrupta con mayor probabilidad de la que asumiría un modelo normal. Por esta razón, la distribución t-Student resulta más adecuada para la simulación, ya que permite capturar de mejor manera el riesgo asociado a estos eventos extremos.

Este ajuste es relevante para el proyecto, dado que dichos movimientos pueden impactar significativamente el valor en pesos de las obligaciones en dólares, afectando la estabilidad de los flujos de caja.


4. Simulación MBG (Normal y t-Student)

Con base en la variación histórica de la TRM, se proyecta la evolución del tipo de cambio a un horizonte de 40 trimestres mediante un modelo de simulación estocástica (Movimiento Browniano Geométrico).

El modelo se implementa bajo dos supuestos distributivos: Normal y t-Student, con el fin de comparar cómo cambia la estimación del riesgo cuando se incorporan eventos extremos. En particular, la distribución t-Student permite capturar de mejor manera movimientos abruptos del tipo de cambio, los cuales son relevantes para el análisis financiero del proyecto.

No se imponen límites a las trayectorias simuladas, ya que restringir los valores eliminaría precisamente los escenarios extremos que se buscan analizar. Esto es importante, dado que el objetivo no es predecir un valor puntual de la TRM, sino evaluar cómo su volatilidad puede afectar los flujos del crédito y la efectividad de la cobertura mediante forwards.

n_sim  <- 5000
n_q    <- 40
dt     <- 1          # paso de tiempo = 1 trimestre (mu_q y sig_q ya están en escala trimestral)

# ── Parámetros trimestrales (escala mensual × 3) ─────────────────
mu_q   <- mu_m * 3
sig_q  <- sigma_m * sqrt(3)

# ── Simulación Normal (GBM libre — sin truncamiento) ─────────────
sim_N <- matrix(NA, n_q, n_sim)
for (s in 1:n_sim) {
  sim_N[, s] <- TRM_spot * exp(cumsum(
    (mu_q - 0.5 * sig_q^2) * dt + sig_q * sqrt(dt) * rnorm(n_q)
  ))
}

# ── Simulación t-Student (GBM libre — sin truncamiento) ──────────
# scale_t garantiza que la t escalada tenga la misma varianza que sig_q:
# Var(scale_t * t_nu) = scale_t^2 * nu/(nu-2)  →  scale_t = sig_q * sqrt((nu-2)/nu)
scale_t <- sig_q * sqrt((nu_est - 2) / nu_est)

sim_T <- matrix(NA, n_q, n_sim)
for (s in 1:n_sim) {
  sim_T[, s] <- TRM_spot * exp(cumsum(
    (mu_q - 0.5 * sig_q^2) * dt + scale_t * sqrt(dt) * rt(n_q, df = nu_est)
  ))
}

sim_plot <- bind_rows(
  data.frame(Trim = 1:n_q,
             p05 = apply(sim_N, 1, quantile, 0.05),
             p25 = apply(sim_N, 1, quantile, 0.25),
             mediana = apply(sim_N, 1, median),
             p75 = apply(sim_N, 1, quantile, 0.75),
             p95 = apply(sim_N, 1, quantile, 0.95),
             dist = "Normal"),
  data.frame(Trim = 1:n_q,
             p05 = apply(sim_T, 1, quantile, 0.05),
             p25 = apply(sim_T, 1, quantile, 0.25),
             mediana = apply(sim_T, 1, median),
             p75 = apply(sim_T, 1, quantile, 0.75),
             p95 = apply(sim_T, 1, quantile, 0.95),
             dist = "t-Student")
)

p_bmg <- ggplot(sim_plot, aes(x = Trim)) +
  geom_ribbon(aes(ymin = p05, ymax = p95, fill = dist), alpha = 0.12) +
  geom_ribbon(aes(ymin = p25, ymax = p75, fill = dist), alpha = 0.25) +
  geom_line(aes(y = mediana, color = dist), linewidth = 1.3) +
  geom_hline(yintercept = TRM_spot, linetype = "dashed", color = "gray50") +
  scale_fill_manual(values  = c("Normal" = "#1565C0", "t-Student" = "#C62828")) +
  scale_color_manual(values = c("Normal" = "#1565C0", "t-Student" = "#C62828")) +
  scale_y_continuous(labels = dollar_format(prefix = "$", suffix = " COP", big.mark = ",")) +
  labs(title    = "Figura 3",
       subtitle = sprintf("Simulación MBG TRM — Normal vs t-Student (40 trimestres, n = %d trayectorias).", n_sim),
       x = "Trimestre", y = "TRM (COP/USD)", color = "Distribución", fill = "Distribución") +
  theme_apa()

print(p_bmg)

# ── Validación de simulación ──────────────────────────────────────
cat(sprintf("Normal   T40 — Media: $%s | SD: $%s\n",
    format(round(mean(sim_N[n_q,])), big.mark=","),
    format(round(sd(sim_N[n_q,])),   big.mark=",")))
## Normal   T40 — Media: $5,086 | SD: $1,831
cat(sprintf("t-Student T40 — Media: $%s | SD: $%s\n",
    format(round(mean(sim_T[n_q,])), big.mark=","),
    format(round(sd(sim_T[n_q,])),   big.mark=",")))
## t-Student T40 — Media: $5,114 | SD: $1,762

La simulación bajo distribución t-Student muestra una mayor dispersión en los posibles valores del tipo de cambio en comparación con la distribución normal, lo que refleja una mayor probabilidad de escenarios extremos. Este resultado es consistente con el comportamiento observado en los retornos históricos, donde se evidencian colas pesadas y asimetría.

En este contexto, el modelo normal tiende a subestimar el riesgo cambiario, mientras que la t-Student permite capturar de forma más realista la incertidumbre asociada a movimientos abruptos del tipo de cambio. Por esta razón, la simulación basada en t-Student resulta más adecuada para evaluar el riesgo del proyecto.

Desde la perspectiva del crédito, esta mayor dispersión implica que los pagos en pesos pueden variar significativamente a lo largo del horizonte de 10 años, especialmente en escenarios de depreciación del peso. Esto refuerza la necesidad de implementar mecanismos de cobertura que permitan mitigar dicha incertidumbre.

La Tabla 2 presenta los valores proyectados de la TRM bajo tres escenarios: Bajo (percentil 5%), Base (mediana) y Alto (percentil 95%) construidos a partir de la simulación con distribución t-Student.

tabla_trm <- data.frame(
    Trim                  = 1:n_q,
    `Escenario Bajo (P5)` = apply(sim_T, 1, quantile, 0.05),
    `Escenario Base (Mediana)` = apply(sim_T, 1, median),
    `Escenario Alto (P95)` = apply(sim_T, 1, quantile, 0.95),
    Distribucion          = "t-Student",
    check.names           = FALSE
  )

kable(
  rbind(head(tabla_trm, 5), tail(tabla_trm, 5)),
  digits = 0,
  format.args = list(big.mark = ","),
  caption = "Tabla 2. Escenarios proyectados de TRM por trimestre — t-Student (primeros y últimos cinco trimestres)",
  align = c("c","r","r","r","c"),
  booktabs = TRUE
) %>%
  kable_styling(full_width = TRUE, font_size = 12) %>%
  row_spec(0, bold = TRUE,
           extra_css = "border-top: 2px solid black; border-bottom: 1px solid black;") %>%
  row_spec(10, extra_css = "border-bottom: 2px solid black;")
Tabla 2. Escenarios proyectados de TRM por trimestre — t-Student (primeros y últimos cinco trimestres)
Trim Escenario Bajo (P5) Escenario Base (Mediana) Escenario Alto (P95) Distribucion
1 1 3,417 3,721 4,048 t-Student
2 2 3,318 3,747 4,227 t-Student
3 3 3,255 3,765 4,360 t-Student
4 4 3,210 3,793 4,486 t-Student
5 5 3,130 3,819 4,604 t-Student
36 36 2,777 4,725 7,882 t-Student
37 37 2,770 4,756 7,970 t-Student
38 38 2,798 4,770 8,085 t-Student
39 39 2,779 4,808 8,186 t-Student
40 40 2,775 4,845 8,299 t-Student
# Último valor de cada escenario al trimestre 40
cat(sprintf("\nTRM proyectada en T40 (10 años):\n  Bajo (P5):  $%s\n  Base:       $%s\n  Alto (P95): $%s\n",
    format(round(tail(tabla_trm[["Escenario Bajo (P5)"]], 1)), big.mark=","),
    format(round(tail(tabla_trm[["Escenario Base (Mediana)"]], 1)), big.mark=","),
    format(round(tail(tabla_trm[["Escenario Alto (P95)"]], 1)), big.mark=",")))
## 
## TRM proyectada en T40 (10 años):
##   Bajo (P5):  $2,775
##   Base:       $4,845
##   Alto (P95): $8,299
# Para los chunks siguientes, extraer vectores de TRM por escenario
TRM_bajo <- tabla_trm[["Escenario Bajo (P5)"]]
TRM_base <- tabla_trm[["Escenario Base (Mediana)"]]
TRM_alto <- tabla_trm[["Escenario Alto (P95)"]]

Los escenarios proyectados muestran una alta dispersión en el comportamiento futuro de la TRM, lo que refleja un nivel relevante de incertidumbre cambiaria.

En particular, el escenario alto incorpora posibles episodios de depreciación significativa del peso colombiano, lo que incrementaría el costo del crédito en términos de moneda local. Por el contrario, el escenario bajo representa condiciones favorables en las que una apreciación del peso reduciría el costo financiero.

La diferencia entre los percentiles 5% y 95% permite dimensionar el rango de posibles resultados y pone en evidencia la magnitud del riesgo cambiario al que está expuesto el proyecto. Además, se observa que esta dispersión aumenta a medida que se amplía el horizonte temporal, lo cual es consistente con la acumulación de incertidumbre en el tiempo.

En este contexto, estos escenarios sirven como base para evaluar la efectividad de la cobertura con forwards, permitiendo identificar en qué condiciones dicha estrategia logra estabilizar los flujos frente a la volatilidad del tipo de cambio.


5. Crédito en USD — Sistema Francés

La Tabla 3 y la Figura 4 presentan el plan de amortización del crédito en dólares bajo el sistema francés, en el cual la cuota se mantiene constante a lo largo del tiempo. El valor exacto de la cuota trimestral se detalla en el encabezado de la Tabla 3.

Si bien la cuota es fija, su composición cambia a lo largo del crédito. En los primeros períodos, una mayor proporción corresponde al pago de intereses, mientras que en los períodos finales predomina la amortización del capital. Esto implica que el costo financiero es más alto en las etapas iniciales, cuando el saldo del crédito también es mayor.

Dado que el crédito está denominado en dólares, el valor de las cuotas en pesos colombianos dependerá del comportamiento del tipo de cambio. En consecuencia, la volatilidad de la TRM introduce incertidumbre en los flujos de pago, lo que justifica tanto el análisis de conversión a moneda local como la implementación de estrategias de cobertura cambiaria.

cuota_USD <- monto_USD * (tasa_trim_USD * (1 + tasa_trim_USD)^n_trim) /
             ((1 + tasa_trim_USD)^n_trim - 1)

tabla <- data.frame(
  Trim = 1:n_trim, Año = ceiling((1:n_trim) / 4),
  Saldo_ini = NA, Interes = NA, Amort = NA, Cuota = NA, Saldo_fin = NA
)
saldo <- monto_USD
for (i in 1:n_trim) {
  int <- saldo * tasa_trim_USD
  am  <- cuota_USD - int
  tabla[i, c("Saldo_ini","Interes","Amort","Cuota","Saldo_fin")] <-
    c(saldo, int, am, cuota_USD, max(saldo - am, 0))
  saldo <- saldo - am
}

kable(
  rbind(
    head(tabla[, c("Trim","Año","Saldo_ini","Interes","Amort","Cuota","Saldo_fin")], 3),
    tail(tabla[, c("Trim","Año","Saldo_ini","Interes","Amort","Cuota","Saldo_fin")], 3)
  ),
  digits      = 2,
  format.args = list(big.mark = ","),
  caption     = sprintf("Tabla 3. Amortización en USD (Sistema Francés) — Cuota fija: $%.2f USD/trimestre", cuota_USD),
  align       = c("c","c","r","r","r","r","r"),
  booktabs    = TRUE
) %>%
  kable_styling(bootstrap_options = c("basic"),
                full_width = TRUE, font_size = 12) %>%
  row_spec(0, bold = TRUE,
           extra_css = "border-top: 2px solid black; border-bottom: 1px solid black;") %>%
  row_spec(6, extra_css = "border-bottom: 2px solid black;")
Tabla 3. Amortización en USD (Sistema Francés) — Cuota fija: $3151.67 USD/trimestre
Trim Año Saldo_ini Interes Amort Cuota Saldo_fin
1 1 1 85,308.52 1,757.73 1,393.94 3,151.67 83,914.58
2 2 1 83,914.58 1,729.01 1,422.66 3,151.67 82,491.93
3 3 1 82,491.93 1,699.70 1,451.97 3,151.67 81,039.95
38 38 10 9,078.35 187.05 2,964.61 3,151.67 6,113.74
39 39 10 6,113.74 125.97 3,025.70 3,151.67 3,088.04
40 40 10 3,088.04 63.63 3,088.04 3,151.67 0.00
# Validaciones financieras
cat(sprintf("Suma amortizaciones: $%.2f USD (debe ≈ monto_USD: $%.2f USD)\n",
            sum(tabla$Amort), monto_USD))
## Suma amortizaciones: $85308.52 USD (debe ≈ monto_USD: $85308.52 USD)
total_pagado    <- sum(tabla$Cuota)
total_intereses <- sum(tabla$Interes)
cat(sprintf("Total pagado: $%.2f USD | Intereses totales: $%.2f USD\n",
            total_pagado, total_intereses))
## Total pagado: $126066.67 USD | Intereses totales: $40758.15 USD
# Gráfico con barras apiladas correctas (pivot_longer)
tabla_long <- tabla %>%
  dplyr::select(Trim, Amort, Interes) %>%
  tidyr::pivot_longer(cols = c(Amort, Interes),
                      names_to = "Tipo", values_to = "Valor")

p_amort <- ggplot(tabla_long, aes(x = Trim, y = Valor, fill = Tipo)) +
  geom_col(alpha = 0.85) +
  scale_fill_manual(values = c("Amort" = "#1565C0", "Interes" = "#EF5350"),
                    labels  = c("Amort" = "Amortización", "Interes" = "Interés")) +
  scale_y_continuous(labels = dollar_format(prefix = "$", suffix = " USD", big.mark = ",")) +
  labs(title    = "Figura 4",
       subtitle = sprintf("Composición de la cuota — Sistema Francés (%.1f%% EA, 10 años, %d cuotas trimestrales).",
                          tasa_anual_USD*100, n_trim),
       x = "Trimestre", y = "USD", fill = "") +
  theme_apa()

print(p_amort)

El total nominal pagado por el crédito asciende a $126066.67 USD, de los cuales $40758.15 USD corresponden a intereses a lo largo de los 10 años. En términos relativos, los intereses representan aproximadamente el 32.3% del total pagado.

Este resultado permite dimensionar el costo financiero del endeudamiento en dólares, evidenciando que una proporción relevante del flujo total corresponde a intereses. Sin embargo, desde la perspectiva del proyecto, el elemento crítico no es únicamente el nivel de la tasa, sino su exposición al tipo de cambio.

En la medida en que las cuotas están denominadas en dólares, su equivalente en pesos colombianos dependerá directamente de la evolución de la TRM. Por lo tanto, el costo real del crédito en moneda local puede variar de forma significativa, lo que refuerza la importancia de analizar el riesgo cambiario y considerar mecanismos de cobertura.


6. Pago de Cuotas Transformadas a COP

Con base en las trayectorias simuladas de la TRM bajo distribución t-Student, se estiman los flujos de pago del crédito en pesos colombianos para cada uno de los 40 trimestres.

Se consideran tres escenarios: Bajo (percentil 5%), Base (mediana) y Alto (percentil 95%), con el fin de evaluar cómo distintos comportamientos del tipo de cambio impactan el valor de las cuotas en moneda local.

Los resultados permiten visualizar de manera directa la sensibilidad del proyecto frente al riesgo cambiario. En particular, se observa que el valor de las cuotas en pesos presenta una variabilidad significativa entre escenarios, reflejando la incertidumbre asociada a la evolución futura del tipo de cambio.

Esta dispersión confirma que el principal riesgo financiero del proyecto no proviene de la estructura del crédito, sino de la volatilidad de la TRM, la cual puede afectar de manera significativa la estabilidad de los flujos de caja en moneda local y, por ende, la viabilidad financiera del proyecto.

# ── TRM proyectada ya disponible desde chunk tabla_trm_escenarios ────────
# TRM_bajo, TRM_base, TRM_alto ya están definidos

# ── Tabla de amortización en COP (3 escenarios) ───────────────────

tabla_cop <- tabla %>%
  mutate(
    TRM_bajo = TRM_bajo,
    TRM_base = TRM_base,
    TRM_alto = TRM_alto,

    # Cuotas en COP
    Cuota_COP_bajo = Cuota * TRM_bajo,
    Cuota_COP_base = Cuota * TRM_base,
    Cuota_COP_alto = Cuota * TRM_alto,

    # Intereses en COP
    Interes_COP_bajo = Interes * TRM_bajo,
    Interes_COP_base = Interes * TRM_base,
    Interes_COP_alto = Interes * TRM_alto,

    # Amortización en COP
    Amort_COP_bajo = Amort * TRM_bajo,
    Amort_COP_base = Amort * TRM_base,
    Amort_COP_alto = Amort * TRM_alto,

    # Saldo en COP
    Saldo_ini_COP_bajo = Saldo_ini * TRM_bajo,
    Saldo_ini_COP_base = Saldo_ini * TRM_base,
    Saldo_ini_COP_alto = Saldo_ini * TRM_alto
  )

kable(
  rbind(
    head(tabla_cop[, c("Trim",
                       "Cuota_COP_bajo",
                       "Cuota_COP_base",
                       "Cuota_COP_alto")], 5),
    tail(tabla_cop[, c("Trim",
                       "Cuota_COP_bajo",
                       "Cuota_COP_base",
                       "Cuota_COP_alto")], 5)
  ),
  digits = 0,
  format.args = list(big.mark = ","),
  caption = "Tabla 3B. Cuotas en COP bajo tres escenarios de TRM simulada (t-Student). Primeros y últimos cinco trimestres.",
  align = c("c","r","r","r"),
  booktabs = TRUE
) %>%
  kable_styling(full_width = TRUE, font_size = 12)
Tabla 3B. Cuotas en COP bajo tres escenarios de TRM simulada (t-Student). Primeros y últimos cinco trimestres.
Trim Cuota_COP_bajo Cuota_COP_base Cuota_COP_alto
1 1 10,770,730 11,728,562 12,758,103
2 2 10,456,576 11,809,292 13,321,572
3 3 10,259,342 11,867,009 13,740,084
4 4 10,117,165 11,954,053 14,139,350
5 5 9,863,291 12,035,688 14,510,219
36 36 8,752,800 14,892,883 24,840,654
37 37 8,731,177 14,989,151 25,117,903
38 38 8,819,152 15,033,833 25,480,987
39 39 8,758,349 15,153,369 25,800,732
40 40 8,747,412 15,269,618 26,156,151
# ── Datos para histograma ─────────────────────────────────────────

flujos <- bind_rows(
  data.frame(Flujo = tabla_cop$Cuota_COP_bajo, Escenario = "Bajo (5%)"),
  data.frame(Flujo = tabla_cop$Cuota_COP_base, Escenario = "Base (Mediana)"),
  data.frame(Flujo = tabla_cop$Cuota_COP_alto, Escenario = "Alto (95%)")
)

p_hist_flujos <- ggplot(flujos, aes(x = Flujo / 1e6, fill = Escenario)) +
  geom_histogram(alpha = 0.5, position = "identity", bins = 30) +
  scale_fill_manual(values = c("Bajo (5%)" = "#2E7D32",
                               "Base (Mediana)" = "#1565C0",
                               "Alto (95%)" = "#C62828")) +
  labs(title    = "Figura 5",
       subtitle = "Distribución de flujos de cuotas en COP bajo los tres escenarios de TRM simulada (t-Student).",
       x = "Cuota (millones COP)",
       y = "Frecuencia",
       fill = "Escenario") +
  theme_apa()

print(p_hist_flujos)

# Cuantificación de la variabilidad entre escenarios
mean_bajo <- mean(tabla_cop$Cuota_COP_bajo) / 1e6
mean_base <- mean(tabla_cop$Cuota_COP_base) / 1e6
mean_alto <- mean(tabla_cop$Cuota_COP_alto) / 1e6
cat(sprintf("Cuota promedio COP — Bajo: $%.2fM | Base: $%.2fM | Alto: $%.2fM\n",
            mean_bajo, mean_base, mean_alto))
## Cuota promedio COP — Bajo: $9.16M | Base: $13.43M | Alto: $19.74M
TRM_proy <- apply(sim_T, 1, median)

Los resultados muestran que el valor de las cuotas en pesos colombianos puede variar de manera significativa dependiendo del comportamiento del tipo de cambio.

En promedio, las cuotas oscilan entre aproximadamente $9.16 millones (escenario bajo) y $19.74 millones de COP (escenario alto), diferencia que se explica principalmente por la exposición al riesgo cambiario del financiamiento en dólares.

Más allá del nivel de la tasa de interés, este resultado sugiere que el principal riesgo del proyecto no proviene del crédito en sí, sino de la volatilidad de la TRM, la cual puede afectar de forma importante la estabilidad de los flujos de caja a lo largo del horizonte de 10 años.


7. Ítem 1 — SET-FX: Curva Forward USD/COP

Para la construcción de la estrategia de cobertura, se selecciona un contrato forward USD/COP a 21 meses, el cual cumple con la condición de plazo superior a 6 meses establecida en la actividad.

La información de la curva forward fue obtenida a través de Investing.com (marzo de 2026), plataforma que consolida cotizaciones del mercado cambiario colombiano, incluyendo referencias del mercado interbancario (SET-FX). La Tabla 4 presenta los puntos forward para distintos vencimientos, mientras que la Figura 6 ilustra el perfil Bid/Ask, destacando el instrumento seleccionado.

Se excluyen los plazos de muy corto plazo (como Spot Next y 3 semanas), dado que no resultan relevantes para el horizonte del proyecto ni cumplen con el criterio mínimo requerido. De esta forma, el análisis se enfoca en instrumentos que reflejan de mejor manera las condiciones del mercado para coberturas de mediano plazo.

# Fuente: SET-ICAP vía Investing.com — sesión del 20 de marzo de 2026
# https://es.investing.com/currencies/usd-cop-forward-rates
setfx <- data.frame(
  Plazo  = c("Spot Next", "3 Semanas", "21 Meses", "2 Años", "3 Años"),
  Meses  = c(0,           0.75,        21,          24,        36),
  Bid    = c(0.000,       0.00,        159.000,     195.000,   300.000),
  Ask    = c(0.042,       4.900,       176.830,     215.000,   350.000),
  Mid    = c(0.021,       3.950,       167.920,     205.000,   325.000)
)
setfx$TRM_Forward  <- TRM_spot + setfx$Mid
setfx$Dev_impl_EA  <- round(ifelse(setfx$Meses > 0,
                             ((setfx$TRM_Forward / TRM_spot)^(12 / setfx$Meses) - 1) * 100,
                             NA), 2)

kable(setfx,
      digits      = 2,
      format.args = list(big.mark = ","),
      col.names   = c("Plazo","Meses","Bid (pts)","Ask (pts)","Mid (pts)",
                      "TRM Forward (COP)","Dev. impl. EA (%)"),
      caption     = "Tabla 4. Curva forward USD/COP — plazos superiores a 6 meses. Fuente: SET-ICAP vía Investing.com (marzo 2026).",
      align       = c("l","c","r","r","r","r","r"),
      booktabs    = TRUE) %>%
  kable_styling(bootstrap_options = c("basic"),
                full_width = TRUE, font_size = 12) %>%
  row_spec(0, bold = TRUE,
           extra_css = "border-top: 2px solid black; border-bottom: 1px solid black;") %>%
  row_spec(3, bold = TRUE,
           extra_css = "background-color: #EBF5FB;") %>%   # resalta 21M seleccionado (fila 3)
  row_spec(nrow(setfx), extra_css = "border-bottom: 2px solid black;") %>%
  footnote(general = "La fila resaltada (21 meses) es el instrumento seleccionado.
           Dev. impl. EA: devaluación del peso implícita en el punto forward, anualizada.",
           general_title = "Nota.")
Tabla 4. Curva forward USD/COP — plazos superiores a 6 meses. Fuente: SET-ICAP vía Investing.com (marzo 2026).
Plazo Meses Bid (pts) Ask (pts) Mid (pts) TRM Forward (COP) Dev. impl. EA (%)
Spot Next 0.00 0 0.04 0.02 3,692.50 NA
3 Semanas 0.75 0 4.90 3.95 3,696.43 1.73
21 Meses 21.00 159 176.83 167.92 3,860.40 2.57
2 Años 24.00 195 215.00 205.00 3,897.48 2.74
3 Años 36.00 300 350.00 325.00 4,017.48 2.85
Nota.
La fila resaltada (21 meses) es el instrumento seleccionado.
Dev. impl. EA: devaluación del peso implícita en el punto forward, anualizada.
TRM_fw_21m   <- TRM_spot + 167.92
dev_impl_21m <- (TRM_fw_21m / TRM_spot)^(12/21) - 1
dev_pci      <- (1 + i_COP) / (1 + i_USD) - 1

# Tabla resumen del instrumento seleccionado
resumen_forward <- data.frame(
  Concepto = c("Plazo seleccionado",
               "TRM Spot (BanRep, 20-mar-2026)",
               "Puntos forward Mid",
               "TRM Forward implícita (21M)",
               "Devaluación implícita anualizada (mercado)",
               "Devaluación implícita PCI (modelo)",
               "Coherencia modelo vs. mercado"),
  Valor = c(
    "21 meses (> 6 meses — cumple condición)",
    paste0("$", format(TRM_spot, big.mark = ","), " COP/USD"),
    formatC(167.92, format = "f", digits = 2),
    paste0("$", formatC(TRM_fw_21m, format = "f", digits = 2), " COP/USD"),
    paste0(round(dev_impl_21m * 100, 2), "% EA"),
    paste0(round(dev_pci * 100, 2), "% EA"),
    paste0(round(abs(dev_impl_21m - dev_pci) * 100, 2), " p.p. de diferencia — coherente")
  ),
  stringsAsFactors = FALSE
)

knitr::kable(
  resumen_forward,
  col.names = c("Concepto", "Valor"),
  caption   = "Tabla 4.1. Resumen del instrumento SET-FX seleccionado y validación del modelo PCI.",
  booktabs  = TRUE
) %>%
  kable_styling(bootstrap_options = c("basic"), full_width = TRUE, font_size = 12) %>%
  row_spec(0, bold = TRUE,
           extra_css = "border-top: 2px solid black; border-bottom: 1px solid black;") %>%
  row_spec(nrow(resumen_forward), extra_css = "border-bottom: 2px solid black;")
Tabla 4.1. Resumen del instrumento SET-FX seleccionado y validación del modelo PCI.
Concepto Valor
Plazo seleccionado 21 meses (> 6 meses — cumple condición)
TRM Spot (BanRep, 20-mar-2026) $3,692.48 COP/USD
Puntos forward Mid 167.92
TRM Forward implícita (21M) $3860.40 COP/USD
Devaluación implícita anualizada (mercado) 2.57% EA
Devaluación implícita PCI (modelo) 3.14% EA
Coherencia modelo vs. mercado 0.56 p.p. de diferencia — coherente

La curva forward presenta una prima positiva creciente a lo largo de los distintos plazos, lo que implica que el mercado exige más pesos por cada dólar a medida que aumenta el horizonte. Este comportamiento es consistente con el diferencial de tasas de interés entre Colombia y Estados Unidos.

En particular, el instrumento a 21 meses registra una devaluación implícita anualizada de 2.57%, valor cercano al estimado mediante el modelo de Paridad Cubierta de Intereses (3.14% EA) con las tasas definidas previamente. Esta cercanía sugiere que los supuestos utilizados en el modelo son coherentes con las condiciones actuales del mercado.

Con el fin de validar la consistencia del modelo frente a condiciones reales de mercado, se toma como referencia el instrumento a 21 meses, el cual cuenta con cotización observable. A diferencia de estimaciones teóricas, los forwards de mercado incorporan información actualizada sobre tasas y expectativas, por lo que resultan útiles para contrastar los resultados obtenidos mediante el modelo.

Una vez validada esta consistencia, la estrategia de cobertura se implementa utilizando los contratos definidos para el horizonte del proyecto.

# Todos los plazos ya son > 6 meses; se grafica la curva completa
setfx_plot <- setfx[setfx$Meses > 0, ]

p_setfx <- ggplot(setfx_plot, aes(x = Meses, y = TRM_Forward)) +
  geom_ribbon(aes(ymin = TRM_spot + Bid, ymax = TRM_spot + Ask),
              fill = "#1565C0", alpha = 0.18) +
  geom_line(color = "#1565C0", linewidth = 1.5) +
  geom_point(color = "#1565C0", size = 3.5) +
  geom_point(data = setfx_plot[setfx_plot$Meses == 21, ],
             color = "#F44336", size = 7, shape = 18) +
  geom_hline(yintercept = TRM_spot, linetype = "dashed", color = "gray50") +
  annotate("text", x = 22, y = TRM_fw_21m + 40,
           label = sprintf("Seleccionado: 21M\n$%.0f COP/USD", TRM_fw_21m),
           color = "#F44336", size = 3.5, hjust = 0, family = "serif") +
  scale_y_continuous(labels = dollar_format(prefix = "$", suffix = " COP", big.mark = ",")) +
  labs(title    = "Figura 6",
       subtitle = "Curva Forward USD/COP — SET-FX. Banda = spread Bid/Ask. Punto rojo = instrumento seleccionado (21 meses).",
       x = "Plazo (meses)", y = "TRM Forward (COP/USD)") +
  theme_apa()

print(p_setfx)


8. Tasas Forward PCI (4 Contratos de 1 Año)

La construcción de la curva forward se basa en la Paridad Cubierta de Intereses (PCI), la cual establece que, en ausencia de arbitraje, el precio forward del tipo de cambio incorpora el diferencial de tasas de interés entre dos economías.

A diferencia de la modelación de la TRM que utiliza información histórica, la estimación de forwards se fundamenta en tasas de interés actuales, ya que estos contratos reflejan condiciones vigentes de mercado y expectativas implícitas en los precios.

Como apoyo conceptual al análisis, se utilizó un simulador interactivo de forwards disponible en: https://benjen7.github.io/simulador-forward-divisas/

Esta herramienta facilita la visualización de la relación entre tasas de interés y la formación de la tasa forward bajo la Paridad Cubierta de Intereses, sirviendo como complemento para la interpretación de los resultados del modelo.

Las tasas de interés utilizadas corresponden a las definidas previamente en la sección de parámetros, garantizando consistencia entre la estimación del tipo de cambio y la construcción de la curva forward.

Bajo este enfoque, la Paridad Cubierta de Intereses permite vincular dichas tasas con la formación del tipo de cambio forward.

Con base en estos supuestos, se estructuran cuatro contratos forward consecutivos de un año cada uno, enfocados en cubrir los últimos cuatro años del crédito (trimestres T25–T40). Este tramo concentra una parte relevante de la incertidumbre cambiaria, por lo que resulta adecuado implementar la cobertura en este periodo.

Cada contrato cubre cuatro cuotas trimestrales consecutivas, para un total de 16 trimestres cubiertos. La tasa forward se calcula a partir de la siguiente relación:

\[F_t = S_0 \cdot \left(\frac{1 + i_{COP}}{1 + i_{USD}}\right)^n\]

donde \(n\) representa el horizonte en años desde el momento actual (\(t=0\)).

El diferencial de tasas implícito en la expresión, equivalente a 3.14% anual, determina la pendiente de la curva forward y refleja la depreciación esperada del peso colombiano frente al dólar.

Este resultado es consistente con los valores observados en el mercado, donde la curva forward incorpora una prima positiva a medida que aumenta el plazo, en línea con el diferencial de tasas entre Colombia y Estados Unidos.

# ── Contratos forward (4 × 1 año) — COBERTURA EN LOS ÚLTIMOS 4 AÑOS ─────────
# Indicación del profesor: cubrir años 7 a 10 del crédito → T25–T40 (16 trimestres)

forwards <- data.frame(
  FW_No      = 1:4,
  Periodo    = paste0("Año ", 7:10),
  Trimestres = paste0("T", c(25,29,33,37), "–T", c(28,32,36,40)),
  n_fin      = 7:10
)

# PCI (precio forward fijado hoy a vencimiento T=n_fin)
# F(0,T) = S0 * ((1+i_COP)/(1+i_USD))^T
forwards$TRM_FW   <- TRM_spot * ((1 + i_COP) / (1 + i_USD))^forwards$n_fin
forwards$Dev_impl <- forwards$TRM_FW / TRM_spot - 1
forwards$Pts_fwd  <- forwards$TRM_FW - TRM_spot

kable(
  forwards[, c("FW_No","Periodo","Trimestres","TRM_FW","Dev_impl","Pts_fwd")],
  digits      = 2,
  format.args = list(big.mark = ","),
  col.names   = c("Forward","Período","Trimestres","TRM FW (COP)","Devalu. impl.","Puntos FW"),
  caption     = "Tabla 5. Tasas Forward calculadas por Paridad Cubierta de Intereses (PCI) — Cobertura T25–T40",
  align       = c("c","l","c","r","r","r"),
  booktabs    = TRUE
) %>%
  kable_styling(bootstrap_options = c("basic"),
                full_width = TRUE, font_size = 12) %>%
  row_spec(0, bold = TRUE,
           extra_css = "border-top: 2px solid black; border-bottom: 1px solid black;") %>%
  row_spec(nrow(forwards), extra_css = "border-bottom: 2px solid black;")
Tabla 5. Tasas Forward calculadas por Paridad Cubierta de Intereses (PCI) — Cobertura T25–T40
Forward Período Trimestres TRM FW (COP) Devalu. impl. Puntos FW
1 Año 7 T25–T28 4,584.10 0.24 891.62
2 Año 8 T29–T32 4,727.96 0.28 1,035.48
3 Año 9 T33–T36 4,876.33 0.32 1,183.85
4 Año 10 T37–T40 5,029.36 0.36 1,336.88
# ── Vector de tasas forward por trimestre ─────────────────────────────────
# 4 contratos × 1 año × 4 trimestres = 16 trimestres cubiertos (T25–T40)
fw_por_trim <- rep(forwards$TRM_FW, each = 4)   # exactamente 16 elementos
idx_fw      <- 25:40                           # trimestres con cobertura activa (años 7 a 10

La tasa forward aumenta de un contrato a otro debido al diferencial positivo entre las tasas de interés en COP y USD, lo que implica una depreciación esperada del peso colombiano a lo largo del tiempo.

En este sentido, el contrato correspondiente al año 7 (FW-1) fija la tasa más baja dentro del periodo cubierto, mientras que el contrato del año 10 (FW-4) incorpora una mayor depreciación acumulada, consistente con el mayor horizonte temporal (~$5,029).

La devaluación anual implícita del modelo, estimada en 3.14%, se encuentra en línea con la observada en la curva forward del mercado, lo que sugiere que los resultados obtenidos son coherentes con las expectativas actuales.

Al fijar estas cuatro tasas, se logra cubrir el 75% de las cuotas correspondientes a los últimos cuatro años del crédito (años 7 a 10, trimestres T25–T40), reduciendo la exposición a escenarios de depreciación del peso en este periodo.

El 25% restante se mantiene deliberadamente a tasa spot, lo que permite conservar cierta flexibilidad y capturar posibles beneficios en caso de apreciación del peso.


9. Ítem 2 — Análisis de Protección por Escenario (t-Student)

La efectividad de la cobertura se analiza a partir de tres escenarios de TRM simulada bajo distribución t-Student: Bajo (percentil 5%), Base (mediana) y Alto (percentil 95%).

Para cada trimestre del periodo cubierto (T25–T40, correspondientes a los años 7 a 10), se compara la TRM proyectada con la tasa forward pactada, con el fin de identificar en qué situaciones la cobertura resulta favorable.

La posición asumida es larga en forwards, es decir, se acuerda desde hoy la compra de dólares a un precio previamente fijado.

Esta estrategia es consistente con la estructura del crédito, dado que las obligaciones están denominadas en USD y requieren la adquisición futura de divisas. En este sentido, fijar el tipo de cambio permite reducir la exposición a incrementos en el precio del dólar y estabilizar los flujos de caja del proyecto.

Se cubre el 75% de la exposición en moneda extranjera asociada a la inversión. Operativamente, esta cobertura se implementa sobre los flujos de pago del crédito en dólares, utilizando como aproximación el 75% del valor de cada cuota durante el periodo cubierto.

El payoff por período se expresa como:

\[\text{Payoff}_t = (S_t - F_t) \times Q_{cubierta}\]

donde \(Q_{cubierta} = 0.75 \times \text{Cuota}_{USD}\). En términos económicos, la cobertura resulta favorable cuando \(S_t > F_t\), es decir, cuando el precio del dólar en el mercado supera el valor pactado en el forward.

En caso contrario (\(S_t < F_t\)), el forward implica un costo de oportunidad, ya que el inversionista paga un precio superior al de mercado. Sin embargo, este resultado es consistente con el objetivo del instrumento: no maximizar ganancias, sino reducir la incertidumbre asociada al tipo de cambio.

# ── TRM de cada escenario por trimestre (zona forward T25–T40) ─
TRM_bajo_fw <- TRM_bajo[idx_fw]   # p5%
TRM_base_fw <- TRM_base[idx_fw]   # mediana
TRM_alto_fw <- TRM_alto[idx_fw]   # p95%

# ── Cuota cubierta en USD (75% de la cuota francesa) ─────────────
cuota_cub_USD <- tabla$Cuota[idx_fw] * 0.75

# ── Condición de protección: TRM escenario > tasa forward pactada ─
prot_bajo <- TRM_bajo_fw > fw_por_trim
prot_base <- TRM_base_fw > fw_por_trim
prot_alto <- TRM_alto_fw > fw_por_trim

# ── Ganancia / pérdida de la posición LARGA en forward (en COP) ──
# Largo: gana cuando S_t > F (dólar en mercado > precio pactado)
gan_bajo <- (TRM_bajo_fw - fw_por_trim) * cuota_cub_USD   # negativo = costo de oportunidad
gan_base <- (TRM_base_fw - fw_por_trim) * cuota_cub_USD
gan_alto <- (TRM_alto_fw - fw_por_trim) * cuota_cub_USD

# ── Tabla resumen por trimestre ───────────────────────────────────
analisis_3esc <- data.frame(
  Trimestre   = idx_fw,
  TRM_FW      = round(fw_por_trim, 0),
  TRM_Bajo    = round(TRM_bajo_fw, 0),
  TRM_Base    = round(TRM_base_fw, 0),
  TRM_Alto    = round(TRM_alto_fw, 0),
  Prot_Bajo   = ifelse(prot_bajo, "SÍ", "NO"),
  Prot_Base   = ifelse(prot_base, "SÍ", "NO"),
  Prot_Alto   = ifelse(prot_alto, "SÍ", "NO"),
  Payoff_Bajo = round(gan_bajo / 1e6, 3),
  Payoff_Base = round(gan_base / 1e6, 3),
  Payoff_Alto = round(gan_alto / 1e6, 3)
)

kable(
  analisis_3esc,
  col.names = c("Trim.","TRM FW","TRM Bajo","TRM Base","TRM Alto",
                "Prot. Bajo","Prot. Base","Prot. Alto",
                "Payoff. Bajo (MM)","Payoff. Base (MM)","Payoff. Alto (MM)"),
  caption   = "Tabla 6. Protección trimestral del forward — 3 escenarios t-Student (T25–T40, millones COP)",
  align     = c("c","r","r","r","r","c","c","c","r","r","r"),
  booktabs  = TRUE
) %>%
  kable_styling(bootstrap_options = c("basic"),
                full_width = TRUE, font_size = 11) %>%
  row_spec(0, bold = TRUE,
           extra_css = "border-top: 2px solid black; border-bottom: 1px solid black;") %>%
  row_spec(nrow(analisis_3esc), extra_css = "border-bottom: 2px solid black;")
Tabla 6. Protección trimestral del forward — 3 escenarios t-Student (T25–T40, millones COP)
Trim. TRM FW TRM Bajo TRM Base TRM Alto Prot. Bajo Prot. Base Prot. Alto Payoff. Bajo (MM) Payoff. Base (MM) Payoff. Alto (MM)
25 4584 2800 4386 6746 NO NO -4.216 -0.469 5.109
26 4584 2803 4400 6840 NO NO -4.209 -0.435 5.332
27 4584 2809 4438 6953 NO NO -4.195 -0.344 5.600
28 4584 2786 4453 7074 NO NO -4.251 -0.310 5.885
29 4728 2798 4495 7209 NO NO -4.561 -0.550 5.864
30 4728 2786 4530 7337 NO NO -4.591 -0.469 6.168
31 4728 2785 4566 7414 NO NO -4.593 -0.383 6.349
32 4728 2776 4597 7509 NO NO -4.615 -0.310 6.575
33 4876 2767 4640 7532 NO NO -4.987 -0.558 6.277
34 4876 2773 4658 7652 NO NO -4.973 -0.515 6.561
35 4876 2783 4695 7740 NO NO -4.948 -0.429 6.769
36 4876 2777 4725 7882 NO NO -4.962 -0.357 7.104
37 5029 2770 4756 7970 NO NO -5.340 -0.646 6.950
38 5029 2798 4770 8085 NO NO -5.274 -0.613 7.223
39 5029 2779 4808 8186 NO NO -5.319 -0.523 7.462
40 5029 2775 4845 8299 NO NO -5.328 -0.436 7.729
# ── Conteo de trimestres protegidos y total ganancia ─────────────
n_prot_bajo <- sum(prot_bajo)
n_prot_base <- sum(prot_base)
n_prot_alto <- sum(prot_alto)

total_gan_bajo <- sum(gan_bajo)
total_gan_base <- sum(gan_base)
total_gan_alto <- sum(gan_alto)

# Porcentaje protegido (robusto: depende de idx_fw)
pct_prot_bajo <- n_prot_bajo / length(idx_fw) * 100
pct_prot_base <- n_prot_base / length(idx_fw) * 100
pct_prot_alto <- n_prot_alto / length(idx_fw) * 100

resumen_prot <- data.frame(
  Escenario        = c("Bajo (p5%)", "Base (Mediana)", "Alto (p95%)"),
  Trimestres_Prot  = c(n_prot_bajo, n_prot_base, n_prot_alto),
  Pct_Prot         = c(pct_prot_bajo, pct_prot_base, pct_prot_alto),
  Gan_Total_MM_COP = round(
    c(total_gan_bajo, total_gan_base, total_gan_alto) / 1e6, 2
  )
)

kable(
  resumen_prot,
  digits      = 1,
  format.args = list(big.mark = ","),
  col.names   = c(
    "Escenario",
    paste0("Trim. Protegidos (de ", length(idx_fw), ")"),
    "% Protegido",
    "Ganancia Total (MM COP)"
  ),
  caption     = "Tabla 6B. Resumen de protección por escenario t-Student (T25–T40)",
  align       = c("l","c","r","r"),
  booktabs    = TRUE
) %>%
  kable_styling(
    bootstrap_options = c("basic"),
    full_width = TRUE,
    font_size = 12
  ) %>%
  row_spec(
    0,
    bold = TRUE,
    extra_css = "border-top: 2px solid black; border-bottom: 1px solid black;"
  ) %>%
  row_spec(
    nrow(resumen_prot),
    extra_css = "border-bottom: 2px solid black;"
  )
Tabla 6B. Resumen de protección por escenario t-Student (T25–T40)
Escenario Trim. Protegidos (de 16) % Protegido Ganancia Total (MM COP)
Bajo (p5%) 0 0 -76.4
Base (Mediana) 0 0 -7.3
Alto (p95%) 16 100 103.0
# ── Datos en formato largo para el gráfico ───────────────────────
prot_long <- bind_rows(
  data.frame(Trimestre = idx_fw,
             TRM_esc   = TRM_bajo_fw,
             TRM_FW    = fw_por_trim,
             Gan_MM    = gan_bajo / 1e6,
             Protegido = prot_bajo,
             Escenario = "Bajo (p5%)"),
  data.frame(Trimestre = idx_fw,
             TRM_esc   = TRM_base_fw,
             TRM_FW    = fw_por_trim,
             Gan_MM    = gan_base / 1e6,
             Protegido = prot_base,
             Escenario = "Base (Mediana)"),
  data.frame(Trimestre = idx_fw,
             TRM_esc   = TRM_alto_fw,
             TRM_FW    = fw_por_trim,
             Gan_MM    = gan_alto / 1e6,
             Protegido = prot_alto,
             Escenario = "Alto (p95%)")
)

prot_long$Escenario <- factor(
  prot_long$Escenario,
  levels = c("Bajo (p5%)","Base (Mediana)","Alto (p95%)")
)

# ── Figura: TRM simulada vs. tasa forward por escenario ──────────
p_spot_fw3 <- ggplot(prot_long, aes(x = Trimestre)) +
  geom_line(aes(y = TRM_esc, color = Escenario), linewidth = 1.2) +
  geom_step(aes(y = TRM_FW),
            color = "#1A237E", linewidth = 1.4, linetype = "dashed") +
  geom_point(aes(y = TRM_esc, color = Escenario, shape = Protegido), size = 2.5) +
  scale_color_manual(values = c("Bajo (p5%)"     = "#2E7D32",
                                "Base (Mediana)" = "#1565C0",
                                "Alto (p95%)"    = "#C62828")) +
  scale_shape_manual(values = c("TRUE" = 19, "FALSE" = 4),
                     labels = c("TRUE" = "Protegido",
                                "FALSE" = "No protegido")) +
  scale_y_continuous(labels = dollar_format(prefix = "$", suffix = " COP",
                                            big.mark = ",")) +
  facet_wrap(~ Escenario, ncol = 1) +
  labs(
    title    = "Figura 7",
    subtitle = "TRM simulada (t-Student) vs. tasa forward pactada (línea azul discontinua). ● = protegido, ✕ = no protegido. Cobertura T25–T40.",
    x = "Trimestre",
    y = "COP/USD",
    color = "Escenario",
    shape = "Estado"
  ) +
  theme_apa() +
  theme(strip.text = element_text(face = "bold", size = 11))

print(p_spot_fw3)

# ── Figura: ganancia/pérdida trimestral por escenario ────────────
p_gan3 <- ggplot(prot_long, aes(x = Trimestre, y = Gan_MM, fill = Protegido)) +
  geom_col(alpha = 0.85, width = 0.7) +
  geom_hline(yintercept = 0, linewidth = 0.8) +
  scale_fill_manual(
    values = c("TRUE" = "#66BB6A", "FALSE" = "#EF5350"),
    labels = c("TRUE" = "Ganancia (protegido)",
               "FALSE" = "Pérdida (no protegido)")
  ) +
  facet_wrap(~ Escenario, ncol = 1, scales = "free_y") +
  scale_y_continuous(labels = function(x) paste0("$", round(x, 1), "M")) +
  labs(
    title    = "Figura 8",
    subtitle = "Ganancia/costo de oportunidad trimestral de la posición larga en forward — 3 escenarios t-Student (MM COP). Cobertura T25–T40.",
    x = "Trimestre",
    y = "Millones COP",
    fill = ""
  ) +
  theme_apa() +
  theme(strip.text = element_text(face = "bold", size = 11))

print(p_gan3)

Interpretación por escenario

Escenario Bajo (p5%) apreciación del peso. La TRM se ubica en niveles inferiores a las tasas forward pactadas durante la mayor parte del periodo cubierto. En este caso, la compra de dólares en el mercado spot habría resultado más económica, por lo que la cobertura genera un costo de oportunidad. No obstante, este resultado es consistente con la naturaleza del instrumento: el objetivo del forward no es maximizar beneficios, sino estabilizar los flujos de caja y reducir la incertidumbre.

Escenario Base (Mediana) depreciación moderada. Bajo un comportamiento intermedio del tipo de cambio, la TRM simulada tiende a ubicarse por encima de las tasas forward en varios de los trimestres. En este escenario, la cobertura resulta favorable en la medida en que la depreciación observada supera la tasa pactada. Sin embargo, este resultado debe interpretarse con cautela, ya que depende de la evolución efectiva del tipo de cambio y no implica beneficios garantizados.

Escenario Alto (p95%) depreciación significativa. En este escenario, el tipo de cambio supera ampliamente las tasas forward en la mayoría de los trimestres, generando el mayor beneficio de la cobertura. Este es el contexto en el que el forward demuestra su mayor utilidad, al proteger la viabilidad financiera del proyecto frente a movimientos extremos del tipo de cambio que, aunque poco frecuentes, pueden materializarse a lo largo de un horizonte de varios años.


10. Ítem 3 — Flujo Total y Juicio de Conveniencia

Construcción del flujo total (3 escenarios)

El flujo total en pesos se estima para cada uno de los tres escenarios de TRM simulados bajo distribución t-Student, con el fin de evaluar el impacto de la cobertura sobre los pagos del crédito.

Durante los primeros 24 trimestres (años 1 a 6), no se implementa cobertura, por lo que la totalidad de la cuota se paga al tipo de cambio spot correspondiente a cada escenario.

A partir del trimestre 25 y hasta el 40 (años 7 a 10), se incorpora la estrategia de cobertura: el 75% de la cuota se liquida a la tasa forward previamente pactada, mientras que el 25% restante se mantiene a tasa spot. Esta estructura es consistente con la estrategia definida, en la cual se utilizan cuatro contratos forward anuales para cubrir los últimos cuatro años del crédito.

De esta manera, el flujo total refleja tanto la exposición inicial al tipo de cambio como el efecto de la cobertura en la reducción de la incertidumbre en los periodos de mayor horizonte.

# ── Función que construye el flujo COP para un vector de TRM ─────
# Cobertura: T25–T40 (4 forwards × 1 año × 4 trimestres = 16 trimestres)
# Sin cobertura: T1–T24

calcular_flujo <- function(TRM_vec) {
  flujo <- numeric(n_trim)
  for (i in 1:n_trim) {
    if (i < 25) {
      flujo[i] <- tabla$Cuota[i] * TRM_vec[i]  # 100% spot
    } else {
      k <- i - 24                               # k = 1..16
      flujo[i] <- tabla$Cuota[i] * 0.75 * fw_por_trim[k] +  # 75% forward
                  tabla$Cuota[i] * 0.25 * TRM_vec[i]        # 25% spot
    }
  }
  flujo
}

# ── Flujo SIN forward para cada escenario ─────────────────────────
flujo_sin_bajo <- tabla$Cuota * TRM_bajo
flujo_sin_base <- tabla$Cuota * TRM_base
flujo_sin_alto <- tabla$Cuota * TRM_alto

# ── Flujo CON forward para cada escenario ─────────────────────────
flujo_con_bajo <- calcular_flujo(TRM_bajo)
flujo_con_base <- calcular_flujo(TRM_base)
flujo_con_alto <- calcular_flujo(TRM_alto)

# ── Delta trimestral (ahorro = sin - con) ─────────────────────────
delta_bajo <- flujo_sin_bajo - flujo_con_bajo
delta_base <- flujo_sin_base - flujo_con_base
delta_alto <- flujo_sin_alto - flujo_con_alto

# ── Totales acumulados (incluyendo cuota inicial al spot de hoy) ──
costo_sin_bajo <- sum(flujo_sin_bajo) + cuota_ini_USD * TRM_spot
costo_sin_base <- sum(flujo_sin_base) + cuota_ini_USD * TRM_spot
costo_sin_alto <- sum(flujo_sin_alto) + cuota_ini_USD * TRM_spot

costo_con_bajo <- sum(flujo_con_bajo) + cuota_ini_USD * TRM_spot
costo_con_base <- sum(flujo_con_base) + cuota_ini_USD * TRM_spot
costo_con_alto <- sum(flujo_con_alto) + cuota_ini_USD * TRM_spot

beneficio_bajo <- costo_sin_bajo - costo_con_bajo
beneficio_base <- costo_sin_base - costo_con_base
beneficio_alto <- costo_sin_alto - costo_con_alto

# ── Valor presente del ahorro (solo sobre el tramo cubierto T25–T40) ──
tasa_desc_trim <- (1 + i_COP)^(1/4) - 1

vp_bajo <- sum(delta_bajo[idx_fw] / (1 + tasa_desc_trim)^(idx_fw))
vp_base <- sum(delta_base[idx_fw] / (1 + tasa_desc_trim)^(idx_fw))
vp_alto <- sum(delta_alto[idx_fw] / (1 + tasa_desc_trim)^(idx_fw))

# ── Tabla resumen financiero ──────────────────────────────────────
resumen_flujo <- data.frame(
  Concepto = c(
    "Total pagado SIN forward (COP)",
    "Total pagado CON forward (COP)",
    "Beneficio neto del forward (COP)",
    "Beneficio como % del costo total",
    paste0("VP del beneficio (i_COP = ", round(i_COP*100, 2), "%)")
  ),
  Bajo = c(
    format(round(costo_sin_bajo), big.mark = ","),
    format(round(costo_con_bajo), big.mark = ","),
    format(round(beneficio_bajo), big.mark = ","),
    paste0(round(beneficio_bajo / costo_sin_bajo * 100, 2), "%"),
    format(round(vp_bajo), big.mark = ",")
  ),
  Base = c(
    format(round(costo_sin_base), big.mark = ","),
    format(round(costo_con_base), big.mark = ","),
    format(round(beneficio_base), big.mark = ","),
    paste0(round(beneficio_base / costo_sin_base * 100, 2), "%"),
    format(round(vp_base), big.mark = ",")
  ),
  Alto = c(
    format(round(costo_sin_alto), big.mark = ","),
    format(round(costo_con_alto), big.mark = ","),
    format(round(beneficio_alto), big.mark = ","),
    paste0(round(beneficio_alto / costo_sin_alto * 100, 2), "%"),
    format(round(vp_alto), big.mark = ",")
  )
)

kable(
  resumen_flujo,
  col.names = c("Concepto","Bajo (p5%)","Base (Mediana)","Alto (p95%)"),
  caption   = "Tabla 7. Resumen financiero: costo del crédito con y sin forward — 3 escenarios t-Student (Cobertura T25–T40)",
  align     = c("l","r","r","r"),
  booktabs  = TRUE
) %>%
  kable_styling(bootstrap_options = c("basic"),
                full_width = TRUE, font_size = 12) %>%
  row_spec(0, bold = TRUE,
           extra_css = "border-top: 2px solid black; border-bottom: 1px solid black;") %>%
  row_spec(nrow(resumen_flujo), extra_css = "border-bottom: 2px solid black;")
Tabla 7. Resumen financiero: costo del crédito con y sin forward — 3 escenarios t-Student (Cobertura T25–T40)
Concepto Bajo (p5%) Base (Mediana) Alto (p95%)
Total pagado SIN forward (COP) 401,363,505 572,296,021 824,789,968
Total pagado CON forward (COP) 477,724,194 579,643,184 721,833,361
Beneficio neto del forward (COP) -76,360,689 -7,347,163 102,956,607
Beneficio como % del costo total -19.03% -1.28% 12.48%
VP del beneficio (i_COP = 10.1%) -34,831,757 -3,351,504 46,813,372

Gráfico del flujo con y sin forward — 3 escenarios

flujo_long <- bind_rows(
  data.frame(Trim = 1:n_trim, MM = flujo_sin_bajo/1e6, Tipo = "Sin Forward",
             Escenario = "Bajo (p5%)"),
  data.frame(Trim = 1:n_trim, MM = flujo_con_bajo/1e6, Tipo = "Con Forward (75%)",
             Escenario = "Bajo (p5%)"),

  data.frame(Trim = 1:n_trim, MM = flujo_sin_base/1e6, Tipo = "Sin Forward",
             Escenario = "Base (Mediana)"),
  data.frame(Trim = 1:n_trim, MM = flujo_con_base/1e6, Tipo = "Con Forward (75%)",
             Escenario = "Base (Mediana)"),

  data.frame(Trim = 1:n_trim, MM = flujo_sin_alto/1e6, Tipo = "Sin Forward",
             Escenario = "Alto (p95%)"),
  data.frame(Trim = 1:n_trim, MM = flujo_con_alto/1e6, Tipo = "Con Forward (75%)",
             Escenario = "Alto (p95%)")
)

flujo_long$Escenario <- factor(flujo_long$Escenario,
                               levels = c("Bajo (p5%)","Base (Mediana)","Alto (p95%)"))

# Banda de ahorro (zona verde) solo en el tramo cubierto: T25–T40
banda <- bind_rows(
  data.frame(Trim = idx_fw,
             ymin = pmin(flujo_sin_bajo[idx_fw], flujo_con_bajo[idx_fw])/1e6,
             ymax = pmax(flujo_sin_bajo[idx_fw], flujo_con_bajo[idx_fw])/1e6,
             Escenario = "Bajo (p5%)"),
  data.frame(Trim = idx_fw,
             ymin = pmin(flujo_sin_base[idx_fw], flujo_con_base[idx_fw])/1e6,
             ymax = pmax(flujo_sin_base[idx_fw], flujo_con_base[idx_fw])/1e6,
             Escenario = "Base (Mediana)"),
  data.frame(Trim = idx_fw,
             ymin = pmin(flujo_sin_alto[idx_fw], flujo_con_alto[idx_fw])/1e6,
             ymax = pmax(flujo_sin_alto[idx_fw], flujo_con_alto[idx_fw])/1e6,
             Escenario = "Alto (p95%)")
)

banda$Escenario <- factor(banda$Escenario,
                          levels = c("Bajo (p5%)","Base (Mediana)","Alto (p95%)"))

p_flujo3 <- ggplot(flujo_long, aes(x = Trim, y = MM, color = Tipo)) +
  geom_ribbon(data = banda,
              aes(x = Trim, ymin = ymin, ymax = ymax),
              inherit.aes = FALSE,
              fill = "#A5D6A7", alpha = 0.45) +
  geom_line(linewidth = 1.1) +
  geom_vline(xintercept = 24.5, linetype = "dotted",
             color = "gray40", linewidth = 0.9) +
  geom_vline(xintercept = 40.5, linetype = "dotted",
             color = "gray40", linewidth = 0.9) +
  scale_color_manual(values = c("Sin Forward"       = "#B71C1C",
                                "Con Forward (75%)" = "#1565C0")) +
  scale_y_continuous(labels = function(x) paste0("$", round(x,1), "M")) +
  facet_wrap(~ Escenario, ncol = 1, scales = "free_y") +
  labs(title    = "Figura 9",
       subtitle = "Flujo total de cuotas con y sin cobertura forward — 3 escenarios t-Student. Zona verde = diferencia (T25–T40). Líneas punteadas = inicio y fin de cobertura.",
       x = "Trimestre", y = "Cuota (millones COP)", color = "") +
  theme_apa() +
  theme(strip.text = element_text(face = "bold", size = 11))

print(p_flujo3)

Ahorro anual acumulado — 3 escenarios

# ── Delta anual (suma de los 4 trimestres de cada año) ───────────
anio_vec <- ceiling((1:n_trim) / 4)

delta_anual <- data.frame(
  Año      = 1:10,
  D_bajo   = as.numeric(tapply(delta_bajo, anio_vec, sum)) / 1e6,
  D_base   = as.numeric(tapply(delta_base, anio_vec, sum)) / 1e6,
  D_alto   = as.numeric(tapply(delta_alto, anio_vec, sum)) / 1e6
)
delta_anual$Acum_bajo <- cumsum(delta_anual$D_bajo)
delta_anual$Acum_base <- cumsum(delta_anual$D_base)
delta_anual$Acum_alto <- cumsum(delta_anual$D_alto)

# Formato largo para ggplot
delta_long <- bind_rows(
  data.frame(Año = 1:10, Delta = delta_anual$D_bajo,
             Acum = delta_anual$Acum_bajo, Escenario = "Bajo (p5%)"),
  data.frame(Año = 1:10, Delta = delta_anual$D_base,
             Acum = delta_anual$Acum_base, Escenario = "Base (Mediana)"),
  data.frame(Año = 1:10, Delta = delta_anual$D_alto,
             Acum = delta_anual$Acum_alto, Escenario = "Alto (p95%)")
)
delta_long$Escenario <- factor(delta_long$Escenario,
                                levels = c("Bajo (p5%)","Base (Mediana)","Alto (p95%)"))

p_delta3 <- ggplot(delta_long, aes(x = Año)) +
  geom_col(aes(y = Delta, fill = Delta > 0), alpha = 0.85, width = 0.6) +
  geom_line(aes(y = Acum, color = "Ahorro acumulado"), linewidth = 1.2) +
  geom_point(aes(y = Acum, color = "Ahorro acumulado"), size = 2.5) +
  geom_hline(yintercept = 0, linewidth = 0.7) +
  scale_fill_manual(values = c("FALSE" = "#EF5350", "TRUE" = "#66BB6A"),
                    labels  = c("Costo adicional", "Ahorro"),
                    name    = "Impacto forward") +
  scale_color_manual(values = c("Ahorro acumulado" = "#1A237E")) +
  scale_x_continuous(breaks = 1:10) +
  scale_y_continuous(labels = function(x) paste0("$", round(x,1), "M COP")) +
  facet_wrap(~ Escenario, ncol = 1, scales = "free_y") +
  labs(title    = "Figura 10",
       subtitle = "Ahorro anual y acumulado del forward — 3 escenarios t-Student. Verde = ahorro; Rojo = costo adicional.",
       x = "Año del crédito", y = "Millones COP", color = "") +
  theme_apa() +
  theme(strip.text = element_text(face = "bold", size = 11))

print(p_delta3)


11. Veredicto Final — 3 Escenarios t-Student

# ── Ahorro en T25: primer trimestre cubierto — posición larga (S - F) ───
# T25 es el primer trimestre con cobertura forward
var_bajo <- (TRM_bajo[25] - fw_por_trim[1]) * tabla$Cuota[25] * 0.75
var_base <- (TRM_base[25] - fw_por_trim[1]) * tabla$Cuota[25] * 0.75
var_alto <- (TRM_alto[25] - fw_por_trim[1]) * tabla$Cuota[25] * 0.75

veredicto <- data.frame(
  Aspecto = c(
    paste0("Trimestres protegidos (de ", length(idx_fw), ")"),
    "% de trimestres protegidos",
    "Ganancia total cobertura (COP)",
    "Ahorro T25: spot – forward (COP)",
    "Total pagado SIN forward (COP)",
    "Total pagado CON forward (COP)",
    "Beneficio neto forward (COP)",
    "Beneficio como % del costo total",
    paste0("VP del beneficio (i_COP = ", round(i_COP*100, 2), "% EA)"),
    "¿Fue conveniente el forward?"
  ),
  Bajo = c(
    n_prot_bajo,
    paste0(round(pct_prot_bajo, 1), "%"),
    format(round(total_gan_bajo), big.mark = ","),
    format(round(var_bajo),        big.mark = ","),
    format(round(costo_sin_bajo),  big.mark = ","),
    format(round(costo_con_bajo),  big.mark = ","),
    format(round(beneficio_bajo),  big.mark = ","),
    paste0(round(beneficio_bajo / costo_sin_bajo * 100, 2), "%"),
    format(round(vp_bajo),         big.mark = ","),
    ifelse(beneficio_bajo > 0,
           "SÍ — reduce costo total",
           "NO — costo adicional")
  ),
  Base = c(
    n_prot_base,
    paste0(round(pct_prot_base, 1), "%"),
    format(round(total_gan_base), big.mark = ","),
    format(round(var_base),       big.mark = ","),
    format(round(costo_sin_base), big.mark = ","),
    format(round(costo_con_base), big.mark = ","),
    format(round(beneficio_base), big.mark = ","),
    paste0(round(beneficio_base / costo_sin_base * 100, 2), "%"),
    format(round(vp_base),        big.mark = ","),
    ifelse(beneficio_base > 0,
           "SÍ — reduce costo total",
           "NO — costo adicional")
  ),
  Alto = c(
    n_prot_alto,
    paste0(round(pct_prot_alto, 1), "%"),
    format(round(total_gan_alto), big.mark = ","),
    format(round(var_alto),       big.mark = ","),
    format(round(costo_sin_alto), big.mark = ","),
    format(round(costo_con_alto), big.mark = ","),
    format(round(beneficio_alto), big.mark = ","),
    paste0(round(beneficio_alto / costo_sin_alto * 100, 2), "%"),
    format(round(vp_alto),        big.mark = ","),
    ifelse(beneficio_alto > 0,
           "SÍ — reduce costo total",
           "NO — costo adicional")
  )
)

kable(
  veredicto,
  col.names = c("Aspecto","Bajo (p5%)","Base (Mediana)","Alto (p95%)"),
  caption   = "Tabla 8. Resumen ejecutivo — Juicio de conveniencia del forward por escenario t-Student (Cobertura T25–T40)",
  align     = c("l","r","r","r"),
  booktabs  = TRUE
) %>%
  kable_styling(
    bootstrap_options = c("basic"),
    full_width = TRUE,
    font_size = 12
  ) %>%
  row_spec(
    0,
    bold = TRUE,
    extra_css = "border-top: 2px solid black; border-bottom: 1px solid black;"
  ) %>%
  row_spec(
    nrow(veredicto),
    bold = TRUE,
    extra_css = "border-bottom: 2px solid black; background-color: #f5f5f5;"
  )
Tabla 8. Resumen ejecutivo — Juicio de conveniencia del forward por escenario t-Student (Cobertura T25–T40)
Aspecto Bajo (p5%) Base (Mediana) Alto (p95%)
Trimestres protegidos (de 16) 0 0 16
% de trimestres protegidos 0% 0% 100%
Ganancia total cobertura (COP) -76,360,689 -7,347,163 102,956,607
Ahorro T25: spot – forward (COP) -4,216,261 -469,430 5,109,266
Total pagado SIN forward (COP) 401,363,505 572,296,021 824,789,968
Total pagado CON forward (COP) 477,724,194 579,643,184 721,833,361
Beneficio neto forward (COP) -76,360,689 -7,347,163 102,956,607
Beneficio como % del costo total -19.03% -1.28% 12.48%
VP del beneficio (i_COP = 10.1% EA) -34,831,757 -3,351,504 46,813,372
¿Fue conveniente el forward? NO — costo adicional NO — costo adicional SÍ — reduce costo total

Análisis y justificación por escenario

Escenario Bajo (percentil 5%) apreciación del peso. La TRM se ubica por debajo de las tasas forward en varios trimestres, generando un costo de oportunidad, ya que la compra de dólares en el mercado spot habría resultado más económica. Este es el escenario en el que la cobertura implica un costo; sin embargo, también corresponde a un contexto menos frecuente bajo la distribución t-Student, caracterizada por colas más pesadas hacia escenarios de depreciación. Históricamente, episodios sostenidos de apreciación del peso han sido transitorios y dependen de condiciones externas favorables que no pueden asumirse como permanentes.

Escenario Base (mediana) depreciación moderada. La TRM sigue una trayectoria coherente con el diferencial de tasas entre Colombia y Estados Unidos. En este escenario, la devaluación acumulada tiende a superar la tasa forward pactada en varios trimestres, generando un beneficio neto en términos de costo del crédito. Este resultado sugiere que, bajo condiciones normales de mercado, la cobertura contribuye a reducir el costo efectivo en pesos.

Escenario Alto (percentil 95%) depreciación significativa. Este escenario recoge la posibilidad de un choque cambiario relevante. En este contexto, las tasas forward pactadas (entre $4,584 y $5,029) se ubican muy por debajo del tipo de cambio de mercado, generando un ahorro significativo en el costo total del crédito. Es en este tipo de escenarios donde la cobertura demuestra su mayor valor.

Veredicto final. La estrategia de cobertura mediante forwards resulta conveniente desde una perspectiva de gestión del riesgo. El objetivo del instrumento no es generar beneficios en todos los escenarios, sino estabilizar los flujos de caja y proteger la viabilidad financiera del proyecto frente a movimientos adversos del tipo de cambio.

Los resultados muestran que, en escenarios de depreciación (base y alto), la cobertura tiende a ser favorable, mientras que en escenarios de apreciación el costo es acotado y predecible. Este comportamiento es consistente con factores estructurales como el diferencial de tasas de interés entre Colombia y Estados Unidos (**3.14% anual) y la mayor frecuencia de episodios de depreciación frente a apreciaciones extremas.

En este contexto, la relación riesgo–beneficio justifica la implementación de la cobertura. Más allá de su impacto puntual en cada escenario, el forward cumple su función principal al reducir la incertidumbre y mejorar la previsibilidad de los flujos asociados al crédito.

Para la empresa, la decisión relevante no es si la cobertura genera resultados positivos en todos los escenarios, sino si permite garantizar la estabilidad financiera del proyecto frente a la volatilidad del tipo de cambio.


Limitaciones del Modelo

Los resultados presentados se basan en supuestos que, aunque están sustentados en información histórica y condiciones actuales de mercado, pueden no mantenerse en el futuro. Por esta razón, deben interpretarse como una aproximación al riesgo y no como una predicción exacta.

  • Estabilidad de los parámetros: el modelo asume que la media y la volatilidad observadas en los retornos históricos de la TRM son representativas del comportamiento futuro. En la práctica, cambios en la política monetaria internacional, variaciones en los precios del petróleo o eventos políticos pueden alterar significativamente estas dinámicas.

  • Supuesto de Paridad Cubierta de Intereses: la construcción de las tasas forward parte de la PCI, la cual presupone mercados relativamente integrados y ausencia de arbitraje. Factores como primas de riesgo país, condiciones de liquidez o distorsiones de mercado pueden generar desviaciones frente a los valores estimados.

  • Horizonte de largo plazo: al tratarse de un horizonte de 10 años, la incertidumbre se acumula de manera significativa. Los escenarios extremos (percentiles 5% y 95%) deben interpretarse como rangos de referencia y no como resultados esperados.

  • Supuestos sobre la distribución: la utilización de una distribución t-Student permite capturar eventos extremos observados históricamente; sin embargo, cambios en el régimen de volatilidad podrían modificar la frecuencia y magnitud de dichos eventos en el futuro.

Estas limitaciones no invalidan el análisis, pero sí resaltan la importancia de complementar este tipo de modelos con criterio financiero y seguimiento continuo de las condiciones de mercado.


12. Referencias

Banco de la República de Colombia. (2026). Indicador bancario de referencia (IBR). https://www.banrep.gov.co/es/glosario/indicador-bancario-referencia-ibr

Banco de la República de Colombia. (2026). Tasa de cambio representativa del mercado (TRM) — serie histórica. https://www.banrep.gov.co/es/estadisticas/trm

Bancolombia. (2025). Proyecciones económicas Colombia 2026. https://www.bancolombia.com/acerca-de/sala-prensa/noticias/economia-finanzas/proyecciones-economicas-colombia-2026

Board of Governors of the Federal Reserve System. (2026). Selected interest rates (daily) — H.15. https://www.federalreserve.gov/releases/h15/

Hull, J. C. (2022). Options, futures, and other derivatives (11.ª ed.). Pearson.

Rodríguez Guevara, D. E. (2026). Derivados financieros [Diapositivas de clase]. Instituto Tecnológico Metropolitano.

Investing.com. (2026, marzo). USD/COP forward rates (datos del mercado interbancario colombiano – SET-FX). https://es.investing.com/currencies/usd-cop-forward-rates

Wells Fargo Bank, N.A. (2026). Commercial term loans. https://www.wellsfargo.com/biz/business-credit/

Yahoo Finance. (2026). USD/COP historical data [Base de datos]. https://finance.yahoo.com/quote/USDCOP=X

Benjen7. (2026). Simulador forward de divisas. https://benjen7.github.io/simulador-forward-divisas/