import { Icon } from "@iconify/react";
import { useCallback, useEffect, useRef, useState } from "react";
import { createPortal } from "react-dom";
import { useSearchParams } from "react-router-dom";
import { RingLoader } from "react-spinners";
import { toast } from "sonner";
import styled, { keyframes } from "styled-components";
import * as XLSX from "xlsx";
import {
  actualizarFechaRecibo,
  anularCobro,
  getCajasFiltroRecibos,
  getCobro,
  getCobros,
  getReciboPdf,
  reintentarEmisionIntereses,
} from "../../api/cobros.service";
import { getCobradoresActivos } from "../../api/vendedores-cobradores.service";
import { printReciboWithFallback } from "../../services/printAgentService";
import { usePosConfigQuery } from "../../tanstack/PosConfigStack";
import { useSesionActivaQuery } from "../../tanstack/TesoreriaStack";
import { useSubmodulo } from "../../hooks/useSubmodulo";
import { usePermission } from "../../hooks/usePermission";
import { NuevoReciboMultiWizard } from "./RecibosMulti/NuevoReciboMultiWizard";
import {
  getReciboMulti,
  anularReciboMulti,
  getReciboMultiPdfBlob,
} from "../../api/recibos-multi.service";
import { aFechaISO, fmtFechaCalendario, fmtFechaHora, hoyISO } from "../../utils/fecha";
import { sanitizeRecords } from "../_standards";

// ─── Helpers ─────────────────────────────────────────────────────────────────
const fmtMoney = (v, moneda) => {
  const num = Number(v || 0);
  if (!moneda || moneda?.codigo === "PYG" || !moneda?.codigo)
    return `Gs. ${num.toLocaleString("es-PY", { maximumFractionDigits: 0 })}`;
  return `${moneda.simbolo || moneda.codigo} ${num.toLocaleString("es-PY", {
    minimumFractionDigits: 2,
    maximumFractionDigits: 2,
  })}`;
};

const fmtFecha = (str) => fmtFechaCalendario(str);

// Valor sentinela del filtro: recibos sin sesión de caja (debe coincidir con
// SIN_CAJA_FILTRO del backend).
const SIN_CAJA = "SIN_CAJA";

/**
 * Auditoría de caja de un recibo: en qué caja/sesión entró la plata.
 * Los cobros mobile los hacen cobradores sin caja propia, así que entran sin
 * sesión y se auditan por la rendición donde el cobrador entrega el efectivo.
 */
const infoCaja = (r) => {
  const ses = r?.sesiones_caja;
  if (ses) {
    const quien = [ses.usuario?.nombres, ses.usuario?.apellidos]
      .filter(Boolean)
      .join(" ")
      .trim();
    const apertura = ses.fecha_apertura ? fmtFecha(ses.fecha_apertura) : null;
    const sub = [
      apertura ? `abierta ${apertura}` : null,
      quien ? `por ${quien}` : null,
      ses.estado ? ses.estado.toLowerCase() : null,
    ]
      .filter(Boolean)
      .join(" · ");
    return {
      texto: ses.cajas?.descripcion || "Caja sin nombre",
      sub: sub || null,
    };
  }

  const rend = r?.rendiciones_cobranza;
  if (rend) {
    const ref = rend.codigo || (rend.numero != null ? `N° ${rend.numero}` : null);
    return {
      texto: "Sin caja",
      sub: `rendición ${[ref, rend.estado?.toLowerCase()].filter(Boolean).join(" · ")}`,
    };
  }

  return { texto: "Sin caja", sub: "pendiente de rendir", $amber: true };
};

const getISODate = (daysOffset = 0) => {
  const d = new Date();
  d.setDate(d.getDate() + daysOffset);
  return d.toISOString().split("T")[0];
};

const QUICK_RANGES = [
  { label: "Personalizado", desde: null, hasta: null },
  { label: "Hoy",           desde: () => getISODate(0),    hasta: () => getISODate(0) },
  { label: "Ayer",          desde: () => getISODate(-1),   hasta: () => getISODate(-1) },
  { label: "Última semana", desde: () => getISODate(-6),   hasta: () => getISODate(0) },
  { label: "Último mes",    desde: () => getISODate(-29),  hasta: () => getISODate(0) },
  { label: "Últimos 3 m",   desde: () => getISODate(-89),  hasta: () => getISODate(0) },
];

const PAGE_SIZE = 20;

