/* Admin: verificar comprobantes — mensualidades y tienda */

const MOCK_COMPROBANTES = [];
const MOCK_ORDENES_TIENDA = [];

const formatFecha = (iso) => {
  if (!iso) return '—';
  try {
    return new Date(iso).toLocaleString('es-MX', { dateStyle: 'medium', timeStyle: 'short' });
  } catch {
    return iso;
  }
};

const rowStudent = (row) => {
  if (row.students?.full_name) return row.students;
  return { code: row.guest_student_code || '—', full_name: row.guest_student_name || 'Invitado' };
};

const rowConcept = (row, kind) => {
  if (kind === 'payment') return row.concept || 'Mensualidad';
  const items = row.order_items || [];
  if (!items.length) return 'Orden tienda';
  return items.map(i => `${i.product_name}${i.size ? ` (${i.size})` : ''} ×${i.qty}`).join(', ');
};

const rowAmount = (row, kind) => (kind === 'payment' ? row.amount : row.total);

/** Alumno en formato mapStudentRow para PDF / WhatsApp desde fila de historial. */
const historialPaymentToAlumno = (row) => {
  const st = row.students || rowStudent(row);
  return {
    id: st.code,
    name: st.full_name,
    cat: st.category || '—',
    tutor: st.tutor_name || '—',
    phone: st.tutor_phone || '—',
    student_phone: st.student_phone || '—',
    photoUrl: st.photo_path && typeof getStoragePublicUrl === 'function'
      ? getStoragePublicUrl('gallery', st.photo_path)
      : null,
  };
};

/** Alumno desde fila de historial (mensualidad u orden tienda). */
const historialRowToAlumno = (row, kind) => {
  if (kind === 'payment') return historialPaymentToAlumno(row);
  const st = row.students || rowStudent(row);
  const phone = (st?.tutor_phone && st.tutor_phone !== '—')
    ? st.tutor_phone
    : (row.guest_phone || '—');
  return {
    id: st?.code || row.guest_student_code || '—',
    name: st?.full_name || row.guest_student_name || '—',
    cat: st?.category || '—',
    tutor: st?.tutor_name || row.guest_tutor_name || '—',
    phone,
    student_phone: st?.student_phone || '—',
    photoUrl: st?.photo_path && typeof getStoragePublicUrl === 'function'
      ? getStoragePublicUrl('gallery', st.photo_path)
      : null,
  };
};

const historialReceiptHasPhone = (alumno) => (
  (alumno.phone && alumno.phone !== '—')
  || (alumno.student_phone && alumno.student_phone !== '—')
);

async function sendHistorialReceiptWhatsApp (alumno, row, kind, theme) {
  const settings = await fetchAcademySettings();
  if (kind === 'payment') {
    if (typeof sendPaymentReceiptPdfViaWhatsApp !== 'function') {
      throw new Error('Módulo PDF no cargado. Recarga la página (F5).');
    }
    return sendPaymentReceiptPdfViaWhatsApp(alumno, row, { settings, theme });
  }
  if (typeof sendStoreOrderReceiptPdfViaWhatsApp !== 'function') {
    throw new Error('Módulo PDF no cargado. Recarga la página (F5).');
  }
  return sendStoreOrderReceiptPdfViaWhatsApp(alumno, row, { settings, theme });
}

const HISTORIAL_MONTH_LABELS = [
  'Enero', 'Febrero', 'Marzo', 'Abril', 'Mayo', 'Junio',
  'Julio', 'Agosto', 'Septiembre', 'Octubre', 'Noviembre', 'Diciembre',
];

const historyConfirmedAtIso = (row, kind) => {
  if (kind === 'payment') return row.reviewed_at || row.paid_at || null;
  return row.reviewed_at || null;
};

const historyConfirmedAtMs = (row, kind) => {
  const iso = historyConfirmedAtIso(row, kind);
  if (!iso) return null;
  const t = new Date(iso).getTime();
  return Number.isNaN(t) ? null : t;
};

const startOfMonthMs = (year, month) => new Date(year, month - 1, 1).getTime();

/** Oculta mensualidades con due_date anterior al mes de ingreso del alumno. */
const historialRowAllowed = (row, kind) => {
  if (kind === 'payment') {
    const joinedAt = row.students?.joined_at;
    if (joinedAt && typeof isPaymentBillableForJoin === 'function') {
      return isPaymentBillableForJoin(row, joinedAt);
    }
    return true;
  }
  const joinedAt = row.students?.joined_at;
  if (joinedAt && typeof isOrderBillableForJoin === 'function') {
    return isOrderBillableForJoin(row, joinedAt);
  }
  if (joinedAt) {
    const join = new Date(joinedAt);
    if (!Number.isNaN(join.getTime())) {
      const joinStart = new Date(join.getFullYear(), join.getMonth(), 1).getTime();
      const at = historyConfirmedAtMs(row, kind);
      return at != null && at >= joinStart;
    }
  }
  return true;
};

const historialSelectStyle = (theme) => ({
  padding: '8px 12px',
  borderRadius: 8,
  border: `1px solid ${theme.border}`,
  background: theme.bgInput,
  color: theme.text,
  fontSize: 13,
  fontFamily: 'Inter, sans-serif',
  minWidth: 0,
});

/** Oculta filas del listado de revisión sin borrar datos en Supabase (solo este navegador). */
const COMPROBANTES_QUEUE_DISMISS_KEYS = {
  payment: 'tecos:comprobantes-queue-dismiss-payments-v1',
  order: 'tecos:comprobantes-queue-dismiss-orders-v1',
};

function readComprobantesQueueDismissed (kind) {
  const key = kind === 'payment'
    ? COMPROBANTES_QUEUE_DISMISS_KEYS.payment
    : COMPROBANTES_QUEUE_DISMISS_KEYS.order;
  try {
    const raw = localStorage.getItem(key);
    const arr = raw ? JSON.parse(raw) : [];
    return new Set((Array.isArray(arr) ? arr : []).map(String));
  } catch {
    return new Set();
  }
}

function saveComprobantesQueueDismissed (kind, idSet) {
  const key = kind === 'payment'
    ? COMPROBANTES_QUEUE_DISMISS_KEYS.payment
    : COMPROBANTES_QUEUE_DISMISS_KEYS.order;
  localStorage.setItem(key, JSON.stringify([...idSet]));
}

function dismissComprobantesQueueIds (ids, kind) {
  const set = readComprobantesQueueDismissed(kind);
  (ids || []).forEach((id) => { if (id != null) set.add(String(id)); });
  saveComprobantesQueueDismissed(kind, set);
}

function clearComprobantesQueueDismissed (kind) {
  saveComprobantesQueueDismissed(kind, new Set());
}

function filterComprobantesQueueRows (rows, kind) {
  const hidden = readComprobantesQueueDismissed(kind);
  return (rows || []).filter((r) => !hidden.has(String(r.id)));
}

/** Oculta filas del historial admin (órdenes confirmadas no se pueden borrar en BD). */
const HISTORIAL_DISMISS_KEYS = {
  payment: 'tecos:historial-dismiss-payments-v1',
  order: 'tecos:historial-dismiss-orders-v1',
};

function readHistorialDismissed (kind) {
  const key = kind === 'payment' ? HISTORIAL_DISMISS_KEYS.payment : HISTORIAL_DISMISS_KEYS.order;
  try {
    const raw = localStorage.getItem(key);
    const arr = raw ? JSON.parse(raw) : [];
    return new Set((Array.isArray(arr) ? arr : []).map(String));
  } catch {
    return new Set();
  }
}

function dismissHistorialIds (ids, kind) {
  const key = kind === 'payment' ? HISTORIAL_DISMISS_KEYS.payment : HISTORIAL_DISMISS_KEYS.order;
  const set = readHistorialDismissed(kind);
  (ids || []).forEach((id) => { if (id != null) set.add(String(id)); });
  localStorage.setItem(key, JSON.stringify([...set]));
}

