// Admin & Profiles — helpers que falam com a tabela `profiles` do Supabase. // Reusa o client criado em auth.jsx. (function () { const TABLE = 'profiles'; function client() { if (window.Auth && typeof window.Auth._client === 'function') { return window.Auth._client(); } return null; } async function getProfile(userId) { const c = client(); if (!c || !userId) return null; const { data, error } = await c .from(TABLE) .select('*') .eq('user_id', userId) .maybeSingle(); if (error) { console.warn('[admin] getProfile error:', error.message); return null; } return data; } // Garante que o profile existe (caso o trigger não tenha disparado por algum motivo) async function ensureProfile(user) { const c = client(); if (!c || !user || !user.id) return null; const existing = await getProfile(user.id); if (existing) return existing; const { data, error } = await c .from(TABLE) .insert({ user_id: user.id, email: user.email }) .select() .single(); if (error) { console.warn('[admin] ensureProfile insert error:', error.message); return null; } return data; } async function updateMyProfile(userId, patch) { const c = client(); if (!c || !userId) return { ok: false }; const { error } = await c.from(TABLE).update(patch).eq('user_id', userId); if (error) return { ok: false, reason: error.message }; return { ok: true }; } // ─── Funções ADMIN — RLS só deixa rodar se is_admin(auth.uid()) = true ─── async function listAllProfiles() { const c = client(); if (!c) return []; const { data, error } = await c .from(TABLE) .select('user_id, email, name, role, plan, is_active, created_at, updated_at') .order('created_at', { ascending: false }); if (error) { console.warn('[admin] listAllProfiles error:', error.message); return []; } return data || []; } async function setUserPlan(userId, plan) { const c = client(); if (!c) return { ok: false }; const { error } = await c.from(TABLE).update({ plan }).eq('user_id', userId); if (error) return { ok: false, reason: error.message }; return { ok: true }; } async function setUserActive(userId, isActive) { const c = client(); if (!c) return { ok: false }; const { error } = await c.from(TABLE).update({ is_active: isActive }).eq('user_id', userId); if (error) return { ok: false, reason: error.message }; return { ok: true }; } async function setUserRole(userId, role) { const c = client(); if (!c) return { ok: false }; const { error } = await c.from(TABLE).update({ role }).eq('user_id', userId); if (error) return { ok: false, reason: error.message }; return { ok: true }; } // Pega métricas de uso agregadas via user_state (admin only) async function getUsageMetrics() { const c = client(); if (!c) return null; const { data, error } = await c .from('user_state') .select('user_id, data, updated_at'); if (error) { console.warn('[admin] getUsageMetrics error:', error.message); return null; } let totalTasks = 0, totalHabits = 0, totalGoals = 0, totalDoneTasks = 0; (data || []).forEach(row => { const d = row.data || {}; if (Array.isArray(d.tasks)) { totalTasks += d.tasks.length; totalDoneTasks += d.tasks.filter(t => t.done).length; } if (Array.isArray(d.habits)) totalHabits += d.habits.length; if (Array.isArray(d.goals)) totalGoals += d.goals.length; }); return { totalUserStates: (data || []).length, totalTasks, totalDoneTasks, totalHabits, totalGoals, }; } // ─── Métricas de receita (LTV / conversão) ──────────── // Estimativa simples baseada no MRR atual e nos pagantes vs trial function getRevenueMetrics(profiles) { if (!Array.isArray(profiles)) return null; const PLAN_MRR = { mensal: 29.90, semestral: 149.40 / 6, anual: 238.80 / 12, essencial: 29.90, premium: 59.90, trial: 0 }; const PAYING = ['mensal', 'semestral', 'anual', 'essencial', 'premium']; const active = profiles.filter(p => p.is_active); const payers = active.filter(p => PAYING.includes(p.plan)); const trial = active.filter(p => p.plan === 'trial'); const mrr = active.reduce((s, p) => s + (PLAN_MRR[p.plan] || 0), 0); // LTV estimado: MRR × 12 meses como base conservadora const ltvTotal = mrr * 12; // Conversão trial -> pago = pagantes / (pagantes + trial) — proxy simples const conversionDen = payers.length + trial.length; const conversion = conversionDen > 0 ? (payers.length / conversionDen) * 100 : 0; return { mrr, ltvTotal, conversion, paying: payers.length, trial: trial.length, paymentRev: { mensal: active.filter(p => p.plan === 'mensal').length * 29.90, semestral: active.filter(p => p.plan === 'semestral').length * (149.40 / 6), anual: active.filter(p => p.plan === 'anual').length * (238.80 / 12), }, }; } // ─── Signups recentes (ordenado por created_at desc) ────── function getRecentSignups(profiles, n) { if (!Array.isArray(profiles)) return []; return profiles .slice() .sort((a, b) => new Date(b.created_at || 0) - new Date(a.created_at || 0)) .slice(0, n || 5); } // ─── Exportar CSV ───────────────────────────────────────── function exportProfilesCSV(profiles) { if (!Array.isArray(profiles) || profiles.length === 0) return false; const cols = ['email', 'name', 'role', 'plan', 'is_active', 'created_at', 'updated_at']; const escape = (v) => { if (v === null || v === undefined) return ''; const s = String(v).replace(/"/g, '""'); return /[",\n]/.test(s) ? `"${s}"` : s; }; const lines = [ cols.join(','), ...profiles.map(p => cols.map(c => escape(p[c])).join(',')), ]; const csv = lines.join('\n'); try { const blob = new Blob([csv], { type: 'text/csv;charset=utf-8' }); const url = URL.createObjectURL(blob); const a = document.createElement('a'); a.href = url; a.download = `centraldavida-assinantes-${new Date().toISOString().slice(0, 10)}.csv`; document.body.appendChild(a); a.click(); document.body.removeChild(a); setTimeout(() => URL.revokeObjectURL(url), 1000); return true; } catch (e) { console.warn('[admin] export error:', e); return false; } } window.AdminAPI = { getProfile, ensureProfile, updateMyProfile, listAllProfiles, setUserPlan, setUserActive, setUserRole, getUsageMetrics, getRevenueMetrics, getRecentSignups, exportProfilesCSV, }; })();