const { useState, useMemo, useEffect, useRef, useCallback } = React;

// ---------- Auth (Supabase Auth) ----------
// El login pide correo + contraseña y autentica contra Supabase Auth.
// Los usuarios se crean en el panel de Supabase (Authentication -> Users).
// Así RLS exige un usuario autenticado y la clave pública 'anon' deja de
// dar acceso a los datos. (Base para roles/permisos más adelante.)
const ATTEMPTS_KEY   = 'mtm_login_attempts';
const MAX_ATTEMPTS   = 5;
const LOCKOUT_MIN    = 15;

function getAttempts() {
  try { return JSON.parse(localStorage.getItem(ATTEMPTS_KEY) || '{"n":0,"t":0}'); }
  catch { return { n: 0, t: 0 }; }
}

function recordFailedAttempt() {
  const a = getAttempts();
  localStorage.setItem(ATTEMPTS_KEY, JSON.stringify({ n: a.n + 1, t: Date.now() }));
}

function resetAttempts() { localStorage.removeItem(ATTEMPTS_KEY); }

// ---------- Supabase ----------
const SUPA_URL  = 'https://eaqmkotyflvmoovvohke.supabase.co';
const SUPA_KEY  = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6ImVhcW1rb3R5Zmx2bW9vdnZvaGtlIiwicm9sZSI6ImFub24iLCJpYXQiOjE3NzgwNzQxODksImV4cCI6MjA5MzY1MDE4OX0.8-VkOQm7-ZPoku98q2vnVyN1zAnSbCdQCV26zL3dSMI';
const sb        = window.supabase.createClient(SUPA_URL, SUPA_KEY);

// ---------- BuilderBot WhatsApp ----------
const BB_URL = 'https://app.builderbot.cloud/api/v2/5d717d70-5cf0-4c87-af16-2e627710b096/messages';
const BB_KEY = 'bb-b813ef99-2ab6-4ee8-9b04-3dfccf78c22a';

const fmtPhoneWA = (tel) => {
  if (!tel) return null;
  const d = String(tel).replace(/\D/g, '');
  if (!d) return null;
  if (d.length === 10 && d.startsWith('3')) return '57' + d; // colombiano sin prefijo
  return d;
};

const sendWA = (telefono, content) => {
  const number = fmtPhoneWA(telefono);
  if (!number) return Promise.resolve({ ok: false, noPhone: true });
  return fetch(BB_URL, {
    method: 'POST',
    headers: { 'Content-Type': 'application/json', 'x-api-builderbot': BB_KEY },
    body: JSON.stringify({ messages: { content }, number, checkIfExists: false }),
  }).then((r) => ({ ok: r.ok })).catch(() => ({ ok: false, err: true }));
};

// Mapeadores snake_case ↔ camelCase
const fromDB = {
  paquete: (p) => ({
    id: p.id, clienteId: p.cliente_id, servicioId: p.servicio_id,
    sesionesTotales: Number(p.sesiones_totales || 0), sesionesUsadas: Number(p.sesiones_usadas || 0),
    valorTotal: Number(p.valor_total), fechaCompra: p.fecha_compra, fechaInicio: p.fecha_inicio,
    esBono: p.es_bono || false,
    compradorId: p.comprador_id || null,
    fechaVencimiento: p.fecha_vencimiento || null,
    metodoPago: p.metodo_pago || 'efectivo',
    montoEfectivo: Number(p.monto_efectivo || 0),
    montoTransferencia: Number(p.monto_transferencia || 0),
    montoDatafono: Number(p.monto_datafono || 0),
    numeroFactura: p.numero_factura || '',
    abonoInicial: Number(p.abono_inicial || 0),
    saldoPendiente: Number(p.saldo_pendiente || 0),
    abonos: Array.isArray(p.abonos) ? p.abonos : [],
    beneficiariosExtra: Array.isArray(p.beneficiarios_extra) ? p.beneficiarios_extra : [],
    historial: Array.isArray(p.historial) ? p.historial : [],
    createdAt: p.created_at || '',
  }),
  sesion: (s) => {
    // Backward compat: prefer new JSONB column; fall back to legacy fisio_2_id
    const extras = Array.isArray(s.fisios_adicionales) && s.fisios_adicionales.length > 0
      ? s.fisios_adicionales.map((f) => ({ fisioId: f.fisio_id || f.fisioId, comision: Number(f.comision || 0) }))
      : (s.fisio_2_id ? [{ fisioId: s.fisio_2_id, comision: Number(s.comision_fisio_2 || 0) }] : []);
    return {
      id: s.id, clienteId: s.cliente_id, paqueteId: s.paquete_id, fisioId: s.fisio_id,
      servicioId: s.servicio_id, fecha: s.fecha,
      valorSesion: Number(s.valor_sesion), comisionFisio: Number(s.comision_fisio),
      // Cortesía/publicidad: sin ingreso, pero con comisión al fisio
      esCortesia: s.es_cortesia === true,
      metodoPago: s.metodo_pago || 'efectivo',
      montoEfectivo: Number(s.monto_efectivo || 0),
      montoTransferencia: Number(s.monto_transferencia || 0),
      montoDatafono: Number(s.monto_datafono || 0),
      comisionPct: Number(s.comision_pct ?? 50),
      // Legacy compat fields (derived from extras[0])
      fisio2Id: extras[0]?.fisioId || null,
      comisionFisio2: extras[0]?.comision || 0,
      // New: full list of additional fisios [{fisioId, comision}]
      fisiosAdicionales: extras,
      // Historial de cambios (auditoría de ediciones)
      historial: Array.isArray(s.historial) ? s.historial : [],
    };
  },
  cita: (c) => ({
    id: c.id, clienteId: c.cliente_id, clienteNombre: c.cliente_nombre || '',
    clienteTelefono: c.cliente_telefono || '',
    fisioId: c.fisio_id, servicioId: c.servicio_id,
    fecha: c.fecha, horaInicio: (c.hora_inicio || '').slice(0, 5),
    horaFin: (c.hora_fin || '').slice(0, 5),
    estado: c.estado || 'agendada', nota: c.nota || '',
    creadoPor: c.creado_por || '',
    historial: Array.isArray(c.historial) ? c.historial : [],
  }),
  servicio: (s) => ({
    id: s.id, nombre: s.nombre, descripcion: s.descripcion || '',
    precio: Number(s.precio), sesiones: s.sesiones,
    categoria: s.categoria, activo: s.activo, orden: s.orden || 0,
    comisionPct: Number(s.comision_pct ?? 50),
  }),
  gasto: (g) => ({
    id: g.id, fecha: g.fecha, descripcion: g.descripcion,
    monto: Number(g.monto), categoria: g.categoria,
    beneficiario: g.beneficiario || '',
    metodoPagoEgreso: g.metodo_pago_egreso || 'efectivo',
    numeroEgreso: g.numero_egreso || null,
    // Marca los egresos generados automáticamente por "Generar egreso" en Liquidación.
    // Esa comisión ya se resta directamente de las sesiones (pagadoFisioMes/reservaFisio),
    // así que este egreso NO debe volver a restarse en los cálculos de utilidad (evita doble conteo).
    esLiquidacionFisio: g.es_liquidacion_fisio || false,
  }),
};
const toDB = {
  paquete: (p) => ({
    id: p.id, cliente_id: p.clienteId, servicio_id: p.servicioId,
    sesiones_totales: p.sesionesTotales, sesiones_usadas: p.sesionesUsadas,
    valor_total: p.valorTotal, fecha_compra: p.fechaCompra, fecha_inicio: p.fechaInicio,
    es_bono: p.esBono || false,
    comprador_id: p.compradorId || null,
    fecha_vencimiento: p.fechaVencimiento || null,
    metodo_pago: p.metodoPago || 'efectivo',
    monto_efectivo: p.montoEfectivo ?? 0,
    monto_transferencia: p.montoTransferencia ?? 0,
    monto_datafono: p.montoDatafono ?? 0,
    numero_factura: p.numeroFactura ?? '',
    abono_inicial: p.abonoInicial ?? 0,
    saldo_pendiente: p.saldoPendiente ?? 0,
    abonos: p.abonos || [],
    beneficiarios_extra: p.beneficiariosExtra || [],
    historial: p.historial || [],
  }),
  cita: (c) => ({
    id: c.id, cliente_id: c.clienteId || null,
    cliente_nombre: c.clienteNombre || null,
    cliente_telefono: c.clienteTelefono || null,
    fisio_id: c.fisioId, servicio_id: c.servicioId || null,
    fecha: c.fecha, hora_inicio: c.horaInicio, hora_fin: c.horaFin || null,
    estado: c.estado || 'agendada', nota: c.nota || null,
    creado_por: c.creadoPor || null,
    historial: c.historial || [],
  }),
  sesion: (s) => {
    const extras = (s.fisiosAdicionales || []).filter((f) => f.fisioId);
    return {
      id: s.id, cliente_id: s.clienteId, paquete_id: s.paqueteId, fisio_id: s.fisioId,
      servicio_id: s.servicioId, fecha: s.fecha,
      valor_sesion: s.valorSesion, comision_fisio: s.comisionFisio,
      es_cortesia: s.esCortesia === true,
      metodo_pago: s.metodoPago || 'efectivo',
      monto_efectivo: s.montoEfectivo || 0,
      monto_transferencia: s.montoTransferencia || 0,
      monto_datafono: s.montoDatafono || 0,
      comision_pct: s.comisionPct ?? 50,
      // Backward compat: keep fisio_2_id from first extra fisio
      fisio_2_id: extras[0]?.fisioId || null,
      comision_fisio_2: extras[0]?.comision || 0,
      // New: full array of extra fisios
      fisios_adicionales: extras.map((f) => ({ fisio_id: f.fisioId, comision: f.comision })),
      // Historial de cambios (auditoría)
      historial: s.historial || [],
    };
  },
};

// Supabase devuelve MÁXIMO 1000 filas por consulta. Con más de 1000 clientes/sesiones,
// una consulta simple trunca los datos en silencio (clientes que "no existen", sesiones
// que desaparecen de la liquidación). Este helper pagina en bloques de 1000 hasta traer todo.
// El doble .order() (columna + id) garantiza un orden estable entre páginas.
async function fetchTodas(tabla, ordenCol, ordenAsc = true) {
  const PAGINA = 1000;
  const filas = [];
  for (let desde = 0; ; desde += PAGINA) {
    const { data, error } = await sb.from(tabla).select('*')
      .order(ordenCol, { ascending: ordenAsc })
      .order('id', { ascending: true })
      .range(desde, desde + PAGINA - 1);
    if (error) throw error;
    filas.push(...(data || []));
    if (!data || data.length < PAGINA) break;
  }
  return filas;
}

async function fetchAll() {
  const [c, f, p, s, g, sv, pr, ct] = await Promise.all([
    fetchTodas('clientes', 'nombre'),
    fetchTodas('fisios', 'nombre'),
    fetchTodas('paquetes', 'created_at', false),
    fetchTodas('sesiones', 'fecha', false),
    fetchTodas('gastos', 'fecha', false),
    fetchTodas('servicios', 'orden'),
    fetchTodas('proveedores', 'nombre'),
    // Si la tabla de citas aún no existe (migración sin correr), no se cae
    // toda la app: sigue funcionando sin agenda.
    fetchTodas('citas', 'fecha', false).catch(() => []),
  ]);
  return {
    clientes:    c,
    fisios:      f,
    paquetes:    p.map(fromDB.paquete),
    sesiones:    s.map(fromDB.sesion),
    gastos:      g.map(fromDB.gasto),
    servicios:   sv.map(fromDB.servicio),
    proveedores: pr,
    citas:       (ct || []).map(fromDB.cita),
  };
}

// Helper: obtener catálogo activo desde state o fallback estático
const getSvs = (state) => (state?.servicios?.length ? state.servicios : SERVICIOS);
const findSv  = (state, id) => getSvs(state).find((s) => s.id === id);

// Helpers para sesiones con múltiples fisioterapeutas (hasta 4 manos)
const sesionesDelFisio = (sesiones, fisioId) =>
  sesiones.filter((s) => s.fisioId === fisioId || (s.fisiosAdicionales || []).some((f) => f.fisioId === fisioId));
const comisionDelFisio = (sesion, fisioId) => {
  if (sesion.fisioId === fisioId) return sesion.comisionFisio || 0;
  const extra = (sesion.fisiosAdicionales || []).find((f) => f.fisioId === fisioId);
  return extra?.comision || 0;
};
// Suma de todas las comisiones de una sesión (fisio 1 + todos los extras)
const comisionTotalSesion = (s) =>
  (s.comisionFisio || 0) + (s.fisiosAdicionales || []).reduce((a, f) => a + (f.comision || 0), 0);
// % de comisión del fisio para un servicio (default 50% si no está configurado)
const comisionPctSv = (sv) => Number(sv?.comisionPct ?? 50);

// Iconos / etiquetas de método de pago (centralizado)
const PAGO_ICON = { efectivo: '💵', transferencia: '📲', datafono: '💳', mixto: '🔀' };
const pagoIcono = (mp) => PAGO_ICON[mp] || '💵';
const pagoLabel = (mp) => ({ efectivo: 'Efectivo', transferencia: 'Transferencia', datafono: 'Datáfono', mixto: 'Mixto' }[mp] || mp);
// Texto compacto del desglose mixto (incluye datáfono solo si > 0)
const mixtoDesglose = (x) => [
  (x.montoEfectivo || 0) > 0 ? `💵${COP(x.montoEfectivo)}` : '',
  (x.montoTransferencia || 0) > 0 ? `📲${COP(x.montoTransferencia)}` : '',
  (x.montoDatafono || 0) > 0 ? `💳${COP(x.montoDatafono)}` : '',
].filter(Boolean).join(' ');

// ---------- Helpers ----------
const CATEGORIAS_GASTO = [
  { id: 'arriendo',      nombre: 'Arriendo',            icon: '🏠', tipo: 'fijo'      },
  { id: 'nomina',        nombre: 'Nómina / Bonos',      icon: '👥', tipo: 'fijo'      },
  { id: 'honorarios',    nombre: 'Honorarios',          icon: '🎓', tipo: 'variable'  },
  { id: 'accionistas',   nombre: 'Pago accionistas',    icon: '💼', tipo: 'variable'  },
  { id: 'servicios',     nombre: 'Servicios públicos',  icon: '💡', tipo: 'fijo'      },
  { id: 'insumos',       nombre: 'Insumos',             icon: '🧴', tipo: 'variable'  },
  { id: 'aseo',          nombre: 'Aseo',                icon: '🧹', tipo: 'variable'  },
  { id: 'marketing',     nombre: 'Marketing',           icon: '📢', tipo: 'variable'  },
  { id: 'mantenimiento', nombre: 'Mantenimiento',       icon: '🔧', tipo: 'variable'  },
  { id: 'papeleria',     nombre: 'Papelería',           icon: '📋', tipo: 'variable'  },
  { id: 'transporte',    nombre: 'Transporte',          icon: '🚗', tipo: 'variable'  },
  { id: 'impuestos',     nombre: 'Impuestos',           icon: '📑', tipo: 'fijo'      },
  { id: 'polizas',       nombre: 'Pólizas de seguro',   icon: '🛡️', tipo: 'fijo'      },
  { id: 'legal',         nombre: 'Gastos legales',      icon: '⚖️', tipo: 'variable'  },
  { id: 'activos',       nombre: 'Activos fijos',       icon: '🏗️', tipo: 'variable'  },
  { id: 'residuos',      nombre: 'Residuos biológicos', icon: '♻️', tipo: 'fijo'      },
  { id: 'otros',         nombre: 'Otros',               icon: '📦', tipo: 'variable'  },
];
const COP     = (n) => new Intl.NumberFormat('es-CO', { style: 'currency', currency: 'COP', maximumFractionDigits: 0 }).format(n || 0);
const fmtDate = (iso) => { if (!iso) return '—'; return new Date(iso + 'T12:00:00').toLocaleDateString('es-CO', { day: '2-digit', month: 'short', year: 'numeric' }); };
// Formatea un timestamp ISO completo (con hora) — para el historial de cambios
const fmtDateTime = (ts) => { if (!ts) return '—'; const d = new Date(ts); return isNaN(d) ? '—' : d.toLocaleString('es-CO', { day: '2-digit', month: 'short', year: 'numeric', hour: '2-digit', minute: '2-digit' }); };
const todayISO = () => new Date().toISOString().slice(0, 10);
// Detecta fechas con año muy alejado del actual (típico error de digitación, ej. "0001" en vez de "2026").
// Es una ADVERTENCIA (confirm), no un bloqueo — Dani sí necesita poder cargar datos históricos reales.
const AÑOS_ATRAS_RAZONABLE = 10;
// Límites duros para los campos <input type="date">: el navegador ni siquiera
// deja guardar algo fuera de este rango (evita años como 0292 por digitación).
const FECHA_MIN = '2020-01-01';
const FECHA_MAX = () => `${new Date().getFullYear() + 1}-12-31`;

const confirmarFechaRazonable = (fechaISO, etiqueta = 'la fecha') => {
  if (!fechaISO) return true;
  const anio = Number(fechaISO.slice(0, 4));
  const anioActual = new Date().getFullYear();

  // BLOQUEO: fechas imposibles (año de 3 dígitos, siglos atrás, futuro lejano).
  // No se puede continuar: siempre son errores de digitación.
  if (!anio || anio < 2000 || anio > anioActual + 1) {
    alert(`🚫 ${etiqueta.charAt(0).toUpperCase() + etiqueta.slice(1)} no es válida: escribiste el año ${fechaISO.slice(0, 4)}.\n\nCorrígela para poder guardar.`);
    return false;
  }
  // AVISO: fecha vieja pero posible (carga de histórico). Se puede continuar.
  if (anio < anioActual - AÑOS_ATRAS_RAZONABLE) {
    return confirm(`⚠️ Revisa ${etiqueta}: escribiste ${fmtDate(fechaISO)}.\n\n¿Es correcta? Si fue un error de digitación, cancela y corrígela.`);
  }
  return true;
};
const monthKey = (iso) => (iso || todayISO()).slice(0, 7);
const uid      = () => Math.random().toString(36).slice(2, 9);
// Cash-basis: dinero recibido EL DÍA DE LA VENTA (depósito inicial, sin contar abonos posteriores).
// Fórmula: abonoInicial - suma(abonos). abonoInicial es un TOTAL ACUMULADO (crece con cada abono),
// así que restarle la suma de abonos recupera exactamente lo recibido el día 1.
// El fallback a valorTotal SOLO aplica cuando nunca hubo abonos posteriores (venta 'completo' desde el día 1,
// donde por convención abonoInicial se guarda en 0). Si hay abonos registrados, 'pago' ya es el valor correcto
// (puede ser 0 si la venta fue 100% a crédito y se pagó después — ese ingreso ya se cuenta vía los abonos).
const pagoInicialVenta = (p) => {
  const sumaAbonos = (p.abonos || []).reduce((a, ab) => a + (ab.monto || 0), 0);
  const pago = Math.max(0, (p.abonoInicial || 0) - sumaAbonos);
  const sinAbonosPosteriores = (p.abonos || []).length === 0;
  return (pago === 0 && !(p.saldoPendiente > 0) && sinAbonosPosteriores) ? (p.valorTotal || 0) : pago;
};

// ---------- Service catalog ----------
const SERVICIOS = [
  { id: 'masaje',            nombre: 'Masaje',                  precio: 100000, categoria: 'masaje' },
  { id: 'masaje_sauna',      nombre: 'Masaje más sauna',        precio: 140000, categoria: 'masaje' },
  { id: 'fisioterapia_manual', nombre: 'Fisioterapia manual',   precio: 90000,  categoria: 'fisio'  },
  { id: 'estiramiento',      nombre: 'Estiramiento',            precio: 60000,  categoria: 'fisio'  },
  { id: 'vendaje',           nombre: 'Vendaje',                 precio: 10000,  categoria: 'fisio'  },
  { id: 'crioterapia',       nombre: 'Crioterapia',             precio: 40000,  categoria: 'fisio'  },
  { id: 'crioterapia_masaje',nombre: 'Crioterapia más masaje',  precio: 130000, categoria: 'masaje' },
  { id: 'full_recovery',     nombre: 'Full recovery',           precio: 150000, categoria: 'masaje' },
  { id: 'sauna',             nombre: 'Sauna',                   precio: 50000,  categoria: 'masaje' },
  { id: 'paquete_fisio_10',  nombre: 'Paquete fisioterapia x10',precio: 700000, sesiones: 10, categoria: 'fisio'  },
  { id: 'paquete_fisio_5',   nombre: 'Paquete fisioterapia x5', precio: 400000, sesiones: 5,  categoria: 'fisio'  },
  { id: 'paquete_fisio_15',  nombre: 'Paquete fisioterapia x15',precio: 900000, sesiones: 15, categoria: 'fisio'  },
  { id: 'paquete_masajes_10',nombre: 'Paquete masajes x10',     precio: 700000, sesiones: 10, categoria: 'masaje' },
  { id: 'paquete_masajes_5', nombre: 'Paquete masajes x5',      precio: 500000, sesiones: 5,  categoria: 'masaje' },
];

// ---------- Icons ----------
const Icon = ({ name, className = 'w-5 h-5' }) => {
  const stroke = { fill: 'none', stroke: 'currentColor', strokeWidth: 2, strokeLinecap: 'round', strokeLinejoin: 'round' };
  const paths = {
    home: <><path d="M3 12 12 3l9 9" /><path d="M5 10v10h14V10" /></>,
    'shopping-cart': <><circle cx="9" cy="21" r="1" /><circle cx="20" cy="21" r="1" /><path d="M1 1h4l2.7 13.4a2 2 0 0 0 2 1.6h9.7a2 2 0 0 0 2-1.6L23 6H6" /></>,
    users: <><path d="M16 21v-2a4 4 0 0 0-4-4H6a4 4 0 0 0-4 4v2" /><circle cx="9" cy="7" r="4" /><path d="M22 21v-2a4 4 0 0 0-3-3.87" /><path d="M16 3.13a4 4 0 0 1 0 7.75" /></>,
    activity: <path d="M22 12h-4l-3 9L9 3l-3 9H2" />,
    'dollar-sign': <><line x1="12" y1="1" x2="12" y2="23" /><path d="M17 5H9.5a3.5 3.5 0 0 0 0 7h5a3.5 3.5 0 0 1 0 7H6" /></>,
    sparkles: <><path d="M12 3v4M12 17v4M3 12h4M17 12h4M5.6 5.6l2.8 2.8M15.6 15.6l2.8 2.8M5.6 18.4l2.8-2.8M15.6 8.4l2.8-2.8" /></>,
    plus: <><line x1="12" y1="5" x2="12" y2="19" /><line x1="5" y1="12" x2="19" y2="12" /></>,
    check: <polyline points="20 6 9 17 4 12" />,
    alert: <><circle cx="12" cy="12" r="10" /><line x1="12" y1="8" x2="12" y2="12" /><line x1="12" y1="16" x2="12.01" y2="16" /></>,
    calendar: <><rect x="3" y="4" width="18" height="18" rx="2" /><line x1="16" y1="2" x2="16" y2="6" /><line x1="8" y1="2" x2="8" y2="6" /><line x1="3" y1="10" x2="21" y2="10" /></>,
    whatsapp: <><path d="M20.5 3.5A11 11 0 0 0 3.6 17.7L2 22l4.4-1.6A11 11 0 1 0 20.5 3.5z" /><path d="M16.6 14.2c-.3-.2-1.7-.8-2-.9-.3-.1-.5-.2-.7.2s-.8.9-1 1.1-.4.2-.7.1c-.3-.1-1.2-.5-2.3-1.5-.9-.8-1.4-1.7-1.6-2-.2-.3 0-.5.1-.6.1-.1.3-.4.4-.5.1-.2.2-.3.3-.5.1-.2 0-.4 0-.5 0-.2-.7-1.7-1-2.3-.2-.6-.5-.5-.7-.5h-.6c-.2 0-.5.1-.8.4s-1 1-1 2.4 1.1 2.8 1.2 3 2.1 3.2 5.1 4.5c.7.3 1.3.5 1.7.6.7.2 1.4.2 1.9.1.6-.1 1.7-.7 2-1.4.2-.7.2-1.2.2-1.4-.1-.2-.3-.3-.6-.4z" /></>,
    chevron: <polyline points="9 18 15 12 9 6" />,
    bot: <><rect x="3" y="11" width="18" height="10" rx="2" /><circle cx="12" cy="5" r="2" /><line x1="12" y1="7" x2="12" y2="11" /><line x1="8" y1="16" x2="8" y2="16" /><line x1="16" y1="16" x2="16" y2="16" /></>,
    send: <><line x1="22" y1="2" x2="11" y2="13" /><polygon points="22 2 15 22 11 13 2 9 22 2" /></>,
    refresh: <><polyline points="23 4 23 10 17 10" /><path d="M20.49 15a9 9 0 1 1-2.12-9.36L23 10" /></>,
    trash: <><polyline points="3 6 5 6 21 6" /><path d="M19 6l-1 14a2 2 0 0 1-2 2H8a2 2 0 0 1-2-2L5 6" /><path d="M10 11v6M14 11v6" /><path d="M9 6V4a2 2 0 0 1 2-2h2a2 2 0 0 1 2 2v2" /></>,
    menu: <><line x1="3" y1="6" x2="21" y2="6" /><line x1="3" y1="12" x2="21" y2="12" /><line x1="3" y1="18" x2="21" y2="18" /></>,
    x: <><line x1="18" y1="6" x2="6" y2="18" /><line x1="6" y1="6" x2="18" y2="18" /></>,
    wallet: <><rect x="3" y="7" width="18" height="13" rx="2" /><path d="M3 9V7a2 2 0 0 1 2-2h12v4" /><circle cx="17" cy="13.5" r="1.3" /></>,
    clock: <><circle cx="12" cy="12" r="10" /><polyline points="12 6 12 12 16 14" /></>,
    'arrow-left': <><line x1="19" y1="12" x2="5" y2="12" /><polyline points="12 19 5 12 12 5" /></>,
    'arrow-right': <><line x1="5" y1="12" x2="19" y2="12" /><polyline points="12 5 19 12 12 19" /></>,
    'arrow-down-right': <><line x1="7" y1="7" x2="17" y2="17" /><polyline points="17 7 17 17 7 17" /></>,
    'arrow-up-right': <><line x1="7" y1="17" x2="17" y2="7" /><polyline points="17 17 17 7 7 7" /></>,
    download: <><path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4" /><polyline points="7 10 12 15 17 10" /><line x1="12" y1="15" x2="12" y2="3" /></>,
    loader: <><line x1="12" y1="2" x2="12" y2="6" /><line x1="12" y1="18" x2="12" y2="22" /><line x1="4.93" y1="4.93" x2="7.76" y2="7.76" /><line x1="16.24" y1="16.24" x2="19.07" y2="19.07" /><line x1="2" y1="12" x2="6" y2="12" /><line x1="18" y1="12" x2="22" y2="12" /><line x1="4.93" y1="19.07" x2="7.76" y2="16.24" /><line x1="16.24" y1="7.76" x2="19.07" y2="4.93" /></>,
    phone: <path d="M22 16.92v3a2 2 0 0 1-2.18 2A19.79 19.79 0 0 1 11.62 19 19.45 19.45 0 0 1 5 12.38 19.79 19.79 0 0 1 2.12 4.18 2 2 0 0 1 4.11 2h3a2 2 0 0 1 2 1.72c.13.96.36 1.9.68 2.81a2 2 0 0 1-.45 2.11L8.09 9.91a16 16 0 0 0 6 6l1.27-1.27a2 2 0 0 1 2.11-.45c.91.32 1.85.55 2.81.68A2 2 0 0 1 22 16.92z" />,
    gift: <><polyline points="20 12 20 22 4 22 4 12" /><rect x="2" y="7" width="20" height="5" /><line x1="12" y1="22" x2="12" y2="7" /><path d="M12 7H7.5a2.5 2.5 0 0 1 0-5C11 2 12 7 12 7z" /><path d="M12 7h4.5a2.5 2.5 0 0 0 0-5C13 2 12 7 12 7z" /></>,
    'trending-down': <><polyline points="23 18 13.5 8.5 8.5 13.5 1 6" /><polyline points="17 18 23 18 23 12" /></>,
    truck: <><rect x="1" y="3" width="15" height="13" /><polygon points="16 8 20 8 23 11 23 16 16 16 16 8" /><circle cx="5.5" cy="18.5" r="2.5" /><circle cx="18.5" cy="18.5" r="2.5" /></>,
    lock: <><rect x="3" y="11" width="18" height="11" rx="2" ry="2" /><path d="M7 11V7a5 5 0 0 1 10 0v4" /></>,
    'log-out': <><path d="M9 21H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h4" /><polyline points="16 17 21 12 16 7" /><line x1="21" y1="12" x2="9" y2="12" /></>,
    'bar-chart': <><line x1="18" y1="20" x2="18" y2="10" /><line x1="12" y1="20" x2="12" y2="4" /><line x1="6" y1="20" x2="6" y2="14" /></>,
    'trending-up': <><polyline points="23 6 13.5 15.5 8.5 10.5 1 18" /><polyline points="17 6 23 6 23 12" /></>,
    'pie-chart': <><path d="M21.21 15.89A10 10 0 1 1 8 2.83" /><path d="M22 12A10 10 0 0 0 12 2v10z" /></>,
    edit: <><path d="M11 4H4a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2v-7"/><path d="M18.5 2.5a2.121 2.121 0 0 1 3 3L12 15l-4 1 1-4 9.5-9.5z"/></>,
  };
  return (
    <svg className={className} viewBox="0 0 24 24" {...stroke} aria-hidden="true">
      {paths[name]}
    </svg>
  );
};

// ---------- App ----------
function App({ onLogout }) {
  const [state, setState]   = useState(null);
  const [tab, setTab]       = useState('dashboard');
  // El fisio no tiene dashboard: su pantalla es la agenda
  const [navOpen, setNavOpen] = useState(false);
  const [error, setError]   = useState(null);
  const [toasts, setToasts] = useState([]);
  // Correo del usuario logueado — se registra en la auditoría para saber
  // QUIÉN hizo cada cambio (antes quedaba siempre 'Dani', aunque fuera otra persona).
  const [usuario, setUsuario] = useState('');
  // El id (uuid) hace falta para saber qué fisio del catálogo es esta persona
  const [usuarioId, setUsuarioId] = useState(null);
  // Rol del usuario: 'dueno', 'recepcion' o 'fisio'.
  //
  // Arranca en null (desconocido) y NO en 'dueno'. Antes el valor inicial era
  // 'dueno' para evitar un parpadeo, pero eso hacía que cualquier fallo —la
  // consulta que se cae, el perfil que no existe, la red que falla— dejara al
  // usuario con acceso total. Fallaba ABRIENDO permisos.
  // Ahora, ante la duda, se restringe: si algo sale mal se queda sin acceso a
  // lo financiero, que es el lado seguro del error.
  const [rol, setRol] = useState(null);
  const esDueno = rol === 'dueno';
  // Fisio: rol nuevo, sin módulo propio todavía (llega con el sistema de citas).
  // No hereda ninguno de los permisos de dueño/recepción — la protección real
  // está en la base de datos (RLS), esto solo evita mostrarle pantallas vacías.
  const esFisio = rol === 'fisio';
  // Qué fila del catálogo de fisios corresponde al usuario logueado.
  // Se vincula con la columna perfil_id (ver migracion_rol_fisio.sql).
  const miFisioId = useMemo(() => {
    if (!state?.fisios) return null;
    return state.fisios.find((f) => f.perfil_id && f.perfil_id === usuarioId)?.id || null;
  }, [state, usuarioId]);

  useEffect(() => {
    (async () => {
      const { data } = await sb.auth.getUser();
      const u = data?.user;
      if (!u) return;
      setUsuario(u.email || '');
      setUsuarioId(u.id);
      const { data: perfil, error } = await sb.from('perfiles').select('rol').eq('id', u.id).maybeSingle();
      if (error) {
        // No se silencia: sin rol la persona queda restringida y tiene que
        // saber por qué, en vez de pensar que el sistema está roto.
        console.error('No se pudo leer el rol:', error);
        toast('No se pudo verificar tu perfil. Trabajas con permisos limitados; recarga la página.', 'warn');
        return;
      }
      if (perfil?.rol) {
        setRol(perfil.rol);
      } else {
        console.warn('El usuario no tiene fila en perfiles — queda restringido.');
        toast('Tu usuario no tiene un rol asignado. Avísale a la dueña.', 'warn');
      }
    })();
  }, []);

  // Registra una acción en la tabla de auditoría (solo-agregar, nadie puede borrarla).
  // Nunca interrumpe la operación principal: si falla, solo avisa por consola.
  const registrarAuditoria = async (accion, entidad, entidadId, descripcion, datos) => {
    try {
      const { error } = await sb.from('auditoria').insert({
        usuario: usuario || '(sesión desconocida)',
        accion, entidad, entidad_id: entidadId ? String(entidadId) : null,
        descripcion, datos: datos || null,
      });
      // Si falla, hay que ENTERARSE: sin este registro la acción queda sin rastro.
      if (error) {
        console.error('Auditoría no registrada:', error);
        toast('⚠️ La acción se hizo pero NO quedó en la auditoría. Avisa al administrador.', 'warn');
      }
    } catch (e) {
      console.error('Auditoría no registrada:', e);
      toast('⚠️ La acción se hizo pero NO quedó en la auditoría. Avisa al administrador.', 'warn');
    }
  };

  const toast = (msg, type = 'warn') => {
    const id = uid();
    setToasts((t) => [...t, { id, msg, type }]);
    setTimeout(() => setToasts((t) => t.filter((x) => x.id !== id)), 5000);
  };

  const reload = async () => {
    try {
      const data = await fetchAll();
      setState(data);
      setError(null);
    } catch (e) {
      setError('No se pudo conectar con la base de datos.');
    }
  };

  useEffect(() => { reload(); }, []);

  // ---------- Mutations ----------
  const addClienteYPaquete = async (clientesNuevos, paquetes, compradorNuevo) => {
    // Acepta uno o varios clientes/paquetes (bonos multi-beneficiario crean uno por persona)
    const clientes = Array.isArray(clientesNuevos) ? clientesNuevos.filter(Boolean) : (clientesNuevos ? [clientesNuevos] : []);
    const paqs     = Array.isArray(paquetes) ? paquetes : [paquetes];

    // Insertar clientes nuevos (beneficiarios)
    for (const c of clientes) {
      const { error } = await sb.from('clientes').insert(c);
      if (error) throw error;
    }
    // Insertar comprador nuevo (si aplica)
    if (compradorNuevo) {
      const { error } = await sb.from('clientes').insert(compradorNuevo);
      if (error) throw error;
    }
    // Insertar paquetes
    for (const p of paqs) {
      const { error } = await sb.from('paquetes').insert(toDB.paquete(p));
      if (error) throw error;
    }
    await reload();

    // WhatsApp a cada beneficiario
    for (const p of paqs) {
      const cli = clientes.find((c) => c.id === p.clienteId) || state.clientes.find((c) => c.id === p.clienteId);
      const nom = cli?.nombre || '';
      const sv  = findSv(state, p.servicioId);
      const tipoTxt = p.esBono ? 'bono regalo' : 'paquete';
      const msg = `Hola ${nom.split(' ')[0]} 👋 Tu ${tipoTxt} quedó registrado en Massage Therapy Manizales.\n\n💆 *${sv?.nombre}*\n📌 ${p.sesionesTotales} ${p.sesionesTotales === 1 ? 'sesión disponible' : 'sesiones disponibles'}\n💰 Valor: ${COP(p.valorTotal)}\n📅 Inicia: ${fmtDate(p.fechaInicio)}\n\n¡Te esperamos! 🌿`;
      const r = await sendWA(cli?.telefono, msg);
      if (r.noPhone) toast(`WhatsApp no enviado: ${nom || 'beneficiario'} no tiene teléfono registrado.`);
    }
  };

  const addSesion = async (sesion, paqueteId) => {
    // Capturar info antes del reload
    const cliente = state.clientes.find((c) => c.id === sesion.clienteId);
    const fisio   = state.fisios.find((f) => f.id === sesion.fisioId);
    const sv      = findSv(state, sesion.servicioId);
    const paq     = paqueteId ? state.paquetes.find((p) => p.id === paqueteId) : null;

    const { error: e } = await sb.from('sesiones').insert(toDB.sesion(sesion));
    if (e) throw e;
    if (paqueteId && paq) {
      await sb.from('paquetes').update({ sesiones_usadas: paq.sesionesUsadas + 1 }).eq('id', paqueteId);
    }
    await reload();

    // WhatsApp al cliente
    const restantes = paq ? (paq.sesionesTotales - paq.sesionesUsadas - 1) : null;
    const msgCliente = `Hola ${cliente?.nombre?.split(' ')[0] || ''} ✅ Tu sesión de *${sv?.nombre}* fue registrada el ${fmtDate(sesion.fecha)}.${restantes !== null ? `\n\n📊 Sesiones restantes: *${restantes}*` : ''}\n\n— Massage Therapy Manizales 🌿`;
    const rc = await sendWA(cliente?.telefono, msgCliente);
    if (rc.noPhone) toast(`WhatsApp no enviado al cliente: ${cliente?.nombre || ''} no tiene teléfono.`);

    // WhatsApp al fisio 1
    const msgFisio = `Hola ${fisio?.nombre?.split(' ')[0] || ''} 💆 Sesión con *${cliente?.nombre}* registrada el ${fmtDate(sesion.fecha)}.\n\n💰 Tu comisión: *${COP(sesion.comisionFisio)}*\n\n— Massage Therapy Manizales`;
    const rf = await sendWA(fisio?.telefono, msgFisio);
    if (rf.noPhone) toast(`WhatsApp no enviado al fisio: ${fisio?.nombre || ''} no tiene teléfono registrado.`);

    // WhatsApp a fisios adicionales (hasta 3 extras)
    const totalFisios = 1 + (sesion.fisiosAdicionales || []).length;
    const manosTxt = totalFisios === 2 ? 'en pareja' : `a ${totalFisios * 2} manos`;
    for (const extra of (sesion.fisiosAdicionales || [])) {
      const fisioExtra = state.fisios.find((f) => f.id === extra.fisioId);
      if (!fisioExtra) continue;
      const msgExtra = `Hola ${fisioExtra.nombre.split(' ')[0]} 🙌 Sesión ${manosTxt} con *${cliente?.nombre}* registrada el ${fmtDate(sesion.fecha)}.\n\n💰 Tu comisión: *${COP(extra.comision)}*\n\n— Massage Therapy Manizales`;
      const rEx = await sendWA(fisioExtra.telefono, msgExtra);
      if (rEx.noPhone) toast(`WhatsApp no enviado: ${fisioExtra.nombre} no tiene teléfono registrado.`);
    }
  };

  // Edita una sesión existente y guarda un historial de cambios (auditoría).
  // nuevaSesion = objeto sesión completo (camelCase) ya modificado; sesionAnterior = original.
  const updateSesion = async (sesionId, nuevaSesion, sesionAnterior) => {
    const fmtFisio = (id) => state.fisios.find((f) => f.id === id)?.nombre || '—';
    const cambios = [];
    if (sesionAnterior.fecha !== nuevaSesion.fecha)
      cambios.push(`Fecha: ${fmtDate(sesionAnterior.fecha)} → ${fmtDate(nuevaSesion.fecha)}`);
    if (sesionAnterior.fisioId !== nuevaSesion.fisioId)
      cambios.push(`Fisio: ${fmtFisio(sesionAnterior.fisioId)} → ${fmtFisio(nuevaSesion.fisioId)}`);
    const oldExtras = (sesionAnterior.fisiosAdicionales || []).map((f) => fmtFisio(f.fisioId)).join(', ') || 'ninguno';
    const newExtras = (nuevaSesion.fisiosAdicionales || []).map((f) => fmtFisio(f.fisioId)).join(', ') || 'ninguno';
    if (oldExtras !== newExtras)
      cambios.push(`Fisios extra: ${oldExtras} → ${newExtras}`);
    if (Number(sesionAnterior.valorSesion) !== Number(nuevaSesion.valorSesion))
      cambios.push(`Valor sesión: ${COP(sesionAnterior.valorSesion)} → ${COP(nuevaSesion.valorSesion)}`);
    if (sesionAnterior.comisionPct != null && nuevaSesion.comisionPct != null && Number(sesionAnterior.comisionPct) !== Number(nuevaSesion.comisionPct))
      cambios.push(`Comisión: ${sesionAnterior.comisionPct}% → ${nuevaSesion.comisionPct}%`);
    if (Number(sesionAnterior.comisionFisio) !== Number(nuevaSesion.comisionFisio))
      cambios.push(`Comisión c/u: ${COP(sesionAnterior.comisionFisio)} → ${COP(nuevaSesion.comisionFisio)}`);

    if (cambios.length === 0) return; // nada cambió, no escribir

    const entrada = { ts: new Date().toISOString(), editor: usuario || '(sesión desconocida)', cambios };
    const historial = [...(sesionAnterior.historial || []), entrada];
    const payload = toDB.sesion({ ...nuevaSesion, historial });
    const { error: e } = await sb.from('sesiones').update(payload).eq('id', sesionId);
    if (e) throw e;
    await reload();
  };

  // ── CITAS ────────────────────────────────────────────────────────────
  // Toda modificación deja rastro en `historial`: quién y cuándo. Ese es el
  // motivo principal de traer la agenda al sistema — en el calendario
  // compartido de iCloud no se veía quién había movido qué.

  const addCita = async (cita) => {
    const payload = toDB.cita({
      ...cita,
      creadoPor: usuario || '(desconocido)',
      historial: [{
        ts: new Date().toISOString(),
        editor: usuario || '(desconocido)',
        cambios: ['Cita creada'],
      }],
    });
    const { error } = await sb.from('citas').insert(payload);
    if (error) throw error;
    await reload();
  };

  const updateCita = async (citaId, cambiosCita, citaAnterior) => {
    const nFisio = (id) => state.fisios.find((f) => f.id === id)?.nombre || '—';
    const nServ  = (id) => findSv(state, id)?.nombre || '—';
    const ESTADOS = { agendada: 'Agendada', atendida: 'Atendida', cancelada: 'Cancelada', no_asistio: 'No asistió' };

    const nueva = { ...citaAnterior, ...cambiosCita };
    const cambios = [];
    if (citaAnterior.fecha !== nueva.fecha || citaAnterior.horaInicio !== nueva.horaInicio)
      cambios.push(`Movida: ${fmtDate(citaAnterior.fecha)} ${citaAnterior.horaInicio} → ${fmtDate(nueva.fecha)} ${nueva.horaInicio}`);
    if (citaAnterior.fisioId !== nueva.fisioId)
      cambios.push(`Fisio: ${nFisio(citaAnterior.fisioId)} → ${nFisio(nueva.fisioId)}`);
    if (citaAnterior.servicioId !== nueva.servicioId)
      cambios.push(`Servicio: ${nServ(citaAnterior.servicioId)} → ${nServ(nueva.servicioId)}`);
    if (citaAnterior.estado !== nueva.estado)
      cambios.push(`Estado: ${ESTADOS[citaAnterior.estado] || citaAnterior.estado} → ${ESTADOS[nueva.estado] || nueva.estado}`);
    if ((citaAnterior.nota || '') !== (nueva.nota || ''))
      cambios.push('Nota modificada');

    if (cambios.length === 0) return;

    const historial = [...(citaAnterior.historial || []), {
      ts: new Date().toISOString(),
      editor: usuario || '(desconocido)',
      cambios,
    }];
    const { error } = await sb.from('citas').update(toDB.cita({ ...nueva, historial })).eq('id', citaId);
    if (error) throw error;
    await reload();
  };

  const deleteCita = async (citaId) => {
    const cita = (state.citas || []).find((c) => c.id === citaId);
    if (!cita) throw new Error('Cita no encontrada');
    const cli = cita.clienteNombre || state.clientes.find((c) => c.id === cita.clienteId)?.nombre || '?';
    // Se registra ANTES de borrar: si el borrado falla, igual queda el intento;
    // si sale bien, queda el rastro de qué se borró.
    await registrarAuditoria(
      'eliminar', 'cita', citaId,
      `Cita eliminada: ${cli} · ${fmtDate(cita.fecha)} ${cita.horaInicio}`,
      cita,
    );
    const { error } = await sb.from('citas').delete().eq('id', citaId);
    if (error) throw error;
    await reload();
  };

  // Marca una deuda como cobrada SIN registrar dinero. Es el movimiento más
  // sensible: hace desaparecer una cuenta por cobrar sin que entre plata al balance.
  // Siempre queda registrado en la auditoría.
  const saldarSinPago = async (paqueteId) => {
    const paq = state.paquetes.find((p) => p.id === paqueteId);
    if (!paq) throw new Error('Venta no encontrada');
    const cli = state.clientes.find((c) => c.id === paq.clienteId)?.nombre || '?';
    await registrarAuditoria('saldar_sin_pago', 'venta', paqueteId,
      `Se dio por cobrado un saldo de ${COP(paq.saldoPendiente)} de ${cli} SIN registrar ingreso · factura ${paq.numeroFactura || '(sin factura)'}`,
      { saldoAnulado: paq.saldoPendiente, paquete: paq });
    const { error: e } = await sb.from('paquetes').update({ saldo_pendiente: 0 }).eq('id', paqueteId);
    if (e) throw e;
    await reload();
  };

  // Elimina una sesión y descuenta sesiones_usadas del paquete (si aplica).
  const deleteSesion = async (sesionId) => {
    const sesion = state.sesiones.find((s) => s.id === sesionId);
    if (!sesion) throw new Error('Sesión no encontrada');

    // 0) Dejar rastro ANTES de borrar (si no, no queda evidencia de qué se eliminó)
    const cli = state.clientes.find((c) => c.id === sesion.clienteId)?.nombre || '?';
    const fis = state.fisios.find((f) => f.id === sesion.fisioId)?.nombre || '?';
    await registrarAuditoria('eliminar', 'sesion', sesionId,
      `Sesión de ${cli} con ${fis} el ${fmtDate(sesion.fecha)} · valor ${COP(sesion.valorSesion)} · comisión ${COP(comisionTotalSesion(sesion))}`,
      sesion);

    // 1) Borrar la sesión
    const { error: e1 } = await sb.from('sesiones').delete().eq('id', sesionId);
    if (e1) throw e1;

    // 2) Si pertenece a un paquete, decrementar sesiones_usadas (mínimo 0)
    if (sesion.paqueteId) {
      const paq = state.paquetes.find((p) => p.id === sesion.paqueteId);
      if (paq) {
        const nuevasUsadas = Math.max(0, (paq.sesionesUsadas || 0) - 1);
        const { error: e2 } = await sb.from('paquetes')
          .update({ sesiones_usadas: nuevasUsadas })
          .eq('id', sesion.paqueteId);
        if (e2) throw e2;
      }
    }
    await reload();
  };

  const addGasto = async (gasto) => {
    const { error: e } = await sb.from('gastos').insert(gasto);
    if (e) throw e;
    await reload();
  };

  const deleteGasto = async (id) => {
    const { error: e } = await sb.from('gastos').delete().eq('id', id);
    if (e) throw e;
    await reload();
  };

  const addFisio = async (fisio) => {
    const { error: e } = await sb.from('fisios').insert(fisio);
    if (e) throw e;
    await reload();
  };

  const deleteFisio = async (id) => {
    const { error: e } = await sb.from('fisios').delete().eq('id', id);
    if (e) throw e;
    await reload();
  };

  const updateFisio = async (id, changes) => {
    const { error: e } = await sb.from('fisios').update(changes).eq('id', id);
    if (e) throw e;
    await reload();
  };

  const addServicio = async (sv) => {
    const { error: e } = await sb.from('servicios').insert(sv);
    if (e) throw e;
    await reload();
  };
  const updateServicio = async (id, changes) => {
    const { error: e } = await sb.from('servicios').update(changes).eq('id', id);
    if (e) throw e;
    await reload();
  };

  // Elimina un servicio del catálogo. Solo si NINGUNA venta lo usa: si no,
  // esas ventas quedarían apuntando a un servicio inexistente.
  const deleteServicio = async (id) => {
    const enUso = state.paquetes.filter((p) => p.servicioId === id).length;
    if (enUso > 0) throw new Error(`Hay ${enUso} venta(s) usando este servicio. Desactívalo en vez de eliminarlo.`);
    const sv = findSv(state, id);
    await registrarAuditoria('eliminar', 'servicio', id,
      `Servicio "${sv?.nombre || id}" eliminado del catálogo (${COP(sv?.precio || 0)}, ${sv?.sesiones || 1} sesión/es)`, sv);
    const { error: e } = await sb.from('servicios').delete().eq('id', id);
    if (e) throw e;
    await reload();
  };

  const addCliente = async (cliente) => {
    const { error: e } = await sb.from('clientes').insert(cliente);
    if (e) throw e;
    await reload();
  };

  const updateCliente = async (id, changes) => {
    const { error: e } = await sb.from('clientes').update(changes).eq('id', id);
    if (e) throw e;
    await reload();
  };

  const updatePaquete = async (id, changes) => {
    const { error: e } = await sb.from('paquetes').update(changes).eq('id', id);
    if (e) throw e;
    await reload();
  };

  // Edita una venta (paquete) con auditoría de cambios.
  // nuevoPaquete = objeto completo camelCase ya modificado; paqueteAnterior = original.
  const updateVentaConAudit = async (paqueteId, nuevoPaquete, paqueteAnterior) => {
    const fmtSv = (id) => findSv(state, id)?.nombre || id;
    const cambios = [];
    if (paqueteAnterior.fechaCompra !== nuevoPaquete.fechaCompra)
      cambios.push(`Fecha compra: ${fmtDate(paqueteAnterior.fechaCompra)} → ${fmtDate(nuevoPaquete.fechaCompra)}`);
    if (paqueteAnterior.servicioId !== nuevoPaquete.servicioId)
      cambios.push(`Servicio: ${fmtSv(paqueteAnterior.servicioId)} → ${fmtSv(nuevoPaquete.servicioId)}`);
    if (Number(paqueteAnterior.valorTotal) !== Number(nuevoPaquete.valorTotal))
      cambios.push(`Valor: ${COP(paqueteAnterior.valorTotal)} → ${COP(nuevoPaquete.valorTotal)}`);
    if (paqueteAnterior.metodoPago !== nuevoPaquete.metodoPago)
      cambios.push(`Método pago: ${pagoLabel(paqueteAnterior.metodoPago)} → ${pagoLabel(nuevoPaquete.metodoPago)}`);
    if (Number(paqueteAnterior.sesionesTotales) !== Number(nuevoPaquete.sesionesTotales))
      cambios.push(`Sesiones: ${paqueteAnterior.sesionesTotales} → ${nuevoPaquete.sesionesTotales}`);
    if ((paqueteAnterior.numeroFactura || '') !== (nuevoPaquete.numeroFactura || ''))
      cambios.push(`N° factura: "${paqueteAnterior.numeroFactura || '—'}" → "${nuevoPaquete.numeroFactura || '—'}"`);
    if (paqueteAnterior.fechaInicio !== nuevoPaquete.fechaInicio)
      cambios.push(`Fecha inicio: ${fmtDate(paqueteAnterior.fechaInicio)} → ${fmtDate(nuevoPaquete.fechaInicio)}`);

    if (cambios.length === 0) return; // nada cambió

    const entrada = { ts: new Date().toISOString(), editor: usuario || '(sesión desconocida)', cambios };
    const historial = [...(paqueteAnterior.historial || []), entrada];
    const payload = toDB.paquete({ ...nuevoPaquete, historial });
    const { error: e } = await sb.from('paquetes').update(payload).eq('id', paqueteId);
    if (e) throw e;
    await reload();
  };

  // Registra un abono parcial sobre un paquete con saldo.
  // abono = { fecha, monto, metodoPago, montoEfectivo, montoTransferencia, nota? }
  const addAbono = async (paqueteId, abono) => {
    const paq = state.paquetes.find((p) => p.id === paqueteId);
    if (!paq) throw new Error('Paquete no encontrado');
    const monto = Math.max(0, Number(abono.monto) || 0);
    if (monto <= 0) throw new Error('Monto inválido');
    const saldoActual = Number(paq.saldoPendiente || 0);
    if (monto > saldoActual) throw new Error(`Monto mayor al saldo pendiente (${COP(saldoActual)})`);
    const nuevoAbono = {
      id: uid(),
      fecha: abono.fecha,
      monto,
      metodoPago: abono.metodoPago || 'efectivo',
      montoEfectivo: Number(abono.montoEfectivo || 0),
      montoTransferencia: Number(abono.montoTransferencia || 0),
      montoDatafono: Number(abono.montoDatafono || 0),
      nota: abono.nota || '',
      numeroFactura: abono.numeroFactura || '',
      createdAt: new Date().toISOString(),
    };
    const nuevosAbonos     = [...(paq.abonos || []), nuevoAbono];
    const nuevoAbonoTotal  = (paq.abonoInicial || 0) + monto;
    const nuevoSaldo       = Math.max(0, saldoActual - monto);
    const { error: e } = await sb.from('paquetes')
      .update({ abonos: nuevosAbonos, abono_inicial: nuevoAbonoTotal, saldo_pendiente: nuevoSaldo })
      .eq('id', paqueteId);
    if (e) throw e;
    await reload();
    return nuevoAbono;
  };

  // Elimina un abono (rollback): suma de vuelta al saldo
  const deleteAbono = async (paqueteId, abonoId) => {
    const paq = state.paquetes.find((p) => p.id === paqueteId);
    if (!paq) throw new Error('Paquete no encontrado');
    const ab = (paq.abonos || []).find((a) => a.id === abonoId);
    if (!ab) throw new Error('Abono no encontrado');
    const nuevosAbonos    = (paq.abonos || []).filter((a) => a.id !== abonoId);
    const nuevoAbonoTotal = Math.max(0, (paq.abonoInicial || 0) - Number(ab.monto || 0));
    const nuevoSaldo      = Math.min(Number(paq.valorTotal || 0), Number(paq.saldoPendiente || 0) + Number(ab.monto || 0));

    // Dejar rastro: borrar un abono elimina un ingreso ya registrado — es el
    // movimiento más sensible del sistema.
    const cliA = state.clientes.find((c) => c.id === paq.clienteId)?.nombre || '?';
    await registrarAuditoria('eliminar', 'abono', abonoId,
      `Abono de ${COP(ab.monto)} (${pagoLabel(ab.metodoPago)}) del ${fmtDate(ab.fecha)} · cliente ${cliA} · el saldo subió a ${COP(nuevoSaldo)}`,
      { abono: ab, paqueteId });

    const { error: e } = await sb.from('paquetes')
      .update({ abonos: nuevosAbonos, abono_inicial: nuevoAbonoTotal, saldo_pendiente: nuevoSaldo })
      .eq('id', paqueteId);
    if (e) throw e;
    await reload();
  };

  const deletePaquete = async (id) => {
    // 0) Dejar rastro ANTES de borrar (venta + sesiones que se van con ella)
    const paq  = state.paquetes.find((p) => p.id === id);
    const hijas = state.sesiones.filter((s) => s.paqueteId === id);
    if (paq) {
      const cli = state.clientes.find((c) => c.id === paq.clienteId)?.nombre || '?';
      const sv  = findSv(state, paq.servicioId)?.nombre || paq.servicioId;
      await registrarAuditoria('eliminar', 'venta', id,
        `Venta de ${cli} · ${sv} · ${COP(paq.valorTotal)} del ${fmtDate(paq.fechaCompra)} · factura ${paq.numeroFactura || '(sin factura)'} · arrastró ${hijas.length} sesión(es)`,
        { paquete: paq, sesionesEliminadas: hijas });
    }

    // 1) Borrar primero las sesiones asociadas (la BD bloquea el borrado si quedan hijas)
    const { error: eSes } = await sb.from('sesiones').delete().eq('paquete_id', id);
    if (eSes) throw eSes;
    // 2) Borrar el paquete
    const { error: e } = await sb.from('paquetes').delete().eq('id', id);
    if (e) throw e;
    await reload();
  };

  const addProveedor = async (p) => {
    const { error: e } = await sb.from('proveedores').insert(p);
    if (e) throw e;
    await reload();
  };
  const updateProveedor = async (id, ch) => {
    const { error: e } = await sb.from('proveedores').update(ch).eq('id', id);
    if (e) throw e;
    await reload();
  };
  const deleteProveedor = async (id) => {
    const { error: e } = await sb.from('proveedores').delete().eq('id', id);
    if (e) throw e;
    await reload();
  };

  // ── Respaldo COMPLETO y restaurable ──────────────────────────────────
  //
  // Diferencia con exportData(): aquel es un INFORME para leer en Excel —
  // usa nombres en vez de identificadores, omite tablas y omite columnas.
  // Con él NO se puede repoblar la base (las sesiones no dicen a qué venta
  // pertenecen, y hay clientes con nombres repetidos: sería adivinar).
  //
  // Esto vuelca cada tabla TAL CUAL está en la base: todas las columnas,
  // con sus identificadores y relaciones. Es lo que permite restaurar.
  const TABLAS_RESPALDO = [
    // Orden de dependencia: primero las que no dependen de nadie.
    // Importa al restaurar — no se puede insertar una venta si su cliente
    // todavía no existe.
    'clientes', 'fisios', 'servicios', 'proveedores',
    'paquetes', 'sesiones', 'gastos', 'perfiles', 'auditoria',
  ];

  const [respaldando, setRespaldando] = useState(false);

  const exportRespaldoCompleto = async () => {
    const XLSX = window.XLSX;
    if (!XLSX) { alert('No se pudo cargar el generador de Excel. Revisa tu conexión.'); return; }
    setRespaldando(true);
    try {
      const wb = XLSX.utils.book_new();
      const resumen = [];
      let fallos = 0;

      for (const tabla of TABLAS_RESPALDO) {
        let filas = [];
        try {
          filas = await fetchTodas(tabla, 'id');
        } catch (err) {
          fallos++;
          resumen.push({ Tabla: tabla, Filas: 'ERROR', Detalle: err.message });
          continue;
        }

        // Las columnas jsonb (abonos, historial, fisios_adicionales) son
        // objetos: se guardan como texto JSON para que quepan en una celda
        // y se puedan volver a leer al restaurar.
        const planas = filas.map((f) => {
          const o = {};
          Object.entries(f).forEach(([k, v]) => {
            o[k] = (v !== null && typeof v === 'object') ? JSON.stringify(v) : v;
          });
          return o;
        });

        // Se recogen las columnas de TODAS las filas, no solo de la primera:
        // si una fila trae un campo que otra no, igual queda en el respaldo.
        const columnas = [...new Set(planas.flatMap((o) => Object.keys(o)))];
        const ws = XLSX.utils.json_to_sheet(planas, { header: columnas });
        XLSX.utils.book_append_sheet(wb, ws, tabla);
        resumen.push({ Tabla: tabla, Filas: filas.length, Detalle: '' });
      }

      // Hoja de control: sirve para verificar que la restauración quedó igual
      const meta = [
        { Campo: 'Generado', Valor: new Date().toLocaleString('es-CO') },
        { Campo: 'Usuario', Valor: usuario || '(desconocido)' },
        { Campo: 'Origen', Valor: SUPA_URL },
        { Campo: '', Valor: '' },
        { Campo: 'QUÉ ES ESTO', Valor: 'Respaldo fiel: una hoja por tabla, con todas las columnas e identificadores.' },
        { Campo: 'PARA QUÉ SIRVE', Valor: 'Permite repoblar la base de datos si se pierde o se daña.' },
        { Campo: 'NO INCLUYE', Valor: 'Usuarios y contraseñas de acceso (viven en el sistema de autenticación, no en estas tablas). Se recrean a mano: son pocos.' },
        { Campo: 'CÓMO RESTAURAR', Valor: 'Con el script restaurar-respaldo.mjs. Respeta el orden de las hojas.' },
        { Campo: '', Valor: '' },
        ...resumen.map((r) => ({ Campo: 'Filas · ' + r.Tabla, Valor: String(r.Filas) + (r.Detalle ? ' — ' + r.Detalle : '') })),
      ];
      const wsMeta = XLSX.utils.json_to_sheet(meta, { header: ['Campo', 'Valor'] });
      XLSX.utils.book_append_sheet(wb, wsMeta, 'LEEME');
      // La hoja de control va primero
      wb.SheetNames = ['LEEME', ...wb.SheetNames.filter((n) => n !== 'LEEME')];

      XLSX.writeFile(wb, `MTM-RESPALDO-COMPLETO-${todayISO()}.xlsx`);

      const detalle = resumen.map((r) => `${r.Tabla}: ${r.Filas}`).join(' · ');
      if (fallos > 0) {
        toast('⚠️ El respaldo se descargó pero ' + fallos + ' tabla(s) fallaron. Revisa la hoja LEEME.', 'warn');
      } else {
        alert('✅ Respaldo completo descargado.\n\n' + detalle + '\n\nGuárdalo en un lugar seguro.');
      }
    } catch (err) {
      alert('Error al generar el respaldo: ' + err.message);
    } finally {
      setRespaldando(false);
    }
  };

  const exportData = () => {
    if (!state) return;
    const XLSX = window.XLSX;
    if (!XLSX) { alert('No se pudo cargar el generador de Excel. Revisa tu conexión e inténtalo de nuevo.'); return; }

    const nCliente  = (id) => state.clientes.find((c) => c.id === id)?.nombre || '';
    const nFisio    = (id) => state.fisios.find((f) => f.id === id)?.nombre || '';
    const nServicio = (id) => findSv(state, id)?.nombre || id;

    // — Clientes —
    const clientes = state.clientes.map((c) => ({
      Nombre: c.nombre, 'Tipo doc': c.tipo_doc || 'CC', Documento: c.cedula || '',
      Teléfono: c.telefono || '', Email: c.email || '', Ciudad: c.ciudad || '', Dirección: c.direccion || '',
    }));

    // — Ventas (paquetes) —
    const ventas = state.paquetes.map((p) => ({
      Fecha: p.fechaCompra, Cliente: nCliente(p.clienteId), Servicio: nServicio(p.servicioId),
      'Es bono': p.esBono ? 'Sí' : 'No', 'Valor total': p.valorTotal, Abonado: p.abonoInicial,
      'Saldo pendiente': p.saldoPendiente, 'Método pago': pagoLabel(p.metodoPago),
      Efectivo: p.montoEfectivo, Transferencia: p.montoTransferencia, Datáfono: p.montoDatafono,
      'Sesiones totales': p.sesionesTotales, 'Sesiones usadas': p.sesionesUsadas,
      Factura: p.numeroFactura || '', 'Fecha inicio': p.fechaInicio, Vencimiento: p.fechaVencimiento || '',
    }));

    // — Sesiones —
    const sesiones = state.sesiones.map((s) => ({
      Fecha: s.fecha, Cliente: nCliente(s.clienteId), Servicio: nServicio(s.servicioId),
      'Fisio principal': nFisio(s.fisioId),
      'Fisios adicionales': (s.fisiosAdicionales || []).map((f) => nFisio(f.fisioId)).join(', '),
      'Valor sesión': s.valorSesion, 'Comisión fisio': s.comisionFisio, 'Comisión total': comisionTotalSesion(s),
      // Sin esta columna, una cortesía se ve como una sesión de $0 y parece un error
      Cortesía: s.esCortesia ? 'Sí' : '',
    }));

    // — Abonos —
    const abonos = state.paquetes.flatMap((p) => (p.abonos || []).map((a) => ({
      Fecha: a.fecha, Cliente: nCliente(p.clienteId), Servicio: nServicio(p.servicioId),
      Monto: a.monto, 'Método pago': pagoLabel(a.metodoPago), Factura: a.numeroFactura || '', Nota: a.nota || '',
    })));

    // — Egresos —
    const egresos = (state.gastos || []).map((g) => ({
      Fecha: g.fecha, 'N° egreso': g.numeroEgreso || '', Descripción: g.descripcion, Categoría: g.categoria,
      Monto: g.monto, Beneficiario: g.beneficiario || '', 'Método pago': g.metodoPagoEgreso || '',
    }));

    // — Fisios —
    const fisios = state.fisios.map((f) => ({ Nombre: f.nombre, Documento: f.cedula || '', Teléfono: f.telefono || '' }));

    // — Servicios —
    const servicios = getSvs(state).map((s) => ({
      Nombre: s.nombre, Precio: s.precio, Sesiones: s.sesiones || 1, Categoría: s.categoria, Activo: s.activo ? 'Sí' : 'No',
    }));

    const wb = XLSX.utils.book_new();
    const addSheet = (nombre, filas) => {
      const ws = XLSX.utils.json_to_sheet(filas.length ? filas : [{ '(sin datos)': '' }]);
      XLSX.utils.book_append_sheet(wb, ws, nombre);
    };
    addSheet('Clientes',  clientes);
    addSheet('Ventas',    ventas);
    addSheet('Sesiones',  sesiones);
    addSheet('Abonos',    abonos);
    addSheet('Egresos',   egresos);
    addSheet('Fisios',    fisios);
    addSheet('Servicios', servicios);

    XLSX.writeFile(wb, `MTM-backup-${todayISO()}.xlsx`);
  };

  // Fisio no ve ninguna de estas pestañas: todas son de gestión del negocio
  // (ventas, clientes, finanzas) y su módulo (agenda propia) aún no existe.
  const TABS = esFisio ? [
    { id: 'agenda', label: 'Mi agenda', short: 'Agenda', icon: 'clock' },
  ] : [
    { id: 'dashboard',   label: 'Dashboard',          short: 'Inicio',     icon: 'home'          },
    { id: 'agenda',      label: 'Agenda',              short: 'Agenda',     icon: 'clock'         },
    { id: 'venta',       label: 'Nueva venta',         short: 'Venta',      icon: 'shopping-cart' },
    { id: 'clientes',    label: 'Clientes',            short: 'Clientes',   icon: 'users'         },
    { id: 'porCobrar',   label: 'Cuentas por cobrar',  short: 'Por cobrar', icon: 'clock'         },
    { id: 'proveedores', label: 'Proveedores',         short: 'Proveed.',   icon: 'truck'         },
    { id: 'bonos',       label: 'Bonos regalo',        short: 'Bonos',      icon: 'gift'          },
    { id: 'sesion',      label: 'Registrar sesión',    short: 'Sesión',     icon: 'activity'      },
    { id: 'balance',     label: 'Balance diario',      short: 'Balance',    icon: 'wallet'        },
    { id: 'egresos',     label: 'Egresos',             short: 'Egresos',    icon: 'trending-down' },
    { id: 'liquidacion', label: 'Liquidación fisio',   short: 'Pagos',      icon: 'dollar-sign'   },
    { id: 'asistente',   label: 'Asistente IA',        short: 'IA',         icon: 'sparkles'      },
  // Recepción no ve la información financiera del negocio.
  ].filter((t) => esDueno || !['balance', 'egresos', 'liquidacion', 'asistente'].includes(t.id));

  // Si el rol cambia y la pestaña abierta ya no está permitida, volver al inicio.
  useEffect(() => {
    if (esFisio) { setTab('agenda'); return; }
    if (!TABS.some((t) => t.id === tab)) setTab('dashboard');
  }, [rol]);

  if (!state) {
    return (
      <div className="min-h-screen flex items-center justify-center bg-stone-50">
        <div className="text-center space-y-3">
          <div className="w-14 h-14 mx-auto rounded-2xl bg-brand-600 text-white flex items-center justify-center text-3xl">💆‍♀️</div>
          {error ? (
            <>
              <p className="text-stone-600 text-sm">{error}</p>
              <button onClick={reload} className="px-4 py-2 rounded-lg bg-brand-600 text-white text-sm font-medium hover:bg-brand-700">
                Reintentar
              </button>
            </>
          ) : (
            <>
              <p className="text-stone-500 text-sm font-medium">Cargando datos…</p>
              <div className="flex justify-center gap-1">
                {[0,1,2].map((i) => (
                  <span key={i} className="w-2 h-2 bg-brand-400 rounded-full animate-bounce" style={{ animationDelay: `${i*120}ms` }} />
                ))}
              </div>
            </>
          )}
        </div>
      </div>
    );
  }

  const SidebarBottom = () => (
    <div className="p-3 space-y-1 border-t border-stone-200">
      {/* Quién está usando el sistema y con qué permisos */}
      {usuario && (
        <div className="px-3 py-2 mb-1 rounded-lg bg-stone-50 border border-stone-200">
          <div className="text-[10px] text-stone-400 truncate" title={usuario}>{usuario}</div>
          <div className={`text-[11px] font-bold ${esDueno ? 'text-brand-700' : 'text-amber-700'}`}>
            {esDueno ? '👑 Dueña — acceso total' : esFisio ? '🩺 Fisioterapeuta — su agenda' : '🧾 Recepción — registro'}
          </div>
        </div>
      )}
      {/* El backup contiene todos los datos del negocio: solo la dueña lo descarga */}
      {esDueno && (
        <>
          <button onClick={exportData} className="w-full flex items-center gap-2 px-3 py-2 rounded-lg text-xs text-stone-600 hover:bg-stone-50 font-medium">
            <Icon name="download" className="w-4 h-4" />
            Informe Excel (para leer)
          </button>
          <button onClick={exportRespaldoCompleto} disabled={respaldando}
            className="w-full flex items-center gap-2 px-3 py-2 rounded-lg text-xs text-brand-700 hover:bg-brand-50 font-semibold disabled:opacity-50">
            <Icon name="download" className="w-4 h-4" />
            {respaldando ? 'Generando respaldo…' : 'Respaldo completo (restaurable)'}
          </button>
        </>
      )}
      <button onClick={reload} className="w-full flex items-center gap-2 px-3 py-2 rounded-lg text-xs text-stone-500 hover:bg-stone-50">
        <Icon name="refresh" className="w-4 h-4" />
        Actualizar datos
      </button>
      <button onClick={() => { if (confirm('¿Cerrar sesión?')) onLogout(); }}
        className="w-full flex items-center gap-2 px-3 py-2 rounded-lg text-xs text-rose-500 hover:bg-rose-50 font-medium">
        <Icon name="log-out" className="w-4 h-4" />
        Cerrar sesión
      </button>
    </div>
  );

  return (
    <div className="min-h-screen flex bg-stone-50 text-stone-900">
      {/* Sidebar desktop */}
      <aside className="hidden lg:flex lg:flex-col w-64 shrink-0 bg-white border-r border-stone-200">
        <div className="px-5 py-5 border-b border-stone-200">
          <div className="flex items-center gap-2.5">
            <div className="w-9 h-9 rounded-xl bg-brand-600 text-white flex items-center justify-center text-lg">💆‍♀️</div>
            <div>
              <div className="font-bold text-sm leading-tight">Massage Therapy</div>
              <div className="text-[11px] text-stone-500 leading-tight">Manizales</div>
            </div>
          </div>
        </div>
        <nav className="flex-1 p-3 space-y-1">
          {TABS.map((t) => (
            <button key={t.id} onClick={() => setTab(t.id)}
              className={`w-full flex items-center gap-3 px-3 py-2.5 rounded-lg text-sm font-medium transition ${tab === t.id ? 'bg-brand-50 text-brand-700' : 'text-stone-600 hover:bg-stone-50'}`}>
              <Icon name={t.icon} className="w-[18px] h-[18px]" />
              {t.label}
            </button>
          ))}
        </nav>
        <SidebarBottom />
      </aside>

      {/* Mobile drawer */}
      {navOpen && (
        <div className="lg:hidden fixed inset-0 z-40">
          <div className="absolute inset-0 bg-stone-900/40" onClick={() => setNavOpen(false)} />
          <aside className="absolute left-0 top-0 bottom-0 w-72 bg-white shadow-xl flex flex-col">
            <div className="px-5 py-4 border-b border-stone-200 flex items-center justify-between">
              <div className="flex items-center gap-2.5">
                <div className="w-9 h-9 rounded-xl bg-brand-600 text-white flex items-center justify-center text-lg">💆‍♀️</div>
                <div>
                  <div className="font-bold text-sm">Massage Therapy</div>
                  <div className="text-[11px] text-stone-500">Manizales</div>
                </div>
              </div>
              <button onClick={() => setNavOpen(false)} className="p-1.5 rounded hover:bg-stone-100"><Icon name="x" /></button>
            </div>
            <nav className="flex-1 p-3 space-y-1">
              {esFisio && (
                <div className="px-3 py-2.5 text-xs text-stone-500 leading-relaxed">
                  Tu agenda de citas está en construcción. Muy pronto vas a poder verla y gestionarla aquí.
                </div>
              )}
              {TABS.map((t) => (
                <button key={t.id} onClick={() => { setTab(t.id); setNavOpen(false); }}
                  className={`w-full flex items-center gap-3 px-3 py-2.5 rounded-lg text-sm font-medium ${tab === t.id ? 'bg-brand-50 text-brand-700' : 'text-stone-600'}`}>
                  <Icon name={t.icon} className="w-[18px] h-[18px]" />
                  {t.label}
                </button>
              ))}
            </nav>
            <SidebarBottom />
          </aside>
        </div>
      )}

      {/* Main */}
      <main className="flex-1 flex flex-col min-w-0">
        <header className="bg-white border-b border-stone-200 px-4 lg:px-8 py-3.5 flex items-center justify-between sticky top-0 z-30">
          <div className="flex items-center gap-3">
            <button onClick={() => setNavOpen(true)} className="lg:hidden p-1.5 rounded hover:bg-stone-100">
              <Icon name="menu" />
            </button>
            <div>
              <h1 className="font-bold text-lg leading-tight">{TABS.find((t) => t.id === tab)?.label || 'Agenda'}</h1>
              <p className="text-xs text-stone-500 leading-tight">Massage Therapy Manizales</p>
            </div>
          </div>
        </header>

        {/* Toasts */}
        {toasts.length > 0 && (
          <div className="fixed top-4 right-4 z-50 space-y-2 max-w-sm w-full">
            {toasts.map((t) => (
              <div key={t.id} className="flex items-start gap-2.5 bg-amber-50 border border-amber-200 text-amber-900 rounded-xl px-4 py-3 shadow-lg text-sm fade-in">
                <span className="text-lg shrink-0">⚠️</span>
                <span className="flex-1">{t.msg}</span>
                <button onClick={() => setToasts((x) => x.filter((i) => i.id !== t.id))} className="text-amber-400 hover:text-amber-700 shrink-0">✕</button>
              </div>
            ))}
          </div>
        )}

        <div className="flex-1 p-4 lg:p-8 pb-24 lg:pb-8 overflow-y-auto">
          <div className="fade-in">
            {tab === 'agenda'      && <Agenda state={state} esDueno={esDueno} esFisio={esFisio} miFisioId={miFisioId}
                                              onAdd={addCita} onUpdate={updateCita} onDelete={deleteCita} />}
            {!esFisio && tab === 'dashboard'   && <Dashboard   state={state} setTab={setTab} esDueno={esDueno} />}
            {tab === 'venta'       && <VentaContainer state={state} onSave={addClienteYPaquete} onDone={() => setTab('clientes')} onDeletePaquete={esDueno ? deletePaquete : undefined} onUpdatePaquete={updatePaquete} onUpdateVenta={esDueno ? updateVentaConAudit : undefined} onAddAbono={addAbono} onDeleteAbono={esDueno ? deleteAbono : undefined} />}
            {tab === 'clientes'    && <Clientes    state={state} onAddCliente={addCliente} onUpdateCliente={updateCliente} onUpdatePaquete={updatePaquete} onDeletePaquete={esDueno ? deletePaquete : undefined} onUpdateVenta={esDueno ? updateVentaConAudit : undefined} onAddAbono={addAbono} onDeleteAbono={esDueno ? deleteAbono : undefined} onUpdateSesion={esDueno ? updateSesion : undefined} onDeleteSesion={esDueno ? deleteSesion : undefined} onSaldarSinPago={esDueno ? saldarSinPago : undefined} />}
            {tab === 'porCobrar'   && <CuentasPorCobrar state={state} onAddAbono={addAbono} onUpdatePaquete={updatePaquete} />}
            {tab === 'proveedores' && <Proveedores state={state} onAdd={addProveedor} onUpdate={updateProveedor} onDelete={deleteProveedor} />}
            {tab === 'bonos'       && <Bonos       state={state} />}
            {tab === 'sesion'      && <RegistrarSesion state={state} onSave={addSesion} onDone={() => setTab('balance')} />}
            {tab === 'balance'     && <Balance     state={state} onAddGasto={addGasto} onDeleteGasto={deleteGasto} />}
            {tab === 'egresos'     && <Egresos     state={state} onAddGasto={addGasto} onDeleteGasto={deleteGasto} />}
            {tab === 'liquidacion' && <Liquidacion state={state} onAddFisio={addFisio} onDeleteFisio={deleteFisio} onUpdateFisio={updateFisio} onAddServicio={addServicio} onUpdateServicio={updateServicio} onDeleteServicio={deleteServicio} onAddGasto={addGasto} />}
            {tab === 'asistente'   && <Asistente   state={state} setTab={setTab} usuarioActual={usuario} />}
          </div>
        </div>

        {/* Bottom nav mobile */}
        {TABS.length > 0 && (
          <nav className="lg:hidden fixed bottom-0 left-0 right-0 bg-white border-t border-stone-200 z-30 grid grid-cols-10">
            {TABS.map((t) => (
              <button key={t.id} onClick={() => setTab(t.id)}
                className={`flex flex-col items-center justify-center py-2 text-[9px] gap-0.5 ${tab === t.id ? 'text-brand-700' : 'text-stone-500'}`}>
                <Icon name={t.icon} className="w-[18px] h-[18px]" />
                <span className="leading-none">{t.short}</span>
              </button>
            ))}
          </nav>
        )}
      </main>
    </div>
  );
}

// ---------- Dashboard ----------
// ---------- AGENDA ----------
// Colores por fisio: el ojo distingue de quién es cada cita sin leer.
// Son fijos por posición en el catálogo para que no cambien entre recargas.
const COLORES_FISIO = [
  { bg: 'bg-teal-100',   br: 'border-teal-400',   tx: 'text-teal-900',   punto: 'bg-teal-500' },
  { bg: 'bg-sky-100',    br: 'border-sky-400',    tx: 'text-sky-900',    punto: 'bg-sky-500' },
  { bg: 'bg-violet-100', br: 'border-violet-400', tx: 'text-violet-900', punto: 'bg-violet-500' },
  { bg: 'bg-amber-100',  br: 'border-amber-400',  tx: 'text-amber-900',  punto: 'bg-amber-500' },
  { bg: 'bg-rose-100',   br: 'border-rose-400',   tx: 'text-rose-900',   punto: 'bg-rose-500' },
  { bg: 'bg-emerald-100',br: 'border-emerald-400',tx: 'text-emerald-900',punto: 'bg-emerald-500' },
  { bg: 'bg-indigo-100', br: 'border-indigo-400', tx: 'text-indigo-900', punto: 'bg-indigo-500' },
];
const colorFisio = (fisios, fisioId) => {
  const i = fisios.findIndex((f) => f.id === fisioId);
  return COLORES_FISIO[(i < 0 ? 0 : i) % COLORES_FISIO.length];
};

const ESTADO_CITA = {
  agendada:   { txt: 'Agendada',   emoji: '🕒' },
  atendida:   { txt: 'Atendida',   emoji: '✅' },
  cancelada:  { txt: 'Cancelada',  emoji: '❌' },
  no_asistio: { txt: 'No asistió', emoji: '⚠️' },
};

const sumarDias = (iso, n) => {
  const d = new Date(iso + 'T12:00:00');
  d.setDate(d.getDate() + n);
  return d.toISOString().slice(0, 10);
};

function Agenda({ state, esDueno, esFisio, miFisioId, onAdd, onUpdate, onDelete }) {
  const [fecha,     setFecha]     = useState(todayISO());
  const [filtroF,   setFiltroF]   = useState('todos');
  const [editando,  setEditando]  = useState(null);   // cita existente
  const [creando,   setCreando]   = useState(false);

  const fisios = state.fisios || [];

  // El fisio solo se ve a sí mismo; no tiene sentido darle un filtro.
  const fisiosVisibles = esFisio ? fisios.filter((f) => f.id === miFisioId) : fisios;

  const citasDia = useMemo(() => {
    let cs = (state.citas || []).filter((c) => c.fecha === fecha);
    if (esFisio) cs = cs.filter((c) => c.fisioId === miFisioId);
    else if (filtroF !== 'todos') cs = cs.filter((c) => c.fisioId === filtroF);
    return cs.sort((a, b) => (a.horaInicio || '').localeCompare(b.horaInicio || ''));
  }, [state.citas, fecha, filtroF, esFisio, miFisioId]);

  // Tira de 7 días para moverse rápido
  const semana = useMemo(() => {
    const base = new Date(fecha + 'T12:00:00');
    base.setDate(base.getDate() - 3);
    return Array.from({ length: 7 }, (_, i) => {
      const d = new Date(base); d.setDate(d.getDate() + i);
      return d.toISOString().slice(0, 10);
    });
  }, [fecha]);

  const nombreCliente = (c) =>
    c.clienteNombre || state.clientes.find((x) => x.id === c.clienteId)?.nombre || 'Sin nombre';

  const contarCitas = (f) => (state.citas || []).filter((c) => {
    if (c.fecha !== f) return false;
    if (esFisio) return c.fisioId === miFisioId;
    return true;
  }).length;

  return (
    <div className="space-y-4">
      {/* Navegación de fechas */}
      <div className="flex items-center justify-between gap-2">
        <button onClick={() => setFecha(sumarDias(fecha, -1))}
          className="px-3 py-2 rounded-lg border border-stone-200 hover:bg-stone-50 text-sm">←</button>
        <div className="text-center flex-1">
          <div className="font-bold text-lg leading-tight">{fmtDate(fecha)}</div>
          <button onClick={() => setFecha(todayISO())}
            className="text-xs text-brand-600 hover:underline">Ir a hoy</button>
        </div>
        <button onClick={() => setFecha(sumarDias(fecha, 1))}
          className="px-3 py-2 rounded-lg border border-stone-200 hover:bg-stone-50 text-sm">→</button>
      </div>

      {/* Tira de 7 días */}
      <div className="grid grid-cols-7 gap-1">
        {semana.map((d) => {
          const activo = d === fecha;
          const esHoy  = d === todayISO();
          const n = contarCitas(d);
          const dd = new Date(d + 'T12:00:00');
          return (
            <button key={d} onClick={() => setFecha(d)}
              className={`py-2 rounded-xl text-center transition border ${activo ? 'bg-brand-600 text-white border-brand-600' : esHoy ? 'border-brand-300 bg-brand-50' : 'border-stone-200 hover:bg-stone-50'}`}>
              <div className={`text-[10px] uppercase ${activo ? 'text-brand-100' : 'text-stone-400'}`}>
                {dd.toLocaleDateString('es-CO', { weekday: 'short' })}
              </div>
              <div className="font-bold text-sm">{dd.getDate()}</div>
              {n > 0 && (
                <div className={`text-[10px] ${activo ? 'text-brand-100' : 'text-stone-500'}`}>{n} cita{n !== 1 ? 's' : ''}</div>
              )}
            </button>
          );
        })}
      </div>

      {/* Filtro por fisio + botón agendar */}
      <div className="flex items-center gap-2 flex-wrap">
        {!esFisio && (
          <div className="flex gap-1 flex-wrap flex-1">
            <button onClick={() => setFiltroF('todos')}
              className={`px-3 py-1.5 rounded-full text-xs font-medium border ${filtroF === 'todos' ? 'bg-stone-800 text-white border-stone-800' : 'border-stone-200 hover:bg-stone-50'}`}>
              Todos
            </button>
            {fisios.map((f) => {
              const col = colorFisio(fisios, f.id);
              const on = filtroF === f.id;
              return (
                <button key={f.id} onClick={() => setFiltroF(f.id)}
                  className={`px-3 py-1.5 rounded-full text-xs font-medium border flex items-center gap-1.5 ${on ? `${col.bg} ${col.br} ${col.tx}` : 'border-stone-200 hover:bg-stone-50'}`}>
                  <span className={`w-2 h-2 rounded-full ${col.punto}`} />
                  {f.nombre.split(' ')[0]}
                </button>
              );
            })}
          </div>
        )}
        <button onClick={() => setCreando(true)}
          className="px-4 py-2 rounded-lg bg-brand-600 text-white text-sm font-medium hover:bg-brand-700 flex items-center gap-1.5">
          <Icon name="plus" className="w-4 h-4" /> Agendar cita
        </button>
      </div>

      {/* Lista del día */}
      {citasDia.length === 0 ? (
        <Empty text={`No hay citas para el ${fmtDate(fecha)}.`} />
      ) : (
        <div className="space-y-2">
          {citasDia.map((c) => {
            const fisio = fisios.find((f) => f.id === c.fisioId);
            const col   = colorFisio(fisios, c.fisioId);
            const sv    = findSv(state, c.servicioId);
            const est   = ESTADO_CITA[c.estado] || ESTADO_CITA.agendada;
            const cancelada = c.estado === 'cancelada';
            return (
              <button key={c.id} onClick={() => setEditando(c)}
                className={`w-full text-left rounded-xl border-l-4 ${col.br} ${col.bg} px-4 py-3 hover:shadow-sm transition ${cancelada ? 'opacity-50' : ''}`}>
                <div className="flex items-start justify-between gap-3">
                  <div className="min-w-0 flex-1">
                    <div className={`font-semibold ${col.tx} ${cancelada ? 'line-through' : ''} truncate`}>
                      {nombreCliente(c)}
                    </div>
                    <div className="text-xs text-stone-600 mt-0.5 truncate">
                      {sv?.nombre || 'Sin servicio'} · {fisio?.nombre || 'Sin fisio'}
                    </div>
                    {c.nota && <div className="text-xs text-stone-500 mt-1 italic truncate">📝 {c.nota}</div>}
                    {!c.clienteId && (
                      <div className="text-[10px] mt-1 inline-block px-1.5 py-0.5 rounded-full bg-amber-200 text-amber-900 font-semibold">
                        Cliente nuevo · dar de alta en recepción
                      </div>
                    )}
                  </div>
                  <div className="text-right shrink-0">
                    <div className={`font-bold text-sm ${col.tx}`}>{c.horaInicio}</div>
                    {c.horaFin && <div className="text-[10px] text-stone-500">a {c.horaFin}</div>}
                    <div className="text-[10px] mt-1">{est.emoji} {est.txt}</div>
                  </div>
                </div>
              </button>
            );
          })}
        </div>
      )}

      {(creando || editando) && (
        <CitaModal
          state={state} fecha={fecha}
          cita={editando} esFisio={esFisio} miFisioId={miFisioId} esDueno={esDueno}
          onClose={() => { setCreando(false); setEditando(null); }}
          onAdd={onAdd} onUpdate={onUpdate} onDelete={onDelete}
        />
      )}
    </div>
  );
}

// ---------- Modal de cita: crear / mover / cancelar ----------
function CitaModal({ state, fecha: fechaDefecto, cita, esFisio, miFisioId, esDueno, onClose, onAdd, onUpdate, onDelete }) {
  const editando = !!cita;
  const fisios   = state.fisios || [];

  // Cliente: puede ser uno registrado o alguien nuevo que aún no pasa por
  // recepción. Ese es el flujo que pidió Dani: el fisio agenda, y cuando la
  // persona llega se le da de alta y se le asigna el servicio.
  const [modoCliente, setModoCliente] = useState(cita?.clienteId ? 'existente' : (cita ? 'nuevo' : 'existente'));
  const [clienteId,   setClienteId]   = useState(cita?.clienteId || '');
  const [busqueda,    setBusqueda]    = useState('');
  const [nombreLibre, setNombreLibre] = useState(cita?.clienteNombre || '');
  const [telefono,    setTelefono]    = useState(cita?.clienteTelefono || '');

  const [fisioId,   setFisioId]   = useState(cita?.fisioId || (esFisio ? miFisioId : (fisios[0]?.id || '')));
  const [servicioId,setServicioId]= useState(cita?.servicioId || '');
  const [fecha,     setFecha]     = useState(cita?.fecha || fechaDefecto);
  const [horaInicio,setHoraInicio]= useState(cita?.horaInicio || '09:00');
  const [horaFin,   setHoraFin]   = useState(cita?.horaFin || '10:00');
  const [estado,    setEstado]    = useState(cita?.estado || 'agendada');
  const [nota,      setNota]      = useState(cita?.nota || '');
  const [saving,    setSaving]    = useState(false);
  const [verHist,   setVerHist]   = useState(false);

  const clientesFiltrados = useMemo(() => {
    const q = busqueda.toLowerCase().trim();
    const lista = state.clientes || [];
    if (!q) return lista.slice(0, 40);
    return lista.filter((c) =>
      (c.nombre || '').toLowerCase().includes(q) || (c.cedula || '').includes(q),
    ).slice(0, 40);
  }, [busqueda, state.clientes]);

  // Aviso de choque: dos citas del mismo fisio a la misma hora.
  // No bloquea (a veces se atiende a dos personas), pero avisa.
  const choque = useMemo(() => {
    return (state.citas || []).find((c) =>
      c.id !== cita?.id && c.fisioId === fisioId && c.fecha === fecha &&
      c.estado !== 'cancelada' && c.horaInicio === horaInicio,
    );
  }, [state.citas, fisioId, fecha, horaInicio, cita]);

  const guardar = async (e) => {
    e.preventDefault();
    if (!fisioId) { alert('Selecciona el fisioterapeuta.'); return; }
    if (modoCliente === 'existente' && !clienteId) { alert('Selecciona el cliente, o cámbialo a "Cliente nuevo".'); return; }
    if (modoCliente === 'nuevo' && !nombreLibre.trim()) { alert('Escribe el nombre de la persona.'); return; }
    if (!horaInicio) { alert('Escribe la hora.'); return; }
    if (horaFin && horaFin <= horaInicio) { alert('La hora de fin debe ser posterior a la de inicio.'); return; }
    if (!confirmarFechaRazonable(fecha, 'la fecha de la cita')) return;

    setSaving(true);
    try {
      const datos = {
        clienteId:       modoCliente === 'existente' ? clienteId : null,
        clienteNombre:   modoCliente === 'nuevo' ? nombreLibre.trim() : '',
        clienteTelefono: modoCliente === 'nuevo' ? telefono.trim() : '',
        fisioId, servicioId: servicioId || null,
        fecha, horaInicio, horaFin, estado, nota: nota.trim(),
      };
      if (editando) await onUpdate(cita.id, datos, cita);
      else          await onAdd({ id: uid(), ...datos });
      onClose();
    } catch (err) {
      alert('Error guardando la cita: ' + err.message);
    } finally { setSaving(false); }
  };

  const eliminar = async () => {
    if (!confirm('¿Eliminar esta cita? Queda registrado en la auditoría.')) return;
    try { await onDelete(cita.id); onClose(); }
    catch (err) { alert('Error: ' + err.message); }
  };

  const clienteSel = state.clientes.find((c) => c.id === clienteId);

  return (
    <div className="fixed inset-0 z-50 flex items-end sm:items-center justify-center bg-stone-900/50 p-0 sm:p-4" onClick={onClose}>
      <div className="bg-white w-full sm:max-w-lg sm:rounded-2xl rounded-t-2xl max-h-[92vh] overflow-y-auto" onClick={(e) => e.stopPropagation()}>
        <div className="sticky top-0 bg-white border-b border-stone-200 px-5 py-3.5 flex items-center justify-between">
          <h3 className="font-bold">{editando ? 'Editar cita' : 'Agendar cita'}</h3>
          <button onClick={onClose} className="p-1.5 rounded hover:bg-stone-100"><Icon name="x" /></button>
        </div>

        <form onSubmit={guardar} className="p-5 space-y-4">
          {/* Cliente */}
          <div>
            <Label>Cliente</Label>
            <div className="flex rounded-lg p-0.5 bg-stone-100 text-sm mb-2">
              {[{ v: 'existente', l: 'Ya registrado' }, { v: 'nuevo', l: 'Cliente nuevo' }].map((o) => (
                <button key={o.v} type="button" onClick={() => setModoCliente(o.v)}
                  className={`flex-1 py-1.5 rounded-md font-medium transition ${modoCliente === o.v ? 'bg-white shadow-sm' : 'text-stone-500'}`}>
                  {o.l}
                </button>
              ))}
            </div>

            {modoCliente === 'existente' ? (
              <>
                {clienteSel && (
                  <div className="mb-2 px-3 py-2 rounded-lg bg-brand-50 border border-brand-200 text-sm flex items-center justify-between">
                    <span className="font-medium truncate">{clienteSel.nombre}</span>
                    <button type="button" onClick={() => setClienteId('')} className="text-xs text-stone-500 hover:text-stone-800 shrink-0 ml-2">Cambiar</button>
                  </div>
                )}
                {!clienteSel && (
                  <>
                    <Input value={busqueda} onChange={setBusqueda} placeholder="Buscar por nombre o cédula…" />
                    <div className="mt-1 max-h-44 overflow-y-auto border border-stone-200 rounded-lg divide-y divide-stone-100">
                      {clientesFiltrados.map((c) => (
                        <button key={c.id} type="button" onClick={() => { setClienteId(c.id); setBusqueda(''); }}
                          className="w-full text-left px-3 py-2 text-sm hover:bg-stone-50 flex justify-between gap-2">
                          <span className="truncate">{c.nombre}</span>
                          <span className="text-xs text-stone-400 shrink-0">{c.cedula}</span>
                        </button>
                      ))}
                      {clientesFiltrados.length === 0 && (
                        <div className="px-3 py-3 text-sm text-stone-400">Sin resultados. Usa "Cliente nuevo".</div>
                      )}
                    </div>
                  </>
                )}
              </>
            ) : (
              <div className="space-y-2">
                <Input value={nombreLibre} onChange={setNombreLibre} placeholder="Nombre de la persona" required />
                <Input value={telefono} onChange={setTelefono} placeholder="Teléfono (opcional)" />
                <p className="text-xs text-amber-700 bg-amber-50 border border-amber-200 rounded-lg px-3 py-2">
                  Queda como cliente nuevo. Cuando llegue a recepción se le da de alta y se le asigna el servicio.
                </p>
              </div>
            )}
          </div>

          {/* Fisio */}
          <div>
            <Label>Fisioterapeuta</Label>
            {esFisio ? (
              <div className="px-3 py-2 rounded-lg bg-stone-100 text-sm">
                {fisios.find((f) => f.id === miFisioId)?.nombre || 'Tú'}
              </div>
            ) : (
              <Select value={fisioId} onChange={setFisioId}>
                {fisios.map((f) => <option key={f.id} value={f.id}>{f.nombre}</option>)}
              </Select>
            )}
          </div>

          {/* Servicio */}
          <div>
            <Label>Servicio</Label>
            <Select value={servicioId} onChange={setServicioId}>
              <option value="">Por definir…</option>
              {getSvs(state).filter((sv) => sv.activo !== false).map((sv) => (
                <option key={sv.id} value={sv.id}>{sv.nombre}</option>
              ))}
            </Select>
          </div>

          {/* Fecha y horas */}
          <div className="grid grid-cols-3 gap-2">
            <div><Label>Fecha</Label><Input type="date" value={fecha} onChange={setFecha} required /></div>
            <div><Label>Desde</Label><Input type="time" value={horaInicio} onChange={setHoraInicio} required /></div>
            <div><Label>Hasta</Label><Input type="time" value={horaFin} onChange={setHoraFin} /></div>
          </div>

          {choque && (
            <div className="px-3 py-2 rounded-lg bg-amber-50 border border-amber-300 text-amber-900 text-xs">
              ⚠️ Ese fisioterapeuta ya tiene una cita a las {choque.horaInicio} ese día. Puedes guardar igual si es a propósito.
            </div>
          )}

          {editando && (
            <div>
              <Label>Estado</Label>
              <Select value={estado} onChange={setEstado}>
                {Object.entries(ESTADO_CITA).map(([k, v]) => (
                  <option key={k} value={k}>{v.emoji} {v.txt}</option>
                ))}
              </Select>
            </div>
          )}

          <div>
            <Label>Nota (opcional)</Label>
            <Input value={nota} onChange={setNota} placeholder="Ej: viene por dolor lumbar, primera vez…" />
          </div>

          {/* Historial: lo que Dani no puede ver hoy en iCloud */}
          {editando && (cita.historial || []).length > 0 && (
            <div className="border border-stone-200 rounded-lg">
              <button type="button" onClick={() => setVerHist(!verHist)}
                className="w-full px-3 py-2 text-left text-xs font-semibold text-stone-600 hover:bg-stone-50 flex justify-between">
                <span>🕓 Historial de cambios ({cita.historial.length})</span>
                <span>{verHist ? '▲' : '▼'}</span>
              </button>
              {verHist && (
                <ul className="px-3 pb-2 space-y-1.5 border-t border-stone-100 pt-2">
                  {[...cita.historial].reverse().map((h, i) => (
                    <li key={i} className="text-xs">
                      <div className="text-stone-400">
                        {new Date(h.ts).toLocaleString('es-CO')} · {h.editor}
                      </div>
                      {(h.cambios || []).map((c, j) => (
                        <div key={j} className="text-stone-700">• {c}</div>
                      ))}
                    </li>
                  ))}
                </ul>
              )}
            </div>
          )}

          <div className="flex gap-2 pt-1">
            {editando && (
              <button type="button" onClick={eliminar}
                className="px-4 py-2.5 rounded-lg border border-rose-200 text-rose-600 text-sm font-medium hover:bg-rose-50">
                Eliminar
              </button>
            )}
            <button type="button" onClick={onClose}
              className="px-4 py-2.5 rounded-lg border border-stone-200 text-sm font-medium hover:bg-stone-50">
              Cancelar
            </button>
            <button type="submit" disabled={saving}
              className="flex-1 px-4 py-2.5 rounded-lg bg-brand-600 text-white text-sm font-semibold hover:bg-brand-700 disabled:opacity-50">
              {saving ? 'Guardando…' : editando ? 'Guardar cambios' : 'Agendar'}
            </button>
          </div>
        </form>
      </div>
    </div>
  );
}

// ---------- Dashboard de recepción: operativo, sin cifras del negocio ----------
function DashboardRecepcion({ state, setTab }) {
  const hoy = todayISO();

  const sesionesHoy = (state.sesiones || []).filter((s) => s.fecha === hoy);
  const ventasHoy   = (state.paquetes || []).filter((p) => p.fechaCompra === hoy);
  const conSaldo    = (state.paquetes || []).filter((p) => (p.saldoPendiente || 0) > 0);
  const conSesiones = (state.paquetes || []).filter(
    (p) => (p.sesionesUsadas || 0) < (p.sesionesTotales || 0),
  );

  const porVencer = (state.paquetes || []).filter((p) => {
    if (!p.fechaVencimiento) return false;
    const dias = Math.floor(
      (new Date(p.fechaVencimiento + 'T12:00:00') - new Date(hoy + 'T12:00:00')) / 86400000,
    );
    return dias >= 0 && dias <= 15;
  });

  const Tarjeta = ({ icono, valor, titulo, nota, onClick }) => (
    <button
      onClick={onClick}
      disabled={!onClick}
      className={`text-left p-4 rounded-2xl border border-stone-200 bg-white transition ${onClick ? 'hover:border-brand-300 hover:shadow-sm cursor-pointer' : 'cursor-default'}`}
    >
      <div className="text-2xl mb-1">{icono}</div>
      <div className="text-2xl font-bold text-stone-800">{valor}</div>
      <div className="text-sm font-medium text-stone-600">{titulo}</div>
      {nota && <div className="text-xs text-stone-400 mt-0.5">{nota}</div>}
    </button>
  );

  return (
    <div className="space-y-5">
      <div>
        <h2 className="text-xl font-bold">Hoy en el centro</h2>
        <p className="text-sm text-stone-500">{fmtDate(hoy)}</p>
      </div>

      <div className="grid grid-cols-2 lg:grid-cols-4 gap-3">
        <Tarjeta icono="💆" valor={sesionesHoy.length} titulo="Sesiones de hoy"
                 nota="registradas" onClick={() => setTab('sesion')} />
        <Tarjeta icono="🧾" valor={ventasHoy.length} titulo="Ventas de hoy"
                 nota="registradas" onClick={() => setTab('venta')} />
        <Tarjeta icono="📦" valor={conSesiones.length} titulo="Paquetes activos"
                 nota="con sesiones pendientes" onClick={() => setTab('clientes')} />
        <Tarjeta icono="⏰" valor={conSaldo.length} titulo="Clientes que deben"
                 nota="por cobrar" onClick={() => setTab('porCobrar')} />
      </div>

      {porVencer.length > 0 && (
        <Card>
          <h3 className="font-semibold mb-3">⚠️ Vencen en los próximos 15 días</h3>
          <ul className="space-y-1.5">
            {porVencer.slice(0, 8).map((p) => {
              const cli = state.clientes.find((c) => c.id === p.clienteId);
              return (
                <li key={p.id} className="flex items-center justify-between text-sm border-b border-stone-100 pb-1.5 last:border-0">
                  <span className="font-medium truncate">{cli?.nombre || '—'}</span>
                  <span className="text-xs text-stone-500 shrink-0 ml-2">
                    {p.sesionesTotales - p.sesionesUsadas} sin usar · vence {fmtDate(p.fechaVencimiento)}
                  </span>
                </li>
              );
            })}
          </ul>
          {porVencer.length > 8 && (
            <p className="text-xs text-stone-400 mt-2">…y {porVencer.length - 8} más.</p>
          )}
        </Card>
      )}

      <Card>
        <h3 className="font-semibold mb-1">Accesos rápidos</h3>
        <p className="text-xs text-stone-400 mb-3">Lo que más usas en el día</p>
        <div className="grid grid-cols-2 gap-2">
          <button onClick={() => setTab('venta')}
            className="px-3 py-2.5 rounded-xl bg-brand-600 text-white text-sm font-medium hover:bg-brand-700">
            Nueva venta
          </button>
          <button onClick={() => setTab('sesion')}
            className="px-3 py-2.5 rounded-xl border border-stone-200 text-sm font-medium hover:bg-stone-50">
            Registrar sesión
          </button>
        </div>
      </Card>
    </div>
  );
}

function Dashboard({ state, setTab, esDueno = true }) {
  const hoy = todayISO();

  // Recepción NO ve el tablero financiero: los indicadores, la gráfica de
  // 6 meses, los métodos de pago, los egresos por categoría y el estado de
  // resultados son información del negocio. Además, con RLS ella no puede
  // leer la tabla de egresos, así que "Utilidad neta" le saldría inflada
  // (egresos = 0) — un número incorrecto es peor que ningún número.
  if (!esDueno) return <DashboardRecepcion state={state} setTab={setTab} />;

  const m   = monthKey(hoy);

  // ── datos del mes actual ──────────────────────────────────────────────────
  const { ingresosMes, sesionesMes, pagadoFisioMes, gastosMes,
          saldosFavor, alertas, ventasMes, egresosDesglose,
          efectivoMes, transferenciaMes, datafonoMes, abonosMes, saldosPendientesTotal } = useMemo(() => {
    const ventasMes    = state.paquetes.filter((p) => monthKey(p.fechaCompra) === m);
    const sueltasMes   = state.sesiones.filter((s) => !s.paqueteId && monthKey(s.fecha) === m);
    // Abonos posteriores recibidos este mes (de cualquier paquete)
    const abonosMes    = state.paquetes.flatMap((p) => (p.abonos || []).filter((a) => monthKey(a.fecha) === m).map((a) => ({ ...a, paqueteId: p.id, clienteId: p.clienteId })));
    // CASH BASIS: solo dinero realmente recibido este mes (no valor de ventas a crédito)
    const recibidoEnVenta = pagoInicialVenta;
    const ingresosMes  = ventasMes.reduce((a, p) => a + recibidoEnVenta(p), 0)
                       + sueltasMes.reduce((a, s) => a + (s.valorSesion || 0), 0)
                       + abonosMes.reduce((a, ab) => a + (ab.monto || 0), 0);
    const sesionesMes  = state.sesiones.filter((s) => monthKey(s.fecha) === m);
    const pagadoFisioMes = sesionesMes.reduce((a, s) => a + comisionTotalSesion(s), 0);
    // Excluye los egresos de honorarios auto-generados en Liquidación: esa comisión
    // ya se resta directamente de las sesiones (pagadoFisioMes) — contarla aquí también duplicaría el gasto.
    const gastosRaw    = (state.gastos || []).filter((g) => monthKey(g.fecha) === m && !g.esLiquidacionFisio);
    const gastosMes    = gastosRaw.reduce((a, g) => a + g.monto, 0);

    // Total saldos pendientes (todos los paquetes, en cualquier mes)
    const saldosPendientesTotal = state.paquetes.reduce((a, p) => a + (p.saldoPendiente || 0), 0);

    // desglose egresos por categoría
    const egresosDesglose = CATEGORIAS_GASTO.map((cat) => {
      const total = gastosRaw.filter((g) => g.categoria === cat.id).reduce((a, g) => a + g.monto, 0);
      return { ...cat, total };
    }).filter((c) => c.total > 0).sort((a, b) => b.total - a.total);

    // método de pago del mes (cash flow real, incluye abonos posteriores)
    const efectivoMes = ventasMes.reduce((a, p) => a + (p.montoEfectivo || (p.metodoPago === 'efectivo' ? recibidoEnVenta(p) : 0)), 0)
      + sueltasMes.reduce((a, s) => a + (s.montoEfectivo || (s.metodoPago === 'efectivo' ? s.valorSesion : 0)), 0)
      + abonosMes.reduce((a, ab) => a + (ab.montoEfectivo || (ab.metodoPago === 'efectivo' ? ab.monto : 0)), 0);
    const transferenciaMes = ventasMes.reduce((a, p) => a + (p.montoTransferencia || (p.metodoPago === 'transferencia' ? recibidoEnVenta(p) : 0)), 0)
      + sueltasMes.reduce((a, s) => a + (s.montoTransferencia || (s.metodoPago === 'transferencia' ? s.valorSesion : 0)), 0)
      + abonosMes.reduce((a, ab) => a + (ab.montoTransferencia || (ab.metodoPago === 'transferencia' ? ab.monto : 0)), 0);
    const datafonoMes = ventasMes.reduce((a, p) => a + (p.montoDatafono || (p.metodoPago === 'datafono' ? recibidoEnVenta(p) : 0)), 0)
      + sueltasMes.reduce((a, s) => a + (s.montoDatafono || (s.metodoPago === 'datafono' ? s.valorSesion : 0)), 0)
      + abonosMes.reduce((a, ab) => a + (ab.montoDatafono || (ab.metodoPago === 'datafono' ? ab.monto : 0)), 0);

    const saldosFavor = state.paquetes
      .filter((p) => p.sesionesUsadas < p.sesionesTotales)
      .map((p) => {
        const cliente  = state.clientes.find((c) => c.id === p.clienteId);
        const servicio = findSv(state, p.servicioId);
        const restante = p.sesionesTotales - p.sesionesUsadas;
        return { ...p, clienteNombre: cliente?.nombre, servicioNombre: servicio?.nombre,
                 restante, valorRestante: (p.valorTotal / p.sesionesTotales) * restante };
      }).sort((a, b) => b.valorRestante - a.valorRestante);

    const alertas = [];
    state.paquetes.forEach((p) => {
      const cliente = state.clientes.find((c) => c.id === p.clienteId);
      if (p.fechaInicio > hoy && p.sesionesUsadas === 0)
        alertas.push({ cliente: cliente?.nombre, mensaje: `Inicia ${fmtDate(p.fechaInicio)}`, tipo: 'alerta' });
      const dias = Math.floor((Date.now() - new Date(p.fechaCompra).getTime()) / 86400000);
      if (p.sesionesUsadas === 0 && dias > 30)
        alertas.push({ cliente: cliente?.nombre, mensaje: `Compró hace ${dias} días y no ha iniciado`, tipo: 'alerta' });
      if (p.esBono && p.fechaVencimiento && p.sesionesUsadas < p.sesionesTotales) {
        const dv = Math.ceil((new Date(p.fechaVencimiento + 'T12:00:00') - new Date(hoy + 'T12:00:00')) / 86400000);
        if (dv >= 0 && dv <= 7)
          alertas.push({ cliente: cliente?.nombre, mensaje: `🎁 Bono vence en ${dv === 0 ? 'HOY' : dv + ' días'} (${fmtDate(p.fechaVencimiento)})`, tipo: 'bono' });
      }
    });
    return { ingresosMes, sesionesMes: sesionesMes.length, pagadoFisioMes,
             gastosMes, saldosFavor, alertas, ventasMes,
             egresosDesglose, efectivoMes, transferenciaMes, datafonoMes, abonosMes, saldosPendientesTotal };
  }, [state, m]);

  const utilidadMes = ingresosMes - pagadoFisioMes - gastosMes;

  // ── datos últimos 6 meses para gráfica ──────────────────────────────────
  const ultimos6 = useMemo(() => {
    const meses = [];
    for (let i = 5; i >= 0; i--) {
      const d = new Date(hoy + 'T12:00:00');
      d.setDate(1);
      d.setMonth(d.getMonth() - i);
      const mk = d.toISOString().slice(0, 7);
      const label = d.toLocaleDateString('es-CO', { month: 'short' });
      const ventas = state.paquetes.filter((p) => monthKey(p.fechaCompra) === mk);
      const sueltas = state.sesiones.filter((s) => !s.paqueteId && monthKey(s.fecha) === mk);
      const abonosMk = state.paquetes.flatMap((p) => (p.abonos || []).filter((a) => monthKey(a.fecha) === mk));
      const recibido = pagoInicialVenta;
      const ing = ventas.reduce((a, p) => a + recibido(p), 0)
                + sueltas.reduce((a, s) => a + (s.valorSesion || 0), 0)
                + abonosMk.reduce((a, ab) => a + (ab.monto || 0), 0);
      const gastos = (state.gastos || []).filter((g) => monthKey(g.fecha) === mk && !g.esLiquidacionFisio)
                      .reduce((a, g) => a + g.monto, 0);
      const fisio = state.sesiones.filter((s) => monthKey(s.fecha) === mk)
                      .reduce((a, s) => a + comisionTotalSesion(s), 0);
      meses.push({ mk, label, ing, gastos, fisio, utilidad: ing - gastos - fisio });
    }
    return meses;
  }, [state]);

  const maxIng = Math.max(...ultimos6.map((x) => x.ing), 1);

  // ── Top clientes del mes ──────────────────────────────────────────────────
  const topClientes = useMemo(() => {
    const mapa = {};
    state.paquetes.filter((p) => monthKey(p.fechaCompra) === m).forEach((p) => {
      if (!mapa[p.clienteId]) mapa[p.clienteId] = 0;
      mapa[p.clienteId] += pagoInicialVenta(p);
    });
    return Object.entries(mapa)
      .map(([id, total]) => ({ nombre: state.clientes.find((c) => c.id === id)?.nombre || 'Desconocido', total }))
      .sort((a, b) => b.total - a.total)
      .slice(0, 5);
  }, [state, m]);

  // ── Sesiones por fisio este mes ──────────────────────────────────────────
  const sesionesPorFisio = useMemo(() => {
    const sesMes = state.sesiones.filter((s) => monthKey(s.fecha) === m);
    return state.fisios.map((f) => {
      const sfs = sesionesDelFisio(sesMes, f.id);
      return {
        nombre: f.nombre,
        sesiones: sfs.length,
        comision: sfs.reduce((a, s) => a + comisionDelFisio(s, f.id), 0),
      };
    }).filter((f) => f.sesiones > 0).sort((a, b) => b.sesiones - a.sesiones);
  }, [state, m]);

  const maxSes = Math.max(...sesionesPorFisio.map((x) => x.sesiones), 1);

  return (
    <div className="space-y-6 max-w-6xl">

      {/* ── KPIs ── */}
      <div className="grid grid-cols-2 lg:grid-cols-4 gap-3 lg:gap-4">
        <Kpi label="Ingresos del mes"  value={COP(ingresosMes)}    sub={`${ventasMes.length} ventas · ${abonosMes.length} abonos${saldosPendientesTotal > 0 ? ` · Pendiente cobrar ${COP(saldosPendientesTotal)}` : ''}`}  tone="brand"   />
        <Kpi label="Pagado al fisio"   value={COP(pagadoFisioMes)} sub={`${sesionesMes} sesiones ejecutadas`}       tone="rose"    />
        <Kpi label="Egresos del mes"   value={COP(gastosMes)}      sub="gastos registrados"                         tone="amber"   />
        <Kpi label="Utilidad neta"     value={COP(utilidadMes)}    sub="ingresos − fisio − egresos"                 tone={utilidadMes >= 0 ? 'emerald' : 'rose'} />
      </div>

      {/* ── Gráfica barras 6 meses ── */}
      <Card>
        <div className="flex items-center justify-between mb-4">
          <div>
            <h3 className="font-semibold">Ingresos vs Egresos — últimos 6 meses</h3>
            <p className="text-xs text-stone-400">Ingresos brutos · Egresos totales (gastos + fisio)</p>
          </div>
          <div className="flex items-center gap-4 text-xs text-stone-500">
            <span className="flex items-center gap-1"><span className="w-3 h-3 rounded-sm bg-brand-500 inline-block" /> Ingresos</span>
            <span className="flex items-center gap-1"><span className="w-3 h-3 rounded-sm bg-rose-400 inline-block" /> Egresos</span>
            <span className="flex items-center gap-1"><span className="w-3 h-3 rounded-sm bg-emerald-400 inline-block" /> Utilidad</span>
          </div>
        </div>
        <div className="flex items-end gap-2 h-40">
          {ultimos6.map((mes) => {
            const ingH    = Math.round((mes.ing / maxIng) * 100);
            const egH     = Math.round(((mes.gastos + mes.fisio) / maxIng) * 100);
            const utH     = mes.utilidad >= 0 ? Math.round((mes.utilidad / maxIng) * 100) : 0;
            const isCurrent = mes.mk === m;
            return (
              <div key={mes.mk} className={`flex-1 flex flex-col items-center gap-1 group ${isCurrent ? '' : 'opacity-75'}`}>
                <div className="w-full flex items-end gap-0.5 h-32">
                  <div title={`Ingresos: ${COP(mes.ing)}`}
                    style={{ height: `${Math.max(ingH, 2)}%` }}
                    className="flex-1 bg-brand-500 rounded-t-sm transition-all group-hover:bg-brand-600" />
                  <div title={`Egresos: ${COP(mes.gastos + mes.fisio)}`}
                    style={{ height: `${Math.max(egH, 2)}%` }}
                    className="flex-1 bg-rose-400 rounded-t-sm transition-all group-hover:bg-rose-500" />
                  <div title={`Utilidad: ${COP(mes.utilidad)}`}
                    style={{ height: `${Math.max(utH, mes.utilidad > 0 ? 2 : 0)}%` }}
                    className="flex-1 bg-emerald-400 rounded-t-sm transition-all group-hover:bg-emerald-500" />
                </div>
                <div className={`text-[10px] font-semibold ${isCurrent ? 'text-brand-700' : 'text-stone-400'}`}>{mes.label}</div>
                <div className="hidden group-hover:block absolute bg-stone-900 text-white text-xs rounded-lg px-2 py-1.5 z-10 pointer-events-none shadow-lg min-w-[140px] -translate-y-32">
                  <div className="font-semibold mb-0.5">{new Date(mes.mk + '-15').toLocaleDateString('es-CO', { month: 'long' })}</div>
                  <div>💰 {COP(mes.ing)}</div>
                  <div>📉 {COP(mes.gastos + mes.fisio)}</div>
                  <div className={mes.utilidad >= 0 ? 'text-emerald-300' : 'text-rose-300'}>✨ {COP(mes.utilidad)}</div>
                </div>
              </div>
            );
          })}
        </div>
        {/* fila de totales */}
        <div className="mt-3 pt-3 border-t border-stone-100 grid grid-cols-3 gap-3 text-center text-xs text-stone-500">
          <div><div className="font-bold text-brand-700 text-sm">{COP(ultimos6.reduce((a,x)=>a+x.ing,0))}</div>Total ingresos</div>
          <div><div className="font-bold text-rose-600 text-sm">{COP(ultimos6.reduce((a,x)=>a+x.gastos+x.fisio,0))}</div>Total egresos</div>
          <div><div className={`font-bold text-sm ${ultimos6.reduce((a,x)=>a+x.utilidad,0) >= 0 ? 'text-emerald-700' : 'text-rose-700'}`}>{COP(ultimos6.reduce((a,x)=>a+x.utilidad,0))}</div>Utilidad 6m</div>
        </div>
      </Card>

      {/* ── Método de pago + Egresos por categoría ── */}
      <div className="grid lg:grid-cols-2 gap-4">
        {/* Método de pago */}
        <Card>
          <h3 className="font-semibold mb-3">Método de pago este mes</h3>
          <div className="space-y-3">
            {[
              { label: '💵 Efectivo',       val: efectivoMes,       color: 'bg-green-500'  },
              { label: '📲 Transferencia',  val: transferenciaMes,  color: 'bg-blue-500'   },
              { label: '💳 Datáfono',       val: datafonoMes,       color: 'bg-indigo-500' },
            ].map(({ label, val, color }) => {
              const pct = ingresosMes > 0 ? Math.round((val / ingresosMes) * 100) : 0;
              return (
                <div key={label}>
                  <div className="flex justify-between text-sm mb-1">
                    <span className="font-medium">{label}</span>
                    <span className="tabular-nums text-stone-600">{COP(val)} <span className="text-stone-400 text-xs">({pct}%)</span></span>
                  </div>
                  <div className="h-2.5 bg-stone-100 rounded-full overflow-hidden">
                    <div className={`h-full ${color} rounded-full transition-all`} style={{ width: `${pct}%` }} />
                  </div>
                </div>
              );
            })}
            <div className="mt-3 pt-3 border-t border-stone-100 flex justify-between text-sm">
              <span className="text-stone-500">Total ingresos</span>
              <span className="font-bold tabular-nums">{COP(ingresosMes)}</span>
            </div>
          </div>
        </Card>

        {/* Egresos por categoría */}
        <Card>
          <div className="flex items-center justify-between mb-3">
            <h3 className="font-semibold">Egresos por categoría</h3>
            <button onClick={() => setTab('egresos')} className="text-xs text-brand-700 hover:underline flex items-center gap-1">
              Ver egresos <Icon name="chevron" className="w-3 h-3" />
            </button>
          </div>
          {egresosDesglose.length === 0 ? <Empty text="Sin egresos este mes." /> : (
            <div className="space-y-2">
              {egresosDesglose.slice(0, 6).map((cat) => {
                const pct = gastosMes > 0 ? Math.round((cat.total / gastosMes) * 100) : 0;
                return (
                  <div key={cat.id}>
                    <div className="flex justify-between text-sm mb-1">
                      <span className="font-medium">{cat.icon} {cat.nombre}</span>
                      <span className="tabular-nums text-stone-600">{COP(cat.total)} <span className="text-stone-400 text-xs">({pct}%)</span></span>
                    </div>
                    <div className="h-2 bg-stone-100 rounded-full overflow-hidden">
                      <div className="h-full bg-amber-400 rounded-full transition-all" style={{ width: `${pct}%` }} />
                    </div>
                  </div>
                );
              })}
              <div className="pt-2 border-t border-stone-100 flex justify-between text-sm font-semibold">
                <span>Total egresos</span><span className="tabular-nums text-amber-700">{COP(gastosMes)}</span>
              </div>
            </div>
          )}
        </Card>
      </div>

      {/* ── Inversión en cortesías / publicidad ── */}
      {(() => {
        const cortesiasMes = (state.sesiones || []).filter(
          (x) => x.esCortesia && monthKey(x.fecha) === m,
        );
        if (cortesiasMes.length === 0) return null;

        const totalMes = cortesiasMes.reduce((a, x) => a + comisionTotalSesion(x), 0);

        // Agrupado por persona: es lo que Dani pidió — saber cuánto se va
        // por cada "embajador", no solo el total.
        const porPersona = {};
        cortesiasMes.forEach((x) => {
          const nom = state.clientes.find((c) => c.id === x.clienteId)?.nombre || 'Sin nombre';
          if (!porPersona[nom]) porPersona[nom] = { costo: 0, veces: 0 };
          porPersona[nom].costo += comisionTotalSesion(x);
          porPersona[nom].veces += 1;
        });
        const lista = Object.entries(porPersona).sort((a, b) => b[1].costo - a[1].costo);

        return (
          <Card>
            <div className="flex items-start justify-between mb-3">
              <div>
                <h3 className="font-semibold">🎁 Inversión en publicidad</h3>
                <p className="text-xs text-stone-400">
                  Sesiones de cortesía · lo que costaron en pago al fisioterapeuta
                </p>
              </div>
              <div className="text-right shrink-0 ml-3">
                <div className="text-xl font-bold text-amber-700">{COP(totalMes)}</div>
                <div className="text-xs text-stone-400">
                  {cortesiasMes.length} {cortesiasMes.length === 1 ? 'sesión' : 'sesiones'} este mes
                </div>
              </div>
            </div>
            <ul className="space-y-1.5">
              {lista.slice(0, 10).map(([nombre, d]) => (
                <li key={nombre} className="flex items-center justify-between text-sm border-b border-stone-100 pb-1.5 last:border-0">
                  <span className="truncate">
                    {nombre}
                    {d.veces > 1 && <span className="text-xs text-stone-400 ml-1.5">×{d.veces}</span>}
                  </span>
                  <span className="font-semibold text-amber-700 shrink-0 ml-2">{COP(d.costo)}</span>
                </li>
              ))}
            </ul>
            {lista.length > 10 && (
              <p className="text-xs text-stone-400 mt-2">…y {lista.length - 10} personas más.</p>
            )}
          </Card>
        );
      })()}

      {/* ── P&L simple ── */}
      <Card>
        <h3 className="font-semibold mb-3">Estado de resultados — {new Date(m + '-15').toLocaleDateString('es-CO', { month: 'long', year: 'numeric' })}</h3>
        <div className="space-y-2 text-sm">
          {[
            { label: '(+) Ingresos brutos',          val: ingresosMes,    color: 'text-emerald-700', sign: '+' },
            { label: '(−) Comisiones fisioterapeutas', val: pagadoFisioMes, color: 'text-rose-600',    sign: '−' },
            { label: '(−) Egresos operativos',         val: gastosMes,      color: 'text-amber-700',   sign: '−' },
          ].map(({ label, val, color, sign }) => (
            <div key={label} className="flex items-center justify-between py-1.5 border-b border-stone-100">
              <span className="text-stone-600">{label}</span>
              <span className={`font-semibold tabular-nums ${color}`}>{sign} {COP(val)}</span>
            </div>
          ))}
          <div className="flex items-center justify-between py-2 bg-stone-50 rounded-xl px-3 -mx-3 mt-2">
            <span className="font-bold text-stone-800">Utilidad neta del mes</span>
            <span className={`text-xl font-bold tabular-nums ${utilidadMes >= 0 ? 'text-emerald-700' : 'text-rose-700'}`}>{COP(utilidadMes)}</span>
          </div>
        </div>
      </Card>

      {/* ── Sesiones por fisio + Top clientes ── */}
      <div className="grid lg:grid-cols-2 gap-4">
        <Card>
          <h3 className="font-semibold mb-3">Sesiones por fisioterapeuta este mes</h3>
          {sesionesPorFisio.length === 0 ? <Empty text="Sin sesiones este mes." /> : (
            <div className="space-y-3">
              {sesionesPorFisio.map((f) => {
                const pct = Math.round((f.sesiones / maxSes) * 100);
                return (
                  <div key={f.nombre}>
                    <div className="flex justify-between text-sm mb-1">
                      <span className="font-medium">{f.nombre}</span>
                      <span className="text-stone-500">{f.sesiones} ses. · <span className="text-rose-600 font-semibold">{COP(f.comision)}</span></span>
                    </div>
                    <div className="h-2.5 bg-stone-100 rounded-full overflow-hidden">
                      <div className="h-full bg-brand-400 rounded-full transition-all" style={{ width: `${pct}%` }} />
                    </div>
                  </div>
                );
              })}
            </div>
          )}
        </Card>

        <Card>
          <div className="flex items-center justify-between mb-3">
            <div><h3 className="font-semibold">Saldos a favor</h3><p className="text-xs text-stone-400">Sesiones aún no ejecutadas</p></div>
            <button onClick={() => setTab('clientes')} className="text-xs text-brand-700 hover:underline flex items-center gap-1">Ver todos <Icon name="chevron" className="w-3 h-3" /></button>
          </div>
          {saldosFavor.length === 0 ? <Empty text="No hay saldos pendientes." /> : (
            <ul className="divide-y divide-stone-100">
              {saldosFavor.slice(0, 5).map((p) => (
                <li key={p.id} className="py-2 flex items-center justify-between gap-3">
                  <div className="min-w-0">
                    <div className="font-medium text-sm truncate">{p.clienteNombre}</div>
                    <div className="text-xs text-stone-400 truncate">{p.servicioNombre}</div>
                  </div>
                  <div className="text-right shrink-0">
                    <div className="font-semibold text-sm text-brand-700">{p.restante} ses.</div>
                    <div className="text-xs text-stone-400">{COP(p.valorRestante)}</div>
                  </div>
                </li>
              ))}
            </ul>
          )}
          <div className="mt-3 pt-3 border-t border-stone-100 flex justify-between text-sm">
            <span className="text-stone-500">Total ({saldosFavor.length} paquetes)</span>
            <span className="font-semibold">{COP(saldosFavor.reduce((a, p) => a + p.valorRestante, 0))}</span>
          </div>
        </Card>
      </div>

      {/* ── Top clientes ── */}
      {topClientes.length > 0 && (
        <Card>
          <h3 className="font-semibold mb-3">Top clientes del mes</h3>
          <div className="space-y-2">
            {topClientes.map((c, i) => {
              const pct = Math.round((c.total / topClientes[0].total) * 100);
              return (
                <div key={c.nombre} className="flex items-center gap-3">
                  <div className="w-5 h-5 rounded-full bg-stone-100 text-stone-500 text-[10px] font-bold flex items-center justify-center shrink-0">{i + 1}</div>
                  <div className="flex-1 min-w-0">
                    <div className="flex justify-between text-sm mb-0.5">
                      <span className="font-medium truncate">{c.nombre}</span>
                      <span className="tabular-nums text-brand-700 font-semibold shrink-0">{COP(c.total)}</span>
                    </div>
                    <div className="h-1.5 bg-stone-100 rounded-full overflow-hidden">
                      <div className="h-full bg-brand-400 rounded-full" style={{ width: `${pct}%` }} />
                    </div>
                  </div>
                </div>
              );
            })}
          </div>
        </Card>
      )}

      {/* ── Alertas ── */}
      {alertas.length > 0 && (
        <Card>
          <div className="flex items-center justify-between mb-3">
            <div><h3 className="font-semibold">Alertas</h3><p className="text-xs text-stone-500">Clientes que requieren seguimiento</p></div>
            <span className="text-xs px-2 py-0.5 rounded-full bg-amber-100 text-amber-800 font-medium">{alertas.length}</span>
          </div>
          <ul className="space-y-2">
            {alertas.slice(0, 6).map((a, i) => (
              <li key={i} className={`flex items-start gap-2.5 p-2.5 rounded-lg ${a.tipo === 'bono' ? 'bg-red-50' : 'bg-amber-50/60'}`}>
                {a.tipo === 'bono'
                  ? <span className="text-base leading-none mt-0.5 shrink-0">🎁</span>
                  : <Icon name="alert" className="w-4 h-4 text-amber-600 mt-0.5 shrink-0" />}
                <div className="text-sm min-w-0">
                  <div className="font-medium">{a.cliente}</div>
                  <div className={`text-xs ${a.tipo === 'bono' ? 'text-red-700 font-semibold' : 'text-stone-600'}`}>{a.mensaje}</div>
                </div>
              </li>
            ))}
          </ul>
        </Card>
      )}

      {/* ── Acciones rápidas ── */}
      <Card>
        <h3 className="font-semibold mb-3">Acciones rápidas</h3>
        <div className="grid grid-cols-2 lg:grid-cols-4 gap-2">
          <QuickAction icon="shopping-cart"  label="Registrar venta"    onClick={() => setTab('venta')}     />
          <QuickAction icon="activity"       label="Registrar sesión"   onClick={() => setTab('sesion')}    />
          <QuickAction icon="trending-down"  label="Registrar egreso"   onClick={() => setTab('egresos')}   />
          <QuickAction icon="wallet"         label="Balance del día"    onClick={() => setTab('balance')}   />
        </div>
      </Card>
    </div>
  );
}

function Kpi({ label, value, sub, tone = 'brand' }) {
  const tones = { brand: 'bg-brand-50 text-brand-700', amber: 'bg-amber-50 text-amber-700', rose: 'bg-rose-50 text-rose-700', emerald: 'bg-emerald-50 text-emerald-700' };
  return (
    <div className="bg-white rounded-2xl p-4 border border-stone-200">
      <div className={`inline-block px-2 py-0.5 rounded-md text-[10px] font-semibold uppercase tracking-wide ${tones[tone]}`}>{label}</div>
      <div className="mt-2 text-2xl font-bold tracking-tight">{value}</div>
      <div className="text-xs text-stone-500 mt-0.5">{sub}</div>
    </div>
  );
}
function QuickAction({ icon, label, onClick }) {
  return (
    <button onClick={onClick} className="flex flex-col items-start gap-2 p-3 rounded-xl border border-stone-200 hover:border-brand-300 hover:bg-brand-50/40 transition text-left">
      <div className="w-9 h-9 rounded-lg bg-brand-50 text-brand-700 flex items-center justify-center"><Icon name={icon} /></div>
      <div className="text-sm font-medium">{label}</div>
    </button>
  );
}

// ---------- PagoSelector (reutilizable) ----------
// Recibe: valor (total), metodoPago, montoEfectivo, montoTransferencia, montoDatafono
// + onChange. En modo mixto los tres campos son editables y se muestra si cuadra.
function PagoSelector({ valor, metodoPago, montoEfectivo, montoTransferencia, montoDatafono = 0, onChange }) {
  // onChange({ metodoPago, montoEfectivo, montoTransferencia, montoDatafono })
  const set = (mp, ef, tr, da) => onChange({ metodoPago: mp, montoEfectivo: ef, montoTransferencia: tr, montoDatafono: da });

  const handleMetodo = (mp) => {
    if (mp === 'efectivo')       set('efectivo',       valor, 0, 0);
    if (mp === 'transferencia')  set('transferencia',  0, valor, 0);
    if (mp === 'datafono')       set('datafono',       0, 0, valor);
    if (mp === 'mixto')          set('mixto',          valor, 0, 0); // arranca todo en efectivo, usuario ajusta
  };

  // En mixto, cada campo es libre; el resto no se auto-rellena (evita confusión con 3 campos)
  const handleField = (campo, v) => {
    const val = Math.max(0, Number(v) || 0);
    const next = { ef: montoEfectivo, tr: montoTransferencia, da: montoDatafono, [campo]: val };
    set('mixto', next.ef, next.tr, next.da);
  };

  const OPTS = [
    { id: 'efectivo',      label: '💵 Efectivo'      },
    { id: 'transferencia', label: '📲 Transfer.'     },
    { id: 'datafono',      label: '💳 Datáfono'      },
    { id: 'mixto',         label: '🔀 Mixto'          },
  ];

  const diferencia = valor - montoEfectivo - montoTransferencia - montoDatafono;
  const ok = Math.abs(diferencia) < 1;

  return (
    <div>
      <label className="block text-sm font-medium text-stone-700 mb-2">Método de pago</label>
      <div className="grid grid-cols-4 rounded-lg p-0.5 bg-stone-100 mb-3 text-xs sm:text-sm gap-0.5">
        {OPTS.map((o) => (
          <button key={o.id} type="button" onClick={() => handleMetodo(o.id)}
            className={`py-1.5 rounded-md font-medium transition ${metodoPago === o.id ? 'bg-white shadow-sm text-stone-900' : 'text-stone-500 hover:text-stone-700'}`}>
            {o.label}
          </button>
        ))}
      </div>

      {metodoPago === 'efectivo' && (
        <div className="bg-green-50 border border-green-200 rounded-xl px-4 py-2.5 text-sm text-green-800 font-medium">
          💵 Pago completo en efectivo: <strong>{COP(valor)}</strong>
        </div>
      )}

      {metodoPago === 'transferencia' && (
        <div className="bg-blue-50 border border-blue-200 rounded-xl px-4 py-2.5 text-sm text-blue-800 font-medium">
          📲 Pago completo por transferencia: <strong>{COP(valor)}</strong>
        </div>
      )}

      {metodoPago === 'datafono' && (
        <div className="bg-indigo-50 border border-indigo-200 rounded-xl px-4 py-2.5 text-sm text-indigo-800 font-medium">
          💳 Pago completo con datáfono: <strong>{COP(valor)}</strong>
        </div>
      )}

      {metodoPago === 'mixto' && (
        <div className="bg-violet-50 border border-violet-200 rounded-xl p-3 space-y-2">
          <p className="text-xs font-semibold text-violet-700 uppercase tracking-wide">Desglose del pago mixto</p>
          <div className="grid grid-cols-3 gap-2">
            <div>
              <label className="block text-xs text-stone-500 mb-1">💵 Efectivo</label>
              <input type="number" value={montoEfectivo} onChange={(e) => handleField('ef', e.target.value)}
                min="0" max={valor} step="1000"
                className="w-full border border-stone-200 rounded-lg px-2 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-violet-400 bg-white" />
            </div>
            <div>
              <label className="block text-xs text-stone-500 mb-1">📲 Transfer.</label>
              <input type="number" value={montoTransferencia} onChange={(e) => handleField('tr', e.target.value)}
                min="0" max={valor} step="1000"
                className="w-full border border-stone-200 rounded-lg px-2 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-violet-400 bg-white" />
            </div>
            <div>
              <label className="block text-xs text-stone-500 mb-1">💳 Datáfono</label>
              <input type="number" value={montoDatafono} onChange={(e) => handleField('da', e.target.value)}
                min="0" max={valor} step="1000"
                className="w-full border border-stone-200 rounded-lg px-2 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-violet-400 bg-white" />
            </div>
          </div>
          <div className={`text-xs px-3 py-1.5 rounded-lg font-semibold flex items-center justify-between ${ok ? 'bg-emerald-100 text-emerald-800' : 'bg-red-100 text-red-800'}`}>
            <span>Total: {COP(montoEfectivo + montoTransferencia + montoDatafono)}</span>
            {ok ? <span>✓ Cuadra</span> : <span>{diferencia > 0 ? `⚠ Faltan ${COP(diferencia)}` : `⚠ Sobran ${COP(Math.abs(diferencia))}`}</span>}
          </div>
        </div>
      )}
    </div>
  );
}

// ---------- Modal: Registrar Abono ----------
function AbonoModal({ paquete, cliente, servicio, onClose, onAddAbono }) {
  const saldo = Number(paquete?.saldoPendiente || 0);
  const [monto,             setMonto]             = useState(saldo);
  const [fecha,             setFecha]             = useState(todayISO());
  const [metodoPago,        setMetodoPago]        = useState('efectivo');
  const [montoEfectivo,     setMontoEfectivo]     = useState(saldo);
  const [montoTransferencia,setMontoTransferencia] = useState(0);
  const [montoDatafono,     setMontoDatafono]     = useState(0);
  const [nota,              setNota]              = useState('');
  const [numeroFactura,     setNumeroFactura]     = useState('');
  const [saving,            setSaving]            = useState(false);

  // Cuando cambia el monto, recalcula los desgloses para métodos simples
  useEffect(() => {
    if (metodoPago === 'efectivo')      { setMontoEfectivo(Number(monto) || 0); setMontoTransferencia(0); setMontoDatafono(0); }
    if (metodoPago === 'transferencia') { setMontoEfectivo(0); setMontoTransferencia(Number(monto) || 0); setMontoDatafono(0); }
    if (metodoPago === 'datafono')      { setMontoEfectivo(0); setMontoTransferencia(0); setMontoDatafono(Number(monto) || 0); }
  }, [monto, metodoPago]);

  const handlePagoChange = ({ metodoPago: mp, montoEfectivo: ef, montoTransferencia: tr, montoDatafono: da }) => {
    setMetodoPago(mp); setMontoEfectivo(ef); setMontoTransferencia(tr); setMontoDatafono(da || 0);
  };

  const submit = async (e) => {
    e.preventDefault();
    const m = Number(monto);
    if (!m || m <= 0)         { alert('Monto inválido'); return; }
    if (m > saldo)            { alert(`El monto no puede ser mayor al saldo pendiente (${COP(saldo)})`); return; }
    if (!fecha)               { alert('Selecciona fecha'); return; }
    const total = (montoEfectivo || 0) + (montoTransferencia || 0) + (montoDatafono || 0);
    if (metodoPago === 'mixto' && Math.abs(total - m) > 1) {
      alert(`El desglose del pago mixto no cuadra. Total: ${COP(total)} vs ${COP(m)}`);
      return;
    }
    if (!confirmarFechaRazonable(fecha, 'la fecha del abono')) return;
    setSaving(true);
    try {
      await onAddAbono(paquete.id, {
        fecha,
        monto: m,
        metodoPago,
        montoEfectivo: metodoPago === 'efectivo' ? m : (metodoPago === 'mixto' ? montoEfectivo : 0),
        montoTransferencia: metodoPago === 'transferencia' ? m : (metodoPago === 'mixto' ? montoTransferencia : 0),
        montoDatafono: metodoPago === 'datafono' ? m : (metodoPago === 'mixto' ? montoDatafono : 0),
        nota: nota.trim(),
        numeroFactura: numeroFactura.trim(),
      });
      onClose();
    } catch (err) {
      alert('Error registrando el abono: ' + err.message);
    } finally {
      setSaving(false);
    }
  };

  const nuevoSaldo = Math.max(0, saldo - (Number(monto) || 0));

  return (
    <div className="fixed inset-0 bg-black/40 z-50 flex items-end sm:items-center justify-center p-3 fade-in" onClick={onClose}>
      <div className="w-full sm:max-w-lg bg-white rounded-2xl shadow-2xl max-h-[92vh] overflow-y-auto" onClick={(e) => e.stopPropagation()}>
        <div className="px-5 py-4 border-b border-stone-200 flex items-center justify-between sticky top-0 bg-white">
          <div>
            <h3 className="font-bold text-base">💵 Registrar abono</h3>
            <p className="text-xs text-stone-500">{cliente?.nombre} · {servicio?.nombre}</p>
          </div>
          <button onClick={onClose} className="w-8 h-8 rounded-lg hover:bg-stone-100 text-stone-500 text-lg">✕</button>
        </div>

        <form onSubmit={submit} className="p-5 space-y-4">
          {/* Resumen */}
          <div className="bg-rose-50 border border-rose-200 rounded-xl p-3 text-sm space-y-1">
            <div className="flex justify-between text-stone-600"><span>Valor total:</span><strong>{COP(paquete?.valorTotal || 0)}</strong></div>
            <div className="flex justify-between text-stone-600"><span>Abonado hasta ahora:</span><strong>{COP(paquete?.abonoInicial || 0)}</strong></div>
            <div className="flex justify-between text-rose-700 border-t border-rose-200 pt-1 mt-1"><span>Saldo pendiente:</span><strong className="text-base">{COP(saldo)}</strong></div>
          </div>

          {/* Monto */}
          <div>
            <Label>Monto del abono</Label>
            <input type="number" value={monto} onChange={(e) => setMonto(e.target.value)}
              min="1" max={saldo} step="1" required autoFocus
              className="w-full px-3 py-2.5 rounded-lg border border-stone-200 bg-white text-base font-semibold focus:outline-none focus:border-brand-500 focus:ring-2 focus:ring-brand-100" />
            <div className="flex gap-1 mt-1.5 flex-wrap">
              {[saldo / 4, saldo / 2, saldo].filter((v) => v >= 1000).map((v, i) => (
                <button key={i} type="button" onClick={() => setMonto(Math.round(v))}
                  className="text-[10px] px-2 py-0.5 rounded-full bg-stone-100 text-stone-600 hover:bg-stone-200 font-medium">
                  {COP(Math.round(v))}
                </button>
              ))}
            </div>
          </div>

          {/* Fecha */}
          <div>
            <Label>Fecha del abono</Label>
            <input type="date" value={fecha} onChange={(e) => setFecha(e.target.value)} min={FECHA_MIN} max={todayISO()} required
              className="w-full px-3 py-2.5 rounded-lg border border-stone-200 bg-white text-sm focus:outline-none focus:border-brand-500 focus:ring-2 focus:ring-brand-100" />
          </div>

          {/* Método de pago */}
          <PagoSelector
            valor={Number(monto) || 0}
            metodoPago={metodoPago}
            montoEfectivo={montoEfectivo}
            montoTransferencia={montoTransferencia}
            montoDatafono={montoDatafono}
            onChange={handlePagoChange}
          />

          {/* Nota */}
          <div>
            <Label>Nota (opcional)</Label>
            <input type="text" value={nota} onChange={(e) => setNota(e.target.value)}
              placeholder="Ej: pagó en consultorio"
              className="w-full px-3 py-2.5 rounded-lg border border-stone-200 bg-white text-sm focus:outline-none focus:border-brand-500 focus:ring-2 focus:ring-brand-100" />
          </div>

          {/* Número de factura */}
          <div>
            <Label>N° de factura (opcional)</Label>
            <input type="text" value={numeroFactura} onChange={(e) => setNumeroFactura(e.target.value)}
              placeholder="Ej: FE-0042"
              className="w-full px-3 py-2.5 rounded-lg border border-stone-200 bg-white text-sm focus:outline-none focus:border-brand-500 focus:ring-2 focus:ring-brand-100" />
          </div>

          {/* Preview saldo */}
          <div className="bg-emerald-50 border border-emerald-200 rounded-xl p-3 text-sm">
            <div className="flex justify-between text-emerald-800"><span>Nuevo saldo pendiente:</span><strong className="text-base">{COP(nuevoSaldo)}</strong></div>
            {nuevoSaldo === 0 && <div className="text-xs text-emerald-700 mt-1 font-semibold">✅ Quedará completamente pago</div>}
          </div>

          {/* Botones */}
          <div className="flex gap-2 pt-2">
            <button type="button" onClick={onClose} className="flex-1 px-4 py-2.5 rounded-lg border border-stone-300 bg-white text-sm font-medium hover:bg-stone-50">Cancelar</button>
            <button type="submit" disabled={saving || !monto || Number(monto) <= 0}
              className="flex-1 px-4 py-2.5 rounded-lg bg-emerald-600 text-white text-sm font-semibold hover:bg-emerald-700 disabled:opacity-50 disabled:cursor-not-allowed">
              {saving ? 'Guardando…' : '💵 Registrar abono'}
            </button>
          </div>
        </form>
      </div>
    </div>
  );
}

// ---------- Nueva venta ----------
// Helper: sumar N días a una fecha ISO
const addDays = (iso, n) => {
  if (!iso) return '';
  const d = new Date(iso + 'T12:00:00');
  if (isNaN(d.getTime())) return '';
  d.setDate(d.getDate() + n);
  return d.toISOString().slice(0, 10);
};

// ---------- Historial de ventas ----------
// ---------- Auditoría de ventas ----------
// Índices que dependen de TODAS las ventas (repetidos, duplicados). Se calculan
// una sola vez y se reutilizan para marcar cada fila.
const construirIndicesAuditoria = (paquetes = []) => {
  const porFactura = {};
  paquetes.filter((p) => (p.numeroFactura || '').trim()).forEach((p) => {
    const f = String(p.numeroFactura).trim();
    (porFactura[f] = porFactura[f] || []).push(p);
  });
  // Un bono con varios beneficiarios comparte factura a propósito: no es hallazgo.
  const facturasRepetidas = new Set(
    Object.entries(porFactura)
      .filter(([, a]) => a.length > 1 &&
        !(a.every((x) => x.esBono) && a[0].compradorId && new Set(a.map((x) => x.compradorId)).size === 1))
      .map(([f]) => f)
  );

  // Misma persona + mismo servicio + misma fecha + mismo valor = posible doble registro
  const claves = {};
  paquetes.forEach((p) => {
    const k = [p.clienteId, p.servicioId, p.fechaCompra, p.valorTotal].join('|');
    (claves[k] = claves[k] || []).push(p.id);
  });
  const idsDuplicados = new Set(Object.values(claves).filter((a) => a.length > 1).flat());

  return { facturasRepetidas, idsDuplicados };
};

// Devuelve la lista de problemas de UNA venta, con explicación en español.
const anomaliasDeVenta = (p, idx) => {
  const out = [];
  const push = (etiqueta, color, explicacion) => out.push({ etiqueta, color, explicacion });
  const anioActual = new Date().getFullYear();
  const anio = Number(String(p.fechaCompra || '').slice(0, 4));

  if (!anio || anio < 2000 || anio > anioActual + 1)
    push('Fecha imposible', 'rose', `La fecha de compra es ${p.fechaCompra}. Se digitó mal el año.`);

  if (p.fechaInicio && p.fechaCompra && p.fechaInicio < p.fechaCompra)
    push('Inicia antes de comprar', 'stone', `Compró el ${fmtDate(p.fechaCompra)} pero la fecha de inicio es anterior (${fmtDate(p.fechaInicio)}).`);

  const f = String(p.numeroFactura || '').trim();
  if (f && idx.facturasRepetidas.has(f))
    push('Factura repetida', 'amber', `La factura ${f} está usada en más de una venta. Si fue una compra grupal del mismo día es normal; si son días distintos, se reutilizó el consecutivo.`);

  if (!f)
    push('Sin factura', 'stone', 'Esta venta no tiene número de factura registrado.');

  if (idx.idsDuplicados.has(p.id))
    push('Posible venta duplicada', 'rose', 'Existe otra venta idéntica (mismo cliente, servicio, fecha y valor). Verifica si el cliente pagó una o dos veces.');

  if ((p.sesionesUsadas || 0) > (p.sesionesTotales || 0))
    push('Sesiones exceden lo comprado', 'amber', `Se registraron ${p.sesionesUsadas} sesiones pero solo se compraron ${p.sesionesTotales}. Probablemente el número de sesiones quedó mal al registrar la venta.`);

  if ((p.saldoPendiente || 0) < 0)
    push('Saldo negativo', 'rose', `El saldo pendiente es ${COP(p.saldoPendiente)}.`);

  if ((p.abonoInicial || 0) > (p.valorTotal || 0) + 1)
    push('Abonado más que el valor', 'rose', `Abonado ${COP(p.abonoInicial)} sobre una venta de ${COP(p.valorTotal)}.`);

  // El desglose de pago solo es comparable si no hubo abonos posteriores
  if ((p.abonos || []).length === 0) {
    const suma = (p.montoEfectivo || 0) + (p.montoTransferencia || 0) + (p.montoDatafono || 0);
    const recibido = (p.abonoInicial || 0) > 0 ? p.abonoInicial
      : ((p.saldoPendiente || 0) > 0 ? 0 : (p.valorTotal || 0));
    if (Math.abs(suma - recibido) > 1)
      push('Pago no cuadra', 'rose', `Registra ${COP(suma)} en métodos de pago pero el dinero realmente recibido fue ${COP(recibido)}. Diferencia de ${COP(suma - recibido)} — infla los ingresos.`);
  }

  return out;
};

const COLOR_HALLAZGO = {
  rose:  'bg-rose-100 text-rose-700 border-rose-200',
  amber: 'bg-amber-100 text-amber-800 border-amber-200',
  stone: 'bg-stone-100 text-stone-600 border-stone-200',
};

// ---------- Modal: Editar venta (con historial de cambios) ----------
function EditarVentaModal({ paquete: paq, state, onClose, onUpdateVenta }) {
  const cliente = state.clientes.find((c) => c.id === paq.clienteId);
  const svs = getSvs(state);

  const [fechaCompra,   setFechaCompra]   = useState(paq.fechaCompra);
  const [fechaInicio,   setFechaInicio]   = useState(paq.fechaInicio);
  const [servicioId,    setServicioId]    = useState(paq.servicioId);
  const [valorTotal,    setValorTotal]    = useState(paq.valorTotal);
  const [sesionesTotales, setSesionesTotales] = useState(paq.sesionesTotales);
  const [numeroFactura, setNumeroFactura] = useState(paq.numeroFactura || '');
  const [metodoPago,    setMetodoPago]    = useState(paq.metodoPago || 'efectivo');
  const [montoEfectivo, setMontoEfectivo] = useState(paq.montoEfectivo || 0);
  const [montoTransferencia, setMontoTransferencia] = useState(paq.montoTransferencia || 0);
  const [montoDatafono, setMontoDatafono] = useState(paq.montoDatafono || 0);
  const [saving,        setSaving]        = useState(false);

  const handlePagoChange = ({ metodoPago: mp, montoEfectivo: ef, montoTransferencia: tr, montoDatafono: da }) => {
    setMetodoPago(mp); setMontoEfectivo(ef); setMontoTransferencia(tr); setMontoDatafono(da || 0);
  };

  // Recalcular saldo cuando cambia el valor total
  const nuevoSaldo = Math.max(0, Number(valorTotal) - (paq.abonoInicial || 0));

  // Dinero REALMENTE recibido por esta venta. El desglose de pago debe cuadrar
  // contra esto — NO contra el valor total. Antes se pasaba valorTotal al selector,
  // así que editar una venta a crédito registraba el total como cobrado e inflaba
  // los ingresos aunque el cliente todavía debiera.
  const esCredito     = Number(paq.abonoInicial || 0) === 0 && Number(paq.saldoPendiente || 0) > 0;
  const montoRecibido = Number(paq.abonoInicial || 0) > 0
    ? Number(paq.abonoInicial || 0)
    : (esCredito ? 0 : Number(valorTotal) || 0);

  // Si es venta de contado y cambia el valor, el desglose sigue al nuevo valor.
  useEffect(() => {
    if (esCredito || Number(paq.abonoInicial || 0) > 0) return;
    const v = Number(valorTotal) || 0;
    if (metodoPago === 'efectivo')      { setMontoEfectivo(v); setMontoTransferencia(0); setMontoDatafono(0); }
    else if (metodoPago === 'transferencia') { setMontoEfectivo(0); setMontoTransferencia(v); setMontoDatafono(0); }
    else if (metodoPago === 'datafono') { setMontoEfectivo(0); setMontoTransferencia(0); setMontoDatafono(v); }
  }, [valorTotal]);

  // Detectar cambios para habilitar el botón
  const huboCambios =
    fechaCompra !== paq.fechaCompra ||
    fechaInicio !== paq.fechaInicio ||
    servicioId  !== paq.servicioId  ||
    Number(valorTotal) !== Number(paq.valorTotal) ||
    Number(sesionesTotales) !== Number(paq.sesionesTotales) ||
    numeroFactura !== (paq.numeroFactura || '') ||
    metodoPago !== paq.metodoPago;

  const submit = async (e) => {
    e.preventDefault();
    if (!huboCambios) { onClose(); return; }
    if (Number(sesionesTotales) < (paq.sesionesUsadas || 0)) {
      alert(`No puedes reducir las sesiones a ${sesionesTotales}: ya se han usado ${paq.sesionesUsadas}.`);
      return;
    }
    if (!confirmarFechaRazonable(fechaCompra, 'la fecha de compra')) return;
    if (!confirmarFechaRazonable(fechaInicio, 'la fecha de inicio')) return;
    setSaving(true);
    try {
      // Blindaje contable: el desglose de pago NUNCA puede superar el dinero
      // realmente recibido. En ventas a crédito debe quedar en cero — si no,
      // el balance contaría como ingreso plata que el cliente todavía debe.
      let ef = Number(montoEfectivo) || 0;
      let tr = Number(montoTransferencia) || 0;
      let da = Number(montoDatafono) || 0;
      if (montoRecibido <= 0) {
        ef = tr = da = 0;
      } else if (ef + tr + da > montoRecibido + 1) {
        // Desglose inconsistente: lo reducimos al método principal por el monto correcto.
        ef = metodoPago === 'efectivo'      ? montoRecibido : 0;
        tr = metodoPago === 'transferencia' ? montoRecibido : 0;
        da = metodoPago === 'datafono'      ? montoRecibido : 0;
        if (metodoPago === 'mixto') { ef = montoRecibido; tr = 0; da = 0; }
      }

      const nuevoPaquete = {
        ...paq,
        fechaCompra,
        fechaInicio,
        servicioId,
        valorTotal: Number(valorTotal),
        sesionesTotales: Number(sesionesTotales),
        numeroFactura: numeroFactura.trim(),
        metodoPago,
        montoEfectivo: ef,
        montoTransferencia: tr,
        montoDatafono: da,
        saldoPendiente: nuevoSaldo,
      };
      await onUpdateVenta(paq.id, nuevoPaquete, paq);
      onClose();
    } catch (err) {
      alert('Error guardando los cambios: ' + err.message);
    } finally {
      setSaving(false);
    }
  };

  return (
    <div className="fixed inset-0 bg-black/40 z-50 flex items-end sm:items-center justify-center p-3 fade-in" onClick={onClose}>
      <div className="w-full sm:max-w-lg bg-white rounded-2xl shadow-2xl max-h-[92vh] overflow-y-auto" onClick={(e) => e.stopPropagation()}>
        <div className="px-5 py-4 border-b border-stone-200 flex items-center justify-between sticky top-0 bg-white">
          <div>
            <h3 className="font-bold text-base">✏️ Editar venta</h3>
            <p className="text-xs text-stone-500">{cliente?.nombre}</p>
          </div>
          <button onClick={onClose} className="w-8 h-8 rounded-lg hover:bg-stone-100 text-stone-500 text-lg">✕</button>
        </div>

        <form onSubmit={submit} className="p-5 space-y-4">
          {/* Servicio */}
          <div>
            <Label>Servicio</Label>
            <Select value={servicioId} onChange={setServicioId}>
              {svs.filter((s) => s.activo || s.id === servicioId).map((s) => (
                <option key={s.id} value={s.id}>{s.nombre}</option>
              ))}
            </Select>
          </div>

          {/* Fechas */}
          <div className="grid grid-cols-2 gap-3">
            <div>
              <Label>Fecha de compra</Label>
              <input type="date" value={fechaCompra} onChange={(e) => setFechaCompra(e.target.value)} min={FECHA_MIN} max={FECHA_MAX()} required
                className="w-full px-3 py-2.5 rounded-lg border border-stone-200 bg-white text-sm focus:outline-none focus:border-brand-500 focus:ring-2 focus:ring-brand-100" />
            </div>
            <div>
              <Label>Fecha de inicio</Label>
              <input type="date" value={fechaInicio} onChange={(e) => setFechaInicio(e.target.value)} min={FECHA_MIN} max={FECHA_MAX()} required
                className="w-full px-3 py-2.5 rounded-lg border border-stone-200 bg-white text-sm focus:outline-none focus:border-brand-500 focus:ring-2 focus:ring-brand-100" />
            </div>
          </div>

          {/* Valor y sesiones */}
          <div className="grid grid-cols-2 gap-3">
            <div>
              <Label>Valor total (COP)</Label>
              <input type="number" value={valorTotal} onChange={(e) => setValorTotal(e.target.value)} min="0" step="1000" required
                className="w-full px-3 py-2.5 rounded-lg border border-stone-200 bg-white text-base font-semibold focus:outline-none focus:border-brand-500 focus:ring-2 focus:ring-brand-100" />
            </div>
            <div>
              <Label>N° de sesiones</Label>
              <input type="number" value={sesionesTotales} onChange={(e) => setSesionesTotales(e.target.value)} min={paq.sesionesUsadas || 1} step="1" required
                className="w-full px-3 py-2.5 rounded-lg border border-stone-200 bg-white text-base font-semibold focus:outline-none focus:border-brand-500 focus:ring-2 focus:ring-brand-100" />
            </div>
          </div>

          {/* Resumen saldo si hay abono */}
          {paq.abonoInicial > 0 && (
            <div className="bg-stone-50 border border-stone-200 rounded-xl p-3 text-sm space-y-1">
              <div className="flex justify-between text-stone-500"><span>Abono ya recibido:</span><strong>{COP(paq.abonoInicial)}</strong></div>
              <div className="flex justify-between text-rose-700 border-t border-stone-200 pt-1 mt-1"><span>Nuevo saldo pendiente:</span><strong>{COP(nuevoSaldo)}</strong></div>
            </div>
          )}

          {/* Método de pago — se cuadra contra el dinero REALMENTE recibido */}
          {esCredito ? (
            <div className="bg-rose-50 border border-rose-200 rounded-xl p-3 text-sm text-rose-800 space-y-1">
              <p className="font-bold uppercase tracking-wide text-xs">🔴 Venta a crédito</p>
              <div className="flex justify-between"><span>Recibido hasta hoy:</span><strong>{COP(0)}</strong></div>
              <div className="flex justify-between"><span>Pendiente por cobrar:</span><strong>{COP(nuevoSaldo)}</strong></div>
              <p className="text-[11px] text-rose-600 pt-1">
                No se registra método de pago porque todavía no ha entrado dinero.
                Cuando el cliente pague, usa <strong>Registrar abono</strong>.
              </p>
            </div>
          ) : (
            <>
              {Number(paq.abonoInicial || 0) > 0 && (
                <p className="text-[11px] text-stone-500 -mb-2">
                  El desglose corresponde al abono recibido ({COP(montoRecibido)}), no al valor total de la venta.
                </p>
              )}
              <PagoSelector
                valor={montoRecibido}
                metodoPago={metodoPago}
                montoEfectivo={montoEfectivo}
                montoTransferencia={montoTransferencia}
                montoDatafono={montoDatafono}
                onChange={handlePagoChange}
              />
            </>
          )}

          {/* Número de factura */}
          <div>
            <Label>N° de factura (opcional)</Label>
            <input type="text" value={numeroFactura} onChange={(e) => setNumeroFactura(e.target.value)} placeholder="Ej: FE-0001"
              className="w-full px-3 py-2.5 rounded-lg border border-stone-200 bg-white text-sm focus:outline-none focus:border-brand-500 focus:ring-2 focus:ring-brand-100" />
          </div>

          {/* Historial previo */}
          {(paq.historial || []).length > 0 && (
            <div className="text-xs text-stone-500">
              <p className="font-semibold mb-1">🕓 Cambios anteriores:</p>
              <div className="space-y-1 bg-stone-50 border border-stone-200 rounded-lg p-2 max-h-32 overflow-y-auto">
                {[...(paq.historial || [])].reverse().map((h, i) => (
                  <div key={i}>
                    <span className="text-stone-400">{fmtDateTime(h.ts)}</span>
                    {h.editor && <span className="font-semibold text-amber-700 ml-1.5">· 👤 {h.editor}</span>}
                    <ul className="ml-3 list-disc">{(h.cambios || []).map((c, j) => <li key={j}>{c}</li>)}</ul>
                  </div>
                ))}
              </div>
            </div>
          )}

          <div className="flex gap-2 pt-1">
            <button type="button" onClick={onClose} className="flex-1 py-2.5 rounded-xl border border-stone-300 bg-white text-sm font-medium hover:bg-stone-50">Cancelar</button>
            <button disabled={saving || !huboCambios} className="flex-1 py-2.5 rounded-xl bg-brand-600 text-white font-semibold hover:bg-brand-700 disabled:opacity-50 flex items-center justify-center gap-2">
              {saving ? <><Icon name="loader" className="w-4 h-4 animate-spin" />Guardando…</> : huboCambios ? 'Guardar cambios' : 'Sin cambios'}
            </button>
          </div>
        </form>
      </div>
    </div>
  );
}

function HistorialVentas({ state, onDeletePaquete, onUpdatePaquete, onUpdateVenta, onAddAbono, onDeleteAbono }) {
  const [abonoPaquete,  setAbonoPaquete]  = useState(null);
  const [editarVenta,   setEditarVenta]   = useState(null); // paquete a editar
  const [verHistVenta,  setVerHistVenta]  = useState(null); // id del paquete cuyo historial está abierto
  const [q,      setQ]      = useState('');
  const [metodo, setMetodo] = useState('todos');
  const [ordenPor, setOrdenPor] = useState('reciente'); // 'reciente' (por created_at) o 'fecha' (por fecha_compra)
  const [limite, setLimite] = useState(50);
  const [soloHallazgos, setSoloHallazgos] = useState(false); // modo auditoría

  // Índices de auditoría: se calculan una vez sobre TODAS las ventas
  const idxAud = useMemo(() => construirIndicesAuditoria(state.paquetes), [state.paquetes]);

  // Búsqueda: ahora también por N° de factura, fecha y valor (antes solo nombre/cédula/servicio,
  // por eso buscar una factura no encontraba nada).
  const coincide = (p, ql) => {
    if (!ql) return true;
    const cliente = state.clientes.find((c) => c.id === p.clienteId);
    const sv      = findSv(state, p.servicioId);
    // Solo se compara contra el valor si la búsqueda tiene dígitos suficientes.
    // (String.includes('') siempre es true, lo que haría que todo coincidiera.)
    const soloNum = ql.replace(/\D/g, '');
    return (cliente?.nombre || '').toLowerCase().includes(ql)
        || (cliente?.cedula || '').includes(ql)
        || (sv?.nombre || '').toLowerCase().includes(ql)
        || String(p.numeroFactura || '').toLowerCase().includes(ql)
        || String(p.fechaCompra || '').includes(ql)
        || (soloNum.length >= 4 && String(p.valorTotal || '').includes(soloNum));
  };

  const filas = useMemo(() => {
    const ql = q.toLowerCase().trim();
    return state.paquetes
      .filter((p) => {
        const matchM = metodo === 'todos' || p.metodoPago === metodo;
        const matchH = !soloHallazgos || anomaliasDeVenta(p, idxAud).length > 0;
        return coincide(p, ql) && matchM && matchH;
      })
      .sort((a, b) => {
        if (ordenPor === 'reciente') {
          // Más recientes primero (por id como proxy, asumiendo que los nuevos van al final)
          return (b.createdAt || '').localeCompare(a.createdAt || '') || b.id.localeCompare(a.id);
        }
        return b.fechaCompra.localeCompare(a.fechaCompra) || b.id.localeCompare(a.id);
      })
      .slice(0, limite);
  }, [state, q, metodo, ordenPor, limite, soloHallazgos, idxAud]);

  const total = useMemo(() =>
    state.paquetes.filter((p) => {
      const ql = q.toLowerCase().trim();
      return coincide(p, ql) && (!soloHallazgos || anomaliasDeVenta(p, idxAud).length > 0);
    }).length,
    [state, q, soloHallazgos, idxAud]
  );

  const METODOS = [
    { v: 'todos',         l: 'Todos' },
    { v: 'efectivo',      l: '💵 Efectivo' },
    { v: 'transferencia', l: '📲 Transferencia' },
    { v: 'datafono',      l: '💳 Datáfono' },
    { v: 'mixto',         l: '🔀 Mixto' },
  ];

  return (
    <div className="space-y-3 max-w-4xl">
      {/* Filtros */}
      <div className="flex flex-col sm:flex-row gap-2">
        <Input value={q} onChange={setQ} placeholder="Buscar por cliente, cédula, servicio, N° factura o fecha…" className="flex-1" />
        <Select value={metodo} onChange={setMetodo} className="sm:w-48">
          {METODOS.map((m) => <option key={m.v} value={m.v}>{m.l}</option>)}
        </Select>
      </div>
      <div className="flex items-center justify-between flex-wrap gap-2">
        <div className="text-xs text-stone-400">{total} ventas registradas{(q || soloHallazgos) ? ` · mostrando ${filas.length}` : ''}</div>
        <div className="flex items-center gap-2 flex-wrap">
          {/* Modo auditoría: deja solo las ventas con algún problema detectado */}
          <button onClick={() => setSoloHallazgos((v) => !v)}
            className={`px-3 py-1.5 rounded-lg text-xs font-semibold border transition ${soloHallazgos ? 'bg-rose-600 text-white border-rose-600' : 'bg-white text-rose-700 border-rose-300 hover:bg-rose-50'}`}>
            {soloHallazgos ? '✕ Ver todas' : '🔍 Solo con hallazgos'}
          </button>
          <div className="flex rounded-lg p-0.5 bg-stone-100 text-xs">
            {[{ v: 'reciente', l: '🆕 Recién creadas' }, { v: 'fecha', l: '📅 Por fecha compra' }].map((o) => (
              <button key={o.v} onClick={() => setOrdenPor(o.v)}
                className={`px-3 py-1.5 rounded-md font-medium transition ${ordenPor === o.v ? 'bg-white shadow-sm text-stone-900' : 'text-stone-500'}`}>
                {o.l}
              </button>
            ))}
          </div>
        </div>
      </div>

      {soloHallazgos && (
        <div className="bg-rose-50 border border-rose-200 rounded-xl px-4 py-2.5 text-xs text-rose-800">
          🔍 <strong>Modo auditoría.</strong> Solo se muestran las ventas con algún problema.
          Pasa el mouse sobre cada etiqueta roja/amarilla para ver la explicación, y usa ✏️ Editar para corregir.
        </div>
      )}

      {filas.length === 0 ? <Card><Empty text="Sin resultados." /></Card> : (
        <Card className="overflow-hidden p-0">
          <div className="overflow-x-auto">
            <table className="w-full text-sm">
              <thead>
                <tr className="border-b border-stone-100 bg-stone-50 text-xs text-stone-500 uppercase tracking-wide">
                  <th className="px-4 py-3 text-left">Fecha</th>
                  <th className="px-4 py-3 text-left">Cliente</th>
                  <th className="px-4 py-3 text-left hidden md:table-cell">Servicio</th>
                  <th className="px-4 py-3 text-center hidden sm:table-cell">Sesiones</th>
                  <th className="px-4 py-3 text-right">Valor</th>
                  <th className="px-4 py-3 text-center hidden sm:table-cell">Pago</th>
                  <th className="px-4 py-3 text-center hidden lg:table-cell">Factura</th>
                  <th className="px-4 py-3"></th>
                </tr>
              </thead>
              <tbody className="divide-y divide-stone-50">
                {filas.map((p) => {
                  const cliente = state.clientes.find((c) => c.id === p.clienteId);
                  const sv      = findSv(state, p.servicioId);
                  const pct     = p.sesionesTotales > 1 ? Math.round((p.sesionesUsadas / p.sesionesTotales) * 100) : null;
                  const pagoIcon = pagoIcono(p.metodoPago);
                  const haySaldo = p.saldoPendiente > 0;
                  // Detectar si se creó hace menos de 10 minutos
                  const esReciente = p.createdAt && (Date.now() - new Date(p.createdAt).getTime()) < 10 * 60 * 1000;
                  const hallazgos = anomaliasDeVenta(p, idxAud);
                  return (
                    <React.Fragment key={p.id}>
                    <tr className={`hover:bg-stone-50 transition ${esReciente ? 'bg-emerald-50' : hallazgos.some((h) => h.color === 'rose') ? 'bg-rose-50/50' : ''}`}>
                      <td className="px-4 py-3 text-stone-500 whitespace-nowrap text-xs">
                        {fmtDate(p.fechaCompra)}
                        {esReciente && <div className="text-[10px] text-emerald-700 font-bold mt-0.5">🆕 RECIÉN</div>}
                      </td>
                      <td className="px-4 py-3">
                        <div className="font-medium">{cliente?.nombre || <span className="text-rose-500 text-xs">Cliente eliminado</span>}</div>
                        {cliente?.cedula && <div className="text-xs text-stone-400">{cliente.tipo_doc || 'CC'} {cliente.cedula}</div>}
                        {haySaldo && <div className="text-xs text-rose-600 font-semibold">💰 Saldo: {COP(p.saldoPendiente)}</div>}
                        {/* Etiquetas de auditoría — el título explica el problema al pasar el mouse */}
                        {hallazgos.length > 0 && (
                          <div className="flex flex-wrap gap-1 mt-1">
                            {hallazgos.map((h, i) => (
                              <span key={i} title={h.explicacion}
                                className={`text-[10px] px-1.5 py-0.5 rounded border font-semibold cursor-help ${COLOR_HALLAZGO[h.color]}`}>
                                {h.etiqueta}
                              </span>
                            ))}
                          </div>
                        )}
                      </td>
                      <td className="px-4 py-3 text-stone-600 hidden md:table-cell">
                        <div>{sv?.nombre || p.servicioId}</div>
                        {p.esBono && <span className="text-[10px] text-amber-700 font-semibold">🎁 Bono</span>}
                      </td>
                      <td className="px-4 py-3 text-center hidden sm:table-cell">
                        {p.sesionesTotales > 1 ? (
                          <div className="flex items-center gap-2">
                            <div className="flex-1 h-1.5 bg-stone-200 rounded-full overflow-hidden w-16">
                              <div className="h-full bg-brand-400 rounded-full" style={{ width: `${pct}%` }} />
                            </div>
                            <span className="text-xs text-stone-500 tabular-nums">{p.sesionesUsadas}/{p.sesionesTotales}</span>
                          </div>
                        ) : (
                          <span className={`text-xs font-semibold ${p.sesionesUsadas >= p.sesionesTotales ? 'text-stone-400' : 'text-emerald-600'}`}>
                            {p.sesionesUsadas >= p.sesionesTotales ? 'Usada' : 'Disponible'}
                          </span>
                        )}
                      </td>
                      <td className="px-4 py-3 text-right font-semibold tabular-nums">
                        {COP(p.valorTotal)}
                        {p.abonoInicial > 0 && p.saldoPendiente > 0 && (
                          <div className="text-xs text-stone-400 font-normal">Abono: {COP(p.abonoInicial)}</div>
                        )}
                        {p.saldoPendiente > 0 && onAddAbono && (
                          <div className="flex gap-1 justify-end mt-1">
                            <button type="button" onClick={() => setAbonoPaquete({ paquete: p, cliente, servicio: sv })}
                              className="text-[10px] font-semibold text-white bg-rose-500 hover:bg-rose-600 px-1.5 py-0.5 rounded transition">
                              💵 + Abono
                            </button>
                          </div>
                        )}
                      </td>
                      <td className="px-4 py-3 text-center hidden sm:table-cell text-base" title={p.metodoPago}>{pagoIcon}</td>
                      <td className="px-4 py-3 text-center hidden lg:table-cell text-xs text-violet-700">
                        {p.numeroFactura || <span className="text-stone-300">—</span>}
                      </td>
                      <td className="px-2 py-3 text-right whitespace-nowrap">
                        <div className="flex items-center justify-end gap-1">
                          {onUpdateVenta && (
                            <button onClick={() => setEditarVenta(p)}
                              className="text-xs font-semibold text-brand-600 hover:text-white hover:bg-brand-600 border border-brand-300 rounded-lg px-2 py-1 transition"
                              title="Editar venta">
                              ✏️ Editar
                            </button>
                          )}
                          {(p.historial || []).length > 0 && (
                            <button onClick={() => setVerHistVenta((v) => v === p.id ? null : p.id)}
                              className="text-[11px] text-stone-400 hover:text-stone-600 border border-stone-200 rounded-lg px-1.5 py-1 transition"
                              title="Ver historial de cambios">
                              🕓 {p.historial.length}
                            </button>
                          )}
                          {onDeletePaquete && (
                            <button onClick={() => {
                              const tieneSesiones = p.sesionesUsadas > 0;
                              const advertencia = tieneSesiones
                                ? `⚠️ ADVERTENCIA: Esta venta tiene ${p.sesionesUsadas} sesión(es) registrada(s).\n\nAl eliminar la venta también se eliminarán esas ${p.sesionesUsadas} sesión(es). Las comisiones de fisio correspondientes desaparecerán de la liquidación.\n\n`
                                : '';
                              if (confirm(`${advertencia}¿Eliminar esta venta?\n\nCliente: ${cliente?.nombre}\nServicio: ${sv?.nombre}\nFecha: ${fmtDate(p.fechaCompra)}\nValor: ${COP(p.valorTotal)}\nSesiones usadas: ${p.sesionesUsadas}/${p.sesionesTotales}\n\nEsta acción no se puede deshacer.`)) {
                                onDeletePaquete(p.id).catch((err) => alert('Error eliminando venta: ' + err.message));
                              }
                            }} className="text-stone-300 hover:text-rose-600 transition" title="Eliminar venta">
                              🗑️
                            </button>
                          )}
                        </div>
                      </td>
                    </tr>
                    {verHistVenta === p.id && (p.historial || []).length > 0 && (
                      <tr className="bg-amber-50">
                        <td colSpan={8} className="px-4 py-2">
                          <p className="text-[11px] font-bold text-amber-800 uppercase tracking-wide mb-1">🕓 Historial de cambios</p>
                          <div className="space-y-1">
                            {[...(p.historial || [])].reverse().map((h, i) => (
                              <div key={i} className="text-[11px] text-stone-600 flex flex-wrap gap-x-3 gap-y-0.5">
                                <span className="text-stone-400 shrink-0">{fmtDateTime(h.ts)}</span>
                                {h.editor && <span className="font-semibold text-amber-700 shrink-0">👤 {h.editor}</span>}
                                <span>{(h.cambios || []).join(' · ')}</span>
                              </div>
                            ))}
                          </div>
                        </td>
                      </tr>
                    )}
                    </React.Fragment>
                  );
                })}
              </tbody>
            </table>
          </div>
          {total > limite && (
            <div className="p-3 text-center border-t border-stone-100">
              <button onClick={() => setLimite((l) => l + 50)} className="text-sm text-brand-600 font-medium hover:underline">
                Cargar más ({total - limite} restantes)
              </button>
            </div>
          )}
        </Card>
      )}
      {abonoPaquete && onAddAbono && (
        <AbonoModal
          paquete={abonoPaquete.paquete}
          cliente={abonoPaquete.cliente}
          servicio={abonoPaquete.servicio}
          onClose={() => setAbonoPaquete(null)}
          onAddAbono={onAddAbono}
        />
      )}
      {editarVenta && onUpdateVenta && (
        <EditarVentaModal
          paquete={editarVenta}
          state={state}
          onClose={() => setEditarVenta(null)}
          onUpdateVenta={onUpdateVenta}
        />
      )}
    </div>
  );
}

// ---------- Nueva venta (contenedor con sub-tabs) ----------
function VentaContainer({ state, onSave, onDone, onDeletePaquete, onUpdatePaquete, onUpdateVenta, onAddAbono, onDeleteAbono }) {
  const [subTab, setSubTab] = useState('nueva');
  return (
    <div className="space-y-4 max-w-4xl">
      <div className="flex rounded-xl p-1 bg-stone-100 text-sm w-fit">
        {[{ id: 'nueva', l: '➕ Nueva venta' }, { id: 'historial', l: '📋 Historial' }].map((o) => (
          <button key={o.id} onClick={() => setSubTab(o.id)}
            className={`px-4 py-2 rounded-lg font-medium transition ${subTab === o.id ? 'bg-white shadow-sm text-stone-900' : 'text-stone-500 hover:text-stone-700'}`}>
            {o.l}
          </button>
        ))}
      </div>
      {subTab === 'nueva'     && <NuevaVenta state={state} onSave={onSave} onDone={onDone} onViewHistorial={() => setSubTab('historial')} />}
      {subTab === 'historial' && <HistorialVentas state={state} onDeletePaquete={onDeletePaquete} onUpdatePaquete={onUpdatePaquete} onUpdateVenta={onUpdateVenta} onAddAbono={onAddAbono} onDeleteAbono={onDeleteAbono} />}
    </div>
  );
}

function NuevaVenta({ state, onSave, onDone, onViewHistorial }) {
  const [clienteMode,   setClienteMode]   = useState('existente');
  const [clienteId,     setClienteId]     = useState(state.clientes[0]?.id || '');
  const [busqueda,      setBusqueda]      = useState('');
  const [nombreNuevo,   setNombreNuevo]   = useState('');
  const [cedulaNuevo,   setCedulaNuevo]   = useState('');
  const [codigoPais,    setCodigoPais]    = useState('57');
  const [telefonoNuevo, setTelefonoNuevo] = useState('');
  const [emailNuevo,    setEmailNuevo]    = useState('');
  const [servicioId,    setServicioId]    = useState(getSvs(state)[0]?.id || '');
  const [fechaCompra,   setFechaCompra]   = useState(todayISO());
  const [fechaInicio,   setFechaInicio]   = useState(todayISO());
  const [valor,         setValor]         = useState(0);
  const [saving,        setSaving]        = useState(false);
  const [done,          setDone]          = useState(null);

  // — bono regalo —
  const [esBono,             setEsBono]             = useState(false);
  const [beneficiariosExtra, setBeneficiariosExtra] = useState([]); // [{nombre, cedula, telefono}]
  const [compradorMode,      setCompradorMode]      = useState('existente'); // 'existente' | 'nuevo'
  const [compradorBusqueda,  setCompradorBusqueda]  = useState('');
  const [compradorId,        setCompradorId]        = useState(state.clientes[0]?.id || '');
  const [nombreComprador,    setNombreComprador]     = useState('');
  const [cedulaComprador,    setCedulaComprador]     = useState('');
  const [telefonoComprador,  setTelefonoComprador]   = useState('');
  const [diasVenc,           setDiasVenc]            = useState(20);

  // — pago —
  const [metodoPago,        setMetodoPago]        = useState('efectivo');
  const [montoEfectivo,     setMontoEfectivo]     = useState(0);
  const [montoTransferencia,setMontoTransferencia] = useState(0);
  const [montoDatafono,     setMontoDatafono]     = useState(0);

  // — factura —
  const [numeroFactura, setNumeroFactura] = useState('');

  // — estado del pago: 'completo' | 'parcial' | 'credito' —
  const [estadoPago,   setEstadoPago]   = useState('completo');
  const [montoAbono,   setMontoAbono]   = useState(0);
  // Derivado: parcial y crédito generan saldo pendiente (no usan el PagoSelector del total)
  const tieneAbono = estadoPago !== 'completo';

  const servicio = findSv(state, servicioId);
  const sesiones = servicio?.sesiones || 1;
  useEffect(() => {
    const p = servicio?.precio || 0;
    setValor(p);
    setMontoEfectivo(p);
    setMontoTransferencia(0);
    setMontoDatafono(0);
    setMetodoPago('efectivo');
    setEstadoPago('completo');
    setMontoAbono(0);
  }, [servicioId]);

  // Cambia el estado del pago y ajusta montos/método de forma coherente
  const cambiarEstadoPago = (nuevo) => {
    setEstadoPago(nuevo);
    if (nuevo === 'completo') {
      setMetodoPago('efectivo');
      setMontoEfectivo(Number(valor)); setMontoTransferencia(0); setMontoDatafono(0);
    } else {
      if (metodoPago === 'mixto') setMetodoPago('efectivo');
      if (nuevo === 'credito') setMontoAbono(0);
    }
  };

  // Auto-seleccionar primer resultado cuando el filtro cambia y el cliente actual ya no está visible
  const clientesFiltrados = useMemo(() => {
    const q = busqueda.toLowerCase();
    return state.clientes.filter((c) => !q || c.nombre.toLowerCase().includes(q) || (c.cedula || '').includes(q));
  }, [busqueda, state.clientes]);

  useEffect(() => {
    if (clientesFiltrados.length > 0 && !clientesFiltrados.find((c) => c.id === clienteId)) {
      setClienteId(clientesFiltrados[0].id);
    }
  }, [clientesFiltrados]);

  const handlePagoChange = ({ metodoPago: mp, montoEfectivo: ef, montoTransferencia: tr, montoDatafono: da }) => {
    setMetodoPago(mp); setMontoEfectivo(ef); setMontoTransferencia(tr); setMontoDatafono(da || 0);
  };
  // Si el valor cambia manualmente, recalcular montos
  const handleValorChange = (v) => {
    const n = Number(v);
    setValor(n);
    if (metodoPago === 'efectivo')      { setMontoEfectivo(n); setMontoTransferencia(0); setMontoDatafono(0); }
    if (metodoPago === 'transferencia') { setMontoEfectivo(0); setMontoTransferencia(n); setMontoDatafono(0); }
    if (metodoPago === 'datafono')      { setMontoEfectivo(0); setMontoTransferencia(0); setMontoDatafono(n); }
    if (metodoPago === 'mixto')         { setMontoEfectivo(n); setMontoTransferencia(0); setMontoDatafono(0); setMetodoPago('mixto'); }
  };

  const submit = async (e) => {
    e.preventDefault();

    // Detección de duplicados (cliente existente)
    if (clienteMode === 'existente' && clienteId) {
      const duplicado = state.paquetes.find((p) =>
        p.clienteId === clienteId &&
        p.servicioId === servicioId &&
        p.fechaCompra === fechaCompra &&
        Number(p.valorTotal) === Number(valor)
      );
      if (duplicado) {
        const cli = state.clientes.find((c) => c.id === clienteId)?.nombre || '';
        const ok = confirm(`⚠️ Ya existe una venta idéntica:\n\n• Cliente: ${cli}\n• Servicio: ${servicio?.nombre}\n• Fecha: ${fechaCompra}\n• Valor: ${COP(Number(valor))}\n\n¿Estás segura de querer registrarla otra vez?`);
        if (!ok) return;
      }
    }

    if (clienteMode === 'nuevo' && !nombreNuevo.trim()) { alert('Escribe el nombre del cliente'); return; }
    if (clienteMode === 'existente' && !clienteId) { alert('Selecciona un cliente'); return; }

    // Evita crear la misma persona dos veces: es la causa de las fichas repetidas,
    // que parten el historial del paciente y hacen que "no aparezca" su información.
    if (clienteMode === 'nuevo') {
      const ced = (cedulaNuevo || '').trim();
      const yaExiste = ced && state.clientes.find((c) => (c.cedula || '').trim() === ced);
      if (yaExiste) {
        alert(`⚠️ Ya existe un cliente con la cédula ${ced}:\n\n👤 ${yaExiste.nombre}\n\nNo lo crees de nuevo: cambia a "Existente" y búscalo, así toda su historia queda en una sola ficha.`);
        return;
      }
      const mismoNombre = state.clientes.find((c) =>
        (c.nombre || '').toUpperCase().replace(/\s+/g, ' ').trim() ===
        nombreNuevo.toUpperCase().replace(/\s+/g, ' ').trim());
      if (mismoNombre && !confirm(`⚠️ Ya hay un cliente llamado "${mismoNombre.nombre}"${mismoNombre.cedula ? ` (CC ${mismoNombre.cedula})` : ''}.\n\n¿Es una persona distinta?\n\nAceptar = crear otra ficha · Cancelar = usar la que ya existe`)) return;
    }
    if (!confirmarFechaRazonable(fechaCompra, 'la fecha de compra')) return;
    if (!confirmarFechaRazonable(fechaInicio, 'la fecha de inicio')) return;

    setSaving(true);
    try {
      const fechaVencimiento = esBono ? addDays(fechaCompra, Number(diasVenc)) : null;

      // Comprador del bono (nuevo o existente)
      let cidComprador = esBono ? compradorId : null;
      let compradorNuevoData = null;
      if (esBono && compradorMode === 'existente' && !cidComprador) { alert('Selecciona el comprador del bono'); setSaving(false); return; }
      if (esBono && compradorMode === 'nuevo') {
        if (!nombreComprador.trim()) { alert('Escribe el nombre del comprador'); setSaving(false); return; }
        cidComprador = uid();
        const telC = telefonoComprador.trim() ? codigoPais.replace(/\D/g, '') + telefonoComprador.replace(/\D/g, '') : '';
        compradorNuevoData = { id: cidComprador, cedula: cedulaComprador.trim(), nombre: nombreComprador.trim(), telefono: telC, email: '', ciudad: 'Manizales' };
      }

      // --- Beneficiarios: el principal + los extra (solo en bono) ---
      const extras = esBono ? beneficiariosExtra.filter((b) => b.nombre.trim()) : [];
      const numBenef = 1 + extras.length;

      // Reparte un total en partes iguales; el primer beneficiario absorbe el residuo
      const split = (total, idx) => {
        const t = Number(total) || 0;
        const base = Math.floor(t / numBenef);
        return idx === 0 ? t - base * (numBenef - 1) : base;
      };

      const abonoTotal = tieneAbono ? Math.min(Number(montoAbono) || 0, Number(valor)) : 0;
      const saldoTotal = tieneAbono ? Math.max(0, Number(valor) - abonoTotal) : 0;

      const clientesNuevos = [];

      // Beneficiario principal
      let cidPrimary = clienteId;
      if (clienteMode === 'nuevo') {
        cidPrimary = uid();
        const telCompleto = telefonoNuevo.trim() ? codigoPais.replace(/\D/g, '') + telefonoNuevo.replace(/\D/g, '') : '';
        clientesNuevos.push({ id: cidPrimary, cedula: cedulaNuevo.trim(), nombre: nombreNuevo.trim(), telefono: telCompleto, email: emailNuevo.trim(), ciudad: 'Manizales' });
      }

      // Lista de beneficiarios con su clienteId resuelto (reusa cliente existente por cédula)
      const benefList = [{ cid: cidPrimary }];
      for (const b of extras) {
        const ced = (b.cedula || '').trim();
        let bid = null;
        if (ced) {
          const existing = state.clientes.find((c) => (c.cedula || '').trim() === ced);
          if (existing) bid = existing.id;
        }
        if (!bid) {
          bid = uid();
          let telB = (b.telefono || '').replace(/\D/g, '');
          if (telB && telB.length === 10 && telB.startsWith('3')) telB = '57' + telB;
          clientesNuevos.push({ id: bid, cedula: ced, nombre: b.nombre.trim(), telefono: telB, email: '', ciudad: 'Manizales' });
        }
        benefList.push({ cid: bid });
      }

      // Un paquete/bono por beneficiario (valores y pagos repartidos en partes iguales)
      const paquetes = benefList.map((bn, idx) => {
        const valorB = split(Number(valor), idx);
        const abonoB = split(abonoTotal, idx);
        const saldoB = tieneAbono ? Math.max(0, valorB - abonoB) : 0;
        let efB, trB, daB;
        if (tieneAbono) {
          // El abono recibido se asigna al bucket del método elegido (mixto → efectivo)
          efB = (metodoPago === 'transferencia' || metodoPago === 'datafono') ? 0 : abonoB;
          trB = metodoPago === 'transferencia' ? abonoB : 0;
          daB = metodoPago === 'datafono' ? abonoB : 0;
        } else {
          efB = split(Number(montoEfectivo), idx);
          trB = split(Number(montoTransferencia), idx);
          daB = split(Number(montoDatafono), idx);
        }
        return {
          id: uid(), clienteId: bn.cid, servicioId,
          sesionesTotales: sesiones, sesionesUsadas: 0,
          valorTotal: valorB, fechaCompra, fechaInicio,
          esBono,
          compradorId: esBono ? cidComprador : null,
          fechaVencimiento,
          metodoPago,
          montoEfectivo: efB,
          montoTransferencia: trB,
          montoDatafono: daB,
          // TODOS los beneficiarios llevan el mismo número: es una sola factura física
          // que cubre el bono completo. Antes solo el primero lo llevaba y los demás
          // quedaban "Sin factura", imposibles de encontrar buscando por factura.
          numeroFactura: numeroFactura.trim(),
          abonoInicial: abonoB,
          saldoPendiente: saldoB,
          beneficiariosExtra: [],
        };
      });

      await onSave(clientesNuevos, paquetes, compradorNuevoData);

      const clienteNombre = clienteMode === 'nuevo' ? nombreNuevo : state.clientes.find((c) => c.id === cidPrimary)?.nombre;
      setDone({ cliente: clienteNombre, servicio: servicio.nombre, sesiones, valor: Number(valor), esBono, fechaVencimiento, abono: abonoTotal, saldo: saldoTotal, numBonos: paquetes.length });
    } catch (err) {
      alert('Error guardando: ' + err.message);
    } finally {
      setSaving(false);
    }
  };

  if (done) {
    return (
      <div className="max-w-lg mx-auto fade-in">
        <Card className="text-center border-2 border-emerald-400 bg-emerald-50">
          <div className="w-20 h-20 mx-auto mb-3 rounded-full bg-emerald-500 text-white flex items-center justify-center shadow-lg">
            {done.esBono ? <span className="text-4xl">🎁</span> : <Icon name="check" className="w-12 h-12" />}
          </div>
          <h2 className="text-2xl font-bold mb-1 text-emerald-800">{done.esBono ? (done.numBonos > 1 ? '🎁 Bonos guardados' : '🎁 Bono guardado') : '✅ Venta GUARDADA'}</h2>
          <p className="text-emerald-700 text-sm font-semibold mb-3">
            {done.numBonos > 1
              ? `Se crearon ${done.numBonos} bonos, uno por beneficiario`
              : 'La venta quedó registrada correctamente'}
          </p>
          <div className="bg-white rounded-xl p-3 mb-3 text-left text-sm space-y-1">
            <div className="flex justify-between"><span className="text-stone-500">{done.numBonos > 1 ? 'Beneficiario principal:' : 'Cliente:'}</span><strong>{done.cliente}</strong></div>
            <div className="flex justify-between"><span className="text-stone-500">Servicio:</span><strong>{done.servicio}</strong></div>
            <div className="flex justify-between"><span className="text-stone-500">{done.numBonos > 1 ? 'Sesiones c/u:' : 'Sesiones:'}</span><strong>{done.sesiones}</strong></div>
            <div className="flex justify-between border-t border-stone-100 pt-1 mt-1"><span className="text-stone-500">Valor total:</span><strong className="text-base">{COP(done.valor)}</strong></div>
            {done.abono > 0 && (
              <>
                <div className="flex justify-between text-amber-700"><span>Abono recibido:</span><strong>{COP(done.abono)}</strong></div>
                <div className="flex justify-between text-rose-700"><span>Saldo pendiente:</span><strong>{COP(done.saldo)}</strong></div>
              </>
            )}
          </div>
          {done.esBono && done.fechaVencimiento && (
            <p className="text-amber-700 text-xs mb-3">⏳ El bono vence el {fmtDate(done.fechaVencimiento)}</p>
          )}
          <div className="flex flex-col gap-2 mt-4">
            <button onClick={() => onViewHistorial && onViewHistorial()} className="w-full px-4 py-3 rounded-xl bg-emerald-600 text-white text-base font-bold hover:bg-emerald-700 flex items-center justify-center gap-2 shadow">
              📋 Ver venta en el historial
            </button>
            <div className="flex flex-col sm:flex-row gap-2">
              <button onClick={() => setDone(null)} className="flex-1 px-4 py-2.5 rounded-lg border border-stone-300 bg-white text-sm font-medium hover:bg-stone-50">Registrar otra venta</button>
              <button onClick={onDone} className="flex-1 px-4 py-2.5 rounded-lg border border-stone-300 bg-white text-sm font-medium hover:bg-stone-50">Ir a clientes</button>
            </div>
          </div>
        </Card>
      </div>
    );
  }

  return (
    <div className="max-w-2xl">
      <Card>
        <form onSubmit={submit} className="space-y-5">

          {/* Toggle bono regalo */}
          <button type="button" onClick={() => setEsBono((v) => !v)}
            className={`w-full flex items-center gap-3 px-4 py-3 rounded-xl border-2 transition font-semibold text-sm ${esBono ? 'border-amber-400 bg-amber-50 text-amber-800' : 'border-stone-200 bg-stone-50 text-stone-500 hover:border-stone-300'}`}>
            <span className="text-2xl">🎁</span>
            <span className="flex-1 text-left">
              {esBono ? 'Bono regalo activado' : '¿Es bono regalo?'}
              <span className="block text-xs font-normal opacity-70">{esBono ? 'El comprador es diferente al beneficiario' : 'Toca para marcar como bono regalo'}</span>
            </span>
            <div className={`w-10 h-5 rounded-full transition-colors ${esBono ? 'bg-amber-400' : 'bg-stone-200'}`}>
              <div className={`w-4 h-4 bg-white rounded-full mt-0.5 shadow transition-transform ${esBono ? 'translate-x-5 ml-0.5' : 'ml-0.5'}`} />
            </div>
          </button>

          {/* Comprador (solo si es bono) */}
          {esBono && (
            <div className="bg-amber-50 border border-amber-200 rounded-xl p-4 space-y-3 fade-in">
              <p className="text-xs font-bold text-amber-800 uppercase tracking-wide">💳 Quién compra el bono</p>
              {/* Toggle existente / nuevo comprador */}
              <div className="flex rounded-lg p-0.5 bg-amber-100 text-sm">
                {[{ v: 'existente', l: 'Existente' }, { v: 'nuevo', l: 'Nuevo' }].map((o) => (
                  <button key={o.v} type="button" onClick={() => setCompradorMode(o.v)}
                    className={`flex-1 py-1.5 rounded-md font-medium transition ${compradorMode === o.v ? 'bg-white shadow-sm text-stone-900' : 'text-amber-700'}`}>
                    {o.l}
                  </button>
                ))}
              </div>
              {compradorMode === 'existente' ? (
                <div className="space-y-2">
                  <Input value={compradorBusqueda} onChange={setCompradorBusqueda} placeholder="Buscar comprador por nombre o cédula…" />
                  <Select value={compradorId} onChange={(v) => setCompradorId(v)}>
                    {state.clientes
                      .filter((c) => { const q = compradorBusqueda.toLowerCase(); return !q || c.nombre.toLowerCase().includes(q) || (c.cedula || '').includes(q); })
                      .map((c) => <option key={c.id} value={c.id}>{c.nombre}{c.cedula ? ` — ${c.cedula}` : ''}</option>)}
                  </Select>
                </div>
              ) : (
                <div className="grid sm:grid-cols-2 gap-2">
                  <Input value={nombreComprador} onChange={setNombreComprador} placeholder="Nombre completo *" required />
                  <Input value={cedulaComprador} onChange={setCedulaComprador} placeholder="Cédula / NIT" />
                  <div className="flex gap-2 sm:col-span-2">
                    <div className="w-20 shrink-0">
                      <input value={codigoPais} onChange={(e) => setCodigoPais(e.target.value)}
                        placeholder="57" maxLength={4}
                        className="w-full px-3 py-2.5 rounded-lg border border-amber-300 bg-white text-sm focus:outline-none focus:border-amber-500 text-center font-medium"
                        title="Código de país" />
                    </div>
                    <div className="flex-1">
                      <Input value={telefonoComprador} onChange={setTelefonoComprador} placeholder="Teléfono" />
                    </div>
                  </div>
                </div>
              )}
              <div>
                <label className="block text-xs text-amber-700 mb-1 font-medium">Días para vencer</label>
                <div className="flex items-center gap-3">
                  <input type="number" value={diasVenc} onChange={(e) => setDiasVenc(e.target.value)} min="1" max="365"
                    className="w-24 border border-amber-300 rounded-lg px-3 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-amber-300 bg-white" />
                  <span className="text-xs text-amber-700">días → vence el <strong>{fechaCompra ? fmtDate(addDays(fechaCompra, Number(diasVenc))) : '—'}</strong></span>
                </div>
              </div>
            </div>
          )}

          {/* Beneficiario */}
          <div>
            <Label>{esBono ? '🎀 Para quién es el bono (beneficiario)' : 'Cliente'}</Label>
            <div className="flex rounded-lg p-0.5 bg-stone-100 mb-2 text-sm">
              {[{ v: 'existente', l: 'Existente' }, { v: 'nuevo', l: 'Nuevo' }].map((o) => (
                <button key={o.v} type="button" onClick={() => setClienteMode(o.v)}
                  className={`flex-1 py-1.5 rounded-md font-medium transition ${clienteMode === o.v ? 'bg-white shadow-sm text-stone-900' : 'text-stone-500'}`}>
                  {o.l}
                </button>
              ))}
            </div>
            {clienteMode === 'existente' ? (
              <div className="space-y-2">
                <Input value={busqueda} onChange={setBusqueda} placeholder="🔍 Escribe el nombre o la cédula…" />
                {/* Lista con clic directo: antes era un desplegable y con más de 1.000
                    clientes tocaba bajar con la flecha hasta encontrarlos. */}
                {clientesFiltrados.length === 0 ? (
                  <div className="px-3 py-3 rounded-lg bg-amber-50 border border-amber-200 text-amber-800 text-sm">
                    No hay ningún cliente que coincida con "{busqueda}". Si es nuevo, usa la pestaña <strong>Nuevo</strong>.
                  </div>
                ) : (
                  <>
                    <div className="text-[11px] text-stone-400 px-1">
                      {clientesFiltrados.length} cliente{clientesFiltrados.length !== 1 ? 's' : ''}
                      {busqueda ? ' encontrados' : ' — escribe para filtrar'}
                    </div>
                    <div className="max-h-52 overflow-y-auto rounded-xl border border-stone-200 divide-y divide-stone-100">
                      {clientesFiltrados.slice(0, 60).map((c) => (
                        <button key={c.id} type="button" onClick={() => setClienteId(c.id)}
                          className={`w-full text-left px-3 py-2 text-sm transition ${clienteId === c.id ? 'bg-brand-50 text-brand-900 font-semibold' : 'hover:bg-stone-50'}`}>
                          {c.nombre}
                          {c.cedula && <span className="block text-[11px] text-stone-400 font-normal">{c.cedula}</span>}
                        </button>
                      ))}
                      {clientesFiltrados.length > 60 && (
                        <div className="px-3 py-2 text-[11px] text-stone-400">
                          Mostrando 60 de {clientesFiltrados.length}. Escribe más letras para afinar.
                        </div>
                      )}
                    </div>
                  </>
                )}
                {/* Banner de confirmación del cliente seleccionado */}
                {(() => {
                  const cli = state.clientes.find((c) => c.id === clienteId);
                  if (!cli) return null;
                  return (
                    <div className="flex items-center gap-2 px-3 py-2 rounded-lg bg-brand-50 border border-brand-200 text-brand-800 text-sm">
                      <span className="text-base">✅</span>
                      <span>Cliente seleccionado: <strong>{cli.nombre}</strong>{cli.cedula ? <span className="text-brand-600"> · {cli.cedula}</span> : ''}</span>
                    </div>
                  );
                })()}
              </div>
            ) : (
              <div className="grid sm:grid-cols-2 gap-2">
                <Input value={nombreNuevo} onChange={setNombreNuevo} placeholder="Nombre completo" required />
                <Input value={cedulaNuevo} onChange={setCedulaNuevo} placeholder="Cédula / NIT" />
                <div className="flex gap-2">
                  <div className="w-20 shrink-0">
                    <input value={codigoPais} onChange={(e) => setCodigoPais(e.target.value)}
                      placeholder="57" maxLength={4}
                      className="w-full px-3 py-2.5 rounded-lg border border-stone-200 bg-white text-sm focus:outline-none focus:border-brand-500 focus:ring-2 focus:ring-brand-100 text-center font-medium"
                      title="Código de país" />
                  </div>
                  <div className="flex-1">
                    <Input value={telefonoNuevo} onChange={setTelefonoNuevo} placeholder="Teléfono" />
                  </div>
                </div>
                <Input value={emailNuevo} onChange={setEmailNuevo} placeholder="Email" />
              </div>
            )}
          </div>

          {/* Beneficiarios adicionales (solo si es bono) */}
          {esBono && (
            <div className="space-y-2">
              <div className="flex items-center justify-between">
                <Label className="mb-0">👥 Otros beneficiarios <span className="text-stone-400 font-normal">(opcional)</span></Label>
                <button type="button"
                  onClick={() => setBeneficiariosExtra(prev => [...prev, { nombre: '', cedula: '', telefono: '' }])}
                  className="text-xs font-semibold text-brand-600 hover:text-brand-800 flex items-center gap-1 px-2 py-1 rounded-lg hover:bg-brand-50 transition">
                  ➕ Agregar
                </button>
              </div>
              {beneficiariosExtra.length === 0 && (
                <p className="text-xs text-stone-400 px-1">Si el bono es para más de una persona, agrégalas aquí.</p>
              )}
              {beneficiariosExtra.map((b, i) => (
                <div key={i} className="flex gap-2 items-start bg-stone-50 border border-stone-200 rounded-xl p-3">
                  <div className="flex-1 grid sm:grid-cols-3 gap-2">
                    <input value={b.nombre} onChange={(e) => { const v = e.target.value; setBeneficiariosExtra(prev => prev.map((x, j) => j === i ? { ...x, nombre: v } : x)); }}
                      placeholder="Nombre *"
                      className="px-3 py-2 rounded-lg border border-stone-200 bg-white text-sm focus:outline-none focus:border-brand-500 focus:ring-2 focus:ring-brand-100" />
                    <input value={b.cedula} onChange={(e) => { const v = e.target.value; setBeneficiariosExtra(prev => prev.map((x, j) => j === i ? { ...x, cedula: v } : x)); }}
                      placeholder="Cédula"
                      className="px-3 py-2 rounded-lg border border-stone-200 bg-white text-sm focus:outline-none focus:border-brand-500 focus:ring-2 focus:ring-brand-100" />
                    <input value={b.telefono} onChange={(e) => { const v = e.target.value; setBeneficiariosExtra(prev => prev.map((x, j) => j === i ? { ...x, telefono: v } : x)); }}
                      placeholder="Teléfono"
                      className="px-3 py-2 rounded-lg border border-stone-200 bg-white text-sm focus:outline-none focus:border-brand-500 focus:ring-2 focus:ring-brand-100" />
                  </div>
                  <button type="button" onClick={() => setBeneficiariosExtra(prev => prev.filter((_, j) => j !== i))}
                    className="text-stone-300 hover:text-rose-500 text-lg pt-1 shrink-0">✕</button>
                </div>
              ))}
              {beneficiariosExtra.filter(b => b.nombre.trim()).length > 0 && (() => {
                const n = 1 + beneficiariosExtra.filter(b => b.nombre.trim()).length;
                return (
                  <div className="text-xs bg-amber-50 border border-amber-200 rounded-lg px-3 py-2 text-amber-800">
                    Se crearán <strong>{n} bonos</strong> (uno por persona), cada uno con <strong>{sesiones} {sesiones === 1 ? 'sesión' : 'sesiones'}</strong>.
                    {Number(valor) > 0 && <> Valor c/u: <strong>{COP(Math.floor(Number(valor) / n))}</strong>.</>}
                  </div>
                );
              })()}
            </div>
          )}

          <div>
            <Label>Servicio o paquete</Label>
            {/* Solo servicios ACTIVOS: antes aparecían también los desactivados,
                así que apagarlos en el catálogo no servía de nada. */}
            <Select value={servicioId} onChange={setServicioId}>
              {getSvs(state).filter((s) => s.activo !== false || s.id === servicioId).map((s) => (
                <option key={s.id} value={s.id}>{s.nombre} {s.sesiones ? `(${s.sesiones} sesiones)` : ''} — {COP(s.precio)}</option>
              ))}
            </Select>
          </div>

          <div className="grid sm:grid-cols-2 gap-4">
            <div>
              <Label>Fecha de compra</Label>
              <Input type="date" value={fechaCompra} onChange={setFechaCompra}  min={FECHA_MIN} max={FECHA_MAX()}/>
            </div>
            <div>
              <Label>Fecha de inicio <span className="text-stone-400 font-normal">(puede ser distinta)</span></Label>
              <Input type="date" value={fechaInicio} onChange={setFechaInicio}  min={FECHA_MIN} max={FECHA_MAX()}/>
            </div>
          </div>

          <div className="grid sm:grid-cols-2 gap-4">
            <div>
              <Label>Valor total</Label>
              <Input type="number" value={valor} onChange={handleValorChange} />
            </div>
            <div>
              <Label>N° factura electrónica <span className="text-stone-400 font-normal">(opcional)</span></Label>
              <Input value={numeroFactura} onChange={setNumeroFactura} placeholder="Ej: FE-0001" />
            </div>
          </div>

          {/* Estado del pago: completo / parcial / crédito */}
          <div>
            <label className="block text-sm font-medium text-stone-700 mb-2">¿Cómo queda este pago?</label>
            <div className="grid grid-cols-3 gap-2">
              {[
                { id: 'completo', emoji: '✅', titulo: 'Pagó completo', sub: 'Recibes todo hoy' },
                { id: 'parcial',  emoji: '🟠', titulo: 'Pagó una parte', sub: 'Abono + saldo' },
                { id: 'credito',  emoji: '🔴', titulo: 'Queda a crédito', sub: 'Paga después' },
              ].map((o) => (
                <button key={o.id} type="button" onClick={() => cambiarEstadoPago(o.id)}
                  className={`flex flex-col items-center text-center gap-0.5 px-2 py-3 rounded-xl border-2 transition ${estadoPago === o.id ? (o.id === 'completo' ? 'border-emerald-400 bg-emerald-50' : o.id === 'parcial' ? 'border-orange-400 bg-orange-50' : 'border-rose-400 bg-rose-50') : 'border-stone-200 bg-white hover:border-stone-300'}`}>
                  <span className="text-xl">{o.emoji}</span>
                  <span className={`text-xs font-bold ${estadoPago === o.id ? 'text-stone-800' : 'text-stone-600'}`}>{o.titulo}</span>
                  <span className="text-[10px] text-stone-400 leading-tight">{o.sub}</span>
                </button>
              ))}
            </div>
          </div>

          {/* PAGÓ COMPLETO → método de pago del total */}
          {estadoPago === 'completo' && (
            <PagoSelector
              valor={Number(valor)}
              metodoPago={metodoPago}
              montoEfectivo={montoEfectivo}
              montoTransferencia={montoTransferencia}
              montoDatafono={montoDatafono}
              onChange={handlePagoChange}
            />
          )}

          {/* PAGÓ UNA PARTE → monto del abono + método + saldo */}
          {estadoPago === 'parcial' && (
            <div className="bg-orange-50 border border-orange-200 rounded-xl p-4 space-y-3 fade-in">
              <p className="text-xs font-bold text-orange-800 uppercase tracking-wide">🟠 Abono recibido hoy</p>
              <div className="grid sm:grid-cols-2 gap-3">
                <div>
                  <label className="block text-xs text-orange-700 mb-1 font-medium">Monto del abono</label>
                  <input type="number" value={montoAbono} onChange={(e) => setMontoAbono(e.target.value)}
                    max={Number(valor)} min={0}
                    className="w-full border border-orange-300 rounded-lg px-3 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-orange-300 bg-white" />
                </div>
                <div className="flex flex-col justify-center text-sm text-orange-800 space-y-1">
                  <div className="flex justify-between">
                    <span>Abono:</span>
                    <strong>{COP(Math.min(Number(montoAbono) || 0, Number(valor)))}</strong>
                  </div>
                  <div className="flex justify-between text-rose-700">
                    <span>Saldo pendiente:</span>
                    <strong>{COP(Math.max(0, Number(valor) - (Number(montoAbono) || 0)))}</strong>
                  </div>
                </div>
              </div>
              <div>
                <label className="block text-xs text-orange-700 mb-1 font-medium">¿Cómo pagó el abono?</label>
                <div className="grid grid-cols-3 rounded-lg p-0.5 bg-white border border-orange-200 text-xs gap-0.5">
                  {[{ id: 'efectivo', l: '💵 Efectivo' }, { id: 'transferencia', l: '📲 Transfer.' }, { id: 'datafono', l: '💳 Datáfono' }].map((o) => (
                    <button key={o.id} type="button" onClick={() => setMetodoPago(o.id)}
                      className={`py-1.5 rounded-md font-medium transition ${metodoPago === o.id ? 'bg-orange-500 text-white shadow-sm' : 'text-orange-700 hover:bg-orange-100'}`}>
                      {o.l}
                    </button>
                  ))}
                </div>
              </div>
            </div>
          )}

          {/* QUEDA A CRÉDITO → no entra dinero hoy */}
          {estadoPago === 'credito' && (
            <div className="bg-rose-50 border border-rose-200 rounded-xl p-4 fade-in text-sm text-rose-800 space-y-1">
              <p className="font-bold uppercase tracking-wide text-xs">🔴 Venta a crédito</p>
              <div className="flex justify-between"><span>Recibes hoy:</span><strong>{COP(0)}</strong></div>
              <div className="flex justify-between border-t border-rose-200 pt-1 mt-1"><span>Queda por cobrar:</span><strong className="text-base">{COP(Number(valor))}</strong></div>
              <p className="text-[11px] text-rose-600 pt-1">Aparecerá en <strong>Cuentas por cobrar</strong>. La comisión del fisio se paga igual cuando atienda. Registra el pago cuando el cliente abone.</p>
            </div>
          )}

          <button disabled={saving} className="w-full py-3 rounded-xl bg-brand-600 text-white font-semibold hover:bg-brand-700 disabled:opacity-60 flex items-center justify-center gap-2">
            {saving ? <><Icon name="loader" className="w-4 h-4 animate-spin" />Guardando…</> : esBono ? '🎁 Registrar bono regalo' : 'Registrar venta'}
          </button>
        </form>
      </Card>
    </div>
  );
}

// ---------- Bonos regalo ----------
const diasHasta = (iso) => {
  if (!iso) return null;
  return Math.ceil((new Date(iso + 'T12:00:00') - new Date(todayISO() + 'T12:00:00')) / 86400000);
};

const URGENCIA = (dias) => {
  if (dias === null) return { color: 'stone', label: 'Sin vencimiento' };
  if (dias < 0)      return { color: 'red',    label: 'Vencido' };
  if (dias === 0)    return { color: 'red',    label: 'Vence HOY' };
  if (dias <= 3)     return { color: 'red',    label: `Vence en ${dias} día${dias > 1 ? 's' : ''}` };
  if (dias <= 7)     return { color: 'orange', label: `Vence en ${dias} días` };
  if (dias <= 15)    return { color: 'amber',  label: `Vence en ${dias} días` };
  return              { color: 'green',  label: `Vence en ${dias} días` };
};

function Bonos({ state }) {
  const today = todayISO();
  const [filtro, setFiltro] = useState('activos'); // activos | vencidos | todos

  const bonos = useMemo(() => {
    return state.paquetes
      .filter((p) => p.esBono)
      .map((p) => {
        const beneficiario = state.clientes.find((c) => c.id === p.clienteId);
        const comprador    = p.compradorId ? state.clientes.find((c) => c.id === p.compradorId) : null;
        const sv           = findSv(state, p.servicioId);
        const dias         = diasHasta(p.fechaVencimiento);
        const urgencia     = URGENCIA(dias);
        const usado        = p.sesionesUsadas >= p.sesionesTotales;
        return { ...p, beneficiario, comprador, sv, dias, urgencia, usado };
      })
      .sort((a, b) => {
        if (a.dias === null && b.dias === null) return 0;
        if (a.dias === null) return 1;
        if (b.dias === null) return -1;
        return a.dias - b.dias;
      });
  }, [state.paquetes, state.clientes, today]);

  const filtrados = bonos.filter((b) => {
    if (filtro === 'activos') return !b.usado && (b.dias === null || b.dias >= 0);
    if (filtro === 'vencidos') return b.usado || (b.dias !== null && b.dias < 0);
    return true;
  });

  const expirando = bonos.filter((b) => !b.usado && b.dias !== null && b.dias >= 0 && b.dias <= 5);

  return (
    <div className="max-w-2xl space-y-5">
      {/* Alerta urgente */}
      {expirando.length > 0 && (
        <div className="bg-red-50 border-2 border-red-300 rounded-2xl p-4 fade-in">
          <p className="font-bold text-red-800 mb-2 flex items-center gap-2">
            <span>🔴</span> {expirando.length} bono{expirando.length > 1 ? 's' : ''} por vencer en los próximos 5 días
          </p>
          <div className="space-y-1.5">
            {expirando.map((b) => (
              <div key={b.id} className="flex items-center justify-between text-sm">
                <span className="text-red-700">
                  🎁 <strong>{b.beneficiario?.nombre || 'Sin nombre'}</strong>
                  {b.comprador && b.comprador.id !== b.clienteId && (
                    <span className="text-red-500 text-xs"> (comprado por {b.comprador.nombre})</span>
                  )}
                  {' — '}{b.sv?.nombre}
                </span>
                <span className={`text-xs font-bold px-2 py-0.5 rounded-full ${b.dias === 0 ? 'bg-red-200 text-red-900' : 'bg-orange-100 text-orange-800'}`}>
                  {b.urgencia.label}
                </span>
              </div>
            ))}
          </div>
        </div>
      )}

      {/* Filtros */}
      <div className="flex rounded-lg p-0.5 bg-stone-100 text-sm">
        {[{ v: 'activos', l: '✅ Activos' }, { v: 'vencidos', l: '❌ Vencidos/Usados' }, { v: 'todos', l: '📋 Todos' }].map((o) => (
          <button key={o.v} type="button" onClick={() => setFiltro(o.v)}
            className={`flex-1 py-1.5 rounded-md font-medium transition ${filtro === o.v ? 'bg-white shadow-sm text-stone-900' : 'text-stone-500'}`}>
            {o.l}
          </button>
        ))}
      </div>

      {filtrados.length === 0 && (
        <div className="text-center py-12 text-stone-400">
          <span className="text-4xl block mb-3">🎁</span>
          <p className="text-sm">No hay bonos en esta categoría</p>
        </div>
      )}

      {filtrados.map((b) => <BonoCard key={b.id} bono={b} />)}
    </div>
  );
}

function BonoCard({ bono }) {
  const [sending, setSending] = useState(false);

  const { urgencia, beneficiario, comprador, sv, dias } = bono;
  const isMismoPerson = !comprador || comprador.id === bono.clienteId;

  const urgColors = {
    red:    { bg: 'bg-red-50',    border: 'border-red-200',    badge: 'bg-red-100 text-red-800' },
    orange: { bg: 'bg-orange-50', border: 'border-orange-200', badge: 'bg-orange-100 text-orange-800' },
    amber:  { bg: 'bg-amber-50',  border: 'border-amber-200',  badge: 'bg-amber-100 text-amber-800' },
    green:  { bg: 'bg-white',     border: 'border-stone-200',  badge: 'bg-emerald-100 text-emerald-800' },
    stone:  { bg: 'bg-white',     border: 'border-stone-200',  badge: 'bg-stone-100 text-stone-600' },
  };
  const uc = urgColors[urgencia.color] || urgColors.stone;

  const handleRecordar = async () => {
    if (!beneficiario?.telefono) { alert(`${beneficiario?.nombre || 'El beneficiario'} no tiene teléfono registrado.`); return; }
    setSending(true);
    const msg = `Hola ${beneficiario.nombre?.split(' ')[0] || ''} 👋 Te recordamos que tienes un bono de *${sv?.nombre}* en Massage Therapy Manizales.\n\n${dias !== null && dias >= 0 ? `⏳ Tu bono vence el *${fmtDate(bono.fechaVencimiento)}* (${dias === 0 ? 'HOY' : `en ${dias} día${dias > 1 ? 's' : ''}`}).\n\n` : ''}¡No pierdas tu sesión! Escríbenos para agendar. 🌿`;
    const r = await sendWA(beneficiario.telefono, msg);
    setSending(false);
    alert(r.ok ? '✅ Recordatorio enviado por WhatsApp' : '⚠️ No se pudo enviar el mensaje');
  };

  return (
    <div className={`rounded-2xl border p-4 ${uc.bg} ${uc.border} fade-in`}>
      <div className="flex items-start justify-between gap-3 flex-wrap">
        <div className="flex-1 min-w-0">
          <div className="flex items-center gap-2 flex-wrap mb-1">
            <span className="font-bold text-stone-800">🎁 {sv?.nombre || 'Servicio'}</span>
            <span className={`text-xs font-semibold px-2 py-0.5 rounded-full ${uc.badge}`}>{urgencia.label}</span>
            {(bono.usado || (dias !== null && dias < 0)) && (
              <span className="text-xs bg-stone-100 text-stone-500 font-medium px-2 py-0.5 rounded-full">
                {bono.usado ? 'Sesiones agotadas' : 'Vencido'}
              </span>
            )}
          </div>

          {/* Beneficiario(s) */}
          <div className="text-sm text-stone-700 mt-1">
            <span className="text-xs text-stone-400 uppercase font-semibold tracking-wide">Para: </span>
            <span className="font-semibold">{beneficiario?.nombre || 'Sin asignar'}</span>
            {beneficiario?.telefono && (
              <a href={`tel:${beneficiario.telefono}`} className="ml-2 text-brand-600 text-xs hover:underline">📞 {beneficiario.telefono}</a>
            )}
            {/* Beneficiarios adicionales */}
            {(bono.beneficiariosExtra || []).filter(b => b.nombre).map((b, i) => (
              <span key={i} className="block ml-4 mt-0.5 text-stone-600">
                <span className="text-stone-400">·</span> {b.nombre}
                {b.cedula && <span className="text-stone-400 text-xs ml-1">({b.cedula})</span>}
                {b.telefono && <a href={`tel:${b.telefono}`} className="ml-1 text-brand-600 text-xs hover:underline">📞 {b.telefono}</a>}
              </span>
            ))}
          </div>

          {/* Comprador (solo si es diferente) */}
          {!isMismoPerson && (
            <div className="text-sm text-stone-500 mt-0.5">
              <span className="text-xs text-stone-400 uppercase font-semibold tracking-wide">Comprado por: </span>
              <span>{comprador.nombre}</span>
              {comprador?.telefono && (
                <a href={`tel:${comprador.telefono}`} className="ml-2 text-brand-600 text-xs hover:underline">📞</a>
              )}
            </div>
          )}

          {/* Sesiones + vencimiento */}
          <div className="flex items-center gap-4 mt-2 text-xs text-stone-500">
            <span>📌 {bono.sesionesTotales - bono.sesionesUsadas} sesión{bono.sesionesTotales - bono.sesionesUsadas !== 1 ? 'es' : ''} restante{bono.sesionesTotales - bono.sesionesUsadas !== 1 ? 's' : ''}</span>
            {bono.fechaVencimiento && <span>⏳ Vence: {fmtDate(bono.fechaVencimiento)}</span>}
            <span>📅 Compra: {fmtDate(bono.fechaCompra)}</span>
          </div>
        </div>

        {/* Botón recordar */}
        {!bono.usado && (
          <button onClick={handleRecordar} disabled={sending}
            className="shrink-0 flex items-center gap-1.5 bg-emerald-600 text-white text-xs font-semibold px-3 py-2 rounded-xl hover:bg-emerald-700 disabled:opacity-50">
            <Icon name="whatsapp" className="w-4 h-4" />
            {sending ? 'Enviando…' : 'Recordar'}
          </button>
        )}
      </div>
    </div>
  );
}

// ---------- Clientes ----------
function Clientes({ state, onAddCliente, onUpdateCliente, onUpdatePaquete, onDeletePaquete, onUpdateVenta, onAddAbono, onDeleteAbono, onUpdateSesion, onDeleteSesion, onSaldarSinPago }) {
  const [q,        setQ]        = useState('');
  const [detalle,  setDetalle]  = useState(null);
  const [showAdd,  setShowAdd]  = useState(false);

  // form nuevo cliente
  const [fNombre,    setFNombre]    = useState('');
  const [fTipoDoc,   setFTipoDoc]   = useState('CC');
  const [fCedula,    setFCedula]    = useState('');
  const [fCodigo,    setFCodigo]    = useState('57');
  const [fTel,       setFTel]       = useState('');
  const [fEmail,     setFEmail]     = useState('');
  const [fCiudad,    setFCiudad]    = useState('Manizales');
  const [fDireccion, setFDireccion] = useState('');
  const [saving,     setSaving]     = useState(false);

  const clientesFiltrados = useMemo(() => {
    const ql = q.toLowerCase();
    return state.clientes.filter((c) =>
      c.nombre.toLowerCase().includes(ql) ||
      (c.cedula   || '').includes(ql)     ||
      (c.telefono || '').includes(ql)
    );
  }, [state.clientes, q]);

  const handleAdd = async (e) => {
    e.preventDefault();
    if (!fNombre.trim()) return;
    setSaving(true);
    try {
      const tel = fTel.trim() ? fCodigo.replace(/\D/g,'') + fTel.replace(/\D/g,'') : '';
      await onAddCliente({ id: uid(), nombre: fNombre.trim(), tipo_doc: fTipoDoc, cedula: fCedula.trim(), telefono: tel, email: fEmail.trim(), ciudad: fCiudad.trim() || 'Manizales', direccion: fDireccion.trim() });
      setShowAdd(false); setFNombre(''); setFCedula(''); setFTel(''); setFEmail(''); setFCodigo('57'); setFCiudad('Manizales'); setFDireccion(''); setFTipoDoc('CC');
    } catch (err) { alert('Error: ' + err.message); }
    finally { setSaving(false); }
  };

  if (detalle) {
    const cliente = state.clientes.find((c) => c.id === detalle);
    if (!cliente) { setDetalle(null); return null; }
    return <ClienteDetalle cliente={cliente} state={state} onBack={() => setDetalle(null)} onUpdate={onUpdateCliente} onUpdatePaquete={onUpdatePaquete} onDeletePaquete={onDeletePaquete} onUpdateVenta={onUpdateVenta} onAddAbono={onAddAbono} onDeleteAbono={onDeleteAbono} onUpdateSesion={onUpdateSesion} onDeleteSesion={onDeleteSesion} onSaldarSinPago={onSaldarSinPago} />;
  }

  return (
    <div className="space-y-4 max-w-4xl">
      <div className="flex items-center gap-3">
        <Input value={q} onChange={setQ} placeholder="Buscar por nombre, cédula o teléfono…" />
        <span className="text-xs text-stone-400 whitespace-nowrap shrink-0">{clientesFiltrados.length}</span>
        <button onClick={() => setShowAdd((v) => !v)}
          className="shrink-0 flex items-center gap-1.5 bg-brand-600 text-white text-sm font-semibold px-4 py-2.5 rounded-xl hover:bg-brand-700 transition">
          <Icon name="plus" className="w-4 h-4" />
          <span className="hidden sm:inline">Nuevo cliente</span>
        </button>
      </div>

      {/* Formulario nuevo cliente */}
      {showAdd && (
        <Card className="fade-in">
          <h3 className="font-semibold text-sm mb-4 flex items-center gap-2"><Icon name="users" className="w-4 h-4 text-brand-600" /> Nuevo cliente</h3>
          <form onSubmit={handleAdd} className="space-y-3">
            <div className="grid sm:grid-cols-2 gap-3">
              <div className="sm:col-span-2">
                <label className="block text-xs text-stone-500 mb-1">Nombre completo *</label>
                <Input value={fNombre} onChange={setFNombre} placeholder="Nombre completo" required />
              </div>
              <div>
                <label className="block text-xs text-stone-500 mb-1">Tipo de documento</label>
                <select value={fTipoDoc} onChange={(e) => setFTipoDoc(e.target.value)}
                  className="w-full px-3 py-2.5 rounded-lg border border-stone-200 text-sm focus:outline-none focus:ring-2 focus:ring-brand-300">
                  <option value="CC">Cédula de ciudadanía (CC)</option>
                  <option value="CE">Cédula de extranjería (CE)</option>
                  <option value="NIT">NIT</option>
                  <option value="PP">Pasaporte</option>
                </select>
              </div>
              <div>
                <label className="block text-xs text-stone-500 mb-1">Número de documento</label>
                <Input value={fCedula} onChange={setFCedula} placeholder="Número de documento" />
              </div>
              <div>
                <label className="block text-xs text-stone-500 mb-1">Teléfono</label>
                <div className="flex gap-2">
                  <input value={fCodigo} onChange={(e) => setFCodigo(e.target.value)} maxLength={4} placeholder="57"
                    className="w-16 px-2 py-2.5 rounded-lg border border-stone-200 text-sm text-center focus:outline-none focus:ring-2 focus:ring-brand-300 font-medium" />
                  <div className="flex-1"><Input value={fTel} onChange={setFTel} placeholder="Teléfono" /></div>
                </div>
              </div>
              <div>
                <label className="block text-xs text-stone-500 mb-1">Email</label>
                <Input value={fEmail} onChange={setFEmail} placeholder="correo@ejemplo.com" />
              </div>
              <div>
                <label className="block text-xs text-stone-500 mb-1">Ciudad</label>
                <Input value={fCiudad} onChange={setFCiudad} placeholder="Manizales" />
              </div>
              <div className="sm:col-span-2">
                <label className="block text-xs text-stone-500 mb-1">Dirección</label>
                <Input value={fDireccion} onChange={setFDireccion} placeholder="Calle / Carrera / Barrio" />
              </div>
            </div>
            <div className="flex gap-2 pt-1">
              <button type="submit" disabled={saving}
                className="flex-1 bg-brand-600 text-white rounded-xl py-2.5 text-sm font-semibold hover:bg-brand-700 disabled:opacity-50">
                {saving ? 'Guardando…' : 'Guardar cliente'}
              </button>
              <button type="button" onClick={() => setShowAdd(false)}
                className="flex-1 bg-stone-100 text-stone-600 rounded-xl py-2.5 text-sm font-semibold hover:bg-stone-200">
                Cancelar
              </button>
            </div>
          </form>
        </Card>
      )}

      <div className="grid gap-2">
        {clientesFiltrados.length === 0 && (
          <div className="bg-white rounded-2xl border border-stone-200 px-5 py-10 text-center text-stone-400 text-sm">
            {q ? 'Sin resultados.' : 'No hay clientes registrados.'}
          </div>
        )}
        {clientesFiltrados.map((c) => {
          const paquetes  = state.paquetes.filter((p) => p.clienteId === c.id);
          const activos   = paquetes.filter((p) => p.sesionesUsadas < p.sesionesTotales);
          const sesiones  = state.sesiones.filter((s) => s.clienteId === c.id);
          const totalGastado = paquetes.reduce((a, p) => a + (p.valorTotal || 0), 0);

          // estado del cliente
          const estado = activos.length > 0 ? 'activo'
            : paquetes.length > 0           ? 'sin sesiones'
            : 'sin paquete';
          const estadoClass = {
            activo:        'bg-emerald-100 text-emerald-700',
            'sin sesiones':'bg-amber-100 text-amber-700',
            'sin paquete': 'bg-stone-100 text-stone-500',
          }[estado];

          // paquete más reciente activo para mostrar progreso
          const paqActivo = activos[0];
          const sv        = paqActivo ? findSv(state, paqActivo.servicioId) : null;
          const pct       = paqActivo ? (paqActivo.sesionesUsadas / paqActivo.sesionesTotales) * 100 : 0;

          return (
            <button key={c.id} onClick={() => setDetalle(c.id)}
              className="w-full text-left bg-white rounded-2xl border border-stone-200 px-4 py-3.5 hover:border-brand-300 hover:bg-brand-50/30 transition group">
              <div className="flex items-start justify-between gap-3">
                {/* Avatar + nombre */}
                <div className="flex items-center gap-3 min-w-0">
                  <div className="w-10 h-10 rounded-full bg-brand-100 text-brand-700 font-bold flex items-center justify-center shrink-0 text-sm">
                    {c.nombre.split(' ').map((n) => n[0]).join('').slice(0, 2).toUpperCase()}
                  </div>
                  <div className="min-w-0">
                    <div className="font-semibold text-sm truncate">{c.nombre}</div>
                    <div className="flex items-center gap-2 flex-wrap mt-0.5">
                      {c.cedula   && <span className="text-xs text-stone-400">{c.cedula}</span>}
                      {c.telefono && (
                        <span className="text-xs text-stone-400 flex items-center gap-1">
                          <svg className="w-3 h-3" fill="none" stroke="currentColor" strokeWidth={2} viewBox="0 0 24 24"><path d="M22 16.92v3a2 2 0 0 1-2.18 2A19.79 19.79 0 0 1 11.62 19 19.45 19.45 0 0 1 5 12.38 19.79 19.79 0 0 1 2.12 4.18 2 2 0 0 1 4.11 2h3a2 2 0 0 1 2 1.72c.13.96.36 1.9.68 2.81a2 2 0 0 1-.45 2.11L8.09 9.91a16 16 0 0 0 6 6l1.27-1.27a2 2 0 0 1 2.11-.45c.91.32 1.85.55 2.81.68A2 2 0 0 1 22 16.92z"/></svg>
                          {c.telefono}
                        </span>
                      )}
                    </div>
                  </div>
                </div>

                {/* Acciones + estado */}
                <div className="flex items-center gap-2 shrink-0">
                  {c.telefono && (
                    <a href={`tel:${c.telefono}`} onClick={(e) => e.stopPropagation()}
                      className="w-8 h-8 rounded-full bg-emerald-100 text-emerald-700 flex items-center justify-center hover:bg-emerald-200 transition"
                      title="Llamar">
                      <svg className="w-4 h-4" fill="none" stroke="currentColor" strokeWidth={2} viewBox="0 0 24 24"><path d="M22 16.92v3a2 2 0 0 1-2.18 2A19.79 19.79 0 0 1 11.62 19 19.45 19.45 0 0 1 5 12.38 19.79 19.79 0 0 1 2.12 4.18 2 2 0 0 1 4.11 2h3a2 2 0 0 1 2 1.72c.13.96.36 1.9.68 2.81a2 2 0 0 1-.45 2.11L8.09 9.91a16 16 0 0 0 6 6l1.27-1.27a2 2 0 0 1 2.11-.45c.91.32 1.85.55 2.81.68A2 2 0 0 1 22 16.92z"/></svg>
                    </a>
                  )}
                  <span className={`text-[10px] px-2 py-0.5 rounded-full font-semibold uppercase ${estadoClass}`}>{estado}</span>
                  <Icon name="chevron" className="w-4 h-4 text-stone-300 group-hover:text-brand-500 transition" />
                </div>
              </div>

              {/* Barra de progreso si hay paquete activo */}
              {paqActivo && (
                <div className="mt-3 pt-3 border-t border-stone-100">
                  <div className="flex items-center justify-between text-xs text-stone-500 mb-1">
                    <span>{sv?.nombre}</span>
                    <span className="tabular-nums">{paqActivo.sesionesUsadas}/{paqActivo.sesionesTotales} sesiones</span>
                  </div>
                  <div className="h-1.5 bg-stone-100 rounded-full overflow-hidden">
                    <div className="h-full bg-brand-500 transition-all" style={{ width: `${pct}%` }} />
                  </div>
                </div>
              )}

              {/* Stats rápidas */}
              <div className="mt-2 flex items-center gap-4 text-xs text-stone-400">
                <span>{paquetes.length} paquete{paquetes.length !== 1 ? 's' : ''}</span>
                <span>{sesiones.length} sesion{sesiones.length !== 1 ? 'es' : ''}</span>
                {totalGastado > 0 && <span>{COP(totalGastado)} total</span>}
              </div>
            </button>
          );
        })}
      </div>
    </div>
  );
}

// ---------- Cliente detalle ----------
function ClienteDetalle({ cliente, state, onBack, onUpdate, onUpdatePaquete, onDeletePaquete, onUpdateVenta, onAddAbono, onDeleteAbono, onUpdateSesion, onDeleteSesion, onSaldarSinPago }) {
  const [abonoPaquete,      setAbonoPaquete]      = useState(null); // {paquete, cliente, servicio}
  const [editSesion,        setEditSesion]        = useState(null); // sesión a editar
  const [verHistSesion,     setVerHistSesion]     = useState(null); // id de sesión cuyo historial se muestra
  const [editarVentaDetalle, setEditarVentaDetalle] = useState(null); // paquete a editar
  const paquetes = state.paquetes.filter((p) => p.clienteId === cliente.id);
  const sesiones = state.sesiones.filter((s) => s.clienteId === cliente.id).sort((a, b) => b.fecha.localeCompare(a.fecha));
  const totalGastado  = paquetes.reduce((a, p) => a + (p.valorTotal || 0), 0);
  const totalSesiones = sesiones.length;
  const activos = paquetes.filter((p) => p.sesionesUsadas < p.sesionesTotales);

  // edición
  const [editing,    setEditing]    = useState(false);
  const [eNombre,    setENombre]    = useState(cliente.nombre    || '');
  const [eTipoDoc,   setETipoDoc]   = useState(cliente.tipo_doc  || 'CC');
  const [eCedula,    setECedula]    = useState(cliente.cedula    || '');
  const [eTel,       setETel]       = useState(cliente.telefono  || '');
  const [eEmail,     setEEmail]     = useState(cliente.email     || '');
  const [eCiudad,    setECiudad]    = useState(cliente.ciudad    || 'Manizales');
  const [eDireccion, setEDireccion] = useState(cliente.direccion || '');
  const [saving,     setSaving]     = useState(false);

  const handleSave = async (e) => {
    e.preventDefault();
    if (!eNombre.trim()) return;
    setSaving(true);
    try {
      await onUpdate(cliente.id, {
        nombre: eNombre.trim(), tipo_doc: eTipoDoc, cedula: eCedula.trim(),
        telefono: eTel.trim(), email: eEmail.trim(),
        ciudad: eCiudad.trim() || 'Manizales', direccion: eDireccion.trim(),
      });
      setEditing(false);
    } catch (err) { alert('Error: ' + err.message); }
    finally { setSaving(false); }
  };

  return (
    <div className="space-y-4 max-w-3xl fade-in">
      {/* Header */}
      <div className="flex items-center gap-3">
        <button onClick={onBack} className="p-2 rounded-xl border border-stone-200 bg-white hover:bg-stone-50">
          <Icon name="arrow-left" className="w-4 h-4" />
        </button>
        <div className="flex-1 min-w-0">
          <h2 className="font-bold text-lg leading-tight truncate">{cliente.nombre}</h2>
          <p className="text-xs text-stone-500">Ficha del cliente</p>
        </div>
        <button onClick={() => setEditing((v) => !v)}
          className={`flex items-center gap-1.5 px-3 py-2 rounded-xl text-sm font-medium border transition ${editing ? 'bg-stone-100 border-stone-200 text-stone-600' : 'bg-white border-stone-200 hover:border-brand-300 hover:bg-brand-50 text-stone-700'}`}>
          <svg className="w-4 h-4" fill="none" stroke="currentColor" strokeWidth={2} viewBox="0 0 24 24"><path d="M11 4H4a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2v-7"/><path d="M18.5 2.5a2.121 2.121 0 0 1 3 3L12 15l-4 1 1-4 9.5-9.5z"/></svg>
          {editing ? 'Cancelar' : 'Editar'}
        </button>
        {cliente.telefono && (
          <a href={`tel:${cliente.telefono}`}
            className="flex items-center gap-2 px-3 py-2 rounded-xl bg-emerald-600 text-white text-sm font-medium hover:bg-emerald-700">
            <svg className="w-4 h-4" fill="none" stroke="currentColor" strokeWidth={2} viewBox="0 0 24 24"><path d="M22 16.92v3a2 2 0 0 1-2.18 2A19.79 19.79 0 0 1 11.62 19 19.45 19.45 0 0 1 5 12.38 19.79 19.79 0 0 1 2.12 4.18 2 2 0 0 1 4.11 2h3a2 2 0 0 1 2 1.72c.13.96.36 1.9.68 2.81a2 2 0 0 1-.45 2.11L8.09 9.91a16 16 0 0 0 6 6l1.27-1.27a2 2 0 0 1 2.11-.45c.91.32 1.85.55 2.81.68A2 2 0 0 1 22 16.92z"/></svg>
            Llamar
          </a>
        )}
      </div>

      {/* Info de contacto — modo edición o lectura */}
      <Card>
        <div className="flex items-center justify-between mb-3">
          <h3 className="font-semibold text-sm">Información de contacto</h3>
        </div>
        {editing ? (
          <form onSubmit={handleSave} className="space-y-3 fade-in">
            <div className="grid sm:grid-cols-2 gap-3">
              <div className="sm:col-span-2">
                <label className="block text-xs text-stone-500 mb-1">Nombre completo *</label>
                <Input value={eNombre} onChange={setENombre} placeholder="Nombre completo" required />
              </div>
              <div>
                <label className="block text-xs text-stone-500 mb-1">Tipo de documento</label>
                <select value={eTipoDoc} onChange={(e) => setETipoDoc(e.target.value)}
                  className="w-full px-3 py-2.5 rounded-lg border border-stone-200 text-sm focus:outline-none focus:ring-2 focus:ring-brand-300">
                  <option value="CC">CC — Cédula de ciudadanía</option>
                  <option value="CE">CE — Cédula de extranjería</option>
                  <option value="NIT">NIT</option>
                  <option value="PP">Pasaporte</option>
                </select>
              </div>
              <div>
                <label className="block text-xs text-stone-500 mb-1">Número de documento</label>
                <Input value={eCedula} onChange={setECedula} placeholder="Número" />
              </div>
              <div>
                <label className="block text-xs text-stone-500 mb-1">Teléfono</label>
                <Input value={eTel} onChange={setETel} placeholder="Ej: 573001234567" />
              </div>
              <div>
                <label className="block text-xs text-stone-500 mb-1">Email</label>
                <Input value={eEmail} onChange={setEEmail} placeholder="correo@ejemplo.com" />
              </div>
              <div>
                <label className="block text-xs text-stone-500 mb-1">Ciudad</label>
                <Input value={eCiudad} onChange={setECiudad} placeholder="Manizales" />
              </div>
              <div className="sm:col-span-2">
                <label className="block text-xs text-stone-500 mb-1">Dirección</label>
                <Input value={eDireccion} onChange={setEDireccion} placeholder="Calle / Carrera / Barrio" />
              </div>
            </div>
            <div className="flex gap-2">
              <button type="submit" disabled={saving}
                className="flex-1 bg-brand-600 text-white rounded-xl py-2.5 text-sm font-semibold hover:bg-brand-700 disabled:opacity-50">
                {saving ? 'Guardando…' : '✓ Guardar cambios'}
              </button>
              <button type="button" onClick={() => setEditing(false)}
                className="flex-1 bg-stone-100 text-stone-600 rounded-xl py-2.5 text-sm font-semibold hover:bg-stone-200">
                Cancelar
              </button>
            </div>
          </form>
        ) : (
          <div className="grid sm:grid-cols-2 gap-3">
            {[
              { label: 'Documento',  value: `${cliente.tipo_doc || 'CC'} ${cliente.cedula || '—'}` },
              { label: 'Teléfono',   value: cliente.telefono || '—' },
              { label: 'Email',      value: cliente.email    || '—' },
              { label: 'Ciudad',     value: cliente.ciudad   || 'Manizales' },
              { label: 'Dirección',  value: cliente.direccion || '—' },
            ].map(({ label, value }) => (
              <div key={label}>
                <div className="text-[11px] uppercase tracking-wide text-stone-400 font-semibold">{label}</div>
                <div className="text-sm font-medium text-stone-800 mt-0.5">{value}</div>
              </div>
            ))}
          </div>
        )}
      </Card>

      {/* KPIs */}
      <div className="grid grid-cols-3 gap-3">
        <div className="bg-white rounded-2xl border border-stone-200 p-4 text-center">
          <div className="text-2xl font-bold">{paquetes.length}</div>
          <div className="text-xs text-stone-500 mt-0.5">Paquetes</div>
        </div>
        <div className="bg-white rounded-2xl border border-stone-200 p-4 text-center">
          <div className="text-2xl font-bold">{totalSesiones}</div>
          <div className="text-xs text-stone-500 mt-0.5">Sesiones</div>
        </div>
        <div className="bg-white rounded-2xl border border-stone-200 p-4 text-center">
          <div className="text-lg font-bold tabular-nums">{COP(totalGastado)}</div>
          <div className="text-xs text-stone-500 mt-0.5">Total invertido</div>
        </div>
      </div>

      {/* Paquetes */}
      <Card>
        <h3 className="font-semibold text-sm mb-3">Paquetes ({paquetes.length})</h3>
        {paquetes.length === 0 ? <Empty text="Sin paquetes." /> : (
          <ul className="space-y-3">
            {paquetes.sort((a, b) => b.fechaCompra.localeCompare(a.fechaCompra)).map((p) => {
              const sv    = findSv(state, p.servicioId);
              const pct   = (p.sesionesUsadas / p.sesionesTotales) * 100;
              const resto = p.sesionesTotales - p.sesionesUsadas;
              const estado = p.sesionesUsadas === p.sesionesTotales ? 'completado' : p.fechaInicio > todayISO() ? 'pendiente' : 'activo';
              const estadoClass = { activo: 'bg-emerald-100 text-emerald-700', pendiente: 'bg-amber-100 text-amber-700', completado: 'bg-stone-100 text-stone-500' }[estado];
              return (
                <li key={p.id} className="p-3 rounded-xl bg-stone-50 border border-stone-100">
                  <div className="flex items-center justify-between gap-2 mb-2">
                    <div className="font-medium text-sm flex items-center gap-1.5">
                      {p.esBono && <span title="Bono regalo">🎁</span>}
                      {sv?.nombre || p.servicioId}
                    </div>
                    <div className="flex items-center gap-1">
                      <span className={`text-[10px] px-2 py-0.5 rounded-full font-semibold uppercase ${estadoClass}`}>{estado}</span>
                      {onUpdateVenta && (
                        <button type="button" onClick={() => setEditarVentaDetalle(p)}
                          className="text-[10px] font-semibold text-brand-600 hover:text-white hover:bg-brand-600 border border-brand-300 rounded-lg px-1.5 py-0.5 transition"
                          title="Editar venta">✏️</button>
                      )}
                      {onDeletePaquete && (
                        <button type="button"
                          title="Eliminar venta"
                          onClick={() => {
                            const tieneSesiones = p.sesionesUsadas > 0;
                            const advertencia = tieneSesiones
                              ? `⚠️ Esta venta tiene ${p.sesionesUsadas} sesión(es). También se eliminarán esas sesiones y sus comisiones.\n\n`
                              : '';
                            if (confirm(`${advertencia}¿Eliminar la venta?\n\nServicio: ${sv?.nombre}\nFecha: ${fmtDate(p.fechaCompra)}\nValor: ${COP(p.valorTotal)}\n\nNo se puede deshacer.`)) {
                              onDeletePaquete(p.id).catch((err) => alert('Error: ' + err.message));
                            }
                          }}
                          className="text-[10px] text-stone-300 hover:text-white hover:bg-rose-500 border border-stone-200 hover:border-rose-500 rounded-lg px-1.5 py-0.5 transition"
                        >🗑️</button>
                      )}
                    </div>
                  </div>
                  {/* bono info */}
                  {p.esBono && (
                    <div className="text-xs text-amber-700 bg-amber-50 rounded-lg px-2 py-1 mb-1.5 flex flex-wrap gap-2">
                      {p.compradorId && p.compradorId !== cliente.id && (
                        <span>💳 Comprado por: <strong>{state.clientes.find((c) => c.id === p.compradorId)?.nombre || 'Desconocido'}</strong></span>
                      )}
                      {p.fechaVencimiento && (
                        <span>⏳ Vence: <strong>{fmtDate(p.fechaVencimiento)}</strong>
                          {(() => { const dv = diasHasta(p.fechaVencimiento); return dv !== null && dv <= 7 ? <span className="ml-1 text-red-600 font-bold">({dv === 0 ? 'HOY' : dv < 0 ? 'VENCIDO' : `en ${dv} días`})</span> : null; })()}
                        </span>
                      )}
                    </div>
                  )}
                  <div className="flex items-center gap-2 mb-1.5">
                    <div className="flex-1 h-2 bg-white rounded-full overflow-hidden border border-stone-200">
                      <div className={`h-full ${pct === 100 ? 'bg-stone-400' : 'bg-brand-500'} transition-all`} style={{ width: `${pct}%` }} />
                    </div>
                    <span className="text-xs text-stone-500 tabular-nums shrink-0">{p.sesionesUsadas}/{p.sesionesTotales}</span>
                  </div>
                  <div className="flex items-center gap-4 text-xs text-stone-500 flex-wrap">
                    <span>Compra: {fmtDate(p.fechaCompra)}</span>
                    <span>Inicio: {fmtDate(p.fechaInicio)}</span>
                    <span>{COP(p.valorTotal)}</span>
                    {p.numeroFactura && <span className="text-violet-700 font-semibold">🧾 {p.numeroFactura}</span>}
                    {resto > 0 && <span className="text-brand-700 font-medium">{resto} sesión{resto !== 1 ? 'es' : ''} restante{resto !== 1 ? 's' : ''}</span>}
                  </div>
                  {p.saldoPendiente > 0 && (
                    <div className="mt-2 bg-rose-50 border border-rose-200 rounded-lg px-3 py-2 space-y-2">
                      <div className="text-xs text-rose-800">
                        💰 Abono pagado: <strong>{COP(p.abonoInicial)}</strong> · Saldo pendiente: <strong>{COP(p.saldoPendiente)}</strong>
                      </div>
                      {/* Historial de abonos posteriores */}
                      {(p.abonos || []).length > 0 && (
                        <div className="text-[11px] text-stone-600 space-y-0.5">
                          {(p.abonos || []).map((a) => (
                            <div key={a.id} className="flex items-center justify-between gap-2 bg-white border border-rose-100 rounded px-2 py-1">
                              <span>
                                <span className="text-stone-400">{fmtDate(a.fecha)}</span>
                                <span className="mx-1">·</span>
                                <span>{pagoIcono(a.metodoPago)}</span>
                                <strong className="ml-1">{COP(a.monto)}</strong>
                                {a.numeroFactura && <span className="text-violet-600 font-semibold ml-1">🧾 {a.numeroFactura}</span>}
                                {a.nota && <span className="text-stone-400 ml-1">— {a.nota}</span>}
                              </span>
                              {onDeleteAbono && (
                                <button type="button"
                                  onClick={() => { if (confirm(`¿Eliminar este abono de ${COP(a.monto)}? El saldo subirá de nuevo.`)) onDeleteAbono(p.id, a.id).catch((err) => alert('Error eliminando abono: ' + err.message)); }}
                                  className="text-stone-300 hover:text-rose-600 text-xs">🗑️</button>
                              )}
                            </div>
                          ))}
                        </div>
                      )}
                      {(onAddAbono || onUpdatePaquete) && (
                        <div className="flex gap-2">
                          {onAddAbono && (
                            <button type="button"
                              onClick={() => setAbonoPaquete({ paquete: p, cliente, servicio: findSv(state, p.servicioId) })}
                              className="flex-1 text-xs font-semibold text-rose-700 bg-white border border-rose-300 hover:bg-rose-100 px-2 py-1.5 rounded-lg transition">
                              💵 Registrar abono
                            </button>
                          )}
                          {onSaldarSinPago && (
                            <button type="button"
                              onClick={() => { if (confirm(`¿Marcar saldo completo de ${COP(p.saldoPendiente)} como cobrado?\n\n⚠️ Esto NO registra método de pago ni queda en el balance del día. Si quieres que quede registrado, usa "Registrar abono".\n\n🔒 Esta acción queda registrada en la auditoría con tu usuario.`)) onSaldarSinPago(p.id).catch((err) => alert('Error: ' + err.message)); }}
                              className="flex-1 text-xs font-semibold text-white bg-rose-600 hover:bg-rose-700 px-2 py-1.5 rounded-lg transition">
                              ✓ Saldar sin pago
                            </button>
                          )}
                        </div>
                      )}
                    </div>
                  )}
                  {/* Historial de abonos cuando el paquete YA está saldado (saldo = 0) */}
                  {p.saldoPendiente <= 0 && (p.abonos || []).length > 0 && (
                    <div className="mt-2 bg-emerald-50 border border-emerald-200 rounded-lg px-3 py-2 space-y-1">
                      <div className="text-xs text-emerald-800 font-semibold flex items-center gap-1">
                        ✅ Pagado completo · {(p.abonos || []).length} abono{(p.abonos || []).length !== 1 ? 's' : ''}
                      </div>
                      <div className="text-[11px] text-stone-600 space-y-0.5">
                        {(p.abonos || []).map((a) => (
                          <div key={a.id} className="flex items-center justify-between gap-2 bg-white border border-emerald-100 rounded px-2 py-1">
                            <span>
                              <span className="text-stone-400">{fmtDate(a.fecha)}</span>
                              <span className="mx-1">·</span>
                              <span>{pagoIcono(a.metodoPago)}</span>
                              <strong className="ml-1">{COP(a.monto)}</strong>
                              {a.numeroFactura && <span className="text-violet-600 font-semibold ml-1">🧾 {a.numeroFactura}</span>}
                              {a.nota && <span className="text-stone-400 ml-1">— {a.nota}</span>}
                            </span>
                            {onDeleteAbono && (
                              <button type="button"
                                onClick={() => { if (confirm(`¿Eliminar este abono de ${COP(a.monto)}? El saldo subirá de nuevo.`)) onDeleteAbono(p.id, a.id).catch((err) => alert('Error eliminando abono: ' + err.message)); }}
                                className="text-stone-300 hover:text-rose-600 text-xs">🗑️</button>
                            )}
                          </div>
                        ))}
                      </div>
                    </div>
                  )}
                </li>
              );
            })}
          </ul>
        )}
      </Card>

      {/* Historial de sesiones */}
      <Card>
        <h3 className="font-semibold text-sm mb-3">Historial de sesiones ({sesiones.length})</h3>
        {sesiones.length === 0 ? <Empty text="Sin sesiones registradas." /> : (() => {
          // Numeración por paquete: agrupar sesiones por paqueteId y ordenarlas cronológicamente
          // para asignar #1 a la más antigua dentro de cada paquete.
          // Las sesiones sueltas (sin paqueteId) se cuentan de forma independiente.
          const numPorSesion = {};
          const totalPorPaquete = {};
          const sesionesPorPaquete = {};
          sesiones.forEach((s) => {
            const key = s.paqueteId || ('suelta_' + s.id);
            if (!sesionesPorPaquete[key]) sesionesPorPaquete[key] = [];
            sesionesPorPaquete[key].push(s);
          });
          Object.entries(sesionesPorPaquete).forEach(([key, arr]) => {
            const ordenadas = [...arr].sort((a, b) => a.fecha.localeCompare(b.fecha));
            const paquete = arr[0].paqueteId ? state.paquetes.find((p) => p.id === arr[0].paqueteId) : null;
            const total = paquete?.sesionesTotales || 1;
            totalPorPaquete[key] = total;
            ordenadas.forEach((s, i) => { numPorSesion[s.id] = i + 1; });
          });
          return (
          <ul className="divide-y divide-stone-100">
            {sesiones.map((s) => {
              const fisio = state.fisios.find((f) => f.id === s.fisioId);
              const sv    = findSv(state, s.servicioId);
              const extras = (s.fisiosAdicionales || []).map((f) => state.fisios.find((x) => x.id === f.fisioId)?.nombre).filter(Boolean);
              const tieneHist = (s.historial || []).length > 0;
              const key = s.paqueteId || ('suelta_' + s.id);
              const numSesion = numPorSesion[s.id] || 1;
              const totalSesiones = totalPorPaquete[key] || 1;
              const esPaquete = !!s.paqueteId && totalSesiones > 1;
              return (
                <li key={s.id} className="py-2.5 text-sm">
                  <div className="flex items-center justify-between gap-3">
                    <div className="min-w-0">
                      <div className="font-medium text-xs text-stone-500 flex items-center gap-1.5">
                        {esPaquete ? (
                          <span className="inline-flex items-center justify-center rounded-full bg-brand-100 text-brand-700 font-bold text-[10px] shrink-0 px-1.5 h-5 whitespace-nowrap">
                            {numSesion}/{totalSesiones}
                          </span>
                        ) : (
                          <span className="inline-flex items-center justify-center w-5 h-5 rounded-full bg-stone-100 text-stone-500 font-bold text-[10px] shrink-0">
                            1
                          </span>
                        )}
                        {fmtDate(s.fecha)}
                      </div>
                      <div className="truncate">{sv?.nombre || s.servicioId}</div>
                      {fisio && <div className="text-xs text-stone-400">{fisio.nombre}{extras.length > 0 ? ` + ${extras.join(' + ')}` : ''}</div>}
                    </div>
                    <div className="text-right shrink-0">
                      <div className="font-semibold tabular-nums">{COP(s.valorSesion)}</div>
                      <div className="text-xs text-stone-400">Fisio: {COP(s.comisionFisio)}</div>
                    </div>
                    <div className="flex gap-1 shrink-0">
                      {onUpdateSesion && (
                        <button type="button" onClick={() => setEditSesion(s)}
                          title="Editar sesión"
                          className="text-xs font-semibold text-brand-600 hover:text-white hover:bg-brand-600 border border-brand-300 rounded-lg px-2 py-1.5 transition">
                          ✏️ Editar
                        </button>
                      )}
                      {onDeleteSesion && (
                        <button type="button"
                          title="Eliminar sesión"
                          onClick={() => {
                            const fisioNombre = state.fisios.find((f) => f.id === s.fisioId)?.nombre || '—';
                            const svNombre = findSv(state, s.servicioId)?.nombre || '—';
                            if (confirm(`⚠️ ¿Eliminar esta sesión?\n\n📅 Fecha: ${fmtDate(s.fecha)}\n💆 Servicio: ${svNombre}\n👤 Fisio: ${fisioNombre}\n💰 Valor: ${COP(s.valorSesion)}\n\n${s.paqueteId ? 'Esto devolverá 1 sesión al paquete.' : ''}\n\nEsta acción no se puede deshacer.`)) {
                              onDeleteSesion(s.id).catch((err) => alert('Error eliminando sesión: ' + err.message));
                            }
                          }}
                          className="text-xs font-semibold text-stone-400 hover:text-white hover:bg-rose-500 border border-stone-200 hover:border-rose-500 rounded-lg px-2 py-1.5 transition">
                          🗑️
                        </button>
                      )}
                    </div>
                  </div>
                  {tieneHist && (
                    <div className="mt-1">
                      <button type="button" onClick={() => setVerHistSesion((v) => v === s.id ? null : s.id)}
                        className="text-[11px] text-stone-400 hover:text-stone-600 flex items-center gap-1">
                        🕓 {(s.historial || []).length} {(s.historial || []).length === 1 ? 'cambio' : 'cambios'} {verHistSesion === s.id ? '▲' : '▼'}
                      </button>
                      {verHistSesion === s.id && (
                        <div className="mt-1 space-y-1 bg-stone-50 border border-stone-200 rounded-lg p-2">
                          {(s.historial || []).slice().reverse().map((h, i) => (
                            <div key={i} className="text-[11px] text-stone-600">
                              <span className="text-stone-400">{fmtDateTime(h.ts)}</span>
                              <ul className="ml-3 list-disc">
                                {(h.cambios || []).map((c, j) => <li key={j}>{c}</li>)}
                              </ul>
                            </div>
                          ))}
                        </div>
                      )}
                    </div>
                  )}
                </li>
              );
            })}
          </ul>
          );
        })()}
      </Card>
      {abonoPaquete && onAddAbono && (
        <AbonoModal
          paquete={abonoPaquete.paquete}
          cliente={abonoPaquete.cliente}
          servicio={abonoPaquete.servicio}
          onClose={() => setAbonoPaquete(null)}
          onAddAbono={onAddAbono}
        />
      )}
      {editSesion && onUpdateSesion && (
        <EditarSesionModal
          sesion={editSesion}
          state={state}
          onClose={() => setEditSesion(null)}
          onUpdateSesion={onUpdateSesion}
        />
      )}
      {editarVentaDetalle && onUpdateVenta && (
        <EditarVentaModal
          paquete={editarVentaDetalle}
          state={state}
          onClose={() => setEditarVentaDetalle(null)}
          onUpdateVenta={onUpdateVenta}
        />
      )}
    </div>
  );
}

// ---------- Cuentas por cobrar ----------
function CuentasPorCobrar({ state, onAddAbono, onUpdatePaquete }) {
  const [abonoPaquete, setAbonoPaquete] = useState(null);
  const hoy = todayISO();

  const deudas = useMemo(() => {
    return state.paquetes
      .filter((p) => Number(p.saldoPendiente || 0) > 0)
      .map((p) => {
        const cliente  = state.clientes.find((c) => c.id === p.clienteId);
        const servicio = findSv(state, p.servicioId);
        const dias = Math.floor((new Date(hoy + 'T12:00:00') - new Date(p.fechaCompra + 'T12:00:00')) / 86400000);
        return { ...p, clienteNombre: cliente?.nombre || 'Desconocido', clienteTel: cliente?.telefono || '', servicioNombre: servicio?.nombre || '', dias };
      })
      .sort((a, b) => b.dias - a.dias); // el más viejo primero
  }, [state, hoy]);

  const totalCobrar = deudas.reduce((a, p) => a + Number(p.saldoPendiente || 0), 0);
  const numClientes = new Set(deudas.map((d) => d.clienteId)).size;

  const waLink = (tel, nombre, saldo) => {
    const t = (tel || '').replace(/\D/g, '');
    if (!t) return null;
    const msg = encodeURIComponent(`Hola ${nombre?.split(' ')[0] || ''} 😊 Te recordamos tu saldo pendiente de ${COP(saldo)} en Massage Therapy Manizales. ¡Mil gracias!`);
    return `https://wa.me/${t}?text=${msg}`;
  };

  return (
    <div className="max-w-3xl mx-auto space-y-4 fade-in">
      <div>
        <h2 className="text-xl font-bold">Cuentas por cobrar</h2>
        <p className="text-sm text-stone-500">Clientes que quedaron a crédito o con saldo pendiente. El dinero entra cuando registres el pago.</p>
      </div>

      {/* Resumen */}
      <div className="grid grid-cols-2 gap-3">
        <div className="bg-white rounded-2xl border border-stone-200 p-4">
          <div className="text-[11px] text-stone-500 uppercase tracking-wide font-semibold">Total por cobrar</div>
          <div className="text-2xl font-bold text-rose-600 tabular-nums">{COP(totalCobrar)}</div>
        </div>
        <div className="bg-white rounded-2xl border border-stone-200 p-4">
          <div className="text-[11px] text-stone-500 uppercase tracking-wide font-semibold">Clientes que deben</div>
          <div className="text-2xl font-bold text-stone-800 tabular-nums">{numClientes}</div>
        </div>
      </div>

      {deudas.length === 0 ? (
        <div className="bg-white rounded-2xl border border-stone-200 p-8 text-center">
          <div className="text-3xl mb-2">🎉</div>
          <p className="text-stone-500 text-sm font-medium">No hay cuentas por cobrar. ¡Todo al día!</p>
        </div>
      ) : (
        <div className="bg-white rounded-2xl border border-stone-200 overflow-hidden">
          <ul className="divide-y divide-stone-100">
            {deudas.map((p) => {
              const wa = waLink(p.clienteTel, p.clienteNombre, p.saldoPendiente);
              const urgente = p.dias > 30;
              return (
                <li key={p.id} className="p-4 flex flex-col sm:flex-row sm:items-center gap-3">
                  <div className="min-w-0 flex-1">
                    <div className="font-semibold truncate">{p.clienteNombre}</div>
                    <div className="text-xs text-stone-500 truncate">{p.servicioNombre}{p.esBono ? ' · 🎁 Bono' : ''}</div>
                    <div className="text-[11px] text-stone-400 mt-0.5">
                      Compró {fmtDate(p.fechaCompra)} · <span className={urgente ? 'text-rose-500 font-semibold' : ''}>hace {p.dias} {p.dias === 1 ? 'día' : 'días'}</span>
                      {p.abonoInicial > 0 && <> · Abonado {COP(p.abonoInicial)}</>}
                    </div>
                  </div>
                  <div className="text-right shrink-0 sm:w-28">
                    <div className="text-[10px] text-stone-400 uppercase tracking-wide">Debe</div>
                    <div className="font-bold text-rose-600 tabular-nums text-lg">{COP(p.saldoPendiente)}</div>
                    <div className="text-[10px] text-stone-400">de {COP(p.valorTotal)}</div>
                  </div>
                  <div className="flex gap-2 shrink-0">
                    {wa && (
                      <a href={wa} target="_blank" rel="noopener noreferrer"
                        title="Abrir WhatsApp con un recordatorio (tú lo envías)"
                        className="px-3 py-2 rounded-xl border border-emerald-200 bg-emerald-50 text-emerald-700 text-xs font-semibold hover:bg-emerald-100 whitespace-nowrap">
                        💬 Recordar
                      </a>
                    )}
                    <button type="button"
                      onClick={() => setAbonoPaquete({ paquete: p, cliente: state.clientes.find((c) => c.id === p.clienteId), servicio: findSv(state, p.servicioId) })}
                      className="px-3 py-2 rounded-xl bg-brand-600 text-white text-xs font-semibold hover:bg-brand-700 whitespace-nowrap">
                      💰 Registrar pago
                    </button>
                  </div>
                </li>
              );
            })}
          </ul>
        </div>
      )}

      {abonoPaquete && (
        <AbonoModal
          paquete={abonoPaquete.paquete}
          cliente={abonoPaquete.cliente}
          servicio={abonoPaquete.servicio}
          onClose={() => setAbonoPaquete(null)}
          onAddAbono={onAddAbono}
        />
      )}
    </div>
  );
}

// ---------- Modal: Editar sesión (con historial de cambios) ----------
function EditarSesionModal({ sesion, state, onClose, onUpdateSesion }) {
  const sv = findSv(state, sesion.servicioId);
  const cliente = state.clientes.find((c) => c.id === sesion.clienteId);
  const [fecha,       setFecha]       = useState(sesion.fecha);
  const [fisioId,     setFisioId]     = useState(sesion.fisioId);
  const [fisiosExtra, setFisiosExtra] = useState((sesion.fisiosAdicionales || []).map((f) => f.fisioId));
  const [valorSesion, setValorSesion] = useState(sesion.valorSesion || 0);
  // Monto de comisión en pesos: usa el total guardado (comisionFisio * numFisios) como base
  const montoInicial = comisionTotalSesion(sesion);
  const [comisionMonto, setComisionMonto] = useState(montoInicial);
  const [saving,        setSaving]        = useState(false);

  // Si el fisio principal coincide con un extra, limpiar ese slot
  useEffect(() => {
    setFisiosExtra((prev) => prev.map((id) => id === fisioId ? '' : id));
  }, [fisioId]);

  const numFisios        = 1 + fisiosExtra.filter(Boolean).length;
  const comisionTotal    = Math.max(0, Math.round(Number(comisionMonto) || 0));
  const comisionPorFisio = Math.round(comisionTotal / numFisios);

  // Fisios disponibles para un slot (excluye principal y otros slots)
  const fisiosDisponibles = (slotIdx) => {
    const usados = new Set([fisioId, ...fisiosExtra.filter((_, i) => i !== slotIdx).filter(Boolean)]);
    return state.fisios.filter((f) => !usados.has(f.id));
  };

  const cambios = [];
  if (sesion.fecha !== fecha) cambios.push('fecha');
  if (sesion.fisioId !== fisioId) cambios.push('fisio');
  if ((sesion.fisiosAdicionales || []).map((f) => f.fisioId).join(',') !== fisiosExtra.filter(Boolean).join(',')) cambios.push('fisios extra');
  if (Number(sesion.valorSesion) !== Number(valorSesion)) cambios.push('valor');
  if (montoInicial !== comisionTotal) cambios.push('comisión');
  const huboCambios = cambios.length > 0;

  const submit = async (e) => {
    e.preventDefault();
    if (!fisioId) { alert('Selecciona el fisioterapeuta'); return; }
    if (fisiosExtra.some((id) => !id)) { alert('Hay fisioterapeutas sin seleccionar. Completa o elimina los espacios vacíos.'); return; }
    if (!huboCambios) { onClose(); return; }
    if (!confirmarFechaRazonable(fecha, 'la fecha de la sesión')) return;
    setSaving(true);
    try {
      const fisiosAdicionalesData = fisiosExtra.filter(Boolean).map((fid) => ({ fisioId: fid, comision: comisionPorFisio }));
      const nuevaSesion = {
        ...sesion,
        fecha,
        fisioId,
        valorSesion: Number(valorSesion),
        comisionFisio: comisionPorFisio,
        comisionPct: Number(valorSesion) > 0 ? Math.round(comisionTotal / Number(valorSesion) * 100) : 0,
        fisio2Id: fisiosAdicionalesData[0]?.fisioId || null,
        comisionFisio2: fisiosAdicionalesData[0]?.comision || 0,
        fisiosAdicionales: fisiosAdicionalesData,
        // Reflejar el valor en los montos cuando el método de pago es simple
        montoEfectivo:      sesion.metodoPago === 'efectivo'      ? Number(valorSesion) : sesion.montoEfectivo,
        montoTransferencia: sesion.metodoPago === 'transferencia' ? Number(valorSesion) : sesion.montoTransferencia,
        montoDatafono:      sesion.metodoPago === 'datafono'      ? Number(valorSesion) : sesion.montoDatafono,
      };
      await onUpdateSesion(sesion.id, nuevaSesion, sesion);
      onClose();
    } catch (err) {
      alert('Error guardando los cambios: ' + err.message);
    } finally {
      setSaving(false);
    }
  };

  return (
    <div className="fixed inset-0 bg-black/40 z-50 flex items-end sm:items-center justify-center p-3 fade-in" onClick={onClose}>
      <div className="w-full sm:max-w-lg bg-white rounded-2xl shadow-2xl max-h-[92vh] overflow-y-auto" onClick={(e) => e.stopPropagation()}>
        <div className="px-5 py-4 border-b border-stone-200 flex items-center justify-between sticky top-0 bg-white">
          <div>
            <h3 className="font-bold text-base">✏️ Editar sesión</h3>
            <p className="text-xs text-stone-500">{cliente?.nombre} · {sv?.nombre}</p>
          </div>
          <button onClick={onClose} className="w-8 h-8 rounded-lg hover:bg-stone-100 text-stone-500 text-lg">✕</button>
        </div>

        <form onSubmit={submit} className="p-5 space-y-4">
          {/* Fecha */}
          <div>
            <Label>Fecha de la sesión</Label>
            <input type="date" value={fecha} onChange={(e) => setFecha(e.target.value)} min={FECHA_MIN} max={todayISO()} required
              className="w-full px-3 py-2.5 rounded-lg border border-stone-200 bg-white text-sm focus:outline-none focus:border-brand-500 focus:ring-2 focus:ring-brand-100" />
          </div>

          {/* Fisio principal */}
          <div>
            <Label>Fisioterapeuta principal</Label>
            <Select value={fisioId} onChange={setFisioId}>
              {state.fisios.map((f) => <option key={f.id} value={f.id}>{f.nombre}</option>)}
            </Select>
          </div>

          {/* Fisios adicionales */}
          <div className="space-y-2">
            <div className="flex items-center justify-between">
              <Label className="mb-0">Fisios adicionales (4 manos)</Label>
              {fisiosExtra.length < 3 && (
                <button type="button" onClick={() => setFisiosExtra((p) => [...p, ''])}
                  className="text-xs font-semibold text-brand-600 hover:text-brand-800 px-2 py-1 rounded-lg hover:bg-brand-50 transition">
                  ➕ Agregar fisio
                </button>
              )}
            </div>
            {fisiosExtra.length === 0 && <p className="text-xs text-stone-400">Solo el fisio principal atiende esta sesión.</p>}
            {fisiosExtra.map((fid, i) => (
              <div key={i} className="flex gap-2 items-center">
                <div className="flex-1">
                  <Select value={fid} onChange={(v) => setFisiosExtra((p) => p.map((x, j) => j === i ? v : x))}>
                    <option value="">— Selecciona fisio —</option>
                    {fisiosDisponibles(i).map((f) => <option key={f.id} value={f.id}>{f.nombre}</option>)}
                  </Select>
                </div>
                <button type="button" onClick={() => setFisiosExtra((p) => p.filter((_, j) => j !== i))}
                  className="text-stone-300 hover:text-rose-500 text-lg shrink-0">✕</button>
              </div>
            ))}
          </div>

          {/* Valor */}
          <div>
            <Label>Valor de la sesión</Label>
            <input type="number" value={valorSesion} onChange={(e) => setValorSesion(e.target.value)} min="0" step="1000"
              className="w-full px-3 py-2.5 rounded-lg border border-stone-200 bg-white text-base font-semibold focus:outline-none focus:border-brand-500 focus:ring-2 focus:ring-brand-100" />
          </div>

          {/* Comisión en pesos */}
          <div>
            <Label>Comisión del fisio ($)</Label>
            <div className="flex items-center gap-2">
              <input type="number" value={comisionMonto} onChange={(e) => setComisionMonto(e.target.value)} min="0" max={valorSesion} step="1"
                className="w-36 px-3 py-2.5 rounded-lg border border-stone-200 bg-white text-base font-semibold focus:outline-none focus:border-brand-500 focus:ring-2 focus:ring-brand-100" />
              <div className="flex gap-1 flex-wrap">
                {[50, 40, 30, 0].map((pct) => {
                  const monto = Math.round(Number(valorSesion) * pct / 100);
                  return (
                    <button key={pct} type="button" onClick={() => setComisionMonto(monto)}
                      className={`text-[11px] px-2 py-1 rounded-full font-medium ${comisionTotal === monto ? 'bg-brand-600 text-white' : 'bg-stone-100 text-stone-600 hover:bg-stone-200'}`}>
                      {COP(monto)}
                    </button>
                  );
                })}
              </div>
            </div>
          </div>

          {/* Resumen comisión */}
          <div className="bg-stone-50 border border-stone-200 rounded-xl p-3 text-sm space-y-1">
            <div className="flex justify-between text-stone-600"><span>Fisioterapeutas:</span><strong>{numFisios}</strong></div>
            <div className="flex justify-between text-stone-600"><span>Comisión total:</span><strong>{COP(comisionTotal)}</strong></div>
            <div className="flex justify-between text-emerald-700 border-t border-stone-200 pt-1 mt-1"><span>Comisión por fisio:</span><strong>{COP(comisionPorFisio)}</strong></div>
          </div>

          {/* Historial previo */}
          {(sesion.historial || []).length > 0 && (
            <div className="text-xs text-stone-500">
              <p className="font-semibold mb-1">🕓 Cambios anteriores:</p>
              <div className="space-y-1 bg-stone-50 border border-stone-200 rounded-lg p-2 max-h-32 overflow-y-auto">
                {(sesion.historial || []).slice().reverse().map((h, i) => (
                  <div key={i}>
                    <span className="text-stone-400">{fmtDateTime(h.ts)}</span>
                    <ul className="ml-3 list-disc">{(h.cambios || []).map((c, j) => <li key={j}>{c}</li>)}</ul>
                  </div>
                ))}
              </div>
            </div>
          )}

          <div className="flex gap-2 pt-1">
            <button type="button" onClick={onClose} className="flex-1 py-2.5 rounded-xl border border-stone-300 bg-white text-sm font-medium hover:bg-stone-50">Cancelar</button>
            <button disabled={saving || !huboCambios} className="flex-1 py-2.5 rounded-xl bg-brand-600 text-white font-semibold hover:bg-brand-700 disabled:opacity-50 flex items-center justify-center gap-2">
              {saving ? <><Icon name="loader" className="w-4 h-4 animate-spin" />Guardando…</> : huboCambios ? 'Guardar cambios' : 'Sin cambios'}
            </button>
          </div>
        </form>
      </div>
    </div>
  );
}

// ---------- Registrar sesión ----------
function RegistrarSesion({ state, onSave, onDone }) {
  const [tipo,         setTipo]         = useState('paquete');
  // Qué se regaló (masaje, fisioterapia…). Dani lo necesita para saber
  // en qué se le está yendo la plata de publicidad.
  const [servicioCortesia, setServicioCortesia] = useState('');
  const [busqueda,     setBusqueda]     = useState('');
  const [clienteId,    setClienteId]    = useState('');
  const [paqueteId,    setPaqueteId]    = useState('');
  const [fisioId,      setFisioId]      = useState(state.fisios[0]?.id || '');
  const [fecha,        setFecha]        = useState(todayISO());
  const [saving,       setSaving]       = useState(false);
  const [done,         setDone]         = useState(null);
  // — fisios adicionales (hasta 3 extras = 4 total) —
  // Array de fisioId strings; '' = slot vacío (usuario aún no eligió)
  const [fisiosExtra,  setFisiosExtra]  = useState([]);
  // — monto total de comisión en pesos para esta sesión (default 50% del valor) —
  const [comisionMonto, setComisionMonto] = useState(0);

  // ── Clientes filtrados por búsqueda ──────────────────────────────────────
  const clientesFiltrados = useMemo(() => {
    const q = busqueda.toLowerCase().trim();
    const lista = state.clientes; // siempre todos — el usuario elige el paquete después
    if (!q) return lista;
    return lista.filter((c) =>
      c.nombre.toLowerCase().includes(q) || (c.cedula || '').includes(q) || (c.telefono || '').includes(q),
    );
  }, [state.clientes, busqueda]);

  // Seleccionar primer cliente al cambiar búsqueda
  useEffect(() => {
    if (clientesFiltrados.length > 0 && !clientesFiltrados.find((c) => c.id === clienteId)) {
      setClienteId(clientesFiltrados[0].id);
    }
  }, [clientesFiltrados]);

  // Si el fisio 1 cambia, limpiar cualquier slot extra que lo tenga duplicado
  useEffect(() => {
    setFisiosExtra((prev) => prev.map((id) => id === fisioId ? '' : id));
  }, [fisioId]);

  // Paquetes activos del cliente seleccionado (sin sesiones agotadas ni bonos vencidos)
  const paquetesCliente = useMemo(
    () => state.paquetes.filter((p) =>
      p.clienteId === clienteId &&
      p.sesionesUsadas < p.sesionesTotales
      // Nota: NO se excluyen los vencidos — Dani debe poder registrar la sesión
      // aunque el bono esté vencido. Se muestra una etiqueta "VENCIDO" en su lugar.
    ),
    [state.paquetes, clienteId],
  );

  // Cuando cambia el cliente, actualizar el paquete seleccionado
  useEffect(() => {
    setPaqueteId(paquetesCliente[0]?.id || '');
  }, [clienteId, tipo]);

  const esCortesia = tipo === 'cortesia';

  // Una cortesía no genera ingreso: su valor SIEMPRE es 0.
  // El costo del negocio es la comisión del fisio, que se escribe a mano
  // porque cambia según sea masaje o sesión de fisioterapia.
  const valorSesion = useMemo(() => {
    if (esCortesia) return 0;
    const p = state.paquetes.find((x) => x.id === paqueteId);
    return p ? Math.round(p.valorTotal / p.sesionesTotales) : 0;
  }, [paqueteId, state, esCortesia]);

  // Al cambiar de paquete, precargar comisión = 50% del valor de sesión por defecto
  useEffect(() => {
    // En cortesía no hay valor de venta del que sacar el 50%: lo pone Dani.
    if (esCortesia) return;
    setComisionMonto(Math.round(valorSesion / 2));
  }, [paqueteId, esCortesia]);

  // Comisión dividida igualmente entre todos los fisios asignados
  const numFisios        = 1 + fisiosExtra.filter(Boolean).length;
  const comisionTotal    = Math.max(0, Math.round(Number(comisionMonto) || 0));
  const comisionPorFisio = Math.round(comisionTotal / numFisios);

  const clienteSeleccionado = state.clientes.find((c) => c.id === clienteId);

  const submit = async (e) => {
    e.preventDefault();
    if (!clienteId) { alert('Selecciona un cliente'); return; }
    if (!fisioId) { alert('Selecciona un fisioterapeuta'); return; }
    // La cortesía no viene de una venta: se salta esta validación.
    if (!esCortesia && !paqueteId) {
      alert(tipo === 'suelta'
        ? 'Este cliente no tiene ventas sueltas pendientes. Registra la venta primero en "Nueva venta".'
        : 'Este cliente no tiene paquetes activos. Registra primero una venta.');
      return;
    }
    if (esCortesia && comisionTotal <= 0) {
      alert('Escribe cuánto se le paga al fisioterapeuta por esta cortesía.');
      return;
    }
    if (esCortesia && !servicioCortesia) {
      alert('Elige qué servicio se regaló (masaje, fisioterapia…).');
      return;
    }
    // Validar slots de fisios extra: si hay slots vacíos, bloquear
    if (fisiosExtra.some((id) => !id)) {
      alert('Hay fisioterapeutas sin seleccionar. Completa la selección o elimina los espacios vacíos.');
      return;
    }
    if (!confirmarFechaRazonable(fecha, 'la fecha de la sesión')) return;
    setSaving(true);
    try {
      // En cortesía no hay paquete que consumir; en el resto sí debe existir.
      const pSel = esCortesia ? null : state.paquetes.find((x) => x.id === paqueteId);
      if (!esCortesia && !pSel) { alert('El paquete ya no está disponible. Recarga la página.'); setSaving(false); return; }
      const fisiosAdicionalesData = fisiosExtra.filter(Boolean).map((fid) => ({ fisioId: fid, comision: comisionPorFisio }));
      const sesion = {
        id: uid(), clienteId, fisioId, fecha,
        valorSesion,
        comisionFisio: comisionPorFisio,
        comisionPct: valorSesion > 0 ? Math.round(comisionTotal / valorSesion * 100) : 0,
        // Sin venta asociada cuando es cortesía
        paqueteId: esCortesia ? null : paqueteId,
        servicioId: esCortesia ? servicioCortesia : (pSel?.servicioId || ''),
        esCortesia,
        // Una cortesía no mueve caja: no entra plata por ningún medio de pago
        metodoPago: 'efectivo',
        montoEfectivo: esCortesia ? 0 : valorSesion,
        montoTransferencia: 0,
        fisio2Id: fisiosAdicionalesData[0]?.fisioId || null,
        comisionFisio2: fisiosAdicionalesData[0]?.comision || 0,
        fisiosAdicionales: fisiosAdicionalesData,
      };
      // El segundo argumento descuenta una sesión del paquete: en cortesía va nulo.
      await onSave(sesion, esCortesia ? null : paqueteId);
      setDone({
        clienteNombre:    clienteSeleccionado?.nombre,
        fisioNombres:     [fisioId, ...fisiosExtra.filter(Boolean)].map((id) => state.fisios.find((f) => f.id === id)?.nombre).filter(Boolean),
        valorSesion,
        comisionPorFisio,
        numFisios,
      });
    } catch (err) {
      alert('Error guardando: ' + err.message);
    } finally {
      setSaving(false);
    }
  };

  if (done) {
    const etiquetaManos = done.numFisios === 1 ? '' : done.numFisios === 2 ? ' 👫' : ` 🙌 ${done.numFisios * 2} manos`;
    return (
      <div className="max-w-lg mx-auto fade-in">
        <Card className="text-center">
          <div className="w-14 h-14 mx-auto mb-3 rounded-full bg-emerald-100 text-emerald-600 flex items-center justify-center"><Icon name="check" className="w-7 h-7" /></div>
          <h2 className="text-xl font-bold mb-1">Sesión registrada{etiquetaManos}</h2>
          <p className="text-stone-500 text-sm mb-5">
            {done.clienteNombre} · {done.fisioNombres.join(' + ')}
          </p>
          <div className="grid gap-2 mb-5" style={{ gridTemplateColumns: `repeat(${Math.min(done.numFisios + 1, 4)}, 1fr)` }}>
            <div className="p-3 rounded-lg bg-stone-50"><div className="text-xs text-stone-500">Valor sesión</div><div className="font-bold">{COP(done.valorSesion)}</div></div>
            {done.fisioNombres.map((n, i) => (
              <div key={i} className="p-3 rounded-lg bg-rose-50 text-rose-900">
                <div className="text-xs">{done.numFisios > 1 ? n.split(' ')[0] : 'Comisión fisio'}</div>
                <div className="font-bold">{COP(done.comisionPorFisio)}</div>
              </div>
            ))}
          </div>
          <div className="flex flex-col sm:flex-row gap-2">
            <button onClick={() => setDone(null)} className="flex-1 px-4 py-2.5 rounded-lg border border-stone-200 text-sm font-medium hover:bg-stone-50">Otra sesión</button>
            <button onClick={onDone} className="flex-1 px-4 py-2.5 rounded-lg bg-brand-600 text-white text-sm font-medium hover:bg-brand-700">Ver liquidación</button>
          </div>
        </Card>
      </div>
    );
  }

  return (
    <div className="max-w-2xl">
      <Card>
        <form onSubmit={submit} className="space-y-5">

          {/* Tipo */}
          <div>
            <Label>Tipo de sesión</Label>
            <div className="flex rounded-lg p-0.5 bg-stone-100 text-sm">
              {[{ v: 'paquete', l: '📦 De un paquete' }, { v: 'suelta', l: '💆 Individual' }, { v: 'cortesia', l: '🎁 Cortesía' }].map((o) => (
                <button key={o.v} type="button" onClick={() => { setTipo(o.v); setBusqueda(''); }}
                  className={`flex-1 py-2 rounded-md font-medium transition ${tipo === o.v ? 'bg-white shadow-sm text-stone-900' : 'text-stone-500'}`}>
                  {o.l}
                </button>
              ))}
            </div>
          </div>

          {/* Buscar cliente */}
          <div>
            <Label>Buscar cliente ({state.clientes.length} registrados)</Label>
            <div className="relative">
              <input
                value={busqueda}
                onChange={(e) => setBusqueda(e.target.value)}
                placeholder="Escribe nombre, cédula o teléfono…"
                className="w-full px-4 py-2.5 rounded-xl border border-stone-200 bg-stone-50 text-sm focus:outline-none focus:border-brand-500 focus:ring-2 focus:ring-brand-100 focus:bg-white"
              />
              {busqueda && (
                <button type="button" onClick={() => setBusqueda('')}
                  className="absolute right-3 top-1/2 -translate-y-1/2 text-stone-300 hover:text-stone-500">
                  <Icon name="x" className="w-4 h-4" />
                </button>
              )}
            </div>
            {/* lista de resultados */}
            {clientesFiltrados.length === 0 ? (
              <p className="text-xs text-amber-600 mt-2 px-1">No se encontró ningún cliente con esa búsqueda.</p>
            ) : (
              <div className="mt-2 border border-stone-200 rounded-xl overflow-hidden max-h-52 overflow-y-auto">
                {clientesFiltrados.slice(0, 50).map((c) => {
                  const paqsActivos = state.paquetes.filter((p) => p.clienteId === c.id && p.sesionesUsadas < p.sesionesTotales).length;
                  const selected    = c.id === clienteId;
                  return (
                    <button
                      key={c.id} type="button"
                      onClick={() => setClienteId(c.id)}
                      className={`w-full flex items-center justify-between px-3 py-2.5 text-sm text-left transition border-b border-stone-100 last:border-0 ${selected ? 'bg-brand-50 text-brand-800' : 'hover:bg-stone-50 text-stone-700'}`}
                    >
                      <div className="min-w-0">
                        <span className="font-medium">{c.nombre}</span>
                        {c.cedula && <span className="text-xs text-stone-400 ml-2">{c.cedula}</span>}
                      </div>
                      <div className="flex items-center gap-1.5 shrink-0 ml-2">
                        {paqsActivos > 0 && (
                          <span className="text-[10px] bg-emerald-100 text-emerald-700 font-bold px-1.5 py-0.5 rounded-full">
                            {paqsActivos} paq.
                          </span>
                        )}
                        {selected && <span className="text-brand-600">✓</span>}
                      </div>
                    </button>
                  );
                })}
                {clientesFiltrados.length > 50 && (
                  <div className="px-3 py-2 text-xs text-stone-400 text-center bg-stone-50">
                    Mostrando 50 de {clientesFiltrados.length}. Refina la búsqueda.
                  </div>
                )}
              </div>
            )}
          </div>

          {/* Cortesía: no hay venta que vincular, se elige qué se regaló */}
          {esCortesia && (
            <div className="rounded-xl border-2 border-amber-200 bg-amber-50 p-4 space-y-3">
              <div className="text-sm text-amber-900">
                <strong>🎁 Sesión de cortesía</strong>
                <p className="text-xs mt-0.5 text-amber-800">
                  No genera ingreso. Se registra para llevar la cuenta de la inversión en publicidad
                  y para que al fisioterapeuta se le pague en su liquidación.
                </p>
              </div>
              <div>
                <Label>¿Qué se regaló?</Label>
                <Select value={servicioCortesia} onChange={setServicioCortesia}>
                  <option value="">Selecciona el servicio…</option>
                  {getSvs(state).filter((sv) => sv.activo !== false).map((sv) => (
                    <option key={sv.id} value={sv.id}>{sv.nombre}</option>
                  ))}
                </Select>
              </div>
            </div>
          )}

          {/* Venta vinculada (paquete o sesión suelta) */}
          <div className={esCortesia ? 'hidden' : ''}>
            <Label>
              {tipo === 'suelta' ? '💆 Venta suelta pendiente de sesión' : '📦 Paquete activo'}
            </Label>
            {tipo === 'suelta' && (
              <p className="text-xs text-stone-400 mb-2">
                Selecciona la venta que ya registraste en <em>Nueva venta</em>. El pago ya quedó ahí — aquí solo indicas quién hizo la sesión.
              </p>
            )}
            {paquetesCliente.length === 0 ? (
              <div className="bg-amber-50 border border-amber-200 rounded-xl px-4 py-3 text-sm text-amber-800 flex items-start gap-2">
                <span className="text-lg shrink-0">⚠️</span>
                <div>
                  <strong>{clienteSeleccionado?.nombre || 'Este cliente'}</strong>{' '}
                  {tipo === 'suelta'
                    ? 'no tiene ventas sueltas pendientes de sesión.'
                    : 'no tiene paquetes activos.'}
                  <div className="text-xs mt-0.5">Registra primero la venta en la pestaña <em>Nueva venta</em>.</div>
                </div>
              </div>
            ) : (
              <div className="space-y-2">
                {paquetesCliente.map((p) => {
                  const sv  = findSv(state, p.servicioId);
                  const pct = Math.round((p.sesionesUsadas / p.sesionesTotales) * 100);
                  const sel = p.id === paqueteId;
                  const dv  = p.fechaVencimiento ? diasHasta(p.fechaVencimiento) : null;
                  const vencido = dv !== null && dv < 0;
                  return (
                    <button key={p.id} type="button" onClick={() => setPaqueteId(p.id)}
                      className={`w-full text-left px-4 py-3 rounded-xl border-2 transition ${sel ? 'border-brand-500 bg-brand-50' : vencido ? 'border-red-200 hover:border-red-300' : 'border-stone-200 hover:border-stone-300'}`}>
                      <div className="flex items-center justify-between mb-1.5">
                        <span className="font-semibold text-sm flex items-center gap-1.5">
                          {p.esBono && <span title="Bono regalo">🎁</span>}
                          {sv?.nombre || 'Servicio'}
                          {vencido && <span className="text-[10px] font-bold px-1.5 py-0.5 rounded-full bg-red-100 text-red-700">VENCIDO</span>}
                        </span>
                        <span className={`text-xs font-bold px-2 py-0.5 rounded-full ${sel ? 'bg-brand-200 text-brand-800' : 'bg-stone-100 text-stone-600'}`}>
                          {p.sesionesTotales - p.sesionesUsadas} restantes
                        </span>
                      </div>
                      <div className="h-1.5 bg-stone-200 rounded-full overflow-hidden">
                        <div className="h-full bg-brand-400 rounded-full" style={{ width: `${pct}%` }} />
                      </div>
                      <div className="flex justify-between mt-1 text-xs text-stone-400">
                        <span>{p.sesionesUsadas}/{p.sesionesTotales} sesiones usadas · vendido {fmtDate(p.fechaCompra)}</span>
                        <span>{COP(Math.round(p.valorTotal / p.sesionesTotales))} / sesión</span>
                      </div>
                      {p.fechaVencimiento && (
                        <div className={`mt-1 text-xs ${vencido ? 'text-red-600 font-semibold' : 'text-stone-400'}`}>
                          {vencido ? `⛔ Venció el ${fmtDate(p.fechaVencimiento)} (puedes registrar igual)` : `⏳ Vence ${fmtDate(p.fechaVencimiento)}`}
                        </div>
                      )}
                      {p.saldoPendiente > 0 && (
                        <div className="mt-1.5 flex items-center gap-1.5 text-xs font-semibold text-rose-700 bg-rose-50 rounded-lg px-2 py-1">
                          💰 Saldo pendiente: {COP(p.saldoPendiente)}
                        </div>
                      )}
                    </button>
                  );
                })}
              </div>
            )}
            {/* Alerta de saldo pendiente en venta seleccionada */}
            {paqueteId && (() => {
              const pSel = state.paquetes.find((x) => x.id === paqueteId);
              return pSel?.saldoPendiente > 0 ? (
                <div className="mt-2 px-4 py-3 rounded-xl bg-rose-50 border-2 border-rose-300 text-rose-800 text-sm fade-in">
                  <div className="font-bold flex items-center gap-2 mb-0.5">
                    💰 Esta venta tiene saldo pendiente de cobro
                  </div>
                  <div className="text-xs">
                    Abono registrado: <strong>{COP(pSel.abonoInicial)}</strong> ·
                    Saldo por cobrar: <strong>{COP(pSel.saldoPendiente)}</strong>
                  </div>
                  <div className="text-xs mt-1 text-rose-600">Recuerda cobrar el saldo restante antes o durante esta sesión.</div>
                </div>
              ) : null;
            })()}
          </div>

          {/* Fisioterapeutas — hasta 4 (fisio principal + 3 extras) */}
          <div className="space-y-4">
            {/* Fisio 1 */}
            <div>
              <div className="flex items-center justify-between mb-1.5">
                <Label>{numFisios > 1 ? 'Fisioterapeuta 1 (principal)' : 'Fisioterapeuta'}</Label>
                {fisiosExtra.length < 3 && state.fisios.length > 1 && (
                  <button type="button"
                    onClick={() => setFisiosExtra((p) => [...p, ''])}
                    className="text-xs px-2.5 py-1 rounded-lg bg-violet-100 text-violet-700 hover:bg-violet-200 font-semibold flex items-center gap-1">
                    <Icon name="plus" className="w-3 h-3" /> Agregar fisio
                  </button>
                )}
              </div>
              {state.fisios.length === 0 ? (
                <p className="text-xs text-amber-600 bg-amber-50 border border-amber-200 rounded-lg px-3 py-2">
                  No hay fisioterapeutas registrados. Agrégalos en la pestaña <em>Liquidación fisio</em>.
                </p>
              ) : (
                <div className="grid grid-cols-2 gap-2">
                  {state.fisios.map((f) => (
                    <button key={f.id} type="button" onClick={() => setFisioId(f.id)}
                      disabled={fisiosExtra.includes(f.id)}
                      className={`px-3 py-2.5 rounded-xl border-2 text-sm font-medium transition text-left ${fisioId === f.id ? 'border-brand-500 bg-brand-50 text-brand-800' : fisiosExtra.includes(f.id) ? 'border-stone-100 bg-stone-50 text-stone-300 cursor-not-allowed' : 'border-stone-200 hover:border-stone-300 text-stone-700'}`}>
                      <div className="font-semibold">{f.nombre}</div>
                      {f.cedula && <div className="text-xs opacity-60">{f.cedula}</div>}
                    </button>
                  ))}
                </div>
              )}
            </div>

            {/* Fisios extra (2, 3, 4) */}
            {fisiosExtra.map((fid, idx) => {
              // Fisios disponibles: ni el fisio1 ni otros slots ya seleccionados
              const ocupados = new Set([fisioId, ...fisiosExtra.filter((x, i) => i !== idx && x)]);
              const disponibles = state.fisios.filter((f) => !ocupados.has(f.id));
              return (
                <div key={idx} className="fade-in border border-violet-200 rounded-xl p-3 bg-violet-50/40">
                  <div className="flex items-center justify-between mb-2">
                    <span className="text-sm font-semibold text-violet-800">Fisioterapeuta {idx + 2}</span>
                    <button type="button"
                      onClick={() => setFisiosExtra((p) => p.filter((_, i) => i !== idx))}
                      className="text-xs text-rose-500 hover:text-rose-700 font-medium flex items-center gap-1">
                      <Icon name="x" className="w-3 h-3" /> Quitar
                    </button>
                  </div>
                  {disponibles.length === 0 ? (
                    <p className="text-xs text-amber-600">Todos los fisioterapeutas ya están asignados.</p>
                  ) : (
                    <div className="grid grid-cols-2 gap-2">
                      {disponibles.map((f) => (
                        <button key={f.id} type="button"
                          onClick={() => setFisiosExtra((p) => p.map((x, i) => i === idx ? f.id : x))}
                          className={`px-3 py-2.5 rounded-xl border-2 text-sm font-medium transition text-left ${fid === f.id ? 'border-violet-500 bg-violet-100 text-violet-800' : 'border-stone-200 bg-white hover:border-violet-300 text-stone-700'}`}>
                          <div className="font-semibold">{f.nombre}</div>
                          {f.cedula && <div className="text-xs opacity-60">{f.cedula}</div>}
                        </button>
                      ))}
                    </div>
                  )}
                  {!fid && <p className="text-xs text-amber-600 mt-2">⚠️ Selecciona un fisioterapeuta para este slot.</p>}
                </div>
              );
            })}

            {/* Info de configuración multi-fisio */}
            {numFisios > 1 && (
              <div className="flex items-center gap-2 text-xs text-violet-700 bg-violet-50 border border-violet-200 rounded-lg px-3 py-2">
                <span>{numFisios === 2 ? '👫' : '🙌'}</span>
                <span>Sesión a {numFisios * 2} manos · Comisión dividida en {numFisios} partes iguales</span>
              </div>
            )}
          </div>


          {/* Fecha */}
          <div>
            <Label>Fecha de la sesión</Label>
            <Input type="date" value={fecha} onChange={setFecha}  min={FECHA_MIN} max={FECHA_MAX()}/>
          </div>

          {/* Comisión del fisio en pesos */}
          {valorSesion > 0 && (
            <div>
              <Label>Comisión del fisio ($)</Label>
              <div className="flex items-center gap-2">
                <input type="number" value={comisionMonto} onChange={(e) => setComisionMonto(e.target.value)}
                  min="0" max={Number(valorSesion)} step="1"
                  className="w-36 px-3 py-2.5 rounded-lg border border-stone-200 bg-white text-base font-semibold focus:outline-none focus:border-brand-500 focus:ring-2 focus:ring-brand-100" />
                {numFisios > 1 && (
                  <span className="text-sm text-stone-400">÷ {numFisios} fisios = <strong className="text-stone-700">{COP(comisionPorFisio)} c/u</strong></span>
                )}
              </div>
              <div className="flex gap-1 mt-1.5 flex-wrap">
                {[50, 40, 30, 0].map((pct) => {
                  const monto = Math.round(valorSesion * pct / 100);
                  return (
                    <button key={pct} type="button" onClick={() => setComisionMonto(monto)}
                      className={`text-[11px] px-2 py-0.5 rounded-full font-medium ${comisionTotal === monto ? 'bg-brand-600 text-white' : 'bg-stone-100 text-stone-600 hover:bg-stone-200'}`}>
                      {COP(monto)}
                    </button>
                  );
                })}
              </div>
            </div>
          )}

          {/* Resumen valor */}
          {valorSesion > 0 && (
            <div className="grid gap-3 p-4 bg-stone-50 rounded-xl text-sm" style={{ gridTemplateColumns: `repeat(${Math.min(numFisios + 1, 4)}, 1fr)` }}>
              <div>
                <div className="text-xs text-stone-500">Valor sesión</div>
                <div className="font-bold text-lg tabular-nums">{COP(valorSesion)}</div>
              </div>
              {[fisioId, ...fisiosExtra.filter(Boolean)].slice(0, 3).map((fid, idx) => {
                const f = state.fisios.find((x) => x.id === fid);
                return (
                  <div key={idx}>
                    <div className="text-xs text-stone-500">
                      {numFisios > 1 ? `${f?.nombre?.split(' ')[0] || `Fisio ${idx+1}`}` : 'Comisión fisio'}
                    </div>
                    <div className="font-bold text-lg tabular-nums text-rose-700">{COP(comisionPorFisio)}</div>
                  </div>
                );
              })}
              {numFisios === 4 && (
                <div>
                  <div className="text-xs text-stone-500">{state.fisios.find((x) => x.id === fisiosExtra[2])?.nombre?.split(' ')[0] || 'Fisio 4'}</div>
                  <div className="font-bold text-lg tabular-nums text-rose-700">{COP(comisionPorFisio)}</div>
                </div>
              )}
            </div>
          )}

          <button
            disabled={saving || !fisioId || !clienteId || !paqueteId || fisiosExtra.some((id) => !id)}
            className="w-full py-3 rounded-xl bg-brand-600 text-white font-semibold hover:bg-brand-700 disabled:opacity-50 flex items-center justify-center gap-2">
            {saving ? <><Icon name="loader" className="w-4 h-4 animate-spin" />Guardando…</> : 'Registrar sesión'}
          </button>
        </form>
      </Card>
    </div>
  );
}

// ---------- Liquidación fisio ----------
function Liquidacion({ state, onAddFisio, onDeleteFisio, onUpdateFisio, onAddServicio, onUpdateServicio, onDeleteServicio, onAddGasto }) {
  const [subTab, setSubTab] = useState('fisios');

  const SUB = [
    { id: 'fisios',      l: 'Fisioterapeutas' },
    { id: 'catalogo',    l: 'Catálogo' },
    { id: 'liquidacion', l: 'Liquidación' },
  ];

  return (
    <div className="space-y-4 max-w-5xl">
      <div className="flex rounded-xl p-1 bg-stone-100 text-sm w-fit flex-wrap gap-1">
        {SUB.map((o) => (
          <button key={o.id} onClick={() => setSubTab(o.id)}
            className={`px-4 py-2 rounded-lg font-medium transition ${subTab === o.id ? 'bg-white shadow-sm text-stone-900' : 'text-stone-500 hover:text-stone-700'}`}>
            {o.l}
          </button>
        ))}
      </div>
      {subTab === 'fisios'      && <FisiosGestion  state={state} onAddFisio={onAddFisio} onDeleteFisio={onDeleteFisio} onUpdateFisio={onUpdateFisio} />}
      {subTab === 'catalogo'    && <Catalogo        state={state} onAdd={onAddServicio} onUpdate={onUpdateServicio} onDelete={onDeleteServicio} />}
      {subTab === 'liquidacion' && <LiquidacionMes  state={state} onAddGasto={onAddGasto} />}
    </div>
  );
}

// ---------- Catálogo de servicios ----------
const CAT_META = {
  masaje:  { label: 'Masajes',          emoji: '💆' },
  fisio:   { label: 'Fisioterapia',     emoji: '🏋️' },
  combo:   { label: 'Combos',           emoji: '✨' },
};

function Catalogo({ state, onAdd, onUpdate, onDelete }) {
  const svs = getSvs(state);

  // — form nuevo servicio —
  const [showForm,   setShowForm]   = useState(false);
  const [fNombre,    setFNombre]    = useState('');
  const [fDesc,      setFDesc]      = useState('');
  const [fPrecio,    setFPrecio]    = useState('');
  const [fSesiones,  setFSesiones]  = useState('');
  const [fCat,       setFCat]       = useState('masaje');
  const [saving,     setSaving]     = useState(false);

  const handleAdd = async (e) => {
    e.preventDefault();
    if (!fNombre.trim() || !fPrecio) return;

    // Si el nombre dice "x10" o "10 sesiones" pero el campo quedó vacío, el paquete
    // se guardaría como 1 sola sesión: cobraría todo el valor en la primera visita
    // y dispararía la comisión del fisio. Es el error que más problemas ha causado.
    const mencion = fNombre.match(/x\s*(\d+)|(\d+)\s*sesion/i);
    const nMencionado = mencion ? Number(mencion[1] || mencion[2]) : null;
    if (nMencionado && nMencionado > 1 && Number(fSesiones || 1) !== nMencionado) {
      const ok = confirm(`⚠️ El nombre dice "${nMencionado} sesiones" pero en el campo N° de sesiones pusiste ${fSesiones || '(vacío = 1)'}.\n\n¿Quieres que lo guarde con ${nMencionado} sesiones?\n\nAceptar = usar ${nMencionado}   ·   Cancelar = corregirlo tú`);
      if (!ok) { return; }
      setFSesiones(String(nMencionado));
      // se usa el valor corregido de inmediato
      e.__sesionesCorregidas = nMencionado;
    }

    setSaving(true);
    try {
      await onAdd({
        id:          uid(),
        nombre:      fNombre.trim(),
        descripcion: fDesc.trim(),
        precio:      Number(fPrecio),
        sesiones:    e.__sesionesCorregidas || (fSesiones ? Number(fSesiones) : null),
        categoria:   fCat,
        activo:      true,
        orden:       svs.filter((s) => s.categoria === fCat).length,
      });
      setShowForm(false); setFNombre(''); setFDesc(''); setFPrecio(''); setFSesiones(''); setFCat('masaje');
    } catch (err) { alert('Error: ' + err.message); }
    finally { setSaving(false); }
  };

  // group by category in display order
  const grouped = ['masaje', 'fisio', 'combo'].map((cat) => ({
    cat,
    items: svs.filter((s) => s.categoria === cat).sort((a, b) => a.orden - b.orden),
  }));

  return (
    <div className="space-y-5">
      {/* info banner */}
      <div className="bg-amber-50 border border-amber-200 rounded-xl px-4 py-3 text-sm text-amber-800 flex items-start gap-2">
        <span className="text-lg leading-none">ℹ️</span>
        <span><strong>Convenios:</strong> aplican 10% de descuento sobre el precio del servicio. Esto se puede gestionar manualmente al registrar cada venta.</span>
      </div>

      {/* botón agregar */}
      <button
        onClick={() => setShowForm((v) => !v)}
        className="w-full flex items-center justify-center gap-2 py-2.5 rounded-xl border-2 border-dashed border-brand-300 text-brand-700 font-semibold hover:bg-brand-50 transition-colors"
      >
        <span className="text-xl leading-none">＋</span> Agregar servicio
      </button>

      {/* formulario nuevo */}
      {showForm && (
        <form onSubmit={handleAdd} className="bg-white border border-stone-200 rounded-2xl p-4 space-y-3 fade-in">
          <p className="font-semibold text-stone-700">Nuevo servicio</p>
          <div className="grid grid-cols-2 gap-3">
            <div className="col-span-2">
              <label className="block text-xs text-stone-500 mb-1">Nombre *</label>
              <input value={fNombre} onChange={(e) => setFNombre(e.target.value)} required placeholder="Ej: Masaje relajante"
                className="w-full border border-stone-200 rounded-lg px-3 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-brand-400" />
            </div>
            <div className="col-span-2">
              <label className="block text-xs text-stone-500 mb-1">Descripción</label>
              <input value={fDesc} onChange={(e) => setFDesc(e.target.value)} placeholder="Descripción corta (opcional)"
                className="w-full border border-stone-200 rounded-lg px-3 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-brand-400" />
            </div>
            <div>
              <label className="block text-xs text-stone-500 mb-1">Precio (COP) *</label>
              <input type="number" value={fPrecio} onChange={(e) => setFPrecio(e.target.value)} required placeholder="100000"
                className="w-full border border-stone-200 rounded-lg px-3 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-brand-400" />
            </div>
            <div>
              <label className="block text-xs text-stone-500 mb-1">N° sesiones (si es paquete)</label>
              <input type="number" value={fSesiones} onChange={(e) => setFSesiones(e.target.value)} placeholder="Ej: 5"
                className="w-full border border-stone-200 rounded-lg px-3 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-brand-400" />
            </div>
            <div className="col-span-2">
              <label className="block text-xs text-stone-500 mb-1">Categoría *</label>
              <select value={fCat} onChange={(e) => setFCat(e.target.value)}
                className="w-full border border-stone-200 rounded-lg px-3 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-brand-400">
                {Object.entries(CAT_META).map(([k, v]) => (
                  <option key={k} value={k}>{v.emoji} {v.label}</option>
                ))}
              </select>
            </div>
          </div>
          <div className="flex gap-2 pt-1">
            <button type="submit" disabled={saving}
              className="flex-1 bg-brand-600 text-white rounded-xl py-2 text-sm font-semibold hover:bg-brand-700 disabled:opacity-50">
              {saving ? 'Guardando…' : 'Guardar servicio'}
            </button>
            <button type="button" onClick={() => setShowForm(false)}
              className="flex-1 bg-stone-100 text-stone-600 rounded-xl py-2 text-sm font-semibold hover:bg-stone-200">
              Cancelar
            </button>
          </div>
        </form>
      )}

      {/* lista por categoría */}
      {grouped.map(({ cat, items }) => items.length === 0 ? null : (
        <div key={cat}>
          <h3 className="text-xs font-bold uppercase tracking-widest text-stone-400 mb-2 flex items-center gap-1.5">
            <span>{CAT_META[cat].emoji}</span> {CAT_META[cat].label}
          </h3>
          <div className="space-y-2">
            {items.map((sv) => (
              <ServicioCard key={sv.id} sv={sv} onUpdate={onUpdate} onDelete={onDelete}
                            ventasQueLoUsan={(state.paquetes || []).filter((p) => p.servicioId === sv.id).length} />
            ))}
          </div>
        </div>
      ))}
    </div>
  );
}

function ServicioCard({ sv, onUpdate, onDelete, ventasQueLoUsan = 0 }) {
  const [editingPrice, setEditingPrice] = useState(false);
  const [precio,       setPrecio]       = useState(sv.precio);
  const [editingSes,   setEditingSes]   = useState(false);
  const [sesiones,     setSesiones]     = useState(sv.sesiones || 1);
  const [saving,       setSaving]       = useState(false);

  const commitPrice = async () => {
    const p = Number(precio);
    if (!p || p === sv.precio) { setPrecio(sv.precio); setEditingPrice(false); return; }
    setSaving(true);
    try { await onUpdate(sv.id, { precio: p }); }
    catch (err) { alert('Error: ' + err.message); setPrecio(sv.precio); }
    finally { setSaving(false); setEditingPrice(false); }
  };

  // Corregir el N° de sesiones: es lo que estaba faltando. Un paquete guardado
  // con 1 sesión hace que el valor completo se cargue a una sola sesión y que la
  // comisión del fisio salga disparada.
  const commitSesiones = async () => {
    const n = Math.max(1, Math.round(Number(sesiones) || 1));
    if (n === (sv.sesiones || 1)) { setSesiones(sv.sesiones || 1); setEditingSes(false); return; }
    setSaving(true);
    try { await onUpdate(sv.id, { sesiones: n }); }
    catch (err) { alert('Error: ' + err.message); setSesiones(sv.sesiones || 1); }
    finally { setSaving(false); setEditingSes(false); }
  };

  const toggleActivo = async () => {
    setSaving(true);
    try { await onUpdate(sv.id, { activo: !sv.activo }); }
    catch (err) { alert('Error: ' + err.message); }
    finally { setSaving(false); }
  };

  const eliminar = async () => {
    // Si tiene ventas, borrarlo dejaría esas ventas sin servicio: mejor desactivar.
    if (ventasQueLoUsan > 0) {
      alert(`No se puede eliminar "${sv.nombre}" porque hay ${ventasQueLoUsan} venta(s) que lo usan.\n\nSi ya no lo ofreces, desactívalo con el interruptor: deja de aparecer al registrar ventas nuevas, pero el historial se conserva.`);
      return;
    }
    if (!confirm(`¿Eliminar el servicio "${sv.nombre}" del catálogo?\n\nNo tiene ventas asociadas, así que no se pierde información.`)) return;
    setSaving(true);
    try { await onDelete(sv.id); }
    catch (err) { alert('Error: ' + err.message); }
    finally { setSaving(false); }
  };

  return (
    <div className={`bg-white border rounded-xl px-4 py-3 flex items-center gap-3 transition-opacity ${!sv.activo ? 'opacity-50' : ''}`}>
      <div className="flex-1 min-w-0">
        <div className="flex items-center gap-2 flex-wrap">
          <span className="font-semibold text-stone-800 text-sm">{sv.nombre}</span>
          {/* N° de sesiones editable */}
          {editingSes ? (
            <span className="inline-flex items-center gap-1">
              <input type="number" value={sesiones} autoFocus min="1" step="1"
                onChange={(e) => setSesiones(e.target.value)}
                onBlur={commitSesiones}
                onKeyDown={(e) => { if (e.key === 'Enter') commitSesiones(); if (e.key === 'Escape') { setSesiones(sv.sesiones || 1); setEditingSes(false); } }}
                className="w-16 border border-brand-300 rounded-lg px-2 py-0.5 text-xs focus:outline-none focus:ring-2 focus:ring-brand-400" />
              <span className="text-[11px] text-stone-400">sesiones</span>
              <button onClick={commitSesiones} disabled={saving}
                className="text-[11px] bg-brand-600 text-white px-2 py-0.5 rounded-lg font-semibold disabled:opacity-50">{saving ? '…' : '✓'}</button>
            </span>
          ) : (
            <button onClick={() => { setSesiones(sv.sesiones || 1); setEditingSes(true); }}
              title="Toca para corregir el número de sesiones del paquete"
              className={`text-xs font-bold px-2 py-0.5 rounded-full hover:ring-2 hover:ring-brand-300 transition ${sv.sesiones > 1 ? 'bg-brand-100 text-brand-700' : 'bg-stone-100 text-stone-500'}`}>
              ×{sv.sesiones || 1} {sv.sesiones > 1 ? 'sesiones' : 'sesión'} ✏️
            </button>
          )}
          {!sv.activo && (
            <span className="text-xs bg-stone-100 text-stone-500 font-medium px-2 py-0.5 rounded-full">Inactivo</span>
          )}
        </div>
        {sv.descripcion && (
          <p className="text-xs text-stone-400 mt-0.5 truncate">{sv.descripcion}</p>
        )}
        {/* precio editable */}
        {editingPrice ? (
          <div className="flex items-center gap-1.5 mt-1">
            <input
              type="number"
              value={precio}
              autoFocus
              onChange={(e) => setPrecio(e.target.value)}
              onBlur={commitPrice}
              onKeyDown={(e) => { if (e.key === 'Enter') commitPrice(); if (e.key === 'Escape') { setPrecio(sv.precio); setEditingPrice(false); } }}
              className="w-28 border border-brand-300 rounded-lg px-2 py-1 text-sm focus:outline-none focus:ring-2 focus:ring-brand-400"
            />
            <button onClick={commitPrice} disabled={saving}
              className="text-xs bg-brand-600 text-white px-2 py-1 rounded-lg font-semibold disabled:opacity-50">
              {saving ? '…' : '✓'}
            </button>
            <button onClick={() => { setPrecio(sv.precio); setEditingPrice(false); }}
              className="text-xs text-stone-400 px-1">✕</button>
          </div>
        ) : (
          <button
            onClick={() => { setPrecio(sv.precio); setEditingPrice(true); }}
            className="mt-1 text-sm font-bold text-brand-700 hover:underline"
            title="Toca para editar precio"
          >
            {COP(sv.precio)}
          </button>
        )}
      </div>
      {/* toggle activo */}
      <button
        onClick={toggleActivo}
        disabled={saving}
        className={`relative inline-flex h-6 w-11 shrink-0 rounded-full transition-colors duration-200 focus:outline-none disabled:opacity-50 ${sv.activo ? 'bg-brand-500' : 'bg-stone-200'}`}
        title={sv.activo ? 'Desactivar (deja de aparecer al vender)' : 'Activar'}
      >
        <span className={`inline-block h-5 w-5 mt-0.5 ml-0.5 rounded-full bg-white shadow transform transition-transform duration-200 ${sv.activo ? 'translate-x-5' : 'translate-x-0'}`} />
      </button>
      {/* eliminar del catálogo */}
      {onDelete && (
        <button onClick={eliminar} disabled={saving}
          title={ventasQueLoUsan > 0 ? `Tiene ${ventasQueLoUsan} venta(s): no se puede eliminar, desactívalo` : 'Eliminar del catálogo'}
          className={`shrink-0 text-sm px-1.5 py-1 rounded-lg transition ${ventasQueLoUsan > 0 ? 'text-stone-200 cursor-not-allowed' : 'text-stone-300 hover:text-white hover:bg-rose-500'}`}>
          🗑️
        </button>
      )}
    </div>
  );
}

// ---------- Fisios — gestión ----------
function FisiosGestion({ state, onAddFisio, onDeleteFisio, onUpdateFisio }) {
  const [showAdd, setShowAdd] = useState(false);
  const [nombre,  setNombre]  = useState('');
  const [cedula,  setCedula]  = useState('');
  const [telefono,setTelefono]= useState('');
  const [saving,  setSaving]  = useState(false);

  const handleAdd = async (e) => {
    e.preventDefault();
    if (!nombre.trim()) return;
    setSaving(true);
    try {
      await onAddFisio({ id: uid(), nombre: nombre.trim(), cedula: cedula.trim(), telefono: telefono.trim() });
      setNombre(''); setCedula(''); setTelefono(''); setShowAdd(false);
    } catch (err) { alert('Error: ' + err.message); }
    finally { setSaving(false); }
  };

  const handleDelete = async (f) => {
    const sesionesF = state.sesiones.filter((s) => s.fisioId === f.id).length;
    const aviso = sesionesF > 0
      ? `${f.nombre} tiene ${sesionesF} sesiones registradas. Si lo eliminas solo se borrará el fisio, las sesiones quedan. ¿Continuar?`
      : `¿Eliminar a ${f.nombre}?`;
    if (!confirm(aviso)) return;
    try { await onDeleteFisio(f.id); } catch (err) { alert('Error: ' + err.message); }
  };

  return (
    <div className="space-y-3">
      <div className="flex items-center justify-between">
        <span className="text-sm text-stone-500">{state.fisios.length} fisioterapeutas registrados</span>
        <button onClick={() => setShowAdd(!showAdd)}
          className="px-3 py-1.5 rounded-lg bg-brand-600 text-white text-xs font-medium hover:bg-brand-700 flex items-center gap-1">
          <Icon name="plus" className="w-3.5 h-3.5" /> Agregar fisio
        </button>
      </div>

      {showAdd && (
        <Card>
          <h4 className="font-semibold text-sm mb-3">Nuevo fisioterapeuta</h4>
          <form onSubmit={handleAdd} className="space-y-3">
            <div className="grid sm:grid-cols-3 gap-2">
              <Input value={nombre}    onChange={setNombre}    placeholder="Nombre completo" required />
              <Input value={cedula}    onChange={setCedula}    placeholder="Cédula" />
              <Input value={telefono}  onChange={setTelefono}  placeholder="Teléfono" />
            </div>
            <div className="flex gap-2">
              <button type="button" onClick={() => setShowAdd(false)}
                className="px-4 py-2 rounded-lg border border-stone-200 text-sm hover:bg-stone-50">Cancelar</button>
              <button disabled={saving}
                className="px-4 py-2 rounded-lg bg-brand-600 text-white text-sm font-semibold hover:bg-brand-700 disabled:opacity-60 flex items-center gap-2">
                {saving ? <Icon name="loader" className="w-4 h-4 animate-spin" /> : null} Guardar
              </button>
            </div>
          </form>
        </Card>
      )}

      {state.fisios.length === 0 ? (
        <Card><Empty text="No hay fisioterapeutas registrados." /></Card>
      ) : (
        <div className="grid gap-3">
          {state.fisios.map((f) => (
            <FisioCard key={f.id} fisio={f} state={state} onDelete={handleDelete} onUpdate={onUpdateFisio} />
          ))}
        </div>
      )}
    </div>
  );
}

function FisioCard({ fisio: f, state, onDelete, onUpdate }) {
  const [editing,  setEditing]  = useState(false);
  const [eNombre,  setENombre]  = useState(f.nombre   || '');
  const [eCedula,  setECedula]  = useState(f.cedula   || '');
  const [eTel,     setETel]     = useState(f.telefono || '');
  const [saving,   setSaving]   = useState(false);

  const sesiones    = sesionesDelFisio(state.sesiones, f.id);
  const mesSesiones = sesiones.filter((s) => monthKey(s.fecha) === monthKey(todayISO()));
  const totalMes    = mesSesiones.reduce((a, s) => a + comisionDelFisio(s, f.id), 0);

  const handleSave = async (e) => {
    e.preventDefault();
    if (!eNombre.trim()) return;
    setSaving(true);
    try {
      await onUpdate(f.id, { nombre: eNombre.trim(), cedula: eCedula.trim(), telefono: eTel.trim() });
      setEditing(false);
    } catch (err) { alert('Error: ' + err.message); }
    finally { setSaving(false); }
  };

  const initials = f.nombre.split(' ').map((n) => n[0]).join('').slice(0, 2).toUpperCase();

  return (
    <div className="bg-white rounded-2xl border border-stone-200 px-4 py-4">
      {editing ? (
        /* —— Modo edición —— */
        <form onSubmit={handleSave} className="space-y-3 fade-in">
          <div className="flex items-center gap-2 mb-1">
            <div className="w-9 h-9 rounded-full bg-brand-100 text-brand-700 font-bold flex items-center justify-center shrink-0 text-sm">{initials}</div>
            <span className="text-sm font-semibold text-stone-500">Editando datos</span>
          </div>
          <div className="grid sm:grid-cols-3 gap-2">
            <div className="sm:col-span-1">
              <label className="block text-xs text-stone-400 mb-1">Nombre completo *</label>
              <Input value={eNombre} onChange={setENombre} placeholder="Nombre completo" required />
            </div>
            <div>
              <label className="block text-xs text-stone-400 mb-1">Cédula</label>
              <Input value={eCedula} onChange={setECedula} placeholder="Cédula" />
            </div>
            <div>
              <label className="block text-xs text-stone-400 mb-1">Teléfono</label>
              <Input value={eTel} onChange={setETel} placeholder="Ej: 573001234567" />
            </div>
          </div>
          <div className="flex gap-2">
            <button type="submit" disabled={saving}
              className="flex-1 bg-brand-600 text-white rounded-xl py-2 text-sm font-semibold hover:bg-brand-700 disabled:opacity-50">
              {saving ? 'Guardando…' : '✓ Guardar cambios'}
            </button>
            <button type="button" onClick={() => { setEditing(false); setENombre(f.nombre); setECedula(f.cedula||''); setETel(f.telefono||''); }}
              className="flex-1 bg-stone-100 text-stone-600 rounded-xl py-2 text-sm font-semibold hover:bg-stone-200">
              Cancelar
            </button>
          </div>
        </form>
      ) : (
        /* —— Modo vista —— */
        <>
          <div className="flex items-start justify-between gap-3">
            <div className="flex items-center gap-3 min-w-0">
              <div className="w-11 h-11 rounded-full bg-brand-100 text-brand-700 font-bold flex items-center justify-center shrink-0">
                {initials}
              </div>
              <div className="min-w-0">
                <div className="font-semibold text-sm">{f.nombre}</div>
                <div className="flex flex-wrap gap-x-3 gap-y-0.5 mt-0.5">
                  {f.cedula   && <span className="text-xs text-stone-400">C.C. {f.cedula}</span>}
                  {f.telefono && <span className="text-xs text-stone-400">📞 {f.telefono}</span>}
                  {!f.cedula && !f.telefono && <span className="text-xs text-stone-300 italic">Sin datos de contacto</span>}
                </div>
              </div>
            </div>
            <div className="flex items-center gap-1.5 shrink-0">
              {f.telefono && (
                <a href={`tel:${f.telefono}`}
                  className="w-8 h-8 rounded-full bg-emerald-100 text-emerald-700 flex items-center justify-center hover:bg-emerald-200 transition"
                  title="Llamar"><Icon name="phone" className="w-3.5 h-3.5" /></a>
              )}
              <button onClick={() => setEditing(true)}
                className="w-8 h-8 rounded-full bg-stone-100 text-stone-500 flex items-center justify-center hover:bg-brand-100 hover:text-brand-700 transition"
                title="Editar">
                <svg className="w-3.5 h-3.5" fill="none" stroke="currentColor" strokeWidth={2} viewBox="0 0 24 24">
                  <path d="M11 4H4a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2v-7"/>
                  <path d="M18.5 2.5a2.121 2.121 0 0 1 3 3L12 15l-4 1 1-4 9.5-9.5z"/>
                </svg>
              </button>
              <button onClick={() => onDelete(f)}
                className="w-8 h-8 rounded-full text-stone-300 hover:text-rose-600 hover:bg-rose-50 flex items-center justify-center transition"
                title="Eliminar"><Icon name="trash" className="w-3.5 h-3.5" /></button>
            </div>
          </div>

          {/* Stats */}
          <div className="mt-3 pt-3 border-t border-stone-100 grid grid-cols-3 gap-3 text-center">
            <div>
              <div className="text-base font-bold">{sesiones.length}</div>
              <div className="text-[11px] text-stone-400">sesiones totales</div>
            </div>
            <div>
              <div className="text-base font-bold">{mesSesiones.length}</div>
              <div className="text-[11px] text-stone-400">este mes</div>
            </div>
            <div>
              <div className="text-base font-bold tabular-nums">{COP(totalMes)}</div>
              <div className="text-[11px] text-stone-400">a pagar este mes</div>
            </div>
          </div>
        </>
      )}
    </div>
  );
}

// ---------- Liquidación mensual ----------
function LiquidacionMes({ state, onAddGasto }) {
  const meses = useMemo(() => {
    const keys = new Set(state.sesiones.map((s) => monthKey(s.fecha)));
    keys.add(monthKey(todayISO()));
    return [...keys].sort().reverse();
  }, [state.sesiones]);

  const [mes, setMes]         = useState(meses[0] || monthKey(todayISO()));
  const [generando, setGenerando] = useState(null); // fisioId en proceso
  const [generado,  setGenerado]  = useState({});   // { fisioId: true }

  // Verificar cuáles ya tienen egreso registrado para este mes
  const egresosMes = useMemo(() => {
    return (state.gastos || []).filter((g) => g.fecha && monthKey(g.fecha) === mes && g.categoria === 'honorarios');
  }, [state.gastos, mes]);

  const filas = useMemo(() => {
    const ses = state.sesiones.filter((s) => monthKey(s.fecha) === mes);
    return state.fisios.map((f) => {
      // incluir tanto sesiones donde es fisio1 como fisio2
      const items = sesionesDelFisio(ses, f.id);
      const total = items.reduce((a, x) => a + comisionDelFisio(x, f.id), 0);
      // Buscar egreso ya creado para este fisio en este mes
      const egresoExistente = egresosMes.find((g) =>
        g.beneficiario && g.beneficiario.toLowerCase() === f.nombre.toLowerCase()
      );
      return { fisio: f, items, total, egresoExistente };
    }).filter((f) => f.items.length);
  }, [state, mes, egresosMes]);

  const grandTotal = filas.reduce((a, f) => a + f.total, 0);

  const enviarWhatsapp = (fisio, items, total) => {
    const lines = items.map((s) => {
      const cliente = state.clientes.find((c) => c.id === s.clienteId)?.nombre || '';
      const com     = comisionDelFisio(s, fisio.id);
      const nFisios = 1 + (s.fisiosAdicionales || []).length;
      const pareja  = nFisios > 1 ? (nFisios === 2 ? ' 👫' : ` 🙌${nFisios * 2}m`) : '';
      return `• ${fmtDate(s.fecha)} — ${cliente}${pareja} — ${COP(com)}`;
    });
    const msg = [`Hola ${fisio.nombre.split(' ')[0]} 👋`, ``, `Liquidación ${mes}:`, ...lines, ``, `*Total: ${COP(total)}*`, ``, `— Massage Therapy Manizales`].join('\n');
    window.open(`https://wa.me/?text=${encodeURIComponent(msg)}`, '_blank');
  };

  const generarEgreso = async (fisio, total, mes) => {
    if (!onAddGasto) return;
    setGenerando(fisio.id);
    try {
      const mesLabel = new Date(mes + '-15').toLocaleDateString('es-CO', { month: 'long', year: 'numeric' });
      // Calcular siguiente número de egreso
      const numeros = (state.gastos || []).map((g) => g.numeroEgreso || 0).filter(Boolean);
      const siguiente = numeros.length ? Math.max(...numeros) + 1 : 1;
      await onAddGasto({
        fecha:               todayISO(),
        descripcion:         `Liquidación honorarios ${fisio.nombre} — ${mesLabel}`,
        monto:               total,
        categoria:           'honorarios',
        beneficiario:        fisio.nombre,
        metodo_pago_egreso:  'efectivo',
        numero_egreso:       siguiente,
        // Evita que esta comisión se reste dos veces (ya se descuenta directo de las sesiones)
        es_liquidacion_fisio: true,
      });
      setGenerado((prev) => ({ ...prev, [fisio.id]: true }));
    } catch (e) {
      alert('Error al crear el egreso: ' + e.message);
    } finally {
      setGenerando(null);
    }
  };

  return (
    <div className="space-y-4">
      <div className="flex items-center justify-between gap-3 flex-wrap">
        <Select value={mes} onChange={(v) => { setMes(v); setGenerado({}); }} className="max-w-xs">
          {meses.map((m) => <option key={m} value={m}>{new Date(m + '-15').toLocaleDateString('es-CO', { month: 'long', year: 'numeric' })}</option>)}
        </Select>
        <div className="text-right">
          <div className="text-xs text-stone-500">Total a pagar</div>
          <div className="text-2xl font-bold tabular-nums">{COP(grandTotal)}</div>
        </div>
      </div>

      {filas.length === 0 && <Card><Empty text="Sin sesiones en este mes." /></Card>}

      {filas.map(({ fisio, items, total, egresoExistente }) => {
        const yaGenerado = generado[fisio.id] || !!egresoExistente;
        return (
        <Card key={fisio.id}>
          <div className="flex items-center justify-between mb-3 flex-wrap gap-2">
            <div className="flex items-center gap-3">
              <div className="w-10 h-10 rounded-full bg-brand-100 text-brand-700 font-bold flex items-center justify-center">
                {fisio.nombre.split(' ').map((n) => n[0]).join('').slice(0, 2)}
              </div>
              <div><div className="font-semibold">{fisio.nombre}</div><div className="text-xs text-stone-500">{items.length} sesiones</div></div>
            </div>
            <div className="flex items-center gap-2 flex-wrap justify-end">
              <div className="text-right"><div className="text-xs text-stone-500">A pagar</div><div className="font-bold text-lg">{COP(total)}</div></div>
              <button onClick={() => enviarWhatsapp(fisio, items, total)}
                className="px-3 py-2 rounded-lg bg-emerald-600 text-white text-sm font-medium hover:bg-emerald-700 flex items-center gap-1.5">
                <Icon name="whatsapp" className="w-4 h-4" /> WhatsApp
              </button>
              {yaGenerado ? (
                <div className="px-3 py-2 rounded-lg bg-stone-100 text-stone-500 text-sm flex items-center gap-1.5">
                  ✅ Egreso creado
                </div>
              ) : (
                <button
                  onClick={() => generarEgreso(fisio, total, mes)}
                  disabled={generando === fisio.id}
                  className="px-3 py-2 rounded-lg bg-rose-600 text-white text-sm font-medium hover:bg-rose-700 flex items-center gap-1.5 disabled:opacity-60">
                  <Icon name="trending-down" className="w-4 h-4" />
                  {generando === fisio.id ? 'Creando…' : 'Generar egreso'}
                </button>
              )}
            </div>
          </div>

          {/* Info del egreso registrado + alerta de desincronización */}
          {egresoExistente && (() => {
            const diff = total - egresoExistente.monto;
            const desincronizado = Math.abs(diff) > 0;
            return (
              <div className={`mb-3 px-3 py-2.5 rounded-xl border text-xs space-y-1 ${desincronizado ? 'bg-rose-50 border-rose-300 text-rose-900' : 'bg-emerald-50 border-emerald-200 text-emerald-800'}`}>
                <div className="flex items-center justify-between flex-wrap gap-1">
                  <span className="font-semibold">
                    {desincronizado ? '⚠️ Egreso desincronizado' : '✅ Egreso registrado'}
                  </span>
                  <span className="text-stone-500">
                    Egreso #{String(egresoExistente.numeroEgreso || 0).padStart(4,'0')} · {fmtDate(egresoExistente.fecha)}
                  </span>
                </div>
                <div className="flex flex-wrap gap-4">
                  <span>Monto pagado: <strong>{COP(egresoExistente.monto)}</strong></span>
                  <span>Recálculo actual: <strong>{COP(total)}</strong></span>
                  {desincronizado && (
                    <span className="font-bold">
                      Diferencia: {diff > 0 ? '+' : ''}{COP(diff)}
                      {diff > 0 ? ' (se le debe más)' : ' (se le pagó de más)'}
                    </span>
                  )}
                </div>
                {desincronizado && (
                  <p className="text-rose-700 pt-0.5">
                    Revisa si hubo sesiones editadas o eliminadas después de registrar el egreso. Ajusta el egreso en <strong>Egresos</strong> si es necesario.
                  </p>
                )}
              </div>
            );
          })()}
          <div className="border-t border-stone-100 pt-3 mt-3">
            <ul className="divide-y divide-stone-100">
              {items.map((s) => {
                const cliente = state.clientes.find((c) => c.id === s.clienteId);
                const sv      = findSv(state, s.servicioId);
                return (
                  <li key={s.id} className="py-2 grid grid-cols-12 gap-2 items-center text-sm">
                    <div className="col-span-3 text-stone-500 text-xs">{fmtDate(s.fecha)}</div>
                    <div className="col-span-5">{cliente?.nombre}{(s.fisiosAdicionales || []).length > 0 && <span className="ml-1 text-[10px] text-violet-600 font-semibold">{(s.fisiosAdicionales||[]).length === 1 ? '👫' : `🙌${(1+(s.fisiosAdicionales||[]).length)*2}m`}</span>}</div>
                    <div className="col-span-2 text-stone-500 text-xs hidden sm:block">{sv?.nombre}</div>
                    <div className="col-span-4 sm:col-span-2 text-right font-medium tabular-nums">{COP(comisionDelFisio(s, fisio.id))}</div>
                  </li>
                );
              })}
            </ul>
          </div>
        </Card>
        );
      })}
    </div>
  );
}

// ---------- Balance diario ----------
function Balance({ state, onAddGasto, onDeleteGasto }) {
  const [fecha,        setFecha]        = useState(todayISO());
  const [modal,        setModal]        = useState(false);
  const [baseCaja,     setBaseCaja]     = useState(() => Number(localStorage.getItem('mtm_base_caja') || 0));
  const [editingBase,  setEditingBase]  = useState(false);
  const [baseInput,    setBaseInput]    = useState('');

  const saveBase = () => {
    const v = Number(baseInput);
    if (isNaN(v) || v < 0) { setEditingBase(false); return; }
    setBaseCaja(v);
    localStorage.setItem('mtm_base_caja', String(v));
    setEditingBase(false);
  };

  const dia = useMemo(() => {
    const ventasDia   = state.paquetes.filter((p) => p.fechaCompra === fecha);
    const sesionesDia = state.sesiones.filter((s) => s.fecha === fecha);
    const sueltasDia  = sesionesDia.filter((s) => !s.paqueteId);
    const gastosDia   = (state.gastos || []).filter((g) => g.fecha === fecha);
    // Abonos posteriores recibidos hoy (de cualquier paquete)
    const abonosDia = state.paquetes.flatMap((p) =>
      (p.abonos || []).filter((a) => a.fecha === fecha).map((a) => ({
        ...a, paqueteId: p.id, clienteId: p.clienteId, servicioId: p.servicioId, valorTotal: p.valorTotal,
      }))
    );
    // Dinero efectivamente recibido por la venta el día de la compra:
    //   - sin saldo pendiente: el valor total fue recibido
    //   - con saldo pendiente: solo el abono inicial entró ese día
    const recibidoEnVenta = pagoInicialVenta;
    const efectivoVentas = ventasDia.reduce((a, p) => {
      // Si hay saldo pendiente, el montoEfectivo ya refleja el abono inicial (cuando es 'efectivo')
      // Si no hay saldo, montoEfectivo refleja todo el valor (cuando es 'efectivo')
      return a + (p.montoEfectivo || (p.metodoPago === 'efectivo' ? recibidoEnVenta(p) : 0));
    }, 0);
    const transferenciaVentas = ventasDia.reduce((a, p) => {
      return a + (p.montoTransferencia || (p.metodoPago === 'transferencia' ? recibidoEnVenta(p) : 0));
    }, 0);
    const datafonoVentas = ventasDia.reduce((a, p) => {
      return a + (p.montoDatafono || (p.metodoPago === 'datafono' ? recibidoEnVenta(p) : 0));
    }, 0);
    const efectivoSueltas = sueltasDia.reduce((a, s) => a + (s.montoEfectivo || (s.metodoPago === 'efectivo' ? s.valorSesion : 0)), 0);
    const transferenciaSueltas = sueltasDia.reduce((a, s) => a + (s.montoTransferencia || (s.metodoPago === 'transferencia' ? s.valorSesion : 0)), 0);
    const datafonoSueltas = sueltasDia.reduce((a, s) => a + (s.montoDatafono || (s.metodoPago === 'datafono' ? s.valorSesion : 0)), 0);
    const efectivoAbonos = abonosDia.reduce((a, ab) => a + (ab.montoEfectivo || (ab.metodoPago === 'efectivo' ? ab.monto : 0)), 0);
    const transferenciaAbonos = abonosDia.reduce((a, ab) => a + (ab.montoTransferencia || (ab.metodoPago === 'transferencia' ? ab.monto : 0)), 0);
    const datafonoAbonos = abonosDia.reduce((a, ab) => a + (ab.montoDatafono || (ab.metodoPago === 'datafono' ? ab.monto : 0)), 0);

    const efectivoDia      = efectivoVentas + efectivoSueltas + efectivoAbonos;
    const transferenciaDia = transferenciaVentas + transferenciaSueltas + transferenciaAbonos;
    const datafonoDia      = datafonoVentas + datafonoSueltas + datafonoAbonos;
    // INGRESOS = dinero realmente recibido hoy (cash flow real)
    const ingresos = ventasDia.reduce((a, p) => a + recibidoEnVenta(p), 0)
                   + sueltasDia.reduce((a, s) => a + (s.valorSesion || 0), 0)
                   + abonosDia.reduce((a, ab) => a + (ab.monto || 0), 0);
    const reservaFisio = sesionesDia.reduce((a, s) => a + comisionTotalSesion(s), 0);
    // gastosDia se mantiene completo para la lista visible; el total de "caja" excluye los
    // egresos de honorarios auto-generados (ya restados vía reservaFisio) para no duplicar el descuento.
    const totalGastos = gastosDia.filter((g) => !g.esLiquidacionFisio).reduce((a, g) => a + (g.monto || 0), 0);
    return { ventasDia, sesionesDia, sueltasDia, gastosDia, abonosDia, ingresos, reservaFisio, totalGastos, cajaNegocio: ingresos - reservaFisio - totalGastos, efectivoDia, transferenciaDia, datafonoDia };
  }, [state, fecha]);

  const mes = monthKey(fecha);
  const acumulado = useMemo(() => {
    const ventas   = state.paquetes.filter((p) => monthKey(p.fechaCompra) === mes);
    const sesiones = state.sesiones.filter((s) => monthKey(s.fecha) === mes);
    const sueltas  = sesiones.filter((s) => !s.paqueteId);
    // Excluye egresos de honorarios auto-generados (ya restados vía reserva, evita doble conteo)
    const gastos   = (state.gastos || []).filter((g) => monthKey(g.fecha) === mes && !g.esLiquidacionFisio);
    const abonosMes = state.paquetes.flatMap((p) => (p.abonos || []).filter((a) => monthKey(a.fecha) === mes));
    const recibidoEnVenta = pagoInicialVenta;
    const ingresos = ventas.reduce((a, p) => a + recibidoEnVenta(p), 0)
                   + sueltas.reduce((a, s) => a + s.valorSesion, 0)
                   + abonosMes.reduce((a, ab) => a + (ab.monto || 0), 0);
    const reserva  = sesiones.reduce((a, s) => a + comisionTotalSesion(s), 0);
    const tg       = gastos.reduce((a, g) => a + g.monto, 0);
    return { ingresos, reserva, gastos: tg, neto: ingresos - reserva - tg };
  }, [state, mes]);

  const cambiarFecha = (delta) => {
    const d = new Date(fecha + 'T12:00:00');
    d.setDate(d.getDate() + delta);
    setFecha(d.toISOString().slice(0, 10));
  };

  const eliminarGasto = async (id) => {
    if (!confirm('¿Eliminar este gasto?')) return;
    try { await onDeleteGasto(id); } catch (e) { alert('Error: ' + e.message); }
  };

  return (
    <div className="space-y-4 max-w-5xl">
      {/* Selector de fecha */}
      <div className="flex items-center justify-between bg-white rounded-2xl border border-stone-200 p-2">
        <button onClick={() => cambiarFecha(-1)} className="p-2 rounded-lg hover:bg-stone-50"><Icon name="arrow-left" /></button>
        <div className="text-center">
          <div className="text-xs text-stone-500 uppercase tracking-wide font-semibold">Balance del día</div>
          <div className="font-bold text-lg">{fmtDate(fecha)}</div>
        </div>
        <button onClick={() => cambiarFecha(1)} disabled={fecha >= todayISO()} className="p-2 rounded-lg hover:bg-stone-50 disabled:opacity-30 disabled:cursor-not-allowed">
          <Icon name="arrow-right" />
        </button>
      </div>

      {/* ── Caja diaria ── */}
      <div className="rounded-2xl border-2 border-stone-300 bg-white overflow-hidden">
        {/* cabecera */}
        <div className="px-5 py-3 bg-stone-50 border-b border-stone-200 flex items-center justify-between">
          <div className="flex items-center gap-2">
            <span className="text-xl">🏦</span>
            <div>
              <div className="font-bold text-sm text-stone-800">Caja del día</div>
              <div className="text-xs text-stone-400">Base + efectivo ingresado → banco al cerrar</div>
            </div>
          </div>
          {/* editar base */}
          {editingBase ? (
            <div className="flex items-center gap-2">
              <input
                autoFocus
                type="number"
                value={baseInput}
                onChange={(e) => setBaseInput(e.target.value)}
                onKeyDown={(e) => { if (e.key === 'Enter') saveBase(); if (e.key === 'Escape') setEditingBase(false); }}
                placeholder="0"
                className="w-32 px-3 py-1.5 text-sm border border-brand-400 rounded-lg focus:outline-none focus:ring-2 focus:ring-brand-200"
              />
              <button onClick={saveBase} className="px-3 py-1.5 bg-brand-600 text-white text-xs font-semibold rounded-lg hover:bg-brand-700">✓</button>
              <button onClick={() => setEditingBase(false)} className="px-2 py-1.5 text-stone-400 text-xs hover:text-stone-600">✕</button>
            </div>
          ) : (
            <button
              onClick={() => { setBaseInput(String(baseCaja)); setEditingBase(true); }}
              className="flex items-center gap-1.5 px-3 py-1.5 rounded-lg border border-stone-200 text-xs font-medium text-stone-600 hover:bg-stone-100 transition"
            >
              <Icon name="edit" className="w-3.5 h-3.5" />
              Cambiar base
            </button>
          )}
        </div>

        {/* cuerpo: flujo del día */}
        <div className="p-5">
          <div className="space-y-2 text-sm">
            {/* base */}
            <div className="flex items-center justify-between py-1.5">
              <div className="flex items-center gap-2 text-stone-600">
                <span className="w-7 h-7 rounded-lg bg-stone-100 flex items-center justify-center text-base">💰</span>
                <span>Base de caja</span>
                {baseCaja === 0 && <span className="text-[10px] text-amber-600 bg-amber-50 px-1.5 py-0.5 rounded-full font-semibold">Configura el monto</span>}
              </div>
              <span className="font-semibold tabular-nums text-stone-700">{COP(baseCaja)}</span>
            </div>
            {/* efectivo que entró */}
            <div className="flex items-center justify-between py-1.5">
              <div className="flex items-center gap-2 text-emerald-700">
                <span className="w-7 h-7 rounded-lg bg-emerald-50 flex items-center justify-center text-base">💵</span>
                <span className="font-medium">Efectivo ingresado hoy</span>
              </div>
              <span className="font-semibold tabular-nums text-emerald-700">+ {COP(dia.efectivoDia)}</span>
            </div>
            {/* divisor */}
            <div className="border-t border-dashed border-stone-200 my-1" />
            {/* total en caja ahora */}
            <div className="flex items-center justify-between py-1.5 bg-stone-50 rounded-xl px-3 -mx-3">
              <div className="flex items-center gap-2 font-bold text-stone-800">
                <span className="w-7 h-7 rounded-lg bg-stone-200 flex items-center justify-center text-base">🗄️</span>
                <span>Total en caja ahora</span>
              </div>
              <span className="font-bold text-lg tabular-nums">{COP(baseCaja + dia.efectivoDia)}</span>
            </div>
            {/* divisor */}
            <div className="border-t border-dashed border-stone-200 my-1" />
            {/* para llevar al banco */}
            <div className="flex items-center justify-between py-1.5">
              <div className="flex items-center gap-2 text-blue-700">
                <span className="w-7 h-7 rounded-lg bg-blue-50 flex items-center justify-center text-base">🏦</span>
                <span className="font-medium">Para llevar al banco</span>
                <span className="text-[10px] text-blue-500">(efectivo del día)</span>
              </div>
              <span className="font-semibold tabular-nums text-blue-700">− {COP(dia.efectivoDia)}</span>
            </div>
            {/* queda en caja */}
            <div className="flex items-center justify-between py-1.5 bg-emerald-50 rounded-xl px-3 -mx-3">
              <div className="flex items-center gap-2 text-emerald-800 font-bold">
                <span className="w-7 h-7 rounded-lg bg-emerald-100 flex items-center justify-center text-base">✅</span>
                <span>Queda en caja</span>
              </div>
              <span className="font-bold text-lg tabular-nums text-emerald-800">{COP(baseCaja)}</span>
            </div>
          </div>
        </div>
      </div>

      {/* Dos bolsillos */}
      <div className="grid sm:grid-cols-2 gap-3">
        <div className="rounded-2xl p-5 border border-emerald-200 bg-gradient-to-br from-emerald-50 to-white">
          <div className="flex items-center justify-between mb-2">
            <span className="text-[11px] uppercase tracking-wide font-bold text-emerald-700">Bolsillo del negocio</span>
            <Icon name="wallet" className="w-5 h-5 text-emerald-600" />
          </div>
          <div className="text-3xl font-bold tabular-nums text-emerald-900">{COP(dia.cajaNegocio)}</div>
          <div className="text-xs text-stone-500 mt-1">Ingresos − fisio − gastos</div>
        </div>
        <div className="rounded-2xl p-5 border border-rose-200 bg-gradient-to-br from-rose-50 to-white">
          <div className="flex items-center justify-between mb-2">
            <span className="text-[11px] uppercase tracking-wide font-bold text-rose-700">Bolsillo del fisio (50%)</span>
            <Icon name="dollar-sign" className="w-5 h-5 text-rose-600" />
          </div>
          <div className="text-3xl font-bold tabular-nums text-rose-900">{COP(dia.reservaFisio)}</div>
          <div className="text-xs text-stone-500 mt-1">Separar para liquidar después</div>
        </div>
      </div>

      {/* Desglose método de pago */}
      {dia.ingresos > 0 && (() => {
        const cards = [
          { icon: '💵', label: 'Efectivo',      val: dia.efectivoDia,      cls: 'bg-green-50 border-green-200',   t: 'text-green-700',  v: 'text-green-900'  },
          { icon: '📲', label: 'Transferencia', val: dia.transferenciaDia, cls: 'bg-blue-50 border-blue-200',     t: 'text-blue-700',   v: 'text-blue-900'   },
          { icon: '💳', label: 'Datáfono',      val: dia.datafonoDia,      cls: 'bg-indigo-50 border-indigo-200', t: 'text-indigo-700', v: 'text-indigo-900' },
        ].filter((c) => c.val > 0 || c.label !== 'Datáfono'); // datáfono solo si hay
        return (
          <div className={`grid gap-2 ${cards.length === 3 ? 'grid-cols-3' : 'grid-cols-2'}`}>
            {cards.map((c) => (
              <div key={c.label} className={`${c.cls} border rounded-2xl p-3 flex items-center gap-2`}>
                <span className="text-2xl">{c.icon}</span>
                <div className="min-w-0">
                  <div className={`text-[11px] ${c.t} font-semibold uppercase tracking-wide`}>{c.label}</div>
                  <div className={`text-base sm:text-lg font-bold ${c.v} tabular-nums`}>{COP(c.val)}</div>
                </div>
              </div>
            ))}
          </div>
        );
      })()}

      {/* Resumen */}
      <div className="grid grid-cols-3 gap-2">
        {[
          { label: 'Ingresos',  value: dia.ingresos,     color: 'text-emerald-600', icon: 'arrow-up-right'   },
          { label: 'Fisio',     value: dia.reservaFisio, color: 'text-rose-600',    icon: 'arrow-down-right' },
          { label: 'Gastos',    value: dia.totalGastos,  color: 'text-amber-600',   icon: 'arrow-down-right' },
        ].map(({ label, value, color, icon }) => (
          <div key={label} className="bg-white rounded-xl p-3 border border-stone-200 text-center">
            <div className={`text-[10px] uppercase tracking-wide text-stone-500 font-semibold flex items-center justify-center gap-1`}>
              <Icon name={icon} className={`w-3 h-3 ${color}`} />{label}
            </div>
            <div className="font-bold text-base mt-0.5 tabular-nums">{COP(value)}</div>
          </div>
        ))}
      </div>

      {/* Ingresos */}
      <Card>
        <div className="flex items-center gap-2 mb-3">
          <Icon name="arrow-up-right" className="w-4 h-4 text-emerald-600" />
          <h3 className="font-semibold">Ingresos del día</h3>
          <span className="text-xs text-stone-500">({dia.ventasDia.length + dia.sueltasDia.length + dia.abonosDia.length})</span>
        </div>
        {dia.ventasDia.length === 0 && dia.sueltasDia.length === 0 && dia.abonosDia.length === 0 ? <Empty text="No hubo ingresos este día." /> : (
          <ul className="divide-y divide-stone-100">
            {dia.ventasDia.map((p) => {
              const cli = state.clientes.find((c) => c.id === p.clienteId);
              const sv  = findSv(state, p.servicioId);
              const pagoIcon = pagoIcono(p.metodoPago);
              const recibido = p.saldoPendiente > 0 ? (p.abonoInicial || 0) : (p.valorTotal || 0);
              return (
                <li key={p.id} className="py-2.5 flex items-center justify-between gap-2 text-sm">
                  <div className="min-w-0">
                    <div className="font-medium truncate">{cli?.nombre}</div>
                    <div className="text-xs text-stone-500 truncate flex items-center gap-1.5 flex-wrap">
                      <span>📦 {sv?.nombre}</span>
                      <span className="bg-stone-100 px-1.5 py-0.5 rounded-full">{pagoIcon} {p.metodoPago}</span>
                      {p.metodoPago === 'mixto' && <span className="text-stone-400">{mixtoDesglose(p)}</span>}
                      {p.numeroFactura && <span className="text-violet-600 font-medium">🧾 {p.numeroFactura}</span>}
                      {p.saldoPendiente > 0 && <span className="text-rose-600 font-medium">⚠ Abono inicial (saldo: {COP(p.saldoPendiente)})</span>}
                    </div>
                  </div>
                  <div className="font-semibold text-emerald-700 tabular-nums shrink-0">
                    +{COP(recibido)}
                    {p.saldoPendiente > 0 && <div className="text-[10px] text-stone-400 font-normal">de {COP(p.valorTotal)}</div>}
                  </div>
                </li>
              );
            })}
            {dia.sueltasDia.map((s) => {
              const cli = state.clientes.find((c) => c.id === s.clienteId);
              const sv  = findSv(state, s.servicioId);
              const pagoIcon = pagoIcono(s.metodoPago);
              return (
                <li key={s.id} className="py-2.5 flex items-center justify-between gap-2 text-sm">
                  <div className="min-w-0">
                    <div className="font-medium truncate">{cli?.nombre}</div>
                    <div className="text-xs text-stone-500 truncate flex items-center gap-1.5">
                      <span>💆 {sv?.nombre} (sesión suelta)</span>
                      <span className="bg-stone-100 px-1.5 py-0.5 rounded-full">{pagoIcon} {s.metodoPago}</span>
                      {s.metodoPago === 'mixto' && <span className="text-stone-400">{mixtoDesglose(s)}</span>}
                    </div>
                  </div>
                  <div className="font-semibold text-emerald-700 tabular-nums shrink-0">+{COP(s.valorSesion)}</div>
                </li>
              );
            })}
            {dia.abonosDia.map((ab) => {
              const cli = state.clientes.find((c) => c.id === ab.clienteId);
              const sv  = findSv(state, ab.servicioId);
              const pagoIcon = pagoIcono(ab.metodoPago);
              return (
                <li key={ab.id} className="py-2.5 flex items-center justify-between gap-2 text-sm bg-rose-50/40">
                  <div className="min-w-0">
                    <div className="font-medium truncate">{cli?.nombre} <span className="text-[10px] text-rose-600 font-semibold ml-1">ABONO</span></div>
                    <div className="text-xs text-stone-500 truncate flex items-center gap-1.5 flex-wrap">
                      <span>💵 Abono sobre {sv?.nombre}</span>
                      <span className="bg-stone-100 px-1.5 py-0.5 rounded-full">{pagoIcon} {ab.metodoPago}</span>
                      {ab.metodoPago === 'mixto' && <span className="text-stone-400">{mixtoDesglose(ab)}</span>}
                      {ab.numeroFactura && <span className="text-violet-600 font-semibold">🧾 {ab.numeroFactura}</span>}
                      {ab.nota && <span className="text-stone-400">— {ab.nota}</span>}
                    </div>
                  </div>
                  <div className="font-semibold text-emerald-700 tabular-nums shrink-0">+{COP(ab.monto)}</div>
                </li>
              );
            })}
          </ul>
        )}
      </Card>

      {/* Comisiones */}
      {dia.sesionesDia.length > 0 && (
        <Card>
          <div className="flex items-center gap-2 mb-3">
            <Icon name="activity" className="w-4 h-4 text-rose-600" />
            <h3 className="font-semibold">Comisiones generadas</h3>
            <span className="text-xs text-stone-500">({dia.sesionesDia.length} sesiones)</span>
          </div>
          <ul className="divide-y divide-stone-100">
            {dia.sesionesDia.map((s) => {
              const cli    = state.clientes.find((c) => c.id === s.clienteId);
              const fis    = state.fisios.find((f) => f.id === s.fisioId);
              const extras = (s.fisiosAdicionales || []).map((ex) => state.fisios.find((f) => f.id === ex.fisioId)).filter(Boolean);
              const todosNombres = [fis?.nombre, ...extras.map((f) => f.nombre)].filter(Boolean);
              const nFisios = 1 + extras.length;
              return (
                <li key={s.id} className="py-2.5 flex items-center justify-between gap-2 text-sm">
                  <div className="min-w-0">
                    <div className="font-medium truncate">
                      {todosNombres.join(' + ')}
                      {nFisios > 1 && <span className="ml-1 text-violet-600">{nFisios === 2 ? '👫' : `🙌${nFisios * 2}m`}</span>}
                    </div>
                    <div className="text-xs text-stone-500 truncate">{cli?.nombre} · {COP(s.valorSesion)}</div>
                  </div>
                  <div className="text-right shrink-0">
                    <div className="font-semibold text-rose-700 tabular-nums">−{COP(comisionTotalSesion(s))}</div>
                    {nFisios > 1 && (
                      <div className="text-xs text-rose-400">
                        {[[fis, s.comisionFisio], ...(s.fisiosAdicionales || []).map((ex, i) => [extras[i], ex.comision])].filter(([f]) => f).map(([f, c]) => `${f.nombre.split(' ')[0]}: ${COP(c)}`).join(' · ')}
                      </div>
                    )}
                  </div>
                </li>
              );
            })}
          </ul>
        </Card>
      )}

      {/* Gastos */}
      <Card>
        <div className="flex items-center justify-between mb-3">
          <div className="flex items-center gap-2">
            <Icon name="arrow-down-right" className="w-4 h-4 text-amber-600" />
            <h3 className="font-semibold">Gastos de caja menor</h3>
            <span className="text-xs text-stone-500">({dia.gastosDia.length})</span>
          </div>
          <button onClick={() => setModal(true)} className="px-3 py-1.5 rounded-lg bg-brand-600 text-white text-xs font-medium hover:bg-brand-700 flex items-center gap-1">
            <Icon name="plus" className="w-3.5 h-3.5" /> Agregar
          </button>
        </div>
        {dia.gastosDia.length === 0 ? <Empty text="Sin gastos este día." /> : (
          <ul className="divide-y divide-stone-100">
            {dia.gastosDia.map((g) => {
              const cat = CATEGORIAS_GASTO.find((c) => c.id === g.categoria) || CATEGORIAS_GASTO[5];
              return (
                <li key={g.id} className="py-2.5 flex items-center justify-between gap-2 text-sm">
                  <div className="min-w-0 flex items-center gap-2">
                    <span className="text-lg shrink-0">{cat.icon}</span>
                    <div className="min-w-0"><div className="font-medium truncate">{g.descripcion}</div><div className="text-xs text-stone-500">{cat.nombre}</div></div>
                  </div>
                  <div className="flex items-center gap-2 shrink-0">
                    <div className="font-semibold text-amber-700 tabular-nums">−{COP(g.monto)}</div>
                    <button onClick={() => eliminarGasto(g.id)} className="p-1.5 rounded text-stone-400 hover:text-rose-600 hover:bg-rose-50">
                      <Icon name="trash" className="w-4 h-4" />
                    </button>
                  </div>
                </li>
              );
            })}
          </ul>
        )}
      </Card>

      {/* Acumulado del mes */}
      <Card className="bg-stone-50/50">
        <h3 className="font-semibold text-sm mb-3">Acumulado de {new Date(mes + '-15').toLocaleDateString('es-CO', { month: 'long', year: 'numeric' })}</h3>
        <div className="grid grid-cols-2 sm:grid-cols-4 gap-3 text-sm">
          <div><div className="text-xs text-stone-500">Ingresos</div><div className="font-bold tabular-nums">{COP(acumulado.ingresos)}</div></div>
          <div><div className="text-xs text-stone-500">Fisio (50%)</div><div className="font-bold tabular-nums">{COP(acumulado.reserva)}</div></div>
          <div><div className="text-xs text-stone-500">Gastos</div><div className="font-bold tabular-nums">{COP(acumulado.gastos)}</div></div>
          <div className="bg-emerald-100 rounded-lg p-2 -m-2">
            <div className="text-xs text-emerald-700 font-semibold">Neto del negocio</div>
            <div className="font-bold tabular-nums text-emerald-900">{COP(acumulado.neto)}</div>
          </div>
        </div>
      </Card>

      {modal && <ModalGasto fecha={fecha} onAddGasto={onAddGasto} onClose={() => setModal(false)} />}
    </div>
  );
}

function ModalGasto({ fecha, onAddGasto, onClose }) {
  const [descripcion,  setDescripcion]  = useState('');
  const [monto,        setMonto]        = useState('');
  const [categoria,    setCategoria]    = useState('insumos');
  const [fechaGasto,   setFechaGasto]   = useState(fecha);
  const [saving,       setSaving]       = useState(false);

  const submit = async (e) => {
    e.preventDefault();
    if (!descripcion.trim() || !monto || Number(monto) <= 0) return;
    if (!confirmarFechaRazonable(fechaGasto, 'la fecha del gasto')) return;
    setSaving(true);
    try {
      await onAddGasto({ id: uid(), fecha: fechaGasto, descripcion: descripcion.trim(), monto: Number(monto), categoria });
      onClose();
    } catch (err) {
      alert('Error: ' + err.message);
      setSaving(false);
    }
  };

  return (
    <div className="fixed inset-0 z-50 flex items-end sm:items-center justify-center p-0 sm:p-4">
      <div className="absolute inset-0 bg-stone-900/40" onClick={onClose} />
      <div className="relative bg-white rounded-t-2xl sm:rounded-2xl w-full max-w-md p-5 fade-in">
        <div className="flex items-center justify-between mb-4">
          <h3 className="font-bold text-lg">Nuevo gasto</h3>
          <button onClick={onClose} className="p-1.5 rounded hover:bg-stone-100"><Icon name="x" /></button>
        </div>
        <form onSubmit={submit} className="space-y-4">
          <div><Label>Descripción</Label><Input value={descripcion} onChange={setDescripcion} placeholder="Ej: Aceites de masaje" required /></div>
          <div className="grid grid-cols-2 gap-3">
            <div><Label>Monto</Label><Input type="number" value={monto} onChange={setMonto} placeholder="0" required /></div>
            <div><Label>Fecha</Label><Input type="date" value={fechaGasto} onChange={setFechaGasto}  min={FECHA_MIN} max={FECHA_MAX()}/></div>
          </div>
          <div>
            <Label>Categoría</Label>
            <div className="grid grid-cols-3 gap-2">
              {CATEGORIAS_GASTO.map((c) => (
                <button key={c.id} type="button" onClick={() => setCategoria(c.id)}
                  className={`p-2 rounded-lg border text-xs flex flex-col items-center gap-0.5 transition ${categoria === c.id ? 'border-brand-500 bg-brand-50 text-brand-700' : 'border-stone-200 text-stone-600 hover:bg-stone-50'}`}>
                  <span className="text-lg">{c.icon}</span>
                  <span className="font-medium">{c.nombre}</span>
                </button>
              ))}
            </div>
          </div>
          <button disabled={saving} className="w-full py-3 rounded-lg bg-brand-600 text-white font-semibold hover:bg-brand-700 disabled:opacity-60 flex items-center justify-center gap-2">
            {saving ? <><Icon name="loader" className="w-4 h-4 animate-spin" />Guardando…</> : 'Registrar gasto'}
          </button>
        </form>
      </div>
    </div>
  );
}

// ---------- Egresos ----------
function Egresos({ state, onAddGasto, onDeleteGasto }) {
  const meses = useMemo(() => {
    const keys = new Set((state.gastos || []).map((g) => monthKey(g.fecha)));
    keys.add(monthKey(todayISO()));
    return [...keys].sort().reverse();
  }, [state.gastos]);

  const [mes,        setMes]        = useState(meses[0] || monthKey(todayISO()));
  const [showForm,   setShowForm]   = useState(false);
  const [catFiltro,  setCatFiltro]  = useState('todas');

  // form state
  const [fDesc,       setFDesc]       = useState('');
  const [fMonto,      setFMonto]      = useState('');
  const [fFecha,      setFFecha]      = useState(todayISO());
  const [fCat,        setFCat]        = useState('insumos');
  const [fBenef,      setFBenef]      = useState('');
  const [fMetodoPago, setFMetodoPago] = useState('efectivo');
  const [saving,      setSaving]      = useState(false);

  const gastosMes = useMemo(
    () => (state.gastos || []).filter((g) => monthKey(g.fecha) === mes),
    [state.gastos, mes],
  );

  const gastosFiltrados = useMemo(
    () => catFiltro === 'todas' ? gastosMes : gastosMes.filter((g) => g.categoria === catFiltro),
    [gastosMes, catFiltro],
  );

  // totales por categoría
  const porCategoria = useMemo(() => {
    return CATEGORIAS_GASTO.map((cat) => ({
      ...cat,
      total: gastosMes.filter((g) => g.categoria === cat.id).reduce((a, g) => a + g.monto, 0),
      count: gastosMes.filter((g) => g.categoria === cat.id).length,
    })).filter((c) => c.total > 0).sort((a, b) => b.total - a.total);
  }, [gastosMes]);

  const totalMes  = gastosMes.reduce((a, g) => a + g.monto, 0);
  const maxCat    = Math.max(...porCategoria.map((c) => c.total), 1);

  // egresos fijos vs variables
  const fijos     = gastosMes.filter((g) => CATEGORIAS_GASTO.find((c) => c.id === g.categoria)?.tipo === 'fijo').reduce((a, g) => a + g.monto, 0);
  const variables = totalMes - fijos;

  const eliminar = async (id) => {
    if (!confirm('¿Eliminar este egreso?')) return;
    try { await onDeleteGasto(id); } catch (e) { alert('Error: ' + e.message); }
  };

  // número correlativo: max existente + 1
  const nextNumero = useMemo(() => {
    const nums = (state.gastos || []).map((g) => g.numeroEgreso || 0).filter(Boolean);
    return nums.length > 0 ? Math.max(...nums) + 1 : 1;
  }, [state.gastos]);

  const handleSubmit = async (e) => {
    e.preventDefault();
    if (!fDesc.trim() || !fMonto || Number(fMonto) <= 0) return;
    if (!confirmarFechaRazonable(fFecha, 'la fecha del egreso')) return;
    setSaving(true);
    try {
      await onAddGasto({
        id: uid(), fecha: fFecha, descripcion: fDesc.trim(), monto: Number(fMonto), categoria: fCat,
        beneficiario: fBenef.trim(), metodo_pago_egreso: fMetodoPago, numero_egreso: nextNumero,
      });
      setFDesc(''); setFMonto(''); setFFecha(todayISO()); setFCat('insumos'); setFBenef(''); setFMetodoPago('efectivo');
      setShowForm(false);
    } catch (err) { alert('Error: ' + err.message); }
    finally { setSaving(false); }
  };

  const mesLabel = (mk) => new Date(mk + '-15').toLocaleDateString('es-CO', { month: 'long', year: 'numeric' });

  return (
    <div className="space-y-5 max-w-5xl">

      {/* ── Cabecera: selector mes + botón agregar ── */}
      <div className="flex flex-wrap items-center justify-between gap-3">
        <div>
          <h2 className="font-bold text-lg capitalize">{mesLabel(mes)}</h2>
          <p className="text-xs text-stone-400">{gastosMes.length} movimientos registrados</p>
        </div>
        <div className="flex items-center gap-2">
          <select value={mes} onChange={(e) => setMes(e.target.value)}
            className="px-3 py-2 rounded-lg border border-stone-200 bg-white text-sm focus:outline-none focus:ring-2 focus:ring-brand-200">
            {meses.map((mk) => <option key={mk} value={mk}>{mesLabel(mk)}</option>)}
          </select>
          <button onClick={() => setShowForm((v) => !v)}
            className={`px-4 py-2 rounded-lg text-sm font-semibold flex items-center gap-1.5 transition ${showForm ? 'bg-stone-200 text-stone-700 hover:bg-stone-300' : 'bg-brand-600 text-white hover:bg-brand-700'}`}>
            <Icon name={showForm ? 'x' : 'plus'} className="w-4 h-4" />
            {showForm ? 'Cancelar' : 'Registrar egreso'}
          </button>
        </div>
      </div>

      {/* ── Formulario inline ── */}
      {showForm && (
        <div className="bg-white border-2 border-brand-200 rounded-2xl p-5 fade-in shadow-sm">
          <h3 className="font-bold text-stone-800 mb-4 flex items-center gap-2">
            <span className="w-7 h-7 rounded-lg bg-amber-100 text-amber-700 flex items-center justify-center text-sm">📉</span>
            Nuevo egreso
          </h3>
          <form onSubmit={handleSubmit} className="space-y-4">
            {/* número + fecha */}
            <div className="grid grid-cols-2 gap-3">
              <div>
                <label className="block text-xs font-semibold text-stone-600 mb-1.5">N° Egreso</label>
                <div className="w-full px-4 py-2.5 rounded-xl border border-stone-200 bg-stone-100 text-sm text-stone-500 font-mono">
                  #{String(nextNumero).padStart(4, '0')}
                </div>
              </div>
              <div>
                <label className="block text-xs font-semibold text-stone-600 mb-1.5">Fecha</label>
                <input type="date" value={fFecha} onChange={(e) => setFFecha(e.target.value)} min={FECHA_MIN} max={FECHA_MAX()}
                  className="w-full px-4 py-2.5 rounded-xl border border-stone-200 bg-stone-50 text-sm focus:outline-none focus:border-brand-500 focus:ring-2 focus:ring-brand-100 focus:bg-white" />
              </div>
            </div>
            {/* descripción */}
            <div>
              <label className="block text-xs font-semibold text-stone-600 mb-1.5">Descripción *</label>
              <input autoFocus value={fDesc} onChange={(e) => setFDesc(e.target.value)} required
                placeholder="Ej: Pago arriendo julio, aceites sesión, etc."
                className="w-full px-4 py-2.5 rounded-xl border border-stone-200 bg-stone-50 text-sm focus:outline-none focus:border-brand-500 focus:ring-2 focus:ring-brand-100 focus:bg-white" />
            </div>
            {/* beneficiario + monto */}
            <div className="grid grid-cols-2 gap-3">
              <div>
                <label className="block text-xs font-semibold text-stone-600 mb-1.5">Beneficiario / Proveedor</label>
                <input value={fBenef} onChange={(e) => setFBenef(e.target.value)}
                  placeholder="Nombre de quien recibe"
                  className="w-full px-4 py-2.5 rounded-xl border border-stone-200 bg-stone-50 text-sm focus:outline-none focus:border-brand-500 focus:ring-2 focus:ring-brand-100 focus:bg-white" />
              </div>
              <div>
                <label className="block text-xs font-semibold text-stone-600 mb-1.5">Monto (COP) *</label>
                <input type="number" value={fMonto} onChange={(e) => setFMonto(e.target.value)}
                  required min="1" placeholder="0"
                  className="w-full px-4 py-2.5 rounded-xl border border-stone-200 bg-stone-50 text-sm focus:outline-none focus:border-brand-500 focus:ring-2 focus:ring-brand-100 focus:bg-white" />
              </div>
            </div>
            {/* método de pago */}
            <div>
              <label className="block text-xs font-semibold text-stone-600 mb-2">Forma de pago</label>
              <div className="grid grid-cols-4 rounded-lg p-0.5 bg-stone-100 text-sm gap-0.5">
                {[{ v: 'efectivo', l: '💵 Efectivo' }, { v: 'transferencia', l: '📲 Transfer.' }, { v: 'datafono', l: '💳 Datáfono' }, { v: 'cheque', l: '📝 Cheque' }].map((o) => (
                  <button key={o.v} type="button" onClick={() => setFMetodoPago(o.v)}
                    className={`py-1.5 rounded-md font-medium transition text-xs ${fMetodoPago === o.v ? 'bg-white shadow-sm text-stone-900' : 'text-stone-500 hover:text-stone-700'}`}>
                    {o.l}
                  </button>
                ))}
              </div>
            </div>
            {/* categoría */}
            <div>
              <label className="block text-xs font-semibold text-stone-600 mb-2">Categoría</label>
              <div className="grid grid-cols-3 sm:grid-cols-4 lg:grid-cols-6 gap-2">
                {CATEGORIAS_GASTO.map((c) => {
                  const esFijo = c.tipo === 'fijo';
                  const sel    = fCat === c.id;
                  return (
                    <button key={c.id} type="button" onClick={() => setFCat(c.id)}
                      className={`p-2.5 rounded-xl border-2 text-xs flex flex-col items-center gap-1 transition font-medium ${
                        sel
                          ? esFijo
                            ? 'border-rose-400 bg-rose-50 text-rose-700'
                            : 'border-amber-400 bg-amber-50 text-amber-700'
                          : 'border-stone-200 text-stone-500 hover:border-stone-300 hover:bg-stone-50'
                      }`}>
                      <span className="text-xl leading-none">{c.icon}</span>
                      <span className="leading-tight text-center">{c.nombre}</span>
                      {esFijo && sel && <span className="text-[9px] uppercase tracking-wide opacity-70">fijo</span>}
                    </button>
                  );
                })}
              </div>
            </div>
            {/* acciones */}
            <div className="flex gap-2 pt-1">
              <button type="submit" disabled={saving || !fDesc.trim() || !fMonto}
                className="flex-1 py-3 rounded-xl bg-brand-600 text-white font-semibold hover:bg-brand-700 disabled:opacity-50 flex items-center justify-center gap-2 text-sm">
                {saving ? <><Icon name="loader" className="w-4 h-4 animate-spin" />Guardando…</> : '✓ Registrar egreso'}
              </button>
              <button type="button" onClick={() => setShowForm(false)}
                className="px-5 py-3 rounded-xl border border-stone-200 text-stone-600 hover:bg-stone-50 text-sm font-medium">
                Cancelar
              </button>
            </div>
          </form>
        </div>
      )}

      {/* ── KPIs del mes ── */}
      <div className="grid grid-cols-2 lg:grid-cols-4 gap-3">
        <div className="bg-white rounded-2xl p-4 border border-stone-200">
          <div className="text-[10px] uppercase tracking-wide text-amber-700 font-bold mb-1">Total egresos</div>
          <div className="text-2xl font-bold tabular-nums">{COP(totalMes)}</div>
          <div className="text-xs text-stone-400 mt-0.5">{gastosMes.length} registros</div>
        </div>
        <div className="bg-white rounded-2xl p-4 border border-stone-200">
          <div className="text-[10px] uppercase tracking-wide text-rose-700 font-bold mb-1">Gastos fijos</div>
          <div className="text-2xl font-bold tabular-nums">{COP(fijos)}</div>
          <div className="text-xs text-stone-400 mt-0.5">arriendo, nómina, etc.</div>
        </div>
        <div className="bg-white rounded-2xl p-4 border border-stone-200">
          <div className="text-[10px] uppercase tracking-wide text-orange-700 font-bold mb-1">Gastos variables</div>
          <div className="text-2xl font-bold tabular-nums">{COP(variables)}</div>
          <div className="text-xs text-stone-400 mt-0.5">insumos, aseo, etc.</div>
        </div>
        <div className="bg-white rounded-2xl p-4 border border-stone-200">
          <div className="text-[10px] uppercase tracking-wide text-stone-500 font-bold mb-1">Categorías usadas</div>
          <div className="text-2xl font-bold tabular-nums">{porCategoria.length}</div>
          <div className="text-xs text-stone-400 mt-0.5">de {CATEGORIAS_GASTO.length} disponibles</div>
        </div>
      </div>

      {/* ── Gráfica por categoría ── */}
      {porCategoria.length > 0 && (
        <Card>
          <h3 className="font-semibold mb-4">Distribución por categoría</h3>
          <div className="space-y-3">
            {porCategoria.map((cat) => {
              const pct = Math.round((cat.total / totalMes) * 100);
              const barPct = Math.round((cat.total / maxCat) * 100);
              const esFijo = cat.tipo === 'fijo';
              return (
                <div key={cat.id}>
                  <div className="flex items-center justify-between text-sm mb-1.5">
                    <div className="flex items-center gap-2 min-w-0">
                      <span className="text-base shrink-0">{cat.icon}</span>
                      <span className="font-medium truncate">{cat.nombre}</span>
                      <span className={`hidden sm:inline-block text-[10px] px-1.5 py-0.5 rounded-full font-semibold ${esFijo ? 'bg-rose-100 text-rose-700' : 'bg-stone-100 text-stone-500'}`}>
                        {esFijo ? 'fijo' : 'variable'}
                      </span>
                      <span className="text-xs text-stone-400 shrink-0">{cat.count} reg.</span>
                    </div>
                    <div className="text-right shrink-0 ml-2">
                      <span className="font-semibold text-stone-700 tabular-nums">{COP(cat.total)}</span>
                      <span className="text-xs text-stone-400 ml-1">({pct}%)</span>
                    </div>
                  </div>
                  <div className="h-2.5 bg-stone-100 rounded-full overflow-hidden">
                    <div
                      className={`h-full rounded-full transition-all ${esFijo ? 'bg-rose-400' : 'bg-amber-400'}`}
                      style={{ width: `${barPct}%` }}
                    />
                  </div>
                </div>
              );
            })}
          </div>
        </Card>
      )}

      {/* ── Lista de movimientos ── */}
      <Card>
        <div className="flex flex-wrap items-center justify-between gap-3 mb-4">
          <h3 className="font-semibold">Movimientos</h3>
          {/* filtro por categoría */}
          <div className="flex flex-wrap gap-1.5">
            <button onClick={() => setCatFiltro('todas')}
              className={`px-3 py-1 rounded-full text-xs font-semibold transition ${catFiltro === 'todas' ? 'bg-brand-600 text-white' : 'bg-stone-100 text-stone-600 hover:bg-stone-200'}`}>
              Todas
            </button>
            {porCategoria.map((cat) => (
              <button key={cat.id} onClick={() => setCatFiltro(cat.id)}
                className={`px-3 py-1 rounded-full text-xs font-semibold transition ${catFiltro === cat.id ? 'bg-amber-500 text-white' : 'bg-stone-100 text-stone-600 hover:bg-stone-200'}`}>
                {cat.icon} {cat.nombre}
              </button>
            ))}
          </div>
        </div>

        {gastosFiltrados.length === 0 ? (
          <Empty text={gastosMes.length === 0 ? 'Sin egresos este mes. Toca "Registrar egreso" para empezar.' : 'Sin registros en esta categoría.'} />
        ) : (
          <div>
            {/* agrupar por fecha descendente */}
            {Object.entries(
              gastosFiltrados.reduce((acc, g) => {
                if (!acc[g.fecha]) acc[g.fecha] = [];
                acc[g.fecha].push(g);
                return acc;
              }, {}),
            )
              .sort(([a], [b]) => b.localeCompare(a))
              .map(([fecha, items]) => (
                <div key={fecha} className="mb-4">
                  <div className="flex items-center gap-2 mb-2">
                    <span className="text-xs font-bold text-stone-400 uppercase tracking-wide">{fmtDate(fecha)}</span>
                    <div className="flex-1 h-px bg-stone-100" />
                    <span className="text-xs font-semibold text-amber-700">{COP(items.reduce((a, g) => a + g.monto, 0))}</span>
                  </div>
                  <ul className="space-y-1">
                    {items.map((g) => {
                      const cat = CATEGORIAS_GASTO.find((c) => c.id === g.categoria) || CATEGORIAS_GASTO[CATEGORIAS_GASTO.length - 1];
                      const esFijo = cat.tipo === 'fijo';
                      return (
                        <li key={g.id} className="flex items-center gap-3 px-3 py-2.5 rounded-xl hover:bg-stone-50 group transition">
                          <div className={`w-9 h-9 rounded-xl flex items-center justify-center text-lg shrink-0 ${esFijo ? 'bg-rose-50' : 'bg-amber-50'}`}>
                            {cat.icon}
                          </div>
                          <div className="flex-1 min-w-0">
                            <div className="flex items-center gap-2 flex-wrap">
                              {g.numeroEgreso && <span className="text-[10px] font-mono text-stone-400">#{String(g.numeroEgreso).padStart(4,'0')}</span>}
                              <div className="font-medium text-sm truncate">{g.descripcion}</div>
                            </div>
                            <div className="flex items-center gap-1.5 mt-0.5 flex-wrap">
                              <span className={`text-[10px] px-1.5 py-0.5 rounded-full font-semibold ${esFijo ? 'bg-rose-100 text-rose-700' : 'bg-amber-100 text-amber-700'}`}>
                                {cat.nombre}
                              </span>
                              {g.beneficiario && <span className="text-[10px] text-stone-500">→ {g.beneficiario}</span>}
                              {g.metodoPagoEgreso && <span className="text-[10px] text-stone-400">{g.metodoPagoEgreso === 'efectivo' ? '💵' : g.metodoPagoEgreso === 'transferencia' ? '📲' : g.metodoPagoEgreso === 'datafono' ? '💳' : '📝'}</span>}
                            </div>
                          </div>
                          <div className="flex items-center gap-2 shrink-0">
                            <span className="font-bold text-sm tabular-nums text-amber-700">−{COP(g.monto)}</span>
                            <button onClick={() => eliminar(g.id)}
                              className="opacity-0 group-hover:opacity-100 p-1.5 rounded-lg text-stone-300 hover:text-rose-600 hover:bg-rose-50 transition">
                              <Icon name="trash" className="w-4 h-4" />
                            </button>
                          </div>
                        </li>
                      );
                    })}
                  </ul>
                </div>
              ))}
          </div>
        )}

        {/* total al pie */}
        {gastosFiltrados.length > 0 && (
          <div className="mt-4 pt-3 border-t border-stone-100 flex items-center justify-between text-sm">
            <span className="text-stone-500">
              {catFiltro === 'todas' ? 'Total del mes' : `Total ${CATEGORIAS_GASTO.find((c) => c.id === catFiltro)?.nombre}`}
            </span>
            <span className="font-bold text-amber-700 tabular-nums">{COP(gastosFiltrados.reduce((a, g) => a + g.monto, 0))}</span>
          </div>
        )}
      </Card>

    </div>
  );
}

// ---------- Proveedores ----------
const TIPOS_DOC_PROV = [
  { v: 'NIT',  l: 'NIT' },
  { v: 'CC',   l: 'CC — Cédula' },
  { v: 'CE',   l: 'CE — Extranjería' },
  { v: 'PP',   l: 'Pasaporte' },
];

function Proveedores({ state, onAdd, onUpdate, onDelete }) {
  const [q,       setQ]       = useState('');
  const [showAdd, setShowAdd] = useState(false);
  const [detalle, setDetalle] = useState(null);

  const [fNombre,   setFNombre]   = useState('');
  const [fTipoDoc,  setFTipoDoc]  = useState('NIT');
  const [fNit,      setFNit]      = useState('');
  const [fTel,      setFTel]      = useState('');
  const [fEmail,    setFEmail]    = useState('');
  const [fDir,      setFDir]      = useState('');
  const [fCiudad,   setFCiudad]   = useState('Manizales');
  const [fContacto, setFContacto] = useState('');
  const [saving,    setSaving]    = useState(false);

  const provs = (state.proveedores || []);
  const filtrados = useMemo(() => {
    const ql = q.toLowerCase();
    return provs.filter((p) =>
      p.nombre.toLowerCase().includes(ql) ||
      (p.nit || '').includes(ql) ||
      (p.telefono || '').includes(ql)
    );
  }, [provs, q]);

  const resetForm = () => { setFNombre(''); setFTipoDoc('NIT'); setFNit(''); setFTel(''); setFEmail(''); setFDir(''); setFCiudad('Manizales'); setFContacto(''); };

  const handleAdd = async (e) => {
    e.preventDefault();
    if (!fNombre.trim()) return;
    setSaving(true);
    try {
      await onAdd({ id: uid(), nombre: fNombre.trim(), tipo_doc: fTipoDoc, nit: fNit.trim(), telefono: fTel.trim(), email: fEmail.trim(), direccion: fDir.trim(), ciudad: fCiudad.trim() || 'Manizales', contacto: fContacto.trim() });
      resetForm(); setShowAdd(false);
    } catch (err) { alert('Error: ' + err.message); }
    finally { setSaving(false); }
  };

  if (detalle) {
    const prov = provs.find((p) => p.id === detalle);
    if (!prov) { setDetalle(null); return null; }
    return <ProveedorDetalle prov={prov} onBack={() => setDetalle(null)} onUpdate={onUpdate} onDelete={async (id) => { if (!confirm('¿Eliminar proveedor?')) return; await onDelete(id); setDetalle(null); }} />;
  }

  return (
    <div className="space-y-4 max-w-4xl">
      <div className="flex items-center gap-3">
        <Input value={q} onChange={setQ} placeholder="Buscar por nombre, NIT o teléfono…" />
        <span className="text-xs text-stone-400 shrink-0">{filtrados.length}</span>
        <button onClick={() => setShowAdd((v) => !v)}
          className="shrink-0 flex items-center gap-1.5 bg-brand-600 text-white text-sm font-semibold px-4 py-2.5 rounded-xl hover:bg-brand-700">
          <Icon name="plus" className="w-4 h-4" />
          <span className="hidden sm:inline">Nuevo proveedor</span>
        </button>
      </div>

      {showAdd && (
        <Card className="fade-in">
          <h3 className="font-semibold text-sm mb-4 flex items-center gap-2"><Icon name="truck" className="w-4 h-4 text-brand-600" /> Nuevo proveedor</h3>
          <form onSubmit={handleAdd} className="space-y-3">
            <div className="grid sm:grid-cols-2 gap-3">
              <div className="sm:col-span-2">
                <label className="block text-xs text-stone-500 mb-1">Nombre / Razón social *</label>
                <Input value={fNombre} onChange={setFNombre} placeholder="Nombre del proveedor" required />
              </div>
              <div>
                <label className="block text-xs text-stone-500 mb-1">Tipo de documento</label>
                <select value={fTipoDoc} onChange={(e) => setFTipoDoc(e.target.value)}
                  className="w-full px-3 py-2.5 rounded-lg border border-stone-200 text-sm focus:outline-none focus:ring-2 focus:ring-brand-300">
                  {TIPOS_DOC_PROV.map((t) => <option key={t.v} value={t.v}>{t.l}</option>)}
                </select>
              </div>
              <div>
                <label className="block text-xs text-stone-500 mb-1">NIT / Número doc.</label>
                <Input value={fNit} onChange={setFNit} placeholder="900.123.456-7" />
              </div>
              <div>
                <label className="block text-xs text-stone-500 mb-1">Teléfono</label>
                <Input value={fTel} onChange={setFTel} placeholder="Teléfono" />
              </div>
              <div>
                <label className="block text-xs text-stone-500 mb-1">Email</label>
                <Input value={fEmail} onChange={setFEmail} placeholder="correo@proveedor.com" />
              </div>
              <div className="sm:col-span-2">
                <label className="block text-xs text-stone-500 mb-1">Dirección</label>
                <Input value={fDir} onChange={setFDir} placeholder="Dirección" />
              </div>
              <div>
                <label className="block text-xs text-stone-500 mb-1">Ciudad</label>
                <Input value={fCiudad} onChange={setFCiudad} placeholder="Manizales" />
              </div>
              <div>
                <label className="block text-xs text-stone-500 mb-1">Persona de contacto</label>
                <Input value={fContacto} onChange={setFContacto} placeholder="Nombre del contacto" />
              </div>
            </div>
            <div className="flex gap-2 pt-1">
              <button type="submit" disabled={saving}
                className="flex-1 bg-brand-600 text-white rounded-xl py-2.5 text-sm font-semibold hover:bg-brand-700 disabled:opacity-50">
                {saving ? 'Guardando…' : 'Guardar proveedor'}
              </button>
              <button type="button" onClick={() => { setShowAdd(false); resetForm(); }}
                className="flex-1 bg-stone-100 text-stone-600 rounded-xl py-2.5 text-sm font-semibold hover:bg-stone-200">
                Cancelar
              </button>
            </div>
          </form>
        </Card>
      )}

      <div className="grid gap-2">
        {filtrados.length === 0 && (
          <div className="bg-white rounded-2xl border border-stone-200 px-5 py-10 text-center text-stone-400 text-sm">
            {q ? 'Sin resultados.' : 'No hay proveedores registrados aún.'}
          </div>
        )}
        {filtrados.map((p) => (
          <button key={p.id} onClick={() => setDetalle(p.id)}
            className="w-full text-left bg-white rounded-2xl border border-stone-200 px-4 py-3.5 hover:border-brand-300 hover:bg-brand-50/30 transition">
            <div className="flex items-center justify-between gap-3">
              <div className="flex items-center gap-3 min-w-0">
                <div className="w-10 h-10 rounded-xl bg-stone-100 text-stone-600 font-bold flex items-center justify-center text-sm shrink-0">
                  {p.nombre.slice(0,2).toUpperCase()}
                </div>
                <div className="min-w-0">
                  <div className="font-semibold text-sm truncate">{p.nombre}</div>
                  <div className="flex items-center gap-2 mt-0.5 flex-wrap">
                    {p.nit && <span className="text-xs text-stone-400">{p.tipo_doc || 'NIT'}: {p.nit}</span>}
                    {p.telefono && <span className="text-xs text-stone-400">📞 {p.telefono}</span>}
                    {p.ciudad && <span className="text-xs text-stone-400">📍 {p.ciudad}</span>}
                  </div>
                </div>
              </div>
              <Icon name="chevron" className="w-4 h-4 text-stone-300 shrink-0" />
            </div>
          </button>
        ))}
      </div>
    </div>
  );
}

function ProveedorDetalle({ prov, onBack, onUpdate, onDelete }) {
  const [editing,   setEditing]   = useState(false);
  const [eNombre,   setENombre]   = useState(prov.nombre    || '');
  const [eTipoDoc,  setETipoDoc]  = useState(prov.tipo_doc  || 'NIT');
  const [eNit,      setENit]      = useState(prov.nit       || '');
  const [eTel,      setETel]      = useState(prov.telefono  || '');
  const [eEmail,    setEEmail]    = useState(prov.email     || '');
  const [eDir,      setEDir]      = useState(prov.direccion || '');
  const [eCiudad,   setECiudad]   = useState(prov.ciudad    || 'Manizales');
  const [eContacto, setEContacto] = useState(prov.contacto  || '');
  const [saving,    setSaving]    = useState(false);

  const handleSave = async (e) => {
    e.preventDefault();
    if (!eNombre.trim()) return;
    setSaving(true);
    try {
      await onUpdate(prov.id, { nombre: eNombre.trim(), tipo_doc: eTipoDoc, nit: eNit.trim(), telefono: eTel.trim(), email: eEmail.trim(), direccion: eDir.trim(), ciudad: eCiudad.trim() || 'Manizales', contacto: eContacto.trim() });
      setEditing(false);
    } catch (err) { alert('Error: ' + err.message); }
    finally { setSaving(false); }
  };

  return (
    <div className="space-y-4 max-w-3xl fade-in">
      <div className="flex items-center gap-3">
        <button onClick={onBack} className="p-2 rounded-xl border border-stone-200 bg-white hover:bg-stone-50">
          <Icon name="arrow-left" className="w-4 h-4" />
        </button>
        <div className="flex-1 min-w-0">
          <h2 className="font-bold text-lg leading-tight truncate">{prov.nombre}</h2>
          <p className="text-xs text-stone-500">Ficha del proveedor</p>
        </div>
        <button onClick={() => setEditing((v) => !v)}
          className="flex items-center gap-1.5 px-3 py-2 rounded-xl text-sm font-medium border border-stone-200 hover:border-brand-300 hover:bg-brand-50 bg-white text-stone-700">
          <Icon name="edit" className="w-4 h-4" />
          {editing ? 'Cancelar' : 'Editar'}
        </button>
        <button onClick={() => onDelete(prov.id)}
          className="p-2 rounded-xl border border-stone-200 text-stone-400 hover:text-rose-600 hover:border-rose-200 hover:bg-rose-50">
          <Icon name="trash" className="w-4 h-4" />
        </button>
      </div>

      <Card>
        <h3 className="font-semibold text-sm mb-3">Información del proveedor</h3>
        {editing ? (
          <form onSubmit={handleSave} className="space-y-3 fade-in">
            <div className="grid sm:grid-cols-2 gap-3">
              <div className="sm:col-span-2">
                <label className="block text-xs text-stone-500 mb-1">Nombre / Razón social *</label>
                <Input value={eNombre} onChange={setENombre} required />
              </div>
              <div>
                <label className="block text-xs text-stone-500 mb-1">Tipo de documento</label>
                <select value={eTipoDoc} onChange={(e) => setETipoDoc(e.target.value)}
                  className="w-full px-3 py-2.5 rounded-lg border border-stone-200 text-sm focus:outline-none focus:ring-2 focus:ring-brand-300">
                  {TIPOS_DOC_PROV.map((t) => <option key={t.v} value={t.v}>{t.l}</option>)}
                </select>
              </div>
              <div><label className="block text-xs text-stone-500 mb-1">NIT / N° doc.</label><Input value={eNit} onChange={setENit} /></div>
              <div><label className="block text-xs text-stone-500 mb-1">Teléfono</label><Input value={eTel} onChange={setETel} /></div>
              <div><label className="block text-xs text-stone-500 mb-1">Email</label><Input value={eEmail} onChange={setEEmail} /></div>
              <div className="sm:col-span-2"><label className="block text-xs text-stone-500 mb-1">Dirección</label><Input value={eDir} onChange={setEDir} /></div>
              <div><label className="block text-xs text-stone-500 mb-1">Ciudad</label><Input value={eCiudad} onChange={setECiudad} /></div>
              <div><label className="block text-xs text-stone-500 mb-1">Contacto</label><Input value={eContacto} onChange={setEContacto} /></div>
            </div>
            <div className="flex gap-2">
              <button type="submit" disabled={saving}
                className="flex-1 bg-brand-600 text-white rounded-xl py-2.5 text-sm font-semibold hover:bg-brand-700 disabled:opacity-50">
                {saving ? 'Guardando…' : '✓ Guardar cambios'}
              </button>
              <button type="button" onClick={() => setEditing(false)}
                className="flex-1 bg-stone-100 text-stone-600 rounded-xl py-2.5 text-sm font-semibold">
                Cancelar
              </button>
            </div>
          </form>
        ) : (
          <div className="grid sm:grid-cols-2 gap-3">
            {[
              { label: 'Documento',  value: `${prov.tipo_doc || 'NIT'} ${prov.nit || '—'}` },
              { label: 'Teléfono',   value: prov.telefono  || '—' },
              { label: 'Email',      value: prov.email     || '—' },
              { label: 'Dirección',  value: prov.direccion || '—' },
              { label: 'Ciudad',     value: prov.ciudad    || '—' },
              { label: 'Contacto',   value: prov.contacto  || '—' },
            ].map(({ label, value }) => (
              <div key={label}>
                <div className="text-[11px] uppercase tracking-wide text-stone-400 font-semibold">{label}</div>
                <div className="text-sm font-medium text-stone-800 mt-0.5">{value}</div>
              </div>
            ))}
          </div>
        )}
      </Card>
    </div>
  );
}

// ---------- Asistente IA ----------
// FILOSOFÍA: los números NUNCA los calcula la IA (alucinaría). Se calculan aquí,
// de forma determinista, revisando el 100% de los registros. La IA solo redacta.

// Detecta anomalías contables recorriendo todos los datos. Exacto y reproducible.
const detectarAnomalias = (state) => {
  const P = state.paquetes || [], S = state.sesiones || [], C = state.clientes || [], G = state.gastos || [];
  const nCli = (id) => C.find((c) => c.id === id)?.nombre || '(cliente borrado)';
  const ab = (p) => Array.isArray(p.abonos) ? p.abonos : [];
  const out = [];
  const add = (severidad, area, titulo, detalle) => out.push({ severidad, area, titulo, detalle });
  const anioActual = new Date().getFullYear();

  // --- Fechas imposibles ---
  P.forEach((p) => {
    const anio = Number(String(p.fechaCompra || '').slice(0, 4));
    if (!anio || anio < anioActual - 10 || anio > anioActual + 1)
      add('alta', 'ventas', 'Fecha de compra imposible',
        `${nCli(p.clienteId)} · venta de ${COP(p.valorTotal)} con fecha ${p.fechaCompra}`);
    if (p.fechaInicio && p.fechaCompra && p.fechaInicio < p.fechaCompra)
      add('baja', 'ventas', 'Inicia antes de comprar',
        `${nCli(p.clienteId)} · compra ${fmtDate(p.fechaCompra)} pero inicia ${fmtDate(p.fechaInicio)}`);
  });

  // --- Dinero registrado que no cuadra con lo recibido ---
  P.filter((p) => ab(p).length === 0).forEach((p) => {
    const suma = (p.montoEfectivo || 0) + (p.montoTransferencia || 0) + (p.montoDatafono || 0);
    const recibido = (p.abonoInicial || 0) > 0 ? p.abonoInicial : ((p.saldoPendiente || 0) > 0 ? 0 : (p.valorTotal || 0));
    if (Math.abs(suma - recibido) > 1)
      add('alta', 'ventas', 'Desglose de pago no cuadra',
        `${nCli(p.clienteId)} (${fmtDate(p.fechaCompra)}): registrado ${COP(suma)} en métodos de pago pero el dinero recibido fue ${COP(recibido)}. Diferencia ${COP(suma - recibido)}`);
  });

  // --- Consistencia de sesiones y saldos ---
  P.forEach((p) => {
    if ((p.sesionesUsadas || 0) > (p.sesionesTotales || 0))
      add('media', 'ventas', 'Más sesiones usadas que compradas',
        `${nCli(p.clienteId)}: ${p.sesionesUsadas} usadas de ${p.sesionesTotales} compradas`);
    if ((p.saldoPendiente || 0) < 0)
      add('alta', 'ventas', 'Saldo negativo', `${nCli(p.clienteId)}: saldo ${COP(p.saldoPendiente)}`);
    if ((p.abonoInicial || 0) > (p.valorTotal || 0) + 1)
      add('alta', 'ventas', 'Abonado más que el valor',
        `${nCli(p.clienteId)}: abonado ${COP(p.abonoInicial)} sobre venta de ${COP(p.valorTotal)}`);
    if ((p.valorTotal || 0) <= 0)
      add('media', 'ventas', 'Venta con valor cero', `${nCli(p.clienteId)} · ${fmtDate(p.fechaCompra)}`);
  });

  // --- Facturas duplicadas (excluye bonos multi-beneficiario, que sí las comparten) ---
  const porFactura = {};
  P.filter((p) => (p.numeroFactura || '').trim()).forEach((p) => {
    const f = String(p.numeroFactura).trim(); (porFactura[f] = porFactura[f] || []).push(p);
  });
  Object.entries(porFactura).filter(([, a]) => a.length > 1).forEach(([f, arr]) => {
    const bonoMulti = arr.every((p) => p.esBono) && new Set(arr.map((p) => p.compradorId)).size === 1 && arr[0].compradorId;
    if (bonoMulti) return;
    add('media', 'ventas', 'Número de factura repetido',
      `Factura ${f} usada en ${arr.length} ventas distintas · ${COP(arr.reduce((a, p) => a + (p.valorTotal || 0), 0))} en total · clientes: ${[...new Set(arr.map((p) => nCli(p.clienteId)))].join(', ')}`);
  });

  // --- Clientes duplicados (misma cédula) ---
  const porCed = {};
  C.forEach((c) => { const x = (c.cedula || '').trim(); if (x) (porCed[x] = porCed[x] || []).push(c); });
  const dupCed = Object.entries(porCed).filter(([, a]) => a.length > 1);
  if (dupCed.length)
    add('media', 'otro', 'Clientes duplicados con la misma cédula',
      `${dupCed.length} cédulas están repetidas en varias fichas (ej: ${dupCed.slice(0, 3).map(([ced, a]) => `${a[0].nombre} (${ced}) x${a.length}`).join('; ')}). Esto parte el historial del mismo paciente en varios registros.`);

  // --- Sesiones ---
  S.forEach((s) => {
    // Las cortesías se saltan esta regla A PROPÓSITO: por definición valen $0
    // y sí llevan comisión. Sin esta excepción, cada masaje de publicidad
    // saldría como hallazgo grave y llenaría la auditoría de falsas alarmas.
    if (!s.esCortesia && (s.comisionFisio || 0) > (s.valorSesion || 0))
      add('alta', 'sesiones', 'Comisión mayor que el valor de la sesión',
        `${nCli(s.clienteId)} (${fmtDate(s.fecha)}): comisión ${COP(s.comisionFisio)} sobre sesión de ${COP(s.valorSesion)}`);
    const anio = Number(String(s.fecha || '').slice(0, 4));
    if (!anio || anio < anioActual - 10 || anio > anioActual + 1)
      add('alta', 'sesiones', 'Fecha de sesión imposible', `${nCli(s.clienteId)} · fecha ${s.fecha}`);
  });

  // --- Huérfanos ---
  const idsCli = new Set(C.map((c) => c.id)), idsPaq = new Set(P.map((p) => p.id));
  const pHuer = P.filter((p) => !idsCli.has(p.clienteId)).length;
  const sHuer = S.filter((s) => !idsCli.has(s.clienteId)).length;
  const sPaqH = S.filter((s) => s.paqueteId && !idsPaq.has(s.paqueteId)).length;
  if (pHuer) add('alta', 'ventas', 'Ventas de clientes borrados', `${pHuer} ventas apuntan a un cliente que ya no existe`);
  if (sHuer) add('alta', 'sesiones', 'Sesiones de clientes borrados', `${sHuer} sesiones apuntan a un cliente que ya no existe`);
  if (sPaqH) add('media', 'sesiones', 'Sesiones sin su venta', `${sPaqH} sesiones apuntan a una venta que fue eliminada`);

  // --- Gastos ---
  G.forEach((g) => {
    if ((g.monto || 0) <= 0) add('media', 'gastos', 'Gasto con monto cero o negativo', `${g.descripcion || '(sin descripción)'} · ${fmtDate(g.fecha)}`);
    if (!(g.descripcion || '').trim()) add('baja', 'gastos', 'Gasto sin descripción', `${COP(g.monto)} el ${fmtDate(g.fecha)}`);
  });

  // --- Cartera vencida ---
  const hoy = todayISO();
  const viejas = P.filter((p) => (p.saldoPendiente || 0) > 0 &&
    Math.floor((new Date(hoy + 'T12:00:00') - new Date(p.fechaCompra + 'T12:00:00')) / 86400000) > 60);
  if (viejas.length)
    add('media', 'otro', 'Cartera vencida (más de 60 días)',
      `${viejas.length} clientes deben hace más de 2 meses · ${COP(viejas.reduce((a, p) => a + (p.saldoPendiente || 0), 0))} en total`);

  const orden = { alta: 0, media: 1, baja: 2 };
  return out.sort((a, b) => orden[a.severidad] - orden[b.severidad]);
};

// Cifras del negocio ya calculadas (la IA solo las lee, nunca las recalcula).
// Busca clientes cuyo nombre aparece mencionado en la pregunta, para poder
// contestar "¿cuánto debe fulano?" con el dato exacto de ESE cliente.
// Compara por palabras completas (no regex con \b: falla con tildes en JS,
// "josé" no cruza el límite de palabra porque é no cuenta como \w).
const buscarClientesEnPregunta = (pregunta, state) => {
  if (!pregunta) return [];
  const palabrasPregunta = new Set(
    pregunta.toLowerCase().split(/[^a-záéíóúñü0-9]+/i).filter(Boolean),
  );
  return (state.clientes || [])
    .filter((c) => {
      const palabrasNombre = (c.nombre || '').toLowerCase().split(/\s+/).filter((w) => w.length >= 4);
      return palabrasNombre.some((w) => palabrasPregunta.has(w));
    })
    .slice(0, 3);
};

// pregunta: opcional. Si viene, se agrega el detalle de los clientes que
// se mencionan por nombre — es la pregunta más común ("¿cuánto debe X?",
// "¿cuántas sesiones le quedan a X?") y antes NO estaba en el contexto,
// por eso el asistente siempre decía "no tengo esa información".
const contextoNegocio = (state, pregunta = '') => {
  const m = monthKey(todayISO()), hoy = todayISO();
  const P = state.paquetes || [], S = state.sesiones || [], G = state.gastos || [];
  const ventasMes  = P.filter((p) => monthKey(p.fechaCompra) === m);
  const sueltasMes = S.filter((s) => !s.paqueteId && monthKey(s.fecha) === m);
  const abonosMes  = P.flatMap((p) => (p.abonos || []).filter((a) => monthKey(a.fecha) === m));
  const sesionesMes = S.filter((s) => monthKey(s.fecha) === m);
  const gastosMes  = G.filter((g) => monthKey(g.fecha) === m && !g.esLiquidacionFisio);

  const ingresos = ventasMes.reduce((a, p) => a + pagoInicialVenta(p), 0)
                 + sueltasMes.reduce((a, s) => a + (s.valorSesion || 0), 0)
                 + abonosMes.reduce((a, x) => a + (x.monto || 0), 0);
  const fisio  = sesionesMes.reduce((a, s) => a + comisionTotalSesion(s), 0);
  const gastos = gastosMes.reduce((a, g) => a + (g.monto || 0), 0);

  const porCobrar = P.filter((p) => (p.saldoPendiente || 0) > 0);
  const deudores = porCobrar.map((p) => ({
    cliente: state.clientes.find((c) => c.id === p.clienteId)?.nombre || '?',
    debe: COP(p.saldoPendiente),
    dias: Math.floor((new Date(hoy + 'T12:00:00') - new Date(p.fechaCompra + 'T12:00:00')) / 86400000),
  })).sort((a, b) => b.dias - a.dias).slice(0, 12);

  const porFisio = (state.fisios || []).map((f) => {
    const items = sesionesDelFisio(sesionesMes, f.id);
    return { fisio: f.nombre, sesiones: items.length, aPagar: COP(items.reduce((a, s) => a + comisionDelFisio(s, f.id), 0)) };
  }).filter((x) => x.sesiones > 0).sort((a, b) => b.sesiones - a.sesiones);

  // Histórico por fisio de TODA la vida (porFisio de arriba es solo del mes actual)
  const fisiosHistorico = (state.fisios || []).map((f) => {
    const items = sesionesDelFisio(S, f.id);
    return { fisio: f.nombre, sesiones_totales: items.length, comision_total: COP(items.reduce((a, s) => a + comisionDelFisio(s, f.id), 0)) };
  }).filter((x) => x.sesiones_totales > 0).sort((a, b) => b.sesiones_totales - a.sesiones_totales);

  // Últimos 6 meses (incluye el actual) — antes solo se mandaba el mes en curso,
  // así que "¿cuánto vendimos en [mes pasado]?" siempre quedaba sin respuesta.
  const historicoMeses = Array.from({ length: 6 }, (_, i) => {
    const d = new Date(hoy + 'T12:00:00'); d.setMonth(d.getMonth() - i);
    const mk = d.toISOString().slice(0, 7);
    const vMes = P.filter((p) => monthKey(p.fechaCompra) === mk);
    const sMes = S.filter((s) => !s.paqueteId && monthKey(s.fecha) === mk);
    const aMes = P.flatMap((p) => (p.abonos || []).filter((a) => monthKey(a.fecha) === mk));
    const sesMes = S.filter((s) => monthKey(s.fecha) === mk);
    const gMes = G.filter((g) => monthKey(g.fecha) === mk && !g.esLiquidacionFisio);
    const ing = vMes.reduce((a, p) => a + pagoInicialVenta(p), 0) + sMes.reduce((a, s) => a + (s.valorSesion || 0), 0) + aMes.reduce((a, x) => a + (x.monto || 0), 0);
    const com = sesMes.reduce((a, s) => a + comisionTotalSesion(s), 0);
    const gto = gMes.reduce((a, g) => a + (g.monto || 0), 0);
    return {
      mes: mk, ingresos_recibidos: COP(ing), comisiones_fisio: COP(com), gastos_operativos: COP(gto),
      utilidad_neta: COP(ing - com - gto), numero_ventas: vMes.length, numero_sesiones: sesMes.length,
    };
  });

  // TODOS los paquetes con sesiones sin usar (no solo los que además deben plata) —
  // es exactamente lo que sugiere el chip "¿Qué clientes tienen saldo a favor?"
  // del propio asistente, y antes no tenía con qué contestarlo.
  const conSaldoAFavor = P.filter((p) => (p.sesionesUsadas || 0) < (p.sesionesTotales || 0));
  const saldoAFavor = {
    total_paquetes: conSaldoAFavor.length,
    valor_total_pendiente: COP(conSaldoAFavor.reduce((a, p) => a + (p.valorTotal / p.sesionesTotales) * (p.sesionesTotales - p.sesionesUsadas), 0)),
    detalle: conSaldoAFavor.slice(0, 20).map((p) => ({
      cliente: state.clientes.find((c) => c.id === p.clienteId)?.nombre || '?',
      servicio: findSv(state, p.servicioId)?.nombre || '?',
      sesiones_pendientes: `${p.sesionesTotales - p.sesionesUsadas}/${p.sesionesTotales}`,
    })),
  };

  // Catálogo completo de servicios: precio y sesiones incluidas, para
  // "¿cuánto cuesta X?" o "¿cuántas sesiones trae el paquete Y?"
  const catalogoServicios = getSvs(state).map((s) => ({
    nombre: s.nombre, precio: COP(s.precio || 0), sesiones_incluidas: s.sesiones || 1,
    activo: s.activo !== false,
  }));

  // Detalle de clientes mencionados por nombre en la pregunta actual.
  const clientesMencionados = buscarClientesEnPregunta(pregunta, state).map((c) => {
    const susPaquetes = P.filter((p) => p.clienteId === c.id);
    return {
      nombre: c.nombre,
      total_paquetes_comprados: susPaquetes.length,
      debe_actualmente: COP(susPaquetes.reduce((a, p) => a + (p.saldoPendiente || 0), 0)),
      paquetes: susPaquetes.slice(0, 8).map((p) => ({
        servicio: findSv(state, p.servicioId)?.nombre || '?',
        fecha_compra: fmtDate(p.fechaCompra),
        sesiones: `${p.sesionesUsadas || 0}/${p.sesionesTotales || 0} usadas`,
        saldo_pendiente: COP(p.saldoPendiente || 0),
      })),
    };
  });

  const hoyVentas  = P.filter((p) => p.fechaCompra === hoy);
  const hoySueltas = S.filter((s) => !s.paqueteId && s.fecha === hoy);
  const hoyAbonos  = P.flatMap((p) => (p.abonos || []).filter((a) => a.fecha === hoy));
  const ingresoHoy = hoyVentas.reduce((a, p) => a + pagoInicialVenta(p), 0)
                   + hoySueltas.reduce((a, s) => a + (s.valorSesion || 0), 0)
                   + hoyAbonos.reduce((a, x) => a + (x.monto || 0), 0);

  return {
    fecha_de_hoy: fmtDate(hoy),
    mes_actual: m,
    hoy: {
      ingresos: COP(ingresoHoy),
      ventas: hoyVentas.length,
      sesiones: S.filter((s) => s.fecha === hoy).length,
      comisiones_fisio: COP(S.filter((s) => s.fecha === hoy).reduce((a, s) => a + comisionTotalSesion(s), 0)),
      gastos: COP(G.filter((g) => g.fecha === hoy && !g.esLiquidacionFisio).reduce((a, g) => a + (g.monto || 0), 0)),
    },
    mes: {
      ingresos_recibidos: COP(ingresos),
      comisiones_fisio: COP(fisio),
      gastos_operativos: COP(gastos),
      utilidad_neta: COP(ingresos - fisio - gastos),
      numero_ventas: ventasMes.length,
      numero_sesiones: sesionesMes.length,
      numero_abonos: abonosMes.length,
    },
    cuentas_por_cobrar: {
      total: COP(porCobrar.reduce((a, p) => a + (p.saldoPendiente || 0), 0)),
      clientes_que_deben: porCobrar.length,
      mas_atrasados: deudores,
    },
    liquidacion_fisios_mes: porFisio,
    liquidacion_fisios_historico: fisiosHistorico,
    historico_ultimos_6_meses: historicoMeses,
    paquetes_con_saldo_a_favor: saldoAFavor,
    catalogo_servicios: catalogoServicios,
    // Solo aparece si la pregunta menciona el nombre de un cliente registrado.
    ...(clientesMencionados.length > 0 ? { clientes_mencionados_en_la_pregunta: clientesMencionados } : {}),
    totales_historicos: {
      clientes: (state.clientes || []).length,
      ventas: P.length,
      sesiones: S.length,
    },
    nota: 'Todas estas cifras ya están calculadas y son exactas. Los ingresos son base caja: solo dinero efectivamente recibido, no ventas a crédito pendientes. Si preguntan por un cliente y no aparece en "clientes_mencionados_en_la_pregunta", puede que su nombre esté escrito distinto — pide que lo repita como está en el sistema.',
  };
};

// Genera el informe de auditoría en PDF (jsPDF viene por CDN en index.html).
// Las fuentes estándar del PDF no dibujan emojis, así que se convierten a texto.
const generarInformePDF = ({ anomalias, resumen, analisisIA, fecha }, usuario) => {
  const jsPDFCtor = window.jspdf?.jsPDF;
  if (!jsPDFCtor) throw new Error('No se pudo cargar el generador de PDF. Revisa tu conexión.');

  // Quita emojis y caracteres que las fuentes base no soportan
  const limpiar = (t) => String(t || '')
    .replace(/[\u{1F000}-\u{1FAFF}\u{2600}-\u{27BF}\u{FE0F}\u{2190}-\u{21FF}\u{2B00}-\u{2BFF}]/gu, '')
    .replace(/→/g, '->').replace(/·/g, '-').replace(/[""]/g, '"').replace(/['']/g, "'")
    .replace(/\s+/g, ' ').trim();

  const doc = new jsPDFCtor({ unit: 'mm', format: 'a4' });
  const MI = 15, ANCHO = 180, ALTO = 297, PIE = 275;
  let y = MI;

  const nuevaPaginaSiHaceFalta = (alto = 6) => {
    if (y + alto > PIE) { doc.addPage(); y = MI; }
  };
  const escribir = (texto, { size = 10, style = 'normal', color = [40, 40, 40], sangria = 0, espacio = 4.6 } = {}) => {
    doc.setFontSize(size); doc.setFont('helvetica', style); doc.setTextColor(...color);
    const lineas = doc.splitTextToSize(limpiar(texto), ANCHO - sangria);
    lineas.forEach((l) => { nuevaPaginaSiHaceFalta(espacio); doc.text(l, MI + sangria, y); y += espacio; });
  };
  const separador = () => { nuevaPaginaSiHaceFalta(4); doc.setDrawColor(220); doc.line(MI, y, MI + ANCHO, y); y += 5; };

  // ---- Portada ----
  doc.setFillColor(15, 118, 110); doc.rect(0, 0, 210, 32, 'F');
  doc.setTextColor(255); doc.setFontSize(18); doc.setFont('helvetica', 'bold');
  doc.text('Informe de Auditoria Contable', MI, 15);
  doc.setFontSize(11); doc.setFont('helvetica', 'normal');
  doc.text('Massage Therapy Manizales', MI, 23);
  y = 42;

  const f = fecha instanceof Date ? fecha : new Date();
  escribir(`Generado: ${f.toLocaleString('es-CO', { dateStyle: 'long', timeStyle: 'short' })}`, { size: 9, color: [110, 110, 110], espacio: 4 });
  escribir(`Usuario: ${usuario || 'no identificado'}`, { size: 9, color: [110, 110, 110], espacio: 4 });
  y += 3;

  // ---- Resumen del negocio ----
  escribir('Resumen del mes', { size: 13, style: 'bold', color: [15, 118, 110], espacio: 6 });
  separador();
  const m = resumen?.mes || {}, cxc = resumen?.cuentas_por_cobrar || {};
  const filas = [
    ['Ingresos recibidos', m.ingresos_recibidos],
    ['Comisiones a fisioterapeutas', m.comisiones_fisio],
    ['Egresos operativos', m.gastos_operativos],
    ['Utilidad neta', m.utilidad_neta],
    ['Ventas registradas', String(m.numero_ventas ?? '-')],
    ['Sesiones realizadas', String(m.numero_sesiones ?? '-')],
    ['Total por cobrar', cxc.total],
    ['Clientes que deben', String(cxc.clientes_que_deben ?? '-')],
  ];
  filas.forEach(([k, v]) => {
    nuevaPaginaSiHaceFalta(5.5);
    doc.setFontSize(10); doc.setFont('helvetica', 'normal'); doc.setTextColor(80);
    doc.text(limpiar(k), MI, y);
    doc.setFont('helvetica', 'bold'); doc.setTextColor(30);
    doc.text(limpiar(v ?? '-'), MI + ANCHO, y, { align: 'right' });
    y += 5.5;
  });
  y += 4;

  // ---- Conteo de hallazgos ----
  const porSev = { alta: 0, media: 0, baja: 0 };
  (anomalias || []).forEach((a) => { porSev[a.severidad] = (porSev[a.severidad] || 0) + 1; });
  escribir('Hallazgos detectados', { size: 13, style: 'bold', color: [15, 118, 110], espacio: 6 });
  separador();
  escribir(`Total: ${anomalias.length}   |   Graves: ${porSev.alta}   |   Medios: ${porSev.media}   |   Leves: ${porSev.baja}`,
    { size: 10, style: 'bold', espacio: 6 });
  escribir('Revision automatica sobre el 100% de las ventas, sesiones y gastos registrados.',
    { size: 8.5, color: [120, 120, 120], espacio: 5 });
  y += 2;

  // ---- Análisis de la IA ----
  if (analisisIA) {
    escribir('Analisis y recomendaciones', { size: 13, style: 'bold', color: [15, 118, 110], espacio: 6 });
    separador();
    analisisIA.split('\n').forEach((linea) => {
      if (!linea.trim()) { y += 2; return; }
      const negrita = /^\s*\*\*.+\*\*\s*:?\s*$/.test(linea) || /^#{1,3}\s/.test(linea);
      escribir(linea.replace(/\*\*/g, '').replace(/^#{1,3}\s*/, ''),
        { size: 10, style: negrita ? 'bold' : 'normal', sangria: /^\s*[-•]/.test(linea) ? 4 : 0 });
    });
    y += 4;
  }

  // ---- Detalle completo ----
  escribir('Detalle de los hallazgos', { size: 13, style: 'bold', color: [15, 118, 110], espacio: 6 });
  separador();
  const etiqueta = { alta: '[GRAVE]', media: '[MEDIO]', baja: '[LEVE]' };
  const colorSev = { alta: [190, 40, 60], media: [180, 120, 20], baja: [90, 120, 90] };
  if (!anomalias.length) {
    escribir('No se encontraron inconsistencias contables.', { size: 10 });
  } else {
    anomalias.forEach((h, i) => {
      nuevaPaginaSiHaceFalta(12);
      escribir(`${i + 1}. ${etiqueta[h.severidad] || ''} ${h.titulo}`,
        { size: 10, style: 'bold', color: colorSev[h.severidad] || [40, 40, 40], espacio: 5 });
      escribir(h.detalle, { size: 9, color: [70, 70, 70], sangria: 5, espacio: 4.4 });
      y += 2;
    });
  }

  // ---- Pie de página en todas las hojas ----
  const paginas = doc.internal.getNumberOfPages();
  for (let i = 1; i <= paginas; i++) {
    doc.setPage(i);
    doc.setFontSize(8); doc.setFont('helvetica', 'normal'); doc.setTextColor(150);
    doc.text('Massage Therapy Manizales - Informe de auditoria', MI, ALTO - 10);
    doc.text(`Pagina ${i} de ${paginas}`, MI + ANCHO, ALTO - 10, { align: 'right' });
  }

  doc.save(`Auditoria-MTM-${f.toISOString().slice(0, 10)}.pdf`);
};

function Asistente({ state, setTab, usuarioActual }) {
  const [messages, setMessages] = useState([{
    role: 'bot',
    text: 'Hola Dani 👋 Soy el asistente del centro. Pregúntame en lenguaje natural sobre el negocio.',
    chips: ['¿Cuánto entró hoy?', '¿Cuánto le debo al fisio?', '¿Cuánto gasté este mes?', 'Resumen del mes', '¿Qué clientes tienen saldo a favor?'],
  }]);
  const [input,     setInput]     = useState('');
  const [thinking,  setThinking]  = useState(false);
  const [auditando, setAuditando] = useState(false);
  const [ultimaAuditoria, setUltimaAuditoria] = useState(null); // para poder descargar el PDF
  const endRef = useRef();

  useEffect(() => { endRef.current?.scrollIntoView({ behavior: 'smooth', block: 'end' }); }, [messages, thinking, auditando]);

  // Manda la petición al servidor incluyendo el token de la sesión.
  // El endpoint rechaza a quien no esté logueado (antes estaba abierto a internet).
  const llamarApi = async (ruta, cuerpo) => {
    const { data } = await sb.auth.getSession();
    const token = data?.session?.access_token;
    if (!token) throw new Error('Tu sesión expiró. Vuelve a iniciar sesión.');
    const resp = await fetch(ruta, {
      method: 'POST',
      headers: { 'Content-Type': 'application/json', Authorization: `Bearer ${token}` },
      body: JSON.stringify(cuerpo),
    });
    const json = await resp.json().catch(() => ({}));
    if (!resp.ok) throw new Error(json.error || `Error ${resp.status}`);
    return json;
  };

  const ask = async (text) => {
    if (!text.trim()) return;
    const NAV = { 'Ver balance del día': 'balance', 'Ir a balance': 'balance', 'Ir a liquidación': 'liquidacion', 'Ir a clientes': 'clientes' };
    if (NAV[text]) { setTab(NAV[text]); return; }
    setMessages((m) => [...m, { role: 'user', text }]);
    setInput('');
    setThinking(true);
    try {
      // Las cifras van ya calculadas: la IA redacta, no hace cuentas.
      const { texto } = await llamarApi('/api/asistente', { pregunta: text, contexto: contextoNegocio(state, text) });
      setMessages((m) => [...m, { role: 'bot', text: texto }]);
    } catch (err) {
      // Si la IA no está disponible, responde el modo básico de siempre.
      const reply = answer(text, state);
      setMessages((m) => [...m, { role: 'bot', ...reply, text: reply.text + `\n\n(ℹ️ Respuesta básica — la IA no está disponible: ${err.message})` }]);
    } finally {
      setThinking(false);
    }
  };

  const auditarConIA = async () => {
    setMessages((m) => [...m, { role: 'user', text: '🤖 Auditar con IA' }]);
    setAuditando(true);
    let anomalias = [], resumen = null, analisisIA = '';
    try {
      // La detección es determinista aquí (exacta, revisa el 100% de los registros).
      anomalias = detectarAnomalias(state);
      resumen   = contextoNegocio(state);

      const iconos = { alta: '🔴', media: '🟡', baja: '🟢' };
      const listado = anomalias.length === 0
        ? '✅ Revisé el 100% de ventas, sesiones y gastos: no encontré inconsistencias contables.'
        : `Encontré ${anomalias.length} hallazgo${anomalias.length !== 1 ? 's' : ''} (revisión exacta sobre todos los registros):\n\n` +
          anomalias.slice(0, 40).map((h) => `${iconos[h.severidad] || '⚪'} ${h.titulo}\n   ${h.detalle}`).join('\n\n') +
          (anomalias.length > 40 ? `\n\n…y ${anomalias.length - 40} más.` : '');
      setMessages((m) => [...m, { role: 'bot', text: listado }]);

      // La IA prioriza y explica qué hacer.
      const { texto } = await llamarApi('/api/auditoria-ia', { anomalias, resumen });
      analisisIA = texto;
      setMessages((m) => [...m, { role: 'bot', text: '🧠 Análisis del auditor:\n\n' + texto }]);
    } catch (err) {
      setMessages((m) => [...m, { role: 'bot', text: '⚠️ La lista de arriba es exacta, pero no pude generar el análisis con IA: ' + err.message }]);
    } finally {
      // Se guarda aunque falle la IA: el informe en PDF igual sirve con los hallazgos exactos.
      if (resumen) setUltimaAuditoria({ anomalias, resumen, analisisIA, fecha: new Date() });
      setAuditando(false);
    }
  };

  const descargarPDF = () => {
    if (!ultimaAuditoria) return;
    try {
      generarInformePDF(ultimaAuditoria, usuarioActual);
    } catch (err) {
      alert('No se pudo generar el PDF: ' + err.message);
    }
  };

  return (
    <div className="max-w-3xl mx-auto h-[calc(100vh-220px)] lg:h-[calc(100vh-160px)] flex flex-col">
      <div className="flex-1 overflow-y-auto space-y-3 pr-1">
        {messages.map((m, i) => <Bubble key={i} m={m} onChip={(c) => ask(c)} />)}
        {(thinking || auditando) && (
          <div className="flex gap-2 items-center text-stone-400 text-sm">
            <div className="w-7 h-7 rounded-full bg-brand-600 text-white flex items-center justify-center"><Icon name="bot" className="w-4 h-4" /></div>
            <div className="flex gap-1">
              {[0,1,2].map((i) => <span key={i} className="w-1.5 h-1.5 bg-stone-300 rounded-full animate-bounce" style={{ animationDelay: `${i*120}ms` }} />)}
            </div>
            {auditando && <span className="text-xs">Auditando con IA… puede tardar unos segundos</span>}
          </div>
        )}
        <div ref={endRef} />
      </div>
      <div className="mt-2 flex gap-2">
        <button type="button" onClick={auditarConIA} disabled={auditando || thinking}
          className="flex-1 flex items-center justify-center gap-2 py-2.5 rounded-xl border-2 border-dashed border-violet-300 text-violet-700 font-semibold text-sm hover:bg-violet-50 disabled:opacity-50 transition">
          <Icon name="sparkles" className="w-4 h-4" />
          {auditando ? 'Auditando…' : '🤖 Auditar con IA (busca errores contables)'}
        </button>
        {/* Aparece solo cuando ya hay una auditoría lista para exportar */}
        {ultimaAuditoria && !auditando && (
          <button type="button" onClick={descargarPDF}
            title="Descargar el informe completo en PDF"
            className="shrink-0 flex items-center justify-center gap-1.5 px-4 py-2.5 rounded-xl bg-rose-600 text-white font-semibold text-sm hover:bg-rose-700 transition">
            <Icon name="download" className="w-4 h-4" />
            PDF
          </button>
        )}
      </div>
      <form onSubmit={(e) => { e.preventDefault(); ask(input); }} className="mt-2 flex gap-2 bg-white rounded-2xl border border-stone-200 p-1.5 shadow-sm">
        <input value={input} onChange={(e) => setInput(e.target.value)} placeholder="Pregúntame algo del negocio…" className="flex-1 px-3 py-2.5 bg-transparent outline-none text-sm" />
        <button type="submit" disabled={!input.trim()} className="px-3 py-2 rounded-xl bg-brand-600 text-white disabled:opacity-40 hover:bg-brand-700">
          <Icon name="send" className="w-4 h-4" />
        </button>
      </form>
    </div>
  );
}

// Convierte **texto** en negrita real. La IA a veces responde con markdown
// aunque se le pida lo contrario; así nunca se ven asteriscos sueltos.
function conNegritas(texto) {
  return String(texto || '').split(/(\*\*[^*]+\*\*)/g).map((parte, i) =>
    parte.startsWith('**') && parte.endsWith('**') && parte.length > 4
      ? <strong key={i}>{parte.slice(2, -2)}</strong>
      : parte
  );
}

function Bubble({ m, onChip }) {
  if (m.role === 'user') return (
    <div className="flex justify-end">
      <div className="max-w-[80%] px-4 py-2.5 rounded-2xl rounded-br-sm bg-brand-600 text-white text-sm">{m.text}</div>
    </div>
  );
  return (
    <div className="flex gap-2 items-start">
      <div className="w-7 h-7 rounded-full bg-brand-600 text-white flex items-center justify-center shrink-0"><Icon name="bot" className="w-4 h-4" /></div>
      <div className="max-w-[85%] space-y-2">
        <div className="px-4 py-2.5 rounded-2xl rounded-tl-sm bg-white border border-stone-200 text-sm whitespace-pre-line">{conNegritas(m.text)}</div>
        {m.chips && (
          <div className="flex flex-wrap gap-1.5">
            {m.chips.map((c) => (
              <button key={c} onClick={() => onChip(c)} className="text-xs px-3 py-1.5 rounded-full bg-white border border-stone-200 hover:border-brand-300 hover:bg-brand-50 text-stone-700">{c}</button>
            ))}
          </div>
        )}
      </div>
    </div>
  );
}

function answer(q, state) {
  const t   = q.toLowerCase();
  const m   = monthKey(todayISO());
  const hoy = todayISO();
  const ventasMes      = state.paquetes.filter((p) => monthKey(p.fechaCompra) === m);
  const sueltasMes     = state.sesiones.filter((s) => !s.paqueteId && monthKey(s.fecha) === m);
  const ingresosMes    = ventasMes.reduce((a, p) => a + pagoInicialVenta(p), 0)
                       + ventasMes.reduce((a, p) => a + (p.abonos || []).filter((ab) => monthKey(ab.fecha) === m).reduce((s, ab) => s + (ab.monto || 0), 0), 0)
                       + sueltasMes.reduce((a, s) => a + (s.valorSesion || 0), 0);
  const sesionesMes    = state.sesiones.filter((s) => monthKey(s.fecha) === m);
  const pagadoFisioMes = sesionesMes.reduce((a, s) => a + comisionTotalSesion(s), 0);
  // Excluye egresos de honorarios auto-generados (ya restados vía pagadoFisioMes, evita doble conteo)
  const gastosMesItems = (state.gastos || []).filter((g) => monthKey(g.fecha) === m && !g.esLiquidacionFisio);
  const gastosMes      = gastosMesItems.reduce((a, g) => a + g.monto, 0);
  const utilidadNeta   = ingresosMes - pagadoFisioMes - gastosMes;

  if (/(hoy|del d.a|de hoy|d.a de hoy)/.test(t)) {
    const ventasHoy  = state.paquetes.filter((p) => p.fechaCompra === hoy);
    const sueltasHoy = state.sesiones.filter((s) => !s.paqueteId && s.fecha === hoy);
    const sesHoy     = state.sesiones.filter((s) => s.fecha === hoy);
    const gastosHoy  = (state.gastos || []).filter((g) => g.fecha === hoy && !g.esLiquidacionFisio);
    const ingHoy     = ventasHoy.reduce((a, p) => a + pagoInicialVenta(p), 0) + sueltasHoy.reduce((a, s) => a + s.valorSesion, 0);
    const fisioHoy   = sesHoy.reduce((a, s) => a + comisionTotalSesion(s), 0);
    const gtHoy      = gastosHoy.reduce((a, g) => a + g.monto, 0);
    return { text: `*Balance de hoy (${fmtDate(hoy)})*:\n\n💰 Ingresos: ${COP(ingHoy)}\n👐 Bolsillo del fisio: ${COP(fisioHoy)}\n📦 Gastos: ${COP(gtHoy)}\n\n*Para tu bolsillo: ${COP(ingHoy - fisioHoy - gtHoy)}*`, chips: ['Ver balance del día'] };
  }
  if (/(gast|caja menor|egres)/.test(t)) {
    if (!gastosMesItems.length) return { text: 'Este mes no has registrado gastos todavía.' };
    const porCat = {};
    gastosMesItems.forEach((g) => { porCat[g.categoria] = (porCat[g.categoria] || 0) + g.monto; });
    const lines = Object.entries(porCat).map(([cat, tot]) => { const c = CATEGORIAS_GASTO.find((x) => x.id === cat) || { nombre: cat, icon: '📦' }; return `${c.icon} ${c.nombre}: ${COP(tot)}`; }).join('\n');
    return { text: `*Gastos del mes:* ${COP(gastosMes)}\n\n${lines}\n\n${gastosMesItems.length} movimientos registrados.` };
  }
  if (/(vend|ingres|fact|ventas|cuanto entr|cuánto entr)/.test(t)) {
    return { text: `Este mes vendiste *${COP(ingresosMes)}*.\n• ${ventasMes.length} paquetes vendidos\n• ${sueltasMes.length} sesiones sueltas\n\nDespués de pagar al fisio (${COP(pagadoFisioMes)}) y restar gastos (${COP(gastosMes)}):\n\n*Utilidad neta: ${COP(utilidadNeta)}*` };
  }
  if (/(deb|paga|liquid|fisio|comisi)/.test(t)) {
    const porFisio = state.fisios.map((f) => {
      const sfs = sesionesDelFisio(sesionesMes, f.id);
      const total = sfs.reduce((a, s) => a + comisionDelFisio(s, f.id), 0);
      return { f, total, n: sfs.length };
    }).filter((x) => x.total > 0);
    if (!porFisio.length) return { text: 'Este mes no hay sesiones registradas todavía.' };
    const detalle = porFisio.map((x) => `• ${x.f.nombre}: ${COP(x.total)} (${x.n} sesiones)`).join('\n');
    return { text: `Este mes le debes a tus fisioterapeutas:\n\n${detalle}\n\n*Total: ${COP(porFisio.reduce((a, x) => a + x.total, 0))}*`, chips: ['Ir a liquidación'] };
  }
  if (/(saldo|favor|pendiente|sin termin|sin usar)/.test(t)) {
    const items = state.paquetes.filter((p) => p.sesionesUsadas < p.sesionesTotales);
    if (!items.length) return { text: 'No hay saldos a favor pendientes 🎉' };
    const total = items.reduce((a, p) => a + (p.valorTotal / p.sesionesTotales) * (p.sesionesTotales - p.sesionesUsadas), 0);
    const lines = items.slice(0, 8).map((p) => { const cli = state.clientes.find((c) => c.id === p.clienteId); const sv = findSv(state, p.servicioId); return `• ${cli?.nombre} — ${p.sesionesTotales - p.sesionesUsadas}/${p.sesionesTotales} de ${sv?.nombre}`; }).join('\n');
    return { text: `Hay ${items.length} paquetes con saldo a favor:\n\n${lines}\n\n*Valor total pendiente: ${COP(total)}*` };
  }
  if (/(resumen|reporte|como va|cómo va|estado|utilidad|ganan)/.test(t)) {
    return { text: `*Resumen del mes:*\n• Ventas: ${COP(ingresosMes)}\n• Sesiones ejecutadas: ${sesionesMes.length}\n• Pagado al fisio: ${COP(pagadoFisioMes)}\n• Gastos caja menor: ${COP(gastosMes)}\n\n*Utilidad neta: ${COP(utilidadNeta)}*\n\n${ventasMes.length} paquetes vendidos. ${state.paquetes.filter((p) => p.sesionesUsadas < p.sesionesTotales).length} paquetes con saldo abiertos.` };
  }
  if (/(client|cuant.*client|nuev.*client)/.test(t)) {
    return { text: `Tienes *${state.clientes.length} clientes* registrados.\n${state.clientes.filter((c) => state.paquetes.some((p) => p.clienteId === c.id)).length} con paquetes activos.` };
  }
  return { text: 'No estoy seguro de eso aún. Prueba con alguna de estas preguntas:', chips: ['¿Cuánto vendí este mes?', '¿Cuánto le debo al fisio?', '¿Qué clientes tienen saldo a favor?', 'Resumen del mes'] };
}

// ---------- UI primitives ----------
function Label({ children }) { return <label className="block text-xs font-semibold text-stone-700 mb-1.5">{children}</label>; }
function Input({ value, onChange, type = 'text', placeholder, required, min, max }) {
  // min/max se reenvían al input nativo: en los campos de fecha el navegador
  // bloquea por sí solo años imposibles (ej. 0292 por digitación).
  return <input type={type} value={value} placeholder={placeholder} required={required} min={min} max={max} onChange={(e) => onChange(e.target.value)} className="w-full px-3 py-2.5 rounded-lg border border-stone-200 bg-white text-sm focus:outline-none focus:border-brand-500 focus:ring-2 focus:ring-brand-100" />;
}
function Select({ value, onChange, children, className = '' }) {
  return <select value={value} onChange={(e) => onChange(e.target.value)} className={`w-full px-3 py-2.5 rounded-lg border border-stone-200 bg-white text-sm focus:outline-none focus:border-brand-500 focus:ring-2 focus:ring-brand-100 ${className}`}>{children}</select>;
}
function Card({ children, className = '' }) {
  return <div className={`bg-white rounded-2xl border border-stone-200 p-5 ${className}`}>{children}</div>;
}
function Empty({ text }) {
  return <p className="text-stone-400 text-sm py-4 text-center">{text}</p>;
}

// ---------- Login ----------
function Login({ onLogin }) {
  const [email,   setEmail]   = useState('');
  const [pw,      setPw]      = useState('');
  const [error,   setError]   = useState('');
  const [loading, setLoading] = useState(false);
  const [showPw,  setShowPw]  = useState(false);

  const attempts = getAttempts();
  const lockedUntil = attempts.n >= MAX_ATTEMPTS ? attempts.t + LOCKOUT_MIN * 60000 : 0;
  const [now, setNow] = useState(Date.now());
  useEffect(() => {
    if (lockedUntil > Date.now()) {
      const t = setInterval(() => setNow(Date.now()), 1000);
      return () => clearInterval(t);
    }
  }, [lockedUntil]);

  const isLocked  = lockedUntil > now;
  const remaining = isLocked ? Math.ceil((lockedUntil - now) / 1000) : 0;

  const submit = async (e) => {
    e.preventDefault();
    if (isLocked) return;
    setLoading(true);
    setError('');
    try {
      const { data, error: authErr } = await sb.auth.signInWithPassword({
        email: email.trim().toLowerCase(),
        password: pw.trim(),
      });
      if (!authErr && data?.session) {
        resetAttempts();
        onLogin();
      } else {
        recordFailedAttempt();
        const a2 = getAttempts();
        const left = MAX_ATTEMPTS - a2.n;
        if (left <= 0) setError(`Demasiados intentos. Espera ${LOCKOUT_MIN} minutos.`);
        else           setError(`Correo o contraseña incorrectos. ${left} intento${left > 1 ? 's' : ''} restante${left > 1 ? 's' : ''}.`);
        setPw('');
      }
    } finally {
      setLoading(false);
    }
  };

  return (
    <div className="min-h-screen flex items-center justify-center bg-gradient-to-br from-stone-50 to-stone-100 p-4">
      <div className="w-full max-w-sm">
        {/* Logo */}
        <div className="text-center mb-8">
          <div className="w-16 h-16 mx-auto rounded-2xl bg-brand-600 text-white flex items-center justify-center text-4xl shadow-lg mb-4">💆‍♀️</div>
          <h1 className="text-2xl font-bold text-stone-900">Massage Therapy</h1>
          <p className="text-stone-500 text-sm mt-0.5">Manizales — Sistema de gestión</p>
        </div>

        <div className="bg-white rounded-2xl border border-stone-200 shadow-sm p-6">
          <div className="flex items-center gap-2 mb-5">
            <Icon name="lock" className="w-5 h-5 text-stone-400" />
            <h2 className="font-semibold text-stone-700">Acceso seguro</h2>
          </div>

          {isLocked ? (
            <div className="text-center py-4">
              <div className="text-4xl mb-3">🔒</div>
              <p className="text-stone-700 font-semibold">Acceso bloqueado</p>
              <p className="text-stone-500 text-sm mt-1">Demasiados intentos fallidos.</p>
              <div className="mt-3 text-2xl font-mono font-bold text-rose-600">
                {Math.floor(remaining / 60)}:{String(remaining % 60).padStart(2, '0')}
              </div>
              <p className="text-xs text-stone-400 mt-1">Tiempo restante</p>
            </div>
          ) : (
            <form onSubmit={submit} className="space-y-4">
              <div>
                <label className="block text-xs font-semibold text-stone-600 mb-1.5">Correo</label>
                <input
                  autoFocus
                  type="email"
                  autoComplete="username"
                  value={email}
                  onChange={(e) => setEmail(e.target.value)}
                  placeholder="tucorreo@ejemplo.com"
                  className="w-full px-4 py-3 rounded-xl border border-stone-200 bg-stone-50 text-sm focus:outline-none focus:border-brand-500 focus:ring-2 focus:ring-brand-100 focus:bg-white"
                />
              </div>
              <div>
                <label className="block text-xs font-semibold text-stone-600 mb-1.5">Contraseña</label>
                <div className="relative">
                  <input
                    type={showPw ? 'text' : 'password'}
                    autoComplete="current-password"
                    value={pw}
                    onChange={(e) => setPw(e.target.value)}
                    placeholder="••••••••••••"
                    className="w-full px-4 py-3 rounded-xl border border-stone-200 bg-stone-50 text-sm focus:outline-none focus:border-brand-500 focus:ring-2 focus:ring-brand-100 focus:bg-white pr-12"
                  />
                  <button type="button" onClick={() => setShowPw((v) => !v)}
                    className="absolute right-3 top-1/2 -translate-y-1/2 text-stone-400 hover:text-stone-600 text-xs">
                    {showPw ? '👁️' : '🙈'}
                  </button>
                </div>
              </div>

              {error && (
                <div className="flex items-start gap-2 bg-rose-50 border border-rose-200 rounded-xl px-3 py-2.5 text-sm text-rose-700">
                  <span className="shrink-0 mt-0.5">⚠️</span>
                  <span>{error}</span>
                </div>
              )}

              <button type="submit" disabled={loading || !email.trim() || !pw.trim()}
                className="w-full py-3 rounded-xl bg-brand-600 text-white font-semibold hover:bg-brand-700 disabled:opacity-50 flex items-center justify-center gap-2 text-sm">
                {loading ? <><Icon name="loader" className="w-4 h-4 animate-spin" />Verificando…</> : 'Ingresar'}
              </button>
            </form>
          )}
        </div>
        <p className="text-center text-xs text-stone-400 mt-4">
          Acceso restringido · Solo personal autorizado
        </p>
      </div>
    </div>
  );
}

// ---------- Root ----------
function Root() {
  const [authed, setAuthed] = useState(null); // null = comprobando sesión

  useEffect(() => {
    sb.auth.getSession().then(({ data }) => setAuthed(!!data.session));
    const { data: sub } = sb.auth.onAuthStateChange((_event, session) => setAuthed(!!session));
    return () => sub.subscription.unsubscribe();
  }, []);

  if (authed === null) {
    return (
      <div className="min-h-screen flex items-center justify-center bg-stone-50">
        <div className="text-stone-400 text-sm">Cargando…</div>
      </div>
    );
  }
  if (!authed) return <Login onLogin={() => setAuthed(true)} />;
  return <App onLogout={async () => { await sb.auth.signOut(); setAuthed(false); }} />;
}

// ---------- Mount ----------
ReactDOM.createRoot(document.getElementById('root')).render(<Root />);
