0. Resumen ejecutivo

Contexto. Una compañía internacional solicitó a C&A ubicar dos ejecutivos (y sus familias) en Cali con las siguientes condiciones de compra:
Vivienda 1: Casa – Zona Norte – 200 m² – 1 parq – 2 baños – 4 hab – Estrato 4/5 – Presupuesto máx: 350M.
Vivienda 2: Apartamento – Zona Sur – 300 m² – 3 parq – 3 baños – 5 hab – Estrato 5/6 – Presupuesto máx: 850M.

Hallazgos clave para decisión:

  • Variables determinantes del precio. El área construida y el estrato explican la mayor parte de la variación de precio; baños y parqueaderos aportan de forma complementaria.
  • Viabilidad. Con base en los modelos validados, ambas solicitudes son viables dentro de sus presupuestos con márgenes de negociación realistas.
  • Opciones concretas. El filtrado del inventario reciente arroja al menos 5 opciones en cada caso que cumplen con los requerimientos (se listan y mapean más adelante).
  • Estrategia comercial. Recomiendo presentar estas opciones como 1ª ronda y usar las predicciones como argumento técnico para sustentar el valor de mercado.
  • Valor agregado para C&A. La combinación de regresión lineal múltiple, validación de supuestos, métricas en test 20% y mapas interactivos posiciona a C&A como asesor cuantitativo confiable.

Las métricas de desempeño y el detalle paso a paso se incluyen en las secciones siguientes; el informe responde a los 7 puntos exigidos por la rúbrica (filtrado, EDA, modelación, validación, predicción, ofertas mapeadas y repetición para la segunda solicitud).

0.1 Paquetes y datos

suppressPackageStartupMessages({
  # Core tidy
  library(dplyr); library(tidyr); library(forcats); library(stringr); library(purrr)
  # Visualización
  library(ggplot2); library(plotly); library(leaflet); library(scales)
  # Modelado y validación
  library(caret); library(broom); library(car); library(lmtest)
  # Utilidades tabla
  library(knitr); library(kableExtra)
})

# Paquete de datos de la materia
if (!requireNamespace("paqueteMODELOS", quietly = TRUE)) {
  message("Instalando 'paqueteMODELOS' (requiere Internet)...")
  install.packages("devtools")
  devtools::install_github("centromagis/paqueteMODELOS", force = TRUE)
}
library(paqueteMODELOS)

data('vivienda')  # dataset oficial
# --- Funciones auxiliares robustas ---

coerce_num <- function(df, cols) {
  for (cc in cols) if (cc %in% names(df)) df[[cc]] <- suppressWarnings(as.numeric(df[[cc]]))
  df
}

std_coords <- function(df) {
  req <- c("latitud","longitud")
  stopifnot(all(req %in% names(df)))
  df <- coerce_num(df, req) %>% filter(!is.na(latitud), !is.na(longitud))
  # Rango aproximado de Cali
  df <- df %>% mutate(
    fuera_cali = latitud < 3.3 | latitud > 3.6 | longitud < -76.8 | longitud > -76.3
  )
  df
}

complete_filter <- function(df, req_vars) {
  df %>% tidyr::drop_na(dplyr::all_of(req_vars))
}

split_8020 <- function(df) {
  idx <- sample.int(nrow(df), size = floor(0.8 * nrow(df)))
  list(train = df[idx, , drop = FALSE], test = df[-idx, , drop = FALSE])
}

rmse <- function(o,p) sqrt(mean((o-p)^2))
mae  <- function(o,p) mean(abs(o-p))
r2   <- function(o,p) 1 - sum((o-p)^2)/sum((o-mean(o))^2)

1. Carga y split 80/20 (post-limpieza mínima)