// ─── Componente ───────────────────────────────────────────────────────────────
export function RecibosPanel() {
  const { hasAccess: tieneModuloMora } = useSubmodulo("ADM_INTERES_MORA");
  const { hasAccess: tienePrintMobile } = useSubmodulo("COB_PRINT_MOBILE");
  const { hasAccess: tieneSubmoduloCobradores } = useSubmodulo("COB_COBRADORES");
  const { can: canCobranzasPanel } = usePermission("COBRANZAS");
  const tienePanelCobrador = tieneSubmoduloCobradores && canCobranzasPanel("COB_CBR_PANEL_COBRADOR_VER");
  const { can: canCobranzas } = usePermission("COBRANZAS");
  const { can: canTesoreria } = usePermission("TESORERIA");
  const puedeEditarRecibo = canCobranzas("COB_REC_RECIBO_EDITAR");
  const puedeAnularRecibo = canCobranzas("COB_REC_RECIBO_ANULAR");

  const { data: sesionCaja } = useSesionActivaQuery(null, { enabled: canTesoreria("TES_CAJ_CAJA_VER") });
  const { data: posConfig } = usePosConfigQuery(sesionCaja?.caja_id);

  useEffect(() => {
    if (posConfig?.printer_name) {
      localStorage.setItem("pos_printer_name", posConfig.printer_name);
      localStorage.setItem("pos_paper_width", String(posConfig.printer_paper_width || 80));
    }
  }, [posConfig]);

  const [recibos, setRecibos] = useState([]);
  const [loading, setLoading] = useState(false);
  const [meta, setMeta] = useState({ total: 0, pages: 1 });
  const [page, setPage] = useState(1);

  // Filtros
  const [busqueda, setBusqueda] = useState("");
  const [busquedaInput, setBusquedaInput] = useState(""); // input sin debounce
  const [quickKey, setQuickKey] = useState("Último mes");
  const [fechaDesde, setFechaDesde] = useState(getISODate(-29));
  const [fechaHasta, setFechaHasta] = useState(getISODate(0));
  const [estado, setEstado] = useState("");
  const [modo, setModo] = useState("");
  const [cobradorId, setCobradorId] = useState("");
  const [cajaId, setCajaId] = useState("");

  // Ordenamiento server-side
  const [sortBy, setSortBy] = useState("fecha_emision");
  const [sortDir, setSortDir] = useState("desc");

  // Recibos Multi
  const [wizardMultiOpen, setWizardMultiOpen] = useState(false);
  const [multiDetalle, setMultiDetalle] = useState(null);
  const [multiLoading, setMultiLoading] = useState(false);
  const [cobradores, setCobradores] = useState([]);
  const [cobradoresLoaded, setCobradoresLoaded] = useState(false);
  const [cajasFiltro, setCajasFiltro] = useState({ cajas: [], sin_caja_total: 0 });

  // Detalle
  const [selectedRecibo, setSelectedRecibo] = useState(null);

  // Anular
  const [anularModal, setAnularModal] = useState(null);
  const [motivoAnulacion, setMotivoAnulacion] = useState("");
  const [anulando, setAnulando] = useState(false);

  // Modificar fecha
  const [fechaModal, setFechaModal] = useState(null);
  const [nuevaFecha, setNuevaFecha] = useState("");
  const [motivoFecha, setMotivoFecha] = useState("");
  const [guardandoFecha, setGuardandoFecha] = useState(false);

  // PDF
  const [loadingPdf, setLoadingPdf] = useState(null);
  const [pdfPreview, setPdfPreview] = useState(null);

  // Menú de acciones por fila. Se renderiza en un portal con position:fixed
  // anclado al botón, para que NO lo recorte el overflow de la tabla (evita
  // que abrir el menú genere un scroll vertical dentro del listado).
  const [actionMenuId, setActionMenuId] = useState(null);
  const [actionMenuAnchor, setActionMenuAnchor] = useState(null);
  const abrirActionMenu = useCallback((e, id) => {
    e.stopPropagation();
    if (actionMenuId === id) {
      setActionMenuId(null);
      setActionMenuAnchor(null);
      return;
    }
    setActionMenuAnchor(e.currentTarget.getBoundingClientRect());
    setActionMenuId(id);
  }, [actionMenuId]);
  useEffect(() => {
    if (!actionMenuId) return;
    const close = () => {
      setActionMenuId(null);
      setActionMenuAnchor(null);
    };
    // El menú es fixed y no sigue al scroll → cerrarlo al hacer scroll/resize.
    window.addEventListener("click", close);
    window.addEventListener("scroll", close, true);
    window.addEventListener("resize", close);
    return () => {
      window.removeEventListener("click", close);
      window.removeEventListener("scroll", close, true);
      window.removeEventListener("resize", close);
    };
  }, [actionMenuId]);

  // ── Carga ─────────────────────────────────────────────────────────────────
  const cargar = useCallback(
    async (pg = 1) => {
      setLoading(true);
      try {
        const skip = (pg - 1) * PAGE_SIZE;
        // Al buscar por texto se busca en todo el historial → ignorar el rango
        // de fechas (si no, un recibo de otro período no aparecería aunque
        // coincida). Mismo comportamiento que facturas venta y compras.
        const buscando = Boolean(busqueda.trim());
        const data = await getCobros({
          fechaDesde: buscando ? undefined : fechaDesde || undefined,
          fechaHasta: buscando ? undefined : fechaHasta || undefined,
          estado: estado || undefined,
          modo: modo || undefined,
          cobradorId: cobradorId || undefined,
          cajaId: cajaId || undefined,
          busqueda: buscando ? busqueda.trim() : undefined,
          orderBy: sortBy || undefined,
          order: sortDir || undefined,
          skip,
          take: PAGE_SIZE,
        });
        setRecibos(data.data || []);
        const total = data.meta?.total || 0;
        setMeta({ total, pages: Math.max(1, Math.ceil(total / PAGE_SIZE)) });
        setPage(pg);
      } catch {
        toast.error("Error al cargar recibos");
      } finally {
        setLoading(false);
      }
    },
    [fechaDesde, fechaHasta, estado, modo, cobradorId, cajaId, busqueda, sortBy, sortDir]
  );

  const handleSort = (col) => {
    if (sortBy === col) {
      setSortDir((d) => (d === "asc" ? "desc" : "asc"));
    } else {
      setSortBy(col);
      setSortDir("asc");
    }
  };

  const sortIndicator = (col) => {
    if (sortBy !== col) return " ↕";
    return sortDir === "asc" ? " ▲" : " ▼";
  };

  useEffect(() => {
    getCobradoresActivos()
      .then((d) => { setCobradores(Array.isArray(d) ? d : d?.data || []); setCobradoresLoaded(true); })
      .catch(() => setCobradoresLoaded(true));
  }, []);

  useEffect(() => {
    getCajasFiltroRecibos()
      .then((d) => setCajasFiltro({
        cajas: Array.isArray(d?.cajas) ? d.cajas : [],
        sin_caja_total: d?.sin_caja_total || 0,
      }))
      .catch(() => {});
  }, []);

  useEffect(() => {
    cargar(1);
  }, [cargar]);

  // Deep-link `?recibo=<id>`: cuando se entra desde el Reporte de Intereses
  // (o cualquier otra vista) abre el modal del recibo apenas se carga la lista.
  // Una sola vez por sesión de búsqueda.
  const deepLinkApliedRef = useRef(false);
  const [deepLinkParams] = useSearchParams();
  useEffect(() => {
    if (deepLinkApliedRef.current) return;
    const targetId = deepLinkParams.get("recibo");
    if (!targetId || recibos.length === 0) return;
    const match = recibos.find((r) => r.id === targetId);
    if (match) {
      setSelectedRecibo(match);
      deepLinkApliedRef.current = true;
    }
  }, [deepLinkParams, recibos]);

  // Cargar detalle completo (intereses_cobrados con factura emitida) al abrir
  // un recibo normal — el listado no trae esa data.
  useEffect(() => {
    if (!selectedRecibo?.id || selectedRecibo.modo === "MULTI") return;
    if (selectedRecibo.intereses_cobrados !== undefined) return;
    let cancel = false;
    getCobro(selectedRecibo.id)
      .then((full) => {
        if (cancel || !full) return;
        setSelectedRecibo((prev) =>
          prev && prev.id === full.id ? { ...prev, ...full } : prev,
        );
      })
      .catch(() => {});
    return () => {
      cancel = true;
    };
  }, [selectedRecibo?.id]);

  const [reintentando, setReintentando] = useState(false);
  const handleReintentarEmision = useCallback(async () => {
    if (!selectedRecibo?.id || reintentando) return;
    setReintentando(true);
    try {
      const res = await reintentarEmisionIntereses(selectedRecibo.id);
      if (res?.factura_cab_id) {
        toast.success("Factura de intereses emitida correctamente");
      } else {
        toast.info(`Emisión procesada (${res?.emitidos ?? 0} emitidos, ${res?.saltados ?? 0} saltados)`);
      }
      const full = await getCobro(selectedRecibo.id);
      if (full) setSelectedRecibo((prev) => (prev && prev.id === full.id ? { ...prev, ...full } : prev));
    } catch (err) {
      toast.error(err?.response?.data?.message || "No se pudo reintentar la emisión");
    } finally {
      setReintentando(false);
    }
  }, [selectedRecibo?.id, reintentando]);

  // Cargar detalle multi cuando se abre un recibo MULTI
  useEffect(() => {
    if (selectedRecibo?.modo !== "MULTI") {
      setMultiDetalle(null);
      return;
    }
    let cancel = false;
    setMultiLoading(true);
    getReciboMulti(selectedRecibo.id)
      .then((d) => { if (!cancel) setMultiDetalle(d); })
      .catch(() => { if (!cancel) setMultiDetalle(null); })
      .finally(() => { if (!cancel) setMultiLoading(false); });
    return () => { cancel = true; };
  }, [selectedRecibo]);

  const handleBuscar = () => {
    setBusqueda(busquedaInput);
  };

  const handleQuickChange = (label) => {
    setQuickKey(label);
    const rng = QUICK_RANGES.find((r) => r.label === label);
    if (rng && rng.desde) {
      setFechaDesde(rng.desde());
      setFechaHasta(rng.hasta());
    }
  };

  const handleFechaChange = (field, val) => {
    if (field === "desde") setFechaDesde(val);
    else setFechaHasta(val);
    setQuickKey("Personalizado");
  };

  // ── Anular ────────────────────────────────────────────────────────────────
  const confirmarAnular = async () => {
    if (!anularModal) return;
    setAnulando(true);
    try {
      if (anularModal.modo === "MULTI") {
        if (!motivoAnulacion.trim()) {
          toast.error("Ingresá un motivo de anulación");
          setAnulando(false);
          return;
        }
        await anularReciboMulti(anularModal.id, { motivo: motivoAnulacion.trim() });
      } else {
        await anularCobro(anularModal.id, motivoAnulacion);
      }
      toast.success(`Recibo #${anularModal.numero_recibo} anulado`);
      setAnularModal(null);
      setMotivoAnulacion("");
      if (selectedRecibo?.id === anularModal.id) setSelectedRecibo(null);
      cargar(page);
    } catch (err) {
      toast.error(err?.response?.data?.message || "Error al anular");
    } finally {
      setAnulando(false);
    }
  };

  // ── Modificar fecha ───────────────────────────────────────────────────────
  const guardarFecha = async () => {
    if (!fechaModal || !nuevaFecha) return;
    setGuardandoFecha(true);
    try {
      const res = await actualizarFechaRecibo(fechaModal.id, nuevaFecha, motivoFecha);
      const d = res?.data;
      const extra = [
        d?.asientos?.length ? `${d.asientos.length} asiento(s)` : null,
        d?.movimientos_tesoreria ? `${d.movimientos_tesoreria} movimiento(s) de tesorería` : null,
      ].filter(Boolean).join(" y ");
      toast.success(`Recibo #${fechaModal.numero_recibo}: fecha cambiada a ${fmtFechaCalendario(nuevaFecha)}${extra ? ` (también ${extra})` : ""}`);
      setFechaModal(null);
      setNuevaFecha("");
      setMotivoFecha("");
      cargar(page);
    } catch (err) {
      toast.error(err?.response?.data?.message || "Error al actualizar la fecha");
    } finally {
      setGuardandoFecha(false);
    }
  };

  // ── Reimprimir vía Novasis Print / QZ / browser ───────────────────────────
  const handleReimprimir = async (reciboId) => {
    setLoadingPdf(reciboId + "print");
    try {
      const printerName = posConfig?.printer_name || localStorage.getItem("pos_printer_name") || null;
      const paperWidth = posConfig?.printer_paper_width || Number(localStorage.getItem("pos_paper_width")) || 80;
      const mobileEnabled = tienePrintMobile && !!posConfig?.print_mobile_enabled;

      const { method, agentError } = await printReciboWithFallback({
        cobroId: reciboId,
        printerName,
        paperWidth,
        mobileEnabled,
        agentRetries: 1,
        getPdfBlob: (anchoOverride) =>
          getReciboPdf(reciboId, { formato: "ticket", paper_width: anchoOverride || paperWidth }),
      });

      if (method === "agent") {
        toast.success("Ticket enviado a la impresora Bluetooth");
      } else if (method === "qz") {
        toast.success("Ticket enviado a la impresora");
      } else {
        toast.info("Ticket abierto en el navegador para imprimir.");
      }
    } catch (err) {
      toast.error(`Error al reimprimir: ${err?.message || "Error desconocido"}`);
    } finally {
      setLoadingPdf(null);
    }
  };

  // ── PDF ───────────────────────────────────────────────────────────────────
  const verPdf = async (reciboId, formato = "a4", isMulti = false) => {
    setLoadingPdf(reciboId + formato);
    try {
      let blob;
      if (isMulti) {
        blob = await getReciboMultiPdfBlob(reciboId);
      } else {
        const paperWidth = posConfig?.printer_paper_width || Number(localStorage.getItem("pos_paper_width")) || 80;
        blob = await getReciboPdf(reciboId, { formato, paper_width: paperWidth });
      }
      const url = URL.createObjectURL(blob);
      setPdfPreview({ url, formato, reciboId });
    } catch {
      toast.error("Error al obtener PDF");
    } finally {
      setLoadingPdf(null);
    }
  };

  const cerrarPdf = () => {
    if (pdfPreview?.url) URL.revokeObjectURL(pdfPreview.url);
    setPdfPreview(null);
  };

  // ── Exportar Excel (todos los resultados del filtro, sin paginación) ───────
  const [exportando, setExportando] = useState(false);

  const exportarExcel = async () => {
    if (exportando) return;
    setExportando(true);
    try {
      const buscando = Boolean(busqueda.trim());
      const data = await getCobros({
        fechaDesde: buscando ? undefined : fechaDesde || undefined,
        fechaHasta: buscando ? undefined : fechaHasta || undefined,
        estado: estado || undefined,
        cobradorId: cobradorId || undefined,
        cajaId: cajaId || undefined,
        busqueda: buscando ? busqueda.trim() : undefined,
        skip: 0,
        take: 10000,
      });
      const todos = data?.data || [];
      if (!todos.length) return;
      const rows = todos.map((r) => {
        const caja = infoCaja(r);
        return {
        "N° Recibo": r.numero_recibo,
        Fecha: fmtFecha(r.fecha_emision),
        "Fecha Registro": r.fecha_registro ? fmtFecha(r.fecha_registro) : "",
        Cliente:
          r.clientes?.personas?.razon_social ||
          r.clientes?.nombre_fantasia ||
          "—",
        Cobrador: r.cobrador
          ? `${r.cobrador.nombre} ${r.cobrador.apellido}`
          : "—",
        Monto: Number(r.monto_total || 0),
        Descuento: Number(r.descuento_monto || 0),
        ...(tieneModuloMora ? { Mora: Number(r.mora_total || 0) } : {}),
        Moneda: r.moneda?.codigo || "PYG",
        Estado: r.estado,
        Caja: caja.texto,
        "Sesión / Rendición": caja.sub || "",
        Origen: r.origen || "web",
        };
      });
      const wb = XLSX.utils.book_new();
      const ws = XLSX.utils.json_to_sheet(sanitizeRecords(rows));
      XLSX.utils.book_append_sheet(wb, ws, "Recibos");
      XLSX.writeFile(wb, `recibos_${fechaDesde}_${fechaHasta}.xlsx`);
    } finally {
      setExportando(false);
    }
  };

  // ─────────────────────────────────────────────────────────────────────────
  return (
    <Wrap>
      {/* ── Filtros ── */}
      <FiltersCard>
        {/* Fila 1: búsqueda + período rápido */}
        <FilterRow>
          <SearchGroup>
            <SearchBox>
              <Icon icon="lucide:search" width={15} color="#9ca3af" />
              <SearchInput
                placeholder="Buscar por cliente, N° recibo..."
                value={busquedaInput}
                onChange={(e) => setBusquedaInput(e.target.value)}
                onKeyDown={(e) => e.key === "Enter" && handleBuscar()}
              />
              {busquedaInput && (
                <ClearBtn
                  onClick={() => { setBusquedaInput(""); setBusqueda(""); }}
                  title="Limpiar"
                >
                  <Icon icon="lucide:x" width={12} />
                </ClearBtn>
              )}
            </SearchBox>
            {busqueda.trim() && (
              <SearchHint>
                <Icon icon="lucide:info" width={12} />
                Buscando en todo el historial (ignora el filtro de fechas)
              </SearchHint>
            )}
          </SearchGroup>

          <FilterGroup>
            <FilterLabel>Período</FilterLabel>
            <CtrlSelect
              value={quickKey}
              onChange={(e) => handleQuickChange(e.target.value)}
              disabled={Boolean(busqueda.trim())}
              title={busqueda.trim() ? "Borrá la búsqueda para volver a filtrar por período" : undefined}
            >
              {QUICK_RANGES.map((r) => (
                <option key={r.label} value={r.label}>
                  {r.label}
                </option>
              ))}
            </CtrlSelect>
          </FilterGroup>

          <FilterGroup>
            <FilterLabel>Estado</FilterLabel>
            <CtrlSelect
              value={estado}
              onChange={(e) => setEstado(e.target.value)}
            >
              <option value="">Todos</option>
              <option value="emitido">Emitido</option>
              <option value="anulado">Anulado</option>
            </CtrlSelect>
          </FilterGroup>

          <FilterGroup>
            <FilterLabel>Tipo</FilterLabel>
            <CtrlSelect
              value={modo}
              onChange={(e) => setModo(e.target.value)}
            >
              <option value="">Todos</option>
              <option value="LEGACY">Normal</option>
              <option value="MULTI">Multi-factura</option>
            </CtrlSelect>
          </FilterGroup>

          {tienePanelCobrador && (
            <FilterGroup>
              <FilterLabel>Cobrador</FilterLabel>
              <CtrlSelect
                value={cobradorId}
                onChange={(e) => setCobradorId(e.target.value)}
                disabled={!cobradoresLoaded}
              >
                <option value="">{!cobradoresLoaded ? "Cargando..." : "Todos"}</option>
                {cobradores.map((c) => (
                  <option key={c.id} value={c.id}>
                    {c.nombre} {c.apellido}
                  </option>
                ))}
              </CtrlSelect>
            </FilterGroup>
          )}

          {(cajasFiltro.cajas.length > 0 || cajasFiltro.sin_caja_total > 0) && (
            <FilterGroup>
              <FilterLabel>Caja</FilterLabel>
              <CtrlSelect
                value={cajaId}
                onChange={(e) => setCajaId(e.target.value)}
                title="Auditoría: caja donde se registró el cobro"
              >
                <option value="">Todas</option>
                {cajasFiltro.cajas.map((c) => (
                  <option key={c.id} value={c.id}>
                    {c.descripcion || "Caja sin nombre"}
                  </option>
                ))}
                {cajasFiltro.sin_caja_total > 0 && (
                  <option value={SIN_CAJA}>
                    Sin caja ({cajasFiltro.sin_caja_total})
                  </option>
                )}
              </CtrlSelect>
            </FilterGroup>
          )}
        </FilterRow>

        {/* Fila 2: fechas + acciones */}
        <FilterRow $secondary>
          <FilterGroup>
            <FilterLabel>Desde</FilterLabel>
            <CtrlInput
              type="date"
              value={fechaDesde}
              onChange={(e) => handleFechaChange("desde", e.target.value)}
              disabled={Boolean(busqueda.trim())}
              title={busqueda.trim() ? "Borrá la búsqueda para volver a filtrar por período" : undefined}
            />
          </FilterGroup>
          <FilterGroup>
            <FilterLabel>Hasta</FilterLabel>
            <CtrlInput
              type="date"
              value={fechaHasta}
              onChange={(e) => handleFechaChange("hasta", e.target.value)}
              disabled={Boolean(busqueda.trim())}
              title={busqueda.trim() ? "Borrá la búsqueda para volver a filtrar por período" : undefined}
            />
          </FilterGroup>

          <ActionsGroup>
            <SearchBtn onClick={() => { setBusqueda(busquedaInput); cargar(1); }} disabled={loading}>
              {loading ? (
                <RingLoader color="currentColor" size={12} />
              ) : (
                <Icon icon="lucide:search" width={14} />
              )}
              Buscar
            </SearchBtn>
            <NewMultiBtn onClick={() => setWizardMultiOpen(true)}>
              <Icon icon="lucide:plus" width={14} />
              Nuevo recibo multi
            </NewMultiBtn>
            <ExportBtn onClick={exportarExcel} disabled={!recibos.length || exportando}>
              <Icon icon={exportando ? "lucide:loader" : "lucide:file-spreadsheet"} width={14} />
              {exportando ? "Exportando..." : `Excel (${meta.total})`}
            </ExportBtn>
          </ActionsGroup>

          <ResultInfo>
            {!loading && (
              <>
                <Icon icon="lucide:list" width={13} />
                {meta.total} recibo{meta.total !== 1 ? "s" : ""}
              </>
            )}
          </ResultInfo>
        </FilterRow>
      </FiltersCard>

      {/* ── Tabla ── */}
      <TableCard>
        <TableScroll>
          <Table>
            <thead>
              <tr>
                <Th style={{ cursor: "pointer" }} onClick={() => handleSort("numero_recibo")}>N° Recibo{sortIndicator("numero_recibo")}</Th>
                <Th style={{ cursor: "pointer" }} onClick={() => handleSort("modo")}>Tipo{sortIndicator("modo")}</Th>
                <Th style={{ cursor: "pointer" }} onClick={() => handleSort("fecha_emision")}>Fecha{sortIndicator("fecha_emision")}</Th>
                <Th style={{ cursor: "pointer" }} onClick={() => handleSort("cliente")}>Cliente{sortIndicator("cliente")}</Th>
                {tienePanelCobrador && (
                  <Th style={{ cursor: "pointer" }} onClick={() => handleSort("cobrador")}>Cobrador{sortIndicator("cobrador")}</Th>
                )}
                <Th $right style={{ cursor: "pointer" }} onClick={() => handleSort("monto_total")}>Monto{sortIndicator("monto_total")}</Th>
                {tieneModuloMora && (
                  <Th $right style={{ cursor: "pointer" }} onClick={() => handleSort("mora_total")}>Mora{sortIndicator("mora_total")}</Th>
                )}
                <Th style={{ cursor: "pointer" }} onClick={() => handleSort("estado")}>Estado{sortIndicator("estado")}</Th>
                <Th>Acciones</Th>
              </tr>
            </thead>
            <tbody>
              {loading ? (
                <tr>
                  <Td
                    colSpan={(tieneModuloMora ? 1 : 0) + (tienePanelCobrador ? 1 : 0) + 7}
                    style={{ textAlign: "center", padding: 32 }}
                  >
                    <LoadingInline>
                      <Icon
                        icon="lucide:loader-circle"
                        width={18}
                        style={{ animation: "spin 1s linear infinite" }}
                      />
                      Cargando recibos...
                    </LoadingInline>
                  </Td>
                </tr>
              ) : recibos.length === 0 ? (
                <tr>
                  <Td
                    colSpan={(tieneModuloMora ? 1 : 0) + (tienePanelCobrador ? 1 : 0) + 7}
                    style={{ textAlign: "center", padding: 40 }}
                  >
                    <EmptyState>
                      <Icon icon="lucide:inbox" width={36} />
                      <span>Sin recibos para los filtros seleccionados</span>
                    </EmptyState>
                  </Td>
                </tr>
              ) : (
                recibos.map((r) => {
                  const cliente =
                    r.clientes?.personas?.razon_social ||
                    r.clientes?.nombre_fantasia ||
                    "—";
                  const p = r.clientes?.personas;
                  const docCliente = p?.ruc
                    ? `RUC ${p.ruc}${p.dv ? "-" + p.dv : ""}`
                    : p?.nro_documento
                    ? `CI ${p.nro_documento}`
                    : null;
                  const cobrador = r.cobrador
                    ? `${r.cobrador.nombre} ${r.cobrador.apellido}`
                    : "—";
                  const isMulti = r.modo === "MULTI";
                  return (
                    <Tr
                      key={r.id}
                      $anulado={r.estado === "anulado"}
                      onClick={() => setSelectedRecibo(r)}
                    >
                      <Td>
                        <ReciboNum>{r.numero_recibo}</ReciboNum>
                      </Td>
                      <Td>
                        <TipoBadge $multi={isMulti}>
                          {isMulti ? "Multi" : "Normal"}
                        </TipoBadge>
                      </Td>
                      <Td>
                        <DateCell>
                          {fmtFecha(r.fecha_emision)}
                          {(r.fecha_registro || r.created_at) && (
                            <DateSub title="Fecha y hora de registro">
                              reg. {fmtFechaHora(r.fecha_registro || r.created_at)}
                            </DateSub>
                          )}
                        </DateCell>
                      </Td>
                      <Td>
                        <ClienteCell>
                          {cliente}
                          {docCliente && <DocSub>{docCliente}</DocSub>}
                          {r.clientes?.personas?.telefono && (
                            <TelLink
                              href={`tel:${r.clientes.personas.telefono}`}
                              onClick={(e) => e.stopPropagation()}
                            >
                              {r.clientes.personas.telefono}
                            </TelLink>
                          )}
                        </ClienteCell>
                      </Td>
                      {tienePanelCobrador && (
                        <Td>
                          <CobradorCell>{cobrador}</CobradorCell>
                        </Td>
                      )}
                      <Td $right>
                        <MontoCell>
                          <strong>{fmtMoney(r.monto_total, r.moneda)}</strong>
                          {Number(r.descuento_monto) > 0 && (
                            <MontoDesc>
                              -{fmtMoney(r.descuento_monto, r.moneda)}
                            </MontoDesc>
                          )}
                        </MontoCell>
                      </Td>
                      {tieneModuloMora && (
                        <Td $right>
                          {Number(r.mora_total) > 0 ? (
                            <MoraBadge>
                              {fmtMoney(r.mora_total, r.moneda)}
                            </MoraBadge>
                          ) : (
                            <span style={{ color: "#6b7280" }}>—</span>
                          )}
                        </Td>
                      )}
                      <Td>
                        <EstadoBadge $estado={r.estado}>{r.estado}</EstadoBadge>
                      </Td>
                      <Td onClick={(e) => e.stopPropagation()}>
                        <ActionWrap onClick={(e) => e.stopPropagation()}>
                          <ActionBtn
                            type="button"
                            $anulado={r.estado === "anulado"}
                            disabled={loadingPdf === r.id + "a4" || loadingPdf === r.id + "ticket" || loadingPdf === r.id + "print"}
                            onClick={(e) => abrirActionMenu(e, r.id)}
                          >
                            <Icon icon="lucide:more-horizontal" width={14} />
                            <span>Acciones</span>
                            <Icon icon="lucide:chevron-down" width={12} />
                          </ActionBtn>
                          {actionMenuId === r.id && actionMenuAnchor && createPortal(
                            <ActionMenu
                              onClick={(e) => e.stopPropagation()}
                              style={{
                                position: "fixed",
                                top: actionMenuAnchor.bottom + 4,
                                left: actionMenuAnchor.right,
                                right: "auto",
                                transform: "translateX(-100%)",
                              }}
                            >
                              {!isMulti && (
                                <ActionItem onClick={() => { setActionMenuId(null); handleReimprimir(r.id); }}>
                                  <Icon icon="lucide:printer" width={14} /> Reimprimir ticket
                                </ActionItem>
                              )}
                              <ActionItem onClick={() => { setActionMenuId(null); verPdf(r.id, "a4", isMulti); }}>
                                <Icon icon="lucide:file-text" width={14} /> Ver PDF A4
                              </ActionItem>
                              {!isMulti && (
                                <ActionItem onClick={() => { setActionMenuId(null); verPdf(r.id, "ticket"); }}>
                                  <Icon icon="lucide:receipt" width={14} /> Ver Ticket
                                </ActionItem>
                              )}
                              {r.estado !== "anulado" && puedeEditarRecibo && (
                                <ActionItem onClick={() => {
                                  setActionMenuId(null);
                                  setFechaModal(r);
                                  setNuevaFecha(r.fecha_emision ? aFechaISO(r.fecha_emision) : "");
                                  setMotivoFecha("");
                                }}>
                                  <Icon icon="lucide:calendar" width={14} /> Cambiar fecha de emisión
                                </ActionItem>
                              )}
                              {r.estado !== "anulado" && puedeAnularRecibo && (
                                <>
                                  <ActionDivider />
                                  <ActionItem $danger onClick={() => {
                                    setActionMenuId(null);
                                    setAnularModal(r);
                                    setMotivoAnulacion("");
                                  }}>
                                    <Icon icon="lucide:ban" width={14} /> Anular recibo
                                  </ActionItem>
                                </>
                              )}
                            </ActionMenu>,
                            document.body,
                          )}
                        </ActionWrap>
                      </Td>
                    </Tr>
                  );
                })
              )}
            </tbody>
          </Table>
        </TableScroll>
      </TableCard>

      {/* ── Paginación ── */}
      {meta.pages > 1 && (
        <PaginationBar>
          <PageBtn
            disabled={page === 1}
            onClick={() => cargar(page - 1)}
          >
            <Icon icon="lucide:chevron-left" width={15} />
          </PageBtn>

          {Array.from({ length: meta.pages }, (_, i) => i + 1)
            .filter(
              (p) =>
                p === 1 ||
                p === meta.pages ||
                Math.abs(p - page) <= 2
            )
            .reduce((acc, p, idx, arr) => {
              if (idx > 0 && p - arr[idx - 1] > 1)
                acc.push("...");
              acc.push(p);
              return acc;
            }, [])
            .map((p, i) =>
              p === "..." ? (
                <PageEllipsis key={`e${i}`}>…</PageEllipsis>
              ) : (
                <PageNum
                  key={p}
                  $active={p === page}
                  onClick={() => p !== page && cargar(p)}
                >
                  {p}
                </PageNum>
              )
            )}

          <PageBtn
            disabled={page === meta.pages}
            onClick={() => cargar(page + 1)}
          >
            <Icon icon="lucide:chevron-right" width={15} />
          </PageBtn>

          <PageInfo>
            Pág. {page}/{meta.pages} · {meta.total} total
          </PageInfo>
        </PaginationBar>
      )}

      {/* ── Drawer detalle ── */}
      {selectedRecibo && (
        <Overlay onClick={() => setSelectedRecibo(null)}>
          <Drawer onClick={(e) => e.stopPropagation()}>
            <DrawerHead>
              <DrawerTitle>
                <Icon icon="lucide:receipt" width={16} />
                Recibo #{selectedRecibo.numero_recibo}
              </DrawerTitle>
              <EstadoBadge $estado={selectedRecibo.estado}>
                {selectedRecibo.estado}
              </EstadoBadge>
              <DrawerClose onClick={() => setSelectedRecibo(null)}>
                <Icon icon="lucide:x" />
              </DrawerClose>
            </DrawerHead>

            <DrawerBody>
              <InfoGrid>
                <InfoRow>
                  <InfoLabel>Cliente</InfoLabel>
                  <InfoVal>
                    {selectedRecibo.clientes?.personas?.razon_social ||
                      selectedRecibo.clientes?.nombre_fantasia ||
                      "—"}
                  </InfoVal>
                </InfoRow>
                {(() => {
                  const p = selectedRecibo.clientes?.personas;
                  const doc = p?.ruc
                    ? `RUC ${p.ruc}${p.dv ? "-" + p.dv : ""}`
                    : p?.nro_documento
                    ? `CI ${p.nro_documento}`
                    : null;
                  if (!doc) return null;
                  return (
                    <InfoRow>
                      <InfoLabel>Documento</InfoLabel>
                      <InfoVal>{doc}</InfoVal>
                    </InfoRow>
                  );
                })()}
                {tienePanelCobrador && (
                  <InfoRow>
                    <InfoLabel>Cobrador</InfoLabel>
                    <InfoVal>
                      {selectedRecibo.cobrador
                        ? `${selectedRecibo.cobrador.nombre} ${selectedRecibo.cobrador.apellido}`
                        : "—"}
                    </InfoVal>
                  </InfoRow>
                )}
                {(() => {
                  const caja = infoCaja(selectedRecibo);
                  return (
                    <InfoRow>
                      <InfoLabel>Caja</InfoLabel>
                      <CajaVal>
                        <InfoVal $amber={caja.$amber}>{caja.texto}</InfoVal>
                        {caja.sub && <CajaSub>{caja.sub}</CajaSub>}
                      </CajaVal>
                    </InfoRow>
                  );
                })()}
                <InfoRow>
                  <InfoLabel>Fecha emisión</InfoLabel>
                  <InfoVal>{fmtFecha(selectedRecibo.fecha_emision)}</InfoVal>
                </InfoRow>
                {(selectedRecibo.fecha_registro || selectedRecibo.created_at) && (
                  <InfoRow>
                    <InfoLabel>Fecha registro</InfoLabel>
                    <InfoVal>
                      {fmtFechaHora(selectedRecibo.fecha_registro || selectedRecibo.created_at)}
                    </InfoVal>
                  </InfoRow>
                )}
                <InfoRow>
                  <InfoLabel>Total cobrado</InfoLabel>
                  <InfoVal $accent>
                    {fmtMoney(
                      selectedRecibo.monto_total,
                      selectedRecibo.moneda
                    )}
                  </InfoVal>
                </InfoRow>
                {Number(selectedRecibo.descuento_monto) > 0 && (
                  <InfoRow>
                    <InfoLabel>Descuento</InfoLabel>
                    <InfoVal $red>
                      -{fmtMoney(
                        selectedRecibo.descuento_monto,
                        selectedRecibo.moneda
                      )}
                    </InfoVal>
                  </InfoRow>
                )}
                {(tieneModuloMora || Number(selectedRecibo.interes_manual_total) > 0) &&
                  Number(selectedRecibo.mora_total) > 0 && (
                    <InfoRow>
                      <InfoLabel>
                        {Number(selectedRecibo.interes_manual_total) > 0 ? "Interés cobrado" : "Mora cobrada"}
                      </InfoLabel>
                      <InfoVal $amber>
                        {fmtMoney(
                          selectedRecibo.mora_total,
                          selectedRecibo.moneda
                        )}
                      </InfoVal>
                    </InfoRow>
                  )}

                {selectedRecibo.modo === "MULTI" && (
                  <>
                    {Number(selectedRecibo.total_facturas) > 0 && (
                      <InfoRow>
                        <InfoLabel>Total facturas</InfoLabel>
                        <InfoVal>{fmtMoney(selectedRecibo.total_facturas, selectedRecibo.moneda)}</InfoVal>
                      </InfoRow>
                    )}
                    {Number(selectedRecibo.total_intereses) > 0 && (
                      <InfoRow>
                        <InfoLabel>Intereses</InfoLabel>
                        <InfoVal $amber>{fmtMoney(selectedRecibo.total_intereses, selectedRecibo.moneda)}</InfoVal>
                      </InfoRow>
                    )}
                    {Number(selectedRecibo.total_nc) > 0 && (
                      <InfoRow>
                        <InfoLabel>NC aplicadas</InfoLabel>
                        <InfoVal $red>-{fmtMoney(selectedRecibo.total_nc, selectedRecibo.moneda)}</InfoVal>
                      </InfoRow>
                    )}
                    {Number(selectedRecibo.total_retenciones) > 0 && (
                      <InfoRow>
                        <InfoLabel>Retenciones</InfoLabel>
                        <InfoVal>{fmtMoney(selectedRecibo.total_retenciones, selectedRecibo.moneda)}</InfoVal>
                      </InfoRow>
                    )}
                    {Number(selectedRecibo.total_pagado) > 0 && (
                      <InfoRow>
                        <InfoLabel>Total pagado</InfoLabel>
                        <InfoVal $accent>{fmtMoney(selectedRecibo.total_pagado, selectedRecibo.moneda)}</InfoVal>
                      </InfoRow>
                    )}
                    {Number(selectedRecibo.diferencia) !== 0 && (
                      <InfoRow>
                        <InfoLabel>
                          Diferencia
                          {selectedRecibo.diferencia_tipo && ` (${selectedRecibo.diferencia_tipo})`}
                        </InfoLabel>
                        <InfoVal $amber>
                          {fmtMoney(selectedRecibo.diferencia, selectedRecibo.moneda)}
                        </InfoVal>
                      </InfoRow>
                    )}
                    {selectedRecibo.diferencia_motivo && (
                      <InfoRow>
                        <InfoLabel>Motivo diferencia</InfoLabel>
                        <InfoVal>{selectedRecibo.diferencia_motivo}</InfoVal>
                      </InfoRow>
                    )}
                  </>
                )}
              </InfoGrid>

              {selectedRecibo.modo === "MULTI" && multiLoading && (
                <DetailTitle>Cargando detalle…</DetailTitle>
              )}

              {selectedRecibo.modo === "MULTI" && multiDetalle && (
                <>
                  {Array.isArray(multiDetalle.facturas) && multiDetalle.facturas.length > 0 && (
                    <DetailSection>
                      <DetailTitle>Facturas imputadas</DetailTitle>
                      {multiDetalle.facturas.map((f) => (
                        <DetailItem key={f.id}>
                          <DetailLeft>
                            <span>{f.factura_numero || "Sin número"}</span>
                            {Number(f.monto_saldo_previo) > 0 && (
                              <small>Saldo previo {fmtMoney(f.monto_saldo_previo, selectedRecibo.moneda)}</small>
                            )}
                            {Number(f.monto_interes) > 0 && (
                              <small style={{ color: "#f59e0b" }}>
                                + Interés {fmtMoney(f.monto_interes, selectedRecibo.moneda)}
                              </small>
                            )}
                            {Number(f.monto_nc) > 0 && (
                              <small style={{ color: "#22c55e" }}>
                                - NC {fmtMoney(f.monto_nc, selectedRecibo.moneda)}
                              </small>
                            )}
                            {Number(f.monto_retencion) > 0 && (
                              <small>Retención {fmtMoney(f.monto_retencion, selectedRecibo.moneda)}</small>
                            )}
                          </DetailLeft>
                          <DetailRight>
                            <strong>{fmtMoney(f.monto_pagado, selectedRecibo.moneda)}</strong>
                          </DetailRight>
                        </DetailItem>
                      ))}
                    </DetailSection>
                  )}

                  {Array.isArray(multiDetalle.medios_pago) && multiDetalle.medios_pago.length > 0 && (
                    <DetailSection>
                      <DetailTitle>Medios de pago</DetailTitle>
                      {multiDetalle.medios_pago.map((m) => (
                        <DetailItem key={m.id}>
                          <DetailLeft>
                            <span>{m.medio ?? m.tipo}</span>
                            {m.referencia && <small>Ref: {m.referencia}</small>}
                            {m.cheque_numero && (
                              <small>
                                Cheque {m.cheque_numero}
                                {m.cheque_banco && ` · ${m.cheque_banco}`}
                              </small>
                            )}
                            {m.cheque_titular && <small>Titular: {m.cheque_titular}</small>}
                            {m.cheque_fecha_vencimiento && (
                              <small>Vence: {fmtFecha(m.cheque_fecha_vencimiento)}</small>
                            )}
                          </DetailLeft>
                          <DetailRight>
                            <strong>{fmtMoney(m.monto, selectedRecibo.moneda)}</strong>
                          </DetailRight>
                        </DetailItem>
                      ))}
                    </DetailSection>
                  )}

                  {Array.isArray(multiDetalle.nc_aplicadas) && multiDetalle.nc_aplicadas.length > 0 && (
                    <DetailSection>
                      <DetailTitle>Notas de crédito aplicadas</DetailTitle>
                      {multiDetalle.nc_aplicadas.map((nc) => (
                        <DetailItem key={nc.id}>
                          <DetailLeft>
                            <span>{nc.nc_numero || "Sin número"}</span>
                          </DetailLeft>
                          <DetailRight>
                            <strong style={{ color: "#22c55e" }}>
                              -{fmtMoney(nc.monto ?? nc.monto_aplicado, selectedRecibo.moneda)}
                            </strong>
                          </DetailRight>
                        </DetailItem>
                      ))}
                    </DetailSection>
                  )}

                  {Array.isArray(multiDetalle.retenciones) && multiDetalle.retenciones.length > 0 && (
                    <DetailSection>
                      <DetailTitle>Retenciones recibidas</DetailTitle>
                      {multiDetalle.retenciones.map((r) => (
                        <DetailItem key={r.id}>
                          <DetailLeft>
                            <span>{r.tipo}</span>
                            {r.numero_comprobante && <small>Nº {r.numero_comprobante}</small>}
                          </DetailLeft>
                          <DetailRight>
                            <strong>{fmtMoney(r.monto, selectedRecibo.moneda)}</strong>
                          </DetailRight>
                        </DetailItem>
                      ))}
                    </DetailSection>
                  )}

                  {Array.isArray(multiDetalle.gastos_asociados) && multiDetalle.gastos_asociados.length > 0 && (
                    <DetailSection>
                      <DetailTitle>Gastos documentados descontados</DetailTitle>
                      {multiDetalle.gastos_asociados.map((g) => (
                        <DetailItem key={g.id}>
                          <DetailLeft>
                            <span>{g.proveedor_razon_social ?? "—"}</span>
                            {g.gasto_numero && <small>Factura {g.gasto_numero}</small>}
                            {g.gasto_fecha && <small>{fmtFecha(g.gasto_fecha)}</small>}
                          </DetailLeft>
                          <DetailRight>
                            <strong style={{ color: "#f59e0b" }}>
                              -{fmtMoney(g.monto_aplicado, selectedRecibo.moneda)}
                            </strong>
                          </DetailRight>
                        </DetailItem>
                      ))}
                    </DetailSection>
                  )}
                </>
              )}

              {selectedRecibo.recibo_cobro_detalle?.length > 0 &&
                (() => {
                  // La mora vive en cob_interes_cobrado (por cuota), no en el
                  // detalle del recibo. Mapeamos para mostrar el total real
                  // cobrado por cuota = capital + mora.
                  const moraPorCuota = new Map();
                  (selectedRecibo.intereses_cobrados || []).forEach((ic) => {
                    moraPorCuota.set(
                      ic.factura_cuota_id,
                      Number(ic.monto_interes_cobrado || 0),
                    );
                  });
                  // Si no hay desglose por cuota pero sí mora_total, usamos
                  // mora_total como fallback en la primera cuota.
                  const totalMoraDesglosada = Array.from(
                    moraPorCuota.values(),
                  ).reduce((a, b) => a + b, 0);
                  // El interés manual de la cuenta no cuelga de ninguna cuota:
                  // tiene su propia sección más abajo, así que no lo mostramos
                  // como "mora" de la primera cuota.
                  const moraSinDesglose =
                    totalMoraDesglosada === 0
                      ? Math.max(
                          0,
                          Number(selectedRecibo.mora_total || 0) -
                            Number(selectedRecibo.interes_manual_total || 0),
                        )
                      : 0;
                  return (
                    <DetailSection>
                      <DetailTitle>Cuotas incluidas</DetailTitle>
                      {selectedRecibo.recibo_cobro_detalle.map((d, idx) => {
                        const cuotaId = d.factura_cuotas?.id;
                        // mora_monto trae la mora de la cuota más el interés
                        // manual atado a ella; ese interés se lista aparte.
                        const interesManualCuota = (selectedRecibo.interes_manual || [])
                          .filter((i) => i.nro_cuota != null && i.nro_cuota === d.factura_cuotas?.nro_cuota)
                          .reduce((a, i) => a + Number(i.monto || 0), 0);
                        const moraCuota =
                          moraPorCuota.get(cuotaId) ??
                          Math.max(0, Number(d.mora_monto || 0) - interesManualCuota);
                        const moraEfectiva =
                          moraCuota > 0
                            ? moraCuota
                            : idx === 0
                              ? moraSinDesglose
                              : 0;
                        const totalCuota =
                          Number(d.monto_pagado || 0) + moraEfectiva;
                        return (
                          <DetailItem key={idx}>
                            <DetailLeft>
                              <span>
                                {d.factura_cuotas?.factura_cab?.dpunexp}-
                                {d.factura_cuotas?.factura_cab?.dnumdoc} · C.
                                {d.factura_cuotas?.nro_cuota}
                              </span>
                              {d.factura_cuotas?.dvenccuo && (
                                <small>
                                  Venc. {fmtFecha(d.factura_cuotas.dvenccuo)}
                                </small>
                              )}
                              {d.es_pago_parcial && (
                                <ParcialBadge>Parcial</ParcialBadge>
                              )}
                            </DetailLeft>
                            <DetailRight>
                              <strong>
                                {fmtMoney(totalCuota, selectedRecibo.moneda)}
                              </strong>
                              <small style={{ color: "#9ca3af" }}>
                                Cuota{" "}
                                {fmtMoney(
                                  d.monto_pagado,
                                  selectedRecibo.moneda,
                                )}
                              </small>
                              {d.descuento_aplicado > 0 && (
                                <small style={{ color: "#ef4444" }}>
                                  Desc. -
                                  {fmtMoney(
                                    d.descuento_aplicado,
                                    selectedRecibo.moneda,
                                  )}
                                </small>
                              )}
                              {tieneModuloMora && moraEfectiva > 0 && (
                                <small style={{ color: "#f59e0b" }}>
                                  Mora +
                                  {fmtMoney(
                                    moraEfectiva,
                                    selectedRecibo.moneda,
                                  )}
                                </small>
                              )}
                            </DetailRight>
                          </DetailItem>
                        );
                      })}
                    </DetailSection>
                  );
                })()}

              {Number(selectedRecibo.interes_manual_total) > 0 && (
                <DetailSection>
                  <DetailTitle>Interés cargado</DetailTitle>
                  {(selectedRecibo.interes_manual || []).map((i, idx) => (
                    <DetailItem key={idx}>
                      <DetailLeft>
                        <span>
                          {i.nro_cuota ? `Interés de la cuota ${i.nro_cuota}` : "Interés de la cuenta"}
                        </span>
                        <small>
                          {i.motivo ? `${i.motivo} · ` : ""}
                          Cargado desde Cuentas por Cobrar. No genera comprobante fiscal.
                          {i.monto < i.monto_cargo &&
                            ` Cobro parcial de ${fmtMoney(i.monto_cargo, selectedRecibo.moneda)}.`}
                        </small>
                      </DetailLeft>
                      <DetailRight>
                        <strong style={{ color: "#f59e0b" }}>
                          {fmtMoney(i.monto, selectedRecibo.moneda)}
                        </strong>
                      </DetailRight>
                    </DetailItem>
                  ))}
                </DetailSection>
              )}

              {/* Aviso solo para la mora por tasa que debería tener su factura de
                  intereses y no la tiene. El interés manual no lleva comprobante. */}
              {Number(selectedRecibo.mora_total) -
                Number(selectedRecibo.interes_manual_total || 0) >
                0 &&
                (!Array.isArray(selectedRecibo.intereses_cobrados) ||
                  selectedRecibo.intereses_cobrados.length === 0) && (
                  <DetailSection>
                    <DetailTitle>Documento de intereses emitido</DetailTitle>
                    <DetailItem>
                      <DetailLeft>
                        <span>Sin comprobante registrado</span>
                        <small>
                          Se cobró mora pero no se encontró el registro fiscal
                          vinculado (puede haber fallado la emisión o el recibo
                          es anterior a la implementación de Fase A.3).
                        </small>
                      </DetailLeft>
                      <DetailRight>
                        <strong style={{ color: "#f59e0b" }}>
                          {fmtMoney(
                            Number(selectedRecibo.mora_total) -
                              Number(selectedRecibo.interes_manual_total || 0),
                            selectedRecibo.moneda,
                          )}
                        </strong>
                      </DetailRight>
                    </DetailItem>
                  </DetailSection>
                )}

              {Array.isArray(selectedRecibo.intereses_cobrados) &&
                selectedRecibo.intereses_cobrados.length > 0 && (
                  <DetailSection>
                    <DetailTitle>Documento de intereses emitido</DetailTitle>
                    {selectedRecibo.intereses_cobrados.map((ic) => {
                      const f = ic.factura_emitida;
                      const numero = f
                        ? `${f.dest}-${f.dpunexp}-${f.dnumdoc}`
                        : null;
                      return (
                        <DetailItem key={ic.id}>
                          <DetailLeft>
                            <span>
                              {ic.tipo_comprobante === "FACTURA"
                                ? "Factura"
                                : ic.tipo_comprobante === "NOTA_DEBITO"
                                  ? "Nota de débito"
                                  : "Sin comprobante"}
                              {numero ? ` ${numero}` : ""}
                            </span>
                            {f?.estado_sifen && (
                              <small>SIFEN: {f.estado_sifen}</small>
                            )}
                            {!f && ic.error_emision && (
                              <small style={{ color: "#ef4444" }}>
                                {ic.error_emision}
                              </small>
                            )}
                          </DetailLeft>
                          <DetailRight>
                            <strong>
                              {fmtMoney(
                                ic.monto_interes_cobrado,
                                selectedRecibo.moneda,
                              )}
                            </strong>
                            {Number(ic.monto_interes_bruto) > 0 &&
                              Number(ic.monto_exonerado) > 0 && (
                                <small style={{ color: "#94a3b8" }}>
                                  Bruto:{" "}
                                  {fmtMoney(
                                    ic.monto_interes_bruto,
                                    selectedRecibo.moneda,
                                  )}
                                </small>
                              )}
                            {Number(ic.monto_exonerado) > 0 && (
                              <small style={{ color: "#ef4444" }}>
                                Exo: -
                                {fmtMoney(
                                  ic.monto_exonerado,
                                  selectedRecibo.moneda,
                                )}
                              </small>
                            )}
                          </DetailRight>
                        </DetailItem>
                      );
                    })}
                    {selectedRecibo.intereses_cobrados.some(
                      (ic) => !ic.factura_emitida && ic.error_emision && Number(ic.monto_interes_cobrado) > 0,
                    ) && selectedRecibo.estado !== "anulado" && (
                      <div
                        style={{
                          marginTop: 8,
                          padding: 8,
                          borderRadius: 6,
                          background: "rgba(245,158,11,0.08)",
                          border: "1px solid rgba(245,158,11,0.25)",
                          display: "flex",
                          alignItems: "center",
                          justifyContent: "space-between",
                          gap: 8,
                        }}
                      >
                        <span style={{ fontSize: "0.78rem", color: "#f59e0b" }}>
                          Corregí los datos del cliente y reintentá la emisión.
                        </span>
                        <button
                          type="button"
                          onClick={handleReintentarEmision}
                          disabled={reintentando}
                          style={{
                            padding: "0.35rem 0.75rem",
                            borderRadius: 6,
                            border: "1px solid #f59e0b",
                            background: "transparent",
                            color: "#f59e0b",
                            fontSize: "0.78rem",
                            fontWeight: 600,
                            cursor: reintentando ? "not-allowed" : "pointer",
                            opacity: reintentando ? 0.6 : 1,
                            display: "inline-flex",
                            alignItems: "center",
                            gap: 4,
                          }}
                        >
                          <Icon icon="lucide:refresh-cw" />
                          {reintentando ? "Reintentando..." : "Reintentar emisión"}
                        </button>
                      </div>
                    )}
                    {selectedRecibo.intereses_cobrados.some(
                      (ic) => ic.autorizacion_exoneracion,
                    ) && (
                      <div
                        style={{
                          marginTop: 8,
                          padding: 8,
                          borderRadius: 6,
                          background: "rgba(239,68,68,0.08)",
                          border: "1px solid rgba(239,68,68,0.2)",
                          fontSize: "0.78rem",
                        }}
                      >
                        <div
                          style={{
                            fontWeight: 700,
                            color: "#ef4444",
                            marginBottom: 4,
                          }}
                        >
                          Exoneración de mora aplicada
                        </div>
                        {selectedRecibo.intereses_cobrados
                          .filter((ic) => ic.autorizacion_exoneracion)
                          .map((ic) => {
                            const a = ic.autorizacion_exoneracion;
                            const aprobador = a.aprobador?.nombres || "—";
                            const valorAut = a.descuento_porcentaje
                              ? `${Number(a.descuento_porcentaje)}%`
                              : a.descuento_monto
                                ? fmtMoney(
                                    a.descuento_monto,
                                    selectedRecibo.moneda,
                                  )
                                : "—";
                            return (
                              <div
                                key={ic.id}
                                style={{ marginBottom: 4, opacity: 0.85 }}
                              >
                                <div>
                                  • Autorizada por <strong>{aprobador}</strong>{" "}
                                  ({valorAut}) — exonerado{" "}
                                  <strong>
                                    {fmtMoney(
                                      ic.monto_exonerado,
                                      selectedRecibo.moneda,
                                    )}
                                  </strong>
                                </div>
                                {a.motivo && (
                                  <div style={{ paddingLeft: 8 }}>
                                    "{a.motivo}"
                                  </div>
                                )}
                                {a.notas_supervisor && (
                                  <div
                                    style={{
                                      paddingLeft: 8,
                                      fontStyle: "italic",
                                    }}
                                  >
                                    Notas: {a.notas_supervisor}
                                  </div>
                                )}
                              </div>
                            );
                          })}
                      </div>
                    )}
                  </DetailSection>
                )}

              {selectedRecibo.recibo_cobro_detalle?.length > 0 && (() => {
                const formas = new Map();
                selectedRecibo.recibo_cobro_detalle.forEach((d) => {
                  const key = d.medio_pago_id || d.medio_pago?.id || d.medio_pago?.descripcion || "otro";
                  if (!formas.has(key)) {
                    formas.set(key, {
                      descripcion: d.medio_pago?.descripcion || "Otro",
                      monto: 0,
                      banco: d.bancos?.descripcion || null,
                      numero_cheque: d.numero_cheque || null,
                      numero_tarjeta: d.numero_tarjeta || null,
                    });
                  }
                  const f = formas.get(key);
                  f.monto += Number(d.monto_pagado || 0);
                });
                // La mora se cobra con el mismo medio de pago dominante pero no
                // figura como detalle propio. Para que las Formas de pago cuadren
                // con Total cobrado, la sumamos al medio de mayor importe.
                const moraTotal = Number(selectedRecibo.mora_total || 0);
                if (moraTotal > 0 && formas.size > 0) {
                  let mayor = null;
                  for (const f of formas.values()) {
                    if (!mayor || f.monto > mayor.monto) mayor = f;
                  }
                  if (mayor) mayor.monto += moraTotal;
                }
                const items = Array.from(formas.values());
                if (items.length === 0) return null;
                return (
                  <DetailSection>
                    <DetailTitle>Formas de pago</DetailTitle>
                    {items.map((f, idx) => (
                      <DetailItem key={idx}>
                        <DetailLeft>
                          <span>{f.descripcion}</span>
                          {f.banco && <small>{f.banco}</small>}
                          {f.numero_cheque && <small>Chq: {f.numero_cheque}</small>}
                          {f.numero_tarjeta && <small>****{f.numero_tarjeta}</small>}
                        </DetailLeft>
                        <DetailRight>
                          <strong>{fmtMoney(f.monto, selectedRecibo.moneda)}</strong>
                        </DetailRight>
                      </DetailItem>
                    ))}
                  </DetailSection>
                );
              })()}

              {selectedRecibo.estado === "anulado" && (
                <AnulacionSection>
                  <Icon icon="lucide:ban" width={14} />
                  <AnulacionInfo>
                    <strong>Recibo anulado</strong>
                    {selectedRecibo.fecha_anulacion && (
                      <span>{fmtFecha(selectedRecibo.fecha_anulacion)}</span>
                    )}
                    {selectedRecibo.motivo_anulacion && (
                      <em>"{selectedRecibo.motivo_anulacion}"</em>
                    )}
                  </AnulacionInfo>
                </AnulacionSection>
              )}
            </DrawerBody>

            {selectedRecibo.estado !== "anulado" && (
              <DrawerFoot>
                <FootBtn
                  onClick={() => verPdf(selectedRecibo.id, "a4", selectedRecibo.modo === "MULTI")}
                  disabled={loadingPdf === selectedRecibo.id + "a4"}
                >
                  <Icon icon="lucide:file-text" width={13} /> A4
                </FootBtn>
                {selectedRecibo.modo !== "MULTI" && (
                  <FootBtn
                    onClick={() => verPdf(selectedRecibo.id, "ticket")}
                    disabled={loadingPdf === selectedRecibo.id + "ticket"}
                  >
                    <Icon icon="lucide:receipt" width={13} /> Ticket
                  </FootBtn>
                )}
                {puedeAnularRecibo && (
                  <FootBtn
                    $danger
                    onClick={() => {
                      setAnularModal(selectedRecibo);
                      setMotivoAnulacion("");
                      setSelectedRecibo(null);
                    }}
                  >
                    <Icon icon="lucide:x-circle" width={13} /> Anular
                  </FootBtn>
                )}
                {selectedRecibo.estado !== "anulado" && puedeEditarRecibo && (
                  <FootBtn
                    onClick={() => {
                      setFechaModal(selectedRecibo);
                      setNuevaFecha(selectedRecibo.fecha_emision ? aFechaISO(selectedRecibo.fecha_emision) : "");
                      setMotivoFecha("");
                      setSelectedRecibo(null);
                    }}
                  >
                    <Icon icon="lucide:calendar" width={13} /> Fecha
                  </FootBtn>
                )}
              </DrawerFoot>
            )}
          </Drawer>
        </Overlay>
      )}

      {/* ── Modal anular ── */}
      {anularModal && (
        <Overlay onClick={() => !anulando && setAnularModal(null)}>
          <ModalBox onClick={(e) => e.stopPropagation()}>
            <ModalHead $danger>
              <Icon icon="lucide:x-circle" width={16} />
              Anular Recibo #{anularModal.numero_recibo}
              <ModalClose
                onClick={() => setAnularModal(null)}
                disabled={anulando}
              >
                <Icon icon="lucide:x" />
              </ModalClose>
            </ModalHead>
            <ModalBody>
              <ModalLabel>Motivo (opcional)</ModalLabel>
              <ModalInput
                value={motivoAnulacion}
                onChange={(e) => setMotivoAnulacion(e.target.value)}
                placeholder="Ej: Cobro duplicado, error de importe..."
                autoFocus
              />
              <ModalActions>
                <ModalCancelBtn
                  onClick={() => setAnularModal(null)}
                  disabled={anulando}
                >
                  Cancelar
                </ModalCancelBtn>
                <ModalDangerBtn
                  onClick={confirmarAnular}
                  disabled={anulando}
                >
                  {anulando ? "Anulando..." : "Confirmar anulación"}
                </ModalDangerBtn>
              </ModalActions>
            </ModalBody>
          </ModalBox>
        </Overlay>
      )}

      {/* ── Modal fecha ── */}
      {fechaModal && (
        <Overlay onClick={() => !guardandoFecha && setFechaModal(null)}>
          <ModalBox onClick={(e) => e.stopPropagation()}>
            <ModalHead>
              <Icon icon="lucide:calendar" width={16} />
              Cambiar fecha de emisión — #{fechaModal.numero_recibo}
              <ModalClose
                onClick={() => setFechaModal(null)}
                disabled={guardandoFecha}
              >
                <Icon icon="lucide:x" />
              </ModalClose>
            </ModalHead>
            <ModalBody>
              <FechaAviso>
                Fecha actual: <strong>{fmtFechaCalendario(fechaModal.fecha_emision)}</strong>. El cambio también mueve
                el asiento contable y los movimientos de tesorería del cobro, y queda en la auditoría con tu usuario y el
                motivo. No recalcula intereses ni descuentos. No se permite si el recibo ya está rendido, conciliado con
                el banco, con la comisión liquidada o si alguna de las dos fechas cae en un período contable cerrado.
              </FechaAviso>
              <ModalLabel>Nueva fecha de emisión</ModalLabel>
              <ModalInput
                type="date"
                value={nuevaFecha}
                max={hoyISO()}
                onChange={(e) => setNuevaFecha(e.target.value)}
              />
              <ModalLabel>Motivo del cambio (obligatorio)</ModalLabel>
              <ModalInput
                value={motivoFecha}
                onChange={(e) => setMotivoFecha(e.target.value)}
                placeholder="Ej: el cobro se hizo el viernes y se cargó el lunes"
              />
              <ModalActions>
                <ModalCancelBtn
                  onClick={() => setFechaModal(null)}
                  disabled={guardandoFecha}
                >
                  Cancelar
                </ModalCancelBtn>
                <ModalPrimaryBtn
                  onClick={guardarFecha}
                  disabled={guardandoFecha || !nuevaFecha || motivoFecha.trim().length < 5}
                >
                  {guardandoFecha ? "Guardando..." : "Guardar"}
                </ModalPrimaryBtn>
              </ModalActions>
            </ModalBody>
          </ModalBox>
        </Overlay>
      )}

      {/* ── Wizard Nuevo Recibo Multi ── */}
      <NuevoReciboMultiWizard
        open={wizardMultiOpen}
        onClose={(refresh) => {
          setWizardMultiOpen(false);
          if (refresh) cargar(1);
        }}
      />

      {/* ── PDF preview ── */}
      {pdfPreview && (
        <Overlay onClick={cerrarPdf}>
          <PdfPanel onClick={(e) => e.stopPropagation()}>
            <DrawerHead>
              <DrawerTitle>
                <Icon
                  icon={
                    pdfPreview.formato === "ticket"
                      ? "lucide:receipt"
                      : "lucide:file-text"
                  }
                  width={16}
                />
                {pdfPreview.formato === "ticket" ? "Ticket" : "A4"}
              </DrawerTitle>
              <DrawerClose onClick={cerrarPdf}>
                <Icon icon="lucide:x" />
              </DrawerClose>
            </DrawerHead>
            <iframe
              src={pdfPreview.url}
              style={{ flex: 1, border: "none", width: "100%", minHeight: 0 }}
              title="Recibo PDF"
            />
            <DrawerFoot>
              <FootBtn
                style={{ flex: 1, justifyContent: "center" }}
                onClick={() => {
                  const a = document.createElement("a");
                  a.href = pdfPreview.url;
                  a.target = "_blank";
                  a.click();
                }}
              >
                <Icon icon="lucide:external-link" width={13} /> Abrir /
                Imprimir
              </FootBtn>
            </DrawerFoot>
          </PdfPanel>
        </Overlay>
      )}
    </Wrap>
  );
}

