12  Aplicaciones del Mundo Real

En este capítulo dominarás
  • Aplicaciones Shiny escalables con data.table
  • Integración con tidymodels para machine learning robusto
  • Conexión con bases de datos y ecosistemas Big Data
  • dtplyr: El puente entre data.table y tidyverse
  • Casos de uso industriales reales y prácticos

12.1 Aplicaciones Shiny Escalables con data.table

12.1.1 1. Arquitectura de App Shiny Profesional

# Estructura modular para apps grandes
library(shiny)
library(shinydashboard)
library(data.table)
library(DT)
library(ggplot2)
library(plotly)

# === MÓDULO: Data Processing ===
# Todas las operaciones data.table en funciones separadas
process_customer_data <- function(clientes_dt, filters = list()) {
  # Aplicar filtros dinámicamente
  result <- clientes_dt
  
  if(!is.null(filters$region) && filters$region != "Todos") {
    result <- result[region == filters$region]
  }
  
  if(!is.null(filters$edad_min)) {
    result <- result[edad >= filters$edad_min]
  }
  
  if(!is.null(filters$fecha_desde)) {
    # Aquí se aplicarían filtros de fecha si tuviéramos esos datos
  }
  
  return(result)
}

calculate_customer_metrics <- function(clientes_dt) {
  clientes_dt[,
    .(
      total_clientes = .N,
      edad_promedio = round(mean(edad), 1),
      ingresos_promedio = round(mean(ingresos), 0),
      satisfaccion_promedio = round(mean(satisfaccion), 2),
      churn_rate = round(mean(churn_flag) * 100, 1),
      valor_promedio = round(mean(valor_cliente), 2),
      engagement_promedio = round(mean(engagement_score), 2)
    ),
    by = .(region, categoria_ingresos)
  ]
}