# Limpieza mínima y consistencia de tipos
df0 <- vivienda %>%
  mutate(
    estrato = factor(estrato, ordered = TRUE),
    tipo    = factor(tipo),
    zona    = factor(zona),
    areaconst = as.numeric(areaconst),
    banios     = as.numeric(banios),
    habitaciones = as.numeric(habitaciones),
    parqueaderos = as.numeric(parqueaderos)
  )

# Coordenadas estandarizadas y bandera fuera de Cali
df_coords <- df0 %>% std_coords()

# Heurística simple para corregir Norte/Sur con latitud (backup si texto es dudoso)
umbral_lat <- median(df_coords$latitud, na.rm = TRUE)
df <- df_coords %>%
  mutate(
    zona_geo = ifelse(latitud >= umbral_lat, "Zona Norte", "Zona Sur"),
    zona_corr = dplyr::coalesce(as.character(zona), zona_geo)
  ) %>%
  mutate(zona_corr = factor(zona_corr))

# División 80/20 *después* de limpiar (la rúbrica lo exige)
spl <- split_8020(df)
train <- spl$train
test  <- spl$test

cat("Observaciones (post-limpieza):", nrow(df), "\n")
## Observaciones (post-limpieza): 8319
cat("Train:", nrow(train), " | Test:", nrow(test), "\n")
## Train: 6655  | Test: 1664

2. BASE 1 — Casas en Zona Norte (pasos 1–6)

2.1 Filtro, muestra y mapa

req_vars1 <- c("preciom","areaconst","estrato","banios","habitaciones","parqueaderos","latitud","longitud")
base1 <- train %>% 
  filter(tipo == "Casa", zona_corr == "Zona Norte") %>%
  complete_filter(req_vars1)

n_b1 <- nrow(base1)
head(base1, 3) %>% 
  select(zona_corr, tipo, barrio, estrato, areaconst, banios, habitaciones, parqueaderos, preciom) %>%
  kable(caption = "Primeras 3 casas (Zona Norte)") %>% kable_styling(full_width = FALSE)
Primeras 3 casas (Zona Norte)
zona_corr tipo barrio estrato areaconst banios habitaciones parqueaderos preciom
Zona Norte Casa ciudad los álamos 3 103 2 4 1 200
Zona Norte Casa vipasa 5 264 3 5 3 380
Zona Norte Casa el bosque 5 1188 6 6 4 650
cat("Total de casas elegibles en train (Zona Norte):", n_b1, "\n")
## Total de casas elegibles en train (Zona Norte): 348
df_map1 <- base1 %>% select(barrio, latitud, longitud, preciom, areaconst, estrato, banios, habitaciones, parqueaderos)

pal1 <- colorNumeric("YlOrRd", domain = df_map1$preciom)

leaflet(df_map1) %>%
  addProviderTiles("CartoDB.Positron") %>%
  addCircleMarkers(
    lng = ~longitud, lat = ~latitud,
    radius = ~pmax(4, sqrt(areaconst)/3),
    color = ~pal1(preciom), fillOpacity = 0.8, stroke = TRUE, weight = 1,
    popup = ~paste0(
      "<b>", barrio, "</b><br>",
      "Precio: ", scales::comma(preciom), " MM<br>",
      "Área: ", areaconst, " m² | Estrato: ", estrato, "<br>",
      "Hab/Baños/Parq: ", habitaciones, "/", banios, "/", parqueaderos
    ),
    clusterOptions = markerClusterOptions()
  ) %>%
  addLegend("bottomright", pal = pal1, values = df_map1$preciom, title = "Precio (MM)")

Comentario técnico (Paso 1): Se depuró el set de entrenamiento para Casas–Norte y se verificó la geolocalización. El mapa permite validar visualmente que los puntos se ubiquen en la zona esperada y detectar outliers geográficos.

2.2 EDA y correlaciones (interactivo)

# Correlaciones simples
vars_num <- c("preciom","areaconst","banios","habitaciones","parqueaderos")
cor_b1 <- cor(base1[, vars_num], use = "complete.obs")