// ─── Styled Components ────────────────────────────────────────────────────────
const Wrap = styled.div`
  display: flex;
  flex-direction: column;
  gap: 12px;
  flex: 1;
  min-height: 0;
  height: 100%;
  @keyframes spin {
    to {
      transform: rotate(360deg);
    }
  }
`;

// ── Filtros ──────────────────────────────────────────────────────────────────
const FiltersCard = styled.div`
  background: ${({ theme }) => theme.bg3 || "#f9fafb"};
  border: 1px solid ${({ theme }) => theme.bg4 || "#e5e7eb"};
  border-radius: 10px;
  padding: 12px 14px;
  display: flex;
  flex-direction: column;
  gap: 10px;
  flex-shrink: 0;
`;

const FilterRow = styled.div`
  display: flex;
  flex-wrap: wrap;
  gap: 10px;
  align-items: flex-end;
  ${({ $secondary }) =>
    $secondary &&
    `border-top: 1px solid ${({ theme }) => theme.bg4 || "#e5e7eb"}; padding-top: 10px;`}
`;

const FilterGroup = styled.div`
  display: flex;
  flex-direction: column;
  gap: 4px;
`;

const FilterLabel = styled.label`
  font-size: 11px;
  color: #6b7280;
  font-weight: 500;
`;

