# ==============================================================================
#  INF 311 - Tarea de Minería de Datos: PRE PROCESAMIENTO (limpieza de datos)
#  Script 2: RESULTADOS -> figuras del informe y datos del dashboard
#  Autor   : Saul Mijael Choquehuanca Huanca
#
#  Requiere haber ejecutado antes 01_limpieza.R (lee salidas/objetos_resultados.rds)
#  Uso:  Rscript 02_resultados.R [carpeta_de_salida] [carpeta_datos_dashboard]
# ==============================================================================

paquetes <- c("dplyr", "ggplot2", "jsonlite")
faltan <- setdiff(paquetes, rownames(installed.packages()))
if (length(faltan) > 0) install.packages(faltan, repos = "https://cloud.r-project.org")
invisible(lapply(paquetes, function(p)
  suppressPackageStartupMessages(library(p, character.only = TRUE))))
Sys.setenv(TZ = "America/La_Paz")

args <- commandArgs(trailingOnly = TRUE)
DIR_SALIDA <- if (length(args) >= 1) args[1] else "salidas"
DIR_WEB    <- if (length(args) >= 2) args[2] else file.path(DIR_SALIDA, "dashboard")
DIR_FIG    <- file.path(DIR_SALIDA, "figuras")
for (d in c(DIR_FIG, DIR_WEB)) dir.create(d, showWarnings = FALSE, recursive = TRUE)

o        <- readRDS(file.path(DIR_SALIDA, "objetos_resultados.rds"))
limpio   <- o$limpio            # resultado final
previo   <- o$snap_formato      # subconjunto tras el paso 2 (antes de imputar y capar)
bitacora <- o$bitacora

# ---- Estilo de los gráficos --------------------------------------------------
AZUL <- "#2a78d6"; NARANJA <- "#eb6834"; GRIS <- "#c3c2b7"      # Empleado, Empleador, contexto
COL_ROL <- c(Empleado = AZUL, Empleador = NARANJA)
TINTA <- "#0b0b0b"; TINTA2 <- "#52514e"; REJILLA <- "#e1e0d9"

tema <- theme_minimal(base_size = 10, base_family = "sans") +
  theme(panel.grid.minor = element_blank(),
        panel.grid.major = element_line(colour = REJILLA, linewidth = 0.3),
        axis.text = element_text(colour = TINTA2), axis.title = element_text(colour = TINTA2, size = 9),
        plot.title = element_text(face = "bold", colour = TINTA, size = 11),
        plot.subtitle = element_text(colour = TINTA2, size = 9),
        plot.title.position = "plot", strip.text = element_text(face = "bold", colour = TINTA, hjust = 0),
        legend.position = "top", legend.justification = "left", legend.title = element_blank(),
        legend.key.size = unit(9, "pt"), legend.margin = margin(0, 0, 0, 0),
        plot.background = element_rect(fill = "white", colour = NA))
tema_barras_h <- tema + theme(panel.grid.major.y = element_blank())
tema_columnas <- tema + theme(panel.grid.major.x = element_blank())

guardar <- function(grafico, nombre, ancho = 6.3, alto = 3.2) {
  ggsave(file.path(DIR_FIG, nombre), grafico, width = ancho, height = alto, dpi = 200, bg = "white")
  cat("   figura:", nombre, "\n")
}
miles <- function(x) format(round(x), big.mark = " ", trim = TRUE)
pct   <- function(x, d = 1) round(100 * x, d)

# ==============================================================================
# 1. AUDITORÍA: cómo llega el dataset crudo
# ==============================================================================
cat("1. Auditoría\n")
etq_categoria <- c("1" = "Obrero/Empleado", "2" = "Empleador/a socio con salario",
                   "3" = "Trabajador/a por cuenta propia", "4" = "Empleador/a o socio/a sin salario",
                   "5" = "Cooperativista de producción", "6" = "Familiar sin remuneración",
                   "7" = "Aprendiz sin remuneración", "8" = "Empleada/o del hogar")
cat_crudo <- as.data.frame(o$categorias_crudo, stringsAsFactors = FALSE)
names(cat_crudo) <- c("codigo", "n")
cat_crudo <- cat_crudo[!is.na(cat_crudo$codigo), ]
cat_crudo$etiqueta <- unname(etq_categoria[cat_crudo$codigo])
cat_crudo$destino  <- ifelse(cat_crudo$codigo == "1", "Empleado",
                      ifelse(cat_crudo$codigo %in% c("2", "4"), "Empleador", "Fuera del objetivo"))

