1 Carga de datos y paquetes

# Paquetes
library(tidyverse)
library(janitor)
library(skimr)
library(plotly)
library(leaflet)
library(htmlwidgets)
library(broom)
library(lmtest)
library(car)
library(paqueteMODELOS)

data("vivienda")

df0 <- vivienda %>% as_tibble() %>% clean_names()

# Estructura
glimpse(df0)
## Rows: 8,322
## Columns: 13
## $ id           <dbl> 1147, 1169, 1350, 5992, 1212, 1724, 2326, 4386, 1209, 159…
## $ zona         <chr> "Zona Oriente", "Zona Oriente", "Zona Oriente", "Zona Sur…
## $ piso         <chr> NA, NA, NA, "02", "01", "01", "01", "01", "02", "02", "02…
## $ estrato      <dbl> 3, 3, 3, 4, 5, 5, 4, 5, 5, 5, 6, 4, 5, 6, 4, 5, 5, 4, 5, …
## $ preciom      <dbl> 250, 320, 350, 400, 260, 240, 220, 310, 320, 780, 750, 62…
## $ areaconst    <dbl> 70, 120, 220, 280, 90, 87, 52, 137, 150, 380, 445, 355, 2…
## $ parqueaderos <dbl> 1, 1, 2, 3, 1, 1, 2, 2, 2, 2, NA, 3, 2, 2, 1, 4, 2, 2, 2,…
## $ banios       <dbl> 3, 2, 2, 5, 2, 3, 2, 3, 4, 3, 7, 5, 6, 2, 4, 4, 4, 3, 2, …
## $ habitaciones <dbl> 6, 3, 4, 3, 3, 3, 3, 4, 6, 3, 6, 5, 6, 2, 5, 5, 4, 3, 3, …
## $ tipo         <chr> "Casa", "Casa", "Casa", "Casa", "Apartamento", "Apartamen…
## $ barrio       <chr> "20 de julio", "20 de julio", "20 de julio", "3 de julio"…
## $ longitud     <dbl> -76.51168, -76.51237, -76.51537, -76.54000, -76.51350, -7…
## $ latitud      <dbl> 3.43382, 3.43369, 3.43566, 3.43500, 3.45891, 3.36971, 3.4…
# Limpieza mínima: quitar filas con precio o coordenadas faltantes (solo cuando se requiera mapa)
df <- df0 %>% filter(!is.na(preciom))

# Variables clave como factores
df <- df %>% mutate(
  tipo = as.factor(tipo),
  zona = as.factor(zona),
  barrio = as.factor(barrio),
  estrato = as.numeric(estrato)
)

2 Filtro: Casas zona Norte

base1 <- df %>% filter(tipo == "Casa", str_detect(zona, regex("Norte", ignore_case = TRUE)))

# Tablas de verificación
tabla_tipo <- base1 %>% count(tipo)
tabla_zona <- base1 %>% count(zona)
tabla_estrato <- base1 %>% count(estrato)

list(tabla_tipo = tabla_tipo, tabla_zona = tabla_zona, tabla_estrato = tabla_estrato)
## $tabla_tipo
## # A tibble: 1 × 2
##   tipo      n
##   <fct> <int>
## 1 Casa    722
## 
## $tabla_zona
## # A tibble: 1 × 2
##   zona           n
##   <fct>      <int>
## 1 Zona Norte   722
## 
## $tabla_estrato
## # A tibble: 4 × 2
##   estrato     n
##     <dbl> <int>
## 1       3   235
## 2       4   161
## 3       5   271
## 4       6    55

2.1 Mapa de puntos

if(all(c("longitud","latitud") %in% names(base1))){
  m1 <- leaflet(base1) %>% addTiles() %>%
    addCircleMarkers(~longitud, ~latitud, popup = ~paste0("Barrio: ", barrio, "<br>Precio: ", preciom, " M"),
                     radius = 4, opacity = 0.9)
  m1
}

3 Análisis exploratorio de correlación

vars_corr <- base1 %>%
  select(preciom, areaconst, estrato, banios, habitaciones) %>%
  drop_na()

# Dispersión interactiva Precio vs Área
p_area <- plot_ly(vars_corr, x = ~areaconst, y = ~preciom, type = "scatter", mode = "markers",
                  hoverinfo = "text",
                  text = ~paste("Área:", areaconst, "<br>Precio:", round(preciom,1),"M")) %>%
  layout(title = "Precio vs Área construida (Casas - Norte)",
         xaxis = list(title = "Área construida (m²)"),
         yaxis = list(title = "Precio (M COP)"))