const SearchGroup = styled(FilterGroup)`
  flex: 1;
  min-width: 200px;
`;

const SearchBox = styled.div`
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 0 10px;
  border: 1px solid ${({ theme }) => theme.bg4 || "#d1d5db"};
  border-radius: 7px;
  background: ${({ theme }) => theme.body};
  height: 34px;
`;

const SearchHint = styled.div`
  display: flex;
  align-items: center;
  gap: 5px;
  margin-top: 5px;
  font-size: 11.5px;
  color: ${({ theme }) => theme.primary || "#2563eb"};
`;

const SearchInput = styled.input`
  flex: 1;
  border: none;
  background: transparent;
  color: ${({ theme }) => theme.text};
  font-size: 13px;
  outline: none;
  min-width: 0;
  &::placeholder {
    color: #9ca3af;
  }
`;

const ClearBtn = styled.button`
  border: none;
  background: none;
  cursor: pointer;
  color: #9ca3af;
  padding: 2px;
  display: flex;
  align-items: center;
  &:hover {
    color: ${({ theme }) => theme.text};
  }
`;

const CtrlSelect = styled.select`
  padding: 6px 10px;
  border-radius: 7px;
  border: 1px solid ${({ theme }) => theme.bg4 || "#d1d5db"};
  background: ${({ theme }) => theme.body};
  color: ${({ theme }) => theme.text};
  font-size: 13px;
  height: 34px;
  min-width: 130px;
  cursor: pointer;
`;