g <- ggplot(cat_crudo, aes(x = n, y = reorder(etiqueta, n), fill = destino)) +
  geom_col(width = 0.55) +
  geom_text(aes(label = miles(n)), hjust = -0.15, size = 3, colour = TINTA2) +
  scale_fill_manual(values = c(COL_ROL, "Fuera del objetivo" = GRIS),
                    breaks = c("Empleado", "Empleador", "Fuera del objetivo")) +
  scale_x_continuous(labels = miles, expand = expansion(mult = c(0, 0.12))) +
  labs(title = "Categoría ocupacional de las personas ocupadas (dataset crudo)",
       subtitle = "Solo tres de las ocho categorías forman una relación empleado-empleador",
       x = "Personas en la muestra", y = NULL) + tema_barras_h
guardar(g, "fig01_categoria_ocupacional.png", alto = 3.3)

# Distribución del porcentaje de faltantes en las 275 columnas
tramos <- cut(o$perfil_crudo$pct_faltantes, breaks = c(-Inf, 0, 10, 50, 90, 100),
              labels = c("Sin faltantes", "Hasta 10%", "10 a 50%", "50 a 90%", "Más de 90%"))
falt_crudo <- as.data.frame(table(tramo = tramos), responseName = "columnas", stringsAsFactors = FALSE)

g <- ggplot(falt_crudo, aes(x = factor(tramo, levels = tramo), y = columnas)) +
  geom_col(width = 0.5, fill = AZUL) +
  geom_text(aes(label = columnas), vjust = -0.5, size = 3, colour = TINTA2) +
  scale_y_continuous(expand = expansion(mult = c(0, 0.12))) +
  labs(title = "Las 275 columnas del crudo según su porcentaje de valores faltantes",
       subtitle = "Dos de cada tres columnas están vacías en más de la mitad de los registros",
       x = "Porcentaje de faltantes en la columna", y = "Número de columnas") + tema_columnas
guardar(g, "fig02_faltantes_crudo.png", alto = 2.9)

# Embudo de delimitación (se lee de la bitácora para no repetir números a mano)
filas_despues <- function(i) as.numeric(gsub("[^0-9]", "", sub(" filas.*", "", sub(".*Después: ", "", bitacora$impacto_dataset[i]))))
embudo <- data.frame(
  etapa = c("Personas encuestadas (crudo)", "Personas ocupadas", "Obrero/empleado o empleador",
            "Con 14 años o más (PET)", "Dataset limpio"),
  n = c(o$dims_crudo[1], sum(cat_crudo$n), filas_despues(2), filas_despues(3), nrow(limpio)))

g <- ggplot(embudo, aes(x = n, y = factor(etapa, levels = rev(etapa)))) +
  geom_col(width = 0.5, fill = AZUL) +
  geom_text(aes(label = miles(n)), hjust = -0.15, size = 3, colour = TINTA2) +
  scale_x_continuous(labels = miles, expand = expansion(mult = c(0, 0.12))) +
  labs(title = "De la encuesta completa a la población objetivo",
       x = "Registros", y = NULL) + tema_barras_h
guardar(g, "fig03_embudo.png", alto = 2.5)

# ==============================================================================
# 2. FORMATOS: ejemplos reales de antes -> después
# ==============================================================================
cat("2. Formatos\n")
ejemplos_formato <- data.frame(
  tecnica = c("Renombrado", "Decodificación", "Decodificación", "Fecha ISO-8601", "Unidad única",
              "Precisión decimal", "Construcción de atributo", "Construcción de atributo", "Clave única"),
  antes = c("s04b_12, yprilab, phrs", "s01a_02 = 2", "depto = 7", "s01a_04a = 5, s01a_04b = 2, s01a_04c = 1975",
            "s04b_11aa = 6, s04b_11ab = 4 (meses)", "yprilab = 4182.6665039062",
            "s04f_35 = 2, s04f_36 = (vacío)", "s04b_12 = 4", "folio = 111-000187-A-0071, nro = 1"),
  despues = c("categoria_ocupacional, ingreso_laboral, horas_semana", "sexo = Mujer", "departamento = Santa Cruz",
              "fecha_nacimiento = 1975-02-05", "antiguedad_anios = 0.5", "ingreso_laboral = 4182.67",
              "afp = No afiliado", "rol = Empleador", "id_persona = 111-000187-A-0071-01"),
  stringsAsFactors = FALSE)