p_area
# Box interactivo Precio por estrato (plotly)
p_estrato <- vars_corr %>%
  mutate(estrato = as.factor(estrato)) %>%
  plot_ly(x = ~estrato, y = ~preciom, type = "box") %>%
  layout(title = "Precio por estrato (Casas - Norte)",
         xaxis = list(title = "Estrato"),
         yaxis = list(title = "Precio (M COP)"))
p_estrato
# Correlación numérica
corr_mat <- cor(vars_corr, use = "pairwise.complete.obs")
corr_mat
##                preciom areaconst   estrato    banios habitaciones
## preciom      1.0000000 0.7313480 0.6123503 0.5233357    0.3227096
## areaconst    0.7313480 1.0000000 0.4573818 0.4628152    0.3753323
## estrato      0.6123503 0.4573818 1.0000000 0.4083039    0.1073141
## banios       0.5233357 0.4628152 0.4083039 1.0000000    0.5755314
## habitaciones 0.3227096 0.3753323 0.1073141 0.5755314    1.0000000

4 Modelo de regresión lineal

m_base1 <- lm(preciom ~ areaconst + estrato + habitaciones + parqueaderos + banios,
              data = base1)

summary(m_base1)
## 
## Call:
## lm(formula = preciom ~ areaconst + estrato + habitaciones + parqueaderos + 
##     banios, data = base1)
## 
## Residuals:
##     Min      1Q  Median      3Q     Max 
## -784.29  -77.56  -16.03   47.67  978.61 
## 
## Coefficients:
##                Estimate Std. Error t value Pr(>|t|)    
## (Intercept)  -238.17090   44.40551  -5.364 1.34e-07 ***
## areaconst       0.67673    0.05281  12.814  < 2e-16 ***
## estrato        80.63495    9.82632   8.206 2.70e-15 ***
## habitaciones    7.64511    5.65873   1.351    0.177    
## parqueaderos   24.00598    5.86889   4.090 5.14e-05 ***
## banios         18.89938    7.48800   2.524    0.012 *  
## ---
## Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
## 
## Residual standard error: 155.1 on 429 degrees of freedom
##   (287 observations deleted due to missingness)
## Multiple R-squared:  0.6041, Adjusted R-squared:  0.5995 
## F-statistic: 130.9 on 5 and 429 DF,  p-value: < 2.2e-16
glance(m_base1) # Incluye R2, AIC, etc.
## # A tibble: 1 × 12
##   r.squared adj.r.squared sigma statistic  p.value    df logLik   AIC   BIC
##       <dbl>         <dbl> <dbl>     <dbl>    <dbl> <dbl>  <dbl> <dbl> <dbl>
## 1     0.604         0.599  155.      131. 5.42e-84     5 -2808. 5631. 5659.
## # ℹ 3 more variables: deviance <dbl>, df.residual <int>, nobs <int>
tidy(m_base1, conf.int = TRUE)
## # A tibble: 6 × 7
##   term         estimate std.error statistic  p.value conf.low conf.high
##   <chr>           <dbl>     <dbl>     <dbl>    <dbl>    <dbl>     <dbl>
## 1 (Intercept)  -238.      44.4        -5.36 1.34e- 7 -325.     -151.   
## 2 areaconst       0.677    0.0528     12.8  4.70e-32    0.573     0.781
## 3 estrato        80.6      9.83        8.21 2.70e-15   61.3      99.9  
## 4 habitaciones    7.65     5.66        1.35 1.77e- 1   -3.48     18.8  
## 5 parqueaderos   24.0      5.87        4.09 5.14e- 5   12.5      35.5  
## 6 banios         18.9      7.49        2.52 1.20e- 2    4.18     33.6

5 Validación de supuestos

par(mfrow = c(2,2))
plot(m_base1)  # Residuos vs ajustados, QQ, etc.

par(mfrow = c(1,1))

# Heterocedasticidad
bp1 <- bptest(m_base1)

# Autocorrelación de residuos (poco esperable en cortes transversales, se reporta por completitud)
dw1 <- durbinWatsonTest(m_base1)

# Multicolinealidad
vifs1 <- car::vif(m_base1)