const CtrlInput = styled.input`
  padding: 6px 10px;
  border-radius: 7px;
  border: 1px solid ${({ theme }) => theme.bg4 || "#d1d5db"};
  background: ${({ theme }) => theme.body};
  color: ${({ theme }) => theme.text};
  font-size: 13px;
  height: 34px;
`;

const ActionsGroup = styled.div`
  display: flex;
  gap: 8px;
  align-items: flex-end;
`;

const SearchBtn = styled.button`
  display: flex;
  align-items: center;
  gap: 6px;
  padding: 0 14px;
  height: 34px;
  border-radius: 7px;
  border: 1px solid ${({ theme }) => theme.bg4 || "#d1d5db"};
  background: ${({ theme }) => theme.body};
  color: ${({ theme }) => theme.text};
  font-size: 13px;
  font-weight: 500;
  cursor: pointer;
  white-space: nowrap;
  &:hover:not(:disabled) {
    border-color: #6b7280;
  }
  &:disabled {
    opacity: 0.5;
    cursor: not-allowed;
  }
`;

const NewMultiBtn = styled.button`
  display: flex;
  align-items: center;
  gap: 6px;
  padding: 0 12px;
  height: 34px;
  border-radius: 7px;
  border: 1px solid #3b82f650;
  background: #3b82f61a;
  color: #3b82f6;
  font-size: 13px;
  font-weight: 600;
  cursor: pointer;
  white-space: nowrap;
  &:hover {
    background: #3b82f630;
  }
`;

