// ============================================================================
// Ateliere Admin Dashboard — single-file React app
// ============================================================================
// Toda la UI vive aquí. Sin build step — Babel standalone hace el JSX
// transform en el navegador. Slow start (~1s) pero zero config y deploy
// directo a Vercel como static site.
//
// Estructura:
//   - createClient() de Supabase con las creds de la app iOS
//   - Auth gate: solo entran usuarios que están en admin_users table
//   - SPA con hash routing (#overview, #events, #users, #sessions, #funnel)
//   - Cada "página" es un componente que consulta Supabase y renderiza
// ============================================================================

const SUPABASE_URL = 'https://nlzdyeelytjxawmaqmew.supabase.co';
const SUPABASE_KEY = 'sb_publishable_usOIF6SkCS3j3C8TMRCaMQ_Q5qf4JP-';

const supa = window.supabase.createClient(SUPABASE_URL, SUPABASE_KEY);

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

// Chart.js defaults — paleta bosque para que todos los gráficos hereden
// los colores correctos (texto claro sobre fondo oscuro).
if (window.Chart) {
    Chart.defaults.color = '#F5EDDE';
    Chart.defaults.borderColor = 'rgba(255, 255, 255, 0.08)';
    Chart.defaults.font.family = '"Avenir Next", Avenir, Nunito, system-ui, sans-serif';
}

// ============================================================================
// Utility: hash-based router
// ============================================================================
function useHashRoute() {
    const [route, setRoute] = useState(window.location.hash.slice(1) || 'overview');
    useEffect(() => {
        const onChange = () => setRoute(window.location.hash.slice(1) || 'overview');
        window.addEventListener('hashchange', onChange);
        return () => window.removeEventListener('hashchange', onChange);
    }, []);
    return route;
}

// ============================================================================
// Auth gate
// ============================================================================
function App() {
    const [session, setSession] = useState(null);
    const [isAdmin, setIsAdmin] = useState(null);  // null = checking
    const [loading, setLoading] = useState(true);

    useEffect(() => {
        supa.auth.getSession().then(({ data }) => {
            setSession(data.session);
            setLoading(false);
        });
        const { data: sub } = supa.auth.onAuthStateChange((_e, s) => {
            setSession(s);
        });
        return () => sub.subscription.unsubscribe();
    }, []);

    // Verificar si el user es admin
    useEffect(() => {
        if (!session?.user) {
            setIsAdmin(null);
            return;
        }
        (async () => {
            const { data, error } = await supa
                .from('admin_users')
                .select('user_id, role')
                .eq('user_id', session.user.id)
                .maybeSingle();
            setIsAdmin(!error && !!data);
        })();
    }, [session]);

    if (loading) {
        return <CenteredLoader text="Cargando..." />;
    }
    if (!session) {
        return <LoginScreen />;
    }
    if (isAdmin === null) {
        return <CenteredLoader text="Verificando permisos..." />;
    }
    if (!isAdmin) {
        return <NotAdmin user={session.user} />;
    }
    return <Shell session={session} />;
}

// ============================================================================
// Login screen
// ============================================================================
function LoginScreen() {
    const [email, setEmail] = useState('');
    const [password, setPassword] = useState('');
    const [error, setError] = useState('');
    const [loading, setLoading] = useState(false);

    async function submit(e) {
        e.preventDefault();
        setLoading(true);
        setError('');
        const { error } = await supa.auth.signInWithPassword({ email, password });
        if (error) setError(error.message);
        setLoading(false);
    }

    return (
        <div className="min-h-screen flex items-center justify-center px-6">
            <div className="w-full max-w-sm">
                <div className="text-center mb-10">
                    <div className="inline-flex items-center justify-center w-16 h-16 rounded-full bg-accent/10 mb-4">
                        <svg className="w-8 h-8 text-accent" viewBox="0 0 24 24" fill="currentColor">
                            <path d="M12 2L13.5 8.5L20 10L13.5 11.5L12 18L10.5 11.5L4 10L10.5 8.5L12 2Z"/>
                        </svg>
                    </div>
                    <h1 className="font-editorial text-4xl text-ink">Ateliere Admin</h1>
                    <p className="text-muted text-sm mt-2">Dashboard de analíticas</p>
                </div>

                <form onSubmit={submit} className="space-y-3">
                    <input type="email" required placeholder="Email"
                        value={email} onChange={e => setEmail(e.target.value)}
                        className="w-full px-4 py-3 bg-card border border-line rounded-lg text-sm focus:outline-none focus:border-accent" />
                    <input type="password" required placeholder="Contraseña"
                        value={password} onChange={e => setPassword(e.target.value)}
                        className="w-full px-4 py-3 bg-card border border-line rounded-lg text-sm focus:outline-none focus:border-accent" />
                    {error && (
                        <div className="text-sm text-red-600 bg-red-50 px-3 py-2 rounded-lg">{error}</div>
                    )}
                    <button type="submit" disabled={loading}
                        className="w-full bg-ink text-bg py-3 rounded-lg text-sm font-semibold disabled:opacity-50">
                        {loading ? 'Entrando…' : 'Iniciar sesión'}
                    </button>
                </form>

                <p className="text-xs text-muted text-center mt-6">
                    Solo usuarios con permiso de admin pueden entrar.<br/>
                    Si necesitas acceso, contacta al owner del proyecto.
                </p>
            </div>
        </div>
    );
}

function NotAdmin({ user }) {
    return (
        <div className="min-h-screen flex items-center justify-center px-6">
            <div className="text-center max-w-md">
                <div className="text-6xl mb-4">🔒</div>
                <h1 className="font-editorial text-3xl text-ink mb-2">Sin acceso</h1>
                <p className="text-muted text-sm mb-4">
                    Tu cuenta <code className="bg-bg2 px-2 py-0.5 rounded text-xs">{user.email}</code> no tiene permisos de admin.
                </p>
                <p className="text-xs text-muted mb-6">
                    Si esto es un error, contacta al owner del proyecto y pide que agregue tu user_id a la tabla <code>admin_users</code> en Supabase.
                </p>
                <button onClick={() => supa.auth.signOut()}
                    className="text-sm text-accent underline">Cerrar sesión</button>
            </div>
        </div>
    );
}

// ============================================================================
// Shell — sidebar + main area
// ============================================================================
function Shell({ session }) {
    const route = useHashRoute();

    const pages = {
        // Producto / uso general
        overview:      { label: 'Overview',      component: <OverviewPage /> },
        events:        { label: 'Eventos',       component: <EventsPage /> },
        sessions:      { label: 'Sesiones',      component: <SessionsPage /> },
        schedules:     { label: 'Horarios',      component: <SchedulesPage /> },
        quality:       { label: 'Calidad',       component: <QualityPage /> },
        // Usuarios
        users:         { label: 'Usuarios',      component: <UsersPage /> },
        topusers:      { label: 'Top users',     component: <TopUsersPage /> },
        demographics:  { label: 'Demografía',    component: <DemographicsPage /> },
        geographic:    { label: 'Geográfico',    component: <GeographicPage /> },
        retention:     { label: 'Retención',     component: <RetentionPage /> },
        growth:        { label: 'Growth',        component: <GrowthPage /> },
        // Engagement social
        engagement:    { label: 'Engagement',    component: <EngagementPage /> },
        social:        { label: 'Social',        component: <SocialPage /> },
        stories:       { label: 'Stories',       component: <StoriesPage /> },
        chat:          { label: 'Chat',          component: <ChatPage /> },
        notifications: { label: 'Notificaciones', component: <NotificationsPage /> },
        search:        { label: 'Search',        component: <SearchPage /> },
        outfitai:      { label: 'Outfit IA',     component: <OutfitAIPage /> },
        funnel:        { label: 'Funnel',        component: <FunnelPage /> },
        quizdropoff:   { label: 'Quiz drop-off', component: <QuizDropoffPage /> },
        styles:        { label: 'Estilos',       component: <StylesPage /> },
        // Negocio
        closet:        { label: 'Clóset',        component: <ClosetPage /> },
        marketplace:   { label: 'Marketplace',   component: <MarketplacePage /> },
        commerce:      { label: 'Commerce deep', component: <CommercePage /> },
        // Catálogo — clasificación manual
        catalog_overview: { label: 'Overview',   component: <CatalogOverviewPage /> },
        catalog_pending:  { label: 'Pendientes', component: <CatalogPendingPage /> },
        catalog_hombre:   { label: 'Hombre',     component: <CatalogGenderPage gender="masculino" /> },
        catalog_mujer:    { label: 'Mujer',       component: <CatalogGenderPage gender="femenino" /> },
        catalog_precios:  { label: 'Precios',     component: <CatalogPreciosPage /> },
        catalog_colores:  { label: 'Colores',     component: <CatalogColoresPage /> },
        // Deep data
        social_graph:  { label: 'Red social',    component: <SocialGraphPage /> },
        crosstabs:     { label: 'Cross-tabs',    component: <CrossTabsPage /> },
        activation:    { label: 'Activación',    component: <ActivationPage /> },
        money:         { label: 'Money spent',   component: <AICostsPage /> },
    };

    // Agrupación visual del sidebar (cosmético)
    const sidebarGroups = [
        { title: 'PRODUCTO',     keys: ['overview', 'events', 'sessions', 'schedules', 'quality'] },
        { title: 'USUARIOS',     keys: ['users', 'topusers', 'demographics', 'geographic', 'retention', 'growth'] },
        { title: 'ENGAGEMENT',   keys: ['engagement', 'social', 'stories', 'chat', 'notifications', 'search', 'outfitai', 'funnel', 'quizdropoff', 'styles'] },
        { title: 'NEGOCIO',      keys: ['closet', 'marketplace', 'commerce'] },
        { title: 'CATÁLOGO',     keys: ['catalog_overview', 'catalog_pending', 'catalog_hombre', 'catalog_mujer', 'catalog_precios', 'catalog_colores'] },
        { title: 'DEEP DATA',    keys: ['social_graph', 'crosstabs', 'activation', 'money'] },
    ];

    const [sidebarOpen, setSidebarOpen] = useState(false);

    // Special route: #user/:id — vista detalle de un usuario
    const userDetailMatch = route.match(/^user\/(.+)$/);

    const currentRoute = userDetailMatch ? 'users' : route;
    const currentComponent = userDetailMatch
        ? <UserDetailPage userId={userDetailMatch[1]} />
        : (pages[route]?.component || <OverviewPage />);

    return (
        <div className="h-screen flex overflow-hidden">
            <SidebarLayout
                pages={pages}
                groups={sidebarGroups}
                route={currentRoute}
                session={session}
                isOpen={sidebarOpen}
                onClose={() => setSidebarOpen(false)}
            />
            <main className="flex-1 overflow-y-auto w-0">
                {/* Mobile top bar con hamburguesa */}
                <div className="md:hidden sticky top-0 z-30 flex items-center gap-3 px-4 py-3 bg-surfaceDeep border-b border-line">
                    <button
                        onClick={() => setSidebarOpen(true)}
                        className="p-1.5 rounded-lg bg-bg2 text-ink active:bg-bg">
                        <svg className="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
                            <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M4 6h16M4 12h16M4 18h16"/>
                        </svg>
                    </button>
                    <span className="font-editorial text-xl text-ink" style={{fontWeight:400}}>Ateliere Admin</span>
                </div>
                {currentComponent}
            </main>
        </div>
    );
}

function SidebarLayout({ pages, groups, route, session, isOpen, onClose }) {
    return (
        <>
            {/* Backdrop oscuro en móvil cuando el sidebar está abierto */}
            {isOpen && (
                <div
                    className="md:hidden fixed inset-0 bg-black/60 z-40 backdrop-blur-sm"
                    onClick={onClose}
                />
            )}

            <aside className={`
                fixed md:relative inset-y-0 left-0 z-50 md:z-auto
                w-72 md:w-60 bg-surfaceDeep border-r border-line flex flex-col flex-shrink-0
                transition-transform duration-300 ease-in-out
                ${isOpen ? 'translate-x-0' : '-translate-x-full md:translate-x-0'}
            `}>
                {/* Header del sidebar */}
                <div className="px-5 py-5 border-b border-line flex items-center justify-between">
                    <div>
                        <div className="font-editorial text-2xl text-ink" style={{ fontWeight: 400 }}>
                            Ateliere
                        </div>
                        <div className="text-[9px] text-muted uppercase tracking-[0.25em] mt-1">Admin</div>
                    </div>
                    {/* Botón cerrar — solo visible en móvil */}
                    <button
                        onClick={onClose}
                        className="md:hidden p-1.5 rounded-lg text-muted hover:text-ink hover:bg-bg2 transition-colors">
                        <svg className="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
                            <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M6 18L18 6M6 6l12 12"/>
                        </svg>
                    </button>
                </div>

                <nav className="flex-1 py-3 px-2 overflow-y-auto hide-scrollbar">
                    {groups.map((g, gi) => (
                        <div key={gi} className={gi > 0 ? 'mt-5' : ''}>
                            <div className="text-[9px] uppercase tracking-[0.2em] text-accent font-semibold px-3 mb-2">
                                {g.title}
                            </div>
                            {g.keys.map(key => {
                                const p = pages[key];
                                if (!p) return null;
                                const isActive = route === key;
                                return (
                                    <a key={key} href={`#${key}`}
                                        onClick={onClose}
                                        className={`relative flex items-center px-3 py-2 md:py-1.5 text-[13px] mb-px transition-colors rounded-sm ${
                                            isActive
                                                ? 'text-ink font-semibold bg-bg2'
                                                : 'text-ink2 hover:text-ink hover:bg-bg2/50'
                                        }`}
                                        style={{ borderLeft: `2px solid ${isActive ? '#C5A668' : 'transparent'}` }}>
                                        <span>{p.label}</span>
                                    </a>
                                );
                            })}
                        </div>
                    ))}
                </nav>

                <div className="px-4 py-4 border-t border-line">
                    <div className="text-[9px] text-muted uppercase tracking-[0.2em] mb-1.5">Sesión</div>
                    <div className="text-xs text-ink2 truncate">{session.user.email}</div>
                    <button onClick={() => supa.auth.signOut()}
                        className="text-[11px] text-muted hover:text-accent mt-2 transition-colors">
                        Cerrar sesión
                    </button>
                </div>
            </aside>
        </>
    );
}

// ============================================================================
// Page: Overview (KPIs)
// ============================================================================
function OverviewPage() {
    const [dau, setDau] = useState([]);
    const [topEvents, setTopEvents] = useState([]);
    const [topScreens, setTopScreens] = useState([]);
    const [sessionStats, setSessionStats] = useState(null);
    const [totalUsers, setTotalUsers] = useState(0);
    const [loading, setLoading] = useState(true);
    const chartRef = useRef(null);
    const chartInstance = useRef(null);

    useEffect(() => {
        (async () => {
            setLoading(true);
            const [
                { data: dauData },
                { data: eventsData },
                { data: screensData },
                { data: sessionsData },
                { count }
            ] = await Promise.all([
                supa.from('analytics_dau').select('*').limit(30),
                supa.from('analytics_top_events').select('*').limit(8),
                supa.from('analytics_top_screens').select('*').limit(8),
                supa.from('analytics_sessions_daily').select('*').limit(30),
                supa.from('profiles').select('*', { count: 'exact', head: true })
            ]);
            setDau(dauData || []);
            setTopEvents(eventsData || []);
            setTopScreens(screensData || []);
            setSessionStats(sessionsData || []);
            setTotalUsers(count || 0);
            setLoading(false);
        })();
    }, []);

    // DAU chart
    useEffect(() => {
        if (loading || !chartRef.current || dau.length === 0) return;
        if (chartInstance.current) chartInstance.current.destroy();

        const sorted = [...dau].sort((a, b) => new Date(a.day) - new Date(b.day));
        chartInstance.current = new Chart(chartRef.current, {
            type: 'line',
            data: {
                labels: sorted.map(d => new Date(d.day).toLocaleDateString('es-MX', { day: 'numeric', month: 'short' })),
                datasets: [{
                    label: 'Usuarios activos diarios',
                    data: sorted.map(d => d.dau),
                    borderColor: '#BD9962',
                    backgroundColor: 'rgba(189, 153, 98, 0.1)',
                    fill: true,
                    tension: 0.3,
                    pointRadius: 4,
                    pointBackgroundColor: '#BD9962',
                }]
            },
            options: {
                responsive: true,
                maintainAspectRatio: false,
                plugins: { legend: { display: false } },
                scales: {
                    x: { grid: { display: false }, ticks: { font: { size: 10 } } },
                    y: { beginAtZero: true, ticks: { font: { size: 10 }, precision: 0 } }
                }
            }
        });
    }, [dau, loading]);

    if (loading) return <CenteredLoader text="Cargando data..." />;

    const totalEvents = topEvents.reduce((a, b) => a + Number(b.event_count || 0), 0);
    const todayDAU = dau[0]?.dau || 0;
    const yesterdayDAU = dau[1]?.dau || 0;
    const dauChange = yesterdayDAU > 0 ? ((todayDAU - yesterdayDAU) / yesterdayDAU * 100).toFixed(1) : 0;

    return (
        <div className="p-8 max-w-7xl mx-auto space-y-4">
            <PageHeader title="Overview" subtitle="Visión general de los últimos 30 días" />

            {/* KPI cards — todos clickables para abrir explicación */}
            <div className="grid grid-cols-2 md:grid-cols-4 gap-4 items-start mb-8">
                <KpiCard label="Usuarios totales" value={totalUsers} infoKey="total_users" />
                <KpiCard label="DAU (hoy)" value={todayDAU}
                    delta={dauChange > 0 ? `+${dauChange}%` : `${dauChange}%`}
                    deltaPositive={dauChange >= 0}
                    infoKey="dau" />
                <KpiCard label="Eventos (30d)" value={totalEvents.toLocaleString()}
                    infoKey="total_events_30d" />
                <KpiCard label="Sesiones (30d)"
                    value={sessionStats.reduce((a, b) => a + Number(b.session_count || 0), 0).toLocaleString()}
                    infoKey="sessions_30d" />
            </div>

            {/* DAU chart */}
            <Card title="Usuarios activos por día" infoKey="dau">
                <div style={{ height: 160 }}>
                    <canvas ref={chartRef}></canvas>
                </div>
            </Card>

            <div className="grid grid-cols-1 md:grid-cols-2 gap-4 items-start mt-4">
                <Card title="Eventos más frecuentes" infoKey="top_events_freq">
                    <table className="w-full text-sm">
                        <thead>
                            <tr className="border-b border-line text-[10px] uppercase tracking-wider text-muted">
                                <th className="text-left pb-2 font-semibold">Evento</th>
                                <th className="text-right pb-2 font-semibold">Usuarios</th>
                                <th className="text-right pb-2 font-semibold">Total</th>
                            </tr>
                        </thead>
                        <tbody>
                            {topEvents.map(e => (
                                <tr key={e.event_name} className="border-b border-line/50">
                                    <td className="py-2">{e.event_name}</td>
                                    <td className="text-right text-muted">{e.unique_users}</td>
                                    <td className="text-right font-semibold">{Number(e.event_count).toLocaleString()}</td>
                                </tr>
                            ))}
                        </tbody>
                    </table>
                </Card>

                <Card title="Top pantallas por tiempo total" infoKey="top_screens_time">
                    <table className="w-full text-sm">
                        <thead>
                            <tr className="border-b border-line text-[10px] uppercase tracking-wider text-muted">
                                <th className="text-left pb-2 font-semibold">Pantalla</th>
                                <th className="text-right pb-2 font-semibold">Vistas</th>
                                <th className="text-right pb-2 font-semibold">Tiempo</th>
                            </tr>
                        </thead>
                        <tbody>
                            {topScreens.map(s => (
                                <tr key={s.screen_name} className="border-b border-line/50">
                                    <td className="py-2">{s.screen_name}</td>
                                    <td className="text-right text-muted">{s.views}</td>
                                    <td className="text-right font-semibold">{formatDuration(s.total_seconds)}</td>
                                </tr>
                            ))}
                        </tbody>
                    </table>
                </Card>
            </div>
        </div>
    );
}

// ============================================================================
// Page: Events (feed cronológico)
// ============================================================================
function EventsPage() {
    const [events, setEvents] = useState([]);
    const [loading, setLoading] = useState(true);
    const [filter, setFilter] = useState('');

    async function load() {
        setLoading(true);
        let query = supa.from('analytics_events')
            .select('*, profiles(handle, display_name)')
            .order('created_at', { ascending: false })
            .limit(200);
        if (filter) query = query.ilike('event_name', `%${filter}%`);
        const { data } = await query;
        setEvents(data || []);
        setLoading(false);
    }

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

    return (
        <div className="p-8 max-w-7xl mx-auto space-y-4">
            <PageHeader title="Eventos" subtitle="Feed cronológico — últimos 200 eventos" />

            <div className="mb-4 flex gap-2">
                <input placeholder="Filtrar por nombre de evento (ej. screen_view)"
                    value={filter} onChange={e => setFilter(e.target.value)}
                    className="flex-1 px-3 py-2 bg-card border border-line rounded-lg text-sm" />
                <button onClick={load}
                    className="px-4 py-2 bg-ink text-bg rounded-lg text-sm font-semibold">
                    Refrescar
                </button>
            </div>

            {loading ? <CenteredLoader text="Cargando..." /> : (
                <Card>
                    <div className="overflow-x-auto">
                        <table className="w-full text-sm">
                            <thead>
                                <tr className="border-b border-line text-[10px] uppercase tracking-wider text-muted">
                                    <th className="text-left p-2 font-semibold">Cuándo</th>
                                    <th className="text-left p-2 font-semibold">Usuario</th>
                                    <th className="text-left p-2 font-semibold">Evento</th>
                                    <th className="text-left p-2 font-semibold">Pantalla</th>
                                    <th className="text-left p-2 font-semibold">Props</th>
                                </tr>
                            </thead>
                            <tbody>
                                {events.map(e => (
                                    <tr key={e.id} className="border-b border-line/40 hover:bg-bg2/40">
                                        <td className="p-2 text-muted text-xs whitespace-nowrap">
                                            {formatTimeAgo(e.created_at)}
                                        </td>
                                        <td className="p-2 text-xs">
                                            {e.profiles?.handle ? `@${e.profiles.handle}` : <span className="text-muted">—</span>}
                                        </td>
                                        <td className="p-2 font-semibold">{e.event_name}</td>
                                        <td className="p-2 text-muted">{e.screen_name || '—'}</td>
                                        <td className="p-2 text-xs text-muted max-w-xs truncate">
                                            {e.properties && Object.keys(e.properties).length > 0
                                                ? JSON.stringify(e.properties)
                                                : '—'}
                                        </td>
                                    </tr>
                                ))}
                            </tbody>
                        </table>
                    </div>
                </Card>
            )}
        </div>
    );
}

// ============================================================================
// Page: Users
// ============================================================================
function UsersPage() {
    const [users, setUsers] = useState([]);
    const [loading, setLoading] = useState(true);

    useEffect(() => {
        (async () => {
            const { data } = await supa
                .from('profiles')
                .select('id, handle, display_name, created_at, garments_count, posts_count, followers_count')
                .order('created_at', { ascending: false })
                .limit(100);
            setUsers(data || []);
            setLoading(false);
        })();
    }, []);

    if (loading) return <CenteredLoader text="Cargando usuarios..." />;

    return (
        <div className="p-8 max-w-7xl mx-auto space-y-4">
            <PageHeader title="Usuarios" subtitle="Últimos 100 usuarios registrados" />
            <Card>
                <table className="w-full text-sm">
                    <thead>
                        <tr className="border-b border-line text-[10px] uppercase tracking-wider text-muted">
                            <th className="text-left p-2 font-semibold">Handle</th>
                            <th className="text-left p-2 font-semibold">Nombre</th>
                            <th className="text-right p-2 font-semibold">Prendas</th>
                            <th className="text-right p-2 font-semibold">Posts</th>
                            <th className="text-right p-2 font-semibold">Seguidores</th>
                            <th className="text-right p-2 font-semibold">Registro</th>
                        </tr>
                    </thead>
                    <tbody>
                        {users.map(u => (
                            <tr key={u.id} className="border-b border-line/40 hover:bg-bg2/40 cursor-pointer"
                                onClick={() => window.location.hash = `user/${u.id}`}>
                                <td className="p-2 font-semibold text-accent">@{u.handle}</td>
                                <td className="p-2">{u.display_name}</td>
                                <td className="p-2 text-right">{u.garments_count || 0}</td>
                                <td className="p-2 text-right">{u.posts_count || 0}</td>
                                <td className="p-2 text-right">{u.followers_count || 0}</td>
                                <td className="p-2 text-right text-muted text-xs">{new Date(u.created_at).toLocaleDateString('es-MX')}</td>
                            </tr>
                        ))}
                    </tbody>
                </table>
            </Card>
        </div>
    );
}

// ============================================================================
// Page: Sessions
// ============================================================================
function SessionsPage() {
    const [sessions, setSessions] = useState([]);
    const [daily, setDaily] = useState([]);
    const [loading, setLoading] = useState(true);

    useEffect(() => {
        (async () => {
            const [{ data: s }, { data: d }] = await Promise.all([
                supa.from('analytics_sessions')
                    .select('*, profiles(handle)')
                    .order('started_at', { ascending: false })
                    .limit(50),
                supa.from('analytics_sessions_daily').select('*').limit(30)
            ]);
            setSessions(s || []);
            setDaily(d || []);
            setLoading(false);
        })();
    }, []);

    if (loading) return <CenteredLoader text="Cargando..." />;

    const avgDuration = sessions.length > 0
        ? sessions.reduce((a, s) => a + (s.duration_seconds || 0), 0) / sessions.length
        : 0;

    return (
        <div className="p-8 max-w-7xl mx-auto space-y-4">
            <PageHeader title="Sesiones" subtitle="Cada vez que el user abre la app" />

            <div className="grid grid-cols-3 gap-4 items-start mb-6">
                <KpiCard label="Total sesiones (30d)"
                    value={daily.reduce((a, b) => a + Number(b.session_count || 0), 0).toLocaleString()} />
                <KpiCard label="Duración promedio" value={formatDuration(avgDuration)} infoKey="avg_session_duration" />
                <KpiCard label="Eventos por sesión"
                    value={(daily.reduce((a, b) => a + Number(b.avg_events_per_session || 0), 0) / Math.max(daily.length, 1)).toFixed(1)} />
            </div>

            <Card title="Últimas 50 sesiones" infoKey="sessions_list">
                <table className="w-full text-sm">
                    <thead>
                        <tr className="border-b border-line text-[10px] uppercase tracking-wider text-muted">
                            <th className="text-left p-2 font-semibold">Usuario</th>
                            <th className="text-left p-2 font-semibold">Inicio</th>
                            <th className="text-right p-2 font-semibold">Duración</th>
                            <th className="text-right p-2 font-semibold">Eventos</th>
                            <th className="text-left p-2 font-semibold">Device</th>
                        </tr>
                    </thead>
                    <tbody>
                        {sessions.map(s => (
                            <tr key={s.id} className="border-b border-line/40">
                                <td className="p-2">{s.profiles?.handle ? `@${s.profiles.handle}` : '—'}</td>
                                <td className="p-2 text-muted text-xs">{formatTimeAgo(s.started_at)}</td>
                                <td className="p-2 text-right">{formatDuration(s.duration_seconds || 0)}</td>
                                <td className="p-2 text-right">{s.events_count || 0}</td>
                                <td className="p-2 text-xs text-muted">{s.device_model || '—'}</td>
                            </tr>
                        ))}
                    </tbody>
                </table>
            </Card>
        </div>
    );
}

// ============================================================================
// Page: Funnel
// ============================================================================
function FunnelPage() {
    const [funnel, setFunnel] = useState(null);
    const [loading, setLoading] = useState(true);

    useEffect(() => {
        (async () => {
            const { data } = await supa.from('analytics_funnel_onboarding').select('*').single();
            setFunnel(data);
            setLoading(false);
        })();
    }, []);

    if (loading) return <CenteredLoader text="Cargando funnel..." />;
    if (!funnel) return <CenteredLoader text="Sin datos" />;

    const steps = [
        { label: '1. Signup', value: funnel.signups, color: 'bg-accent' },
        { label: '2. Completó quiz de estilo', value: funnel.completed_quiz, color: 'bg-accentDark' },
        { label: '3. Subió primera prenda', value: funnel.added_first_garment, color: 'bg-green' },
        { label: '4. Publicó primer post', value: funnel.published_first_post, color: 'bg-ink' },
    ];
    const max = Math.max(...steps.map(s => s.value), 1);

    return (
        <div className="p-8 max-w-7xl mx-auto space-y-4">
            <PageHeader title="Funnel de onboarding" subtitle="Cuántos users completan cada paso clave" />

            <Card>
                <div className="space-y-3">
                    {steps.map((s, i) => {
                        const pct = (s.value / max) * 100;
                        const dropoff = i > 0 ? steps[i - 1].value - s.value : 0;
                        const conversion = i > 0 && steps[i - 1].value > 0
                            ? (s.value / steps[i - 1].value * 100).toFixed(1)
                            : null;
                        return (
                            <div key={s.label}>
                                <div className="flex items-baseline justify-between mb-1">
                                    <div className="text-sm font-semibold">{s.label}</div>
                                    <div className="text-sm">
                                        <span className="font-semibold">{s.value}</span>
                                        {conversion !== null && (
                                            <span className="text-muted text-xs ml-2">
                                                ({conversion}% del paso anterior)
                                            </span>
                                        )}
                                    </div>
                                </div>
                                <div className="h-8 bg-bg2 rounded overflow-hidden relative">
                                    <div className={`h-full ${s.color} transition-all duration-500`}
                                        style={{ width: `${pct}%` }}></div>
                                </div>
                                {dropoff > 0 && (
                                    <div className="text-[11px] text-red-600 mt-0.5">
                                        ⤷ {dropoff} usuarios no avanzaron a este paso
                                    </div>
                                )}
                            </div>
                        );
                    })}
                </div>
            </Card>

            <div className="mt-6 p-4 bg-bg2 rounded-lg text-xs text-muted">
                💡 <strong>Cómo leer esto:</strong> idealmente cada paso tiene alta conversion del anterior. Si ves un drop grande en algún paso, ese es el momento donde los users se van — ahí debes mejorar UX.
            </div>
        </div>
    );
}

// ============================================================================
// Page: Styles (resultado del quiz)
// ============================================================================
function StylesPage() {
    const [styles, setStyles] = useState([]);
    const [loading, setLoading] = useState(true);

    useEffect(() => {
        (async () => {
            const { data } = await supa
                .from('style_profiles')
                .select('primary_style, secondary_style, primary_score, vibe_soft, vibe_edgy, vibe_refined, vibe_eclectic, vibe_casual, vibe_vintage')
                .not('primary_style', 'is', null);

            // Agregar conteos por primary_style
            const counts = {};
            (data || []).forEach(r => {
                counts[r.primary_style] = (counts[r.primary_style] || 0) + 1;
            });
            const sorted = Object.entries(counts)
                .map(([style, count]) => ({ style, count }))
                .sort((a, b) => b.count - a.count);
            setStyles(sorted);
            setLoading(false);
        })();
    }, []);

    if (loading) return <CenteredLoader text="Cargando..." />;

    const total = styles.reduce((a, b) => a + b.count, 0);

    return (
        <div className="p-8 max-w-7xl mx-auto space-y-4">
            <PageHeader title="Distribución de estilos" subtitle={`${total} usuarios completaron el quiz`} />

            <Card title="Estilo primario más común" infoKey="styles_distribution">
                {styles.length === 0 ? (
                    <div className="text-center py-12 text-muted text-sm">
                        Todavía nadie ha completado el quiz de estilo.
                    </div>
                ) : (
                    <div className="space-y-3">
                        {styles.map(s => {
                            const pct = (s.count / total * 100).toFixed(1);
                            return (
                                <div key={s.style}>
                                    <div className="flex justify-between mb-1">
                                        <div className="text-sm font-semibold">{s.style}</div>
                                        <div className="text-sm text-muted">{s.count} ({pct}%)</div>
                                    </div>
                                    <div className="h-6 bg-bg2 rounded overflow-hidden">
                                        <div className="h-full bg-accent transition-all duration-500"
                                            style={{ width: `${pct}%` }}></div>
                                    </div>
                                </div>
                            );
                        })}
                    </div>
                )}
            </Card>
        </div>
    );
}

