Primera version de frontend

This commit is contained in:
2025-07-28 11:00:25 -06:00
parent 748e37cbcc
commit 0dac802736
78 changed files with 18757 additions and 0 deletions

25
src/api/auth.js Normal file
View File

@@ -0,0 +1,25 @@
const API_URL = import.meta.env.VITE_EFC_API_URL;
export async function login(username, password) {
const response = await fetch(`${API_URL}/token/`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ username, password }),
});
console.log('API URL:', `${API_URL}/token/`);
if (!response.ok) {
throw new Error('Credenciales inválidas');
}
return response.json(); // { access, refresh }
}
export async function refreshToken(refresh) {
const res = await fetch(`${API_URL}/token/refresh/`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ refresh }),
});
if (!res.ok) throw new Error('SESSION_EXPIRED');
return res.json(); // { access: '...' }
}

60
src/api/documentos.ts Normal file
View File

@@ -0,0 +1,60 @@
// src/api/pedimentoDocuments.ts
export interface PedimentoDocument {
id: string;
organizacion: string;
pedimento: string;
pedimento_numero:string;
archivo: string;
document_type: number;
size: number;
extension: string;
created_at: string;
updated_at: string;
}
export interface PedimentoDocumentsResponse {
count: number;
next: string | null;
previous: string | null;
results: PedimentoDocument[];
}
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: {
pedimento_numero?: string;
extension?: string;
document_type?: string | number;
created_at?: string;
} = {}
): Promise<PedimentoDocumentsResponse> {
const params = new URLSearchParams();
params.append('page', String(page));
params.append('page_size', String(pageSize));
if (pedimentoId) params.append('pedimento', pedimentoId);
if (filters.pedimento_numero) params.append('pedimento_numero', filters.pedimento_numero);
if (filters.extension) params.append('extension', filters.extension);
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',
},
}
);
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();
}

102
src/api/documents.js Normal file
View File

@@ -0,0 +1,102 @@
/**
* @typedef {Object} Document
* @property {string} id
* @property {string} organizacion
* @property {string} pedimento
* @property {string} archivo
* @property {number} document_type
* @property {number} size
* @property {string} extension
* @property {string} created_at
* @property {string} updated_at
*/
/**
* @typedef {Object} DocumentsResponse
* @property {number} count
* @property {string|null} next
* @property {string|null} previous
* @property {Document[]} results
*/
import { refreshToken } from './auth';
const API_URL = import.meta.env.VITE_EFC_API_URL;
/**
* Obtiene la lista de documentos (pedimentos)
* @param {string} token
* @returns {Promise<DocumentsResponse>}
*/
export async function fetchDocuments(token, queryString = '') {
let url = `${API_URL}/customs/pedimentos/`;
if (queryString) {
url += `?${queryString}`;
}
let res = await fetch(url, {
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/`, {
headers: {
'Authorization': `Bearer ${data.access}`,
'Content-Type': 'application/json',
},
});
} catch (err) {
throw new Error('SESSION_EXPIRED');
}
} else {
throw new Error('SESSION_EXPIRED');
}
}
if (!res.ok) throw new Error('No autorizado o error en la petición');
return res.json(); // Tipado por JSDoc: Promise<DocumentsResponse>
}
/**
* Obtiene los documentos por id de pedimento
* @param {string} token
* @param {string} id
* @returns {Promise<DocumentsResponse>}
*/
export async function fetchDocumentById(token, id) {
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');
}
}
if (!res.ok) throw new Error('No autorizado o error en la petición');
return res.json(); // Tipado por JSDoc: Promise<DocumentsResponse>
}

0
src/api/documents.ts Normal file
View File

115
src/api/expedientes.ts Normal file
View File

@@ -0,0 +1,115 @@
export interface Document {
id: string;
organizacion: string;
pedimento: string;
archivo: string;
document_type: number;
size: number;
extension: string;
created_at: string;
updated_at: string;
}
export interface DocumentsResponse {
count: number;
next: string | null;
previous: string | null;
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 {
search?: string;
pedimento?: string;
existe_expediente?: string | boolean;
alerta?: string | boolean;
contribuyente?: string;
curp_apoderado?: string;
fecha_pago?: string;
patente?: string;
aduana?: string;
tipo_operacion?: string;
clave_pedimento?: string;
}
export async function fetchDocuments(
token: string,
page: number = 1,
pageSize: number = 10,
filters: PedimentosFilters = {}
): Promise<DocumentsResponse> {
const params = new URLSearchParams();
params.append('page', String(page));
params.append('page_size', String(pageSize));
Object.entries(filters).forEach(([key, value]) => {
if (value !== undefined && value !== null && value !== '') {
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');
}
}
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');
}
}
if (!res.ok) throw new Error('No autorizado o error en la petición');
return res.json();
}

68
src/api/notificaciones.ts Normal file
View File

@@ -0,0 +1,68 @@
// 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 })
});
if (!res.ok) throw new Error('Error al actualizar notificación');
return await res.json();
}
// src/api/notificaciones.ts
export interface TipoNotificacion {
id: number;
tipo: string;
descripcion: string;
}
export interface Notificacion {
id: number;
tipo: TipoNotificacion;
dirigido: string;
mensaje: string;
fecha_envio: string;
created_at: string;
visto: boolean;
}
export interface NotificacionesResponse {
count: number;
next: string | null;
previous: string | null;
results: Notificacion[];
}
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,
});
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,
});
if (!res.ok) throw new Error('Error al obtener notificaciones');
return await res.json();
}

36
src/api/organizacion.js Normal file
View File

@@ -0,0 +1,36 @@
import { refreshToken } from './auth';
const API_URL = import.meta.env.VITE_EFC_API_URL;
export async function fetchOrganizationUsage(token) {
let res = await fetch(`${API_URL}/organization/uso-almacenamiento/mi_organizacion/`, {
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}/organization/uso-almacenamiento/mi_organizacion/`, {
headers: {
'Authorization': `Bearer ${data.access}`,
'Content-Type': 'application/json',
},
});
if (res.status === 401) throw new Error('SESSION_EXPIRED');
} catch (err) {
throw new Error('SESSION_EXPIRED');
}
} else {
throw new Error('SESSION_EXPIRED');
}
}
if (!res.ok) throw new Error('No autorizado o error en la petición');
return res.json();
}

