This commit is contained in:
2025-10-05 22:48:15 -06:00
parent bb0b11edbc
commit d09c99f1a9
6 changed files with 1136 additions and 132 deletions

View File

@@ -2,6 +2,7 @@ import Documents from './pages/Documents';
import Datastage from './pages/Datastage';
import Agenda from './pages/Agenda';
import Vucem from './pages/Vucem';
import Auditor from './pages/Auditor';
import { BrowserRouter, Routes, Route, useLocation } from 'react-router-dom';
import { UserProvider } from './context/UserContext';
import Navbar from './components/Navbar';
@@ -122,6 +123,12 @@ function AppContent() {
<Vucem />
</RequireAuth>
} />
{/* Ruta para Auditor */}
<Route path="/auditor" element={
<RequireAuth>
<Auditor />
</RequireAuth>
} />
</Routes>
</Layout>
);

View File

@@ -100,6 +100,15 @@ export default function Sidebar({ isMobileOpen, onMobileClose }) {
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M12 8v4l3 3m6 0a9 9 0 11-18 0 9 9 0 0118 0z" />
</svg>
)
},
{
name: 'Auditor',
path: '/auditor',
icon: (
<svg className="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2m-3 7h3m-3 4h3m-6-4h.01M9 16h.01" />
</svg>
)
}
]
},

649
src/pages/Auditor.jsx Normal file
View File