const TipoBadge = styled.span`
  display: inline-flex;
  padding: 2px 8px;
  border-radius: 12px;
  font-size: 11px;
  font-weight: 600;
  background: ${({ $multi }) => ($multi ? "#8b5cf61a" : "#64748b1a")};
  color: ${({ $multi }) => ($multi ? "#8b5cf6" : "#64748b")};
`;

const ExportBtn = styled.button`
  display: flex;
  align-items: center;
  gap: 6px;
  padding: 0 12px;
  height: 34px;
  border-radius: 7px;
  border: 1px solid ${({ theme }) => theme.bg4 || "#d1d5db"};
  background: ${({ theme }) => theme.body};
  color: ${({ theme }) => theme.text};
  font-size: 13px;
  font-weight: 500;
  cursor: pointer;
  white-space: nowrap;
  &:hover:not(:disabled) {
    border-color: #6b7280;
  }
  &:disabled {
    opacity: 0.4;
    cursor: not-allowed;
  }
`;

const ResultInfo = styled.div`
  display: flex;
  align-items: center;
  gap: 5px;
  font-size: 12px;
  color: #6b7280;
  margin-left: auto;
  align-self: flex-end;
  padding-bottom: 6px;
`;

// ── Tabla ─────────────────────────────────────────────────────────────────────
const TableCard = styled.div`
  border: 1px solid ${({ theme }) => theme.bg4 || "#e5e7eb"};
  border-radius: 10px;
  overflow: hidden;
  /* Se dimensiona al contenido (no estira a toda la altura) con un piso mínimo.
     shrink:1 permite que scrollee internamente solo cuando el contenido supera
     el espacio disponible, evitando scrolls verticales innecesarios. */
  flex: 0 1 auto;
  min-height: 220px;
  display: flex;
  flex-direction: column;
`;

