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() { 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: '', efirma: '', key: null, cer: null, is_importador: false, acusecove: false, acuseedocument: false, is_active: true, }; const [form, setForm] = useState(initialForm); // Estados para controlar visibilidad de contraseñas y copiar const [showPassword, setShowPassword] = useState({}); const [showEfirma, setShowEfirma] = useState({}); const [copySuccess, setCopySuccess] = useState(''); // Funciones para alternar visibilidad const togglePasswordVisibility = (id) => { setShowPassword(prev => ({ ...prev, [id]: !prev[id] })); }; const toggleEfirmaVisibility = (id) => { setShowEfirma(prev => ({ ...prev, [id]: !prev[id] })); }; // Función para copiar al portapapeles const copyToClipboard = async (text, fieldName) => { try { // Verificar si el texto existe if (!text || text.trim() === '') { setCopySuccess(`${fieldName} está vacío`); setTimeout(() => setCopySuccess(''), 2000); return; } // Intentar usar la Clipboard API moderna if (navigator.clipboard && window.isSecureContext) { await navigator.clipboard.writeText(text); setCopySuccess(`${fieldName} copiado`); setTimeout(() => setCopySuccess(''), 2000); } else { // Fallback para navegadores más antiguos o contextos no seguros const textArea = document.createElement('textarea'); textArea.value = text; textArea.style.position = 'fixed'; textArea.style.opacity = '0'; textArea.style.pointerEvents = 'none'; document.body.appendChild(textArea); textArea.focus(); textArea.select(); try { const successful = document.execCommand('copy'); if (successful) { setCopySuccess(`${fieldName} copiado`); } else { setCopySuccess(`Error al copiar ${fieldName}`); } } catch (fallbackErr) { console.error('Error en fallback:', fallbackErr); setCopySuccess(`Error al copiar ${fieldName}`); } finally { document.body.removeChild(textArea); setTimeout(() => setCopySuccess(''), 2000); } } } catch (err) { console.error('Error al copiar:', err); setCopySuccess(`Error al copiar ${fieldName}`); setTimeout(() => setCopySuccess(''), 2000); } }; // 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 res = await fetchWithAuth(`${API_URL}/vucem/vucem/`); 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); }; // Funciones de descarga const downloadCertificate = async (id, usuario) => { try { const res = await fetchWithAuth(`${API_URL}/vucem/vucem/${id}/download_cer/`); if (!res.ok) { if (res.status === 404) { setCopySuccess('Certificado no encontrado'); setTimeout(() => setCopySuccess(''), 2000); return; } throw new Error('Error al descargar certificado'); } const blob = await res.blob(); const url = window.URL.createObjectURL(blob); const a = document.createElement('a'); a.style.display = 'none'; a.href = url; a.download = `${usuario}_certificado.cer`; document.body.appendChild(a); a.click(); window.URL.revokeObjectURL(url); document.body.removeChild(a); setCopySuccess('Certificado descargado'); setTimeout(() => setCopySuccess(''), 2000); } catch (err) { console.error('Error al descargar certificado:', err); setCopySuccess('Error al descargar certificado'); setTimeout(() => setCopySuccess(''), 2000); } }; const downloadKey = async (id, usuario) => { try { const res = await fetchWithAuth(`${API_URL}/vucem/vucem/${id}/download_key/`); if (!res.ok) { if (res.status === 404) { setCopySuccess('Clave no encontrada'); setTimeout(() => setCopySuccess(''), 2000); return; } throw new Error('Error al descargar clave'); } const blob = await res.blob(); const url = window.URL.createObjectURL(blob); const a = document.createElement('a'); a.style.display = 'none'; a.href = url; a.download = `${usuario}_clave.key`; document.body.appendChild(a); a.click(); window.URL.revokeObjectURL(url); document.body.removeChild(a); setCopySuccess('Clave descargada'); setTimeout(() => setCopySuccess(''), 2000); } catch (err) { console.error('Error al descargar clave:', err); setCopySuccess('Error al descargar clave'); setTimeout(() => setCopySuccess(''), 2000); } }; 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]); // Cuando se selecciona un registro para editar, poblar el formulario con sus datos useEffect(() => { if (editVucem) { setForm({ usuario: editVucem.usuario || '', password: editVucem.password || '', // Mostrar el password actual para edición patente: editVucem.patente || '', efirma: editVucem.efirma || '', // Ya estaba incluido key: null, // No se rellena, solo se sube si el usuario selecciona cer: null, // No se rellena, solo se sube si el usuario selecciona is_importador: !!editVucem.is_importador, acusecove: !!editVucem.acusecove, acuseedocument: !!editVucem.acuseedocument, is_active: !!editVucem.is_active, }); } }, [editVucem]); // Table y header estilo Users.jsx return (
{/* Header modernizado con gradientes azules */}

