Se soluciono autenticacion
This commit is contained in:
@@ -1,4 +1,5 @@
|
||||
// src/api/pedimentoDocuments.ts
|
||||
import { fetchWithAuth } from '../fetchWithAuth';
|
||||
|
||||
export interface PedimentoDocument {
|
||||
id: string;
|
||||
@@ -23,8 +24,6 @@ export interface PedimentoDocumentsResponse {
|
||||
const API_URL = import.meta.env.VITE_EFC_API_URL;
|
||||
|
||||
export async function fetchPedimentoDocuments(
|
||||
token: string,
|
||||
pedimentoId: string = '',
|
||||
page: number = 1,
|
||||
pageSize: number = 10,
|
||||
filters: {
|
||||
@@ -32,7 +31,8 @@ export async function fetchPedimentoDocuments(
|
||||
extension?: string;
|
||||
document_type?: string | number;
|
||||
created_at?: string;
|
||||
} = {}
|
||||
} = {},
|
||||
pedimentoId: string = ''
|
||||
): Promise<PedimentoDocumentsResponse> {
|
||||
const params = new URLSearchParams();
|
||||
params.append('page', String(page));
|
||||
@@ -43,18 +43,10 @@ export async function fetchPedimentoDocuments(
|
||||
if (filters.document_type) params.append('document_type', String(filters.document_type));
|
||||
if (filters.created_at) params.append('created_at', filters.created_at);
|
||||
|
||||
const res = await fetch(
|
||||
`${API_URL}/record/documents/?${params.toString()}`,
|
||||
{
|
||||
headers: {
|
||||
'Authorization': `Bearer ${token}`,
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
}
|
||||
const res = await fetchWithAuth(
|
||||
`${API_URL}/record/documents/?${params.toString()}`
|
||||
);
|
||||
if (res.status === 401) {
|
||||
throw new Error('SESSION_EXPIRED');
|
||||
}
|
||||
|
||||
if (!res.ok) throw new Error('No autorizado o error en la petición');
|
||||
return res.json();
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
|
||||
import { fetchWithAuth } from '../fetchWithAuth';
|
||||
|
||||
export interface Document {
|
||||
id: string;
|
||||
@@ -19,8 +20,6 @@ export interface DocumentsResponse {
|
||||
results: Document[];
|
||||
}
|
||||
|
||||
import { refreshToken } from './auth';
|
||||
|
||||
const API_URL = import.meta.env.VITE_EFC_API_URL;
|
||||
// Obtiene la lista de documentos (pedimentos)
|
||||
export interface PedimentosFilters {
|
||||
@@ -38,7 +37,6 @@ export interface PedimentosFilters {
|
||||
}
|
||||
|
||||
export async function fetchDocuments(
|
||||
token: string,
|
||||
page: number = 1,
|
||||
pageSize: number = 10,
|
||||
filters: PedimentosFilters = {}
|
||||
@@ -51,65 +49,14 @@ export async function fetchDocuments(
|
||||
params.append(key, String(value));
|
||||
}
|
||||
});
|
||||
let res = await fetch(`${API_URL}/customs/pedimentos/?${params.toString()}`, {
|
||||
headers: {
|
||||
'Authorization': `Bearer ${token}`,
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
});
|
||||
if (res.status === 401) {
|
||||
// Intentar refrescar el token
|
||||
const refresh = localStorage.getItem('refresh');
|
||||
if (refresh) {
|
||||
try {
|
||||
const data = await refreshToken(refresh);
|
||||
localStorage.setItem('access', data.access);
|
||||
// Reintenta la petición con el nuevo access token
|
||||
res = await fetch(`${API_URL}/customs/pedimentos/?page=${page}&page_size=${pageSize}`, {
|
||||
headers: {
|
||||
'Authorization': `Bearer ${data.access}`,
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
});
|
||||
} catch (err) {
|
||||
throw new Error('SESSION_EXPIRED');
|
||||
}
|
||||
} else {
|
||||
throw new Error('SESSION_EXPIRED');
|
||||
}
|
||||
}
|
||||
|
||||
const res = await fetchWithAuth(`${API_URL}/customs/pedimentos/?${params.toString()}`);
|
||||
if (!res.ok) throw new Error('No autorizado o error en la petición');
|
||||
return res.json();
|
||||
}
|
||||
// Obtiene los documentos por id de pedimento
|
||||
export async function fetchDocumentById(token: string, id: string): Promise<DocumentsResponse> {
|
||||
let res = await fetch(`${API_URL}/record/documents/?page=1&page_size=10&pedimento=${id}/`, {
|
||||
headers: {
|
||||
'Authorization': `Bearer ${token}`,
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
});
|
||||
if (res.status === 401) {
|
||||
// Intentar refrescar el token
|
||||
const refresh = localStorage.getItem('refresh');
|
||||
if (refresh) {
|
||||
try {
|
||||
const data = await refreshToken(refresh);
|
||||
localStorage.setItem('access', data.access);
|
||||
// Reintenta la petición con el nuevo access token
|
||||
res = await fetch(`${API_URL}/record/documents/?page=1&page_size=10&pedimento=${id}/`, {
|
||||
headers: {
|
||||
'Authorization': `Bearer ${data.access}`,
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
});
|
||||
} catch (err) {
|
||||
throw new Error('SESSION_EXPIRED');
|
||||
}
|
||||
} else {
|
||||
throw new Error('SESSION_EXPIRED');
|
||||
}
|
||||
}
|
||||
export async function fetchDocumentById(id: string): Promise<DocumentsResponse> {
|
||||
const res = await fetchWithAuth(`${API_URL}/record/documents/?page=1&page_size=10&pedimento=${id}/`);
|
||||
if (!res.ok) throw new Error('No autorizado o error en la petición');
|
||||
return res.json();
|
||||
}
|
||||
|
||||
@@ -1,15 +1,9 @@
|
||||
import { fetchWithAuth, putWithAuth } from '../fetchWithAuth';
|
||||
|
||||
// PUT para marcar una notificación como vista
|
||||
export async function marcarNotificacionComoVista(id: number): Promise<Notificacion> {
|
||||
const token = localStorage.getItem('access');
|
||||
const url = `${API_URL}/notificaciones/notificaciones/${id}/`;
|
||||
const headers = new Headers();
|
||||
if (token) headers.append('Authorization', `Bearer ${token}`);
|
||||
headers.append('Content-Type', 'application/json');
|
||||
const res = await fetch(url, {
|
||||
method: 'PUT',
|
||||
headers,
|
||||
body: JSON.stringify({ visto: true })
|
||||
});
|
||||
const res = await putWithAuth(url, { visto: true });
|
||||
if (!res.ok) throw new Error('Error al actualizar notificación');
|
||||
return await res.json();
|
||||
}
|
||||
@@ -41,28 +35,16 @@ export interface NotificacionesResponse {
|
||||
const API_URL = import.meta.env.VITE_EFC_API_URL;
|
||||
|
||||
export async function fetchNotificaciones({ page = 1, pageSize = 10, visto = false } = {}): Promise<NotificacionesResponse> {
|
||||
const token = localStorage.getItem('access');
|
||||
const url = `${API_URL}/notificaciones/notificaciones/?page=${page}&page_size=${pageSize}&visto=${visto}`;
|
||||
const headers = new Headers();
|
||||
if (token) headers.append('Authorization', `Bearer ${token}`);
|
||||
headers.append('Content-Type', 'application/json');
|
||||
const res = await fetch(url, {
|
||||
headers,
|
||||
});
|
||||
const res = await fetchWithAuth(url);
|
||||
if (!res.ok) throw new Error('Error al obtener notificaciones');
|
||||
return await res.json();
|
||||
}
|
||||
|
||||
|
||||
export async function fetchAllNotifications({page = 1, page_size=10}): Promise<NotificacionesResponse>{
|
||||
const token = localStorage.getItem('access');
|
||||
const url = `${API_URL}/notificaciones/notificaciones/?page=${page}&page_size=${page_size}`;
|
||||
const headers = new Headers();
|
||||
if (token) headers.append('Authorization', `Bearer ${token}`);
|
||||
headers.append('Content-Type', 'application/json');
|
||||
const res = await fetch(url, {
|
||||
headers,
|
||||
});
|
||||
const res = await fetchWithAuth(url);
|
||||
if (!res.ok) throw new Error('Error al obtener notificaciones');
|
||||
return await res.json();
|
||||
}
|
||||
@@ -1,5 +1,6 @@
|
||||
// organization.ts
|
||||
// Tipos para la respuesta del endpoint de uso de almacenamiento de organización
|
||||
import { fetchWithAuth } from '../fetchWithAuth';
|
||||
|
||||
export interface OrganizationUsage {
|
||||
organizacion: string;
|
||||
@@ -16,16 +17,9 @@ export interface OrganizationUsage {
|
||||
const API_URL = import.meta.env.VITE_EFC_API_URL;
|
||||
|
||||
// Ejemplo de función para obtener la información tipada
|
||||
export async function fetchOrganizationUsage(token: string): Promise<OrganizationUsage> {
|
||||
const res = await fetch(`${API_URL}/organization/uso-almacenamiento/mi_organizacion/`, {
|
||||
headers: {
|
||||
'Authorization': `Bearer ${token}`,
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
});
|
||||
if (res.status === 401) {
|
||||
throw new Error('SESSION_EXPIRED');
|
||||
}
|
||||
export async function fetchOrganizationUsage(): Promise<OrganizationUsage> {
|
||||
const res = await fetchWithAuth(`${API_URL}/organization/uso-almacenamiento/mi_organizacion/`);
|
||||
|
||||
if (!res.ok) {
|
||||
throw new Error('Error al obtener información de la organización');
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
// src/api/pedimentoDocuments.ts
|
||||
import { fetchWithAuth } from '../fetchWithAuth';
|
||||
|
||||
export interface PedimentoDocument {
|
||||
id: string;
|
||||
@@ -19,26 +20,25 @@ export interface PedimentoDocumentsResponse {
|
||||
results: PedimentoDocument[];
|
||||
}
|
||||
|
||||
const API_URL = import.meta.env.VITE_EFC_API_URL;
|
||||
const API_URL = (import.meta as any).env.VITE_EFC_API_URL;
|
||||
|
||||
export async function fetchPedimentoDocuments(
|
||||
token: string,
|
||||
pedimentoId: string,
|
||||
page: number = 1,
|
||||
pageSize: number = 10
|
||||
): Promise<PedimentoDocumentsResponse> {
|
||||
const res = await fetch(
|
||||
`${API_URL}/record/documents/?page=${page}&page_size=${pageSize}&pedimento=${pedimentoId}`,
|
||||
{
|
||||
headers: {
|
||||
'Authorization': `Bearer ${token}`,
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
try {
|
||||
const res = await fetchWithAuth(
|
||||
`${API_URL}/record/documents/?page=${page}&page_size=${pageSize}&pedimento=${pedimentoId}`
|
||||
);
|
||||
|
||||
if (!res.ok) {
|
||||
throw new Error('No autorizado o error en la petición');
|
||||
}
|
||||
);
|
||||
if (res.status === 401) {
|
||||
throw new Error('SESSION_EXPIRED');
|
||||
|
||||
return res.json();
|
||||
} catch (error) {
|
||||
console.error('Error in fetchPedimentoDocuments:', error);
|
||||
throw error;
|
||||
}
|
||||
if (!res.ok) throw new Error('No autorizado o error en la petición');
|
||||
return res.json();
|
||||
}
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
import { fetchWithAuth } from '../fetchWithAuth';
|
||||
|
||||
// Tipos para la respuesta y registros
|
||||
export interface ProcesamientoPedimento {
|
||||
id: number;
|
||||
@@ -20,14 +22,34 @@ export interface ProcesamientoPedimentosResponse {
|
||||
|
||||
// API para customs/procesamientopedimentos/
|
||||
export async function fetchProcesamientoPedimentos(
|
||||
token: string | null,
|
||||
page: number = 1,
|
||||
pageSize: number = 20
|
||||
pageSize: number = 20,
|
||||
filters: Record<string, any> = {}
|
||||
): Promise<ProcesamientoPedimentosResponse> {
|
||||
const API_URL = import.meta.env.VITE_EFC_API_URL;
|
||||
const headers: Record<string, string> = {};
|
||||
if (token) headers['Authorization'] = `Bearer ${token}`;
|
||||
const res = await fetch(`${API_URL}/customs/procesamientopedimentos/?page=${page}&page_size=${pageSize}`, { headers });
|
||||
if (!res.ok) throw new Error('Error al obtener procesamiento de pedimentos');
|
||||
return await res.json();
|
||||
try {
|
||||
const API_URL = (import.meta as any).env.VITE_EFC_API_URL;
|
||||
|
||||
// Construir query params
|
||||
const params = new URLSearchParams();
|
||||
params.append('page', String(page));
|
||||
params.append('page_size', String(pageSize));
|
||||
|
||||
// Agregar filtros
|
||||
Object.entries(filters).forEach(([key, value]) => {
|
||||
if (value !== undefined && value !== null && value !== '') {
|
||||
params.append(key, String(value));
|
||||
}
|
||||
});
|
||||
|
||||
const res = await fetchWithAuth(`${API_URL}/customs/procesamientopedimentos/?${params.toString()}`);
|
||||
|
||||
if (!res.ok) {
|
||||
throw new Error('Error al obtener procesamiento de pedimentos');
|
||||
}
|
||||
|
||||
return await res.json();
|
||||
} catch (error) {
|
||||
console.error('Error in fetchProcesamientoPedimentos:', error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,7 @@
|
||||
const API_URL = import.meta.env.VITE_EFC_API_URL || 'http://localhost:8000';
|
||||
import { fetchWithAuth, postWithAuth, putWithAuth, deleteWithAuth } from '../fetchWithAuth';
|
||||
|
||||
// Función helper para manejar respuestas
|
||||
|
||||
async function handleResponse(response, operation = 'operación') {
|
||||
if (response.status === 401) {
|
||||
@@ -15,57 +18,27 @@ async function handleResponse(response, operation = 'operación') {
|
||||
return response.json();
|
||||
}
|
||||
|
||||
export async function fetchUsers(token) {
|
||||
export async function fetchUsers() {
|
||||
const url = `${API_URL}/user/users/`;
|
||||
const res = await fetch(url, {
|
||||
headers: {
|
||||
'Authorization': `Bearer ${token}`,
|
||||
'Content-Type': 'application/json',
|
||||
'Accept': 'application/json',
|
||||
},
|
||||
});
|
||||
const res = await fetchWithAuth(url);
|
||||
return handleResponse(res, 'Fetch Users');
|
||||
}
|
||||
|
||||
export async function createUser(token, userData) {
|
||||
export async function createUser(userData) {
|
||||
const url = `${API_URL}/user/users/`;
|
||||
const res = await fetch(url, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Authorization': `Bearer ${token}`,
|
||||
'Content-Type': 'application/json',
|
||||
'Accept': 'application/json',
|
||||
},
|
||||
body: JSON.stringify(userData),
|
||||
});
|
||||
const res = await postWithAuth(url, userData);
|
||||
return handleResponse(res, 'Create User');
|
||||
}
|
||||
|
||||
export async function updateUser(token, id, userData) {
|
||||
export async function updateUser(id, userData) {
|
||||
const url = `${API_URL}/user/users/${id}/`;
|
||||
const res = await fetch(url, {
|
||||
method: 'PUT',
|
||||
headers: {
|
||||
'Authorization': `Bearer ${token}`,
|
||||
'Content-Type': 'application/json',
|
||||
'Accept': 'application/json',
|
||||
},
|
||||
body: JSON.stringify(userData),
|
||||
});
|
||||
const res = await putWithAuth(url, userData);
|
||||
return handleResponse(res, 'Update User');
|
||||
}
|
||||
|
||||
export async function deleteUser(token, id) {
|
||||
export async function deleteUser(id) {
|
||||
const url = `${API_URL}/user/users/${id}/`;
|
||||
const res = await fetch(url, {
|
||||
method: 'DELETE',
|
||||
headers: {
|
||||
'Authorization': `Bearer ${token}`,
|
||||
'Content-Type': 'application/json',
|
||||
'Accept': 'application/json',
|
||||
},
|
||||
});
|
||||
if (res.status === 401) throw new Error('SESSION_EXPIRED');
|
||||
const res = await deleteWithAuth(url);
|
||||
if (!res.ok) throw new Error(`Error ${res.status}: ${res.statusText}`);
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
const API_URL = import.meta.env.VITE_EFC_API_URL || 'http://localhost:8000';
|
||||
import { fetchWithAuth, postWithAuth, putWithAuth, deleteWithAuth } from '../fetchWithAuth';
|
||||
|
||||
// Función helper para manejar respuestas
|
||||
async function handleResponse(response, operation = 'operación') {
|
||||
@@ -26,18 +27,12 @@ async function handleResponse(response, operation = 'operación') {
|
||||
return response.json();
|
||||
}
|
||||
|
||||
export async function fetchUsers(token) {
|
||||
export async function fetchUsers() {
|
||||
try {
|
||||
const url = `${API_URL}/user/users/`;
|
||||
console.log('👥 Fetching users from:', url);
|
||||
|
||||
const res = await fetch(url, {
|
||||
headers: {
|
||||
'Authorization': `Bearer ${token}`,
|
||||
'Content-Type': 'application/json',
|
||||
'Accept': 'application/json',
|
||||
},
|
||||
});
|
||||
const res = await fetchWithAuth(url);
|
||||
|
||||
const data = await handleResponse(res, 'Fetch Users');
|
||||
console.log('✅ Users data received');
|
||||
@@ -52,20 +47,12 @@ export async function fetchUsers(token) {
|
||||
}
|
||||
}
|
||||
|
||||
export async function createUser(token, userData) {
|
||||
export async function createUser(userData) {
|
||||
try {
|
||||
const url = `${API_URL}/user/users/`;
|
||||
console.log('➕ Creating user at:', url);
|
||||
|
||||
const res = await fetch(url, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Authorization': `Bearer ${token}`,
|
||||
'Content-Type': 'application/json',
|
||||
'Accept': 'application/json',
|
||||
},
|
||||
body: JSON.stringify(userData),
|
||||
});
|
||||
const res = await postWithAuth(url, userData);
|
||||
|
||||
const data = await handleResponse(res, 'Create User');
|
||||
console.log('✅ User created successfully');
|
||||
@@ -80,20 +67,12 @@ export async function createUser(token, userData) {
|
||||
}
|
||||
}
|
||||
|
||||
export async function updateUser(token, id, userData) {
|
||||
export async function updateUser(id, userData) {
|
||||
try {
|
||||
const url = `${API_URL}/user/users/${id}/`;
|
||||
console.log('✏️ Updating user at:', url);
|
||||
|
||||
const res = await fetch(url, {
|
||||
method: 'PUT',
|
||||
headers: {
|
||||
'Authorization': `Bearer ${token}`,
|
||||
'Content-Type': 'application/json',
|
||||
'Accept': 'application/json',
|
||||
},
|
||||
body: JSON.stringify(userData),
|
||||
});
|
||||
const res = await putWithAuth(url, userData);
|
||||
|
||||
const data = await handleResponse(res, 'Update User');
|
||||
console.log('✅ User updated successfully');
|
||||
@@ -108,19 +87,12 @@ export async function updateUser(token, id, userData) {
|
||||
}
|
||||
}
|
||||
|
||||
export async function deleteUser(token, id) {
|
||||
export async function deleteUser(id) {
|
||||
try {
|
||||
const url = `${API_URL}/user/users/${id}/`;
|
||||
console.log('🗑️ Deleting user at:', url);
|
||||
|
||||
const res = await fetch(url, {
|
||||
method: 'DELETE',
|
||||
headers: {
|
||||
'Authorization': `Bearer ${token}`,
|
||||
'Content-Type': 'application/json',
|
||||
'Accept': 'application/json',
|
||||
},
|
||||
});
|
||||
const res = await deleteWithAuth(url);
|
||||
|
||||
if (res.status === 401) {
|
||||
console.error('❌ Unauthorized - session expired');
|
||||
@@ -145,18 +117,12 @@ export async function deleteUser(token, id) {
|
||||
}
|
||||
}
|
||||
|
||||
export async function getCurrentUser(token) {
|
||||
export async function getCurrentUser() {
|
||||
try {
|
||||
const url = `${API_URL}/user/users/me/`;
|
||||
console.log('👤 Fetching current user from:', url);
|
||||
|
||||
const res = await fetch(url, {
|
||||
headers: {
|
||||
'Authorization': `Bearer ${token}`,
|
||||
'Content-Type': 'application/json',
|
||||
'Accept': 'application/json',
|
||||
},
|
||||
});
|
||||
const res = await fetchWithAuth(url);
|
||||
|
||||
const data = await handleResponse(res, 'Get Current User');
|
||||
console.log('✅ Current user data received:', data);
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import React, { createContext, useContext, useEffect, useState, useRef } from 'react';
|
||||
import { getCurrentUser } from '../api/users.ts';
|
||||
import { refreshToken } from '../api/auth.js';
|
||||
import { fetchWithAuth } from '../fetchWithAuth';
|
||||
|
||||
const UserContext = createContext({
|
||||
user: null,
|
||||
@@ -19,51 +18,28 @@ export function UserProvider({ children }) {
|
||||
if (fetchedOnce.current && loading) return;
|
||||
setLoading(true);
|
||||
setError(null);
|
||||
let token = localStorage.getItem('access');
|
||||
let triedRefresh = false;
|
||||
while (true) {
|
||||
try {
|
||||
if (token) {
|
||||
const userData = await getCurrentUser(token);
|
||||
|
||||
try {
|
||||
const token = localStorage.getItem('access');
|
||||
if (token) {
|
||||
const API_URL = import.meta.env.VITE_EFC_API_URL;
|
||||
const response = await fetchWithAuth(`${API_URL}/user/users/me/`);
|
||||
|
||||
if (response.ok) {
|
||||
const userData = await response.json();
|
||||
setUser(userData);
|
||||
} else {
|
||||
setUser(null);
|
||||
}
|
||||
break;
|
||||
} catch (err) {
|
||||
// Si el token expiró, intenta refrescarlo una vez
|
||||
if (!triedRefresh && (err.message === 'SESSION_EXPIRED' || err.message.includes('401'))) {
|
||||
triedRefresh = true;
|
||||
const refresh = localStorage.getItem('refresh');
|
||||
if (refresh) {
|
||||
try {
|
||||
const data = await refreshToken(refresh);
|
||||
if (data.access) {
|
||||
localStorage.setItem('access', data.access);
|
||||
token = data.access;
|
||||
continue; // Reintenta con el nuevo token
|
||||
} else {
|
||||
throw new Error('No se pudo refrescar el token');
|
||||
}
|
||||
} catch (refreshErr) {
|
||||
setError(refreshErr);
|
||||
setUser(null);
|
||||
localStorage.removeItem('access');
|
||||
localStorage.removeItem('refresh');
|
||||
window.dispatchEvent(new CustomEvent('authStateChanged'));
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
setUser(null);
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
setError(err);
|
||||
setUser(null);
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
setUser(null);
|
||||
}
|
||||
} catch (err) {
|
||||
console.error('Error fetching user:', err);
|
||||
setError(err);
|
||||
setUser(null);
|
||||
}
|
||||
|
||||
setLoading(false);
|
||||
fetchedOnce.current = true;
|
||||
};
|
||||
|
||||
@@ -0,0 +1,226 @@
|
||||
const API_URL = import.meta.env.VITE_EFC_API_URL;
|
||||
|
||||
// Variable para controlar si ya hay una renovación de token en proceso
|
||||
let isRefreshing = false;
|
||||
let failedQueue = [];
|
||||
|
||||
// Función para procesar la cola de peticiones fallidas después de renovar el token
|
||||
const processQueue = (error, token = null) => {
|
||||
failedQueue.forEach(prom => {
|
||||
if (error) {
|
||||
prom.reject(error);
|
||||
} else {
|
||||
prom.resolve(token);
|
||||
}
|
||||
});
|
||||
|
||||
failedQueue = [];
|
||||
};
|
||||
|
||||
// Función para renovar el token usando el refresh token
|
||||
const refreshToken = async () => {
|
||||
try {
|
||||
const refresh = localStorage.getItem('refresh');
|
||||
if (!refresh) {
|
||||
throw new Error('No refresh token available');
|
||||
}
|
||||
|
||||
const response = await fetch(`${API_URL}/auth/token/refresh/`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify({
|
||||
refresh: refresh
|
||||
}),
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error('Failed to refresh token');
|
||||
}
|
||||
|
||||
const data = await response.json();
|
||||
|
||||
// Guardar el nuevo access token
|
||||
localStorage.setItem('access', data.access);
|
||||
|
||||
// Si viene un nuevo refresh token, guardarlo también
|
||||
if (data.refresh) {
|
||||
localStorage.setItem('refresh', data.refresh);
|
||||
}
|
||||
|
||||
return data.access;
|
||||
} catch (error) {
|
||||
// Si falla la renovación, limpiar tokens y redirigir al login
|
||||
localStorage.removeItem('access');
|
||||
localStorage.removeItem('refresh');
|
||||
localStorage.removeItem('user_id');
|
||||
localStorage.removeItem('user_is_importador');
|
||||
|
||||
// Redirigir al login después de un pequeño delay
|
||||
setTimeout(() => {
|
||||
window.location.href = '/login';
|
||||
}, 1000);
|
||||
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
|
||||
// Función principal para hacer peticiones con manejo automático de tokens
|
||||
export const fetchWithAuth = async (url, options = {}) => {
|
||||
// Obtener el token actual
|
||||
let token = localStorage.getItem('access');
|
||||
|
||||
// Configurar headers por defecto
|
||||
const defaultHeaders = {
|
||||
'Content-Type': 'application/json',
|
||||
...(token && { 'Authorization': `Bearer ${token}` })
|
||||
};
|
||||
|
||||
// Combinar headers
|
||||
const finalOptions = {
|
||||
...options,
|
||||
headers: {
|
||||
...defaultHeaders,
|
||||
...options.headers
|
||||
}
|
||||
};
|
||||
|
||||
try {
|
||||
// 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;
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
// Si todavía hay un 401 después del intento de renovación, redirigir al login
|
||||
if (response.status === 401) {
|
||||
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');
|
||||
}
|
||||
|
||||
return response;
|
||||
|
||||
} catch (error) {
|
||||
// Si hay un error de red o cualquier otro error, propagarlo
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
|
||||
// Función auxiliar para hacer peticiones GET con manejo de tokens
|
||||
export const getWithAuth = async (url) => {
|
||||
return fetchWithAuth(url, { method: 'GET' });
|
||||
};
|
||||
|
||||
// Función auxiliar para hacer peticiones POST con manejo de tokens
|
||||
export const postWithAuth = async (url, data) => {
|
||||
return fetchWithAuth(url, {
|
||||
method: 'POST',
|
||||
body: JSON.stringify(data)
|
||||
});
|
||||
};
|
||||
|
||||
// Función auxiliar para hacer peticiones PUT con manejo de tokens
|
||||
export const putWithAuth = async (url, data) => {
|
||||
return fetchWithAuth(url, {
|
||||
method: 'PUT',
|
||||
body: JSON.stringify(data)
|
||||
});
|
||||
};
|
||||
|
||||
// Función auxiliar para hacer peticiones PATCH con manejo de tokens
|
||||
export const patchWithAuth = async (url, data) => {
|
||||
return fetchWithAuth(url, {
|
||||
method: 'PATCH',
|
||||
body: JSON.stringify(data)
|
||||
});
|
||||
};
|
||||
|
||||
// Función auxiliar para hacer peticiones DELETE con manejo de tokens
|
||||
export const deleteWithAuth = async (url) => {
|
||||
return fetchWithAuth(url, { method: 'DELETE' });
|
||||
};
|
||||
|
||||
// Función para hacer peticiones con FormData (para archivos)
|
||||
export const postFormDataWithAuth = async (url, formData) => {
|
||||
let token = localStorage.getItem('access');
|
||||
|
||||
const options = {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
...(token && { 'Authorization': `Bearer ${token}` })
|
||||
},
|
||||
body: formData
|
||||
};
|
||||
|
||||
try {
|
||||
let response = await fetch(url, options);
|
||||
|
||||
if (response.status === 401 && !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;
|
||||
}
|
||||
}
|
||||
|
||||
if (response.status === 401) {
|
||||
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');
|
||||
}
|
||||
|
||||
return response;
|
||||
|
||||
} catch (error) {
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
|
||||
export default fetchWithAuth;
|
||||
|
||||
12
src/main.jsx
12
src/main.jsx
@@ -10,10 +10,18 @@ import '@fontsource/roboto/500.css';
|
||||
import '@fontsource/roboto/700.css';
|
||||
|
||||
|
||||
const isDevelopment = import.meta.env.DEV;
|
||||
|
||||
createRoot(document.getElementById('root')).render(
|
||||
<StrictMode>
|
||||
isDevelopment ? (
|
||||
<NotificationProvider>
|
||||
<App />
|
||||
</NotificationProvider>
|
||||
</StrictMode>,
|
||||
) : (
|
||||
<StrictMode>
|
||||
<NotificationProvider>
|
||||
<App />
|
||||
</NotificationProvider>
|
||||
</StrictMode>
|
||||
),
|
||||
)
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import { fetchWithAuth } from '../fetchWithAuth';
|
||||
// Animación fade-in/slide-up para cards
|
||||
const fadeInSlideUp = `@keyframes fadein-slideup {
|
||||
0% { opacity: 0; transform: translateY(40px); }
|
||||
@@ -45,33 +46,31 @@ export default function Admin() {
|
||||
setLoading(true);
|
||||
setError('');
|
||||
try {
|
||||
const token = localStorage.getItem('access');
|
||||
const headers = token ? { 'Authorization': `Bearer ${token}` } : {};
|
||||
|
||||
// Servicios
|
||||
const resServices = await fetch(`${API_URL}/cards/services-util-information/`, { headers });
|
||||
const resServices = await fetchWithAuth(`${API_URL}/cards/services-util-information/`);
|
||||
if (!resServices.ok) throw new Error('Error al obtener estados de servicios');
|
||||
const dataServices = await resServices.json();
|
||||
setServices(dataServices);
|
||||
|
||||
// Descargas
|
||||
const resDownloads = await fetch(`${API_URL}/cards/document-util-information/`, { headers });
|
||||
const resDownloads = await fetchWithAuth(`${API_URL}/cards/document-util-information/`);
|
||||
if (!resDownloads.ok) throw new Error('Error al obtener información de descargas');
|
||||
const dataDownloads = await resDownloads.json();
|
||||
setDownloads(dataDownloads);
|
||||
|
||||
// Últimos documentos
|
||||
const resDocs = await fetch(`${API_URL}/cards/downloaded-documents/`, { headers });
|
||||
const resDocs = await fetchWithAuth(`${API_URL}/cards/downloaded-documents/`);
|
||||
if (!resDocs.ok) throw new Error('Error al obtener últimos documentos');
|
||||
const dataDocs = await resDocs.json();
|
||||
setLatestDocs(dataDocs.documentos);
|
||||
|
||||
// Análisis de actividad de usuario
|
||||
const resUserActivity = await fetch(`${API_URL}/cards/user-activity-analysis/`, { headers });
|
||||
const resUserActivity = await fetchWithAuth(`${API_URL}/cards/user-activity-analysis/`);
|
||||
if (!resUserActivity.ok) throw new Error('Error al obtener análisis de actividad de usuario');
|
||||
const dataUserActivity = await resUserActivity.json();
|
||||
setUserActivity(dataUserActivity);
|
||||
} catch (err) {
|
||||
console.error('Error fetching admin data:', err);
|
||||
setError(err instanceof Error ? err.message : String(err));
|
||||
} finally {
|
||||
setLoading(false);
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import React, { useEffect, useState, useLayoutEffect, useRef } from 'react';
|
||||
import SuccessModal from '../components/SuccessModal.jsx';
|
||||
import { fetchWithAuth, postWithAuth } from '../fetchWithAuth';
|
||||
// Animación fade-in/slide-up para bloques
|
||||
const fadeInSlideUp = `@keyframes fadein-slideup { 0% { opacity: 0; transform: translateY(40px); } 100% { opacity: 1; transform: translateY(0); } }`;
|
||||
if (typeof document !== 'undefined' && !document.getElementById('fadein-slideup-documents')) {
|
||||
@@ -17,35 +18,28 @@ const API_URL = import.meta.env.VITE_EFC_API_URL;
|
||||
|
||||
// Descarga individual
|
||||
const downloadFile = async (id, filename = 'archivo', setSuccess, setError, showMessage) => {
|
||||
const token = localStorage.getItem('access');
|
||||
const res = await fetch(`${API_URL}/record/documents/descargar/${id}/`, {
|
||||
headers: {
|
||||
'Authorization': `Bearer ${token}`,
|
||||
},
|
||||
});
|
||||
if (res.status === 401) {
|
||||
showMessage('Tu sesión ha expirado, por favor inicia sesión de nuevo.', 'error');
|
||||
localStorage.removeItem('access');
|
||||
localStorage.removeItem('refresh');
|
||||
setTimeout(() => {
|
||||
window.location.href = '/login';
|
||||
}, 2000);
|
||||
return;
|
||||
try {
|
||||
const res = await fetchWithAuth(`${API_URL}/record/documents/descargar/${id}/`);
|
||||
|
||||
if (!res.ok) {
|
||||
alert('No autorizado o error en la descarga');
|
||||
return;
|
||||
}
|
||||
|
||||
const blob = await res.blob();
|
||||
const url = window.URL.createObjectURL(blob);
|
||||
const a = document.createElement('a');
|
||||
a.href = url;
|
||||
a.download = filename;
|
||||
document.body.appendChild(a);
|
||||
a.click();
|
||||
a.remove();
|
||||
window.URL.revokeObjectURL(url);
|
||||
if (setSuccess) setSuccess('Descarga exitosa');
|
||||
} catch (error) {
|
||||
console.error('Error downloading file:', error);
|
||||
showMessage('Error al descargar el archivo', 'error');
|
||||
}
|
||||
if (!res.ok) {
|
||||
alert('No autorizado o error en la descarga');
|
||||
return;
|
||||
}
|
||||
const blob = await res.blob();
|
||||
const url = window.URL.createObjectURL(blob);
|
||||
const a = document.createElement('a');
|
||||
a.href = url;
|
||||
a.download = filename;
|
||||
document.body.appendChild(a);
|
||||
a.click();
|
||||
a.remove();
|
||||
window.URL.revokeObjectURL(url);
|
||||
if (setSuccess) setSuccess('Descarga exitosa');
|
||||
};
|
||||
|
||||
// Descarga masiva (bulk)
|
||||
@@ -54,38 +48,32 @@ const downloadBulkZip = async (ids, showMessage, setSuccess, nombreZip = 'docume
|
||||
showMessage('Selecciona al menos un documento.', 'error');
|
||||
return;
|
||||
}
|
||||
const token = localStorage.getItem('access');
|
||||
const res = await fetch(`${API_URL}/record/documents/bulk-download/`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Authorization': `Bearer ${token}`,
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify({ document_ids: ids, pedimento_nombre: nombreZip }),
|
||||
});
|
||||
if (res.status === 401) {
|
||||
showMessage('Tu sesión ha expirado, por favor inicia sesión de nuevo.', 'error');
|
||||
localStorage.removeItem('access');
|
||||
localStorage.removeItem('refresh');
|
||||
setTimeout(() => {
|
||||
window.location.href = '/login';
|
||||
}, 2000);
|
||||
return;
|
||||
|
||||
try {
|
||||
const res = await postWithAuth(`${API_URL}/record/documents/bulk-download/`, {
|
||||
document_ids: ids,
|
||||
pedimento_nombre: nombreZip
|
||||
});
|
||||
|
||||
if (!res.ok) {
|
||||
showMessage('No autorizado o error en la descarga masiva', 'error');
|
||||
return;
|
||||
}
|
||||
|
||||
const blob = await res.blob();
|
||||
const url = window.URL.createObjectURL(blob);
|
||||
const a = document.createElement('a');
|
||||
a.href = url;
|
||||
a.download = `${nombreZip || 'documentos'}.zip`;
|
||||
document.body.appendChild(a);
|
||||
a.click();
|
||||
a.remove();
|
||||
window.URL.revokeObjectURL(url);
|
||||
if (setSuccess) setSuccess('Descarga(s) completada(s)');
|
||||
} catch (error) {
|
||||
console.error('Error in bulk download:', error);
|
||||
showMessage('Error en la descarga masiva', 'error');
|
||||
}
|
||||
if (!res.ok) {
|
||||
showMessage('No autorizado o error en la descarga masiva', 'error');
|
||||
return;
|
||||
}
|
||||
const blob = await res.blob();
|
||||
const url = window.URL.createObjectURL(blob);
|
||||
const a = document.createElement('a');
|
||||
a.href = url;
|
||||
a.download = `${nombreZip || 'documentos'}.zip`;
|
||||
document.body.appendChild(a);
|
||||
a.click();
|
||||
a.remove();
|
||||
window.URL.revokeObjectURL(url);
|
||||
if (setSuccess) setSuccess('Descarga(s) completada(s)');
|
||||
};
|
||||
|
||||
export default function Documents() {
|
||||
@@ -128,8 +116,7 @@ export default function Documents() {
|
||||
setLoading(true);
|
||||
setError(null);
|
||||
try {
|
||||
const token = localStorage.getItem('access');
|
||||
const data = await fetchPedimentoDocuments(token, '', currentPage, itemsPerPage, {
|
||||
const data = await fetchPedimentoDocuments(currentPage, itemsPerPage, {
|
||||
pedimento_numero: pedimentoNumeroFilter,
|
||||
extension: extensionFilter,
|
||||
document_type: documentTypeFilter,
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import React, { useEffect, useState, useLayoutEffect, useRef } from 'react';
|
||||
import { fetchWithAuth, postWithAuth } from '../fetchWithAuth';
|
||||
// Animación fade-in/slide-up para bloques
|
||||
const fadeInSlideUp = `@keyframes fadein-slideup { 0% { opacity: 0; transform: translateY(40px); } 100% { opacity: 1; transform: translateY(0); } }`;
|
||||
if (typeof document !== 'undefined' && !document.getElementById('fadein-slideup-documents')) {
|
||||
@@ -15,35 +16,28 @@ import { Link } from 'react-router-dom';
|
||||
const API_URL = import.meta.env.VITE_EFC_API_URL;
|
||||
|
||||
const downloadFile = async (id, filename = 'archivo', setSuccess, setError, showMessage) => {
|
||||
const token = localStorage.getItem('access');
|
||||
const res = await fetch(`${API_URL}/record/documents/descargar/${id}/`, {
|
||||
headers: {
|
||||
'Authorization': `Bearer ${token}`,
|
||||
},
|
||||
});
|
||||
if (res.status === 401) {
|
||||
showMessage('Tu sesión ha expirado, por favor inicia sesión de nuevo.', 'error');
|
||||
localStorage.removeItem('access');
|
||||
localStorage.removeItem('refresh');
|
||||
setTimeout(() => {
|
||||
window.location.href = '/login';
|
||||
}, 2000);
|
||||
return;
|
||||
try {
|
||||
const res = await fetchWithAuth(`${API_URL}/record/documents/descargar/${id}/`);
|
||||
|
||||
if (!res.ok) {
|
||||
alert('No autorizado o error en la descarga');
|
||||
return;
|
||||
}
|
||||
|
||||
const blob = await res.blob();
|
||||
const url = window.URL.createObjectURL(blob);
|
||||
const a = document.createElement('a');
|
||||
a.href = url;
|
||||
a.download = filename;
|
||||
document.body.appendChild(a);
|
||||
a.click();
|
||||
a.remove();
|
||||
window.URL.revokeObjectURL(url);
|
||||
if (setSuccess) setSuccess('Descarga exitosa');
|
||||
} catch (error) {
|
||||
console.error('Error downloading file:', error);
|
||||
showMessage('Error al descargar el archivo', 'error');
|
||||
}
|
||||
if (!res.ok) {
|
||||
alert('No autorizado o error en la descarga');
|
||||
return;
|
||||
}
|
||||
const blob = await res.blob();
|
||||
const url = window.URL.createObjectURL(blob);
|
||||
const a = document.createElement('a');
|
||||
a.href = url;
|
||||
a.download = filename;
|
||||
document.body.appendChild(a);
|
||||
a.click();
|
||||
a.remove();
|
||||
window.URL.revokeObjectURL(url);
|
||||
if (setSuccess) setSuccess('Descarga exitosa');useEffect
|
||||
};
|
||||
|
||||
export default function Documents() {
|
||||
@@ -83,7 +77,6 @@ export default function Documents() {
|
||||
|
||||
// Fetching usando la función tipada de TypeScript
|
||||
const fetchPedimentosData = async (page = currentPage, pageSize = itemsPerPage) => {
|
||||
const token = localStorage.getItem('access');
|
||||
// Construir objeto de filtros
|
||||
const filters = {
|
||||
search: searchFilter || undefined,
|
||||
@@ -98,7 +91,7 @@ export default function Documents() {
|
||||
tipo_operacion: tipoOperacionFilter || undefined,
|
||||
clave_pedimento: clavePedimentoFilter || undefined,
|
||||
};
|
||||
return await fetchDocuments(token, page, pageSize, filters);
|
||||
return await fetchDocuments(page, pageSize, filters);
|
||||
};
|
||||
|
||||
// Hook de polling que se ejecuta cada 30 segundos
|
||||
|
||||
@@ -12,28 +12,14 @@ export default function Organization() {
|
||||
const [animatedPercent, setAnimatedPercent] = useState(0);
|
||||
|
||||
useEffect(() => {
|
||||
const token = localStorage.getItem('access');
|
||||
if (!token) {
|
||||
setError('No se encontró el token de acceso.');
|
||||
setLoading(false);
|
||||
return;
|
||||
}
|
||||
fetchOrganizationUsage(token)
|
||||
fetchOrganizationUsage()
|
||||
.then(data => {
|
||||
setInfo(data);
|
||||
setLoading(false);
|
||||
})
|
||||
.catch(err => {
|
||||
if (err.message === 'SESSION_EXPIRED') {
|
||||
localStorage.removeItem('access');
|
||||
localStorage.removeItem('refresh');
|
||||
showMessage('Tu sesión ha expirado, por favor inicia sesión de nuevo.', 'error');
|
||||
setTimeout(() => {
|
||||
window.location.href = '/login';
|
||||
}, 2000);
|
||||
} else {
|
||||
setError(err.message);
|
||||
}
|
||||
console.error('Error fetching organization data:', err);
|
||||
setError('Error al cargar la información de la organización.');
|
||||
setLoading(false);
|
||||
});
|
||||
}, [showMessage]);
|
||||
|
||||
@@ -16,40 +16,38 @@ import 'highlight.js/styles/github.css';
|
||||
hljs.registerLanguage('xml', xml);
|
||||
// import type removed for JSX compatibility
|
||||
import { fetchPedimentoDocuments } from '../api/pedimentoDocuments';
|
||||
import { fetchWithAuth, postWithAuth } from '../fetchWithAuth';
|
||||
import { useParams, Link } from 'react-router-dom';
|
||||
import { useNotification } from '../context/NotificationContext';
|
||||
|
||||
const API_URL = import.meta.env.VITE_EFC_API_URL;
|
||||
|
||||
const downloadFile = async (id, filename = 'archivo', showMessage) => {
|
||||
const token = localStorage.getItem('access');
|
||||
const res = await fetch(`${API_URL}/record/documents/descargar/${id}/`, {
|
||||
headers: {
|
||||
'Authorization': `Bearer ${token}`,
|
||||
},
|
||||
});
|
||||
if (res.status === 401) {
|
||||
showMessage('Tu sesión ha expirado, por favor inicia sesión de nuevo.', 'error');
|
||||
localStorage.removeItem('access');
|
||||
localStorage.removeItem('refresh');
|
||||
setTimeout(() => {
|
||||
window.location.href = '/login';
|
||||
}, 2000);
|
||||
return;
|
||||
try {
|
||||
const res = await fetchWithAuth(`${API_URL}/record/documents/descargar/${id}/`);
|
||||
|
||||
if (!res.ok) {
|
||||
showMessage('Error en la descarga del archivo', 'error');
|
||||
return;
|
||||
}
|
||||
|
||||
const blob = await res.blob();
|
||||
const url = window.URL.createObjectURL(blob);
|
||||
const a = document.createElement('a');
|
||||
a.href = url;
|
||||
a.download = filename;
|
||||
document.body.appendChild(a);
|
||||
a.click();
|
||||
a.remove();
|
||||
window.URL.revokeObjectURL(url);
|
||||
} catch (error) {
|
||||
console.error('Error downloading file:', error);
|
||||
if (error.message === 'SESSION_EXPIRED') {
|
||||
showMessage('Tu sesión ha expirado, por favor inicia sesión de nuevo.', 'error');
|
||||
} else {
|
||||
showMessage('Error al descargar el archivo', 'error');
|
||||
}
|
||||
}
|
||||
if (!res.ok) {
|
||||
alert('No autorizado o error en la descarga');
|
||||
return;
|
||||
}
|
||||
const blob = await res.blob();
|
||||
const url = window.URL.createObjectURL(blob);
|
||||
const a = document.createElement('a');
|
||||
a.href = url;
|
||||
a.download = filename;
|
||||
document.body.appendChild(a);
|
||||
a.click();
|
||||
a.remove();
|
||||
window.URL.revokeObjectURL(url);
|
||||
};
|
||||
|
||||
const downloadBulkZip = async (ids, showMessage, pedimentoNombre) => {
|
||||
@@ -57,37 +55,35 @@ const downloadBulkZip = async (ids, showMessage, pedimentoNombre) => {
|
||||
showMessage('Selecciona al menos un documento.', 'error');
|
||||
return;
|
||||
}
|
||||
const token = localStorage.getItem('access');
|
||||
const res = await fetch(`${API_URL}/record/documents/bulk-download/`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Authorization': `Bearer ${token}`,
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify({ document_ids: ids, pedimento_nombre: pedimentoNombre }),
|
||||
});
|
||||
if (res.status === 401) {
|
||||
showMessage('Tu sesión ha expirado, por favor inicia sesión de nuevo.', 'error');
|
||||
localStorage.removeItem('access');
|
||||
localStorage.removeItem('refresh');
|
||||
setTimeout(() => {
|
||||
window.location.href = '/login';
|
||||
}, 2000);
|
||||
return;
|
||||
|
||||
try {
|
||||
const res = await postWithAuth(`${API_URL}/record/documents/bulk-download/`, {
|
||||
document_ids: ids,
|
||||
pedimento_nombre: pedimentoNombre
|
||||
});
|
||||
|
||||
if (!res.ok) {
|
||||
showMessage('Error en la descarga masiva', 'error');
|
||||
return;
|
||||
}
|
||||
|
||||
const blob = await res.blob();
|
||||
const url = window.URL.createObjectURL(blob);
|
||||
const a = document.createElement('a');
|
||||
a.href = url;
|
||||
a.download = `${pedimentoNombre || 'documentos'}.zip`;
|
||||
document.body.appendChild(a);
|
||||
a.click();
|
||||
a.remove();
|
||||
window.URL.revokeObjectURL(url);
|
||||
} catch (error) {
|
||||
console.error('Error in bulk download:', error);
|
||||
if (error.message === 'SESSION_EXPIRED') {
|
||||
showMessage('Tu sesión ha expirado, por favor inicia sesión de nuevo.', 'error');
|
||||
} else {
|
||||
showMessage('Error en la descarga masiva', 'error');
|
||||
}
|
||||
}
|
||||
if (!res.ok) {
|
||||
showMessage('No autorizado o error en la descarga masiva', 'error');
|
||||
return;
|
||||
}
|
||||
const blob = await res.blob();
|
||||
const url = window.URL.createObjectURL(blob);
|
||||
const a = document.createElement('a');
|
||||
a.href = url;
|
||||
a.download = `${pedimentoNombre || 'documentos'}.zip`;
|
||||
document.body.appendChild(a);
|
||||
a.click();
|
||||
a.remove();
|
||||
window.URL.revokeObjectURL(url);
|
||||
};
|
||||
|
||||
import { useRef, useLayoutEffect } from 'react';
|
||||
@@ -176,23 +172,8 @@ const [docsPrev, setDocsPrev] = useState(null);
|
||||
const { showMessage } = useNotification();
|
||||
|
||||
useEffect(() => {
|
||||
const token = localStorage.getItem('access');
|
||||
fetch(`${API_URL}/customs/pedimentos/${id}/`, {
|
||||
headers: {
|
||||
'Authorization': `Bearer ${token}`,
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
})
|
||||
fetchWithAuth(`${API_URL}/customs/pedimentos/${id}/`)
|
||||
.then(res => {
|
||||
if (res.status === 401) {
|
||||
localStorage.removeItem('access');
|
||||
localStorage.removeItem('refresh');
|
||||
showMessage('Tu sesión ha expirado, por favor inicia sesión de nuevo.', 'error');
|
||||
setTimeout(() => {
|
||||
window.location.href = '/login';
|
||||
}, 2000);
|
||||
return null;
|
||||
}
|
||||
if (!res.ok) throw new Error('No autorizado o error en la petición');
|
||||
return res.json();
|
||||
})
|
||||
@@ -201,7 +182,12 @@ const [docsPrev, setDocsPrev] = useState(null);
|
||||
setLoading(false);
|
||||
})
|
||||
.catch(err => {
|
||||
setError(err.message);
|
||||
console.error('Error fetching pedimento:', err);
|
||||
if (err.message === 'SESSION_EXPIRED') {
|
||||
showMessage('Tu sesión ha expirado, por favor inicia sesión de nuevo.', 'error');
|
||||
} else {
|
||||
setError(err.message);
|
||||
}
|
||||
setLoading(false);
|
||||
});
|
||||
}, [id, showMessage]);
|
||||
@@ -209,10 +195,9 @@ const [docsPrev, setDocsPrev] = useState(null);
|
||||
// Fetch paginated documents
|
||||
useEffect(() => {
|
||||
if (!id) return;
|
||||
const token = localStorage.getItem('access');
|
||||
setDocsLoading(true);
|
||||
setDocsError('');
|
||||
fetchPedimentoDocuments(token, id, page, pageSize)
|
||||
fetchPedimentoDocuments(id, page, pageSize)
|
||||
.then((data) => {
|
||||
setDocuments(data.results);
|
||||
setDocsCount(data.count);
|
||||
@@ -221,13 +206,9 @@ const [docsPrev, setDocsPrev] = useState(null);
|
||||
setDocsLoading(false);
|
||||
})
|
||||
.catch(err => {
|
||||
console.error('Error fetching documents:', err);
|
||||
if (err.message === 'SESSION_EXPIRED') {
|
||||
localStorage.removeItem('access');
|
||||
localStorage.removeItem('refresh');
|
||||
showMessage('Tu sesión ha expirado, por favor inicia sesión de nuevo.', 'error');
|
||||
setTimeout(() => {
|
||||
window.location.href = '/login';
|
||||
}, 2000);
|
||||
} else {
|
||||
setDocsError(err.message);
|
||||
}
|
||||
@@ -288,22 +269,14 @@ const [docsPrev, setDocsPrev] = useState(null);
|
||||
setPreviewXml('');
|
||||
setPreviewOpen(true);
|
||||
try {
|
||||
const token = localStorage.getItem('access');
|
||||
const res = await fetch(`${API_URL}/record/documents/descargar/${doc.id}/`, {
|
||||
headers: {
|
||||
'Authorization': `Bearer ${token}`,
|
||||
},
|
||||
});
|
||||
if (res.status === 401) {
|
||||
setPreviewError('Tu sesión ha expirado, por favor inicia sesión de nuevo.');
|
||||
setPreviewLoading(false);
|
||||
return;
|
||||
}
|
||||
const res = await fetchWithAuth(`${API_URL}/record/documents/descargar/${doc.id}/`);
|
||||
|
||||
if (!res.ok) {
|
||||
setPreviewError('No autorizado o error en la descarga');
|
||||
setPreviewError('Error al obtener el archivo');
|
||||
setPreviewLoading(false);
|
||||
return;
|
||||
}
|
||||
|
||||
// Detectar tipo de archivo
|
||||
let type = '';
|
||||
if (doc.extension) {
|
||||
@@ -332,7 +305,12 @@ const [docsPrev, setDocsPrev] = useState(null);
|
||||
setPreviewLoading(false);
|
||||
}
|
||||
} catch (err) {
|
||||
setPreviewError('Error al obtener el archivo');
|
||||
console.error('Error in preview:', err);
|
||||
if (err.message === 'SESSION_EXPIRED') {
|
||||
setPreviewError('Tu sesión ha expirado, por favor inicia sesión de nuevo.');
|
||||
} else {
|
||||
setPreviewError('Error al obtener el archivo');
|
||||
}
|
||||
setPreviewLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import { fetchProcesamientoPedimentos } from '../api/procesos.ts';
|
||||
import { postWithAuth } from '../fetchWithAuth';
|
||||
const API_URL = import.meta.env.VITE_EFC_API_URL;
|
||||
const MICROSERVICE_URL = import.meta.env.VITE_EFC_MICROSERVICE_URL;
|
||||
|
||||
@@ -62,31 +63,26 @@ export default function Procesos() {
|
||||
setExecutingId(null);
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const token = localStorage.getItem('access');
|
||||
if (!token) {
|
||||
alert('No hay token de autenticación. Por favor, inicia sesión nuevamente.');
|
||||
setExecutingId(null);
|
||||
return;
|
||||
}
|
||||
const headers = {
|
||||
'Content-Type': 'application/json',
|
||||
'Authorization': `Bearer ${token}`,
|
||||
};
|
||||
const body = JSON.stringify({
|
||||
const body = {
|
||||
pedimento: typeof proc.pedimento === 'object' && proc.pedimento !== null ? proc.pedimento.id : proc.pedimento,
|
||||
organizacion: proc.organizacion_id || proc.organizacion || proc.organizacionId,
|
||||
});
|
||||
const res = await fetch(`${MICROSERVICE_URL}${endpoint}`, {
|
||||
method: 'POST',
|
||||
headers,
|
||||
body,
|
||||
});
|
||||
};
|
||||
|
||||
const res = await postWithAuth(`${MICROSERVICE_URL}${endpoint}`, body);
|
||||
|
||||
if (!res.ok) throw new Error('Error al ejecutar el servicio');
|
||||
|
||||
alert('Servicio ejecutado correctamente');
|
||||
setOpenDropdownId(null);
|
||||
} catch (err) {
|
||||
alert('Error al ejecutar el servicio: ' + (err instanceof Error ? err.message : String(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 {
|
||||
alert('Error al ejecutar el servicio: ' + (err instanceof Error ? err.message : String(err)));
|
||||
}
|
||||
} finally {
|
||||
setExecutingId(null);
|
||||
}
|
||||
@@ -115,41 +111,29 @@ export default function Procesos() {
|
||||
setLoading(true);
|
||||
setError('');
|
||||
try {
|
||||
const token = localStorage.getItem('access');
|
||||
if (!token) {
|
||||
setError('No hay token de autenticación. Por favor, inicia sesión nuevamente.');
|
||||
return;
|
||||
}
|
||||
// Construir query params
|
||||
const params = new URLSearchParams();
|
||||
params.append('page', String(page));
|
||||
params.append('page_size', String(itemsPerPage));
|
||||
if (pedimentoPedimentoFilter) params.append('pedimento__pedimento', pedimentoPedimentoFilter);
|
||||
if (estadoFilter) params.append('estado', estadoFilter);
|
||||
if (servicioFilter) params.append('servicio', servicioFilter);
|
||||
// Construir filtros
|
||||
const filters = {};
|
||||
if (pedimentoPedimentoFilter) filters['pedimento__pedimento'] = pedimentoPedimentoFilter;
|
||||
if (estadoFilter) filters['estado'] = estadoFilter;
|
||||
if (servicioFilter) filters['servicio'] = servicioFilter;
|
||||
if (sortField) {
|
||||
params.append('ordering', (sortOrder === 'desc' ? '-' : '') + sortField);
|
||||
filters['ordering'] = (sortOrder === 'desc' ? '-' : '') + sortField;
|
||||
}
|
||||
const API_URL = import.meta.env.VITE_EFC_API_URL;
|
||||
const headers = {
|
||||
'Authorization': `Bearer ${token}`,
|
||||
'Content-Type': 'application/json'
|
||||
};
|
||||
console.log('Fetching procesos with token:', token ? 'Token present' : 'No token');
|
||||
console.log('URL:', `${API_URL}/customs/procesamientopedimentos/?${params.toString()}`);
|
||||
const res = await fetch(`${API_URL}/customs/procesamientopedimentos/?${params.toString()}`, { headers });
|
||||
console.log('Response status:', res.status);
|
||||
if (!res.ok) {
|
||||
const errorText = await res.text();
|
||||
console.log('Error response:', errorText);
|
||||
throw new Error(`Error al obtener procesamiento de pedimentos: ${res.status} - ${errorText}`);
|
||||
}
|
||||
const data = await res.json();
|
||||
|
||||
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) {
|
||||
setError(err instanceof Error ? err.message : String(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 {
|
||||
setError(err instanceof Error ? err.message : String(err));
|
||||
}
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import React, { useState, useEffect, useLayoutEffect, useRef } from 'react';
|
||||
import { getCurrentUser } from '../api/users.ts';
|
||||
import { useNotification } from '../context/NotificationContext';
|
||||
import { fetchWithAuth, patchWithAuth, postWithAuth, postFormDataWithAuth } from '../fetchWithAuth';
|
||||
|
||||
// Animación fade-in/slide-up para el componente
|
||||
const fadeInSlideUp = `@keyframes fadein-slideup { 0% { opacity: 0; transform: translateY(40px); } 100% { opacity: 1; transform: translateY(0); } }`;
|
||||
@@ -27,6 +28,16 @@ const Settings = () => {
|
||||
rfc: ''
|
||||
});
|
||||
|
||||
// Estados para cambio de contraseña
|
||||
const [passwordData, setPasswordData] = useState({
|
||||
current_password: '',
|
||||
new_password: '',
|
||||
confirm_password: ''
|
||||
});
|
||||
|
||||
const [passwordErrors, setPasswordErrors] = useState({});
|
||||
const [changingPassword, setChangingPassword] = useState(false);
|
||||
|
||||
// Estado para controlar la animación de entrada
|
||||
const [showAnimation, setShowAnimation] = useState(false);
|
||||
const [hasAnimated, setHasAnimated] = useState(false);
|
||||
@@ -79,28 +90,10 @@ const Settings = () => {
|
||||
|
||||
// Función para actualizar el usuario
|
||||
const updateUser = async (userData) => {
|
||||
const token = localStorage.getItem('access');
|
||||
const API_URL = import.meta.env.VITE_EFC_API_URL;
|
||||
|
||||
try {
|
||||
const response = await fetch(`${API_URL}/user/users/${currentUser.id}/`, {
|
||||
method: 'PATCH',
|
||||
headers: {
|
||||
'Authorization': `Bearer ${token}`,
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify(userData),
|
||||
});
|
||||
|
||||
if (response.status === 401) {
|
||||
showMessage('Tu sesión ha expirado, por favor inicia sesión de nuevo.', 'error');
|
||||
localStorage.removeItem('access');
|
||||
localStorage.removeItem('refresh');
|
||||
setTimeout(() => {
|
||||
window.location.href = '/login';
|
||||
}, 2000);
|
||||
return null;
|
||||
}
|
||||
const response = await patchWithAuth(`${API_URL}/user/users/${currentUser.id}/`, userData);
|
||||
|
||||
if (!response.ok) {
|
||||
const errorData = await response.json();
|
||||
@@ -123,6 +116,105 @@ const Settings = () => {
|
||||
}));
|
||||
};
|
||||
|
||||
// Manejar cambios en el formulario de contraseña
|
||||
const handlePasswordChange = (e) => {
|
||||
const { name, value } = e.target;
|
||||
setPasswordData(prev => ({
|
||||
...prev,
|
||||
[name]: value
|
||||
}));
|
||||
// Limpiar errores cuando el usuario empiece a escribir
|
||||
if (passwordErrors[name]) {
|
||||
setPasswordErrors(prev => ({
|
||||
...prev,
|
||||
[name]: ''
|
||||
}));
|
||||
}
|
||||
};
|
||||
|
||||
// Validar contraseña
|
||||
const validatePassword = (password) => {
|
||||
const errors = [];
|
||||
if (password.length < 8) {
|
||||
errors.push('Debe tener al menos 8 caracteres');
|
||||
}
|
||||
if (!/(?=.*[a-z])/.test(password)) {
|
||||
errors.push('Debe contener al menos una letra minúscula');
|
||||
}
|
||||
if (!/(?=.*[A-Z])/.test(password)) {
|
||||
errors.push('Debe contener al menos una letra mayúscula');
|
||||
}
|
||||
if (!/(?=.*\d)/.test(password)) {
|
||||
errors.push('Debe contener al menos un número');
|
||||
}
|
||||
return errors;
|
||||
};
|
||||
|
||||
// Cambiar contraseña
|
||||
const handleChangePassword = async () => {
|
||||
const errors = {};
|
||||
|
||||
// Validaciones
|
||||
if (!passwordData.current_password.trim()) {
|
||||
errors.current_password = 'La contraseña actual es requerida';
|
||||
}
|
||||
|
||||
if (!passwordData.new_password.trim()) {
|
||||
errors.new_password = 'La nueva contraseña es requerida';
|
||||
} else {
|
||||
const passwordValidationErrors = validatePassword(passwordData.new_password);
|
||||
if (passwordValidationErrors.length > 0) {
|
||||
errors.new_password = passwordValidationErrors[0]; // Mostrar el primer error
|
||||
}
|
||||
}
|
||||
|
||||
if (!passwordData.confirm_password.trim()) {
|
||||
errors.confirm_password = 'Confirma tu nueva contraseña';
|
||||
} else if (passwordData.new_password !== passwordData.confirm_password) {
|
||||
errors.confirm_password = 'Las contraseñas no coinciden';
|
||||
}
|
||||
|
||||
if (Object.keys(errors).length > 0) {
|
||||
setPasswordErrors(errors);
|
||||
return;
|
||||
}
|
||||
|
||||
setChangingPassword(true);
|
||||
try {
|
||||
const API_URL = import.meta.env.VITE_EFC_API_URL;
|
||||
|
||||
const response = await postWithAuth(`${API_URL}/user/users/${currentUser.id}/change_password/`, {
|
||||
old_password: passwordData.current_password,
|
||||
new_password: passwordData.new_password
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
const errorData = await response.json();
|
||||
if (errorData.old_password) {
|
||||
setPasswordErrors({ current_password: 'La contraseña actual es incorrecta' });
|
||||
} else {
|
||||
throw new Error(errorData.detail || 'Error al cambiar la contraseña');
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// Limpiar formulario y mostrar mensaje de éxito
|
||||
setPasswordData({
|
||||
current_password: '',
|
||||
new_password: '',
|
||||
confirm_password: ''
|
||||
});
|
||||
setPasswordErrors({});
|
||||
showMessage('Contraseña cambiada exitosamente', 'success');
|
||||
|
||||
} catch (error) {
|
||||
console.error('Error changing password:', error);
|
||||
showMessage(error.message || 'Error al cambiar la contraseña', 'error');
|
||||
} finally {
|
||||
setChangingPassword(false);
|
||||
}
|
||||
};
|
||||
|
||||
// Guardar cambios del perfil
|
||||
const handleSaveProfile = async () => {
|
||||
if (!currentUser) return;
|
||||
@@ -642,9 +734,19 @@ const Settings = () => {
|
||||
</label>
|
||||
<input
|
||||
type="password"
|
||||
className="w-full px-4 py-3 border border-gray-300 rounded-lg shadow-sm focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-blue-500 text-sm transition-all duration-200 hover:border-gray-400"
|
||||
name="current_password"
|
||||
value={passwordData.current_password}
|
||||
onChange={handlePasswordChange}
|
||||
className={`w-full px-4 py-3 border rounded-lg shadow-sm focus:outline-none focus:ring-2 text-sm transition-all duration-200 hover:border-gray-400 ${
|
||||
passwordErrors.current_password
|
||||
? 'border-red-300 focus:ring-red-500 focus:border-red-500'
|
||||
: 'border-gray-300 focus:ring-blue-500 focus:border-blue-500'
|
||||
}`}
|
||||
placeholder="Ingresa tu contraseña actual"
|
||||
/>
|
||||
{passwordErrors.current_password && (
|
||||
<p className="mt-1 text-sm text-red-600">{passwordErrors.current_password}</p>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className="space-y-2">
|
||||
@@ -653,9 +755,19 @@ const Settings = () => {
|
||||
</label>
|
||||
<input
|
||||
type="password"
|
||||
className="w-full px-4 py-3 border border-gray-300 rounded-lg shadow-sm focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-blue-500 text-sm transition-all duration-200 hover:border-gray-400"
|
||||
name="new_password"
|
||||
value={passwordData.new_password}
|
||||
onChange={handlePasswordChange}
|
||||
className={`w-full px-4 py-3 border rounded-lg shadow-sm focus:outline-none focus:ring-2 text-sm transition-all duration-200 hover:border-gray-400 ${
|
||||
passwordErrors.new_password
|
||||
? 'border-red-300 focus:ring-red-500 focus:border-red-500'
|
||||
: 'border-gray-300 focus:ring-blue-500 focus:border-blue-500'
|
||||
}`}
|
||||
placeholder="Ingresa una nueva contraseña"
|
||||
/>
|
||||
{passwordErrors.new_password && (
|
||||
<p className="mt-1 text-sm text-red-600">{passwordErrors.new_password}</p>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className="space-y-2">
|
||||
@@ -664,28 +776,52 @@ const Settings = () => {
|
||||
</label>
|
||||
<input
|
||||
type="password"
|
||||
className="w-full px-4 py-3 border border-gray-300 rounded-lg shadow-sm focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-blue-500 text-sm transition-all duration-200 hover:border-gray-400"
|
||||
name="confirm_password"
|
||||
value={passwordData.confirm_password}
|
||||
onChange={handlePasswordChange}
|
||||
className={`w-full px-4 py-3 border rounded-lg shadow-sm focus:outline-none focus:ring-2 text-sm transition-all duration-200 hover:border-gray-400 ${
|
||||
passwordErrors.confirm_password
|
||||
? 'border-red-300 focus:ring-red-500 focus:border-red-500'
|
||||
: 'border-gray-300 focus:ring-blue-500 focus:border-blue-500'
|
||||
}`}
|
||||
placeholder="Confirma tu nueva contraseña"
|
||||
/>
|
||||
{passwordErrors.confirm_password && (
|
||||
<p className="mt-1 text-sm text-red-600">{passwordErrors.confirm_password}</p>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="mt-6">
|
||||
<button
|
||||
type="button"
|
||||
className="inline-flex items-center px-6 py-3 border border-transparent rounded-lg shadow-sm text-sm font-medium text-white bg-blue-600 hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500 transition-colors duration-200"
|
||||
onClick={handleChangePassword}
|
||||
disabled={changingPassword}
|
||||
className="inline-flex items-center px-6 py-3 border border-transparent rounded-lg shadow-sm text-sm font-medium text-white bg-blue-600 hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500 transition-colors duration-200 disabled:opacity-50 disabled:cursor-not-allowed"
|
||||
>
|
||||
<svg className="w-4 h-4 mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z" />
|
||||
</svg>
|
||||
Actualizar contraseña
|
||||
{changingPassword ? (
|
||||
<>
|
||||
<svg className="animate-spin -ml-1 mr-2 h-4 w-4 text-white" fill="none" viewBox="0 0 24 24">
|
||||
<circle className="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" strokeWidth="4"></circle>
|
||||
<path className="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"></path>
|
||||
</svg>
|
||||
Cambiando contraseña...
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<svg className="w-4 h-4 mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z" />
|
||||
</svg>
|
||||
Actualizar contraseña
|
||||
</>
|
||||
)}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Información de seguridad adicional */}
|
||||
<div className="bg-gray-50 rounded-xl p-6">
|
||||
<h4 className="text-lg font-semibold text-gray-900 mb-4">Consejos de seguridad</h4>
|
||||
<h4 className="text-lg font-semibold text-gray-900 mb-4">Requisitos de contraseña</h4>
|
||||
<div className="space-y-3">
|
||||
<div className="flex items-start">
|
||||
<div className="flex-shrink-0">
|
||||
@@ -694,7 +830,7 @@ const Settings = () => {
|
||||
</svg>
|
||||
</div>
|
||||
<p className="ml-3 text-sm text-gray-600">
|
||||
Usa al menos 8 caracteres con una combinación de letras, números y símbolos
|
||||
Mínimo 8 caracteres de longitud
|
||||
</p>
|
||||
</div>
|
||||
<div className="flex items-start">
|
||||
@@ -704,7 +840,7 @@ const Settings = () => {
|
||||
</svg>
|
||||
</div>
|
||||
<p className="ml-3 text-sm text-gray-600">
|
||||
Evita usar información personal como nombres o fechas de nacimiento
|
||||
Al menos una letra minúscula (a-z)
|
||||
</p>
|
||||
</div>
|
||||
<div className="flex items-start">
|
||||
@@ -714,9 +850,55 @@ const Settings = () => {
|
||||
</svg>
|
||||
</div>
|
||||
<p className="ml-3 text-sm text-gray-600">
|
||||
No reutilices contraseñas de otras cuentas
|
||||
Al menos una letra mayúscula (A-Z)
|
||||
</p>
|
||||
</div>
|
||||
<div className="flex items-start">
|
||||
<div className="flex-shrink-0">
|
||||
<svg className="w-5 h-5 text-green-500 mt-0.5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M5 13l4 4L19 7" />
|
||||
</svg>
|
||||
</div>
|
||||
<p className="ml-3 text-sm text-gray-600">
|
||||
Al menos un número (0-9)
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="mt-6 pt-4 border-t border-gray-200">
|
||||
<h5 className="text-sm font-semibold text-gray-900 mb-2">Consejos adicionales</h5>
|
||||
<div className="space-y-2">
|
||||
<div className="flex items-start">
|
||||
<div className="flex-shrink-0">
|
||||
<svg className="w-4 h-4 text-blue-500 mt-0.5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M13 16h-1v-4h-1m1-4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z" />
|
||||
</svg>
|
||||
</div>
|
||||
<p className="ml-2 text-xs text-gray-600">
|
||||
Evita usar información personal como nombres o fechas de nacimiento
|
||||
</p>
|
||||
</div>
|
||||
<div className="flex items-start">
|
||||
<div className="flex-shrink-0">
|
||||
<svg className="w-4 h-4 text-blue-500 mt-0.5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M13 16h-1v-4h-1m1-4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z" />
|
||||
</svg>
|
||||
</div>
|
||||
<p className="ml-2 text-xs text-gray-600">
|
||||
No reutilices contraseñas de otras cuentas
|
||||
</p>
|
||||
</div>
|
||||
<div className="flex items-start">
|
||||
<div className="flex-shrink-0">
|
||||
<svg className="w-4 h-4 text-blue-500 mt-0.5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M13 16h-1v-4h-1m1-4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z" />
|
||||
</svg>
|
||||
</div>
|
||||
<p className="ml-2 text-xs text-gray-600">
|
||||
Considera usar símbolos especiales para mayor seguridad
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import { fetchUsers, createUser, updateUser, deleteUser } from '../api/users.ts';
|
||||
import { fetchUsers, createUser, updateUser, deleteUser, getCurrentUser } from '../api/users.ts';
|
||||
import { useNotification } from '../context/NotificationContext';
|
||||
|
||||
const initialForm = {
|
||||
@@ -44,33 +44,24 @@ export default function Users() {
|
||||
const [itemsPerPage, setItemsPerPage] = useState(10);
|
||||
const { showMessage } = useNotification();
|
||||
|
||||
const token = localStorage.getItem('access');
|
||||
|
||||
const loadUsers = () => {
|
||||
setLoading(true);
|
||||
fetchUsers(token)
|
||||
fetchUsers()
|
||||
.then(data => {
|
||||
setUsers(data);
|
||||
setLoading(false);
|
||||
})
|
||||
.catch(err => {
|
||||
if (err.message === 'SESSION_EXPIRED') {
|
||||
localStorage.removeItem('access');
|
||||
localStorage.removeItem('refresh');
|
||||
showMessage('Tu sesión ha expirado, por favor inicia sesión de nuevo.', 'error');
|
||||
setTimeout(() => {
|
||||
window.location.href = '/login';
|
||||
}, 2000);
|
||||
} else {
|
||||
setError(err.message);
|
||||
}
|
||||
console.error('Error loading users:', err);
|
||||
setError('Error al cargar usuarios');
|
||||
setLoading(false);
|
||||
});
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
// Si no hay token, limpiar y redirigir a login inmediatamente
|
||||
if (!token) {
|
||||
const accessToken = localStorage.getItem('access');
|
||||
if (!accessToken) {
|
||||
localStorage.removeItem('access');
|
||||
localStorage.removeItem('refresh');
|
||||
localStorage.removeItem('username');
|
||||
@@ -86,27 +77,7 @@ export default function Users() {
|
||||
|
||||
loadUsers();
|
||||
// Siempre sincroniza la información del usuario autenticado en localStorage
|
||||
fetch(`${import.meta.env.VITE_EFC_API_URL}/user/users/me/`, {
|
||||
headers: { 'Authorization': `Bearer ${token}` }
|
||||
})
|
||||
.then(async res => {
|
||||
if (!res.ok) {
|
||||
// Token inválido o expirado, limpiar localStorage y redirigir a login
|
||||
console.log('Token inválido o expirado');
|
||||
localStorage.removeItem('access');
|
||||
localStorage.removeItem('refresh');
|
||||
localStorage.removeItem('username');
|
||||
localStorage.removeItem('user_email');
|
||||
localStorage.removeItem('user_id');
|
||||
localStorage.removeItem('user_groups');
|
||||
localStorage.removeItem('user_first_name');
|
||||
localStorage.removeItem('user_last_name');
|
||||
localStorage.removeItem('user_is_importador');
|
||||
window.location.href = '/login';
|
||||
return null;
|
||||
}
|
||||
return res.json();
|
||||
})
|
||||
getCurrentUser()
|
||||
.then(data => {
|
||||
console.log('Respuesta de /api/users/me/:', data);
|
||||
if (data && data.username) {
|
||||
@@ -146,13 +117,13 @@ export default function Users() {
|
||||
setSubmitting(true);
|
||||
try {
|
||||
if (editingId) {
|
||||
await updateUser(token, editingId, form);
|
||||
await updateUser(editingId, form);
|
||||
showMessage('Usuario actualizado exitosamente', 'success');
|
||||
setShowEditModal(false);
|
||||
} else {
|
||||
const groups = createType === 'importador' ? [3, 5] : [4, 3];
|
||||
const extra = createType === 'importador' ? { is_importador: true } : {};
|
||||
await createUser(token, { ...form, groups, ...extra });
|
||||
await createUser({ ...form, groups, ...extra });
|
||||
showMessage(createType === 'importador' ? 'Importador creado exitosamente' : 'Usuario creado exitosamente', 'success');
|
||||
setShowCreateModal(false);
|
||||
}
|
||||
@@ -170,7 +141,7 @@ export default function Users() {
|
||||
if (!userToDelete) return;
|
||||
setSubmitting(true);
|
||||
try {
|
||||
await deleteUser(token, userToDelete.id);
|
||||
await deleteUser(userToDelete.id);
|
||||
showMessage('Usuario eliminado exitosamente', 'success');
|
||||
setShowDeleteModal(false);
|
||||
setUserToDelete(null);
|
||||
|
||||
@@ -24,26 +24,16 @@ export default function Users() {
|
||||
const [searchTerm, setSearchTerm] = useState('');
|
||||
const { showMessage } = useNotification();
|
||||
|
||||
const token = localStorage.getItem('access');
|
||||
|
||||
const loadUsers = () => {
|
||||
setLoading(true);
|
||||
fetchUsers(token)
|
||||
fetchUsers()
|
||||
.then(data => {
|
||||
setUsers(data);
|
||||
setLoading(false);
|
||||
})
|
||||
.catch(err => {
|
||||
if (err.message === 'SESSION_EXPIRED') {
|
||||
localStorage.removeItem('access');
|
||||
localStorage.removeItem('refresh');
|
||||
showMessage('Tu sesión ha expirado, por favor inicia sesión de nuevo.', 'error');
|
||||
setTimeout(() => {
|
||||
window.location.href = '/login';
|
||||
}, 2000);
|
||||
} else {
|
||||
setError(err.message);
|
||||
}
|
||||
console.error('Error loading users:', err);
|
||||
setError('Error al cargar usuarios');
|
||||
setLoading(false);
|
||||
});
|
||||
};
|
||||
@@ -62,11 +52,11 @@ export default function Users() {
|
||||
setSubmitting(true);
|
||||
try {
|
||||
if (editingId) {
|
||||
await updateUser(token, editingId, form);
|
||||
await updateUser(editingId, form);
|
||||
showMessage('Usuario actualizado exitosamente', 'success');
|
||||
setShowEditModal(false);
|
||||
} else {
|
||||
await createUser(token, form);
|
||||
await createUser(form);
|
||||
showMessage('Usuario creado exitosamente', 'success');
|
||||
setShowCreateModal(false);
|
||||
}
|
||||
@@ -101,7 +91,7 @@ export default function Users() {
|
||||
if (!userToDelete) return;
|
||||
setSubmitting(true);
|
||||
try {
|
||||
await deleteUser(token, userToDelete.id);
|
||||
await deleteUser(userToDelete.id);
|
||||
showMessage('Usuario eliminado exitosamente', 'success');
|
||||
setShowDeleteModal(false);
|
||||
setUserToDelete(null);
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import { fetchWithAuth, postWithAuth, putWithAuth, deleteWithAuth } from '../fetchWithAuth';
|
||||
|
||||
const API_URL = import.meta.env.VITE_EFC_API_URL;
|
||||
export default function Vucem() {
|
||||
@@ -118,10 +119,7 @@ export default function Vucem() {
|
||||
const fetchVucem = async () => {
|
||||
setLoading(true);
|
||||
try {
|
||||
const token = localStorage.getItem('access');
|
||||
const res = await fetch(`${API_URL}/vucem/vucem/`, {
|
||||
headers: token ? { 'Authorization': `Bearer ${token}` } : {},
|
||||
});
|
||||
const res = await fetchWithAuth(`${API_URL}/vucem/vucem/`);
|
||||
if (!res.ok) throw new Error('Error al cargar VUCEM');
|
||||
const data = await res.json();
|
||||
setVucemList(data);
|
||||
@@ -135,10 +133,7 @@ export default function Vucem() {
|
||||
// Funciones de descarga
|
||||
const downloadCertificate = async (id, usuario) => {
|
||||
try {
|
||||
const token = localStorage.getItem('access');
|
||||
const res = await fetch(`${API_URL}/vucem/vucem/${id}/download_cer/`, {
|
||||
headers: token ? { 'Authorization': `Bearer ${token}` } : {},
|
||||
});
|
||||
const res = await fetchWithAuth(`${API_URL}/vucem/vucem/${id}/download_cer/`);
|
||||
|
||||
if (!res.ok) {
|
||||
if (res.status === 404) {
|
||||
@@ -171,10 +166,7 @@ export default function Vucem() {
|
||||
|
||||
const downloadKey = async (id, usuario) => {
|
||||
try {
|
||||
const token = localStorage.getItem('access');
|
||||
const res = await fetch(`${API_URL}/vucem/vucem/${id}/download_key/`, {
|
||||
headers: token ? { 'Authorization': `Bearer ${token}` } : {},
|
||||
});
|
||||
const res = await fetchWithAuth(`${API_URL}/vucem/vucem/${id}/download_key/`);
|
||||
|
||||
if (!res.ok) {
|
||||
if (res.status === 404) {
|
||||
@@ -939,12 +931,7 @@ export default function Vucem() {
|
||||
formData.append('acuseedocument', form.acuseedocument);
|
||||
formData.append('is_active', form.is_active);
|
||||
try {
|
||||
const token = localStorage.getItem('access');
|
||||
const res = await fetch(`${API_URL}/vucem/vucem/`, {
|
||||
method: 'POST',
|
||||
headers: token ? { 'Authorization': `Bearer ${token}` } : {},
|
||||
body: formData,
|
||||
});
|
||||
const res = await postWithAuth(`${API_URL}/vucem/vucem/`, formData);
|
||||
if (!res.ok) throw new Error('Error al crear VUCEM');
|
||||
await fetchVucem();
|
||||
closeModals();
|
||||
@@ -1108,12 +1095,7 @@ export default function Vucem() {
|
||||
formData.append('acuseedocument', form.acuseedocument);
|
||||
formData.append('is_active', form.is_active);
|
||||
try {
|
||||
const token = localStorage.getItem('access');
|
||||
const res = await fetch(`${API_URL}/vucem/vucem/${editVucem.id}/`, {
|
||||
method: 'PATCH',
|
||||
headers: token ? { 'Authorization': `Bearer ${token}` } : {},
|
||||
body: formData,
|
||||
});
|
||||
const res = await putWithAuth(`${API_URL}/vucem/vucem/${editVucem.id}/`, formData);
|
||||
if (!res.ok) throw new Error('Error al actualizar VUCEM');
|
||||
await fetchVucem();
|
||||
closeModals();
|
||||
@@ -1273,11 +1255,7 @@ export default function Vucem() {
|
||||
<button type="button" onClick={async () => {
|
||||
if (!deleteVucem) return;
|
||||
try {
|
||||
const token = localStorage.getItem('access');
|
||||
const res = await fetch(`${API_URL}/vucem/vucem/${deleteVucem.id}/`, {
|
||||
method: 'DELETE',
|
||||
headers: token ? { 'Authorization': `Bearer ${token}` } : {},
|
||||
});
|
||||
const res = await deleteWithAuth(`${API_URL}/vucem/vucem/${deleteVucem.id}/`);
|
||||
if (!res.ok) throw new Error('Error al eliminar VUCEM');
|
||||
await fetchVucem();
|
||||
closeModals();
|
||||
|
||||
Reference in New Issue
Block a user