/** Tras reinicio en Supabase: quita filas ocultas solo en este navegador. */
function clearTecosHistorialDismissedStorage () {
  try {
    Object.values(HISTORIAL_DISMISS_KEYS).forEach((key) => localStorage.removeItem(key));
  } catch (_) { /* ignore */ }
}

const ComprobantesModule = ({ theme }) => {
  const [tab, setTab] = React.useState('mensualidades');
  const [items, setItems] = React.useState([]);
  const [loading, setLoading] = React.useState(true);
  const [selected, setSelected] = React.useState(null);
  const [previewUrl, setPreviewUrl] = React.useState(null);
  const [previewLoading, setPreviewLoading] = React.useState(false);
  const [rejectNotes, setRejectNotes] = React.useState('');
  const [busy, setBusy] = React.useState(false);
  const [error, setError] = React.useState('');
  const [authHint, setAuthHint] = React.useState('');
  const [pendingFocusId, setPendingFocusId] = React.useState(null);
  const [selectedIds, setSelectedIds] = React.useState(() => new Set());

  const kind = tab === 'mensualidades' ? 'payment' : 'order';

  const mapRows = (dbRows) => dbRows.map(r => ({ ...r, kind: tab === 'mensualidades' ? 'payment' : 'order' }));

  const load = React.useCallback(async () => {
    setLoading(true);
    setError('');
    setAuthHint('');
    if (!isSupabaseReady()) {
      setItems([]);
      setLoading(false);
      return;
    }
    const session = await getAuthSession();
    if (!session) {
      setAuthHint('Inicia sesión como administrador con tu correo y contraseña (pantalla Iniciar sesión → Administrador). Sin sesión no se ven los comprobantes guardados en la base de datos.');
      setItems([]);
      setLoading(false);
      return;
    }
    try {
      const rows = tab === 'mensualidades'
        ? await fetchPaymentsPendingReview()
        : (typeof fetchStoreOrdersAdminQueue === 'function'
          ? await fetchStoreOrdersAdminQueue()
          : await fetchOrdersPendingReview());
      setItems(filterComprobantesQueueRows(mapRows(rows), kind));
    } catch (e) {
      console.error('[Tecos] comprobantes', e);
      setAuthHint(e?.message?.includes('JWT') || e?.code === 'PGRST301'
        ? 'Sesión expirada. Vuelve a iniciar sesión como administrador.'
        : 'No se pudieron leer comprobantes. Confirma que tu usuario tiene role = admin en la tabla profiles.');
      setItems([]);
    }
    setLoading(false);
  }, [tab, kind]);

  React.useEffect(() => { load(); }, [load]);

  React.useEffect(() => {
    setSelectedIds(new Set());
    setSelected(null);
  }, [tab]);

  React.useEffect(() => {
    const onRefresh = () => load();
    window.addEventListener('tecos:comprobantes-changed', onRefresh);
    window.addEventListener('tecos:orders-changed', onRefresh);
    return () => {
      window.removeEventListener('tecos:comprobantes-changed', onRefresh);
      window.removeEventListener('tecos:orders-changed', onRefresh);
    };
  }, [load]);

  React.useEffect(() => {
    const onFocus = (e) => {
      const d = e.detail;
      if (!d || d.view !== 'comprobantes') return;
      if (d.comprobantesTab) setTab(d.comprobantesTab);
      if (d.entityId) setPendingFocusId(d.entityId);
    };
    window.addEventListener('tecos:admin-focus-entity', onFocus);
    return () => window.removeEventListener('tecos:admin-focus-entity', onFocus);
  }, []);

  React.useEffect(() => {
    if (!pendingFocusId || loading) return;
    const row = items.find(r => r.id === pendingFocusId);
    if (row) {
      setSelected(row);
      setPendingFocusId(null);
    }
  }, [pendingFocusId, items, loading]);

  React.useEffect(() => {
    let cancelled = false;
    (async () => {
      if (!selected || kind !== 'order') return;
      if (
        selected.receipt_path
        && selected.status === 'pendiente'
        && typeof adminRepairOrderReceiptStatus === 'function'
        && isSupabaseReady()
      ) {
        try {
          await adminRepairOrderReceiptStatus(selected.id);
          const repaired = { ...selected, status: 'en_revision' };
          if (!cancelled) {
            setSelected(repaired);
            setItems(prev => prev.map(r => (r.id === repaired.id ? { ...r, status: 'en_revision' } : r)));
          }
        } catch (e) {
          console.warn('[Tecos] repair order receipt status', e);
        }
      }
    })();
    return () => { cancelled = true; };
  }, [selected?.id, kind]);

  React.useEffect(() => {
    let cancelled = false;
    (async () => {
      if (!selected?.receipt_path) {
        setPreviewUrl(null);
        setPreviewLoading(false);
        return;
      }
      if (!isSupabaseReady()) {
        setPreviewUrl(null);
        setPreviewLoading(false);
        return;
      }
      setPreviewLoading(true);
      setPreviewUrl(null);
      const url = await getReceiptSignedUrl(selected.receipt_path);
      if (!cancelled) {
        setPreviewUrl(url);
        setPreviewLoading(false);
      }
    })();
    return () => { cancelled = true; };
  }, [selected?.id, selected?.receipt_path, selected?.status]);

  const closeDetail = () => {
    setSelected(null);
    setPreviewUrl(null);
    setRejectNotes('');
    setError('');
  };

  const studentCodeForSync = (row) => {
    const raw = row?.students?.code || row?.guest_student_code || '';
    return typeof normalizeTecStudentCode === 'function' ? normalizeTecStudentCode(raw) : String(raw).trim().toUpperCase();
  };

  const afterComprobanteAction = (code, isOrder) => {
    notifyPaymentsChanged?.();
    notifyComprobantesChanged?.();
    if (isOrder && typeof notifyOrdersChanged === 'function') notifyOrdersChanged();
    window.dispatchNotificationsChanged?.();
    if (code) syncMockAlumnoPayment(code, null);
    if (isOrder) {
      notifyProductsChanged?.();
      if (typeof notifyOrdersChanged === 'function') notifyOrdersChanged();
    }
  };

  const handleApprove = async () => {
    if (!selected) return;
    setBusy(true);
    setError('');
    const isMock = String(selected.id).startsWith('mock-');
    const code = studentCodeForSync(selected);
    try {
      if (isSupabaseReady() && !isMock) {
        if (kind === 'payment') {
          await approvePaymentReceipt(selected.id);
        } else {
          await approveOrderReceipt(selected.id);
          syncMockAlumnoOrder(selected.order_number, 'confirmado');
        }
      } else {
        setItems(prev => prev.filter(p => p.id !== selected.id));
        if (kind === 'order') {
          syncMockAlumnoOrder(selected.order_number, 'confirmado');
        }
      }
      closeDetail();
      afterComprobanteAction(kind === 'payment' ? code : null, kind === 'order');
      await load();
    } catch (e) {
      setError(e.message || 'No se pudo aprobar');
    }
    setBusy(false);
  };

  const handleReject = async () => {
    if (!selected) return;
    setBusy(true);
    setError('');
    const isMock = String(selected.id).startsWith('mock-');
    const code = studentCodeForSync(selected);
    try {
      if (isSupabaseReady() && !isMock) {
        if (kind === 'payment') {
          await rejectPaymentReceipt({ paymentId: selected.id, adminNotes: rejectNotes });
        } else {
          await rejectOrderReceipt({ orderId: selected.id, adminNotes: rejectNotes });
          syncMockAlumnoOrder(selected.order_number, 'rechazado', rejectNotes);
        }
      } else {
        setItems(prev => prev.filter(p => p.id !== selected.id));
        if (kind === 'order') {
          syncMockAlumnoOrder(selected.order_number, 'rechazado', rejectNotes);
        }
      }
      closeDetail();
      afterComprobanteAction(kind === 'payment' ? code : null, kind === 'order');
      await load();
    } catch (e) {
      setError(e.message || 'No se pudo rechazar');
    }
    setBusy(false);
  };

  const toggleSelect = (id) => {
    setSelectedIds(prev => {
      const next = new Set(prev);
      if (next.has(id)) next.delete(id);
      else next.add(id);
      return next;
    });
  };

  const toggleSelectAll = () => {
    if (selectedIds.size === items.length) setSelectedIds(new Set());
    else setSelectedIds(new Set(items.map(r => r.id)));
  };

  const handleBulkDecline = async () => {
    if (tab !== 'tienda' || !selectedIds.size) return;
    const notes = window.prompt('Motivo de la declinación (opcional):', 'Orden cancelada por administración.');
    if (notes === null) return;
    if (!confirm(`¿Declinar ${selectedIds.size} orden(es)? El alumno deberá subir comprobante de nuevo.`)) return;
    setBusy(true);
    setError('');
    try {
      for (const id of selectedIds) {
        const row = items.find(r => r.id === id);
        if (!row) continue;
        await rejectOrderReceipt({ orderId: id, adminNotes: notes.trim() || null });
        syncMockAlumnoOrder(row.order_number, 'rechazado', notes.trim());
      }
      setSelectedIds(new Set());
      closeDetail();
      afterComprobanteAction(null, true);
      await load();
    } catch (e) {
      setError(e.message || 'No se pudo declinar');
      alert(e.message || 'No se pudo declinar');
    }
    setBusy(false);
  };

  const handleBulkDelete = async () => {
    if (tab !== 'tienda' || !selectedIds.size) return;
    const notes = window.prompt('Motivo para el alumno (opcional):', 'Orden eliminada. Realiza un nuevo pedido en la tienda.');
    if (notes === null) return;
    if (!confirm(`¿Eliminar ${selectedIds.size} orden(es) de forma permanente? Esta acción no se puede deshacer.`)) return;
    setBusy(true);
    try {
      if (typeof adminDeleteStoreOrders === 'function') {
        await adminDeleteStoreOrders([...selectedIds], notes.trim() || null);
      } else {
        throw new Error('Función admin_delete_store_orders no disponible. Ejecuta la migración 049 en Supabase.');
      }
      setSelectedIds(new Set());
      closeDetail();
      afterComprobanteAction(null, true);
      await load();
    } catch (e) {
      alert(e.message || 'No se pudo eliminar');
    }
    setBusy(false);
  };

  const dismissQueueConfirm = (count, singleLabel) => {
    const intro = count === 1
      ? `¿Quitar ${singleLabel} de esta lista?`
      : `¿Ocultar ${count} registro(s) de esta lista?`;
    return confirm(
      `${intro}\n\n`
      + 'Solo limpia la pantalla de revisión en este navegador. '
      + 'No borra pagos ni órdenes en la base de datos. '
      + 'Si el registro sigue pendiente en el sistema, puedes volver a verlo con Actualizar '
      + 'después de restaurar ocultos (si aplica).',
    );
  };

  const handleDismissFromQueue = (row) => {
    if (!row) return;
    const label = kind === 'payment'
      ? `la mensualidad de ${rowStudent(row).full_name}`
      : `la orden ${row.order_number}`;
    if (!dismissQueueConfirm(1, label)) return;
    dismissComprobantesQueueIds([row.id], kind);
    if (selected?.id === row.id) closeDetail();
    setSelectedIds((prev) => {
      const next = new Set(prev);
      next.delete(row.id);
      return next;
    });
    setItems((prev) => prev.filter((p) => p.id !== row.id));
  };

  const handleVaciarQueue = () => {
    if (!items.length) return;
    const label = tab === 'mensualidades' ? 'todas las mensualidades visibles' : 'todos los comprobantes visibles';
    if (!dismissQueueConfirm(items.length, label)) return;
    dismissComprobantesQueueIds(items.map((r) => r.id), kind);
    setSelectedIds(new Set());
    closeDetail();
    setItems([]);
  };

  const handleRestoreDismissed = () => {
    const hidden = readComprobantesQueueDismissed(kind);
    if (!hidden.size) return;
    if (!confirm(
      `¿Volver a mostrar ${hidden.size} registro(s) ocultos en esta pestaña?\n\n`
      + 'Volverán a cargarse desde la base al actualizar.',
    )) return;
    clearComprobantesQueueDismissed(kind);
    load();
  };

  const hiddenQueueCount = readComprobantesQueueDismissed(kind).size;

  const handleDeleteOne = async () => {
    if (!selected || kind !== 'order') return;
    const notes = window.prompt('Motivo para el alumno (opcional):', rejectNotes.trim() || 'Orden eliminada. Realiza un nuevo pedido.');
    if (notes === null) return;
    if (!confirm(`¿Eliminar la orden ${selected.order_number} permanentemente?`)) return;
    setBusy(true);
    setError('');
    try {
      await adminDeleteStoreOrders([selected.id], notes.trim() || null);
      closeDetail();
      afterComprobanteAction(studentCodeForSync(selected), true);
      await load();
    } catch (e) {
      setError(e.message || 'No se pudo eliminar');
    }
    setBusy(false);
  };

  const tabBtn = (id, label, count) => (
    <button key={id} onClick={() => { setTab(id); setSelected(null); }} style={{
      padding: '10px 18px', borderRadius: 999, border: 'none', cursor: 'pointer',
      background: tab === id ? theme.primary : theme.bgInput,
      color: tab === id ? '#fff' : theme.textDim,
      fontSize: 12, fontWeight: 700, letterSpacing: 0.5, textTransform: 'uppercase',
      fontFamily: 'Inter, sans-serif', display: 'flex', alignItems: 'center', gap: 8,
    }}>
      {label}
      <span style={{
        padding: '2px 8px', borderRadius: 999, fontSize: 10,
        background: tab === id ? 'rgba(255,255,255,0.25)' : theme.bgElev,
      }}>{count}</span>
    </button>
  );

  return (
    <div className="tecos-page-shell" style={{ padding: 32 }}>
      <div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'flex-start', marginBottom: 16, flexWrap: 'wrap', gap: 12 }}>
        <div>
          <h2 style={{ fontFamily: 'Bebas Neue, sans-serif', fontSize: 36, color: theme.text, letterSpacing: 1, margin: 0, fontWeight: 400 }}>
            VERIFICAR COMPROBANTES
          </h2>
          <p style={{ color: theme.textDim, fontSize: 14, fontFamily: 'Inter, sans-serif', marginTop: 6, maxWidth: 640 }}>
            Mensualidades con comprobante subido. En <strong>Tienda online</strong> aparecen órdenes <strong>pendientes</strong> (sin comprobante aún) y <strong>en revisión</strong> (con comprobante).
            Al confirmar una orden se descuenta inventario por producto y talla.
            <strong> Vaciar</strong> y <strong>Eliminar</strong> en fila solo ocultan el listado aquí; no borran datos guardados (usa <strong>Eliminar orden</strong> en el detalle de tienda para borrar en la base).
          </p>
        </div>
        <div style={{ display: 'flex', gap: 8, flexWrap: 'wrap', alignItems: 'center' }}>
          {items.length > 0 && (
            <Btn theme={theme} kind="ghost" icon="trash" onClick={handleVaciarQueue} disabled={loading || busy}>
              {tab === 'mensualidades' ? 'Vaciar mensualidades' : 'Vaciar comprobantes'}
            </Btn>
          )}
          {hiddenQueueCount > 0 && (
            <Btn theme={theme} kind="ghost" size="sm" onClick={handleRestoreDismissed} disabled={loading || busy}>
              Restaurar ocultos ({hiddenQueueCount})
            </Btn>
          )}
          <Btn theme={theme} kind="ghost" icon="history" onClick={load} disabled={loading}>Actualizar</Btn>
        </div>
      </div>

      {authHint && (
        <div style={{
          marginBottom: 16, padding: 14, borderRadius: 10,
          background: `${theme.warning}18`, border: `1px solid ${theme.warning}55`,
          color: theme.text, fontSize: 13, fontFamily: 'Inter, sans-serif', lineHeight: 1.5,
        }}>
          {authHint}
        </div>
      )}

      <div style={{ display: 'flex', gap: 8, marginBottom: 12, flexWrap: 'wrap', alignItems: 'center' }}>
        {tabBtn('mensualidades', 'Mensualidades', tab === 'mensualidades' && !loading ? items.length : '—')}
        {tabBtn('tienda', 'Tienda online', tab === 'tienda' && !loading ? items.length : '—')}
      </div>

      {tab === 'tienda' && items.length > 0 && (
        <div style={{ display: 'flex', gap: 8, marginBottom: 16, flexWrap: 'wrap', alignItems: 'center' }}>
          <Btn theme={theme} kind="ghost" size="sm" onClick={toggleSelectAll} disabled={loading || busy}>
            {selectedIds.size === items.length ? 'Quitar selección' : 'Seleccionar todas'}
          </Btn>
          <Btn theme={theme} kind="danger" size="sm" icon="x" onClick={handleBulkDecline}
            disabled={!selectedIds.size || busy}>
            Declinar seleccionadas ({selectedIds.size})
          </Btn>
          <Btn theme={theme} kind="danger" size="sm" icon="trash" onClick={handleBulkDelete}
            disabled={!selectedIds.size || busy}>
            Eliminar seleccionadas ({selectedIds.size})
          </Btn>
          <span style={{ fontSize: 11, color: theme.textDim, fontFamily: 'Inter, sans-serif' }}>
            Declinar: el alumno ve la orden rechazada y debe subir comprobante otra vez. Eliminar: quita la orden (útil para pedidos inválidos como ID 001).
          </span>
        </div>
      )}

      <Card theme={theme} style={{ padding: 0, overflow: 'hidden' }}>
        {loading ? (
          <div style={{ padding: 48, textAlign: 'center', color: theme.textDim, fontFamily: 'Inter, sans-serif' }}>Cargando…</div>
        ) : items.length === 0 ? (
          <div style={{ padding: 48, textAlign: 'center' }}>
            <Icon name="check" size={48} style={{ color: theme.success, marginBottom: 12 }} />
            <div style={{ color: theme.text, fontWeight: 600, fontFamily: 'Inter, sans-serif' }}>
              {tab === 'mensualidades'
                ? 'No hay mensualidades pendientes de revisión'
                : 'No hay órdenes de tienda pendientes ni en revisión'}
            </div>
          </div>
        ) : (
          <table style={{ width: '100%', borderCollapse: 'collapse', fontFamily: 'Inter, sans-serif' }}>
            <thead>
              <tr style={{ background: theme.bgInput, borderBottom: `1px solid ${theme.border}` }}>
                {(kind === 'payment'
                  ? ['Alumno', 'ID', 'Concepto', 'Monto', 'Subido', 'Acción']
                  : ['', 'Alumno', 'ID', 'Orden / Productos', 'Monto', 'Estado', 'Acción']
                ).map(h => (
                  <th key={h} style={{ padding: '14px 16px', textAlign: 'left', fontSize: 10, fontWeight: 700, color: theme.textMute, letterSpacing: 1, textTransform: 'uppercase' }}>{h}</th>
                ))}
              </tr>
            </thead>
            <tbody>
              {items.map((row, i) => {
                const st = rowStudent(row);
                return (
                  <tr key={row.id} style={{
                    borderBottom: i < items.length - 1 ? `1px solid ${theme.border}` : 'none',
                    background: selectedIds.has(row.id) ? `${theme.primary}10` : 'transparent',
                  }}
                    onMouseEnter={e => { if (!selectedIds.has(row.id)) e.currentTarget.style.background = theme.bgInput; }}
                    onMouseLeave={e => { if (!selectedIds.has(row.id)) e.currentTarget.style.background = selectedIds.has(row.id) ? `${theme.primary}10` : 'transparent'; }}
                  >
                    {kind === 'order' && (
                      <td style={{ ...tdStyle(theme), width: 44 }}>
                        <input
                          type="checkbox"
                          checked={selectedIds.has(row.id)}
                          onChange={() => toggleSelect(row.id)}
                          aria-label={`Seleccionar ${row.order_number}`}
                        />
                      </td>
                    )}
                    <td style={tdStyle(theme)}>
                      <div style={{ display: 'flex', alignItems: 'center', gap: 10 }}>
                        <Avatar name={st.full_name} size={36} theme={theme} />
                        <span style={{ fontWeight: 600 }}>{st.full_name}</span>
                      </div>
                    </td>
                    <td style={tdStyle(theme)}>
                      <span style={{ fontFamily: 'Bebas Neue, sans-serif', fontSize: 16, color: theme.primary }}>{st.code}</span>
                    </td>
                    <td style={tdStyle(theme)}>
                      {kind === 'order' && (
                        <div style={{ fontSize: 11, color: theme.primary, fontWeight: 700, marginBottom: 4 }}>{row.order_number}</div>
                      )}
                      <div style={{ fontSize: 13 }}>{rowConcept(row, kind)}</div>
                    </td>
                    <td style={tdStyle(theme)}>
                      <span style={{ fontFamily: 'Bebas Neue, sans-serif', fontSize: 20 }}>${Number(rowAmount(row, kind))}</span>
                    </td>
                    <td style={tdStyle(theme)}>
                      {kind === 'order'
                        ? <StatusPill status={row.status} theme={theme} />
                        : formatFecha(row.receipt_uploaded_at)}
                    </td>
                    <td style={tdStyle(theme)}>
                      <div style={{ display: 'flex', gap: 6, flexWrap: 'wrap', alignItems: 'center' }}>
                        <Btn theme={theme} size="sm" icon="eye" onClick={() => { setSelected(row); setRejectNotes(''); setError(''); }}>
                          {kind === 'order' && !row.receipt_path ? 'Ver orden' : 'Revisar'}
                        </Btn>
                        <Btn
                          theme={theme}
                          size="sm"
                          kind="ghost"
                          icon="trash"
                          onClick={() => handleDismissFromQueue(row)}
                          disabled={busy}
                          title="Quitar de la lista sin borrar datos guardados"
                        >
                          Eliminar
                        </Btn>
                      </div>
                    </td>
                  </tr>
                );
              })}
            </tbody>
          </table>
        )}
      </Card>

      <ComprobanteReviewModal
        open={!!selected}
        onClose={closeDetail}
        theme={theme}
        row={selected}
        kind={kind}
        previewUrl={previewUrl}
        previewLoading={previewLoading}
        rejectNotes={rejectNotes}
        setRejectNotes={setRejectNotes}
        onApprove={handleApprove}
        onReject={handleReject}
        onDelete={tab === 'tienda' ? handleDeleteOne : undefined}
        busy={busy}
        error={error}
      />
    </div>
  );
};