// ============================================================================
// METRIC_INFO — diccionario de explicaciones por métrica
// ============================================================================
// Cada key corresponde a un `infoKey` que se pasa a Card o KpiCard.
// Cuando el user toca el card, se abre un panel lateral con esta info.
//
// Estructura de cada métrica:
//   • title:          Nombre completo de la métrica
//   • short:          Una frase para qué es
//   • formula:        Cómo se calcula (en plain English/SQL pseudocódigo)
//   • meaning:        Qué significa, cómo interpretarla
//   • benchmarks:     Rangos típicos (bad/good/great) cuando aplique
//   • actions:        Lista de acciones a tomar según el valor
//   • why_it_matters: Por qué te importa esta métrica
const METRIC_INFO = {
    // ─────── KPIs Overview ───────
    'total_users': {
        title: 'Usuarios totales',
        short: 'Cuántas cuentas se han registrado en Ateliere desde el inicio.',
        formula: 'COUNT(*) FROM profiles',
        meaning: 'Número crudo de signups acumulados. NO mide actividad, solo crecimiento del registro.',
        why_it_matters: 'Es la métrica de vanidad clásica. Mientras más users tengas, más opciones de monetizar — pero solo si están activos. Compárala SIEMPRE con DAU/MAU para entender si tu base es real o "fantasma".',
        actions: [
            'Si crece pero DAU no sube: tu adquisición funciona pero tu retención no.',
            'Si está estancada: foco en marketing y onboarding viral (K-factor).',
        ]
    },
    'dau': {
        title: 'DAU (Daily Active Users)',
        short: 'Usuarios únicos que abrieron la app hoy.',
        formula: 'COUNT(DISTINCT user_id) FROM analytics_events WHERE day = today',
        meaning: 'Mide el engagement diario. Si DAU crece consistentemente, tu producto se está volviendo hábito para los usuarios.',
        benchmarks: {
            bad: '< 10% del MAU (los users no regresan)',
            good: '15-25% del MAU (engagement saludable)',
            great: '> 30% del MAU (stickiness alta tipo Instagram/TikTok)',
        },
        why_it_matters: 'Es el termómetro más honesto de tu producto. Una app sin DAU creciente está muerta aunque tenga millones de signups.',
        actions: [
            'Si baja: revisa qué cambió la última semana (nueva UI, bug, push fatigue).',
            'Si sube: identifica qué cohort está activando y dóblale la apuesta (campaña, feature, etc.)',
        ]
    },
    'total_events_30d': {
        title: 'Eventos totales (30 días)',
        short: 'Cantidad cruda de acciones que los users han hecho en los últimos 30 días.',
        formula: 'COUNT(*) FROM analytics_events WHERE created_at > now() - 30 days',
        meaning: 'Volumen total de interacción con la app. Eventos típicos: screen_view, tab_switched, garment_uploaded, post_liked, etc.',
        why_it_matters: 'Mide qué tanto "ruido" hay en tu app. Si es alto pero DAU es bajo, significa que tienes pocos usuarios muy enganchados (power users).',
        actions: [
            'Si bajan los eventos pero DAU se mantiene: los users entran y salen rápido — revisa el "tiempo en pantalla".',
            'Si suben: identifica qué evento creció más (ver tab Eventos).',
        ]
    },
    'sessions_30d': {
        title: 'Sesiones (30 días)',
        short: 'Cuántas veces se abrió la app en los últimos 30 días.',
        formula: 'COUNT(*) FROM analytics_sessions WHERE started_at > now() - 30 days',
        meaning: 'Cada vez que el user abre la app después de 5+ min cerrada cuenta como sesión nueva.',
        why_it_matters: 'Si un user solo abre la app 1 vez al día, su retención es frágil. 3+ sesiones/día = hábito formado.',
        actions: [
            'Compara con DAU: sessions ÷ DAU = sesiones por usuario. >2 indica hábito; <1.5 indica curiosidad.',
        ]
    },

    // ─────── Engagement / Tiempo ───────
    'time_per_garment': {
        title: 'Tiempo viendo prenda',
        short: 'Cuánto tiempo se quedan mirando cada prenda en detalle.',
        formula: 'SUM(duration_ms) WHERE event = "garment_viewed"',
        meaning: 'Indica cuáles prendas captan más atención. Las que tienen >15s de promedio son "candy" — el user las contempla.',
        why_it_matters: 'Las prendas con mucho dwell time son las que mejor convierten a outfit guardado o compra. Identificarlas te ayuda a mostrar más contenido similar.',
        actions: [
            'Top prendas por tiempo → estudia qué tienen en común (categoría, brand, color) y promueve similares.',
            'Prendas con <2s: probablemente confusas o irrelevantes. Mejora sus fotos o quítales relevancia.',
        ]
    },
    'engagement_score': {
        title: 'Engagement score',
        short: 'Puntaje 0-100 que mide qué tan involucrado está el user con la app.',
        formula: '+30 activo 7d · +20 ≥3 sesiones 7d · +15 ha posteado · +15 >5 prendas · +10 quiz completo · +10 acciones sociales 7d',
        meaning: 'Score compuesto. Un user con 80+ es "power user" que probablemente nunca te abandona. 30-60 son casuales. <30 está churneando.',
        benchmarks: {
            bad: '< 30 (probable churn)',
            good: '50-75 (engagement saludable)',
            great: '> 80 (power user, advocate potencial)',
        },
        why_it_matters: 'Te permite segmentar usuarios para campañas distintas. A los <30 mándales push de retención; a los 80+ pídeles feedback o invítalos a beta.',
        actions: [
            'Identifica TODOS los users con score >80 → tu base de evangelizadores.',
            'Trackea cuántos users cruzan de <30 → 60: es tu activation funnel real.',
        ]
    },

    // ─────── Retention ───────
    'retention_d1': { title: 'D1 retention', short: 'Cuántos users vuelven al día siguiente de registrarse.', formula: 'DISTINCT users que tuvieron evento entre +1d y +2d desde signup', meaning: 'Métrica más estricta de retención. Si no vuelven D1, casi nunca vuelven.', benchmarks: { bad: '< 25%', good: '30-40%', great: '> 45% (top tier)' }, why_it_matters: 'D1 es el indicador más temprano de PMF (product-market fit). Si está bajo, no escales — arregla el onboarding primero.', actions: ['Si <25%: tu onboarding tiene fricción. Mide drop-off por paso del Style Quiz.', 'Si >40%: estás en buen camino — empuja para D7.'] },
    'retention_d7': { title: 'D7 retention', short: 'Cuántos siguen activos a la semana de registrarse.', formula: 'DISTINCT users con evento al día 7 desde signup', meaning: 'Indica si el producto formó hábito en la primera semana.', benchmarks: { bad: '< 10%', good: '15-25%', great: '> 30%' }, why_it_matters: 'Si pasaron de D1 pero no llegan a D7, tu producto se "vuelve aburrido". Necesitas más contenido fresco / features de engagement.', actions: ['Si bajo: agrega push semanales con contenido nuevo.', 'Manda push relevante al día 3-5 para evitar el drop.'] },
    'retention_d30': { title: 'D30 retention', short: 'Quiénes siguen al mes — los verdaderos "core users".', formula: 'DISTINCT users con evento al día 30 desde signup', meaning: 'D30 es tu base estable, los que ya no se van.', benchmarks: { bad: '< 5%', good: '8-15%', great: '> 20%' }, why_it_matters: 'D30 alta es señal clara de PMF y app de monetización viable a largo plazo.', actions: ['<5%: el producto no es habitual todavía. Profundiza en features que hagan que regresen sin push.'] },

    // ─────── Activación ───────
    'activation_rate': {
        title: 'Activation rate',
        short: '% de signups que llegan al "aha moment" (guardar primer outfit IA en 7 días).',
        formula: 'COUNT(users con outfit_saved en primeros 7d) / COUNT(signups) × 100',
        meaning: 'Es la métrica norte (North Star) de tu producto. Define cuántos users encuentran VALOR real en su primer intento.',
        benchmarks: {
            bad: '< 15% (onboarding débil — tu app no entrega valor temprano)',
            good: '25-40% (estándar para consumer apps)',
            great: '> 50% (top tier — la gente "lo entiende" al toque)',
        },
        why_it_matters: 'Sin activación no hay retención. Sin retención no hay revenue. Esta métrica determina TODO el resto del growth.',
        actions: [
            'Si <15%: el onboarding es complejo o no enseña el valor. Acorta el quiz, agrega un outfit auto-generado de bienvenida.',
            'Si 25-40%: estás en buen camino — optimiza con A/B testing pequeño.',
            'Si >50%: empuja por crecimiento — el producto está listo.',
        ]
    },

    // ─────── Marketplace ───────
    'gmv_sold': {
        title: 'GMV vendido',
        short: 'Suma del precio de todos los items que se han vendido.',
        formula: 'SUM(price_mxn) WHERE status = "sold"',
        meaning: 'Es el valor bruto que ha pasado por tu marketplace. NO es tu revenue (a menos que cobres fee).',
        why_it_matters: 'GMV es la métrica de tamaño de tu marketplace. Mientras más GMV, más relevancia tiene tu plataforma.',
        actions: [
            'Si crece: hay liquidez (vendedores Y compradores).',
            'Si listings sube pero GMV no: precios mal calibrados o demanda baja.',
        ]
    },
    'sell_through': {
        title: 'Sell-through rate',
        short: '% de listings que terminan vendiéndose.',
        formula: 'COUNT(sold) / COUNT(total listings) × 100, por categoría',
        meaning: 'Indica qué tan eficiente es tu marketplace por categoría.',
        benchmarks: {
            bad: '< 5%',
            good: '15-30%',
            great: '> 40%',
        },
        why_it_matters: 'Te dice qué categorías son demanda y cuáles son cementerio. Si vestidos venden al 40% y accesorios al 3%, sabes en qué enfocar inventario.',
        actions: [
            'Categorías con <10% sell-through: revisa precios y demanda.',
            'Empuja contenido (posts, IA recs) hacia categorías con sell-through alto.',
        ]
    },
    'aov': {
        title: 'AOV (Average Order Value)',
        short: 'Precio promedio de los items vendidos.',
        formula: 'AVG(price_mxn) WHERE status = "sold"',
        meaning: 'Indica el ticket promedio de tu marketplace.',
        why_it_matters: 'Estrategias de monetización dependen del AOV. Con $100 AOV no puedes cobrar $50 de comisión; con $5,000 sí.',
        actions: [
            'Si baja: los users prefieren items baratos — considera fee porcentual fijo.',
            'Si sube: hay segmento premium — agrega features de listing destacado.',
        ]
    },
    'wishlist_rate': {
        title: 'Wishlist rate',
        short: '% de users que han marcado al menos un favorito.',
        formula: 'COUNT(DISTINCT users con favs) / COUNT(profiles) × 100',
        meaning: 'Señal de intent de compra — antes de comprar, los users guardan en favs.',
        why_it_matters: 'Es un funnel intermedio entre browse y purchase. Si wishlist sube pero ventas no, hay fricción en el checkout.',
        actions: [
            'Si <20%: los users no encuentran items interesantes — mejora el match.',
            'Activación de wishlist → email/push de reduce de precio dispara compras.',
        ]
    },

    // ─────── Social ───────
    'friend_graph_density': {
        title: 'Friend graph density',
        short: 'Qué tan conectada está tu red social. 0% = nadie sigue a nadie. 100% = todos siguen a todos.',
        formula: 'follow_edges / (n_users × (n_users - 1)) × 100',
        meaning: 'Indica si tu plataforma es solo "broadcast" (pocos influencers, muchos lurkers) o "comunidad" (todos interactúan).',
        benchmarks: {
            bad: '< 0.1% (red dispersa)',
            good: '0.5%–3% (saludable)',
            great: '> 5% (comunidad densa tipo Twitter early days)',
        },
        why_it_matters: 'Density alta = engagement orgánico alto. Density baja = dependes mucho de creators.',
        actions: [
            'Si <0.5%: incentiva follows mutuos en onboarding ("sigue a 5 personas").',
            'Si bajo: organiza eventos sociales (challenges, hashtags) que conecten users.',
        ]
    },
    'engagement_rate': {
        title: 'Engagement rate por post',
        short: 'Promedio de likes y comments por post.',
        formula: 'AVG(likes_count) + AVG(comments_count) FROM posts',
        meaning: 'Mide qué tan resonante es el contenido en tu plataforma.',
        why_it_matters: 'Engagement rate alto significa que el algoritmo (o el feed cronológico) muestra contenido relevante a la gente correcta.',
        actions: [
            'Si baja: probable problema con el orden del feed o calidad de contenido.',
            'Trackea engagement por categoría para saber qué tipo de posts funcionan.',
        ]
    },
    'cascade': {
        title: 'Influence cascade',
        short: 'Cuántos seguidores de un influencer postean en las 24h después de que él lo hace.',
        formula: 'Por cada post de user con >10 followers, contar followers que postearon en las 24h siguientes',
        meaning: 'Indica si tu plataforma tiene "viralidad por imitación" — cuando alguien postea, otros se inspiran.',
        why_it_matters: 'Cascade alto = cultura creativa activa. Cascade bajo = la gente solo consume, no crea.',
        actions: [
            'Identifica los influencers con cascade alto — son tus motores de contenido.',
            'Crea features para amplificar su impacto (ej. "Inspired by @X" badges).',
        ]
    },

    // ─────── Quiz / Estilo ───────
    'style_quiz_completion': {
        title: 'Quiz completion rate',
        short: '% de signups que completan el cuestionario de estilo.',
        formula: 'COUNT(users con quiz_completed_at) / COUNT(signups) × 100',
        meaning: 'Mide qué tanta gente se involucra en personalizar su experiencia.',
        benchmarks: {
            bad: '< 30%',
            good: '50-70%',
            great: '> 75%',
        },
        why_it_matters: 'Sin quiz no podemos personalizar. Si baja, las recomendaciones de IA serán genéricas y matarán retención.',
        actions: [
            'Si baja: el quiz es muy largo o aburrido. Acórtalo o mejora las imágenes.',
            'Considera hacer obligatorio el quiz o premiar con badge.',
        ]
    },
    'outfit_acceptance': {
        title: 'Outfit acceptance rate',
        short: '% de outfits IA generados que se guardan (vs rechazar).',
        formula: 'COUNT(outfit_saved) / COUNT(outfit_generated) × 100',
        meaning: 'Indica qué tan bien funciona tu algoritmo de recomendación.',
        benchmarks: {
            bad: '< 20% (mal match — el IA no entiende al user)',
            good: '30-50%',
            great: '> 60% (recomendaciones casi siempre dan en el clavo)',
        },
        why_it_matters: 'Si <20% el feature de outfit IA está fracasando. Si >60% es tu killer feature.',
        actions: [
            'Si bajo: revisa qué estilos están aceptando menos (ver "Acceptance × estilo"). Mejora ese segmento.',
            'Si alto: amplifica este feature con push diario "Outfit del día".',
        ]
    },

    // ─────── Overview adicionales ───────
    'top_events_freq': { title: 'Eventos más frecuentes', short: 'Los 8 eventos con más conteo en los últimos 30 días.', meaning: 'Indica qué acciones dominan el uso de tu app: si screen_view es lo más alto, tu app es de "browsing"; si garment_uploaded es alto, es app de "creación".', why_it_matters: 'Te dice CUÁL FEATURE están realmente usando, no la que tú crees. Si lanzaste el quiz pero nadie lo completa, va a salir aquí.', actions: ['Si un evento crítico (como outfit_saved) no está en el top, hay problema de UX en esa parte del flujo.'] },
    'top_screens_time': { title: 'Top pantallas por tiempo', short: 'En qué pantallas pasan más tiempo total los usuarios.', meaning: 'No solo cuenta visitas — multiplica por duración. Una pantalla que se ve 100 veces 5s tiene 500s; una que se ve 10 veces 1min tiene 600s.', why_it_matters: 'Identifica dónde se queda atrapada la atención. Esa pantalla es tu producto real, las demás son acceso.', actions: ['Esa pantalla DEBE tener experience pulida — mide su scroll, su engagement, sus conversiones.', 'Si Closet tiene 80% del tiempo, eres una "closet app" no una "social app" — ajusta la marca.'] },

    // ─────── Sessions ───────
    'avg_session_duration': { title: 'Duración promedio de sesión', short: 'Cuánto tiempo en promedio dura cada sesión.', meaning: 'Mide profundidad de engagement por visita.', benchmarks: { bad: '< 1 min', good: '2-4 min', great: '> 5 min' }, why_it_matters: 'Una app con sesiones de 30s no genera hábito. >5 min indica que la gente se queda navegando.', actions: ['Si bajo: revisa qué los hace cerrar — push de bienvenida, contenido aburrido, navegación confusa.'] },
    'events_per_session': { title: 'Eventos por sesión', short: 'Cuántas acciones hace un user en cada sesión promedio.', meaning: 'Indica densidad de interacción dentro de una visita.', benchmarks: { bad: '< 5', good: '10-20', great: '> 30' }, why_it_matters: 'Sessions de 30 eventos = user activo explorando. Sesiones de 3 eventos = user que abrió, vio algo, cerró.', actions: ['Sube agregando elementos interactivos (likes, saves) en pantallas con alto tráfico.'] },
    'sessions_list': { title: 'Últimas sesiones', short: 'Las 50 sesiones más recientes con sus stats.', meaning: 'Vista granular para debug — ver qué usuario específico tuvo X comportamiento.', why_it_matters: 'Útil cuando un user específico reporta problema o quieres analizar usuarios prototípicos.', actions: ['Click en cualquier @handle para ir a su detalle individual completo.'] },

    // ─────── Schedules ───────
    'peak_hour': { title: 'Hora pico', short: 'La hora del día con MÁS actividad de tu app.', meaning: 'Indica cuándo está más viva la comunidad.', why_it_matters: 'Esa hora es el mejor momento para enviar push notifications, lanzar features, postear contenido oficial. Si es 9pm tienes audiencia atenta.', actions: ['Programa pushes 30 min ANTES del peak para que llegue justo al inicio de la oleada.', 'Si hace producto-launch, sale a esa hora.'] },
    'time_of_day_buckets': { title: 'Por momento del día', short: 'Distribución de actividad entre mañana / tarde / noche / madrugada.', meaning: 'Indica si tu app es de "salir de casa" (mañana), "trabajo" (tarde) o "leisure" (noche).', why_it_matters: 'Define la personalidad de uso. Apps de moda suelen ser noche-dominantes (la gente decide outfit antes de dormir).', actions: ['Si noche domina: ese feature de "Outfit del día" debería notificar en la noche para mañana.'] },
    'events_by_hour': { title: 'Eventos por hora del día', short: 'Volumen de eventos cada hora 0-23h.', meaning: 'Versión granular del momento del día. Identifica horas específicas pico.', why_it_matters: 'Permite micro-targeting de notifs (no a las 7pm sino a las 8:32pm cuando tienes peak real).', actions: ['Identifica las 3 horas pico y úsalas como windows de comunicación.'] },
    'events_by_dow': { title: 'Eventos por día de la semana', short: 'Cuál día de la semana es más activo.', meaning: 'Patrones semanales. Muchas apps de moda explotan fines de semana (vestirse para salir).', why_it_matters: 'Te dice si tu app es de "diario" (lun-vie equilibrado) o "evento" (viernes-sábado pico).', actions: ['Si tienes pico finde: tu calendar feature ("outfit del día") tiene su mayor uso ahí.'] },
    'heatmap_dow_hour': { title: 'Heatmap día × hora', short: 'Matriz 7×24 con la actividad combinada de cada celda.', meaning: 'La vista MÁS rica del uso. Ej: "Miércoles 8pm" puede ser micro-peak vs el resto de los miércoles.', why_it_matters: 'Identifica patrones específicos invisibles en charts simples. Ej: viernes a las 6pm (planeando salida) puede ser un microsegmento valiosísimo.', actions: ['Las 3-5 celdas más oscuras son tus "horas doradas" — usa eso para push.', 'Las celdas vacías son oportunidades — manda contenido programado.'] },
    'weekday_vs_weekend': { title: 'Entre semana vs fin de semana', short: 'Comparativa total de actividad semana laboral vs fin de semana.', meaning: 'Define si tu app es "weekday tool" (productividad, oficina) o "weekend leisure" (recreación, eventos).', why_it_matters: 'Ateliere espera ser weekend-heavy (la gente planea outfits para salir). Si es weekday-heavy, hay algo interesante por explorar.', actions: ['Si weekend-heavy: empuja stories ahí. Si weekday-heavy: optimiza para "outfit para oficina".'] },
    'top_peak_hours': { title: 'Top 5 horas pico', short: 'Las 5 horas con más eventos, con el evento dominante en cada una.', meaning: 'Granular: te muestra QUÉ están haciendo en sus horas pico (browsing? subiendo? buscando?).', why_it_matters: 'Si a las 10pm el evento top es "post_liked" sabes que es hora de consumo social, no de creación.', actions: ['Diseña features distintos para cada horario peak según lo que ya están haciendo.'] },
    'session_duration_by_hour': { title: 'Duración por hora de inicio', short: 'Qué tan largas son las sesiones según en qué hora arrancan.', meaning: 'Sesiones nocturnas suelen ser más largas (deep engagement). Sesiones de hora-de-oficina son cortas.', why_it_matters: 'Las sesiones largas son las que generan retención. Identifica qué horas las producen.', actions: ['Si sesiones nocturnas largas: agrega contenido "para ver antes de dormir" (mood boards, inspiración).'] },
    'events_by_hour_breakdown': { title: 'Qué se hace cada hora', short: 'Para cada hora del día, los 5 eventos más comunes.', meaning: 'Vista detallada de comportamiento horario. A las 8am: screen_view (revisar). A las 10pm: post_liked (consumir).', why_it_matters: 'Te dice las dinámicas hora-por-hora. Útil para diseñar push notifs contextuales.', actions: ['Si "outfit_saved" sube a la 6am: la gente planea antes de salir. Manda push de outfit a las 5:30am.'] },

    // ─────── Demografía ───────
    'demo_by_age': { title: 'Distribución por edad', short: 'Cuántos users hay en cada bucket de edad.', meaning: 'Mide a quién atrae tu app. Si 80% es 18-24, eres app de Gen Z. Si es 35-44, eres app de millennials con poder de compra.', why_it_matters: 'Determina TODO: precio, tono de marketing, partnerships, monetización.', actions: ['Si edad target no aparece (ej. quieres 25-34 y solo viene <18): hay un mismatch de marketing.'] },
    'demo_by_gender': { title: 'Distribución por género', short: 'Composición de género de tus usuarios.', meaning: 'Apps de moda suelen ser 70-90% femenino. Pero hay nichos masculinos creciendo (streetwear, sneakers).', why_it_matters: 'Define qué marketing y partnerships hacer. Si solo es femenino, tu marketplace debe destacar categorías feministas (vestidos, accesorios).', actions: ['Si <5% masculino: pregunta a tus power male users qué les falta — opportunity gap.'] },
    'demo_by_country': { title: 'Distribución por país', short: 'De qué países son tus users.', meaning: 'Identifica TAM (total addressable market) real vs. esperado.', why_it_matters: 'Ateliere se piensa para México. Si 20% viene de Colombia/Argentina, hay expansión LATAM natural.', actions: ['Si un país sube orgánicamente: localiza UI a esa zona (precios, marcas locales).'] },
    'demo_by_city': { title: 'Top ciudades', short: 'Top 30 ciudades por usuarios.', meaning: 'Las ciudades más representadas en tu base.', why_it_matters: 'CDMX, GDL, MTY suelen dominar México. Si una ciudad inesperada aparece, hay una comunidad orgánica creciendo.', actions: ['Ciudades con engagement alto pero pocos users: oportunidad de marketing local.'] },

    // ─────── Top Users ───────
    'top_users_engagement': { title: 'Top users por engagement', short: 'Ranking de los 100 usuarios más activos.', meaning: 'Tus "power users" — los que dan vida a la app.', why_it_matters: 'Estos users son tus evangelizadores potenciales, betas testers, y los que mejor te dan feedback.', actions: ['Contacta a los top 20 personalmente para feedback.', 'Crea un "Verified" badge o programa de partners con los top 50.'] },

    // ─────── Funnel ───────
    'funnel_onboarding': { title: 'Funnel de onboarding', short: 'Cuántos signups completan cada paso clave.', meaning: 'Signup → Quiz → Demografía → Primera prenda → Primer post.', why_it_matters: 'El "drop-off" entre pasos te dice DÓNDE pierdes users. Si 100% completa signup pero solo 30% sube primera prenda, ahí está el problema.', actions: ['Identifica el paso con mayor drop-off (>30% de pérdida) y rediseña ese flow.', 'A/B test: simplifica el paso o agrega bypass ("subir después").'] },

    // ─────── Styles ───────
    'styles_distribution': { title: 'Distribución de estilos primarios', short: 'Qué estilos predominan en tu base de usuarios.', meaning: 'Si Cottagecore domina, atraes esa estética. Si es Y2K, otra cultura.', why_it_matters: 'Define con qué marcas y celebs hacer partnership, qué contenido publicar oficial, qué influencers contactar.', actions: ['Top 3 estilos: enfoca el catálogo del marketplace y los outfits IA.', 'Estilos minoritarios: o nicho, o problema de match — investiga.'] },

    // ─────── Retention ───────
    'cohort_retention': { title: 'Cohort retention table', short: 'Para cada semana de signup, cuántos vuelven D1, D7, D30.', meaning: 'La métrica más profunda de salud de producto. Si la cohort de hace 2 meses tiene 25% D7 y la última 15%, estás perdiendo gente.', why_it_matters: 'Es el único way para detectar cambios sutiles en retención. Una cohort declinante = problema CRÍTICO.', actions: ['Compara cohorts mes a mes. Si bajan, identifica qué cambió en la app en ese tiempo (nuevo feature, bug, cambio UI).'] },

    // ─────── Social ───────
    'avg_likes_per_post': { title: 'Likes promedio por post', short: 'Likes que recibe un post promedio.', meaning: 'Mide engagement de tu feed.', benchmarks: { bad: '< 5', good: '15-50', great: '> 100' }, why_it_matters: 'Si baja, tu feed no está mostrando el contenido correcto a la gente correcta.', actions: ['Si baja: revisa algoritmo de feed o agrega más contenido fresh.'] },
    'avg_comments_per_post': { title: 'Comentarios promedio', short: 'Comments que recibe un post promedio.', meaning: 'Comments son engagement más profundo que likes — indica conversación real.', why_it_matters: 'Comments altos = comunidad genuina. Likes altos pero comments bajos = "drive-by liking" sin profundidad.', actions: ['Empuja prompts en captions ("¿cuál te gusta más?") para incentivar comments.'] },
    'follower_curve': { title: 'Curva de followers (Pareto)', short: 'Qué % de followers están en el top 5%, 20%, 50% de users.', meaning: 'Si el top 5% tiene 80% de los followers, tu plataforma es "winner-takes-all" (tipo Twitter early). Si es 40%, está más distribuida.', why_it_matters: 'Curva concentrada = pocos influencers dominan. Curva distribuida = comunidad sana, todos aportan.', actions: ['Si muy concentrada: incentiva descubrimiento de creators chicos (algoritmo, tabs "rising").'] },
    'top_influencers': { title: 'Top influencers', short: 'Top 20 usuarios por followers.', meaning: 'Tus mega-creators. Cada uno trae audiencia y contenido.', why_it_matters: 'Mantenerlos felices = mantener la app llena de contenido. Si se van, su audiencia se va con ellos.', actions: ['Contacto directo con los top 10. Verifica que tengan todas las features que necesitan.', 'Crea programa "Atelier Creator" para retenerlos.'] },
    'top_posts_viral': { title: 'Top posts virales', short: 'Los 25 posts con mayor engagement_score en 90 días.', meaning: 'Tu contenido más exitoso. Estudíalo: ¿qué tienen en común?', why_it_matters: 'Decodificar viralidad te permite educar a creators y mejorar tu algoritmo.', actions: ['Identifica patrones: ¿hora de publicación, hashtags, tipo de outfit, brand?', 'Comparte tips con creators basado en lo que funciona.'] },

    // ─────── Stories ───────
    'stories_total_30d': { title: 'Stories totales (30 días)', short: 'Cuántas stories se crearon en el mes.', meaning: 'Volumen de contenido efímero.', why_it_matters: 'Stories indican engagement diario — la gente que crea stories es la más activa.', actions: ['Si baja: stories puede no ser feature core. Considera promocionarlo o re-pensarlo.'] },
    'stories_unique_creators': { title: 'Creadores únicos de stories', short: 'Cuántos users distintos hicieron stories en 30 días.', meaning: 'Mide la base de creators de stories.', why_it_matters: 'Más creators = más diversidad de contenido = mejor experiencia.', actions: ['% creators / total users: si <5% es muy bajo. Promociona el feature.'] },
    'stories_per_day': { title: 'Stories por día', short: 'Volumen diario de stories nuevas.', meaning: 'Patrones de creación.', why_it_matters: 'Fines de semana suelen tener más stories. Si tu pico es martes, hay anomalía.', actions: ['Identifica el día con menos stories y diseña features que lo incentiven (challenges, prompts).'] },

    // ─────── Chat ───────
    'total_conversations': { title: 'Conversaciones totales', short: 'Total de chats 1-on-1 abiertos.', meaning: 'Cada conversación = relación entre 2 users con al menos 1 mensaje.', why_it_matters: 'Volumen de la red de mensajería. Crece con tu base activa.', actions: ['Conversaciones activas / Total = retención de relaciones. Si baja, las amistades se enfrían.'] },
    'active_conversations_7d': { title: 'Conversaciones activas (7d)', short: 'Cuántos chats tuvieron actividad en la última semana.', meaning: 'Solo las relaciones VIVAS, no las dormidas.', why_it_matters: 'Indicador profundo de comunidad: cuántos pares de users realmente se comunican.', actions: ['Si baja: el feature de chat puede estar perdiendo relevancia. Promueve replies a stories, posts.'] },
    'active_conversations_24h': { title: 'Conversaciones últimas 24h', short: 'Chats con actividad en el último día.', meaning: 'El pulso real-time de la comunicación.', why_it_matters: 'Aquí está la actividad social en vivo.', actions: ['Compara con sesiones del día: si las sessions superan mucho los chats, los users no se comunican entre ellos.'] },
    'messages_per_day': { title: 'Mensajes por día', short: 'Volumen diario de mensajes intercambiados.', meaning: 'Pulso de la comunicación.', why_it_matters: 'Mensajes con media (foto/video) son señal de profundidad de conversación.', actions: ['% con media vs total: si muy bajo (<10%), los chats son aburridos, gente solo manda texto.'] },
    'top_messengers': { title: 'Top mensajeros', short: 'Top 20 users que mandan más mensajes.', meaning: 'Los más comunicativos. Su engagement social es alto.', why_it_matters: 'Son tus power-communicators. Si se van, varios chats se mueren.', actions: ['Estos users pueden ser candidatos para features de comunidad (admin de grupos, etc.).'] },

    // ─────── Notifications ───────
    'notifs_opened_per_day': { title: 'Aperturas de push por día', short: 'Cuántas push notifs se tocan diariamente.', meaning: 'Indica si tus notifs son relevantes o spam.', why_it_matters: 'Aperturas crecientes = relevancia. Cayendo = fatiga.', actions: ['Si baja: reduce frecuencia o mejora copy. Personaliza más.'] },
    'push_best_hour': { title: 'Mejor hora para push', short: 'A qué horas los users abren más pushes.', meaning: 'Optimiza el timing de tus campañas.', why_it_matters: 'Mandar push a las 3am cuando todos duermen = 0% open rate. Mandarla a las 7pm cuando van por su outfit = 30%+ open rate.', actions: ['Programa pushes en las top 3 horas identificadas.', 'A/B test: misma campaña, distintas horas.'] },

    // ─────── Search ───────
    'searches_30d': { title: 'Búsquedas (30d)', short: 'Total de búsquedas hechas en el mes.', meaning: 'Indica intent activo de los users.', why_it_matters: 'Búsquedas altas = users explorando con propósito. Bajas = navegación pasiva.', actions: ['Crece search promoviendo "Encuentra @username", "Busca outfits", etc.'] },
    'search_clicks': { title: 'Clicks en resultados', short: 'Cuántas veces tocan un resultado de búsqueda.', meaning: 'Mide si los resultados son relevantes.', why_it_matters: 'Si search es alto pero clicks bajos, tus resultados no son lo que esperaban.', actions: ['Si CTR bajo: mejora el ranking de search.'] },
    'search_ctr': { title: 'Search CTR', short: '% de búsquedas que terminan en click a un resultado.', meaning: 'Eficiencia de tu search.', benchmarks: { bad: '< 30%', good: '50-70%', great: '> 80%' }, why_it_matters: 'CTR bajo = users frustrados. Cada búsqueda fallida es un near-churn moment.', actions: ['<30%: rediseña algoritmo de ranking. Considera fuzzy match, sinónimos.'] },
    'top_searches': { title: 'Top búsquedas', short: 'Las queries más populares.', meaning: 'Te dice qué QUIEREN encontrar los users.', why_it_matters: 'Si una query es muy popular, hay demanda inexplotada. Si buscan "@vestidos rosas" y no hay resultados, oportunidad gigante.', actions: ['Top queries → asegúrate que tienen excelentes resultados.', 'Considera convertirlas en sub-páginas dedicadas.'] },
    'zero_click_searches': { title: 'Búsquedas sin click', short: 'Queries que los users buscan pero NO clickean nada.', meaning: 'Gap entre demanda y oferta. El user buscó algo y no le gustó nada.', why_it_matters: 'Cada query sin click es una oportunidad PERDIDA. El user iba a comprar/save pero se fue con las manos vacías.', actions: ['Top "sin click" → enfoca content sourcing en esas categorías.', 'Si buscan "blazer azul" sin click, agrega más blazers azules al marketplace.'] },

    // ─────── Outfit IA adicionales ───────
    'outfit_generated_count': { title: 'Outfits generados', short: 'Cuántas veces la IA propuso un outfit.', meaning: 'Volumen de uso del feature de outfit IA.', why_it_matters: 'Si genera mucho pero se guardan pocos, el algoritmo necesita mejora.', actions: ['Combina con save_rate para entender el funnel completo.'] },
    'outfit_saved_count': { title: 'Outfits guardados', short: 'De los generados, cuántos se guardaron con corazón.', meaning: 'Conversion del feature.', why_it_matters: 'Cada save es un voto de confianza al algoritmo.', actions: ['Usa los saved outfits para entrenar mejor el matching.'] },
    'outfit_rejected_count': { title: 'Outfits rechazados', short: 'Cuántos outfits IA fueron descartados (swipe left).', meaning: 'Señal negativa del algoritmo.', why_it_matters: 'Estudia patrones: ¿se rechazan más en ciertas combinaciones? ¿En cierto clima?', actions: ['Si muchos se rechazan a la primera: el algoritmo está fallando en el primer impacto.'] },
    'outfit_acceptance_by_style': { title: 'Acceptance × estilo', short: 'Save rate desglosado por estilo primario del user.', meaning: 'Si Cottagecore tiene 60% save y Punk 10%, tu algoritmo no entiende Punk.', why_it_matters: 'Identifica estilos donde tu IA es DEBIL. Ahí necesitas más data o reglas custom.', actions: ['Estilos con <20%: revisar manualmente outfits generados y entender el problema.'] },
    'style_evolution': { title: 'Style evolution', short: 'Cuántos users han re-tomado el quiz y cuántos estilos distintos.', meaning: 'Indica si los estilos del catálogo son suficientemente distintos o si la gente se re-clasifica.', why_it_matters: 'Si muchos re-toman el quiz, hay desconfianza del resultado inicial.', actions: ['Si pocos re-toman: o están satisfechos o no saben que pueden hacerlo. Promueve "Mi estilo evoluciona — re-toma el quiz".'] },

    // ─────── Quiz drop-off ───────
    'quiz_dropoff': { title: 'Drop-off por pregunta', short: 'En qué pregunta del quiz abandonan más users.', meaning: 'Identifica la "pregunta problemática".', why_it_matters: 'Cada drop-off es un user que NO completará el quiz, por ende menos personalización, por ende menos retención.', actions: ['Pregunta con drop-off alto: simplifícala, agrega skip option, o muévela al final.'] },
    'style_combinations': { title: 'Combinaciones primary × secondary', short: 'Las parejas de estilos más comunes en la base.', meaning: 'Te dice qué fusiones existen (ej. "Cottagecore + Coquette" es común).', why_it_matters: 'Permite crear filtros, etiquetas y partnerships específicos a esos cruces.', actions: ['Top combinaciones: empújalas como "Looks Cottagecoquette" o similar.'] },

    // ─────── Engagement profundo ───────
    'garment_view_duration_dist': { title: 'Distribución de tiempo viendo prendas', short: 'Cómo se reparte el tiempo en cada vista de detalle.', meaning: '<2s = click accidental. 5-15s = exploración. >30s = contemplación profunda.', why_it_matters: 'La proporción te dice qué tan "atrapante" es tu detalle de prenda.', actions: ['Si <2s domina: la fotografía o el primer vistazo no convence. Mejora hero image.'] },
    'feed_scroll_depth': { title: 'Scroll depth en Feed', short: '% del feed que recorren antes de salir.', meaning: 'Mide engagement profundo del feed.', why_it_matters: 'Si llegan al 100% es señal de hambre por contenido. Si paran al 25%, el contenido pierde calidad o se vuelve repetitivo.', actions: ['Si bajo: revisar orden del feed, agregar más diversidad de contenido.'] },
    'top_garments_dwell': { title: 'Top prendas por tiempo total', short: 'Las prendas con más tiempo acumulado de visualización.', meaning: 'Las "stars" del catálogo.', why_it_matters: 'Estas prendas captan atención. Estúdialas — sus fotos, descripción, brand, precio.', actions: ['Aplica mismas características a otras prendas para subir su atractivo.'] },
    'refresh_freq': { title: 'Pull-to-refresh', short: 'Cuántas veces hacen pull-to-refresh por pantalla.', meaning: 'Indica expectativa de contenido fresco.', why_it_matters: 'Refresh alto = los users ESPERAN ver novedades. Si no ven nada nuevo, frustración.', actions: ['Si feed tiene mucho refresh: necesitas contenido más freshly. Crea más posts oficiales o promociona creators activos.'] },

    // ─────── Clóset ───────
    'avg_garments_per_user': { title: 'Promedio de prendas/user', short: 'Cuántas prendas tiene un usuario promedio en su clóset.', meaning: 'Mide qué tan "comprometido" está cada user con la app.', benchmarks: { bad: '< 5', good: '15-50', great: '> 100' }, why_it_matters: 'Más prendas = más datos para personalizar = mejor IA. Y más fricción para irse.', actions: ['Promueve upload masivo en onboarding ("sube 10 prendas y desbloquea X").'] },
    'median_garments': { title: 'Mediana de prendas', short: 'El user "típico" tiene exactamente X prendas.', meaning: 'Más representativo que el promedio (que puede distorsionar con outliers).', why_it_matters: 'Si la mediana es 3 pero el promedio es 30, hay pocos super-uploaders y muchos que apenas suben.', actions: ['Si mediana muy baja, foco en hacer upload menos friccioso.'] },
    'power_closets': { title: 'Power closets (>50 prendas)', short: 'Cuántos users tienen más de 50 prendas.', meaning: 'Tus "super-users" de clóset.', why_it_matters: 'Son los más invertidos en la app. Su retention es altísima.', actions: ['Crea features VIP para ellos (filtros avanzados, estadísticas personales, AI insights).'] },
    'empty_closets': { title: 'Clósets vacíos', short: 'Users que NO han subido ni una prenda.', meaning: 'Signups que nunca activaron.', why_it_matters: 'Cada uno es un near-churn. La app no les dio valor.', actions: ['Push agresivo a los con 0 prendas en sus primeros 3 días.', 'Onboarding con "sube tu primera prenda en 30 segundos".'] },
    'top_brands': { title: 'Top marcas', short: 'Las marcas más representadas en los clósets.', meaning: 'Tu base demográfica revelada.', why_it_matters: 'Si Zara, H&M dominan = audiencia mass market. Si dominan Off-White, Acne = luxury audience.', actions: ['Top marcas: partnerships, contenido oficial, filtros pre-curados.'] },
    'garment_categories_dist': { title: 'Categorías de prendas', short: 'Cuántas prendas hay de cada categoría.', meaning: 'Composición del clóset agregado.', why_it_matters: 'Si vestidos > 50%, eres app femenina-formal. Si jeans/tees dominan, casual streetwear.', actions: ['Categorías subrepresentadas: oportunidad de incentivar upload o agregar al marketplace.'] },
    'top_colors_dist': { title: 'Colores dominantes', short: 'Paleta colectiva del clóset de la app.', meaning: 'Identifica el "color zeitgeist" de tu audiencia.', why_it_matters: 'Si negro/blanco dominan = base minimalista. Si abundan pasteles = romantic/coquette base.', actions: ['Top colores → curaduría editorial ("Lo más visto en negro").'] },
    'render_stats': { title: 'Render IA status', short: 'Cuántas prendas se procesaron OK, en proceso, o fallaron.', meaning: 'Salud técnica del pipeline de IA de prendas.', why_it_matters: 'Si % failed es alto, las prendas se ven mal y los users se frustran.', actions: ['% failed > 5%: hay un bug en el rendering pipeline. Investiga inmediato.'] },

    // ─────── Marketplace adicionales ───────
    'gmv_listed': { title: 'GMV listado', short: 'Valor total de items listados (no necesariamente vendidos).', meaning: 'Capacidad bruta del marketplace.', why_it_matters: 'Inventario disponible — qué tanto se ofrece.', actions: ['GMV listado / GMV vendido = ratio. Si muy alto, hay inventario sin demanda.'] },
    'active_listings': { title: 'Listings activos', short: 'Items disponibles para comprar AHORA.', meaning: 'El "shop floor" en vivo.', why_it_matters: 'Más listings activos = más opciones para compradores.', actions: ['Si baja, los vendedores están desactivos. Promueve listings nuevos.'] },
    'sold_listings': { title: 'Listings vendidos', short: 'Total acumulado de ventas.', meaning: 'Tu volumen de transacciones.', why_it_matters: 'Ventas = liquidez = network effects que atraen más vendedores.', actions: ['Promueve ventas para crear más velocity.'] },
    'avg_days_to_sell': { title: 'Promedio listing → venta', short: 'Cuánto tarda un item promedio en venderse.', meaning: 'Velocidad del marketplace.', why_it_matters: 'Marketplaces saludables venden en días, no meses.', actions: ['Si >30 días: pricing alto o falta de demanda. Sugiere bajar precios.'] },
    'sell_through_by_category': { title: 'Sell-through por categoría', short: 'Qué % de cada categoría se vende.', meaning: 'Identifica qué se mueve y qué se queda.', why_it_matters: 'Te dice dónde la demanda existe.', actions: ['Categorías con sell-through bajo: o muy oferta sin demanda, o pricing alto.'] },
    'price_buckets': { title: 'Distribución de precios', short: 'Cómo se reparten los precios de listings.', meaning: 'Indica el rango económico del marketplace.', why_it_matters: 'Si todo es <$500, eres bargain marketplace. Si todo es >$2,000, eres premium.', actions: ['El bucket más popular es tu sweet spot — promociónalo.'] },
    'top_sellers': { title: 'Top vendedores', short: 'Top 20 users por items vendidos.', meaning: 'Tus "tiendas" estrella.', why_it_matters: 'Estos crean la mayoría del GMV. Cuidarlos = cuidar el marketplace.', actions: ['Programa de "Vendedor verificado" con badge + algoritmo boost.'] },

    // ─────── Commerce Deep ───────
    'median_sold_price': { title: 'Mediana de precio vendido', short: 'El item "típico" se vendió a este precio.', meaning: 'Más robusto que el promedio.', why_it_matters: 'Si AOV es $1,500 pero mediana es $300, hay outliers caros. La realidad es que la mayoría compra a $300.', actions: ['Optimiza la experiencia para el segmento de la mediana, no del promedio.'] },
    'resale_rate': { title: 'Resale rate', short: '% de items que se re-listan después de vendidos.', meaning: 'Indica si tu marketplace tiene "second-hand of second-hand" — items que circulan.', why_it_matters: 'Circular fashion mature = misma prenda pasa por varias manos. Cada re-listing es revenue adicional.', actions: ['Promueve "Vende lo que compraste aquí" para incentivar circulación.'] },
    'brand_affinity': { title: 'Brand affinity matrix', short: 'Marcas que coexisten en el mismo clóset.', meaning: 'Cross-sell oportunidad: si A y B siempre van juntas, recomienda B a quien tiene A.', why_it_matters: 'Te da el grafo de marcas tipo "los que compran Reformation también compran Ganni".', actions: ['Top pares: crea bundles, filtros pre-curados, push de "Combina X con Y".'] },
    'power_buyers': { title: 'Power buyers (por favoritos)', short: 'Users con más items en wishlist.', meaning: 'Intent de compra acumulado.', why_it_matters: 'Cada uno representa decenas de potential purchases.', actions: ['Email con "Tu wishlist te espera" o reducción de precio en sus favoritos.'] },

    // ─────── Social Graph ───────
    'total_follow_edges': { title: 'Total follow edges', short: 'Total de relaciones "X sigue a Y".', meaning: 'Volumen del grafo social.', why_it_matters: 'Cada edge es una conexión que activa engagement (likes, shares).', actions: ['Crece con onboarding de "sigue a 5 personas" o "amigos sugeridos".'] },
    'avg_follows_per_user': { title: 'Avg follows/user', short: 'Cuántas personas sigue un user promedio.', meaning: 'Mide qué tan involucrado está cada user con la red.', why_it_matters: 'Si <3, los users no exploran. Si >50, hay descubrimiento sano.', actions: ['Promueve "Personas que te pueden gustar" en home.'] },
    'dm_response_time': { title: 'DM response time', short: 'Cuánto tardan en responder DMs.', meaning: 'Velocidad de comunicación.', benchmarks: { bad: '> 1 hora', good: '5-30 min', great: '< 5 min' }, why_it_matters: 'Indica engagement profundo. Respuestas rápidas = users que tienen la app abierta.', actions: ['Si lento: agrega notification badges en chat para acelerar.'] },
    'story_reply_rate': { title: 'Story reply rate', short: '% de stories que generan respuesta.', meaning: 'Mide si las stories disparan conversación o solo se ven.', why_it_matters: 'Stories que generan replies son las que crean comunidad real.', actions: ['Promueve story formats que inviten reply ("¿qué piensas de este look?").'] },
    'influence_cascade': { title: 'Influence cascade detallado', short: 'Para cada influencer, cuántos followers postean después de ellos.', meaning: 'El "efecto X" de cada creator.', why_it_matters: 'Identifica creators con verdadero impacto vs los que solo tienen followers vanity.', actions: ['Top de cascade: tus verdaderos influencers — colaboraciones, sponsors.'] },

    // ─────── Cross-tabs ───────
    'age_by_budget': { title: 'Edad × presupuesto', short: 'Qué edades tienen qué presupuestos.', meaning: 'Los <25 suelen tener presupuesto bajo, 35+ presupuestos altos.', why_it_matters: 'Define qué tier de precio mostrar a cada edad.', actions: ['Personaliza el marketplace por edad: <25 → items económicos prominentes.'] },
    'gender_by_category': { title: 'Género × categoría', short: 'Qué categorías predominan por género.', meaning: 'Identifica skews demográficos por categoría.', why_it_matters: 'Si "vestidos" es 95% femenino, casi no tienes inventario "andrógino". Decide si abrir o focalizar.', actions: ['Categorías muy sesgadas: o focaliza o agrega variantes para otros géneros.'] },
    'style_by_region_mx': { title: 'Estilo × región (MX)', short: 'Qué estilos dominan por estado.', meaning: 'Norte vs Sur, costas vs interior — culturas distintas, estilos distintos.', why_it_matters: 'Permite hyper-localización del catálogo.', actions: ['CDMX vs Mérida: contenido y marketplace adaptado regionalmente.'] },
    'color_affinity_table': { title: 'Color affinity', short: 'Para cada color: cuántos hay en clósets vs cuántas veces se han usado.', meaning: 'Utilización por color. Si tienes 100 negros pero solo 30 se han usado, hay closet underutilization.', why_it_matters: 'Identifica colores que la gente compra pero no usa — gasto sin uso.', actions: ['Sugiere outfits con colores subutilizados ("redescubre tu beige").'] },
    'crosstab_demo': { title: 'Cross-tab demográfico', short: 'Combinación de género × edad × país con engagement promedio.', meaning: 'Vista master para identificar segmentos.', why_it_matters: 'Tu segmento más rentable está aquí (ej. "femenino · 25-34 · México · engagement 75").', actions: ['Foco de marketing y producto al segmento con mayor engagement promedio.'] },
    'app_version_dist': { title: 'App version distribution', short: 'Qué versiones de la app están corriendo activamente.', meaning: 'Te dice quién está atrasado.', why_it_matters: 'Si lanzaste una feature en v2.5 y 60% sigue en v2.0, esa feature NO se está usando.', actions: ['Force update si la diferencia es grande (>30% en versión vieja).', 'Promueve update con changelog visible.'] },

    // ─────── Growth ───────
    'growth_daily': { title: 'Growth diario (DoD)', short: 'Nuevos signups por día con % de cambio respecto al día anterior.', meaning: 'Pulso de adquisición.', why_it_matters: 'Te dice si tu marketing y referrals están funcionando.', actions: ['DoD volátil: normal. WoW negativo consistente: hay problema de adquisición.'] },
    'growth_weekly': { title: 'Growth semanal (WoW)', short: 'Crecimiento semana a semana.', meaning: 'Más estable que diario, mide tendencia real.', why_it_matters: 'WoW de +10% sostenido = hipercrecimiento. -5% sostenido = declive.', actions: ['Identifica cambios en la app correlacionados con cambios en growth.'] },
    'growth_monthly': { title: 'Growth mensual (MoM)', short: 'Crecimiento mes a mes.', meaning: 'La métrica más robusta de tendencia macro.', why_it_matters: 'MoM positivo consistente = trayectoria saludable. Decline = pivot needed.', actions: ['Comparte MoM con stakeholders/inversionistas como métrica principal.'] },

    // ─────── Geographic ───────
    'users_by_state_mx': { title: 'Usuarios por estado (MX)', short: 'Cuántos users por cada estado de México + activos últimos 7d.', meaning: 'Densidad geográfica.', why_it_matters: 'Identifica estados con potential vs estados saturados.', actions: ['Estados con muchos users pero pocos activos: campaña de reactivación local.'] },
    'engagement_by_country': { title: 'Engagement por país', short: 'Cuántos events/user en cada país.', meaning: 'Países con users más engaged.', why_it_matters: 'Países con engagement alto: invierte en expansión ahí.', actions: ['Si país externo (Colombia, etc.) tiene engagement alto: prepara entrada formal a ese mercado.'] },

    // ─────── User detail ───────
    'user_total_events': { title: 'Total eventos (user)', short: 'Suma de eventos que este user ha generado en toda su vida en la app.', meaning: 'Indica qué tan activo es este individuo.', why_it_matters: 'Compara contra el promedio: este user está sobre/debajo.', actions: ['Si muy activo: candidato a beta tester, embajador.', 'Si muy poco: en riesgo de churn — push de re-engagement.'] },
    'user_total_sessions': { title: 'Total sesiones (user)', short: 'Cuántas veces ha abierto la app este user.', meaning: 'Hábito formado o no.', why_it_matters: 'Indica frecuencia de uso individual.', actions: ['Sessions / días desde signup = frecuencia. Si >0.5/día = uso casi diario.'] },
    'user_total_time': { title: 'Tiempo total (user)', short: 'Cuánto tiempo lleva este user usando la app acumulado.', meaning: 'Inversión de tiempo personal.', why_it_matters: 'Tiempo invertido = compromiso con la app.', actions: ['Top users por tiempo: candidatos a entrevista de UX.'] },
    'user_days_active': { title: 'Días activos (user)', short: 'En cuántos días distintos abrió la app.', meaning: 'Consistencia.', why_it_matters: 'Más días activos = más hábito.', actions: ['Si días activos > 50% desde signup: super-user.'] },

    // ─────── Genéricos ───────
    'generic_count': {
        title: 'Conteo simple',
        short: 'Cantidad total — explícito en el nombre del card.',
        meaning: 'Métrica de volumen. Útil para ver tendencias en el tiempo.',
        why_it_matters: 'Combina con su evolución (Growth tab) para entender si crece, baja o se estanca.',
    }
};