# === UI: Dashboard Layout ===
ui <- dashboardPage(
  dashboardHeader(title = "Customer Analytics - Powered by data.table"),
  
  dashboardSidebar(
    sidebarMenu(
      menuItem("Overview", tabName = "overview", icon = icon("dashboard")),
      menuItem("Clientes", tabName = "clientes", icon = icon("users")),
      menuItem("Segmentación", tabName = "segmentacion", icon = icon("chart-pie")),
      menuItem("Predicciones", tabName = "predicciones", icon = icon("brain")),
      menuItem("Datos Raw", tabName = "datos", icon = icon("table"))
    )
  ),
  
  dashboardBody(
    tags$head(
      tags$style(HTML("
        .content-wrapper, .right-side {
          background-color: #f4f4f4;
        }
        .main-header .navbar {
          background-color: #2E8B57 !important;
        }
      "))
    ),
    
    tabItems(
      # Tab Overview
      tabItem(tabName = "overview",
        fluidRow(
          valueBoxOutput("total_clientes"),
          valueBoxOutput("churn_rate"),
          valueBoxOutput("valor_promedio")
        ),
        fluidRow(
          box(
            title = "Filtros Globales", status = "primary", solidHeader = TRUE,
            width = 3,
            selectInput("region_filter", "Región:",
                       choices = c("Todos", unique(datos_clientes$region)),
                       selected = "Todos"),
            sliderInput("edad_range", "Rango de Edad:",
                       min = 18, max = 80, value = c(18, 80)),
            actionButton("aplicar_filtros", "Aplicar Filtros", 
                        class = "btn-primary")
          ),
          box(
            title = "Revenue por Región", status = "success", solidHeader = TRUE,
            width = 9,
            plotlyOutput("revenue_plot", height = "400px")
          )
        )
      ),
      
      # Tab Clientes
      tabItem(tabName = "clientes",
        fluidRow(
          box(
            title = "Análisis de Clientes", status = "primary", solidHeader = TRUE,
            width = 12,
            DT::dataTableOutput("clientes_table")
          )
        )
      ),
      
      # Tab Segmentación
      tabItem(tabName = "segmentacion",
        fluidRow(
          box(
            title = "Segmentación por Valor", status = "warning", solidHeader = TRUE,
            width = 6,
            plotOutput("segmentacion_plot")
          ),
          box(
            title = "Matriz de Retención", status = "info", solidHeader = TRUE,
            width = 6,
            plotOutput("matriz_retencion")
          )
        )
      )
    )
  )
)

# === SERVER: Lógica Reactiva ===
server <- function(input, output, session) {
  
  # Datos reactivos - aquí brilla data.table
  datos_filtrados <- reactive({
    input$aplicar_filtros  # Dependencia del botón
    
    isolate({
      filtros <- list(
        region = input$region_filter,
        edad_min = input$edad_range[1],
        edad_max = input$edad_range[2]
      )
      
      result <- datos_clientes[
        edad >= filtros$edad_min & edad <= filtros$edad_max
      ]
      
      if(filtros$region != "Todos") {
        result <- result[region == filtros$region]
      }
      
      return(result)
    })
  })
  
  # ValueBoxes
  output$total_clientes <- renderValueBox({
    valueBox(
      value = comma(nrow(datos_filtrados())),
      subtitle = "Total Clientes",
      icon = icon("users"),
      color = "blue"
    )
  })
  
  output$churn_rate <- renderValueBox({
    rate <- round(mean(datos_filtrados()$churn_flag) * 100, 1)
    valueBox(
      value = paste0(rate, "%"),
      subtitle = "Tasa de Churn",
      icon = icon("exclamation-triangle"),
      color = if(rate > 15) "red" else if(rate > 10) "yellow" else "green"
    )
  })
  
  output$valor_promedio <- renderValueBox({
    valor <- round(mean(datos_filtrados()$valor_cliente), 2)
    valueBox(
      value = dollar(valor),
      subtitle = "Valor Promedio",
      icon = icon("dollar-sign"),
      color = "green"
    )
  })
  
  # Gráfico principal
  output$revenue_plot <- renderPlotly({
    # Cálculo super rápido con data.table
    plot_data <- datos_filtrados()[,
      .(
        valor_total = sum(valor_cliente),
        clientes = .N,
        satisfaccion_avg = round(mean(satisfaccion), 2)
      ),
      by = region
    ]
    
    p <- ggplot(plot_data, aes(x = reorder(region, valor_total), y = valor_total)) +
      geom_col(aes(fill = satisfaccion_avg), alpha = 0.8) +
      geom_text(aes(label = dollar(valor_total, scale = 1e-3, suffix = "K")), 
               hjust = -0.1) +
      scale_fill_viridis_c(name = "Satisfacción\nPromedio") +
      coord_flip() +
      labs(title = "Valor Total por Región", x = "Región", y = "Valor Total") +
      theme_minimal()
    
    ggplotly(p)
  })
  
  # Tabla de clientes
  output$clientes_table <- DT::renderDataTable({
    tabla_data <- datos_filtrados()[,
      .(
        cliente_id, edad, region, 
        ingresos = dollar(ingresos),
        valor_cliente = round(valor_cliente, 2),
        satisfaccion,
        engagement_score = round(engagement_score, 2),
        riesgo_churn = ifelse(riesgo_churn == 1, "Alto", "Bajo"),
        es_vip = ifelse(cliente_vip == 1, "Sí", "No")
      )
    ]
    
    DT::datatable(
      tabla_data,
      options = list(pageLength = 25, scrollX = TRUE),
      rownames = FALSE
    ) %>%
      DT::formatStyle(
        "riesgo_churn",
        backgroundColor = DT::styleEqual("Alto", "#ffebee")
      ) %>%
      DT::formatStyle(
        "es_vip",
        backgroundColor = DT::styleEqual("Sí", "#e8f5e8")
      )
  })
}

# Lanzar la app
# shinyApp(ui = ui, server = server)

12.1.2 2. Optimización de Performance en Shiny

# === TÉCNICAS DE OPTIMIZACIÓN ===

# 1. Pre-procesar datos pesados al inicio
onSessionStart <- function(session) {
  # Cálculos que no cambian frecuentemente
  session$userData$metricas_estaticas <- datos_clientes[,
    .(
      clientes_por_region = .N,
      valor_total = sum(valor_cliente),
      edad_promedio = mean(edad)
    ),
    by = region
  ]
  
  # Índices para búsquedas rápidas
  setkey(session$userData$clientes_dt, cliente_id)
  setindex(session$userData$clientes_dt, region, categoria_ingresos)
}

# 2. Usar reactive values para cache inteligente
server <- function(input, output, session) {
  # Cache de cálculos costosos
  cache_metricas <- reactiveValues()
  
  # Solo recalcular cuando cambien inputs relevantes
  observe({
    key <- paste(input$region_filter, input$edad_range[1], input$edad_range[2], sep = "_")
    
    if(is.null(cache_metricas[[key]])) {
      cache_metricas[[key]] <- calculate_customer_metrics(
        datos_clientes[
          region %in% input$region_filter & 
          edad %between% input$edad_range
        ]
      )
    }
  })
  
  # 3. Renderizado condicional
  output$tabla_grande <- DT::renderDataTable({
    # Solo renderizar si la pestaña está visible
    req(input$tabs == "datos_detallados")
    
    # data.table operation ultrarrápida
    datos_tabla <- datos_filtrados()[,
      .(cliente_id, region, valor_cliente, churn_flag)
    ][order(-valor_cliente)]
    
    DT::datatable(datos_tabla, options = list(pageLength = 50))
  })
}

# 4. Módulos para escalabilidad
customerMetricsUI <- function(id) {
  ns <- NS(id)
  tagList(
    valueBoxOutput(ns("total_value")),
    plotOutput(ns("distribution_plot"))
  )
}

customerMetricsServer <- function(id, data) {
  moduleServer(id, function(input, output, session) {
    output$total_value <- renderValueBox({
      # Cálculo modular con data.table
      total <- data()[, sum(valor_cliente)]
      valueBox(
        value = dollar(total, scale = 1e-6, suffix = "M"),
        subtitle = "Valor Total",
        icon = icon("chart-line"),
        color = "green"
      )
    })
  })
}

12.2 Integración con tidymodels: Machine Learning Robusto

12.2.1 1. Workflow Completo: data.table → tidymodels → data.table

# PASO 1: Feature Engineering con data.table (ultrarrápido)
preparar_datos_ml <- function(clientes_dt, transacciones_dt) {
  # Calcular métricas de transacciones por cliente
  metricas_transaccionales <- transacciones_dt[, monto_final := monto * (1 - descuento_aplicado)][
    fecha_transaccion >= Sys.Date() - 365,  # Último año
    .(
      transacciones_año = .N,
      monto_total_año = sum(monto_final),
      monto_promedio = round(mean(monto_final), 2),
      categorias_compradas = uniqueN(producto_categoria),
      canal_principal = names(sort(table(canal), decreasing = TRUE))[1],
      frecuencia_compra_dias = as.numeric(max(fecha_transaccion) - min(fecha_transaccion)) / .N,
      usa_descuentos = mean(descuento_aplicado > 0),
      tasa_devolucion = mean(es_devolucion)
    ),
    by = cliente_id
  ]
  
  # Unir con datos de clientes
  datos_completos <- clientes_dt[metricas_transaccionales, on = .(cliente_id)]
  
  # Feature engineering adicional
  datos_completos[, `:=`(
    # Variables de interacción
    ingresos_edad_ratio = ingresos / edad,
    valor_por_producto = valor_cliente / num_productos,
    engagement_per_month = engagement_score / pmax(antiguedad_meses, 1),
    
    # Variables categóricas optimizadas
    segmento_valor = cut(valor_cliente, 
                        breaks = quantile(valor_cliente, c(0, 0.25, 0.5, 0.75, 1), na.rm = TRUE),
                        labels = c("Bajo", "Medio-Bajo", "Medio-Alto", "Alto"),
                        include.lowest = TRUE),
    
    # Variables binarias
    cliente_frecuente = as.numeric(transacciones_año > quantile(transacciones_año, 0.75, na.rm = TRUE)),
    multicanal = as.numeric(categorias_compradas >= 3),
    
    # Target variable limpia
    churn = factor(ifelse(churn_flag == 1, "Si", "No"), levels = c("No", "Si"))
  )]
  
  # Remover casos con NAs en variables críticas
  datos_limpios <- datos_completos[
    !is.na(transacciones_año) & 
    !is.na(monto_total_año) & 
    !is.na(churn)
  ]
  
  return(datos_limpios)
}

# Ejecutar preparación
datos_ml <- preparar_datos_ml(datos_clientes, transacciones_detalle)

cat("Datos preparados para ML:\n")
#> Datos preparados para ML:
cat("- Filas:", nrow(datos_ml), "\n")
#> - Filas: 5938
cat("- Columnas:", ncol(datos_ml), "\n")
#> - Columnas: 33
cat("- Distribución de churn:\n")
#> - Distribución de churn:
print(datos_ml[, .N, by = churn])
#>     churn     N
#>    <fctr> <int>
#> 1:     Si   707
#> 2:     No  5231

12.2.2 2. Modelado con tidymodels (Ejemplo Conceptual)

# Ejemplo de workflow completo con tidymodels
library(tidymodels)
library(themis)  # Para balanceo de clases

# PASO 2: Convertir a tibble para tidymodels
datos_tibble <- as_tibble(datos_ml)

# PASO 3: Split de datos
set.seed(123)
data_split <- initial_split(
  datos_tibble, 
  prop = 0.8, 
  strata = churn
)

train_data <- training(data_split)
test_data <- testing(data_split)

# PASO 4: Receta de preprocesamiento
receta_churn <- recipe(churn ~ ., data = train_data) %>%
  # Remover variables no predictivas
  step_rm(cliente_id, churn_flag) %>%
  
  # Imputación de NAs
  step_impute_median(all_numeric_predictors()) %>%
  step_impute_mode(all_nominal_predictors()) %>%
  
  # Feature engineering
  step_log(ingresos, valor_cliente, offset = 1) %>%
  step_normalize(all_numeric_predictors()) %>%
  
  # Variables dummy
  step_dummy(all_nominal_predictors()) %>%
  
  # Balanceo de clases
  step_downsample(churn, under_ratio = 2) %>%
  
  # Remover variables con varianza cero
  step_zv(all_predictors()) %>%
  
  # Correlación alta
  step_corr(all_numeric_predictors(), threshold = 0.9)

# PASO 5: Modelos
modelo_rf <- rand_forest(
  trees = tune(),
  mtry = tune(),
  min_n = tune()
) %>%
  set_mode("classification") %>%
  set_engine("randomForest")

modelo_xgb <- boost_tree(
  trees = tune(),
  tree_depth = tune(),
  learn_rate = tune(),
  min_n = tune()
) %>%
  set_mode("classification") %>%
  set_engine("xgboost")

# PASO 6: Workflows
workflow_rf <- workflow() %>%
  add_recipe(receta_churn) %>%
  add_model(modelo_rf)

workflow_xgb <- workflow() %>%
  add_recipe(receta_churn) %>%
  add_model(modelo_xgb)

# PASO 7: Cross-validation y tuning
cv_folds <- vfold_cv(train_data, v = 5, strata = churn)

# Tuning Random Forest
tune_rf <- workflow_rf %>%
  tune_grid(
    resamples = cv_folds,
    grid = 20,
    metrics = metric_set(roc_auc, precision, recall, f_meas)
  )

# PASO 8: Mejor modelo
best_rf <- select_best(tune_rf, metric = "roc_auc")
final_workflow_rf <- finalize_workflow(workflow_rf, best_rf)

# PASO 9: Fit final y predicciones
modelo_final <- fit(final_workflow_rf, train_data)
predicciones <- predict(modelo_final, test_data, type = "prob")

# Métricas de evaluación
metricas_modelo <- test_data %>%
  cbind(predicciones) %>%
  mutate(pred = factor(fifelse(.pred_Si >= 0.5, 1, 0), levels = c(0, 1), labels = c("No", "Si"))) %>% 
  metrics(truth = churn, pred)

print(metricas_modelo)

12.2.3 3. Post-procesamiento y Análisis con data.table

# Simular predicciones para el ejemplo (en producción vendrían del modelo)
set.seed(789)
predicciones_simuladas <- data.table(
  cliente_id = datos_ml[sample(.N, 2000), cliente_id],
  prob_churn = runif(2000, 0, 1),
  pred_churn = sample(c("Si", "No"), 2000, replace = TRUE, prob = c(0.15, 0.85)),
  confidence_score = runif(2000, 0.6, 0.95)
)

# ANÁLISIS DE RESULTADOS con data.table
# Unir predicciones con datos originales
resultados_ml <- datos_ml[predicciones_simuladas, on = .(cliente_id)]

# Análisis de segmentos de riesgo
analisis_riesgo <- resultados_ml[,
  .(
    clientes_total = .N,
    churn_predicho = sum(pred_churn == "Si"),
    prob_churn_media = round(mean(prob_churn), 3),
    valor_en_riesgo = sum(valor_cliente[pred_churn == "Si"]),
    revenue_en_riesgo = sum(monto_total_año[pred_churn == "Si"], na.rm = TRUE),
    ingresos_promedio = round(mean(ingresos), 0),
    engagement_promedio = round(mean(engagement_score), 2)
  ),
  by = .(region, categoria_ingresos)
][order(-valor_en_riesgo)]

cat("=== ANÁLISIS DE RIESGO DE CHURN POR SEGMENTO ===\n")
#> === ANÁLISIS DE RIESGO DE CHURN POR SEGMENTO ===
print(analisis_riesgo)
#>     region categoria_ingresos clientes_total churn_predicho prob_churn_media
#>     <char>             <fctr>          <int>          <int>            <num>
#>  1: Centro               Alto            115             27            0.496
#>  2:  Norte               Alto            149             22            0.474
#>  3:   Este               Alto            149             26            0.476
#>  4:    Sur               Alto            136             15            0.472
#>  5: Centro              Medio            132             23            0.480
#> ---                                                                         
#> 11:  Oeste              Medio            123             20            0.477
#> 12: Centro               Bajo            141             19            0.477
#> 13:    Sur               Bajo            118             25            0.501
#> 14:   Este               Bajo            142             17            0.531
#> 15:  Oeste               Bajo            133             22            0.523
#>     valor_en_riesgo revenue_en_riesgo ingresos_promedio engagement_promedio
#>               <num>             <num>             <num>               <num>
#>  1:       3971.9904          4357.158            101231                7.74
#>  2:       3448.6127          3115.558            101081                7.56
#>  3:       2752.8342          2759.645            103060                7.62
#>  4:       1918.1725          1584.725             93285                7.55
#>  5:       1900.4059          2514.022             48193                7.54
#> ---                                                                        
#> 11:       1159.6149          2632.164             49278                7.63
#> 12:        912.9440          3273.967             27460                7.72
#> 13:        879.7883          3768.650             26754                7.62
#> 14:        812.1611          1624.176             27489                7.43
#> 15:        794.0182          2108.543             27076                7.72

# Identificar clientes de alta prioridad para retención
clientes_retencion <- resultados_ml[
  pred_churn == "Si" & 
  prob_churn > 0.7 & 
  valor_cliente > quantile(resultados_ml$valor_cliente, 0.7, na.rm = TRUE),
  .(
    cliente_id, region, edad, ingresos, valor_cliente,
    prob_churn = round(prob_churn, 3),
    transacciones_año, monto_total_año,
    satisfaccion, engagement_score,
    accion_recomendada = fcase(
      satisfaccion < 3, "Mejora_Servicio",
      engagement_score < 5, "Aumentar_Engagement", 
      monto_total_año < 1000, "Incentivo_Compra",
      default = "Programa_Lealtad"
    )
  )
][order(-valor_cliente)]

cat("\n=== TOP 10 CLIENTES PARA RETENCIÓN INMEDIATA ===\n")
#> 
#> === TOP 10 CLIENTES PARA RETENCIÓN INMEDIATA ===
print(head(clientes_retencion, 10))
#>     cliente_id region  edad ingresos valor_cliente prob_churn transacciones_año
#>          <int> <char> <num>    <num>         <num>      <num>             <int>
#>  1:         69 Centro    49   312277      761.9559      0.899                 1
#>  2:       9176 Centro    22    70610      406.0075      0.888                 2
#>  3:       6866    Sur    49    61726      274.0634      0.967                 1
#>  4:       3461    Sur    23   138172      269.4354      0.998                 2
#>  5:       8123  Oeste    49   140832      266.1725      0.705                 1
#>  6:       3497  Norte    39   102743      240.4186      0.849                 1
#>  7:       2847   Este    41    82017      216.5249      0.890                 2
#>  8:       2896   Este    21    83534      197.9756      0.836                 2
#>  9:       4247  Oeste    35    68037      175.5355      0.709                 1
#> 10:       9463  Norte    20    46169      164.3616      0.843                 2
#>     monto_total_año satisfaccion engagement_score accion_recomendada
#>               <num>        <num>            <num>             <char>
#>  1:        11.56000          3.1             10.0   Incentivo_Compra
#>  2:       292.31936          3.5             10.0   Incentivo_Compra
#>  3:        55.99000          3.1             10.0   Incentivo_Compra
#>  4:       190.29448          4.3              9.3   Incentivo_Compra
#>  5:        42.78000          1.0              8.0    Mejora_Servicio
#>  6:       103.06000          2.4              6.8    Mejora_Servicio
#>  7:       169.53063          3.1             10.0   Incentivo_Compra
#>  8:        73.40943          4.7             10.0   Incentivo_Compra
#>  9:        49.41055          2.5              4.5    Mejora_Servicio
#> 10:       148.34431          2.3             10.0    Mejora_Servicio

# Análisis de efectividad del modelo por segmento
if(nrow(resultados_ml[!is.na(churn_flag)]) > 0) {
  efectividad_modelo <- resultados_ml[!is.na(churn_flag),
    .(
      precision = sum(pred_churn == "Si" & churn == "Si") / sum(pred_churn == "Si"),
      recall = sum(pred_churn == "Si" & churn == "Si") / sum(churn == "Si"),
      accuracy = mean(pred_churn == churn),
      clientes_evaluados = .N
    ),
    by = .(region, categoria_edad)
  ][clientes_evaluados >= 10]  # Solo segmentos con suficientes datos
  
  cat("\n=== EFECTIVIDAD DEL MODELO POR SEGMENTO ===\n")
  print(head(efectividad_modelo[order(-accuracy)]))
}
#> 
#> === EFECTIVIDAD DEL MODELO POR SEGMENTO ===
#>    region categoria_edad  precision     recall  accuracy clientes_evaluados
#>    <char>         <fctr>      <num>      <num>     <num>              <int>
#> 1:  Oeste         Senior 0.20000000 0.25000000 0.8478261                 46
#> 2:   Este         Adulto 0.22222222 0.33333333 0.8225806                124
#> 3:   Este          Joven 0.13333333 0.22222222 0.8148148                108
#> 4:  Norte         Adulto 0.19047619 0.30769231 0.8000000                130
#> 5:  Oeste         Adulto 0.08333333 0.07142857 0.7837838                111
#> 6:  Norte          Joven 0.07142857 0.09090909 0.7676768                 99

12.3 dtplyr: El Puente Entre data.table y tidyverse

12.3.1 1. Introducción y Casos de Uso

if(requireNamespace("dtplyr", quietly = TRUE) && requireNamespace("dplyr", quietly = TRUE)) {
  library(dtplyr)
  library(dplyr, warn.conflicts = FALSE)
  
  # Convertir data.table a lazy_dt
  clientes_lazy <- lazy_dt(datos_clientes)
  
  # Workflow con sintaxis dplyr que se traduce a data.table
  analisis_dtplyr <- clientes_lazy %>%
    filter(edad >= 25, edad <= 65) %>%
    mutate(
      categoria_valor = case_when(
        valor_cliente >= quantile(valor_cliente, 0.8) ~ "Premium",
        valor_cliente >= quantile(valor_cliente, 0.5) ~ "Standard",
        TRUE ~ "Basic"
      ),
      riesgo_total = riesgo_churn + (5 - satisfaccion) / 5
    ) %>%
    group_by(region, categoria_valor) %>%
    summarise(
      clientes = n(),
      valor_promedio = round(mean(valor_cliente), 2),
      engagement_promedio = round(mean(engagement_score), 2),
      churn_rate = round(mean(churn_flag) * 100, 1),
      riesgo_promedio = round(mean(riesgo_total), 2),
      .groups = 'drop'
    ) %>%
    arrange(desc(valor_promedio)) %>%
    as.data.table()  # Convertir de vuelta a data.table
  
  print("Resultado del análisis con dtplyr:")
  print(head(analisis_dtplyr, 10))
  
  # Ver el código data.table generado
  cat("\n=== CÓDIGO DATA.TABLE GENERADO POR DTPLYR ===\n")
  codigo_generado <- clientes_lazy %>%
    filter(edad >= 25, edad <= 65) %>%
    group_by(region) %>%
    summarise(valor_promedio = mean(valor_cliente), .groups = 'drop') %>%
    show_query()
  
} else {
  cat("💡 Para usar dtplyr, instala los paquetes:\n")
  cat("install.packages(c('dtplyr', 'dplyr'))\n")
}
#> [1] "Resultado del análisis con dtplyr:"
#>     region categoria_valor clientes valor_promedio engagement_promedio
#>     <char>          <char>    <int>          <num>               <num>
#>  1: Centro         Premium      329         278.55                9.09
#>  2:  Norte         Premium      361         272.26                9.14
#>  3:   Este         Premium      327         270.18                9.18
#>  4:    Sur         Premium      328         267.10                9.03
#>  5:  Oeste         Premium      293         266.28                8.93
#>  6:   Este        Standard      508          91.43                7.97
#>  7:    Sur        Standard      486          90.45                8.09
#>  8:  Norte        Standard      476          89.11                7.87
#>  9:  Oeste        Standard      527          88.67                7.88
#> 10: Centro        Standard      460          87.94                8.00
#>     churn_rate riesgo_promedio
#>          <num>           <num>
#>  1:       12.5            0.92
#>  2:        9.1            0.84
#>  3:       11.6            0.97
#>  4:        8.5            0.94
#>  5:       12.6            0.91
#>  6:       13.6            0.87
#>  7:       14.4            0.87
#>  8:       12.8            0.99
#>  9:       10.8            0.91
#> 10:       13.0            0.92
#> 
#> === CÓDIGO DATA.TABLE GENERADO POR DTPLYR ===

12.3.2 2. Comparación de Performance: dtplyr vs dplyr puro

if(requireNamespace("dtplyr", quietly = TRUE) && requireNamespace("dplyr", quietly = TRUE)) {
  library(microbenchmark)
  
  # Crear dataset más grande para benchmark
  datos_benchmark <- rbindlist(replicate(5, datos_clientes, simplify = FALSE))
  
  # Operación compleja para comparar
  operacion_compleja <- function(data, metodo) {
    if(metodo == "dplyr") {
      # dplyr puro sobre data.frame
      as.data.frame(data) %>%
        filter(edad >= 30, satisfaccion >= 3) %>%
        group_by(region, categoria_ingresos) %>%
        summarise(
          clientes = n(),
          valor_total = sum(valor_cliente),
          engagement_avg = mean(engagement_score),
          .groups = 'drop'
        ) %>%
        arrange(desc(valor_total))
    } else if(metodo == "dtplyr") {
      # dtplyr (sintaxis dplyr + motor data.table)
      lazy_dt(data) %>%
        filter(edad >= 30, satisfaccion >= 3) %>%
        group_by(region, categoria_ingresos) %>%
        summarise(
          clientes = n(),
          valor_total = sum(valor_cliente),
          engagement_avg = mean(engagement_score),
          .groups = 'drop'
        ) %>%
        arrange(desc(valor_total)) %>%
        as.data.table()
    } else {
      # data.table puro
      data[
        edad >= 30 & satisfaccion >= 3,
        .(
          clientes = .N,
          valor_total = sum(valor_cliente),
          engagement_avg = mean(engagement_score)
        ),
        by = .(region, categoria_ingresos)
      ][order(-valor_total)]
    }
  }
  
  # Benchmark
  benchmark_results <- microbenchmark(
    dplyr_puro = operacion_compleja(datos_benchmark, "dplyr"),
    dtplyr = operacion_compleja(datos_benchmark, "dtplyr"),
    data_table = operacion_compleja(datos_benchmark, "data.table"),
    times = 10
  )
  
  cat("=== BENCHMARK DE PERFORMANCE ===\n")
  print(benchmark_results)
  
  # Calcular speedup
  medias <- aggregate(time ~ expr, data = benchmark_results, FUN = mean)
  medias$speedup <- medias$time[medias$expr == "dplyr_puro"] / medias$time
  
  cat("\n=== SPEEDUP RELATIVO (vs dplyr puro) ===\n")
  print(medias[, c("expr", "speedup")])
  
} else {
  cat("Benchmark requiere dtplyr y dplyr instalados\n")
}
#> === BENCHMARK DE PERFORMANCE ===
#> Unit: milliseconds
#>        expr      min       lq      mean   median        uq      max neval
#>  dplyr_puro 7.651211 7.991026 11.723056 9.585700 10.979950 33.61189    10
#>      dtplyr 7.059959 7.183349  8.336634 7.435068  9.899625 11.70520    10
#>  data_table 2.452785 2.534377  2.845249 2.733674  3.121031  3.36696    10
#> 
#> === SPEEDUP RELATIVO (vs dplyr puro) ===
#>         expr  speedup
#> 1 dplyr_puro 1.000000
#> 2     dtplyr 1.406210
#> 3 data_table 4.120222

12.4 Conexión con Bases de Datos y Big Data

12.4.1 1. Lectura/Escritura Eficiente con fread/fwrite

# === FREAD: Lectura ultrarrápida ===
# Crear archivo de ejemplo grande
archivo_test <- tempfile(fileext = ".csv")

# Generar datos sintéticos para el ejemplo
datos_grandes <- data.table(
  id = 1:100000,
  timestamp = seq(as.POSIXct("2024-01-01"), by = "min", length.out = 100000),
  sensor_value = round(rnorm(100000, 50, 15), 2),
  location = sample(c("Factory_A", "Factory_B", "Factory_C"), 100000, replace = TRUE),
  quality_score = round(runif(100000, 0.8, 1.0), 3),
  batch_id = sample(1:1000, 100000, replace = TRUE)
)

# Escribir archivo
cat("Escribiendo archivo de prueba...\n")
#> Escribiendo archivo de prueba...
tiempo_write <- system.time({
  fwrite(datos_grandes, archivo_test, 
         nThread = getDTthreads(),
         showProgress = FALSE)
})

# Información del archivo
info_archivo <- file.info(archivo_test)
cat("Archivo creado:", round(info_archivo$size / 1024^2, 2), "MB\n")
#> Archivo creado: 4.06 MB
cat("Tiempo de escritura:", round(tiempo_write[3], 3), "segundos\n")
#> Tiempo de escritura: 0.018 segundos

# Lectura con diferentes configuraciones
cat("\n=== COMPARACIÓN DE MÉTODOS DE LECTURA ===\n")
#> 
#> === COMPARACIÓN DE MÉTODOS DE LECTURA ===

# 1. fread básico
tiempo_fread_basico <- system.time({
  datos_fread <- fread(archivo_test, showProgress = FALSE)
})

# 2. fread optimizado
tiempo_fread_opt <- system.time({
  datos_fread_opt <- fread(
    archivo_test,
    nThread = getDTthreads(),
    select = c("id", "timestamp", "sensor_value", "location"),  # Solo columnas necesarias
    colClasses = list(character = "location", numeric = c("sensor_value")),
    showProgress = FALSE
  )
})

# 3. read.csv para comparación
tiempo_base_r <- system.time({
  datos_base <- read.csv(archivo_test, stringsAsFactors = FALSE)
})

cat("fread básico:", round(tiempo_fread_basico[3], 3), "segundos\n")
#> fread básico: 0.009 segundos
cat("fread optimizado:", round(tiempo_fread_opt[3], 3), "segundos\n")
#> fread optimizado: 0.008 segundos
cat("read.csv (base R):", round(tiempo_base_r[3], 3), "segundos\n")
#> read.csv (base R): 0.184 segundos
cat("Speedup fread vs base R:", round(tiempo_base_r[3] / tiempo_fread_basico[3], 1), "x\n")
#> Speedup fread vs base R: 20.4 x

# Verificar que los datos son idénticos
cat("Datos idénticos:", identical(datos_fread$id, datos_fread_opt$id), "\n")
#> Datos idénticos: TRUE

# Limpiar
unlink(archivo_test)

12.4.2 2. Integración con Bases de Datos

# Ejemplo de integración con bases de datos
library(DBI)
library(RSQLite)  # o RPostgreSQL, RMySQL, etc.

# === SETUP DE CONEXIÓN ===
# Crear base de datos SQLite para el ejemplo
con <- dbConnect(RSQLite::SQLite(), ":memory:")

# Escribir data.table a la base de datos
dbWriteTable(con, "clientes", datos_clientes)
dbWriteTable(con, "transacciones", transacciones_detalle)

# === WORKFLOW HÍBRIDO: SQL + data.table ===

# 1. Query inicial en SQL (aprovechar índices de DB)
query_sql <- "
  SELECT c.cliente_id, c.region, c.edad, c.ingresos,
         t.monto, t.fecha_transaccion, t.producto_categoria
  FROM clientes c
  JOIN transacciones t ON c.cliente_id = t.cliente_id
  WHERE c.edad >= 25 AND c.edad <= 65
    AND t.fecha_transaccion >= '2024-01-01'
"

# 2. Traer datos a data.table
datos_query <- as.data.table(dbGetQuery(con, query_sql))

# 3. Análisis complejo con data.table (más rápido que SQL)
analisis_hibrido <- datos_query[,
  .(
    transacciones_total = .N,
    monto_total = sum(monto),
    monto_promedio = round(mean(monto), 2),
    categorias_distintas = uniqueN(producto_categoria),
    primera_compra = min(fecha_transaccion),
    ultima_compra = max(fecha_transaccion)
  ),
  by = .(cliente_id, region)
][, `:=`(
  dias_activo = as.numeric(as.Date(ultima_compra) - as.Date(primera_compra)) + 1,
  frecuencia_compra = transacciones_total / pmax(as.numeric(as.Date(ultima_compra) - as.Date(primera_compra)) + 1, 1)
)][order(-monto_total)]

# 4. Escribir resultados de vuelta a DB (opcional)
dbWriteTable(con, "analisis_clientes", analisis_hibrido, overwrite = TRUE)

# 5. Verificación
resumen_db <- dbGetQuery(con, "SELECT COUNT(*) as clientes_analizados FROM analisis_clientes")
cat("Clientes analizados en DB:", resumen_db$clientes_analizados, "\n")

# Cerrar conexión
dbDisconnect(con)

# === MEJORES PRÁCTICAS ===
# 1. Usar SQL para filtros iniciales y joins simples
# 2. Traer datos a data.table para análisis complejos
# 3. Aprovechar índices de DB para WHERE y JOIN
# 4. Usar data.table para agregaciones complejas y feature engineering
# 5. Escribir resultados finales de vuelta a DB si es necesario

12.4.3 3. Integración con Apache Arrow/Parquet

# Ejemplo de integración con ecosistema Arrow
library(arrow)

# === ESCRITURA A PARQUET ===
# Parquet es ultra-eficiente para datasets grandes
archivo_parquet <- tempfile(fileext = ".parquet")

# Escribir data.table a Parquet
write_parquet(datos_clientes, archivo_parquet)

# === LECTURA DESDE PARQUET ===
# Leer directo a data.table
datos_parquet <- read_parquet(archivo_parquet, as_data_frame = TRUE)
setDT(datos_parquet)  # Asegurar que es data.table

# === DATASETS PARTICIONADOS ===
# Para datasets muy grandes, usar particiones
directorio_particionado <- file.path(tempdir(), "partitioned_data")
dir.create(directorio_particionado, showWarnings = FALSE, recursive = TRUE)

# Particionar por región - método más robusto
for(region_name in unique(datos_clientes$region)) {
  region_data <- datos_clientes[region == region_name]
  archivo_region <- file.path(directorio_particionado, paste0("region_", region_name, ".parquet"))
  write_parquet(region_data, archivo_region)
}

# Verificar que los archivos se crearon correctamente
archivos_parquet <- list.files(directorio_particionado, pattern = "\\.parquet$", full.names = TRUE)
cat("Archivos Parquet creados:", length(archivos_parquet), "\n")

# Leer dataset particionado solo si hay archivos válidos
if(length(archivos_parquet) > 0) {
  dataset <- open_dataset(directorio_particionado)
} else {
  stop("No se pudieron crear archivos Parquet válidos")
}

# Query con pushdown de predicados (muy eficiente)
tryCatch({
  resultado_arrow <- dataset %>%
    filter(edad >= 30, satisfaccion >= 4) %>%
    group_by(region) %>%
    summarise(
      clientes = n(),
      valor_promedio = mean(valor_cliente),
      ingresos_promedio = mean(ingresos)
    ) %>%
    collect() %>%  # Traer a memoria
    as.data.table()  # Convertir a data.table
  
  print(resultado_arrow)
}, error = function(e) {
  cat("Error en consulta Arrow:", conditionMessage(e), "\n")
  cat("Usando método alternativo con data.table directo...\n")
  
  # Fallback: usar data.table directamente
  resultado_arrow <- datos_clientes[edad >= 30 & satisfaccion >= 4, .(
    clientes = .N,
    valor_promedio = mean(valor_cliente),
    ingresos_promedio = mean(ingresos)
  ), by = region]
  
  print(resultado_arrow)
})

# === VENTAJAS DEL WORKFLOW ARROW + DATA.TABLE ===
# 1. Parquet es extremadamente eficiente en espacio
# 2. Pushdown de predicados reduce transferencia de datos
# 3. Compatibilidad con otros lenguajes (Python, Spark)
# 4. data.table para análisis final en R

12.5 Casos de Uso Industriales Reales

12.5.1 1. Sistema de Monitoreo IoT en Tiempo Real

# === ANÁLISIS DE SENSORES IOT ===
# Simular análisis en tiempo real de sensores

# Función para procesar batch de datos de sensores
procesar_batch_sensores <- function(datos_sensores, ventana_horas = 1) {
  # Análisis de anomalías en tiempo real
  datos_sensores[,
    `:=`(
      temp_anomaly = abs(temperatura - mean(temperatura)) > 2 * sd(temperatura),
      humidity_anomaly = abs(humedad - mean(humedad)) > 2 * sd(humedad),
      battery_critical = nivel_bateria < 15
    ),
    by = .(sensor_id, fecha)
  ]
  
  # Resumen por sensor y hora
  resumen_sensores <- datos_sensores[,
    .(
      temp_promedio = round(mean(temperatura), 2),
      temp_min = min(temperatura),
      temp_max = max(temperatura),
      humedad_promedio = round(mean(humedad), 1),
      presion_promedio = round(mean(presion), 1),
      movimientos_detectados = sum(movimiento_detectado),
      anomalias_temp = sum(temp_anomaly),
      anomalias_humedad = sum(humidity_anomaly),
      alertas_bateria = sum(battery_critical),
      lecturas_total = .N,
      uptime_pct = round((1 - sum(is.na(temperatura)) / .N) * 100, 1)
    ),
    by = .(sensor_id, fecha, hora)
  ][, `:=`(
    estado_sensor = fcase(
      alertas_bateria > 0, "CRÍTICO",
      anomalias_temp + anomalias_humedad > 5, "ADVERTENCIA",
      uptime_pct < 95, "DEGRADADO",
      default = "NORMAL"
    ),
    score_salud = pmin(100, uptime_pct - (anomalias_temp + anomalias_humedad) * 5 - alertas_bateria * 20)
  )]
  
  return(resumen_sensores)
}

# Procesar datos de sensores
analisis_sensores <- procesar_batch_sensores(sensores_iot)

# Dashboard de alertas críticas
alertas_criticas <- analisis_sensores[
  estado_sensor %in% c("CRÍTICO", "ADVERTENCIA"),
  .(
    sensor_id, fecha, hora,
    estado_sensor, score_salud,
    anomalias_temp, anomalias_humedad, alertas_bateria,
    accion_requerida = fcase(
      alertas_bateria > 0, "Cambiar_Batería",
      anomalias_temp > 3, "Revisar_Sensor_Temperatura",
      anomalias_humedad > 3, "Revisar_Sensor_Humedad",
      default = "Inspección_General"
    )
  )
][order(fecha, hora, -score_salud)]

cat("=== ALERTAS CRÍTICAS DEL SISTEMA IOT ===\n")
#> === ALERTAS CRÍTICAS DEL SISTEMA IOT ===
print(head(alertas_criticas, 15))
#>     sensor_id      fecha  hora estado_sensor score_salud anomalias_temp
#>        <char>     <Date> <int>        <char>       <num>          <int>
#> 1: SENSOR_091 2024-01-22    20       CRÍTICO          80              0
#> 2: SENSOR_078 2024-01-24     4       CRÍTICO          80              0
#> 3: SENSOR_030 2024-01-26    19       CRÍTICO          80              0
#> 4: SENSOR_032 2024-01-31     7       CRÍTICO          80              0
#>    anomalias_humedad alertas_bateria accion_requerida
#>                <int>           <int>           <char>
#> 1:                 0               1  Cambiar_Batería
#> 2:                 0               1  Cambiar_Batería
#> 3:                 0               1  Cambiar_Batería
#> 4:                 0               1  Cambiar_Batería

# Estadísticas de salud del sistema
salud_sistema <- analisis_sensores[,
  .(
    sensores_activos = uniqueN(sensor_id),
    sensores_criticos = uniqueN(sensor_id[estado_sensor == "CRÍTICO"]),
    sensores_degradados = uniqueN(sensor_id[estado_sensor %in% c("ADVERTENCIA", "DEGRADADO")]),
    score_salud_promedio = round(mean(score_salud), 1),
    anomalias_totales = sum(anomalias_temp + anomalias_humedad),
    uptime_sistema = round(mean(uptime_pct), 1)
  ),
  by = .(fecha)
]

cat("\n=== SALUD GENERAL DEL SISTEMA ===\n")
#> 
#> === SALUD GENERAL DEL SISTEMA ===
print(head(salud_sistema))
#>         fecha sensores_activos sensores_criticos sensores_degradados
#>        <Date>            <int>             <int>               <int>
#> 1: 2024-01-01              100                 0                   0
#> 2: 2024-01-02              100                 0                   0
#> 3: 2024-01-03              100                 0                   0
#> 4: 2024-01-04              100                 0                   0
#> 5: 2024-01-05              100                 0                   0
#> 6: 2024-01-06              100                 0                   0
#>    score_salud_promedio anomalias_totales uptime_sistema
#>                   <num>             <int>          <num>
#> 1:                  100                 0            100
#> 2:                  100                 0            100
#> 3:                  100                 0            100
#> 4:                  100                 0            100
#> 5:                  100                 0            100
#> 6:                  100                 0            100

12.5.2 2. Sistema de Recomendaciones E-commerce

# === MOTOR DE RECOMENDACIONES ===
# Sistema basado en comportamiento de compra

# Función para calcular similaridad entre clientes
calcular_recomendaciones <- function(transacciones_dt, cliente_target, top_n = 5) {
  
  # Matriz de compras por cliente-categoría
  matriz_compras <- transacciones_dt[,
    .(
      total_comprado = sum(monto_final),
      frecuencia = .N
    ),
    by = .(cliente_id, producto_categoria)
  ]
  
  # Perfil del cliente target
  perfil_target <- matriz_compras[cliente_id == cliente_target]
  
  if(nrow(perfil_target) == 0) {
    return(data.table(mensaje = "Cliente no encontrado"))
  }
  
  # Encontrar clientes similares
  clientes_similares <- matriz_compras[
    producto_categoria %in% perfil_target$producto_categoria & 
    cliente_id != cliente_target,
    .(
      overlap_categorias = .N,
      valor_similar = sum(total_comprado)
    ),
    by = cliente_id
  ][overlap_categorias >= 2][order(-overlap_categorias, -valor_similar)]
  
  # Recomendaciones basadas en clientes similares
  if(nrow(clientes_similares) > 0) {
    recomendaciones <- matriz_compras[
      cliente_id %in% head(clientes_similares$cliente_id, 20) &
      !producto_categoria %in% perfil_target$producto_categoria,
      .(
        score_recomendacion = sum(total_comprado),
        clientes_que_compran = .N,
        frecuencia_promedio = round(mean(frecuencia), 1)
      ),
      by = producto_categoria
    ][clientes_que_compran >= 3][order(-score_recomendacion)][1:top_n]
    
    return(recomendaciones)
  } else {
    return(data.table(mensaje = "No hay suficientes datos para recomendaciones"))
  }
}

# Función para análisis de mercado
analizar_tendencias_mercado <- function(transacciones_dt, periodo_dias = 30) {
  fecha_corte <- max(transacciones_dt$fecha_transaccion) - periodo_dias
  
  tendencias <- transacciones_dt[
    fecha_transaccion >= fecha_corte,
    .(
      ventas_recientes = sum(monto_final),
      transacciones_recientes = .N,
      clientes_unicos = uniqueN(cliente_id),
      ticket_promedio = round(mean(monto_final), 2),
      crecimiento_semanal = .N / (periodo_dias / 7)
    ),
    by = producto_categoria
  ][order(-ventas_recientes)]
  
  # Calcular métricas adicionales
  tendencias[, `:=`(
    penetracion_mercado = round((clientes_unicos / uniqueN(transacciones_dt$cliente_id)) * 100, 1),
    velocidad_venta = round(transacciones_recientes / periodo_dias, 2),
    categoria_trend = fcase(
      crecimiento_semanal > quantile(crecimiento_semanal, 0.75), "📈 CRECIENTE",
      crecimiento_semanal < quantile(crecimiento_semanal, 0.25), "📉 DECLINANTE",
      default = "➡️ ESTABLE"
    )
  )]
  
  return(tendencias)
}

# Ejecutar análisis de recomendaciones
cliente_ejemplo <- datos_clientes[sample(.N, 1), cliente_id]
recomendaciones <- calcular_recomendaciones(transacciones_detalle, cliente_ejemplo)

cat("=== RECOMENDACIONES PARA CLIENTE", cliente_ejemplo, "===\n")
#> === RECOMENDACIONES PARA CLIENTE 5677 ===
print(recomendaciones)
#>    producto_categoria score_recomendacion clientes_que_compran
#>                <char>               <num>                <int>
#> 1:               <NA>                  NA                   NA
#> 2:               <NA>                  NA                   NA
#> 3:               <NA>                  NA                   NA
#> 4:               <NA>                  NA                   NA
#> 5:               <NA>                  NA                   NA
#>    frecuencia_promedio
#>                  <num>
#> 1:                  NA
#> 2:                  NA
#> 3:                  NA
#> 4:                  NA
#> 5:                  NA

# Análisis de tendencias de mercado
tendencias_mercado <- analizar_tendencias_mercado(transacciones_detalle, 60)

cat("\n=== TENDENCIAS DE MERCADO (últimos 60 días) ===\n")
#> 
#> === TENDENCIAS DE MERCADO (últimos 60 días) ===
print(tendencias_mercado)
#>    producto_categoria ventas_recientes transacciones_recientes clientes_unicos
#>                <char>            <num>                   <int>           <int>
#> 1:             Sports         59738.74                     728             696
#> 2:              Books         57747.86                     673             658
#> 3:               Home         57641.77                     700             678
#> 4:           Clothing         55649.79                     723             699
#> 5:             Beauty         55388.89                     680             662
#> 6:        Electronics         52302.97                     689             663
#>    ticket_promedio crecimiento_semanal penetracion_mercado velocidad_venta
#>              <num>               <num>               <num>           <num>
#> 1:           82.06            84.93333                 7.0           12.13
#> 2:           85.81            78.51667                 6.6           11.22
#> 3:           82.35            81.66667                 6.8           11.67
#> 4:           76.97            84.35000                 7.0           12.05
#> 5:           81.45            79.33333                 6.7           11.33
#> 6:           75.91            80.38333                 6.7           11.48
#>    categoria_trend
#>             <char>
#> 1:    📈 CRECIENTE
#> 2:   📉 DECLINANTE
#> 3:       ➡️ ESTABLE
#> 4:    📈 CRECIENTE
#> 5:   📉 DECLINANTE
#> 6:       ➡️ ESTABLE

# Análisis de cohorstes de clientes
analisis_cohortes <- transacciones_detalle[,
  .(
    primera_compra = min(fecha_transaccion),
    ultima_compra = max(fecha_transaccion),
    valor_total = sum(monto_final),
    frecuencia_compra = .N
  ),
  by = cliente_id
][, `:=`(
  cohorte_mes = format(primera_compra, "%Y-%m"),
  dias_como_cliente = as.numeric(ultima_compra - primera_compra) + 1
)][, `:=`(
  valor_por_dia = round(valor_total / pmax(dias_como_cliente, 1), 2)
)][,
  .(
    clientes_cohorte = .N,
    valor_promedio_cohorte = round(mean(valor_total), 2),
    dias_retencion_promedio = round(mean(dias_como_cliente), 1),
    valor_por_dia_promedio = round(mean(valor_por_dia), 2)
  ),
  by = cohorte_mes
][order(cohorte_mes)]

cat("\n=== ANÁLISIS DE COHORTES POR MES ===\n")
#> 
#> === ANÁLISIS DE COHORTES POR MES ===
print(head(analisis_cohortes, 12))
#>     cohorte_mes clientes_cohorte valor_promedio_cohorte dias_retencion_promedio
#>          <char>            <int>                  <num>                   <num>
#>  1:     2023-01             1919                 480.73                   576.0
#>  2:     2023-02             1419                 468.32                   540.2
#>  3:     2023-03             1254                 449.64                   513.0
#>  4:     2023-04             1018                 434.10                   479.9
#>  5:     2023-05              797                 418.61                   458.0
#> ---                                                                            
#>  8:     2023-08              451                 354.58                   356.5
#>  9:     2023-09              366                 342.35                   336.0
#> 10:     2023-10              310                 326.32                   289.0
#> 11:     2023-11              218                 323.99                   274.6
#> 12:     2023-12              192                 277.01                   260.5
#>     valor_por_dia_promedio
#>                      <num>
#>  1:                   1.71
#>  2:                   1.81
#>  3:                   2.11
#>  4:                   2.59
#>  5:                   2.89
#> ---                       
#>  8:                   3.89
#>  9:                   5.20
#> 10:                   7.84
#> 11:                   6.48
#> 12:                   5.16

12.6 Ejercicio Final: Aplicación Completa de Producción

🏋️ Ejercicio 10: Sistema de Analytics Empresarial

Diseña un sistema completo que integre:

  1. Pipeline de datos con data.table
  2. Dashboard Shiny interactivo
  3. Modelo predictivo con tidymodels
  4. Alertas automáticas
  5. Reportes ejecutivos

Usa los datasets de clientes, transacciones y sensores como base.

# === PIPELINE DE DATOS UNIFICADO ===
crear_pipeline_analytics <- function() {
  
  # 1. CONSOLIDACIÓN DE DATOS
  pipeline_data <- list()
  
  # Métricas de clientes
  pipeline_data$clientes_kpis <- datos_clientes[,
    .(
      clientes_total = .N,
      valor_total = sum(valor_cliente),
      churn_rate = round(mean(churn_flag) * 100, 1),
      satisfaccion_promedio = round(mean(satisfaccion), 2),
      engagement_promedio = round(mean(engagement_score), 2)
    ),
    by = .(region, categoria_ingresos)
  ]
  
  # Métricas de transacciones
  pipeline_data$transacciones_kpis <- transacciones_detalle[
    fecha_transaccion >= Sys.Date() - 90,  # Últimos 90 días
    .(
      revenue_total = sum(monto_final),
      transacciones_total = .N,
      ticket_promedio = round(mean(monto_final), 2),
      clientes_activos = uniqueN(cliente_id),
      productos_vendidos = sum(1 - es_devolucion)
    ),
    by = .(mes = month(fecha_transaccion), producto_categoria)
  ]
  
  # Estado de sensores IoT
  pipeline_data$sensores_status <- sensores_iot[
    fecha >= Sys.Date() - 7,  # Última semana
    .(
      sensores_activos = uniqueN(sensor_id),
      alertas_criticas = sum(alerta_temperatura + alerta_bateria + alerta_humedad),
      uptime_promedio = round(mean(1 - is.na(temperatura)) * 100, 1),
      score_salud = round(mean(100 - alerta_temperatura * 20 - alerta_bateria * 30), 1)
    ),
    by = fecha
  ]
  
  # 2. ALERTAS AUTOMÁTICAS
  alertas <- list()
  
  # Alerta de churn elevado
  alertas$churn_critico <- pipeline_data$clientes_kpis[
    churn_rate > 15,
    .(region, categoria_ingresos, churn_rate, valor_total)
  ]
  
  # Alerta de caída de revenue
  alertas$revenue_bajo <- pipeline_data$transacciones_kpis[,
    .(revenue_cambio = (revenue_total / shift(revenue_total, 1) - 1) * 100),
    by = producto_categoria
  ][revenue_cambio < -10 & !is.na(revenue_cambio)]
  
  # Alerta de sensores críticos
  alertas$sensores_criticos <- pipeline_data$sensores_status[
    score_salud < 80 | alertas_criticas > 10
  ]
  
  # 3. DASHBOARD SUMMARY
  dashboard_summary <- list(
    kpis_generales = list(
      clientes_total = sum(pipeline_data$clientes_kpis$clientes_total),
      revenue_total = sum(pipeline_data$transacciones_kpis$revenue_total),
      churn_promedio = round(weighted.mean(pipeline_data$clientes_kpis$churn_rate, 
                                          pipeline_data$clientes_kpis$clientes_total), 1),
      alertas_activas = length(alertas$churn_critico) + nrow(alertas$revenue_bajo) + nrow(alertas$sensores_criticos)
    ),
    alertas_activas = alertas,
    datos_pipeline = pipeline_data
  )
  
  return(dashboard_summary)
}

# Ejecutar pipeline
sistema_analytics <- crear_pipeline_analytics()

# === REPORTE EJECUTIVO AUTOMÁTICO ===
generar_reporte_ejecutivo <- function(analytics_data) {
  cat("━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n")
  cat("                    📊 REPORTE EJECUTIVO EMPRESARIAL                     \n")
  cat("━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n\n")
  
  kpis <- analytics_data$kpis_generales
  alertas <- analytics_data$alertas_activas
  
  # KPIs principales
  cat("📈 INDICADORES CLAVE DE RENDIMIENTO:\n")
  cat("   • Total de Clientes:", scales::comma(kpis$clientes_total), "\n")
  cat("   • Revenue Total:", scales::dollar(kpis$revenue_total), "\n")
  cat("   • Tasa de Churn Promedio:", kpis$churn_promedio, "%\n")
  cat("   • Alertas Activas:", kpis$alertas_activas, "\n\n")
  
  # Estado de alertas
  cat("🚨 ESTADO DE ALERTAS:\n")
  cat("   • Regiones con Churn Crítico:", nrow(alertas$churn_critico), "\n")
  cat("   • Productos con Revenue Bajo:", nrow(alertas$revenue_bajo), "\n")
  cat("   • Sensores en Estado Crítico:", nrow(alertas$sensores_criticos), "\n\n")
  
  # Recomendaciones automáticas
  cat("💡 RECOMENDACIONES AUTOMÁTICAS:\n")
  if(nrow(alertas$churn_critico) > 0) {
    cat("   • ACCIÓN INMEDIATA: Implementar programa de retención en regiones críticas\n")
  }
  if(nrow(alertas$revenue_bajo) > 0) {
    cat("   • ANÁLISIS REQUERIDO: Investigar caída de ventas en productos específicos\n")
  }
  if(nrow(alertas$sensores_criticos) > 0) {
    cat("   • MANTENIMIENTO: Revisar sensores con bajo score de salud\n")
  }
  if(kpis$alertas_activas == 0) {
    cat("   • ✅ SISTEMA SALUDABLE: Todos los indicadores dentro de rangos normales\n")
  }
  
  cat("\n━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n")
  cat("Reporte generado automáticamente:", format(Sys.time(), "%Y-%m-%d %H:%M:%S"), "\n")
  cat("━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n")
}

# Generar reporte
generar_reporte_ejecutivo(sistema_analytics)
#> ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
#>                     📊 REPORTE EJECUTIVO EMPRESARIAL                     
#> ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
#> 
#> 📈 INDICADORES CLAVE DE RENDIMIENTO:
#>    • Total de Clientes: 10,000 
#>    • Revenue Total: $0 
#>    • Tasa de Churn Promedio: 12.1 %
#>    • Alertas Activas: 4 
#> 
#> 🚨 ESTADO DE ALERTAS:
#>    • Regiones con Churn Crítico: 0 
#>    • Productos con Revenue Bajo: 0 
#>    • Sensores en Estado Crítico: 0 
#> 
#> 💡 RECOMENDACIONES AUTOMÁTICAS:
#> 
#> ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
#> Reporte generado automáticamente: 2025-08-21 07:05:24 
#> ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

# === MÉTRICAS DETALLADAS ===
cat("\n📊 DETALLE DE ALERTAS CRÍTICAS:\n\n")
#> 
#> 📊 DETALLE DE ALERTAS CRÍTICAS:

if(nrow(sistema_analytics$alertas_activas$churn_critico) > 0) {
  cat("🔴 CHURN CRÍTICO POR REGIÓN:\n")
  print(sistema_analytics$alertas_activas$churn_critico)
  cat("\n")
}

if(nrow(sistema_analytics$alertas_activas$revenue_bajo) > 0) {
  cat("📉 PRODUCTOS CON REVENUE BAJO:\n")
  print(head(sistema_analytics$alertas_activas$revenue_bajo))
  cat("\n")
}

if(nrow(sistema_analytics$alertas_activas$sensores_criticos) > 0) {
  cat("⚠️ SENSORES EN ESTADO CRÍTICO:\n")
  print(head(sistema_analytics$alertas_activas$sensores_criticos))
}

Componentes del Sistema Completo:

  1. Pipeline de Datos: Consolidación automática de múltiples fuentes
  2. Sistema de Alertas: Detección automática de anomalías
  3. Dashboard KPIs: Métricas en tiempo real
  4. Reporte Ejecutivo: Generación automática de insights
  5. Recomendaciones: Acciones basadas en datos
  6. Escalabilidad: Modular y extensible

🎯 Puntos Clave de Este Capítulo
  1. Shiny + data.table = Aplicaciones web ultrarrápidas y responsivas
  2. tidymodels se integra perfectamente con data.table para ML robusto
  3. dtplyr facilita la transición desde dplyr manteniendo performance
  4. fread/fwrite son las herramientas más rápidas de R para I/O
  5. Bases de datos + data.table = Workflow híbrido óptimo
  6. Casos reales demuestran la versatilidad industrial de data.table
  7. Sistemas completos integran múltiples componentes de forma modular

Has completado tu formación integral en aplicaciones del mundo real con data.table. Ahora tienes las herramientas para construir sistemas completos de analytics empresarial de nivel industrial.