@@ -0,0 +1,649 @@
import React, { useState, useEffect } from 'react';
import { getWithAuth, postWithAuth } from '../fetchWithAuth';
const API_URL = import.meta.env.VITE_EFC_API_URL;
function Auditor() {
const [loading, setLoading] = useState(false);
const [error, setError] = useState('');
const [pedimentos, setPedimentos] = useState([]);
const [showInstructions, setShowInstructions] = useState(false);
const [count, setCount] = useState(0);
const [page, setPage] = useState(1);
const [itemsPerPage] = useState(10);
const [auditando, setAuditando] = useState(false);
const [auditandoPartidas, setAuditandoPartidas] = useState(false);
const [auditandoRemesas, setAuditandoRemesas] = useState(false);
const [procesandoPedimento, setProcesandoPedimento] = useState(null);
const [procesandoRemesa, setProcesandoRemesa] = useState(null);
const handleAuditarRemesaPedimento = async (pedimentoId) => {
if (procesandoRemesa) return;
try {
setProcesandoRemesa(pedimentoId);
const response = await postWithAuth(`${API_URL}/customs/auditor/auditar-procesamiento-remesa/pedimento/`, {
pedimento_id: pedimentoId
});
if (!response.ok) {
throw new Error('Error al procesar las remesas del pedimento');
}
// Mostrar mensaje de éxito
alert('Las remesas del pedimento se están procesando');
} catch (error) {
console.error('Error:', error);
alert(error.message);
} finally {
setProcesandoRemesa(null);
}
};
const handleAuditarRemesas = async () => {
if (auditandoRemesas) return;
try {
setAuditandoRemesas(true);
// Obtener el organizacion_id del primer pedimento
const organizacionId = pedimentos[0]?.organizacion;
if (!organizacionId) {
throw new Error('No hay organización disponible para auditar');
}
const response = await postWithAuth(`${API_URL}/customs/auditor/auditar-procesamiento-remesas/`, {
organizacion_id: organizacionId
});
if (!response.ok) {
throw new Error('Error al iniciar la auditoría de remesas');
}
// Mostrar mensaje de éxito
alert('La auditoría de remesas se ha iniciado correctamente');
} catch (error) {
console.error('Error:', error);
alert(error.message);
} finally {
setAuditandoRemesas(false);
}
};
const handleAuditarPartidasPedimento = async (pedimentoId) => {
if (procesandoPedimento) return;
try {
setProcesandoPedimento(pedimentoId);
const response = await postWithAuth(`${API_URL}/customs/auditor/crear-partidas/pedimento/`, {
pedimento_id: pedimentoId
});
if (!response.ok) {
throw new Error('Error al procesar las partidas del pedimento');
}
// Mostrar mensaje de éxito
alert('Las partidas del pedimento se están procesando');
} catch (error) {
console.error('Error:', error);
alert(error.message);
} finally {
setProcesandoPedimento(null);
}
};
const handleAuditarPartidas = async () => {
if (auditandoPartidas) return;
try {
setAuditandoPartidas(true);
// Obtener el organizacion_id del primer pedimento
const organizacionId = pedimentos[0]?.organizacion;
if (!organizacionId) {
throw new Error('No hay organización disponible para auditar');
}
const response = await postWithAuth(`${API_URL}/customs/auditor/crear-partidas/organizacion/`, {
organizacion_id: organizacionId
});
if (!response.ok) {
throw new Error('Error al iniciar la auditoría de partidas');
}
// Mostrar mensaje de éxito
alert('La auditoría de partidas se ha iniciado correctamente');
} catch (error) {
console.error('Error:', error);
alert(error.message);
} finally {
setAuditandoPartidas(false);
}
};
const handleAuditarTodos = async () => {
if (auditando) return;
try {
setAuditando(true);
// Obtener el organizacion_id del primer pedimento
const organizacionId = pedimentos[0]?.organizacion;
if (!organizacionId) {
throw new Error('No hay organización disponible para auditar');
}
const response = await postWithAuth(`${API_URL}/customs/auditor/auditar-pedimentos/`, {
organizacion_id: organizacionId
});
if (!response.ok) {
throw new Error('Error al iniciar la auditoría');
}
// Mostrar mensaje de éxito
alert('La auditoría se ha iniciado correctamente');
} catch (error) {
console.error('Error:', error);
alert(error.message);
} finally {
setAuditando(false);
}
};
useEffect(() => {
const fetchPedimentos = async () => {
setLoading(true);
try {
const response = await getWithAuth(`${API_URL}/customs/pedimentos/?page=${page}&page_size=${itemsPerPage}`);
if (!response.ok) throw new Error('Error al cargar los pedimentos');
const data = await response.json();
setPedimentos(data.results);
setCount(data.count);
} catch (err) {
setError(err.message);
} finally {
setLoading(false);
}
};
fetchPedimentos();
}, [page, itemsPerPage]);
return (
<div className="min-h-screen bg-gradient-to-br from-slate-50 via-blue-50 to-indigo-100 p-4 sm:p-6 lg:p-8">
<div className="max-w-7xl mx-auto">
{/* Header mejorado y responsivo */}
<div className="mb-6 sm:mb-8 relative overflow-hidden rounded-3xl shadow-2xl bg-gradient-to-r from-blue-600 via-blue-700 to-blue-800 p-6 sm:p-8 flex items-center gap-4 sm:gap-6 animate-fadein-slideup opacity-0"
style={{ animation: 'fadein-slideup 0.7s cubic-bezier(0.22,1,0.36,1) 0.05s forwards' }}>
<div className="flex-shrink-0 bg-white/20 backdrop-blur-sm rounded-full p-3 sm:p-4 shadow-lg animate-bounce-slow">
<svg className="h-8 w-8 sm:h-10 sm:w-10 text-white" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2m-3 7h3m-3 4h3m-6-4h.01M9 16h.01" />
</svg>
</div>
<div className="flex-1 min-w-0">
<h1 className="text-2xl sm:text-3xl lg:text-4xl font-extrabold text-white tracking-tight mb-1">
Panel de Auditoría
</h1>
<div className="flex items-center gap-4">
<p className="text-sm sm:text-lg text-blue-100 font-medium leading-relaxed">
Monitoreo y control de operaciones
</p>
<button
onClick={() => setShowInstructions(true)}
className="inline-flex items-center px-3 py-1 border border-transparent text-sm leading-4 font-medium rounded-md text-blue-700 bg-blue-100 hover:bg-blue-200 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500 transition-colors duration-200"
>
<svg className="mr-1.5 h-4 w-4" 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>
Instrucciones
</button>
</div>
</div>
{/* Efectos decorativos de fondo */}
<div className="absolute -top-10 -right-10 opacity-20 pointer-events-none select-none">
<div className="w-32 h-32 bg-white/10 rounded-full blur-xl"></div>
</div>
<div className="absolute -bottom-6 -left-6 opacity-15 pointer-events-none select-none">
<div className="w-24 h-24 bg-white/10 rounded-full blur-lg"></div>
</div>
{/* Partículas flotantes */}
<div className="absolute inset-0 overflow-hidden pointer-events-none">
<div className="absolute top-1/4 left-1/4 w-2 h-2 bg-white/30 rounded-full animate-ping"></div>
<div className="absolute top-3/4 right-1/3 w-1 h-1 bg-white/40 rounded-full animate-pulse"></div>
<div className="absolute top-1/2 right-1/4 w-3 h-3 bg-white/20 rounded-full animate-bounce"></div>
</div>
</div>
{/* Contenido principal */}
<div className="bg-white rounded-3xl shadow-2xl border border-gray-100 p-4 sm:p-6 lg:p-8 animate-fadein-slideup opacity-0"
style={{ animation: 'fadein-slideup 0.7s cubic-bezier(0.22,1,0.36,1) 0.15s forwards' }}>
{loading ? (
<div className="flex flex-col items-center justify-center py-12">
<div className="relative">
<div className="animate-spin rounded-full h-16 w-16 border-b-2 border-blue-600"></div>
<div className="absolute inset-0 bg-blue-500/10 rounded-full blur-xl animate-pulse"></div>
</div>
<p className="mt-4 text-gray-600 font-medium">Cargando datos...</p>
</div>
) : error ? (
<div className="bg-red-50 border border-red-200 rounded-2xl p-6 text-center">
<div className="bg-red-100 rounded-full p-3 w-12 h-12 mx-auto mb-4 flex items-center justify-center">
<svg className="w-6 h-6 text-red-600" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M12 8v4m0 4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z" />
</svg>
</div>
<h3 className="text-lg font-semibold text-red-800 mb-2">Error al cargar</h3>
<p className="text-red-600">{error}</p>
</div>
) : (
<div className="space-y-6">
<div className="bg-white rounded-2xl p-4 sm:p-6 border border-gray-200 shadow-sm">
<div className="flex justify-between items-center mb-6">
<h3 className="text-xl font-semibold text-gray-900 flex items-center gap-2">
<svg className="w-6 h-6 text-indigo-600" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2m-3 7h3m-3 4h3m-6-4h.01M9 16h.01" />
</svg>
Servicios de Auditoría
</h3>
<div className="flex gap-4">
<button
onClick={handleAuditarTodos}
disabled={auditando || auditandoPartidas || pedimentos.length === 0}
className={`inline-flex items-center px-4 py-2 rounded-lg shadow-sm text-white transition-all duration-200
${auditando || auditandoPartidas
? 'bg-gray-400 cursor-not-allowed'
: 'bg-emerald-600 hover:bg-emerald-700 hover:shadow-md transform hover:scale-105'
}
`}
>
{auditando ? (
<>
<svg className="animate-spin -ml-1 mr-3 h-5 w-5 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>
Auditando...
</>
) : (
<>
<svg className="mr-2 h-5 w-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2m-3 7h3m-3 4h3m-6-4h.01M9 16h.01" />
</svg>
Auditar Todos los Pedimentos
</>
)}
</button>
<button
onClick={handleAuditarPartidas}
disabled={auditando || auditandoPartidas || pedimentos.length === 0}
className={`inline-flex items-center px-4 py-2 rounded-lg shadow-sm text-white transition-all duration-200
${auditando || auditandoPartidas
? 'bg-gray-400 cursor-not-allowed'
: 'bg-indigo-600 hover:bg-indigo-700 hover:shadow-md transform hover:scale-105'
}
`}
>
{auditandoPartidas ? (
<>
<svg className="animate-spin -ml-1 mr-3 h-5 w-5 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>
Auditando Partidas...
</>
) : (
<>
<svg className="mr-2 h-5 w-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M9 17v-2m3 2v-4m3 4v-6m2 10H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z" />
</svg>
Auditar Partidas
</>
)}
</button>
<button
onClick={handleAuditarRemesas}
disabled={auditando || auditandoRemesas || pedimentos.length === 0}
className={`inline-flex items-center px-4 py-2 rounded-lg shadow-sm text-white transition-all duration-200
${auditando || auditandoRemesas
? 'bg-gray-400 cursor-not-allowed'
: 'bg-purple-600 hover:bg-purple-700 hover:shadow-md transform hover:scale-105'
}
`}
>
{auditandoRemesas ? (
<>
<svg className="animate-spin -ml-1 mr-3 h-5 w-5 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>
Auditando Remesas...
</>
) : (
<>
<svg className="mr-2 h-5 w-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M9 12l2 2 4-4m5.618-4.016A11.955 11.955 0 0112 2.944a11.955 11.955 0 01-8.618 3.04A12.02 12.02 0 003 9c0 5.591 3.824 10.29 9 11.622 5.176-1.332 9-6.03 9-11.622 0-1.042-.133-2.052-.382-3.016z" />
</svg>
Auditar Remesas
</>
)}
</button>
</div>
</div>
{/* Tabla de pedimentos */}
<div className="overflow-x-auto">
<table className="min-w-full divide-y divide-gray-300">
<thead>
<tr>
<th scope="col" className="px-3 py-3.5 text-left text-sm font-semibold text-gray-900">
Pedimento
</th>
<th scope="col" className="px-3 py-3.5 text-center text-sm font-semibold text-gray-900 group">
PC
<span className="block text-xs text-gray-500">Pedimento Completo</span>
</th>
<th scope="col" className="px-3 py-3.5 text-center text-sm font-semibold text-gray-900">
RM
<span className="block text-xs text-gray-500">Remesas</span>
</th>
<th scope="col" className="px-3 py-3.5 text-center text-sm font-semibold text-gray-900">
PT
<span className="block text-xs text-gray-500">Partidas</span>
</th>
<th scope="col" className="px-3 py-3.5 text-center text-sm font-semibold text-gray-900">
AC
<span className="block text-xs text-gray-500">Acuse</span>
</th>
<th scope="col" className="px-3 py-3.5 text-center text-sm font-semibold text-gray-900">
COVE
<span className="block text-xs text-gray-500">Cove</span>
</th>
<th scope="col" className="px-3 py-3.5 text-center text-sm font-semibold text-gray-900">
AC_COVE
<span className="block text-xs text-gray-500">Acuse de Cove</span>
</th>
<th scope="col" className="px-3 py-3.5 text-center text-sm font-semibold text-gray-900">
EDoc
<span className="block text-xs text-gray-500">EDocument</span>
</th>
</tr>
</thead>
<tbody className="divide-y divide-gray-200 bg-white">
{pedimentos.map((pedimento) => (
<tr key={pedimento.id} className="hover:bg-gray-50">
<td className="whitespace-nowrap py-4 pl-4 pr-3 text-sm font-medium text-gray-900">
{pedimento.pedimento_app}
</td>
{/* PC - Pedimento Completo */}
<td className="whitespace-nowrap px-3 py-4 text-sm text-gray-500 text-center">
<button className="inline-flex items-center justify-center w-8 h-8 rounded-full bg-white border border-gray-200 shadow-sm hover:shadow-md focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-green-500 transition-all duration-200 hover:scale-110">
<svg className="h-4 w-4 text-green-600" fill="currentColor" viewBox="0 0 24 24">
<path d="M8 5v14l11-7z"/>
</svg>
</button>
</td>
{/* RM - Remesas */}
<td className="whitespace-nowrap px-3 py-4 text-sm text-gray-500 text-center">
<button
onClick={() => handleAuditarRemesaPedimento(pedimento.id)}
disabled={procesandoRemesa === pedimento.id}
className={`inline-flex items-center justify-center w-8 h-8 rounded-full bg-white border border-gray-200
${procesandoRemesa === pedimento.id
? 'opacity-50 cursor-not-allowed'
: 'shadow-sm hover:shadow-md focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-green-500 transition-all duration-200 hover:scale-110'
}
`}
>
{procesandoRemesa === pedimento.id ? (
<svg className="h-4 w-4 text-gray-600 animate-spin" 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>
) : (
<svg className="h-4 w-4 text-green-600" fill="currentColor" viewBox="0 0 24 24">
<path d="M8 5v14l11-7z"/>
</svg>
)}
</button>
</td>
{/* PT - Partidas */}
<td className="whitespace-nowrap px-3 py-4 text-sm text-gray-500 text-center">
<button
onClick={() => handleAuditarPartidasPedimento(pedimento.id)}
disabled={procesandoPedimento === pedimento.id}
className={`inline-flex items-center justify-center w-8 h-8 rounded-full bg-white border border-gray-200
${procesandoPedimento === pedimento.id
? 'opacity-50 cursor-not-allowed'
: 'shadow-sm hover:shadow-md focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-green-500 transition-all duration-200 hover:scale-110'
}
`}
>
{procesandoPedimento === pedimento.id ? (
<svg className="h-4 w-4 text-gray-600 animate-spin" 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>
) : (
<svg className="h-4 w-4 text-green-600" fill="currentColor" viewBox="0 0 24 24">
<path d="M8 5v14l11-7z"/>
</svg>
)}
</button>
</td>
{/* AC - Acuse */}
<td className="whitespace-nowrap px-3 py-4 text-sm text-gray-500 text-center">
<button className="inline-flex items-center justify-center w-8 h-8 rounded-full bg-white border border-gray-200 shadow-sm hover:shadow-md focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-green-500 transition-all duration-200 hover:scale-110">
<svg className="h-4 w-4 text-green-600" fill="currentColor" viewBox="0 0 24 24">
<path d="M8 5v14l11-7z"/>
</svg>
</button>
</td>
{/* COVE */}
<td className="whitespace-nowrap px-3 py-4 text-sm text-gray-500 text-center">
<button className="inline-flex items-center justify-center w-8 h-8 rounded-full bg-white border border-gray-200 shadow-sm hover:shadow-md focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-green-500 transition-all duration-200 hover:scale-110">
<svg className="h-4 w-4 text-green-600" fill="currentColor" viewBox="0 0 24 24">
<path d="M8 5v14l11-7z"/>
</svg>
</button>
</td>
{/* AC_COVE */}
<td className="whitespace-nowrap px-3 py-4 text-sm text-gray-500 text-center">
<button className="inline-flex items-center justify-center w-8 h-8 rounded-full bg-white border border-gray-200 shadow-sm hover:shadow-md focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-green-500 transition-all duration-200 hover:scale-110">
<svg className="h-4 w-4 text-green-600" fill="currentColor" viewBox="0 0 24 24">
<path d="M8 5v14l11-7z"/>
</svg>
</button>
</td>
{/* EDoc */}
<td className="whitespace-nowrap px-3 py-4 text-sm text-gray-500 text-center">
<button className="inline-flex items-center justify-center w-8 h-8 rounded-full bg-white border border-gray-200 shadow-sm hover:shadow-md focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-green-500 transition-all duration-200 hover:scale-110">
<svg className="h-4 w-4 text-green-600" fill="currentColor" viewBox="0 0 24 24">
<path d="M8 5v14l11-7z"/>
</svg>
</button>
</td>
</tr>
))}
</tbody>
</table>
</div>
{/* Paginación */}
{count > 0 && (
<div className="mt-4 flex items-center justify-between">
<div className="flex-1 flex justify-between sm:hidden">
<button
onClick={() => setPage(page => Math.max(1, page - 1))}
disabled={page === 1}
className="relative inline-flex items-center px-4 py-2 border border-gray-300 text-sm font-medium rounded-md text-gray-700 bg-white hover:bg-gray-50"
>
Anterior
</button>
<button
onClick={() => setPage(page => page + 1)}
disabled={page >= Math.ceil(count / itemsPerPage)}
className="ml-3 relative inline-flex items-center px-4 py-2 border border-gray-300 text-sm font-medium rounded-md text-gray-700 bg-white hover:bg-gray-50"
>
Siguiente
</button>
</div>
<div className="hidden sm:flex-1 sm:flex sm:items-center sm:justify-between">
<div>
<p className="text-sm text-gray-700">
Mostrando <span className="font-medium">{((page - 1) * itemsPerPage) + 1}</span> a{' '}
<span className="font-medium">{Math.min(page * itemsPerPage, count)}</span> de{' '}
<span className="font-medium">{count}</span> resultados
</p>
</div>
<div>
<nav className="relative z-0 inline-flex rounded-md shadow-sm -space-x-px">
<button
onClick={() => setPage(1)}
disabled={page === 1}
className="relative inline-flex items-center px-2 py-2 rounded-l-md border border-gray-300 bg-white text-sm font-medium text-gray-500 hover:bg-gray-50"
>
<span className="sr-only">Primera</span>
««
</button>
<button
onClick={() => setPage(page => Math.max(1, page - 1))}
disabled={page === 1}
className="relative inline-flex items-center px-2 py-2 border border-gray-300 bg-white text-sm font-medium text-gray-500 hover:bg-gray-50"
>
<span className="sr-only">Anterior</span>
«
</button>
<span className="relative inline-flex items-center px-4 py-2 border border-gray-300 bg-white text-sm font-medium text-gray-700">
Página {page}
</span>
<button
onClick={() => setPage(page => page + 1)}
disabled={page >= Math.ceil(count / itemsPerPage)}
className="relative inline-flex items-center px-2 py-2 border border-gray-300 bg-white text-sm font-medium text-gray-500 hover:bg-gray-50"
>
<span className="sr-only">Siguiente</span>
»
</button>
<button
onClick={() => setPage(Math.ceil(count / itemsPerPage))}
disabled={page >= Math.ceil(count / itemsPerPage)}
className="relative inline-flex items-center px-2 py-2 rounded-r-md border border-gray-300 bg-white text-sm font-medium text-gray-500 hover:bg-gray-50"
>
<span className="sr-only">Última</span>
»»
</button>
</nav>
</div>
</div>
</div>
)}
</div>
</div>
)}
</div>
</div>
{/* Modal de Instrucciones */}
{showInstructions && (
<div className="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center p-4 z-50">
<div className="bg-white rounded-lg max-w-2xl w-full shadow-xl transform transition-all">
<div className="border-b px-6 py-4 flex items-center justify-between">
<h3 className="text-xl font-semibold text-gray-900 flex items-center gap-2">
<svg className="w-6 h-6 text-blue-600" 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>
Instrucciones de Auditoría
</h3>
<button
onClick={() => setShowInstructions(false)}
className="text-gray-400 hover:text-gray-500 focus:outline-none"
>
<svg className="h-6 w-6" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M6 18L18 6M6 6l12 12" />
</svg>
</button>
</div>
<div className="px-6 py-4 space-y-6">
<div className="space-y-4">
<div>
<h4 className="text-lg font-medium text-gray-900 flex items-center gap-2 mb-2">
<svg className="w-5 h-5 text-blue-500" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2" />
</svg>
Auditar Todos los Pedimentos
</h4>
<p className="text-gray-600 ml-7">Este proceso revisará uno a uno cada XML del pedimento completo y agregará los campos restantes en nuestra tabla de pedimentos. Si el documento tiene COVEs y E-Docs, también los subirá a nuestra base de datos.</p>
</div>
<div>
<h4 className="text-lg font-medium text-gray-900 flex items-center gap-2 mb-2">
<svg className="w-5 h-5 text-indigo-500" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M9 17v-2m3 2v-4m3 4v-6m2 10H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z" />
</svg>
Auditar Partidas
</h4>
<p className="text-gray-600 ml-7">Creará todas las partidas de cada uno de los pedimentos, extrayendo la información detallada de cada una.</p>
</div>
<div>
<h4 className="text-lg font-medium text-gray-900 flex items-center gap-2 mb-2">
<svg className="w-5 h-5 text-purple-500" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M9 12l2 2 4-4m5.618-4.016A11.955 11.955 0 0112 2.944a11.955 11.955 0 01-8.618 3.04A12.02 12.02 0 003 9c0 5.591 3.824 10.29 9 11.622 5.176-1.332 9-6.03 9-11.622 0-1.042-.133-2.052-.382-3.016z" />
</svg>
Auditar Remesas
</h4>
<p className="text-gray-600 ml-7">Obtendrá y agregará todos los COVEs existentes en el documento a nuestra base de datos.</p>
</div>
</div>
</div>
<div className="bg-gray-50 px-6 py-4 rounded-b-lg">
<button
onClick={() => setShowInstructions(false)}
className="w-full inline-flex justify-center items-center px-4 py-2 border border-transparent text-sm font-medium rounded-md text-white bg-blue-600 hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500"
>
Entendido
</button>
</div>
</div>
</div>
)}
{/* Animaciones CSS */}
<style>{`
@keyframes bounce-slow {
0%, 100% { transform: translateY(0) scale(1); }
50% { transform: translateY(-8px) scale(1.05); }
}
.animate-bounce-slow {
animation: bounce-slow 3s infinite;
}
@keyframes fadein-slideup {
0% { opacity: 0; transform: translateY(40px); }
100% { opacity: 1; transform: translateY(0); }
}
.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) translateY(10px); }
to { opacity: 1; transform: scale(1) translateY(0); }
}
.animate-fade-in {
animation: fade-in 0.8s ease-out;
}
`}</style>
</div>
);
}
export default Auditor;

