Resumen ejecutivo

Row

Total Chile

2.131

Total California

74

Máxima magnitud Chile

Mw 8,8

Máxima magnitud California

Mw 7,2

Row

Lectura ejecutiva

70,51

Densidad Chile

Sismos por cada 100.000 km² dentro del área estudiada.

6,82

Densidad California

Sismos por cada 100.000 km² dentro del área estudiada.

273,9 km

Profundidad máxima Chile

El contraste principal se observa en la estructura vertical de la sismicidad.

Hallazgo principal: las magnitudes centrales son parecidas entre zonas, pero Chile presenta mayor frecuencia, mayor densidad espacial, eventos extremos más grandes y una distribución de profundidades mucho más amplia. California concentra su sismicidad en rangos superficiales.
Alcance: este dashboard reproduce y organiza visualmente los resultados del informe inicial: mapas de epicentros, distribuciones de magnitud y profundidad, frecuencia temporal mensual, conteos normalizados, sensibilidad por umbral y matrices de correlación.

Tabla 5 — Resumen estadístico comparativo

Mapa sísmico

Column

Vista combinada

Filtros generales

Los puntos responden a los filtros. El mapa de calor y las líneas tectónicas son capas de referencia para explorar los mapas de epicentros del informe.

Chile

Filtros Chile

Los puntos responden a los filtros. El mapa de calor y las líneas tectónicas son capas de referencia para explorar los mapas de epicentros del informe.

California

Filtros California

Los puntos responden a los filtros. El mapa de calor y las líneas tectónicas son capas de referencia para explorar los mapas de epicentros del informe.

Magnitud

Row

Figura 10 — Distribución porcentual normalizada

Figura 11 — Sismos por 100.000 km² según magnitud

Row

Tabla 8 — Resumen de conteos normalizados por magnitud

Profundidad y sensibilidad

Row

Figura 12 — Sensibilidad porcentual por profundidad

Figura 13 — Densidad espacial por profundidad

Row

Tabla 10 — Profundidad, umbral y superficie

Frecuencia temporal

Row

Figura 8 — Sismos por mes, según categoría de magnitud

Asociación

Column

Figura 14 — Correlaciones California

Figura 15 — Correlaciones Chile

Tablas del informe

Column

Tabla 5 — Resumen estadístico

Tabla 8 — Magnitud normalizada

Tabla 10 — Profundidad y sensibilidad

Notas metodológicas

Notas para RPubs: este dashboard es HTML estático. Funciona sin Shiny Server. Los filtros del mapa usan crosstalk, los gráficos usan plotly y las tablas usan DT.
Consistencia con el informe: se retiraron gráficos auxiliares que no estaban en el informe inicial y se conservaron únicamente visualizaciones equivalentes a las figuras y tablas principales del documento que funcionan bien en formato dashboard. Las líneas tectónicas del mapa son referencias aproximadas, dibujadas en coordenadas longitud-latitud para orientar la lectura espacial.
Parámetros centrales: Chile [-46,-17] latitud y [-76,-66] longitud; California [32,42] latitud y [-125,-114] longitud; periodo 2000–2025; magnitud mínima Mw ≥ 5,0.
---
title: "InferaData | Dashboard de Sismicidad Chile vs California"
subtitle: "USGS Earthquake Catalog · Mw ≥ 5,0 · 2000–2025"
author: "InferaData"
output:
  flexdashboard::flex_dashboard:
    orientation: rows
    vertical_layout: scroll
    theme: cosmo
    source_code: embed
---