# Diccionario del dataset limpio
descripcion <- c(
  id_persona = "Clave única de la persona (folio + número de persona)|folio, nro",
  folio = "Identificador anonimizado del hogar|folio",
  nro_persona = "Número de orden de la persona dentro del hogar|nro",
  upm = "Unidad primaria de muestreo|upm",
  estrato = "Estrato del diseño muestral|estrato",
  factor_expansion = "Personas de la población que representa el registro|factor",
  departamento = "Departamento de residencia|depto",
  area = "Área de residencia|area",
  sexo = "Sexo|s01a_02",
  edad = "Edad en años cumplidos|s01a_03",
  fecha_nacimiento = "Fecha de nacimiento en formato ISO-8601|s01a_04a, s01a_04b, s01a_04c",
  estado_civil = "Estado civil o conyugal|s01a_10",
  jefe_hogar = "Es jefe o jefa del hogar|s01a_05",
  nivel_educativo = "Nivel educativo alcanzado|niv_ed",
  anios_estudio = "Años de estudio aprobados|aestudio",
  rol = "VARIABLE OBJETIVO: Empleado o Empleador|s04b_12",
  categoria_ocupacional = "Categoría ocupacional original|s04b_12",
  grupo_ocupacional = "Grupo ocupacional (COB, 1 dígito)|cob_op",
  actividad_economica = "Rama de actividad de la unidad económica (CAEB)|caeb_op",
  sector = "Sector de la unidad económica|s04b_13",
  tam_empresa = "Personas que trabajan en la unidad económica|s04b_14",
  tam_empresa_cat = "Tamaño de la unidad económica en tramos|derivada de tam_empresa",
  antiguedad_anios = "Años trabajando en la unidad económica|s04b_11aa, s04b_11ab",
  dias_semana = "Días trabajados por semana|s04b_15",
  horas_semana = "Horas trabajadas por semana en la ocupación principal|phrs",
  ocupacion_secundaria = "Tiene una segunda ocupación|s04e_25",
  ingreso_laboral = "Ingreso laboral mensual de la ocupación principal (Bs)|yprilab",
  log_ingreso_laboral = "Logaritmo natural del ingreso laboral|derivada de ingreso_laboral",
  afp = "Situación frente al sistema de pensiones|s04f_35, s04f_36")
tipo_legible <- function(x) {
  if (inherits(x, "Date")) "Fecha"
  else if (is.factor(x)) sprintf("Categórica (%d)", nlevels(x))
  else if (is.integer(x)) "Entero"
  else if (is.numeric(x)) "Decimal" else "Texto"
}
diccionario <- data.frame(
  variable    = names(limpio),
  tipo        = vapply(limpio, tipo_legible, character(1)),
  descripcion = sub("\\|.*", "", descripcion[names(limpio)]),
  origen      = sub(".*\\|", "", descripcion[names(limpio)]),
  ejemplo     = vapply(limpio, function(x) as.character(x[1]), character(1)),
  row.names = NULL, stringsAsFactors = FALSE)
stopifnot(!anyNA(diccionario$descripcion))

# ==============================================================================
# 4. FALTANTES del subconjunto de trabajo
# ==============================================================================
cat("4. Faltantes\n")
faltantes <- o$faltantes_antes[o$faltantes_antes$n_faltantes > 0, ]
tratamiento_faltantes <- c(
  horas_semana_sec    = "Estructural (solo quien tiene segunda ocupación)|Columna eliminada (más de 50%)",
  ingreso_laboral_sec = "Estructural (solo quien tiene segunda ocupación)|Columna eliminada (más de 50%)",
  sector              = "Estructural (no se pregunta al empleador sin salario)|Regla lógica: sector = Privado",
  ingreso_laboral     = "Dato no declarado|Imputación KNN (k = 5)")
faltantes$mecanismo   <- sub("\\|.*", "", tratamiento_faltantes[faltantes$variable])
faltantes$tratamiento <- sub(".*\\|", "", tratamiento_faltantes[faltantes$variable])
faltantes <- faltantes[order(-faltantes$n_faltantes), ]