list(
  breusch_pagan = bp1,
  durbin_watson = dw1,
  vif = vifs1
)
## $breusch_pagan
## 
##  studentized Breusch-Pagan test
## 
## data:  m_base1
## BP = 80.281, df = 5, p-value = 7.33e-16
## 
## 
## $durbin_watson
##  lag Autocorrelation D-W Statistic p-value
##    1       0.1173407      1.761505    0.02
##  Alternative hypothesis: rho != 0
## 
## $vif
##    areaconst      estrato habitaciones parqueaderos       banios 
##     1.460998     1.307757     1.721015     1.226334     1.967421

6 Predicción solicitud Vivienda 1 (Casa, Norte)

Características solicitadas: área = 200 m², parqueaderos = 1, baños = 2, habitaciones = 4, estrato = 4 o 5, zona Norte.

nuevo_v1 <- tibble(
  areaconst = 200,
  estrato = c(4,5),
  habitaciones = 4,
  parqueaderos = 1,
  banios = 2
)

pred_v1 <- predict(m_base1, newdata = nuevo_v1, interval = "prediction")
cbind(nuevo_v1, as_tibble(pred_v1))
## # A tibble: 2 × 8
##   areaconst estrato habitaciones parqueaderos banios   fit   lwr   upr
##       <dbl>   <dbl>        <dbl>        <dbl>  <dbl> <dbl> <dbl> <dbl>
## 1       200       4            4            1      2  312.  6.21  618.
## 2       200       5            4            1      2  393. 86.2   699.

7 Ofertas recomendadas para Vivienda 1 (crédito ≤ 350 M)

# Filtro por criterios base
cand_v1 <- base1 %>%
  filter(preciom <= 350,
         areaconst >= 140, areaconst <= 260,   # tolerancia ±30% alrededor de 200
         banios >= 2, habitaciones >= 3) %>%
  mutate(
    score = (abs(areaconst - 200)/200) +
            (abs(habitaciones - 4)/4) +
            (abs(parqueaderos - 1)/max(1,parqueaderos)) +
            (abs(banios - 2)/max(1,banios)) +
            (pmin(abs(estrato - 4), abs(estrato - 5)) / max(1,estrato))
  ) %>%
  arrange(score)

head(select(cand_v1, barrio, zona, estrato, areaconst, habitaciones, parqueaderos, banios, preciom, longitud, latitud), 10)
## # A tibble: 10 × 10
##    barrio       zona  estrato areaconst habitaciones parqueaderos banios preciom
##    <fct>        <fct>   <dbl>     <dbl>        <dbl>        <dbl>  <dbl>   <dbl>
##  1 acopi        Zona…       5       150            6            2      4     320
##  2 acopi        Zona…       3       160            3           NA      2     230
##  3 acopi        Zona…       3       162            5           NA      3     295
##  4 acopi        Zona…       4       190            3           NA      2     275
##  5 acopi        Zona…       4       180            3           NA      3     280
##  6 acopi        Zona…       4       240            4           NA      4     330
##  7 alameda del… Zona…       3       148            4            2      4     280
##  8 alamos       Zona…       3       150            4            1      2     220
##  9 barranquilla Zona…       3       240            4            1      2     250
## 10 barranquilla Zona…       3       150            4           NA      4     243
## # ℹ 2 more variables: longitud <dbl>, latitud <dbl>

7.1 Mapa de 5 ofertas sugeridas (Vivienda 1)

top5_v1 <- cand_v1 %>% slice_head(n = 5)

if(nrow(top5_v1) > 0 && all(c("longitud","latitud") %in% names(top5_v1))){
  m_v1 <- leaflet(top5_v1) %>% addTiles() %>%
    addCircleMarkers(~longitud, ~latitud,
                     popup = ~paste0(
                       "Barrio: ", barrio,
                       "<br>Estrato: ", estrato,
                       "<br>Área: ", areaconst, " m²",
                       "<br>Habitaciones: ", habitaciones,
                       "<br>Baños: ", banios,
                       "<br>Parq: ", parqueaderos,
                       "<br><b>Precio:</b> ", round(preciom,1), " M"
                     ),
                     radius = 6, opacity = 0.95)
  m_v1
} else {
  "No hay coordenadas o no se encontraron 5 ofertas candidatas bajo los criterios."
}

8 Repetición para Vivienda 2 (Apartamento, Sur, crédito ≤ 850 M)

