// Pantalla de Portafolio: resumen + holdings + watchlist + movers function Portfolio({ onOpenTicker, watchlist, holdings, accent, intensity }) { // Cálculos sobre holdings const positions = holdings.map((h) => { const t = window.TICKERS[h.symbol]; const value = t.price * h.shares; const cost = h.avgCost * h.shares; const pl = value - cost; const plPct = (pl / cost) * 100; const dayPl = t.change * h.shares; return { ...h, t, value, cost, pl, plPct, dayPl }; }); const totalValue = positions.reduce((s, p) => s + p.value, 0); const totalCost = positions.reduce((s, p) => s + p.cost, 0); const totalPl = totalValue - totalCost; const totalPlPct = (totalPl / totalCost) * 100; const dayPl = positions.reduce((s, p) => s + p.dayPl, 0); const dayPlPct = (dayPl / (totalValue - dayPl)) * 100; const cash = 12480.50; // Movers: del universo de tickers, top 3 ganadores y 3 perdedores const allTickers = Object.values(window.TICKERS); const gainers = [...allTickers].sort((a, b) => b.pct - a.pct).slice(0, 3); const losers = [...allTickers].sort((a, b) => a.pct - b.pct).slice(0, 3); // Serie agregada del portafolio (suma de series de holdings, normalizada) const portfolioSeries = React.useMemo(() => { const N = 80; const sums = new Array(N).fill(0); positions.forEach((p) => { const s = window.genSeries(p.symbol, '1A', N); s.forEach((v, i) => { sums[i] += v * p.shares; }); }); return sums; }, [holdings]); const fmt = (n) => n.toLocaleString('en-US', { minimumFractionDigits: 2, maximumFractionDigits: 2 }); const allocations = positions .map((p) => ({ ...p, alloc: (p.value / totalValue) * 100 })) .sort((a, b) => b.alloc - a.alloc); return (
{/* ── HERO RESUMEN ── */}
Valor total del portafolio
${fmt(totalValue)}
= 0 ? 'up' : 'dn'}`}> {dayPl >= 0 ? '▲' : '▼'} ${fmt(Math.abs(dayPl))} · {dayPlPct >= 0 ? '+' : ''}{dayPlPct.toFixed(2)}% hoy · = 0 ? 'up' : 'dn'} style={{ fontWeight: 600 }}> {totalPl >= 0 ? '+' : ''}${fmt(totalPl)} total · {totalPlPct >= 0 ? '+' : ''}{totalPlPct.toFixed(1)}% desde apertura
Efectivo disponible ${fmt(cash)}
Posiciones abiertas {positions.length}
Coste base ${fmt(totalCost)}
Poder de compra ${fmt(cash + totalValue * 0.5)}
= 0 ? 'var(--accent)' : 'var(--neg)'} width={320} height={120} strokeWidth={2} fill={true}/>
Hace 1 año Hoy
{/* ── HOLDINGS ── */}

Mis posiciones

{positions.map((p) => { const spark = window.genSeries(p.symbol, '1A', 36); const dayUp = p.t.pct >= 0; const plUp = p.pl >= 0; return ( onOpenTicker(p.symbol)}> ); })}
Ticker Acciones Coste medio Último Cambio hoy Valor P&L total 1A
{p.symbol}
{p.t.name}
{p.shares} ${p.avgCost.toFixed(2)} ${p.t.price.toFixed(2)} {dayUp ? '+' : ''}{p.t.pct.toFixed(2)}% ${fmt(p.value)}
{plUp ? '+' : ''}${fmt(Math.abs(p.pl))}
{plUp ? '+' : ''}{p.plPct.toFixed(1)}%
{/* ── ASIGNACIÓN ── */}

Asignación

{allocations.map((p) => (
onOpenTicker(p.symbol)}>
{p.symbol} {p.alloc.toFixed(1)}%
))}
Efectivo {((cash / (totalValue + cash)) * 100).toFixed(1)}%
{/* ── WATCHLIST ── */}

Watchlist

{watchlist.map((sym) => { const t = window.TICKERS[sym]; const spark = window.genSeries(sym, '1S', 28); const up = t.pct >= 0; return (
onOpenTicker(sym)}>
{sym}
{t.name}
${t.price.toFixed(2)}
{up ? '+' : ''}{t.pct.toFixed(2)}%
); })}
{/* ── MOVERS ── */}

Movimientos del mercado

{gainers.map((t) => { const spark = window.genSeries(t.symbol, '1D', 24); return (
onOpenTicker(t.symbol)}>
{t.symbol}
{t.name}
${t.price.toFixed(2)}
+{t.pct.toFixed(2)}%
); })}
Perdedores
{losers.map((t) => { const spark = window.genSeries(t.symbol, '1D', 24); return (
onOpenTicker(t.symbol)}>
{t.symbol}
{t.name}
${t.price.toFixed(2)}
{t.pct.toFixed(2)}%
); })}
); } window.Portfolio = Portfolio;