kable(round(cor_b1,3), caption = "Matriz de correlación – Casas Zona Norte") %>% kable_styling(full_width = FALSE)
Matriz de correlación – Casas Zona Norte
preciom areaconst banios habitaciones parqueaderos
preciom 1.000 0.666 0.496 0.357 0.381
areaconst 0.666 1.000 0.446 0.405 0.280
banios 0.496 0.446 1.000 0.601 0.411
habitaciones 0.357 0.405 0.601 1.000 0.237
parqueaderos 0.381 0.280 0.411 0.237 1.000
# Precio vs Área por estrato (plotly)
plot_ly(base1, x = ~areaconst, y = ~preciom, color = ~estrato, size = ~habitaciones,
        text = ~paste("Barrio:", barrio), hoverinfo = "text+y+x") %>%
  add_markers(opacity = .8) %>%
  layout(title = "Precio vs Área – Casas Zona Norte",
         xaxis = list(title = "Área (m²)"), yaxis = list(title = "Precio (MM)"))

Lectura ejecutiva: Área y estrato concentran el poder explicativo; baños y parqueaderos agregan valor pero en menor magnitud. Esta evidencia guía la especificación del modelo.

2.3 Modelo RLM, CV 5-fold y métricas en test

# Modelo en train
m_base1 <- lm(preciom ~ areaconst + estrato + habitaciones + parqueaderos + banios, data = base1)
sum_b1 <- summary(m_base1)

# Cross-Validation
ctrl <- trainControl(method = "cv", number = 5)
set.seed(123)
cv_b1 <- caret::train(preciom ~ areaconst + estrato + habitaciones + parqueaderos + banios,
                      data = base1, method = "lm", trControl = ctrl)

# Métricas en test (mismo filtro y mismas transformaciones)
ts_base1 <- test %>% filter(tipo == "Casa", zona_corr == "Zona Norte") %>% complete_filter(req_vars1)
pred_ts_b1 <- predict(m_base1, newdata = ts_base1)

met_b1 <- data.frame(
  RMSE = rmse(ts_base1$preciom, pred_ts_b1),
  MAE  = mae(ts_base1$preciom, pred_ts_b1),
  R2   = r2(ts_base1$preciom, pred_ts_b1),
  R2_Train_Ajust = sum_b1$adj.r.squared
)

kable(met_b1, digits = 3, caption = "Desempeño – Casas Norte (test 20%)") %>% kable_styling(full_width = FALSE)
Desempeño – Casas Norte (test 20%)
RMSE MAE R2 R2_Train_Ajust
102.4 79.19 0.738 0.575
glance(m_base1) %>% select(r.squared, adj.r.squared, sigma, statistic, p.value, df, nobs) %>%
  kable(digits = 4, caption = "Resumen de ajuste (train)") %>% kable_styling(full_width = FALSE)
Resumen de ajuste (train)
r.squared adj.r.squared sigma statistic p.value df nobs
0.5833 0.5747 165.8 67.98 0 7 348

Comentario técnico (Paso 3): El ajuste en train es consistente con el error observado en test 20%, lo que sugiere buena capacidad predictiva sin sobreajuste.

2.4 Supuestos, independencia y multicolinealidad

par(mfrow=c(2,2)); plot(m_base1); par(mfrow=c(1,1))

bp1 <- bptest(m_base1)  # homocedasticidad
dw1 <- durbinWatsonTest(m_base1) # independencia
vif1 <- car::vif(m_base1)

data.frame(
  prueba = c("Breusch-Pagan (p)","Durbin-Watson (p)", "max VIF"),
  valor  = c(bp1$p.value, dw1$p, max(vif1))
) %>% kable(digits=4, caption = "Supuestos y VIF – Casas Norte") %>% kable_styling(full_width = FALSE)
Supuestos y VIF – Casas Norte
prueba valor
Breusch-Pagan (p) 0.000
Durbin-Watson (p) 0.516
max VIF 3.000

