// Sisyphus — App shell v2
// Shared state lifted here: paidPagos, collectedDeudores, pendingBandeja, activeNotifs
// Responsive: sidebar (desktop ≥1024) + bottom nav (mobile)

const { useState: useS, useEffect: useE, useRef: useR } = React;
const seedA = window.SisyphusSeed;
const IconA  = window.Icon;

// ─── Sisyphus logo SVG ────────────────────────────────────────────────────────
function SisyphusLogoMark({ size = 28 }) {
  return (
    <svg width={size} height={size} viewBox="0 0 32 32" fill="none" xmlns="http://www.w3.org/2000/svg" style={{ flexShrink:0, borderRadius: size * 0.28 }}>
      <defs>
        <radialGradient id="sis-bg" cx="38%" cy="36%" r="68%">
          <stop offset="0%" stopColor="#c4b5fd"/>
          <stop offset="100%" stopColor="#7c3aed"/>
        </radialGradient>
        <radialGradient id="sis-bld" cx="42%" cy="38%" r="62%">
          <stop offset="0%" stopColor="#ede9fe"/>
          <stop offset="100%" stopColor="#8b5cf6"/>
        </radialGradient>
      </defs>
      <rect width="32" height="32" rx="9" fill="url(#sis-bg)"/>
      <circle cx="23" cy="9"  r="5.5" fill="rgba(255,255,255,0.12)"/>
      <circle cx="16" cy="16" r="9"   fill="url(#sis-bld)"/>
      <circle cx="14" cy="14" r="3.5" fill="rgba(255,255,255,0.35)"/>
      <circle cx="9"  cy="15" r="6.5" fill="url(#sis-bld)"/>
      <circle cx="7"  cy="13" r="1.4" fill="rgba(255,255,255,0.55)"/>
    </svg>
  );
}
window.SisyphusLogoMark = SisyphusLogoMark;

// ─── Seed movimientos ────────────────────────────────────────────────────────
function makeInitialMovimientos() {
  return []; // Clean slate — user adds real data via Gmail sync or manually
}

// ─── Responsive hook ──────────────────────────────────────────────────────────
function useIsDesktop() {
  const [is, setIs] = useS(() => window.matchMedia('(min-width: 1024px)').matches);
  useE(() => {
    const mq = window.matchMedia('(min-width: 1024px)');
    const fn = e => setIs(e.matches);
    mq.addEventListener('change', fn);
    return () => mq.removeEventListener('change', fn);
  }, []);
  return is;
}

// ─── Desktop dropdown for categories / payment in QuickAdd ───────────────────
function QaDropdown({ label, icon, active, activeColor, activeLabel, children }) {
  const [open, setOpen] = useS(false);
  return (
    <div style={{ marginBottom:12, position:'relative' }}>
      <button onClick={() => setOpen(o => !o)} style={{
        display:'flex', alignItems:'center', gap:8, padding:'9px 14px', borderRadius:10, width:'100%',
        background: active ? (activeColor ? activeColor+'18' : 'var(--accent-dim)') : 'rgba(255,255,255,0.04)',
        border:`1px solid ${active ? (activeColor ? activeColor+'55' : 'var(--border-strong)') : 'var(--border)'}`,
        color: active ? (activeColor || 'var(--accent-bright)') : 'var(--text-2)',
        fontFamily:'inherit', fontSize:13, fontWeight:600, cursor:'pointer', transition:'all 0.13s',
      }}>
        <IconA name={icon} size={14} />
        <span style={{ flex:1, textAlign:'left' }}>{active && activeLabel ? activeLabel : label}</span>
        <IconA name={open ? 'chevDown' : 'chevRight'} size={12} style={{ transform: open ? 'rotate(0deg)' : 'rotate(0deg)' }} />
      </button>
      {open && (
        <>
          <div onClick={() => setOpen(false)} style={{ position:'fixed', inset:0, zIndex:49 }} />
          <div style={{ position:'absolute', top:'calc(100% + 4px)', left:0, right:0, zIndex:50,
            background:'rgba(14,9,28,0.98)', backdropFilter:'blur(20px)',
            border:'1px solid var(--border-strong)', borderRadius:12,
            boxShadow:'0 16px 40px rgba(0,0,0,0.6)', animation:'fadeSlide 0.14s ease',
          }}>
            {children}
          </div>
        </>
      )}
    </div>
  );
}