// ============================================================================
// Info modal context — para abrir desde cualquier Card/KpiCard
// ============================================================================
const InfoModalContext = React.createContext({ open: (key) => {} });

function useInfoModal() {
    return React.useContext(InfoModalContext);
}

function InfoModalProvider({ children }) {
    const [activeKey, setActiveKey] = useState(null);
    const info = activeKey ? METRIC_INFO[activeKey] : null;

    return (
        <InfoModalContext.Provider value={{ open: setActiveKey }}>
            {children}
            {info && <InfoModal info={info} onClose={() => setActiveKey(null)} />}
        </InfoModalContext.Provider>
    );
}

/// Panel lateral que aparece desde la derecha con la explicación de la métrica.
function InfoModal({ info, onClose }) {
    // Cerrar con ESC
    useEffect(() => {
        const onKey = (e) => { if (e.key === 'Escape') onClose(); };
        window.addEventListener('keydown', onKey);
        return () => window.removeEventListener('keydown', onKey);
    }, [onClose]);

    return (
        <>
            {/* Backdrop semi-transparente */}
            <div onClick={onClose}
                className="fixed inset-0 z-40"
                style={{ background: 'rgba(14, 38, 32, 0.6)' }}>
            </div>

            {/* Panel desde la derecha */}
            <div className="fixed top-0 right-0 bottom-0 w-[460px] z-50 overflow-y-auto shadow-2xl"
                style={{
                    background: '#1B3D32',
                    borderLeft: '1px solid rgba(255,255,255,0.12)',
                    animation: 'slideIn 0.25s ease-out'
                }}>
                <style>{`
                    @keyframes slideIn {
                        from { transform: translateX(100%); }
                        to { transform: translateX(0); }
                    }
                `}</style>
                <div className="p-6">
                    {/* Close */}
                    <button onClick={onClose}
                        className="absolute top-4 right-4 w-8 h-8 rounded-full hover:bg-bg2 flex items-center justify-center text-muted hover:text-ink transition-colors">
                        ✕
                    </button>

                    {/* Title */}
                    <div className="text-[9px] uppercase tracking-[0.2em] text-accent font-semibold mb-2">
                        Sobre esta métrica
                    </div>
                    <h2 className="font-editorial text-2xl text-ink mb-3 pr-8 leading-tight">
                        {info.title}
                    </h2>
                    <p className="text-sm text-ink2 mb-6 leading-relaxed">{info.short}</p>

                    {/* Sections */}
                    {info.formula && (
                        <InfoSection title="Cómo se calcula">
                            <code className="block text-xs bg-bg2 px-3 py-2 rounded font-mono text-ink2 leading-relaxed">
                                {info.formula}
                            </code>
                        </InfoSection>
                    )}

                    {info.meaning && (
                        <InfoSection title="Qué significa">
                            <p className="text-sm text-ink2 leading-relaxed">{info.meaning}</p>
                        </InfoSection>
                    )}

                    {info.benchmarks && (
                        <InfoSection title="Rangos típicos">
                            <div className="space-y-2 text-sm">
                                {info.benchmarks.bad && (
                                    <div className="flex items-start gap-2">
                                        <div className="w-1.5 h-1.5 rounded-full bg-like mt-1.5 flex-shrink-0"></div>
                                        <div><strong className="text-like">Malo:</strong> <span className="text-ink2">{info.benchmarks.bad}</span></div>
                                    </div>
                                )}
                                {info.benchmarks.good && (
                                    <div className="flex items-start gap-2">
                                        <div className="w-1.5 h-1.5 rounded-full bg-accent mt-1.5 flex-shrink-0"></div>
                                        <div><strong className="text-accent">Bueno:</strong> <span className="text-ink2">{info.benchmarks.good}</span></div>
                                    </div>
                                )}
                                {info.benchmarks.great && (
                                    <div className="flex items-start gap-2">
                                        <div className="w-1.5 h-1.5 rounded-full bg-gold mt-1.5 flex-shrink-0"></div>
                                        <div><strong className="text-gold">Excelente:</strong> <span className="text-ink2">{info.benchmarks.great}</span></div>
                                    </div>
                                )}
                            </div>
                        </InfoSection>
                    )}

                    {info.why_it_matters && (
                        <InfoSection title="Por qué importa">
                            <p className="text-sm text-ink2 leading-relaxed">{info.why_it_matters}</p>
                        </InfoSection>
                    )}

                    {info.actions && info.actions.length > 0 && (
                        <InfoSection title="Qué hacer">
                            <ul className="space-y-2">
                                {info.actions.map((a, i) => (
                                    <li key={i} className="text-sm text-ink2 flex items-start gap-2 leading-relaxed">
                                        <span className="text-accent flex-shrink-0">→</span>
                                        <span>{a}</span>
                                    </li>
                                ))}
                            </ul>
                        </InfoSection>
                    )}
                </div>
            </div>
        </>
    );
}

function InfoSection({ title, children }) {
    return (
        <div className="mb-5">
            <div className="text-[9px] uppercase tracking-[0.2em] text-muted font-semibold mb-2">
                {title}
            </div>
            {children}
        </div>
    );
}

// ============================================================================
// Components compartidos
// ============================================================================
function PageHeader({ title, subtitle }) {
    return (
        <div className="mb-5">
            <h1 className="font-editorial text-[28px] text-ink leading-tight">{title}</h1>
            {subtitle && <p className="text-muted text-xs mt-1">{subtitle}</p>}
        </div>
    );
}

function Card({ title, children, className = '', infoKey }) {
    const { open } = useInfoModal();
    const isClickable = !!infoKey && !!METRIC_INFO[infoKey];

    return (
        <div
            onClick={isClickable ? () => open(infoKey) : undefined}
            className={`bg-card rounded-xl border border-line p-4 transition-all ${
                isClickable ? 'cursor-pointer hover:border-accent/40 hover:shadow-lg' : ''
            } ${className}`}>
            {title && (
                <div className="text-[10px] uppercase tracking-[0.15em] text-muted font-semibold mb-3">
                    {title}
                </div>
            )}
            {children}
        </div>
    );
}

function KpiCard({ label, value, delta, deltaPositive, infoKey }) {
    const { open } = useInfoModal();
    const isClickable = !!infoKey && !!METRIC_INFO[infoKey];

    return (
        <div
            onClick={isClickable ? () => open(infoKey) : undefined}
            className={`bg-card rounded-xl border border-line p-4 transition-all ${
                isClickable ? 'cursor-pointer hover:border-accent/40 hover:shadow-lg' : ''
            }`}>
            <div className="text-[10px] uppercase tracking-wider text-muted font-semibold">{label}</div>
            <div className="font-editorial text-3xl text-ink mt-2">{value}</div>
            {delta && (
                <div className={`text-xs mt-1 ${deltaPositive ? 'text-green' : 'text-red-600'}`}>
                    {delta} vs ayer
                </div>
            )}
        </div>
    );
}

function CenteredLoader({ text }) {
    return (
        <div className="min-h-screen flex items-center justify-center">
            <div className="text-center">
                <div className="inline-block w-8 h-8 border-2 border-accent border-t-transparent rounded-full animate-spin mb-3"></div>
                <div className="text-sm text-muted">{text}</div>
            </div>
        </div>
    );
}

// ============================================================================
// Helpers
// ============================================================================
function formatTimeAgo(timestamp) {
    const d = new Date(timestamp);
    const diff = (Date.now() - d.getTime()) / 1000;
    if (diff < 60) return `${Math.floor(diff)}s`;
    if (diff < 3600) return `${Math.floor(diff / 60)}m`;
    if (diff < 86400) return `${Math.floor(diff / 3600)}h`;
    if (diff < 86400 * 7) return `${Math.floor(diff / 86400)}d`;
    return d.toLocaleDateString('es-MX', { day: 'numeric', month: 'short' });
}

function formatDuration(seconds) {
    seconds = Number(seconds) || 0;
    if (seconds < 60) return `${Math.round(seconds)}s`;
    if (seconds < 3600) return `${Math.round(seconds / 60)}m`;
    const h = Math.floor(seconds / 3600);
    const m = Math.round((seconds % 3600) / 60);
    return `${h}h ${m}m`;
}

// ============================================================================
// Page: Demographics (edad, género, ubicación)
// ============================================================================
function DemographicsPage() {
    const [byAge, setByAge] = useState([]);
    const [byGender, setByGender] = useState([]);
    const [byCountry, setByCountry] = useState([]);
    const [byCity, setByCity] = useState([]);
    const [loading, setLoading] = useState(true);

    useEffect(() => {
        (async () => {
            const [a, g, c, ci] = await Promise.all([
                supa.from('analytics_users_by_age').select('*'),
                supa.from('analytics_users_by_gender').select('*'),
                supa.from('analytics_users_by_country').select('*'),
                supa.from('analytics_users_by_city').select('*').limit(15),
            ]);
            setByAge(a.data || []);
            setByGender(g.data || []);
            setByCountry(c.data || []);
            setByCity(ci.data || []);
            setLoading(false);
        })();
    }, []);

    if (loading) return <CenteredLoader text="Cargando demografía..." />;

    const ageLabels = {
        'under_18': '< 18', '18_24': '18-24', '25_34': '25-34',
        '35_44': '35-44', '45_54': '45-54', '55_64': '55-64',
        '65_plus': '65+', 'unknown': 'No especificada'
    };
    const genderLabels = {
        'femenino': 'Femenino', 'masculino': 'Masculino',
        'no_binario': 'No binario', 'otro': 'Otro',
        'prefiero_no_decir': 'Prefiere no decir',
        'no_especificado': 'No especificado'
    };

    return (
        <div className="p-8 max-w-7xl mx-auto space-y-4">
            <PageHeader title="Demografía" subtitle="Distribución de tus usuarios" />

            <div className="grid grid-cols-1 md:grid-cols-2 gap-4 items-start">
                <Card title="Por edad" infoKey="demo_by_age">
                    {byAge.length === 0 ? <EmptyState text="Sin data demográfica aún" /> :
                        <DistributionBars data={byAge.map(r => ({
                            label: ageLabels[r.age_bucket] || r.age_bucket,
                            value: Number(r.user_count)
                        }))} />}
                </Card>
                <Card title="Por género" infoKey="demo_by_gender">
                    {byGender.length === 0 ? <EmptyState text="Sin data demográfica aún" /> :
                        <DistributionBars data={byGender.map(r => ({
                            label: genderLabels[r.gender] || r.gender,
                            value: Number(r.user_count)
                        }))} />}
                </Card>
            </div>

            <div className="grid grid-cols-1 md:grid-cols-2 gap-4 items-start mt-4">
                <Card title="Por país" infoKey="demo_by_country">
                    {byCountry.length === 0 ? <EmptyState text="Sin data" /> :
                        <DistributionBars data={byCountry.map(r => ({
                            label: `${r.country_code ? r.country_code + ' ' : ''}${r.country}`,
                            value: Number(r.user_count)
                        }))} />}
                </Card>
                <Card title="Top ciudades" infoKey="demo_by_city">
                    {byCity.length === 0 ? <EmptyState text="Sin data" /> :
                        <DistributionBars data={byCity.map(r => ({
                            label: `${r.city}${r.state ? ', ' + r.state : ''}`,
                            value: Number(r.user_count)
                        }))} />}
                </Card>
            </div>
        </div>
    );
}

// ============================================================================
// Page: Retention (cohort heatmap D1/D7/D30)
// ============================================================================
function RetentionPage() {
    const [cohorts, setCohorts] = useState([]);
    const [loading, setLoading] = useState(true);

    useEffect(() => {
        (async () => {
            const { data } = await supa.from('analytics_retention_cohorts').select('*');
            setCohorts(data || []);
            setLoading(false);
        })();
    }, []);

    if (loading) return <CenteredLoader text="Cargando retención..." />;

    return (
        <div className="p-8 max-w-7xl mx-auto space-y-4">
            <PageHeader title="Retención por cohort" subtitle="Cuántos users vuelven después de N días desde signup" />

            <Card title="Cohort retention (últimas 12 semanas)" infoKey="cohort_retention">
                {cohorts.length === 0 ? <EmptyState text="Necesitas más datos para ver cohorts" /> : (
                    <div className="overflow-x-auto">
                        <table className="w-full text-sm">
                            <thead>
                                <tr className="border-b border-line text-[10px] uppercase tracking-wider text-muted">
                                    <th className="text-left p-2 font-semibold">Semana de signup</th>
                                    <th className="text-right p-2 font-semibold">Tamaño cohort</th>
                                    <th className="text-right p-2 font-semibold">D0</th>
                                    <th className="text-right p-2 font-semibold">D1</th>
                                    <th className="text-right p-2 font-semibold">D7</th>
                                    <th className="text-right p-2 font-semibold">D30</th>
                                </tr>
                            </thead>
                            <tbody>
                                {cohorts.map(c => {
                                    const pct = (n) => c.total_in_cohort > 0 ? (n / c.total_in_cohort * 100).toFixed(0) : 0;
                                    return (
                                        <tr key={c.cohort_week} className="border-b border-line/40">
                                            <td className="p-2 font-semibold">
                                                {new Date(c.cohort_week).toLocaleDateString('es-MX', { day: 'numeric', month: 'short', year: '2-digit' })}
                                            </td>
                                            <td className="p-2 text-right">{c.total_in_cohort}</td>
                                            <RetentionCell value={c.day_0} pct={pct(c.day_0)} />
                                            <RetentionCell value={c.day_1} pct={pct(c.day_1)} />
                                            <RetentionCell value={c.day_7} pct={pct(c.day_7)} />
                                            <RetentionCell value={c.day_30} pct={pct(c.day_30)} />
                                        </tr>
                                    );
                                })}
                            </tbody>
                        </table>
                    </div>
                )}
            </Card>

            <div className="mt-4 p-4 bg-bg2 rounded-lg text-xs text-muted">
                💡 <strong>Cómo leer:</strong> cada fila es un grupo de users que se registraron esa semana. D1 = cuántos volvieron al día siguiente, D7 = una semana, D30 = al mes. Buena retención = ~30% D1, ~15% D7, ~5% D30.
            </div>
        </div>
    );
}

