diff --git a/docs/design/pages/e2e/fixtures/datasets.js b/docs/design/pages/e2e/fixtures/datasets.js index 848c60b..365e43f 100644 --- a/docs/design/pages/e2e/fixtures/datasets.js +++ b/docs/design/pages/e2e/fixtures/datasets.js @@ -236,8 +236,57 @@ export const MARKET_SIGNAL = { valid_until: new Date(Date.now() + 3600e3).toISOString(), as_of: new Date().toISOString(), }; -// GET /v1/hitl/config — the header's autonomy badge. -export const HITL_CONFIG = { level: 1, threshold_usdt: 100.0, label: "Assistido" }; +// GET /v1/hitl/config — header autonomy badge + the HITL Controls screen. +export const HITL_CONFIG = { + level: 1, current_level: 1, threshold_usdt: 100.0, label: "Assistido", + level_description: "Ordens acima do limite exigem aprovação humana.", + pending_orders_count: 2, human_approved_today: 5, human_rejected_today: 1, + levels: [ + { level: 0, threshold_usdt: 0, description: "Manual — toda ordem exige aprovação." }, + { level: 1, threshold_usdt: 100, description: "Assistido — ordens acima do limite exigem aprovação." }, + { level: 2, threshold_usdt: 500, description: "Semi-autônomo — só ordens grandes exigem aprovação." }, + { level: 3, threshold_usdt: 999999, description: "Autônomo — sem aprovação (confirmação extra)." }, + ], +}; + +// GET /v1/orders — pending queue (HITL) + Orders table. ≥2 pares incl. BTC/USDT +// para os chips de par (N4); confidence (Fix #3) + confidence_breakdown por ordem. +export const ORDERS = [ + { id: "ord_a1", pair: "BTC/USDT", side: "buy", quantity: 0.03, price: 64500, entry: 64500, + stop_loss: 63200, take_profit: 66800, position_size_pct: 2, confidence: 0.82, status: "pending", + strategy: "trend_following", agent_id: "strategy_agent", reason: "Rompimento confirmado por volume.", + created_at: new Date().toISOString(), + confidence_breakdown: [{ key: "trend", score: 0.85 }, { key: "momentum", score: 0.7 }, { key: "volume", score: 0.8 }] }, + { id: "ord_b2", pair: "ETH/USDT", side: "buy", quantity: 0.5, price: 3200, entry: 3200, + stop_loss: 3080, take_profit: 3450, position_size_pct: 2, confidence: 0.71, status: "pending", + strategy: "mean_reversion", agent_id: "strategy_agent", reason: "Reversão no suporte confirmada.", + created_at: new Date().toISOString(), + confidence_breakdown: [{ key: "trend", score: 0.6 }, { key: "momentum", score: 0.75 }, { key: "volume", score: 0.8 }] }, +]; + +// GET /v1/trades — trades fechados (P&L realizado por par, N5). ≥2 símbolos incl. BTC/USDT. +export const TRADES = [ + { order_id: "t1", symbol: "BTC/USDT", side: "buy", entry_price: 61200, exit_price: 64810, quantity: 0.03, fee: 0.5, pnl: 108.30, pnl_pct: 5.9 }, + { order_id: "t2", symbol: "BTC/USDT", side: "buy", entry_price: 65000, exit_price: 63600, quantity: 0.03, fee: 0.5, pnl: -42.10, pnl_pct: -2.1 }, + { order_id: "t3", symbol: "ETH/USDT", side: "buy", entry_price: 3100, exit_price: 3223, quantity: 0.5, fee: 0.3, pnl: 61.50, pnl_pct: 4.0 }, + { order_id: "t4", symbol: "SOL/USDT", side: "buy", entry_price: 150, exit_price: 160.3, quantity: 1.2, fee: 0.2, pnl: 12.38, pnl_pct: 1.5 }, + { order_id: "t5", symbol: "SOL/USDT", side: "buy", entry_price: 148, exit_price: 168.7, quantity: 1.2, fee: 0.2, pnl: 24.90, pnl_pct: 3.0 }, + { order_id: "t6", symbol: "XRP/USDT", side: "buy", entry_price: 0.62, exit_price: 0.60, quantity: 400, fee: 0.1, pnl: -8.20, pnl_pct: -1.3 }, +]; + +// GET /v1/users — exatamente 3 (rbac test 3 afirma tbody tr === 3). +export const USERS = [ + { id: "u_demo", name: "Operador Demo", email: "demo@criptotrade.dev", role: "admin", status: "active", last_login_at: new Date().toISOString(), invite_id: null }, + { id: "u_ana", name: "Ana", email: "ana@criptotrade.dev", role: "operador", status: "active", last_login_at: new Date(Date.now() - 86400e3).toISOString(), invite_id: null }, + { id: "u_novo", name: null, email: "novo@criptotrade.dev", role: "visualizador", status: "pending", last_login_at: null, invite_id: "inv_1" }, +]; + +// GET /v1/roles — a "Matriz de permissões" renderiza de r.permissions. +export const ROLES = [ + { id: "admin", label: "Admin", permissions: ["approve_order", "change_autonomy", "change_risk", "edit_settings", "manage_keys", "view_audit", "manage_users"] }, + { id: "operador", label: "Operador", permissions: ["approve_order", "change_autonomy", "view_audit"] }, + { id: "visualizador", label: "Visualizador", permissions: [] }, +]; // GET /v1/market/{pair}/ticker — the header price (when a single pair is selected). export function ticker(pair) { diff --git a/docs/design/pages/e2e/fixtures/mockApi.js b/docs/design/pages/e2e/fixtures/mockApi.js index 4257887..563ae89 100644 --- a/docs/design/pages/e2e/fixtures/mockApi.js +++ b/docs/design/pages/e2e/fixtures/mockApi.js @@ -15,8 +15,9 @@ import { AGENTS, authMe, candles, deskSummary, EQUITY, HEALTH, HITL_CONFIG, MARKET_INDICATORS, JOURNAL, JOURNAL_METRICS, MARKET_LEVELS, MARKET_PATTERNS, MARKET_REGIME, MARKET_SIGNAL, MARKET_VOLUME_PROFILE, - metrics, ONBOARDING, PAIRS_FLAT, PAIRS_RICH, processEvents, RISK_CIRCUIT_BREAKER, - RISK_CONFIG, RISK_KELLY, RISK_PROTECTIONS, RISK_SKIPS, RISK_SLOTS, SYS_CONFIG, ticker, + metrics, ONBOARDING, ORDERS, PAIRS_FLAT, PAIRS_RICH, processEvents, RISK_CIRCUIT_BREAKER, + RISK_CONFIG, RISK_KELLY, RISK_PROTECTIONS, RISK_SKIPS, RISK_SLOTS, ROLES, SYS_CONFIG, + ticker, TRADES, USERS, } from "./datasets.js"; // Wrap a payload in the API envelope the client's req() unwraps ({ data, meta }). @@ -41,7 +42,10 @@ function baseline({ authMode, role }) { "GET /v1/metrics/equity": () => EQUITY, "GET /v1/onboarding/status": () => ONBOARDING, "GET /v1/desk/summary": () => deskSummary(), - "GET /v1/orders": () => [], // sidebar pending-count poll → empty + "GET /v1/orders": () => ORDERS, // sidebar poll + HITL queue + Ordens + "GET /v1/trades": () => TRADES, // Ordens: P&L realizado por par (N5) + "GET /v1/users": () => USERS, // Usuários & Permissões (exatamente 3) + "GET /v1/roles": () => ROLES, // matriz de permissões // Risco (screen_risk) "GET /v1/risk/protections": () => RISK_PROTECTIONS, "GET /v1/risk/circuit-breaker": () => RISK_CIRCUIT_BREAKER, diff --git a/docs/design/pages/e2e/pair_dimension.spec.js b/docs/design/pages/e2e/pair_dimension.spec.js index 2740df9..74c65d8 100644 --- a/docs/design/pages/e2e/pair_dimension.spec.js +++ b/docs/design/pages/e2e/pair_dimension.spec.js @@ -2,9 +2,10 @@ // N4: HITL ganha mini-contexto do par (preço/regime) + filtro por par na fila. // N5: Ordens ganham o rodapé de P&L realizado por par no modo ∑. import { test, expect } from "@playwright/test"; +import { installMockApi } from "./fixtures/mockApi.js"; test.beforeEach(async ({ page }) => { - await page.addInitScript(() => { window.USE_MOCK_DATA = true; }); + await installMockApi(page, { authMode: "user" }); }); test("N4 — HITL: cada ordem mostra o mini-contexto do par (preço atual)", async ({ page }) => { diff --git a/docs/design/pages/e2e/rbac.spec.js b/docs/design/pages/e2e/rbac.spec.js index 71c422f..98b7b04 100644 --- a/docs/design/pages/e2e/rbac.spec.js +++ b/docs/design/pages/e2e/rbac.spec.js @@ -1,12 +1,11 @@ // A3 RBAC in the console: role-driven visibility (visualizador hides actions, // demo shows them disabled with the discovery tooltip, admin sees everything). +// Cenários de auth/role vêm do fixture (/v1/auth/me), não mais de MOCK_ROLE/MOCK_AUTH. import { test, expect } from "@playwright/test"; +import { installMockApi } from "./fixtures/mockApi.js"; test("visualizador sees no approve/reject and no admin nav", async ({ page }) => { - await page.addInitScript(() => { - window.USE_MOCK_DATA = true; - window.MOCK_ROLE = "visualizador"; - }); + await installMockApi(page, { authMode: "user", role: "visualizador" }); await page.goto("/#hitl"); await expect(page.locator(".page-title")).toContainText("HITL"); await expect(page.getByRole("button", { name: "Aprovar" })).toHaveCount(0); @@ -18,10 +17,7 @@ test("visualizador sees no approve/reject and no admin nav", async ({ page }) => }); test("demo mode shows actions disabled with the discovery tooltip", async ({ page }) => { - await page.addInitScript(() => { - window.USE_MOCK_DATA = true; - window.MOCK_AUTH = "demo"; - }); + await installMockApi(page, { authMode: "demo" }); await page.goto("/#hitl"); const approve = page.getByRole("button", { name: "Aprovar" }).first(); await expect(approve).toBeVisible(); @@ -34,7 +30,7 @@ test("demo mode shows actions disabled with the discovery tooltip", async ({ pag }); test("admin sees the Administração group and the users screen", async ({ page }) => { - await page.addInitScript(() => { window.USE_MOCK_DATA = true; }); + await installMockApi(page, { authMode: "user", role: "admin" }); await page.goto("/"); await page.locator(".nav-item", { hasText: "Usuários & Permissões" }).click(); await expect(page).toHaveURL(/#users$/); @@ -44,10 +40,7 @@ test("admin sees the Administração group and the users screen", async ({ page }); test("operador keeps approve buttons but has no admin nav", async ({ page }) => { - await page.addInitScript(() => { - window.USE_MOCK_DATA = true; - window.MOCK_ROLE = "operador"; - }); + await installMockApi(page, { authMode: "user", role: "operador" }); await page.goto("/#hitl"); await expect(page.getByRole("button", { name: "Aprovar" }).first()).toBeEnabled(); await expect(page.locator(".nav-item", { hasText: "Usuários & Permissões" })) diff --git a/docs/design/pages/e2e/system.spec.js b/docs/design/pages/e2e/system.spec.js index 31b5e15..f62b1f3 100644 --- a/docs/design/pages/e2e/system.spec.js +++ b/docs/design/pages/e2e/system.spec.js @@ -25,6 +25,9 @@ test("route without permission shows 403 with the required permission", async ({ }); test("admin still reaches the users screen directly", async ({ page }) => { + // Users de-mockada (5.4) → serve /v1/users + /v1/roles pelo fixture; USE_MOCK_DATA + // segue só para a auth mock admin (o route-guard). Coexistência transitória. + await installMockApi(page, { authMode: "user", role: "admin" }); await page.addInitScript(() => { window.USE_MOCK_DATA = true; }); await page.goto("/#users"); await expect(page.locator(".page-title")).toContainText("Usuários & Permissões"); diff --git a/docs/design/pages/screen_hitl.jsx b/docs/design/pages/screen_hitl.jsx index 7fae34a..407f272 100644 --- a/docs/design/pages/screen_hitl.jsx +++ b/docs/design/pages/screen_hitl.jsx @@ -97,7 +97,7 @@ function PendingOrderCard({ order, onDecide, highlight = false, ctx = null }) {

)} - + {!canApprove && !demoView && (
({ - level: l.level, - threshold_usdt: l.threshold, - description: l.desc, - })), - }; - const mockOrders = CT.pendingOrders.map(o => ({ - ...o, - stop_loss: o.stop, - take_profit: o.takeProfit, - position_size_pct: o.sizePct, - })); - - const [config, setConfig] = useState(mock ? mockConfig : null); - const [orders, setOrders] = useState(mock ? mockOrders : []); - const [loading, setLoading] = useState(!mock); + const [config, setConfig] = useState(null); + const [orders, setOrders] = useState([]); + const [loading, setLoading] = useState(true); const [error, setError] = useState(null); // M2: pair handed over by Mercado's "Ver no HITL" — highlight its orders once. const [focusPair] = useState(() => { @@ -199,24 +177,16 @@ function ScreenHITL({ addToast }) { const [pairFilter, setPairFilter] = useState(focusPair); // N4: per-pair context (preço atual + regime) from the desk snapshot — fetched // ONCE here and distributed to the cards, never one request per order. - const [deskCtx, setDeskCtx] = useState(() => mock ? { - 'BTC/USDT': { last: 64810, regime_label: 'Alta forte' }, - 'ETH/USDT': { last: 3208, regime_label: 'Lateral' }, - 'SOL/USDT': { last: 160.42, regime_label: 'Alta forte' }, - 'XRP/USDT': { last: 0.61, regime_label: 'Desconhecido' }, - 'BNB/USDT': { last: 592, regime_label: 'Caótico' }, - } : {}); + const [deskCtx, setDeskCtx] = useState(() => ({})); useEffect(() => { - if (mock) return; CT_API.getDeskSummary().then(d => { const map = {}; (d.rows || []).forEach(r => { map[r.symbol] = { last: r.last, regime_label: r.regime_label }; }); setDeskCtx(map); }).catch(() => {}); - }, [mock]); + }, []); const load = useCallback(() => { - if (mock) return; setLoading(true); Promise.all([ CT_API.getHITL(), @@ -228,15 +198,11 @@ function ScreenHITL({ addToast }) { setLoading(false); }) .catch(e => { setError(e); setLoading(false); }); - }, [mock]); + }, []); useEffect(() => { load(); }, [load]); const decide = async (orderId, action, note) => { - if (mock) { - setOrders(prev => prev.filter(o => o.id !== orderId)); - return; - } try { // A3: no client-sent operator — the server stamps the session identity. await CT_API.decideOrder(orderId, { @@ -252,14 +218,6 @@ function ScreenHITL({ addToast }) { }; const setLevel = async (level) => { - if (mock) { - setConfig(prev => ({ - ...prev, - current_level: level, - level_description: prev.levels[level]?.description ?? '', - })); - return; - } try { // A3: no client-sent operator — the server stamps the session identity. const updated = await CT_API.patchHITL({ diff --git a/docs/design/pages/screen_orders.jsx b/docs/design/pages/screen_orders.jsx index 2e09e96..0ed692e 100644 --- a/docs/design/pages/screen_orders.jsx +++ b/docs/design/pages/screen_orders.jsx @@ -20,32 +20,15 @@ const ORDER_STATUS_LABEL = { }; function ScreenOrders() { - const mock = !!window.USE_MOCK_DATA; - const mockOrders = CT.orders.map(o => ({ - ...o, - stop_loss: o.stop, - take_profit: o.takeProfit, - position_size_pct: o.sizePct, - operator_note: o.operatorNote, - })); - - // N5: mock closed trades (realized P&L) for the per-pair footer. - const mockTrades = [ - { symbol: 'BTC/USDT', pnl: 108.30 }, { symbol: 'BTC/USDT', pnl: -42.10 }, - { symbol: 'ETH/USDT', pnl: 61.50 }, { symbol: 'SOL/USDT', pnl: 12.38 }, - { symbol: 'SOL/USDT', pnl: 24.90 }, { symbol: 'XRP/USDT', pnl: -8.20 }, - ]; - const [scope] = useCurrentPair(); - const [orders, setOrders] = useState(mock ? mockOrders : null); - const [trades, setTrades] = useState(mock ? mockTrades : null); - const [loading, setLoading] = useState(!mock); + const [orders, setOrders] = useState(null); + const [trades, setTrades] = useState(null); + const [loading, setLoading] = useState(true); const [error, setError] = useState(null); const [statusF, setStatusF] = useState('all'); const [sideF, setSideF] = useState('all'); useEffect(() => { - if (mock) return; setLoading(true); const pairQ = scope && scope !== 'ALL' ? `&pair=${encodeURIComponent(scope)}` : ''; Promise.all([CT_API.getOrders(100, 0, pairQ), CT_API.getTrades(200, 0, pairQ).catch(() => [])]) @@ -55,7 +38,7 @@ function ScreenOrders() { setLoading(false); }) .catch(e => { setError(e); setLoading(false); }); - }, [mock, scope]); + }, [scope]); if (loading) return ; if (error) return { setError(null); setLoading(true); }} />; diff --git a/docs/design/pages/screen_users.jsx b/docs/design/pages/screen_users.jsx index 587bab2..acb18cb 100644 --- a/docs/design/pages/screen_users.jsx +++ b/docs/design/pages/screen_users.jsx @@ -58,25 +58,22 @@ function InviteModal({ roles, onClose, onInvited, addToast }) { } function ScreenUsers({ addToast }) { - const mock = !!window.USE_MOCK_DATA; - const [users, setUsers] = useUsersState(mock ? (CT.adminUsers ?? []) : null); - const [roles, setRoles] = useUsersState(mock ? (CT.roles ?? []) : []); - const [loading, setLoading] = useUsersState(!mock); + const [users, setUsers] = useUsersState(null); + const [roles, setRoles] = useUsersState([]); + const [loading, setLoading] = useUsersState(true); const [error, setError] = useUsersState(null); const [inviting, setInviting] = useUsersState(false); const load = useUsersCallback(() => { - if (mock) return; setLoading(true); Promise.all([CT_API.getUsers(), CT_API.getRoles()]) .then(([u, r]) => { setUsers(u); setRoles(r); setLoading(false); setError(null); }) .catch(e => { setError(e); setLoading(false); }); - }, [mock]); + }, []); useUsersEffect(() => { load(); }, [load]); const act = async (fn, okMsg) => { - if (mock) { addToast?.('Modo demo: ação não aplicada.', 'info'); return; } try { await fn(); addToast?.(okMsg, 'check'); load(); } catch (e) { addToast?.(e?.message ?? 'Falha na ação.', 'alert'); } };