From 3e498c57ad8e5f7faf268a1fd938ec1c589b1ff6 Mon Sep 17 00:00:00 2001 From: Kevin Rosales Date: Wed, 20 Aug 2025 09:15:59 -0600 Subject: [PATCH] Se agregaron datos del ticker 2025-08-046 --- src/api/expedientes.ts | 31 +- src/assets/animate-fade-in-fast.css | 8 + src/components/Sidebar.jsx | 10 + src/pages/Datastage.jsx | 253 ++++++---- src/pages/Expedientes.jsx | 36 +- src/pages/Importers.jsx | 710 +++++++++++++++++----------- src/pages/Vucem.jsx | 236 +++++++++ 7 files changed, 889 insertions(+), 395 deletions(-) create mode 100644 src/assets/animate-fade-in-fast.css diff --git a/src/api/expedientes.ts b/src/api/expedientes.ts index 7d4245f..d062b00 100644 --- a/src/api/expedientes.ts +++ b/src/api/expedientes.ts @@ -24,16 +24,35 @@ const API_URL = import.meta.env.VITE_EFC_API_URL; // Obtiene la lista de documentos (pedimentos) export interface PedimentosFilters { search?: string; + id?: string; + documentos_count?: number; + documentos_peso_total?: number; pedimento?: string; - existe_expediente?: string | boolean; - alerta?: string | boolean; - contribuyente?: string; - curp_apoderado?: string; - fecha_pago?: string; + pedimento_app?: string; patente?: string; aduana?: string; - tipo_operacion?: string; + regimen?: string; clave_pedimento?: string; + fecha_inicio?: string; + fecha_fin?: string; + fecha_pago?: string; + alerta?: string | boolean; + agente_aduanal?: string; + curp_apoderado?: string; + importe_total?: string; + saldo_disponible?: string; + importe_pedimento?: string; + existe_expediente?: string | boolean; + remesas?: string | boolean; + numero_partidas?: number; + numero_operacion?: string; + created_at?: string; + updated_at?: string; + organizacion?: string; + tipo_operacion?: string | number; + contribuyente?: string; + numero_edocs?: number; + numero_coves?: number; } export async function fetchDocuments( diff --git a/src/assets/animate-fade-in-fast.css b/src/assets/animate-fade-in-fast.css new file mode 100644 index 0000000..6fc16f0 --- /dev/null +++ b/src/assets/animate-fade-in-fast.css @@ -0,0 +1,8 @@ +@keyframes fadeInFast { + from { opacity: 0; transform: translateY(16px) scale(0.98); } + to { opacity: 1; transform: translateY(0) scale(1); } +} + +.animate-fade-in-fast { + animation: fadeInFast 0.12s cubic-bezier(0.4,0,0.2,1); +} diff --git a/src/components/Sidebar.jsx b/src/components/Sidebar.jsx index c6a1998..968427a 100644 --- a/src/components/Sidebar.jsx +++ b/src/components/Sidebar.jsx @@ -183,6 +183,16 @@ export default function Sidebar({ isMobileOpen, onMobileClose }) { { title: 'Acceso a Usuarios', items: [ + // Botón Importadores como primer elemento + { + name: 'Importadores', + path: '/importers', + icon: ( + + + + ) + }, ...( isImportador ? [] diff --git a/src/pages/Datastage.jsx b/src/pages/Datastage.jsx index 4642ca7..a028e9f 100644 --- a/src/pages/Datastage.jsx +++ b/src/pages/Datastage.jsx @@ -60,8 +60,10 @@ function RegistrosCargadosModal({ open, onClose, registros }) { } // Procesar datastage (adaptado para mostrar registros cargados) -async function procesarDatastage(item, setDatastages, setSuccess, setError, setRegistrosCargados, setShowRegistrosModal) { +// Recibe setEnProcesoId como argumento para manejar el estado desde el componente +async function procesarDatastage(item, setDatastages, setSuccess, setError, setRegistrosCargados, setShowRegistrosModal, setEnProcesoId) { try { + setEnProcesoId(item.id); const url = `${import.meta.env.VITE_EFC_API_URL}/datastage/datastages/${item.id}/procesar/`; const body = { organizacion: item.organizacion, @@ -88,6 +90,8 @@ async function procesarDatastage(item, setDatastages, setSuccess, setError, setR } } catch (e) { setError('No se pudo procesar el datastage'); + } finally { + setEnProcesoId(null); } } // Descarga autenticada de archivos datastage @@ -128,6 +132,8 @@ export default function Datastage() { // Animación header const [showAnimation, setShowAnimation] = useState(false); const [hasAnimated, setHasAnimated] = useState(false); + // Estado para mostrar mensaje "En proceso" por cada datastage + const [enProcesoId, setEnProcesoId] = useState(null); useLayoutEffect(() => { setShowAnimation(true); }, []); useEffect(() => { if (showAnimation && !hasAnimated) setTimeout(() => setHasAnimated(true), 800); }, [showAnimation, hasAnimated]); @@ -180,46 +186,6 @@ export default function Datastage() { setLoading(false); }; - // Editar - const handleEdit = async (e) => { - e.preventDefault(); - setLoading(true); - setError(null); - try { - const fd = new FormData(); - fd.append('contribuyente', form.contribuyente); - if (form.archivo) fd.append('archivo', form.archivo); - await patchFormDataWithAuth(`${import.meta.env.VITE_EFC_API_URL}/datastage/datastages/${editingId}/`, fd); - setForm({ archivo: null, contribuyente: '' }); - setEditingId(null); - setShowEditModal(false); - setSuccess('Datastage actualizado exitosamente'); - setShowSuccessModal(true); - load(); - } catch (e) { - setError(e.message); - } - setLoading(false); - }; - - // Eliminar - const handleDelete = async () => { - if (!deleteId) return; - setLoading(true); - setError(null); - try { - await deleteDatastage(deleteId); - if (selected && selected.id === deleteId) setSelected(null); - setShowDeleteModal(false); - setSuccess('Datastage eliminado exitosamente'); - setShowSuccessModal(true); - load(); - } catch (e) { - setError(e.message); - } - setLoading(false); - }; - // Abrir modal de edición const openEditModal = (item) => { setForm({ archivo: null, contribuyente: item.contribuyente }); @@ -403,14 +369,18 @@ export default function Datastage() { @@ -480,14 +450,18 @@ export default function Datastage() { @@ -497,44 +471,141 @@ export default function Datastage() { {/* Modales */} - {/* Modal de creación */} - {showCreateModal && ( -
-
-