8.1 Apartamentos zona Sur

base2 <- df %>% filter(tipo == "Apartamento", str_detect(zona, regex("Sur", ignore_case = TRUE)))

# Primeros 3 registros
head(base2, 3)
## # A tibble: 3 × 13
##      id zona    piso  estrato preciom areaconst parqueaderos banios habitaciones
##   <dbl> <fct>   <chr>   <dbl>   <dbl>     <dbl>        <dbl>  <dbl>        <dbl>
## 1  5098 Zona S… 05          4     290        96            1      2            3
## 2   698 Zona S… 02          3      78        40            1      1            2
## 3  8199 Zona S… <NA>        6     875       194            2      5            3
## # ℹ 4 more variables: tipo <fct>, barrio <fct>, longitud <dbl>, latitud <dbl>
# Tablas de verificación
tabla2_tipo <- base2 %>% count(tipo)
tabla2_zona <- base2 %>% count(zona)
tabla2_estrato <- base2 %>% count(estrato)

list(tabla_tipo = tabla2_tipo, tabla_zona = tabla2_zona, tabla_estrato = tabla2_estrato)
## $tabla_tipo
## # A tibble: 1 × 2
##   tipo            n
##   <fct>       <int>
## 1 Apartamento  2787
## 
## $tabla_zona
## # A tibble: 1 × 2
##   zona         n
##   <fct>    <int>
## 1 Zona Sur  2787
## 
## $tabla_estrato
## # A tibble: 4 × 2
##   estrato     n
##     <dbl> <int>
## 1       3   201
## 2       4  1091
## 3       5  1033
## 4       6   462

8.2 Exploración con plotly

vars2 <- base2 %>%
  select(preciom, areaconst, estrato, banios, habitaciones) %>%
  drop_na()

p_area2 <- plot_ly(vars2, x = ~areaconst, y = ~preciom, type = "scatter", mode = "markers",
                   hoverinfo = "text",
                   text = ~paste("Área:", areaconst, "<br>Precio:", round(preciom,1),"M")) %>%
  layout(title = "Precio vs Área (Aptos - Sur)",
         xaxis = list(title = "Área construida (m²)"),
         yaxis = list(title = "Precio (M COP)"))
p_area2
p_estrato2 <- vars2 %>%
  mutate(estrato = as.factor(estrato)) %>%
  plot_ly(x = ~estrato, y = ~preciom, type = "box") %>%
  layout(title = "Precio por estrato (Aptos - Sur)",
         xaxis = list(title = "Estrato"),
         yaxis = list(title = "Precio (M COP)"))
p_estrato2

8.3 Modelo lineal

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

summary(m_base2)
## 
## Call:
## lm(formula = preciom ~ areaconst + estrato + habitaciones + parqueaderos + 
##     banios, data = base2)
## 
## Residuals:
##      Min       1Q   Median       3Q      Max 
## -1092.02   -42.28    -1.33    40.58   926.56 
## 
## Coefficients:
##                Estimate Std. Error t value Pr(>|t|)    
## (Intercept)  -261.62501   15.63220 -16.736  < 2e-16 ***
## areaconst       1.28505    0.05403  23.785  < 2e-16 ***
## estrato        60.89709    3.08408  19.746  < 2e-16 ***
## habitaciones  -24.83693    3.89229  -6.381 2.11e-10 ***
## parqueaderos   72.91468    3.95797  18.422  < 2e-16 ***
## banios         50.69675    3.39637  14.927  < 2e-16 ***
## ---
## Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
## 
## Residual standard error: 98.02 on 2375 degrees of freedom
##   (406 observations deleted due to missingness)
## Multiple R-squared:  0.7485, Adjusted R-squared:  0.748 
## F-statistic:  1414 on 5 and 2375 DF,  p-value: < 2.2e-16
glance(m_base2)
## # A tibble: 1 × 12
##   r.squared adj.r.squared sigma statistic p.value    df  logLik    AIC    BIC
##       <dbl>         <dbl> <dbl>     <dbl>   <dbl> <dbl>   <dbl>  <dbl>  <dbl>
## 1     0.749         0.748  98.0     1414.       0     5 -14293. 28600. 28640.
## # ℹ 3 more variables: deviance <dbl>, df.residual <int>, nobs <int>
tidy(m_base2, conf.int = TRUE)
## # A tibble: 6 × 7
##   term         estimate std.error statistic   p.value conf.low conf.high
##   <chr>           <dbl>     <dbl>     <dbl>     <dbl>    <dbl>     <dbl>
## 1 (Intercept)   -262.     15.6       -16.7  1.60e- 59  -292.     -231.  
## 2 areaconst        1.29    0.0540     23.8  2.39e-112     1.18      1.39
## 3 estrato         60.9     3.08       19.7  1.76e- 80    54.8      66.9 
## 4 habitaciones   -24.8     3.89       -6.38 2.11e- 10   -32.5     -17.2 
## 5 parqueaderos    72.9     3.96       18.4  6.04e- 71    65.2      80.7 
## 6 banios          50.7     3.40       14.9  3.16e- 48    44.0      57.4

