diff --git a/.env b/.env index 6160c3f..6e3cec2 100644 --- a/.env +++ b/.env @@ -1,4 +1,4 @@ -DEBUG_MODE=true +DEBUG_MODE=false VITE_EFC_API_URL=https://api.efc-aduanasoft.com/api/v1 VITE_EFC_MICROSERVICE_URL=https://api.efc-aduanasoft.com/microservice/api/v1 diff --git a/src/App.jsx b/src/App.jsx index a1a10fd..00b1f61 100644 --- a/src/App.jsx +++ b/src/App.jsx @@ -26,11 +26,6 @@ import PasswordResetConfirm from './pages/PasswordResetConfirm'; function AppContent() { const location = useLocation(); const isAuthPage = location.pathname === '/login' || location.pathname === '/' || location.pathname === '/forgot-password' || location.pathname.startsWith('/user/password-reset-confirm/'); - - console.log('🚀 AppContent renderizado'); - console.log('📍 Ubicación actual:', location.pathname); - console.log('🔐 Es página de auth:', isAuthPage); - console.log('🎫 Token en localStorage:', !!localStorage.getItem('access')); if (isAuthPage) { return ( diff --git a/src/api/auth.js b/src/api/auth.js index 2be833c..661a7bf 100644 --- a/src/api/auth.js +++ b/src/api/auth.js @@ -7,7 +7,6 @@ export async function login(username, password) { body: JSON.stringify({ username, password }), }); - console.log('API URL:', `${API_URL}/token/`); if (!response.ok) { throw new Error('Credenciales inválidas'); } diff --git a/src/components/RequireAuth.jsx b/src/components/RequireAuth.jsx index a52b3a9..d1a5abd 100644 --- a/src/components/RequireAuth.jsx +++ b/src/components/RequireAuth.jsx @@ -4,19 +4,15 @@ import { Navigate } from 'react-router-dom'; // Esta función verifica si el usuario está autenticado (por ejemplo, si hay un token en localStorage) function isAuthenticated() { const token = localStorage.getItem('access'); - console.log('🔐 Verificando autenticación, token:', token ? 'presente' : 'ausente'); return !!token; } export default function RequireAuth({ children }) { const authenticated = isAuthenticated(); - console.log('🛡️ RequireAuth - usuario autenticado:', authenticated); if (!authenticated) { - console.log('❌ No autenticado, redirigiendo a /login'); return ; } - console.log('✅ Usuario autenticado, mostrando contenido'); return children; } diff --git a/src/fetchWithAuth.js b/src/fetchWithAuth.js index 7a56731..c726a8b 100644 --- a/src/fetchWithAuth.js +++ b/src/fetchWithAuth.js @@ -25,7 +25,10 @@ const refreshToken = async () => { throw new Error('No refresh token available'); } - const response = await fetch(`${API_URL}/auth/token/refresh/`, { + // Intentar primero con el endpoint completo + let refreshEndpoint = `${API_URL}/token/refresh/`; + + let response = await fetch(refreshEndpoint, { method: 'POST', headers: { 'Content-Type': 'application/json', @@ -35,8 +38,23 @@ const refreshToken = async () => { }), }); + // Si falla con 404, intentar con el endpoint alternativo + if (response.status === 404) { + refreshEndpoint = `${API_URL}/token/refresh/`; + response = await fetch(refreshEndpoint, { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify({ + refresh: refresh + }), + }); + } + if (!response.ok) { - throw new Error('Failed to refresh token'); + const errorText = await response.text(); + throw new Error(`Failed to refresh token: ${response.status} - ${errorText}`); } const data = await response.json(); @@ -62,7 +80,7 @@ const refreshToken = async () => { window.location.href = '/login'; }, 1000); - throw error; + throw new Error('SESSION_EXPIRED'); } }; @@ -90,42 +108,75 @@ export const fetchWithAuth = async (url, options = {}) => { // Hacer la petición inicial let response = await fetch(url, finalOptions); - // Si la respuesta es 401 (Unauthorized), intentar renovar el token - if (response.status === 401 && !isRefreshing) { - isRefreshing = true; + // Si la respuesta es 401 (Unauthorized), manejar renovación de token + if (response.status === 401) { + if (!isRefreshing) { + isRefreshing = true; - try { - // Renovar el token - const newToken = await refreshToken(); - - // Procesar la cola de peticiones pendientes - processQueue(null, newToken); - - // Actualizar el header de autorización y reintentar la petición original - finalOptions.headers['Authorization'] = `Bearer ${newToken}`; - response = await fetch(url, finalOptions); - - } catch (refreshError) { - // Si falla la renovación, procesar la cola con error - processQueue(refreshError, null); - throw refreshError; - } finally { - isRefreshing = false; + try { + // Renovar el token + const newToken = await refreshToken(); + + // Procesar la cola de peticiones pendientes + processQueue(null, newToken); + + // Actualizar el header de autorización y reintentar la petición original + finalOptions.headers['Authorization'] = `Bearer ${newToken}`; + response = await fetch(url, finalOptions); + + } catch (refreshError) { + // Si falla la renovación, procesar la cola con error + processQueue(refreshError, null); + throw refreshError; + } finally { + isRefreshing = false; + } + } else { + // Ya hay un refresh en proceso, agregar esta petición a la cola + return new Promise((resolve, reject) => { + failedQueue.push({ + resolve: (token) => { + finalOptions.headers['Authorization'] = `Bearer ${token}`; + fetch(url, finalOptions) + .then(resolve) + .catch(reject); + }, + reject + }); + }); } } - // Si todavía hay un 401 después del intento de renovación, redirigir al login + // Si todavía hay un 401 después del intento de renovación, verificar si realmente es un problema de auth if (response.status === 401) { - localStorage.removeItem('access'); - localStorage.removeItem('refresh'); - localStorage.removeItem('user_id'); - localStorage.removeItem('user_is_importador'); + // Verificar si tenemos un token válido en localStorage + const currentToken = localStorage.getItem('access'); - setTimeout(() => { - window.location.href = '/login'; - }, 1000); + // Intentar obtener más información del error + let errorDetails = ''; + try { + const errorText = await response.text(); + errorDetails = errorText; + } catch (e) { + // Error al leer respuesta + } - throw new Error('Session expired'); + // Si no hay token o el error indica problema de autenticación, limpiar sesión + if (!currentToken || errorDetails.includes('token') || errorDetails.includes('auth')) { + localStorage.removeItem('access'); + localStorage.removeItem('refresh'); + localStorage.removeItem('user_id'); + localStorage.removeItem('user_is_importador'); + + setTimeout(() => { + window.location.href = '/login'; + }, 1000); + + throw new Error('SESSION_EXPIRED'); + } else { + // Si hay token pero sigue fallando, podría ser un problema del servidor + // No limpiar la sesión, dejar que el error se propague + } } return response; @@ -185,21 +236,36 @@ export const postFormDataWithAuth = async (url, formData) => { try { let response = await fetch(url, options); - if (response.status === 401 && !isRefreshing) { - isRefreshing = true; + if (response.status === 401) { + if (!isRefreshing) { + isRefreshing = true; - try { - const newToken = await refreshToken(); - processQueue(null, newToken); - - options.headers['Authorization'] = `Bearer ${newToken}`; - response = await fetch(url, options); - - } catch (refreshError) { - processQueue(refreshError, null); - throw refreshError; - } finally { - isRefreshing = false; + try { + const newToken = await refreshToken(); + processQueue(null, newToken); + + options.headers['Authorization'] = `Bearer ${newToken}`; + response = await fetch(url, options); + + } catch (refreshError) { + processQueue(refreshError, null); + throw refreshError; + } finally { + isRefreshing = false; + } + } else { + // Ya hay un refresh en proceso, agregar esta petición a la cola + return new Promise((resolve, reject) => { + failedQueue.push({ + resolve: (token) => { + options.headers['Authorization'] = `Bearer ${token}`; + fetch(url, options) + .then(resolve) + .catch(reject); + }, + reject + }); + }); } } diff --git a/src/pages/Procesos.jsx b/src/pages/Procesos.jsx index f835a67..40b1c8c 100644 --- a/src/pages/Procesos.jsx +++ b/src/pages/Procesos.jsx @@ -1,7 +1,7 @@ import React, { useEffect, useState } from 'react'; import { fetchProcesamientoPedimentos } from '../api/procesos.ts'; -import { postWithAuth } from '../fetchWithAuth'; +import { postWithAuth, putWithAuth } from '../fetchWithAuth'; const API_URL = import.meta.env.VITE_EFC_API_URL; const MICROSERVICE_URL = import.meta.env.VITE_EFC_MICROSERVICE_URL; @@ -29,10 +29,73 @@ export default function Procesos() { // Estado para loading de ejecución de servicio const [executingId, setExecutingId] = useState(null); + // Estado para loading de cambio de estado + const [changingStateId, setChangingStateId] = useState(null); // Dropdown state: id del proceso abierto o null const [openDropdownId, setOpenDropdownId] = useState(null); + // Función para cambiar estado de Error a En Espera + const handlePasarAEspera = async (proc) => { + setChangingStateId(proc.id); + + try { + const body = { + estado: 1, // Cambiar a En Espera + tipo_procesamiento: 2, + pedimento: typeof proc.pedimento === 'object' && proc.pedimento !== null ? proc.pedimento.id : proc.pedimento, + servicio: proc.servicio, + organizacion: proc.organizacion_id || proc.organizacion || proc.organizacionId + }; + + const res = await putWithAuth(`${API_URL}/customs/procesamientopedimentos/${proc.id}/`, body); + + if (!res.ok) { + // Intentar obtener más detalles del error + let errorText = 'Error desconocido'; + try { + errorText = await res.text(); + } catch (textErr) { + // Error al leer respuesta + } + throw new Error(`Error al cambiar el estado del proceso: ${errorText}`); + } + + alert('Estado cambiado a "En Espera" correctamente'); + setOpenDropdownId(null); + + // Refrescar la lista de procesos + window.location.reload(); + } catch (err) { + // Crear un mensaje de error más detallado y persistente + const errorDetails = { + message: err.message, + status: err.status || 'N/A', + stack: err.stack, + timestamp: new Date().toISOString() + }; + + // Alert más detallado que permanece visible + const detailedMessage = ` +🚨 ERROR DETALLADO: +⏰ Tiempo: ${new Date().toLocaleString()} +📝 Mensaje: ${err.message} +🔢 Status: ${err.status || 'N/A'} +🔍 Tipo: ${err.name || 'Error'} + +📋 Copia este mensaje y compártelo para debugging. + `.trim(); + + if (err.message === 'SESSION_EXPIRED') { + alert('🚪 SESIÓN EXPIRADA\n\nTu sesión ha expirado. Por favor, inicia sesión nuevamente.\n\n' + detailedMessage); + } else { + alert(detailedMessage); + } + } finally { + setChangingStateId(null); + } + }; + // Función para ejecutar el servicio según el tipo de proceso const handleEjecutarServicio = async (proc) => { @@ -77,7 +140,6 @@ export default function Procesos() { alert('Servicio ejecutado correctamente'); setOpenDropdownId(null); } catch (err) { - console.error('Error executing service:', err); if (err.message === 'SESSION_EXPIRED') { alert('Tu sesión ha expirado. Por favor, inicia sesión nuevamente.'); } else { @@ -91,18 +153,25 @@ export default function Procesos() { // Cierra el dropdown si se hace click fuera useEffect(() => { if (openDropdownId === null) return; + function handleClickOutside(e) { - const el = document.getElementById(`dropdown-acciones-${openDropdownId}`); - if (el && !el.contains(e.target)) { + // Buscar el dropdown específico que está abierto + const dropdownContainer = document.getElementById(`dropdown-acciones-${openDropdownId}`); + + // Si el click fue fuera del dropdown, cerrarlo + if (dropdownContainer && !dropdownContainer.contains(e.target)) { setOpenDropdownId(null); } } - // Usar setTimeout para evitar que el click que abre el dropdown lo cierre inmediatamente - setTimeout(() => { - document.addEventListener('click', handleClickOutside); + + // Agregar el listener con un pequeño delay para evitar que se cierre inmediatamente + const timeoutId = setTimeout(() => { + document.addEventListener('click', handleClickOutside, true); }, 100); + return () => { - document.removeEventListener('click', handleClickOutside); + clearTimeout(timeoutId); + document.removeEventListener('click', handleClickOutside, true); }; }, [openDropdownId]); @@ -120,15 +189,11 @@ export default function Procesos() { filters['ordering'] = (sortOrder === 'desc' ? '-' : '') + sortField; } - console.log('Fetching procesos with filters:', filters); - const data = await fetchProcesamientoPedimentos(page, itemsPerPage, filters); - console.log('Data received:', data); setProcesos(data.results || []); setCount(data.count || 0); } catch (err) { - console.error('Error fetching procesos:', err); if (err.message === 'SESSION_EXPIRED') { setError('Tu sesión ha expirado. Por favor, inicia sesión nuevamente.'); } else { @@ -306,9 +371,10 @@ export default function Procesos() { ) : ( <> {/* Vista de tabla para pantallas grandes */} -
- - +
+
+ - + {procesos.length === 0 ? ( @@ -591,29 +664,18 @@ export default function Procesos() { -
{ @@ -365,7 +431,7 @@ export default function Procesos() {
@@ -419,7 +485,7 @@ export default function Procesos() { : String(proc.servicio)} -
+
{openDropdownId === proc.id && ( -
-
- - - + <> + {/* Overlay invisible para cerrar dropdown */} +
setOpenDropdownId(null)} + /> + {/* Dropdown menu */} +
+
+ + +
-
+ )}