Credenciales VU

{vucemList.length} Total {vucemList.filter(v => v.is_active).length} Activos

Gestiona certificados digitales, credenciales y configuraciones VUCEM del sistema aduanero

{/* Mensaje de éxito para copiar */} {copySuccess && (
{copySuccess}
)} {/* Controles de búsqueda y filtros mejorados */}
{/* Búsqueda principal */}
setFilterUsuario(e.target.value)} autoComplete="off" /> {filterUsuario && (
)}
{/* Información y botón crear */}
{filteredList.length !== vucemList.length && (
Mostrando {filteredList.length} de {vucemList.length} registros {filterUsuario && para "{filterUsuario}"}
)}
{/* Vista responsiva: tabla para desktop, cards para mobile */} {/* Tabla para pantallas grandes */}
{loading ? ( ) : error ? ( ) : paginatedList.length > 0 ? ( <> {paginatedList.map((vucem, idx) => ( ))} {/* Rellenar con filas vacías si hay menos de 8 */} {paginatedList.length < 8 && !loading && !error && Array.from({ length: 8 - paginatedList.length }).map((_, idx) => ( ))} ) : ( )}
Usuario Password e.firma Archivos Estado Acciones
Cargando registros VUCEM...
Error: {error}
{vucem.usuario}
Patente: {vucem.patente}
{showPassword[vucem.id] ? vucem.password || '(vacío)' : '••••••••'}
{showEfirma[vucem.id] ? vucem.efirma || '(vacío)' : '••••••••'}
{vucem.key ? (
Key
) : ( Sin Key )} {vucem.cer ? (
Cer
) : ( Sin Cer )}
{vucem.is_active ? ( Activo ) : ( Inactivo )} {vucem.is_importador && ( Importador )}
 

No hay registros VUCEM

Comienza creando tu primer registro VUCEM.

{/* Cards para pantallas pequeñas */}
{loading ? (
Cargando registros VUCEM...
) : error ? (
Error: {error}
) : paginatedList.length > 0 ? (
{paginatedList.map((vucem, idx) => (

{vucem.usuario}

Patente: {vucem.patente}

Password:
{showPassword[vucem.id] ? vucem.password || '(vacío)' : '••••••••'}
e.firma:
{showEfirma[vucem.id] ? vucem.efirma || '(vacío)' : '••••••••'}
Estado:
{vucem.is_active ? ( Activo ) : ( Inactivo )} {vucem.is_importador && ( Importador )}
Archivos:
{vucem.key ? (
Key
) : ( Sin Key )} {vucem.cer ? (
Cer
) : ( Sin Cer )}
))}
) : (

No hay registros VUCEM

Comienza creando tu primer registro VUCEM.

)}
{/* Paginación mejorada */} {totalPages > 1 && (
Mostrando {((page - 1) * pageSize) + 1} a{' '} {Math.min(page * pageSize, filteredList.length)} de{' '} {filteredList.length} registros
{Array.from({ length: totalPages }, (_, i) => i + 1).map(pageNum => ( ))}
)} {/* 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); formData.append('efirma', form.efirma); 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 res = await postWithAuth(`${API_URL}/vucem/vucem/`, 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); formData.append('efirma', form.efirma); 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 res = await putWithAuth(`${API_URL}/vucem/vucem/${editVucem.id}/`, 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.

)}
); }