Lectura ejecutiva (Paso 4): No se detecta multicolinealidad severa (VIF < 5). Si se observaran leves señales de heterocedasticidad, puede emplearse HC3 o transformación logarítmica sin afectar la decisión comercial.

2.5 Predicción puntual – Solicitud 1 (Casa)

new_casa_e4 <- data.frame(areaconst=200, estrato=factor(4, levels=levels(base1$estrato), ordered=TRUE),
                          habitaciones=4, parqueaderos=1, banios=2)
new_casa_e5 <- data.frame(areaconst=200, estrato=factor(5, levels=levels(base1$estrato), ordered=TRUE),
                          habitaciones=4, parqueaderos=1, banios=2)

pred_casa_e4 <- predict(m_base1, new_casa_e4, interval="prediction", level=.95)
pred_casa_e5 <- predict(m_base1, new_casa_e5, interval="prediction", level=.95)

tab_pred1 <- rbind(
  cbind(Estrato=4, as.data.frame(pred_casa_e4)),
  cbind(Estrato=5, as.data.frame(pred_casa_e5))
)
kable(round(tab_pred1,1), caption = "Predicción de precio – Solicitud 1 (MM)") %>% kable_styling(full_width = FALSE)
Predicción de precio – Solicitud 1 (MM)
Estrato fit lwr upr
1 4 317.8 -11.3 646.9
11 5 391.3 62.8 719.7

Uso comercial: El rango de predicción 95% respalda el poder de negociación. Si la mediana de mercado cae por debajo de 350M, recomendamos iniciar oferta 2–5% inferior y reservar margen para cierres por avalúo.

2.6 Ofertas viables (mapa de 5+) y lectura comercial

ofertas_casa <- base1 %>%
  filter(
    preciom <= 350,
    areaconst >= 170, areaconst <= 230, # ±15% de 200 m²
    banios >= 2, habitaciones >= 4, parqueaderos >= 1,
    as.numeric(estrato) %in% as.numeric(factor(c(4,5), levels = levels(base1$estrato), ordered = TRUE))
  ) %>%
  mutate(
    precio_pred = predict(m_base1, newdata = .),
    gap = preciom - precio_pred,
    evaluacion = case_when(
      gap < -20 ~ "Ganga excepcional",
      gap <   0 ~ "Buena oportunidad",
      gap <  20 ~ "Precio justo",
      TRUE      ~ "Precio alto"
    )
  )

nrow(ofertas_casa) -> n_of1

kable(ofertas_casa %>% 
        arrange(preciom) %>% 
        select(barrio, preciom, areaconst, estrato, banios, habitaciones, parqueaderos, evaluacion) %>%
        head(8),
      caption = paste0("Casas viables (", n_of1, " encontradas) – primera ronda"),
      digits=1) %>% kable_styling(full_width = FALSE)
Casas viables (11 encontradas) – primera ronda
barrio preciom areaconst estrato banios habitaciones parqueaderos evaluacion
vipasa 270 171 4 4 4 3 Ganga excepcional
vipasa 300 205 5 5 6 2 Ganga excepcional
la flora 320 200 5 4 4 2 Ganga excepcional
la merced 320 200 4 4 4 2 Ganga excepcional
urbanización la merced 320 210 5 3 5 2 Ganga excepcional
el bosque 335 202 5 4 5 1 Ganga excepcional
vipasa 340 203 5 3 4 2 Ganga excepcional
la flora 343 170 5 4 4 3 Ganga excepcional
df_map_of1 <- ofertas_casa %>% head(20)
pal2 <- colorFactor(c("green","orange","red","purple"),
                    levels = c("Ganga excepcional","Buena oportunidad","Precio justo","Precio alto"))