Nuevo Datastage

- {error &&
{error}
} - - setForm(f => ({ ...f, archivo: e.target.files[0] }))} required /> - - setForm(f => ({ ...f, contribuyente: e.target.value }))} required /> -
- - -
-
-
- )} - {/* Modal de edición */} - {showEditModal && ( -
-
-

Editar Datastage

- {error &&
{error}
} - - setForm(f => ({ ...f, archivo: e.target.files[0] }))} /> - - setForm(f => ({ ...f, contribuyente: e.target.value }))} required /> -
- - -
-
-
- )} + {/* Modal de creación - estilo Users/Importers */} + {showCreateModal && ( +
+
+ {/* Header */} +
+
+
+
+ + + +
+
+

Nuevo Datastage

+

Carga un archivo .zip y asigna un contribuyente

+
+
+ +
+
+ {/* Content */} +
+ {error &&
{error}
} +
+ + setForm(f => ({ ...f, archivo: e.target.files[0] }))} required /> +
+
+ + setForm(f => ({ ...f, contribuyente: e.target.value }))} required /> +
+
+ + +
+
+
+
+ )} - {/* Modal de confirmación para eliminar */} - setShowDeleteModal(false)} onConfirm={handleDelete} message="¿Seguro que deseas eliminar este datastage?" confirmText="Eliminar" cancelText="Cancelar" /> + + {/* Modal de edición - estilo Users/Importers */} + {showEditModal && ( +
+
+ {/* Header */} +
+
+
+
+ + + +
+
+

Editar Datastage

+

Actualiza el archivo o el contribuyente

+
+
+ +
+
+ {/* Content */} +
+ {error &&
{error}
} +
+ + setForm(f => ({ ...f, archivo: e.target.files[0] }))} /> +
+
+ + setForm(f => ({ ...f, contribuyente: e.target.value }))} required /> +
+
+ + +
+
+
+
+ )} + + + {/* Modal de eliminación - estilo Users/Importers */} + {showDeleteModal && ( +
+
+ {/* Header */} +
+
+
+
+ + + +
+
+

Eliminar Datastage

+

Esta acción no se puede deshacer.

+
+
+ +
+
+ {/* Content */} +
+
+ + + +
+

¿Eliminar este datastage?

+

¿Seguro que deseas eliminar este datastage? Esta acción no se puede deshacer.

+
+ + +
+
+
+
+ )} {/* Modal de detalle */} {showDetailModal && selected && ( diff --git a/src/pages/Expedientes.jsx b/src/pages/Expedientes.jsx index 3305799..dac402e 100644 --- a/src/pages/Expedientes.jsx +++ b/src/pages/Expedientes.jsx @@ -419,8 +419,11 @@ export default function Documents() { Contribuyente CURP Apoderado Partidas - Saldo disponible - Importe pedimento + Fecha de Carga + Tipo Operacion + Clave + No. Archivos + Peso Total Expediente @@ -462,8 +465,11 @@ export default function Documents() { {ped.contribuyente} {ped.curp_apoderado} {ped.numero_partidas} - ${ped.saldo_disponible} - ${ped.importe_pedimento_app} + {ped.created_at ? ped.created_at.slice(0, 10) : ''} + {ped.tipo_operacion} + {ped.clave_pedimento} + {ped.documentos_count} + {ped.documentos_peso_total} )}
+
+ Tipo Operacion + {ped.tipo_operacion} +
Partidas - ${ped.numero_partidas} + {ped.numero_partidas}
- Saldo disponible: - ${ped.saldo_disponible} + Fecha de Carga + {ped.created_at ? ped.created_at.slice(0, 10) : ''}
- Importe pedimento: - ${ped.importe_pedimento_app} + Clave + {ped.clave_pedimento} +
+
+ No. Archivos + +
+
+ Peso Total + Peso total
diff --git a/src/pages/Importers.jsx b/src/pages/Importers.jsx index 5211412..9768dcd 100644 --- a/src/pages/Importers.jsx +++ b/src/pages/Importers.jsx @@ -1,309 +1,456 @@ -import React, { useState, useEffect } from 'react'; +import '../assets/animate-fade-in-fast.css'; +import React, { useState, useEffect, useLayoutEffect, useRef } from 'react'; +// Endpoint de credenciales VUCEM + +import { fetchWithAuth } from '../fetchWithAuth'; +const API_URL = import.meta.env.VITE_EFC_API_URL; + +// Animación fade-in/slide-up para bloques (igual que Expedientes) +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-importers')) { + const style = document.createElement('style'); + style.id = 'fadein-slideup-importers'; + style.innerHTML = fadeInSlideUp; + document.head.appendChild(style); +} +const VUCEM_CREDENTIALS_URL = `${API_URL}/vucem/vucem/`; export default function Importers() { + const focusKeeperRef = useRef(null); const [importers, setImporters] = useState([]); + const [modalOpen, setModalOpen] = useState(false); + const [modalMode, setModalMode] = useState('create'); // 'create' | 'edit' | 'view' | 'delete' + const [modalData, setModalData] = useState({ rfc: '', nombre: '', organizacion: '' }); + const [modalLoading, setModalLoading] = useState(false); + const [editId, setEditId] = useState(null); + const [errorMsg, setErrorMsg] = useState(''); + const openViewModal = (importer) => { + setModalMode('view'); + setModalData({ rfc: importer.rfc, nombre: importer.nombre, organizacion: importer.organizacion }); + setModalOpen(true); + }; + const openEditModal = (importer) => { + setModalMode('edit'); + setModalData({ rfc: importer.rfc, nombre: importer.nombre, organizacion: importer.organizacion }); + setEditId(importer.rfc); + setErrorMsg(''); + setModalOpen(true); + }; + const closeModal = () => { + setModalOpen(false); + setModalData({ rfc: '', nombre: '', organizacion: '' }); + setEditId(null); + setErrorMsg(''); + }; + const handleModalChange = e => { + const { name, value } = e.target; + setModalData(prev => ({ ...prev, [name]: value })); + }; + const openDeleteModal = (importer) => { + setModalMode('delete'); + setModalData({ rfc: importer.rfc, nombre: importer.nombre, organizacion: importer.organizacion }); + setModalOpen(true); + }; + + const handleDeleteConfirm = async () => { + setModalLoading(true); + try { + const res = await fetchWithAuth(`${API_URL}/customs/importadores/${modalData.rfc}/`, { method: 'DELETE' }); + if (!res.ok) throw new Error('Error al eliminar importador'); + // Refrescar lista + const res2 = await fetchWithAuth(`${API_URL}/customs/importadores/`); + if (!res2.ok) throw new Error('Error al obtener importadores'); + const data = await res2.json(); + setImporters(Array.isArray(data) ? data : []); + closeModal(); + } catch { + setErrorMsg('Error al eliminar importador'); + } finally { + setModalLoading(false); + } + }; const [loading, setLoading] = useState(true); const [searchTerm, setSearchTerm] = useState(''); const [currentPage, setCurrentPage] = useState(1); const [itemsPerPage, setItemsPerPage] = useState(10); + const [showAnimation, setShowAnimation] = useState(false); + const [hasAnimated, setHasAnimated] = useState(false); - // Datos dummy para mostrar - const dummyImporters = [ - { - id: 1, - name: 'Importadora ABC S.A.', - rfc: 'ABC123456789', - email: 'contacto@abc.com', - status: 'Activo', - lastActivity: '2024-01-15', - documentsCount: 45 - }, - { - id: 2, - name: 'Comercial XYZ Ltda.', - rfc: 'XYZ987654321', - email: 'info@xyz.com', - status: 'Activo', - lastActivity: '2024-01-14', - documentsCount: 23 - }, - { - id: 3, - name: 'Global Trade Corp.', - rfc: 'GTC555666777', - email: 'admin@globaltrade.com', - status: 'Inactivo', - lastActivity: '2024-01-10', - documentsCount: 12 + useLayoutEffect(() => { setShowAnimation(true); }, []); + useEffect(() => { + if (showAnimation && !hasAnimated) { + const timeout = setTimeout(() => { + setHasAnimated(true); + setShowAnimation(false); + }, 700); + return () => clearTimeout(timeout); } - ]; + }, [showAnimation, hasAnimated]); useEffect(() => { - // Simular carga de datos - const timer = setTimeout(() => { - setImporters(dummyImporters); - setLoading(false); - }, 1000); - - return () => clearTimeout(timer); + setLoading(true); + fetchWithAuth(`${API_URL}/customs/importadores/`) + .then(async res => { + if (!res.ok) throw new Error('Error al obtener importadores'); + const data = await res.json(); + setImporters(Array.isArray(data) ? data : []); + }) + .catch(() => setImporters([])) + .finally(() => setLoading(false)); }, []); const filteredImporters = importers.filter(importer => - importer.name.toLowerCase().includes(searchTerm.toLowerCase()) || - importer.rfc.toLowerCase().includes(searchTerm.toLowerCase()) || - importer.email.toLowerCase().includes(searchTerm.toLowerCase()) + (importer.nombre || '').toLowerCase().includes(searchTerm.toLowerCase()) || + (importer.rfc || '').toLowerCase().includes(searchTerm.toLowerCase()) ); - // Cálculos de paginación + // Paginación const totalImporters = filteredImporters.length; - const totalPages = Math.ceil(totalImporters / itemsPerPage); + const totalPages = Math.ceil(totalImporters / itemsPerPage) || 1; const startIndex = (currentPage - 1) * itemsPerPage; const endIndex = startIndex + itemsPerPage; const currentImporters = filteredImporters.slice(startIndex, endIndex); - // Reset página cuando cambia el filtro - useEffect(() => { - setCurrentPage(1); - }, [searchTerm]); + useEffect(() => { setCurrentPage(1); }, [searchTerm]); - const handlePageChange = (page) => { + const handlePageChange = (page, e) => { + if (e && typeof e.preventDefault === 'function') e.preventDefault(); + if (e && typeof e.stopPropagation === 'function') e.stopPropagation(); + if (page < 1 || page > totalPages || page === currentPage) return; setCurrentPage(page); + if (typeof document !== 'undefined' && document.activeElement instanceof HTMLElement) { + document.activeElement.blur(); + } }; - - const handleItemsPerPageChange = (newItemsPerPage) => { - setItemsPerPage(newItemsPerPage); - setCurrentPage(1); // Reset a la primera página - }; - - const getStatusBadge = (status) => { - return status === 'Activo' - ? 'bg-success-100 text-success-800 border border-success-200' - : 'bg-danger-100 text-danger-800 border border-danger-200'; - }; - - if (loading) { - return ( -
-
- - - - -

Cargando información de importadores...

-
-
- ); - } + useLayoutEffect(() => { if (focusKeeperRef.current) focusKeeperRef.current.focus(); }, [currentPage]); + const handleItemsPerPageChange = (newItemsPerPage) => { setItemsPerPage(newItemsPerPage); setCurrentPage(1); }; + // No hay status real en el endpoint, así que lo dejamos azul + const getStatusBadge = () => 'bg-blue-100 text-blue-800 border border-blue-200'; return ( -
+
+
- {/* Header */} -
-
-