const HistorialModule = ({ theme }) => {
  const now = React.useMemo(() => new Date(), []);
  const [tab, setTab] = React.useState('mensualidades');
  const [rawItems, setRawItems] = React.useState([]);
  const [fromYear, setFromYear] = React.useState(() => now.getFullYear());
  const [fromMonth, setFromMonth] = React.useState(1);
  const [loading, setLoading] = React.useState(true);
  const [selected, setSelected] = React.useState(null);
  const [previewUrl, setPreviewUrl] = React.useState(null);
  const [previewLoading, setPreviewLoading] = React.useState(false);
  const [error, setError] = React.useState('');
  const [busy, setBusy] = React.useState(false);
  const [waBusyId, setWaBusyId] = React.useState(null);
  const [dismissTick, setDismissTick] = React.useState(0);

  const kind = tab === 'mensualidades' ? 'payment' : 'order';
  const yearOptions = React.useMemo(() => {
    const y = now.getFullYear();
    return [y, y - 1, y - 2, y - 3];
  }, [now]);

  const mapRows = (dbRows) => dbRows.map(r => ({ ...r, kind: tab === 'mensualidades' ? 'payment' : 'order' }));

  const fromCutoffMs = React.useMemo(
    () => startOfMonthMs(fromYear, fromMonth),
    [fromYear, fromMonth],
  );

  const hiddenHistorial = React.useMemo(
    () => readHistorialDismissed(kind),
    [kind, dismissTick],
  );

  const items = React.useMemo(() => {
    return rawItems.filter(row => {
      if (hiddenHistorial.has(String(row.id))) return false;
      const at = historyConfirmedAtMs(row, kind);
      if (at == null) return false;
      return at >= fromCutoffMs;
    });
  }, [rawItems, kind, fromCutoffMs, hiddenHistorial]);

  const load = React.useCallback(async () => {
    setLoading(true);
    setError('');
    if (!isSupabaseReady()) {
      setRawItems([]);
      setLoading(false);
      return;
    }
    const session = await getAuthSession();
    if (!session) {
      setError('Inicia sesión como administrador para ver el historial.');
      setRawItems([]);
      setLoading(false);
      return;
    }
    try {
      const rows = tab === 'mensualidades'
        ? await fetchPaymentsConfirmedHistory()
        : await fetchStoreOrdersHistory();
      setRawItems(mapRows(rows));
    } catch (e) {
      console.error('[Tecos] historial', e);
      setError('No se pudo cargar el historial.');
      setRawItems([]);
    }
    setLoading(false);
  }, [tab]);

  React.useEffect(() => { load(); }, [load]);

  React.useEffect(() => {
    setSelected(null);
    setPreviewUrl(null);
  }, [tab]);

  React.useEffect(() => {
    const onRefresh = () => load();
    window.addEventListener('tecos:comprobantes-changed', onRefresh);
    window.addEventListener('tecos:orders-changed', onRefresh);
    return () => {
      window.removeEventListener('tecos:comprobantes-changed', onRefresh);
      window.removeEventListener('tecos:orders-changed', onRefresh);
    };
  }, [load]);

  React.useEffect(() => {
    let cancelled = false;
    (async () => {
      if (!selected?.receipt_path) {
        setPreviewUrl(null);
        setPreviewLoading(false);
        return;
      }
      if (!isSupabaseReady()) return;
      setPreviewLoading(true);
      setPreviewUrl(null);
      const url = await getReceiptSignedUrl(selected.receipt_path);
      if (!cancelled) {
        setPreviewUrl(url);
        setPreviewLoading(false);
      }
    })();
    return () => { cancelled = true; };
  }, [selected?.id, selected?.receipt_path]);

  const sendRowReceiptWa = async (row, ev) => {
    ev?.stopPropagation?.();
    const alumno = historialRowToAlumno(row, kind);
    if (!historialReceiptHasPhone(alumno)) {
      alert('Registra el teléfono del tutor o del alumno para enviar el comprobante por WhatsApp.');
      return;
    }
    setWaBusyId(row.id);
    try {
      const sent = await sendHistorialReceiptWhatsApp(alumno, row, kind, theme);
      const dest = sent?.chatId ? `\n\nChat WhatsApp: ${sent.chatId}` : '';
      alert(`Comprobante enviado por WhatsApp al teléfono registrado.${dest}\n\nSi no lo ves en el móvil, abre Chrome del bot y confirma que el mensaje salió en ese chat.`);
    } catch (e) {
      if (e?.name !== 'AbortError') alert(e?.message || 'No se pudo enviar el comprobante.');
    }
    setWaBusyId(null);
  };

  const handleDeleteHistorial = async (row) => {
    if (!row) return;
    const isOrder = kind === 'order';
    const msg = isOrder
      ? '¿Quitar esta orden del historial en este navegador? (No borra la orden confirmada en la base.)'
      : '¿Eliminar este pago del historial y de la base de datos?';
    if (!window.confirm(msg)) return;
    setBusy(true);
    try {
      if (isOrder) {
        dismissHistorialIds([row.id], kind);
        setDismissTick((t) => t + 1);
        if (selected?.id === row.id) setSelected(null);
      } else if (isSupabaseReady() && typeof deletePayment === 'function') {
        await deletePayment(row.id);
        notifyPaymentsChanged?.();
        await load();
        if (selected?.id === row.id) setSelected(null);
      }
    } catch (e) {
      alert(e?.message || 'No se pudo eliminar.');
    }
    setBusy(false);
  };

  const historyDate = (row) => formatFecha(historyConfirmedAtIso(row, kind) || row.receipt_uploaded_at);

  const filterLabel = `${HISTORIAL_MONTH_LABELS[fromMonth - 1]} ${fromYear}`;
  const hasRawItems = rawItems.length > 0;
  const filterSelectStyle = historialSelectStyle(theme);

  const tabBtn = (id, label, count) => (
    <button key={id} onClick={() => { setTab(id); setSelected(null); }} style={{
      padding: '10px 18px', borderRadius: 999, border: 'none', cursor: 'pointer',
      background: tab === id ? theme.primary : theme.bgInput,
      color: tab === id ? '#fff' : theme.textDim,
      fontSize: 12, fontWeight: 700, letterSpacing: 0.5, textTransform: 'uppercase',
      fontFamily: 'Inter, sans-serif', display: 'flex', alignItems: 'center', gap: 8,
    }}>
      {label}
      <span style={{
        padding: '2px 8px', borderRadius: 999, fontSize: 10,
        background: tab === id ? 'rgba(255,255,255,0.25)' : theme.bgElev,
      }}>{count}</span>
    </button>
  );

  return (
    <div className="tecos-page-shell" style={{ padding: 32 }}>
      <div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'flex-start', marginBottom: 16, flexWrap: 'wrap', gap: 12 }}>
        <div>
          <h2 style={{ fontFamily: 'Bebas Neue, sans-serif', fontSize: 36, color: theme.text, letterSpacing: 1, margin: 0, fontWeight: 400 }}>
            HISTORIAL
          </h2>
          <p style={{ color: theme.textDim, fontSize: 14, fontFamily: 'Inter, sans-serif', marginTop: 6, maxWidth: 640 }}>
            Mensualidades <strong>pagadas</strong> y órdenes de tienda <strong>confirmadas</strong> o <strong>entregadas</strong>,
            solo desde la <strong>fecha de ingreso</strong> de cada alumno (meses anteriores no aparecen).
            Los pendientes de revisión están en <strong>Verificar comprobantes</strong>.
            Usa <strong>Eliminar</strong> en cada fila: en mensualidades borra el pago en la base; en tienda solo oculta la fila del historial.
          </p>
        </div>
        <Btn theme={theme} kind="ghost" icon="history" onClick={load} disabled={loading || busy}>Actualizar</Btn>
      </div>

      {error && (
        <div style={{
          marginBottom: 16, padding: 14, borderRadius: 10,
          background: `${theme.danger}18`, border: `1px solid ${theme.danger}55`,
          color: theme.text, fontSize: 13, fontFamily: 'Inter, sans-serif',
        }}>
          {error}
        </div>
      )}

      <div style={{ display: 'flex', gap: 8, marginBottom: 12, flexWrap: 'wrap' }}>
        {tabBtn('mensualidades', 'Mensualidades', tab === 'mensualidades' && !loading ? items.length : '—')}
        {tabBtn('tienda', 'Tienda online', tab === 'tienda' && !loading ? items.length : '—')}
      </div>

      <div style={{
        display: 'flex', flexWrap: 'wrap', gap: 10, alignItems: 'center',
        marginBottom: 16, padding: '12px 14px', borderRadius: 10,
        background: theme.bgInput, border: `1px solid ${theme.border}`,
      }}>
        <span style={{
          fontSize: 11, color: theme.textMute, fontWeight: 700, textTransform: 'uppercase',
          fontFamily: 'Inter, sans-serif', letterSpacing: 0.5,
        }}>
          Mostrar desde
        </span>
        <select
          value={fromMonth}
          onChange={e => setFromMonth(Number(e.target.value))}
          style={filterSelectStyle}
          aria-label="Mes desde"
        >
          {HISTORIAL_MONTH_LABELS.map((label, i) => (
            <option key={label} value={i + 1}>{label}</option>
          ))}
        </select>
        <select
          value={fromYear}
          onChange={e => setFromYear(Number(e.target.value))}
          style={filterSelectStyle}
          aria-label="Año desde"
        >
          {yearOptions.map(y => <option key={y} value={y}>{y}</option>)}
        </select>
        {!loading && hasRawItems && items.length === 0 && (
          <span style={{ fontSize: 12, color: theme.textDim, fontFamily: 'Inter, sans-serif' }}>
            Sin registros confirmados desde {filterLabel}.
          </span>
        )}
      </div>

      <Card theme={theme} style={{ padding: 0, overflow: 'hidden' }}>
        {loading ? (
          <div style={{ padding: 48, textAlign: 'center', color: theme.textDim, fontFamily: 'Inter, sans-serif' }}>Cargando…</div>
        ) : items.length === 0 ? (
          <div style={{ padding: 48, textAlign: 'center' }}>
            <Icon name="doc" size={48} style={{ color: theme.textMute, marginBottom: 12, opacity: 0.5 }} />
            <div style={{ color: theme.textDim, fontFamily: 'Inter, sans-serif' }}>
              {hasRawItems
                ? `No hay registros confirmados desde ${filterLabel}.`
                : (tab === 'mensualidades'
                  ? 'Aún no hay mensualidades confirmadas en el historial'
                  : 'Aún no hay órdenes confirmadas o entregadas')}
            </div>
          </div>
        ) : (
          <table style={{ width: '100%', borderCollapse: 'collapse', fontFamily: 'Inter, sans-serif' }}>
            <thead>
              <tr style={{ background: theme.bgInput, borderBottom: `1px solid ${theme.border}` }}>
                {(kind === 'payment'
                  ? ['Alumno', 'ID', 'Concepto', 'Monto', 'Confirmado', 'Estado', 'Acciones']
                  : ['Alumno', 'ID', 'Orden / Productos', 'Monto', 'Confirmado', 'Estado', 'Acciones']
                ).map(h => (
                  <th key={h} style={{ padding: '14px 16px', textAlign: 'left', fontSize: 10, fontWeight: 700, color: theme.textMute, letterSpacing: 1, textTransform: 'uppercase' }}>{h}</th>
                ))}
              </tr>
            </thead>
            <tbody>
              {items.map((row, i) => {
                const st = rowStudent(row);
                return (
                  <tr key={row.id} style={{
                    borderBottom: i < items.length - 1 ? `1px solid ${theme.border}` : 'none',
                  }}
                    onMouseEnter={e => { e.currentTarget.style.background = theme.bgInput; }}
                    onMouseLeave={e => { e.currentTarget.style.background = 'transparent'; }}
                  >
                    <td style={tdStyle(theme)}>
                      <div style={{ display: 'flex', alignItems: 'center', gap: 10 }}>
                        <Avatar name={st.full_name} size={36} theme={theme} />
                        <span style={{ fontWeight: 600 }}>{st.full_name}</span>
                      </div>
                    </td>
                    <td style={tdStyle(theme)}>
                      <span style={{ fontFamily: 'Bebas Neue, sans-serif', fontSize: 16, color: theme.primary }}>{st.code}</span>
                    </td>
                    <td style={tdStyle(theme)}>
                      {kind === 'order' && (
                        <div style={{ fontSize: 11, color: theme.primary, fontWeight: 700, marginBottom: 4 }}>{row.order_number}</div>
                      )}
                      <div style={{ fontSize: 13 }}>{rowConcept(row, kind)}</div>
                    </td>
                    <td style={tdStyle(theme)}>
                      <span style={{ fontFamily: 'Bebas Neue, sans-serif', fontSize: 20 }}>${Number(rowAmount(row, kind))}</span>
                    </td>
                    <td style={tdStyle(theme)}>{historyDate(row)}</td>
                    <td style={tdStyle(theme)}><StatusPill status={row.status} theme={theme} /></td>
                    <td style={tdStyle(theme)}>
                      <div style={{ display: 'flex', gap: 6, flexWrap: 'wrap', alignItems: 'center' }}>
                        <Btn theme={theme} size="sm" icon="eye" onClick={() => setSelected(row)}>
                          Ver detalle
                        </Btn>
                        <Btn
                          theme={theme}
                          size="sm"
                          kind="wa"
                          icon="wa"
                          onClick={(e) => sendRowReceiptWa(row, e)}
                          disabled={busy || waBusyId === row.id}
                          title="Enviar comprobante por WhatsApp"
                        >
                          {waBusyId === row.id ? '…' : 'WhatsApp'}
                        </Btn>
                        <Btn
                          theme={theme}
                          size="sm"
                          kind="ghost"
                          icon="trash"
                          onClick={() => handleDeleteHistorial(row)}
                          disabled={busy || !!waBusyId}
                          title={kind === 'payment' ? 'Eliminar pago del historial y la base' : 'Quitar del historial (no borra la orden confirmada)'}
                        >
                          Eliminar
                        </Btn>
                      </div>
                    </td>
                  </tr>
                );
              })}
            </tbody>
          </table>
        )}
      </Card>

      <HistorialDetalleModal
        open={!!selected}
        onClose={() => { setSelected(null); setPreviewUrl(null); }}
        theme={theme}
        row={selected}
        kind={kind}
        previewUrl={previewUrl}
        previewLoading={previewLoading}
        confirmedLabel={selected ? historyDate(selected) : '—'}
        onDelete={selected ? () => handleDeleteHistorial(selected) : undefined}
        busy={busy}
      />
    </div>
  );
};