View File

@@ -511,7 +511,6 @@ export default function Documents() {
<td className="px-6 py-4 whitespace-nowrap align-middle text-gray-700">{doc.extension}</td>
<td className="px-6 py-4 whitespace-nowrap text-center align-middle">
<button
className="group inline-flex items-center justify-center w-9 h-9 rounded-lg border transition-all duration-200 transform hover:scale-110 focus:outline-none focus:ring-2 focus:ring-offset-1 bg-blue-50 border-blue-200 hover:bg-blue-100 hover:border-blue-300 focus:ring-blue-500 cursor-pointer shadow-sm hover:shadow-md"
className="group inline-flex items-center justify-center w-9 h-9 rounded-lg border transition-all duration-200 transform hover:scale-110 focus:outline-none focus:ring-2 focus:ring-offset-1 bg-blue-50 border-blue-200 hover:bg-blue-100 hover:border-blue-300 focus:ring-blue-500 cursor-pointer shadow-sm hover:shadow-md"
title="Descargar"
onClick={async () => {

View File

@@ -124,6 +124,7 @@ export default function PedimentoDetail() {
const [selected, setSelected] = useState([]);
const [downloading, setDownloading] = useState(false);
const [downloadingAll, setDownloadingAll] = useState(false);
const [dashboardSummary, setDashboardSummary] = useState(null);
const [showFilters, setShowFilters] = useState(false);
// Filtros simplificados de documentos (nuevo diseño)
@@ -205,6 +206,27 @@ export default function PedimentoDetail() {
const [processingEdoc, setProcessingEdoc] = useState(null);
const [processingAcuseEdoc, setProcessingAcuseEdoc] = useState(null);
// Función para obtener el resumen del dashboard
const fetchDashboardSummary = async () => {
try {
if (!pedimento) return;
const url = `${import.meta.env.VITE_EFC_API_URL}/reports/dashboard/summary/?organizacion_id=${pedimento.organizacion}&pedimento_app=${pedimento.pedimento_app}`;
const res = await fetchWithAuth(url);
if (!res.ok) {
throw new Error('Error al obtener el resumen del dashboard');
}
const data = await res.json();
setDashboardSummary(data);
} catch (err) {
console.error('Error obteniendo el resumen del dashboard:', err);
showMessage('Error al obtener el resumen del dashboard: ' + err.message, 'error');
}
};
// Función para obtener credenciales VUCEM
const fetchCredenciales = async (contribuyente) => {
try {
@@ -385,6 +407,9 @@ export default function PedimentoDetail() {
// Función para cambiar de pestaña
const handleTabChange = (tab) => {
setActiveTab(tab);
if (tab === 'auditor') {
fetchDashboardSummary();
}
};
// Handler SPA para paginación
@@ -3623,6 +3648,8 @@ export default function PedimentoDetail() {
Análisis Completo
</span>
</div>
{/* Botones de Auditoría */}
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 gap-2 sm:gap-3 w-full lg:w-auto">
@@ -3720,63 +3747,255 @@ export default function PedimentoDetail() {
{/* Contenido del Auditor */}
<div className="space-y-6">
{/* Resumen de Estado */}
<div className="bg-gradient-to-r from-green-50 to-blue-50 rounded-xl p-6 border border-green-200">
<h4 className="text-lg font-semibold text-gray-900 mb-4 flex items-center">
<svg className="w-5 h-5 mr-2 text-green-600" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<div className="bg-gradient-to-b from-white to-gray-50 rounded-xl p-6 border border-gray-200 shadow-lg">
<h4 className="text-lg font-semibold text-gray-900 mb-6 flex items-center border-b pb-4">
<svg className="w-5 h-5 mr-2 text-blue-600" 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>
Resumen de Estado del Pedimento
</h4>
{dashboardSummary ? (
<>
{/* Indicador de Cumplimiento Principal */}
<div className="mb-8">
<div className="flex items-center justify-between mb-2">
<span className="text-sm font-medium text-gray-600">Cumplimiento Total</span>
<span className="text-2xl font-bold text-blue-600">{dashboardSummary.cumplimiento_total.toFixed(1)}%</span>
</div>
<div className="w-full bg-gray-200 rounded-full h-2.5">
<div
className="bg-blue-600 h-2.5 rounded-full transition-all duration-500"
style={{ width: `${dashboardSummary.cumplimiento_total}%` }}
/>
</div>
</div>
{/* Grid de Estadísticas */}
<div className="grid grid-cols-2 lg:grid-cols-3 gap-6">
{/* Pedimentos */}
<div className="flex flex-col bg-white rounded-lg p-4 border border-gray-200 shadow-sm">
<div className="flex items-center justify-between mb-4">
<span className="text-sm font-medium text-gray-600">Pedimentos</span>
<div className="h-8 w-8 rounded-full bg-green-100 flex items-center justify-center">
<svg className="w-4 h-4 text-green-600" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M9 12h6m-6 4h6m2 5H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z" />
</svg>
</div>
</div>
<div className="space-y-2">
<div className="flex justify-between text-sm">
<span className="text-gray-600">Completos</span>
<span className="font-medium">{dashboardSummary.pedimentos.completos}</span>
</div>
<div className="flex justify-between text-sm">
<span className="text-gray-600">Pendientes</span>
<span className="font-medium text-yellow-600">{dashboardSummary.pedimentos.pendientes}</span>
</div>
<div className="pt-2 border-t">
<div className="flex justify-between items-center">
<span className="text-xs text-gray-500">Cumplimiento</span>
<span className="text-sm font-semibold text-green-600">{dashboardSummary.pedimentos.cumplimiento.toFixed(1)}%</span>
</div>
<div className="w-full bg-gray-100 rounded-full h-1.5 mt-1">
<div className="bg-green-500 h-1.5 rounded-full" style={{ width: `${dashboardSummary.pedimentos.cumplimiento}%` }} />
</div>
</div>
</div>
</div>
{/* COVEs */}
<div className="flex flex-col bg-white rounded-lg p-4 border border-gray-200 shadow-sm">
<div className="flex items-center justify-between mb-4">
<span className="text-sm font-medium text-gray-600">COVEs</span>
<div className="h-8 w-8 rounded-full bg-purple-100 flex items-center justify-center">
<svg className="w-4 h-4 text-purple-600" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M9 12h6m-6 4h6m2 5H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z" />
</svg>
</div>
</div>
<div className="space-y-2">
<div className="flex justify-between text-sm">
<span className="text-gray-600">Procesados</span>
<span className="font-medium">{dashboardSummary?.coves?.coves_procesados || 0}</span>
</div>
<div className="flex justify-between text-sm">
<span className="text-gray-600">Pendientes</span>
<span className="font-medium text-yellow-600">{dashboardSummary?.coves?.coves_pendientes || 0}</span>
</div>
<div className="pt-2 border-t">
<div className="flex justify-between items-center">
<span className="text-xs text-gray-500">Cumplimiento</span>
<span className="text-sm font-semibold text-purple-600">{(dashboardSummary?.coves?.coves_cumplimiento || 0).toFixed(1)}%</span>
</div>
<div className="w-full bg-gray-100 rounded-full h-1.5 mt-1">
<div className="bg-purple-500 h-1.5 rounded-full" style={{ width: `${dashboardSummary?.coves?.coves_cumplimiento || 0}%` }} />
</div>
</div>
</div>
</div>
{/* Acuses de COVEs */}
<div className="flex flex-col bg-white rounded-lg p-4 border border-gray-200 shadow-sm">
<div className="flex items-center justify-between mb-4">
<span className="text-sm font-medium text-gray-600">Acuses de COVEs</span>
<div className="h-8 w-8 rounded-full bg-indigo-100 flex items-center justify-center">
<svg className="w-4 h-4 text-indigo-600" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M5 13l4 4L19 7" />
</svg>
</div>
</div>
<div className="space-y-2">
<div className="flex justify-between text-sm">
<span className="text-gray-600">Procesados</span>
<span className="font-medium">{dashboardSummary?.acuse_coves?.acuse_coves_procesados || 0}</span>
</div>
<div className="flex justify-between text-sm">
<span className="text-gray-600">Pendientes</span>
<span className="font-medium text-yellow-600">{dashboardSummary?.acuse_coves?.acuse_coves_pendientes || 0}</span>
</div>
<div className="pt-2 border-t">
<div className="flex justify-between items-center">
<span className="text-xs text-gray-500">Cumplimiento</span>
<span className="text-sm font-semibold text-indigo-600">{(dashboardSummary?.acuse_coves?.acuse_coves_cumplimiento || 0).toFixed(1)}%</span>
</div>
<div className="w-full bg-gray-100 rounded-full h-1.5 mt-1">
<div className="bg-indigo-500 h-1.5 rounded-full" style={{ width: `${dashboardSummary?.acuse_coves?.acuse_coves_cumplimiento || 0}%` }} />
</div>
</div>
</div>
</div>
{/* E-Documents */}
<div className="flex flex-col bg-white rounded-lg p-4 border border-gray-200 shadow-sm">
<div className="flex items-center justify-between mb-4">
<span className="text-sm font-medium text-gray-600">E-Documents</span>
<div className="h-8 w-8 rounded-full bg-blue-100 flex items-center justify-center">
<svg className="w-4 h-4 text-blue-600" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M9 12h6m-6 4h6m2 5H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z" />
</svg>
</div>
</div>
<div className="space-y-2">
<div className="flex justify-between text-sm">
<span className="text-gray-600">Descargados</span>
<span className="font-medium">{dashboardSummary.edocuments.edocs_descargados}</span>
</div>
<div className="flex justify-between text-sm">
<span className="text-gray-600">Pendientes</span>
<span className="font-medium text-yellow-600">{dashboardSummary.edocuments.edocs_pendientes}</span>
</div>
<div className="pt-2 border-t">
<div className="flex justify-between items-center">
<span className="text-xs text-gray-500">Cumplimiento</span>
<span className="text-sm font-semibold text-blue-600">{dashboardSummary.edocuments.edocs_cumplimiento.toFixed(1)}%</span>
</div>
<div className="w-full bg-gray-100 rounded-full h-1.5 mt-1">
<div className="bg-blue-500 h-1.5 rounded-full" style={{ width: `${dashboardSummary.edocuments.edocs_cumplimiento}%` }} />
</div>
</div>
</div>
</div>
{/* Acuses */}
<div className="flex flex-col bg-white rounded-lg p-4 border border-gray-200 shadow-sm">
<div className="flex items-center justify-between mb-4">
<span className="text-sm font-medium text-gray-600">Acuses</span>
<div className="h-8 w-8 rounded-full bg-indigo-100 flex items-center justify-center">
<svg className="w-4 h-4 text-indigo-600" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M5 13l4 4L19 7" />
</svg>
</div>
</div>
<div className="space-y-2">
<div className="flex justify-between text-sm">
<span className="text-gray-600">Descargados</span>
<span className="font-medium">{dashboardSummary?.acuses?.acuse_descargados || 0}</span>
</div>
<div className="flex justify-between text-sm">
<span className="text-gray-600">Pendientes</span>
<span className="font-medium text-yellow-600">{dashboardSummary?.acuses?.acuses_pendientes || 0}</span>
</div>
<div className="pt-2 border-t">
<div className="flex justify-between items-center">
<span className="text-xs text-gray-500">Cumplimiento</span>
<span className="text-sm font-semibold text-indigo-600">{(dashboardSummary?.acuses?.acuses_cumplimiento || 0).toFixed(1)}%</span>
</div>
<div className="w-full bg-gray-100 rounded-full h-1.5 mt-1">
<div className="bg-indigo-500 h-1.5 rounded-full" style={{ width: `${dashboardSummary?.acuses?.acuses_cumplimiento || 0}%` }} />
</div>
</div>
</div>
</div>
{/* Partidas */}
<div className="flex flex-col bg-white rounded-lg p-4 border border-gray-200 shadow-sm">
<div className="flex items-center justify-between mb-4">
<span className="text-sm font-medium text-gray-600">Partidas</span>
<div className="h-8 w-8 rounded-full bg-orange-100 flex items-center justify-center">
<svg className="w-4 h-4 text-orange-600" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M9 17v-2m3 2v-4m3 4v-6m2 10H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z" />
</svg>
</div>
</div>
<div className="space-y-2">
<div className="flex justify-between text-sm">
<span className="text-gray-600">Descargadas</span>
<span className="font-medium">{dashboardSummary.partidas.partidas_descargadas}</span>
</div>
<div className="flex justify-between text-sm">
<span className="text-gray-600">Pendientes</span>
<span className="font-medium text-yellow-600">{dashboardSummary.partidas.partidas_pendientes}</span>
</div>
<div className="pt-2 border-t">
<div className="flex justify-between items-center">
<span className="text-xs text-gray-500">Cumplimiento</span>
<span className="text-sm font-semibold text-orange-600">{dashboardSummary.partidas.cumplimiento.toFixed(1)}%</span>
</div>
<div className="w-full bg-gray-100 rounded-full h-1.5 mt-1">
<div className="bg-orange-500 h-1.5 rounded-full" style={{ width: `${dashboardSummary.partidas.cumplimiento}%` }} />
</div>
</div>
</div>
</div>
{/* Estadísticas Generales */}
<div className="flex flex-col bg-white rounded-lg p-4 border border-gray-200 shadow-sm">
<div className="flex items-center justify-between mb-4">
<span className="text-sm font-medium text-gray-600">Estadísticas Generales</span>
<div className="h-8 w-8 rounded-full bg-gray-100 flex items-center justify-center">
<svg className="w-4 h-4 text-gray-600" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M9 19v-6a2 2 0 00-2-2H5a2 2 0 00-2 2v6a2 2 0 002 2h2a2 2 0 002-2zm0 0V9a2 2 0 012-2h2a2 2 0 012 2v10m-6 0a2 2 0 002 2h2a2 2 0 002-2m0 0V5a2 2 0 012-2h2a2 2 0 012 2v14a2 2 0 01-2 2h-2a2 2 0 01-2-2z" />
</svg>
</div>
</div>
<div className="space-y-3">
<div className="flex justify-between text-sm">
<span className="text-gray-600">Total Documentos</span>
<span className="font-medium">{dashboardSummary.documentos.descargados}</span>
</div>
<div className="flex justify-between text-sm">
<span className="text-gray-600">Total Remesas</span>
<span className="font-medium">{dashboardSummary.remesas.total}</span>
</div>
<div className="flex justify-between text-sm">
<span className="text-gray-600">Total Partidas</span>
<span className="font-medium">{dashboardSummary.partidas.total}</span>
</div>
</div>
</div>
</div>
</>
) : (
<div className="text-center py-4">
<svg className="mx-auto h-12 w-12 text-gray-400" 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>
<h3 className="mt-2 text-sm font-medium text-gray-900">No hay datos disponibles</h3>
<p className="mt-1 text-sm text-gray-500">Intente recargar la página o contacte a soporte si el problema persiste.</p>
</div>
)}
<div className="grid grid-cols-2 sm:grid-cols-2 lg:grid-cols-4 gap-3 sm:gap-4">
<div className="bg-white rounded-lg p-4 shadow-sm border">
<div className="flex items-center justify-between">
<div>
<p className="text-sm font-medium text-gray-600">Documentos</p>
<p className="text-2xl font-bold text-blue-600">{docsCount || 0}</p>
</div>
<svg className="w-8 h-8 text-blue-500" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M9 12h6m-6 4h6m2 5H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z" />
</svg>
</div>
</div>
<div className="bg-white rounded-lg p-4 shadow-sm border">
<div className="flex items-center justify-between">
<div>
<p className="text-sm font-medium text-gray-600">COVEs</p>
<p className="text-2xl font-bold text-purple-600">{covesCount || 0}</p>
</div>
<svg className="w-8 h-8 text-purple-500" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M9 17V7m0 10a2 2 0 01-2 2H5a2 2 0 01-2-2V7a2 2 0 012-2h2a2 2 0 012 2m0 10a2 2 0 002 2h2a2 2 0 002-2M9 7a2 2 0 012-2h2a2 2 0 012 2m0 0V17m0-10a2 2 0 012-2h2a2 2 0 002-2M13 7h2a2 2 0 012 2v6a2 2 0 01-2 2h-2v4" />
</svg>
</div>
</div>
<div className="bg-white rounded-lg p-4 shadow-sm border">
<div className="flex items-center justify-between">
<div>
<p className="text-sm font-medium text-gray-600">EDocs</p>
<p className="text-2xl font-bold text-teal-600">{edocsCount || 0}</p>
</div>
<svg className="w-8 h-8 text-teal-500" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M12 10v6m0 0l-3-3m3 3l3-3m2 8H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z" />
</svg>
</div>
</div>
<div className="bg-white rounded-lg p-4 shadow-sm border">
<div className="flex items-center justify-between">
<div>
<p className="text-sm font-medium text-gray-600">Procesos</p>
<p className="text-2xl font-bold text-orange-600">{procesosCount || 0}</p>
</div>
<svg className="w-8 h-8 text-orange-500" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M9 5H7a2 2 0 00-2 2v10a2 2 0 002 2h8a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2m-6 9l2 2 4-4" />
</svg>
</div>
</div>
</div>
</div>
{/* Análisis de Integridad */}
@@ -3788,36 +4007,193 @@ export default function PedimentoDetail() {
Análisis de Integridad
</h4>
<div className="space-y-4">
<div className="flex items-center justify-between p-4 bg-green-50 border border-green-200 rounded-lg">
<div className="flex items-center">
<svg className="w-5 h-5 text-green-600 mr-3" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M5 13l4 4L19 7" />
</svg>
<span className="font-medium text-green-800">Documentos Requeridos</span>
</div>
<span className="text-green-600 font-semibold">Completo</span>
</div>
<div className="flex items-center justify-between p-4 bg-blue-50 border border-blue-200 rounded-lg">
<div className="flex items-center">
<svg className="w-5 h-5 text-blue-600 mr-3" 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>
<span className="font-medium text-blue-800">Validación de Estructura</span>
</div>
<span className="text-blue-600 font-semibold">Verificado</span>
</div>
<div className="flex items-center justify-between p-4 bg-purple-50 border border-purple-200 rounded-lg">
<div className="flex items-center">
<svg className="w-5 h-5 text-purple-600 mr-3" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M12 15v2m-6 4h12a2 2 0 002-2v-6a2 2 0 00-2-2H6a2 2 0 00-2 2v6a2 2 0 002 2zm10-10V7a4 4 0 00-8 0v4h8z" />
</svg>
<span className="font-medium text-purple-800">Seguridad y Firmas</span>
</div>
<span className="text-purple-600 font-semibold">Auditado</span>
</div>
<div className="space-y-6">
{dashboardSummary && (
<>
{/* Estado General del Pedimento */}
<div className="flex items-center justify-between p-4 bg-gray-50 border border-gray-200 rounded-lg">
<div className="flex items-center space-x-4">
<div className={`p-2 rounded-full ${
dashboardSummary.cumplimiento_total >= 100 ? 'bg-green-100' :
dashboardSummary.cumplimiento_total >= 75 ? 'bg-yellow-100' :
'bg-red-100'
}`}>
<svg className={`w-6 h-6 ${
dashboardSummary.cumplimiento_total >= 100 ? 'text-green-600' :
dashboardSummary.cumplimiento_total >= 75 ? 'text-yellow-600' :
'text-red-600'
}`} fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2"
d={dashboardSummary.cumplimiento_total >= 100
? "M5 13l4 4L19 7" // check mark
: dashboardSummary.cumplimiento_total >= 75
? "M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-3L13.732 4c-.77-1.333-2.694-1.333-3.464 0L3.34 16c-.77 1.333.192 3 1.732 3z" // warning
: "M12 8v4m0 4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z" // error
}
/>
</svg>
</div>
<div>
<h5 className="font-medium text-gray-900">Estado del Pedimento</h5>
<p className="text-sm text-gray-500">
{dashboardSummary.cumplimiento_total >= 100
? 'Todos los documentos y validaciones están completos'
: dashboardSummary.cumplimiento_total >= 75
? 'El pedimento requiere atención en algunos aspectos'
: 'Se requiere atención urgente en varios aspectos'}
</p>
</div>
</div>
<div className="text-right">
<span className={`inline-flex items-center px-3 py-1 rounded-full text-sm font-medium ${
dashboardSummary.cumplimiento_total >= 100 ? 'bg-green-100 text-green-800' :
dashboardSummary.cumplimiento_total >= 75 ? 'bg-yellow-100 text-yellow-800' :
'bg-red-100 text-red-800'
}`}>
{dashboardSummary.cumplimiento_total.toFixed(1)}% Completado
</span>
</div>
</div>
{/* Análisis Detallado */}
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
{/* Documentos Base */}
<div className={`p-4 rounded-lg border ${
dashboardSummary.pedimentos.cumplimiento >= 100 ? 'bg-green-50 border-green-200' : 'bg-yellow-50 border-yellow-200'
}`}>
<div className="flex items-start">
<svg className={`w-5 h-5 mt-0.5 mr-3 ${
dashboardSummary.pedimentos.cumplimiento >= 100 ? 'text-green-600' : 'text-yellow-600'
}`} fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M9 12h6m-6 4h6m2 5H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z" />
</svg>
<div>
<h6 className={`font-medium ${
dashboardSummary.pedimentos.cumplimiento >= 100 ? 'text-green-800' : 'text-yellow-800'
}`}>Documentos Base</h6>
<p className="text-sm text-gray-600 mt-1">
{`${dashboardSummary.pedimentos.completos} de ${dashboardSummary.pedimentos.total} documentos procesados`}
</p>
</div>
</div>
</div>
{/* Validación de COVEs */}
<div className={`p-4 rounded-lg border ${
(dashboardSummary.coves.coves_cumplimiento >= 100 &&
dashboardSummary.acuse_coves.acuse_coves_cumplimiento >= 100) ?
'bg-green-50 border-green-200' : 'bg-yellow-50 border-yellow-200'
}`}>
<div className="flex items-start">
<svg className={`w-5 h-5 mt-0.5 mr-3 ${
(dashboardSummary.coves.coves_cumplimiento >= 100 &&
dashboardSummary.acuse_coves.acuse_coves_cumplimiento >= 100) ?
'text-green-600' : 'text-yellow-600'
}`} 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>
<div>
<h6 className={`font-medium ${
(dashboardSummary.coves.coves_cumplimiento >= 100 &&
dashboardSummary.acuse_coves.acuse_coves_cumplimiento >= 100) ?
'text-green-800' : 'text-yellow-800'
}`}>Validación de COVEs</h6>
<div className="space-y-1 mt-1">
<p className="text-sm text-gray-600">
{`COVEs: ${dashboardSummary.coves.coves_procesados} de ${dashboardSummary.coves.total} procesados`}
</p>
<p className="text-sm text-gray-600">
{`Acuses: ${dashboardSummary.acuse_coves.acuse_coves_procesados} de ${dashboardSummary.acuse_coves.total} procesados`}
</p>
</div>
</div>
</div>
</div>
{/* E-Documents y Acuses */}
<div className={`p-4 rounded-lg border ${
(dashboardSummary.edocuments.edocs_cumplimiento >= 100 &&
dashboardSummary.acuses.acuses_cumplimiento >= 100) ?
'bg-green-50 border-green-200' : 'bg-yellow-50 border-yellow-200'
}`}>
<div className="flex items-start">
<svg className={`w-5 h-5 mt-0.5 mr-3 ${
(dashboardSummary.edocuments.edocs_cumplimiento >= 100 &&
dashboardSummary.acuses.acuses_cumplimiento >= 100) ?
'text-green-600' : 'text-yellow-600'
}`} fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M8 7v8a2 2 0 002 2h6M8 7V5a2 2 0 012-2h4.586a1 1 0 01.707.293l4.414 4.414a1 1 0 01.293.707V15a2 2 0 01-2 2h-2M8 7H6a2 2 0 00-2 2v10a2 2 0 002 2h8a2 2 0 002-2v-2" />
</svg>
<div>
<h6 className={`font-medium ${
(dashboardSummary.edocuments.edocs_cumplimiento >= 100 &&
dashboardSummary.acuses.acuses_cumplimiento >= 100) ?
'text-green-800' : 'text-yellow-800'
}`}>E-Documents y Acuses</h6>
<div className="space-y-1 mt-1">
<p className="text-sm text-gray-600">
{`E-Docs: ${dashboardSummary.edocuments.edocs_descargados} de ${dashboardSummary.edocuments.total} descargados`}
</p>
<p className="text-sm text-gray-600">
{`Acuses: ${dashboardSummary.acuses.acuse_descargados} de ${dashboardSummary.acuses.total} descargados`}
</p>
</div>
</div>
</div>
</div>
{/* Partidas */}
<div className={`p-4 rounded-lg border ${
dashboardSummary.partidas.cumplimiento >= 100 ? 'bg-green-50 border-green-200' : 'bg-yellow-50 border-yellow-200'
}`}>
<div className="flex items-start">
<svg className={`w-5 h-5 mt-0.5 mr-3 ${
dashboardSummary.partidas.cumplimiento >= 100 ? 'text-green-600' : 'text-yellow-600'
}`} fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M9 17v-2m3 2v-4m3 4v-6m2 10H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z" />
</svg>
<div>
<h6 className={`font-medium ${
dashboardSummary.partidas.cumplimiento >= 100 ? 'text-green-800' : 'text-yellow-800'
}`}>Validación de Partidas</h6>
<p className="text-sm text-gray-600 mt-1">
{`${dashboardSummary.partidas.partidas_descargadas} de ${dashboardSummary.partidas.total} partidas procesadas`}
</p>
</div>
</div>
</div>
</div>
{/* Recomendaciones */}
{(dashboardSummary.cumplimiento_total < 100) && (
<div className="mt-4 p-4 bg-blue-50 border border-blue-200 rounded-lg">
<h6 className="font-medium text-blue-800 mb-2 flex items-center">
<svg className="w-5 h-5 mr-2 text-blue-600" 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>
Recomendaciones
</h6>
<ul className="space-y-2 text-sm text-blue-900">
{dashboardSummary.coves.coves_cumplimiento < 100 && (
<li> Procesar los COVEs pendientes ({dashboardSummary.coves.coves_pendientes} restantes)</li>
)}
{dashboardSummary.acuse_coves.acuse_coves_cumplimiento < 100 && (
<li> Obtener los acuses de COVEs faltantes ({dashboardSummary.acuse_coves.acuse_coves_pendientes} restantes)</li>
)}
{dashboardSummary.edocuments.edocs_cumplimiento < 100 && (
<li> Completar la descarga de E-Documents ({dashboardSummary.edocuments.edocs_pendientes} pendientes)</li>
)}
{dashboardSummary.acuses.acuses_cumplimiento < 100 && (
<li> Obtener los acuses de E-Documents ({dashboardSummary.acuses.acuses_pendientes} pendientes)</li>
)}
{dashboardSummary.partidas.cumplimiento < 100 && (
<li> Procesar las partidas pendientes ({dashboardSummary.partidas.partidas_pendientes} restantes)</li>
)}
</ul>
</div>
)}
</>
)}
</div>
</div>
@@ -3829,52 +4205,16 @@ export default function PedimentoDetail() {
</svg>
Timeline de Actividades Recientes
</h4>
<div className="space-y-4">
<div className="flex items-start">
<div className="flex-shrink-0">
<div className="w-8 h-8 bg-green-100 rounded-full flex items-center justify-center">
<svg className="w-4 h-4 text-green-600" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M5 13l4 4L19 7" />
</svg>
</div>
</div>
<div className="ml-4 flex-1">
<p className="text-sm font-medium text-gray-900">Pedimento procesado correctamente</p>
<p className="text-sm text-gray-500">Todos los documentos han sido validados y procesados</p>
<p className="text-xs text-gray-400 mt-1">{new Date().toLocaleDateString('es-ES')}</p>
</div>
</div>
<div className="flex items-start">
<div className="flex-shrink-0">
<div className="w-8 h-8 bg-blue-100 rounded-full flex items-center justify-center">
<svg className="w-4 h-4 text-blue-600" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M9 12h6m-6 4h6m2 5H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z" />
</svg>
</div>
</div>
<div className="ml-4 flex-1">
<p className="text-sm font-medium text-gray-900">Documentos cargados</p>
<p className="text-sm text-gray-500">Se han cargado {docsCount || 0} documentos al expediente</p>
<p className="text-xs text-gray-400 mt-1">{pedimento?.created_at ? new Date(pedimento.created_at).toLocaleDateString('es-ES') : 'Fecha no disponible'}</p>
</div>
</div>
<div className="flex items-start">
<div className="flex-shrink-0">
<div className="w-8 h-8 bg-purple-100 rounded-full flex items-center justify-center">
<svg className="w-4 h-4 text-purple-600" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M12 6v6m0 0v6m0-6h6m-6 0H6" />
</svg>
</div>
</div>
<div className="ml-4 flex-1">
<p className="text-sm font-medium text-gray-900">Expediente creado</p>
<p className="text-sm text-gray-500">Expediente #{pedimento?.pedimento_app || 'N/A'} iniciado</p>
<p className="text-xs text-gray-400 mt-1">{pedimento?.created_at ? new Date(pedimento.created_at).toLocaleDateString('es-ES') : 'Fecha no disponible'}</p>
</div>
<div className="flex flex-col items-center justify-center py-6">
<div className="w-16 h-16 bg-blue-100 rounded-full flex items-center justify-center mb-4">
<svg className="w-8 h-8 text-blue-600 animate-spin" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M4 4v5h.582m15.356 2A8.001 8.001 0 004.582 9m0 0H9m11 11v-5h-.581m0 0a8.003 8.003 0 01-15.357-2m15.357 2H15" />
</svg>
</div>
<p className="text-lg font-medium text-gray-900">Trabajando en ello</p>
<p className="text-sm text-gray-500 mt-2">Estamos preparando el historial de actividades</p>
<p className="text-sm text-blue-600 mt-4">Esta funcionalidad estará disponible próximamente</p>
</div>
</div>
</div>

View File

@@ -266,7 +266,7 @@ export default function TableroAlmacenamiento() {
<div className="text-2xl font-bold text-blue-700">{summary.edocuments?.total ?? '-'}</div>
<div className="grid grid-cols-2 gap-2 mt-2 text-xs">
<div>
<span className="block text-slate-500">Descargados</span>
<span className="block text-slate-500">asd</span>
<span className="block font-semibold">{summary.edocuments?.edocs_descargados ?? '-'}</span>
</div>
<div>
@@ -291,17 +291,17 @@ export default function TableroAlmacenamiento() {
<div className="grid grid-cols-2 gap-2 text-xs">
<div>
<span className="block text-slate-500">Descargados</span>
<span className="block font-semibold">{summary.edocuments?.acuse_descargados ?? '-'}</span>
<span className="block font-semibold">{summary.edocuments.acuse_descargados ?? '-'}</span>
</div>
<div>
<span className="block text-slate-500">Pendientes</span>
<span className="block font-semibold">{summary.edocuments?.acuses_pendientes ?? '-'}</span>
<span className="block font-semibold">{summary.edocuments.acuses_pendientes ?? '-'}</span>
</div>
</div>
<div className="w-full bg-slate-100 rounded h-2 mt-2">
<div
className="bg-blue-400 h-2 rounded"
style={{ width: `${summary.edocuments?.acuses_cumplimiento ?? 0}%` }}
style={{ width: `${summary.edocuments.acuses_cumplimiento ?? 0}%` }}
/>
</div>
<span className="block text-xs text-right text-blue-400 font-semibold mt-1">