const TableScroll = styled.div`
  flex: 1;
  overflow: auto;
  min-height: 0;
`;

const Table = styled.table`
  width: 100%;
  border-collapse: collapse;
  background: ${({ theme }) => theme.body || "#fff"};
`;

const Th = styled.th`
  padding: 10px 12px;
  text-align: ${({ $right }) => ($right ? "right" : "left")};
  font-size: 12px;
  color: #6b7280;
  font-weight: 600;
  background: ${({ theme }) => theme.bg3 || "#f9fafb"};
  border-bottom: 1px solid ${({ theme }) => theme.bg4 || "#e5e7eb"};
  white-space: nowrap;
`;

const Tr = styled.tr`
  cursor: pointer;
  opacity: ${({ $anulado }) => ($anulado ? 0.55 : 1)};
  &:hover {
    background: ${({ theme }) => theme.bg3 || "#f9fafb"};
  }
  &:not(:last-child) td {
    border-bottom: 1px solid ${({ theme }) => theme.bg4 || "#e5e7eb"};
  }
`;

const Td = styled.td`
  padding: 9px 12px;
  font-size: 13px;
  color: ${({ theme }) => theme.text};
  vertical-align: middle;
  text-align: ${({ $right }) => ($right ? "right" : "left")};
`;

const ReciboNum = styled.span`
  font-weight: 600;
  color: ${({ theme }) => theme.accent || "#3b82f6"};
  font-size: 12px;
`;

const DateCell = styled.div`
  display: flex;
  flex-direction: column;
  gap: 1px;
`;

const DateSub = styled.span`
  font-size: 11px;
  color: #9ca3af;
`;

const DocSub = styled.span`
  font-size: 11px;
  color: #6b7280;
  font-variant-numeric: tabular-nums;
`;

const ClienteCell = styled.div`
  display: flex;
  flex-direction: column;
  gap: 2px;
`;

const TelLink = styled.a`
  font-size: 11px;
  color: #6b7280;
  text-decoration: none;
  &:hover {
    text-decoration: underline;
  }
`;

const CobradorCell = styled.span`
  font-size: 12px;
  color: #6b7280;
`;

const MontoCell = styled.div`
  display: flex;
  flex-direction: column;
  align-items: flex-end;
  gap: 1px;
`;

const MontoDesc = styled.span`
  font-size: 11px;
  color: #ef4444;
`;

const MoraBadge = styled.span`
  font-size: 12px;
  color: #f59e0b;
`;

const EstadoBadge = styled.span`
  display: inline-flex;
  padding: 2px 8px;
  border-radius: 12px;
  font-size: 11px;
  font-weight: 600;
  background: ${({ $estado }) =>
    $estado === "anulado" ? "#ef44441a" : "#22c55e1a"};
  color: ${({ $estado }) =>
    $estado === "anulado" ? "#ef4444" : "#22c55e"};
`;

const ActionWrap = styled.div`
  position: relative;
  display: inline-block;
`;
const ActionBtn = styled.button`
  display: inline-flex;
  align-items: center;
  gap: 6px;
  background: #1e293b;
  color: ${({ $anulado }) => ($anulado ? "#6b7280" : "#e2e8f0")};
  border: 1px solid ${({ $anulado }) => ($anulado ? "#374151" : "#334155")};
  border-radius: 6px;
  padding: 5px 10px;
  font-size: 11px;
  font-weight: 500;
  cursor: ${({ $anulado }) => ($anulado ? "not-allowed" : "pointer")};
  outline: none;
  min-width: 110px;
  transition: border-color 0.15s, background-color 0.15s;
  &:hover:not(:disabled) {
    border-color: #475569;
    background-color: #253347;
  }
  &:focus-visible {
    border-color: #3b82f6;
    box-shadow: 0 0 0 2px #3b82f620;
  }
  &:disabled {
    opacity: 0.4;
    cursor: not-allowed;
  }
  > span {
    flex: 1;
    text-align: left;
  }
`;
const ActionMenu = styled.div`
  position: absolute;
  right: 0;
  top: calc(100% + 4px);
  z-index: 20;
  min-width: 180px;
  background: #0f172a;
  border: 1px solid #334155;
  border-radius: 8px;
  padding: 4px;
  box-shadow: 0 8px 24px rgba(0, 0, 0, 0.4);
  display: flex;
  flex-direction: column;
  gap: 2px;
`;
const ActionItem = styled.button`
  display: flex;
  align-items: center;
  gap: 8px;
  width: 100%;
  background: transparent;
  border: 0;
  color: ${({ $danger }) => ($danger ? "#fca5a5" : "#e2e8f0")};
  font-size: 12px;
  font-weight: 500;
  padding: 7px 10px;
  border-radius: 5px;
  cursor: pointer;
  text-align: left;
  transition: background 0.12s, color 0.12s;
  &:hover {
    background: ${({ $danger }) => ($danger ? "#ef44441a" : "#1e293b")};
    color: ${({ $danger }) => ($danger ? "#ef4444" : "#fff")};
  }
`;
const ActionDivider = styled.div`
  height: 1px;
  background: #1e293b;
  margin: 4px 2px;
`;