```{r setup, include=FALSE}
# ============================================================
# DASHBOARD INFERADATA - Chile vs California
# Diseñado para publicar en RPubs como HTML estático.
# No usa Shiny. La interactividad se logra con htmlwidgets,
# leaflet, crosstalk, plotly y DT.
# ============================================================

knitr::opts_chunk$set(
  echo = FALSE,
  warning = FALSE,
  message = FALSE,
  fig.align = "center"
)

paquetes <- c(
  "dplyr", "tidyr", "readr", "lubridate", "stringr",
  "ggplot2", "plotly", "leaflet", "leaflet.extras",
  "crosstalk", "DT", "htmltools", "scales", "viridisLite",
  "flexdashboard", "tibble"
)

faltan <- paquetes[!vapply(paquetes, requireNamespace, logical(1), quietly = TRUE)]
if (length(faltan) > 0) {
  stop(
    "Antes de tejer el dashboard instala estos paquetes: ",
    paste(faltan, collapse = ", "),
    "\nEjemplo: install.packages(c(",
    paste(sprintf('"%s"', faltan), collapse = ", "),
    "))"
  )
}

invisible(lapply(paquetes, library, character.only = TRUE))

# ------------------------------
# Formatos generales
# ------------------------------
fmt_num <- function(x, digits = 0) {
  format(round(x, digits), big.mark = ".", decimal.mark = ",", nsmall = digits)
}

fmt_pct <- function(x, digits = 1) {
  paste0(fmt_num(x, digits), "%")
}

col_zona <- c("Chile" = "#0B6FA4", "California" = "#F28E2B")
col_depth <- c(
  "0 - 20 km" = "#2DC7FF",
  "20 - 70 km" = "#2CA25F",
  "70 - 300 km" = "#FDB863",
  "300 km o más" = "#B2182B"
)
col_mag <- c(
  "5.0 - 5.9" = "#BFD7EA",
  "6.0 - 6.9" = "#F6C85F",
  "7.0 - 7.9" = "#F18F01",
  "8.0 o más" = "#C73E1D"
)

anios <- 2000:2025
meses_es <- c("Ene", "Feb", "Mar", "Abr", "May", "Jun", "Jul", "Ago", "Sep", "Oct", "Nov", "Dic")

# ------------------------------
# Descarga reproducible USGS
# ------------------------------
descargar_usgs_anual <- function(minlat, maxlat, minlon, maxlon,
                                 anio_inicio = 2000, anio_fin = 2025,
                                 minmag = 5) {
  base_url <- "https://earthquake.usgs.gov/fdsnws/event/1/query"
  lista_datos <- list()

  for (anio in anio_inicio:anio_fin) {
    url <- paste0(
      base_url,
      "?format=csv",
      "&starttime=", anio, "-01-01",
      "&endtime=", anio + 1, "-01-01",
      "&minlatitude=", minlat,
      "&maxlatitude=", maxlat,
      "&minlongitude=", minlon,
      "&maxlongitude=", maxlon,
      "&minmagnitude=", minmag,
      "&eventtype=earthquake",
      "&orderby=time-asc"
    )

    datos_anio <- tryCatch(
      readr::read_csv(url, show_col_types = FALSE, progress = FALSE),
      error = function(e) {
        message("No se pudo descargar el año ", anio, ": ", e$message)
        NULL
      }
    )

    if (!is.null(datos_anio) && nrow(datos_anio) > 0) {
      lista_datos[[as.character(anio)]] <- datos_anio
    }
  }

  dplyr::bind_rows(lista_datos)
}

leer_o_descargar <- function(nombre_csv, zona) {
  if (file.exists(nombre_csv)) {
    return(readr::read_csv(nombre_csv, show_col_types = FALSE, progress = FALSE))
  }

  if (zona == "Chile") {
    descargar_usgs_anual(
      minlat = -46, maxlat = -17,
      minlon = -76, maxlon = -66,
      anio_inicio = 2000, anio_fin = 2025,
      minmag = 5
    )
  } else {
    descargar_usgs_anual(
      minlat = 32, maxlat = 42,
      minlon = -125, maxlon = -114,
      anio_inicio = 2000, anio_fin = 2025,
      minmag = 5
    )
  }
}

# Si existen los CSV generados por el script original, se leen.
# Si no existen, se descargan desde USGS al momento de tejer el Rmd.
chile_raw <- leer_o_descargar("chile_limpia.csv", "Chile")
california_raw <- leer_o_descargar("california_limpia.csv", "California")

# ------------------------------
# Preprocesamiento
# ------------------------------
variables_clave <- c("id", "time", "latitude", "longitude", "place", "depth", "mag", "magType", "type")

chile <- chile_raw %>%
  dplyr::filter(if ("type" %in% names(.)) type == "earthquake" else TRUE) %>%
  dplyr::filter(stats::complete.cases(dplyr::across(dplyr::any_of(variables_clave)))) %>%
  dplyr::distinct() %>%
  dplyr::mutate(
    time = lubridate::ymd_hms(time, tz = "UTC", quiet = TRUE),
    year = lubridate::year(time),
    month = lubridate::month(time),
    zona = "Chile",
    Mw_hom = dplyr::case_when(
      tolower(magType) %in% c("mw", "mwb", "mwc", "mwr", "mww") ~ mag,
      tolower(magType) == "ml" & depth <= 50 ~ 0.80 * mag + 1.15,
      tolower(magType) == "ml" & depth > 50  ~ 0.94 * mag + 0.30,
      tolower(magType) == "ms" ~ 0.74 * mag + 1.60,
      tolower(magType) == "mb" ~ 1.04 * mag - 0.02,
      tolower(magType) %in% c("m", "md", "mc") ~ mag,
      TRUE ~ NA_real_
    ),
    magnitud = Mw_hom
  ) %>%
  dplyr::filter(!is.na(magnitud), year >= 2000, year <= 2025)

california <- california_raw %>%
  dplyr::filter(if ("type" %in% names(.)) type == "earthquake" else TRUE) %>%
  dplyr::filter(stats::complete.cases(dplyr::across(dplyr::any_of(variables_clave)))) %>%
  dplyr::distinct() %>%
  dplyr::filter(tolower(magType) %in% c("mw", "mwr")) %>%
  dplyr::mutate(
    time = lubridate::ymd_hms(time, tz = "UTC", quiet = TRUE),
    year = lubridate::year(time),
    month = lubridate::month(time),
    zona = "California",
    magnitud = mag
  ) %>%
  dplyr::filter(!is.na(magnitud), year >= 2000, year <= 2025)

clasificar_magnitud <- function(x) {
  dplyr::case_when(
    x >= 5.0 & x < 6.0 ~ "5.0 - 5.9",
    x >= 6.0 & x < 7.0 ~ "6.0 - 6.9",
    x >= 7.0 & x < 8.0 ~ "7.0 - 7.9",
    x >= 8.0 ~ "8.0 o más",
    TRUE ~ NA_character_
  )
}

clasificar_profundidad <- function(x) {
  # Se respeta la regla usada en el informe: los valores negativos de profundidad
  # se conservan en la base, pero no se asignan a las categorías 0-20, 20-70, 70-300.
  dplyr::case_when(
    x >= 0 & x < 20 ~ "0 - 20 km",
    x >= 20 & x < 70 ~ "20 - 70 km",
    x >= 70 & x < 300 ~ "70 - 300 km",
    x >= 300 ~ "300 km o más",
    TRUE ~ NA_character_
  )
}

sismos <- dplyr::bind_rows(chile, california) %>%
  dplyr::mutate(
    depth_plot = pmax(depth, 0),
    rango_magnitud = factor(clasificar_magnitud(magnitud), levels = names(col_mag)),
    categoria_magnitud = factor(
      dplyr::case_when(
        magnitud >= 5 & magnitud < 6 ~ "Moderada",
        magnitud >= 6 & magnitud < 7 ~ "Fuerte",
        magnitud >= 7 & magnitud < 8 ~ "Mayor",
        magnitud >= 8 ~ "Gran Terremoto",
        TRUE ~ NA_character_
      ),
      levels = c("Moderada", "Fuerte", "Mayor", "Gran Terremoto")
    ),
    rango_profundidad = factor(clasificar_profundidad(depth), levels = names(col_depth)),
    evento_fuerte = ifelse(magnitud >= 6, "Mw ≥ 6,0", "Mw 5,0–5,9"),
    etiqueta_evento = ifelse(magnitud >= 7, "Evento mayor", ifelse(magnitud >= 6, "Evento fuerte", "Evento moderado")),
    mes_nombre = factor(meses_es[month], levels = meses_es),
    id_sismo = paste0(zona, "_", dplyr::row_number()),
    fecha = as.Date(time),
    popup = paste0(
      "<b>", zona, "</b><br>",
      "<b>Lugar:</b> ", place, "<br>",
      "<b>Fecha UTC:</b> ", format(time, "%Y-%m-%d %H:%M"), "<br>",
      "<b>Magnitud Mw:</b> ", round(magnitud, 2), "<br>",
      "<b>Profundidad:</b> ", round(depth, 2), " km<br>",
      "<b>Tipo magnitud:</b> ", magType
    )
  )

# Áreas usadas en el informe. Se dejan fijas para mantener consistencia entre el informe y el dashboard.
areas_zonas <- tibble::tibble(
  zona = c("Chile", "California"),
  area_km2 = c(3022191, 1085219)
)

kpi_zona <- sismos %>%
  dplyr::group_by(zona) %>%
  dplyr::summarise(
    total = dplyr::n(),
    mag_media = mean(magnitud, na.rm = TRUE),
    mag_mediana = median(magnitud, na.rm = TRUE),
    mag_max = max(magnitud, na.rm = TRUE),
    prof_media = mean(depth, na.rm = TRUE),
    prof_mediana = median(depth, na.rm = TRUE),
    prof_max = max(depth, na.rm = TRUE),
    eventos_mw6 = sum(magnitud >= 6, na.rm = TRUE),
    eventos_mw7 = sum(magnitud >= 7, na.rm = TRUE),
    .groups = "drop"
  ) %>%
  dplyr::left_join(areas_zonas, by = "zona") %>%
  dplyr::mutate(
    sismos_100k = total / area_km2 * 100000,
    pct_mw6 = eventos_mw6 / total * 100
  )

get_kpi <- function(zona_nombre, var_name) {
  # Función deliberadamente simple para evitar problemas de tidy-eval al publicar en RPubs.
  # Devuelve siempre un único valor, porque valueBox/htmltools falla si recibe vectores.
  salida <- kpi_zona[kpi_zona$zona == zona_nombre, var_name, drop = TRUE]
  if (length(salida) == 0) return(NA_real_)
  salida[[1]]
}

valor_kpi <- function(zona_nombre, var_name, digits = 0, prefijo = "", sufijo = "") {
  paste0(prefijo, fmt_num(get_kpi(zona_nombre, var_name), digits), sufijo)
}

# Tabla 5 del informe: resumen estadístico comparativo de magnitud y profundidad
resumen_estadistico <- sismos %>%
  dplyr::select(zona, Magnitud = magnitud, Profundidad = depth) %>%
  tidyr::pivot_longer(cols = c(Magnitud, Profundidad), names_to = "Variable", values_to = "valor") %>%
  dplyr::group_by(zona, Variable) %>%
  dplyr::summarise(
    Media = mean(valor, na.rm = TRUE),
    `Cuartil 1` = stats::quantile(valor, 0.25, na.rm = TRUE, type = 7),
    Mediana = stats::median(valor, na.rm = TRUE),
    `Cuartil 3` = stats::quantile(valor, 0.75, na.rm = TRUE, type = 7),
    `Percentil 90` = stats::quantile(valor, 0.90, na.rm = TRUE, type = 7),
    `Percentil 95` = stats::quantile(valor, 0.95, na.rm = TRUE, type = 7),
    `Desv. Est.` = stats::sd(valor, na.rm = TRUE),
    `Mín.` = min(valor, na.rm = TRUE),
    `Máx.` = max(valor, na.rm = TRUE),
    .groups = "drop"
  ) %>%
  dplyr::arrange(factor(zona, levels = c("Chile", "California")), Variable)

# Figura 8 del informe: sismos por mes, según categoría de magnitud
conteo_mensual_mag <- sismos %>%
  dplyr::count(zona, year, month, categoria_magnitud, name = "n") %>%
  tidyr::complete(
    zona,
    year = anios,
    month = 1:12,
    categoria_magnitud = factor(c("Moderada", "Fuerte", "Mayor", "Gran Terremoto"),
                                levels = c("Moderada", "Fuerte", "Mayor", "Gran Terremoto")),
    fill = list(n = 0)
  ) %>%
  dplyr::mutate(
    fecha_mes = as.Date(sprintf("%04d-%02d-01", year, month)),
    mes_nombre = factor(meses_es[month], levels = meses_es)
  )

# Tablas normalizadas
niveles_magnitud <- names(col_mag)
niveles_profundidad <- names(col_depth)

combinaciones_magnitud <- tidyr::expand_grid(
  zona = c("Chile", "California"),
  rango_magnitud = factor(niveles_magnitud, levels = niveles_magnitud)
)

tabla_magnitud <- sismos %>%
  dplyr::count(zona, rango_magnitud, name = "n_sismos") %>%
  dplyr::right_join(combinaciones_magnitud, by = c("zona", "rango_magnitud")) %>%
  dplyr::mutate(n_sismos = tidyr::replace_na(n_sismos, 0L)) %>%
  dplyr::group_by(zona) %>%
  dplyr::mutate(
    total_zona = sum(n_sismos),
    porcentaje = 100 * n_sismos / total_zona
  ) %>%
  dplyr::ungroup() %>%
  dplyr::left_join(areas_zonas, by = "zona") %>%
  dplyr::mutate(sismos_100k = n_sismos / area_km2 * 100000)

cortes <- tibble::tibble(corte = c("Mw ≥ 5,0", "Mw ≥ 6,0"), min_mag = c(5, 6))

base_cortes <- sismos %>%
  tidyr::crossing(cortes) %>%
  dplyr::filter(magnitud >= min_mag)

combinaciones_prof <- tidyr::expand_grid(
  zona = c("Chile", "California"),
  corte = c("Mw ≥ 5,0", "Mw ≥ 6,0"),
  rango_profundidad = factor(niveles_profundidad, levels = niveles_profundidad)
)

tabla_profundidad <- base_cortes %>%
  dplyr::count(zona, corte, rango_profundidad, name = "n_sismos") %>%
  dplyr::right_join(combinaciones_prof, by = c("zona", "corte", "rango_profundidad")) %>%
  dplyr::mutate(n_sismos = tidyr::replace_na(n_sismos, 0L)) %>%
  dplyr::group_by(zona, corte) %>%
  dplyr::mutate(
    total_zona_corte = sum(n_sismos),
    porcentaje = ifelse(total_zona_corte > 0, 100 * n_sismos / total_zona_corte, 0)
  ) %>%
  dplyr::ungroup() %>%
  dplyr::left_join(areas_zonas, by = "zona") %>%
  dplyr::mutate(sismos_100k = n_sismos / area_km2 * 100000)

conteo_anual <- sismos %>%
  tidyr::crossing(cortes) %>%
  dplyr::filter(magnitud >= min_mag) %>%
  dplyr::count(zona, corte, year, name = "n") %>%
  tidyr::complete(zona, corte, year = anios, fill = list(n = 0))

conteo_mensual <- sismos %>%
  dplyr::count(zona, year, month, mes_nombre, name = "n") %>%
  tidyr::complete(
    zona,
    year = anios,
    month = 1:12,
    fill = list(n = 0)
  ) %>%
  dplyr::mutate(mes_nombre = factor(meses_es[month], levels = meses_es))

# Objetos Crosstalk para mapas
sismos_mapa <- sismos %>%
  dplyr::filter(!is.na(latitude), !is.na(longitude), !is.na(depth), !is.na(magnitud)) %>%
  dplyr::mutate(
    radio = pmax((magnitud - 4.6) * 4, 3),
    profundidad_mapa = depth_plot
  )

sismos_sd <- crosstalk::SharedData$new(sismos_mapa, key = ~id_sismo, group = "sismos_mapa")
chile_sd <- crosstalk::SharedData$new(dplyr::filter(sismos_mapa, zona == "Chile"), key = ~id_sismo, group = "sismos_chile")
california_sd <- crosstalk::SharedData$new(dplyr::filter(sismos_mapa, zona == "California"), key = ~id_sismo, group = "sismos_california")

pal_depth <- leaflet::colorNumeric(viridisLite::viridis(256), domain = sismos_mapa$depth_plot)
pal_zona <- leaflet::colorFactor(col_zona, domain = sismos_mapa$zona)

# Leyendas manuales
leyenda_heatmap <- htmltools::HTML("<div style='background:white; padding:8px; border-radius:6px; box-shadow:0 0 8px rgba(0,0,0,.25); font-size:12px;'><b>Mapa de calor</b><br><div style='width:170px;height:14px;margin:6px 0 4px 0;background:linear-gradient(to right, blue, cyan, green, yellow, red);border:1px solid #999;'></div><div style='display:flex;justify-content:space-between;width:170px;'><span>Baja</span><span>Alta</span></div><span>Concentración / intensidad</span></div>")

leyenda_punto <- htmltools::HTML("<div style='background:white; padding:8px; border-radius:6px; box-shadow:0 0 8px rgba(0,0,0,.25); font-size:12px;'><b>Tamaño del punto</b><br><span style='display:inline-block;width:8px;height:8px;border-radius:50%;background:#777;'></span> Menor magnitud<br><span style='display:inline-block;width:15px;height:15px;border-radius:50%;background:#777;'></span> Magnitud media<br><span style='display:inline-block;width:23px;height:23px;border-radius:50%;background:#777;'></span> Mayor magnitud</div>")

# Referencias tectónicas aproximadas para el mapa.
# Coordenadas en orden longitud-latitud. Se dibujan como referencia visual, no como cartografía oficial.
linea_chile <- tibble::tibble(
  lon = c(-72.2, -72.6, -73.0, -73.5, -74.0, -74.6, -75.2, -75.7, -76.0),
  lat = c(-18.0, -22.0, -26.0, -30.0, -34.0, -38.0, -42.0, -44.5, -46.0),
  nombre = "Fosa Perú-Chile / margen de subducción",
  tipo = "Referencia tectónica"
)

linea_san_andres <- tibble::tibble(
  lon = c(-115.5, -116.0, -116.5, -117.2, -118.0, -118.8, -119.6, -120.4, -121.1, -121.8, -122.4, -123.1, -123.7, -124.0),
  lat = c(  32.6,   33.0,   33.5,   34.1,   34.6,   35.2,   35.7,   36.3,   36.9,   37.4,   38.0,   38.8,   39.6,   40.2),
  nombre = "Falla de San Andrés / límite transformante",
  tipo = "Referencia tectónica"
)

agregar_lineas_tectonicas <- function(mapa) {
  mapa %>%
    leaflet::addPolylines(
      data = linea_chile, lng = ~lon, lat = ~lat,
      color = "#D7191C", weight = 4, opacity = 0.9, dashArray = "8,6",
      label = ~nombre, group = "Referencias tectónicas"
    ) %>%
    leaflet::addPolylines(
      data = linea_san_andres, lng = ~lon, lat = ~lat,
      color = "#6A00A8", weight = 4, opacity = 0.9, dashArray = "8,6",
      label = ~nombre, group = "Referencias tectónicas"
    )
}

leyenda_tectonica <- htmltools::HTML("<div style='background:white; padding:8px; border-radius:6px; box-shadow:0 0 8px rgba(0,0,0,.25); font-size:12px;'><b>Referencias tectónicas</b><br><span style='display:inline-block;width:24px;border-top:4px dashed #D7191C;margin-right:6px;'></span> Fosa Perú-Chile<br><span style='display:inline-block;width:24px;border-top:4px dashed #6A00A8;margin-right:6px;'></span> Falla de San Andrés<br><span style='color:#607D8B;'>Trazos aproximados para orientación visual.</span></div>")

# ------------------------------
# Funciones de gráficos
# ------------------------------
tema_dash <- function(base_size = 12) {
  ggplot2::theme_minimal(base_size = base_size) +
    ggplot2::theme(
      plot.title = ggplot2::element_text(face = "bold", hjust = 0, color = "#263238"),
      plot.subtitle = ggplot2::element_text(color = "#546E7A"),
      legend.position = "bottom",
      panel.grid.minor = ggplot2::element_blank()
    )
}

plotly_clean <- function(p, tooltip = "text", height = 640) {
  # En flexdashboard + RPubs, los gráficos se ven mejor con altura fija.
  # responsive=TRUE mantiene el ajuste horizontal, pero evita que queden achatados.
  plotly::ggplotly(p, tooltip = tooltip, height = height) %>%
    plotly::layout(
      height = height,
      autosize = TRUE,
      hoverlabel = list(bgcolor = "white"),
      margin = list(l = 55, r = 30, b = 90, t = 90)
    ) %>%
    plotly::config(responsive = TRUE, displaylogo = FALSE)
}

crear_mapa <- function(shared, data_heat, bounds, titulo_filtros = "Filtros", prefix = "mapa", map_height = 900) {
  # Estructura robusta para RPubs: filtros arriba y mapa en tarjeta grande.
  filtros <- htmltools::tags$div(
    class = "map-filter-grid",
    htmltools::tags$div(class = "map-filter-title", htmltools::tags$h4(titulo_filtros)),
    crosstalk::filter_select(paste0(prefix, "_zona"), "Zona", shared, ~zona, multiple = TRUE, allLevels = TRUE),
    crosstalk::filter_select(paste0(prefix, "_rprof"), "Profundidad", shared, ~rango_profundidad, multiple = TRUE, allLevels = TRUE),
    crosstalk::filter_select(paste0(prefix, "_rmag"), "Rango de magnitud", shared, ~rango_magnitud, multiple = TRUE, allLevels = TRUE),
    crosstalk::filter_slider(paste0(prefix, "_anio"), "Año", shared, ~year, step = 1),
    crosstalk::filter_slider(paste0(prefix, "_mag"), "Magnitud Mw", shared, ~magnitud, step = 0.1),
    crosstalk::filter_slider(paste0(prefix, "_dep"), "Profundidad (km)", shared, ~depth_plot, step = 1),
    htmltools::tags$div(
      class = "map-filter-note",
      "Los puntos responden a los filtros. El mapa de calor y las líneas tectónicas son capas de referencia para explorar los mapas de epicentros del informe."
    )
  )

  mapa <- leaflet::leaflet(shared, width = "100%", height = map_height) %>%
    leaflet::addTiles(group = "OpenStreetMap") %>%
    leaflet::addProviderTiles(leaflet::providers$CartoDB.Positron, group = "Mapa claro") %>%
    leaflet::addProviderTiles(leaflet::providers$Esri.WorldTopoMap, group = "Topográfico") %>%
    leaflet.extras::addHeatmap(
      data = data_heat,
      lng = ~longitude, lat = ~latitude,
      intensity = ~magnitud,
      radius = 20, blur = 15,
      max = max(data_heat$magnitud, na.rm = TRUE),
      minOpacity = 0.45,
      gradient = c("0.10" = "blue", "0.30" = "cyan", "0.55" = "green", "0.75" = "yellow", "1.00" = "red"),
      group = "Mapa de calor"
    ) %>%
    agregar_lineas_tectonicas() %>%
    leaflet::addCircleMarkers(
      lng = ~longitude, lat = ~latitude,
      radius = ~radio,
      fillColor = ~pal_depth(depth_plot),
      fillOpacity = .82,
      color = "#111111",
      weight = .7,
      opacity = .98,
      popup = ~popup,
      label = ~paste0(zona, " · Mw ", round(magnitud, 2), " · ", round(depth, 1), " km"),
      group = "Epicentros"
    ) %>%
    leaflet::addLegend("bottomright", pal = pal_depth, values = data_heat$depth_plot,
                       title = "Profundidad (km)", opacity = 1) %>%
    leaflet::addControl(leyenda_punto, position = "bottomleft") %>%
    leaflet::addControl(leyenda_heatmap, position = "bottomleft") %>%
    leaflet::addControl(leyenda_tectonica, position = "topright") %>%
    leaflet::addLayersControl(
      position = "topright",
      baseGroups = c("OpenStreetMap", "Mapa claro", "Topográfico"),
      overlayGroups = c("Epicentros", "Mapa de calor", "Referencias tectónicas"),
      options = leaflet::layersControlOptions(collapsed = TRUE)
    ) %>%
    leaflet::fitBounds(lng1 = bounds[1], lat1 = bounds[2], lng2 = bounds[3], lat2 = bounds[4])

  htmltools::tagList(
    filtros,
    htmltools::tags$div(class = "map-card", mapa)
  )
}


```