function RetentionCell({ value, pct }) {
    const intensity = Math.min(pct / 100, 1);
    return (
        <td className="p-2 text-right">
            <div className="inline-block px-2 py-1 rounded text-xs font-semibold"
                style={{
                    background: `rgba(189, 153, 98, ${intensity * 0.8})`,
                    color: intensity > 0.5 ? '#0E2620' : '#F5EDDE'
                }}>
                {value} ({pct}%)
            </div>
        </td>
    );
}

// ============================================================================
// Page: Top Users (engagement score ranking)
// ============================================================================
function TopUsersPage() {
    const [users, setUsers] = useState([]);
    const [loading, setLoading] = useState(true);

    useEffect(() => {
        (async () => {
            const { data } = await supa.from('analytics_top_users').select('*');
            setUsers(data || []);
            setLoading(false);
        })();
    }, []);

    if (loading) return <CenteredLoader text="Cargando..." />;

    return (
        <div className="p-8 max-w-7xl mx-auto space-y-4">
            <PageHeader title="Top Users por engagement" subtitle="Score 0-100 calculado con actividad reciente" />

            <Card>
                <table className="w-full text-sm">
                    <thead>
                        <tr className="border-b border-line text-[10px] uppercase tracking-wider text-muted">
                            <th className="text-left p-2 font-semibold">#</th>
                            <th className="text-left p-2 font-semibold">Usuario</th>
                            <th className="text-right p-2 font-semibold">Score</th>
                            <th className="text-right p-2 font-semibold">Eventos</th>
                            <th className="text-right p-2 font-semibold">Sesiones</th>
                            <th className="text-right p-2 font-semibold">Tiempo</th>
                            <th className="text-right p-2 font-semibold">Última visita</th>
                        </tr>
                    </thead>
                    <tbody>
                        {users.map((u, i) => (
                            <tr key={u.user_id} className="border-b border-line/40 hover:bg-bg2/40 cursor-pointer"
                                onClick={() => window.location.hash = `user/${u.user_id}`}>
                                <td className="p-2 text-muted">{i + 1}</td>
                                <td className="p-2">
                                    <div className="font-semibold text-accent">@{u.handle}</div>
                                    <div className="text-xs text-muted">{u.display_name}</div>
                                </td>
                                <td className="p-2 text-right">
                                    <div className="inline-block px-2 py-1 rounded text-xs font-bold"
                                        style={{
                                            background: `rgba(189, 153, 98, ${(u.engagement_score || 0) / 100})`,
                                            color: u.engagement_score > 50 ? '#0E2620' : '#F5EDDE'
                                        }}>
                                        {u.engagement_score || 0}
                                    </div>
                                </td>
                                <td className="p-2 text-right">{u.event_count || 0}</td>
                                <td className="p-2 text-right">{u.session_count || 0}</td>
                                <td className="p-2 text-right">{u.total_time_minutes || 0}m</td>
                                <td className="p-2 text-right text-xs text-muted">{formatTimeAgo(u.last_seen)}</td>
                            </tr>
                        ))}
                    </tbody>
                </table>
            </Card>
        </div>
    );
}

// ============================================================================
// Page: User Detail (todo de un usuario en una sola vista)
// ============================================================================
function UserDetailPage({ userId }) {
    const [summary, setSummary] = useState(null);
    const [recentEvents, setRecentEvents] = useState([]);
    const [hourlyPattern, setHourlyPattern] = useState([]);
    const [loading, setLoading] = useState(true);
    const hourlyChartRef = useRef(null);

    useEffect(() => {
        (async () => {
            const [{ data: sum }, { data: evt }, { data: hp }] = await Promise.all([
                supa.rpc('get_user_admin_summary', { p_user_id: userId }),
                supa.from('analytics_events').select('*')
                    .eq('user_id', userId)
                    .order('created_at', { ascending: false })
                    .limit(50),
                supa.rpc('get_user_hourly_pattern', { p_user_id: userId })
            ]);
            setSummary(sum || null);
            setRecentEvents(evt || []);
            setHourlyPattern(hp || []);
            setLoading(false);
        })();
    }, [userId]);

    // Render hourly pattern chart
    useEffect(() => {
        if (loading || !hourlyChartRef.current || hourlyPattern.length === 0) return;
        if (hourlyChartRef.current.chart) hourlyChartRef.current.chart.destroy();
        const hours = Array.from({ length: 24 }, (_, i) => i);
        const map = Object.fromEntries(hourlyPattern.map(h => [h.hour_of_day, Number(h.event_count)]));
        hourlyChartRef.current.chart = new Chart(hourlyChartRef.current, {
            type: 'bar',
            data: {
                labels: hours.map(h => `${h}`),
                datasets: [{
                    data: hours.map(h => map[h] || 0),
                    backgroundColor: '#BD9962',
                }]
            },
            options: {
                responsive: true, maintainAspectRatio: false,
                plugins: { legend: { display: false } },
                scales: {
                    x: { grid: { display: false }, ticks: { font: { size: 9 } } },
                    y: { beginAtZero: true, ticks: { font: { size: 9 }, precision: 0 } }
                }
            }
        });
    }, [hourlyPattern, loading]);

    if (loading) return <CenteredLoader text="Cargando usuario..." />;
    if (!summary) return <div className="p-8 max-w-7xl mx-auto space-y-4"><EmptyState text="Usuario no encontrado" /></div>;

    const p = summary.profile || {};
    const d = summary.demographics || {};
    const s = summary.stats || {};
    const style = summary.style || {};
    const closet = summary.closet_summary || {};

    return (
        <div className="p-8 max-w-5xl">
            {/* Header */}
            <div className="flex items-center gap-4 mb-6">
                <a href="#users" className="text-sm text-muted hover:text-accent">← Volver</a>
            </div>

            <div className="flex items-baseline justify-between mb-6">
                <div>
                    <h1 className="font-editorial text-4xl text-ink">@{p.handle || '?'}</h1>
                    <p className="text-muted text-sm mt-1">{p.display_name}</p>
                </div>
                <div className="text-right">
                    <div className="text-[10px] uppercase tracking-wider text-muted font-semibold">Engagement</div>
                    <div className="font-editorial text-4xl text-accent">{summary.engagement_score || 0}</div>
                    <div className="text-[10px] text-muted">/100</div>
                </div>
            </div>

            {/* Stats Row */}
            <div className="grid grid-cols-2 md:grid-cols-4 gap-3 items-start mb-6">
                <KpiCard label="Eventos" value={s.total_events?.toLocaleString() || 0} infoKey="user_total_events" />
                <KpiCard label="Sesiones" value={s.total_sessions || 0} infoKey="user_total_sessions" />
                <KpiCard label="Tiempo total" value={formatDuration(s.total_time_seconds || 0)} infoKey="user_total_time" />
                <KpiCard label="Días activos" value={s.days_active || 0} infoKey="user_days_active" />
            </div>

            <div className="grid grid-cols-1 md:grid-cols-2 gap-4 items-start mb-4">
                {/* Demographics */}
                <Card title="Perfil demográfico">
                    {Object.keys(d).length === 0 ? <EmptyState text="No completó el quiz demográfico" /> : (
                        <DetailGrid rows={[
                            { label: 'Edad', value: d.age || '—' },
                            { label: 'Género', value: d.gender || '—' },
                            { label: 'Pronombres', value: d.pronouns || '—' },
                            { label: 'Ubicación', value: d.city ? `${d.city}, ${d.state || d.country || ''}` : '—' },
                            { label: 'País', value: d.country || '—' },
                            { label: 'Ocupación', value: d.occupation || '—' },
                            { label: 'Talla top', value: d.top_size || '—' },
                            { label: 'Talla bottom', value: d.bottom_size || '—' },
                            { label: 'Talla calzado', value: d.shoe_size || '—' },
                            { label: 'Presupuesto', value: d.budget_range?.replace('_', ' – $') || '—' },
                            { label: 'Frecuencia compra', value: d.shopping_frequency || '—' },
                            { label: '♻️ Sustentable', value: d.sustainability_priority ? 'Sí' : 'No' },
                            { label: '🔄 Segunda mano', value: d.secondhand_buyer ? 'Compra' : '' + (d.secondhand_seller ? ' Vende' : '') || '—' },
                        ]} />
                    )}
                </Card>

                {/* Style */}
                <Card title="Estilo (del quiz)">
                    {!style.primary_style ? <EmptyState text="No completó el quiz de estilo" /> : (
                        <div className="space-y-3">
                            <div>
                                <div className="text-[10px] uppercase tracking-wider text-muted">Estilo primario</div>
                                <div className="font-editorial text-2xl text-ink">{style.primary_style}</div>
                                <div className="text-xs text-accent">{Math.round((style.primary_score || 0) * 100)}% match</div>
                            </div>
                            {style.secondary_style && style.secondary_style !== style.primary_style && (
                                <div>
                                    <div className="text-[10px] uppercase tracking-wider text-muted">Toque secundario</div>
                                    <div className="font-editorial text-lg text-ink">{style.secondary_style}</div>
                                </div>
                            )}
                            {style.emerging_styles?.length > 0 && (
                                <div>
                                    <div className="text-[10px] uppercase tracking-wider text-muted">También vibra con</div>
                                    <div className="flex flex-wrap gap-1 mt-1">
                                        {style.emerging_styles.map(s => (
                                            <span key={s} className="text-[10px] bg-bg2 px-2 py-0.5 rounded">{s}</span>
                                        ))}
                                    </div>
                                </div>
                            )}
                        </div>
                    )}
                </Card>
            </div>

            <div className="grid grid-cols-1 md:grid-cols-2 gap-4 items-start mb-4">
                {/* Top eventos */}
                <Card title="Sus eventos más frecuentes">
                    {!summary.top_events?.length ? <EmptyState text="Sin eventos" /> : (
                        <table className="w-full text-sm">
                            <tbody>
                                {summary.top_events.map(e => (
                                    <tr key={e.event_name} className="border-b border-line/50">
                                        <td className="py-1.5">{e.event_name}</td>
                                        <td className="py-1.5 text-right font-semibold">{Number(e.count).toLocaleString()}</td>
                                    </tr>
                                ))}
                            </tbody>
                        </table>
                    )}
                </Card>

                {/* Top pantallas */}
                <Card title="Pantallas con más tiempo">
                    {!summary.top_screens?.length ? <EmptyState text="Sin pantallas" /> : (
                        <table className="w-full text-sm">
                            <tbody>
                                {summary.top_screens.map(s => (
                                    <tr key={s.screen_name} className="border-b border-line/50">
                                        <td className="py-1.5">{s.screen_name}</td>
                                        <td className="py-1.5 text-right font-semibold">{formatDuration(s.total_seconds)}</td>
                                    </tr>
                                ))}
                            </tbody>
                        </table>
                    )}
                </Card>
            </div>

            {/* Patrón horario individual */}
            <Card title="Patrón horario (todas las horas que usa la app)">
                {hourlyPattern.length === 0 ? <EmptyState text="Sin eventos suficientes para ver patrón" /> : (
                    <>
                        <div style={{ height: 160 }}>
                            <canvas ref={hourlyChartRef}></canvas>
                        </div>
                        <div className="text-[11px] text-muted mt-2">
                            Su hora pico:&nbsp;
                            <strong>
                                {hourlyPattern.reduce((max, h) =>
                                    Number(h.event_count) > Number(max.event_count || 0) ? h : max
                                    , { hour_of_day: 0, event_count: 0 }
                                ).hour_of_day}:00
                            </strong>
                            &nbsp;· Total horas distintas activas: <strong>{hourlyPattern.length}/24</strong>
                        </div>
                    </>
                )}
            </Card>

            {/* Closet summary */}
            {closet.total_garments > 0 && (
                <Card title="Su clóset">
                    <div className="text-2xl font-editorial text-ink mb-2">{closet.total_garments} prendas</div>
                    <div className="text-xs text-muted">
                        Categorías: {Object.entries(closet.categories || {}).map(([k, v]) => `${k} (${v})`).join(' · ')}
                    </div>
                </Card>
            )}

            {/* Recent events feed */}
            <Card title="Últimos 50 eventos">
                {recentEvents.length === 0 ? <EmptyState text="Sin eventos" /> : (
                    <div className="max-h-96 overflow-y-auto">
                        <table className="w-full text-xs">
                            <tbody>
                                {recentEvents.map(e => (
                                    <tr key={e.id} className="border-b border-line/40">
                                        <td className="py-1.5 text-muted whitespace-nowrap pr-2">{formatTimeAgo(e.created_at)}</td>
                                        <td className="py-1.5 font-semibold">{e.event_name}</td>
                                        <td className="py-1.5 text-muted">{e.screen_name || '—'}</td>
                                        <td className="py-1.5 text-muted truncate max-w-xs">
                                            {e.properties && Object.keys(e.properties).length > 0
                                                ? JSON.stringify(e.properties).substring(0, 60) + (JSON.stringify(e.properties).length > 60 ? '...' : '')
                                                : '—'}
                                        </td>
                                    </tr>
                                ))}
                            </tbody>
                        </table>
                    </div>
                )}
            </Card>
        </div>
    );
}

// ============================================================================
// Reusable components
// ============================================================================
function DistributionBars({ data }) {
    const max = Math.max(...data.map(d => d.value), 1);
    const total = data.reduce((a, b) => a + b.value, 0);
    return (
        <div className="space-y-2">
            {data.map((d, i) => {
                const pct = (d.value / max) * 100;
                const sharePct = total > 0 ? (d.value / total * 100).toFixed(0) : 0;
                return (
                    <div key={i}>
                        <div className="flex justify-between text-xs mb-0.5">
                            <span className="text-ink">{d.label}</span>
                            <span className="text-muted">{d.value} ({sharePct}%)</span>
                        </div>
                        <div className="h-2 bg-bg2 rounded overflow-hidden">
                            <div className="h-full bg-accent" style={{ width: `${pct}%` }}></div>
                        </div>
                    </div>
                );
            })}
        </div>
    );
}

function DetailGrid({ rows }) {
    return (
        <div className="grid grid-cols-2 gap-x-4 gap-y-2 text-sm">
            {rows.map((r, i) => (
                <div key={i} className="flex justify-between border-b border-line/30 pb-1">
                    <span className="text-muted text-xs">{r.label}</span>
                    <span className="text-ink font-semibold text-xs">{r.value || '—'}</span>
                </div>
            ))}
        </div>
    );
}

function EmptyState({ text }) {
    return (
        <div className="text-center py-8 text-muted text-sm">
            {text}
        </div>
    );
}

// ============================================================================
// Page: Schedules (análisis temporal — horas, días, heatmap)
// ============================================================================
function SchedulesPage() {
    const [hourly, setHourly] = useState([]);
    const [dow, setDow] = useState([]);
    const [heatmap, setHeatmap] = useState([]);
    const [peakHours, setPeakHours] = useState([]);
    const [buckets, setBuckets] = useState([]);
    const [weekdayVsWeekend, setWeekdayVsWeekend] = useState([]);
    const [sessionDuration, setSessionDuration] = useState([]);
    const [eventsByHour, setEventsByHour] = useState([]);
    const [loading, setLoading] = useState(true);

    const hourlyChart = useRef(null);
    const dowChart = useRef(null);
    const sessionChart = useRef(null);

    useEffect(() => {
        (async () => {
            const [h, d, hm, pk, bk, ww, sd, ebh] = await Promise.all([
                supa.from('analytics_hourly_activity').select('*'),
                supa.from('analytics_dow_activity').select('*'),
                supa.from('analytics_heatmap_dow_hour').select('*'),
                supa.from('analytics_peak_hours').select('*'),
                supa.from('analytics_time_of_day_buckets').select('*'),
                supa.from('analytics_weekday_vs_weekend').select('*'),
                supa.from('analytics_session_duration_by_hour').select('*'),
                supa.from('analytics_events_by_hour_breakdown').select('*'),
            ]);
            setHourly(h.data || []);
            setDow(d.data || []);
            setHeatmap(hm.data || []);
            setPeakHours(pk.data || []);
            setBuckets(bk.data || []);
            setWeekdayVsWeekend(ww.data || []);
            setSessionDuration(sd.data || []);
            setEventsByHour(ebh.data || []);
            setLoading(false);
        })();
    }, []);

    // Render hourly chart
    useEffect(() => {
        if (loading || !hourlyChart.current || hourly.length === 0) return;
        if (hourlyChart.current.chart) hourlyChart.current.chart.destroy();
        const hours = Array.from({ length: 24 }, (_, i) => i);
        const dataByHour = Object.fromEntries(hourly.map(h => [h.hour_of_day, Number(h.event_count)]));
        hourlyChart.current.chart = new Chart(hourlyChart.current, {
            type: 'bar',
            data: {
                labels: hours.map(h => `${h.toString().padStart(2, '0')}:00`),
                datasets: [{
                    label: 'Eventos',
                    data: hours.map(h => dataByHour[h] || 0),
                    backgroundColor: '#BD9962',
                }]
            },
            options: {
                responsive: true, maintainAspectRatio: false,
                plugins: { legend: { display: false } },
                scales: {
                    x: { grid: { display: false }, ticks: { font: { size: 9 } } },
                    y: { beginAtZero: true, ticks: { font: { size: 10 }, precision: 0 } }
                }
            }
        });
    }, [hourly, loading]);

    // Render DOW chart
    useEffect(() => {
        if (loading || !dowChart.current || dow.length === 0) return;
        if (dowChart.current.chart) dowChart.current.chart.destroy();
        // Reordenar: lunes a domingo
        const sorted = [1, 2, 3, 4, 5, 6, 0].map(idx => dow.find(d => d.day_of_week === idx) || { day_of_week: idx, day_name: '?', event_count: 0 });
        dowChart.current.chart = new Chart(dowChart.current, {
            type: 'bar',
            data: {
                labels: sorted.map(d => d.day_name?.substring(0, 3) || '?'),
                datasets: [{
                    label: 'Eventos',
                    data: sorted.map(d => Number(d.event_count)),
                    backgroundColor: sorted.map(d => [0, 6].includes(d.day_of_week) ? '#8C6F42' : '#BD9962'),
                }]
            },
            options: {
                responsive: true, maintainAspectRatio: false,
                plugins: { legend: { display: false } },
                scales: {
                    x: { grid: { display: false }, ticks: { font: { size: 11 } } },
                    y: { beginAtZero: true, ticks: { font: { size: 10 }, precision: 0 } }
                }
            }
        });
    }, [dow, loading]);

    // Render session duration chart
    useEffect(() => {
        if (loading || !sessionChart.current || sessionDuration.length === 0) return;
        if (sessionChart.current.chart) sessionChart.current.chart.destroy();
        const hours = Array.from({ length: 24 }, (_, i) => i);
        const map = Object.fromEntries(sessionDuration.map(s => [s.hour_of_day, Math.round((s.avg_duration_seconds || 0) / 60 * 10) / 10]));
        sessionChart.current.chart = new Chart(sessionChart.current, {
            type: 'line',
            data: {
                labels: hours.map(h => `${h.toString().padStart(2, '0')}:00`),
                datasets: [{
                    label: 'Duración promedio (min)',
                    data: hours.map(h => map[h] || 0),
                    borderColor: '#21493C', backgroundColor: 'rgba(33,73,60,0.1)',
                    fill: true, tension: 0.3,
                }]
            },
            options: {
                responsive: true, maintainAspectRatio: false,
                plugins: { legend: { display: false } },
                scales: {
                    x: { grid: { display: false }, ticks: { font: { size: 9 } } },
                    y: { beginAtZero: true, ticks: { font: { size: 10 } } }
                }
            }
        });
    }, [sessionDuration, loading]);

    if (loading) return <CenteredLoader text="Cargando horarios..." />;

    const totalEvents = hourly.reduce((a, b) => a + Number(b.event_count), 0);
    const totalUniqueUsers = Math.max(...hourly.map(h => Number(h.unique_users)), 0);

    // Compute peak hour automatically (most activity)
    const peakHour = hourly.reduce((max, h) =>
        Number(h.event_count) > Number(max.event_count || 0) ? h : max,
        { hour_of_day: 0, event_count: 0 }
    );

    return (
        <div className="p-8 max-w-7xl mx-auto space-y-4">
            <PageHeader title="Horarios y patrones" subtitle="Cuándo y cómo usan tu app (zona horaria CDMX, últimos 30 días)" />

            {/* KPI strip */}
            <div className="grid grid-cols-2 md:grid-cols-4 gap-3 items-start mb-6">
                <KpiCard label="Hora pico" value={`${peakHour.hour_of_day}:00`} infoKey="peak_hour" />
                <KpiCard label="Eventos a esa hora" value={Number(peakHour.event_count).toLocaleString()} infoKey="peak_hour" />
                <KpiCard label="Total eventos (30d)" value={totalEvents.toLocaleString()} infoKey="total_events_30d" />
                <KpiCard label="Users únicos máx por hora" value={totalUniqueUsers} infoKey="peak_hour" />
            </div>

            {/* Time of day buckets */}
            <Card title="Por momento del día" infoKey="time_of_day_buckets">
                {buckets.length === 0 ? <EmptyState text="Sin data aún" /> : (
                    <DistributionBars data={buckets.map(b => ({
                        label: b.bucket,
                        value: Number(b.event_count)
                    }))} />
                )}
            </Card>

            <div className="grid grid-cols-1 md:grid-cols-2 gap-4 items-start mt-4">
                <Card title="Eventos por hora del día" infoKey="events_by_hour">
                    {hourly.length === 0 ? <EmptyState text="Sin data aún" /> : (
                        <div style={{ height: 160 }}>
                            <canvas ref={hourlyChart}></canvas>
                        </div>
                    )}
                </Card>

                <Card title="Eventos por día de la semana" infoKey="events_by_dow">
                    {dow.length === 0 ? <EmptyState text="Sin data aún" /> : (
                        <div style={{ height: 160 }}>
                            <canvas ref={dowChart}></canvas>
                        </div>
                    )}
                </Card>
            </div>

            {/* Heatmap */}
            <Card title="Heatmap día × hora" infoKey="heatmap_dow_hour">
                <Heatmap data={heatmap} />
                <div className="text-[11px] text-muted mt-3">
                    💡 Cada cuadrito = combinación día/hora. Más oscuro = más actividad. Patrones típicos: noche entre semana (cuando ven feed antes de dormir), tardes fines de semana (cuando deciden outfit).
                </div>
            </Card>

            <div className="grid grid-cols-1 md:grid-cols-2 gap-4 items-start mt-4">
                {/* Weekday vs weekend */}
                <Card title="Entre semana vs fin de semana" infoKey="weekday_vs_weekend">
                    {weekdayVsWeekend.length === 0 ? <EmptyState text="Sin data" /> : (
                        <DistributionBars data={weekdayVsWeekend.map(w => ({
                            label: w.period_type === 'weekend' ? 'Fin de semana' : 'Entre semana',
                            value: Number(w.event_count)
                        }))} />
                    )}
                </Card>

                <Card title="Top 5 horas pico" infoKey="top_peak_hours">
                    {peakHours.length === 0 ? <EmptyState text="Sin data" /> : (
                        <table className="w-full text-sm">
                            <thead>
                                <tr className="border-b border-line text-[10px] uppercase tracking-wider text-muted">
                                    <th className="text-left pb-2 font-semibold">Hora</th>
                                    <th className="text-right pb-2 font-semibold">Eventos</th>
                                    <th className="text-right pb-2 font-semibold">Users</th>
                                    <th className="text-right pb-2 font-semibold">Top evento</th>
                                </tr>
                            </thead>
                            <tbody>
                                {peakHours.map(h => (
                                    <tr key={h.hour_of_day} className="border-b border-line/40">
                                        <td className="py-2 font-semibold">{h.hour_of_day}:00</td>
                                        <td className="py-2 text-right">{Number(h.event_count).toLocaleString()}</td>
                                        <td className="py-2 text-right text-muted">{h.unique_users}</td>
                                        <td className="py-2 text-right text-xs text-muted truncate max-w-xs">{h.top_event}</td>
                                    </tr>
                                ))}
                            </tbody>
                        </table>
                    )}
                </Card>
            </div>

            {/* Session duration by hour */}
            <Card title="Duración promedio de sesión según hora de inicio" infoKey="session_duration_by_hour">
                {sessionDuration.length === 0 ? <EmptyState text="Sin data de sesiones" /> : (
                    <div style={{ height: 160 }}>
                        <canvas ref={sessionChart}></canvas>
                    </div>
                )}
                <div className="text-[11px] text-muted mt-2">
                    Si las sesiones nocturnas son mucho más largas, hay engagement profundo en la noche.
                </div>
            </Card>

            {/* Events breakdown por hora */}
            <Card title="Qué eventos pasan más en cada hora" infoKey="events_by_hour_breakdown">
                {eventsByHour.length === 0 ? <EmptyState text="Sin data" /> : (
                    <div className="overflow-x-auto">
                        <HourBreakdownTable data={eventsByHour} />
                    </div>
                )}
            </Card>
        </div>
    );
}

// ============================================================================
// Heatmap 7×24 (días × horas)
// ============================================================================
function Heatmap({ data }) {
    const dayLabels = ['Dom', 'Lun', 'Mar', 'Mié', 'Jue', 'Vie', 'Sáb'];
    const hours = Array.from({ length: 24 }, (_, i) => i);

    // Build matrix: { [dow]: { [hour]: count } }
    const matrix = {};
    let maxVal = 0;
    data.forEach(d => {
        if (!matrix[d.day_of_week]) matrix[d.day_of_week] = {};
        matrix[d.day_of_week][d.hour_of_day] = Number(d.event_count);
        maxVal = Math.max(maxVal, Number(d.event_count));
    });

    if (maxVal === 0) return <EmptyState text="Necesitas más eventos para ver el heatmap" />;

    return (
        <div className="overflow-x-auto">
            <table className="border-separate" style={{ borderSpacing: 2 }}>
                <thead>
                    <tr>
                        <th></th>
                        {hours.map(h => (
                            <th key={h} className="text-[9px] text-muted font-normal w-7"
                                style={{ writingMode: 'vertical-rl', transform: 'rotate(180deg)' }}>
                                {h.toString().padStart(2, '0')}
                            </th>
                        ))}
                    </tr>
                </thead>
                <tbody>
                    {[1, 2, 3, 4, 5, 6, 0].map(dow => (   // lunes a domingo
                        <tr key={dow}>
                            <td className="text-[10px] text-muted font-semibold pr-2 text-right">{dayLabels[dow]}</td>
                            {hours.map(h => {
                                const val = matrix[dow]?.[h] || 0;
                                const intensity = val / maxVal;
                                return (
                                    <td key={h}
                                        title={`${dayLabels[dow]} ${h}:00 → ${val} eventos`}
                                        style={{
                                            width: 22, height: 22,
                                            background: val === 0
                                                ? 'rgba(255,255,255,0.05)'
                                                : `rgba(197, 166, 104, ${0.20 + intensity * 0.80})`,
                                            borderRadius: 3
                                        }}>
                                    </td>
                                );
                            })}
                        </tr>
                    ))}
                </tbody>
            </table>
        </div>
    );
}

function HourBreakdownTable({ data }) {
    // Group by hour
    const grouped = {};
    data.forEach(d => {
        if (!grouped[d.hour_of_day]) grouped[d.hour_of_day] = [];
        grouped[d.hour_of_day].push(d);
    });
    const hours = Object.keys(grouped).map(Number).sort((a, b) => a - b);

    return (
        <table className="w-full text-xs">
            <thead>
                <tr className="border-b border-line text-[10px] uppercase tracking-wider text-muted">
                    <th className="text-left p-2 font-semibold">Hora</th>
                    <th className="text-left p-2 font-semibold">Top 5 eventos</th>
                </tr>
            </thead>
            <tbody>
                {hours.map(h => (
                    <tr key={h} className="border-b border-line/40">
                        <td className="p-2 font-semibold align-top">{h}:00</td>
                        <td className="p-2">
                            <div className="flex flex-wrap gap-1.5">
                                {grouped[h].map(e => (
                                    <span key={e.event_name} className="bg-bg2 px-2 py-0.5 rounded text-[10px]">
                                        {e.event_name} <span className="text-muted">({e.event_count})</span>
                                    </span>
                                ))}
                            </div>
                        </td>
                    </tr>
                ))}
            </tbody>
        </table>
    );
}

// ============================================================================
// Page: Clóset (top brands, categorías, colores, render stats)
// ============================================================================
function ClosetPage() {
    const [brands, setBrands] = useState([]);
    const [categories, setCategories] = useState([]);
    const [colors, setColors] = useState([]);
    const [sizeStats, setSizeStats] = useState(null);
    const [renderStats, setRenderStats] = useState([]);
    const [loading, setLoading] = useState(true);

    useEffect(() => {
        (async () => {
            const [b, c, co, ss, rs] = await Promise.all([
                supa.from('analytics_top_brands').select('*').limit(15),
                supa.from('analytics_garment_categories').select('*'),
                supa.from('analytics_top_colors').select('*').limit(15),
                supa.from('analytics_closet_size_stats').select('*').single(),
                supa.from('analytics_render_stats').select('*'),
            ]);
            setBrands(b.data || []);
            setCategories(c.data || []);
            setColors(co.data || []);
            setSizeStats(ss.data || null);
            setRenderStats(rs.data || []);
            setLoading(false);
        })();
    }, []);

    if (loading) return <CenteredLoader text="Cargando..." />;

    return (
        <div className="p-8 max-w-7xl mx-auto space-y-4">
            <PageHeader title="Clóset" subtitle="Qué tienen los users en sus clósets" />

            <div className="grid grid-cols-2 md:grid-cols-4 gap-3 items-start mb-6">
                <KpiCard label="Promedio prendas/user" value={sizeStats?.avg_garments_per_user || 0} infoKey="avg_garments_per_user" />
                <KpiCard label="Mediana" value={sizeStats?.median_garments || 0} infoKey="median_garments" />
                <KpiCard label="Power closets (>50)" value={sizeStats?.power_closets || 0} infoKey="power_closets" />
                <KpiCard label="Clósets vacíos" value={sizeStats?.empty_closets || 0} infoKey="empty_closets" />
            </div>

            <div className="grid grid-cols-1 md:grid-cols-2 gap-4 items-start mb-4">
                <Card title="Top marcas" infoKey="top_brands">
                    {brands.length === 0 ? <EmptyState text="Sin marcas registradas" /> :
                        <DistributionBars data={brands.map(b => ({
                            label: b.brand, value: Number(b.garment_count)
                        }))} />}
                </Card>
                <Card title="Categorías de prendas" infoKey="garment_categories_dist">
                    {categories.length === 0 ? <EmptyState text="Sin prendas" /> :
                        <DistributionBars data={categories.map(c => ({
                            label: c.category, value: Number(c.garment_count)
                        }))} />}
                </Card>
            </div>

            <div className="grid grid-cols-1 md:grid-cols-2 gap-4 items-start mb-4">
                <Card title="Colores dominantes" infoKey="top_colors_dist">
                    {colors.length === 0 ? <EmptyState text="Sin colores" /> :
                        <DistributionBars data={colors.map(c => ({
                            label: c.color, value: Number(c.occurrence_count)
                        }))} />}
                </Card>
                <Card title="Status de renderizado IA" infoKey="render_stats">
                    {renderStats.length === 0 ? <EmptyState text="Sin data" /> :
                        <DistributionBars data={renderStats.map(r => ({
                            label: `${r.status} (${r.percentage}%)`, value: Number(r.count)
                        }))} />}
                </Card>
            </div>
        </div>
    );
}

