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

  // ✅ Nuevo endpoint real
  const API_REPORTES = "../php/reportes/summary.php";

  // ✅ Catálogos (NO editar list.php; solo consumirlos)
  const API_SECTORIZACION = "../php/config/sectorizacion/list.php";
  const API_SECTORES = "../php/config/sectores/list.php";

  // Desktop filters
  const tPersonas = $("tPersonas");
  const tFamilias = $("tFamilias");
  const tViviendas = $("tViviendas");
  const fSz = $("fSz");
  const fSec = $("fSec");
  const fGeo = $("fGeo");
  const fPercapitado = $("fPercapitado");
  const fEstratificacion = $("fEstratificacion");
  const fDiagnostico = $("fDiagnostico");
  const btnAplicar = $("btnAplicar");
  const btnReset = $("btnReset");
  const btnRecargar = $("btnRecargar");
  const btnExportCSV = $("btnExportCSV");

  // Mobile filters
  const tPersonasM = $("tPersonasM");
  const tFamiliasM = $("tFamiliasM");
  const tViviendasM = $("tViviendasM");
  const fSzM = $("fSzM");
  const fSecM = $("fSecM");
  const fGeoM = $("fGeoM");
  const fPercapitadoM = $("fPercapitadoM");
  const fEstratificacionM = $("fEstratificacionM");
  const fDiagnosticoM = $("fDiagnosticoM");
  const btnAplicarM = $("btnAplicarM");
  const btnResetM = $("btnResetM");

  // Mobile quick actions (top right)
  const btnExportCSVM = $("btnExportCSVM");
  const btnRecargarM = $("btnRecargarM");

  // UI
  const lblTotalBase = $("lblTotalBase");

  const kpiP = $("kpiP");
  const kpiP2 = $("kpiP2");
  const kpiF = $("kpiF");
  const kpiF2 = $("kpiF2");
  const kpiV = $("kpiV");
  const kpiV2 = $("kpiV2");
  const kpiGeo = $("kpiGeo");
  const kpiGeo2 = $("kpiGeo2");
  const kpiPerc = $("kpiPerc");
  const kpiPerc2 = $("kpiPerc2");
  const kpiNoPerc = $("kpiNoPerc");
  const kpiNoPerc2 = $("kpiNoPerc2");

  const lblInscrito = $("lblInscrito");
  const lblPercapitaInsc = $("lblPercapitaInsc");
  const lblGenero = $("lblGenero");
  const lblSz = $("lblSz");
  const lblSec = $("lblSec");
  const lblFamRisk = $("lblFamRisk");
  const lblEstratificacion = $("lblEstratificacion");

  const lblResumen = $("lblResumen");
  const lblTop = $("lblTop");
  const tblResumen = $("tblResumen").querySelector("tbody");
  const tblTop = $("tblTopSectores").querySelector("tbody");

  // Charts
  const chInscrito = $("chInscrito");
  const chPercapitaInsc = $("chPercapitaInsc");
  const chGenero = $("chGenero");
  const chSz = $("chSz");
  const chSec = $("chSec");
  const chFamRisk = $("chFamRisk");
  const chEstratificacion = $("chEstratificacion");

  // Chart instances
  let chartInscrito = null;
  let chartPercapitaInsc = null;
  let chartGenero = null;
  let chartSz = null;
  let chartSec = null;
  let chartFamRisk = null;
  let chartEstratificacion = null;

  // Estado
  let lastSummary = null;
  let baseMeta = null;

  // ===== Catálogos: Sectorización / Sectores (para combos dependientes y nombres) =====
  let SZ_ROWS = [];
  let ALL_SEC = [];
  const MAP_SZ = new Map();     // "2" -> "B"
  const MAP_SEC = new Map();    // "10" -> {id, sectorizacion, nombre}
  const SEC_BY_SZ = new Map();  // "2" -> [ {..}, .. ]
  let catalogsReady = false;

  function idStr(v) {
    const s = String(v ?? "").trim();
    return s === "" ? "" : s;
  }

  function setOptionsKeepFirst(selectEl, rows, getVal, getLab, keepValue) {
    if (!selectEl) return;

    const firstVal = selectEl.options?.[0]?.value ?? "";
    const firstText = selectEl.options?.[0]?.textContent ?? "";

    // reconstruye sin tocar HTML: conserva solo el primer <option> (Todas/Todos)
    selectEl.innerHTML = "";
    selectEl.add(new Option(firstText, firstVal));

    (rows || []).forEach((r) => {
      const v = idStr(getVal(r));
      if (!v) return;
      const lab = String(getLab(r) ?? v).trim() || v;
      selectEl.add(new Option(lab, v));
    });

    const want = idStr(keepValue);
    if (want && Array.from(selectEl.options).some((o) => o.value === want)) {
      selectEl.value = want;
    } else {
      selectEl.value = firstVal;
    }
  }

  function buildLookups(szRows, secRows) {
    SZ_ROWS = Array.isArray(szRows) ? szRows.slice() : [];
    ALL_SEC = Array.isArray(secRows) ? secRows.slice() : [];

    MAP_SZ.clear();
    MAP_SEC.clear();
    SEC_BY_SZ.clear();

    // sectorizaciones
    SZ_ROWS.forEach((r) => {
      const id = idStr(r?.id);
      if (!id) return;
      const name = String(r?.nombre_sectorizacion ?? r?.nombre ?? id).trim() || id;
      MAP_SZ.set(id, name);
    });

    // sectores (normalizados)
    const tmpAll = [];
    ALL_SEC.forEach((r) => {
      const id = idStr(r?.id);
      if (!id) return;

      const sz = idStr(r?.sectorizacion ?? r?.sectorizacion_id ?? r?.sectorizacionId);
      const nombre = String(r?.nombre_sector ?? r?.nombre ?? r?.nombre_sector).trim() || id;

      const rec = { id, sectorizacion: sz, nombre };
      tmpAll.push(rec);
      MAP_SEC.set(id, rec);

      if (sz) {
        if (!SEC_BY_SZ.has(sz)) SEC_BY_SZ.set(sz, []);
        SEC_BY_SZ.get(sz).push(rec);
      }
    });

    const num = (x) => {
      const n = Number(String(x).trim());
      return Number.isFinite(n) ? n : null;
    };

    SZ_ROWS.sort((a, b) => {
      const na = num(a?.id), nb = num(b?.id);
      if (na === null || nb === null) return String(a?.id ?? "").localeCompare(String(b?.id ?? ""));
      return na - nb;
    });

    tmpAll.sort((a, b) => {
      const na = num(a?.id), nb = num(b?.id);
      if (na === null || nb === null) return String(a?.id ?? "").localeCompare(String(b?.id ?? ""));
      return na - nb;
    });

    ALL_SEC = tmpAll;

    SEC_BY_SZ.forEach((arr) => {
      arr.sort((a, b) => {
        const na = num(a?.id), nb = num(b?.id);
        if (na === null || nb === null) return String(a?.id ?? "").localeCompare(String(b?.id ?? ""));
        return na - nb;
      });
    });
  }

  function populateSectorizacionSelects() {
    if (!SZ_ROWS.length) return;
    setOptionsKeepFirst(fSz, SZ_ROWS, (r) => r.id, (r) => r.nombre_sectorizacion ?? r.nombre ?? r.id, fSz.value);
    setOptionsKeepFirst(fSzM, SZ_ROWS, (r) => r.id, (r) => r.nombre_sectorizacion ?? r.nombre ?? r.id, fSzM.value);
  }

  function sectorsFor(szId) {
    const key = idStr(szId);
    if (!key) return ALL_SEC;
    return SEC_BY_SZ.get(key) ?? [];
  }

  function populateSectoresSelect(selectEl, szId) {
    const rows = sectorsFor(szId);
    setOptionsKeepFirst(selectEl, rows, (r) => r.id, (r) => r.nombre, selectEl.value);
  }

  function populateSectoresSelectsFrom(szId) {
    populateSectoresSelect(fSec, szId);
    populateSectoresSelect(fSecM, szId);
  }

  function isNumericLabel(s) {
    return /^[0-9]+$/.test(String(s ?? "").trim());
  }

  function szNameFromLabel(label) {
    const s = String(label ?? "").trim();
    if (!s) return s;
    if (/^sin dato$/i.test(s)) return "Sin dato";
    if (isNumericLabel(s) && MAP_SZ.size) return MAP_SZ.get(s) || s;
    return s;
  }

  function secNameFromLabel(label) {
    const s = String(label ?? "").trim();
    if (!s) return s;
    if (/^sin dato$/i.test(s)) return "Sin dato";
    if (isNumericLabel(s) && MAP_SEC.size) return (MAP_SEC.get(s)?.nombre || s);
    return s;
  }

  function decorateSummary(raw) {
    const s = raw || emptySummary();
    return {
      ...s,
      bySz: {
        labels: (s.bySz?.labels || []).map(szNameFromLabel),
        data: s.bySz?.data || [],
      },
      bySec: {
        labels: (s.bySec?.labels || []).map(secNameFromLabel),
        data: s.bySec?.data || [],
      },
      famRisk: normalizeFamRisk(s.famRisk),
      bySzSec: Array.isArray(s.bySzSec)
        ? s.bySzSec.map((it) => ({
            ...it,
            sz: szNameFromLabel(it.sz),
            sec: secNameFromLabel(it.sec),
          }))
        : [],
    };
  }

  async function loadCatalogs() {
    try {
      const z = await fetchJSON(API_SECTORIZACION, { method: "GET" });
      const s = await fetchJSON(API_SECTORES, { method: "GET" });

      const szRows = Array.isArray(z?.data) ? z.data : [];
      const secRows = Array.isArray(s?.data) ? s.data : [];

      buildLookups(szRows, secRows);
      populateSectorizacionSelects();

      // aplica dependencia actual
      populateSectoresSelectsFrom(fSz.value || "");

      catalogsReady = true;
    } catch (e) {
      catalogsReady = false;
      console.warn("[reportes] No se pudieron cargar catálogos:", e);
    }
  }

  // ===== Utils =====
  function getThemeColors() {
    const root = getComputedStyle(document.documentElement);
    const accent = (root.getPropertyValue("--accent") || "#2fb7ff").trim();
    const accent2 = (root.getPropertyValue("--accent2") || "#34d399").trim();
    const text = (root.getPropertyValue("--text") || "#0b1220").trim();
    const muted = (root.getPropertyValue("--muted") || "rgba(11,18,32,.72)").trim();
    return { accent, accent2, text, muted };
  }

  function destroyCharts() {
    [chartInscrito, chartPercapitaInsc, chartGenero, chartSz, chartSec, chartFamRisk, chartEstratificacion].forEach((ch) => {
      if (ch) ch.destroy();
    });
    chartInscrito = chartPercapitaInsc = chartGenero = chartSz = chartSec = chartFamRisk = chartEstratificacion = null;
  }

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

  // Acordeón exclusivo como en otras páginas
  function wireAccordions() {
    const accordions = Array.from(document.querySelectorAll("details.acc"));
    if (!accordions.length) return;

    const opened = accordions.filter((a) => a.hasAttribute("open"));
    if (opened.length > 1) opened.slice(1).forEach((a) => a.removeAttribute("open"));

    accordions.forEach((acc) => {
      acc.addEventListener("toggle", () => {
        if (!acc.open) return;
        accordions.forEach((o) => {
          if (o !== acc && o.open) o.removeAttribute("open");
        });
      });
    });
  }

  function syncMobileFromDesktop() {
    tPersonasM.checked = tPersonas.checked;
    tFamiliasM.checked = tFamilias.checked;
    tViviendasM.checked = tViviendas.checked;

    // Dependencia: Sectorización -> Sectores
    fSzM.value = fSz.value;
    if (catalogsReady) populateSectoresSelect(fSecM, fSz.value || "");
    fSecM.value = fSec.value;

    fGeoM.value = fGeo.value;
    if (fPercapitado && fPercapitadoM) fPercapitadoM.value = fPercapitado.value;
    if (fEstratificacion && fEstratificacionM) fEstratificacionM.value = fEstratificacion.value;
    if (fDiagnostico && fDiagnosticoM) fDiagnosticoM.value = fDiagnostico.value;
  }

  function syncDesktopFromMobile() {
    tPersonas.checked = tPersonasM.checked;
    tFamilias.checked = tFamiliasM.checked;
    tViviendas.checked = tViviendasM.checked;

    // Dependencia: Sectorización -> Sectores
    fSz.value = fSzM.value;
    if (catalogsReady) populateSectoresSelect(fSec, fSzM.value || "");
    fSec.value = fSecM.value;

    fGeo.value = fGeoM.value;
    if (fPercapitado && fPercapitadoM) fPercapitado.value = fPercapitadoM.value;
    if (fEstratificacion && fEstratificacionM) fEstratificacion.value = fEstratificacionM.value;
    if (fDiagnostico && fDiagnosticoM) fDiagnostico.value = fDiagnosticoM.value;
  }

  function filters() {
    return {
      types: { p: !!tPersonas.checked, f: !!tFamilias.checked, v: !!tViviendas.checked },
      sz: fSz.value,
      sec: fSec.value,
      geo: fGeo.value, // solo personas
      percapitado: fPercapitado?.value || "",
      estratificacion_riesgo: (fEstratificacion?.value || "").trim().toUpperCase(),
      diagnostico_q: (fDiagnostico?.value || "").trim(),
    };
  }

  
  function normalizeFamRisk(fr) {
    const x = fr || {};
    const total = Number(x.total || 0);
    return {
      total,
      bajo: Number(x.bajo || 0),
      medio: Number(x.medio || 0),
      alto: Number(x.alto || 0),
      sinDato: Number(x.sinDato || 0),
    };
  }

