Contexto del Problema

ElectroTienda S.A. necesita entender el posicionamiento competitivo de precios en el mercado de smartphones para su próxima estrategia comercial.

Objetivos de la Investigación

  1. Realizar benchmarking de precios en smartphones

  2. Identificar patrones de pricing por marca

  3. Evaluar estrategias de descuentos

#Carga de librerias
library(rvest)    # Web scraping
library(dplyr)    # Manipulación de datos
library(ggplot2)  # Visualización
library(knitr)    # Reportes
library(scales)   # Formato de números
library(stringr)  # Manipulación de texto
library(tidyr)    # Datos ordenados

Parte 1: Extracción de Datos

  1. Configuración inicial
# Establecemos la ruta al archivo HTML que debe estar en el mismo directorio que este script
ruta_html <- "electrotienda.html"  

# Verificamos si el archivo existe antes de continuar
if(!file.exists(ruta_html)) {
  stop("❌ Error: No se encontró el archivo HTML. Verifica que:")
  cat("\n1. El archivo 'electrotienda.html' esté en la misma carpeta que este Rmd")
  cat("\n2. El nombre del archivo coincida exactamente (incluyendo mayúsculas)")
}
  1. Función de Extracción Segura
# Creamos una función robusta para extraer datos que maneje posibles errores
extraer_dato <- function(nodo, selector) {
  resultado <- tryCatch({
    # Intenta extraer el texto del nodo con el selector CSS
    texto <- nodo %>% html_nodes(selector) %>% html_text() %>% trimws()
    
    # Si no encuentra nada, devuelve NA en lugar de error
    if(length(texto) == 0) NA_character_ else texto
  }, 
  # Manejo de errores (si el selector no existe)
  error = function(e) NA_character_)
  
  return(resultado)
}
  1. Lectura y Verificación del HTML
# Leemos el archivo HTML
pagina <- read_html(ruta_html)

# Extraemos todos los nodos con clase "producto"
productos <- html_nodes(pagina, ".producto")

# Verificación crítica
if(length(productos) == 0) {
  stop("❌ Error: No se encontraron productos. Revisa:")
  cat("\n1. Que el HTML use la clase 'producto' para cada item")
  cat("\n2. La estructura del archivo HTML")
} else {
  cat("✅ Éxito: Se encontraron", length(productos), "productos para analizar")
}
## ✅ Éxito: Se encontraron 51 productos para analizar
  1. Extracción Estructurada de Datos
# Creamos un dataframe con los datos extraídos
datos <- data.frame(
  # Nombre del producto (extraído de la clase CSS ".nombre")
  Producto = sapply(productos, extraer_dato, ".nombre"),
  
  # Marca del producto (extraído de la clase CSS ".marca")
  Marca = sapply(productos, extraer_dato, ".marca"),
  
  # Precio (con limpieza de formato $ y conversión a numérico)
  Precio = sapply(productos, function(p) {
    precio_texto <- extraer_dato(p, ".precio")
    if(is.na(precio_texto)) return(NA_real_)
    
    # Expresión regular para extraer números con decimales
    precio_limpio <- str_extract(precio_texto, "[0-9,]+\\.[0-9]{2}") %>% 
      str_replace(",", "") %>%  # Elimina comas de miles
      as.numeric()              # Convierte a número
    return(precio_limpio)
  }),
  
  # Descuento (extrae el % y maneja casos sin descuento)
  Descuento = sapply(productos, function(p) {
    desc_texto <- extraer_dato(p, ".descuento")
    if(is.na(desc_texto)) return(0)  # Si no hay descuento, devuelve 0
    
    # Extrae solo los dígitos del descuento
    desc_num <- str_extract(desc_texto, "\\d+") %>% as.numeric()
    return(desc_num)
  }),
  
  stringsAsFactors = FALSE
) %>% 
  # Calculamos el precio original antes del descuento
  mutate(
    Precio_Original = ifelse(Descuento > 0, Precio / (1 - Descuento/100), Precio),
    
    # Clasificación por gama de precio
    Gama = case_when(
      Precio >= 800 ~ "Premium",
      Precio >= 400 ~ "Media",
      TRUE ~ "Básica"
    )
  )