const HistorialDetalleModal = ({ open, onClose, theme, row, kind, previewUrl, previewLoading, confirmedLabel, onDelete, busy }) => {
  const [pdfBusy, setPdfBusy] = React.useState(false);
  const [waBusy, setWaBusy] = React.useState(false);

  React.useEffect(() => {
    if (!open) {
      setPdfBusy(false);
      setWaBusy(false);
    }
  }, [open]);

  if (!row) return null;
  const st = rowStudent(row);
  const isPdf = row.receipt_path?.toLowerCase().endsWith('.pdf');
  const isPayment = kind === 'payment';

  const downloadReceiptPdf = async () => {
    const alumno = historialRowToAlumno(row, kind);
    if (isPayment) {
      if (typeof exportPaymentReceiptPdf !== 'function') {
        alert('Módulo PDF no cargado. Recarga la página (F5).');
        return;
      }
      setPdfBusy(true);
      try {
        const settings = await fetchAcademySettings();
        await exportPaymentReceiptPdf(alumno, row, settings, theme);
      } catch (e) {
        alert(e?.message || 'No se pudo generar el PDF.');
      }
      setPdfBusy(false);
      return;
    }
    if (typeof exportStoreOrderReceiptPdf !== 'function') {
      alert('Módulo PDF no cargado. Recarga la página (F5).');
      return;
    }
    setPdfBusy(true);
    try {
      const settings = await fetchAcademySettings();
      const order = typeof orderRowToPortalOrder === 'function' ? orderRowToPortalOrder(row) : row;
      await exportStoreOrderReceiptPdf(alumno, order, settings, theme);
    } catch (e) {
      alert(e?.message || 'No se pudo generar el PDF.');
    }
    setPdfBusy(false);
  };

  const sendReceiptWa = async () => {
    const alumno = historialRowToAlumno(row, kind);
    if (!historialReceiptHasPhone(alumno)) {
      alert('Registra el teléfono del tutor o del alumno para enviar el comprobante por WhatsApp.');
      return;
    }
    setWaBusy(true);
    try {
      const sent = await sendHistorialReceiptWhatsApp(alumno, row, kind, theme);
      const dest = sent?.chatId ? `\n\nChat WhatsApp: ${sent.chatId}` : '';
      alert(`Comprobante enviado por WhatsApp al teléfono registrado.${dest}\n\nSi no lo ves en el móvil, abre Chrome del bot y confirma que el mensaje salió en ese chat.`);
    } catch (e) {
      if (e?.name !== 'AbortError') alert(e?.message || 'No se pudo enviar el comprobante.');
    }
    setWaBusy(false);
  };

  const actionBusy = busy || pdfBusy || waBusy;
  const due = isPayment && row.due_date ? new Date(row.due_date) : null;
  const periodoLabel = due && !Number.isNaN(due.getTime())
    ? due.toLocaleDateString('es-MX', { month: 'long', year: 'numeric' })
    : (row.concept || '—');

  const detailFields = kind === 'payment'
    ? [
        ['Periodo', periodoLabel],
        ['Concepto', row.concept],
        ['Monto', `$${Number(row.amount)} MXN`],
        ['Confirmado', confirmedLabel],
        ['Referencia', row.transfer_reference || '—'],
      ]
    : [
        ['Orden', row.order_number],
        ['Monto', `$${Number(row.total)} MXN`],
        ['Confirmado', confirmedLabel],
        ['Tutor', row.guest_tutor_name || '—'],
        ['Referencia', row.transfer_reference || '—'],
      ];

  return (
    <Modal open={open} onClose={onClose} theme={theme} width={680}>
      <div style={{ padding: '20px 24px', borderBottom: `1px solid ${theme.border}`, display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}>
        <div>
          <h3 style={{ fontFamily: 'Bebas Neue, sans-serif', fontSize: 28, color: theme.text, margin: 0, letterSpacing: 1 }}>
            {kind === 'payment' ? 'MENSUALIDAD CONFIRMADA' : 'ORDEN CONFIRMADA'}
          </h3>
          <p style={{ color: theme.textDim, fontSize: 13, margin: '4px 0 0', fontFamily: 'Inter, sans-serif' }}>{st.full_name} · {st.code}</p>
        </div>
        <button onClick={onClose} style={{ width: 32, height: 32, borderRadius: 8, background: theme.bgInput, border: `1px solid ${theme.border}`, color: theme.text, cursor: 'pointer', display: 'grid', placeItems: 'center' }}>
          <Icon name="x" size={16} />
        </button>
      </div>
      <div style={{ padding: 24, display: 'grid', gridTemplateColumns: row.receipt_path ? '1fr 1fr' : '1fr', gap: 20 }}>
        <div style={{ display: 'grid', gap: 12 }}>
          {detailFields.map(([l, v]) => (
            <div key={l} style={{ padding: 12, borderRadius: 8, background: theme.bgInput, border: `1px solid ${theme.border}` }}>
              <div style={{ fontSize: 10, color: theme.textMute, fontWeight: 700, letterSpacing: 1, textTransform: 'uppercase' }}>{l}</div>
              <div style={{ fontSize: 14, color: theme.text, fontWeight: 600, marginTop: 4 }}>{v}</div>
            </div>
          ))}
          <StatusPill status={row.status} theme={theme} />
          <div style={{ display: 'flex', gap: 8, flexWrap: 'wrap', marginTop: 4 }}>
            <Btn theme={theme} icon="download" onClick={downloadReceiptPdf} disabled={actionBusy}>
              {pdfBusy ? 'Generando PDF…' : (isPayment ? 'Descargar comprobante PDF' : 'Descargar comprobante de compra PDF')}
            </Btn>
            <Btn theme={theme} kind="wa" icon="wa" onClick={sendReceiptWa} disabled={actionBusy}>
              {waBusy ? 'Enviando…' : 'Enviar comprobante por WhatsApp'}
            </Btn>
          </div>
        </div>
        {row.receipt_path && (
          <div>
            <div style={{ fontSize: 11, fontWeight: 600, color: theme.textDim, textTransform: 'uppercase', marginBottom: 8 }}>Comprobante</div>
            <div style={{ minHeight: 280, borderRadius: 12, border: `1px solid ${theme.border}`, background: theme.bgInput, display: 'grid', placeItems: 'center', overflow: 'hidden' }}>
              {previewLoading ? (
                <div style={{ fontFamily: 'Inter, sans-serif', fontSize: 13, color: theme.textDim }}>Cargando…</div>
              ) : previewUrl && !isPdf ? (
                <img src={previewUrl} alt="Comprobante" style={{ maxWidth: '100%', maxHeight: 360, objectFit: 'contain' }} />
              ) : previewUrl && isPdf ? (
                <iframe title="PDF" src={previewUrl} style={{ width: '100%', height: 320, border: 'none' }} />
              ) : (
                <div style={{ padding: 24, color: theme.textDim, fontSize: 13, fontFamily: 'Inter, sans-serif' }}>Vista previa no disponible</div>
              )}
            </div>
            {previewUrl && (
              <a href={previewUrl} target="_blank" rel="noopener noreferrer"
                style={{ display: 'inline-block', marginTop: 8, fontSize: 12, color: theme.primary, fontWeight: 600 }}>
                Abrir comprobante en pestaña nueva
              </a>
            )}
          </div>
        )}
      </div>
      <div style={{ padding: '16px 24px', borderTop: `1px solid ${theme.border}`, display: 'flex', justifyContent: 'flex-end', gap: 10, flexWrap: 'wrap' }}>
        <Btn theme={theme} kind="ghost" onClick={onClose} disabled={actionBusy}>Cerrar</Btn>
        {onDelete && (
          <Btn theme={theme} kind="danger" icon="trash" onClick={onDelete} disabled={actionBusy}>
            {busy ? 'Procesando…' : 'Eliminar'}
          </Btn>
        )}
      </div>
    </Modal>
  );
};

const ComprobanteReviewModal = ({
  open, onClose, theme, row, kind, previewUrl, previewLoading, rejectNotes, setRejectNotes,
  onApprove, onReject, onDelete, busy, error,
}) => {
  if (!row) return null;
  const st = rowStudent(row);
  const isPdf = row.receipt_path?.toLowerCase().endsWith('.pdf');
  const orderCanReview = kind === 'order' && row.receipt_path && row.status === 'en_revision';
  const orderCanDecline = kind === 'order' && ['pendiente', 'en_revision'].includes(row.status);
  const detailFields = kind === 'payment'
    ? [
        ['Concepto', row.concept],
        ['Monto', `$${Number(row.amount)} MXN`],
        ['Fecha límite', row.due_date || '—'],
        ['Referencia', row.transfer_reference || '—'],
        ['Subido', formatFecha(row.receipt_uploaded_at)],
      ]
    : [
        ['Orden', row.order_number],
        ['Monto', `$${Number(row.total)} MXN`],
        ['Tutor', row.guest_tutor_name || '—'],
        ['Teléfono', row.guest_phone || '—'],
        ['Referencia', row.transfer_reference || '—'],
        ['Subido', formatFecha(row.receipt_uploaded_at)],
      ];

  const orderItems = kind === 'order' ? (row.order_items || []) : [];

  return (
    <Modal open={open} onClose={onClose} theme={theme} width={720}>
      <div style={{ padding: '20px 24px', borderBottom: `1px solid ${theme.border}`, display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}>
        <div>
          <h3 style={{ fontFamily: 'Bebas Neue, sans-serif', fontSize: 28, color: theme.text, margin: 0, letterSpacing: 1 }}>
            {kind === 'payment' ? 'REVISAR MENSUALIDAD' : 'REVISAR ORDEN TIENDA'}
          </h3>
          <p style={{ color: theme.textDim, fontSize: 13, margin: '4px 0 0', fontFamily: 'Inter, sans-serif' }}>{st.full_name} · {st.code}</p>
        </div>
        <button onClick={onClose} style={{ width: 32, height: 32, borderRadius: 8, background: theme.bgInput, border: `1px solid ${theme.border}`, color: theme.text, cursor: 'pointer', display: 'grid', placeItems: 'center' }}>
          <Icon name="x" size={16} />
        </button>
      </div>

      <div style={{ padding: 24, display: 'grid', gridTemplateColumns: '1fr 1fr', gap: 20 }}>
        <div style={{ display: 'grid', gap: 12 }}>
          {detailFields.map(([l, v]) => (
            <div key={l} style={{ padding: 12, borderRadius: 8, background: theme.bgInput, border: `1px solid ${theme.border}` }}>
              <div style={{ fontSize: 10, color: theme.textMute, fontWeight: 700, letterSpacing: 1, textTransform: 'uppercase' }}>{l}</div>
              <div style={{ fontSize: 14, color: theme.text, fontWeight: 600, marginTop: 4 }}>{v}</div>
            </div>
          ))}
          {kind === 'order' && orderItems.length > 0 && (
            <div style={{ padding: 12, borderRadius: 8, background: theme.bgInput, border: `1px solid ${theme.border}` }}>
              <div style={{ fontSize: 10, color: theme.textMute, fontWeight: 700, letterSpacing: 1, textTransform: 'uppercase', marginBottom: 8 }}>Líneas de la orden</div>
              <table style={{ width: '100%', borderCollapse: 'collapse', fontSize: 12, fontFamily: 'Inter, sans-serif' }}>
                <thead>
                  <tr style={{ color: theme.textMute, textAlign: 'left' }}>
                    <th style={{ padding: '4px 6px 4px 0' }}>Producto</th>
                    <th style={{ padding: 4 }}>Talla</th>
                    <th style={{ padding: 4 }}>Cant.</th>
                    <th style={{ padding: '4px 0 4px 6px', textAlign: 'right' }}>Subtotal</th>
                  </tr>
                </thead>
                <tbody>
                  {orderItems.map((it, idx) => (
                    <tr key={idx} style={{ borderTop: `1px solid ${theme.border}`, color: theme.text }}>
                      <td style={{ padding: '6px 6px 6px 0' }}>{it.product_name}</td>
                      <td style={{ padding: 6 }}>{it.size || '—'}</td>
                      <td style={{ padding: 6 }}>{it.qty}</td>
                      <td style={{ padding: '6px 0 6px 6px', textAlign: 'right' }}>${Number(it.line_total ?? (it.unit_price * it.qty))}</td>
                    </tr>
                  ))}
                </tbody>
              </table>
            </div>
          )}
          {kind === 'order' && (
            <div style={{ padding: 12, borderRadius: 8, background: `${theme.warning}14`, border: `1px solid ${theme.warning}44`, fontSize: 12, color: theme.textDim, fontFamily: 'Inter, sans-serif', lineHeight: 1.5 }}>
              Al <strong style={{ color: theme.text }}>confirmar</strong>, se descuenta el inventario por talla de cada producto. Si no hay stock suficiente, la confirmación fallará.
            </div>
          )}
          <StatusPill status={row.status || 'pendiente'} theme={theme} />
          {kind === 'order' && !row.receipt_path && (
            <div style={{ padding: 12, borderRadius: 8, background: `${theme.info}18`, border: `1px solid ${theme.info}44`, fontSize: 12, color: theme.textDim, fontFamily: 'Inter, sans-serif' }}>
              Sin comprobante aún. Usa <strong style={{ color: theme.text }}>Declinar / cancelar</strong> para quitar la orden del listado o <strong style={{ color: theme.text }}>Eliminar</strong> si el pedido es inválido (ej. ID incorrecto).
            </div>
          )}
          {kind === 'order' && row.receipt_path && row.status === 'pendiente' && (
            <div style={{ padding: 12, borderRadius: 8, background: `${theme.warning}18`, border: `1px solid ${theme.warning}44`, fontSize: 12, color: theme.textDim, fontFamily: 'Inter, sans-serif' }}>
              Hay comprobante guardado pero el estado estaba atascado en «Por pagar». Se activó automáticamente en revisión para que puedas verlo y confirmar o declinar.
            </div>
          )}
          <label style={{ fontSize: 11, fontWeight: 600, color: theme.textDim, textTransform: 'uppercase' }}>Motivo si declina (opcional)</label>
          <textarea
            value={rejectNotes}
            onChange={e => setRejectNotes(e.target.value)}
            placeholder="Ej. Monto incorrecto, comprobante ilegible…"
            rows={3}
            style={{
              width: '100%', padding: 12, borderRadius: 8, resize: 'vertical',
              background: theme.bgInput, border: `1px solid ${theme.border}`,
              color: theme.text, fontFamily: 'Inter, sans-serif', fontSize: 13,
            }}
          />
          {error && (
            <div style={{ padding: 10, borderRadius: 8, background: `${theme.danger}18`, color: theme.danger, fontSize: 13 }}>{error}</div>
          )}
        </div>

        <div>
          <div style={{ fontSize: 11, fontWeight: 600, color: theme.textDim, textTransform: 'uppercase', marginBottom: 8 }}>Comprobante</div>
          <div style={{ minHeight: 320, borderRadius: 12, border: `1px solid ${theme.border}`, background: theme.bgInput, display: 'grid', placeItems: 'center', overflow: 'hidden' }}>
            {previewLoading ? (
              <div style={{ fontFamily: 'Inter, sans-serif', fontSize: 13, color: theme.textDim }}>Cargando vista previa…</div>
            ) : previewUrl && !isPdf ? (
              <img src={previewUrl} alt="Comprobante" style={{ maxWidth: '100%', maxHeight: 400, objectFit: 'contain' }} />
            ) : previewUrl && isPdf ? (
              <iframe title="PDF" src={previewUrl} style={{ width: '100%', height: 360, border: 'none' }} />
            ) : (
              <div style={{ textAlign: 'center', padding: 24, color: theme.textDim }}>
                <Icon name="doc" size={48} style={{ marginBottom: 12, opacity: 0.5 }} />
                <div style={{ fontFamily: 'Inter, sans-serif', fontSize: 13 }}>
                  {row.receipt_path ? 'Vista previa no disponible. Confirma sesión admin.' : 'Sin archivo de comprobante'}
                </div>
              </div>
            )}
          </div>
          {previewUrl && (
            <a href={previewUrl} target="_blank" rel="noopener noreferrer"
              style={{ display: 'inline-block', marginTop: 8, fontSize: 12, color: theme.primary, fontWeight: 600 }}>
              Abrir comprobante en pestaña nueva
            </a>
          )}
        </div>
      </div>

      <div style={{ padding: '16px 24px', borderTop: `1px solid ${theme.border}`, display: 'flex', justifyContent: 'flex-end', gap: 10, flexWrap: 'wrap' }}>
        <Btn theme={theme} kind="ghost" onClick={onClose} disabled={busy}>Cancelar</Btn>
        {onDelete && kind === 'order' && (
          <Btn theme={theme} kind="danger" icon="trash" onClick={onDelete} disabled={busy}>
            {busy ? 'Procesando…' : 'Eliminar orden'}
          </Btn>
        )}
        <Btn theme={theme} kind="danger" icon="x" onClick={onReject} disabled={busy || (kind === 'order' && !orderCanDecline)}>
          {busy ? 'Procesando…' : kind === 'order' ? 'Declinar / cancelar' : 'Declinar'}
        </Btn>
        <Btn theme={theme} kind="success" icon="check" onClick={onApprove} disabled={busy || (kind === 'order' && !orderCanReview)}>
          {busy ? 'Procesando…' : kind === 'payment' ? 'Aceptar pago' : 'Confirmar orden (inventario)'}
        </Btn>
      </div>
    </Modal>
  );
};

window.pushMockComprobante = (item) => {
  MOCK_COMPROBANTES.push({ ...item, kind: 'payment' });
  window.dispatchEvent(new Event('tecos:comprobantes-changed'));
};

window.pushMockOrdenTienda = (item) => {
  MOCK_ORDENES_TIENDA.push({ ...item, kind: 'order' });
  window.dispatchEvent(new Event('tecos:comprobantes-changed'));
};

window.syncMockAlumnoPayment = (code) => {
  if (window.__onAlumnoPaymentSync) window.__onAlumnoPaymentSync(code);
};

window.syncMockAlumnoOrder = (orderNumber, status, adminNotes) => {
  if (window.__onAlumnoOrderSync) window.__onAlumnoOrderSync(orderNumber, status, adminNotes);
};

Object.assign(window, {
  ComprobantesModule, HistorialModule, MOCK_COMPROBANTES, MOCK_ORDENES_TIENDA,
  clearTecosHistorialDismissedStorage,
});
