import React, { createContext, useContext, useState, useEffect, useCallback } from 'react'; import { fetchNotificacionByTaskId } from '../api/notificaciones'; const STORAGE_KEY = 'efc_audit_tasks'; /** * TaskProgressContext * * Gestiona las tareas de auditoría en background. Persiste en localStorage para * sobrevivir recargas de página. Reconecta SSE automáticamente para tareas que * quedaron en "processing" tras un refresh. * * Estructura de cada tarea: * { * task_id: string, * label: string, // 'EDocuments', 'COVEs', etc. * status: 'submitted' | 'processing' | 'completed' | 'failed', * message: string, * progress: number, // 0-100 * resultado: object | null, * organizacion_id: string, * started_at: ISO string, * dismissed: boolean, * } */ const TaskProgressContext = createContext(null); export function useTaskProgress() { const ctx = useContext(TaskProgressContext); if (!ctx) throw new Error('useTaskProgress debe usarse dentro de TaskProgressProvider'); return ctx; } function loadFromStorage() { try { const raw = localStorage.getItem(STORAGE_KEY); if (!raw) return []; const tasks = JSON.parse(raw); const cutoff = Date.now() - 24 * 60 * 60 * 1000; // 24 horas // Auto-descartar tareas "processing/submitted" con más de 24h (nunca terminaron) return tasks.map(t => { if ( (t.status === 'processing' || t.status === 'submitted') && t.started_at && new Date(t.started_at).getTime() < cutoff ) { return { ...t, dismissed: true, status: 'failed', message: 'Tarea expirada' }; } return t; }); } catch { return []; } } function saveToStorage(tasks) { try { // Solo persistir las últimas 20 tareas no descartadas para no crecer indefinidamente const toSave = tasks.slice(-20); localStorage.setItem(STORAGE_KEY, JSON.stringify(toSave)); } catch { // Storage lleno — ignorar } } export function TaskProgressProvider({ children }) { const [tasks, setTasksRaw] = useState(() => loadFromStorage()); // task_id que Auditor.jsx debe abrir automáticamente al montar const [pendingOpenTaskId, setPendingOpenTaskId] = useState(null); const setTasks = useCallback((updater) => { setTasksRaw((prev) => { const next = typeof updater === 'function' ? updater(prev) : updater; saveToStorage(next); return next; }); }, []); // Añade una tarea nueva (cuando el usuario dispara una auditoría) const addTask = useCallback((taskData) => { setTasks((prev) => { // Evitar duplicados if (prev.find((t) => t.task_id === taskData.task_id)) return prev; return [ ...prev, { status: 'submitted', message: 'Tarea enviada', progress: 0, resultado: null, dismissed: false, started_at: new Date().toISOString(), ...taskData, }, ]; }); }, [setTasks]); // Actualiza campos de una tarea existente por task_id const updateTask = useCallback((task_id, patch) => { setTasks((prev) => prev.map((t) => (t.task_id === task_id ? { ...t, ...patch } : t)) ); }, [setTasks]); // Descarta una tarea de la vista (no la borra de storage) const dismissTask = useCallback((task_id) => { setTasks((prev) => prev.map((t) => (t.task_id === task_id ? { ...t, dismissed: true } : t)) ); }, [setTasks]); // Llamado desde TaskProgressCard cuando el usuario hace click en "Ver resultado" const openTaskInAuditor = useCallback((task_id) => { setPendingOpenTaskId(task_id); }, []); // Llamado desde Auditor.jsx después de consumir el pendingOpenTaskId const clearPendingOpen = useCallback(() => { setPendingOpenTaskId(null); }, []); const getTask = useCallback( (task_id) => tasks.find((t) => t.task_id === task_id) ?? null, [tasks] ); // Al montar: intenta recuperar tareas que completaron mientras el cliente no estaba conectado // (Redis TTL expirado, página cerrada, etc.). Consulta la Notificacion en DB por task_id. useEffect(() => { const staleTasks = loadFromStorage().filter( (t) => !t.dismissed && t.status !== 'completed' && t.status !== 'failed' ); if (staleTasks.length === 0) return; staleTasks.forEach(async (task) => { try { const notif = await fetchNotificacionByTaskId(task.task_id); if (notif?.datos?.resultado) { updateTask(task.task_id, { status: 'completed', resultado: notif.datos.resultado, message: notif.mensaje, progress: 100, }); } } catch { // Tarea aún en proceso o usuario no autenticado aún — ignorar } }); }, []); // eslint-disable-line react-hooks/exhaustive-deps // Tareas visibles (no descartadas) const visibleTasks = tasks.filter((t) => !t.dismissed); return ( {children} ); }