# ==============================================================================
# 5. ATÍPICOS: antes y después
# ==============================================================================
cat("5. Atípicos\n")
decision_atipicos <- c(
  edad = "Se conservan (valores reales)", anios_estudio = "Se conservan (valores reales)",
  antiguedad_anios = "Se conservan (valores reales)", dias_semana = "Se conservan (valores reales)",
  tam_empresa = "Discretización en 4 tramos",
  horas_semana = "Error > 168 h anulado e imputado; capado en Z = 3",
  ingreso_laboral = "Capado P1-P99 por rol + logaritmo")
atipicos <- o$resumen_atipicos
atipicos$decision <- unname(decision_atipicos[atipicos$variable])

histograma <- function(x, cortes) {
  h <- hist(x[!is.na(x)], breaks = cortes, plot = FALSE, right = FALSE)
  data.frame(desde = head(h$breaks, -1), hasta = tail(h$breaks, -1), n = h$counts)
}
tope_ingreso <- 26000
cortes_ing <- c(seq(0, tope_ingreso, 1000), Inf)
hist_ingreso <- rbind(
  cbind(momento = "Antes (crudo)", histograma(previo$ingreso_laboral, cortes_ing)),
  cbind(momento = "Después (capado P1-P99 por rol)", histograma(limpio$ingreso_laboral, cortes_ing)))
hist_log   <- histograma(limpio$log_ingreso_laboral, seq(5.5, 10.25, 0.25))
cortes_hrs <- seq(0, 175, 5)
hist_horas <- rbind(
  cbind(momento = "Antes (crudo)", histograma(previo$horas_semana, cortes_hrs)),
  cbind(momento = "Después (capado en Z = 3)", histograma(limpio$horas_semana, cortes_hrs)))

g <- ggplot(transform(hist_ingreso, hasta = ifelse(is.infinite(hasta), desde + 1000, hasta)),
            aes(x = (desde + hasta) / 2, y = n)) +
  geom_col(width = 800, fill = AZUL) +
  facet_wrap(~momento, ncol = 1) +
  scale_x_continuous(labels = miles, breaks = seq(0, tope_ingreso, 4000)) +
  scale_y_continuous(labels = miles, expand = expansion(mult = c(0, 0.08))) +
  labs(title = "Ingreso laboral mensual antes y después del tratamiento de atípicos",
       subtitle = sprintf("Tramos de 1 000 Bs; el último tramo agrupa los ingresos mayores a %s Bs", miles(tope_ingreso)),
       x = "Ingreso laboral (Bs/mes)", y = "Registros") + tema_columnas
guardar(g, "fig04_ingreso_antes_despues.png", alto = 3.9)

g <- ggplot(hist_log, aes(x = (desde + hasta) / 2, y = n)) +
  geom_col(width = 0.2, fill = AZUL) +
  scale_y_continuous(labels = miles, expand = expansion(mult = c(0, 0.08))) +
  labs(title = "Ingreso laboral tras la transformación logarítmica",
       subtitle = sprintf("La asimetría pasa de %.2f (bolivianos) a %.2f (logaritmo)",
                          atipicos$asimetria[atipicos$variable == "ingreso_laboral"],
                          mean((limpio$log_ingreso_laboral - mean(limpio$log_ingreso_laboral))^3) / sd(limpio$log_ingreso_laboral)^3),
       x = "ln(ingreso laboral)", y = "Registros") + tema_columnas
guardar(g, "fig05_log_ingreso.png", alto = 2.6)

g <- ggplot(hist_horas, aes(x = (desde + hasta) / 2, y = n)) +
  geom_col(width = 4, fill = AZUL) +
  facet_wrap(~momento, ncol = 1) +
  scale_x_continuous(breaks = seq(0, 168, 24)) +
  scale_y_continuous(labels = miles, expand = expansion(mult = c(0, 0.08))) +
  labs(title = "Horas trabajadas por semana antes y después",
       subtitle = sprintf("Una semana tiene 168 horas; el límite de capado quedó en %.1f h", o$lim_horas),
       x = "Horas por semana (ocupación principal)", y = "Registros") + tema_columnas
guardar(g, "fig06_horas_antes_despues.png", alto = 3.7)

# ==============================================================================
# 6. VALIDACIÓN: reglas e iteraciones
# ==============================================================================
reglas <- data.frame(id = names(o$reglas), regla = unname(o$reglas),
                     iteracion_1 = unname(o$historial_validacion[[1]]),
                     iteracion_2 = unname(o$historial_validacion[[length(o$historial_validacion)]]),
                     stringsAsFactors = FALSE)