// ─── Quick Add ────────────────────────────────────────────────────────────────
function QuickAdd({ open, onClose, onCommit, onCommitDebt, onCommitCuota, onCommitPago, isDesktop, tarjetasState }) {
  // Mode: 'gasto' | 'deuda' | 'pago'
  const [mode, setMode] = useS('gasto');

  // ── Gasto state ──
  const [q, setQ] = useS('');
  const [parsed, setParsed] = useS(null);
  const [listening, setListening] = useS(false);
  const [voiceErr, setVoiceErr] = useS('');
  const inputRef = useR(null);

  // ── Tarjeta + Cuotas state (gasto mode) ──
  const [tarjetaId,  setTarjetaId]  = useS('');        // '' = efectivo/débito
  const [esCuota,    setEsCuota]    = useS(false);
  const [numCuotas,  setNumCuotas]  = useS(3);
  const [cuotaDesc,  setCuotaDesc]  = useS('');

  // ── Deuda state ──
  const [debtTipo, setDebtTipo] = useS('me_deben'); // 'me_deben' | 'debo'
  const [debtSplitOf, setDebtSplitOf] = useS(''); // description of expense being split
  const [debtNombre, setDebtNombre] = useS('');
  const [debtMonto, setDebtMonto] = useS('');
  const [debtCcy,   setDebtCcy]   = useS('ARS'); // currency for debt
  const [debtNota, setDebtNota] = useS('');
  const defaultDue = () => {
    const d = new Date(); d.setDate(d.getDate() + 30);
    return `${d.getFullYear()}-${String(d.getMonth()+1).padStart(2,'0')}-${String(d.getDate()).padStart(2,'0')}`;
  };
  const [debtVenc, setDebtVenc] = useS(defaultDue);
  const [debtIsSplit, setDebtIsSplit] = useS(false); // true = split 50/50

  // ── Pago fijo state ──
  const [pagoNombre, setPagoNombre] = useS('');
  const [pagoMonto,  setPagoMonto]  = useS('');
  const [pagoCat,    setPagoCat]    = useS('servicios');
  const [pagoCcy,    setPagoCcy]    = useS('ARS');
  const defaultPagoDue = () => {
    const d = new Date(); d.setDate(d.getDate() + 30);
    return `${d.getFullYear()}-${String(d.getMonth()+1).padStart(2,'0')}-${String(d.getDate()).padStart(2,'0')}`;
  };
  const [pagoVenc, setPagoVenc] = useS(defaultPagoDue);

  useE(() => {
    if (open) {
      setTimeout(() => inputRef.current?.focus(), 60);
      setQ(''); setParsed(null); setVoiceErr('');
      setTarjetaId(''); setEsCuota(false); setNumCuotas(3); setCuotaDesc('');
      setDebtNombre(''); setDebtMonto(''); setDebtCcy('ARS'); setDebtNota(''); setDebtVenc(defaultDue()); setDebtIsSplit(false);
      setPagoNombre(''); setPagoMonto(''); setPagoCat('servicios'); setPagoVenc(defaultPagoDue()); setPagoCcy('ARS');
    }
  }, [open]);

  // Close on Escape
  useE(() => {
    if (!open) return;
    const handler = e => { if (e.key === 'Escape') onClose(); };
    document.addEventListener('keydown', handler);
    return () => document.removeEventListener('keydown', handler);
  }, [open, onClose]);

  // ── Enhanced NLP parser ──────────────────────────────────────────────────────
  useE(() => {
    if (mode !== 'gasto') return;

    // Normalize: lowercase + remove accents (use proper Unicode range)
    const norm = s => s.toLowerCase().normalize('NFD').replace(/[̀-ͯ]/g, '');
    // "más" (con tilde, input de voz) y "mas" → "+" antes de parsear
    // Se reemplaza "más" ANTES de normalizar para no perder la tilde en voz
    // También strip "pesos/peso" para que "5000 pesos más 3000" → "5000 + 3000"
    const t = norm(q.replace(/más/gi, '+')).replace(/\bmas\b/g, '+').replace(/\bpesos?\b/gi, '');

    // ── Amount (supports "58k + 522 propina", "22.917,15", "5k") ──
    function parseOne(s) {
      // strips thousand-sep dots, converts comma decimal, handles k/m
      const m = s.trim().match(/^([\d]+[\d.,]*)\s*([km]?)$/i);
      if (!m) return 0;
      const raw = m[1].replace(/\./g,'').replace(/,/g,'.');
      const mult = m[2].toLowerCase() === 'k' ? 1000 : m[2].toLowerCase() === 'm' ? 1_000_000 : 1;
      return parseFloat(raw) * mult;
    }
    // Detect addition "A + B" before anything else
    const addRx = /([\d][\d.,]*\s*[km]?)\s*\+\s*([\d][\d.,]*\s*[km]?)/i;
    const addM  = t.match(addRx);
    let amount, rest;
    if (addM) {
      amount = parseFloat((parseOne(addM[1]) + parseOne(addM[2])).toFixed(2));
      rest   = t.replace(addM[0], ' ');
    } else {
      const amtRx = /([\d]+[\d.,]*)\s*([km]?)\b/i;
      const amtM  = t.match(amtRx);
      if (!amtM) { setParsed(null); return; }
      amount = parseFloat((parseOne(amtM[1] + (amtM[2]||''))).toFixed(2));
      rest   = t.replace(amtM[0], ' ');
    }
    if (!amount) { setParsed(null); return; }

    // ── Cuotas ────────────────────────────────────────────────────
    // "en 3 cuotas", "3 cuotas", "3x", "en cuotas"
    const cuotaRx = /(?:en\s+)?(\d+)\s*(?:x\b|cuotas?)|en\s+cuotas?/;
    const cuotaM  = rest.match(cuotaRx);
    const detectedCuotas = cuotaM ? (parseInt(cuotaM[1]) || 0) : 0;
    rest = rest.replace(cuotaRx, ' ');

    // ── Payment method ────────────────────────────────────────────
    const pmOptions = [
      { id:'efectivo', kws:['efectivo','cash','contado'] },
      { id:'debito',   kws:['debito','tarjeta de debito','debit'] },
      ...((tarjetasState||[]).filter(tc=>tc.active).map(tc => ({
        id: tc.id,
        kws: [tc.name, tc.bank, tc.network].filter(Boolean).map(norm),
      }))),
    ];
    let detectedPm = '';
    for (const pm of pmOptions) {
      if (pm.kws.some(k => k && rest.includes(k))) {
        detectedPm = pm.id;
        // Remove the matched keyword from rest so it doesn't pollute merchant
        pm.kws.forEach(k => { if (k && rest.includes(k)) rest = rest.replace(k, ' '); });
        break;
      }
    }

    // ── Category ─────────────────────────────────────────────────
    const keywords = {
      cafe:       ['cafe','cafeteria','starbucks','havanna','pedidos','rappi','delivery','heladeria','coffee','medialunas','facturas','cortado','cappuccino'],
      transporte: ['nafta','ypf','shell','uber','sube','subte','colectivo','peaje','combustible','taxi','cabify','remis','estacionamiento','tren','omnibus'],
      super:      ['super','carrefour','coto','dia','jumbo','disco','supermercado','mercado','almacen','chino'],
      comer:      ['restaurant','restau','almuerzo','cena','don julio','sushi','parrilla','comida','asado','bodegon','pizza','pizzeria','hamburgesa','milanesa'],
      ocio:       ['cine','teatro','hoyts','bar','salida','boliche','museo','concierto','recital','entrada','show','fiesta'],
      compras:    ['zara','ropa','amazon','mercadolibre','ml','compra','tienda','nike','adidas','zapatillas','indumentaria'],
      subs:       ['netflix','spotify','disney','hbo','suscripcion','apple','google','openai','chatgpt','claude'],
      salud:      ['farmacia','medico','farmacity','prepaga','dentista','doctor','turno','psicologo','swiss medical','remedios'],
      servicios:  ['edesur','luz','gas','metrogas','internet','agua','aysa','telefono','cable','fibertel','telecom'],
      hogar:      ['alquiler','expensas','administracion','limpieza','mueble','ferreteria','pintura'],
      regalos:    ['regalo','cumple','cumpleanos','flores','presente','tarjeta regalo'],
      mascota:    ['veterinario','vet','mascota','perro','gato','pet','rocky','pelusa'],
    };
    let best = null;
    for (const [cat, kws] of Object.entries(keywords)) {
      if (kws.some(k => rest.includes(k))) { best = cat; break; }
    }

    // ── Merchant: what's left after stripping noise ───────────────
    const noiseRx = /\b(en|de|por|con|a|y|los|las|el|la|pesos?|dolares?|usd|ars|cuotas?|propina)\b/g;
    let merchant = rest.replace(noiseRx, ' ').replace(/\s+/g,' ').trim();
    // Capitalize first letter (fix #11)
    if (merchant) merchant = merchant.charAt(0).toUpperCase() + merchant.slice(1);
    if (!merchant) merchant = best ? (seedA.catPorId[best]?.name || 'Gasto') : 'Gasto';

    // No default category fallback — null means "user should pick" (fix #2)
    setParsed({ amount, cat: best, merchant });

    // Auto-apply side effects
    if (detectedPm)       setTarjetaId(detectedPm);
    if (detectedCuotas >= 2) { setEsCuota(true); setNumCuotas(detectedCuotas); }
  }, [q, mode]);

  function startVoice() {
    const SR = window.SpeechRecognition || window.webkitSpeechRecognition;
    if (!SR) { setVoiceErr('Tu navegador no soporta reconocimiento de voz'); return; }
    setVoiceErr('');
    const rec = new SR();
    rec.lang = 'es-AR';
    rec.continuous = false;
    rec.interimResults = false;
    setListening(true);
    rec.onresult = e => { setQ(e.results[0][0].transcript); setListening(false); };
    rec.onerror = () => { setListening(false); setVoiceErr('No se pudo escuchar — intentalo de nuevo'); };
    rec.onend = () => setListening(false);
    rec.start();
  }

  function handleDebtCommit() {
    const monto = parseFloat(debtMonto.replace(/\./g,'').replace(/,/g,'.'));
    if (!debtNombre.trim() || !monto) return;
    const montoFinal = (debtTipo === 'me_deben' && debtIsSplit) ? monto / 2 : monto;
    if (onCommitDebt) onCommitDebt({ tipo: debtTipo, nombre: debtNombre.trim(), monto: montoFinal, montoTotal: monto, ccy: debtCcy, venc: debtVenc, nota: debtNota.trim(), isSplit: debtTipo === 'me_deben' && debtIsSplit, splitOf: debtSplitOf.trim() });
    onClose();
  }

  const cat = parsed ? seedA.catPorId[parsed.cat] : null;
  const shortcuts = seedA.categories.filter(c => c.group !== 'ahorro');
  const debtMontoNum = parseFloat(debtMonto.replace(/\./g,'').replace(/,/g,'.')) || 0;
  const debtReady = debtNombre.trim().length > 0 && debtMontoNum > 0;

  if (!open) return null;

  // ── Mode tab switcher ──────────────────────────────────────────────
  const modeTabs = (
    <div style={{ display:'flex', padding:4, background:'rgba(255,255,255,0.04)', border:'1px solid var(--border)', borderRadius:12, gap:2, marginBottom:20 }}>
      {[{ id:'gasto', label:'Gasto', icon:'bolt' }, { id:'deuda', label:'Deuda', icon:'exchange' }, { id:'pago', label:'Pago fijo', icon:'calendar' }].map(m => (
        <button key={m.id} onClick={() => setMode(m.id)} style={{
          flex:1, padding:'9px 0', borderRadius:9,
          background: mode===m.id ? 'var(--accent-dim)' : 'transparent',
          color: mode===m.id ? 'var(--accent-bright)' : 'var(--text-2)',
          border:'none', cursor:'pointer', fontFamily:'inherit',
          fontSize:13, fontWeight:600,
          display:'flex', alignItems:'center', justifyContent:'center', gap:7,
          transition:'background 0.14s, color 0.14s',
        }}>
          <IconA name={m.icon} size={14} strokeWidth={mode===m.id ? 2.2 : 1.8} />
          {m.label}
        </button>
      ))}
    </div>
  );

  // ── Gasto form ────────────────────────────────────────────────────
  const gastoForm = (
    <>
      <div style={{ marginBottom:14 }}>
        <div style={{ fontSize:10.5, color:'var(--text-3)', textTransform:'uppercase', letterSpacing:'0.14em', fontWeight:600, marginBottom:8 }}>
          Decilo en una linea
        </div>
        <div style={{ display:'flex', alignItems:'center', gap:10, padding:'14px 16px', background:'rgba(255,255,255,0.04)', border:'1px solid var(--border-strong)', borderRadius:14 }}>
          <input
            ref={inputRef} value={q} onChange={e => setQ(e.target.value)}
            onKeyDown={e => { if (e.key === 'Enter' && parsed) { onCommit(parsed); onClose(); } }}
            placeholder="Ej: 4500 cafe  o  45000 alquiler"
            style={{ flex:1, background:'transparent', border:'none', outline:'none', color:'var(--text)', fontFamily:'inherit', fontSize:15, minWidth:0 }}
          />
          <button onClick={startVoice} title="Reconocimiento de voz" style={{ width:32, height:32, borderRadius:'50%', background: listening ? 'var(--accent)' : 'var(--accent-dim)', color: listening ? 'var(--on-accent)' : 'var(--accent-bright)', border:'none', cursor:'pointer', display:'flex', alignItems:'center', justifyContent:'center', transition:'background 0.2s' }}>
            <IconA name="mic" size={15} />
          </button>
        </div>
        {voiceErr && <div style={{ fontSize:11.5, color:'var(--red)', marginTop:6, paddingLeft:2 }}>{voiceErr}</div>}
        {listening && <div style={{ fontSize:11.5, color:'var(--accent-bright)', marginTop:6, paddingLeft:2, display:'flex', alignItems:'center', gap:6 }}><span style={{ width:8, height:8, borderRadius:'50%', background:'var(--accent-bright)', display:'inline-block', animation:'pulse 1s infinite' }} />Escuchando...</div>}
      </div>

      {parsed && (
        <div style={{ padding:16, borderRadius:14, marginBottom:14, background:'var(--accent-dim)', border:'1px solid var(--border-strong)', animation:'fadeSlide 0.18s ease' }}>
          <div style={{ fontSize:10.5, color:'var(--accent-bright)', textTransform:'uppercase', letterSpacing:'0.14em', fontWeight:600, marginBottom:10 }}>Interpretado como</div>
          <div style={{ display:'flex', alignItems:'center', gap:14 }}>
            {cat
              ? <div style={{ width:42, height:42, borderRadius:11, flexShrink:0, background:cat.color+'24', border:'1px solid '+cat.color+'40', color:cat.color, display:'flex', alignItems:'center', justifyContent:'center' }}><IconA name={cat.icon} size={19} /></div>
              : <div style={{ width:42, height:42, borderRadius:11, flexShrink:0, background:'rgba(255,255,255,0.08)', border:'1px solid var(--border)', color:'var(--text-3)', display:'flex', alignItems:'center', justifyContent:'center', fontSize:11, fontWeight:700 }}>?</div>
            }
            <div style={{ flex:1, minWidth:0 }}>
              <div style={{ fontFamily:'Space Grotesk, sans-serif', fontSize:24, fontWeight:600, fontVariantNumeric:'tabular-nums', letterSpacing:'-0.02em' }}>
                {new Intl.NumberFormat('es-AR',{minimumFractionDigits: parsed.amount % 1 !== 0 ? 2 : 0, maximumFractionDigits:2}).format(parsed.amount).startsWith('-') ? '' : '$ '}{new Intl.NumberFormat('es-AR',{minimumFractionDigits: parsed.amount % 1 !== 0 ? 2 : 0, maximumFractionDigits:2}).format(parsed.amount)}
              </div>
              <div style={{ fontSize:12.5, color: cat ? 'var(--text-2)' : 'var(--amber)', marginTop:3 }}>{cat ? `${cat.name} · ` : '⚠ Elegí categoría · '}{parsed.merchant}</div>
            </div>
          </div>
        </div>
      )}

      {/* ── Categories ── */}
      {isDesktop ? (
        <QaDropdown label="Categoría" icon="bolt" active={parsed?.cat} activeColor={parsed?.cat ? seedA.catPorId[parsed.cat]?.color : null} activeLabel={parsed?.cat ? seedA.catPorId[parsed.cat]?.name : null}>
          <div style={{ display:'flex', flexWrap:'wrap', gap:6, padding:'10px 12px' }}>
            {shortcuts.map(c => (
              <button key={c.id} onClick={() => setQ(prev => { const m = prev.match(/(\d[\d.,]*)/); return m ? `${m[1]} ${c.name}` : c.name; })} style={{
                padding:'7px 12px', borderRadius:999, background: parsed?.cat===c.id ? c.color+'22' : 'rgba(255,255,255,0.04)',
                border:`1px solid ${parsed?.cat===c.id ? c.color+'55' : 'var(--border)'}`, color: parsed?.cat===c.id ? c.color : 'var(--text-2)',
                fontFamily:'inherit', fontSize:12, fontWeight:600, display:'inline-flex', alignItems:'center', gap:6, cursor:'pointer', transition:'all 0.13s',
              }}>
                <span style={{ color:c.color, display:'inline-flex' }}><IconA name={c.icon} size={12} /></span>{c.name}
              </button>
            ))}
          </div>
        </QaDropdown>
      ) : (
        <div style={{ marginBottom:12 }}>
          <div style={{ fontSize:10.5, color:'var(--text-3)', textTransform:'uppercase', letterSpacing:'0.14em', fontWeight:600, marginBottom:8 }}>Categoría</div>
          <div style={{ display:'flex', gap:7, overflowX:'auto', paddingBottom:2, scrollbarWidth:'none', WebkitOverflowScrolling:'touch' }}>
            {shortcuts.map(c => (
              <button key={c.id} onClick={() => setQ(prev => { const m = prev.match(/(\d[\d.,]*)/); return m ? `${m[1]} ${c.name}` : c.name; })} style={{
                flexShrink:0, padding:'8px 13px', borderRadius:999,
                background: parsed?.cat===c.id ? c.color+'22' : 'rgba(255,255,255,0.05)',
                border:`1px solid ${parsed?.cat===c.id ? c.color+'55' : 'var(--border)'}`,
                color: parsed?.cat===c.id ? c.color : 'var(--text-2)',
                fontFamily:'inherit', fontSize:12.5, fontWeight:600,
                display:'inline-flex', alignItems:'center', gap:7, cursor:'pointer', transition:'all 0.13s',
              }}>
                <span style={{ color:c.color, display:'inline-flex' }}><IconA name={c.icon} size={13} /></span>{c.name}
              </button>
            ))}
          </div>
        </div>
      )}

      {/* ── Payment methods ── */}
      {(() => {
        const pmList = [
          { id:'efectivo', label:'Efectivo', icon:'dollar', color:'#4ade80' },
          { id:'debito',   label:'Débito',   icon:'cards',  color:'#60a5fa' },
          ...((tarjetasState||[]).filter(t=>t.active).map(t => ({
            id: t.id, label: t.name, icon:'cards', color:'#818cf8',
          }))),
        ];
        if (isDesktop) return (
          <QaDropdown label="Medio de pago" icon="cards" active={tarjetaId} activeColor={pmList.find(p=>p.id===tarjetaId)?.color} activeLabel={pmList.find(p=>p.id===tarjetaId)?.label}>
            <div style={{ display:'flex', flexWrap:'wrap', gap:6, padding:'10px 12px' }}>
              {pmList.map(pm => (
                <button key={pm.id} onClick={() => setTarjetaId(pm.id === tarjetaId ? '' : pm.id)} style={{
                  padding:'7px 12px', borderRadius:999,
                  background: tarjetaId===pm.id ? pm.color+'22' : 'rgba(255,255,255,0.04)',
                  border:`1px solid ${tarjetaId===pm.id ? pm.color+'55' : 'var(--border)'}`,
                  color: tarjetaId===pm.id ? pm.color : 'var(--text-2)',
                  fontFamily:'inherit', fontSize:12, fontWeight:600, display:'inline-flex', alignItems:'center', gap:6, cursor:'pointer', transition:'all 0.13s',
                }}>
                  <span style={{ color:pm.color, display:'inline-flex' }}><IconA name={pm.icon} size={12} /></span>{pm.label}
                </button>
              ))}
            </div>
          </QaDropdown>
        );
        return (
          <div style={{ marginBottom:12 }}>
            <div style={{ fontSize:10.5, color:'var(--text-3)', textTransform:'uppercase', letterSpacing:'0.14em', fontWeight:600, marginBottom:8 }}>Medio de pago</div>
            <div style={{ display:'flex', gap:7, overflowX:'auto', paddingBottom:2, scrollbarWidth:'none', WebkitOverflowScrolling:'touch' }}>
              {pmList.map(pm => (
                <button key={pm.id} onClick={() => setTarjetaId(pm.id === tarjetaId ? '' : pm.id)} style={{
                  flexShrink:0, padding:'8px 13px', borderRadius:999,
                  background: tarjetaId===pm.id ? pm.color+'22' : 'rgba(255,255,255,0.05)',
                  border:`1px solid ${tarjetaId===pm.id ? pm.color+'55' : 'var(--border)'}`,
                  color: tarjetaId===pm.id ? pm.color : 'var(--text-2)',
                  fontFamily:'inherit', fontSize:12.5, fontWeight:600,
                  display:'inline-flex', alignItems:'center', gap:7, cursor:'pointer', transition:'all 0.13s',
                }}>
                  <span style={{ color:pm.color, display:'inline-flex' }}><IconA name={pm.icon} size={13} /></span>{pm.label}
                </button>
              ))}
            </div>
          </div>
        );
      })()}

      {/* ── ¿Es en cuotas? — siempre visible para gastos ── */}
      {true && (
        <div style={{ marginBottom:14 }}>
          <label style={{ display:'flex', alignItems:'center', gap:10, cursor:'pointer', padding:'11px 14px', borderRadius:12, background: esCuota ? 'rgba(99,102,241,0.12)' : 'rgba(255,255,255,0.04)', border:`1px solid ${esCuota ? 'rgba(99,102,241,0.35)' : 'var(--border)'}`, transition:'all 0.15s' }}>
            <input
              type="checkbox"
              checked={esCuota}
              onChange={e => setEsCuota(e.target.checked)}
              style={{ width:16, height:16, accentColor:'var(--accent-bright)', cursor:'pointer', flexShrink:0 }}
            />
            <div>
              <div style={{ fontSize:13, fontWeight:600, color: esCuota ? '#818cf8' : 'var(--text-2)' }}>¿Es un pago en cuotas?</div>
              {esCuota && parsed?.amount && (
                <div style={{ fontSize:11, color:'var(--text-3)', marginTop:3 }}>
                  {numCuotas} cuotas de $ {new Intl.NumberFormat('es-AR', { minimumFractionDigits:2, maximumFractionDigits:2 }).format(parsed.amount / numCuotas)} / mes
                </div>
              )}
            </div>
          </label>
          {esCuota && (
            <div style={{ marginTop:8, display:'flex', flexDirection:'column', gap:8, padding:'12px 14px', borderRadius:12, background:'rgba(255,255,255,0.03)', border:'1px solid var(--border)' }}>
              <div style={{ fontSize:10.5, color:'var(--text-3)', textTransform:'uppercase', letterSpacing:'0.12em', fontWeight:600 }}>Cantidad de cuotas</div>
              <div style={{ display:'flex', gap:6, flexWrap:'wrap' }}>
                {[2,3,6,9,12,18,24].map(n => (
                  <button
                    key={n}
                    onClick={() => setNumCuotas(n)}
                    style={{
                      padding:'7px 14px', borderRadius:999, cursor:'pointer', fontFamily:'inherit',
                      fontSize:12.5, fontWeight:700,
                      background: numCuotas === n ? 'var(--accent)' : 'rgba(255,255,255,0.05)',
                      border: `1px solid ${numCuotas === n ? 'transparent' : 'var(--border)'}`,
                      color: numCuotas === n ? 'var(--on-accent)' : 'var(--text-2)',
                      transition:'all 0.13s',
                    }}
                  >
                    {n}x
                  </button>
                ))}
              </div>
              <input
                value={cuotaDesc}
                onChange={e => setCuotaDesc(e.target.value)}
                placeholder="Descripción (ej: iPhone 15, Heladera...)"
                style={{ padding:'9px 12px', borderRadius:9, background:'rgba(255,255,255,0.05)', border:'1px solid var(--border)', color:'var(--text)', fontFamily:'inherit', fontSize:13, outline:'none' }}
              />
            </div>
          )}
        </div>
      )}

      {/* ── Confirm button always visible when there's parseable amount ── */}
      {parsed && (
        <button
          onClick={() => {
            if (esCuota) {
              const g = {
                descripcion: cuotaDesc.trim() || parsed.merchant || (cat ? cat.name : 'Cuota'),
                montoTotal: parsed.amount, totalCuotas: numCuotas,
                catId: parsed.cat, tarjetaId: tarjetaId || null,
                fechaInicio: new Date().toISOString().split('T')[0],
              };
              if (onCommitCuota) onCommitCuota(g);
            } else {
              onCommit({ ...parsed, tarjetaId: tarjetaId || null });
            }
            onClose();
          }}
          className="btn-accent-hov"
          style={{ width:'100%', marginTop:4, padding:'13px 0', borderRadius:10, fontSize:13.5, fontWeight:600, background:'var(--accent)', color:'var(--on-accent)', border:'none', cursor:'pointer', fontFamily:'inherit', display:'flex', alignItems:'center', justifyContent:'center', gap:8 }}
        >
          <IconA name="check" size={15} strokeWidth={2.5} />
          {esCuota ? `Registrar ${numCuotas} cuotas de $${new Intl.NumberFormat('es-AR',{maximumFractionDigits:0}).format(parsed.amount/numCuotas)}` : `Confirmar gasto · $${new Intl.NumberFormat('es-AR').format(parsed.amount)}`}
        </button>
      )}
    </>
  );

  // ── Deuda form ────────────────────────────────────────────────────
  const fieldStyle = {
    width:'100%', padding:'11px 14px', borderRadius:10, boxSizing:'border-box',
    background:'rgba(255,255,255,0.04)', border:'1px solid var(--border-strong)',
    color:'var(--text)', fontFamily:'inherit', fontSize:14, outline:'none',
    transition:'border-color 0.14s',
  };
  const labelStyle = { fontSize:10.5, color:'var(--text-3)', textTransform:'uppercase', letterSpacing:'0.12em', fontWeight:600, marginBottom:6, display:'block' };

  const deudaForm = (
    <div style={{ display:'flex', flexDirection:'column', gap:14 }}>
      {/* Tipo toggle */}
      <div>
        <span style={labelStyle}>Tipo</span>
        <div style={{ display:'flex', gap:6 }}>
          {[{ id:'me_deben', label:'Me deben', icon:'arrowDown' }, { id:'debo', label:'Yo debo', icon:'arrowUp' }].map(t => (
            <button key={t.id} onClick={() => setDebtTipo(t.id)} style={{
              flex:1, padding:'10px 0', borderRadius:10, fontSize:13, fontWeight:600,
              background: debtTipo===t.id ? (t.id==='me_deben' ? 'rgba(74,222,128,0.1)' : 'rgba(248,113,113,0.1)') : 'var(--card)',
              color: debtTipo===t.id ? (t.id==='me_deben' ? '#4ade80' : '#f87171') : 'var(--text-2)',
              border: debtTipo===t.id ? `1px solid ${t.id==='me_deben' ? 'rgba(74,222,128,0.35)' : 'rgba(248,113,113,0.35)'}` : '1px solid var(--border)',
              cursor:'pointer', fontFamily:'inherit',
              display:'flex', alignItems:'center', justifyContent:'center', gap:7,
              transition:'all 0.15s',
            }}>
              <IconA name={t.icon} size={13} strokeWidth={2.2} />
              {t.label}
            </button>
          ))}
        </div>
        <div style={{ fontSize:11.5, color:'var(--text-3)', marginTop:7, paddingLeft:2, lineHeight:1.4 }}>
          {debtTipo === 'me_deben'
            ? 'Alguien te debe plata — aparecerá en "Te deben" de Pagos.'
            : 'Vos debés plata — aparecerá en "Por pagar" de Pagos.'}
        </div>
      </div>

      {/* Nombre */}
      <div>
        <label style={labelStyle}>{debtTipo === 'me_deben' ? 'Nombre del deudor' : 'A quién le debés'}</label>
        <input
          value={debtNombre} onChange={e => setDebtNombre(e.target.value)}
          placeholder={debtTipo === 'me_deben' ? 'Ej: Juan García' : 'Ej: María López'}
          style={fieldStyle}
          onFocus={e => e.target.style.borderColor='var(--accent)'}
          onBlur={e => e.target.style.borderColor='var(--border-strong)'}
        />
      </div>

      {/* Monto + Moneda */}
      <div>
        <label style={labelStyle}>Monto</label>
        <div style={{ display:'flex', gap:8, alignItems:'stretch' }}>
          <input
            type="number" inputMode="decimal"
            value={debtMonto} onChange={e => setDebtMonto(e.target.value)}
            placeholder="0"
            style={{ ...fieldStyle, flex:1, fontFamily:'Space Grotesk, sans-serif', fontSize:22, fontWeight:600 }}
            onFocus={e => e.target.style.borderColor='var(--accent)'}
            onBlur={e => e.target.style.borderColor='var(--border-strong)'}
          />
          <div style={{ display:'flex', flexDirection:'column', gap:4 }}>
            {['ARS','USD'].map(c => (
              <button key={c} onClick={() => setDebtCcy(c)} style={{
                flex:1, padding:'0 12px', borderRadius:9, border:`1px solid ${debtCcy===c ? 'var(--accent)' : 'var(--border)'}`,
                background: debtCcy===c ? 'var(--accent-dim)' : 'rgba(255,255,255,0.04)',
                color: debtCcy===c ? 'var(--accent-bright)' : 'var(--text-2)',
                fontFamily:'inherit', fontSize:12, fontWeight:700, cursor:'pointer', transition:'all 0.13s',
              }}>{c}</button>
            ))}
          </div>
        </div>
        {debtMontoNum > 0 && (
          <div style={{ fontSize:11.5, color:'var(--text-3)', marginTop:5, paddingLeft:2 }}>
            {debtCcy === 'USD' ? `US$ ${new Intl.NumberFormat('en-US',{minimumFractionDigits:2,maximumFractionDigits:2}).format(debtMontoNum)}` : `$ ${new Intl.NumberFormat('es-AR').format(debtMontoNum)}`}
            {debtCcy === 'USD' && seedA?.fxBlue && <span style={{ marginLeft:8 }}>≈ $ {new Intl.NumberFormat('es-AR').format(Math.round(debtMontoNum * seedA.fxBlue))} ARS</span>}
          </div>
        )}
      </div>

      {/* Vencimiento */}
      <div>
        <label style={labelStyle}>Fecha de vencimiento</label>
        <div style={{ background:'rgba(255,255,255,0.03)', border:'1px solid var(--border-strong)', borderRadius:10, padding:'14px 12px 12px' }}>
          {window.MiniCalendar
            ? React.createElement(window.MiniCalendar, { value: debtVenc, onChange: (str) => setDebtVenc(str) })
            : <input type="date" value={debtVenc} onChange={e => setDebtVenc(e.target.value)} style={{ ...fieldStyle, colorScheme:'dark' }} />
          }
        </div>
        <div style={{ fontSize:11.5, color:'var(--text-3)', marginTop:5, paddingLeft:2 }}>
          Vence: {debtVenc ? new Date(debtVenc + 'T12:00:00').toLocaleDateString('es-AR', { weekday:'short', day:'numeric', month:'long' }) : '—'}
        </div>
      </div>

      {/* Nota opcional */}
      <div>
        <label style={labelStyle}>Nota (opcional)</label>
        <input
          value={debtNota} onChange={e => setDebtNota(e.target.value)}
          placeholder="Ej: Préstamo para mudanza"
          style={fieldStyle}
          onFocus={e => e.target.style.borderColor='var(--accent)'}
          onBlur={e => e.target.style.borderColor='var(--border-strong)'}
        />
      </div>

      {/* Split toggle */}
      {debtTipo === 'me_deben' && (
        <div>
          <label style={{ display:'flex', alignItems:'center', gap:10, cursor:'pointer', padding:'12px 14px', borderRadius:11,
            background: debtIsSplit ? 'rgba(74,222,128,0.07)' : 'rgba(255,255,255,0.03)',
            border: debtIsSplit ? '1px solid rgba(74,222,128,0.28)' : '1px solid var(--border)',
            transition:'all 0.15s' }}>
            <input
              type="checkbox"
              checked={debtIsSplit}
              onChange={e => setDebtIsSplit(e.target.checked)}
              style={{ width:16, height:16, accentColor:'#4ade80', cursor:'pointer', flexShrink:0 }}
            />
            <div>
              <div style={{ fontSize:13.5, fontWeight:600, color: debtIsSplit ? '#4ade80' : 'var(--text-2)' }}>
                ¿Es un gasto compartido? (Split)
              </div>
              <div style={{ fontSize:11.5, color:'var(--text-3)', marginTop:2 }}>
                Divide el total en 2 — te deben la mitad
              </div>
            </div>
          </label>

          {debtIsSplit && debtMontoNum > 0 && (
            <div style={{ marginTop:8, padding:'12px 14px', borderRadius:10, background:'rgba(74,222,128,0.06)', border:'1px solid rgba(74,222,128,0.2)', display:'flex', justifyContent:'space-between', alignItems:'center' }}>
              <div style={{ display:'flex', flexDirection:'column', gap:2 }}>
                <div style={{ fontSize:11, color:'var(--text-3)', textTransform:'uppercase', letterSpacing:'0.08em', fontWeight:600 }}>Total del gasto</div>
                <div style={{ fontFamily:'Space Grotesk, sans-serif', fontSize:15, fontWeight:600, fontVariantNumeric:'tabular-nums', color:'var(--text-2)' }}>
                  $ {new Intl.NumberFormat('es-AR').format(debtMontoNum)}
                </div>
              </div>
              <div style={{ fontSize:18, color:'rgba(74,222,128,0.4)' }}>→</div>
              <div style={{ display:'flex', flexDirection:'column', gap:2, textAlign:'right' }}>
                <div style={{ fontSize:11, color:'rgba(74,222,128,0.8)', textTransform:'uppercase', letterSpacing:'0.08em', fontWeight:600 }}>Te deben</div>
                <div style={{ fontFamily:'Space Grotesk, sans-serif', fontSize:18, fontWeight:700, fontVariantNumeric:'tabular-nums', color:'#4ade80' }}>
                  $ {new Intl.NumberFormat('es-AR').format(debtMontoNum / 2)}
                </div>
              </div>
            </div>
          )}

          {debtIsSplit && (
            <div style={{ marginTop:8 }}>
              <div style={{ fontSize:10.5, color:'var(--text-3)', textTransform:'uppercase', letterSpacing:'0.12em', fontWeight:600, marginBottom:6 }}>¿De qué gasto?</div>
              <input
                value={debtSplitOf} onChange={e => setDebtSplitOf(e.target.value)}
                placeholder="Ej: Cena Don Julio, Airbnb Bariloche…"
                style={{ ...fieldStyle, fontSize:13 }}
                onFocus={e => e.target.style.borderColor='rgba(74,222,128,0.5)'}
                onBlur={e => e.target.style.borderColor='var(--border-strong)'}
              />
            </div>
          )}
        </div>
      )}

      {/* Confirm */}
      <button
        onClick={handleDebtCommit}
        disabled={!debtReady}
        className="btn-accent-hov"
        style={{
          width:'100%', padding:'13px 0', borderRadius:12,
          background: debtReady
            ? (debtTipo === 'me_deben' ? 'linear-gradient(145deg, #4ade80, #16a34a)' : 'linear-gradient(145deg, var(--accent-bright), var(--accent-deep))')
            : 'var(--card)',
          color: debtReady ? 'white' : 'var(--text-3)',
          border: debtReady ? 'none' : '1px solid var(--border)',
          cursor: debtReady ? 'pointer' : 'not-allowed',
          fontFamily:'inherit', fontSize:14, fontWeight:600,
          display:'flex', alignItems:'center', justifyContent:'center', gap:8,
          transition:'all 0.15s',
        }}
      >
        <IconA name="check" size={15} strokeWidth={2.5} />
        {debtTipo === 'me_deben' ? (debtIsSplit && debtMontoNum > 0 ? `Registrar split · $ ${new Intl.NumberFormat('es-AR').format(debtMontoNum/2)}` : 'Registrar deudor') : 'Registrar deuda'}
      </button>
    </div>
  );

  // ── Pago fijo form ─────────────────────────────────────────────────
  const pagoMontoNum = parseFloat((pagoMonto||'').replace(/\./g,'').replace(/,/g,'.')) || 0;
  const pagoReady = pagoNombre.trim().length > 0 && pagoMontoNum > 0;
  function handlePagoCommit() {
    if (!pagoReady) return;
    const fxBlue = seedA?.fxBlue || 1480;
    const montoARS = pagoCcy === 'USD' ? Math.round(pagoMontoNum * fxBlue) : pagoMontoNum;
    if (onCommitPago) onCommitPago({ nombre: pagoNombre.trim(), monto: montoARS, montoCcy: pagoCcy, cat: pagoCat, venc: pagoVenc });
    onClose();
  }
  const pagoCatOptions = (seedA.categories || []).filter(c => c.group !== 'ahorro');
  const pagoFmtPreview = pagoMontoNum > 0
    ? (pagoCcy === 'USD'
        ? 'US$ ' + new Intl.NumberFormat('en-US', { maximumFractionDigits:0 }).format(pagoMontoNum)
        : '$ ' + new Intl.NumberFormat('es-AR', { maximumFractionDigits:0 }).format(pagoMontoNum))
    : null;

  const pagoForm = (
    <div style={{ display:'flex', flexDirection:'column', gap:14 }}>
      <div>
        <span style={labelStyle}>Nombre del pago</span>
        <input
          value={pagoNombre} onChange={e => setPagoNombre(e.target.value)}
          onKeyDown={e => e.key === 'Enter' && handlePagoCommit()}
          placeholder="Ej: Alquiler, Internet, Luz"
          style={fieldStyle}
          onFocus={e => e.target.style.borderColor='var(--accent)'}
          onBlur={e => e.target.style.borderColor='var(--border-strong)'}
        />
      </div>
      <div>
        <span style={labelStyle}>Monto</span>
        {/* Currency toggle */}
        <div style={{ display:'flex', gap:6, marginBottom:8 }}>
          {['ARS','USD'].map(c => (
            <button key={c} onClick={() => setPagoCcy(c)} style={{
              padding:'5px 14px', borderRadius:8, fontSize:12, fontWeight:700, cursor:'pointer', fontFamily:'inherit',
              background: pagoCcy===c ? 'var(--accent-dim)' : 'rgba(255,255,255,0.04)',
              color: pagoCcy===c ? 'var(--accent-bright)' : 'var(--text-3)',
              border: `1px solid ${pagoCcy===c ? 'var(--border-strong)' : 'var(--border)'}`,
              transition:'all 0.13s',
            }}>{c}</button>
          ))}
        </div>
        <div style={{ display:'flex', alignItems:'center', gap:8, padding:'10px 14px', borderRadius:12, background:'rgba(255,255,255,0.04)', border:'1px solid var(--border-strong)' }}>
          <span style={{ fontSize:18, color:'var(--text-3)', fontWeight:600 }}>{pagoCcy === 'USD' ? 'US$' : '$'}</span>
          <input
            type="text" inputMode="numeric"
            value={pagoMonto} onChange={e => setPagoMonto(e.target.value)}
            onKeyDown={e => e.key === 'Enter' && handlePagoCommit()}
            placeholder="0"
            style={{ flex:1, background:'transparent', border:'none', outline:'none', color:'var(--text)', fontFamily:'Space Grotesk, sans-serif', fontSize:22, fontWeight:700, fontVariantNumeric:'tabular-nums' }}
          />
        </div>
        {pagoFmtPreview && (
          <div style={{ fontSize:12, color:'var(--text-3)', marginTop:5, paddingLeft:2 }}>{pagoFmtPreview}</div>
        )}
      </div>
      <div>
        <span style={labelStyle}>Categoría</span>
        <div style={{ display:'flex', flexWrap:'wrap', gap:6 }}>
          {pagoCatOptions.slice(0,8).map(c => (
            <button key={c.id} onClick={() => setPagoCat(c.id)} style={{
              padding:'6px 12px', borderRadius:9, fontSize:12.5, fontWeight:600, cursor:'pointer', fontFamily:'inherit',
              background: pagoCat===c.id ? c.color+'28' : 'rgba(255,255,255,0.04)',
              color: pagoCat===c.id ? c.color : 'var(--text-3)',
              border: `1px solid ${pagoCat===c.id ? c.color+'55' : 'var(--border)'}`,
              transition:'all 0.13s',
            }}>{c.name}</button>
          ))}
        </div>
      </div>
      <div>
        <span style={labelStyle}>Fecha límite</span>
        <div style={{ background:'rgba(255,255,255,0.03)', border:'1px solid var(--border-strong)', borderRadius:10, padding:'14px 12px 12px' }}>
          {window.MiniCalendar
            ? React.createElement(window.MiniCalendar, { value: pagoVenc, onChange: str => setPagoVenc(str) })
            : <input type="date" value={pagoVenc} onChange={e => setPagoVenc(e.target.value)} style={{ ...fieldStyle, colorScheme:'dark' }} />
          }
        </div>
        <div style={{ fontSize:11.5, color:'var(--text-3)', marginTop:5 }}>
          Vence: {pagoVenc ? new Date(pagoVenc+'T12:00:00').toLocaleDateString('es-AR',{weekday:'short',day:'numeric',month:'long'}) : '—'}
        </div>
      </div>
      <button
        onClick={handlePagoCommit}
        disabled={!pagoReady}
        className="btn-accent-hov"
        style={{
          width:'100%', padding:'14px 0', borderRadius:13,
          background: pagoReady ? 'var(--accent)' : 'var(--card)',
          color: pagoReady ? 'var(--on-accent)' : 'var(--text-3)',
          border: pagoReady ? 'none' : '1px solid var(--border)',
          cursor: pagoReady ? 'pointer' : 'not-allowed',
          fontFamily:'inherit', fontSize:14, fontWeight:600,
          display:'flex', alignItems:'center', justifyContent:'center', gap:8,
          transition:'all 0.15s',
        }}
      >
        <IconA name="calendar" size={15} strokeWidth={2.5} />
        {pagoReady ? `Agregar pago · $ ${new Intl.NumberFormat('es-AR').format(pagoMontoNum)}` : 'Completá los datos'}
      </button>
    </div>
  );

  const inner = (
    <>
      {!isDesktop && (
        <div style={{ width:36, height:4, background:'rgba(255,255,255,0.18)', borderRadius:999, margin:'0 auto 18px' }} />
      )}
      <div style={{ display:'flex', alignItems:'center', justifyContent:'space-between', marginBottom:18 }}>
        <div style={{ fontFamily:'Space Grotesk, sans-serif', fontSize:20, fontWeight:600, letterSpacing:'-0.02em' }}>
          {mode === 'gasto' ? 'Nuevo gasto' : mode === 'pago' ? 'Pago fijo / servicio' : 'Registrar deuda'}
        </div>
        <button onClick={onClose} style={{ background:'none', border:'none', color:'var(--text-3)', cursor:'pointer', padding:6 }}>
          <IconA name="x" size={18} />
        </button>
      </div>
      {modeTabs}
      {mode === 'gasto' ? gastoForm : mode === 'pago' ? pagoForm : deudaForm}
    </>
  );

  if (isDesktop) {
    return (
      <div style={{ position:'fixed', inset:0, zIndex:50, display:'flex', alignItems:'center', justifyContent:'center', background:'rgba(5,3,13,0.72)', backdropFilter:'blur(10px)', animation:'fadeIn 0.18s', padding:24 }} onClick={onClose}>
        <div onClick={e => e.stopPropagation()} style={{ width:'100%', maxWidth:520, maxHeight:'85vh', background:'rgba(18,10,36,0.98)', backdropFilter:'blur(32px) saturate(1.5)', border:'1px solid var(--border-strong)', borderRadius:24, padding:'28px 28px 36px', animation:'fadeSlide 0.22s cubic-bezier(0.2,0.8,0.2,1)', overflowY:'auto' }}>
          {inner}
        </div>
      </div>
    );
  }

  return (
    <div style={{ position:'fixed', inset:0, zIndex:50, display:'flex', alignItems:'flex-end', background:'rgba(5,3,13,0.60)', backdropFilter:'blur(8px)', animation:'fadeIn 0.18s' }} onClick={onClose}>
      <div onClick={e => e.stopPropagation()} style={{ width:'100%', background:'rgba(20,12,40,0.96)', backdropFilter:'blur(28px) saturate(1.4)', border:'1px solid var(--border-strong)', borderBottom:'none', borderRadius:'24px 24px 0 0', padding:'12px 20px 32px', animation:'slideUp 0.28s cubic-bezier(0.2,0.8,0.2,1)', maxHeight:'90dvh', overflowY:'auto' }}>
        {inner}
      </div>
    </div>
  );
}