// ============================================================================
// Page: Marketplace
// ============================================================================
function MarketplacePage() {
    const [gmv, setGmv] = useState(null);
    const [topCategories, setTopCategories] = useState([]);
    const [priceBuckets, setPriceBuckets] = useState([]);
    const [timeToSell, setTimeToSell] = useState(null);
    const [topSellers, setTopSellers] = useState([]);
    const [loading, setLoading] = useState(true);

    useEffect(() => {
        (async () => {
            const [g, tc, pb, tts, ts] = await Promise.all([
                supa.from('analytics_marketplace_gmv').select('*').single(),
                supa.from('analytics_marketplace_top_categories').select('*'),
                supa.from('analytics_marketplace_price_buckets').select('*'),
                supa.from('analytics_marketplace_time_to_sell').select('*').single(),
                supa.from('analytics_marketplace_top_sellers').select('*'),
            ]);
            setGmv(g.data);
            setTopCategories(tc.data || []);
            setPriceBuckets(pb.data || []);
            setTimeToSell(tts.data);
            setTopSellers(ts.data || []);
            setLoading(false);
        })();
    }, []);

    if (loading) return <CenteredLoader text="Cargando marketplace..." />;

    return (
        <div className="p-8 max-w-7xl mx-auto space-y-4">
            <PageHeader title="Marketplace" subtitle="Ventas y comportamiento del e-commerce" />

            <div className="grid grid-cols-2 md:grid-cols-4 gap-3 items-start mb-6">
                <KpiCard label="GMV listado" value={`$${Number(gmv?.gmv_listed || 0).toLocaleString()}`} infoKey="gmv_listed" />
                <KpiCard label="GMV vendido" value={`$${Number(gmv?.gmv_sold || 0).toLocaleString()}`} infoKey="gmv_sold" />
                <KpiCard label="Activos" value={gmv?.active_listings || 0} infoKey="active_listings" />
                <KpiCard label="Vendidos" value={gmv?.sold_listings || 0} infoKey="sold_listings" />
            </div>

            {timeToSell && (
                <div className="grid grid-cols-1 md:grid-cols-3 gap-3 items-start mb-6">
                    <KpiCard label="Promedio listing → venta" value={timeToSell.avg_days_to_sell ? `${timeToSell.avg_days_to_sell} días` : '—'} infoKey="avg_days_to_sell" />
                    <KpiCard label="Mediana" value={timeToSell.median_days_to_sell ? `${timeToSell.median_days_to_sell} días` : '—'} infoKey="median_garments" />
                    <KpiCard label="Máximo" value={timeToSell.max_days ? `${timeToSell.max_days} días` : '—'} infoKey="avg_days_to_sell" />
                </div>
            )}

            <div className="grid grid-cols-1 md:grid-cols-2 gap-4 items-start mb-4">
                <Card title="Sell-through por categoría" infoKey="sell_through_by_category">
                    {topCategories.length === 0 ? <EmptyState text="Sin ventas" /> : (
                        <table className="w-full text-sm">
                            <thead>
                                <tr className="border-b border-line text-[10px] uppercase tracking-wider text-muted">
                                    <th className="text-left p-1 font-semibold">Categoría</th>
                                    <th className="text-right p-1 font-semibold">Listings</th>
                                    <th className="text-right p-1 font-semibold">Vendidos</th>
                                    <th className="text-right p-1 font-semibold">% conv</th>
                                    <th className="text-right p-1 font-semibold">Precio prom</th>
                                </tr>
                            </thead>
                            <tbody>
                                {topCategories.map(c => (
                                    <tr key={c.category} className="border-b border-line/40">
                                        <td className="p-1">{c.category}</td>
                                        <td className="p-1 text-right">{c.listings}</td>
                                        <td className="p-1 text-right font-semibold">{c.sold || 0}</td>
                                        <td className="p-1 text-right text-accent">{c.sell_through_pct || 0}%</td>
                                        <td className="p-1 text-right text-muted">${Math.round(c.avg_price || 0)}</td>
                                    </tr>
                                ))}
                            </tbody>
                        </table>
                    )}
                </Card>
                <Card title="Distribución de precios" infoKey="price_buckets">
                    {priceBuckets.length === 0 ? <EmptyState text="Sin listings" /> :
                        <DistributionBars data={priceBuckets.map(p => ({
                            label: `$${p.price_bucket.replace('_', '–$').replace('menos_200', '<$200').replace('mas_5000', '>$5,000')}`,
                            value: Number(p.listings_count)
                        }))} />}
                </Card>
            </div>

            <Card title="Top vendedores" infoKey="top_sellers">
                {topSellers.length === 0 ? <EmptyState text="Sin vendedores aún" /> : (
                    <table className="w-full text-sm">
                        <thead>
                            <tr className="border-b border-line text-[10px] uppercase tracking-wider text-muted">
                                <th className="text-left p-2 font-semibold">Vendedor</th>
                                <th className="text-right p-2 font-semibold">Listings</th>
                                <th className="text-right p-2 font-semibold">Vendidos</th>
                                <th className="text-right p-2 font-semibold">Revenue</th>
                            </tr>
                        </thead>
                        <tbody>
                            {topSellers.map(s => (
                                <tr key={s.seller_id} className="border-b border-line/40 hover:bg-bg2/40 cursor-pointer"
                                    onClick={() => window.location.hash = `user/${s.seller_id}`}>
                                    <td className="p-2"><span className="font-semibold text-accent">@{s.handle}</span><span className="text-muted text-xs ml-2">{s.display_name}</span></td>
                                    <td className="p-2 text-right">{s.total_listings}</td>
                                    <td className="p-2 text-right font-semibold">{s.items_sold}</td>
                                    <td className="p-2 text-right">${Number(s.total_revenue).toLocaleString()}</td>
                                </tr>
                            ))}
                        </tbody>
                    </table>
                )}
            </Card>
        </div>
    );
}

// ============================================================================
// Page: Social engagement
// ============================================================================
function SocialPage() {
    const [topPosts, setTopPosts] = useState([]);
    const [engagement, setEngagement] = useState(null);
    const [followerCurve, setFollowerCurve] = useState([]);
    const [influencers, setInfluencers] = useState([]);
    const [postsPerDay, setPostsPerDay] = useState([]);
    const [loading, setLoading] = useState(true);

    useEffect(() => {
        (async () => {
            const [p, e, fc, i, ppd] = await Promise.all([
                supa.from('analytics_top_posts').select('*'),
                supa.from('analytics_engagement_rate').select('*').single(),
                supa.from('analytics_follower_curve').select('*'),
                supa.from('analytics_top_influencers').select('*'),
                supa.from('analytics_posts_per_day').select('*'),
            ]);
            setTopPosts(p.data || []);
            setEngagement(e.data);
            setFollowerCurve(fc.data || []);
            setInfluencers(i.data || []);
            setPostsPerDay(ppd.data || []);
            setLoading(false);
        })();
    }, []);

    if (loading) return <CenteredLoader text="Cargando social..." />;

    return (
        <div className="p-8 max-w-7xl mx-auto space-y-4">
            <PageHeader title="Social engagement" subtitle="Posts, likes, comments, viralidad" />

            {engagement && (
                <div className="grid grid-cols-2 md:grid-cols-4 gap-3 items-start mb-6">
                    <KpiCard label="Likes promedio/post" value={engagement.avg_likes_per_post || 0} infoKey="avg_likes_per_post" />
                    <KpiCard label="Comments promedio" value={engagement.avg_comments_per_post || 0} infoKey="avg_comments_per_post" />
                    <KpiCard label="Max likes single post" value={engagement.max_likes_single_post || 0} infoKey="avg_likes_per_post" />
                    <KpiCard label="Posts sin likes" value={`${engagement.posts_with_zero_likes || 0}/${engagement.total_posts || 0}`} infoKey="avg_likes_per_post" />
                </div>
            )}

            <div className="grid grid-cols-1 md:grid-cols-2 gap-4 items-start mb-4">
                <Card title="Curva de followers (Pareto)" infoKey="follower_curve">
                    {followerCurve.length === 0 ? <EmptyState text="Sin followers" /> : (
                        <DistributionBars data={followerCurve.map(b => ({
                            label: `${b.band.replace('_', ' ').replace('pct', '%')} (${b.users_in_band} users)`,
                            value: Number(b.share_of_all_followers)
                        }))} />
                    )}
                </Card>
                <Card title="Top influencers (por followers)" infoKey="top_influencers">
                    {influencers.length === 0 ? <EmptyState text="Sin influencers" /> : (
                        <table className="w-full text-sm">
                            <thead>
                                <tr className="border-b border-line text-[10px] uppercase tracking-wider text-muted">
                                    <th className="text-left p-1 font-semibold">User</th>
                                    <th className="text-right p-1 font-semibold">Followers</th>
                                    <th className="text-right p-1 font-semibold">Posts</th>
                                    <th className="text-right p-1 font-semibold">Avg likes</th>
                                </tr>
                            </thead>
                            <tbody>
                                {influencers.slice(0, 10).map(i => (
                                    <tr key={i.id} className="border-b border-line/40 hover:bg-bg2/40 cursor-pointer"
                                        onClick={() => window.location.hash = `user/${i.id}`}>
                                        <td className="p-1 font-semibold text-accent">@{i.handle}</td>
                                        <td className="p-1 text-right">{i.followers_count}</td>
                                        <td className="p-1 text-right text-muted">{i.posts_count}</td>
                                        <td className="p-1 text-right">{i.avg_likes_per_post}</td>
                                    </tr>
                                ))}
                            </tbody>
                        </table>
                    )}
                </Card>
            </div>

            <Card title="Top posts virales (90 días)" infoKey="top_posts_viral">
                {topPosts.length === 0 ? <EmptyState text="Sin posts virales" /> : (
                    <table className="w-full text-sm">
                        <thead>
                            <tr className="border-b border-line text-[10px] uppercase tracking-wider text-muted">
                                <th className="text-left p-2 font-semibold">Autor</th>
                                <th className="text-left p-2 font-semibold">Caption</th>
                                <th className="text-right p-2 font-semibold">Likes</th>
                                <th className="text-right p-2 font-semibold">Comments</th>
                                <th className="text-right p-2 font-semibold">Score</th>
                            </tr>
                        </thead>
                        <tbody>
                            {topPosts.slice(0, 10).map(p => (
                                <tr key={p.post_id} className="border-b border-line/40 hover:bg-bg2/40">
                                    <td className="p-2 font-semibold text-accent cursor-pointer"
                                        onClick={() => window.location.hash = `user/${p.author_id}`}>
                                        @{p.author_handle}
                                    </td>
                                    <td className="p-2 text-xs text-muted truncate max-w-xs">{p.caption || '—'}</td>
                                    <td className="p-2 text-right font-semibold">{p.likes_count}</td>
                                    <td className="p-2 text-right">{p.comments_count}</td>
                                    <td className="p-2 text-right text-accent">{p.engagement_score}</td>
                                </tr>
                            ))}
                        </tbody>
                    </table>
                )}
            </Card>
        </div>
    );
}

// ============================================================================
// Page: Stories
// ============================================================================
function StoriesPage() {
    const [perDay, setPerDay] = useState([]);
    const [summary, setSummary] = useState(null);
    const [loading, setLoading] = useState(true);

    useEffect(() => {
        (async () => {
            const [pd, s] = await Promise.all([
                supa.from('analytics_stories_per_day').select('*'),
                supa.from('analytics_stories_summary').select('*').single(),
            ]);
            setPerDay(pd.data || []);
            setSummary(s.data);
            setLoading(false);
        })();
    }, []);

    if (loading) return <CenteredLoader text="Cargando stories..." />;

    return (
        <div className="p-8 max-w-7xl mx-auto space-y-4">
            <PageHeader title="Stories" subtitle="Contenido efímero (24h)" />

            {summary && (
                <div className="grid grid-cols-2 md:grid-cols-4 gap-3 items-start mb-6">
                    <KpiCard label="Stories totales (30d)" value={summary.total_stories || 0} infoKey="stories_total_30d" />
                    <KpiCard label="Creadores únicos" value={summary.unique_creators || 0} infoKey="stories_unique_creators" />
                    <KpiCard label="Últimos 7 días" value={summary.stories_last_7d || 0} infoKey="stories_total_30d" />
                    <KpiCard label="Vivas ahora" value={summary.currently_live || 0} infoKey="stories_total_30d" />
                </div>
            )}

            <Card title="Stories creadas por día" infoKey="stories_per_day">
                {perDay.length === 0 ? <EmptyState text="Sin stories" /> :
                    <DistributionBars data={perDay.slice(0, 14).map(d => ({
                        label: new Date(d.day).toLocaleDateString('es-MX', { day: 'numeric', month: 'short' }),
                        value: Number(d.stories_created)
                    }))} />}
            </Card>
        </div>
    );
}

// ============================================================================
// Page: Chat
// ============================================================================
function ChatPage() {
    const [perDay, setPerDay] = useState([]);
    const [active, setActive] = useState(null);
    const [topMessengers, setTopMessengers] = useState([]);
    const [loading, setLoading] = useState(true);

    useEffect(() => {
        (async () => {
            const [pd, a, tm] = await Promise.all([
                supa.from('analytics_messages_per_day').select('*'),
                supa.from('analytics_active_conversations').select('*').single(),
                supa.from('analytics_top_messengers').select('*'),
            ]);
            setPerDay(pd.data || []);
            setActive(a.data);
            setTopMessengers(tm.data || []);
            setLoading(false);
        })();
    }, []);

    if (loading) return <CenteredLoader text="Cargando chat..." />;

    return (
        <div className="p-8 max-w-7xl mx-auto space-y-4">
            <PageHeader title="Chat & mensajes" subtitle="Conversaciones 1-on-1" />

            {active && (
                <div className="grid grid-cols-1 md:grid-cols-3 gap-3 items-start mb-6">
                    <KpiCard label="Conversaciones totales" value={active.total_conversations || 0} infoKey="total_conversations" />
                    <KpiCard label="Activas últimos 7d" value={active.active_last_7d || 0} infoKey="active_conversations_7d" />
                    <KpiCard label="Activas últimas 24h" value={active.active_last_24h || 0} infoKey="active_conversations_24h" />
                </div>
            )}

            <div className="grid grid-cols-1 md:grid-cols-2 gap-4 items-start">
                <Card title="Mensajes por día" infoKey="messages_per_day">
                    {perDay.length === 0 ? <EmptyState text="Sin mensajes" /> :
                        <DistributionBars data={perDay.slice(0, 14).map(d => ({
                            label: new Date(d.day).toLocaleDateString('es-MX', { day: 'numeric', month: 'short' }),
                            value: Number(d.messages)
                        }))} />}
                </Card>
                <Card title="Top mensajeros" infoKey="top_messengers">
                    {topMessengers.length === 0 ? <EmptyState text="Sin mensajeros" /> : (
                        <table className="w-full text-sm">
                            <tbody>
                                {topMessengers.slice(0, 10).map(m => (
                                    <tr key={m.id} className="border-b border-line/40 hover:bg-bg2/40 cursor-pointer"
                                        onClick={() => window.location.hash = `user/${m.id}`}>
                                        <td className="p-1 font-semibold text-accent">@{m.handle}</td>
                                        <td className="p-1 text-right">{m.messages_sent}</td>
                                    </tr>
                                ))}
                            </tbody>
                        </table>
                    )}
                </Card>
            </div>
        </div>
    );
}

// ============================================================================
// Page: Notifications
// ============================================================================
function NotificationsPage() {
    const [perDay, setPerDay] = useState([]);
    const [bestHour, setBestHour] = useState([]);
    const [loading, setLoading] = useState(true);

    useEffect(() => {
        (async () => {
            const [pd, bh] = await Promise.all([
                supa.from('analytics_notifications_opened').select('*'),
                supa.from('analytics_push_best_hour').select('*'),
            ]);
            setPerDay(pd.data || []);
            setBestHour(bh.data || []);
            setLoading(false);
        })();
    }, []);

    if (loading) return <CenteredLoader text="Cargando notifs..." />;

    return (
        <div className="p-8 max-w-7xl mx-auto space-y-4">
            <PageHeader title="Notificaciones push" subtitle="Aperturas y mejor hora para mandar" />

            <div className="grid grid-cols-1 md:grid-cols-2 gap-4 items-start">
                <Card title="Aperturas por día" infoKey="notifs_opened_per_day">
                    {perDay.length === 0 ? <EmptyState text="Aún no hay aperturas trackeadas" /> :
                        <DistributionBars data={perDay.slice(0, 14).map(d => ({
                            label: new Date(d.day).toLocaleDateString('es-MX', { day: 'numeric', month: 'short' }),
                            value: Number(d.opens)
                        }))} />}
                </Card>
                <Card title="Mejor hora para mandar push" infoKey="push_best_hour">
                    {bestHour.length === 0 ? <EmptyState text="Sin datos" /> :
                        <DistributionBars data={bestHour.slice(0, 8).map(h => ({
                            label: `${h.hour_of_day}:00`,
                            value: Number(h.opens)
                        }))} />}
                </Card>
            </div>

            <div className="mt-4 p-4 bg-bg2 rounded-lg text-xs text-muted">
                💡 Cuando agreguemos tracking de <code>notification_opened</code> en el AppDelegate (cuando un user toca una push y abre la app), esta página se llena. Por ahora solo está la infraestructura.
            </div>
        </div>
    );
}

// ============================================================================
// Page: Quiz drop-off
// ============================================================================
function QuizDropoffPage() {
    const [dropoff, setDropoff] = useState([]);
    const [combinations, setCombinations] = useState([]);
    const [loading, setLoading] = useState(true);

    useEffect(() => {
        (async () => {
            const [d, c] = await Promise.all([
                supa.from('analytics_quiz_dropoff').select('*'),
                supa.from('analytics_style_combinations').select('*'),
            ]);
            setDropoff(d.data || []);
            setCombinations(c.data || []);
            setLoading(false);
        })();
    }, []);

    if (loading) return <CenteredLoader text="Cargando..." />;

    return (
        <div className="p-8 max-w-7xl mx-auto space-y-4">
            <PageHeader title="Quiz drop-off & estilos" subtitle="Dónde abandonan el quiz y combinaciones más comunes" />

            <Card title="Drop-off por pregunta" infoKey="quiz_dropoff">
                {dropoff.length === 0 ? <EmptyState text="Aún no hay tracking de preguntas individuales del quiz" /> : (
                    <table className="w-full text-sm">
                        <thead>
                            <tr className="border-b border-line text-[10px] uppercase tracking-wider text-muted">
                                <th className="text-left p-2 font-semibold">Pregunta</th>
                                <th className="text-right p-2 font-semibold">Users que respondieron</th>
                                <th className="text-right p-2 font-semibold">% del total</th>
                            </tr>
                        </thead>
                        <tbody>
                            {dropoff.map(d => (
                                <tr key={d.question_id} className="border-b border-line/40">
                                    <td className="p-2 font-semibold">{d.question_id}</td>
                                    <td className="p-2 text-right">{d.users_answered}</td>
                                    <td className="p-2 text-right text-accent">{d.pct_of_started}%</td>
                                </tr>
                            ))}
                        </tbody>
                    </table>
                )}
            </Card>

            <Card title="Combinaciones primary × secondary más comunes" infoKey="style_combinations">
                {combinations.length === 0 ? <EmptyState text="Sin combinaciones aún" /> : (
                    <table className="w-full text-sm">
                        <thead>
                            <tr className="border-b border-line text-[10px] uppercase tracking-wider text-muted">
                                <th className="text-left p-2 font-semibold">Primary</th>
                                <th className="text-left p-2 font-semibold">Secondary</th>
                                <th className="text-right p-2 font-semibold">Users</th>
                            </tr>
                        </thead>
                        <tbody>
                            {combinations.map((c, i) => (
                                <tr key={i} className="border-b border-line/40">
                                    <td className="p-2 font-semibold">{c.primary_style}</td>
                                    <td className="p-2 text-muted">{c.secondary_style}</td>
                                    <td className="p-2 text-right">{c.user_count}</td>
                                </tr>
                            ))}
                        </tbody>
                    </table>
                )}
            </Card>
        </div>
    );
}

// ============================================================================
// Page: Quality (errores, permisos denegados)
// ============================================================================
function QualityPage() {
    const [errorsByScreen, setErrorsByScreen] = useState([]);
    const [denials, setDenials] = useState([]);
    const [loading, setLoading] = useState(true);

    useEffect(() => {
        (async () => {
            const [e, d] = await Promise.all([
                supa.from('analytics_errors_by_screen').select('*'),
                supa.from('analytics_permission_denials').select('*'),
            ]);
            setErrorsByScreen(e.data || []);
            setDenials(d.data || []);
            setLoading(false);
        })();
    }, []);

    if (loading) return <CenteredLoader text="Cargando calidad..." />;

    return (
        <div className="p-8 max-w-7xl mx-auto space-y-4">
            <PageHeader title="Calidad técnica" subtitle="Errores, crashes, permisos rechazados" />

            <div className="grid grid-cols-1 md:grid-cols-2 gap-4 items-start">
                <Card title="Errores por pantalla">
                    {errorsByScreen.length === 0 ? <EmptyState text="✅ Sin errores reportados" /> : (
                        <table className="w-full text-sm">
                            <thead>
                                <tr className="border-b border-line text-[10px] uppercase tracking-wider text-muted">
                                    <th className="text-left p-2 font-semibold">Pantalla</th>
                                    <th className="text-right p-2 font-semibold">Errores</th>
                                    <th className="text-right p-2 font-semibold">Users afectados</th>
                                </tr>
                            </thead>
                            <tbody>
                                {errorsByScreen.map(e => (
                                    <tr key={e.screen} className="border-b border-line/40">
                                        <td className="p-2 font-semibold">{e.screen}</td>
                                        <td className="p-2 text-right text-red-600">{e.error_count}</td>
                                        <td className="p-2 text-right text-muted">{e.affected_users}</td>
                                    </tr>
                                ))}
                            </tbody>
                        </table>
                    )}
                </Card>
                <Card title="Permisos denegados">
                    {denials.length === 0 ? <EmptyState text="Aún no hay tracking de permisos" /> : (
                        <DistributionBars data={denials.map(d => ({
                            label: d.permission_type, value: Number(d.denials)
                        }))} />
                    )}
                </Card>
            </div>
        </div>
    );
}

// ============================================================================
// Page: Growth (MoM, WoW, DoD)
// ============================================================================
function GrowthPage() {
    const [daily, setDaily] = useState([]);
    const [weekly, setWeekly] = useState([]);
    const [monthly, setMonthly] = useState([]);
    const [loading, setLoading] = useState(true);

    useEffect(() => {
        (async () => {
            const [d, w, m] = await Promise.all([
                supa.from('analytics_growth_daily').select('*').limit(30),
                supa.from('analytics_growth_weekly').select('*'),
                supa.from('analytics_growth_monthly').select('*'),
            ]);
            setDaily(d.data || []);
            setWeekly(w.data || []);
            setMonthly(m.data || []);
            setLoading(false);
        })();
    }, []);

    if (loading) return <CenteredLoader text="Cargando growth..." />;

    return (
        <div className="p-8 max-w-7xl mx-auto space-y-4">
            <PageHeader title="Growth" subtitle="Crecimiento por día, semana y mes" />

            <Card title="Growth diario (30 días)" infoKey="growth_daily">
                <GrowthTable data={daily} periodLabel="Día" pctLabel="DoD" />
            </Card>

            <div className="grid grid-cols-1 md:grid-cols-2 gap-4 items-start mt-4">
                <Card title="Growth semanal (WoW)" infoKey="growth_weekly">
                    <GrowthTable data={weekly} periodLabel="Semana de" pctLabel="WoW" dateKey="week_start" />
                </Card>
                <Card title="Growth mensual (MoM)" infoKey="growth_monthly">
                    <GrowthTable data={monthly} periodLabel="Mes" pctLabel="MoM" dateKey="month_start" />
                </Card>
            </div>
        </div>
    );
}

function GrowthTable({ data, periodLabel, pctLabel, dateKey = 'day' }) {
    if (data.length === 0) return <EmptyState text="Sin datos suficientes" />;
    return (
        <table className="w-full text-sm">
            <thead>
                <tr className="border-b border-line text-[10px] uppercase tracking-wider text-muted">
                    <th className="text-left p-2 font-semibold">{periodLabel}</th>
                    <th className="text-right p-2 font-semibold">Nuevos users</th>
                    <th className="text-right p-2 font-semibold">{pctLabel}</th>
                </tr>
            </thead>
            <tbody>
                {data.slice(0, 12).map((row, i) => {
                    const pct = Number(row.pct_change_dod ?? row.pct_change_wow ?? row.pct_change_mom ?? 0);
                    return (
                        <tr key={i} className="border-b border-line/40">
                            <td className="p-2">{new Date(row[dateKey]).toLocaleDateString('es-MX', { day: 'numeric', month: 'short', year: '2-digit' })}</td>
                            <td className="p-2 text-right font-semibold">{row.new_users || 0}</td>
                            <td className={`p-2 text-right ${pct > 0 ? 'text-green' : pct < 0 ? 'text-red-600' : 'text-muted'}`}>
                                {pct > 0 ? '+' : ''}{pct}%
                            </td>
                        </tr>
                    );
                })}
            </tbody>
        </table>
    );
}

// ============================================================================
// Page: Geographic
// ============================================================================
function GeographicPage() {
    const [byState, setByState] = useState([]);
    const [byCountry, setByCountry] = useState([]);
    const [loading, setLoading] = useState(true);

    useEffect(() => {
        (async () => {
            const [s, c] = await Promise.all([
                supa.from('analytics_users_by_state_mx').select('*'),
                supa.from('analytics_engagement_by_country').select('*'),
            ]);
            setByState(s.data || []);
            setByCountry(c.data || []);
            setLoading(false);
        })();
    }, []);

    if (loading) return <CenteredLoader text="Cargando geográfico..." />;

    return (
        <div className="p-8 max-w-7xl mx-auto space-y-4">
            <PageHeader title="Geográfico" subtitle="Distribución y engagement por región" />

            <div className="grid grid-cols-1 md:grid-cols-2 gap-4 items-start">
                <Card title="Usuarios por estado (MX)" infoKey="users_by_state_mx">
                    {byState.length === 0 ? <EmptyState text="Sin datos demográficos aún" /> :
                        <DistributionBars data={byState.map(s => ({
                            label: `${s.state} (${s.active_last_7d || 0} activos)`,
                            value: Number(s.user_count)
                        }))} />}
                </Card>
                <Card title="Engagement por país" infoKey="engagement_by_country">
                    {byCountry.length === 0 ? <EmptyState text="Sin datos" /> : (
                        <table className="w-full text-sm">
                            <thead>
                                <tr className="border-b border-line text-[10px] uppercase tracking-wider text-muted">
                                    <th className="text-left p-2 font-semibold">País</th>
                                    <th className="text-right p-2 font-semibold">Users</th>
                                    <th className="text-right p-2 font-semibold">Events/user</th>
                                </tr>
                            </thead>
                            <tbody>
                                {byCountry.map(c => (
                                    <tr key={c.country} className="border-b border-line/40">
                                        <td className="p-2 font-semibold">{c.country}</td>
                                        <td className="p-2 text-right">{c.users}</td>
                                        <td className="p-2 text-right text-accent">{c.events_per_user || 0}</td>
                                    </tr>
                                ))}
                            </tbody>
                        </table>
                    )}
                </Card>
            </div>
        </div>
    );
}

// ============================================================================
// Page: Engagement profundo (dwell, scroll, refresh)
// ============================================================================
function EngagementPage() {
    const [topGarments, setTopGarments] = useState([]);
    const [scrollDepth, setScrollDepth] = useState([]);
    const [refreshFreq, setRefreshFreq] = useState([]);
    const [durationDist, setDurationDist] = useState([]);
    const [loading, setLoading] = useState(true);

    useEffect(() => {
        (async () => {
            const [tg, sd, rf, dd] = await Promise.all([
                supa.from('analytics_top_garments_by_dwell').select('*').limit(15),
                supa.from('analytics_feed_scroll_depth').select('*'),
                supa.from('analytics_refresh_frequency').select('*'),
                supa.from('analytics_garment_view_duration_dist').select('*'),
            ]);
            setTopGarments(tg.data || []);
            setScrollDepth(sd.data || []);
            setRefreshFreq(rf.data || []);
            setDurationDist(dd.data || []);
            setLoading(false);
        })();
    }, []);

    if (loading) return <CenteredLoader text="Cargando engagement..." />;

    const durationLabels = {
        'less_2s': '< 2s', '2_5s': '2–5s', '5_15s': '5–15s',
        '15_30s': '15–30s', 'more_30s': '> 30s'
    };

    return (
        <div className="p-8 max-w-7xl mx-auto space-y-4">
            <PageHeader title="Engagement profundo" subtitle="Cuánto tiempo se quedan viendo cada cosa" />

            <div className="grid grid-cols-1 md:grid-cols-2 gap-4 items-start mb-4">
                <Card title="Distribución de tiempo viendo prendas" infoKey="garment_view_duration_dist">
                    {durationDist.length === 0 ? <EmptyState text="Aún no hay tracking de duración" /> :
                        <DistributionBars data={durationDist.map(d => ({
                            label: durationLabels[d.duration_bucket] || d.duration_bucket,
                            value: Number(d.views)
                        }))} />}
                </Card>
                <Card title="Scroll depth en Feed" infoKey="feed_scroll_depth">
                    {scrollDepth.length === 0 ? <EmptyState text="Sin data de scroll" /> :
                        <DistributionBars data={scrollDepth.map(s => ({
                            label: s.depth_bucket, value: Number(s.sessions)
                        }))} />}
                </Card>
            </div>

            <Card title="Top prendas por tiempo total visto" infoKey="top_garments_dwell">
                {topGarments.length === 0 ? <EmptyState text="Sin tracking de garment views" /> : (
                    <table className="w-full text-sm">
                        <thead>
                            <tr className="border-b border-line text-[10px] uppercase tracking-wider text-muted">
                                <th className="text-left p-2 font-semibold">Garment ID</th>
                                <th className="text-right p-2 font-semibold">Vistas</th>
                                <th className="text-right p-2 font-semibold">Users únicos</th>
                                <th className="text-right p-2 font-semibold">Tiempo total</th>
                                <th className="text-right p-2 font-semibold">Promedio/vista</th>
                            </tr>
                        </thead>
                        <tbody>
                            {topGarments.map(g => (
                                <tr key={g.garment_id} className="border-b border-line/40">
                                    <td className="p-2 text-xs font-mono text-muted">{g.garment_id.substring(0, 8)}...</td>
                                    <td className="p-2 text-right">{g.views}</td>
                                    <td className="p-2 text-right text-muted">{g.unique_viewers}</td>
                                    <td className="p-2 text-right font-semibold">{formatDuration(g.total_seconds || 0)}</td>
                                    <td className="p-2 text-right text-accent">{g.avg_seconds_per_view}s</td>
                                </tr>
                            ))}
                        </tbody>
                    </table>
                )}
            </Card>

            <Card title="Pull-to-refresh por pantalla" infoKey="refresh_freq">
                {refreshFreq.length === 0 ? <EmptyState text="Sin tracking de refresh" /> : (
                    <table className="w-full text-sm">
                        <thead>
                            <tr className="border-b border-line text-[10px] uppercase tracking-wider text-muted">
                                <th className="text-left p-2 font-semibold">Pantalla</th>
                                <th className="text-right p-2 font-semibold">Refreshes</th>
                                <th className="text-right p-2 font-semibold">Users</th>
                                <th className="text-right p-2 font-semibold">Por sesión</th>
                            </tr>
                        </thead>
                        <tbody>
                            {refreshFreq.map(r => (
                                <tr key={r.screen} className="border-b border-line/40">
                                    <td className="p-2 font-semibold">{r.screen}</td>
                                    <td className="p-2 text-right">{r.refresh_count}</td>
                                    <td className="p-2 text-right text-muted">{r.unique_refreshers}</td>
                                    <td className="p-2 text-right text-accent">{r.refreshes_per_session}</td>
                                </tr>
                            ))}
                        </tbody>
                    </table>
                )}
            </Card>
        </div>
    );
}

// ============================================================================
// Page: Search analytics
// ============================================================================
function SearchPage() {
    const [funnel, setFunnel] = useState(null);
    const [zeroClick, setZeroClick] = useState([]);
    const [topSearches, setTopSearches] = useState([]);
    const [loading, setLoading] = useState(true);

    useEffect(() => {
        (async () => {
            const [f, zc, ts] = await Promise.all([
                supa.from('analytics_search_funnel').select('*').single(),
                supa.from('analytics_zero_click_searches').select('*'),
                supa.from('analytics_top_searches').select('*'),
            ]);
            setFunnel(f.data);
            setZeroClick(zc.data || []);
            setTopSearches(ts.data || []);
            setLoading(false);
        })();
    }, []);

    if (loading) return <CenteredLoader text="Cargando search..." />;

    return (
        <div className="p-8 max-w-7xl mx-auto space-y-4">
            <PageHeader title="Search analytics" subtitle="Qué buscan y si encuentran" />

            {funnel && (
                <div className="grid grid-cols-1 md:grid-cols-3 gap-3 items-start mb-6">
                    <KpiCard label="Búsquedas (30d)" value={funnel.searches || 0} infoKey="searches_30d" />
                    <KpiCard label="Clicks en resultados" value={funnel.clicks || 0} infoKey="search_clicks" />
                    <KpiCard label="CTR" value={`${funnel.ctr_pct || 0}%`} infoKey="search_ctr" />
                </div>
            )}

            <div className="grid grid-cols-1 md:grid-cols-2 gap-4 items-start">
                <Card title="Top búsquedas" infoKey="top_searches">
                    {topSearches.length === 0 ? <EmptyState text="Sin búsquedas trackeadas" /> :
                        <DistributionBars data={topSearches.slice(0, 12).map(s => ({
                            label: s.query, value: Number(s.search_count)
                        }))} />}
                </Card>
                <Card title="Búsquedas sin click (gap de inventario)" infoKey="zero_click_searches">
                    {zeroClick.length === 0 ? <EmptyState text="Sin data" /> : (
                        <table className="w-full text-sm">
                            <thead>
                                <tr className="border-b border-line text-[10px] uppercase tracking-wider text-muted">
                                    <th className="text-left p-2 font-semibold">Query</th>
                                    <th className="text-right p-2 font-semibold">Búsquedas</th>
                                    <th className="text-right p-2 font-semibold">Sin click</th>
                                </tr>
                            </thead>
                            <tbody>
                                {zeroClick.map((z, i) => (
                                    <tr key={i} className="border-b border-line/40">
                                        <td className="p-2 font-semibold">{z.query}</td>
                                        <td className="p-2 text-right">{z.search_count}</td>
                                        <td className="p-2 text-right text-like">{z.searches_without_click}</td>
                                    </tr>
                                ))}
                            </tbody>
                        </table>
                    )}
                </Card>
            </div>

            <div className="mt-4 p-4 bg-bg2 rounded-lg text-xs text-muted">
                💡 Las búsquedas sin click son oportunidades: el user buscó algo y no lo encontró/no le interesó lo que apareció. Considera agregar contenido o mejorar resultados para esas queries.
            </div>
        </div>
    );
}

