/** * Cliente API para comunicación con el backend */ const API_BASE_URL = process.env.NEXT_PUBLIC_API_URL || 'http://localhost:3001/api'; interface ApiResponse { data?: T; error?: string; message?: string; status: number; } interface RequestOptions extends Omit { body?: unknown; params?: Record; } class ApiError extends Error { status: number; data?: unknown; constructor(message: string, status: number, data?: unknown) { super(message); this.name = 'ApiError'; this.status = status; this.data = data; } } /** * Obtiene el token de autenticación del storage */ function getAuthToken(): string | null { if (typeof window === 'undefined') return null; try { const stored = localStorage.getItem('horux-auth-storage'); if (stored) { const parsed = JSON.parse(stored); return parsed?.state?.token || null; } } catch { return null; } return null; } /** * Construye la URL con query params */ function buildUrl(endpoint: string, params?: Record): string { const url = new URL(endpoint.startsWith('http') ? endpoint : `${API_BASE_URL}${endpoint}`); if (params) { Object.entries(params).forEach(([key, value]) => { if (value !== undefined && value !== null) { url.searchParams.append(key, String(value)); } }); } return url.toString(); } /** * Cliente fetch base con manejo de errores y auth */ async function fetchApi(endpoint: string, options: RequestOptions = {}): Promise> { const { body, params, headers: customHeaders, ...restOptions } = options; const token = getAuthToken(); const headers: HeadersInit = { 'Content-Type': 'application/json', ...customHeaders, }; if (token) { (headers as Record)['Authorization'] = `Bearer ${token}`; } const config: RequestInit = { ...restOptions, headers, }; if (body) { config.body = JSON.stringify(body); } try { const url = buildUrl(endpoint, params); const response = await fetch(url, config); let data: T | undefined; const contentType = response.headers.get('content-type'); if (contentType?.includes('application/json')) { data = await response.json(); } if (!response.ok) { const errorMessage = (data as { message?: string })?.message || (data as { error?: string })?.error || `Error ${response.status}`; throw new ApiError(errorMessage, response.status, data); } return { data, status: response.status, }; } catch (error) { if (error instanceof ApiError) { throw error; } throw new ApiError( error instanceof Error ? error.message : 'Error de conexión', 0 ); } } /** * API Client con métodos HTTP */ export const api = { get(endpoint: string, options?: RequestOptions): Promise> { return fetchApi(endpoint, { ...options, method: 'GET' }); }, post(endpoint: string, body?: unknown, options?: RequestOptions): Promise> { return fetchApi(endpoint, { ...options, method: 'POST', body }); }, put(endpoint: string, body?: unknown, options?: RequestOptions): Promise> { return fetchApi(endpoint, { ...options, method: 'PUT', body }); }, patch(endpoint: string, body?: unknown, options?: RequestOptions): Promise> { return fetchApi(endpoint, { ...options, method: 'PATCH', body }); }, delete(endpoint: string, options?: RequestOptions): Promise> { return fetchApi(endpoint, { ...options, method: 'DELETE' }); }, }; /** * Endpoints tipados para la API */ export const endpoints = { // Auth auth: { login: '/auth/login', register: '/auth/register', logout: '/auth/logout', me: '/auth/me', refresh: '/auth/refresh', }, // Users users: { base: '/users', byId: (id: string) => `/users/${id}`, profile: '/users/profile', }, // Strategies strategies: { base: '/strategies', byId: (id: string) => `/strategies/${id}`, activate: (id: string) => `/strategies/${id}/activate`, deactivate: (id: string) => `/strategies/${id}/deactivate`, backtest: (id: string) => `/strategies/${id}/backtest`, }, // Trades trades: { base: '/trades', byId: (id: string) => `/trades/${id}`, active: '/trades/active', history: '/trades/history', }, // Portfolio portfolio: { base: '/portfolio', balance: '/portfolio/balance', positions: '/portfolio/positions', performance: '/portfolio/performance', }, // Market Data market: { prices: '/market/prices', ticker: (symbol: string) => `/market/ticker/${symbol}`, candles: (symbol: string) => `/market/candles/${symbol}`, }, // Analytics analytics: { dashboard: '/analytics/dashboard', performance: '/analytics/performance', risk: '/analytics/risk', }, } as const; export { ApiError }; export type { ApiResponse, RequestOptions };