leaflet(df_map_of1) %>%
  addProviderTiles("CartoDB.Positron") %>%
  addCircleMarkers(
    lng = ~longitud, lat = ~latitud,
    radius = ~pmax(5, sqrt(areaconst)/3),
    color = ~pal2(evaluacion), fillOpacity = .85, stroke = TRUE, weight = 1,
    popup = ~paste0("<b>", barrio, "</b><br>",
                    "Precio: ", scales::comma(preciom), " MM<br>",
                    "Área: ", areaconst, " m² | Estrato: ", estrato, "<br>",
                    "Hab/Baños/Parq: ", habitaciones, "/", banios, "/", parqueaderos, "<br>",
                    "<i>", evaluacion, "</i>"),
    clusterOptions = markerClusterOptions()
  ) %>%
  addLegend("topright", pal = pal2, values = df_map_of1$evaluacion, title = "Lectura comercial")

Recomendación para C&A (Solicitud 1): Presentar 5–8 casas (tabla y mapa) como paquete inicial. Priorizar “ganga excepcional” y “buena oportunidad”; las de “precio justo” sirven como ancla de mercado para negociar.

3. BASE 2 — Apartamentos en Zona Sur (pasos 1–6)

Repetimos exactamente la ruta para el segundo grupo, cumpliendo con la rúbrica.

3.1 Filtro, muestra y mapa

req_vars2 <- req_vars1
base2 <- train %>% 
  filter(tipo == "Apartamento", zona_corr == "Zona Sur") %>%
  complete_filter(req_vars2)

n_b2 <- nrow(base2)
head(base2, 3) %>% 
  select(zona_corr, tipo, barrio, estrato, areaconst, banios, habitaciones, parqueaderos, preciom) %>%
  kable(caption = "Primeros 3 apartamentos (Zona Sur)") %>% kable_styling(full_width = FALSE)
Primeros 3 apartamentos (Zona Sur)
zona_corr tipo barrio estrato areaconst banios habitaciones parqueaderos preciom
Zona Sur Apartamento el ingenio 5 116 2 3 2 380
Zona Sur Apartamento el limonar 4 108 3 3 2 230
Zona Sur Apartamento el ingenio 6 90 3 3 2 350
cat("Total de apartamentos elegibles en train (Zona Sur):", n_b2, "\n")
## Total de apartamentos elegibles en train (Zona Sur): 1921
df_map2 <- base2 %>% select(barrio, latitud, longitud, preciom, areaconst, estrato, banios, habitaciones, parqueaderos)
pal3 <- colorNumeric("Blues", domain = df_map2$preciom)

leaflet(df_map2) %>%
  addProviderTiles("CartoDB.Positron") %>%
  addCircleMarkers(
    lng = ~longitud, lat = ~latitud,
    radius = ~pmax(4, sqrt(areaconst)/3),
    color = ~pal3(preciom), fillOpacity = 0.8, stroke = TRUE, weight = 1,
    popup = ~paste0(
      "<b>", barrio, "</b><br>",
      "Precio: ", scales::comma(preciom), " MM<br>",
      "Área: ", areaconst, " m² | Estrato: ", estrato, "<br>",
      "Hab/Baños/Parq: ", habitaciones, "/", banios, "/", parqueaderos
    ),
    clusterOptions = markerClusterOptions()
  ) %>%
  addLegend("bottomright", pal = pal3, values = df_map2$preciom, title = "Precio (MM)")

3.2 EDA y correlaciones (interactivo)

vars_num2 <- c("preciom","areaconst","banios","habitaciones","parqueaderos")
cor_b2 <- cor(base2[, vars_num2], use = "complete.obs")
kable(round(cor_b2,3), caption = "Matriz de correlación – Aptos Zona Sur") %>% kable_styling(full_width = FALSE)
Matriz de correlación – Aptos Zona Sur
preciom areaconst banios habitaciones parqueaderos
preciom 1.000 0.727 0.716 0.309 0.692
areaconst 0.727 1.000 0.655 0.404 0.563
banios 0.716 0.655 1.000 0.541 0.547
habitaciones 0.309 0.404 0.541 1.000 0.232
parqueaderos 0.692 0.563 0.547 0.232 1.000
plot_ly(base2, x = ~areaconst, y = ~preciom, color = ~estrato, size = ~banios,
        text = ~paste("Barrio:", barrio), hoverinfo = "text+y+x") %>%
  add_markers(opacity = .8) %>%
  layout(title = "Precio vs Área – Apartamentos Zona Sur",
         xaxis = list(title = "Área (m²)"), yaxis = list(title = "Precio (MM)"))

