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

  /* =========================
     Endpoints
  ========================== */
  const API_BASE = "../php/vivienda";
  const API = {
    nextCode: `${API_BASE}/next_code.php`,
    save: `${API_BASE}/save.php`,
    list: `${API_BASE}/list.php`,
    get: `${API_BASE}/get.php`,
    del: `${API_BASE}/delete.php`,
  };

  // Config
  const API_SECTORIZACION = "../php/config/sectorizacion";
  const API_SECTORES = "../php/config/sectores";

  // ✅ Familias
  const API_FAMILIAS = "../php/familias";
  const API_PERSONAS = "../php/personas";

  /* =========================
     Form fields (NO tocar HTML)
  ========================== */
  const cod_vivienda = $("cod_vivienda");
  const direccion = $("direccion");
  const sectorizacion = $("sectorizacion");
  const sector = $("sector");
  const latitud = $("latitud");
  const longitud = $("longitud");

  const tipo_vivienda = $("tipo_vivienda");
  const tenencia = $("tenencia");
  const conservacion = $("conservacion");
  const calefaccion = $("calefaccion");
  const numero_piezas = $("numero_piezas");
  const numero_dormitorios = $("numero_dormitorios");
  const numero_camas = $("numero_camas");

  const agua = $("agua");
  const luz = $("luz");
  const cocina = $("cocina");
  const deposicion = $("deposicion");
  const basuras = $("basuras");
  const animales_vectores = $("animales_vectores");

  const fam_cod = $("fam_cod");
  const fam_apellido = $("fam_apellido");
  const btnAddFamilia = $("btnAddFamilia");
  const familiasList = $("familiasList");

  const btnNuevo = $("btnNuevo");
  const btnGuardar = $("btnGuardar");
  const btnEliminar = $("btnEliminar");
  const btnCancelar = $("btnCancelar");
  const btnGenCodigo = $("btnGenCodigo");
  const btnCapturarGPS = $("btnCapturarGPS");

  const lblEstado = $("lblEstado");

  // List
  const lblTotal = $("lblTotal");
  const lblCount = $("lblCount");
  const qList = $("qList");
  const btnClearList = $("btnClearList");
  const fSz = $("fSz");
  const fSec = $("fSec");
  const fTipo = $("fTipo");
  const btnAplicarList = $("btnAplicarList");
  const btnResetList = $("btnResetList");
  const resultsList = $("resultsList");

  /* =========================
     State
  ========================== */
  let all = [];
  let filtered = [];
  let currentId = null;          // ID vivienda seleccionada (DB)
  let currentFamilias = [];      // [{codigo, apellido}]
  let cacheSectorizaciones = [];
  let cacheSectores = [];
  let listReqSeq = 0;

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

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

  function isCodigoFamiliaLike(raw) {
    const t = String(raw ?? "").trim();
    if (!t) return false;
    // Si NO tiene ningún dígito, casi seguro es apellido
    if (!/\d/.test(t)) return false;
    // Permite letras/números/guiones/underscore
    return /^[A-Za-z0-9\-_]+$/.test(t);
  }

  function authHeaders() {
    const rut =
      sessionStorage.getItem("fp_rut") ||
      localStorage.getItem("fp_rut") ||
      sessionStorage.getItem("rut") ||
      localStorage.getItem("rut") ||
      "";
    const rol =
      sessionStorage.getItem("fp_rol") ||
      localStorage.getItem("fp_rol") ||
      sessionStorage.getItem("rol") ||
      localStorage.getItem("rol") ||
      "";

    const h = {};
    if (rut) h["X-FP-RUT"] = rut;
    if (rol) h["X-FP-ROL"] = rol;
    return h;
  }

  async function fetchJSON(url, options = {}) {
    const method = (options.method || "GET").toUpperCase();
    const headers = {
      ...(method !== "GET" ? { "Content-Type": "application/json" } : {}),
      ...authHeaders(),
      ...(options.headers || {}),
    };

    const res = await fetch(url, {
      credentials: "same-origin",
      cache: "no-store",
      ...options,
      method,
      headers,
    });

    const data = await res.json().catch(() => null);

    if (!res.ok || !data || data.ok === false) {
      const msg =
        (data && data.message) ||
        (res.status === 401 ? "No autenticado." : "Error de servidor");
      throw new Error(msg);
    }

    return data;
  }

  // versión "soft": no revienta, devuelve null
  async function fetchJSONSoft(url, options = {}) {
    try {
      return await fetchJSON(url, options);
    } catch (_) {
      return null;
    }
  }

  function pickArrayPayload(data) {
    if (!data || typeof data !== "object") return [];
    if (Array.isArray(data.data)) return data.data;
    if (Array.isArray(data.items)) return data.items;
    if (Array.isArray(data.rows)) return data.rows;
    return [];
  }

  function fmtDT(dt) {
    if (!dt) return "";
    const d = new Date(dt);
    if (Number.isNaN(d.getTime())) return safe(dt);
    return d.toLocaleString();
  }

  function openFirstAccordion() {
    const accs = Array.from(document.querySelectorAll("details.acc"));
    if (!accs.length) return;
    accs.forEach((a, i) => {
      if (i === 0) a.setAttribute("open", "");
      else a.removeAttribute("open");
    });
  }

  /* =========================
     ✅ Familias: lookup usando SOLO:
     - get.php (por código exacto)
     - list.php (por apellido / texto)
  ========================== */
  function normalizeFamiliaRow(row) {
    if (!row || typeof row !== "object") return null;

    const codigo = safe(
      row.codigo_familia ?? row.codigo ?? row.cod_familia ?? row.familia ?? ""
    ).trim();

    const apellido = safe(
      row.apellido_familia ?? row.apellido ?? row.apellidos ?? row.referencia ?? ""
    ).trim();

    if (!codigo) return null;
    return { codigo, apellido };
  }

  function normalizeFamiliaHit(row) {
    const fam = normalizeFamiliaRow(row);
    if (!fam) return null;

    return {
      codigo: fam.codigo,
      apellido: fam.apellido,
      direccion: safe(row.direccion ?? "").trim(),
      sectorizacion: row.sectorizacion ?? "",
      sector: row.sector ?? "",
      fecha_evaluacion: row.fecha_evaluacion ?? "",
    };
  }

  // 1) GET exacto por código (get.php)
  async function fetchFamiliaByCodigo(codigo) {
    const c = safe(codigo).trim();
    if (!c) return null;

    // ✅ SOLO get.php (evitamos 404 de obtener/buscar/search)
    const urls = [
      `${API_FAMILIAS}/get.php?codigo_familia=${encodeURIComponent(c)}`,
      `${API_FAMILIAS}/get.php?codigo=${encodeURIComponent(c)}`,
      `${API_FAMILIAS}/get.php?cod=${encodeURIComponent(c)}`,
    ];

    for (const u of urls) {
      const res = await fetchJSONSoft(u);
      if (!res) continue;

      // algunos endpoints devuelven {data:{...}}, otros {item:{...}}
      const row = res.data ?? res.item ?? res.familia ?? null;
      const fam = normalizeFamiliaRow(row);
      if (fam) return fam;
    }

    // 2) fallback: buscar en list.php y tomar match exacto por código
    const hits = await searchFamiliasByTexto(c);
    const exact = hits.find(x => safe(x.codigo).trim() === c);
    if (exact) return { codigo: exact.codigo, apellido: exact.apellido };
    return null;
  }

  // 2) Búsqueda por texto/apellido/código usando list.php (idealmente GET ?q=)
  async function searchFamiliasByTexto(term) {
    const q = safe(term).trim();
    if (!q) return [];

    // ✅ Primero GET con querystring (muchos list.php están hechos así y evita HY093 por JSON)
    const tryGetUrls = [
      `${API_FAMILIAS}/list.php?q=${encodeURIComponent(q)}`,
      `${API_FAMILIAS}/list.php?search=${encodeURIComponent(q)}`,
      `${API_FAMILIAS}/list.php?term=${encodeURIComponent(q)}`,
    ];

    for (const u of tryGetUrls) {
      const res = await fetchJSONSoft(u);
      if (!res) continue;
      const arr = pickArrayPayload(res);
      const out = arr.map(normalizeFamiliaHit).filter(Boolean);
      if (out.length) return out;
    }

    // ✅ Si tu list.php sí acepta POST JSON, probamos como fallback pero SOLO con "q"
    const resPost = await fetchJSONSoft(`${API_FAMILIAS}/list.php`, {
      method: "POST",
      body: JSON.stringify({ q }),
    });

    if (resPost) {
      const arr = pickArrayPayload(resPost);
      return arr.map(normalizeFamiliaHit).filter(Boolean);
    }

    return [];
  }

  async function hydrateFamiliasApellidosIfMissing() {
    const missing = currentFamilias.filter(f => f && f.codigo && !f.apellido);
    if (!missing.length) return;

    const uniqueCodes = Array.from(new Set(missing.map(f => safe(f.codigo).trim()))).slice(0, 15);

    for (const c of uniqueCodes) {
      const fam = await fetchFamiliaByCodigo(c).catch(() => null);
      if (fam && fam.apellido) {
        const idx = currentFamilias.findIndex(x => safe(x.codigo).trim() === c);
        if (idx >= 0) currentFamilias[idx].apellido = fam.apellido;
      }
    }
    renderFamiliasChips();
  }

  /* =========================
     Form
  ========================== */
  function resetForm() {
    currentId = null;
    currentFamilias = [];

    cod_vivienda.value = "";
    direccion.value = "";
    sectorizacion.value = "";
    sector.value = "";
    latitud.value = "";
    longitud.value = "";

    tipo_vivienda.value = "";
    tenencia.value = "";
    conservacion.value = "";
    calefaccion.value = "";
    numero_piezas.value = "";
    numero_dormitorios.value = "";
    numero_camas.value = "";

    agua.value = "";
    luz.value = "";
    cocina.value = "";
    deposicion.value = "";
    basuras.value = "";
    animales_vectores.value = "";

    fam_cod.value = "";
    fam_apellido.value = "";

    btnEliminar.disabled = true;
    lblEstado.textContent = "Modo: nuevo registro";
    renderFamiliasChips();
    openFirstAccordion();
  }

  function normalizeFamiliasFromAny(arr) {
    if (!Array.isArray(arr)) return [];
    const out = [];
    arr.forEach((f) => {
      if (!f) return;

      const codigo =
        (typeof f.codigo === "string" || typeof f.codigo === "number")
          ? String(f.codigo).trim()
          : (typeof f.cod_familia === "string" || typeof f.cod_familia === "number")
            ? String(f.cod_familia).trim()
            : (typeof f.codigo_familia === "string" || typeof f.codigo_familia === "number")
              ? String(f.codigo_familia).trim()
              : "";

      const apellido =
        (typeof f.apellido === "string")
          ? f.apellido
          : (typeof f.apellido_familia === "string")
            ? f.apellido_familia
            : "";

      if (!codigo && !apellido) return;
      out.push({ codigo, apellido });
    });

    const seen = new Set();
    const dedup = [];
    out.forEach((f) => {
      const key = f.codigo ? `c:${f.codigo}` : `a:${f.apellido}`;
      if (seen.has(key)) return;
      seen.add(key);
      dedup.push(f);
    });

    return dedup;
  }

  function fillForm(v) {
    currentId = v?.id != null ? String(v.id) : null;
    currentFamilias = normalizeFamiliasFromAny(v?.familias);

    cod_vivienda.value = v?.cod_vivienda || "";
    direccion.value = v?.direccion || "";
    sectorizacion.value = safe(v?.sectorizacion || "");
    syncSectorOptionsForForm();

    sector.value = safe(v?.sector || "");
    latitud.value = v?.latitud ?? "";
    longitud.value = v?.longitud ?? "";

    tipo_vivienda.value = v?.tipo_vivienda || "";
    tenencia.value = v?.tenencia || "";
    conservacion.value = v?.conservacion || "";
    calefaccion.value = v?.calefaccion || "";
    numero_piezas.value = v?.numero_piezas ?? "";
    numero_dormitorios.value = v?.numero_dormitorios ?? "";
    numero_camas.value = v?.numero_camas ?? "";

    const c = v?.condiciones || {};
    agua.value = c.agua || "";
    luz.value = c.luz || "";
    cocina.value = c.cocina || "";
    deposicion.value = c.deposicion || "";
    basuras.value = c.basuras || "";
    animales_vectores.value = c.animales_vectores || "";

    btnEliminar.disabled = !currentId;

    const labelCode = cod_vivienda.value || "(sin código)";
    const last = v?.ultima_actualizacion || v?.creado_en || "";
    lblEstado.textContent = `Editando: ${labelCode} · Últ. act: ${fmtDT(last)}`;

    renderFamiliasChips();
    openFirstAccordion();
    hydrateFamiliasApellidosIfMissing();
  }

  function gatherFormPayload() {
    return {
      id: currentId || "",
      cod_vivienda: (cod_vivienda.value || "").trim(),
      direccion: (direccion.value || "").trim(),
      sectorizacion: sectorizacion.value ? sectorizacion.value : "",
      sector: sector.value ? sector.value : "",
      latitud: toNum(latitud.value),
      longitud: toNum(longitud.value),

      tipo_vivienda: tipo_vivienda.value || "",
      tenencia: tenencia.value || "",
      conservacion: conservacion.value || "",
      calefaccion: calefaccion.value || "",
      numero_piezas: numero_piezas.value === "" ? "" : toNum(numero_piezas.value),
      numero_dormitorios: numero_dormitorios.value === "" ? "" : toNum(numero_dormitorios.value),
      numero_camas: numero_camas.value === "" ? "" : toNum(numero_camas.value),

      condiciones: {
        agua: agua.value || null,
        luz: luz.value || null,
        cocina: cocina.value || null,
        deposicion: deposicion.value || null,
        basuras: basuras.value || null,
        animales_vectores: animales_vectores.value || null,
      },

      familias: normalizeFamiliasFromAny(currentFamilias).map((f) => ({
        codigo: (f.codigo || "").trim(),
      })),
    };
  }

  function validatePayload(p) {
    if (!p.cod_vivienda && !p.direccion) {
      return "Debes ingresar al menos Código vivienda o Dirección/Referencia.";
    }
    return null;
  }

  function findSzName(id) {
    const it = cacheSectorizaciones.find(x => String(x.id) === String(id));
    return it?.nombre_sectorizacion || "";
  }
  function findSecName(id) {
    const it = cacheSectores.find(x => String(x.id) === String(id));
    return it?.nombre_sector || "";
  }

  /* =========================
     Familias chips
  ========================== */
  function renderFamiliasChips() {
    familiasList.innerHTML = "";

    if (!currentFamilias.length) {
      familiasList.innerHTML = `<div class="text-muted small">Sin familias asociadas todavía.</div>`;
      return;
    }

    currentFamilias.forEach((f, idx) => {
      const code = safe(f.codigo || "—");
      const ap = safe(f.apellido || "Familia");
      const label = `${code} · ${ap}`;

      familiasList.insertAdjacentHTML(
        "beforeend",
        `
        <div class="chip">
          <span>${esc(label)}</span>
          <button class="x" type="button" data-x="${idx}" title="Quitar">
            <i class="bi bi-x-lg"></i>
          </button>
        </div>
      `
      );
    });
  }

  // ✅ Agregar familia: por código (get.php) o por apellido/texto (list.php)
  async function addFamilia() {
    const cfRaw = (fam_cod.value || "").trim();
    const apRaw = (fam_apellido.value || "").trim();

    // Regla: si el usuario puso algo en "Código", eso manda (búsqueda exacta).
    // Si no, usamos apellido/texto (list.php).
    let mode = "text";
    let term = "";

    if (cfRaw) {
      mode = "code";
      term = cfRaw;
    } else if (apRaw) {
      mode = "text";
      term = apRaw;
    } else {
      Swal.fire({ icon: "warning", title: "Falta información", text: "Ingresa código o apellido de familia." });
      return;
    }

    // Si el usuario escribió un código en el input de apellido, igual lo aceptamos
    if (mode === "text" && isCodigoFamiliaLike(term)) {
      mode = "code";
    }

    // =========================
    // A) Código exacto -> get.php
    // =========================
    if (mode === "code") {
      const code = term.trim();

      const exists = currentFamilias.some(x => safe(x.codigo).trim() === code);
      if (exists) {
        Swal.fire({ icon: "info", title: "Ya existe", text: "Esa familia ya está asociada.", timer: 900, showConfirmButton: false });
        return;
      }

      try {
        Swal.fire({ title: "Validando familia...", didOpen: () => Swal.showLoading(), allowOutsideClick: false });

        const fam = await fetchFamiliaByCodigo(code);
        if (!fam) {
          Swal.fire({ icon: "error", title: "No encontrada", text: `El código ${code} no existe en familias.` });
          return;
        }

        currentFamilias.push({ codigo: fam.codigo, apellido: fam.apellido || "" });

        fam_cod.value = "";
        fam_apellido.value = "";
        renderFamiliasChips();

        Swal.fire({ icon: "success", title: "Familia agregada", timer: 700, showConfirmButton: false });
      } catch (e) {
        Swal.fire({ icon: "error", title: "Error", text: e?.message || "No se pudo validar la familia." });
      }
      return;
    }

    // =========================
    // B) Texto / Apellido -> list.php?q=...
    // =========================
    try {
      Swal.fire({ title: "Buscando familias...", didOpen: () => Swal.showLoading(), allowOutsideClick: false });

      const hits = await searchFamiliasByTexto(term);

      if (!hits.length) {
        Swal.fire({ icon: "warning", title: "Sin resultados", text: "No encontré familias con ese criterio." });
        return;
      }

      // Si hay 1 sola, agregar directo
      if (hits.length === 1) {
        const f = hits[0];
        const exists = currentFamilias.some(x => safe(x.codigo).trim() === safe(f.codigo).trim());
        if (!exists) currentFamilias.push({ codigo: f.codigo, apellido: f.apellido || term });

        fam_cod.value = "";
        fam_apellido.value = "";
        renderFamiliasChips();

        Swal.fire({ icon: "success", title: "Familia agregada", timer: 800, showConfirmButton: false });
        return;
      }

      // Si hay varias: selector
      const slice = hits.slice(0, 25);

      const inputOptions = {};
      slice.forEach((f) => {
        const sz = f.sectorizacion ? (findSzName(f.sectorizacion) || f.sectorizacion) : "—";
        const sc = f.sector ? (findSecName(f.sector) || f.sector) : "—";
        const dir = f.direccion || "(sin dirección)";
        inputOptions[f.codigo] = `${f.codigo} · ${f.apellido || term} · ${dir} · ${sz}/${sc}`;
      });

      const pick = await Swal.fire({
        icon: "question",
        title: "Selecciona la familia",
        input: "select",
        inputOptions,
        inputPlaceholder: "Seleccionar...",
        showCancelButton: true,
        confirmButtonText: "Agregar",
        cancelButtonText: "Cancelar",
        inputValidator: (v) => (!v ? "Debes seleccionar una opción." : undefined),
      });

      if (!pick.isConfirmed) return;

      const chosen = slice.find(x => safe(x.codigo) === safe(pick.value));
      if (!chosen) return;

      const exists = currentFamilias.some(x => safe(x.codigo).trim() === safe(chosen.codigo).trim());
      if (!exists) currentFamilias.push({ codigo: chosen.codigo, apellido: chosen.apellido || term });

      fam_cod.value = "";
      fam_apellido.value = "";
      renderFamiliasChips();

      Swal.fire({ icon: "success", title: "Familia agregada", timer: 800, showConfirmButton: false });
    } catch (e) {
      Swal.fire({ icon: "error", title: "Error", text: e?.message || "No se pudo buscar familias." });
    }
  }

  /* =========================
     List (server-side) - Vivienda
  ========================== */
  function currentListFilters() {
    return {
      q: (qList.value || "").trim(),
      sectorizacion: (fSz.value || "").trim(),
      sector: (fSec.value || "").trim(),
      tipo: (fTipo.value || "").trim(),
    };
  }

  async function applyListFilters() {
    const seq = ++listReqSeq;
    const F = currentListFilters();

    try {
      resultsList.innerHTML = `<div class="p-3 text-muted">Cargando listado...</div>`;

      const res = await fetchJSON(API.list, {
        method: "POST",
        body: JSON.stringify({
          q: F.q,
          sectorizacion: F.sectorizacion,
          sector: F.sector,
          tipo: F.tipo,
        }),
      });

      if (seq !== listReqSeq) return;

      const rows = pickArrayPayload(res);

      all = Array.isArray(rows) ? rows : [];
      filtered = all.slice();

      renderKPIs(res?.total);
      renderList();
    } catch (e) {
      if (seq !== listReqSeq) return;

      resultsList.innerHTML = `<div class="p-3 text-muted">No se pudo cargar el listado.</div>`;
      lblTotal.textContent = "—";
      lblCount.textContent = "Coincidencias: —";

      Swal.fire({
        icon: "error",
        title: "Error",
        text: e?.message || "No se pudo cargar el listado.",
      });
    }
  }

  function renderKPIs(serverTotal) {
    const shown = filtered.length;
    lblTotal.textContent = `${shown} total`;
    lblCount.textContent = `Coincidencias: ${shown}`;
    void serverTotal;
  }

  function sectorLabelFromRow(v) {
    const szName = v?.nombre_sectorizacion || "";
    const scName = v?.nombre_sector || "";
    const szVal = v?.sectorizacion ?? "—";
    const scVal = v?.sector ?? "—";

    const left = szName ? szName : szVal;
    const right = scName ? scName : scVal;
    return `${left} / ${right}`;
  }

  function renderList() {
    resultsList.innerHTML = "";

    if (!filtered.length) {
      resultsList.innerHTML = `<div class="p-3 text-muted">No hay viviendas que coincidan con los filtros.</div>`;
      return;
    }

    const limit = 250;
    const slice = filtered.slice(0, limit);

    slice.forEach((v) => {
      const id = v?.id != null ? String(v.id) : "";
      const code = v?.cod_vivienda || "Sin código";
      const dir = v?.direccion || "Sin dirección";
      const sectorTxt = sectorLabelFromRow(v);

      const famCount =
        (typeof v?.familias_count === "number" ? v.familias_count : null) ??
        (Array.isArray(v?.familias) ? v.familias.length : 0);

      resultsList.insertAdjacentHTML(
        "beforeend",
        `
        <div class="result-item" data-open="${esc(id)}">
          <div>
            <div class="name">${esc(code)}</div>
            <div class="meta">${esc(dir)} · Sector: ${esc(sectorTxt)} · Familias: ${esc(famCount)}</div>
          </div>
          <div class="badges">
            <button class="btn-mini" type="button" data-act="edit" data-id="${esc(id)}" title="Editar">
              <i class="bi bi-pencil-square"></i>
            </button>
            <button class="btn-mini" type="button" data-act="del" data-id="${esc(id)}" title="Eliminar">
              <i class="bi bi-trash3"></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.</div>`
      );
    }
  }

  async function openById(id) {
    if (!id) return;

    try {
      const res = await fetchJSON(`${API.get}?id=${encodeURIComponent(id)}`);
      fillForm(res.data || {});
      window.scrollTo({ top: 0, behavior: "smooth" });
    } catch (e) {
      Swal.fire({
        icon: "error",
        title: "Error",
        text: e?.message || "No se pudo abrir la vivienda.",
      });
    }
  }

  /* =========================
     CRUD
  ========================== */
  async function saveCurrent() {
    const payload = gatherFormPayload();
    const err = validatePayload(payload);
    if (err) {
      Swal.fire({ icon: "warning", title: "Revisa el formulario", text: err });
      return;
    }

    try {
      Swal.fire({
        title: "Guardando...",
        didOpen: () => Swal.showLoading(),
        allowOutsideClick: false,
      });

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

      const newId = res?.data?.id != null ? String(res.data.id) : currentId;
      const newCode = res?.data?.cod_vivienda || payload.cod_vivienda;

      currentId = newId || currentId;
      if (newCode) cod_vivienda.value = newCode;

      Swal.fire({
        icon: "success",
        title: "Guardado",
        text: res?.message || "Vivienda guardada",
        timer: 900,
        showConfirmButton: false,
      });

      await applyListFilters();
      if (currentId) await openById(currentId);
      else resetForm();
    } catch (e) {
      Swal.fire({
        icon: "error",
        title: "Error",
        text: e?.message || "No se pudo guardar.",
      });
    }
  }

  async function deleteById(id) {
    if (!id) return;

    const v = all.find((x) => String(x?.id) === String(id));
    const code = v?.cod_vivienda || cod_vivienda.value || "Sin código";

    const res = await Swal.fire({
      icon: "warning",
      title: "¿Eliminar vivienda?",
      text: `${code} · Esta acción no se puede deshacer.`,
      showCancelButton: true,
      confirmButtonText: "Sí, eliminar",
      cancelButtonText: "Cancelar",
    });

    if (!res.isConfirmed) return;

    try {
      Swal.fire({
        title: "Eliminando...",
        didOpen: () => Swal.showLoading(),
        allowOutsideClick: false,
      });

      await fetchJSON(`${API.del}?id=${encodeURIComponent(id)}`);

      Swal.fire({
        icon: "success",
        title: "Eliminada",
        timer: 900,
        showConfirmButton: false,
      });

      if (currentId && String(currentId) === String(id)) resetForm();
      await applyListFilters();
    } catch (e) {
      Swal.fire({
        icon: "error",
        title: "Error",
        text: e?.message || "No se pudo eliminar.",
      });
    }
  }

  async function generateCodigoFromServer() {
    try {
      const res = await fetchJSON(API.nextCode);
      const code = res?.code || res?.data?.code || "";
      if (!code) throw new Error("No se recibió código.");
      cod_vivienda.value = code;

      Swal.fire({
        icon: "success",
        title: "Código generado",
        timer: 700,
        showConfirmButton: false,
      });
    } catch (e) {
      Swal.fire({
        icon: "error",
        title: "Error",
        text: e?.message || "No se pudo generar el código.",
      });
    }
  }

  function captureGPS() {
    if (!navigator.geolocation) {
      Swal.fire({
        icon: "error",
        title: "No disponible",
        text: "Este dispositivo/navegador no permite geolocalización.",
      });
      return;
    }

    Swal.fire({
      title: "Capturando GPS...",
      didOpen: () => Swal.showLoading(),
      allowOutsideClick: false,
    });

    navigator.geolocation.getCurrentPosition(
      (pos) => {
        const { latitude, longitude } = pos.coords;
        latitud.value = String(latitude);
        longitud.value = String(longitude);

        Swal.fire({
          icon: "success",
          title: "GPS capturado",
          text: `${latitude.toFixed(6)}, ${longitude.toFixed(6)}`,
          timer: 1300,
          showConfirmButton: false,
        });
      },
      (err) => {
        Swal.fire({
          icon: "error",
          title: "No se pudo capturar GPS",
          text: err.message || "Permisos denegados o señal débil.",
        });
      },
      { enableHighAccuracy: true, timeout: 12000, maximumAge: 0 }
    );
  }

  /* =========================
     Config: sectorización / sectores
  ========================== */
  function setSelectOptions(selectEl, items, placeholderText, labelKey, valueKey) {
    if (!selectEl) return;
    if (!Array.isArray(items) || !items.length) return;

    const cur = selectEl.value;
    let html = `<option value="">${esc(placeholderText || "Seleccionar")}</option>`;
    items.forEach((it) => {
      const val = safe(it?.[valueKey] ?? it?.id ?? "");
      const lab = safe(it?.[labelKey] ?? it?.nombre ?? it?.name ?? val);
      html += `<option value="${esc(val)}">${esc(lab)}</option>`;
    });

    selectEl.innerHTML = html;

    if (cur && Array.from(selectEl.options).some((o) => o.value === cur)) {
      selectEl.value = cur;
    }
  }

  function syncSectorOptionsForForm() {
    const sz = sectorizacion.value || "";
    const list = sz
      ? cacheSectores.filter((s) => safe(s.sectorizacion) === safe(sz))
      : cacheSectores.slice();

    if (list.length) {
      setSelectOptions(sector, list, "Seleccionar", "nombre_sector", "id");
    }
  }

  function syncSectorOptionsForFilter() {
    const sz = fSz.value || "";
    const list = sz
      ? cacheSectores.filter((s) => safe(s.sectorizacion) === safe(sz))
      : cacheSectores.slice();

    if (list.length) {
      setSelectOptions(fSec, list, "Todos", "nombre_sector", "id");
    }
  }

  async function loadConfigSectorizacion() {
    const tryUrls = [
      `${API_SECTORIZACION}/list.php`,
      `${API_SECTORIZACION}/listar.php`,
      `${API_SECTORIZACION}/index.php`,
    ];

    let data = null;

    for (const u of tryUrls) {
      data = await fetchJSONSoft(u);
      if (data) break;
    }
    if (!data) {
      data = await fetchJSONSoft(`${API_SECTORIZACION}/list.php`, {
        method: "POST",
        body: JSON.stringify({}),
      });
    }

    const arr = pickArrayPayload(data);
    if (!arr.length) return;

    cacheSectorizaciones = arr.map((x) => ({
      id: x.id ?? x.ID ?? x.Id,
      nombre_sectorizacion:
        x.nombre_sectorizacion ?? x.nombre ?? x.name ?? x.titulo ?? x.label,
      color_sector: x.color_sector ?? x.color ?? null,
    }));

    setSelectOptions(sectorizacion, cacheSectorizaciones, "Seleccionar", "nombre_sectorizacion", "id");
    setSelectOptions(fSz, cacheSectorizaciones, "Todas", "nombre_sectorizacion", "id");
  }

  async function loadConfigSectores() {
    const tryUrls = [
      `${API_SECTORES}/list.php`,
      `${API_SECTORES}/listar.php`,
      `${API_SECTORES}/index.php`,
    ];

    let data = null;

    for (const u of tryUrls) {
      data = await fetchJSONSoft(u);
      if (data) break;
    }
    if (!data) {
      data = await fetchJSONSoft(`${API_SECTORES}/list.php`, {
        method: "POST",
        body: JSON.stringify({}),
      });
    }

    const arr = pickArrayPayload(data);
    if (!arr.length) return;

    cacheSectores = arr.map((x) => ({
      id: x.id ?? x.ID ?? x.Id,
      nombre_sector: x.nombre_sector ?? x.nombre ?? x.name ?? x.label,
      sectorizacion: x.sectorizacion ?? x.id_sectorizacion ?? x.sz ?? null,
    }));

    syncSectorOptionsForForm();
    syncSectorOptionsForFilter();
  }

  /* =========================
     UX: acordeón exclusivo
  ========================== */
  function setupExclusiveAccordions() {
    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");
        });
      });
    });
  }

  /* =========================
     Events
  ========================== */
  btnNuevo.addEventListener("click", () => resetForm());
  btnCancelar.addEventListener("click", () => resetForm());

  btnGuardar.addEventListener("click", saveCurrent);

  btnEliminar.addEventListener("click", () => {
    if (currentId) deleteById(currentId);
  });

  btnGenCodigo.addEventListener("click", generateCodigoFromServer);
  btnCapturarGPS.addEventListener("click", captureGPS);

  // ✅ agregar familia
  btnAddFamilia.addEventListener("click", () => addFamilia());

  // ✅ ENTER en inputs = agregar
  fam_cod?.addEventListener("keydown", (e) => {
    if (e.key === "Enter") { e.preventDefault(); addFamilia(); }
  });
  fam_apellido?.addEventListener("keydown", (e) => {
    if (e.key === "Enter") { e.preventDefault(); addFamilia(); }
  });

  // ✅ si escribes código y sales del input, autocompleta apellido si existe
  fam_cod.addEventListener("blur", async () => {
    const cf = (fam_cod.value || "").trim();
    if (!cf) return;
    if ((fam_apellido.value || "").trim()) return;

    const fam = await fetchFamiliaByCodigo(cf).catch(() => null);
    if (fam && fam.apellido) fam_apellido.value = fam.apellido;
  });

  familiasList.addEventListener("click", (e) => {
    const b = e.target.closest("[data-x]");
    if (!b) return;
    const idx = Number(b.dataset.x);
    currentFamilias.splice(idx, 1);
    renderFamiliasChips();
  });

  sectorizacion.addEventListener("change", () => {
    syncSectorOptionsForForm();
    if (
      sector.value &&
      !cacheSectores.some(
        (s) =>
          safe(s.id) === safe(sector.value) &&
          (!sectorizacion.value || safe(s.sectorizacion) === safe(sectorizacion.value))
      )
    ) {
      sector.value = "";
    }
  });

  fSz.addEventListener("change", () => {
    syncSectorOptionsForFilter();
    if (
      fSec.value &&
      !cacheSectores.some(
        (s) =>
          safe(s.id) === safe(fSec.value) &&
          (!fSz.value || safe(s.sectorizacion) === safe(fSz.value))
      )
    ) {
      fSec.value = "";
    }
  });

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

  const applyListDebounced = debounce(applyListFilters, 300);

  qList.addEventListener("input", applyListDebounced);
  fSec.addEventListener("change", applyListDebounced);
  fTipo.addEventListener("change", applyListDebounced);

  btnClearList.addEventListener("click", () => {
    qList.value = "";
    applyListFilters();
  });

  btnAplicarList.addEventListener("click", applyListFilters);

  btnResetList.addEventListener("click", () => {
    qList.value = "";
    fSz.value = "";
    fSec.value = "";
    fTipo.value = "";
    syncSectorOptionsForFilter();
    applyListFilters();
  });

  // ✅ click en fila abre (excepto cuando apretas botones)
  resultsList.addEventListener("click", (e) => {
    const btn = e.target.closest("[data-act]");
    if (btn) {
      const act = btn.dataset.act;
      const id = btn.dataset.id;
      if (act === "edit") openById(id);
      if (act === "del") deleteById(id);
      return;
    }

    const row = e.target.closest("[data-open]");
    if (row) openById(row.dataset.open);
  });

  /* =========================
     Init
  ========================== */
  async function init() {
    setupExclusiveAccordions();

    try { await loadConfigSectorizacion(); } catch (_) { }
    try { await loadConfigSectores(); } catch (_) { }

    resetForm();
    await applyListFilters();
  }

  init();
})();