// ── Paginación ────────────────────────────────────────────────────────────────
const PaginationBar = styled.div`
  display: flex;
  align-items: center;
  gap: 4px;
  justify-content: center;
  flex-wrap: wrap;
  flex-shrink: 0;
`;

const PageBtn = styled.button`
  width: 32px;
  height: 32px;
  border-radius: 7px;
  border: 1px solid ${({ theme }) => theme.bg4 || "#e5e7eb"};
  background: transparent;
  cursor: pointer;
  display: flex;
  align-items: center;
  justify-content: center;
  color: ${({ theme }) => theme.text};
  &:disabled {
    opacity: 0.35;
    cursor: not-allowed;
  }
`;

const PageNum = styled.button`
  min-width: 32px;
  height: 32px;
  border-radius: 7px;
  border: 1px solid
    ${({ $active, theme }) => ($active ? "currentColor" : theme.bg4 || "#e5e7eb")};
  background: transparent;
  color: ${({ $active, theme }) => ($active ? theme.accent || "#3b82f6" : theme.text)};
  font-size: 13px;
  font-weight: ${({ $active }) => ($active ? 700 : 400)};
  cursor: ${({ $active }) => ($active ? "default" : "pointer")};
  padding: 0 6px;
  &:hover:not([style*="default"]) {
    border-color: #6b7280;
  }
`;

const PageEllipsis = styled.span`
  width: 24px;
  text-align: center;
  color: #6b7280;
  font-size: 13px;
`;

const PageInfo = styled.span`
  font-size: 12px;
  color: #6b7280;
  margin-left: 8px;
`;

// ── Drawer detalle ────────────────────────────────────────────────────────────
const slideIn = keyframes`
  from { transform: translateX(100%); }
  to   { transform: translateX(0); }
`;

const Overlay = styled.div`
  position: fixed;
  inset: 0;
  background: rgba(0, 0, 0, 0.5);
  z-index: 200;
  display: flex;
  align-items: stretch;
  justify-content: flex-end;
`;

const Drawer = styled.div`
  width: min(460px, 100vw);
  background: ${({ theme }) => theme.body || "#fff"};
  display: flex;
  flex-direction: column;
  box-shadow: -4px 0 24px #0004;
  animation: ${slideIn} 0.22s ease;
  max-height: 100vh;
`;

const DrawerHead = styled.div`
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 14px 16px;
  border-bottom: 1px solid ${({ theme }) => theme.bg4 || "#e5e7eb"};
  position: relative;
  flex-shrink: 0;
`;

const DrawerTitle = styled.div`
  display: flex;
  align-items: center;
  gap: 8px;
  font-size: 15px;
  font-weight: 700;
  color: ${({ theme }) => theme.text};
  flex: 1;
`;

const DrawerClose = styled.button`
  position: absolute;
  right: 12px;
  top: 50%;
  transform: translateY(-50%);
  width: 28px;
  height: 28px;
  border-radius: 50%;
  border: 1px solid ${({ theme }) => theme.bg4 || "#e5e7eb"};
  background: transparent;
  cursor: pointer;
  display: flex;
  align-items: center;
  justify-content: center;
  color: ${({ theme }) => theme.text};
`;

const DrawerBody = styled.div`
  flex: 1;
  overflow-y: auto;
  padding: 14px;
  display: flex;
  flex-direction: column;
  gap: 10px;
`;

const DrawerFoot = styled.div`
  display: flex;
  gap: 6px;
  flex-wrap: wrap;
  padding: 10px 14px;
  border-top: 1px solid ${({ theme }) => theme.bg4 || "#e5e7eb"};
  flex-shrink: 0;
`;

const FootBtn = styled.button`
  display: inline-flex;
  align-items: center;
  gap: 5px;
  padding: 7px 12px;
  border-radius: 7px;
  font-size: 12px;
  font-weight: 500;
  border: 1px solid
    ${({ $danger, theme }) =>
      $danger ? "#ef444430" : theme.bg4 || "#e5e7eb"};
  background: transparent;
  color: ${({ $danger, theme }) => ($danger ? "#ef4444" : theme.text)};
  cursor: pointer;
  &:hover:not(:disabled) {
    background: ${({ $danger }) =>
      $danger ? "#ef44441a" : "#6b72801a"};
  }
  &:disabled {
    opacity: 0.5;
    cursor: not-allowed;
  }
`;

const InfoGrid = styled.div`
  display: flex;
  flex-direction: column;
  gap: 6px;
  background: ${({ theme }) => theme.bg3 || "#f9fafb"};
  border-radius: 8px;
  padding: 10px 12px;
  border: 1px solid ${({ theme }) => theme.bg4 || "#e5e7eb"};
`;

const InfoRow = styled.div`
  display: flex;
  justify-content: space-between;
  align-items: center;
  font-size: 13px;
  gap: 12px;
`;

const InfoLabel = styled.span`
  color: #6b7280;
  flex-shrink: 0;
`;

const InfoVal = styled.span`
  font-weight: 500;
  text-align: right;
  color: ${({ $accent, $red, $amber, theme }) =>
    $accent ? theme.accent || "#3b82f6" : $red ? "#ef4444" : $amber ? "#f59e0b" : theme.text};
`;

const CajaVal = styled.div`
  display: flex;
  flex-direction: column;
  align-items: flex-end;
  gap: 1px;
  min-width: 0;
`;

const CajaSub = styled.span`
  font-size: 11px;
  color: #6b7280;
  text-align: right;
`;

const DetailSection = styled.div`
  display: flex;
  flex-direction: column;
  gap: 6px;
`;

const DetailTitle = styled.div`
  font-size: 11px;
  font-weight: 600;
  color: #6b7280;
  text-transform: uppercase;
  letter-spacing: 0.05em;
`;

const DetailItem = styled.div`
  display: flex;
  justify-content: space-between;
  align-items: flex-start;
  padding: 7px 10px;
  border-radius: 7px;
  background: ${({ theme }) => theme.bg3 || "#f9fafb"};
  font-size: 12px;
  border: 1px solid ${({ theme }) => theme.bg4 || "#e5e7eb"};
`;

const DetailLeft = styled.div`
  display: flex;
  flex-direction: column;
  gap: 2px;
  color: ${({ theme }) => theme.text};
`;

const DetailRight = styled.div`
  display: flex;
  flex-direction: column;
  align-items: flex-end;
  gap: 2px;
  color: ${({ theme }) => theme.text};
`;

const ParcialBadge = styled.span`
  font-size: 10px;
  color: #f59e0b;
  background: #f59e0b1a;
  border: 1px solid #f59e0b30;
  padding: 1px 5px;
  border-radius: 10px;
`;

const AnulacionSection = styled.div`
  display: flex;
  align-items: flex-start;
  gap: 8px;
  margin: 12px 0 0;
  padding: 10px 12px;
  background: #ef444410;
  border: 1px solid #ef444430;
  border-radius: 8px;
  color: #ef4444;
  font-size: 0.8rem;
  svg { margin-top: 2px; flex-shrink: 0; }
`;

const AnulacionInfo = styled.div`
  display: flex;
  flex-direction: column;
  gap: 2px;
  strong { font-weight: 600; }
  span { color: #f87171; font-size: 0.75rem; }
  em { color: #fca5a5; font-size: 0.75rem; font-style: italic; }
`;

// ── Modales ───────────────────────────────────────────────────────────────────
const ModalBox = styled.div`
  margin: auto;
  width: min(420px, 95vw);
  background: ${({ theme }) => theme.body || "#fff"};
  border-radius: 12px;
  box-shadow: 0 20px 60px rgba(0, 0, 0, 0.35);
  overflow: hidden;
`;

const ModalHead = styled.div`
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 14px 16px;
  font-size: 14px;
  font-weight: 700;
  color: ${({ theme }) => theme.text};
  border-bottom: 1px solid ${({ theme }) => theme.bg4 || "#e5e7eb"};
  position: relative;
`;

const ModalClose = styled.button`
  position: absolute;
  right: 12px;
  border: none;
  background: none;
  cursor: pointer;
  color: #6b7280;
  padding: 4px;
  &:disabled {
    opacity: 0.4;
  }
`;

const ModalBody = styled.div`
  padding: 14px 16px;
  display: flex;
  flex-direction: column;
  gap: 8px;
`;

const FechaAviso = styled.p`
  margin: 0 0 12px;
  font-size: 12px;
  line-height: 1.5;
  color: #6b7280;
`;
const ModalLabel = styled.label`
  font-size: 12px;
  color: #6b7280;
`;

const ModalInput = styled.input`
  width: 100%;
  padding: 8px 10px;
  border-radius: 7px;
  border: 1px solid ${({ theme }) => theme.bg4 || "#d1d5db"};
  font-size: 13px;
  background: ${({ theme }) => theme.body};
  color: ${({ theme }) => theme.text};
  box-sizing: border-box;
  &::placeholder {
    color: #9ca3af;
  }
`;

const ModalActions = styled.div`
  display: flex;
  gap: 8px;
  justify-content: flex-end;
  margin-top: 4px;
`;

const ModalCancelBtn = styled.button`
  padding: 7px 14px;
  border-radius: 7px;
  border: 1px solid ${({ theme }) => theme.bg4 || "#e5e7eb"};
  background: transparent;
  font-size: 13px;
  cursor: pointer;
  color: ${({ theme }) => theme.text};
  &:disabled {
    opacity: 0.5;
  }
`;

const ModalDangerBtn = styled.button`
  padding: 7px 14px;
  border-radius: 7px;
  border: 1px solid #ef444450;
  background: #ef44441a;
  color: #ef4444;
  font-size: 13px;
  font-weight: 600;
  cursor: pointer;
  &:hover:not(:disabled) {
    background: #ef444430;
  }
  &:disabled {
    opacity: 0.5;
    cursor: not-allowed;
  }
`;

const ModalPrimaryBtn = styled.button`
  padding: 7px 14px;
  border-radius: 7px;
  border: 1px solid ${({ theme }) => (theme.accent || "#3b82f6") + "50"};
  background: ${({ theme }) => (theme.accent || "#3b82f6") + "1a"};
  color: ${({ theme }) => theme.accent || "#3b82f6"};
  font-size: 13px;
  font-weight: 600;
  cursor: pointer;
  &:hover:not(:disabled) {
    background: ${({ theme }) => (theme.accent || "#3b82f6") + "30"};
  }
  &:disabled {
    opacity: 0.5;
    cursor: not-allowed;
  }
`;

// ── PDF ───────────────────────────────────────────────────────────────────────
const PdfPanel = styled.div`
  width: min(560px, 100vw);
  height: 100vh;
  background: ${({ theme }) => theme.body || "#fff"};
  display: flex;
  flex-direction: column;
  box-shadow: -4px 0 24px #0004;
  animation: ${slideIn} 0.22s ease;
`;

// ── Feedback ──────────────────────────────────────────────────────────────────
const LoadingInline = styled.div`
  display: inline-flex;
  align-items: center;
  gap: 8px;
  color: #6b7280;
  font-size: 14px;
`;

const EmptyState = styled.div`
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 8px;
  color: #9ca3af;
  span {
    font-size: 14px;
  }
`;
