(() => {
  const $ = (id) => document.getElementById(id);
  const KEY_PERSONAS = "fp_personas_v1";
  const API_GEO = "../php/geolocalizacion/list.php";
  const API_SECTOR_MAP = "../php/config/sectores_mapa/list.php";

  const q = $("q");
  const btnClearQ = $("btnClearQ");
  const btnFiltrarUbicacion = $("btnFiltrarUbicacion");
  const fInscrito = $("fInscrito");
  const fMigrante = $("fMigrante");
  const fSectorizacion = $("fSectorizacion");
  const fSector = $("fSector");
  const fEstratificacion = $("fEstratificacion");
  const fDiagnostico = $("fDiagnostico");
  const fGenero = $("fGenero");
  const fEdadMin = $("fEdadMin");
  const fEdadMax = $("fEdadMax");
  const fPercapitado = $("fPercapitado");
  const onlyBounds = $("onlyBounds");
  const btnAplicar = $("btnAplicar");
  const btnReset = $("btnReset");

  const qM = $("qM");
  const btnClearQM = $("btnClearQM");
  const fInscritoM = $("fInscritoM");
  const fMigranteM = $("fMigranteM");
  const fSectorizacionM = $("fSectorizacionM");
  const fSectorM = $("fSectorM");
  const fEstratificacionM = $("fEstratificacionM");
  const fDiagnosticoM = $("fDiagnosticoM");
  const onlyBoundsM = $("onlyBoundsM");
  const btnAplicarM = $("btnAplicarM");
  const btnResetM = $("btnResetM");

  const lblTotal = $("lblTotal");
  const lblCount = $("lblCount");
  const kpiGeo = $("kpiGeo");
  const kpiMatch = $("kpiMatch");
  const resultsList = $("resultsList");
  const btnRecentrar = $("btnRecentrar");
  const btnCargarLocal = $("btnCargarLocal");
  const btnViewUbicacion = $("btnViewUbicacion");
  const btnViewEpi = $("btnViewEpi");
  const viewUbicacion = $("viewUbicacion");
  const viewEpidemiologico = $("viewEpidemiologico");

  const kpiEpiTotal = $("kpiEpiTotal");
  const kpiEpiSectores = $("kpiEpiSectores");
  const kpiEpiRefs = $("kpiEpiRefs");
  const lblEpiCount = $("lblEpiCount");
  const epiSummaryList = $("epiSummaryList");
  const btnToggleEpiLegend = $("btnToggleEpiLegend");
  const epiLegend = $("epiLegend");
  const epiLegendSubtitle = $("epiLegendSubtitle");
  const epiLegendBadge = $("epiLegendBadge");
  const epiLegendTotal = $("epiLegendTotal");
  const epiLegendSectores = $("epiLegendSectores");
  const epiLegendRefs = $("epiLegendRefs");
  const epiLegendCronicos = $("epiLegendCronicos");
  const epiLegendTopList = $("epiLegendTopList");
  const epiLegendFilters = $("epiLegendFilters");
  const sectorEditorSelect = $("sectorEditorSelect");
  const sectorRadiusInput = $("sectorRadiusInput");
  const sectorEditorStatus = $("sectorEditorStatus");
  const btnSectorPick = $("btnSectorPick");
  const btnSectorSave = $("btnSectorSave");
  const btnSectorDelete = $("btnSectorDelete");
  const btnSectorExport = $("btnSectorExport");
  const selectedDiagnosisCount = $("selectedDiagnosisCount");
  const selectedDiagnosisList = $("selectedDiagnosisList");
  const selectedDiagnosisEmpty = $("selectedDiagnosisEmpty");
  const btnClearDiagnosisPanel = $("btnClearDiagnosisPanel");

  const PANQUEHUE_BOUNDS = [
    [-32.804165, -70.948224],
    [-32.755789, -70.739231]
  ];
  const PANQUEHUE_CENTER = [-32.7800, -70.8450];

  const SZ_COLORS = {
    "1": "#2fb7ff",
    "2": "#34d399",
    "3": "#94a3b8"
  };

  const SECTOR_REFERENCE_POINTS = {
    "el escorial": [-32.7735, -70.9165],
    "colunquen": [-32.7788, -70.9305],
    "palomar": [-32.7805, -70.8870],
    "palomar interior": [-32.7830, -70.8755],
    "panquehue centro": [-32.7800, -70.8475],
    "la cabaña": [-32.7805, -70.8345],
    "viña errázuriz": [-32.7825, -70.8170],
    "la pirca a": [-32.7895, -70.7935],
    "la pirca b": [-32.7900, -70.7805],
    "san roque": [-32.7828, -70.8015],
    "lo blanco": [-32.7878, -70.8525],
    "el mirador": [-32.7816, -70.8235],
    "lo campo": [-32.7845, -70.8415],
    "llayquen": [-32.7925, -70.8680],
    "san felipe": [-32.7505, -70.7260],
    "catemu": [-32.7845, -70.9660],
    "la redonda": [-32.7710, -70.7645],
    "llay llay": [-32.8425, -70.9580]
  };

  let all = [];
  let geo = [];
  let filtered = [];
  let epiSummary = [];
  let markerIndex = new Map();
  let sectorIndex = new Map();
  let activeView = "ubicacion";
  let focusRing = null;
  let epiHeatLayer = null;
  let epiRenderTimer = null;
  let sectorAreas = {};
  let sectorAreaDraft = null;
  let sectorPickMode = false;
  let epiFiltersApplied = false;

  const sectorCache = new Map();
  let sectorizacionesCatalog = [];
  let diagnosticoChoices = [];
  let diagnosticoSelectDesktop = null;
  let diagnosticoSelectMobile = null;
  let syncingDiagnosisControls = false;

  const safe = (v) => String(v ?? "");
  const esc = (s) => safe(s)
    .replaceAll("&", "&amp;")
    .replaceAll("<", "&lt;")
    .replaceAll(">", "&gt;")
    .replaceAll('"', "&quot;")
    .replaceAll("'", "&#039;");

  const normText = (v) => String(v ?? "").trim();
  const normLower = (v) => normText(v).toLowerCase();

  function normalizeDiagnosisValue(v) {
    return String(v ?? "").replace(/\s+/g, " ").trim();
  }

  function normalizeDiagnosisKey(v) {
    return normalizeDiagnosisValue(v).toLowerCase();
  }

  function splitDiagnosisParts(raw) {
    const text = String(raw ?? "")
      .replace(/[\r\n]+/g, "|")
      .replace(/[;,]+/g, "|")
      .replace(/\s*\|\s*/g, "|");
    return text
      .split("|")
      .map((part) => normalizeDiagnosisValue(part))
      .filter(Boolean);
  }

  function diagnosisValuesFromPerson(p) {
    const seen = new Set();
    const out = [];
    [p?.detalle_diagnosticos_cronicos, p?.diagnosticos_cronicos].forEach((raw) => {
      splitDiagnosisParts(raw).forEach((item) => {
        const key = normalizeDiagnosisKey(item);
        if (!key || seen.has(key)) return;
        seen.add(key);
        out.push(item);
      });
    });
    return out;
  }

  function getDiagnosisSelectValues(instance) {
    if (!instance) return [];
    const value = instance.getValue();
    const arr = Array.isArray(value) ? value : String(value || "").split(",");
    const seen = new Set();
    return arr
      .map((item) => normalizeDiagnosisValue(item))
      .filter((item) => {
        const key = normalizeDiagnosisKey(item);
        if (!key || seen.has(key)) return false;
        seen.add(key);
        return true;
      });
  }

  function setDiagnosisSelectValues(instance, values = [], silent = false) {
    if (!instance) return;
    const items = Array.isArray(values) ? values.map((v) => normalizeDiagnosisValue(v)).filter(Boolean) : [];
    if (silent) syncingDiagnosisControls = true;
    instance.clear(silent);
    items.forEach((item) => instance.addItem(item, silent));
    if (silent) syncingDiagnosisControls = false;
  }

  function summarizeDiagnosticos(values = []) {
    const arr = Array.isArray(values) ? values.filter(Boolean) : [];
    if (!arr.length) return "";
    if (arr.length <= 3) return arr.join(", ");
    return `${arr.slice(0, 3).join(", ")} +${arr.length - 3}`;
  }

  function updateDiagnosisCompactState(values = getDiagnosisSelectValues(diagnosticoSelectDesktop)) {
    const count = values.length;
    const label = count ? `${count} diagnóstico${count === 1 ? "" : "s"} seleccionado${count === 1 ? "" : "s"}` : "";
    [diagnosticoSelectDesktop, diagnosticoSelectMobile].forEach((instance) => {
      if (!instance) return;
      instance.wrapper?.classList.add("diag-compact-select");
      instance.wrapper?.classList.toggle("has-diag-items", count > 0);
      instance.control?.setAttribute("data-selected-label", label);
    });
  }

  function renderSelectedDiagnosesPanel(values = getDiagnosisSelectValues(diagnosticoSelectDesktop)) {
    const count = values.length;
    updateDiagnosisCompactState(values);

    if (selectedDiagnosisCount) selectedDiagnosisCount.textContent = String(count);
    if (selectedDiagnosisEmpty) selectedDiagnosisEmpty.classList.toggle("d-none", count > 0);
    if (btnClearDiagnosisPanel) btnClearDiagnosisPanel.disabled = count === 0;
    if (!selectedDiagnosisList) return;

    selectedDiagnosisList.innerHTML = values.map((diag, idx) => `
      <div class="selected-diagnosis-chip" title="${esc(diag)}">
        <span>${esc(diag)}</span>
        <button type="button" class="diag-remove-btn" data-remove-diagnosis-index="${idx}" aria-label="Quitar diagnóstico">
          <i class="bi bi-x"></i>
        </button>
      </div>
    `).join("");
  }

  function removeSelectedDiagnosisAt(index) {
    const values = getDiagnosisSelectValues(diagnosticoSelectDesktop);
    if (!Number.isInteger(index) || index < 0 || index >= values.length) return;
    values.splice(index, 1);
    syncingDiagnosisControls = true;
    setDiagnosisSelectValues(diagnosticoSelectDesktop, values, true);
    setDiagnosisSelectValues(diagnosticoSelectMobile, values, true);
    syncingDiagnosisControls = false;
    epiFiltersApplied = false;
    renderSelectedDiagnosesPanel(values);
    applyDebounced();
  }

  function initDiagnosticoSelect(el, onChange) {
    if (!el || typeof TomSelect === "undefined") return null;
    return new TomSelect(el, {
      plugins: {
        remove_button: { title: 'Quitar' }
      },
      valueField: 'value',
      labelField: 'text',
      searchField: ['text'],
      options: diagnosticoChoices.map((item) => ({ value: item, text: item })),
      items: [],
      create: false,
      persist: false,
      closeAfterSelect: false,
      hideSelected: true,
      maxItems: null,
      placeholder: 'Buscar y seleccionar diagnósticos...',
      onChange,
      onInitialize() {
        this.wrapper?.classList.add("diag-compact-select");
        updateDiagnosisCompactState(getDiagnosisSelectValues(diagnosticoSelectDesktop));
      }
    });
  }

  async function loadDiagnosticosCatalog() {
    try {
      const r = await fetchJSON(API_GEO, {
        method: 'POST',
        body: JSON.stringify({ mode: 'diagnostics' })
      });
      diagnosticoChoices = Array.isArray(r.diagnosticos) ? r.diagnosticos.map((item) => normalizeDiagnosisValue(item)).filter(Boolean) : [];
    } catch (e) {
      diagnosticoChoices = [];
    }

    const desktopValues = getDiagnosisSelectValues(diagnosticoSelectDesktop);
    const mobileValues = getDiagnosisSelectValues(diagnosticoSelectMobile);

    if (!diagnosticoSelectDesktop) {
      diagnosticoSelectDesktop = initDiagnosticoSelect(fDiagnostico, () => {
        if (syncingDiagnosisControls) return;
        syncingDiagnosisControls = true;
        const values = getDiagnosisSelectValues(diagnosticoSelectDesktop);
        setDiagnosisSelectValues(diagnosticoSelectMobile, values, true);
        syncingDiagnosisControls = false;
        epiFiltersApplied = false;
        renderSelectedDiagnosesPanel(values);
        applyDebounced();
      });
    }

    if (!diagnosticoSelectMobile) {
      diagnosticoSelectMobile = initDiagnosticoSelect(fDiagnosticoM, () => {
        if (syncingDiagnosisControls) return;
        syncingDiagnosisControls = true;
        const values = getDiagnosisSelectValues(diagnosticoSelectMobile);
        setDiagnosisSelectValues(diagnosticoSelectDesktop, values, true);
        syncingDiagnosisControls = false;
        renderSelectedDiagnosesPanel(values);
      });
    }

    [diagnosticoSelectDesktop, diagnosticoSelectMobile].forEach((instance) => {
      if (!instance) return;
      instance.clearOptions();
      diagnosticoChoices.forEach((item) => instance.addOption({ value: item, text: item }));
      instance.refreshOptions(false);
    });

    const selectedValues = desktopValues.length ? desktopValues : mobileValues;
    setDiagnosisSelectValues(diagnosticoSelectDesktop, selectedValues, true);
    setDiagnosisSelectValues(diagnosticoSelectMobile, selectedValues, true);
    renderSelectedDiagnosesPanel(selectedValues);
  }

  function normalizeTodos(v) {
    const t = normLower(v);
    if (!t) return "";
    if (["todos", "todas", "all", "-"].includes(t)) return "";
    return normText(v);
  }

  function normalizeBoolFilter(v) {
    const t = normalizeTodos(v).toLowerCase();
    if (!t) return "";
    if (["1", "si", "sí", "s", "true", "yes", "y"].includes(t)) return "1";
    if (["0", "no", "n", "false"].includes(t)) return "0";
    return "";
  }

  function normalizeIdFilter(v) {
    const t = normalizeTodos(v);
    if (!t) return "";
    return /^\d+$/.test(t) ? t : "";
  }

  function toNum(v) {
    const n = Number(String(v).replace(",", "."));
    return Number.isFinite(n) ? n : null;
  }

  function debounce(fn, ms = 250) {
    let t;
    return (...args) => {
      clearTimeout(t);
      t = setTimeout(() => fn(...args), ms);
    };
  }

  const userRaw = sessionStorage.getItem("fp_user");
  const fpUser = userRaw ? JSON.parse(userRaw) : null;
  const authHeaders = () => ({
    ...(fpUser?.rut ? { "X-FP-RUT": String(fpUser.rut) } : {}),
    ...(fpUser?.rol ? { "X-FP-ROL": String(fpUser.rol) } : {}),
    ...(fpUser?.rol_funcionario ? { "X-FP-ROL": String(fpUser.rol_funcionario) } : {}),
  });

  async function fetchJSON(url, opts = {}) {
    const res = await fetch(url, {
      credentials: "same-origin",
      headers: { "Content-Type": "application/json", ...authHeaders(), ...(opts.headers || {}) },
      ...opts
    });
    const json = await res.json().catch(() => null);
    if (!res.ok || !json || json.ok === false) {
      throw new Error(json?.message || "Error de servidor");
    }
    return json;
  }

  function setSelectOptions(sel, { placeholderLabel = "Todos", placeholderValue = "" } = {}, items = [], getValue, getLabel) {
    if (!sel) return;
    const current = sel.value;
    const opts = [`<option value="${placeholderValue}">${placeholderLabel}</option>`];
    for (const it of items) {
      const val = String(getValue(it) ?? "");
      const lab = String(getLabel(it) ?? val);
      opts.push(`<option value="${val}">${lab}</option>`);
    }
    sel.innerHTML = opts.join("");
    if (current && [...sel.options].some((o) => o.value === current)) sel.value = current;
    else sel.value = placeholderValue;
  }

  function selectedText(sel) {
    if (!sel) return "";
    const opt = sel.options?.[sel.selectedIndex];
    return String(opt?.textContent || "").trim();
  }

  function getSectorAreaKey(item = {}) {
    return String(item.sector_id || item.sector || item.id || item.sector_nombre || "").trim();
  }

  function validSectorPolygon(area) {
    return String(area?.type || '') === 'polygon' && Array.isArray(area?.coordinates) && area.coordinates.length >= 3;
  }

  function sectorAreaCentroid(area) {
    if (validSectorPolygon(area)) {
      const coords = area.coordinates.filter(pair => Array.isArray(pair) && pair.length >= 2).map(pair => [Number(pair[0]), Number(pair[1])]).filter(pair => Number.isFinite(pair[0]) && Number.isFinite(pair[1]));
      if (coords.length >= 3) {
        const lat = coords.reduce((a,p)=>a+p[0],0) / coords.length;
        const lng = coords.reduce((a,p)=>a+p[1],0) / coords.length;
        return [lat, lng];
      }
    }

    if (area && Number.isFinite(Number(area.lat)) && Number.isFinite(Number(area.lng))) {
      return [Number(area.lat), Number(area.lng)];
    }
    return null;
  }

  function countSectorAreaReferences() {
    return Object.values(sectorAreas).filter(area => !!sectorAreaCentroid(area)).length;
  }

  async function loadSectorAreasConfig() {
    try {
      const r = await fetchJSON(API_SECTOR_MAP);
      sectorAreas = (r.data && typeof r.data === "object") ? r.data : {};
    } catch {
      sectorAreas = {};
    }
  }

  function writeSectorAreas() { return; }

  async function fetchCatalog({ sectorizacion = "" } = {}) {
    return fetchJSON(API_GEO, {
      method: "POST",
      body: JSON.stringify({ mode: "filters", sectorizacion })
    });
  }

  async function loadSectorizaciones() {
    const r = await fetchCatalog();
    sectorizacionesCatalog = Array.isArray(r.sectorizaciones) ? r.sectorizaciones : [];
    sectorCache.set("", Array.isArray(r.sectores) ? r.sectores : []);
  }

  async function loadSectoresBySectorizacion(szId) {
    const key = szId || "";
    if (sectorCache.has(key)) return sectorCache.get(key);
    const r = await fetchCatalog({ sectorizacion: key });
    const arr = Array.isArray(r.sectores) ? r.sectores : [];
    sectorCache.set(key, arr);
    return arr;
  }

  async function refreshSectorizacionAndSectoresUI({ keepSector = true } = {}) {
    const getSzLabel = (x) => x.nombre_sectorizacion || x.nombre || x.label || x.id;
    setSelectOptions(fSectorizacion, { placeholderLabel: "Todas", placeholderValue: "" }, sectorizacionesCatalog, (x) => x.id, getSzLabel);
    setSelectOptions(fSectorizacionM, { placeholderLabel: "Todas", placeholderValue: "" }, sectorizacionesCatalog, (x) => x.id, getSzLabel);

    const sz = normalizeIdFilter(fSectorizacion?.value);
    const sectores = await loadSectoresBySectorizacion(sz);
    const getSecLabel = (x) => x.nombre_sector || x.nombre || x.label || x.id;
    const prevSector = keepSector ? (fSector?.value || "") : "";

    setSelectOptions(fSector, { placeholderLabel: "Todos", placeholderValue: "" }, sectores, (x) => x.id, getSecLabel);
    setSelectOptions(fSectorM, { placeholderLabel: "Todos", placeholderValue: "" }, sectores, (x) => x.id, getSecLabel);

    const okPrev = prevSector && fSector && [...fSector.options].some((o) => o.value === prevSector);
    const newSector = okPrev ? prevSector : "";
    if (fSector) fSector.value = newSector;
    if (fSectorM) fSectorM.value = newSector;
  }

  async function onSectorizacionChanged(from = "desktop", { apply = true } = {}) {
    if (from === "desktop") syncMobileFromDesktop();
    else syncDesktopFromMobile();

    const sz = normalizeIdFilter(fSectorizacion.value);
    fSectorizacion.value = sz;
    fSectorizacionM.value = sz;

    try {
      await refreshSectorizacionAndSectoresUI({ keepSector: false });
    } catch (e) {}

    if (apply) applyFilters();
  }

  async function fetchAllGeo() {
    const limit = 5000;
    let offset = 0;
    const out = [];

    while (true) {
      const r = await fetchJSON(API_GEO, {
        method: "POST",
        body: JSON.stringify({ limit, offset })
      });

      out.push(...(r.data || []));
      const total = Number(r.meta?.total ?? out.length);
      offset += limit;
      if (offset >= total) break;
    }
    return out;
  }

  async function syncGeoFromBackend({ silent = false } = {}) {
    try {
      if (!silent) {
        Swal.fire({
          title: "Cargando geolocalización...",
          allowOutsideClick: false,
          didOpen: () => Swal.showLoading()
        });
      }

      const rows = await fetchAllGeo();
      localStorage.setItem(KEY_PERSONAS, JSON.stringify(rows));
      if (!silent) Swal.close();
      return true;
    } catch (err) {
      if (!silent) Swal.fire("Error", err?.message || "No se pudo cargar geolocalización", "error");
      return false;
    }
  }

  function readLocalPersons() {
    const raw = localStorage.getItem(KEY_PERSONAS);
    if (!raw) return [];
    try {
      const arr = JSON.parse(raw);
      return Array.isArray(arr) ? arr : [];
    } catch {
      return [];
    }
  }

  function seedGeoDemoIfEmpty() {
    if (all.length) return;
    const demo = [
      {
        rut: "12345678-5",
        nombres: "María",
        apellidos: "González Rojas",
        genero: "F",
        inscrito_cesfam: true,
        migrante: false,
        direccion: "Panquehue (demo)",
        sectorizacion: 1,
        sector: 5,
        estratificacion_riesgo: "G2",
        detalle_diagnosticos_cronicos: "I10 Hipertensión",
        latitud: -32.7805,
        longitud: -70.8450,
        sectorizacion_nombre: "SECTOR A",
        sector_nombre: "Panquehue Centro"
      },
      {
        rut: "98765432-K",
        nombres: "Juan",
        apellidos: "Pérez Díaz",
        genero: "M",
        inscrito_cesfam: false,
        migrante: null,
        direccion: "Ruta 60 (demo)",
        sectorizacion: 2,
        sector: 10,
        estratificacion_riesgo: "G3",
        detalle_diagnosticos_cronicos: "E11 Diabetes",
        latitud: -32.7908,
        longitud: -70.9000,
        sectorizacion_nombre: "SECTOR B",
        sector_nombre: "San Roque"
      }
    ];
    localStorage.setItem(KEY_PERSONAS, JSON.stringify(demo));
    all = demo;
  }

  function syncDesktopFromMobile() {
    q.value = qM.value;
    fInscrito.value = fInscritoM.value;
    fMigrante.value = fMigranteM.value;
    fSectorizacion.value = fSectorizacionM.value;
    fSector.value = fSectorM.value;
    fEstratificacion.value = fEstratificacionM.value;
    const values = getDiagnosisSelectValues(diagnosticoSelectMobile);
    setDiagnosisSelectValues(diagnosticoSelectDesktop, values, true);
    renderSelectedDiagnosesPanel(values);
    onlyBounds.checked = onlyBoundsM.checked;
  }

  function syncMobileFromDesktop() {
    qM.value = q.value;
    fInscritoM.value = fInscrito.value;
    fMigranteM.value = fMigrante.value;
    fSectorizacionM.value = fSectorizacion.value;
    fSectorM.value = fSector.value;
    fEstratificacionM.value = fEstratificacion.value;
    const values = getDiagnosisSelectValues(diagnosticoSelectDesktop);
    setDiagnosisSelectValues(diagnosticoSelectMobile, values, true);
    renderSelectedDiagnosesPanel(values);
    onlyBoundsM.checked = onlyBounds.checked;
  }

  function currentFilters() {
    const diagnosticos = getDiagnosisSelectValues(diagnosticoSelectDesktop);
    const edadMin = String(fEdadMin?.value || "").trim();
    const edadMax = String(fEdadMax?.value || "").trim();
    return {
      text: "",
      inscrito: normalizeBoolFilter(fInscrito?.value || ""),
      migrante: normalizeBoolFilter(fMigrante?.value || ""),
      percapitado: normalizeBoolFilter(fPercapitado?.value || ""),
      genero: String(fGenero?.value || "").trim().toLowerCase(),
      edad_min: edadMin,
      edad_max: edadMax,
      sectorizacion: normalizeIdFilter(fSectorizacion?.value || ""),
      sector: normalizeIdFilter(fSector?.value || ""),
      estratificacion: (fEstratificacion?.value || "").trim().toUpperCase(),
      diagnosticos,
      diagnostico: summarizeDiagnosticos(diagnosticos),
      onlyBounds: false
    };
  }

  function currentLocationFilters() {
    return {
      text: (q?.value || "").trim().toLowerCase()
    };
  }

  const map = L.map("map", {
    preferCanvas: true,
    maxBounds: PANQUEHUE_BOUNDS,
    maxBoundsViscosity: 1.0
  });

  const epiMap = L.map("epiMap", {
    preferCanvas: true,
    maxBounds: PANQUEHUE_BOUNDS,
    maxBoundsViscosity: 1.0,
    zoomControl: true
  });

  L.tileLayer("https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png", {
    maxZoom: 19,
    attribution: "&copy; OpenStreetMap"
  }).addTo(map);

  L.tileLayer("https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png", {
    maxZoom: 19,
    attribution: "&copy; OpenStreetMap"
  }).addTo(epiMap);

  const cluster = L.markerClusterGroup({
    chunkedLoading: true,
    chunkInterval: 200,
    chunkDelay: 30,
    showCoverageOnHover: false,
    spiderfyOnMaxZoom: true,
    disableClusteringAtZoom: 18,
    maxClusterRadius: 60
  });
  map.addLayer(cluster);

  const epiCirclesLayer = L.layerGroup().addTo(epiMap);
  const epiSectorAreasLayer = L.layerGroup().addTo(epiMap);

  function fitMapToPanquehue(targetMap) {
    const targetEl = targetMap.getContainer?.();
    if (targetEl && !isElementVisible(targetEl)) return;
    targetMap.invalidateSize(true);
    targetMap.fitBounds(PANQUEHUE_BOUNDS, { padding: [18, 18] });
    setTimeout(() => {
      const retryEl = targetMap.getContainer?.();
      if (retryEl && !isElementVisible(retryEl)) return;
      targetMap.invalidateSize(true);
      targetMap.fitBounds(PANQUEHUE_BOUNDS, { padding: [18, 18] });
    }, 120);
  }

  map.whenReady(() => fitMapToPanquehue(map));
  epiMap.whenReady(() => {
    if (isElementVisible(viewEpidemiologico)) fitMapToPanquehue(epiMap);
  });
  window.addEventListener("load", () => {
    fitMapToPanquehue(map);
    if (isElementVisible(viewEpidemiologico)) fitMapToPanquehue(epiMap);
  });

  const resizeMaps = debounce(() => {
    map.invalidateSize(true);
    if (isElementVisible(viewEpidemiologico)) {
      epiMap.invalidateSize(true);
      scheduleEpiRefresh(currentFilters(), 80);
    }
  }, 180);
  window.addEventListener("resize", resizeMaps);
  document.addEventListener("fp:sidebar-toggle", () => setTimeout(resizeMaps, 260));

  const canvas = document.getElementById("filtersCanvas");
  if (canvas) {
    canvas.addEventListener("shown.bs.offcanvas", () => setTimeout(() => {
      map.invalidateSize(true);
      if (isElementVisible(viewEpidemiologico)) scheduleEpiRefresh(currentFilters(), 80);
    }, 80));
    canvas.addEventListener("hidden.bs.offcanvas", () => setTimeout(() => {
      map.invalidateSize(true);
      if (isElementVisible(viewEpidemiologico)) scheduleEpiRefresh(currentFilters(), 80);
    }, 80));
  }

  function isElementVisible(el) {
    if (!el) return false;
    const style = window.getComputedStyle(el);
    const rect = el.getBoundingClientRect();
    return style.display !== "none" && style.visibility !== "hidden" && rect.width > 0 && rect.height > 0;
  }

  function scheduleEpiRefresh(F = currentFilters(), delay = 160) {
    clearTimeout(epiRenderTimer);
    epiRenderTimer = setTimeout(() => {
      if (!isElementVisible(viewEpidemiologico)) return;
      epiMap.invalidateSize(true);
      renderEpiMap(F);
    }, delay);
  }

  function setActiveView(view) {
    activeView = view;
    const isUbicacion = view === "ubicacion";

    btnViewUbicacion.classList.toggle("active", isUbicacion);
    btnViewEpi.classList.toggle("active", !isUbicacion);
    viewUbicacion.classList.toggle("active", isUbicacion);
    viewEpidemiologico.classList.toggle("active", !isUbicacion);

    setTimeout(() => {
      if (isUbicacion) {
        map.invalidateSize(true);
      } else {
        scheduleEpiRefresh(currentFilters(), 120);
      }
    }, 120);
  }

  function withinBounds(lat, lng) {
    const b = map.getBounds();
    return b.contains([lat, lng]);
  }

  function personMatchesLocation(p, F) {
    if (!F.text) return true;
    const hay = [
      p.rut, p.nombres, p.apellidos, p.direccion
    ].map((x) => safe(x).toLowerCase()).join(" | ");
    return hay.includes(F.text);
  }

  function personMatches(p, F) {
    if (F.inscrito !== "") {
      const ins = normalizeBoolFilter(p.inscrito_cesfam);
      if (ins !== F.inscrito) return false;
    }

    if (F.migrante !== "") {
      const mig = normalizeBoolFilter(p.migrante);
      if (mig !== F.migrante) return false;
    }

    if (F.sectorizacion !== "" && String(p.sectorizacion ?? "") !== F.sectorizacion) return false;
    if (F.sector !== "" && String(p.sector ?? "") !== F.sector) return false;

    if (F.estratificacion && String(p.estratificacion_riesgo || "").trim().toUpperCase() !== F.estratificacion) {
      return false;
    }

    if (Array.isArray(F.diagnosticos) && F.diagnosticos.length) {
      const personDiags = diagnosisValuesFromPerson(p);
      const personKeys = new Set(personDiags.map((item) => normalizeDiagnosisKey(item)));
      const hayDiag = [
        p.detalle_diagnosticos_cronicos,
        p.diagnosticos_cronicos,
        p.cantidad_condiciones_cronicas
      ].map((x) => safe(x).toLowerCase()).join(" | ");
      const matched = F.diagnosticos.some((diag) => {
        const key = normalizeDiagnosisKey(diag);
        return personKeys.has(key) || hayDiag.includes(key);
      });
      if (!matched) return false;
    }

    if (F.onlyBounds && !withinBounds(p._lat, p._lng)) return false;

    if (!F.text) return true;
    const hay = [
      p.rut, p.nombres, p.apellidos, p.direccion, p.celular, p.telefonos, p.genero
    ].map((x) => safe(x).toLowerCase()).join(" | ");
    return hay.includes(F.text);
  }

  function renderKPIs() {
    lblTotal.textContent = `${all.length} total`;
    kpiGeo.textContent = geo.length;
    kpiMatch.textContent = filtered.length;
    lblCount.textContent = `Coincidencias: ${filtered.length} (con coordenadas)`;
  }

  function markerForPerson(p) {
    const color = (p.sectorizacion_color || SZ_COLORS[String(p.sectorizacion ?? "")] || "#2fb7ff");
    const icon = L.divIcon({
      className: "fp-marker",
      html: `<div style="width:14px;height:14px;border-radius:999px;background:${color};border:2px solid rgba(255,255,255,.95);box-shadow:0 10px 20px rgba(0,0,0,.18);"></div>`,
      iconSize: [14, 14],
      iconAnchor: [7, 7]
    });

    const m = L.marker([p._lat, p._lng], { icon });
    const full = `${safe(p.nombres)} ${safe(p.apellidos)}`.trim();
    const ins01 = normalizeBoolFilter(p.inscrito_cesfam);
    const inscr = (ins01 === "" ? "—" : (ins01 === "1" ? "Sí" : "No"));
    const sec = `${p.sectorizacion_nombre ?? p.sectorizacion ?? "—"} / ${p.sector_nombre ?? p.sector ?? "—"}`;
    const sexo = safe(p.genero || "—");
    const estra = safe(p.estratificacion_riesgo || "Sin dato");
    const cronicos = Number(p.cantidad_condiciones_cronicas || 0);
    const diags = safe(p.detalle_diagnosticos_cronicos || "—");
    const gmaps = `https://www.google.com/maps?q=${encodeURIComponent(p._lat + "," + p._lng)}`;

    m.bindPopup(`
      <div style="min-width:240px">
        <div style="font-weight:900">${esc(full || "Sin nombre")}</div>
        <div style="color:rgba(11,18,32,.72);font-size:.92rem">${esc(p.rut || "")}</div>
        <hr style="margin:.5rem 0"/>
        <div><b>Sexo:</b> ${esc(sexo)}</div>
        <div><b>Inscrito:</b> ${esc(inscr)}</div>
        <div><b>Estratificación:</b> ${esc(estra)}</div>
        <div><b>Condiciones crónicas:</b> ${esc(String(cronicos))}</div>
        <div><b>Sector:</b> ${esc(sec)}</div>
        <div><b>Dirección:</b> ${esc(p.direccion || "—")}</div>
        <div style="margin-top:.35rem"><b>Diagnósticos:</b> ${esc(diags)}</div>
        <div style="margin-top:.5rem;display:flex;gap:.4rem;flex-wrap:wrap">
          <a class="btn btn-sm btn-outline-secondary" href="${gmaps}" target="_blank" rel="noopener">Abrir en Maps</a>
        </div>
      </div>
    `);
    return m;
  }

  function renderMarkers() {
    cluster.clearLayers();
    markerIndex.clear();

    const markers = filtered.map((p) => {
      const m = markerForPerson(p);
      if (p.rut) markerIndex.set(p.rut, m);
      return m;
    });

    cluster.addLayers(markers);
  }

  function renderList() {
    resultsList.innerHTML = "";
    if (!filtered.length) {
      resultsList.innerHTML = `<div class="p-3 text-muted">No hay coincidencias con coordenadas. Puedes revisar la vista epidemiológica para agrupar por sector.</div>`;
      return;
    }

    const limit = 250;
    const slice = filtered.slice(0, limit);
    slice.forEach((p) => {
      const full = `${safe(p.nombres)} ${safe(p.apellidos)}`.trim() || "Sin nombre";
      const ins01 = normalizeBoolFilter(p.inscrito_cesfam);
      const inscr = (ins01 === "" ? "—" : (ins01 === "1" ? "Sí" : "No"));
      const sec = `${p.sectorizacion_nombre ?? p.sectorizacion ?? "—"} / ${p.sector_nombre ?? p.sector ?? "—"}`;
      const sexo = safe(p.genero || "—");
      const estra = safe(p.estratificacion_riesgo || "Sin dato");
      const cronicos = Number(p.cantidad_condiciones_cronicas || 0);

      resultsList.insertAdjacentHTML("beforeend", `
        <div class="result-item">
          <div>
            <div class="name">${esc(full)}</div>
            <div class="meta">${esc(p.rut || "—")} · Sexo: ${esc(sexo)} · Inscrito: ${esc(inscr)} · Estratificación: ${esc(estra)} · Crónicos: ${esc(String(cronicos))} · Sector: ${esc(sec)}</div>
          </div>
          <div class="badges">
            <button class="btn-mini" type="button" data-act="zoom" data-rut="${esc(p.rut)}" title="Localizar en el mapa">
              <i class="bi bi-crosshair"></i>
            </button>
            <button class="btn-mini" type="button" data-act="popup" data-rut="${esc(p.rut)}" title="Abrir ficha">
              <i class="bi bi-chat-square-text"></i>
            </button>
          </div>
        </div>
      `);
    });

    if (filtered.length > limit) {
      resultsList.insertAdjacentHTML("beforeend", `
        <div class="p-2 text-muted small">
          Mostrando ${limit} de ${filtered.length}. Usa más filtros para acotar.
        </div>
      `);
    }
  }

  function highlightLatLng(latlng) {
    if (focusRing) {
      try { map.removeLayer(focusRing); } catch {}
      focusRing = null;
    }

    focusRing = L.circleMarker(latlng, {
      radius: 18,
      weight: 4,
      color: "#ff3b30",
      fillOpacity: 0
    }).addTo(map);

    let r = 18;
    const t = setInterval(() => {
      if (!focusRing) { clearInterval(t); return; }
      r += 4;
      focusRing.setRadius(r);
    }, 120);

    setTimeout(() => {
      clearInterval(t);
      if (focusRing) {
        try { map.removeLayer(focusRing); } catch {}
        focusRing = null;
      }
    }, 1200);
  }

  function focusMarker(m, { openPopup = true } = {}) {
    if (!m) return;
    cluster.zoomToShowLayer(m, () => {
      const ll = m.getLatLng();
      map.setView(ll, Math.max(map.getZoom(), 17), { animate: true });
      setTimeout(() => map.invalidateSize(true), 60);
      highlightLatLng(ll);
      if (openPopup) m.openPopup();
    });
  }

  function recenterActiveMap() {
    if (activeView === "ubicacion") fitMapToPanquehue(map);
    else fitMapToPanquehue(epiMap);
  }

  function loadAndPrepare() {
    all = readLocalPersons();
    seedGeoDemoIfEmpty();

    geo = all
      .map((p) => {
        const lat = toNum(p.latitud);
        const lng = toNum(p.longitud);
        if (lat === null || lng === null) return null;
        return { ...p, _lat: lat, _lng: lng };
      })
      .filter(Boolean);

    if (!geo.length) {
      cluster.clearLayers();
      resultsList.innerHTML = `
        <div class="p-3 text-muted">
          No hay personas con coordenadas para mostrar.
          <br/>En <b>Personas</b> agrega latitud/longitud o usa “Capturar GPS”.
        </div>
      `;
      fitMapToPanquehue(map);
    } else {
      filtered = geo.slice();
      renderMarkers();
      renderList();
      fitMapToPanquehue(map);
    }

    renderKPIs();
  }

  function buildFilterPayload(F) {
    return {
      q: F.text || "",
      inscrito_cesfam: F.inscrito,
      migrante: F.migrante,
      percapitado: F.percapitado,
      genero: F.genero,
      edad_min: F.edad_min,
      edad_max: F.edad_max,
      sectorizacion: F.sectorizacion,
      sector: F.sector,
      estratificacion_riesgo: F.estratificacion,
      diagnostico_q: "",
      diagnosticos: Array.isArray(F.diagnosticos) ? F.diagnosticos : []
    };
  }

  function sectorRefPoint(item) {
    const areaKey = getSectorAreaKey(item);
    const manual = areaKey ? sectorAreas[areaKey] : null;
    const manualCenter = sectorAreaCentroid(manual);
    if (manualCenter) return manualCenter;

    const lat = toNum(item.avg_lat);
    const lng = toNum(item.avg_lng);
    if (lat !== null && lng !== null) return [lat, lng];

    const secName = normLower(item.sector_nombre);
    if (secName && SECTOR_REFERENCE_POINTS[secName]) return SECTOR_REFERENCE_POINTS[secName];

    const sz = String(item.sectorizacion_id || item.sectorizacion || "");
    if (sz === "1") return [-32.7790, -70.8850];
    if (sz === "2") return [-32.7850, -70.8190];
    if (sz === "3") return [-32.7750, -70.7600];
    return null;
  }

  function intensityColor(ratio) {
    if (ratio >= 0.9) return "#7f1d1d";
    if (ratio >= 0.75) return "#b91c1c";
    if (ratio >= 0.55) return "#ea580c";
    if (ratio >= 0.35) return "#f59e0b";
    if (ratio > 0) return "#fde68a";
    return "#e5e7eb";
  }

  function formatFilterSummary(F) {
    const parts = [];
    const szTxt = fSectorizacion?.options?.[fSectorizacion.selectedIndex]?.textContent?.trim() || "";
    const secTxt = fSector?.options?.[fSector.selectedIndex]?.textContent?.trim() || "";
    if (F.estratificacion) parts.push(`Estratificación ${F.estratificacion}`);
    if (F.diagnostico) parts.push(`Diagnóstico ${F.diagnostico}`);
    if (F.inscrito !== "") parts.push(`Inscrito ${F.inscrito === "1" ? "Sí" : "No"}`);
    if (F.migrante !== "") parts.push(`Migrante ${F.migrante === "1" ? "Sí" : "No"}`);
    if (F.percapitado !== "") parts.push(`Percápita ${F.percapitado === "1" ? "Sí" : "No"}`);
    if (F.genero) parts.push(`Sexo ${F.genero === "f" ? "Femenino" : (F.genero === "m" ? "Masculino" : "Otro")}`);
    if (F.edad_min) parts.push(`Edad mín. ${F.edad_min}`);
    if (F.edad_max) parts.push(`Edad máx. ${F.edad_max}`);
    if (F.sectorizacion && szTxt) parts.push(`Sectorización ${szTxt}`);
    if (F.sector && secTxt) parts.push(`Sector ${secTxt}`);
    if (F.text) parts.push(`Búsqueda ${F.text}`);
    return parts.length ? parts.join(' · ') : 'Sin filtros adicionales';
  }

  function renderSectorAreaEditorStatus() {
    if (!sectorEditorStatus || !sectorEditorSelect) return;
    const key = String(sectorEditorSelect.value || "");
    const saved = key ? sectorAreas[key] : null;
    if (sectorAreaDraft && sectorAreaDraft.key === key) {
      sectorEditorStatus.textContent = `Área temporal lista: centro ${sectorAreaDraft.lat.toFixed(5)}, ${sectorAreaDraft.lng.toFixed(5)} · radio ${sectorAreaDraft.radius} m.`;
      return;
    }
    if (saved) {
      sectorEditorStatus.textContent = `${saved.name || 'Sector'} con área guardada · radio ${saved.radius || 0} m.`;
      return;
    }
    sectorEditorStatus.textContent = key ? 'Selecciona “Marcar área” y luego haz clic en el mapa.' : 'Selecciona un sector para comenzar.';
  }

  function renderStoredSectorAreas(summaryMap = new Map(), F = currentFilters()) {
    epiSectorAreasLayer.clearLayers();
    const bounds = [];
    const hasSummary = epiFiltersApplied && summaryMap.size > 0;
    const summaryValues = [...summaryMap.values()].map(item => Number(item.total_personas || 0)).filter(v => Number.isFinite(v) && v > 0);
    const maxTotal = summaryValues.length ? Math.max(...summaryValues) : 0;
    const minTotal = summaryValues.length ? Math.min(...summaryValues) : 0;
    const filterSummary = formatFilterSummary(F);

    Object.entries(sectorAreas).forEach(([key, area]) => {
      const item = summaryMap.get(String(key)) || null;
      const total = Number(item?.total_personas || 0);
      const cronicos = Number(item?.con_cronicos || 0);
      const ratio = hasSummary && maxTotal > 0 ? (minTotal === maxTotal ? 1 : Math.max(0, (total - minTotal) / (maxTotal - minTotal))) : 0;
      const fillColor = hasSummary ? intensityColor(total > 0 ? ratio : 0) : '#dbeafe';
      const fillOpacity = hasSummary ? (total > 0 ? 0.62 : 0.15) : 0.08;
      const weight = hasSummary && total > 0 ? 2.8 : 1.8;
      const tooltipHtml = `
        <div class="sector-tooltip-card">
          <div class="sector-tooltip-title">${esc(item?.sector_nombre || area?.label || area?.name || key)}</div>
          <div class="sector-tooltip-sub">${esc(item?.sectorizacion_nombre || area?.sectorizacion_nombre || 'Sector comunal')}</div>
          <div class="sector-tooltip-row"><span>Personas</span><strong>${total.toLocaleString('es-CL')}</strong></div>
          <div class="sector-tooltip-row"><span>Crónicos</span><strong>${cronicos.toLocaleString('es-CL')}</strong></div>
          <div class="sector-tooltip-row"><span>Filtro</span><strong>${esc(filterSummary)}</strong></div>
        </div>`;

      if (validSectorPolygon(area)) {
        const coords = area.coordinates
          .filter(pair => Array.isArray(pair) && pair.length >= 2)
          .map(pair => [Number(pair[0]), Number(pair[1])])
          .filter(pair => Number.isFinite(pair[0]) && Number.isFinite(pair[1]));
        if (coords.length < 3) return;
        coords.forEach(pair => bounds.push(pair));
        const polygon = L.polygon(coords, {
          color: total > 0 ? '#1d4ed8' : '#2563eb',
          weight,
          fillColor,
          fillOpacity
        }).addTo(epiSectorAreasLayer);
        polygon.bindTooltip(tooltipHtml, { sticky:true, direction:'top', className:'sector-hover-tooltip' });
        polygon.on('mouseover', () => polygon.setStyle({ weight: weight + 1, fillOpacity: Math.min(fillOpacity + 0.12, 0.82) }));
        polygon.on('mouseout', () => polygon.setStyle({ weight, fillOpacity }));
        sectorIndex.set(String(key), { layer: polygon, latlng: polygon.getBounds().getCenter() });
        return;
      }

      const lat = Number(area?.lat), lng = Number(area?.lng), radius = Math.max(50, Number(area?.radius || 450));
      if (!Number.isFinite(lat) || !Number.isFinite(lng)) return;
      bounds.push([lat, lng]);
      const circle = L.circle([lat, lng], {
        radius,
        color: total > 0 ? '#1d4ed8' : '#2563eb',
        weight,
        fillColor,
        fillOpacity: hasSummary ? Math.max(fillOpacity - 0.08, 0.18) : 0.07
      }).addTo(epiSectorAreasLayer);
      circle.bindTooltip(tooltipHtml, { sticky:true, direction:'top', className:'sector-hover-tooltip' });
      circle.on('mouseover', () => circle.setStyle({ weight: weight + 1, fillOpacity: Math.min(fillOpacity + 0.1, 0.78) }));
      circle.on('mouseout', () => circle.setStyle({ weight, fillOpacity: hasSummary ? Math.max(fillOpacity - 0.08, 0.18) : 0.07 }));
      sectorIndex.set(String(key), { layer: circle, latlng: circle.getBounds().getCenter() });
    });
    return bounds;
  }

  function refreshSectorEditorOptions() { return; }

  function exportSectorAreas() {
    const blob = new Blob([JSON.stringify(sectorAreas, null, 2)], { type: 'application/json' });
    const a = document.createElement('a');
    a.href = URL.createObjectURL(blob);
    a.download = 'sectores_referencias.json';
    a.click();
    setTimeout(() => URL.revokeObjectURL(a.href), 500);
  }

  function formatLegendFilters(F) {
    const parts = [];
    if (F.text) parts.push(`Búsqueda: ${F.text}`);
    if (F.inscrito !== "") parts.push(`Inscrito: ${F.inscrito === "1" ? "Sí" : "No"}`);
    if (F.migrante !== "") parts.push(`Migrante: ${F.migrante === "1" ? "Sí" : "No"}`);
    if (F.percapitado !== "") parts.push(`Percápita: ${F.percapitado === "1" ? "Sí" : "No"}`);
    if (F.genero) parts.push(`Sexo: ${F.genero === "f" ? "Femenino" : (F.genero === "m" ? "Masculino" : "Otro")}`);
    if (F.edad_min) parts.push(`Edad mín.: ${F.edad_min}`);
    if (F.edad_max) parts.push(`Edad máx.: ${F.edad_max}`);
    const szTxt = fSectorizacion?.options?.[fSectorizacion.selectedIndex]?.textContent?.trim() || "";
    const secTxt = fSector?.options?.[fSector.selectedIndex]?.textContent?.trim() || "";
    if (F.sectorizacion && szTxt) parts.push(`Sectorización: ${szTxt}`);
    if (F.sector && secTxt) parts.push(`Sector: ${secTxt}`);
    if (F.estratificacion) parts.push(`Estratificación: ${F.estratificacion}`);
    if (F.diagnostico) parts.push(`Diagnóstico: ${F.diagnostico}`);
    if (F.onlyBounds) parts.push(`Solo visible en mapa de ubicación`);
    return parts.length ? parts.join(' · ') : 'Sin filtros adicionales. Mostrando lectura territorial completa.';
  }

  function renderEpiLegend(meta = {}, F = currentFilters()) {
    const total = Number(meta.total ?? 0);
    const sectors = Number(meta.sectors ?? epiSummary.length ?? 0);
    const refs = Number(meta.with_reference_points ?? 0);
    const cronicos = epiSummary.reduce((acc, x) => acc + Number(x.con_cronicos || 0), 0);
    epiLegendTotal.textContent = total.toLocaleString('es-CL');
    epiLegendSectores.textContent = sectors.toLocaleString('es-CL');
    epiLegendRefs.textContent = refs.toLocaleString('es-CL');
    epiLegendCronicos.textContent = cronicos.toLocaleString('es-CL');
    epiLegendSubtitle.textContent = total ? 'Resumen según filtros activos' : 'Sin coincidencias con los filtros activos';
    epiLegendBadge.textContent = total ? `${sectors.toLocaleString('es-CL')} sectores activos` : 'Sin datos';
    epiLegendFilters.textContent = formatLegendFilters(F);
    refreshSectorEditorOptions();

    epiLegendTopList.innerHTML = '';
    const top = epiSummary
      .slice()
      .sort((a,b) => Number(b.total_personas||0) - Number(a.total_personas||0))
      .slice(0, 5);

    if (!top.length) {
      epiLegendTopList.innerHTML = `<div class="text-muted small">No hay sectores para resumir.</div>`;
      return;
    }

    top.forEach((item, idx) => {
      const totalSector = Number(item.total_personas || 0).toLocaleString('es-CL');
      const secName = esc(item.sector_nombre || 'Sin sector');
      const secz = esc(item.sectorizacion_nombre || 'Sin sectorización');
      const cron = Number(item.con_cronicos || 0).toLocaleString('es-CL');
      epiLegendTopList.insertAdjacentHTML('beforeend', `
        <div class="epi-top-item">
          <div class="epi-top-rank">${idx + 1}</div>
          <div class="epi-top-main">
            <div class="epi-top-name">${secName}</div>
            <div class="epi-top-meta">${secz} · Crónicos ${cron}</div>
          </div>
          <div class="epi-top-value">${totalSector}</div>
        </div>
      `);
    });
  }

  function renderEpiKPIs(meta = {}) {
    kpiEpiTotal.textContent = Number(meta.total ?? 0).toLocaleString("es-CL");
    kpiEpiSectores.textContent = Number(meta.sectors ?? epiSummary.length).toLocaleString("es-CL");
    kpiEpiRefs.textContent = Number(meta.with_reference_points ?? 0).toLocaleString("es-CL");
    lblEpiCount.textContent = `Coincidencias agregadas en ${Number(meta.sectors ?? epiSummary.length).toLocaleString("es-CL")} sectores.`;
  }

  function renderEpiMap(F = currentFilters()) {
    epiCirclesLayer.clearLayers();
    epiSectorAreasLayer.clearLayers();
    sectorIndex.clear();
    if (epiHeatLayer) {
      try { epiMap.removeLayer(epiHeatLayer); } catch {}
      epiHeatLayer = null;
    }

    if (!epiFiltersApplied) {
      epiSummaryList.innerHTML = `<div class="p-3 text-muted">Aplica filtros para pintar los sectores y ver el resumen territorial.</div>`;
      const baseBounds = renderStoredSectorAreas(new Map(), F);
      if (baseBounds.length) epiMap.fitBounds(baseBounds, { padding: [18, 18], maxZoom: 15 });
      else fitMapToPanquehue(epiMap);
      const emptyMeta = { total: 0, sectors: 0, with_reference_points: countSectorAreaReferences() };
      renderEpiKPIs(emptyMeta);
      renderEpiLegend(emptyMeta, F);
      return;
    }

    const summaryMap = new Map();
    epiSummary.forEach((item) => {
      const key = String(item.sector_id || item.sector || item.sector_nombre || '').trim();
      if (key) summaryMap.set(key, item);
    });

    if (!epiSummary.length) {
      epiSummaryList.innerHTML = `<div class="p-3 text-muted">No hay coincidencias para construir el mapa epidemiológico con los filtros actuales.</div>`;
      const baseBounds = renderStoredSectorAreas(summaryMap, F);
      if (baseBounds.length) epiMap.fitBounds(baseBounds, { padding: [18, 18], maxZoom: 15 });
      else fitMapToPanquehue(epiMap);
      const emptyMeta = { total: 0, sectors: 0, with_reference_points: countSectorAreaReferences() };
      renderEpiKPIs(emptyMeta);
      renderEpiLegend(emptyMeta, F);
      return;
    }

    const baseBounds = renderStoredSectorAreas(summaryMap, F);
    const coloredCount = epiSummary.filter((item) => summaryMap.has(String(item.sector_id || item.sector || item.sector_nombre || '').trim())).length;
    const meta = {
      total: epiSummary.reduce((acc, x) => acc + Number(x.total_personas || 0), 0),
      sectors: epiSummary.length,
      with_reference_points: countSectorAreaReferences()
    };
    renderEpiKPIs(meta);
    renderEpiLegend(meta, F);

    if (baseBounds.length) epiMap.fitBounds(baseBounds, { padding: [18, 18], maxZoom: 15 });
    else fitMapToPanquehue(epiMap);

    if (!coloredCount && epiSummaryList) {
      epiSummaryList.insertAdjacentHTML('afterbegin', `<div class="p-3 text-muted">Los filtros sí arrojaron coincidencias, pero hay sectores aún sin polígono oficial dibujado para pintar en el mapa.</div>`);
    }
  }

  function renderEpiSummaryList() {
    epiSummaryList.innerHTML = "";
    if (!epiSummary.length) {
      epiSummaryList.innerHTML = `<div class="p-3 text-muted">No hay datos agregados por sector para mostrar.</div>`;
      return;
    }

    epiSummary.forEach((item) => {
      const total = Number(item.total_personas || 0);
      const secName = item.sector_nombre || "Sin sector";
      const seczName = item.sectorizacion_nombre || "Sin sectorización";
      const key = String(item.sector_id || item.sector || secName);
      const hasRef = !!sectorRefPoint(item);

      epiSummaryList.insertAdjacentHTML("beforeend", `
        <div class="epi-item">
          <div>
            <div class="name">${esc(secName)}</div>
            <div class="meta">${esc(seczName)} · Coincidencias: ${esc(String(total))} · Con crónicos: ${esc(String(item.con_cronicos || 0))} · G0: ${esc(String(item.g0 || 0))} · G1: ${esc(String(item.g1 || 0))} · G2: ${esc(String(item.g2 || 0))} · G3: ${esc(String(item.g3 || 0))}</div>
          </div>
          <div class="epi-badges">
            <span class="badge text-bg-light">Inscritos ${esc(String(item.inscritos || 0))}</span>
            <span class="badge text-bg-light">Migrantes ${esc(String(item.migrantes || 0))}</span>
            <span class="badge ${hasRef ? "text-bg-success" : "text-bg-secondary"}">${hasRef ? "Con referencia" : "Sin referencia"}</span>
            <button class="btn-mini" type="button" data-epi-center="${esc(key)}" title="Ver sector en el mapa epidemiológico">
              <i class="bi bi-crosshair"></i>
            </button>
          </div>
        </div>
      `);
    });
  }

  async function loadEpiSummary(F) {
    const r = await fetchJSON(API_GEO, {
      method: "POST",
      body: JSON.stringify({
        mode: "sector_summary",
        ...buildFilterPayload(F)
      })
    });
    epiSummary = Array.isArray(r.summary) ? r.summary : [];
    renderEpiMap(F);
    renderEpiSummaryList();
    refreshSectorEditorOptions();
  }

  async function applyFilters(markEpiApplied = false) {
    const F = currentFilters();
    const LF = currentLocationFilters();
    filtered = geo.filter((p) => personMatchesLocation(p, LF));
    renderKPIs();
    renderMarkers();
    renderList();

    if (markEpiApplied) epiFiltersApplied = true;

    try {
      if (epiFiltersApplied) await loadEpiSummary(F);
      else { renderEpiMap(F); renderEpiSummaryList(); }
    } catch (e) {
      epiSummary = [];
      if (isElementVisible(viewEpidemiologico)) renderEpiMap(F);
      renderEpiSummaryList();
    }
  }

  const applyDebounced = debounce(() => {
    syncMobileFromDesktop();
    epiFiltersApplied = false;
    applyFilters(false);
  }, 280);

  q?.addEventListener("keydown", (e) => {
    if (e.key === "Enter") {
      e.preventDefault();
      applyFilters(false);
    }
  });
  fInscrito?.addEventListener("change", applyDebounced);
  fMigrante?.addEventListener("change", applyDebounced);
  fEstratificacion?.addEventListener("change", applyDebounced);
  fGenero?.addEventListener("change", applyDebounced);
  fEdadMin?.addEventListener("input", applyDebounced);
  fEdadMax?.addEventListener("input", applyDebounced);
  fPercapitado?.addEventListener("change", applyDebounced);
  fSectorizacion?.addEventListener("change", () => { onSectorizacionChanged("desktop", { apply: true }).catch(() => {}); });
  fSectorizacionM?.addEventListener("change", () => { onSectorizacionChanged("mobile", { apply: false }).catch(() => {}); });
  fSector?.addEventListener("change", applyDebounced);
  onlyBounds?.addEventListener("change", applyDebounced);

  btnClearQ?.addEventListener("click", () => { q.value = ""; applyFilters(false); });
  btnFiltrarUbicacion?.addEventListener("click", () => { applyFilters(false); });
  btnClearDiagnosisPanel?.addEventListener("click", () => {
    setDiagnosisSelectValues(diagnosticoSelectDesktop, [], true);
    setDiagnosisSelectValues(diagnosticoSelectMobile, [], true);
    epiFiltersApplied = false;
    renderSelectedDiagnosesPanel([]);
    applyDebounced();
  });
  btnAplicar.addEventListener("click", () => { syncMobileFromDesktop(); applyFilters(true); });
  btnReset.addEventListener("click", async () => {
    if (fInscrito) fInscrito.value = "";
    if (fMigrante) fMigrante.value = "";
    if (fEstratificacion) fEstratificacion.value = "";
    if (fGenero) fGenero.value = "";
    if (fEdadMin) fEdadMin.value = "";
    if (fEdadMax) fEdadMax.value = "";
    if (fPercapitado) fPercapitado.value = "";
    setDiagnosisSelectValues(diagnosticoSelectDesktop, [], true);
    renderSelectedDiagnosesPanel([]);
    if (fSectorizacion) fSectorizacion.value = "";
    try { await refreshSectorizacionAndSectoresUI({ keepSector: false }); } catch (e) {}
    if (fSector) fSector.value = "";
    if (onlyBounds) onlyBounds.checked = false;
    syncMobileFromDesktop();
    applyFilters(true);
  });

  btnClearQM.addEventListener("click", () => { qM.value = ""; });
  btnAplicarM.addEventListener("click", () => {
    q.value = qM.value;
    fInscrito.value = fInscritoM.value;
    fMigrante.value = fMigranteM.value;
    fSectorizacion.value = fSectorizacionM.value;
    fSector.value = fSectorM.value;
    fEstratificacion.value = fEstratificacionM.value;
    const mobileDiagnosisValues = getDiagnosisSelectValues(diagnosticoSelectMobile);
    setDiagnosisSelectValues(diagnosticoSelectDesktop, mobileDiagnosisValues, true);
    renderSelectedDiagnosesPanel(mobileDiagnosisValues);
    onlyBounds.checked = onlyBoundsM.checked;
    applyFilters(false);
  });

  btnResetM.addEventListener("click", async () => {
    qM.value = "";
    fInscritoM.value = "";
    fMigranteM.value = "";
    fEstratificacionM.value = "";
    setDiagnosisSelectValues(diagnosticoSelectMobile, [], true);
    renderSelectedDiagnosesPanel([]);
    fSectorizacionM.value = "";
    try { await refreshSectorizacionAndSectoresUI({ keepSector: false }); } catch (e) {}
    fSectorM.value = "";
    onlyBoundsM.checked = false;
    syncDesktopFromMobile();
    epiFiltersApplied = false;
    applyFilters(false);
  });

  map.on("moveend", () => {
    if (onlyBounds?.checked) applyFilters();
  });

  btnRecentrar.addEventListener("click", recenterActiveMap);
  btnCargarLocal.addEventListener("click", async () => {
    const ok = await syncGeoFromBackend();
    if (ok) {
      loadAndPrepare();
      applyFilters(epiFiltersApplied);
    }
  });



  btnViewUbicacion.addEventListener("click", () => setActiveView("ubicacion"));
  btnViewEpi.addEventListener("click", () => setActiveView("epidemiologico"));
  btnToggleEpiLegend?.addEventListener("click", () => {
    epiLegend?.classList.toggle("is-collapsed");
    const hidden = epiLegend?.classList.contains("is-collapsed");
    btnToggleEpiLegend.innerHTML = hidden
      ? '<i class="bi bi-layout-sidebar-inset"></i>'
      : '<i class="bi bi-layout-text-sidebar-reverse"></i>';
  });

  document.addEventListener("click", (e) => {
    const diagBtn = e.target.closest("[data-remove-diagnosis-index]");
    if (diagBtn) {
      e.preventDefault();
      removeSelectedDiagnosisAt(Number(diagBtn.dataset.removeDiagnosisIndex));
      return;
    }

    const btn = e.target.closest("[data-act]");
    if (btn) {
      const act = btn.dataset.act;
      const rut = btn.dataset.rut;
      const m = markerIndex.get(rut);
      if (!m) return;
      if (act === "zoom") focusMarker(m, { openPopup: false });
      if (act === "popup") focusMarker(m, { openPopup: true });
      return;
    }

    const epiBtn = e.target.closest("[data-epi-center]");
    if (epiBtn) {
      const key = epiBtn.dataset.epiCenter;
      const item = sectorIndex.get(key);
      if (!item) return;
      setActiveView("epidemiologico");
      epiMap.setView(item.latlng, Math.max(epiMap.getZoom(), 15), { animate: true });
      setTimeout(() => item.layer.openPopup(), 220);
    }
  });

  (async () => {
    try {
      await loadSectorizaciones();
      await refreshSectorizacionAndSectoresUI({ keepSector: true });
    } catch (e) {}

    await loadDiagnosticosCatalog();
    syncMobileFromDesktop();

    await loadSectorAreasConfig();
    refreshSectorEditorOptions();
    renderStoredSectorAreas();

    const ok = await syncGeoFromBackend({ silent: true });
    if (!ok) seedGeoDemoIfEmpty();

    loadAndPrepare();
    renderSelectedDiagnosesPanel();
    setActiveView("ubicacion");
    applyFilters(false);
  })();
})();