# ==============================================================================
# RESULTADOS: perfil del empleado y del empleador en el dataset limpio
# ==============================================================================
cat("Resultados\n")
n_rol   <- table(limpio$rol)
pob_rol <- tapply(limpio$factor_expansion, limpio$rol, sum)

perfil_numerico <- limpio %>% group_by(rol) %>%
  summarise(registros = n(),
            edad_mediana = median(edad),
            anios_estudio_media = round(mean(anios_estudio), 1),
            antiguedad_mediana = median(antiguedad_anios),
            horas_media = round(mean(horas_semana), 1),
            tam_empresa_mediana = median(tam_empresa),
            ingreso_mediana = round(median(ingreso_laboral)),
            ingreso_media = round(mean(ingreso_laboral)),
            pct_mujeres = pct(mean(sexo == "Mujer")),
            pct_jefe_hogar = pct(mean(jefe_hogar == "Sí")),
            pct_superior = pct(mean(nivel_educativo == "Superior")),
            pct_urbano = pct(mean(area == "Urbana")),
            pct_micro = pct(mean(tam_empresa_cat == "Micro (1-9)")),
            pct_aporta_afp = pct(mean(afp == "Afiliado que aporta")),
            pct_segunda_ocupacion = pct(mean(ocupacion_secundaria == "Sí")), .groups = "drop") %>%
  as.data.frame()

distribucion <- function(variable) {
  t <- prop.table(table(limpio[[variable]], limpio$rol), margin = 2)
  data.frame(variable = variable, categoria = rownames(t), Empleado = pct(t[, "Empleado"]),
             Empleador = pct(t[, "Empleador"]), row.names = NULL, stringsAsFactors = FALSE)
}
vars_perfil <- c("sexo", "area", "nivel_educativo", "sector", "tam_empresa_cat", "afp",
                 "grupo_ocupacional", "actividad_economica", "departamento", "estado_civil")
perfil_categorico <- do.call(rbind, lapply(vars_perfil, distribucion))

panel <- function(variable, titulo, ordenar = FALSE) {
  d <- perfil_categorico[perfil_categorico$variable == variable, ]
  d <- rbind(data.frame(categoria = d$categoria, rol = "Empleado", pct = d$Empleado),
             data.frame(categoria = d$categoria, rol = "Empleador", pct = d$Empleador))
  niveles <- if (ordenar) names(sort(tapply(d$pct, d$categoria, max))) else rev(levels(limpio[[variable]]))
  ggplot(d, aes(x = pct, y = factor(categoria, levels = niveles), fill = rol)) +
    geom_col(position = position_dodge2(width = 0.75, padding = 0.15, reverse = TRUE), width = 0.7) +
    geom_text(aes(label = sprintf("%.0f%%", pct)), position = position_dodge2(width = 0.7, reverse = TRUE),
              hjust = -0.15, size = 2.6, colour = TINTA2) +
    scale_fill_manual(values = COL_ROL) +
    scale_x_continuous(labels = function(v) paste0(v, "%"), expand = expansion(mult = c(0, 0.16))) +
    labs(title = titulo, x = "Porcentaje dentro de cada rol", y = NULL) + tema_barras_h
}
guardar(panel("nivel_educativo", "Nivel educativo por rol"), "fig07_nivel_educativo.png", alto = 3.6)
guardar(panel("tam_empresa_cat", "Tamaño de la unidad económica por rol"), "fig08_tam_empresa.png", alto = 2.6)
guardar(panel("afp", "Situación frente a la AFP por rol"), "fig09_afp.png", alto = 2.3)
guardar(panel("sexo", "Sexo por rol"), "fig10_sexo.png", alto = 1.9)

# Distribución del ingreso por rol (porcentaje dentro de cada rol)
cortes_rol <- c(seq(0, 16000, 1000), Inf)
ingreso_rol <- do.call(rbind, lapply(levels(limpio$rol), function(r) {
  h <- histograma(limpio$ingreso_laboral[limpio$rol == r], cortes_rol)
  cbind(rol = r, h, pct = pct(h$n / sum(h$n)))
}))
g <- ggplot(transform(ingreso_rol, x = desde + 500), aes(x = x, y = pct, colour = rol)) +
  geom_line(linewidth = 0.8) + geom_point(size = 1.6) +
  scale_colour_manual(values = COL_ROL) +
  scale_x_continuous(labels = miles, breaks = seq(0, 16000, 2000)) +
  scale_y_continuous(labels = function(v) paste0(v, "%"), expand = expansion(mult = c(0, 0.08)), limits = c(0, NA)) +
  labs(title = "Distribución del ingreso laboral por rol",
       subtitle = "Porcentaje de cada rol por tramo de 1 000 Bs; el último punto agrupa 16 000 Bs o más",
       x = "Ingreso laboral (Bs/mes)", y = "Porcentaje del rol") + tema