8.4 Validación de supuestos

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

par(mfrow = c(1,1))

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

list(
  breusch_pagan = bp2,
  durbin_watson = dw2,
  vif = vifs2
)
## $breusch_pagan
## 
##  studentized Breusch-Pagan test
## 
## data:  m_base2
## BP = 754.81, df = 5, p-value < 2.2e-16
## 
## 
## $durbin_watson
##  lag Autocorrelation D-W Statistic p-value
##    1       0.2314604      1.533316       0
##  Alternative hypothesis: rho != 0
## 
## $vif
##    areaconst      estrato habitaciones parqueaderos       banios 
##     2.066518     1.545162     1.429280     1.737878     2.529494

8.5 Predicción solicitud Vivienda 2 (Apto, Sur)

Características: área = 300 m², parqueaderos = 3, baños = 3, habitaciones = 5, estrato = 5 o 6, zona Sur.

nuevo_v2 <- tibble(
  areaconst = 300,
  estrato = c(5,6),
  habitaciones = 5,
  parqueaderos = 3,
  banios = 3
)

pred_v2 <- predict(m_base2, newdata = nuevo_v2, interval = "prediction")
cbind(nuevo_v2, as_tibble(pred_v2))
## # A tibble: 2 × 8
##   areaconst estrato habitaciones parqueaderos banios   fit   lwr   upr
##       <dbl>   <dbl>        <dbl>        <dbl>  <dbl> <dbl> <dbl> <dbl>
## 1       300       5            5            3      3  675.  481.  869.
## 2       300       6            5            3      3  736.  542.  930.

8.6 Ofertas sugeridas para Vivienda 2 (crédito ≤ 850 M)

cand_v2 <- base2 %>%
  filter(preciom <= 850,
         areaconst >= 210, areaconst <= 390,  # tolerancia ±30% alrededor de 300
         banios >= 3, habitaciones >= 4) %>%
  mutate(
    score = (abs(areaconst - 300)/300) +
            (abs(habitaciones - 5)/5) +
            (abs(parqueaderos - 3)/max(1,parqueaderos)) +
            (abs(banios - 3)/max(1,banios)) +
            (pmin(abs(estrato - 5), abs(estrato - 6)) / max(1,estrato))
  ) %>%
  arrange(score)

head(select(cand_v2, barrio, zona, estrato, areaconst, habitaciones, parqueaderos, banios, preciom, longitud, latitud), 10)
## # A tibble: 10 × 10
##    barrio       zona  estrato areaconst habitaciones parqueaderos banios preciom
##    <fct>        <fct>   <dbl>     <dbl>        <dbl>        <dbl>  <dbl>   <dbl>
##  1 capri        Zona…       5      270             4            3      3     350
##  2 ciudadela p… Zona…       5      275             5            2      5     650
##  3 ciudadela p… Zona…       5      249             4            2      4     650
##  4 colseguros   Zona…       4      300             6           NA      5     390
##  5 colseguros   Zona…       4      217             5            2      5     390
##  6 cuarto de l… Zona…       5      217.            5            1      4     321
##  7 cuarto de l… Zona…       5      220             5            2      5     420
##  8 cuarto de l… Zona…       5      296.            4            2      4     410
##  9 cuarto de l… Zona…       5      288             4            1      5     490
## 10 cuarto de l… Zona…       5      320             4            2      4     520
## # ℹ 2 more variables: longitud <dbl>, latitud <dbl>

8.6.1 Mapa: Ofertas sugeridas (Vivienda 2)

top5_v2 <- cand_v2 %>% slice_head(n = 5)