// ============================================================================
// Page: Outfit IA acceptance
// ============================================================================
function OutfitAIPage() {
    const [acceptance, setAcceptance] = useState(null);
    const [byStyle, setByStyle] = useState([]);
    const [evolution, setEvolution] = useState([]);
    const [loading, setLoading] = useState(true);

    useEffect(() => {
        (async () => {
            const [a, bs, ev] = await Promise.all([
                supa.from('analytics_outfit_acceptance').select('*').single(),
                supa.from('analytics_outfit_acceptance_by_style').select('*'),
                supa.from('analytics_style_evolution').select('*'),
            ]);
            setAcceptance(a.data);
            setByStyle(bs.data || []);
            setEvolution(ev.data || []);
            setLoading(false);
        })();
    }, []);

    if (loading) return <CenteredLoader text="Cargando..." />;

    return (
        <div className="p-8 max-w-7xl mx-auto space-y-4">
            <PageHeader title="Outfit IA performance" subtitle="Cuántos outfits propuestos guardan vs rechazan" />

            {acceptance && (
                <div className="grid grid-cols-2 md:grid-cols-5 gap-3 items-start mb-6">
                    <KpiCard label="Generados" value={acceptance.generated || 0} infoKey="outfit_generated_count" />
                    <KpiCard label="Guardados" value={acceptance.saved || 0} infoKey="outfit_saved_count" />
                    <KpiCard label="Rechazados" value={acceptance.rejected || 0} infoKey="outfit_rejected_count" />
                    <KpiCard label="Save rate" value={`${acceptance.save_rate_pct || 0}%`} infoKey="outfit_acceptance" />
                    <KpiCard label="Reject rate" value={`${acceptance.reject_rate_pct || 0}%`} infoKey="outfit_acceptance" />
                </div>
            )}

            <Card title="Acceptance rate por estilo del usuario" infoKey="outfit_acceptance_by_style">
                {byStyle.length === 0 ? <EmptyState text="Sin data de outfits IA aún" /> : (
                    <table className="w-full text-sm">
                        <thead>
                            <tr className="border-b border-line text-[10px] uppercase tracking-wider text-muted">
                                <th className="text-left p-2 font-semibold">Estilo</th>
                                <th className="text-right p-2 font-semibold">Generados</th>
                                <th className="text-right p-2 font-semibold">Guardados</th>
                                <th className="text-right p-2 font-semibold">Save rate</th>
                            </tr>
                        </thead>
                        <tbody>
                            {byStyle.map(s => (
                                <tr key={s.primary_style} className="border-b border-line/40">
                                    <td className="p-2 font-semibold">{s.primary_style}</td>
                                    <td className="p-2 text-right">{s.generated}</td>
                                    <td className="p-2 text-right">{s.saved || 0}</td>
                                    <td className="p-2 text-right text-accent">{s.save_rate_pct || 0}%</td>
                                </tr>
                            ))}
                        </tbody>
                    </table>
                )}
            </Card>

            <Card title="Style evolution (versiones del quiz)" infoKey="style_evolution">
                {evolution.length === 0 ? <EmptyState text="Sin re-tomas del quiz aún" /> : (
                    <table className="w-full text-sm">
                        <thead>
                            <tr className="border-b border-line text-[10px] uppercase tracking-wider text-muted">
                                <th className="text-left p-2 font-semibold">Versión del quiz</th>
                                <th className="text-right p-2 font-semibold">Usuarios</th>
                                <th className="text-right p-2 font-semibold">Estilos distintos</th>
                            </tr>
                        </thead>
                        <tbody>
                            {evolution.map(e => (
                                <tr key={e.quiz_version} className="border-b border-line/40">
                                    <td className="p-2 font-semibold">v{e.quiz_version}</td>
                                    <td className="p-2 text-right">{e.users_at_version}</td>
                                    <td className="p-2 text-right text-muted">{e.distinct_styles_at_version}</td>
                                </tr>
                            ))}
                        </tbody>
                    </table>
                )}
            </Card>

            <div className="mt-4 p-4 bg-bg2 rounded-lg text-xs text-muted">
                💡 Si save rate &lt; 30% en cierto estilo, el algoritmo de outfit IA no está captando bien las preferencias de ese grupo. Considera mejorar el matching para esos estilos.
            </div>
        </div>
    );
}

// ============================================================================
// Page: Commerce deep (AOV, wishlist rate, brand affinity, resale rate)
// ============================================================================
function CommercePage() {
    const [aov, setAov] = useState(null);
    const [wishlist, setWishlist] = useState(null);
    const [brandAffinity, setBrandAffinity] = useState([]);
    const [resale, setResale] = useState(null);
    const [powerBuyers, setPowerBuyers] = useState([]);
    const [loading, setLoading] = useState(true);

    useEffect(() => {
        (async () => {
            const [a, w, ba, r, pb] = await Promise.all([
                supa.from('analytics_aov').select('*').single(),
                supa.from('analytics_wishlist_rate').select('*').single(),
                supa.from('analytics_brand_affinity').select('*'),
                supa.from('analytics_resale_rate').select('*').single(),
                supa.from('analytics_power_buyers').select('*'),
            ]);
            setAov(a.data);
            setWishlist(w.data);
            setBrandAffinity(ba.data || []);
            setResale(r.data);
            setPowerBuyers(pb.data || []);
            setLoading(false);
        })();
    }, []);

    if (loading) return <CenteredLoader text="Cargando commerce..." />;

    return (
        <div className="p-8 max-w-7xl mx-auto space-y-4">
            <PageHeader title="Commerce profundo" subtitle="AOV, wishlist, brand affinity, resale rate" />

            <div className="grid grid-cols-2 md:grid-cols-4 gap-3 items-start">
                <KpiCard label="AOV (vendido)" value={`$${aov?.aov_sold?.toLocaleString() || 0}`} infoKey="aov" />
                <KpiCard label="Mediana precio vendido" value={`$${aov?.median_sold_price?.toLocaleString() || 0}`} infoKey="median_sold_price" />
                <KpiCard label="Wishlist rate" value={`${wishlist?.pct_users_with_favs || 0}%`} infoKey="wishlist_rate" />
                <KpiCard label="Resale rate" value={`${resale?.relist_rate_pct || 0}%`} infoKey="resale_rate" />
            </div>

            <div className="grid grid-cols-1 md:grid-cols-2 gap-4 items-start">
                <Card title="Wishlist stats" infoKey="wishlist_rate">
                    {!wishlist ? <EmptyState text="Sin data" /> : (
                        <DetailGrid rows={[
                            { label: 'Total users', value: wishlist.total_users || 0 },
                            { label: 'Users con favs', value: wishlist.users_with_favs || 0 },
                            { label: 'Total favoritos', value: wishlist.total_favs || 0 },
                            { label: 'Promedio por user activo', value: wishlist.avg_favs_per_active_user || 0 },
                        ]} />
                    )}
                </Card>
                <Card title="Resale stats" infoKey="resale_rate">
                    {!resale ? <EmptyState text="Sin data" /> : (
                        <DetailGrid rows={[
                            { label: 'Total listings', value: resale.total_listed_garments || 0 },
                            { label: 'Relistados', value: resale.relisted_garments || 0 },
                            { label: 'Rate %', value: `${resale.relist_rate_pct || 0}%` },
                        ]} />
                    )}
                </Card>
            </div>

            <Card title="Brand affinity (marcas que conviven en mismo clóset)" infoKey="brand_affinity">
                {brandAffinity.length === 0 ? <EmptyState text="Necesitas más users con varias marcas" /> : (
                    <table className="w-full text-sm">
                        <thead>
                            <tr className="border-b border-line text-[10px] uppercase tracking-wider text-muted">
                                <th className="text-left p-2 font-semibold">Marca A</th>
                                <th className="text-left p-2 font-semibold">Marca B</th>
                                <th className="text-right p-2 font-semibold">Users compartidos</th>
                            </tr>
                        </thead>
                        <tbody>
                            {brandAffinity.map((b, i) => (
                                <tr key={i} className="border-b border-line/40">
                                    <td className="p-2 font-semibold">{b.brand_a}</td>
                                    <td className="p-2 font-semibold">{b.brand_b}</td>
                                    <td className="p-2 text-right text-accent">{b.shared_owners}</td>
                                </tr>
                            ))}
                        </tbody>
                    </table>
                )}
            </Card>

            <Card title="Power buyers (intent señalado por favoritos)" infoKey="power_buyers">
                {powerBuyers.length === 0 ? <EmptyState text="Sin compradores activos" /> : (
                    <table className="w-full text-sm">
                        <thead>
                            <tr className="border-b border-line text-[10px] uppercase tracking-wider text-muted">
                                <th className="text-left p-2 font-semibold">Usuario</th>
                                <th className="text-right p-2 font-semibold">Favoritos</th>
                                <th className="text-right p-2 font-semibold">Valor wishlist</th>
                            </tr>
                        </thead>
                        <tbody>
                            {powerBuyers.map(b => (
                                <tr key={b.id} className="border-b border-line/40 hover:bg-bg2/40 cursor-pointer"
                                    onClick={() => window.location.hash = `user/${b.id}`}>
                                    <td className="p-2 font-semibold text-accent">@{b.handle}</td>
                                    <td className="p-2 text-right">{b.favorites_count}</td>
                                    <td className="p-2 text-right">${Number(b.total_wishlist_value || 0).toLocaleString()}</td>
                                </tr>
                            ))}
                        </tbody>
                    </table>
                )}
            </Card>
        </div>
    );
}

// ============================================================================
// Page: Social graph + influence cascade + DM/Story metrics
// ============================================================================
function SocialGraphPage() {
    const [density, setDensity] = useState(null);
    const [cascade, setCascade] = useState([]);
    const [dmResponse, setDmResponse] = useState(null);
    const [storyReply, setStoryReply] = useState(null);
    const [loading, setLoading] = useState(true);

    useEffect(() => {
        (async () => {
            const [d, c, dm, sr] = await Promise.all([
                supa.from('analytics_friend_graph_density').select('*').single(),
                supa.from('analytics_influence_cascade').select('*'),
                supa.from('analytics_dm_response_time').select('*').single(),
                supa.from('analytics_story_reply_rate').select('*').single(),
            ]);
            setDensity(d.data);
            setCascade(c.data || []);
            setDmResponse(dm.data);
            setStoryReply(sr.data);
            setLoading(false);
        })();
    }, []);

    if (loading) return <CenteredLoader text="Cargando..." />;

    return (
        <div className="p-8 max-w-7xl mx-auto space-y-4">
            <PageHeader title="Red social profunda" subtitle="Density, influence, response times" />

            <div className="grid grid-cols-2 md:grid-cols-4 gap-3 items-start">
                <KpiCard label="Density (%)" value={`${density?.density_pct || 0}%`} infoKey="friend_graph_density" />
                <KpiCard label="Total users" value={density?.total_users || 0} infoKey="total_users" />
                <KpiCard label="Follow edges" value={density?.total_follow_edges || 0} infoKey="total_follow_edges" />
                <KpiCard label="Avg follows/user" value={density?.avg_follows_per_user || 0} infoKey="avg_follows_per_user" />
            </div>

            <div className="grid grid-cols-1 md:grid-cols-2 gap-4 items-start">
                <Card title="DM response time" infoKey="dm_response_time">
                    {!dmResponse ? <EmptyState text="Sin data" /> : (
                        <DetailGrid rows={[
                            { label: 'Pares analizados', value: dmResponse.pairs_analyzed || 0 },
                            { label: 'Mediana respuesta', value: formatDuration(dmResponse.median_response_seconds || 0) },
                            { label: 'Promedio', value: formatDuration(dmResponse.avg_response_seconds || 0) },
                            { label: 'P90 (lento)', value: formatDuration(dmResponse.p90_response_seconds || 0) },
                        ]} />
                    )}
                </Card>
                <Card title="Story reply rate (proxy)" infoKey="story_reply_rate">
                    {!storyReply ? <EmptyState text="Sin data" /> : (
                        <DetailGrid rows={[
                            { label: 'Total stories', value: storyReply.total_stories || 0 },
                            { label: 'Con respuesta', value: storyReply.stories_with_reply || 0 },
                            { label: 'Reply rate', value: `${storyReply.reply_rate_pct || 0}%` },
                            { label: 'Avg repliers', value: storyReply.avg_repliers_per_story || 0 },
                        ]} />
                    )}
                </Card>
            </div>

            <Card title="Influence cascade — efecto de influencers en sus seguidores" infoKey="influence_cascade">
                {cascade.length === 0 ? <EmptyState text="Necesitas users con >10 followers" /> : (
                    <table className="w-full text-sm">
                        <thead>
                            <tr className="border-b border-line text-[10px] uppercase tracking-wider text-muted">
                                <th className="text-left p-2 font-semibold">Influencer</th>
                                <th className="text-right p-2 font-semibold">Posts</th>
                                <th className="text-right p-2 font-semibold">Cascada promedio 24h</th>
                                <th className="text-right p-2 font-semibold">Max cascada</th>
                            </tr>
                        </thead>
                        <tbody>
                            {cascade.map(c => (
                                <tr key={c.influencer_id} className="border-b border-line/40 hover:bg-bg2/40 cursor-pointer"
                                    onClick={() => window.location.hash = `user/${c.influencer_id}`}>
                                    <td className="p-2 font-semibold text-accent">@{c.handle}</td>
                                    <td className="p-2 text-right">{c.posts_made}</td>
                                    <td className="p-2 text-right">{c.avg_cascade_24h}</td>
                                    <td className="p-2 text-right text-muted">{c.max_cascade}</td>
                                </tr>
                            ))}
                        </tbody>
                    </table>
                )}
            </Card>

            <div className="p-4 bg-bg2 rounded-lg text-xs text-muted">
                💡 <strong>Density</strong>: 0% = nadie sigue a nadie; 100% = todos siguen a todos. Plataformas sanas suelen estar 0.5%-5%. <strong>Cascade</strong>: cuántos seguidores postearon en las 24h después de que el influencer publicó.
            </div>
        </div>
    );
}

// ============================================================================
// Page: Cross-tabs demográficos
// ============================================================================
function CrossTabsPage() {
    const [styleByRegion, setStyleByRegion] = useState([]);
    const [ageByBudget, setAgeByBudget] = useState([]);
    const [genderByCategory, setGenderByCategory] = useState([]);
    const [colorAffinity, setColorAffinity] = useState([]);
    const [crosstab, setCrosstab] = useState([]);
    const [versionDist, setVersionDist] = useState([]);
    const [loading, setLoading] = useState(true);

    useEffect(() => {
        (async () => {
            const [s, a, g, c, ct, v] = await Promise.all([
                supa.from('analytics_style_by_region_mx').select('*'),
                supa.from('analytics_age_by_budget').select('*'),
                supa.from('analytics_gender_by_category').select('*'),
                supa.from('analytics_color_affinity').select('*'),
                supa.from('analytics_demo_crosstab').select('*'),
                supa.from('analytics_app_version_distribution').select('*'),
            ]);
            setStyleByRegion(s.data || []);
            setAgeByBudget(a.data || []);
            setGenderByCategory(g.data || []);
            setColorAffinity(c.data || []);
            setCrosstab(ct.data || []);
            setVersionDist(v.data || []);
            setLoading(false);
        })();
    }, []);

    if (loading) return <CenteredLoader text="Cargando..." />;

    return (
        <div className="p-8 max-w-7xl mx-auto space-y-4">
            <PageHeader title="Cross-tabs demográficos" subtitle="Edad × presupuesto, género × categoría, estilo × región" />

            <div className="grid grid-cols-1 md:grid-cols-2 gap-4 items-start">
                <Card title="Edad × presupuesto" infoKey="age_by_budget">
                    {ageByBudget.length === 0 ? <EmptyState text="Sin data demográfica" /> : (
                        <table className="w-full text-xs">
                            <thead>
                                <tr className="border-b border-line text-[10px] uppercase tracking-wider text-muted">
                                    <th className="text-left p-1 font-semibold">Edad</th>
                                    <th className="text-left p-1 font-semibold">Presupuesto</th>
                                    <th className="text-right p-1 font-semibold">Users</th>
                                </tr>
                            </thead>
                            <tbody>
                                {ageByBudget.map((r, i) => (
                                    <tr key={i} className="border-b border-line/30">
                                        <td className="p-1">{r.age_bucket}</td>
                                        <td className="p-1 text-muted">{r.budget_range}</td>
                                        <td className="p-1 text-right">{r.user_count}</td>
                                    </tr>
                                ))}
                            </tbody>
                        </table>
                    )}
                </Card>
                <Card title="Género × categoría de prenda" infoKey="gender_by_category">
                    {genderByCategory.length === 0 ? <EmptyState text="Sin data" /> : (
                        <table className="w-full text-xs">
                            <thead>
                                <tr className="border-b border-line text-[10px] uppercase tracking-wider text-muted">
                                    <th className="text-left p-1 font-semibold">Género</th>
                                    <th className="text-left p-1 font-semibold">Categoría</th>
                                    <th className="text-right p-1 font-semibold">Prendas</th>
                                </tr>
                            </thead>
                            <tbody>
                                {genderByCategory.map((r, i) => (
                                    <tr key={i} className="border-b border-line/30">
                                        <td className="p-1 font-semibold">{r.gender}</td>
                                        <td className="p-1 text-muted">{r.category}</td>
                                        <td className="p-1 text-right">{r.garment_count}</td>
                                    </tr>
                                ))}
                            </tbody>
                        </table>
                    )}
                </Card>
            </div>

            <Card title="Estilo × región (México)" infoKey="style_by_region_mx">
                {styleByRegion.length === 0 ? <EmptyState text="Necesitas users con state + quiz tomado" /> : (
                    <table className="w-full text-sm">
                        <thead>
                            <tr className="border-b border-line text-[10px] uppercase tracking-wider text-muted">
                                <th className="text-left p-2 font-semibold">Estado</th>
                                <th className="text-left p-2 font-semibold">Estilo primario</th>
                                <th className="text-right p-2 font-semibold">Users</th>
                            </tr>
                        </thead>
                        <tbody>
                            {styleByRegion.map((r, i) => (
                                <tr key={i} className="border-b border-line/40">
                                    <td className="p-2 font-semibold">{r.state}</td>
                                    <td className="p-2">{r.primary_style}</td>
                                    <td className="p-2 text-right">{r.user_count}</td>
                                </tr>
                            ))}
                        </tbody>
                    </table>
                )}
            </Card>

            <Card title="Color affinity (clóset vs uso)" infoKey="color_affinity_table">
                {colorAffinity.length === 0 ? <EmptyState text="Sin colores trackeados" /> : (
                    <table className="w-full text-sm">
                        <thead>
                            <tr className="border-b border-line text-[10px] uppercase tracking-wider text-muted">
                                <th className="text-left p-2 font-semibold">Color</th>
                                <th className="text-right p-2 font-semibold">En clóset</th>
                                <th className="text-right p-2 font-semibold">Veces usado</th>
                                <th className="text-right p-2 font-semibold">% utilización</th>
                            </tr>
                        </thead>
                        <tbody>
                            {colorAffinity.map((c, i) => (
                                <tr key={i} className="border-b border-line/40">
                                    <td className="p-2 font-semibold">{c.color}</td>
                                    <td className="p-2 text-right">{c.in_closet}</td>
                                    <td className="p-2 text-right">{c.worn_count}</td>
                                    <td className="p-2 text-right text-accent">{c.utilization_pct || 0}%</td>
                                </tr>
                            ))}
                        </tbody>
                    </table>
                )}
            </Card>

            <div className="grid grid-cols-1 md:grid-cols-2 gap-4 items-start">
                <Card title="App version distribution" infoKey="app_version_dist">
                    {versionDist.length === 0 ? <EmptyState text="Sin data" /> :
                        <DistributionBars data={versionDist.map(v => ({
                            label: v.app_version, value: Number(v.users)
                        }))} />}
                </Card>
                <Card title="Cross-tab completo (gender × age × país)" infoKey="crosstab_demo">
                    {crosstab.length === 0 ? <EmptyState text="Sin data" /> : (
                        <table className="w-full text-xs">
                            <thead>
                                <tr className="border-b border-line text-[10px] uppercase tracking-wider text-muted">
                                    <th className="text-left p-1 font-semibold">G</th>
                                    <th className="text-left p-1 font-semibold">Edad</th>
                                    <th className="text-left p-1 font-semibold">País</th>
                                    <th className="text-right p-1 font-semibold">Users</th>
                                    <th className="text-right p-1 font-semibold">Engagement</th>
                                </tr>
                            </thead>
                            <tbody>
                                {crosstab.slice(0, 12).map((r, i) => (
                                    <tr key={i} className="border-b border-line/30">
                                        <td className="p-1">{r.gender}</td>
                                        <td className="p-1">{r.age_bucket}</td>
                                        <td className="p-1">{r.country}</td>
                                        <td className="p-1 text-right">{r.users}</td>
                                        <td className="p-1 text-right text-accent">{r.avg_engagement}</td>
                                    </tr>
                                ))}
                            </tbody>
                        </table>
                    )}
                </Card>
            </div>
        </div>
    );
}

// ============================================================================
// Page: Activation rate — métrica norte de growth
// ============================================================================
function ActivationPage() {
    const [activation, setActivation] = useState(null);
    const [loading, setLoading] = useState(true);

    useEffect(() => {
        (async () => {
            const { data } = await supa.from('analytics_activation_rate').select('*').single();
            setActivation(data);
            setLoading(false);
        })();
    }, []);

    if (loading) return <CenteredLoader text="Cargando..." />;

    return (
        <div className="p-8 max-w-7xl mx-auto space-y-4">
            <PageHeader title="Activación" subtitle="¿Cuántos signups llegan al 'aha moment'?" />

            <div className="grid grid-cols-1 md:grid-cols-3 gap-3 items-start">
                <KpiCard label="Total signups" value={activation?.total_signups || 0} infoKey="total_users" />
                <KpiCard label="Usuarios activados" value={activation?.activated_users || 0} infoKey="activation_rate" />
                <KpiCard label="Activation rate" value={`${activation?.activation_rate_pct || 0}%`} infoKey="activation_rate" />
            </div>

            <Card title="Definición de activación">
                <div className="text-sm text-ink2 space-y-2">
                    <p><strong>Aha moment</strong>: el momento donde el user "se convierte" — descubre el valor real de tu app.</p>
                    <p>Para Ateliere lo definí como: <strong>guardar el primer outfit IA en los primeros 7 días después de signup</strong>.</p>
                    <p className="text-muted">Cuando el user llega al outfit_saved temprano, los datos muestran que retiene mucho mejor. Por eso esta métrica es la <strong>North Star</strong> del producto.</p>
                </div>
            </Card>

            <div className="p-4 bg-bg2 rounded-lg text-xs text-muted">
                💡 Benchmark: activation rates de 25-40% son normales en apps consumer. &lt;15% indica un onboarding débil. Si quieres mover la aguja, optimiza la ruta desde signup → quiz → upload → primer outfit.
            </div>
        </div>
    );
}

// ============================================================================
// AICostsPage — Gastos de AI (renders, tagging, generación 3D, etc.)
// ============================================================================
//
// Fuente principal: tabla `public.ai_interactions`. Cada call a OpenAI/Claude
// registra un row con `feature`, `model`, `cost_usd`, tokens, duración.
//
// Para Meshy 3D: la tabla NO tiene `cost_usd` para esos rows (Meshy se cobra
// con su API key, no facturamos directo). Aplicamos una TARIFA FIJA estimada
// por generación basada en el plan Pro ($20/mes / 200 créditos ≈ $0.10/gen).
//
// El page muestra:
//   1. KPIs: total mes / mes anterior / día actual / promedio diario
//   2. Línea de tiempo: costo por día últimos 30 días
//   3. Tabla: distribución por feature (con count, USD, %)
//   4. Top usuarios por gasto del mes
//   5. Últimas 30 interactions detalladas

/// Tarifas FALLBACK por si algún row antiguo no tiene cost_usd.
/// Desde v15 de `generate-3d-model`, Meshy guarda cost_usd directamente,
/// y los rows viejos fueron back-filleados. Estas tarifas son solo
/// defensa por si entran rows nuevos sin costo en el futuro.
const AI_COST_FALLBACK = {
    generate_3d_model_meshy: 0.10,
    generate_3d_model: 0.50,
    generate_3d_model_rodin: 0.30,
};

const FEATURE_LABELS = {
    garment_tag: 'Identificar prenda (tags automáticos)',
    garment_render: 'Render 2D editorial de prenda',
    process_garment_render: 'Render 2D editorial de prenda',
    ai_detect_piece_type: 'Detectar tipo de prenda en mirror pic',
    ai_detect_shoes: 'Detectar zapatos en foto',
    generate_3d_model_meshy: 'Generar modelo 3D de prenda',
    generate_3d_model: 'Generar modelo 3D (Tripo legacy)',
    generate_3d_model_rodin: 'Generar modelo 3D (Rodin legacy)',
    color_correction: 'Corregir color de prenda',
    style_quiz: 'Quiz de estilo personal',
    colormetria: 'Análisis de colormetría',
    outfit_generator: 'Generar outfit con IA',
};

/// Determina qué APLICACIÓN externa cobra por cada interaction, basado en
/// el `model` o el `feature`. Cada app se factura por separado (cuenta + key
/// distinta), así que agrupar por aquí es lo más útil para ver costos.
function appForRow(r) {
    const m = String(r.model || '').toLowerCase();
    const f = String(r.feature || '').toLowerCase();
    if (m.includes('claude') || m.includes('haiku') || m.includes('sonnet') || m.includes('opus')) {
        return { id: 'anthropic', label: 'Anthropic (Claude)', emoji: '🧠' };
    }
    if (m.includes('gpt') || m.includes('openai') || m.includes('dall') || m.includes('o1') || m.includes('o3')) {
        return { id: 'openai', label: 'OpenAI (GPT)', emoji: '✨' };
    }
    if (m.includes('meshy') || f.includes('meshy')) {
        return { id: 'meshy', label: 'Meshy AI (3D)', emoji: '🧊' };
    }
    if (m.includes('tripo') || (f === 'generate_3d_model' && !m.includes('meshy') && !m.includes('rodin'))) {
        return { id: 'tripo', label: 'Tripo AI (legacy)', emoji: '📦' };
    }
    if (m.includes('rodin') || f.includes('rodin')) {
        return { id: 'rodin', label: 'Hyper3D Rodin (legacy)', emoji: '🪨' };
    }
    return { id: 'other', label: 'Otra IA', emoji: '❓' };
}