function emptySummary() {
    return {
      pTotal: 0,
      fTotal: 0,
      vTotal: 0,
      inscYes: 0,
      inscNo: 0,
      percYes: 0,
      percNo: 0,
      migYes: 0,
      migNo: 0,
      migNd: 0,
      geoYes: 0,
      geoNo: 0,
      cronicosTotal: 0,
      g0: 0,
      g1: 0,
      g2: 0,
      g3: 0,
      genero: { labels: [], data: [] },
      bySz: { labels: [], data: [] },
      bySec: { labels: [], data: [] },
      byEstratificacion: { labels: [], data: [] },
      bySzSec: [],
      famRisk: { total: 0, bajo: 0, medio: 0, alto: 0, sinDato: 0 },
    };
  }

  // ===== Render =====
  function renderKPIs(summary) {
    const pTotal = Number(summary?.pTotal || 0);
    const fTotal = Number(summary?.fTotal || 0);
    const vTotal = Number(summary?.vTotal || 0);

    const geoCount = Number(summary?.geoYes || 0);
    const geoPct = pTotal ? Math.round((geoCount / pTotal) * 100) : 0;

    kpiP.textContent = pTotal;
    kpiP2.textContent = tPersonas.checked ? "incluido" : "excluido";

    kpiF.textContent = fTotal;
    kpiF2.textContent = tFamilias.checked ? "incluido" : "excluido";

    kpiV.textContent = vTotal;
    kpiV2.textContent = tViviendas.checked ? "incluido" : "excluido";

    kpiGeo.textContent = geoCount;
    kpiGeo2.textContent = pTotal ? `${geoPct}% de Personas` : "—";

    const percYes = Number(summary?.percYes || 0);
    const percNo = Number(summary?.percNo || 0);
    const percPct = pTotal ? Math.round((percYes / pTotal) * 100) : 0;
    const noPercPct = pTotal ? Math.round((percNo / pTotal) * 100) : 0;

    if (kpiPerc) kpiPerc.textContent = percYes;
    if (kpiPerc2) kpiPerc2.textContent = pTotal ? `${percPct}% de Personas` : "—";

    if (kpiNoPerc) kpiNoPerc.textContent = percNo;
    if (kpiNoPerc2) kpiNoPerc2.textContent = pTotal ? `${noPercPct}% de Personas` : "—";
  }

  function renderTables(summary) {
    tblResumen.innerHTML = "";

    const rows = [
      ["Personas incluidas", summary.pTotal],
      ["Familias incluidas", summary.fTotal],
      ["Familias (riesgo bajo)", summary.famRisk?.bajo ?? 0],
      ["Familias (riesgo medio)", summary.famRisk?.medio ?? 0],
      ["Familias (riesgo alto)", summary.famRisk?.alto ?? 0],
      ["Familias (sin clasificación)", summary.famRisk?.sinDato ?? 0],
      ["Viviendas incluidas", summary.vTotal],
      ["Personas inscritas CESFAM", summary.inscYes],
      ["Personas no inscritas CESFAM", summary.inscNo],
      ["Personas percapitadas", summary.percYes],
      ["Personas no percapitadas", summary.percNo],
      ["Personas migrantes (Sí)", summary.migYes],
      ["Personas migrantes (No)", summary.migNo],
      ["Personas migrantes (Sin dato)", summary.migNd],
      ["Personas con coordenadas", summary.geoYes],
      ["Personas sin coordenadas", summary.geoNo],
      ["Personas con condiciones crónicas", summary.cronicosTotal],
      ["Estratificación G0", summary.g0],
      ["Estratificación G1", summary.g1],
      ["Estratificación G2", summary.g2],
      ["Estratificación G3", summary.g3],
    ];

    rows.forEach(([k, v]) => {
      const tr = document.createElement("tr");
      tr.innerHTML = `<td>${k}</td><td class="text-end fw-bold">${Number(v || 0)}</td>`;
      tblResumen.appendChild(tr);
    });

    lblResumen.textContent = `Personas ${Number(summary.pTotal || 0)} · Familias ${Number(summary.fTotal || 0)} · Viviendas ${Number(summary.vTotal || 0)}`;

    // Top sectores
    tblTop.innerHTML = "";
    const all = Array.isArray(summary.bySzSec) ? summary.bySzSec.slice() : [];
    all.sort((a, b) => (b.count || 0) - (a.count || 0));
    const top = all.slice(0, 12);

    top.forEach((it) => {
      const tr = document.createElement("tr");
      tr.innerHTML = `<td>${it.sz}</td><td>${it.sec}</td><td class="text-end fw-bold">${Number(it.count || 0)}</td>`;
      tblTop.appendChild(tr);
    });

    lblTop.textContent = top.length ? `Top ${top.length}` : "—";
  }

  function renderCharts(summary) {
    const { accent, accent2, text, muted } = getThemeColors();
    const grid = "rgba(15,23,42,.10)";

    destroyCharts();

    const pTotal = Number(summary.pTotal || 0);
    const inscYes = Number(summary.inscYes || 0);
    const inscNo = Number(summary.inscNo || 0);

    // INSCRITO
    chartInscrito = new Chart(chInscrito, {
      type: "doughnut",
      data: {
        labels: ["Inscrito", "No inscrito"],
        datasets: [
          {
            data: [inscYes, inscNo],
            backgroundColor: [accent2, accent],
            borderWidth: 0,
          },
        ],
      },
      options: {
        responsive: true,
        maintainAspectRatio: false,
        plugins: {
          legend: { labels: { color: text } },
          tooltip: { enabled: true },
        },
      },
    });
    lblInscrito.textContent = `${inscYes} / ${pTotal} inscritas`;

    // PERCÁPITA FONASA vs INSCRIPCIÓN CESFAM
    const percYes = Number(summary.percYes || 0);
    const percNo = Number(summary.percNo || 0);
    if (chPercapitaInsc) {
      chartPercapitaInsc = new Chart(chPercapitaInsc, {
        type: "bar",
        data: {
          labels: ["Inscritos CESFAM", "Percapitados", "No percapitados"],
          datasets: [
            {
              label: "Personas",
              data: [inscYes, percYes, percNo],
            },
          ],
        },
        options: {
          responsive: true,
          maintainAspectRatio: false,
          scales: {
            x: { ticks: { color: muted }, grid: { color: grid } },
            y: { ticks: { color: muted }, grid: { color: grid }, beginAtZero: true },
          },
          plugins: { legend: { display: false } },
        },
      });

      if (lblPercapitaInsc) {
        lblPercapitaInsc.textContent = `${percYes} percapitados · ${percNo} no percapitados`;
      }
    }

    // GENERO
    chartGenero = new Chart(chGenero, {
      type: "doughnut",
      data: {
        labels: summary.genero?.labels || [],
        datasets: [
          {
            data: summary.genero?.data || [],
            borderWidth: 0,
          },
        ],
      },
      options: {
        responsive: true,
        maintainAspectRatio: false,
        plugins: { legend: { labels: { color: text } } },
      },
    });
    lblGenero.textContent = `${pTotal} personas`;

    // POR SECTORIZACIÓN
    chartSz = new Chart(chSz, {
      type: "bar",
      data: {
        labels: summary.bySz?.labels || [],
        datasets: [
          {
            label: "Personas",
            data: summary.bySz?.data || [],
          },
        ],
      },
      options: {
        responsive: true,
        maintainAspectRatio: false,
        scales: {
          x: { ticks: { color: muted }, grid: { color: grid } },
          y: { ticks: { color: muted }, grid: { color: grid } },
        },
        plugins: { legend: { display: false } },
      },
    });
    lblSz.textContent = `${(summary.bySz?.labels || []).length} categorías`;

    // POR SECTOR
    chartSec = new Chart(chSec, {
      type: "bar",
      data: {
        labels: summary.bySec?.labels || [],
        datasets: [
          {
            label: "Personas",
            data: summary.bySec?.data || [],
          },
        ],
      },
      options: {
        responsive: true,
        maintainAspectRatio: false,
        scales: {
          x: { ticks: { color: muted }, grid: { color: grid } },
          y: { ticks: { color: muted }, grid: { color: grid } },
        },
        plugins: { legend: { display: false } },
      },
    });
    lblSec.textContent = `${(summary.bySec?.labels || []).length} sectores`;

    // CLASIFICACIÓN FAMILIAR (Familias)
    if (chFamRisk && summary.famRisk) {
      const fr = summary.famRisk;
      const totalF = Number(fr.total || 0);
      const bajo = Number(fr.bajo || 0);
      const medio = Number(fr.medio || 0);
      const alto = Number(fr.alto || 0);
      const sinDato = Number(fr.sinDato || 0);

      chartFamRisk = new Chart(chFamRisk, {
        type: "doughnut",
        data: {
          labels: ["Bajo", "Medio", "Alto", "Sin clasificación"],
          datasets: [
            {
              data: [bajo, medio, alto, sinDato],
              borderWidth: 0,
            },
          ],
        },
        options: {
          responsive: true,
          maintainAspectRatio: false,
          plugins: { legend: { labels: { color: text } } },
        },
      });

      if (lblFamRisk) {
        lblFamRisk.textContent = `${bajo} bajo · ${medio} medio · ${alto} alto · ${sinDato} sin dato (Total: ${totalF})`;
      }
    }

    if (chEstratificacion) {
      chartEstratificacion = new Chart(chEstratificacion, {
        type: "bar",
        data: {
          labels: summary.byEstratificacion?.labels || [],
          datasets: [
            {
              label: "Personas",
              data: summary.byEstratificacion?.data || [],
            },
          ],
        },
        options: {
          responsive: true,
          maintainAspectRatio: false,
          scales: {
            x: { ticks: { color: muted }, grid: { color: grid } },
            y: { ticks: { color: muted }, grid: { color: grid } },
          },
          plugins: { legend: { display: false } },
        },
      });

      if (lblEstratificacion) {
        lblEstratificacion.textContent = `G0 ${Number(summary.g0 || 0)} · G1 ${Number(summary.g1 || 0)} · G2 ${Number(summary.g2 || 0)} · G3 ${Number(summary.g3 || 0)}`;
      }
    }

  }

  function renderAll(summary) {
    renderKPIs(summary);
    renderTables(summary);
    renderCharts(summary);
  }

  // ===== CSV export (desde resumen real) =====
  function downloadText(filename, text) {
    const blob = new Blob([text], { type: "text/csv;charset=utf-8" });
    const url = URL.createObjectURL(blob);
    const a = document.createElement("a");
    a.href = url;
    a.download = filename;
    document.body.appendChild(a);
    a.click();
    a.remove();
    URL.revokeObjectURL(url);
  }

  function exportCSV() {
    const s = lastSummary || emptySummary();

    const lines = [];
    lines.push("Metrica,Valor");
    lines.push(`Personas,${Number(s.pTotal || 0)}`);
    lines.push(`Familias,${Number(s.fTotal || 0)}`);
    lines.push(`Familias_Riesgo_Bajo,${Number(s.famRisk?.bajo || 0)}`);
    lines.push(`Familias_Riesgo_Medio,${Number(s.famRisk?.medio || 0)}`);
    lines.push(`Familias_Riesgo_Alto,${Number(s.famRisk?.alto || 0)}`);
    lines.push(`Familias_Sin_Clasificacion,${Number(s.famRisk?.sinDato || 0)}`);
    lines.push(`Viviendas,${Number(s.vTotal || 0)}`);
    lines.push(`Inscrito_CESFAM_Si,${Number(s.inscYes || 0)}`);
    lines.push(`Inscrito_CESFAM_No,${Number(s.inscNo || 0)}`);
    lines.push(`Percapitado_Si,${Number(s.percYes || 0)}`);
    lines.push(`Percapitado_No,${Number(s.percNo || 0)}`);
    lines.push(`Migrante_Si,${Number(s.migYes || 0)}`);
    lines.push(`Migrante_No,${Number(s.migNo || 0)}`);
    lines.push(`Migrante_SinDato,${Number(s.migNd || 0)}`);
    lines.push(`Con_Coordenadas,${Number(s.geoYes || 0)}`);
    lines.push(`Sin_Coordenadas,${Number(s.geoNo || 0)}`);
    lines.push(`Con_Condiciones_Cronicas,${Number(s.cronicosTotal || 0)}`);
    lines.push(`Estratificacion_G0,${Number(s.g0 || 0)}`);
    lines.push(`Estratificacion_G1,${Number(s.g1 || 0)}`);
    lines.push(`Estratificacion_G2,${Number(s.g2 || 0)}`);
    lines.push(`Estratificacion_G3,${Number(s.g3 || 0)}`);
    lines.push("");

    lines.push("Distribucion_Genero,Valor");
    (s.genero?.labels || []).forEach((lab, i) => lines.push(`${lab},${Number((s.genero.data || [])[i] || 0)}`));
    lines.push("");

    lines.push("Personas_por_Sectorizacion,Valor");
    (s.bySz?.labels || []).forEach((lab, i) => lines.push(`${lab},${Number((s.bySz.data || [])[i] || 0)}`));
    lines.push("");

    lines.push("Personas_por_Sector,Valor");
    (s.bySec?.labels || []).forEach((lab, i) => lines.push(`${lab},${Number((s.bySec.data || [])[i] || 0)}`));
    lines.push("");

    lines.push("Personas_por_Estratificacion,Valor");
    (s.byEstratificacion?.labels || []).forEach((lab, i) => lines.push(`${lab},${Number((s.byEstratificacion.data || [])[i] || 0)}`));
    lines.push("");

    lines.push("Personas_por_Sectorizacion_y_Sector,Valor");
    const ss = Array.isArray(s.bySzSec) ? s.bySzSec.slice() : [];
    ss.sort((a, b) => (b.count || 0) - (a.count || 0));
    ss.forEach((it) => lines.push(`${it.sz}-${it.sec},${Number(it.count || 0)}`));

    const ts = new Date().toISOString().slice(0, 19).replaceAll(":", "-");
    downloadText(`reporte_familia_panquehue_${ts}.csv`, lines.join("\n"));
  }

  // ===== Backend load =====
  let reqId = 0;
  async function applyAsync(showToast = false) {
    const my = ++reqId;

    const Flt = filters();
    try {
      const payload = {
        types: Flt.types,
        sz: Flt.sz,
        sec: Flt.sec,
        geo: Flt.geo,
        percapitado: Flt.percapitado,
        estratificacion_riesgo: Flt.estratificacion_riesgo,
        diagnostico_q: Flt.diagnostico_q,
      };

      const res = await fetchJSON(API_REPORTES, {
        method: "POST",
        body: JSON.stringify(payload),
      });

      if (my !== reqId) return; // ignora respuesta vieja

      baseMeta = res.meta || baseMeta;
      lastSummary = decorateSummary(res.summary || emptySummary());

      if (baseMeta?.total_base?.all != null) {
        lblTotalBase.textContent = `${Number(baseMeta.total_base.all)} registros`;
      } else {
        lblTotalBase.textContent = "—";
      }

      renderAll(lastSummary);

      if (showToast) {
        await Swal.fire({ icon: "success", title: "Datos recargados", timer: 900, showConfirmButton: false });
      }
    } catch (e) {
      if (my !== reqId) return;
      await Swal.fire({ icon: "error", title: "Error", text: String(e?.message || e) });
    }
  }

  // ===== Events =====
  function wire() {
    // desktop changes (auto-aplicar)
    [tPersonas, tFamilias, tViviendas, fSec, fGeo, fPercapitado, fEstratificacion].forEach((el) => {
      el.addEventListener("change", () => {
        syncMobileFromDesktop();
        applyAsync(false);
      });
    });

    // Sectorización: actualiza Sectores dependientes antes de aplicar
    fSz.addEventListener("change", () => {
      if (catalogsReady) populateSectoresSelect(fSec, fSz.value || "");
      syncMobileFromDesktop();
      applyAsync(false);
    });

    fDiagnostico?.addEventListener("input", () => {
      syncMobileFromDesktop();
      applyAsync(false);
    });

    btnAplicar.addEventListener("click", () => {
      syncMobileFromDesktop();
      applyAsync(false);
    });

    btnReset.addEventListener("click", () => {
      tPersonas.checked = true;
      tFamilias.checked = true;
      tViviendas.checked = true;
      fSz.value = "";
      fSec.value = "";
      fGeo.value = "";
      if (fPercapitado) fPercapitado.value = "";
      if (fEstratificacion) fEstratificacion.value = "";
      if (fDiagnostico) fDiagnostico.value = "";
      if (catalogsReady) populateSectoresSelectsFrom("");
      syncMobileFromDesktop();
      applyAsync(false);
    });

    btnRecargar.addEventListener("click", async () => {
      await applyAsync(true);
    });

    btnExportCSV.addEventListener("click", exportCSV);

    // mobile
    btnAplicarM.addEventListener("click", () => {
      syncDesktopFromMobile();
      applyAsync(false);
    });

    btnResetM.addEventListener("click", () => {
      tPersonasM.checked = true;
      tFamiliasM.checked = true;
      tViviendasM.checked = true;
      fSzM.value = "";
      fSecM.value = "";
      fGeoM.value = "";
      if (fPercapitadoM) fPercapitadoM.value = "";
      if (fEstratificacionM) fEstratificacionM.value = "";
      if (fDiagnosticoM) fDiagnosticoM.value = "";
      if (catalogsReady) populateSectoresSelect(fSecM, "");
      syncDesktopFromMobile();
      applyAsync(false);
    });

    // Sectorización en móvil: refresca el combo Sector (sin tocar desktop hasta aplicar)
    fSzM.addEventListener("change", () => {
      if (catalogsReady) populateSectoresSelect(fSecM, fSzM.value || "");
    });

    // quick top actions
    btnRecargarM.addEventListener("click", async () => {
      await applyAsync(true);
    });
    btnExportCSVM.addEventListener("click", exportCSV);

    // mantener consistencia al abrir offcanvas
    document.addEventListener("shown.bs.offcanvas", () => {
      syncMobileFromDesktop();
    });
  }

  // ===== Init =====
  async function init() {
    wireAccordions();
    wire();
    syncMobileFromDesktop();
    await loadCatalogs();
    // luego de cargar catálogos, re-sincroniza y aplica
    syncMobileFromDesktop();
    applyAsync(false);
  }

  void init();
})();