<style>
.navbar { background: linear-gradient(90deg, #0B1F33, #114B5F) !important; }
.navbar-brand { font-weight: 800 !important; letter-spacing: .2px; }
.value-box { border-radius: 16px !important; box-shadow: 0 8px 22px rgba(0,0,0,.10); }
.chart-title { font-weight: 800; }
.panel-filtros { background: #F7FAFC; border: 1px solid #DDE7EE; border-radius: 16px; padding: 16px; box-shadow: 0 8px 18px rgba(0,0,0,.06); }
.panel-filtros h4 { margin-top: 0; color: #0B1F33; font-weight: 800; }
.insight { background: linear-gradient(135deg, #F8FBFF, #EEF7FA); border-left: 6px solid #0B6FA4; border-radius: 14px; padding: 16px 18px; margin: 8px 0 14px 0; font-size: 15px; box-shadow: 0 6px 16px rgba(0,0,0,.06); }
.insight strong { color: #0B1F33; }
.small-note { color:#607D8B; font-size:12px; }
.exec-grid { display: grid; grid-template-columns: repeat(3, minmax(230px, 1fr)); gap: 16px; margin-bottom: 18px; }
.exec-card { background: white; border: 1px solid #DDE7EE; border-radius: 18px; padding: 18px; box-shadow: 0 8px 20px rgba(0,0,0,.07); }
.exec-card h3 { margin: 0 0 8px 0; color: #0B1F33; font-size: 18px; font-weight: 800; }
.exec-card p { margin: 0; color: #455A64; font-size: 14px; line-height: 1.45; }
.exec-card .big { font-size: 28px; font-weight: 900; color: #0B6FA4; margin-bottom: 6px; }
@media (max-width: 900px) { .exec-grid { grid-template-columns: 1fr; } }
.dt-center { text-align: center; }
.map-filter-grid {
  display: grid;
  grid-template-columns: repeat(3, minmax(230px, 1fr));
  gap: 12px 16px;
  background: #F7FAFC;
  border: 1px solid #DDE7EE;
  border-radius: 18px;
  padding: 16px;
  margin-bottom: 16px;
  box-shadow: 0 8px 18px rgba(0,0,0,.06);
}
.map-filter-title { grid-column: 1 / -1; }
.map-filter-title h4 { margin: 0; color: #0B1F33; font-weight: 800; }
.map-filter-note {
  grid-column: 1 / -1;
  color: #546E7A;
  font-size: 13px;
  border-top: 1px solid #DDE7EE;
  padding-top: 10px;
}
.map-card {
  min-height: 920px;
  background: white;
  border: 1px solid #DDE7EE;
  border-radius: 18px;
  padding: 10px;
  box-shadow: 0 10px 24px rgba(0,0,0,.08);
  overflow: hidden;
}
.map-card .leaflet, .map-card .leaflet-container, .map-card .html-widget {
  min-height: 900px !important;
  height: 900px !important;
  width: 100% !important;
}
.plotly, .js-plotly-plot, .plot-container, .svg-container {
  min-height: 600px !important;
}
.chart-wrapper, .chart-stage {
  overflow: visible !important;
}
@media (max-width: 900px) {
  .map-filter-grid { grid-template-columns: 1fr; }
  .map-card { min-height: 760px; }
  .map-card .leaflet, .map-card .leaflet-container, .map-card .html-widget { min-height: 740px !important; height: 740px !important; }
}
</style>

<script>
// Leaflet dentro de flexdashboard/tabsets a veces queda en blanco hasta que recibe resize.
// Este pequeño ajuste fuerza el redibujado al cargar y al cambiar de pestaña.
document.addEventListener("DOMContentLoaded", function() {
  function resizeWidgets() {
    window.dispatchEvent(new Event("resize"));
  }
  setTimeout(resizeWidgets, 400);
  setTimeout(resizeWidgets, 1200);
  if (window.jQuery) {
    jQuery(document).on("shown.bs.tab shown.bs.collapse", function() {
      setTimeout(resizeWidgets, 250);
      setTimeout(resizeWidgets, 800);
    });
  }
});
</script>


Resumen ejecutivo
=======================================================================

Row {data-height=170}
-----------------------------------------------------------------------

### Total Chile

```{r}
flexdashboard::valueBox(
  value = valor_kpi("Chile", "total"),
  caption = "eventos Mw ≥ 5,0 en Chile",
  icon = "fa-globe",
  color = "primary"
)
```

### Total California

```{r}
flexdashboard::valueBox(
  value = valor_kpi("California", "total"),
  caption = "eventos Mw ≥ 5,0 en California",
  icon = "fa-map-marker",
  color = "warning"
)
```

### Máxima magnitud Chile

```{r}
flexdashboard::valueBox(
  value = paste0("Mw ", valor_kpi("Chile", "mag_max", digits = 1)),
  caption = "máximo observado en Chile",
  icon = "fa-bolt",
  color = "danger"
)
```

### Máxima magnitud California

```{r}
flexdashboard::valueBox(
  value = paste0("Mw ", valor_kpi("California", "mag_max", digits = 1)),
  caption = "máximo observado en California",
  icon = "fa-bolt",
  color = "warning"
)
```

Row {data-height=520}
-----------------------------------------------------------------------

### Lectura ejecutiva

```{r}
htmltools::HTML(paste0(
  "<div class='exec-grid'>",
  "<div class='exec-card'><div class='big'>", fmt_num(get_kpi("Chile", "sismos_100k"), 2), "</div><h3>Densidad Chile</h3><p>Sismos por cada 100.000 km² dentro del área estudiada.</p></div>",
  "<div class='exec-card'><div class='big'>", fmt_num(get_kpi("California", "sismos_100k"), 2), "</div><h3>Densidad California</h3><p>Sismos por cada 100.000 km² dentro del área estudiada.</p></div>",
  "<div class='exec-card'><div class='big'>", fmt_num(get_kpi("Chile", "prof_max"), 1), " km</div><h3>Profundidad máxima Chile</h3><p>El contraste principal se observa en la estructura vertical de la sismicidad.</p></div>",
  "</div>",
  "<div class='insight'><strong>Hallazgo principal:</strong> las magnitudes centrales son parecidas entre zonas, pero Chile presenta mayor frecuencia, mayor densidad espacial, eventos extremos más grandes y una distribución de profundidades mucho más amplia. California concentra su sismicidad en rangos superficiales.</div>",
  "<div class='insight'><strong>Alcance:</strong> este dashboard reproduce y organiza visualmente los resultados del informe inicial: mapas de epicentros, distribuciones de magnitud y profundidad, frecuencia temporal mensual, conteos normalizados, sensibilidad por umbral y matrices de correlación.</div>"
))
```

### Tabla 5 — Resumen estadístico comparativo

```{r}
resumen_tabla5_dt <- resumen_estadistico %>%
  dplyr::mutate(dplyr::across(where(is.numeric), ~round(.x, 3))) %>%
  dplyr::rename(Zona = zona)

DT::datatable(
  resumen_tabla5_dt,
  rownames = FALSE,
  options = list(dom = "t", pageLength = 10, scrollX = TRUE),
  class = "stripe hover compact"
) %>%
  DT::formatStyle("Zona", fontWeight = "bold")
```

Mapa sísmico
=======================================================================

Column {.tabset}
-----------------------------------------------------------------------

### Vista combinada

```{r}
crear_mapa(
  shared = sismos_sd,
  data_heat = sismos_mapa,
  bounds = c(-126, -47, -65, 43),
  titulo_filtros = "Filtros generales",
  prefix = "general"
)
```

### Chile

```{r}
crear_mapa(
  shared = chile_sd,
  data_heat = dplyr::filter(sismos_mapa, zona == "Chile"),
  bounds = c(-76, -46, -66, -17),
  titulo_filtros = "Filtros Chile",
  prefix = "chile"
)
```

### California

```{r}
crear_mapa(
  shared = california_sd,
  data_heat = dplyr::filter(sismos_mapa, zona == "California"),
  bounds = c(-125, 32, -114, 42),
  titulo_filtros = "Filtros California",
  prefix = "california"
)
```

Magnitud
=======================================================================

Row {data-height=700}
-----------------------------------------------------------------------

### Figura 10 — Distribución porcentual normalizada

```{r}
p_mag_pct <- tabla_magnitud %>%
  ggplot2::ggplot(ggplot2::aes(
    x = rango_magnitud, y = porcentaje, fill = zona,
    text = paste0(
      "Zona: ", zona,
      "<br>Rango: ", rango_magnitud,
      "<br>N° sismos: ", fmt_num(n_sismos),
      "<br>Porcentaje: ", fmt_pct(porcentaje, 2)
    )
  )) +
  ggplot2::geom_col(position = ggplot2::position_dodge(width = .8), width = .72) +
  ggplot2::scale_fill_manual(values = col_zona) +
  ggplot2::labs(
    title = "Distribución porcentual normalizada por tamaño muestral",
    subtitle = "Figura 10 del informe",
    x = "Rango de magnitud",
    y = "Porcentaje dentro de cada zona"
  ) +
  tema_dash()

plotly_clean(p_mag_pct)
```

### Figura 11 — Sismos por 100.000 km² según magnitud

```{r}
p_mag_den <- tabla_magnitud %>%
  ggplot2::ggplot(ggplot2::aes(
    x = rango_magnitud, y = sismos_100k, fill = zona,
    text = paste0(
      "Zona: ", zona,
      "<br>Rango: ", rango_magnitud,
      "<br>N° sismos: ", fmt_num(n_sismos),
      "<br>Sismos/100.000 km²: ", fmt_num(sismos_100k, 3)
    )
  )) +
  ggplot2::geom_col(position = ggplot2::position_dodge(width = .8), width = .72) +
  ggplot2::scale_fill_manual(values = col_zona) +
  ggplot2::labs(
    title = "Sismos por cada 100.000 km² según categoría de magnitud",
    subtitle = "Figura 11 del informe",
    x = "Rango de magnitud",
    y = "Sismos / 100.000 km²"
  ) +
  tema_dash()

plotly_clean(p_mag_den)
```

Row {data-height=600}
-----------------------------------------------------------------------

### Tabla 8 — Resumen de conteos normalizados por magnitud

```{r}
tabla_magnitud_dt <- tabla_magnitud %>%
  dplyr::transmute(
    Zona = zona,
    Rango = as.character(rango_magnitud),
    `N° sismos` = n_sismos,
    `Total zona` = total_zona,
    Proporción = round(n_sismos / total_zona, 5),
    Porcentaje = paste0(round(porcentaje, 3), "%"),
    `Área km²` = round(area_km2, 0),
    `Sismos/100.000 km²` = round(sismos_100k, 4)
  )

DT::datatable(
  tabla_magnitud_dt,
  rownames = FALSE,
  options = list(dom = "t", pageLength = 10, scrollX = TRUE),
  class = "stripe hover compact"
)
```

Profundidad y sensibilidad
=======================================================================

Row {data-height=700}
-----------------------------------------------------------------------

### Figura 12 — Sensibilidad porcentual por profundidad

```{r}
p_prof_pct <- tabla_profundidad %>%
  dplyr::filter(rango_profundidad != "300 km o más") %>%
  ggplot2::ggplot(ggplot2::aes(
    x = rango_profundidad, y = porcentaje, fill = corte,
    text = paste0(
      "Zona: ", zona,
      "<br>Corte: ", corte,
      "<br>Profundidad: ", rango_profundidad,
      "<br>N° sismos: ", fmt_num(n_sismos),
      "<br>Porcentaje: ", fmt_pct(porcentaje, 2)
    )
  )) +
  ggplot2::geom_col(position = ggplot2::position_dodge(width = .8), width = .72) +
  ggplot2::facet_wrap(~zona) +
  ggplot2::scale_fill_manual(values = c("Mw ≥ 5,0" = "#8ECAE6", "Mw ≥ 6,0" = "#FFB703")) +
  ggplot2::labs(
    title = "Distribución porcentual de sismos por profundidad según umbral",
    subtitle = "Figura 12 del informe",
    x = "Categoría de profundidad",
    y = "Porcentaje"
  ) +
  tema_dash()

plotly_clean(p_prof_pct)
```

### Figura 13 — Densidad espacial por profundidad

```{r}
p_prof_den <- tabla_profundidad %>%
  dplyr::filter(rango_profundidad != "300 km o más") %>%
  ggplot2::ggplot(ggplot2::aes(
    x = rango_profundidad, y = sismos_100k, fill = corte,
    text = paste0(
      "Zona: ", zona,
      "<br>Corte: ", corte,
      "<br>Profundidad: ", rango_profundidad,
      "<br>N° sismos: ", fmt_num(n_sismos),
      "<br>Sismos/100.000 km²: ", fmt_num(sismos_100k, 3)
    )
  )) +
  ggplot2::geom_col(position = ggplot2::position_dodge(width = .8), width = .72) +
  ggplot2::facet_wrap(~zona, scales = "free_y") +
  ggplot2::scale_fill_manual(values = c("Mw ≥ 5,0" = "#8ECAE6", "Mw ≥ 6,0" = "#FFB703")) +
  ggplot2::labs(
    title = "Densidad espacial de sismos por profundidad según corte de magnitud",
    subtitle = "Figura 13 del informe",
    x = "Categoría de profundidad",
    y = "Sismos / 100.000 km²"
  ) +
  tema_dash()

plotly_clean(p_prof_den)
```

Row {data-height=600}
-----------------------------------------------------------------------

### Tabla 10 — Profundidad, umbral y superficie

```{r}
tabla_profundidad_dt <- tabla_profundidad %>%
  dplyr::transmute(
    Zona = zona,
    Corte = corte,
    Profundidad = as.character(rango_profundidad),
    `N°` = n_sismos,
    Total = total_zona_corte,
    `%` = round(porcentaje, 2),
    `Sismos/100.000 km²` = round(sismos_100k, 4)
  )

DT::datatable(
  tabla_profundidad_dt,
  rownames = FALSE,
  options = list(dom = "t", pageLength = 20, scrollX = TRUE),
  class = "stripe hover compact"
)
```

Frecuencia temporal
=======================================================================

Row {data-height=820}
-----------------------------------------------------------------------

### Figura 8 — Sismos por mes, según categoría de magnitud

```{r}
# Versión robusta para RPubs: se muestran solo meses con ocurrencia observada.
# Así se evitan paneles visualmente vacíos por la gran cantidad de meses con cero eventos,
# especialmente en California y en categorías de alta magnitud.
conteo_mensual_mag_plot <- conteo_mensual_mag %>%
  dplyr::filter(n > 0) %>%
  dplyr::mutate(
    categoria_magnitud = factor(
      as.character(categoria_magnitud),
      levels = c("Moderada", "Fuerte", "Mayor", "Gran Terremoto")
    )
  )

p_mensual_mag <- conteo_mensual_mag_plot %>%
  ggplot2::ggplot(ggplot2::aes(
    x = fecha_mes, y = n, fill = zona,
    text = paste0(
      "Zona: ", zona,
      "<br>Mes: ", format(fecha_mes, "%Y-%m"),
      "<br>Categoría: ", categoria_magnitud,
      "<br>N° sismos: ", fmt_num(n)
    )
  )) +
  ggplot2::geom_col(width = 25, alpha = .85) +
  ggplot2::facet_grid(zona ~ categoria_magnitud, scales = "free_y") +
  ggplot2::scale_fill_manual(values = col_zona) +
  ggplot2::scale_x_date(date_breaks = "5 years", date_labels = "%Y") +
  ggplot2::labs(
    title = "Sismos por mes, según categoría de magnitud",
    subtitle = "Figura 8 del informe · se muestran los meses con al menos un evento registrado",
    x = "Año",
    y = "N° de sismos mensuales"
  ) +
  tema_dash(base_size = 11) +
  ggplot2::theme(
    legend.position = "none",
    axis.text.x = ggplot2::element_text(angle = 35, hjust = 1),
    strip.text = ggplot2::element_text(face = "bold")
  )

plotly_clean(p_mensual_mag, height = 820)
```

Asociación
=======================================================================

Column {.tabset}
-----------------------------------------------------------------------

### Figura 14 — Correlaciones California

```{r}
cor_long <- function(df, zona_nombre) {
  vars <- df %>%
    dplyr::filter(.data$zona == .env$zona_nombre) %>%
    dplyr::select(Magnitud = magnitud, Profundidad = depth_plot, Latitud = latitude, Longitud = longitude)

  metodos <- c("Pearson" = "pearson", "Spearman" = "spearman", "Kendall" = "kendall")
  dplyr::bind_rows(lapply(names(metodos), function(nm) {
    mat <- stats::cor(vars, use = "complete.obs", method = metodos[[nm]])
    as.data.frame(as.table(mat)) %>%
      dplyr::rename(`Variable 1` = Var1, `Variable 2` = Var2, r = Freq) %>%
      dplyr::mutate(Método = nm)
  }))
}

plot_cor_zona <- function(zona_nombre) {
  df_cor <- cor_long(sismos, zona_nombre)
  p <- df_cor %>%
    ggplot2::ggplot(ggplot2::aes(
      x = `Variable 1`, y = `Variable 2`, fill = r,
      text = paste0("Método: ", Método, "<br>r: ", round(r, 3))
    )) +
    ggplot2::geom_tile(color = "white", linewidth = 1) +
    ggplot2::geom_text(ggplot2::aes(label = round(r, 2)), fontface = "bold", size = 3.2) +
    ggplot2::facet_wrap(~Método, nrow = 1) +
    ggplot2::scale_fill_gradient2(low = "#B2182B", mid = "white", high = "#2166AC", midpoint = 0, limits = c(-1, 1)) +
    ggplot2::labs(
      title = paste0("Matrices de correlación — ", zona_nombre),
      subtitle = "Pearson, Spearman y Kendall, como en el informe",
      x = NULL,
      y = NULL,
      fill = "r"
    ) +
    tema_dash(base_size = 11) +
    ggplot2::theme(axis.text.x = ggplot2::element_text(angle = 35, hjust = 1))

  plotly_clean(p, height = 680)
}

plot_cor_zona("California")
```

### Figura 15 — Correlaciones Chile

```{r}
plot_cor_zona("Chile")
```

Tablas del informe
=======================================================================

Column {.tabset}
-----------------------------------------------------------------------

### Tabla 5 — Resumen estadístico

```{r}
DT::datatable(
  resumen_tabla5_dt,
  rownames = FALSE,
  extensions = c("Buttons"),
  options = list(dom = "Bfrtip", buttons = c("copy", "csv", "excel"), pageLength = 10, scrollX = TRUE),
  class = "stripe hover compact"
)
```

### Tabla 8 — Magnitud normalizada

```{r}
DT::datatable(
  tabla_magnitud_dt,
  rownames = FALSE,
  extensions = c("Buttons"),
  options = list(dom = "Bfrtip", buttons = c("copy", "csv", "excel"), pageLength = 10, scrollX = TRUE),
  class = "stripe hover compact"
)
```

### Tabla 10 — Profundidad y sensibilidad

```{r}
DT::datatable(
  tabla_profundidad_dt,
  rownames = FALSE,
  extensions = c("Buttons"),
  options = list(dom = "Bfrtip", buttons = c("copy", "csv", "excel"), pageLength = 20, scrollX = TRUE),
  class = "stripe hover compact"
)
```

Notas metodológicas
=======================================================================

```{r}
htmltools::HTML(paste0(
  "<div class='insight'><strong>Notas para RPubs:</strong> este dashboard es HTML estático. Funciona sin Shiny Server. Los filtros del mapa usan <em>crosstalk</em>, los gráficos usan <em>plotly</em> y las tablas usan <em>DT</em>.</div>",
  "<div class='insight'><strong>Consistencia con el informe:</strong> se retiraron gráficos auxiliares que no estaban en el informe inicial y se conservaron únicamente visualizaciones equivalentes a las figuras y tablas principales del documento que funcionan bien en formato dashboard. Las líneas tectónicas del mapa son referencias aproximadas, dibujadas en coordenadas longitud-latitud para orientar la lectura espacial.</div>",
  "<div class='small-note'>Parámetros centrales: Chile [-46,-17] latitud y [-76,-66] longitud; California [32,42] latitud y [-125,-114] longitud; periodo 2000–2025; magnitud mínima Mw ≥ 5,0.</div>"
))
```