33
src/api/organization.ts Normal file
View File

@@ -0,0 +1,33 @@
// organization.ts
// Tipos para la respuesta del endpoint de uso de almacenamiento de organización
export interface OrganizationUsage {
organizacion: string;
limite_almacenamiento_gb: number;
espacio_utilizado_bytes: number;
espacio_utilizado_gb: number;
espacio_disponible_bytes: number;
porcentaje_utilizado: number;
total_documentos: number;
total_pedimentos: number;
total_usuarios: number;
}
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');
}
if (!res.ok) {
throw new Error('Error al obtener información de la organización');
}
return res.json();
}

View File

@@ -0,0 +1,44 @@
// src/api/pedimentoDocuments.ts
export interface PedimentoDocument {
id: string;
organizacion: string;
pedimento: string;
archivo: string;
document_type: number;
size: number;
extension: string;
created_at: string;
updated_at: string;
}
export interface PedimentoDocumentsResponse {
count: number;
next: string | null;
previous: string | null;
results: PedimentoDocument[];
}
const API_URL = import.meta.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',
},
}
);
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();
}

33
src/api/procesos.ts Normal file
View File

@@ -0,0 +1,33 @@
// Tipos para la respuesta y registros
export interface ProcesamientoPedimento {
id: number;
created_at: string;
updated_at: string;
organizacion: string;
organizacion_name: string;
estado: number;
tipo_procesamiento: number;
pedimento: string;
servicio: number;
}
export interface ProcesamientoPedimentosResponse {
count: number;
next: string | null;
previous: string | null;
results: ProcesamientoPedimento[];
}
// API para customs/procesamientopedimentos/
export async function fetchProcesamientoPedimentos(
token: string | null,
page: number = 1,
pageSize: number = 20
): 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();
}

83
src/api/users.js Normal file
View File

