(() => {
  const $ = (id) => document.getElementById(id);

  /* =========================
     CONFIG API
  ========================= */
  const API = {
    list: "../php/personas/list.php",
    get: "../php/personas/get.php",
    save: "../php/personas/save.php",
    del: "../php/personas/delete.php",
    import: "../php/personas/import_estratificacion.php",
    importPadron: "../php/personas/import_padron.php",
    importPercapita: "../php/personas/import_percapita.php",
    stats: "../php/personas/stats.php",
    sectorizacion: "../php/config/sectorizacion/list.php",
    sectores: "../php/config/sectores/list.php"
  };

  /* =========================
     UI refs
  ========================= */
  const tbody = $("tbodyPersonas");
  const cards = $("cardsPersonas");
  const lblCount = $("lblCount");
  const paginationSummary = $("paginationSummary");
  const paginationNav = $("paginationNav");

  const btnMapa = $("btnMapa"); // botón abrir mapa (manual)

  const kpiTotal = $("kpiTotal");
  const kpiInscritos = $("kpiInscritos");
  const kpiNoInscritos = $("kpiNoInscritos");
  const kpiPercapitados = $("kpiPercapitados");
  const kpiNoPercapitados = $("kpiNoPercapitados");

  const q = $("q");
  const btnClearQ = $("btnClearQ");
  const fInscrito = $("fInscrito");
  const fMigrante = $("fMigrante");
  const fSectorizacion = $("fSectorizacion");
  const fSector = $("fSector");
  const fEstratificacion = $("fEstratificacion");
  const fPercapitado = $("fPercapitado");

  const btnNuevaPersona = $("btnNuevaPersona");
  const btnImportEstratificacion = $("btnImportEstratificacion");
  const btnImportPadron = $("btnImportPadron");
  const btnImportPercapita = $("btnImportPercapita");

  // Modal persona
  const modalEl = $("personaModal");
  const modal = modalEl ? new bootstrap.Modal(modalEl) : null;
  const modalTitle = $("modalTitle");
  const btnGuardar = $("btnGuardar");

  const importModalEl = $("importEstratificacionModal");
  const importModal = importModalEl ? new bootstrap.Modal(importModalEl) : null;
  const excelEstratificacion = $("excelEstratificacion");
  const btnProcesarImportEstratificacion = $("btnProcesarImportEstratificacion");
  const importResumenEstratificacion = $("importResumenEstratificacion");

  const importPadronModalEl = $("importPadronModal");
  const importPadronModal = importPadronModalEl ? new bootstrap.Modal(importPadronModalEl) : null;
  const excelPadron = $("excelPadron");
  const btnProcesarImportPadron = $("btnProcesarImportPadron");
  const importResumenPadron = $("importResumenPadron");

  const importPercapitaModalEl = $("importPercapitaModal");
  const importPercapitaModal = importPercapitaModalEl ? new bootstrap.Modal(importPercapitaModalEl) : null;
  const csvPercapita = $("csvPercapita");
  const btnProcesarImportPercapita = $("btnProcesarImportPercapita");
  const importResumenPercapita = $("importResumenPercapita");

  // Fields
  const rut = $("rut");
  const nombres = $("nombres");
  const apellidos = $("apellidos");
  const fecha_nacimiento = $("fecha_nacimiento");
  const edad = $("edad");

  let genero = $("genero");
  let estado_civil = $("estado_civil");
  let escolaridad = $("escolaridad");
  let profesion = $("profesion");
  let prevision = $("prevision");
  let nacionalidad = $("nacionalidad");
  let pueblo_originario = $("pueblo_originario");
  let parentezco = $("parentezco");
  let participacion_ciudadana = $("participacion_ciudadana");

  const estratificacion_riesgo = $("estratificacion_riesgo");
  const cantidad_condiciones_cronicas = $("cantidad_condiciones_cronicas");
  const fecha_actualizacion_estratificacion = $("fecha_actualizacion_estratificacion");
  const detalle_diagnosticos_cronicos = $("detalle_diagnosticos_cronicos");

  const direccion = $("direccion");
  const celular = $("celular");
  const telefonos = $("telefonos");

  const sectorizacion = $("sectorizacion");
  const sector = $("sector");
  const familia = $("familia");

  const latitud = $("latitud");
  const longitud = $("longitud");
  const btnGPS = $("btnGPS");
  const gpsLbl = $("gpsLbl");

  // checkbox inscrito + select migrante
  const inscrito_cesfam = $("inscrito_cesfam");
  const percapitado = $("percapitado");
  const migrante = $("migrante");

  const PAGE_SIZE = 200;

  let data = [];
  let editingRut = null;
  let currentPage = 1;
  let totalRows = 0;
  let totalPages = 1;

  /* =========================
     HELPERS
  ========================= */
  function normalizeRut(s) {
  return (s || "")
    .toUpperCase()
    .replace(/[^0-9K]/g, "")   // quita puntos, guion, espacios, etc.
    .slice(0, 15);
}


  function esc(s) {
    return String(s ?? "")
      .replaceAll("&", "&amp;")
      .replaceAll("<", "&lt;")
      .replaceAll(">", "&gt;")
      .replaceAll('"', "&quot;")
      .replaceAll("'", "&#039;");
  }


  function debounce(fn, delay = 350) {
    let timer = null;
    return (...args) => {
      clearTimeout(timer);
      timer = setTimeout(() => fn(...args), delay);
    };
  }

  function calcEdad(fecha) {
    if (!fecha) return "";
    const f = new Date(fecha);
    const hoy = new Date();
    let e = hoy.getFullYear() - f.getFullYear();
    const m = hoy.getMonth() - f.getMonth();
    if (m < 0 || (m === 0 && hoy.getDate() < f.getDate())) e--;
    return e >= 0 ? e : "";
  }

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

  function countDiagnosticos(detalle) {
    const txt = String(detalle || "").trim();
    if (!txt) return 0;
    return txt.split('|').map(s => s.trim()).filter(Boolean).length;
  }

  function normalizeEstratificacion(value) {
    const v = String(value || "").trim().toUpperCase();
    return ['G0', 'G1', 'G2', 'G3'].includes(v) ? v : 'G0';
  }

  function normalizeCronicos(persona) {
    const raw = persona?.cantidad_condiciones_cronicas;
    if (raw !== null && raw !== undefined && String(raw).trim() !== '') {
      const n = Number(raw);
      if (Number.isFinite(n)) return n;
    }
    return countDiagnosticos(persona?.detalle_diagnosticos_cronicos);
  }

  function estratBadgeHtml(value) {
    const estra = normalizeEstratificacion(value);
    const klass = ({ G0:'badge-estrat-g0', G1:'badge-estrat-g1', G2:'badge-estrat-g2', G3:'badge-estrat-g3' })[estra] || 'badge-estrat-sd';
    return `<span class="badge-estrat ${klass}">${esc(estra)}</span>`;
  }

  async function fetchJSON(url, opts = {}) {
    const res = await fetch(url, {
      credentials: "same-origin",
      headers: { "Content-Type": "application/json" },
      ...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 makeSelect(id, options, placeholder = "Seleccione...") {
    const el = document.getElementById(id);
    if (!el) return null;

    const select = document.createElement("select");
    select.id = el.id;
    select.className = el.className;

    select.innerHTML =
      `<option value="">${placeholder}</option>` +
      options.map(o => `<option value="${o}">${o}</option>`).join("");

    el.replaceWith(select);
    return select;
  }

  /* =========================
     MAPS: Sectorización/Sector (NOMBRES)
  ========================= */
  let cacheSectores = [];
  let mapSectorizacion = {};
  let mapSectores = {};

  async function loadFiltroSectorizacion() {
    const r = await fetchJSON(API.sectorizacion);

    mapSectorizacion = {};
    fSectorizacion.innerHTML = `<option value="">Sectorización</option>`;

    (r.data || []).forEach(s => {
      mapSectorizacion[String(s.id)] = s.nombre_sectorizacion;
      fSectorizacion.insertAdjacentHTML(
        "beforeend",
        `<option value="${s.id}">${s.nombre_sectorizacion}</option>`
      );
    });
  }

  async function loadFiltroSectores(sectorizacionId = "") {
    if (!cacheSectores.length) {
      const r = await fetchJSON(API.sectores);
      cacheSectores = r.data || [];
      mapSectores = {};
      cacheSectores.forEach(s => (mapSectores[String(s.id)] = s.nombre_sector));
    }

    fSector.innerHTML = `<option value="">Sector</option>`;

    cacheSectores
      .filter(s => !sectorizacionId || String(s.sectorizacion) === String(sectorizacionId))
      .forEach(s => {
        fSector.insertAdjacentHTML(
          "beforeend",
          `<option value="${s.id}">${s.nombre_sector}</option>`
        );
      });
  }

  /* =========================
     INPUT → SELECT (APS)
  ========================= */
  genero = makeSelect("genero", ["Masculino", "Femenino"]);
  estado_civil = makeSelect("estado_civil", [
    "Soltero(a)",
    "Divorciado(a)",
    "Conviviente Civil",
    "Casado(a)",
    "Viudo(a)",
    "Separado(a) Judicialmente",
    "Desconocido"
  ]);

  escolaridad = makeSelect("escolaridad", [
    "No Informado",
    "Analfabeto",
    "Sin instrucción",
    "Prebásica",
    "Básica",
    "Media",
    "Técnico de nivel superior",
    "Profesional Universitario",
    "Enseñanza Superior Incompleta",
    "Postgrado",
    "Lactante",
    "Educación Diferencial",
    "Sala Cuna",
    "No Recuerda",
    "No Responde"
  ]);

  profesion = makeSelect("profesion", [
    "Agricultura, caza, pesca y silvicultura",
    "Explotación de minas y canteras",
    "Industria manufacturera",
    "Electricidad, gas y agua",
    "Construcción",
    "Comercio mayor y menor, restaurantes y hoteles",
    "Transporte y comunicaciones",
    "Establecimientos financieros y seguros",
    "Servicios comunales y sociales",
    "Otros",
    "Ninguna"
  ]);

  prevision = makeSelect("prevision", [
    "Fonasa",
    "Banmédica S.A.",
    "Capredena",
    "Chuquicamata Ltda.",
    "Chuquicamata Ltda.",
    "Colmena Golden Cross S.A.",
    "Consalud S.A.",
    "Cruz Blanca S.A.",
    "Isapre Fundación",
    "MasVida S.A."
  ]);

  nacionalidad = makeSelect("nacionalidad", [
    "Chilena",
    "Argentina",
    "Peruana",
    "Boliviana",
    "Colombiana",
    "Venezolana",
    "Brasileña",
    "Ecuatoriana",
    "Otra"
  ]);

  pueblo_originario = makeSelect("pueblo_originario", [
    "No Pertenece",
    "Mapuche",
    "Aymara",
    "Rapa Nui o Pascuense",
    "Quechua",
    "Colla",
    "Diaguita",
    "Kawésqar",
    "Yagán",
    "Chango",
    "Otro"
  ]);

  parentezco = makeSelect("parentezco", [
    "La misma persona",
    "Jefe/a de hogar",
    "Hijo/a",
    "Cónyuge o Pareja",
    "Hermano/a",
    "Padre",
    "Madre",
    "Abuelo/a",
    "Nieto/a",
    "Suegro/a",
    "Cuñado/a",
    "Sobrino/a",
    "Cuidador/a",
    "No Pariente",
    "Otro Pariente"
  ]);

  participacion_ciudadana = makeSelect(
    "participacion_ciudadana",
    [
      "Club Deportivo",
      "Club Adulto Mayor",
      "Centro de Madres",
      "JJ.VV",
      "Club de Pesca y Caza",
      "CODELO",
      "Otros",
      "No Participa"
    ],
    "Seleccione (puede elegir varios)"
  );

  if (participacion_ciudadana) {
    participacion_ciudadana.multiple = true;
    participacion_ciudadana.size = 5;
  }

  /* =========================
     SECTORIZACIÓN / SECTOR (MODAL)
  ========================= */
  async function loadSectorizacion() {
    const r = await fetchJSON(API.sectorizacion);
    sectorizacion.innerHTML = `<option value="">Seleccione...</option>`;
    (r.data || []).forEach(s => {
      sectorizacion.insertAdjacentHTML(
        "beforeend",
        `<option value="${s.id}">${s.nombre_sectorizacion}</option>`
      );
    });
  }

  async function loadSectores() {
    const r = await fetchJSON(API.sectores);
    sector.innerHTML = `<option value="">Seleccione...</option>`;
    (r.data || []).forEach(s => {
      sector.insertAdjacentHTML(
        "beforeend",
        `<option value="${s.id}">${s.nombre_sector}</option>`
      );
    });
  }

  async function loadSectoresModal(sectorizacionId = "") {
    if (!cacheSectores.length) {
      const r = await fetchJSON(API.sectores);
      cacheSectores = r.data || [];
      mapSectores = {};
      cacheSectores.forEach(s => (mapSectores[String(s.id)] = s.nombre_sector));
    }

    sector.innerHTML = `<option value="">Seleccione...</option>`;

    cacheSectores
      .filter(s => !sectorizacionId || String(s.sectorizacion) === String(sectorizacionId))
      .forEach(s => {
        sector.insertAdjacentHTML(
          "beforeend",
          `<option value="${s.id}">${s.nombre_sector}</option>`
        );
      });
  }

  /* =========================
     KPIs + LISTADO
  ========================= */
  async function loadStats() {
    const r = await fetchJSON(API.stats);
    kpiTotal.textContent = r.data.total;
    kpiInscritos.textContent = r.data.inscritos;
    if (kpiNoInscritos) kpiNoInscritos.textContent = r.data.no_inscritos;
    if (kpiPercapitados) kpiPercapitados.textContent = r.data.percapitados ?? "—";
    if (kpiNoPercapitados) kpiNoPercapitados.textContent = r.data.no_percapitados ?? "—";
  }

  function buildPaginationItems(current, total) {
    const items = [];
    const addRange = (from, to) => {
      for (let i = from; i <= to; i++) items.push(i);
    };

    if (total <= 7) {
      addRange(1, total);
      return items;
    }

    items.push(1);

    let start = Math.max(2, current - 1);
    let end = Math.min(total - 1, current + 1);

    if (current <= 3) {
      start = 2;
      end = 4;
    }

    if (current >= total - 2) {
      start = total - 3;
      end = total - 1;
    }

    if (start > 2) items.push('ellipsis-left');
    addRange(start, end);
    if (end < total - 1) items.push('ellipsis-right');

    items.push(total);
    return items;
  }

  function renderPagination() {
    if (!paginationNav || !paginationSummary) return;

    paginationNav.innerHTML = "";

    if (!totalRows) {
      paginationSummary.textContent = `Página 1 de 1 · ${PAGE_SIZE} por página`;
      paginationNav.innerHTML = `
        <li class="page-item disabled"><span class="page-link">Anterior</span></li>
        <li class="page-item active"><span class="page-link">1</span></li>
        <li class="page-item disabled"><span class="page-link">Siguiente</span></li>
      `;
      return;
    }

    paginationSummary.textContent = `Página ${currentPage} de ${totalPages} · ${PAGE_SIZE} registros por página`;

    const items = buildPaginationItems(currentPage, totalPages);
    const parts = [];

    parts.push(`
      <li class="page-item ${currentPage <= 1 ? 'disabled' : ''}">
        <button class="page-link" type="button" data-page="${currentPage - 1}">Anterior</button>
      </li>
    `);

    items.forEach(item => {
      if (typeof item === "string") {
        parts.push(`<li class="page-item disabled"><span class="page-link">…</span></li>`);
        return;
      }

      parts.push(`
        <li class="page-item ${item === currentPage ? 'active' : ''}">
          <button class="page-link" type="button" data-page="${item}">${item}</button>
        </li>
      `);
    });

    parts.push(`
      <li class="page-item ${currentPage >= totalPages ? 'disabled' : ''}">
        <button class="page-link" type="button" data-page="${currentPage + 1}">Siguiente</button>
      </li>
    `);

    paginationNav.innerHTML = parts.join("");
  }

  async function loadList({ resetPage = false } = {}) {
    if (resetPage) currentPage = 1;

    const params = {
      q: q.value || "",
      inscrito_cesfam: fInscrito.value,
      migrante: fMigrante.value,
      sectorizacion: fSectorizacion.value,
      sector: fSector.value,
      estratificacion_riesgo: fEstratificacion?.value || "",
      percapitado: fPercapitado?.value || "",
      page: currentPage,
      limit: PAGE_SIZE
    };

    const r = await fetchJSON(API.list, {
      method: "POST",
      body: JSON.stringify(params)
    });

    const meta = r.meta || {};
    data = r.data || [];
    currentPage = Number(meta.page || 1);
    totalRows = Number(meta.total || 0);
    totalPages = Math.max(Number(meta.total_pages || 1), 1);

    render();
    renderPagination();

    if (!totalRows) {
      lblCount.textContent = "Sin resultados";
      return;
    }

    lblCount.textContent = `Mostrando ${meta.from || 0} a ${meta.to || 0} de ${totalRows}`;
  }

  /* =========================
     RENDER
  ========================= */
  function render() {
    tbody.innerHTML = "";
    cards.innerHTML = "";

    if (!data.length) {
      tbody.innerHTML = `<tr><td colspan="9" class="text-muted">Sin resultados</td></tr>`;
      cards.innerHTML = `<div class="person-card text-muted">Sin resultados</div>`;
      return;
    }

    data.forEach(p => {
      const badge = p.inscrito_cesfam == 1
        ? `<span class="badge text-bg-success">Sí</span>`
        : `<span class="badge text-bg-warning">No</span>`;

      const badgePercapita = p.percapitado == 1
        ? `<span class="badge text-bg-success">Sí</span>`
        : `<span class="badge text-bg-secondary">No</span>`;

      const nombreSectorizacion = mapSectorizacion[String(p.sectorizacion)] || "—";
      const nombreSector = mapSectores[String(p.sector)] || "—";
      const estra = normalizeEstratificacion(p.estratificacion_riesgo);
      const cronicos = normalizeCronicos(p);

      tbody.insertAdjacentHTML("beforeend", `
        <tr>
          <td><b>${esc(p.rut)}</b></td>
          <td>${esc(p.nombres)} ${esc(p.apellidos)}</td>
          <td>${p.edad ?? "—"}</td>
          <td>${badge}</td>
          <td>${badgePercapita}</td>
          <td class="td-estrat">${estratBadgeHtml(estra)}</td>
          <td class="td-cronicos">${cronicos}</td>
          <td>${esc(nombreSectorizacion + " / " + nombreSector)}</td>
          <td class="text-end">
            <button class="btn btn-sm btn-outline-primary" data-edit="${p.rut}">
              <i class="bi bi-pencil"></i>
            </button>
            <button class="btn btn-sm btn-outline-danger" data-del="${p.rut}">
              <i class="bi bi-trash"></i>
            </button>
          </td>
        </tr>
      `);

      cards.insertAdjacentHTML("beforeend", `
        <div class="person-card">
          <div class="t">${esc(p.nombres)} ${esc(p.apellidos)}</div>
          <div class="m">${esc(p.rut)} · Edad ${p.edad ?? "—"} · ${esc(estra)}</div>
          <div class="m">Crónicos: ${cronicos} · Percápita: ${p.percapitado == 1 ? "Sí" : "No"} · Sector ${esc(nombreSectorizacion + " / " + nombreSector)}</div>
          <div class="d-flex gap-2 mt-2">
            <button class="btn btn-sm btn-outline-primary" data-edit="${p.rut}">Editar</button>
            <button class="btn btn-sm btn-outline-danger" data-del="${p.rut}">Eliminar</button>
          </div>
        </div>
      `);
    });
  }

  /* =========================
     MODAL
  ========================= */
  function clearForm() {
    editingRut = null;
    if (modalTitle) modalTitle.textContent = "Nueva persona";

    modalEl?.querySelectorAll("input, textarea, select").forEach(i => {
      if (i.type === "checkbox") i.checked = false;
      else i.value = "";
    });

    if (participacion_ciudadana?.options) {
      Array.from(participacion_ciudadana.options).forEach(o => (o.selected = false));
    }

    if (percapitado) percapitado.checked = true;
    if (gpsLbl) gpsLbl.textContent = "—";
    if (estratificacion_riesgo) estratificacion_riesgo.value = "";
    if (cantidad_condiciones_cronicas) cantidad_condiciones_cronicas.value = "";
    if (fecha_actualizacion_estratificacion) fecha_actualizacion_estratificacion.value = "";
    if (detalle_diagnosticos_cronicos) detalle_diagnosticos_cronicos.value = "";
  }

  async function openEdit(r) {
    const res = await fetchJSON(`${API.get}?rut=${encodeURIComponent(r)}`);
    const p = res.data;

    editingRut = p.rut;
    if (modalTitle) modalTitle.textContent = `Editar persona · ${p.rut}`;

    rut.value = p.rut;
    nombres.value = p.nombres || "";
    apellidos.value = p.apellidos || "";
    fecha_nacimiento.value = p.fecha_nacimiento || "";
    edad.value = p.edad ?? "";

    genero.value = p.genero || "";
    estado_civil.value = p.estado_civil || "";
    escolaridad.value = p.escolaridad || "";
    profesion.value = p.profesion || "";
    prevision.value = p.prevision || "";

    inscrito_cesfam.checked = p.inscrito_cesfam == 1;
    if (percapitado) percapitado.checked = p.percapitado != 0;
    migrante.value = p.migrante ?? "";
    nacionalidad.value = p.nacionalidad || "";
    pueblo_originario.value = p.pueblo_originario || "";

    direccion.value = p.direccion || "";
    celular.value = p.celular || "";
    telefonos.value = p.telefonos || "";

    sectorizacion.value = p.sectorizacion ?? "";
    await loadSectoresModal(sectorizacion.value);
    sector.value = p.sector ?? "";

    familia.value = p.familia ?? "";
    parentezco.value = p.parentezco || "";

    if (participacion_ciudadana?.options) {
      Array.from(participacion_ciudadana.options).forEach(o => (o.selected = false));
      if (p.participacion_ciudadana) {
        const selected = String(p.participacion_ciudadana).split(" | ");
        Array.from(participacion_ciudadana.options).forEach(o => {
          if (selected.includes(o.value)) o.selected = true;
        });
      }
    }

    latitud.value = p.latitud ?? "";
    longitud.value = p.longitud ?? "";

    if (gpsLbl) {
      gpsLbl.textContent = p.latitud && p.longitud ? `GPS: ${p.latitud}, ${p.longitud}` : "—";
    }

    if (estratificacion_riesgo) estratificacion_riesgo.value = p.estratificacion_riesgo || "";
    if (cantidad_condiciones_cronicas) cantidad_condiciones_cronicas.value = p.cantidad_condiciones_cronicas ?? "";
    if (fecha_actualizacion_estratificacion) fecha_actualizacion_estratificacion.value = p.fecha_actualizacion_estratificacion || "";
    if (detalle_diagnosticos_cronicos) detalle_diagnosticos_cronicos.value = p.detalle_diagnosticos_cronicos || "";

    modal?.show();
  }

  async function savePersona() {
    const payload = {
      rut: normalizeRut(rut.value),
      nombres: nombres.value,
      apellidos: apellidos.value,
      fecha_nacimiento: fecha_nacimiento.value,
      edad: edad.value,
      genero: genero.value,
      estado_civil: estado_civil.value,
      escolaridad: escolaridad.value,
      profesion: profesion.value,
      prevision: prevision.value,
      inscrito_cesfam: inscrito_cesfam.checked ? 1 : 0,
      percapitado: percapitado?.checked ? 1 : 0,
      migrante: migrante.value,
      nacionalidad: nacionalidad.value,
      pueblo_originario: pueblo_originario.value,
      direccion: direccion.value,
      celular: celular.value,
      telefonos: telefonos.value,
      sectorizacion: sectorizacion.value,
      sector: sector.value,
      familia: familia.value,
      parentezco: parentezco.value,
      participacion_ciudadana: participacion_ciudadana
        ? Array.from(participacion_ciudadana.selectedOptions).map(o => o.value).join(" | ")
        : "",
      estratificacion_riesgo: estratificacion_riesgo?.value || "",
      cantidad_condiciones_cronicas: cantidad_condiciones_cronicas?.value || "",
      fecha_actualizacion_estratificacion: fecha_actualizacion_estratificacion?.value || "",
      detalle_diagnosticos_cronicos: detalle_diagnosticos_cronicos?.value || "",
      latitud: latitud.value,
      longitud: longitud.value
    };

    await fetchJSON(API.save, {
      method: "POST",
      body: JSON.stringify(payload)
    });

    modal?.hide();
    await loadStats();
    await loadList();

    Swal.fire({
      icon: "success",
      title: "Persona guardada correctamente",
      timer: 1200,
      showConfirmButton: false
    });
  }

  async function removePersona(r) {
    const ok = await Swal.fire({
      icon: "warning",
      title: "Eliminar persona",
      text: `¿Eliminar ${r}?`,
      showCancelButton: true,
      confirmButtonText: "Sí, eliminar"
    });
    if (!ok.isConfirmed) return;

    await fetchJSON(API.del, {
      method: "POST",
      body: JSON.stringify({ rut: r })
    });

    await loadStats();
    await loadList();
  }

  /* =========================
     EVENTOS
  ========================= */
  btnNuevaPersona?.addEventListener("click", async () => {
    clearForm();
    await loadSectoresModal("");
    modal?.show();
  });

  btnGuardar?.addEventListener("click", savePersona);

  btnImportEstratificacion?.addEventListener("click", () => {
    if (importResumenEstratificacion) importResumenEstratificacion.innerHTML = "";
    if (excelEstratificacion) excelEstratificacion.value = "";
    importModal?.show();
  });

  btnImportPadron?.addEventListener("click", () => {
    if (importResumenPadron) importResumenPadron.innerHTML = "";
    if (excelPadron) excelPadron.value = "";
    importPadronModal?.show();
  });

  btnImportPercapita?.addEventListener("click", () => {
    if (importResumenPercapita) importResumenPercapita.innerHTML = "";
    if (csvPercapita) csvPercapita.value = "";
    importPercapitaModal?.show();
  });

  btnProcesarImportEstratificacion?.addEventListener("click", importarEstratificacionDesdeExcel);
  btnProcesarImportPadron?.addEventListener("click", importarPadronDesdeExcel);
  btnProcesarImportPercapita?.addEventListener("click", importarPercapitaDesdeCsv);

  const debouncedSearch = debounce(() => loadList({ resetPage: true }));

  btnClearQ?.addEventListener("click", () => {
    q.value = "";
    loadList({ resetPage: true });
  });

  document.addEventListener("click", e => {
    const ed = e.target.closest("[data-edit]");
    const dl = e.target.closest("[data-del]");
    if (ed) openEdit(ed.dataset.edit);
    if (dl) removePersona(dl.dataset.del);
  });

  paginationNav?.addEventListener("click", e => {
    const btn = e.target.closest("[data-page]");
    if (!btn || btn.closest(".disabled") || btn.closest(".active")) return;

    const page = Number(btn.dataset.page || 0);
    if (!page || page < 1 || page > totalPages || page === currentPage) return;

    currentPage = page;
    loadList();
  });

  q?.addEventListener("input", debouncedSearch);
  q?.addEventListener("search", debouncedSearch);

  [fInscrito, fMigrante, fSector, fEstratificacion, fPercapitado].forEach(el => {
    el?.addEventListener("change", () => loadList({ resetPage: true }));
  });

  fecha_nacimiento?.addEventListener("change", () => {
    edad.value = calcEdad(fecha_nacimiento.value);
  });

  btnGPS?.addEventListener("click", () => {
    navigator.geolocation.getCurrentPosition(
      pos => {
        latitud.value = Number(pos.coords.latitude).toFixed(6);
        longitud.value = Number(pos.coords.longitude).toFixed(6);
        if (gpsLbl) gpsLbl.textContent = `GPS: ${latitud.value}, ${longitud.value}`;
      },
      () => {
        Swal.fire({
          icon: "warning",
          title: "No se pudo capturar GPS",
          text: "Puedes usar la opción 'Ubicar en mapa' para seleccionar manualmente."
        });
      },
      { enableHighAccuracy: true, timeout: 8000 }
    );
  });

  fSectorizacion?.addEventListener("change", async () => {
    await loadFiltroSectores(fSectorizacion.value);
    if (fSector) fSector.value = "";
    loadList({ resetPage: true });
  });

  sectorizacion?.addEventListener("change", () => {
    loadSectoresModal(sectorizacion.value);
  });


  /* =========================
    MAPA MANUAL (Leaflet) ✅ FINAL
 ========================= */
  const mapaModalEl = $("mapaModal");
  const btnUsarUbicacion = $("btnUsarUbicacion");
  const mapEl = $("map");

  let pickMap = null;
  let pickMarker = null;
  let selectedLatLng = null;

  const PANQUEHUE_CENTER = [-32.7800, -70.8450];

  function ensureLeafletFixStyles() {
    if (document.getElementById("leafletFixFP")) return;
    const st = document.createElement("style");
    st.id = "leafletFixFP";
    st.textContent = `
    /* FIX: reglas globales tipo img{max-width:100%} rompen tiles */
    .leaflet-container img,
    .leaflet-container img.leaflet-tile{
      max-width: none !important;
      max-height: none !important;
      width: auto !important;
      height: auto !important;
    }
  `;
    document.head.appendChild(st);
  }

  function ensureMapSizing() {
    if (!mapEl) return;
    mapEl.style.width = "100%";
    mapEl.style.height = "60vh";
    mapEl.style.minHeight = "460px";
    mapEl.style.borderRadius = "14px";
    mapEl.style.overflow = "hidden";
    mapEl.style.background = "rgba(255,255,255,.6)";
  }

  function pickerIcon() {
    // marcador visible sin PNG (evita bloqueos de unpkg icon)
    return L.divIcon({
      className: "",
      html: `<div style="
      width:18px;height:18px;border-radius:999px;
      background:#0d6efd;
      border:3px solid rgba(255,255,255,.95);
      box-shadow:0 10px 18px rgba(0,0,0,.22);
    "></div>`,
      iconSize: [18, 18],
      iconAnchor: [9, 9]
    });
  }

  function updateMarkerTooltip() {
    if (!pickMarker || !selectedLatLng) return;
    const t = `${Number(selectedLatLng.lat).toFixed(6)}, ${Number(selectedLatLng.lng).toFixed(6)}`;
    if (!pickMarker.getTooltip()) {
      pickMarker.bindTooltip(`Seleccionado: ${t}`, {
        permanent: true,
        direction: "top",
        offset: [0, -12],
        opacity: 0.95
      }).openTooltip();
    } else {
      pickMarker.setTooltipContent(`Seleccionado: ${t}`);
    }
  }

  function setPickLatLng(latlng, { center = true } = {}) {
    selectedLatLng = { lat: latlng.lat, lng: latlng.lng };

    if (!pickMarker) {
      pickMarker = L.marker(latlng, { draggable: true, icon: pickerIcon() }).addTo(pickMap);
      pickMarker.on("drag", () => {
        const ll = pickMarker.getLatLng();
        selectedLatLng = { lat: ll.lat, lng: ll.lng };
        updateMarkerTooltip();
      });
      pickMarker.on("dragend", () => {
        const ll = pickMarker.getLatLng();
        selectedLatLng = { lat: ll.lat, lng: ll.lng };
        updateMarkerTooltip();
      });
    } else {
      pickMarker.setLatLng(latlng);
    }

    updateMarkerTooltip();

    if (center) {
      pickMap.setView(latlng, Math.max(pickMap.getZoom(), 17), { animate: true });
    }
  }

  function clearPick() {
    selectedLatLng = null;
    if (pickMarker && pickMap) {
      try { pickMap.removeLayer(pickMarker); } catch { }
    }
    pickMarker = null;
  }

  function ensurePickMap() {
    if (!mapEl) return;
    if (!window.L) {
      Swal.fire("Falta Leaflet", "No está cargado Leaflet en esta página.", "error");
      return;
    }

    ensureLeafletFixStyles();
    ensureMapSizing();

    if (pickMap) return;

    pickMap = L.map(mapEl, {
      preferCanvas: true,
      zoomControl: true,
      scrollWheelZoom: true,
      dragging: true,
      doubleClickZoom: true,
      touchZoom: true
    });

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

    pickMap.setView(PANQUEHUE_CENTER, 14);

    pickMap.on("click", (e) => {
      setPickLatLng(e.latlng, { center: false });
    });
  }

  // abrir modal
  btnMapa?.addEventListener("click", () => {
    if (!mapaModalEl) {
      Swal.fire("Falta el modal del mapa", "No existe #mapaModal en el HTML.", "warning");
      return;
    }

    const inst = bootstrap.Modal.getOrCreateInstance(mapaModalEl);
    inst.show();

    // ✅ marca SOLO el backdrop del mapa para subirlo (sin bloquear personaModal)
    setTimeout(() => {
      const bds = document.querySelectorAll(".modal-backdrop");
      const last = bds[bds.length - 1];
      last?.classList.add("mapa-backdrop");
    }, 0);
  });


  // al mostrarse -> init + invalidateSize (CLAVE)
  mapaModalEl?.addEventListener("shown.bs.modal", () => {
    ensurePickMap();

    setTimeout(() => {
      if (!pickMap) return;
      pickMap.invalidateSize(true);

      // si ya hay coords, las coloca y centra
      const has = latitud.value && longitud.value;
      if (has) {
        const lt = Number(String(latitud.value).replace(",", "."));
        const lg = Number(String(longitud.value).replace(",", "."));
        if (Number.isFinite(lt) && Number.isFinite(lg)) {
          setPickLatLng({ lat: lt, lng: lg }, { center: true });
          pickMap.setView([lt, lg], 17, { animate: true });
          return;
        }
      }

      // si no hay coords válidas, limpia y centra
      clearPick();
      pickMap.setView(PANQUEHUE_CENTER, 14);
    }, 200);
  });

  // usar ubicación elegida
  btnUsarUbicacion?.addEventListener("click", () => {
    if (!selectedLatLng) {
      Swal.fire("Selecciona un punto", "Pincha (o toca) el mapa para marcar la ubicación.", "info");
      return;
    }

    latitud.value = Number(selectedLatLng.lat).toFixed(6);
    longitud.value = Number(selectedLatLng.lng).toFixed(6);
    gpsLbl.textContent = `Mapa: ${latitud.value}, ${longitud.value}`;

    bootstrap.Modal.getInstance(mapaModalEl)?.hide();
  });





  /* =========================
     IMPORTACIÓN ESTRATIFICACIÓN
  ========================= */
  function normalizeHeaderKey(key) {
    return String(key ?? "")
      .normalize("NFD")
      .replace(/[\u0300-\u036f]/g, "")
      .replace(/[^a-zA-Z0-9]+/g, "_")
      .replace(/^_+|_+$/g, "")
      .toLowerCase();
  }

  function buildImportRowsFromExcel(rawRows) {
    const rows = [];
    const byKey = (row, candidates) => {
      for (const c of candidates) {
        if (c in row) return row[c];
      }
      return "";
    };

    for (const original of rawRows || []) {
      const row = {};
      Object.entries(original || {}).forEach(([k, v]) => {
        row[normalizeHeaderKey(k)] = v;
      });

      const rutBase = String(byKey(row, ["rut"]) || "").replace(/\D/g, "");
      const dv = String(byKey(row, ["dv"]) || "").trim().toUpperCase();
      const rut = `${rutBase}${dv}`.replace(/[^0-9K]/g, "");

      if (!rut) continue;

      rows.push({
        rut,
        cantidad_condiciones_cronicas: String(byKey(row, ["cantidad_de_condiciones_cronicas"]) || "").trim(),
        estratificacion_riesgo: String(byKey(row, ["estratificacion_de_riesgo_actual"]) || "").trim().toUpperCase(),
        fecha_actualizacion_estratificacion: String(byKey(row, ["fecha_de_actualizacion_de_estratificacion"]) || "").trim(),
        detalle_diagnosticos_cronicos: String(byKey(row, ["detalle_de_condiciones_cronicas_diagnosticos"]) || "").trim()
      });
    }

    return rows;
  }

  async function importarEstratificacionDesdeExcel() {
    const file = excelEstratificacion?.files?.[0];
    if (!file) {
      Swal.fire("Falta el archivo", "Selecciona el archivo de estratificación (.xlsx o .csv) antes de continuar.", "warning");
      return;
    }

    const fd = new FormData();
    fd.append("excel", file);
    fd.append("solo_existentes", "1");

    Swal.fire({
      title: "Importando estratificación...",
      html: "Subiendo archivo y procesando registros en el servidor. Puede demorar un poco si el padrón es grande.",
      allowOutsideClick: false,
      didOpen: () => Swal.showLoading()
    });

    try {
      const res = await fetch(API.import, {
        method: "POST",
        credentials: "same-origin",
        body: fd
      });

      const json = await res.json().catch(() => null);
      if (!res.ok || !json || json.ok === false) {
        throw new Error(json?.message || "No se pudo importar la estratificación.");
      }

      const totals = json.summary || {};
      const noEncontrados = Array.isArray(totals.ruts_no_encontrados) ? totals.ruts_no_encontrados : [];

      Swal.close();

      if (importResumenEstratificacion) {
        importResumenEstratificacion.innerHTML = `
          <div class="alert alert-success mb-0">
            <div><b>Recibidos:</b> ${Number(totals.recibidos || 0)}</div>
            <div><b>Procesados:</b> ${Number(totals.procesados || 0)}</div>
            <div><b>Actualizados:</b> ${Number(totals.actualizados || 0)}</div>
            <div><b>No encontrados en usuarios:</b> ${Number(totals.no_encontrados || 0)}</div>
          </div>
        `;
      }

      importModal?.hide();
      await loadStats();
      await loadList();

      Swal.fire({
        icon: "success",
        title: "Importación completada",
        html: `
          <div class="text-start small">
            <div><b>Registros leídos:</b> ${Number(totals.recibidos || 0)}</div>
            <div><b>Usuarios actualizados:</b> ${Number(totals.actualizados || 0)}</div>
            <div><b>RUT no encontrados:</b> ${Number(totals.no_encontrados || 0)}</div>
            ${noEncontrados.length ? `<hr><div><b>Primeros RUT no encontrados:</b><br>${noEncontrados.map(esc).join(", ")}</div>` : ""}
          </div>
        `
      });
    } catch (err) {
      Swal.close();
      Swal.fire("Error", err?.message || "No se pudo importar la estratificación.", "error");
    }
  }



  async function importarPadronDesdeExcel() {
    const file = excelPadron?.files?.[0];
    if (!file) {
      Swal.fire("Falta el archivo", "Selecciona el archivo de padrón (.xlsx o .csv) antes de continuar.", "warning");
      return;
    }

    const fd = new FormData();
    fd.append("excel", file);

    Swal.fire({
      title: "Importando padrón...",
      html: "Subiendo archivo y procesando inscritos/adscritos en el servidor. Puede tardar unos minutos si el padrón es grande.",
      allowOutsideClick: false,
      didOpen: () => Swal.showLoading()
    });

    try {
      const res = await fetch(API.importPadron, {
        method: "POST",
        credentials: "same-origin",
        body: fd
      });

      const json = await res.json().catch(() => null);
      if (!res.ok || !json || json.ok === false) {
        throw new Error(json?.message || "No se pudo importar el padrón.");
      }

      const totals = json.summary || {};
      Swal.close();

      if (importResumenPadron) {
        importResumenPadron.innerHTML = `
          <div class="alert alert-success mb-0">
            <div><b>Filas leídas:</b> ${Number(totals.recibidos || 0)}</div>
            <div><b>Procesadas:</b> ${Number(totals.procesados || 0)}</div>
            <div><b>Nuevos insertados:</b> ${Number(totals.insertados || 0)}</div>
            <div><b>Usuarios actualizados:</b> ${Number(totals.actualizados || 0)}</div>
            <div><b>Sin RUT válido:</b> ${Number(totals.sin_rut || 0)}</div>
            <div><b>Sin fecha válida en Excel:</b> ${Number(totals.sin_fecha_excel || 0)}</div>
            <div><b>Omitidos por fecha antigua:</b> ${Number(totals.omitidos_por_fecha || 0)}</div>
            <div><b>Omitidos:</b> ${Number(totals.omitidos || 0)}</div>
          </div>
        `;
      }

      importPadronModal?.hide();
      await loadStats();
      await loadList();

      Swal.fire({
        icon: "success",
        title: "Padrón importado",
        html: `
          <div class="text-start small">
            <div><b>Filas leídas:</b> ${Number(totals.recibidos || 0)}</div>
            <div><b>Insertados:</b> ${Number(totals.insertados || 0)}</div>
            <div><b>Actualizados:</b> ${Number(totals.actualizados || 0)}</div>
            <div><b>Sin RUT válido:</b> ${Number(totals.sin_rut || 0)}</div>
            <div><b>Sin fecha válida en Excel:</b> ${Number(totals.sin_fecha_excel || 0)}</div>
            <div><b>Omitidos por fecha antigua:</b> ${Number(totals.omitidos_por_fecha || 0)}</div>
            <div><b>Omitidos:</b> ${Number(totals.omitidos || 0)}</div>
          </div>
        `
      });
    } catch (err) {
      Swal.close();
      Swal.fire("Error", err?.message || "No se pudo importar el padrón.", "error");
    }
  }



  async function importarPercapitaDesdeCsv() {
    const file = csvPercapita?.files?.[0];
    if (!file) {
      Swal.fire("Falta el archivo", "Selecciona el CSV de Percápita FONASA antes de continuar.", "warning");
      return;
    }

    const confirm = await Swal.fire({
      icon: "warning",
      title: "Actualizar Percápita",
      html: "Los RUN presentes en el CSV quedarán como <b>percapitados</b> y todos los demás quedarán como <b>no percapitados</b>.<br><br>Si el CSV trae RUN que no existen en la base, se crearán como personas nuevas con nombres, apellidos, fecha de nacimiento y género del archivo FONASA.<br><br>No se eliminarán personas ni datos clínicos.",
      showCancelButton: true,
      confirmButtonText: "Sí, actualizar",
      cancelButtonText: "Cancelar"
    });
    if (!confirm.isConfirmed) return;

    const fd = new FormData();
    fd.append("percapita", file);

    Swal.fire({
      title: "Procesando Percápita...",
      html: "Leyendo columna RUN, marcando estado percapitado/no percapitado y creando los RUN nuevos que no existan en la base.",
      allowOutsideClick: false,
      didOpen: () => Swal.showLoading()
    });

    try {
      const res = await fetch(API.importPercapita, {
        method: "POST",
        credentials: "same-origin",
        body: fd
      });
      const json = await res.json().catch(() => null);
      if (!res.ok || !json || json.ok === false) {
        throw new Error(json?.message || "No se pudo actualizar el estado Percápita.");
      }

      const t = json.summary || {};
      Swal.close();

      if (importResumenPercapita) {
        importResumenPercapita.innerHTML = `
          <div class="alert alert-success mb-0">
            <div><b>RUN únicos CSV:</b> ${Number(t.run_unicos_csv || 0)}</div>
            <div><b>Total usuarios BD antes:</b> ${Number(t.total_usuarios_bd_antes ?? t.total_usuarios_bd ?? 0)}</div>
            <div><b>Total usuarios BD después:</b> ${Number(t.total_usuarios_bd_despues ?? 0)}</div>
            <div><b>Coincidencias encontradas en BD:</b> ${Number(t.filas_bd_coincidentes || 0)}</div>
            <div><b>Nuevos creados desde CSV:</b> ${Number(t.usuarios_creados_desde_csv || 0)}</div>
            <div><b>Marcados percapitados final:</b> ${Number(t.marcados_percapitados || 0)}</div>
            <div><b>Marcados no percapitados:</b> ${Number(t.marcados_no_percapitados || 0)}</div>
          </div>
        `;
      }

      importPercapitaModal?.hide();
      await loadStats();
      await loadList({ resetPage: true });

      Swal.fire({
        icon: "success",
        title: "Percápita actualizado",
        html: `
          <div class="text-start small">
            <div><b>RUN únicos del CSV:</b> ${Number(t.run_unicos_csv || 0)}</div>
            <div><b>Coincidencias encontradas en BD:</b> ${Number(t.filas_bd_coincidentes || 0)}</div>
            <div><b>RUN que no existían en BD:</b> ${Number(t.run_csv_no_existian_en_bd ?? t.run_csv_no_encontrados ?? 0)}</div>
            <div><b>Usuarios nuevos creados desde CSV:</b> ${Number(t.usuarios_creados_desde_csv || 0)}</div>
            <div><b>Usuarios marcados percapitados final:</b> ${Number(t.marcados_percapitados || 0)}</div>
            <div><b>Usuarios marcados no percapitados:</b> ${Number(t.marcados_no_percapitados || 0)}</div>
            ${Number(t.usuarios_no_creados_por_error || 0) > 0 ? `<div class="text-danger"><b>No creados por error:</b> ${Number(t.usuarios_no_creados_por_error || 0)}</div>` : ""}
            ${Array.isArray(t.muestra_creados) && t.muestra_creados.length ? `<hr><div><b>Muestra de nuevos creados:</b><br>${t.muestra_creados.map(esc).join("<br>")}</div>` : ""}
          </div>
        `
      });
    } catch (err) {
      Swal.close();
      Swal.fire("Error", err?.message || "No se pudo actualizar Percápita.", "error");
    }
  }
  /* =========================
     INIT (orden correcto)
  ========================= */
  (async () => {
    try {
      await loadFiltroSectorizacion();
      await loadFiltroSectores(""); // llena cache + mapSectores
      await loadSectorizacion();
      await loadSectores();

      await loadStats();
      await loadList();
    } catch (err) {
      console.error(err);
      Swal.fire("Error", err?.message || "Error de servidor", "error");
    }
  })();

})();