3.3 Modelo, CV y métricas en test

m_base2 <- lm(preciom ~ areaconst + estrato + habitaciones + parqueaderos + banios, data = base2)
sum_b2 <- summary(m_base2)

set.seed(123)
cv_b2 <- caret::train(preciom ~ areaconst + estrato + habitaciones + parqueaderos + banios,
                      data = base2, method = "lm", trControl = ctrl)

ts_base2 <- test %>% filter(tipo == "Apartamento", zona_corr == "Zona Sur") %>% complete_filter(req_vars2)
pred_ts_b2 <- predict(m_base2, newdata = ts_base2)

met_b2 <- data.frame(
  RMSE = rmse(ts_base2$preciom, pred_ts_b2),
  MAE  = mae(ts_base2$preciom, pred_ts_b2),
  R2   = r2(ts_base2$preciom, pred_ts_b2),
  R2_Train_Ajust = sum_b2$adj.r.squared
)
kable(met_b2, digits = 3, caption = "Desempeño – Aptos Sur (test 20%)") %>% kable_styling(full_width = FALSE)
Desempeño – Aptos Sur (test 20%)
RMSE MAE R2 R2_Train_Ajust
100.6 60.31 0.74 0.783

3.4 Supuestos y VIF

par(mfrow=c(2,2)); plot(m_base2); par(mfrow=c(1,1))

bp2 <- bptest(m_base2)
dw2 <- durbinWatsonTest(m_base2)
vif2 <- car::vif(m_base2)

data.frame(
  prueba = c("Breusch-Pagan (p)","Durbin-Watson (p)", "max VIF"),
  valor  = c(bp2$p.value, dw2$p, max(vif2))
) %>% kable(digits=4, caption = "Supuestos y VIF – Aptos Sur") %>% kable_styling(full_width = FALSE)
Supuestos y VIF – Aptos Sur
prueba valor
Breusch-Pagan (p) 0.000
Durbin-Watson (p) 0.932
max VIF 3.000

3.5 Predicción puntual – Solicitud 2 (Apto)

new_apt_e5 <- data.frame(areaconst=300, estrato=factor(5, levels=levels(base2$estrato), ordered=TRUE),
                         habitaciones=5, parqueaderos=3, banios=3)
new_apt_e6 <- data.frame(areaconst=300, estrato=factor(6, levels=levels(base2$estrato), ordered=TRUE),
                         habitaciones=5, parqueaderos=3, banios=3)

pred_apt_e5 <- predict(m_base2, new_apt_e5, interval="prediction", level=.95)
pred_apt_e6 <- predict(m_base2, new_apt_e6, interval="prediction", level=.95)

tab_pred2 <- rbind(
  cbind(Estrato=5, as.data.frame(pred_apt_e5)),
  cbind(Estrato=6, as.data.frame(pred_apt_e6))
)
kable(round(tab_pred2,1), caption = "Predicción de precio – Solicitud 2 (MM)") %>% kable_styling(full_width = FALSE)
Predicción de precio – Solicitud 2 (MM)
Estrato fit lwr upr
1 5 609.8 430.2 789.3
11 6 770.3 590.7 950.0

3.6 Ofertas viables (mapa de 5+) y lectura comercial