// ─── Notification Panel ────────────────────────────────────────────────────────
function NotifPanel({ open, onClose, notifs, onDismiss, isDesktop, pagos, paidPagos, onTogglePaid, deudores, collectedDeudores, onMarkCollected, ingresos, confirmedIngs, onConfirmIngreso, ccy, pendingBandeja, onConfirmBandeja, onDiscardBandeja }) {
  const kindColor = { urgent:'#f87171', warn:'#fbbf24', info:'var(--accent-bright)' };
  const kindBg    = { urgent:'rgba(248,113,113,0.10)', warn:'rgba(251,191,36,0.10)', info:'rgba(139,92,246,0.08)' };
  const kindBorder = { urgent:'rgba(248,113,113,0.3)', warn:'rgba(251,191,36,0.28)', info:'rgba(139,92,246,0.2)' };
  const kindIcon  = { urgent:'bell', warn:'alert', info:'pin' };

  const bandejaItems  = pendingBandeja || [];
  const allPagos      = pagos || [];
  const allDeudores   = deudores || [];
  const paid          = paidPagos || new Set();
  const collected     = collectedDeudores || new Set();
  const pendingPagos  = allPagos.filter(p => !paid.has(p.id)).sort((a,b) => new Date(a.due||'9999') - new Date(b.due||'9999'));
  const pendingDeus   = allDeudores.filter(d => !collected.has(d.id));
  const totalCount    = notifs.length + bandejaItems.length + pendingPagos.length + pendingDeus.length;

  const inner = (
    <div style={{ display:'flex', flexDirection:'column', height:'100%', overflowY:'auto' }}>
      {/* Header */}
      <div style={{ display:'flex', alignItems:'center', justifyContent:'space-between', marginBottom:16, flexShrink:0, paddingBottom:14, borderBottom:'1px solid var(--border)' }}>
        <div>
          <div style={{ fontFamily:'Space Grotesk, sans-serif', fontSize:16, fontWeight:600, letterSpacing:'-0.01em' }}>Notificaciones</div>
          {totalCount > 0 && (
            <div style={{ fontSize:11.5, color:'var(--text-3)', marginTop:2 }}>
              {bandejaItems.length > 0 ? `${bandejaItems.length} sin clasificar · ` : ''}{notifs.length} avis{notifs.length===1?'o':'os'}
            </div>
          )}
        </div>
        <button onClick={onClose} style={{ width:28, height:28, borderRadius:8, background:'rgba(255,255,255,0.05)', border:'1px solid var(--border)', color:'var(--text-3)', cursor:'pointer', display:'flex', alignItems:'center', justifyContent:'center' }}>
          <IconA name="x" size={12} />
        </button>
      </div>

      {/* AVISOS section */}
      {notifs.length > 0 && (
        <div style={{ marginBottom:20 }}>
          <div style={{ fontSize:10, color:'var(--text-3)', textTransform:'uppercase', letterSpacing:'0.14em', fontWeight:700, marginBottom:10 }}>Avisos</div>
          <div style={{ display:'flex', flexDirection:'column', gap:8 }}>
            {notifs.map(n => (
              <div key={n.id} style={{
                display:'flex', alignItems:'flex-start', gap:12, padding:'12px 14px', borderRadius:13,
                background: kindBg[n.kind] || 'rgba(255,255,255,0.03)',
                border:`1px solid ${kindBorder[n.kind] || 'var(--border)'}`,
                position:'relative',
              }}>
                <div style={{ width:34, height:34, borderRadius:10, flexShrink:0,
                  background: n.kind==='urgent'?'rgba(248,113,113,0.15)':n.kind==='warn'?'rgba(251,191,36,0.15)':'rgba(139,92,246,0.15)',
                  color: kindColor[n.kind]||'var(--accent-bright)',
                  display:'flex', alignItems:'center', justifyContent:'center' }}>
                  <IconA name={kindIcon[n.kind]||'bell'} size={15} strokeWidth={2} />
                </div>
                <div style={{ flex:1, minWidth:0, paddingRight:18 }}>
                  <div style={{ fontSize:13, fontWeight:600, lineHeight:1.35 }}>{n.title}</div>
                  <div style={{ fontSize:11.5, color:'var(--text-3)', marginTop:2, lineHeight:1.4 }}>{n.meta}</div>
                  {n.amount != null && (
                    <div style={{ fontFamily:'Space Grotesk, sans-serif', fontSize:14, fontWeight:700, color: kindColor[n.kind]||'var(--accent-bright)', marginTop:5, fontVariantNumeric:'tabular-nums' }}>
                      $ {new Intl.NumberFormat('es-AR').format(n.amount)}
                    </div>
                  )}
                </div>
                <button onClick={() => onDismiss(n.id)} style={{ position:'absolute', top:9, right:9, width:20, height:20, borderRadius:6, background:'transparent', border:'none', color:'var(--text-3)', cursor:'pointer', display:'flex', alignItems:'center', justifyContent:'center', opacity:0.6 }}
                  onMouseEnter={e=>e.currentTarget.style.opacity='1'} onMouseLeave={e=>e.currentTarget.style.opacity='0.6'}>
                  <IconA name="x" size={10} />
                </button>
              </div>
            ))}
          </div>
        </div>
      )}

      {/* POR PAGAR section */}
      {pendingPagos.length > 0 && (
        <div style={{ marginBottom:20 }}>
          <div style={{ fontSize:10, color:'var(--text-3)', textTransform:'uppercase', letterSpacing:'0.14em', fontWeight:700, marginBottom:10 }}>Por pagar</div>
          <div style={{ display:'flex', flexDirection:'column', gap:8 }}>
            {pendingPagos.map(p => {
              const ds  = window.S?.dueState(p.due||'') || 'info';
              const dl  = window.S?.dueLabel(p.due||'') || '';
              const isU = ds === 'urgent' || ds === 'late';
              const cat = seedA?.catPorId?.[p.cat];
              const CatChipDyn = window.CatChip;
              const fmt = n => window.S ? window.S.fmtMoney(n, ccy||'ARS') : ('$ '+n);
              return (
                <div key={p.id} style={{ display:'flex', alignItems:'center', gap:12, padding:'12px 14px', borderRadius:13, background: isU?'rgba(248,113,113,0.07)':'rgba(255,255,255,0.03)', border:`1px solid ${isU?'rgba(248,113,113,0.25)':'var(--border)'}` }}>
                  {CatChipDyn && cat ? <CatChipDyn cat={cat} size={34} /> :
                    <div style={{ width:34, height:34, borderRadius:10, background: isU?'rgba(248,113,113,0.15)':'rgba(251,191,36,0.12)', display:'flex', alignItems:'center', justifyContent:'center', color: isU?'#f87171':'#fbbf24', flexShrink:0 }}>
                      <IconA name="calendar" size={15} strokeWidth={2} />
                    </div>}
                  <div style={{ flex:1, minWidth:0 }}>
                    <div style={{ fontSize:13, fontWeight:600 }}>{p.name}</div>
                    <div style={{ fontSize:11, color: isU?'#f87171':ds==='warn'?'#fbbf24':'var(--text-3)', marginTop:1, fontWeight: isU?700:400, textTransform:'uppercase', letterSpacing:'0.06em' }}>{dl}</div>
                  </div>
                  <div style={{ fontFamily:'Space Grotesk, sans-serif', fontSize:14, fontWeight:700, color:'#f87171', fontVariantNumeric:'tabular-nums', flexShrink:0 }}>
                    <span style={{ color:'#f87171' }}>$</span>{' '}{new Intl.NumberFormat('es-AR').format(Math.round(p.amount))}
                  </div>
                  <button onClick={() => onTogglePaid(p.id)} style={{ padding:'6px 12px', borderRadius:8, background:'var(--accent-dim)', border:'1px solid var(--border-strong)', color:'var(--accent-bright)', cursor:'pointer', fontFamily:'inherit', fontSize:11.5, fontWeight:700, flexShrink:0 }}>Pagar</button>
                </div>
              );
            })}
          </div>
        </div>
      )}

      {/* TE DEBEN section */}
      {pendingDeus.length > 0 && (
        <div style={{ marginBottom:20 }}>
          <div style={{ fontSize:10, color:'var(--text-3)', textTransform:'uppercase', letterSpacing:'0.14em', fontWeight:700, marginBottom:10 }}>Te deben</div>
          <div style={{ display:'flex', flexDirection:'column', gap:8 }}>
            {pendingDeus.map(d => (
              <div key={d.id} style={{ display:'flex', alignItems:'center', gap:12, padding:'12px 14px', borderRadius:13, background:'rgba(74,222,128,0.05)', border:'1px solid rgba(74,222,128,0.2)' }}>
                <div style={{ width:34, height:34, borderRadius:'50%', background:'rgba(74,222,128,0.15)', color:'#4ade80', fontWeight:700, fontSize:14, display:'flex', alignItems:'center', justifyContent:'center', flexShrink:0 }}>
                  {(d.name||'?').charAt(0).toUpperCase()}
                </div>
                <div style={{ flex:1, minWidth:0 }}>
                  <div style={{ fontSize:13, fontWeight:600 }}>{d.name}</div>
                  {d.due && <div style={{ fontSize:11, color:'var(--text-3)', marginTop:1 }}>{window.S?.dueLabel(d.due)||''}</div>}
                </div>
                <div style={{ fontFamily:'Space Grotesk, sans-serif', fontSize:14, fontWeight:700, fontVariantNumeric:'tabular-nums', color:'var(--text)', flexShrink:0 }}>
                  <span style={{ color:'#4ade80' }}>$</span>{' '}{new Intl.NumberFormat('es-AR').format(Math.round(Math.abs(d.amount)))}
                </div>
                <button onClick={() => onMarkCollected(d.id)} style={{ padding:'6px 12px', borderRadius:8, background:'rgba(74,222,128,0.12)', border:'1px solid rgba(74,222,128,0.25)', color:'#4ade80', cursor:'pointer', fontFamily:'inherit', fontSize:11.5, fontWeight:700, flexShrink:0 }}>Cobrar</button>
              </div>
            ))}
          </div>
        </div>
      )}

      {/* BANCO · SIN CLASIFICAR section */}
      {bandejaItems.length > 0 && (
        <div style={{ marginBottom:16 }}>
          <div style={{ display:'flex', alignItems:'center', gap:8, marginBottom:10 }}>
            <div style={{ fontSize:10, color:'var(--text-3)', textTransform:'uppercase', letterSpacing:'0.14em', fontWeight:700 }}>Banco · Sin clasificar</div>
            <span style={{ fontSize:10, fontWeight:800, padding:'1px 7px', borderRadius:999, background:'rgba(99,102,241,0.18)', color:'#818cf8' }}>{bandejaItems.length}</span>
          </div>
          <div style={{ display:'flex', flexDirection:'column', gap:8 }}>
            {bandejaItems.map(b => {
              const cat = seedA.catPorId[b.suggested || b.cat] || seedA.catPorId['super'];
              const CatChipDyn = window.CatChip;
              const fmt = n => window.S ? window.S.fmtMoney(Math.abs(n), ccy||'ARS') : ('$'+Math.abs(n));
              return (
                <div key={b.id} style={{ borderRadius:13, background:'rgba(255,255,255,0.03)', border:'1px solid var(--border)', overflow:'hidden' }}>
                  <div style={{ display:'flex', alignItems:'center', gap:10, padding:'10px 12px 8px' }}>
                    <div style={{ width:34, height:34, flexShrink:0 }}>
                      {CatChipDyn ? <CatChipDyn cat={cat} size={34} /> : <div style={{ width:34, height:34, borderRadius:9, background:'var(--accent-dim)' }} />}
                    </div>
                    <div style={{ flex:1, minWidth:0 }}>
                      <div style={{ fontSize:13, fontWeight:600, overflow:'hidden', textOverflow:'ellipsis', whiteSpace:'nowrap' }}>{b.merchant}</div>
                      <div style={{ fontSize:11, color:'var(--text-3)', marginTop:1 }}>{b.source} · {cat?.name}</div>
                    </div>
                    <div style={{ fontFamily:'Space Grotesk, sans-serif', fontSize:13.5, fontWeight:700, fontVariantNumeric:'tabular-nums', color:'#f87171', flexShrink:0 }}>-{fmt(b.amount)}</div>
                  </div>
                  <div style={{ display:'flex', gap:6, padding:'0 10px 10px' }}>
                    <button onClick={() => onConfirmBandeja && onConfirmBandeja(b.id)} style={{ flex:1, padding:'8px 0', borderRadius:9, background:'var(--accent)', color:'var(--on-accent)', border:'none', cursor:'pointer', fontFamily:'inherit', fontSize:12.5, fontWeight:700, display:'flex', alignItems:'center', justifyContent:'center', gap:5 }}>
                      <IconA name="check" size={12} strokeWidth={2.5} /> Confirmar
                    </button>
                    <button onClick={() => onDiscardBandeja && onDiscardBandeja(b.id)} style={{ width:34, height:34, borderRadius:9, background:'rgba(255,255,255,0.05)', border:'1px solid var(--border)', color:'var(--text-3)', cursor:'pointer', display:'flex', alignItems:'center', justifyContent:'center', flexShrink:0 }}>
                      <IconA name="x" size={13} />
                    </button>
                  </div>
                </div>
              );
            })}
          </div>
        </div>
      )}

      {totalCount === 0 && (
        <div style={{ flex:1, display:'flex', flexDirection:'column', alignItems:'center', justifyContent:'center', color:'var(--text-3)', gap:12, padding:'40px 0' }}>
          <div style={{ width:48, height:48, borderRadius:14, background:'var(--accent-dim)', display:'flex', alignItems:'center', justifyContent:'center', color:'var(--accent-bright)' }}>
            <IconA name="check" size={22} strokeWidth={1.8} />
          </div>
          <div style={{ fontSize:13.5, fontWeight:500, color:'var(--text-2)' }}>Todo al día</div>
          <div style={{ fontSize:12, color:'var(--text-3)', textAlign:'center' }}>Sin avisos ni transacciones pendientes</div>
        </div>
      )}
    </div>
  );

  if (isDesktop) {
    return (
      <>
        {open && <div className="notif-panel-overlay" onClick={onClose} />}
        <aside style={{
          position:'fixed', top:0, right:0, bottom:0, width:380,
          background:'rgba(8,5,15,0.98)', backdropFilter:'blur(40px) saturate(1.5)',
          borderLeft:'1px solid var(--border-strong)',
          zIndex:60, padding:'24px 20px',
          transform: open ? 'translateX(0)' : 'translateX(100%)',
          transition:'transform 0.28s cubic-bezier(0.2,0.8,0.2,1)',
          display:'flex', flexDirection:'column',
          boxShadow: open ? '-20px 0 60px rgba(0,0,0,0.5)' : 'none',
        }}>
          {inner}
        </aside>
      </>
    );
  }

  if (!open) return null;
  return (
    <div onClick={onClose} style={{ position:'fixed', inset:0, zIndex:50, display:'flex', alignItems:'flex-end', background:'rgba(5,3,13,0.60)', backdropFilter:'blur(8px)', animation:'fadeIn 0.18s' }}>
      <div onClick={e => e.stopPropagation()} style={{
        width:'100%', background:'rgba(12,7,24,0.97)', backdropFilter:'blur(32px) saturate(1.4)',
        border:'1px solid var(--border-strong)', borderBottom:'none',
        borderRadius:'24px 24px 0 0', padding:'16px 20px 44px',
        animation:'slideUp 0.28s cubic-bezier(0.2,0.8,0.2,1)',
        maxHeight:'80dvh', display:'flex', flexDirection:'column',
      }}>
        <div style={{ width:36, height:4, background:'rgba(255,255,255,0.15)', borderRadius:999, margin:'0 auto 16px' }} />
        {inner}
      </div>
    </div>
  );
}



