/** * Cliente API centralizado. * En dev: Vite hace proxy de /api → http://localhost:8000 * En prod: FastAPI sirve desde el mismo origen. * * Modo demo público: las rutas de lectura y el chat LLM no requieren key. * Las acciones de administración (upload, run-analysis, config) exigen * X-API-Key; la key se pide solo al ejecutar una de ellas y se guarda * en localStorage. */ const BASE = '/api'; const STORAGE_KEY = 'ddmrp_api_key'; export function hasAdminKey(): boolean { return !!localStorage.getItem(STORAGE_KEY); } export function setAdminKey(key: string) { localStorage.setItem(STORAGE_KEY, key.trim()); } export function clearAdminKey() { localStorage.removeItem(STORAGE_KEY); } function requireAdminKey(): string { let key = localStorage.getItem(STORAGE_KEY) || ''; if (!key) { key = (window.prompt('Acción de administrador: ingresa la API key.') || '').trim(); if (key) localStorage.setItem(STORAGE_KEY, key); } return key; } async function apiFetch( path: string, init: RequestInit = {}, opts: { admin?: boolean } = {}, ): Promise { const headers = new Headers(init.headers || {}); if (opts.admin) { headers.set('X-API-Key', requireAdminKey()); } else { // Si hay key guardada la adjuntamos (inofensivo en rutas públicas). const stored = localStorage.getItem(STORAGE_KEY); if (stored) headers.set('X-API-Key', stored); } const res = await fetch(`${BASE}${path}`, { ...init, headers }); if (res.status === 401 && opts.admin) { clearAdminKey(); alert('API key inválida. Reintenta la acción para ingresarla de nuevo.'); } return res; } async function _json(res: Response) { if (!res.ok) throw new Error(`API error ${res.status}: ${await res.text()}`); return res.json(); } export const api = { // Dashboard getSkus: () => apiFetch(`/skus`).then(_json), analyze: (selectedSkus: string[], demandAdjustment: number) => apiFetch(`/analyze`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ selectedSkus, demandAdjustment }), }).then(_json), // DataUpload (admin) uploadFile: (tipo: 'demanda-pt' | 'materiales' | 'capacidad', file: File) => { const form = new FormData(); form.append('file', file); return apiFetch(`/upload/${tipo}`, { method: 'POST', body: form }, { admin: true }).then(_json); }, validateAll: () => apiFetch(`/validate`, { method: 'POST' }, { admin: true }).then(_json), runAnalysis: () => apiFetch(`/run-analysis`, { method: 'POST' }, { admin: true }).then(_json), getRunStatus: (runId: string) => apiFetch(`/run-status/${runId}`).then(_json), // AgenteMeta / BotConfig getAgentsStatus: () => apiFetch(`/agents/status`).then(_json), getLogs: () => apiFetch(`/logs`).then(_json), getProposals: () => apiFetch(`/proposals`).then(_json), approveProposal: (id: string) => apiFetch(`/proposals/${id}/approve`, { method: 'POST' }, { admin: true }).then(_json), rejectProposal: (id: string) => apiFetch(`/proposals/${id}/reject`, { method: 'POST' }, { admin: true }).then(_json), saveTelegramConfig: (config: { team_token: string; team_chat_id: string; admin_chat_id: string; proactive_alerts: boolean; meta_notifications: boolean; xyz_threshold: string; }) => apiFetch(`/config/telegram`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(config), }, { admin: true }).then(_json), newAnalysis: () => apiFetch(`/new-analysis`, { method: 'POST' }, { admin: true }).then(_json), };