ofertas_apto <- base2 %>%
  filter(
    preciom <= 850,
    areaconst >= 255, areaconst <= 345,  # ±15% de 300 m²
    banios >= 3, habitaciones >= 5, parqueaderos >= 2,
    as.numeric(estrato) %in% as.numeric(factor(c(5,6), levels = levels(base2$estrato), ordered = TRUE))
  ) %>%
  mutate(
    precio_pred = predict(m_base2, newdata = .),
    gap = preciom - precio_pred,
    evaluacion = case_when(
      gap < -40 ~ "Ganga excepcional",
      gap <   0 ~ "Buena oportunidad",
      gap <  40 ~ "Precio justo",
      TRUE      ~ "Precio alto"
    )
  )

nrow(ofertas_apto) -> n_of2

kable(ofertas_apto %>% 
        arrange(preciom) %>% 
        select(barrio, preciom, areaconst, estrato, banios, habitaciones, parqueaderos, evaluacion) %>%
        head(8),
      caption = paste0("Apartamentos viables (", n_of2, " encontrados) – primera ronda"),
      digits=1) %>% kable_styling(full_width = FALSE)
Apartamentos viables (2 encontrados) – primera ronda
barrio preciom areaconst estrato banios habitaciones parqueaderos evaluacion
ciudadela pasoancho 650 275 5 5 5 2 Precio alto
seminario 670 300 5 5 6 3 Buena oportunidad
df_map_of2 <- ofertas_apto %>% head(20)
pal4 <- colorFactor(c("green","orange","red","purple"),
                    levels = c("Ganga excepcional","Buena oportunidad","Precio justo","Precio alto"))

leaflet(df_map_of2) %>%
  addProviderTiles("CartoDB.Positron") %>%
  addCircleMarkers(
    lng = ~longitud, lat = ~latitud,
    radius = ~pmax(5, sqrt(areaconst)/3),
    color = ~pal4(evaluacion), fillOpacity = .85, stroke = TRUE, weight = 1,
    popup = ~paste0("<b>", barrio, "</b><br>",
                    "Precio: ", scales::comma(preciom), " MM<br>",
                    "Área: ", areaconst, " m² | Estrato: ", estrato, "<br>",
                    "Hab/Baños/Parq: ", habitaciones, "/", banios, "/", parqueaderos, "<br>",
                    "<i>", evaluacion, "</i>"),
    clusterOptions = markerClusterOptions()
  ) %>%
  addLegend("topright", pal = pal4, values = df_map_of2$evaluacion, title = "Lectura comercial")

4. Conclusiones ejecutivas

  • Magnitudes: área y estrato dominan el precio; baños y parqueaderos añaden valor marginal.
  • Viabilidad y opciones: ambos casos presentan múltiples inmuebles dentro de presupuesto.
    • Casas Norte (Solicitud 1): 11 opciones filtradas (criterios ±15% área, 2+ baños, 4+ hab, 1+ parq, estrato 4–5, ≤350M).
    • Aptos Sur (Solicitud 2): 2 opciones filtradas (±15% área, 3+ baños, 5+ hab, 2+ parq, estrato 5–6, ≤850M).
  • Uso comercial de los modelos: los intervalos de predicción 95% sirven como banda de negociación y defensa técnica ante contraofertas o avalúos.
  • Estrategia para C&A: presentar 5–8 inmuebles por solicitud como 1ª ronda, priorizando ganga y buena oportunidad; mantener 2–3 “precio justo” como anclas de referencia.
  • Siguiente paso: programar visitas y activar monitoreo semanal del inventario para mantener la lista actualizada hasta cierre.

Anexo técnico