// ── CloseMonthModal ──────────────────────────────────────────────────────────
function CloseMonthModal({ open, onClose, spentState, budgets, movimientos, onToast }) {
  const [cerrando, setCerrando] = useS(false);
  if (!open) return null;
  const seed = window.SisyphusSeed;
  const S    = window.S;
  const stats = window.MonthClose?.buildStats?.() || null;
  const monthNext = new Date(Date.now() + 32*86400000).toLocaleDateString('es-AR',{month:'long'});
  const monthCur  = new Date().toLocaleDateString('es-AR',{month:'long', year:'numeric'});
  const movCount  = (movimientos||[]).length;
  const fmt = n => S ? S.fmtMoney(n, 'ARS') : ('$'+n);

  async function handleClose() {
    setCerrando(true);
    try {
      window.MonthClose?.cerrarMes?.(() => {});
      if (onToast) onToast(`Mes cerrado · empezamos ${monthNext} en limpio ✓`);
      onClose();
    } catch(e) { console.error(e); if (onToast) onToast('Error al cerrar el mes'); }
    finally { setCerrando(false); }
  }

  return (
    <div onClick={onClose} style={{ position:'fixed', inset:0, zIndex:900, background:'rgba(5,3,13,0.75)', backdropFilter:'blur(14px)', display:'flex', alignItems:'center', justifyContent:'center', padding:20, animation:'fadeIn 0.15s' }}>
      <div onClick={e=>e.stopPropagation()} style={{ width:'100%', maxWidth:500, background:'rgba(12,7,24,0.98)', border:'1px solid var(--border-strong)', borderRadius:22, overflow:'hidden', boxShadow:'0 40px 80px rgba(0,0,0,0.7)', animation:'popIn 0.22s cubic-bezier(0.2,0.8,0.2,1)' }}>
        {/* Header */}
        <div style={{ padding:'24px 24px 20px', display:'flex', alignItems:'flex-start', gap:16 }}>
          <div style={{ width:44, height:44, borderRadius:13, background:'var(--accent-dim)', border:'1px solid var(--border-strong)', display:'flex', alignItems:'center', justifyContent:'center', color:'var(--accent-bright)', flexShrink:0 }}>
            <IconA name="archive" size={20} strokeWidth={1.8} />
          </div>
          <div style={{ flex:1 }}>
            <div style={{ fontFamily:'Space Grotesk, sans-serif', fontSize:20, fontWeight:700, letterSpacing:'-0.02em' }}>Cerrar {monthCur}</div>
            <div style={{ fontSize:12.5, color:'var(--text-3)', marginTop:3 }}>Archivar el mes y arrancar {monthNext}</div>
          </div>
          <button onClick={onClose} style={{ width:28, height:28, borderRadius:8, background:'rgba(255,255,255,0.06)', border:'none', color:'var(--text-3)', cursor:'pointer', display:'flex', alignItems:'center', justifyContent:'center' }}>
            <IconA name="x" size={13} />
          </button>
        </div>

        {/* Description */}
        <div style={{ padding:'0 24px 16px', fontSize:13.5, color:'var(--text-2)', lineHeight:1.6 }}>
          Vamos a archivar los datos del mes. Las categorías se resetean a cero, pero los ítems base (sueldo, alquiler, préstamos, ahorros) se mantienen. Te enviamos el resumen por mail.
        </div>

        {/* Stats table */}
        {stats && (
          <div style={{ margin:'0 24px 20px', borderRadius:13, border:'1px solid var(--border)', overflow:'hidden' }}>
            {[['Ingresos', fmt(stats.ingTotal)], ['Egresos', fmt(stats.egrTotal)], ['Ahorro neto', fmt(stats.ahoTotal)], ['Movimientos', String(movCount)]].map(([l, v], i) => (
              <div key={l} style={{ display:'flex', justifyContent:'space-between', alignItems:'center', padding:'12px 16px', borderTop: i===0?'none':'1px solid var(--border)' }}>
                <span style={{ fontSize:13.5, color:'var(--text-2)' }}>{l}</span>
                <span style={{ fontFamily:'Space Grotesk, sans-serif', fontSize:14, fontWeight:600, fontVariantNumeric:'tabular-nums' }}>{v}</span>
              </div>
            ))}
          </div>
        )}

        {/* Actions */}
        <div style={{ padding:'0 24px 24px', display:'flex', gap:10 }}>
          <button onClick={onClose} style={{ flex:1, padding:'12px 0', borderRadius:11, background:'rgba(255,255,255,0.05)', border:'1px solid var(--border)', color:'var(--text-2)', cursor:'pointer', fontFamily:'inherit', fontSize:14, fontWeight:600, transition:'all 0.14s' }}>Cancelar</button>
          <button onClick={handleClose} disabled={cerrando} style={{ flex:2, padding:'12px 0', borderRadius:11, background:'var(--accent)', color:'var(--on-accent)', border:'none', cursor:cerrando?'not-allowed':'pointer', fontFamily:'inherit', fontSize:14, fontWeight:600, transition:'all 0.14s', display:'flex', alignItems:'center', justifyContent:'center', gap:7 }}>
            <IconA name="check" size={14} strokeWidth={2.5} />
            {cerrando ? 'Cerrando…' : `Cerrar mes y empezar ${monthNext}`}
          </button>
        </div>
      </div>
    </div>
  );
}