@@ -0,0 +1,83 @@
const API_URL = import.meta.env.VITE_EFC_API_URL || 'http://localhost:8000';
async function handleResponse(response, operation = 'operación') {
if (response.status === 401) {
throw new Error('SESSION_EXPIRED');
}
if (!response.ok) {
const errorText = await response.text();
throw new Error(`Error ${response.status}: ${response.statusText}`);
}
const contentType = response.headers.get('content-type');
if (!contentType || !contentType.includes('application/json')) {
throw new Error('El servidor no devolvió JSON válido');
}
return response.json();
}
export async function fetchUsers(token) {
const url = `${API_URL}/user/users/`;
const res = await fetch(url, {
headers: {
'Authorization': `Bearer ${token}`,
'Content-Type': 'application/json',
'Accept': 'application/json',
},
});
return handleResponse(res, 'Fetch Users');
}
export async function createUser(token, 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),
});
return handleResponse(res, 'Create User');
}
export async function updateUser(token, 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),
});
return handleResponse(res, 'Update User');
}
export async function deleteUser(token, 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');
if (!res.ok) throw new Error(`Error ${res.status}: ${res.statusText}`);
return true;
}
export async function getCurrentUser(token) {
const url = `${API_URL}/user/users/me/`;
const res = await fetch(url, {
headers: {
'Authorization': `Bearer ${token}`,
'Content-Type': 'application/json',
'Accept': 'application/json',
},
});
return handleResponse(res, 'Get Current User');
}

172
src/api/users.ts Normal file
View File

@@ -0,0 +1,172 @@
const API_URL = import.meta.env.VITE_EFC_API_URL || 'http://localhost:8000';
// Función helper para manejar respuestas
async function handleResponse(response, operation = 'operación') {
console.log(`📡 ${operation} response:`, response.status, response.statusText);
if (response.status === 401) {
console.error('❌ Unauthorized - session expired');
throw new Error('SESSION_EXPIRED');
}
if (!response.ok) {
const errorText = await response.text();
console.error(`${operation} error:`, response.status, errorText);
throw new Error(`Error ${response.status}: ${response.statusText}`);
}
// Verificar que la respuesta es JSON
const contentType = response.headers.get('content-type');
if (!contentType || !contentType.includes('application/json')) {
const text = await response.text();
console.error('❌ Response is not JSON:', text.substring(0, 200));
throw new Error('El servidor no devolvió JSON válido');
}
return response.json();
}
export async function fetchUsers(token) {
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 data = await handleResponse(res, 'Fetch Users');
console.log('✅ Users data received');
return data;
} catch (error) {
console.error('❌ Error in fetchUsers:', error);
if (error.name === 'TypeError' && error.message.includes('fetch')) {
throw new Error('Error de conexión al servidor');
}
throw error;
}
}
export async function createUser(token, 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 data = await handleResponse(res, 'Create User');
console.log('✅ User created successfully');
return data;
} catch (error) {
console.error('❌ Error in createUser:', error);
if (error.name === 'TypeError' && error.message.includes('fetch')) {
throw new Error('Error de conexión al servidor');
}
throw error;
}
}
export async function updateUser(token, 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 data = await handleResponse(res, 'Update User');
console.log('✅ User updated successfully');
return data;
} catch (error) {
console.error('❌ Error in updateUser:', error);
if (error.name === 'TypeError' && error.message.includes('fetch')) {
throw new Error('Error de conexión al servidor');
}
throw error;
}
}
export async function deleteUser(token, 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',
},
});
if (res.status === 401) {
console.error('❌ Unauthorized - session expired');
throw new Error('SESSION_EXPIRED');
}
if (!res.ok) {
const errorText = await res.text();
console.error('❌ Delete User error:', res.status, errorText);
throw new Error(`Error ${res.status}: ${res.statusText}`);
}
console.log('✅ User deleted successfully');
return true; // DELETE suele no devolver contenido
} catch (error) {
console.error('❌ Error in deleteUser:', error);
if (error.name === 'TypeError' && error.message.includes('fetch')) {
throw new Error('Error de conexión al servidor');
}
throw error;
}
}
export async function getCurrentUser(token) {
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 data = await handleResponse(res, 'Get Current User');
console.log('✅ Current user data received:', data);
return data;
} catch (error) {
console.error('❌ Error in getCurrentUser:', error);
if (error.name === 'TypeError' && error.message.includes('fetch')) {
throw new Error('Error de conexión al servidor');
}
throw error;
}
}