function AICostsPage() {
    const [interactions, setInteractions] = useState([]);
    const [loading, setLoading] = useState(true);
    const [refreshing, setRefreshing] = useState(false);
    const [lastUpdated, setLastUpdated] = useState(null);
    const chartRef = useRef(null);
    const chartInstance = useRef(null);

    // Fetch data — invocable manualmente desde el botón refresh
    const fetchData = async (showLoading = true) => {
        if (showLoading) setLoading(true);
        else setRefreshing(true);
        const since = new Date();
        since.setDate(since.getDate() - 60);
        const { data, error } = await supa
            .from('ai_interactions')
            .select('feature, model, cost_usd, input_tokens, output_tokens, duration_ms, user_id, created_at, error')
            .gte('created_at', since.toISOString())
            .order('created_at', { ascending: false })
            .limit(5000);
        if (error) console.error('ai_interactions error:', error);
        setInteractions(data || []);
        setLastUpdated(new Date());
        setLoading(false);
        setRefreshing(false);
    };

    useEffect(() => {
        fetchData(true);
        // Auto-refresh cada 20s mientras la página esté abierta — para que
        // los costos se actualicen casi en vivo cuando se hacen llamadas
        // nuevas a las IAs sin tener que recargar manualmente.
        const interval = setInterval(() => fetchData(false), 20_000);
        return () => clearInterval(interval);
    }, []);

    // Costo USD efectivo de un interaction (cost_usd si está, sino fallback).
    const effectiveCost = (r) => {
        if (r.cost_usd != null && r.cost_usd > 0) return Number(r.cost_usd);
        return AI_COST_FALLBACK[r.feature] || 0;
    };

    const { kpis, byApp, byDay, topUsers, recent } = useMemo(() => {
        if (interactions.length === 0) {
            return { kpis: {}, byApp: [], byDay: [], topUsers: [], recent: [] };
        }
        const now = new Date();
        const startOfMonth = new Date(now.getFullYear(), now.getMonth(), 1);
        const startOfPrevMonth = new Date(now.getFullYear(), now.getMonth() - 1, 1);
        const startOfToday = new Date(now.getFullYear(), now.getMonth(), now.getDate());
        const enriched = interactions.map(r => ({ ...r, _cost: effectiveCost(r) }));

        const monthTotal = enriched
            .filter(r => new Date(r.created_at) >= startOfMonth)
            .reduce((a, r) => a + r._cost, 0);
        const prevMonthTotal = enriched
            .filter(r => {
                const d = new Date(r.created_at);
                return d >= startOfPrevMonth && d < startOfMonth;
            })
            .reduce((a, r) => a + r._cost, 0);
        const todayTotal = enriched
            .filter(r => new Date(r.created_at) >= startOfToday)
            .reduce((a, r) => a + r._cost, 0);

        const lastRow = enriched[enriched.length - 1];
        const lastTs = (lastRow && lastRow.created_at) ? lastRow.created_at : now;
        const daysInData = Math.min(60, Math.max(1, Math.ceil((now - new Date(lastTs)) / 86400000)));
        const avgDaily = enriched.reduce((a, r) => a + r._cost, 0) / daysInData;

        const kpis = {
            monthTotal,
            prevMonthTotal,
            monthDelta: prevMonthTotal > 0 ? ((monthTotal - prevMonthTotal) / prevMonthTotal * 100) : 0,
            todayTotal,
            avgDaily,
            totalCalls: enriched.length,
            errorRate: enriched.length > 0
                ? (enriched.filter(r => r.error).length / enriched.length * 100)
                : 0,
        };

        // Agrupar por APLICACIÓN (OpenAI, Claude, Meshy, etc.) y dentro de
        // cada app por FEATURE (qué se compró/usó). Esto es lo que el user
        // ve primero — qué app está cobrando + en qué se gasta cada cuenta.
        const appMap = {};
        for (const r of enriched) {
            const app = appForRow(r);
            if (!appMap[app.id]) {
                appMap[app.id] = {
                    id: app.id, label: app.label, emoji: app.emoji,
                    count: 0, cost: 0, errors: 0,
                    tokens_in: 0, tokens_out: 0,
                    features: {},
                };
            }
            const a = appMap[app.id];
            a.count++;
            a.cost += r._cost;
            if (r.error) a.errors++;
            a.tokens_in += (r.input_tokens || 0);
            a.tokens_out += (r.output_tokens || 0);

            const fkey = r.feature || 'unknown';
            if (!a.features[fkey]) {
                a.features[fkey] = { feature: fkey, count: 0, cost: 0, errors: 0 };
            }
            a.features[fkey].count++;
            a.features[fkey].cost += r._cost;
            if (r.error) a.features[fkey].errors++;
        }
        // Convertir a array y ordenar features dentro de cada app por costo
        const byApp = Object.values(appMap)
            .map(a => ({
                ...a,
                features: Object.values(a.features).sort((x, y) => y.cost - x.cost),
            }))
            .sort((a, b) => b.cost - a.cost);

        // Por día — últimos 30 días
        const dayMap = {};
        for (let i = 0; i < 30; i++) {
            const d = new Date(now);
            d.setDate(d.getDate() - i);
            const key = d.toISOString().slice(0, 10);
            dayMap[key] = 0;
        }
        for (const r of enriched) {
            const key = (r.created_at || '').slice(0, 10);
            if (key in dayMap) dayMap[key] += r._cost;
        }
        const byDay = Object.entries(dayMap)
            .sort(([a], [b]) => a.localeCompare(b))
            .map(([day, cost]) => ({ day, cost }));

        // Top users por costo del mes
        const userMap = {};
        for (const r of enriched) {
            if (!r.user_id || new Date(r.created_at) < startOfMonth) continue;
            if (!userMap[r.user_id]) userMap[r.user_id] = { user_id: r.user_id, count: 0, cost: 0 };
            userMap[r.user_id].count++;
            userMap[r.user_id].cost += r._cost;
        }
        const topUsers = Object.values(userMap).sort((a, b) => b.cost - a.cost).slice(0, 10);

        // Recent 30
        const recent = enriched.slice(0, 30);

        return { kpis, byApp, byDay, topUsers, recent };
    }, [interactions]);

    // Chart de gasto diario
    useEffect(() => {
        if (loading || !chartRef.current || byDay.length === 0) return;
        if (chartInstance.current) chartInstance.current.destroy();
        chartInstance.current = new Chart(chartRef.current, {
            type: 'bar',
            data: {
                labels: byDay.map(d => new Date(d.day).toLocaleDateString('es-MX', { day: 'numeric', month: 'short' })),
                datasets: [{
                    label: 'USD',
                    data: byDay.map(d => d.cost),
                    backgroundColor: 'rgba(189, 153, 98, 0.8)',
                    borderColor: '#BD9962',
                    borderWidth: 1,
                }]
            },
            options: {
                responsive: true,
                maintainAspectRatio: false,
                plugins: {
                    legend: { display: false },
                    tooltip: {
                        callbacks: {
                            label: (ctx) => `$${Number(ctx.raw).toFixed(4)}`,
                        }
                    }
                },
                scales: {
                    x: { grid: { display: false }, ticks: { font: { size: 9 }, maxRotation: 0 } },
                    y: {
                        beginAtZero: true,
                        ticks: {
                            font: { size: 10 },
                            callback: (v) => `$${Number(v).toFixed(2)}`,
                        }
                    }
                }
            }
        });
    }, [byDay, loading]);

    if (loading) return <CenteredLoader text="Cargando gastos de AI..." />;

    const fmt = (n) => `$${Number(n || 0).toFixed(2)}`;
    const fmtFine = (n) => `$${Number(n || 0).toFixed(4)}`;
    const totalCostShown = byApp.reduce((a, b) => a + b.cost, 0);

    // Defaults safe — cuando interactions=[] kpis es {} y .toFixed() crashea
    const monthDelta = Number(kpis.monthDelta || 0);
    const errorRate = Number(kpis.errorRate || 0);
    const totalCalls = Number(kpis.totalCalls || 0);

    return (
        <div className="p-8 max-w-7xl mx-auto space-y-4">
            <div className="flex items-start justify-between">
                <PageHeader
                    title="Money spent"
                    subtitle="Gastos de OpenAI, Claude, Meshy y otras IAs (últimos 60 días)"
                />
                <div className="flex flex-col items-end gap-1 mt-1">
                    <button
                        onClick={() => fetchData(false)}
                        disabled={refreshing}
                        className="px-3 py-1.5 text-xs font-semibold rounded-full bg-accent/10 text-accent border border-accent/30 hover:bg-accent/20 transition disabled:opacity-50"
                    >
                        {refreshing ? '↻ Actualizando…' : '↻ Refrescar'}
                    </button>
                    {lastUpdated && (
                        <div className="text-[10px] text-muted">
                            Actualizado: {lastUpdated.toLocaleTimeString('es-MX')}
                            <span className="ml-1 opacity-60">· auto cada 20s</span>
                        </div>
                    )}
                </div>
            </div>

            {/* KPIs */}
            <div className="grid grid-cols-2 md:grid-cols-4 gap-3">
                <KpiCard
                    label="Este mes"
                    value={fmt(kpis.monthTotal)}
                    delta={`${monthDelta > 0 ? '+' : ''}${monthDelta.toFixed(0)}% vs mes anterior`}
                    deltaPositive={monthDelta < 0}
                />
                <KpiCard
                    label="Hoy"
                    value={fmt(kpis.todayTotal)}
                    delta={`Promedio: ${fmt(kpis.avgDaily)}/día`}
                />
                <KpiCard
                    label="Mes anterior"
                    value={fmt(kpis.prevMonthTotal)}
                />
                <KpiCard
                    label="Total calls"
                    value={totalCalls.toLocaleString('es-MX')}
                    delta={`${errorRate.toFixed(1)}% errores`}
                    deltaPositive={errorRate < 5}
                />
            </div>

            {/* Chart de gasto diario */}
            <Card title="Gasto diario últimos 30 días">
                <div style={{ height: 280 }}>
                    <canvas ref={chartRef}></canvas>
                </div>
            </Card>

            {/* Gasto POR APLICACIÓN — cada card es una app/cuenta distinta,
                con sus features dentro mostrando en qué se va el dinero. */}
            <div className="space-y-3">
                {byApp.map(a => {
                    const pct = totalCostShown > 0 ? (a.cost / totalCostShown * 100) : 0;
                    return (
                        <Card key={a.id}>
                            <div className="flex items-start justify-between mb-3">
                                <div>
                                    <div className="flex items-center gap-2">
                                        <span className="text-2xl">{a.emoji}</span>
                                        <h3 className="font-semibold text-ink text-base">{a.label}</h3>
                                    </div>
                                    <div className="text-xs text-muted mt-1">
                                        {a.count.toLocaleString('es-MX')} llamadas
                                        {a.errors > 0 && (
                                            <span className="text-red-500 ml-2">· {a.errors} errores</span>
                                        )}
                                        {(a.tokens_in > 0 || a.tokens_out > 0) && (
                                            <span className="ml-2">
                                                · {((a.tokens_in + a.tokens_out)/1000).toFixed(1)}k tokens
                                            </span>
                                        )}
                                    </div>
                                </div>
                                <div className="text-right">
                                    <div className="text-2xl font-semibold text-ink">{fmtFine(a.cost)}</div>
                                    <div className="text-xs text-muted">{pct.toFixed(1)}% del total</div>
                                </div>
                            </div>

                            {/* Lista de features (qué se compra con cada cuenta) */}
                            <div className="border-t border-line/40 pt-3">
                                <div className="text-[10px] uppercase tracking-wider text-muted mb-2">
                                    Desglose por uso
                                </div>
                                <div className="space-y-1.5">
                                    {a.features.map(f => {
                                        const fPct = a.cost > 0 ? (f.cost / a.cost * 100) : 0;
                                        return (
                                            <div key={f.feature} className="flex items-center gap-3 text-sm">
                                                <div className="flex-1 min-w-0">
                                                    <div className="text-ink truncate">
                                                        {FEATURE_LABELS[f.feature] || f.feature}
                                                    </div>
                                                    <div className="text-[10px] text-muted">
                                                        {f.count} llamadas
                                                        {f.errors > 0 && (
                                                            <span className="text-red-500 ml-1">· {f.errors} errores</span>
                                                        )}
                                                    </div>
                                                </div>
                                                {/* barra de porcentaje */}
                                                <div className="w-24 h-1.5 bg-bg2 rounded-full overflow-hidden">
                                                    <div className="h-full bg-accent" style={{ width: `${fPct}%` }} />
                                                </div>
                                                <div className="text-right w-20">
                                                    <div className="font-semibold text-ink text-sm">{fmtFine(f.cost)}</div>
                                                </div>
                                            </div>
                                        );
                                    })}
                                </div>
                            </div>
                        </Card>
                    );
                })}

                {/* TOTAL al final */}
                <div className="bg-bg2 rounded-xl p-4 flex justify-between items-center border border-accent/30">
                    <div>
                        <div className="text-[10px] uppercase tracking-wider text-muted">TOTAL ÚLTIMOS 60 DÍAS</div>
                        <div className="text-xs text-muted mt-0.5">
                            {totalCalls.toLocaleString('es-MX')} llamadas · {byApp.length} aplicaciones
                        </div>
                    </div>
                    <div className="text-3xl font-semibold text-accent">{fmt(totalCostShown)}</div>
                </div>
            </div>

            {/* Top users por gasto */}
            <Card title="Top usuarios por gasto este mes">
                {topUsers.length === 0
                    ? <EmptyState text="Sin gastos registrados este mes" />
                    : (
                        <table className="w-full text-sm">
                            <thead>
                                <tr className="text-left text-muted text-xs uppercase tracking-wider border-b border-line">
                                    <th className="py-2">User ID</th>
                                    <th className="py-2 text-right">Llamadas</th>
                                    <th className="py-2 text-right">Gasto USD</th>
                                </tr>
                            </thead>
                            <tbody>
                                {topUsers.map(u => (
                                    <tr key={u.user_id} className="border-b border-line/30">
                                        <td className="py-2.5 font-mono text-xs">
                                            <a href={`#user/${u.user_id}`} className="text-accent hover:underline">
                                                {u.user_id.slice(0, 8)}...{u.user_id.slice(-4)}
                                            </a>
                                        </td>
                                        <td className="py-2.5 text-right">{u.count}</td>
                                        <td className="py-2.5 text-right font-semibold">{fmtFine(u.cost)}</td>
                                    </tr>
                                ))}
                            </tbody>
                        </table>
                    )
                }
            </Card>

            {/* Detalle reciente */}
            <Card title="Últimas 30 llamadas">
                <table className="w-full text-xs">
                    <thead>
                        <tr className="text-left text-muted uppercase tracking-wider border-b border-line">
                            <th className="py-2">Cuándo</th>
                            <th className="py-2">Feature</th>
                            <th className="py-2">Modelo</th>
                            <th className="py-2 text-right">Duración</th>
                            <th className="py-2 text-right">Costo</th>
                            <th className="py-2">Estado</th>
                        </tr>
                    </thead>
                    <tbody>
                        {recent.map((r, i) => (
                            <tr key={i} className="border-b border-line/30">
                                <td className="py-2 text-muted">{formatTimeAgo(r.created_at)}</td>
                                <td className="py-2 text-ink">{FEATURE_LABELS[r.feature] || r.feature}</td>
                                <td className="py-2 text-muted font-mono text-[10px]">{r.model || '—'}</td>
                                <td className="py-2 text-right text-muted">
                                    {r.duration_ms ? `${(r.duration_ms / 1000).toFixed(1)}s` : '—'}
                                </td>
                                <td className="py-2 text-right font-semibold">{fmtFine(r._cost)}</td>
                                <td className="py-2">
                                    {r.error
                                        ? <span className="text-red-500 text-[10px]">ERROR</span>
                                        : <span className="text-green-600 text-[10px]">OK</span>}
                                </td>
                            </tr>
                        ))}
                    </tbody>
                </table>
            </Card>

            {/* Nota sobre tarifas estimadas */}
            <div className="p-4 bg-bg2 rounded-lg text-xs text-muted space-y-1">
                <div>💡 <strong>Sobre el cálculo:</strong> los costos de OpenAI y Claude vienen directos de la columna <code>cost_usd</code> de cada llamada. Los de Meshy y Tripo se estiman con tarifa fija (~$0.10 y ~$0.50 por generación) porque facturas vía su API key, no nosotros.</div>
                <div>🔍 Para auditoría detallada, consulta directo la tabla <code>ai_interactions</code> en Supabase.</div>
            </div>
        </div>
    );
}

// ============================================================================
// CATÁLOGO — Clasificación manual de productos del marketplace
// ============================================================================

// Supabase limita a 1000 filas por request. Este helper pagina hasta traer todo.
async function fetchAllCatalogProducts(columns) {
    const PAGE = 1000;
    let all = [], from = 0;
    while (true) {
        const { data, error } = await supa
            .from('brand_products')
            .select(columns)
            .eq('status', 'active')
            .range(from, from + PAGE - 1);
        if (error || !data || data.length === 0) break;
        all = all.concat(data);
        if (data.length < PAGE) break;
        from += PAGE;
    }
    return all;
}

// ── Auto-detector de color dominante ──────────────────────────────────────
// Puerto directo del algoritmo ColorScannerView.swift del app iOS:
//   sampleCenterColorHex() — crop central 5%, promedio RGB, sin blancos.
// Diferencia clave vs versión anterior: usa el centro de la imagen (donde
// está la prenda) en lugar del frame completo, igual que el scanner del app.
function extractDominantColor(imageUrl) {
    return new Promise(resolve => {
        const img = new Image();
        img.crossOrigin = 'anonymous';
        img.onload = () => {
            try {
                // Crop central ~5% del lado menor — idéntico a iOS
                const cropSide = Math.max(20, Math.min(img.naturalWidth, img.naturalHeight) / 20);
                const canvas   = document.createElement('canvas');
                canvas.width   = cropSide;
                canvas.height  = cropSide;
                const ctx = canvas.getContext('2d');
                const cx  = (img.naturalWidth  - cropSide) / 2;
                const cy  = (img.naturalHeight - cropSide) / 2;
                ctx.drawImage(img, cx, cy, cropSide, cropSide, 0, 0, cropSide, cropSide);

                const px = ctx.getImageData(0, 0, cropSide, cropSide).data;
                let rSum=0, gSum=0, bSum=0, count=0;
                for (let i = 0; i < px.length; i += 4) {
                    const r=px[i], g=px[i+1], b=px[i+2], a=px[i+3];
                    if (a < 120) continue;                        // transparente
                    if (r > 238 && g > 238 && b > 238) continue; // fondo blanco
                    if (r < 15  && g < 15  && b < 15)  continue; // negro hueco
                    rSum+=r; gSum+=g; bSum+=b; count++;
                }
                if (!count) { resolve('808080'); return; }
                const r = Math.round(rSum/count);
                const g = Math.round(gSum/count);
                const b = Math.round(bSum/count);

                // Mapear al color más cercano de la paleta (distancia euclidiana)
                let bestHex = '808080', bestDist = Infinity;
                for (const c of CAT_KNOWN_COLORS) {
                    const cr = parseInt(c.hex.slice(0,2),16);
                    const cg = parseInt(c.hex.slice(2,4),16);
                    const cb = parseInt(c.hex.slice(4,6),16);
                    const d  = (r-cr)**2 + (g-cg)**2 + (b-cb)**2;
                    if (d < bestDist) { bestDist=d; bestHex=c.hex; }
                }
                resolve(bestHex);
            } catch { resolve('808080'); }
        };
        img.onerror = () => resolve('808080');
        img.src = imageUrl;
    });
}

const CAT_CATEGORIES = ['tops', 'bottoms', 'dresses', 'outerwear', 'shoes', 'accessories'];
const CAT_GENDERS    = ['masculino', 'femenino', 'unisex'];
const CAT_SUBCATS = {
    tops:        ['Camiseta', 'Polo', 'Suéter', 'Hoodie', 'Blusa', 'Camisa', 'Tank top', 'Crop top', 'Turtleneck', 'Cardigan'],
    bottoms:     ['Jeans', 'Pantalón', 'Shorts', 'Falda', 'Leggings', 'Joggers', 'Chinos', 'Cargo'],
    dresses:     ['Vestido', 'Jumpsuit', 'Romper', 'Bodysuit', 'Maxi dress', 'Mini dress', 'Midi dress'],
    outerwear:   ['Chaqueta', 'Abrigo', 'Blazer', 'Parka', 'Chaleco', 'Anorak', 'Cárdigan'],
    shoes:       ['Tenis', 'Botas', 'Sandalias', 'Mocasines', 'Zapatos', 'Sneakers', 'Loafers', 'Oxfords'],
    accessories: ['Bolsa', 'Cinturón', 'Joyería', 'Sombrero', 'Bufanda', 'Cartera', 'Calcetines', 'Ropa interior', 'Maquillaje'],
};
const CAT_KNOWN_COLORS = [
    { hex: '000000', name: 'Negro'       },
    { hex: 'FFFFFF', name: 'Blanco'      },
    { hex: '808080', name: 'Gris'        },
    { hex: 'C0C0C0', name: 'Plata'       },
    { hex: 'F5F5DC', name: 'Beige'       },
    { hex: 'F5EDDE', name: 'Crema'       },
    { hex: 'D2B48C', name: 'Café claro' },
    { hex: '8B4513', name: 'Café'        },
    { hex: '1B3A5C', name: 'Marino'      },
    { hex: '2D5A88', name: 'Azul'        },
    { hex: '87CEEB', name: 'Azul cielo' },
    { hex: '228B22', name: 'Verde'       },
    { hex: '2D5A4A', name: 'Verde oscuro'},
    { hex: 'FF0000', name: 'Rojo'        },
    { hex: 'D32B2B', name: 'Rojo oscuro'},
    { hex: 'FF69B4', name: 'Rosa'        },
    { hex: 'FFB6C1', name: 'Rosa claro' },
    { hex: 'FF8C00', name: 'Naranja'     },
    { hex: 'FFD700', name: 'Amarillo'    },
    { hex: '800080', name: 'Morado'      },
    { hex: '4B0082', name: 'Índigo'      },
    { hex: 'FFFFF0', name: 'Ivory'       },
    { hex: '36454F', name: 'Carbón'      },
    { hex: 'F0E68C', name: 'Khaki'       },
    { hex: 'BC8F5F', name: 'Tan'         },
];

// ── CatalogOverviewPage ────────────────────────────────────────────────────
function CatalogOverviewPage() {
    const [products, setProducts] = useState([]);
    const [vendors, setVendors]   = useState([]);
    const [loading, setLoading]   = useState(true);

    useEffect(() => {
        (async () => {
            const [prods, { data: vends }] = await Promise.all([
                fetchAllCatalogProducts('id,category,gender_target,price_cents,color_hex,manually_reviewed,vendor_id'),
                supa.from('brand_vendors').select('id,name,gender_target').eq('status','active'),
            ]);
            setProducts(prods);
            setVendors(vends || []);
            setLoading(false);
        })();
    }, []);

    if (loading) return <CenteredLoader text="Cargando catálogo..." />;

    const total    = products.length;
    const reviewed = products.filter(p => p.manually_reviewed).length;
    const pending  = total - reviewed;
    const pctDone  = total ? Math.round(reviewed / total * 100) : 0;

    const byGender = {};
    products.forEach(p => { const g = p.gender_target || 'unisex'; byGender[g] = (byGender[g]||0)+1; });

    const byCat = {};
    const catPriceSum = {}, catPriceCnt = {};
    products.forEach(p => {
        const c = p.category || 'sin cat.';
        byCat[c] = (byCat[c]||0)+1;
        if (p.price_cents) {
            catPriceSum[c] = (catPriceSum[c]||0) + p.price_cents;
            catPriceCnt[c] = (catPriceCnt[c]||0) + 1;
        }
    });
    const catEntries = Object.entries(byCat).sort((a,b)=>b[1]-a[1]);
    const avgPrice   = total ? Math.round(products.reduce((s,p)=>s+(p.price_cents||0),0)/total/100) : 0;

    // Vendor progress (% reviewed per vendor)
    const vendorMap = {};
    vendors.forEach(v => { vendorMap[v.id] = v.name; });
    const vendorReviewed = {}, vendorTotal = {};
    products.forEach(p => {
        const n = vendorMap[p.vendor_id] || p.vendor_id;
        vendorTotal[n]    = (vendorTotal[n]||0)+1;
        if (p.manually_reviewed) vendorReviewed[n] = (vendorReviewed[n]||0)+1;
    });
    const vendorRows = Object.entries(vendorTotal)
        .map(([name,cnt]) => ({ name, total: cnt, done: vendorReviewed[name]||0 }))
        .sort((a,b) => (b.done/b.total) - (a.done/a.total));

    return (
        <div className="p-8 max-w-7xl mx-auto space-y-4">
            <PageHeader title="Catálogo" subtitle="Estado general del inventario del marketplace" />

            <div className="grid grid-cols-2 md:grid-cols-4 gap-3">
                <KpiCard label="Total productos"          value={total.toLocaleString()} />
                <KpiCard label="Clasificados manualmente" value={`${reviewed.toLocaleString()} (${pctDone}%)`} />
                <KpiCard label="Pendientes"               value={pending.toLocaleString()} />
                <KpiCard label="Precio promedio"          value={`$${avgPrice}`} />
            </div>

            <div className="grid grid-cols-2 gap-4">
                <Card title="Distribución por género">
                    {Object.entries(byGender).sort((a,b)=>b[1]-a[1]).map(([g,n]) => (
                        <div key={g} className="flex items-center gap-3 mb-2">
                            <div className="w-20 text-[11px] text-muted capitalize">{g}</div>
                            <div className="flex-1 bg-line rounded-full h-2">
                                <div className="bg-accent rounded-full h-2" style={{width:`${Math.round(n/total*100)}%`}} />
                            </div>
                            <div className="text-xs text-ink2 w-20 text-right">
                                {n.toLocaleString()} <span className="text-muted">({Math.round(n/total*100)}%)</span>
                            </div>
                        </div>
                    ))}
                </Card>
                <Card title="Distribución por categoría">
                    {catEntries.map(([c,n]) => (
                        <div key={c} className="flex items-center gap-3 mb-2">
                            <div className="w-20 text-[11px] text-muted capitalize">{c}</div>
                            <div className="flex-1 bg-line rounded-full h-2">
                                <div className="bg-accent rounded-full h-2" style={{width:`${Math.round(n/total*100)}%`}} />
                            </div>
                            <div className="text-xs text-ink2 w-20 text-right">
                                {n.toLocaleString()} <span className="text-muted">({Math.round(n/total*100)}%)</span>
                            </div>
                        </div>
                    ))}
                </Card>
            </div>

            <div className="grid grid-cols-2 gap-4">
                <Card title="Precio promedio por categoría">
                    <table className="w-full text-sm">
                        <thead>
                            <tr className="border-b border-line text-[10px] uppercase tracking-wider text-muted">
                                <th className="text-left pb-2">Categoría</th>
                                <th className="text-right pb-2">Productos</th>
                                <th className="text-right pb-2">Precio prom.</th>
                            </tr>
                        </thead>
                        <tbody>
                            {catEntries.map(([c,n]) => (
                                <tr key={c} className="border-b border-line/40">
                                    <td className="py-1.5 capitalize">{c}</td>
                                    <td className="py-1.5 text-right text-muted">{n}</td>
                                    <td className="py-1.5 text-right text-accent font-semibold">
                                        ${catPriceCnt[c] ? Math.round(catPriceSum[c]/catPriceCnt[c]/100) : '—'}
                                    </td>
                                </tr>
                            ))}
                        </tbody>
                    </table>
                </Card>

                <Card title="Progreso de clasificación manual">
                    <div className="mb-4">
                        <div className="flex justify-between text-xs text-muted mb-1">
                            <span>Completado</span><span>{pctDone}%</span>
                        </div>
                        <div className="bg-line rounded-full h-3">
                            <div className="bg-accent rounded-full h-3 transition-all" style={{width:`${pctDone}%`}} />
                        </div>
                    </div>
                    <div className="text-xs text-muted space-y-1 mb-4">
                        <div className="flex justify-between"><span>✓ Revisados</span><span className="text-ink2 font-semibold">{reviewed}</span></div>
                        <div className="flex justify-between"><span>⏳ Pendientes</span><span className="text-ink2 font-semibold">{pending}</span></div>
                        <div className="flex justify-between border-t border-line pt-1 font-semibold"><span>Total</span><span>{total}</span></div>
                    </div>
                    <a href="#catalog_pending"
                        className="block text-center text-xs bg-accent/10 text-accent hover:bg-accent hover:text-white rounded-lg py-2 transition-colors font-medium">
                        Ir a Pendientes →
                    </a>
                </Card>
            </div>

            <Card title="Progreso por marca">
                <div className="grid grid-cols-2 gap-x-8 gap-y-1.5">
                    {vendorRows.slice(0,20).map(v => {
                        const pct = v.total ? Math.round(v.done/v.total*100) : 0;
                        return (
                            <div key={v.name} className="flex items-center gap-2">
                                <div className="w-28 text-[10px] text-muted truncate">{v.name}</div>
                                <div className="flex-1 bg-line rounded-full h-1.5">
                                    <div className={`rounded-full h-1.5 ${pct===100?'bg-green-500':'bg-accent'}`} style={{width:`${pct}%`}} />
                                </div>
                                <div className="text-[10px] text-ink2 w-8 text-right">{pct}%</div>
                            </div>
                        );
                    })}
                </div>
            </Card>
        </div>
    );
}

// ── CatalogPendingPage ─────────────────────────────────────────────────────
const BATCH_SIZE      = 100;  // cuántos cargamos por lote
const REFILL_AT       = 20;   // cuando quedan ≤20, rellenamos automáticamente

