Spaces:
Sleeping
Sleeping
| /** | |
| * 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<Response> { | |
| 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), | |
| }; | |