guardar(g, "fig11_ingreso_por_rol.png", alto = 3.1)

# Relevancia de cada atributo: fuerza de asociación con la variable objetivo
v_cramer <- function(x, y) {
  t <- table(droplevels(x), y)
  as.numeric(sqrt(suppressWarnings(chisq.test(t, correct = FALSE)$statistic) / (sum(t) * (min(dim(t)) - 1))))
}
eta <- function(x, g) sqrt(summary(lm(x ~ g))$r.squared)   # razón de correlación
categoricas <- c("departamento", "area", "sexo", "estado_civil", "jefe_hogar", "nivel_educativo",
                 "grupo_ocupacional", "actividad_economica", "sector", "tam_empresa_cat",
                 "ocupacion_secundaria", "afp")
numericas   <- c("edad", "anios_estudio", "antiguedad_anios", "dias_semana", "horas_semana",
                 "tam_empresa", "ingreso_laboral", "log_ingreso_laboral")
relevancia <- rbind(
  data.frame(variable = categoricas, medida = "V de Cramér",
             valor = vapply(categoricas, function(v) v_cramer(limpio[[v]], limpio$rol), numeric(1))),
  data.frame(variable = numericas, medida = "Eta (razón de correlación)",
             valor = vapply(numericas, function(v) eta(limpio[[v]], limpio$rol), numeric(1))))
relevancia$valor <- round(relevancia$valor, 3)
relevancia <- relevancia[order(-relevancia$valor), ]; rownames(relevancia) <- NULL

g <- ggplot(relevancia, aes(x = valor, y = reorder(variable, valor))) +
  geom_col(width = 0.55, fill = AZUL) +
  geom_text(aes(label = sprintf("%.2f", valor)), hjust = -0.2, size = 2.8, colour = TINTA2) +
  scale_x_continuous(expand = expansion(mult = c(0, 0.1))) +
  labs(title = "Fuerza de asociación de cada atributo con el rol (0 = ninguna, 1 = total)",
       subtitle = "V de Cramér para atributos categóricos y eta para numéricos",
       x = "Asociación con la variable objetivo", y = NULL) + tema_barras_h
guardar(g, "fig12_relevancia.png", alto = 4.4)

# Indicadores de calidad antes y después (sobre las columnas que sobreviven)
calidad <- data.frame(
  indicador = c("Filas", "Columnas", "Celdas con valor faltante", "Columnas con algún faltante",
                "Variables categóricas sin etiqueta", "Jornadas de más de 168 h/semana",
                "Ingreso laboral máximo (Bs/mes)", "Asimetría del ingreso laboral",
                "Violaciones a reglas de negocio"),
  antes = c(miles(o$dims_crudo[1]), o$dims_crudo[2],
            miles(sum(o$perfil_crudo$n_faltantes)), sum(o$perfil_crudo$n_faltantes > 0),
            "Todas", sum(previo$horas_semana > 168), miles(max(previo$ingreso_laboral, na.rm = TRUE)),
            sprintf("%.2f", atipicos$asimetria[atipicos$variable == "ingreso_laboral"]),
            sum(o$historial_validacion[[1]])),
  despues = c(miles(nrow(limpio)), ncol(limpio), sum(o$perfil_final$n_faltantes), sum(o$perfil_final$n_faltantes > 0),
              "Ninguna", sum(limpio$horas_semana > 168), miles(max(limpio$ingreso_laboral)),
              sprintf("%.2f (%.2f en logaritmo)", mean((limpio$ingreso_laboral - mean(limpio$ingreso_laboral))^3) / sd(limpio$ingreso_laboral)^3,
                      mean((limpio$log_ingreso_laboral - mean(limpio$log_ingreso_laboral))^3) / sd(limpio$log_ingreso_laboral)^3),
              sum(o$historial_validacion[[length(o$historial_validacion)]])),
  stringsAsFactors = FALSE)