- Importadores + {/* Header animado y decorativo */} +
+
+ + + +
+
+

+ Importadores + {totalImporters > 0 && ( + + {totalImporters} registros + + )}

-

Gestiona y supervisa las empresas importadoras registradas en el sistema.

+

Gestiona y supervisa las empresas importadoras registradas en el sistema.

+
+ {/* Efectos decorativos de fondo modernos */} +
+
+
+
+
+
+ {/* Partículas flotantes */} +
+
+
+
+ - {/* Stats Cards */} -
-
-
-
-
-
- - - -
-
-
-
-
Total Importadores
-
{importers.length}
-
-
+ {/* Filtros y acciones */} +
+
+
+
+

+ + + + Filtros de búsqueda +

+
-
-
- -
-
-
-
-
- - - -
-
-
-
-
Activos
-
- {importers.filter(i => i.status === 'Activo').length} -
-
-
-
-
-
- -
-
-
-
-
- - - -
-
-
-
-
Inactivos
-
- {importers.filter(i => i.status === 'Inactivo').length} -
-
-
-
-
-
- -
-
-
-
-
- - - -
-
-
-
-
Total Documentos
-
- {importers.reduce((sum, i) => sum + i.documentsCount, 0)} -
-
-
-
-
-
-
- - {/* Search and Actions */} -
-
-
-
-
-
- - - -
+
+
+ setSearchTerm(e.target.value)} + onChange={e => setSearchTerm(e.target.value)} + placeholder="Buscar importador, RFC, email..." + className="w-full border border-gray-300 rounded-xl px-3 py-2.5 text-sm focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-blue-500 bg-white shadow-sm transition-all duration-200 hover:shadow-md" />
-
- -
- {/* Table */} -
- + {/* Tabla de importadores */} +
+
- - - - - - + + + + + + {currentImporters.map((importer, index) => ( - - + + + + + + - - - - - @@ -321,19 +468,18 @@ export default function Importers() { - {totalPages > 1 && (
-
{[...Array(totalPages)].map((_, index) => { const page = index + 1; const isCurrentPage = page === currentPage; const isNearCurrentPage = Math.abs(page - currentPage) <= 2; const isFirstOrLast = page === 1 || page === totalPages; - if (totalPages <= 7 || isNearCurrentPage || isFirstOrLast) { return ( ); } else if (page === currentPage - 3 || page === currentPage + 3) { return ( - - ... - + ... ); } return null; })}
-
- - Página {currentPage} de {totalPages} - + Página {currentPage} de {totalPages}
-
- {/* Empty state */} + {/* Estado vacío */} {currentImporters.length === 0 && !loading && ( -
+
-
- +
+
-

No se encontraron importadores

-

- {searchTerm ? 'Intenta con otros términos de búsqueda.' : 'Comienza agregando un nuevo importador.'} -

+

No se encontraron importadores

+

{searchTerm ? 'Intenta con otros términos de búsqueda.' : 'Comienza agregando un nuevo importador.'}

{!searchTerm && ( - +
+
+ {/* Contenido del modal */} +
+
+
+ Usuario: + {vucem.usuario} +
+
+ Patente: + {vucem.patente} +
+
+
+ {/* Importadores disponibles */} +
+

Disponibles

+
    + {importadoresDisponibles.length === 0 && ( +
  • Sin importadores
  • + )} + {importadoresDisponibles.map(imp => ( +
  • seleccionarImportador(imp)} + > + + + {imp.rfc} + + {imp.nombre} + + +
  • + ))} +
+
+ {/* Importadores seleccionados */} +
+

Seleccionados

+
    + {importadoresSeleccionados.length === 0 && ( +
  • Sin seleccionados
  • + )} + {importadoresSeleccionados.map(imp => ( +
  • quitarImportador(imp)} + > + + + + {imp.rfc} + + {imp.nombre} + +
  • + ))} +
+
+
+
+ +
+
+ +
+
+ ); +} import React, { useEffect, useState } from 'react'; import { fetchWithAuth, postWithAuth, putWithAuth, deleteWithAuth, putFormDataWithAuth, postFormDataWithAuth, patchWithAuth } from '../fetchWithAuth'; const API_URL = import.meta.env.VITE_EFC_API_URL; export default function Vucem() { + // Estado para modal de relacionar importadores + const [showRelacionarModal, setShowRelacionarModal] = useState(false); + const [selectedVucem, setSelectedVucem] = useState(null); const [vucemList, setVucemList] = useState([]); const [loading, setLoading] = useState(true); const [error, setError] = useState(null); @@ -292,6 +497,14 @@ export default function Vucem() { // Table y header estilo Users.jsx return (
+ {/* Modal Relacionar Importadores */} + {showRelacionarModal && selectedVucem && ( + setShowRelacionarModal(false)} + vucem={selectedVucem} + /> + )} {/* Header modernizado con gradientes azules */}
@@ -638,6 +851,18 @@ export default function Vucem() {
- Importador - - RFC - - Estado - - Documentos - - Última Actividad - - Acciones - RFCNombreOrganizaciónCreadoActualizadoAcciones
-
-
-
- - - +
{importer.rfc}{importer.nombre || Sin nombre}{importer.organizacion}{importer.created_at ? new Date(importer.created_at).toLocaleString() : ''}{importer.updated_at ? new Date(importer.updated_at).toLocaleString() : ''} +
+ + + + {/* Modal moved outside table for valid JSX and Users.jsx style will be applied below */} + {/* MODALS: Styled like Users.jsx, rendered outside the table for valid JSX */} + {modalOpen && ( +
+
+ {/* Modal Header */} +
+
+
+
+ {modalMode === 'delete' ? ( + + + + ) : ( + + + + )} +
+
+

+ {modalMode === 'delete' ? 'Eliminar Importador' : modalMode === 'edit' ? 'Editar Importador' : 'Ver Importador'} +

+

+ {modalMode === 'delete' ? 'Esta acción no se puede deshacer.' : 'Sistema de Gestión de Importadores'} +

+
+
+ +
+
+ + {/* Modal Content */} +
+ {modalMode === 'delete' ? ( +
+
+ + + +
+

¿Eliminar Importador?

+
+

+ ¿Estás seguro que deseas eliminar el importador {modalData.nombre} (RFC: {modalData.rfc})? +

+
+
+
+ + + +
+
+

{modalData.nombre}

+

RFC: {modalData.rfc}

+
+
+
+ {errorMsg &&
{errorMsg}
} +
+
+ + +
+
+ ) : ( +
{ + e.preventDefault(); + setModalLoading(true); + setErrorMsg(''); + try { + let res; + if (modalMode === 'edit') { + res = await fetchWithAuth(`${API_URL}/customs/importadores/${modalData.rfc}/`, { + method: 'PUT', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ rfc: modalData.rfc, nombre: modalData.nombre, organizacion: modalData.organizacion }) + }); + if (!res.ok) throw new Error('Error al actualizar importador'); + } else if (modalMode === 'create') { + res = await fetchWithAuth(`${API_URL}/customs/importadores/`, { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ rfc: modalData.rfc, nombre: modalData.nombre, organizacion: modalData.organizacion }) + }); + if (!res.ok) throw new Error('Error al crear importador'); + } + // Refrescar lista + const res2 = await fetchWithAuth(`${API_URL}/customs/importadores/`); + if (!res2.ok) throw new Error('Error al obtener importadores'); + const data = await res2.json(); + setImporters(Array.isArray(data) ? data : []); + closeModal(); + } catch { + setErrorMsg(modalMode === 'create' ? 'Error al crear importador' : 'Error al actualizar importador'); + } finally { + setModalLoading(false); + } + }} className="space-y-5"> +
+ + +
+
+ + +
+
+ + +
+ {errorMsg &&
{errorMsg}
} +
+ {modalMode !== 'view' && ( + + )} + {(modalMode === 'edit' || modalMode === 'create') && ( + + )} +
+
+ )} +
+
-
-
-
{importer.name}
-
{importer.email}
-
- -
- {importer.rfc} - - - {importer.status} - - -
- {importer.documentsCount} - - docs - -
-
- {new Date(importer.lastActivity).toLocaleDateString()} - -
- - - + )}
+
+ +