// ─── Mobile Shell Components ──────────────────────────────────────────────────
function Header({ onNotif, notifCount, onConfig, profilePhoto, onCalc }) {
  return (
    <div style={{ flexShrink:0, paddingTop:'calc(10px + env(safe-area-inset-top, 44px))', paddingBottom:12, paddingLeft:18, paddingRight:18, background:'linear-gradient(180deg, rgba(8,5,15,0.95) 70%, rgba(8,5,15,0) 100%)', backdropFilter:'blur(12px)', display:'flex', alignItems:'center', justifyContent:'space-between' }}>
      <div style={{ display:'flex', alignItems:'center', gap:9 }}>
        <SisyphusLogoMark size={26} />
        <div style={{ fontFamily:'Space Grotesk, sans-serif', fontSize:19, fontWeight:700, letterSpacing:'-0.02em' }}>Sisyphus<span style={{ color:'var(--accent)' }}>.</span></div>
      </div>
      <div style={{ display:'flex', alignItems:'center', gap:8 }}>
        <button onClick={onCalc} title="Calculadora" style={{ width:36, height:36, borderRadius:'50%', background:'var(--card)', border:'1px solid var(--border)', color:'var(--text-2)', cursor:'pointer', display:'flex', alignItems:'center', justifyContent:'center' }}>
          <IconA name="dollar" size={15} />
        </button>
        <button onClick={onNotif} style={{ width:36, height:36, borderRadius:'50%', background:'var(--card)', border:'1px solid var(--border)', color:'var(--text-2)', cursor:'pointer', position:'relative', display:'flex', alignItems:'center', justifyContent:'center' }}>
          <IconA name="bell" size={15} />
          {notifCount > 0 && <span style={{ position:'absolute', top:6, right:7, width:7, height:7, borderRadius:'50%', background:'var(--accent)', boxShadow:'0 0 8px var(--accent-glow)' }} />}
        </button>
        <button onClick={onConfig} style={{ width:36, height:36, borderRadius:'50%', background: profilePhoto ? 'transparent' : 'linear-gradient(145deg, var(--accent-bright), var(--accent-deep))', color:'var(--on-accent)', fontWeight:700, fontSize:13, display:'flex', alignItems:'center', justifyContent:'center', cursor:'pointer', border:'none', overflow:'hidden', padding:0 }}>
          {profilePhoto ? <img src={profilePhoto} style={{ width:'100%', height:'100%', objectFit:'cover' }} alt="perfil" /> : 'A'}
        </button>
      </div>
    </div>
  );
}

function BottomNav({ tab, onTab, onAdd }) {
  const items = [
    { id:'home',        label:'Inicio',   icon:'home' },
    { id:'movimientos', label:'Movs',     icon:'cards' },
    { id:'_fab',        label:'',         icon:'plus' },
    { id:'pagos',       label:'Pagos',    icon:'calendar' },
    { id:'mas',         label:'Saldos',   icon:'wallet' },
  ];
  return (
    <div style={{ flexShrink:0, padding:'10px 14px 0', paddingBottom:'env(safe-area-inset-bottom, 0px)', background:'linear-gradient(180deg, rgba(8,5,15,0) 0%, rgba(8,5,15,0.88) 35%, rgba(8,5,15,0.98) 100%)', zIndex:30 }}>
      <div style={{ display:'flex', alignItems:'center', justifyContent:'space-around', background:'rgba(20,12,40,0.88)', backdropFilter:'blur(20px) saturate(1.4)', border:'1px solid var(--border-strong)', borderRadius:22, padding:'8px 6px', boxShadow:'0 16px 40px -10px rgba(0,0,0,0.6)' }}>
        {items.map(it => {
          if (it.id === '_fab') return (
            <button key="fab" onClick={onAdd} style={{ width:52, height:52, borderRadius:'50%', background:'linear-gradient(145deg, var(--accent-bright), var(--accent-deep))', color:'var(--on-accent)', border:'none', cursor:'pointer', display:'flex', alignItems:'center', justifyContent:'center', marginTop:-20, boxShadow:'0 8px 24px rgba(139,92,246,0.5), 0 0 0 4px rgba(20,12,40,0.88)' }}>
              <IconA name="plus" size={22} strokeWidth={2.4} />
            </button>
          );
          const active = tab === it.id;
          return (
            <button key={it.id} onClick={() => onTab(it.id)} style={{ flex:1, background:'none', border:'none', cursor:'pointer', padding:'8px 0', display:'flex', flexDirection:'column', alignItems:'center', gap:3, color: active ? 'var(--accent-bright)' : 'var(--text-3)', fontFamily:'inherit', fontSize:10, fontWeight:600, transition:'color 0.15s' }}>
              <IconA name={it.icon} size={20} strokeWidth={active ? 2.2 : 1.8} />
              <span style={{ letterSpacing:'0.02em' }}>{it.label}</span>
            </button>
          );
        })}
      </div>
    </div>
  );
}

// Fallback desktop sidebar if desktop.jsx not loaded
function Sidebar({ tab, onTab, onAdd, onNotif, notifCount, profilePhoto, userName }) {
  const items = [
    { id:'home', label:'Resumen', icon:'home' }, { id:'movimientos', label:'Movimientos', icon:'cards' },
    { id:'pagos', label:'Pagos', icon:'calendar' }, { id:'analisis', label:'Análisis', icon:'pie' },
    { id:'cuentas', label:'Cuentas', icon:'wallet' }, { id:'cierre', label:'Cierre', icon:'archive' },
  ];
  return (
    <aside style={{ width:220, flexShrink:0, height:'100dvh', borderRight:'1px solid var(--border)', padding:'16px 10px', display:'flex', flexDirection:'column', gap:2, background:'rgba(8,5,15,0.98)', position:'sticky', top:0 }}>
      <div style={{ display:'flex', alignItems:'center', gap:9, padding:'4px 8px 16px' }}>
        <SisyphusLogoMark size={26} />
        <span style={{ fontFamily:'Space Grotesk, sans-serif', fontSize:17, fontWeight:700 }}>Sisyphus<span style={{ color:'var(--accent)' }}>.</span></span>
      </div>
      {items.map(it => {
        const active = tab === it.id;
        return (
          <button key={it.id} onClick={() => onTab(it.id)} style={{ display:'flex', alignItems:'center', gap:10, padding:'9px 12px', borderRadius:10, background: active?'var(--accent-dim)':'transparent', color: active?'var(--accent-bright)':'var(--text-2)', border:`1px solid ${active?'var(--border-strong)':'transparent'}`, fontFamily:'inherit', fontSize:13, fontWeight: active?600:500, cursor:'pointer', width:'100%', transition:'all 0.13s' }}>
            <IconA name={it.icon} size={15} strokeWidth={active?2.2:1.8} />{it.label}
          </button>
        );
      })}
      <div style={{ flex:1 }} />
      <button onClick={() => onTab('config')} style={{ display:'flex', alignItems:'center', gap:9, padding:'9px 12px', borderRadius:10, background:'transparent', border:'none', cursor:'pointer', fontFamily:'inherit', width:'100%' }}>
        <div style={{ width:28, height:28, borderRadius:'50%', background:'linear-gradient(145deg, var(--accent-bright), var(--accent-deep))', color:'var(--on-accent)', fontWeight:700, fontSize:12, display:'flex', alignItems:'center', justifyContent:'center', flexShrink:0 }}>
          {(userName||'A').charAt(0).toUpperCase()}
        </div>
        <span style={{ fontSize:12, fontWeight:500, color:'var(--text-2)' }}>{userName||'Adolfo'}</span>
      </button>
    </aside>
  );
}

// Fallback desktop topbar
function DesktopTopBar({ tab, onNotif, ccy, onCcy, notifCount }) {
  const titles = { home:'Resumen', movimientos:'Movimientos', pagos:'Pagos', analisis:'Análisis', cuentas:'Cuentas', cierre:'Cierre de mes', config:'Configuración' };
  return (
    <div style={{ height:56, flexShrink:0, display:'flex', alignItems:'center', justifyContent:'space-between', padding:'0 24px', borderBottom:'1px solid var(--border)', background:'rgba(8,5,15,0.92)', backdropFilter:'blur(20px)' }}>
      <div style={{ fontFamily:'Space Grotesk, sans-serif', fontSize:17, fontWeight:600 }}>{titles[tab]||'Sisyphus'}</div>
      <div style={{ display:'flex', gap:8 }}>
        <button onClick={onCcy} style={{ padding:'6px 12px', borderRadius:9, background:'rgba(255,255,255,0.04)', border:'1px solid var(--border)', fontSize:12, fontWeight:600, color:'var(--text-2)', cursor:'pointer', fontFamily:'inherit' }}>{ccy}</button>
        <button onClick={() => window.SisExport?.csv?.()} title="Exportar CSV"
          style={{ width:32, height:32, borderRadius:9, background:'rgba(255,255,255,0.04)', border:'1px solid var(--border)', color:'var(--text-2)', cursor:'pointer', display:'flex', alignItems:'center', justifyContent:'center' }}>
          <IconA name="arrowDown" size={14} />
        </button>
        <button onClick={onNotif} style={{ width:32, height:32, borderRadius:9, background:'rgba(255,255,255,0.04)', border:'1px solid var(--border)', color:'var(--text-2)', cursor:'pointer', display:'flex', alignItems:'center', justifyContent:'center', position:'relative' }}>
          <IconA name="bell" size={14} />
          {notifCount > 0 && <span style={{ position:'absolute', top:6, right:7, width:6, height:6, borderRadius:'50%', background:'var(--accent)' }} />}
        </button>
      </div>
    </div>
  );
}