# Mostramos las primeras filas para verificación
head(datos) %>% kable(caption = "Muestra de Datos Extraídos")
Muestra de Datos Extraídos
Producto Marca Precio Descuento Precio_Original Gama
Galaxy S23 Ultra Samsung 1199.99 10 1333.322 Premium
Galaxy S23+ Samsung 999.99 0 999.990 Premium
Galaxy S23 Samsung 799.99 0 799.990 Media
Galaxy Z Flip5 Samsung 999.00 15 1175.294 Premium
Galaxy Z Fold5 Samsung 1799.99 0 1799.990 Premium
Galaxy A54 5G Samsung 449.99 0 449.990 Media
  1. Exportación de Datos
# Guardamos los datos en CSV para análisis posteriores
write.csv(datos, "datos_smartphones.csv", row.names = FALSE, fileEncoding = "UTF-8")

cat("📊 Datos exportados correctamente a 'datos_smartphones.csv'")
## 📊 Datos exportados correctamente a 'datos_smartphones.csv'

Parte 2: Análisis Exploratorio

  1. Resumen General del Mercado
resumen_general <- datos %>%
  summarise(
    Total_Productos = n(),
    Marcas_Unicas = n_distinct(Marca),
    Precio_Promedio = mean(Precio, na.rm = TRUE),
    Precio_Mediano = median(Precio, na.rm = TRUE),
    Rango_Precios = paste("$", min(Precio, na.rm = TRUE), "-", "$", max(Precio, na.rm = TRUE)),
    Productos_Descuento = sum(Descuento > 0),
    Porcentaje_Descuento = mean(Descuento[Descuento > 0])
  )

kable(resumen_general, caption = "Métricas Clave del Mercado")
Métricas Clave del Mercado
Total_Productos Marcas_Unicas Precio_Promedio Precio_Mediano Rango_Precios Productos_Descuento Porcentaje_Descuento
51 16 710.8986 699 $ 149.99 - $ 1799.99 11 10.63636
  1. Distribución de Precios
ggplot(datos, aes(x = Precio)) +
  geom_histogram(fill = "steelblue", bins = 15, color = "white") +
  scale_x_continuous(labels = scales::dollar) +
  labs(title = "Distribución de Precios en el Mercado",
       x = "Precio (USD)", y = "Número de Productos") +
  theme_minimal()

Parte 3: Análisis Competitivo

  1. Posicionamiento por Marca
top_marcas <- datos %>%
  group_by(Marca) %>%
  summarise(
    Productos = n(),
    Precio_Promedio = mean(Precio),
    Descuento_Promedio = mean(Descuento),
    .groups = 'drop'
  ) %>%
  arrange(desc(Precio_Promedio))

ggplot(top_marcas, aes(x = reorder(Marca, Precio_Promedio), y = Precio_Promedio)) +
  geom_col(fill = "steelblue") +
  coord_flip() +
  labs(title = "Posicionamiento de Precio por Marca",
       x = "", y = "Precio Promedio (USD)") +
  scale_y_continuous(labels = scales::dollar) +
  theme_minimal()

  1. Estrategias de Descuento
datos %>%
  filter(Descuento > 0) %>%
  group_by(Marca) %>%
  summarise(
    Productos = n(),
    Descuento_Promedio = mean(Descuento),
    .groups = 'drop'
  ) %>%
  ggplot(aes(x = Productos, y = Descuento_Promedio, size = Productos, color = Marca)) +
  geom_point() +
  labs(title = "Estrategias de Descuento por Marca",
       x = "Número de Productos con Descuento",
       y = "Descuento Promedio (%)") +
  theme_minimal() +
  theme(legend.position = "none")

Conclusiones y Recomendaciones

Hallazgos Clave

# 1. Calcular marca con precio promedio más alto
marca_premium <- datos %>%
  group_by(Marca) %>%
  summarise(Precio_Promedio = mean(Precio)) %>%
  arrange(desc(Precio_Promedio)) %>%
  slice(1) %>%
  pull(Marca)

# 2. Identificar marca con más descuentos
marca_mas_descuentos <- datos %>%
  filter(Descuento > 0) %>%
  count(Marca, sort = TRUE) %>%
  slice(1) %>%
  pull(Marca)

# 3. Calcular porcentaje de productos con descuento
porcentaje_descuentos <- mean(datos$Descuento > 0) * 100

# 4. Obtener rango de precios
precio_min <- min(datos$Precio)
precio_max <- max(datos$Precio)

# 5. Contar productos por gama
gama_media_count <- sum(datos$Gama == "Media")

Resultados : 1. La marca premium es:

marca_premium
## [1] "Asus"

2.Rango de precios:

precio_max - precio_min
## [1] 1650
  1. % de productos con descuentos
round(porcentaje_descuentos,1)
## [1] 21.6