# ==============================================================================
# EXPORTACIÓN PARA EL INFORME Y EL DASHBOARD (JSON)
# ==============================================================================
cat("Exportación JSON\n")
pasos_bitacora <- as.data.frame(table(paso = sub(" \\(retorno.*", "", bitacora$metodologia)),
                                responseName = "registros", stringsAsFactors = FALSE)

resultados <- list(
  meta = list(autor = "Saul Mijael Choquehuanca Huanca", docente = "Lic. Menfy Morales Rios",
              sigla = "INF 311", fuente = "Encuesta de Hogares 2024, Instituto Nacional de Estadística (Bolivia)",
              r_version = sub(" \\(.*", "", R.version.string),
              paquetes = paste(vapply(c("data.table", "dplyr", "VIM", "ggplot2", "jsonlite"), function(p)
                paste(p, as.character(packageVersion(p))), character(1)), collapse = ", "),
              ejecucion = format(Sys.time(), "%Y-%m-%d %H:%M")),
  kpi = list(filas_crudo = o$dims_crudo[1], columnas_crudo = o$dims_crudo[2],
             filas_limpio = nrow(limpio), columnas_limpio = ncol(limpio),
             registros_bitacora = nrow(bitacora), anomalias = nrow(o$anomalias),
             empleados = n_rol[["Empleado"]], empleadores = n_rol[["Empleador"]],
             razon = round(n_rol[["Empleado"]] / n_rol[["Empleador"]], 1),
             poblacion_empleados = round(pob_rol[["Empleado"]]), poblacion_empleadores = round(pob_rol[["Empleador"]]),
             faltantes_final = sum(o$perfil_final$n_faltantes), reglas = nrow(reglas),
             columnas_mas_50 = sum(o$perfil_crudo$pct_faltantes > 50),
             lim_horas = o$lim_horas),
  tipos_crudo = data.frame(tipo = c("Entero", "Decimal", "Texto"),
                           columnas = as.numeric(o$tipos_crudo[c("integer", "numeric", "character")])),
  faltantes_crudo = falt_crudo, categorias_crudo = cat_crudo, embudo = embudo,
  anomalias = o$anomalias, ejemplos_formato = ejemplos_formato, diccionario = diccionario,
  faltantes = faltantes, atipicos = atipicos, limites_ingreso = o$limites_ingreso,
  hist_ingreso = hist_ingreso, hist_log = hist_log, hist_horas = hist_horas,
  reglas = reglas, calidad = calidad, pasos_bitacora = pasos_bitacora,
  perfil_numerico = perfil_numerico, perfil_categorico = perfil_categorico,
  ingreso_rol = ingreso_rol, relevancia = relevancia)
# Inf no existe en JSON: el último tramo de cada histograma queda con "hasta" = null
write_json(resultados, file.path(DIR_WEB, "resultados.json"), auto_unbox = TRUE, digits = 6, na = "null", pretty = TRUE)
write_json(bitacora, file.path(DIR_WEB, "bitacora.json"), auto_unbox = TRUE, pretty = TRUE)

# Registros del dataset limpio para el explorador del dashboard (formato compacto:
# cada categórica se guarda como índice + lista de niveles)
vars_web <- c("rol", "departamento", "area", "sexo", "edad", "estado_civil", "jefe_hogar", "nivel_educativo",
              "anios_estudio", "grupo_ocupacional", "actividad_economica", "sector", "tam_empresa",
              "tam_empresa_cat", "antiguedad_anios", "dias_semana", "horas_semana", "ocupacion_secundaria",
              "ingreso_laboral", "afp")
registros <- list(n = nrow(limpio), niveles = list(), columnas = list())
for (v in vars_web) {
  if (is.factor(limpio[[v]])) {
    registros$niveles[[v]]  <- levels(limpio[[v]])
    registros$columnas[[v]] <- as.integer(limpio[[v]]) - 1L
  } else registros$columnas[[v]] <- limpio[[v]]
}
write_json(registros, file.path(DIR_WEB, "registros.json"), auto_unbox = TRUE, digits = 6)

cat("\nPerfil por rol:\n"); print(t(perfil_numerico))
cat("\nRelevancia:\n"); print(relevancia)
cat("\nListo. Figuras en", DIR_FIG, "| JSON en", DIR_WEB, "\n")