// ─── App ──────────────────────────────────────────────────────────────────────
function App() {
  // ── Window aliases for cross-file components ─────────────────────────────
  const HomeScreen    = window.HomeScreen;
  const MovScreen     = window.MovScreen;
  const PagosScreen   = window.PagosScreen;
  const AnalisisScreen = window.AnalisisScreen;
  const MasScreen     = window.MasScreen;
  const ConfigScreen       = window.ConfigScreen;
  const OnboardingWizard   = window.OnboardingWizard;

  const isDesktop = useIsDesktop();
  const [tab, setTab] = useS('home');
  const [addOpen, setAddOpen] = useS(false);
  const [notifOpen, setNotifOpen] = useS(false);
  const [calcOpen,  setCalcOpen]  = useS(false);
  const [settOpen,  setSettOpen]  = useS(false);
  const [closeMonthOpen, setCloseMonthOpen] = useS(false);

  // ── Pull-to-refresh ────────────────────────────────────────────────────────
  const [pullY,        setPullY]        = useS(0);
  const [isRefreshing, setIsRefreshing] = useS(false);
  const pullRef   = useR({ startY: 0, pulling: false });
  const scrollRef = useR(null);
  const PULL_THRESHOLD = 64;

  function onTouchStart(e) {
    if (scrollRef.current?.scrollTop > 0) return;
    pullRef.current.startY  = e.touches[0].clientY;
    pullRef.current.pulling = true;
  }
  function onTouchMove(e) {
    if (!pullRef.current.pulling) return;
    if (scrollRef.current?.scrollTop > 0) { pullRef.current.pulling = false; return; }
    const dy = e.touches[0].clientY - pullRef.current.startY;
    if (dy > 0) setPullY(Math.min(dy * 0.45, PULL_THRESHOLD + 20));
  }
  async function onTouchEnd() {
    if (!pullRef.current.pulling) return;
    pullRef.current.pulling = false;
    if (pullY >= PULL_THRESHOLD) {
      setIsRefreshing(true); setPullY(0);
      try { await window.SisyStorage?.cloudPull?.(); } catch {}
      setTimeout(() => setIsRefreshing(false), 700);
    } else {
      setPullY(0);
    }
  }
  const selectedMonthIdx = 0; // always current month — no month selector
  const [convOpen, setConvOpen] = useS(false);
  const [convPos,  setConvPos]  = useS({ top:70, right:16 });
  const convPanelRef = useR(null);
  const [toast, setToast] = useS(null);

  function toggleConv() {
    const btn = document.getElementById('conv-trigger-btn');
    if (btn) {
      const r = btn.getBoundingClientRect();
      setConvPos({ top: r.bottom + 10, right: window.innerWidth - r.right });
    }
    setConvOpen(o => !o);
  }
  // Close conversor on outside click
  useE(() => {
    if (!convOpen) return;
    const fn = e => {
      const btn = document.getElementById('conv-trigger-btn');
      const inBtn   = btn?.contains(e.target);
      const inPanel = convPanelRef.current?.contains(e.target);
      if (!inBtn && !inPanel) setConvOpen(false);
    };
    document.addEventListener('mousedown', fn);
    return () => document.removeEventListener('mousedown', fn);
  }, [convOpen]);

  // ── Persistent storage (survives page reload) ──────────────────────
  const SS = window.SisyStorage;

  // Simple persisted values
  const [ccy,          setCcyRaw]      = SS.useStorage('ccy',          'ARS');
  const [profilePhoto, setProfilePhoto] = SS.useStorage('profile_photo', null);
  const [userName,     setUserName]     = SS.useStorage('user_name',    'Adolfo');
  const [photoPos,     setPhotoPos]     = SS.useStorage('photo_pos',    { x:50, y:50 });
  const [budgets,      setBudgets]      = SS.useStorage('budgets',      () => Object.fromEntries(seedA.categories.map(c => [c.id, c.budget])));
  const [spentState,   setSpentState]   = SS.useStorage('spent_state',  () => ({ ...seedA.spent }));
  const [extraIngresos,setExtraIngresos]= SS.useStorage('extra_ing',    0);
  const [sueldo,       setSueldo]       = SS.useStorage('sueldo',       0);
  const [sueldoDia,    setSueldoDia]    = SS.useStorage('sueldo_dia',   25);
  const [confirmedIngs, setConfirmedIngs] = SS.useStorage('confirmed_ings', {});
  function confirmIngreso(id) {
    setConfirmedIngs(prev => ({ ...prev, [id]: true }));
    showToast('Ingreso confirmado ✓');
  }
  const [dynamicDeudores,setDynamicDeudores] = SS.useStorage('dyn_deudores', []);
  const [dynamicPagos,   setDynamicPagos]    = SS.useStorage('dyn_pagos',    []);
  const [metas,          setMetas]           = SS.useStorage('metas',        []);

  // Cuentas — merge persisted with seed on first load (new seed accounts added automatically)
  const [cuentasState, setCuentasState] = SS.useStorage('cuentas_state', () => seedA.cuentas.map(c => ({ ...c })));

  // Tarjetas de crédito — inicializar desde seed defaults
  const [tarjetasState, setTarjetasState] = SS.useStorage('tarjetas_state', () => seedA.tarjetas.map(t => ({ ...t })));
  // Merge seed defaults into persisted tarjetas (adds 'limite' if missing from old data)
  useE(() => {
    setTarjetasState(prev => {
      const byId = Object.fromEntries(seedA.tarjetas.map(t=>[t.id,t]));
      return prev.map(t => ({ limite: 0, ...byId[t.id], ...t, limite: t.limite ?? byId[t.id]?.limite ?? 0 }));
    });
  // eslint-disable-next-line react-hooks/exhaustive-deps
  }, []);

  // Onboarding — shown on first visit
  const [onboarded, setOnboarded] = SS.useStorage('onboarded', false);

  // Grupos de cuotas — registros de planes de financiamiento
  const [cuotasGrupos, setCuotasGrupos] = SS.useStorage('cuotas_grupos', []);

  // Sets — stored as arrays, exposed as Sets
  const [paidPagosArr,         setPaidPagosArr]         = SS.useStorage('paid_pagos',          []);
  const [collectedDeudoresArr, setCollectedDeudoresArr] = SS.useStorage('collected_deudores',  []);
  const paidPagos         = React.useMemo(() => new Set(paidPagosArr),         [paidPagosArr]);
  const collectedDeudores = React.useMemo(() => new Set(collectedDeudoresArr), [collectedDeudoresArr]);

  // Notifications — initialize from seed but never re-seed after first visit
  const [activeNotifs, setActiveNotifs] = SS.useStorage('active_notifs', () => [...seedA.notifs]);

  // Bandeja — initialize from seed
  const [pendingBandeja, setPendingBandeja] = SS.useStorage('pending_bandeja', () => [...seedA.bandeja]);

  // Movimientos — dates stored as ISO strings, rehydrated to Date on load
  const [movimientosRaw, setMovimientosRaw] = SS.useStorage('movimientos', null);
  const movimientos = React.useMemo(() => {
    if (!movimientosRaw) return makeInitialMovimientos();
    return movimientosRaw.map(m => ({ ...m, date: new Date(m.date) }));
  }, [movimientosRaw]);

  // Total income = salary + additional (used everywhere for budget calculations)
  const totalIngresos = (sueldo || 0) + (extraIngresos || 0);

  function setCcy(updater) {
    setCcyRaw(typeof updater === 'function' ? updater(ccy) : updater);
  }

  function go(t) { setTab(t); }
  function toggleCcy() { setCcy(c => c === 'ARS' ? 'USD' : 'ARS'); }

  function addMovimiento(mov) {
    // Auto-detectar tarjeta si no se especificó explícitamente
    let finalTarjetaId = mov.tarjetaId !== undefined ? mov.tarjetaId : null;
    if (!finalTarjetaId && window.TarjetasEngine) {
      const detectedCard = window.TarjetasEngine.detectarTarjeta(tarjetasState, mov);
      if (detectedCard) finalTarjetaId = detectedCard.id;
    }
    // Acumular en tarjeta si corresponde
    if (finalTarjetaId && window.TarjetasEngine && (mov.kind === 'variable' || mov.kind === 'fijo' || !mov.kind)) {
      setTarjetasState(prev => window.TarjetasEngine.acumular(prev, finalTarjetaId, Math.abs(mov.amount || 0)));
    }
    // Store dates as ISO strings for JSON serialization
    const serialized = { ...mov, tarjetaId: finalTarjetaId, id: mov.id || ('dyn-' + Date.now()), date: (mov.date instanceof Date ? mov.date : new Date(mov.date)).toISOString() };
    setMovimientosRaw(ms => {
      const base = ms || makeInitialMovimientos().map(m => ({ ...m, date: m.date.toISOString() }));
      return [serialized, ...base];
    });
  }
  function togglePaid(id) {
    const wasPaid = paidPagos.has(id);
    setPaidPagosArr(arr => {
      const s = new Set(arr);
      s.has(id) ? s.delete(id) : s.add(id);
      return Array.from(s);
    });
    const pago = seedA.pagos.find(p => p.id === id) || dynamicPagos.find(p => p.id === id);
    if (pago) {
      if (!wasPaid) {
        addMovimiento({ cat: pago.cat, desc: pago.name + ' · pagado', amount: -pago.amount, date: new Date(), kind: 'fijo', account: 'BBVA' });
        addExpense(pago.cat, pago.amount);
        // Si es un resumen de tarjeta CC, resetear el acumulado
        if (pago.tipoPago === 'cierre_cc' && pago.tarjetaId && window.TarjetasEngine) {
          setTarjetasState(prev => window.TarjetasEngine.resetAcumulado(prev, pago.tarjetaId));
        }
      } else {
        setSpentState(s => ({ ...s, [pago.cat]: Math.max(0, (s[pago.cat] || 0) - pago.amount) }));
        // Revertir reset de acumulado no es posible — solo informativo
      }
    }
  }
  function markCollected(id) {
    setCollectedDeudoresArr(arr => Array.from(new Set([...arr, id])));
    const deudor = (seedA.deudores || []).find(d => d.id === id) || dynamicDeudores.find(d => d.id === id);
    if (deudor) {
      const desc = deudor.isSplit
        ? `Split cobrado · ${deudor.name}${deudor.splitOf ? ' (' + deudor.splitOf + ')' : ''}`
        : `Cobro recibido · ${deudor.name}`;
      // Register as income movement — shows in Movimientos + Home balance
      addMovimiento({
        cat:     'ingreso',
        desc,
        amount:  deudor.amount,
        date:    new Date().toISOString(),
        kind:    'ingreso',
        source:  'cobro',
      });
    }
    showToast(`💸 Cobro registrado en Movimientos`);
  }
  function confirmBandeja(id, confirmedCat) {
    const item = pendingBandeja.find(x => x.id === id);
    setPendingBandeja(p => p.filter(x => x.id !== id));
    if (item) {
      const finalCat = confirmedCat || item.suggested || item.cat || 'super';
      const absAmt   = Math.abs(item.amount);
      addMovimiento({
        cat:     finalCat,
        desc:    item.merchant || item.desc || item.name || 'Movimiento',
        amount:  -absAmt,
        date:    item.date || new Date(),
        kind:    'variable',
        account: item.source || item.account || 'BBVA',
      });
      addExpense(finalCat, absAmt);
    }
    showToast('Movimiento confirmado');
  }
  function discardBandeja(id) {
    setPendingBandeja(p => p.filter(x => x.id !== id));
  }
  function dismissNotif(id) {
    setActiveNotifs(n => n.filter(x => x.id !== id));
  }
  function deleteMovimiento(id) {
    setMovimientosRaw(ms => (ms || []).filter(m => m.id !== id));
    showToast('Movimiento eliminado');
  }
  function deleteDeudor(id) {
    setDynamicDeudores(prev => prev.filter(d => d.id !== id));
    showToast('Deudor eliminado');
  }
  function deletePago(id) {
    setDynamicPagos(prev => prev.filter(p => p.id !== id));
    showToast('Pago eliminado');
  }
  function addExpense(cat, amount) {
    setSpentState(s => ({ ...s, [cat]: (s[cat] || 0) + Math.abs(amount) }));
  }
  function updateMovimiento(id, changes) {
    const old = movimientos.find(m => m.id === id);
    if (old && old.kind !== 'ingreso') {
      // Adjust spentState: undo old, apply new
      const oldCat = old.cat, oldAmt = Math.abs(old.amount);
      const newCat = changes.cat || old.cat;
      const newAmt = changes.amount !== undefined ? Math.abs(changes.amount) : oldAmt;
      const newKind = changes.kind || old.kind;
      if (newKind !== 'ingreso') {
        setSpentState(s => {
          const next = { ...s };
          next[oldCat] = Math.max(0, (next[oldCat] || 0) - oldAmt);
          next[newCat] = (next[newCat] || 0) + newAmt;
          return next;
        });
      } else {
        // Changed to ingreso — just remove old expense
        setSpentState(s => ({ ...s, [oldCat]: Math.max(0, (s[oldCat] || 0) - oldAmt) }));
      }
    }
    setMovimientosRaw(ms => ms.map(m => m.id === id
      ? { ...m, ...changes, amount: changes.amount !== undefined ? changes.amount : m.amount, date: changes.date ? (typeof changes.date === 'string' ? changes.date : new Date(changes.date).toISOString()) : m.date }
      : m
    ));
    showToast('Movimiento actualizado ✓');
  }
  function updateCuenta(cuentaOrId, newBalance) {
    if (typeof cuentaOrId === 'object') {
      // full object update (name + balance + any other field)
      setCuentasState(cs => cs.map(c => c.id === cuentaOrId.id ? { ...c, ...cuentaOrId } : c));
    } else {
      // legacy: (id, balance) tuple
      setCuentasState(cs => cs.map(c => c.id === cuentaOrId ? { ...c, balance: newBalance } : c));
    }
  }
  function addCuenta(cuenta) {
    setCuentasState(cs => [...cs, { ...cuenta, id: 'acc-' + Date.now() }]);
  }
  function deleteCuenta(id) {
    setCuentasState(cs => cs.filter(c => c.id !== id));
  }

  // ── Tarjetas CC ────────────────────────────────────────────────────
  function addTarjeta(t) {
    setTarjetasState(prev => [...prev, { ...t, id: 'tc-' + Date.now(), acumulado: 0, limite: t.limite || 0, active: true }]);
  }
  function updateTarjeta(id, changes) {
    setTarjetasState(prev => prev.map(t => t.id === id ? { ...t, ...changes } : t));
  }
  function deleteTarjeta(id) {
    setTarjetasState(prev => prev.filter(t => t.id !== id));
  }

  // ── Cuotas ────────────────────────────────────────────────────────
  function commitCuotaGrupo(grupo) {
    const g = { ...grupo, id: 'cg-' + Date.now(), createdAt: new Date().toISOString() };
    setCuotasGrupos(prev => [g, ...prev]);
    if (window.CuotasEngine) {
      const movs = window.CuotasEngine.generarMovimientos(g);
      const hoy  = new Date();
      const montoCuota = Math.round(g.montoTotal / g.totalCuotas);

      movs.forEach((m, idx) => {
        addMovimiento({ ...m, kind: 'fijo' });
        const d = new Date(m.date + 'T12:00:00');
        // Impact spent only for the current month
        if (d.getFullYear() === hoy.getFullYear() && d.getMonth() === hoy.getMonth()) {
          addExpense(m.cat, Math.abs(m.amount));
        }
        // ── Add a "pago" entry for each installment so it shows in Pagos ──
        // due date = first day of that month (approximation; same as movimiento date)
        const dueStr = m.date; // 'YYYY-MM-DD'
        const pagoId = `cuota-${g.id}-${idx+1}`;
        const pagoEntry = {
          id:        pagoId,
          name:      `${g.descripcion} · ${idx+1}/${g.totalCuotas}`,
          amount:    montoCuota,
          due:       dueStr,
          cat:       g.catId || 'compras',
          type:      `Cuota ${idx+1}/${g.totalCuotas}`,
          tipoPago:  'cuota',
          cuotaGrupoId: g.id,
          tarjetaId: g.tarjetaId || null,
        };
        setDynamicPagos(prev => [...prev.filter(p => p.id !== pagoId), pagoEntry]);
      });
      showToast(`${g.totalCuotas} cuotas de $${new Intl.NumberFormat('es-AR',{minimumFractionDigits:0,maximumFractionDigits:0}).format(montoCuota)} registradas`);
    }
  }

  function commitAdd(p) {
    const cap = s => s ? s.charAt(0).toUpperCase() + s.slice(1) : s;
    const desc = cap(p.merchant || p.cat || 'Gasto');
    const cat  = p.cat || 'super'; // fallback only when storing
    addExpense(cat, Math.abs(p.amount));
    addMovimiento({ cat, desc, amount: -Math.abs(p.amount), date: new Date(), kind: 'variable', account: 'MP', tarjetaId: p.tarjetaId || null });
    showToast('Gasto registrado · ' + window.S.fmtMoney(Math.abs(p.amount), ccy));
  }
  function completeOnboarding({ userName: uName, sueldo, sueldoCcy, freelance, freelanceCcy, tarjetas: ob_tarjetas, photo }) {
    if (uName) setUserName(uName);
    if (photo) setProfilePhoto(photo);
    const fxBlue = seedA.fxBlue || 1480;
    const toARS  = (n, c) => c === 'USD' ? Math.round(n * fxBlue) : n;
    if (sueldo > 0) {
      setSueldo(toARS(sueldo, sueldoCcy));
      if (freelance > 0) setExtraIngresos(toARS(freelance, freelanceCcy));
    }
    if (ob_tarjetas && ob_tarjetas.length > 0) {
      setTarjetasState(ob_tarjetas);
    }
    setOnboarded(true);
  }

  const _lastDebtRef = React.useRef(null);
  function commitPago({ nombre, monto, cat, venc }) {
    const id = 'dyn-pag-' + Date.now() + '-' + Math.random().toString(36).slice(2,6);
    setDynamicPagos(prev => [{ id, name: nombre, amount: monto, due: venc, cat: cat || 'servicios' }, ...prev]);
    showToast('Pago registrado · ' + window.S.fmtMoney(monto, ccy));
  }

  function addMovRaw({ desc, amount, kind, cat, date, tarjetaId, dueDate }) {
    if (kind !== 'ingreso') addExpense(cat || 'super', Math.abs(amount));
    addMovimiento({ cat: cat || 'super', desc, amount: kind === 'ingreso' ? Math.abs(amount) : -Math.abs(amount), date: date || new Date(), kind: kind || 'variable', tarjetaId: tarjetaId || null });
    // Si tiene fecha límite, registrar también como pago programado en dynamicPagos
    if (dueDate && kind !== 'ingreso') {
      const pagoId = 'dyn-pag-' + Date.now() + '-' + Math.random().toString(36).slice(2,5);
      setDynamicPagos(prev => [{ id: pagoId, name: desc, amount: Math.abs(amount), due: dueDate, cat: cat || 'super' }, ...prev]);
    }
    showToast((kind === 'ingreso' ? 'Ingreso' : 'Gasto') + ' registrado · ' + window.S.fmtMoney(Math.abs(amount), ccy));
  }

  function commitDebt(d) {
    // Prevent double-submit (e.g. double-tap)
    const sig = `${d.tipo}-${d.nombre}-${d.monto}`;
    if (_lastDebtRef.current === sig) return;
    _lastDebtRef.current = sig;
    setTimeout(() => { _lastDebtRef.current = null; }, 1500);
    const id = (d.tipo === 'me_deben' ? 'dyn-deu-' : 'dyn-pag-') + Date.now() + '-' + Math.random().toString(36).slice(2,6);
    if (d.tipo === 'me_deben') {
      // Store with split context so DeudorCard can show it clearly
      setDynamicDeudores(prev => [{
        id,
        name:    d.nombre,
        amount:  d.monto,
        due:     d.venc,
        note:    d.nota || 'Cobro pendiente',
        isSplit: !!d.isSplit,
        splitOf: d.splitOf || null,   // description of original expense
      }, ...prev]);
      showToast(`✓ ${d.nombre} registrado · ${window.S.fmtMoney(d.monto, ccy)}`);
    } else {
      setDynamicPagos(prev => [{ id, name: d.nombre, amount: d.monto, due: d.venc, cat: 'regalos', type: 'Deuda personal', note: d.nota || '' }, ...prev]);
      showToast('Deuda registrada · ' + window.S.fmtMoney(d.monto, ccy));
    }
  }
  function showToast(label) {
    setToast({ label });
    setTimeout(() => setToast(null), 2800);
  }

  // ── Metas CRUD ────────────────────────────────────────────────────────────
  function addMeta(meta) {
    const id = 'meta-' + Date.now() + '-' + Math.random().toString(36).slice(2,5);
    setMetas(prev => [...prev, { ...meta, id, createdAt: new Date().toISOString() }]);
  }
  function updateMeta(id, changes) {
    setMetas(prev => prev.map(m => m.id === id ? { ...m, ...changes } : m));
  }
  function deleteMeta(id) {
    setMetas(prev => prev.filter(m => m.id !== id));
  }

  // ── Export helpers (exposed globally so ConfigScreen can call them) ────────
  useE(() => {
    window.SisExport = {
      csv: () => {
        const allMovs = movimientos || [];
        const catPorId = seedA?.catPorId || {};
        const header = 'Fecha,Descripción,Categoría,Tipo,Monto';
        const rows = allMovs.map(m => {
          const cat = catPorId[m.cat]?.name || m.cat || '';
          const date = (m.date||'').slice(0,10);
          return `${date},"${(m.desc||'').replace(/"/g,'""')}","${cat}",${m.kind||''},${m.amount||0}`;
        });
        const csv = [header, ...rows].join('\n');
        const blob = new Blob(['﻿' + csv], { type:'text/csv;charset=utf-8;' });
        const a = document.createElement('a');
        a.href = URL.createObjectURL(blob);
        a.download = `sisyphus-movimientos-${new Date().toISOString().slice(0,10)}.csv`;
        a.click();
      },
      backup: () => {
        const NS = 'sisyphus.v1.';
        const data = { _exportedAt: new Date().toISOString(), _version: '1.0' };
        for (let i = 0; i < localStorage.length; i++) {
          const k = localStorage.key(i);
          if (k && (k.startsWith(NS) || k.startsWith('sis.'))) {
            try { data[k] = JSON.parse(localStorage.getItem(k)); } catch { data[k] = localStorage.getItem(k); }
          }
        }
        const blob = new Blob([JSON.stringify(data, null, 2)], { type:'application/json' });
        const a = document.createElement('a');
        a.href = URL.createObjectURL(blob);
        a.download = `sisyphus-backup-${new Date().toISOString().slice(0,10)}.json`;
        a.click();
      },
      restore: (file, onDone) => {
        const reader = new FileReader();
        reader.onload = e => {
          try {
            const data = JSON.parse(e.target.result);
            Object.entries(data).forEach(([k, v]) => {
              if (k.startsWith('sisyphus.v1.') || k.startsWith('sis.')) {
                localStorage.setItem(k, typeof v === 'string' ? v : JSON.stringify(v));
              }
            });
            if (onDone) onDone(null);
            setTimeout(() => window.location.reload(), 600);
          } catch(err) { if (onDone) onDone(err); }
        };
        reader.readAsText(file);
      },
    };
  }, [movimientos]);

  // ── Notificaciones — check upcoming pagos on app load ────────────────────
  useE(() => {
    if (window.SisNotif?.getPermission() !== 'granted') return;
    const allPagos = [...(seedA.pagos || []), ...dynamicPagos];
    const paid = paidPagos;
    const today = new Date(); today.setHours(0,0,0,0);
    allPagos.forEach(p => {
      if (paid.has(p.id) || !p.due) return;
      const due = new Date(p.due); due.setHours(0,0,0,0);
      const days = Math.round((due - today) / 86400000);
      if (days === 0) window.SisNotif.show(`⚠ ${p.name} vence HOY`, `$ ${new Intl.NumberFormat('es-AR').format(p.amount)}`, p.id);
      else if (days === 1) window.SisNotif.show(`📅 ${p.name} vence mañana`, `$ ${new Intl.NumberFormat('es-AR').format(p.amount)}`, p.id);
    });
  // eslint-disable-next-line react-hooks/exhaustive-deps
  }, []); // once on mount

  const allPagosForNotif   = [...(seedA.pagos||[]), ...dynamicPagos];
  const allDeudoresForNotif = [...(seedA.deudores||[]), ...dynamicDeudores];
  const pendingPagosCount   = allPagosForNotif.filter(p => !paidPagos.has(p.id)).length;
  const pendingDeuCount     = allDeudoresForNotif.filter(d => !collectedDeudores.has(d.id)).length;
  const notifCount = activeNotifs.length + pendingPagosCount + pendingDeuCount;

  // Expose tarjetasState globally so MovScreen can use it for filtering
  useE(() => { window._tarjetasForFilter = tarjetasState; }, [tarjetasState]);

  // Add salary upcoming notification
  useE(() => {
    const today = new Date();
    const dayOfMonth = today.getDate();
    const payDay = sueldoDia || 25;
    const daysUntil = payDay >= dayOfMonth ? payDay - dayOfMonth : (new Date(today.getFullYear(), today.getMonth()+1, payDay) - today) / 86400000;
    if (daysUntil <= 7 && !(confirmedIngs || {})['ing-1']) {
      const existing = activeNotifs.find(n => n.id === 'salary-upcoming');
      if (!existing) {
        const notif = {
          id:    'salary-upcoming',
          kind:  daysUntil <= 2 ? 'warn' : 'info',
          title: daysUntil === 0 ? '¡Hoy es día de cobro!' : daysUntil === 1 ? 'Mañana cobrás el sueldo' : `Sueldo en ${Math.round(daysUntil)} días`,
          meta:  `$ ${new Intl.NumberFormat('es-AR').format(sueldo)} · Confirmalo cuando llegue`,
          amount: sueldo,
        };
        setActiveNotifs(prev => prev.some(n => n.id === 'salary-upcoming') ? prev : [notif, ...prev]);
      }
    }
  // eslint-disable-next-line react-hooks/exhaustive-deps
  }, []);


  // ── Weekly Sunday email ────────────────────────────────────────────────────
  useE(() => {
    const USER_EMAIL = 'adolforafaellang@gmail.com';
    const LAST_KEY   = 'sisyphus.v1.last_weekly_email';
    const now        = new Date();
    const isSunday   = now.getDay() === 0;
    const hour       = now.getHours(); // local time
    const lastSent   = localStorage.getItem(LAST_KEY);
    const lastDate   = lastSent ? new Date(lastSent) : null;
    const alreadySentToday = lastDate && lastDate.toDateString() === now.toDateString();

    if (isSunday && hour >= 15 && !alreadySentToday) {
      // Send after a short delay so the app fully loads first
      setTimeout(async () => {
        try {
          if (!window.GmailAPI?.isConnected()) return;
          const stats = window.MonthClose?.buildStats();
          if (!stats) return;
          const html  = window.MonthClose?.buildEmailHTML(stats);
          if (!html) return;
          await window.GmailAPI.sendEmail({
            to: USER_EMAIL,
            subject: `📊 Sisyphus · Resumen semanal — ${new Date().toLocaleDateString('es-AR', { weekday:'long', day:'numeric', month:'long' })}`,
            html,
          });
          localStorage.setItem(LAST_KEY, now.toISOString());
          showToast('Resumen semanal enviado a tu correo ✓');
        } catch(e) { console.warn('Weekly email failed:', e); }
      }, 3000);
    }
  // eslint-disable-next-line react-hooks/exhaustive-deps
  }, []);

  async function sendAnalisisEmail() {
    const USER_EMAIL = 'adolforafaellang@gmail.com';
    try {
      if (!window.GmailAPI?.isConnected()) {
        showToast('Conectá Gmail en Configuración primero');
        return false;
      }
      const stats = window.MonthClose?.buildStats();
      if (!stats) { showToast('Error al calcular estadísticas'); return false; }
      const html  = window.MonthClose?.buildEmailHTML(stats);
      await window.GmailAPI.sendEmail({
        to: USER_EMAIL,
        subject: `📊 Sisyphus · Análisis rápido — ${new Date().toLocaleDateString('es-AR', { day:'numeric', month:'long', year:'numeric' })}`,
        html,
      });
      showToast('Análisis enviado a ' + USER_EMAIL + ' ✓');
      return true;
    } catch(e) {
      console.error('sendAnalisisEmail:', e);
      showToast('Error al enviar el análisis');
      return false;
    }
  }

  // Verificar cierres de tarjetas al iniciar la app (una vez por día)
  useE(() => {
    if (!window.TarjetasEngine) return;
    const nuevosPagos = window.TarjetasEngine.generarPagosPorCierre(tarjetasState, dynamicPagos);
    if (nuevosPagos.length > 0) {
      setDynamicPagos(prev => [...prev, ...nuevosPagos]);
      showToast(`${nuevosPagos.length} resumen(es) de tarjeta generado(s)`);
    }
  // eslint-disable-next-line react-hooks/exhaustive-deps
  }, []); // solo al montar

  // Notificaciones de vencimiento de tarjetas
  useE(() => {
    if (!tarjetasState || tarjetasState.length === 0) return;
    const today = new Date();
    today.setHours(0,0,0,0);
    tarjetasState.forEach(t => {
      if (!t.active || !t.fechaVencimiento) return;
      // Build next vencimiento date
      const vto = new Date(today.getFullYear(), today.getMonth(), t.fechaVencimiento);
      if (vto < today) vto.setMonth(vto.getMonth() + 1);
      const days = Math.round((vto - today) / 86400000);
      const notifId = `tarjeta-vto-${t.id}`;
      const alreadyExists = activeNotifs.some(n => n.id === notifId);
      if (!alreadyExists && days <= 7) {
        const kind = days <= 2 ? 'urgent' : 'warn';
        const label = days === 0 ? 'vence HOY' : days === 1 ? 'vence mañana' : `vence en ${days} días`;
        setActiveNotifs(prev => {
          if (prev.some(n => n.id === notifId)) return prev;
          return [...prev, {
            id: notifId,
            kind,
            title: `${t.name} — ${label}`,
            meta: `Vencimiento día ${t.fechaVencimiento}${t.acumulado > 0 ? ` · Acumulado: $${new Intl.NumberFormat('es-AR').format(Math.round(t.acumulado))}` : ''}`,
            amount: t.acumulado || null,
            tarjetaId: t.id,
          }];
        });
      }
    });
  // eslint-disable-next-line react-hooks/exhaustive-deps
  }, [tarjetasState]);

  // Apply saved theme on mount
  useE(() => {
    const savedTheme = localStorage.getItem('sisyphus.v1.theme');
    if (savedTheme === 'light') {
      document.documentElement.setAttribute('data-theme', 'light');
    }
  }, []);

  // Dismiss splash screen once React has painted the first frame
  useE(() => {
    const el = document.getElementById('sisyphus-loader');
    if (!el) return;
    // rAF ensures the first real frame has rendered before we fade the loader
    requestAnimationFrame(() => {
      requestAnimationFrame(() => {
        el.classList.add('fade-out');
        setTimeout(() => { if (el.parentNode) el.parentNode.removeChild(el); }, 520);
      });
    });
  }, []);

  return (<>
    {!onboarded && window.OnboardingWizard && (
      <OnboardingWizard onComplete={completeOnboarding} existingName={userName} />
    )}
    <div style={{ position:'fixed', inset:0, display:'flex', flexDirection:'row', overflow:'hidden' }}>
      <div style={{ position:'fixed', inset:0, pointerEvents:'none', zIndex:0,
        background:'radial-gradient(72% 58% at 14% 8%, rgba(124,58,237,0.28), transparent 62%), radial-gradient(64% 54% at 86% 16%, rgba(139,92,246,0.16), transparent 60%), radial-gradient(78% 64% at 50% 94%, rgba(79,20,168,0.26), transparent 66%)' }} />

      {/* ── DESKTOP SHELL ── */}
      {isDesktop && window.DesktopSidebar && (
        <window.DesktopSidebar tab={tab} onTab={go} profilePhoto={profilePhoto} userName={userName} onOpenAdd={() => setAddOpen(true)} onOpenSettings={() => setSettOpen(true)} />
      )}
      {isDesktop && !window.DesktopSidebar && (
        <Sidebar tab={tab} onTab={go} onAdd={() => setAddOpen(true)} onNotif={() => setNotifOpen(o => !o)} notifCount={notifCount} profilePhoto={profilePhoto} userName={userName} />
      )}

      <div style={{ position:'relative', zIndex:1, flex:1, display:'flex', flexDirection:'column', minHeight:0, minWidth:0, overflow:'hidden' }}>
        {isDesktop && window.DesktopTopbar && (
          <window.DesktopTopbar
            title={{ home:'Resumen', movimientos:'Movimientos', pagos:'Pagos', analisis:'Análisis', cuentas:'Cuentas', cierre:'Cierre de mes', config:'Configuración' }[tab] || 'Sisyphus'}
            sub={{ home:'Tu mes financiero de un vistazo', movimientos:'Todo lo que entró y salió', pagos:'Vencimientos y deudores', analisis:'Distribución, presupuesto y conversión', cuentas:'Patrimonio y saldos', cierre:`Resumí y archivá ${new Date().toLocaleDateString('es-AR',{month:'long'})}` }[tab] || ''}
            tab={tab} ccy={ccy} onCcy={toggleCcy}
            onNotif={() => setNotifOpen(o => !o)} notifCount={notifCount}
            onCalc={() => setCalcOpen(o => !o)}
            onAdd={() => setAddOpen(true)}
            monthLabel={new Date().toLocaleDateString('es-AR', { month:'long', year:'numeric' }).replace(' de ', ' ').replace(/^\w/, c => c.toUpperCase())}
          />
        )}
        {!isDesktop && <Header onNotif={() => setNotifOpen(o => !o)} notifCount={notifCount} onConfig={() => setSettOpen(true)} profilePhoto={profilePhoto} onCalc={() => setCalcOpen(o => !o)} />}
        {isDesktop && !window.DesktopTopbar && <DesktopTopBar tab={tab} onNotif={() => setNotifOpen(o=>!o)} ccy={ccy} onCcy={toggleCcy} notifCount={notifCount} />}

        <div ref={scrollRef} style={{ flex:1, overflowY:'auto', overflowX:'hidden', WebkitOverflowScrolling:'touch' }}
          onTouchStart={isDesktop ? undefined : onTouchStart}
          onTouchMove={isDesktop ? undefined : onTouchMove}
          onTouchEnd={isDesktop ? undefined : onTouchEnd}>
          {/* Pull-to-refresh indicator */}
          {!isDesktop && (pullY > 0 || isRefreshing) && (
            <div style={{ display:'flex', alignItems:'center', justifyContent:'center', height: isRefreshing ? 48 : pullY, overflow:'hidden', transition: pullRef.current.pulling ? 'none' : 'height 0.3s ease' }}>
              <div style={{ width:28, height:28, borderRadius:'50%', border:'2.5px solid var(--border-strong)', borderTopColor:'var(--accent-bright)', animation: (isRefreshing || pullY >= PULL_THRESHOLD) ? 'spin 0.7s linear infinite' : 'none', transform: !isRefreshing ? `rotate(${pullY * 3}deg)` : undefined, transition:'transform 0.1s' }} />
            </div>
          )}
          <div key={tab} style={{ animation:'tabEnter 0.2s ease', minHeight:'100%' }}>
            {/* Desktop-specific screens */}
            {isDesktop && tab === 'home'        && window.HomeDesktop    && <window.HomeDesktop ccy={ccy} spentState={spentState} budgets={budgets} extraIngresos={totalIngresos} paidPagos={paidPagos} onTogglePaid={togglePaid} onNav={go} userName={userName} cuotasGrupos={cuotasGrupos} movimientos={movimientos} confirmedIngs={confirmedIngs} onConfirmIngreso={confirmIngreso} onOpenCloseMonth={() => setCloseMonthOpen(true)} extraPagos={dynamicPagos} collectedDeudores={collectedDeudores} metas={metas} onAddMeta={addMeta} onUpdateMeta={updateMeta} onDeleteMeta={deleteMeta} />}
            {isDesktop && tab === 'movimientos' && window.MovDesktop      && <window.MovDesktop ccy={ccy} movimientos={movimientos} spentState={spentState} budgets={budgets} onSendAnalisis={sendAnalisisEmail} tarjetasState={tarjetasState} extraIngresos={totalIngresos} onDeleteMov={deleteMovimiento} onAddMov={addMovRaw} onEditMov={updateMovimiento} />}
            {isDesktop && tab === 'pagos'       && window.PagosDesktop    && <window.PagosDesktop ccy={ccy} paidPagos={paidPagos} onTogglePaid={togglePaid} collectedDeudores={collectedDeudores} onMarkCollected={markCollected} extraPagos={dynamicPagos} extraDeudores={dynamicDeudores} tarjetasState={tarjetasState} />}
            {isDesktop && tab === 'analisis'    && window.AnalisisDesktop && <window.AnalisisDesktop ccy={ccy} spentState={spentState} budgets={budgets} onBudgetsChange={setBudgets} />}
            {isDesktop && tab === 'analisis'    && !window.AnalisisDesktop && AnalisisScreen && <AnalisisScreen ccy={ccy} isDesktop={true} spentState={spentState} budgets={budgets} onBudgetsChange={setBudgets} sueldo={sueldo} onSueldoChange={setSueldo} sueldoDia={sueldoDia} onSueldoDiaChange={setSueldoDia} extraIngresos={extraIngresos} onExtraIngresosChange={setExtraIngresos} />}
            {isDesktop && tab === 'cuentas'     && window.CuentasDesktop  && <window.CuentasDesktop ccy={ccy} cuentasState={cuentasState} tarjetasState={tarjetasState} onAddCuenta={addCuenta} onUpdateCuenta={updateCuenta} onDeleteCuenta={deleteCuenta} onAddTarjeta={addTarjeta} onUpdateTarjeta={updateTarjeta} onDeleteTarjeta={deleteTarjeta} />}
            {isDesktop && tab === 'cierre'      && window.CierreDesktop   && <window.CierreDesktop ccy={ccy} spentState={spentState} budgets={budgets} movimientos={movimientos} onClose={() => showToast('Mes cerrado ✓')} onOpenModal={() => setCloseMonthOpen(true)} />}
            {isDesktop && tab === 'config'      && <ConfigScreen ccy={ccy} isDesktop={isDesktop} budgets={budgets} onBudgetsChange={setBudgets} profilePhoto={profilePhoto} onProfileChange={setProfilePhoto} onToast={showToast} spentState={spentState} userName={userName} onUserNameChange={setUserName} photoPos={photoPos} onPhotoPosChange={setPhotoPos} extraIngresos={extraIngresos} onExtraIngresosChange={setExtraIngresos} sueldo={sueldo} onSueldoChange={setSueldo} sueldoDia={sueldoDia} onSueldoDiaChange={setSueldoDia} metas={metas} onAddMeta={addMeta} onUpdateMeta={updateMeta} onDeleteMeta={deleteMeta} movimientos={movimientos} />}
            {/* Fallback to original screens if desktop components not loaded */}
            {(!isDesktop || !window.HomeDesktop)    && tab === 'home'        && <HomeScreen ccy={ccy} onNav={go} onOpenAdd={() => setAddOpen(true)} onCcy={toggleCcy} isDesktop={isDesktop} paidPagos={paidPagos} onTogglePaid={togglePaid} spentState={spentState} budgets={budgets} extraIngresos={totalIngresos} userName={userName} selectedMonthIdx={0} onSelectMonth={() => {}} cuotasGrupos={cuotasGrupos} movimientos={movimientos} confirmedIngs={confirmedIngs} onConfirmIngreso={confirmIngreso} metas={metas} onAddMeta={addMeta} onUpdateMeta={updateMeta} onDeleteMeta={deleteMeta} />}
            {(!isDesktop || !window.MovDesktop)      && tab === 'movimientos' && <MovScreen ccy={ccy} isDesktop={isDesktop} movimientos={movimientos} spentState={spentState} budgets={budgets} onSendAnalisis={sendAnalisisEmail} extraIngresos={totalIngresos} onDeleteMov={deleteMovimiento} onEditMov={updateMovimiento} />}
            {(!isDesktop || !window.PagosDesktop)    && tab === 'pagos'       && <PagosScreen ccy={ccy} isDesktop={isDesktop} paidPagos={paidPagos} onTogglePaid={togglePaid} collectedDeudores={collectedDeudores} onMarkCollected={markCollected} extraPagos={dynamicPagos} extraDeudores={dynamicDeudores} pendingBandeja={pendingBandeja} onConfirmBandeja={confirmBandeja} onDiscardBandeja={discardBandeja} tarjetasState={tarjetasState} onDeletePago={deletePago} onDeleteDeudor={deleteDeudor} />}
            {!isDesktop && tab === 'analisis' && AnalisisScreen && <AnalisisScreen ccy={ccy} isDesktop={false} spentState={spentState} budgets={budgets} onBudgetsChange={setBudgets} sueldo={sueldo} onSueldoChange={setSueldo} sueldoDia={sueldoDia} onSueldoDiaChange={setSueldoDia} extraIngresos={extraIngresos} onExtraIngresosChange={setExtraIngresos} />}
            {!isDesktop && tab === 'config'   && window.ConfigScreen && React.createElement(window.ConfigScreen, { ccy, isDesktop:false, budgets, onBudgetsChange:setBudgets, profilePhoto, onProfileChange:setProfilePhoto, onToast:showToast, spentState, userName, onUserNameChange:setUserName, photoPos, onPhotoPosChange:setPhotoPos, extraIngresos, onExtraIngresosChange:setExtraIngresos, sueldo, onSueldoChange:setSueldo, sueldoDia, onSueldoDiaChange:setSueldoDia, metas, onAddMeta:addMeta, onUpdateMeta:updateMeta, onDeleteMeta:deleteMeta, movimientos })}
            {(tab === 'mas' || (isDesktop && tab === 'notifs')) && (
              <MasScreen ccy={ccy} isDesktop={isDesktop} activeNotifs={activeNotifs} onDismissNotif={dismissNotif} onToast={showToast} cuentasState={cuentasState} onUpdateCuenta={updateCuenta} onAddCuenta={addCuenta} onDeleteCuenta={deleteCuenta} tarjetasState={tarjetasState} onAddTarjeta={addTarjeta} onUpdateTarjeta={updateTarjeta} onDeleteTarjeta={deleteTarjeta} initialTab={tab === 'mas' ? 'notifs' : tab} spentState={spentState} budgets={budgets} onBudgetsChange={setBudgets} userName={userName} onUserNameChange={setUserName} profilePhoto={profilePhoto} onProfileChange={setProfilePhoto} extraIngresos={totalIngresos} onExtraIngresosChange={setExtraIngresos} photoPos={photoPos} onPhotoPosChange={setPhotoPos} sueldo={sueldo} onSueldoChange={setSueldo} sueldoDia={sueldoDia} onSueldoDiaChange={setSueldoDia} />
            )}
          </div>
        </div>

        {!isDesktop && <BottomNav tab={tab} onTab={go} onAdd={() => setAddOpen(true)} />}
      </div>

      {calcOpen && window.Calculator && <window.Calculator onClose={() => setCalcOpen(false)} />}
      <CloseMonthModal open={closeMonthOpen} onClose={() => setCloseMonthOpen(false)} spentState={spentState} budgets={budgets} movimientos={movimientos} onToast={showToast} />
      {settOpen && window.Settings   && <window.Settings   onClose={() => setSettOpen(false)} onEdit={() => { setSettOpen(false); go('config'); }} userName={userName} onUserNameChange={setUserName} ccy={ccy} onCcy={v => { SS.useStorage && setCcyRaw(v); toggleCcy(); }} profilePhoto={profilePhoto} onProfileChange={setProfilePhoto} spentState={spentState} budgets={budgets} onBudgetsChange={setBudgets} />}
      <NotifPanel open={notifOpen} onClose={() => setNotifOpen(false)} notifs={activeNotifs} onDismiss={dismissNotif} isDesktop={isDesktop} pagos={[...(seedA.pagos||[]), ...dynamicPagos]} paidPagos={paidPagos} onTogglePaid={togglePaid} deudores={[...(seedA.deudores||[]), ...dynamicDeudores]} collectedDeudores={collectedDeudores} onMarkCollected={markCollected} ingresos={seedA.ingresos||[]} confirmedIngs={confirmedIngs} onConfirmIngreso={confirmIngreso} ccy={ccy} pendingBandeja={pendingBandeja} onConfirmBandeja={confirmBandeja} onDiscardBandeja={discardBandeja} />
      <QuickAdd open={addOpen} onClose={() => setAddOpen(false)} onCommit={commitAdd} onCommitDebt={commitDebt} onCommitCuota={commitCuotaGrupo} onCommitPago={commitPago} isDesktop={isDesktop} tarjetasState={tarjetasState} />

      {/* Conversor panel — rendered at root so it's above ALL stacking contexts */}
      {convOpen && isDesktop && (() => {
        const FXPaneDyn = window.FXPane;
        if (!FXPaneDyn) return null;
        return (
          <div ref={convPanelRef} style={{
            position:'fixed', top: convPos.top, right: convPos.right, zIndex:10000,
            width:360,
            background:'#0a0714',
            border:'1px solid var(--border-strong)', borderRadius:20,
            padding:'18px 18px 16px',
            boxShadow:'0 24px 80px rgba(0,0,0,0.85), 0 0 0 1px rgba(167,139,250,0.08)',
            animation:'fadeSlide 0.15s ease',
          }}>
            <div style={{ display:'flex', alignItems:'center', justifyContent:'space-between', marginBottom:14 }}>
              <div style={{ fontSize:10, color:'var(--text-3)', textTransform:'uppercase', letterSpacing:'0.14em', fontWeight:700 }}>
                Conversor · Modo fórmula (ej: 100000*2)
              </div>
              <button onClick={() => setConvOpen(false)} style={{ width:22, height:22, borderRadius:6, background:'rgba(255,255,255,0.07)', border:'none', color:'var(--text-3)', cursor:'pointer', display:'flex', alignItems:'center', justifyContent:'center' }}>
                <IconA name="x" size={10} />
              </button>
            </div>
            <FXPaneDyn />
          </div>
        );
      })()}

      {toast && (
        <div style={{ position:'fixed', bottom: isDesktop ? 32 : 96, left:'50%', transform:'translateX(-50%)', background:'rgba(10,6,22,0.97)', color:'var(--text)', padding:'11px 18px', borderRadius:12, border:'1px solid var(--border-strong)', fontSize:13, fontWeight:500, display:'flex', alignItems:'center', gap:10, zIndex:200, boxShadow:'0 20px 40px rgba(0,0,0,0.5)', animation:'slideUp 0.22s ease', whiteSpace:'nowrap' }}>
          <span style={{ width:18, height:18, borderRadius:'50%', background:'var(--accent)', color:'var(--on-accent)', display:'inline-flex', alignItems:'center', justifyContent:'center', flexShrink:0, animation:'checkPop 0.3s cubic-bezier(0.2,0.8,0.2,1)' }}>
            <IconA name="check" size={11} strokeWidth={2.6} />
          </span>
          {toast.label}
        </div>
      )}
    </div>
  </>);
}

window.SisyphusApp = App;

// Mount React here — at this point Babel has finished transpiling all JSX files.
// Wait for cloudPull to finish (or resolve immediately if no token/localhost).
(window._sisyphusReady || Promise.resolve()).finally(function() {
  var container = document.getElementById('app-root');
  if (!container || !window.SisyphusApp) return;
  ReactDOM.createRoot(container).render(React.createElement(window.SisyphusApp));
});