function CatalogPendingPage() {
    const [products,     setProducts]     = useState([]);
    const [vendorNames,  setVendorNames]  = useState({});
    const [loading,      setLoading]      = useState(true);
    const [saving,       setSaving]       = useState({});
    const [savedIds,     setSavedIds]     = useState({});
    const [errorIds,     setErrorIds]     = useState({});
    const [edits,        setEdits]        = useState({});
    const [detecting,    setDetecting]    = useState({});
    const [detectingAll, setDetectingAll] = useState(false);
    const [deleting,     setDeleting]     = useState({});
    const [confirmDel,   setConfirmDel]   = useState({});
    // Progreso global
    const [totalCount,   setTotalCount]   = useState(0);
    const [doneCount,    setDoneCount]    = useState(0);
    const [localSaved,   setLocalSaved]   = useState(0);
    // Paginación / cola
    const [offset,       setOffset]       = useState(0);
    const [hasMore,      setHasMore]      = useState(true);
    const [loadingMore,  setLoadingMore]  = useState(false);
    const [showDone,     setShowDone]     = useState(false);
    // Pixel picker
    const [pickingId,    setPickingId]    = useState(null);
    const [hoverHex,     setHoverHex]     = useState(null);
    const [cursorPos,    setCursorPos]    = useState({x:0,y:0});
    const imgRefs    = useRef({});
    const canvasCache = useRef({});
    const [search,       setSearch]       = useState('');
    const [fGender,      setFGender]      = useState('');
    const [fCategory,    setFCategory]    = useState('');
    const [fVendor,      setFVendor]      = useState('');

    useEffect(() => {
        setProducts([]); setEdits({}); setOffset(0); setHasMore(true); setLocalSaved(0);
        load();
    }, [showDone]);

    async function load() {
        setLoading(true);

        // Conteos globales (para la progress bar) — dos queries rápidas de cabecera
        const [{ count: tot }, { count: done }] = await Promise.all([
            supa.from('brand_products').select('*', { count:'exact', head:true }).eq('status','active'),
            supa.from('brand_products').select('*', { count:'exact', head:true }).eq('status','active').eq('manually_reviewed', true),
        ]);
        setTotalCount(tot || 0);
        setDoneCount(done || 0);

        // Primer lote de 100
        await fetchBatch(0, true);
        setLoading(false);
    }

    async function fetchBatch(fromOffset, isFirst = false) {
        const q = supa.from('brand_products')
            .select('id,vendor_id,title,category,subcategory,price_cents,image_url,color_hex,gender_target,manually_reviewed,brand_vendors(name)')
            .eq('status','active')
            .order('id', { ascending: true })
            .range(fromOffset, fromOffset + BATCH_SIZE - 1);

        const finalQ = showDone
            ? q.eq('manually_reviewed', true)
            : q.or('manually_reviewed.eq.false,manually_reviewed.is.null');

        const { data } = await finalQ;
        const prods = data || [];

        // Actualizar vendor names
        setVendorNames(prev => {
            const vm = {...prev};
            prods.forEach(p => { if (p.brand_vendors?.name) vm[p.vendor_id] = p.brand_vendors.name; });
            return vm;
        });

        // Inicializar edits
        setEdits(prev => {
            const next = {...prev};
            prods.forEach(p => {
                if (!next[p.id]) next[p.id] = {
                    gender_target: p.gender_target || 'unisex',
                    category:      p.category      || 'tops',
                    subcategory:   p.subcategory   || '',
                    color_hex:     (p.color_hex    || '808080').replace('#',''),
                };
            });
            return next;
        });

        setProducts(prev => isFirst ? prods : [...prev, ...prods]);
        setOffset(fromOffset + prods.length);
        if (prods.length < BATCH_SIZE) setHasMore(false);
        return prods.length;
    }

    async function refillIfNeeded(remainingCount) {
        if (!hasMore || loadingMore) return;
        if (remainingCount <= REFILL_AT) {
            setLoadingMore(true);
            await fetchBatch(offset);
            setLoadingMore(false);
        }
    }

    function upd(id, field, value) {
        setEdits(prev => ({ ...prev, [id]: { ...(prev[id]||{}), [field]: value } }));
    }

    async function save(id) {
        const e = edits[id]; if (!e) return;
        setSaving(p => ({...p, [id]:true}));
        setErrorIds(p => ({...p, [id]:null}));

        const { error } = await supa.from('brand_products').update({
            gender_target:     e.gender_target,
            category:          e.category,
            subcategory:       e.subcategory || null,
            color_hex:         e.color_hex.replace('#',''),
            manually_reviewed: true,
        }).eq('id', id);

        setSaving(p => ({...p, [id]:false}));
        if (error) {
            setErrorIds(p => ({...p, [id]: error.message || 'Error al guardar'}));
        } else {
            setSavedIds(p => ({...p, [id]:true}));
            setLocalSaved(n => n + 1);
            setTimeout(() => {
                setProducts(prev => {
                    const next = prev.filter(x => x.id !== id);
                    if (!showDone) refillIfNeeded(next.length);
                    return next;
                });
                setSavedIds(p => { const n={...p}; delete n[id]; return n; });
            }, 600);
        }
    }

    // Borrar prenda (soft-delete: status → 'inactive', desaparece del app)
    async function deleteProduct(id) {
        setDeleting(p => ({...p, [id]: true}));
        const { error } = await supa
            .from('brand_products')
            .update({ status: 'inactive' })
            .eq('id', id);
        setDeleting(p => ({...p, [id]: false}));
        if (!error) {
            setProducts(prev => {
                const next = prev.filter(x => x.id !== id);
                if (!showDone) refillIfNeeded(next.length);
                return next;
            });
            setConfirmDel(p => { const n={...p}; delete n[id]; return n; });
            setTotalCount(n => Math.max(0, n - 1));
        }
    }

    // Auto-detectar color de una sola prenda
    async function autoColor(id, imageUrl) {
        if (!imageUrl) return;
        setDetecting(prev => ({...prev, [id]: true}));
        const hex = await extractDominantColor(imageUrl);
        setEdits(prev => ({ ...prev, [id]: { ...(prev[id]||{}), color_hex: hex } }));
        setDetecting(prev => ({...prev, [id]: false}));
    }

    // Auto-detectar color de todas las prendas visibles sin color real
    async function autoColorAll() {
        setDetectingAll(true);
        const toProcess = filtered.filter(p => {
            const e = edits[p.id]||{};
            return p.image_url && (e.color_hex === '808080' || !e.color_hex);
        });
        // Procesar en lotes de 5 en paralelo
        for (let i = 0; i < toProcess.length; i += 5) {
            const batch = toProcess.slice(i, i + 5);
            await Promise.all(batch.map(p => autoColor(p.id, p.image_url)));
        }
        setDetectingAll(false);
    }

    // ── Pixel picker (eyedropper) ────────────────────────────────────────────
    function preloadCanvas(id) {
        const imgEl = imgRefs.current[id];
        if (!imgEl || canvasCache.current[id]) return;
        try {
            const c = document.createElement('canvas');
            c.width  = imgEl.naturalWidth  || 400;
            c.height = imgEl.naturalHeight || 500;
            c.getContext('2d').drawImage(imgEl, 0, 0);
            canvasCache.current[id] = c;
        } catch(e) { /* CORS bloqueado — silencioso */ }
    }

    function sampleAtMouse(id, ev) {
        const canvas = canvasCache.current[id];
        const imgEl  = imgRefs.current[id];
        if (!canvas || !imgEl) return null;
        try {
            const rect  = imgEl.getBoundingClientRect();
            const scaleX = (imgEl.naturalWidth  || canvas.width)  / rect.width;
            const scaleY = (imgEl.naturalHeight || canvas.height) / rect.height;
            const x = Math.max(0, Math.floor((ev.clientX - rect.left) * scaleX));
            const y = Math.max(0, Math.floor((ev.clientY - rect.top)  * scaleY));
            const [r,g,b] = canvas.getContext('2d').getImageData(x, y, 1, 1).data;
            return `${r.toString(16).padStart(2,'0')}${g.toString(16).padStart(2,'0')}${b.toString(16).padStart(2,'0')}`.toUpperCase();
        } catch(e) { return null; }
    }

    function startPick(id) {
        preloadCanvas(id);
        setPickingId(id);
        setHoverHex(null);
    }

    function onPickMove(id, ev) {
        if (pickingId !== id) return;
        const hex = sampleAtMouse(id, ev);
        if (hex) { setHoverHex(hex); setCursorPos({x: ev.clientX, y: ev.clientY}); }
    }

    function onPickClick(id, ev) {
        if (pickingId !== id) return;
        ev.stopPropagation();
        const hex = sampleAtMouse(id, ev);
        if (hex) upd(id, 'color_hex', hex);
        setPickingId(null);
        setHoverHex(null);
    }

    // ESC cancela pick mode
    useEffect(() => {
        if (!pickingId) return;
        const onKey = (e) => { if (e.key === 'Escape') { setPickingId(null); setHoverHex(null); }};
        window.addEventListener('keydown', onKey);
        return () => window.removeEventListener('keydown', onKey);
    }, [pickingId]);

    const uniqueVendors = [...new Set(products.map(p => vendorNames[p.vendor_id]).filter(Boolean))].sort();

    const filtered = products.filter(p => {
        const vn = vendorNames[p.vendor_id]||'';
        if (search && !p.title.toLowerCase().includes(search.toLowerCase()) && !vn.toLowerCase().includes(search.toLowerCase())) return false;
        if (fGender   && p.gender_target !== fGender)   return false;
        if (fCategory && p.category      !== fCategory) return false;
        if (fVendor   && vn              !== fVendor)   return false;
        return true;
    });

    // Calcular progreso
    const totalDone = doneCount + localSaved;
    const pctDone   = totalCount > 0 ? Math.min(100, Math.round(totalDone / totalCount * 100)) : 0;
    const pending   = Math.max(0, totalCount - totalDone);

    return (
        <div className="max-w-[1500px] mx-auto">

            {/* ── Progress bar sticky ────────────────────────────────────── */}
            <div className="sticky top-[53px] md:top-0 z-20 bg-surfaceDeep/95 backdrop-blur-sm px-4 md:px-8 py-3 border-b border-line">
                <div className="flex items-center justify-between text-[10px] text-muted mb-1.5 gap-2">
                    <span className="text-green-400 font-semibold">✓ {totalDone.toLocaleString()} clasificadas</span>
                    <span className="font-bold text-accent text-xs">{pctDone}%</span>
                    <span>⏳ {pending.toLocaleString()} pendientes</span>
                </div>
                <div className="bg-line rounded-full h-2 overflow-hidden">
                    <div className="bg-accent rounded-full h-2 transition-all duration-500"
                        style={{width:`${pctDone}%`}} />
                </div>
                {loadingMore && (
                    <div className="text-[9px] text-muted mt-1 flex items-center gap-1">
                        <span className="inline-block w-2.5 h-2.5 border border-muted border-t-transparent rounded-full animate-spin"/>
                        Cargando más prendas…
                    </div>
                )}
            </div>

            <div className="p-4 md:p-8">
                {/* Header + tabs */}
                <div className="flex items-start justify-between mb-4">
                    <PageHeader
                        title={showDone ? '✓ Clasificados' : '⏳ Pendientes'}
                        subtitle={`${filtered.length} en pantalla${!showDone && hasMore ? ' · cargando más automáticamente' : ''}`}
                    />
                    <div className="flex gap-2 mt-1">
                        <button onClick={()=>{setShowDone(false);setSearch('');setFGender('');setFCategory('');setFVendor('');}}
                            className={`px-3 py-1.5 rounded-lg text-xs font-medium transition-colors ${!showDone?'bg-accent text-white':'bg-card border border-line text-muted hover:text-ink'}`}>
                            Pendientes
                        </button>
                        <button onClick={()=>{setShowDone(true);setSearch('');setFGender('');setFCategory('');setFVendor('');}}
                            className={`px-3 py-1.5 rounded-lg text-xs font-medium transition-colors ${showDone?'bg-accent text-white':'bg-card border border-line text-muted hover:text-ink'}`}>
                            Clasificados
                        </button>
                    </div>
                </div>

                {/* Filtros */}
                <div className="flex flex-wrap gap-2 mb-4">
                    <input type="text" placeholder="Buscar prenda o marca..."
                        value={search} onChange={e=>setSearch(e.target.value)}
                        className="bg-card border border-line rounded-lg px-3 py-1.5 text-xs text-ink placeholder:text-muted outline-none focus:border-accent w-full sm:w-48"/>
                    <select value={fGender} onChange={e=>setFGender(e.target.value)}
                        className="bg-card border border-line rounded-lg px-3 py-1.5 text-xs text-ink outline-none focus:border-accent">
                        <option value="">Todos géneros</option>
                        {CAT_GENDERS.map(g=><option key={g} value={g}>{g}</option>)}
                    </select>
                    <select value={fCategory} onChange={e=>setFCategory(e.target.value)}
                        className="bg-card border border-line rounded-lg px-3 py-1.5 text-xs text-ink outline-none focus:border-accent">
                        <option value="">Todas cats.</option>
                        {CAT_CATEGORIES.map(c=><option key={c} value={c}>{c}</option>)}
                    </select>
                    <select value={fVendor} onChange={e=>setFVendor(e.target.value)}
                        className="bg-card border border-line rounded-lg px-3 py-1.5 text-xs text-ink outline-none focus:border-accent">
                        <option value="">Todas las marcas</option>
                        {uniqueVendors.map(v=><option key={v} value={v}>{v}</option>)}
                    </select>
                    {!showDone && (
                        <button onClick={autoColorAll} disabled={detectingAll}
                            className={`flex items-center gap-1.5 px-3 py-1.5 rounded-lg text-xs font-medium transition-colors ${
                                detectingAll
                                    ? 'bg-purple-500/20 text-purple-300 cursor-wait'
                                    : 'bg-purple-500/10 text-purple-400 hover:bg-purple-500/20 border border-purple-500/30'
                            }`}>
                            {detectingAll
                                ? <><span className="inline-block w-3 h-3 border border-purple-400 border-t-transparent rounded-full animate-spin"/>Detectando…</>
                                : <>🎨 Auto-color</>}
                        </button>
                    )}
                </div>

            {/* Floating eyedropper preview */}
            {pickingId && hoverHex && (
                <div className="fixed z-50 pointer-events-none"
                    style={{left: cursorPos.x + 16, top: cursorPos.y - 36}}>
                    <div className="flex items-center gap-2 bg-black/90 backdrop-blur rounded-lg px-2.5 py-1.5 shadow-xl border border-white/10">
                        <div className="w-5 h-5 rounded border border-white/30 shadow-inner flex-shrink-0"
                            style={{backgroundColor:`#${hoverHex}`}} />
                        <span className="text-white text-[11px] font-mono tracking-wider">#{hoverHex}</span>
                    </div>
                </div>
            )}

            {loading ? <CenteredLoader text="Cargando prendas…" /> : filtered.length === 0 ? (
                <div className="flex flex-col items-center justify-center py-24 text-muted">
                    <div className="text-5xl mb-4">{showDone?'📋':'🎉'}</div>
                    <div className="text-sm">{showDone?'No hay productos clasificados aún.':'¡Todo clasificado!'}</div>
                </div>
            ) : (
                <div className="grid grid-cols-3 md:grid-cols-4 lg:grid-cols-5 gap-2">
                    {filtered.map(p => {
                        const e      = edits[p.id] || {};
                        const isSav  = saving[p.id];
                        const isDone = savedIds[p.id];
                        const err    = errorIds[p.id];
                        const vname  = vendorNames[p.vendor_id] || '—';
                        const price  = p.price_cents ? `$${Math.round(p.price_cents/100)}` : '—';
                        const subs   = CAT_SUBCATS[e.category] || [];
                        const colorHex = (e.color_hex||'808080').replace('#','');

                        return (
                            <div key={p.id}
                                className={`bg-card border rounded-xl overflow-hidden flex flex-col transition-all duration-300 ${
                                    isDone ? 'border-green-500 opacity-40 scale-95 pointer-events-none' :
                                    err    ? 'border-red-500' :
                                    'border-line hover:border-accent/50'}`}>

                                {/* Foto */}
                                <div className="relative bg-surfaceDeep overflow-hidden" style={{aspectRatio:'3/4'}}>
                                    {p.image_url
                                        ? <img
                                              ref={el=>{ if(el) imgRefs.current[p.id]=el; }}
                                              src={p.image_url} alt={p.title}
                                              crossOrigin="anonymous"
                                              className="w-full h-full object-cover"
                                              style={{cursor: pickingId===p.id ? 'crosshair' : 'default'}}
                                              onError={ev=>{ev.target.style.display='none'}}
                                              onLoad={ev=>{
                                                  // Preload canvas para pixel picker
                                                  preloadCanvas(p.id);
                                                  // Auto-color si sigue en gris genérico
                                                  const cur=(edits[p.id]?.color_hex||p.color_hex||'808080').replace('#','').toUpperCase();
                                                  if(cur==='808080') autoColor(p.id, p.image_url);
                                              }}
                                              onMouseMove={ev=>onPickMove(p.id, ev)}
                                              onClick={ev=>pickingId===p.id && onPickClick(p.id, ev)}
                                          />
                                        : <div className="w-full h-full flex items-center justify-center text-4xl text-muted">👕</div>}
                                    {isDone && (
                                        <div className="absolute inset-0 bg-green-500/30 flex items-center justify-center">
                                            <span className="text-white text-5xl font-bold">✓</span>
                                        </div>
                                    )}
                                    <div className="absolute top-2 left-2 bg-black/60 backdrop-blur-sm rounded px-1.5 py-0.5 text-[9px] text-white font-medium">
                                        {price}
                                    </div>
                                    {/* Botón borrar */}
                                    {!confirmDel[p.id] ? (
                                        <button
                                            onClick={()=>setConfirmDel(prev=>({...prev,[p.id]:true}))}
                                            className="absolute top-2 right-2 w-6 h-6 flex items-center justify-center bg-black/60 backdrop-blur-sm rounded text-white/60 hover:text-red-400 hover:bg-black/80 transition-colors text-xs"
                                            title="Borrar del marketplace">
                                            🗑
                                        </button>
                                    ) : (
                                        <div className="absolute top-2 right-2 flex gap-1">
                                            <button
                                                onClick={()=>deleteProduct(p.id)}
                                                disabled={deleting[p.id]}
                                                className="px-1.5 py-0.5 bg-red-600 hover:bg-red-500 text-white text-[9px] font-bold rounded transition-colors">
                                                {deleting[p.id] ? '…' : '✓ Sí'}
                                            </button>
                                            <button
                                                onClick={()=>setConfirmDel(prev=>{const n={...prev};delete n[p.id];return n;})}
                                                className="px-1.5 py-0.5 bg-black/70 text-white/70 hover:text-white text-[9px] rounded transition-colors">
                                                No
                                            </button>
                                        </div>
                                    )}
                                </div>

                                {/* Contenido */}
                                <div className="p-1.5 md:p-2.5 flex flex-col gap-1.5 md:gap-2 flex-1">
                                    <div>
                                        <div className="text-[8px] md:text-[9px] text-accent uppercase tracking-wider font-semibold truncate">{vname}</div>
                                        <div className="text-[9px] md:text-[11px] text-ink leading-tight mt-0.5 line-clamp-2">{p.title}</div>
                                    </div>

                                    {/* Género */}
                                    <div>
                                        <div className="text-[7px] md:text-[8px] text-muted uppercase tracking-wider mb-0.5">Género</div>
                                        <select value={e.gender_target||'unisex'} onChange={ev=>upd(p.id,'gender_target',ev.target.value)}
                                            className="w-full bg-surfaceDeep border border-line rounded px-1 md:px-2 py-0.5 md:py-1 text-[9px] md:text-[11px] text-ink outline-none focus:border-accent">
                                            {CAT_GENDERS.map(g=><option key={g} value={g}>{g}</option>)}
                                        </select>
                                    </div>

                                    {/* Categoría */}
                                    <div>
                                        <div className="text-[7px] md:text-[8px] text-muted uppercase tracking-wider mb-0.5">Categoría</div>
                                        <select value={e.category||'tops'} onChange={ev=>upd(p.id,'category',ev.target.value)}
                                            className="w-full bg-surfaceDeep border border-line rounded px-1 md:px-2 py-0.5 md:py-1 text-[9px] md:text-[11px] text-ink outline-none focus:border-accent">
                                            {CAT_CATEGORIES.map(c=><option key={c} value={c}>{c}</option>)}
                                        </select>
                                    </div>

                                    {/* Subcategoría */}
                                    <div>
                                        <div className="text-[7px] md:text-[8px] text-muted uppercase tracking-wider mb-0.5">Subcat.</div>
                                        <select value={e.subcategory||''} onChange={ev=>upd(p.id,'subcategory',ev.target.value)}
                                            className="w-full bg-surfaceDeep border border-line rounded px-1 md:px-2 py-0.5 md:py-1 text-[9px] md:text-[11px] text-ink outline-none focus:border-accent">
                                            <option value="">—</option>
                                            {subs.map(s=><option key={s} value={s}>{s}</option>)}
                                        </select>
                                    </div>

                                    {/* Color — hex libre + eyedropper + auto */}
                                    <div>
                                        <div className="text-[7px] md:text-[8px] text-muted uppercase tracking-wider mb-0.5">Color</div>
                                        <div className="flex items-center gap-0.5 md:gap-1">
                                            {/* Swatch */}
                                            <div className="w-4 h-4 md:w-6 md:h-6 rounded border border-line flex-shrink-0 shadow-inner"
                                                style={{backgroundColor:`#${colorHex}`}} />
                                            {/* Input hex libre */}
                                            <input
                                                type="text"
                                                value={e.color_hex||'808080'}
                                                onChange={ev=>{
                                                    const v=ev.target.value.replace(/[^0-9a-fA-F]/g,'').toUpperCase().slice(0,6);
                                                    upd(p.id,'color_hex',v);
                                                }}
                                                maxLength={6}
                                                placeholder="HEX"
                                                className="flex-1 bg-surfaceDeep border border-line rounded px-1 py-0.5 text-[8px] md:text-[10px] text-ink outline-none focus:border-accent font-mono w-0 min-w-0"
                                            />
                                            {/* Eyedropper 🎯 */}
                                            <button
                                                onClick={()=> pickingId===p.id ? setPickingId(null) : startPick(p.id)}
                                                title={pickingId===p.id ? 'ESC para cancelar' : 'Eyedropper'}
                                                className={`flex-shrink-0 w-5 h-5 md:w-6 md:h-6 flex items-center justify-center rounded transition-colors text-[9px] md:text-[11px] ${
                                                    pickingId===p.id
                                                        ? 'bg-blue-500 text-white ring-2 ring-blue-400/50'
                                                        : 'bg-blue-500/10 text-blue-400 hover:bg-blue-500/20'
                                                }`}>
                                                🎯
                                            </button>
                                            {/* Auto-color 🎨 */}
                                            <button onClick={()=>autoColor(p.id, p.image_url)}
                                                disabled={detecting[p.id]||!p.image_url}
                                                title="Auto-detectar color"
                                                className="flex-shrink-0 w-5 h-5 md:w-6 md:h-6 flex items-center justify-center rounded bg-purple-500/10 text-purple-400 hover:bg-purple-500/20 disabled:opacity-30 transition-colors text-[8px] md:text-[10px]">
                                                {detecting[p.id]
                                                    ? <span className="w-2 h-2 border border-purple-400 border-t-transparent rounded-full animate-spin block"/>
                                                    : '🎨'}
                                            </button>
                                        </div>
                                        {pickingId===p.id && (
                                            <div className="mt-0.5 text-[7px] text-blue-400 animate-pulse">
                                                👆 Click en imagen · ESC cancela
                                            </div>
                                        )}
                                    </div>

                                    {err && <div className="text-[8px] text-red-400 bg-red-500/10 rounded p-1 break-words">{err}</div>}

                                    {/* Botón guardar */}
                                    <button onClick={()=>save(p.id)} disabled={isSav||isDone}
                                        className={`mt-auto w-full py-1 md:py-1.5 rounded-lg text-[9px] md:text-[11px] font-semibold transition-all ${
                                            isDone ? 'bg-green-600/20 text-green-400' :
                                            isSav  ? 'bg-accent/30 text-white/50 cursor-wait' :
                                            'bg-accent/10 text-accent hover:bg-accent hover:text-white'}`}>
                                        {isDone?'✓': isSav?'…':'✓ Guardar'}
                                    </button>
                                </div>
                            </div>
                        );
                    })}
                </div>
            )}
            </div>{/* /p-4 md:p-8 */}
        </div>
    );
}

// ── CatalogGenderPage — compartido para Hombre y Mujer ────────────────────
function CatalogGenderPage({ gender }) {
    const [products, setProducts] = useState([]);
    const [loading,  setLoading]  = useState(true);

    useEffect(() => {
        (async () => {
            const PAGE = 1000;
            let all = [], from = 0;
            while (true) {
                const { data, error } = await supa.from('brand_products')
                    .select('id,category,subcategory,price_cents,color_hex,manually_reviewed,vendor_id,brand_vendors(name)')
                    .eq('status','active').eq('gender_target', gender)
                    .range(from, from + PAGE - 1);
                if (error || !data || data.length === 0) break;
                all = all.concat(data);
                if (data.length < PAGE) break;
                from += PAGE;
            }
            setProducts(all);
            setLoading(false);
        })();
    }, [gender]);

    if (loading) return <CenteredLoader text={`Cargando productos ${gender}…`} />;

    const total    = products.length;
    const prices   = products.map(p=>(p.price_cents||0)/100).filter(v=>v>0);
    const avgPrice = prices.length ? Math.round(prices.reduce((s,v)=>s+v,0)/prices.length) : 0;
    const minPrice = prices.length ? Math.round(Math.min(...prices)) : 0;
    const maxPrice = prices.length ? Math.round(Math.max(...prices)) : 0;
    const reviewed = products.filter(p=>p.manually_reviewed).length;

    const byCat = {};
    products.forEach(p=>{ const c=p.category||'sin cat.'; byCat[c]=(byCat[c]||0)+1; });
    const catEntries = Object.entries(byCat).sort((a,b)=>b[1]-a[1]);

    const bySubcat = {};
    products.forEach(p=>{ if(p.subcategory){ bySubcat[p.subcategory]=(bySubcat[p.subcategory]||0)+1; }});
    const subcatEntries = Object.entries(bySubcat).sort((a,b)=>b[1]-a[1]).slice(0,10);

    const byVendor = {};
    products.forEach(p=>{ const n=p.brand_vendors?.name||'—'; byVendor[n]=(byVendor[n]||0)+1; });
    const topVendors = Object.entries(byVendor).sort((a,b)=>b[1]-a[1]).slice(0,8);

    const byColor = {};
    products.forEach(p=>{ const h=(p.color_hex||'808080').replace('#','').toUpperCase(); byColor[h]=(byColor[h]||0)+1; });
    const topColors = Object.entries(byColor).sort((a,b)=>b[1]-a[1]).slice(0,16);

    const emoji = gender==='masculino'?'👔':'👗';
    const label = gender==='masculino'?'Hombre':'Mujer';

    return (
        <div className="p-8 max-w-7xl mx-auto space-y-4">
            <PageHeader title={`${emoji} ${label}`}
                subtitle={`Análisis de los ${total.toLocaleString()} productos ${gender==='masculino'?'masculinos':'femeninos'}`} />

            <div className="grid grid-cols-2 md:grid-cols-4 gap-3">
                <KpiCard label="Total productos"  value={total.toLocaleString()} />
                <KpiCard label="Precio promedio"  value={`$${avgPrice}`} />
                <KpiCard label="Rango de precios" value={`$${minPrice}–$${maxPrice}`} />
                <KpiCard label="Clasificados"     value={`${reviewed} (${total?Math.round(reviewed/total*100):0}%)`} />
            </div>

            <div className="grid grid-cols-2 gap-4">
                <Card title="Por categoría">
                    {catEntries.map(([c,n])=>(
                        <div key={c} className="flex items-center gap-3 mb-2">
                            <div className="w-24 text-[11px] text-muted capitalize">{c}</div>
                            <div className="flex-1 bg-line rounded-full h-2">
                                <div className="bg-accent rounded-full h-2" style={{width:`${total?Math.round(n/total*100):0}%`}} />
                            </div>
                            <div className="text-xs text-ink2 w-16 text-right">
                                {n} <span className="text-muted">({total?Math.round(n/total*100):0}%)</span>
                            </div>
                        </div>
                    ))}
                </Card>
                <Card title="Top marcas">
                    {topVendors.map(([name,n])=>(
                        <div key={name} className="flex items-center gap-3 mb-2">
                            <div className="flex-1 text-[11px] text-muted truncate">{name}</div>
                            <div className="w-32 bg-line rounded-full h-2">
                                <div className="bg-accent rounded-full h-2"
                                    style={{width:`${topVendors[0]?Math.round(n/topVendors[0][1]*100):0}%`}} />
                            </div>
                            <div className="text-xs text-ink2 w-8 text-right">{n}</div>
                        </div>
                    ))}
                </Card>
            </div>

            {subcatEntries.length > 0 && (
                <Card title="Subcategorías más frecuentes">
                    <div className="flex flex-wrap gap-2">
                        {subcatEntries.map(([s,n])=>(
                            <span key={s} className="bg-accent/10 text-accent text-xs px-3 py-1 rounded-full">
                                {s} <span className="opacity-60">({n})</span>
                            </span>
                        ))}
                    </div>
                </Card>
            )}

            <Card title="Top colores">
                <div className="flex flex-wrap gap-4">
                    {topColors.map(([hex,n])=>{
                        const info = CAT_KNOWN_COLORS.find(c=>c.hex.toUpperCase()===hex);
                        return (
                            <div key={hex} className="flex flex-col items-center gap-1 w-12">
                                <div className="w-10 h-10 rounded-xl border border-line shadow-sm"
                                    style={{backgroundColor:`#${hex}`}} title={`#${hex}`} />
                                <div className="text-[9px] text-muted text-center leading-tight">
                                    {info?.name||`#${hex.slice(0,3)}`}
                                </div>
                                <div className="text-[10px] text-ink2 font-semibold">{n}</div>
                            </div>
                        );
                    })}
                </div>
            </Card>
        </div>
    );
}

// ── CatalogPreciosPage ─────────────────────────────────────────────────────
function CatalogPreciosPage() {
    const [products, setProducts] = useState([]);
    const [loading,  setLoading]  = useState(true);

    useEffect(() => {
        (async () => {
            const PAGE = 1000;
            let all = [], from = 0;
            while (true) {
                const { data, error } = await supa.from('brand_products')
                    .select('id,title,category,gender_target,price_cents,vendor_id,brand_vendors(name)')
                    .eq('status','active')
                    .range(from, from + PAGE - 1);
                if (error || !data || data.length === 0) break;
                all = all.concat(data);
                if (data.length < PAGE) break;
                from += PAGE;
            }
            setProducts(all);
            setLoading(false);
        })();
    }, []);

    if (loading) return <CenteredLoader text="Cargando precios…" />;

    const prices  = products.map(p=>(p.price_cents||0)/100).filter(v=>v>0);
    const avg     = prices.length ? Math.round(prices.reduce((s,v)=>s+v,0)/prices.length) : 0;
    const min     = prices.length ? Math.round(Math.min(...prices)) : 0;
    const max     = prices.length ? Math.round(Math.max(...prices)) : 0;
    const sorted  = [...prices].sort((a,b)=>a-b);
    const median  = sorted.length ? Math.round(sorted[Math.floor(sorted.length/2)]) : 0;

    const dollarBuckets = [
        { label:'< $30',      count: prices.filter(p=>p<30).length },
        { label:'$30–$60',    count: prices.filter(p=>p>=30&&p<60).length },
        { label:'$60–$100',   count: prices.filter(p=>p>=60&&p<100).length },
        { label:'$100–$200',  count: prices.filter(p=>p>=100&&p<200).length },
        { label:'$200–$400',  count: prices.filter(p=>p>=200&&p<400).length },
        { label:'> $400',     count: prices.filter(p=>p>=400).length },
    ];
    const maxBucket = Math.max(...dollarBuckets.map(b=>b.count), 1);

    // Por categoría
    const catGroups = {};
    products.forEach(p => {
        if (!p.price_cents) return;
        const c = p.category||'sin cat.';
        if (!catGroups[c]) catGroups[c]=[];
        catGroups[c].push(p.price_cents/100);
    });
    const catRows = Object.entries(catGroups).map(([c,ps])=>({
        cat: c,
        count: ps.length,
        avg:   Math.round(ps.reduce((s,v)=>s+v,0)/ps.length),
        min:   Math.round(Math.min(...ps)),
        max:   Math.round(Math.max(...ps)),
    })).sort((a,b)=>b.avg-a.avg);

    // Por género
    const gGroups = {};
    products.forEach(p=>{
        if(!p.price_cents) return;
        const g=p.gender_target||'unisex';
        if(!gGroups[g]) gGroups[g]=[];
        gGroups[g].push(p.price_cents/100);
    });

    const top10Exp  = [...products].filter(p=>p.price_cents>0).sort((a,b)=>b.price_cents-a.price_cents).slice(0,10);
    const top10Chp  = [...products].filter(p=>p.price_cents>0).sort((a,b)=>a.price_cents-b.price_cents).slice(0,10);

    return (
        <div className="p-8 max-w-7xl mx-auto space-y-4">
            <PageHeader title="💰 Precios" subtitle="Análisis de precios del catálogo completo" />

            <div className="grid grid-cols-2 md:grid-cols-4 gap-3">
                <KpiCard label="Precio promedio" value={`$${avg}`} />
                <KpiCard label="Mediana"         value={`$${median}`} />
                <KpiCard label="Mínimo"          value={`$${min}`} />
                <KpiCard label="Máximo"          value={`$${max}`} />
            </div>

            <div className="grid grid-cols-2 gap-4">
                <Card title="Distribución por rango">
                    {dollarBuckets.map(b=>(
                        <div key={b.label} className="flex items-center gap-3 mb-2">
                            <div className="w-20 text-[11px] text-muted">{b.label}</div>
                            <div className="flex-1 bg-line rounded-full h-2">
                                <div className="bg-accent rounded-full h-2" style={{width:`${Math.round(b.count/maxBucket*100)}%`}} />
                            </div>
                            <div className="text-xs text-ink2 w-14 text-right">{b.count.toLocaleString()}</div>
                        </div>
                    ))}
                </Card>
                <Card title="Por género">
                    {Object.entries(gGroups).map(([g,gps])=>{
                        const gAvg = Math.round(gps.reduce((s,v)=>s+v,0)/gps.length);
                        const gMin = Math.round(Math.min(...gps));
                        const gMax = Math.round(Math.max(...gps));
                        return (
                            <div key={g} className="flex items-center justify-between py-2 border-b border-line/40">
                                <span className="text-sm capitalize w-24">{g}</span>
                                <span className="text-xs text-muted">{gps.length} productos</span>
                                <span className="text-xs text-muted">${gMin}–${gMax}</span>
                                <span className="text-sm font-semibold text-accent">${gAvg} prom.</span>
                            </div>
                        );
                    })}
                </Card>
            </div>

            <Card title="Por categoría — detalle de precios">
                <table className="w-full text-sm">
                    <thead>
                        <tr className="border-b border-line text-[10px] uppercase tracking-wider text-muted">
                            <th className="text-left pb-2">Categoría</th>
                            <th className="text-right pb-2">Productos</th>
                            <th className="text-right pb-2">Mínimo</th>
                            <th className="text-right pb-2">Promedio</th>
                            <th className="text-right pb-2">Máximo</th>
                        </tr>
                    </thead>
                    <tbody>
                        {catRows.map(r=>(
                            <tr key={r.cat} className="border-b border-line/40">
                                <td className="py-1.5 capitalize">{r.cat}</td>
                                <td className="py-1.5 text-right text-muted">{r.count}</td>
                                <td className="py-1.5 text-right text-muted">${r.min}</td>
                                <td className="py-1.5 text-right text-accent font-semibold">${r.avg}</td>
                                <td className="py-1.5 text-right text-muted">${r.max}</td>
                            </tr>
                        ))}
                    </tbody>
                </table>
            </Card>

            <div className="grid grid-cols-2 gap-4">
                <Card title="10 más caros">
                    {top10Exp.map(p=>(
                        <div key={p.id} className="flex items-center gap-2 py-1.5 border-b border-line/40">
                            <div className="flex-1 min-w-0">
                                <div className="text-xs truncate">{p.title}</div>
                                <div className="text-[10px] text-muted">{p.brand_vendors?.name} · {p.category}</div>
                            </div>
                            <div className="text-sm font-semibold text-accent flex-shrink-0">
                                ${Math.round(p.price_cents/100)}
                            </div>
                        </div>
                    ))}
                </Card>
                <Card title="10 más baratos">
                    {top10Chp.map(p=>(
                        <div key={p.id} className="flex items-center gap-2 py-1.5 border-b border-line/40">
                            <div className="flex-1 min-w-0">
                                <div className="text-xs truncate">{p.title}</div>
                                <div className="text-[10px] text-muted">{p.brand_vendors?.name} · {p.category}</div>
                            </div>
                            <div className="text-sm font-semibold text-green-400 flex-shrink-0">
                                ${Math.round(p.price_cents/100)}
                            </div>
                        </div>
                    ))}
                </Card>
            </div>
        </div>
    );
}

// ── CatalogColoresPage ─────────────────────────────────────────────────────
function CatalogColoresPage() {
    const [products, setProducts] = useState([]);
    const [loading,  setLoading]  = useState(true);

    useEffect(() => {
        (async () => {
            setProducts(await fetchAllCatalogProducts('id,color_hex,category,gender_target'));
            setLoading(false);
        })();
    }, []);

    if (loading) return <CenteredLoader text="Cargando colores…" />;

    const total = products.length;

    const byColor = {};
    products.forEach(p=>{
        const h=(p.color_hex||'808080').replace('#','').toUpperCase();
        byColor[h]=(byColor[h]||0)+1;
    });
    const topColors    = Object.entries(byColor).sort((a,b)=>b[1]-a[1]);
    const top24        = topColors.slice(0,24);
    const distinct     = Object.keys(byColor).length;
    const genericCount = byColor['808080']||0;
    const pctGeneric   = total ? Math.round(genericCount/total*100) : 0;

    // Colores por categoría
    const catColors = {};
    products.forEach(p=>{
        const cat = p.category||'sin cat.';
        const hex = (p.color_hex||'808080').replace('#','').toUpperCase();
        if(!catColors[cat]) catColors[cat]={};
        catColors[cat][hex]=(catColors[cat][hex]||0)+1;
    });
    const catColorRows = Object.entries(catColors).map(([cat,cols])=>({
        cat,
        total: Object.values(cols).reduce((s,v)=>s+v,0),
        top5:  Object.entries(cols).sort((a,b)=>b[1]-a[1]).slice(0,5),
    })).sort((a,b)=>b.total-a.total);

    return (
        <div className="p-8 max-w-7xl mx-auto space-y-4">
            <PageHeader title="🎨 Colores" subtitle="Distribución de colores en el catálogo" />

            <div className="grid grid-cols-3 gap-3">
                <KpiCard label="Total productos"    value={total.toLocaleString()} />
                <KpiCard label="Colores distintos"  value={distinct} />
                <KpiCard label="Sin color real (#808080)" value={`${genericCount} (${pctGeneric}%)`} />
            </div>

            <Card title="Top 24 colores del catálogo">
                <div className="flex flex-wrap gap-4">
                    {top24.map(([hex,n])=>{
                        const info = CAT_KNOWN_COLORS.find(c=>c.hex.toUpperCase()===hex);
                        return (
                            <div key={hex} className="flex flex-col items-center gap-1 w-14">
                                <div className="w-12 h-12 rounded-xl border border-line shadow-sm"
                                    style={{backgroundColor:`#${hex}`}} title={`#${hex}`} />
                                <div className="text-[9px] text-muted text-center leading-tight">
                                    {info?.name||`#${hex.slice(0,4)}`}
                                </div>
                                <div className="text-[10px] text-ink2 font-semibold">{n}</div>
                            </div>
                        );
                    })}
                </div>
            </Card>

            <Card title="Paleta principal por categoría">
                <div className="space-y-3">
                    {catColorRows.map(({cat,top5})=>(
                        <div key={cat} className="flex items-center gap-4">
                            <div className="w-24 text-[11px] text-muted capitalize flex-shrink-0">{cat}</div>
                            <div className="flex gap-2 flex-wrap">
                                {top5.map(([hex,n])=>(
                                    <div key={hex} className="flex flex-col items-center gap-0.5">
                                        <div className="w-8 h-8 rounded-lg border border-line"
                                            style={{backgroundColor:`#${hex}`}} title={`#${hex} (${n})`} />
                                        <div className="text-[9px] text-muted">{n}</div>
                                    </div>
                                ))}
                            </div>
                        </div>
                    ))}
                </div>
            </Card>

            {pctGeneric > 20 && (
                <div className="bg-yellow-500/10 border border-yellow-500/30 rounded-xl p-4 text-xs text-yellow-300">
                    ⚠️ <strong>{pctGeneric}% de los productos</strong> tienen el color genérico #808080 (gris).
                    Ve a <a href="#catalog_pending" className="underline">Pendientes</a> para corregirlos y mejorar las recomendaciones por color.
                </div>
            )}
        </div>
    );
}

// ============================================================================
// Mount
// ============================================================================
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
    <InfoModalProvider>
        <App />
    </InfoModalProvider>
);
