From 4bc8a9202165a14e67509bd13dd9e6c811dae2e1 Mon Sep 17 00:00:00 2001 From: Kevin Rosales Date: Mon, 4 Aug 2025 11:05:41 -0600 Subject: [PATCH] Primera version para produccion --- docker-compose.dev.yml | 16 ++ src/App.jsx | 7 + src/components/Sidebar.jsx | 132 +++++---- src/pages/Admin.jsx | 13 +- src/pages/Agenda.jsx | 13 + src/pages/Organization.jsx | 29 +- src/pages/Procesos.jsx | 104 ++++++-- src/pages/Vucem.jsx | 533 ++++++++++++++++++++++++++++++++++++- 8 files changed, 747 insertions(+), 100 deletions(-) create mode 100644 docker-compose.dev.yml create mode 100644 src/pages/Agenda.jsx diff --git a/docker-compose.dev.yml b/docker-compose.dev.yml new file mode 100644 index 0000000..e9c5930 --- /dev/null +++ b/docker-compose.dev.yml @@ -0,0 +1,16 @@ +version: '3.8' +services: + frontend: + build: + context: . + dockerfile: Dockerfile + image: efc_frontend_dev:latest + ports: + - "5173:5173" + volumes: + - .:/app + - /app/node_modules + environment: + - NODE_ENV=development + command: ["npm", "run", "dev", "--", "--host"] + restart: unless-stopped diff --git a/src/App.jsx b/src/App.jsx index bedb887..a1a10fd 100644 --- a/src/App.jsx +++ b/src/App.jsx @@ -1,4 +1,5 @@ import Documents from './pages/Documents'; +import Agenda from './pages/Agenda'; import Vucem from './pages/Vucem'; import { BrowserRouter, Routes, Route, useLocation } from 'react-router-dom'; import { UserProvider } from './context/UserContext'; @@ -101,6 +102,12 @@ function AppContent() { } /> + {/* Ruta para agenda */} + + + + } /> {/* Ruta para Uso de Almacenamiento */} diff --git a/src/components/Sidebar.jsx b/src/components/Sidebar.jsx index 58bc86f..87f0f62 100644 --- a/src/components/Sidebar.jsx +++ b/src/components/Sidebar.jsx @@ -5,6 +5,17 @@ import { useUser } from '../context/UserContext'; export default function Sidebar() { // Leer si el usuario es importador desde localStorage const isImportador = typeof window !== 'undefined' && localStorage.getItem('user_is_importador') === 'true'; + // Leer grupos del usuario desde localStorage + let userGroups = []; + if (typeof window !== 'undefined') { + try { + userGroups = JSON.parse(localStorage.getItem('user_groups') || '[]'); + } catch { + userGroups = []; + } + } + // Si los grupos son exactamente [3,5] + const isGroup35 = Array.isArray(userGroups) && userGroups.length === 2 && userGroups.includes(3) && userGroups.includes(5); const [isCollapsed, setIsCollapsed] = useState(false); const location = useLocation(); const navigate = useNavigate(); @@ -33,15 +44,22 @@ export default function Sidebar() { ) }, - { - name: 'Mi Organización', - path: '/organization', - icon: ( - - - - ) - } + // Ocultar 'Mi Organización' si es importador o si esGroup35 + ...( + (!isImportador && !isGroup35) + ? [ + { + name: 'Mi Organización', + path: '/organization', + icon: ( + + + + ) + } + ] + : [] + ) ] }, { @@ -55,6 +73,16 @@ export default function Sidebar() { ) + }, + { + name: 'Agenda', + path: '/agenda', + icon: ( + + + + + ) } ] }, @@ -92,50 +120,62 @@ export default function Sidebar() { ] }, // Nueva sección Tableros - { - title: 'Tableros', - items: [ - { - name: 'Uso de Almacenamiento', - path: '/tablero/almacenamiento', - icon: ( - - - - ) - } - ] - }, - { - title: 'Acceso a Usuarios', - items: [ - ...( - isImportador - ? [] - : [ + ...( + isGroup35 + ? [] + : [ + { + title: 'Tableros', + items: [ { - name: 'Usuarios', - path: '/users', + name: 'Uso de Almacenamiento', + path: '/tablero/almacenamiento', icon: ( - + ) } ] - ), - { - name: 'Vucem', - path: '/vucem', - icon: ( - - - - - ) - } - ] - } + } + ] + ), + ...( + isGroup35 + ? [] + : [ + { + title: 'Acceso a Usuarios', + items: [ + ...( + isImportador + ? [] + : [ + { + name: 'Usuarios', + path: '/users', + icon: ( + + + + ) + } + ] + ), + { + name: 'Vucem', + path: '/vucem', + icon: ( + + + + + ) + } + ] + } + ] + ) ]; // Filtrar secciones según si es importador diff --git a/src/pages/Admin.jsx b/src/pages/Admin.jsx index 289bf2f..d5aa601 100644 --- a/src/pages/Admin.jsx +++ b/src/pages/Admin.jsx @@ -18,6 +18,17 @@ import { colors } from '../theme'; const API_URL = import.meta.env.VITE_EFC_API_URL; export default function Admin() { + // Leer grupos del usuario desde localStorage + let userGroups = []; + if (typeof window !== 'undefined') { + try { + userGroups = JSON.parse(localStorage.getItem('user_groups') || '[]'); + } catch { + userGroups = []; + } + } + // Si los grupos son exactamente [3,5] + const isGroup35 = Array.isArray(userGroups) && userGroups.length === 2 && userGroups.includes(3) && userGroups.includes(5); // Estado de servicios const [services, setServices] = useState(null); // Estado de descargas @@ -285,7 +296,7 @@ export default function Admin() { {/* Análisis de actividad de usuario */} - {!(typeof window !== 'undefined' && localStorage.getItem('user_is_importador') === 'true') && ( + {!(typeof window !== 'undefined' && localStorage.getItem('user_is_importador') === 'true') && !isGroup35 && (
+

Agenda

+

+
+

¡Bienvenido a la Agenda! Próximamente podrás ver y administrar tus actividades aquí.

+
+
+ ); +} diff --git a/src/pages/Organization.jsx b/src/pages/Organization.jsx index b75992e..7c96155 100644 --- a/src/pages/Organization.jsx +++ b/src/pages/Organization.jsx @@ -266,34 +266,7 @@ export default function Organization() { - - {/* Acciones */} -
-
-
- - - -
-

Acciones

-
- -
- - - -
-
+ {/* ...existing code... */} ); diff --git a/src/pages/Procesos.jsx b/src/pages/Procesos.jsx index d1acb6e..4282868 100644 --- a/src/pages/Procesos.jsx +++ b/src/pages/Procesos.jsx @@ -9,6 +9,7 @@ const MICROSERVICE_URL = import.meta.env.VITE_EFC_MICROSERVICE_URL; + export default function Procesos() { const [procesos, setProcesos] = useState([]); const [loading, setLoading] = useState(true); @@ -21,6 +22,10 @@ export default function Procesos() { const [estadoFilter, setEstadoFilter] = useState(''); const [servicioFilter, setServicioFilter] = useState(''); + // Sorting + const [sortField, setSortField] = useState(''); + const [sortOrder, setSortOrder] = useState('asc'); // 'asc' | 'desc' + // Estado para loading de ejecución de servicio const [executingId, setExecutingId] = useState(null); @@ -34,6 +39,9 @@ export default function Procesos() { let endpoint = ''; // Determinar endpoint según el tipo de servicio switch (proc.servicio) { + case 3: + endpoint = '/services/pedimento_completo'; + break; case 4: // Partidas endpoint = '/services/partidas'; break; @@ -43,7 +51,10 @@ export default function Procesos() { case 6: // Acuse endpoint = '/services/acuse'; break; - case 8: // Acuse Cove + case 8: // Coves + endpoint = '/services/Coves'; + break; + case 9: // Acuse Cove endpoint = '/services/acuseCove'; break; default: @@ -102,7 +113,9 @@ export default function Procesos() { if (pedimentoPedimentoFilter) params.append('pedimento__pedimento', pedimentoPedimentoFilter); if (estadoFilter) params.append('estado', estadoFilter); if (servicioFilter) params.append('servicio', servicioFilter); - // ...existing code... + if (sortField) { + params.append('ordering', (sortOrder === 'desc' ? '-' : '') + sortField); + } const API_URL = import.meta.env.VITE_EFC_API_URL; const headers = token ? { 'Authorization': `Bearer ${token}` } : {}; const res = await fetch(`${API_URL}/customs/procesamientopedimentos/?${params.toString()}`, { headers }); @@ -117,7 +130,7 @@ export default function Procesos() { } } fetchProcesos(); - }, [page, itemsPerPage, pedimentoPedimentoFilter, estadoFilter, servicioFilter]); + }, [page, itemsPerPage, pedimentoPedimentoFilter, estadoFilter, servicioFilter, sortField, sortOrder]); return (
@@ -131,6 +144,9 @@ export default function Procesos() {

Procesos del Sistema + + {count} +

Estado actual de los procesos de la agencia aduanal

@@ -160,10 +176,19 @@ export default function Procesos() { .animate-fadein-slideup { animation: fadein-slideup 0.7s cubic-bezier(0.22,1,0.36,1) 0.05s forwards; } + @keyframes fade-in { + from { opacity: 0; transform: scale(0.9); } + to { opacity: 1; transform: scale(1); } + } + .animate-fade-in { + animation: fade-in 0.7s ease; + } `}
-

Procesamiento de Pedimentos

+
+

Procesamiento de Pedimentos

+
{/* Filtros */}
@@ -206,9 +231,10 @@ export default function Procesos() { + +
- {/* ...filtros anteriores... */}
{loading ? (
Cargando procesos...
@@ -220,14 +246,51 @@ export default function Procesos() { - - - - - - - - + + + + + + + + {procesos.length === 0 ? ( @@ -259,6 +322,7 @@ export default function Procesos() { : proc.servicio === 6 ? 'Acuse' : proc.servicio === 7 ? 'EDocument' : proc.servicio === 8 ? 'Cove' + : proc.servicio === 9 ? 'Acuse Cove' : String(proc.servicio) }
IDOrganizaciónEstadoPedimentoServicioAcciones
{ + setSortField('id'); + setSortOrder(sortField === 'id' && sortOrder === 'asc' ? 'desc' : 'asc'); + }} + > + ID {sortField === 'id' && (sortOrder === 'asc' ? '▲' : '▼')} + { + setSortField('organizacion_name'); + setSortOrder(sortField === 'organizacion_name' && sortOrder === 'asc' ? 'desc' : 'asc'); + }} + > + Organización {sortField === 'organizacion_name' && (sortOrder === 'asc' ? '▲' : '▼')} + { + setSortField('estado'); + setSortOrder(sortField === 'estado' && sortOrder === 'asc' ? 'desc' : 'asc'); + }} + > + Estado {sortField === 'estado' && (sortOrder === 'asc' ? '▲' : '▼')} + { + setSortField('pedimento'); + setSortOrder(sortField === 'pedimento' && sortOrder === 'asc' ? 'desc' : 'asc'); + }} + > + Pedimento {sortField === 'pedimento' && (sortOrder === 'asc' ? '▲' : '▼')} + { + setSortField('servicio'); + setSortOrder(sortField === 'servicio' && sortOrder === 'asc' ? 'desc' : 'asc'); + }} + > + Servicio {sortField === 'servicio' && (sortOrder === 'asc' ? '▲' : '▼')} + + Acciones +
@@ -277,11 +341,21 @@ export default function Procesos() { - + diff --git a/src/pages/Vucem.jsx b/src/pages/Vucem.jsx index b87d3c2..c1e90b9 100644 --- a/src/pages/Vucem.jsx +++ b/src/pages/Vucem.jsx @@ -1,19 +1,532 @@ -import React from 'react'; +import React, { useEffect, useState } from 'react'; + +const API_URL = 'http://192.168.1.195:8000/api/v1/vucem/vucem/'; export default function Vucem() { + const [vucemList, setVucemList] = useState([]); + const [loading, setLoading] = useState(true); + const [error, setError] = useState(null); + const [showCreateModal, setShowCreateModal] = useState(false); + const [showEditModal, setShowEditModal] = useState(false); + const [editVucem, setEditVucem] = useState(null); + const [showDeleteModal, setShowDeleteModal] = useState(false); + const [deleteVucem, setDeleteVucem] = useState(null); + + // Estado para formulario de creación/edición + const initialForm = { + usuario: '', + password: '', + patente: '', + key: null, + cer: null, + is_importador: false, + acusecove: false, + acuseedocument: false, + is_active: true, + }; + const [form, setForm] = useState(initialForm); + + // Handlers básicos para inputs + const handleInputChange = e => { + const { name, value, type, checked, files } = e.target; + if (type === 'checkbox') { + setForm(f => ({ ...f, [name]: checked })); + } else if (type === 'file') { + setForm(f => ({ ...f, [name]: files[0] })); + } else { + setForm(f => ({ ...f, [name]: value })); + } + }; + const closeModals = () => { + setShowCreateModal(false); + setShowEditModal(false); + setShowDeleteModal(false); + setForm(initialForm); + setEditVucem(null); + setDeleteVucem(null); + }; + + // Fetch list + const fetchVucem = async () => { + setLoading(true); + try { + const token = localStorage.getItem('access'); + const res = await fetch(API_URL, { + headers: token ? { 'Authorization': `Bearer ${token}` } : {}, + }); + if (!res.ok) throw new Error('Error al cargar VUCEM'); + const data = await res.json(); + setVucemList(data); + setError(null); + } catch (err) { + setError('Error al cargar VUCEM'); + } + setLoading(false); + }; + + useEffect(() => { + fetchVucem(); + }, []); + + // Filtros visuales + const [filterUsuario, setFilterUsuario] = useState(''); + const [filterPatente, setFilterPatente] = useState(''); + const filteredList = vucemList.filter(v => + v.usuario.toLowerCase().includes(filterUsuario.toLowerCase()) && + v.patente.toLowerCase().includes(filterPatente.toLowerCase()) + ); + + // Paginación estilo Users.jsx + const [page, setPage] = useState(1); + const pageSize = 10; + const totalPages = Math.max(1, Math.ceil(filteredList.length / pageSize)); + const paginatedList = filteredList.slice((page - 1) * pageSize, page * pageSize); + + // Reset page si cambia el filtro + useEffect(() => { setPage(1); }, [filterUsuario, filterPatente]); + + // Table y header estilo Users.jsx return ( -
-
-

Vucem

-

Esta es la vista de integración con VUCEM. Aquí podrás consultar, gestionar o integrar funcionalidades relacionadas con la Ventanilla Única de Comercio Exterior Mexicana.

-
- - - +
+ {/* Header Mejorado igual que Users.jsx */} +
+
+ + + +
+
+

+ VUCEM + {vucemList.length} + + Activos: {vucemList.filter(v => v.is_active).length} + +

+

Gestiona y supervisa los accesos y certificados VUCEM registrados en el sistema.

+
+
+ + + + + + + + - Próximamente podrás ver información y acciones de VUCEM aquí.
+ + {/* Filtros y botón Crear VUCEM igual que Users.jsx */} +
+
+
+
+
+ + + +
+ setFilterUsuario(e.target.value)} + autoComplete="off" + /> + {filterUsuario && ( +
+ +
+ )} +
+
+
+ +
+
+ {/* Tabla igual que Users.jsx */} +
+
+ + + + + + + + + + + + + {loading ? ( + + + + ) : error ? ( + + + + ) : paginatedList.length > 0 ? ( + paginatedList.map((vucem, idx) => ( + + + + + + + + + )) + ) : ( + + + + )} + +
UsuarioPatenteKeyCerActivoAcciones
+
+ Cargando VUCEM... +
+
+
+ Error: {error} +
+
{vucem.usuario}{vucem.patente} + {vucem.key ? ( + + + + + Archivo cargado + + ) : ( + + + + + Sin archivo + + )} + + {vucem.cer ? ( + + + + + Archivo cargado + + ) : ( + + + + + Sin archivo + + )} + + {vucem.is_active ? ( + + + + + Activo + + ) : ( + + + + + Inactivo + + )} + + + +
No hay registros
+
+
+ {/* Paginación igual que Users.jsx */} + {filteredList.length > 0 && ( +
+
+ + Mostrando {filteredList.length} de {vucemList.length} registros + +
+
+ + Página {page} de {totalPages} + +
+
+ )} +
+ + {/* Modal de creación */} + {showCreateModal && ( +
+
+
+

Crear VUCEM

+ +
+
{ + e.preventDefault(); + const formData = new FormData(); + formData.append('usuario', form.usuario); + formData.append('password', form.password); + formData.append('patente', form.patente); + if (form.key) formData.append('key', form.key); + if (form.cer) formData.append('cer', form.cer); + formData.append('is_importador', form.is_importador); + formData.append('acusecove', form.acusecove); + formData.append('acuseedocument', form.acuseedocument); + formData.append('is_active', form.is_active); + try { + const token = localStorage.getItem('access'); + const res = await fetch(API_URL, { + method: 'POST', + headers: token ? { 'Authorization': `Bearer ${token}` } : {}, + body: formData, + }); + if (!res.ok) throw new Error('Error al crear VUCEM'); + await fetchVucem(); + closeModals(); + } catch (err) { + alert('Error al crear VUCEM'); + } + }}> +
+
+ + +
+
+ + +
+
+ + +
+
+ + + {form.key && {form.key.name}} +
+
+ + + {form.cer && {form.cer.name}} +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+
+ + +
+
+
+
+ )} + + {/* Modal de edición */} + {showEditModal && editVucem && ( +
+
+
+

Editar VUCEM

+ +
+
{ + e.preventDefault(); + if (!editVucem) return; + const formData = new FormData(); + formData.append('usuario', form.usuario); + if (form.password) formData.append('password', form.password); + formData.append('patente', form.patente); + if (form.key) formData.append('key', form.key); + if (form.cer) formData.append('cer', form.cer); + formData.append('is_importador', form.is_importador); + formData.append('acusecove', form.acusecove); + formData.append('acuseedocument', form.acuseedocument); + formData.append('is_active', form.is_active); + try { + const token = localStorage.getItem('access'); + const res = await fetch(`${API_URL}${editVucem.id}/`, { + method: 'PATCH', + headers: token ? { 'Authorization': `Bearer ${token}` } : {}, + body: formData, + }); + if (!res.ok) throw new Error('Error al actualizar VUCEM'); + await fetchVucem(); + closeModals(); + } catch (err) { + alert('Error al actualizar VUCEM'); + } + }}> +
+
+ + +
+
+ + +
+
+ + +
+
+ + + {form.key && {form.key.name}} +
+
+ + + {form.cer && {form.cer.name}} +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+
+ + +
+
+
+
+ )} + + {/* Modal de eliminación */} + {showDeleteModal && deleteVucem && ( +
+
+
+
+ + + +
+

¿Eliminar VUCEM?

+
+

+ ¿Estás seguro que deseas eliminar el registro {deleteVucem.usuario}? +

+

Esta acción no se puede deshacer.

+
+
+ + +
+
+
+
+ )}
); }