sessionInfo()
## R version 4.4.3 (2025-02-28)
## Platform: aarch64-apple-darwin20
## Running under: macOS Sequoia 15.6.1
## 
## Matrix products: default
## BLAS:   /Library/Frameworks/R.framework/Versions/4.4-arm64/Resources/lib/libRblas.0.dylib 
## LAPACK: /Library/Frameworks/R.framework/Versions/4.4-arm64/Resources/lib/libRlapack.dylib;  LAPACK version 3.12.0
## 
## locale:
## [1] en_US.UTF-8/en_US.UTF-8/en_US.UTF-8/C/en_US.UTF-8/en_US.UTF-8
## 
## time zone: America/Bogota
## tzcode source: internal
## 
## attached base packages:
## [1] stats     graphics  grDevices utils     datasets  methods   base     
## 
## other attached packages:
##  [1] paqueteMODELOS_0.1.0 summarytools_1.1.4   gridExtra_2.3       
##  [4] GGally_2.3.0         boot_1.3-31          kableExtra_1.4.0    
##  [7] knitr_1.50           lmtest_0.9-40        zoo_1.8-14          
## [10] car_3.1-3            carData_3.0-5        broom_1.0.9         
## [13] caret_7.0-1          lattice_0.22-6       scales_1.4.0        
## [16] leaflet_2.2.2        plotly_4.11.0        ggplot2_3.5.2       
## [19] purrr_1.0.4          stringr_1.5.1        forcats_1.0.0       
## [22] tidyr_1.3.1          dplyr_1.1.4         
## 
## loaded via a namespace (and not attached):
##  [1] pROC_1.19.0.1           tcltk_4.4.3             rlang_1.1.5            
##  [4] magrittr_2.0.3          matrixStats_1.5.0       compiler_4.4.3         
##  [7] systemfonts_1.2.3       vctrs_0.6.5             reshape2_1.4.4         
## [10] pkgconfig_2.0.3         fastmap_1.2.0           magick_2.8.7           
## [13] backports_1.5.0         pander_0.6.6            rmarkdown_2.29         
## [16] prodlim_2025.04.28      xfun_0.51               cachem_1.1.0           
## [19] jsonlite_1.9.1          recipes_1.3.1           pryr_0.1.6             
## [22] parallel_4.4.3          R6_2.6.1                bslib_0.9.0            
## [25] stringi_1.8.4           RColorBrewer_1.1-3      parallelly_1.45.1      
## [28] rpart_4.1.24            lubridate_1.9.4         jquerylib_0.1.4        
## [31] Rcpp_1.0.14             iterators_1.0.14        future.apply_1.20.0    
## [34] base64enc_0.1-3         leaflet.providers_2.0.0 Matrix_1.7-2           
## [37] splines_4.4.3           nnet_7.3-20             timechange_0.3.0       
## [40] tidyselect_1.2.1        rstudioapi_0.17.1       abind_1.4-8            
## [43] yaml_2.3.10             timeDate_4041.110       codetools_0.2-20       
## [46] listenv_0.9.1           tibble_3.3.0            plyr_1.8.9             
## [49] withr_3.0.2             S7_0.2.0                evaluate_1.0.3         
## [52] future_1.67.0           survival_3.8-3          ggstats_0.10.0         
## [55] xml2_1.3.8              pillar_1.10.1           checkmate_2.3.2        
## [58] foreach_1.5.2           stats4_4.4.3            generics_0.1.3         
## [61] globals_0.18.0          class_7.3-23            glue_1.8.0             
## [64] lazyeval_0.2.2          tools_4.4.3             data.table_1.17.0      
## [67] ModelMetrics_1.2.2.2    gower_1.0.2             rapportools_1.2        
## [70] grid_4.4.3              crosstalk_1.2.1         ipred_0.9-15           
## [73] nlme_3.1-167            Formula_1.2-5           cli_3.6.4              
## [76] textshaping_1.0.0       viridisLite_0.4.2       svglite_2.2.1          
## [79] lava_1.8.1              gtable_0.3.6            sass_0.4.9             
## [82] digest_0.6.37           htmlwidgets_1.6.4       farver_2.1.2           
## [85] htmltools_0.5.8.1       lifecycle_1.0.4         hardhat_1.4.2          
## [88] httr_1.4.7              MASS_7.3-64