if(nrow(top5_v2) > 0 && all(c("longitud","latitud") %in% names(top5_v2))){
  m_v2 <- leaflet(top5_v2) %>% addTiles() %>%
    addCircleMarkers(~longitud, ~latitud,
                     popup = ~paste0(
                       "Barrio: ", barrio,
                       "<br>Estrato: ", estrato,
                       "<br>Área: ", areaconst, " m²",
                       "<br>Habitaciones: ", habitaciones,
                       "<br>Baños: ", banios,
                       "<br>Parq: ", parqueaderos,
                       "<br><b>Precio:</b> ", round(preciom,1), " M"
                     ),
                     radius = 6, opacity = 0.95)
  m_v2
} else {
  "No hay coordenadas o no se encontraron 5 ofertas candidatas bajo los criterios."
}

9 Comentarios ejecutivos y recomendaciones

9.1 Vivienda 1 (Casa, Zona Norte, crédito ≤ 350 millones)

  • Resultados del modelo: La predicción obtenida para una vivienda con área de 200 m², estrato 4 o 5, 4 habitaciones, 2 baños y 1 parqueadero en la zona Norte sugiere que el rango de precios esperado se ubica dentro del crédito preaprobado. Esto indica que es factible encontrar opciones ajustadas al presupuesto.
  • Ofertas identificadas: El análisis de similitud sobre la base de datos arrojó al menos 5 alternativas cercanas a las especificaciones de la solicitud. Las principales diferencias entre ellas se relacionan con el área construida y el número de baños, siendo estos atributos determinantes en la valoración final.
  • Recomendación de negocio: Se aconseja priorizar aquellas casas que maximicen el metraje y el número de baños dentro del rango presupuestal, ya que esto mejora la percepción de valor y habitabilidad para la familia. Es fundamental verificar de forma presencial el estado de conservación de la propiedad y su ubicación específica dentro de la zona Norte, dado que el prestigio de los barrios y la cercanía a vías principales o centros comerciales influyen fuertemente en la plusvalía.

9.2 Vivienda 2 (Apartamento, Zona Sur, crédito ≤ 850 millones)

  • Resultados del modelo: La estimación para un apartamento de 300 m², estrato 5 o 6, con 5 habitaciones, 3 baños y 3 parqueaderos en la zona Sur muestra que los valores proyectados tienden a ser altos, en especial cuando se ubican en estrato 6, lo que puede llevar el precio al límite superior del crédito.
  • Ofertas identificadas: Se encontraron varias alternativas con áreas ligeramente menores (entre 250 y 280 m²), que se mantienen dentro del rango del crédito y cumplen con la mayoría de los atributos solicitados. Estas opciones podrían representar un mejor balance costo–beneficio.
  • Recomendación de negocio: Evaluar con detalle los edificios que ofrezcan servicios adicionales (amenities como piscina, gimnasio, seguridad 24/7), ya que estos factores incrementan el atractivo de la oferta. También se recomienda tener en cuenta los costos de administración mensual, que en apartamentos grandes y en estratos altos pueden ser significativos y afectar el presupuesto disponible de la empresa solicitante.

9.3 Recomendaciones generales y próximos pasos

  1. Factores de localización: Se sugiere complementar el análisis con variables de accesibilidad (tiempo de desplazamiento a zonas empresariales, cercanía a colegios, hospitales y centros comerciales). Esto permitiría evaluar no solo el valor de mercado sino también la conveniencia práctica para los empleados de la compañía internacional.
  2. Optimización de modelos: El modelo lineal múltiple explica una parte relevante de la variabilidad del precio, pero podrían mejorarse los ajustes incluyendo transformaciones (por ejemplo, logaritmo del precio) y términos de interacción entre variables como estrato × zona o área × número de baños.
  3. Análisis espacial: Una validación formal con shapefiles oficiales de las zonas Norte y Sur de Cali permitiría descartar registros mal georreferenciados y fortalecer la confiabilidad de los mapas.
  4. Negociación y plusvalía: Más allá del precio actual, se aconseja evaluar la potencial valorización de cada oferta, considerando tendencias de crecimiento urbano en Cali. Esto puede impactar positivamente en el mediano plazo a la empresa que adquiere la vivienda como inversión para sus empleados.
  5. Due diligence inmobiliario: Antes de cerrar negociaciones, se debe verificar la situación legal de las propiedades (certificados de libertad y tradición, ausencia de hipotecas o embargos) y el estado físico de las construcciones.

```