16 Commits

Author SHA1 Message Date
Dulce
5a0e9a8259 actualizar el formulario de vucem para permitir agregar organizacion 2026-01-16 09:54:04 -07:00
dfddb418d0 Merge pull request 'modificacion reportes' (#14) from correccion-reportes into main
Reviewed-on: #14
2025-12-16 15:14:24 +00:00
Dulce
d46ea97340 modificacion reportes 2025-12-16 08:13:00 -07:00
29e0f1a68d Merge pull request 'Fix: se ajusta campo de fuente del pedimento detalle.' (#13) from T2025-10-021 into main
Reviewed-on: #13
2025-12-16 14:33:24 +00:00
3f01709952 Fix: se ajusta campo de fuente del pedimento detalle. 2025-12-16 07:24:40 -07:00
5580c3cc91 Merge pull request 'fix: Se agrega deteccion de pestaña seleccionada para mostrarla en el modal de documentos.' (#12) from T2025-10-152 into main
Reviewed-on: #12
2025-12-12 22:00:39 +00:00
bb59d1e487 Merge pull request 'fix/landingpage-ui-adjustment' (#11) from fix/landingpage-ui-adjustment into main
Reviewed-on: #11
2025-12-12 22:00:01 +00:00
769a1fd4e8 fix: Se agrega deteccion de pestaña seleccionada para mostrarla en el modal de documentos. 2025-12-10 11:18:19 -07:00
06ec641691 feat/Landingpage 2025-12-09 16:22:46 -07:00
a91ef2f11a style: update landing page 2025-12-08 10:17:23 -07:00
70f0b38e93 fix/Se modifica vista principal de detalle de pedimento y se ajusta la visualizacion de los documentos respectivos a cada pestaña. 2025-12-05 08:22:45 -07:00
d8c23dcf09 Remove .env from Git tracking 2025-11-26 08:59:23 -07:00
97a39a6d37 Merge pull request 'Subo archivo JSX sin afectar rama principal' (#10) from fixed/subir-expediente-jsx into main
Reviewed-on: #10
2025-11-26 15:52:56 +00:00
756815983d Subo archivo JSX sin afectar rama principal 2025-11-26 08:48:50 -07:00
33ca76c054 Merge pull request 'retirar debug mode' (#9) from reportes into main
Reviewed-on: #9
2025-11-26 14:48:22 +00:00
bdabc94974 Merge pull request 'reportes update' (#8) from reportes into main
Reviewed-on: #8
2025-11-25 22:24:39 +00:00
10 changed files with 2126 additions and 762 deletions

5
.env
View File

@@ -1,5 +0,0 @@
VITE_DEBUG_MODE=true
VITE_EFC_API_URL=http://192.168.1.79:8000/api/v1
VITE_EFC_MICROSERVICE_URL=http://192.168.1.79:8001/api/v1
VITE_EFC_MICROSERVICE_URL_2=http://192.168.1.79:8001/api/v2

1
.gitignore vendored
View File

@@ -23,3 +23,4 @@ dist-ssr
*.sln *.sln
*.sw? *.sw?
.env .env
*.bak

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 MiB

View File

@@ -0,0 +1,68 @@
// src\api\pedimentoCompleto.ts
import { fetchWithAuth } from '../fetchWithAuth';
export interface PedimentoCompleto {
id: string;
organizacion: string;
pedimento: string;
pedimento_numero: string;
archivo: string;
document_type: number;
size: number;
extension: string;
fuente: number;
created_at: string;
updated_at: string;
}
export interface PedimentoCompletoResponse {
count: number;
next: string | null;
previous: string | null;
results: PedimentoCompleto[];
}
export interface DocumentFilters {
document_type?: string;
archivo__icontains?: string;
extension?: string;
created_at__date?: string;
ordering?: string;
}
const API_URL = (import.meta as any).env.VITE_EFC_API_URL;
export async function fetchPedimentoCompleto(
pedimentoId: string,
page: number = 1,
pageSize: number = 10,
filters: DocumentFilters = {}
): Promise<PedimentoCompletoResponse> {
try {
// Construir URL con filtros
const params = new URLSearchParams({
page: page.toString(),
page_size: pageSize.toString(),
pedimento: pedimentoId
});
// Agregar filtros si existen
Object.entries(filters).forEach(([key, value]) => {
if (value !== undefined && value !== '') {
params.append(key, value.toString());
}
});
// const res = await fetchWithAuth(`${API_URL}/record/documents/?${params.toString()}`);
const res = await fetchWithAuth(`${API_URL}/record/pedimento-documents/?${params.toString()}`);
if (!res.ok) {
throw new Error('No autorizado o error en la petición');
}
return res.json();
} catch (error) {
console.error('Error in fetchPedimentoCompleto:', error);
throw error;
}
}

View File

@@ -18,11 +18,13 @@ const API_URL = import.meta.env.VITE_EFC_API_URL;
const downloadFile = async (id, filename = 'archivo', setSuccess, setError, showMessage) => { const downloadFile = async (id, filename = 'archivo', setSuccess, setError, showMessage) => {
try { try {
console.log('Descargar: ',id);
const res = await fetchWithAuth(`${API_URL}/record/documents/descargar/${id}/`); const res = await fetchWithAuth(`${API_URL}/record/documents/descargar/${id}/`);
if (!res.ok) { if (!res.ok) {
alert('No autorizado o error en la descarga'); //alert('No autorizado o error en la descarga');
return; showMessage('No autorizado o error en la descarga.', 'error');
return false;
} }
const blob = await res.blob(); const blob = await res.blob();
@@ -38,7 +40,9 @@ const downloadFile = async (id, filename = 'archivo', setSuccess, setError, show
} catch (error) { } catch (error) {
console.error('Error downloading file:', error); console.error('Error downloading file:', error);
showMessage('Error al descargar el archivo', 'error'); showMessage('Error al descargar el archivo', 'error');
return false;
} }
return true;
}; };
export default function Documents() { export default function Documents() {
@@ -192,6 +196,40 @@ export default function Documents() {
} }
}; };
// // Función para descargar documentos seleccionados
// const handleDownloadSelected = async () => {
// if (selectedDocuments.length === 0) {
// showMessage('No hay documentos seleccionados para descargar', 'warning');
// return;
// }
// try {
// showMessage(`Iniciando descarga de ${selectedDocuments.length} documento(s)...`, 'info');
// const resultados = [];
// for (const docId of selectedDocuments) {
// const document = currentDocuments.find(doc => doc.id === docId);
// if (document) {
// // const exito =await downloadFile(docId, `expediente_${document.pedimento_app}`, null, null, showMessage);
// const exito =await downloadExpediente(docId, `expediente_${document.pedimento_app}`, null, showMessage);
// resultados.push(exito);
// // Pequeña pausa entre descargas para no sobrecargar
// await new Promise(resolve => setTimeout(resolve, 500));
// }
// }
// const todosExitosos = resultados.every(Boolean);
// if(todosExitosos){
// showMessage(`Descarga completada de ${selectedDocuments.length} documento(s)`, 'success');
// }else{
// showMessage('Algunas descargas fallaron. Revisa los archivos seleccionados.', 'warning');
// }
// setSelectedDocuments([]);
// setIsSelectAll(false);
// } catch (error) {
// showMessage('Error durante la descarga masiva', 'error');
// }
// // showMessage('Error durante la descarga masiva', 'error');
// };
// Función para descargar documentos seleccionados // Función para descargar documentos seleccionados
const handleDownloadSelected = async () => { const handleDownloadSelected = async () => {
if (selectedDocuments.length === 0) { if (selectedDocuments.length === 0) {
@@ -200,18 +238,61 @@ export default function Documents() {
} }
try { try {
showMessage(`Iniciando descarga de ${selectedDocuments.length} documento(s)...`, 'info');
for (const docId of selectedDocuments) { showMessage(`Iniciando descarga de ${selectedDocuments.length} documento(s) selecciobado(s)...`, 'info');
const document = currentDocuments.find(doc => doc.id === docId);
const res = await postWithAuth(`${API_URL}/record/documents/multi-pedimento-zip/`, {
pedimento_ids: selectedDocuments
});
if (!res.ok){
showMessage('Algunas descargas fallaron. Revisa los archivos seleccionados.', 'warning');
}else {
// Leer el nombre que eligió el backend
const zipFileName = res.headers.get('X-Zip-Filename') || 'expedientes.zip';
const blob = await res.blob();
const url = window.URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = zipFileName;
a.click();
a.remove();
window.URL.revokeObjectURL(url);
showMessage(`Descarga completada de ${selectedDocuments.length} documento(s)`, 'success');
setSelectedDocuments([]);
setIsSelectAll(false);
}
} catch (error) {
showMessage('Error durante la descarga masiva', 'error');
}
// showMessage('Error durante la descarga masiva', 'error');
};
// Función para descargar todo el expediente
const handleDownloadTodoElExpediente = async (pedimentoId, pedimentoName) => {
try {
showMessage(`Iniciando descarga de ${selectedDocuments.length} documento(s)...`, 'info');
const resultados = [];
const document = currentDocuments.find(doc => doc.id === pedimentoId);
if (document) { if (document) {
await downloadFile(docId, `expediente_${document.pedimento_app}`, null, null, showMessage); const exito =await downloadExpediente(pedimentoId, `expediente_${pedimentoName}`, null, showMessage);
resultados.push(exito);
// Pequeña pausa entre descargas para no sobrecargar // Pequeña pausa entre descargas para no sobrecargar
await new Promise(resolve => setTimeout(resolve, 500)); await new Promise(resolve => setTimeout(resolve, 500));
} }
const todosExitosos = resultados.every(Boolean);
if(todosExitosos){
showMessage(`Descarga completada de ${selectedDocuments.length} documento(s)`, 'success');
}else{
// showMessage('Algunas descargas fallaron. Revisa los archivos seleccionados.', 'warning');
} }
showMessage(`Descarga completada de ${selectedDocuments.length} documento(s)`, 'success');
setSelectedDocuments([]); setSelectedDocuments([]);
setIsSelectAll(false); setIsSelectAll(false);
} catch (error) { } catch (error) {
@@ -219,6 +300,38 @@ export default function Documents() {
} }
}; };
const downloadExpediente = async (pedimentoId, pedimentoName, setSuccess, showMessage) => {
try {
const res = await postWithAuth(`${API_URL}/record/documents/expediente-zip/`, {
pedimento_id: pedimentoId,
});
if (!res.ok) {
// alert('No autorizado o error en la descarga');
// showMessage('No autorizado o error en la descarga.', 'error');
const err = await res.json();
showMessage(err.error || 'Error al generar ZIP', 'error');
return false;
}
const blob = await res.blob();
const url = window.URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = `${pedimentoName}.zip`;
// document.body.appendChild(a);
a.click();
a.remove();
window.URL.revokeObjectURL(url);
if (setSuccess) setSuccess('Descarga exitosa');
} catch (error) {
console.error('Error downloading file:', error);
showMessage('Error al descargar el archivo', 'error');
return false;
}
return true;
};
// Función para eliminar documentos seleccionados // Función para eliminar documentos seleccionados
const handleDeleteSelected = async () => { const handleDeleteSelected = async () => {
if (selectedDocuments.length === 0) { if (selectedDocuments.length === 0) {
@@ -485,10 +598,30 @@ export default function Documents() {
const uploadEndpoint = `${API_URL}/customs/pedimentos/bulk-create/`; const uploadEndpoint = `${API_URL}/customs/pedimentos/bulk-create/`;
const result = await postFormDataWithAuth(uploadEndpoint, formData); const result = await postFormDataWithAuth(uploadEndpoint, formData);
if(!result.ok){
let errorMsg = 'Error al intentar cargar los archivos';
try {
const errorData = await result.json();
errorMsg = errorData.error || errorMsg;
} catch {
// Si no es JSON válido, usar texto plano
const text = await result.text();
errorMsg = text || errorMsg;
}
showMessage(errorMsg, 'error');
}else{
showMessage( showMessage(
`${result.uploaded_count || fileCount} archivo(s) subido(s) exitosamente`, `${result.uploaded_count || fileCount} archivo(s) subido(s) exitosamente`,
'success' 'success'
); );
}
// showMessage(
// `${result.uploaded_count || fileCount} archivo(s) subido(s) exitosamente`,
// 'success'
// );
// console.log(result);
// Limpiar archivos seleccionados y cerrar modal // Limpiar archivos seleccionados y cerrar modal
setSelectedFiles([]); setSelectedFiles([]);
@@ -522,47 +655,60 @@ export default function Documents() {
setIsSelectAll(false); setIsSelectAll(false);
}, [currentPage]); }, [currentPage]);
// función que detecta si hay filtros activos
const hasActiveFilters = [
searchFilter,
pedimentoFilter,
expedienteFilter !== 'all',
contribuyenteFilter,
curpApoderadoFilter,
fechaPagoFilter,
patenteFilter,
aduanaFilter,
tipoOperacionFilter,
clavePedimentoFilter,
].some(Boolean);
// El layout principal y la tabla siempre se renderizan, loader/error/empty solo dentro del área de la tabla // El layout principal y la tabla siempre se renderizan, loader/error/empty solo dentro del área de la tabla
return ( 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="min-h-screen p-4 bg-gradient-to-br from-slate-50 via-blue-50 to-indigo-100 sm:p-6 lg:p-8">
<div ref={focusKeeperRef} tabIndex={-1} style={{position:'absolute',width:0,height:0,overflow:'hidden',outline:'none'}} aria-hidden="true"></div> <div ref={focusKeeperRef} tabIndex={-1} style={{position:'absolute',width:0,height:0,overflow:'hidden',outline:'none'}} aria-hidden="true"></div>
<div className="max-w-7xl mx-auto"> <div className="mx-auto max-w-7xl">
{/* Header mejorado y decorativo */} {/* Header mejorado y decorativo */}
<div className={ <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"+ "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"+
(showAnimation && !hasAnimated ? ' animate-fadein-slideup opacity-0' : '') (showAnimation && !hasAnimated ? ' animate-fadein-slideup opacity-0' : '')
} }
style={showAnimation && !hasAnimated ? { animation: 'fadein-slideup 0.7s cubic-bezier(0.22,1,0.36,1) 0.05s forwards' } : undefined}> style={showAnimation && !hasAnimated ? { animation: 'fadein-slideup 0.7s cubic-bezier(0.22,1,0.36,1) 0.05s forwards' } : undefined}>
<div className="flex-shrink-0 bg-white/20 backdrop-blur-sm rounded-full p-3 sm:p-4 shadow-lg animate-bounce-slow"> <div className="flex-shrink-0 p-3 rounded-full shadow-lg bg-white/20 backdrop-blur-sm sm:p-4 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"> <svg className="w-8 h-8 text-white sm:h-10 sm:w-10" 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" /> <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> </svg>
</div> </div>
<div className="flex-1 min-w-0"> <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 flex flex-col sm:flex-row sm:items-center gap-2"> <h1 className="flex flex-col gap-2 mb-1 text-2xl font-extrabold tracking-tight text-white sm:text-3xl lg:text-4xl sm:flex-row sm:items-center">
<span>Expedientes</span> <span>Expedientes</span>
{totalDocuments > 0 && ( {totalDocuments > 0 && (
<span className="inline-block bg-white/20 backdrop-blur-sm text-white text-xs sm:text-sm font-semibold px-3 py-1 rounded-full shadow-lg animate-fade-in"> <span className="inline-block px-3 py-1 text-xs font-semibold text-white rounded-full shadow-lg bg-white/20 backdrop-blur-sm sm:text-sm animate-fade-in">
{totalDocuments} registros {totalDocuments} registros
</span> </span>
)} )}
</h1> </h1>
<p className="text-sm sm:text-lg text-blue-100 font-medium leading-relaxed">Gestiona y descarga los documentos de tus pedimentos</p> <p className="text-sm font-medium leading-relaxed text-blue-100 sm:text-lg">Gestiona y descarga los documentos de tus pedimentos</p>
</div> </div>
{/* Efectos decorativos de fondo modernos */} {/* Efectos decorativos de fondo modernos */}
<div className="absolute -top-10 -right-10 opacity-20 pointer-events-none select-none"> <div className="absolute pointer-events-none select-none -top-10 -right-10 opacity-20">
<div className="w-32 h-32 bg-white/10 rounded-full blur-xl"></div> <div className="w-32 h-32 rounded-full bg-white/10 blur-xl"></div>
</div> </div>
<div className="absolute -bottom-6 -left-6 opacity-15 pointer-events-none select-none"> <div className="absolute pointer-events-none select-none -bottom-6 -left-6 opacity-15">
<div className="w-24 h-24 bg-white/10 rounded-full blur-lg"></div> <div className="w-24 h-24 rounded-full bg-white/10 blur-lg"></div>
</div> </div>
{/* Partículas flotantes */} {/* Partículas flotantes */}
<div className="absolute inset-0 overflow-hidden pointer-events-none"> <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 w-2 h-2 rounded-full top-1/4 left-1/4 bg-white/30 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 w-1 h-1 rounded-full top-3/4 right-1/3 bg-white/40 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 className="absolute w-3 h-3 rounded-full top-1/2 right-1/4 bg-white/20 animate-bounce"></div>
</div> </div>
</div> </div>
{/* Animación personalizada para el icono y contador */} {/* Animación personalizada para el icono y contador */}
@@ -588,16 +734,16 @@ export default function Documents() {
(showAnimation && !hasAnimated ? ' animate-fadein-slideup opacity-0' : '') (showAnimation && !hasAnimated ? ' animate-fadein-slideup opacity-0' : '')
} }
style={showAnimation && !hasAnimated ? { animation: 'fadein-slideup 0.7s cubic-bezier(0.22,1,0.36,1) 0.15s forwards' } : undefined}> style={showAnimation && !hasAnimated ? { animation: 'fadein-slideup 0.7s cubic-bezier(0.22,1,0.36,1) 0.15s forwards' } : undefined}>
<div className="px-4 sm:px-6 py-4 sm:py-6 border-b border-gray-200 bg-gradient-to-r from-gray-50 to-blue-50/30"> <div className="px-4 py-4 border-b border-gray-200 sm:px-6 sm:py-6 bg-gradient-to-r from-gray-50 to-blue-50/30">
{/* Filtros avanzados */} {/* Filtros avanzados */}
<div className="mb-4 sm:mb-6"> <div className="mb-4 sm:mb-6">
<h3 className="text-sm font-semibold text-gray-800 mb-3 flex items-center"> <h3 className="flex items-center mb-3 text-sm font-semibold text-gray-800">
<svg className="w-4 h-4 mr-2 text-blue-600" fill="none" stroke="currentColor" viewBox="0 0 24 24"> <svg className="w-4 h-4 mr-2 text-blue-600" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M3 4a1 1 0 011-1h16a1 1 0 011 1v2.586a1 1 0 01-.293.707l-6.414 6.414a1 1 0 00-.293.707V17l-4 4v-6.586a1 1 0 00-.293-.707L3.293 7.414A1 1 0 013 6.707V4z" /> <path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M3 4a1 1 0 011-1h16a1 1 0 011 1v2.586a1 1 0 01-.293.707l-6.414 6.414a1 1 0 00-.293.707V17l-4 4v-6.586a1 1 0 00-.293-.707L3.293 7.414A1 1 0 013 6.707V4z" />
</svg> </svg>
Filtros de búsqueda Filtros de búsqueda
</h3> </h3>
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 gap-3 sm:gap-4"> <div className="grid grid-cols-1 gap-3 sm:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 sm:gap-4">
{/* Search global */} {/* Search global */}
<div className="flex flex-col"> <div className="flex flex-col">
<label className="text-xs font-semibold text-gray-700 mb-1.5">Buscar</label> <label className="text-xs font-semibold text-gray-700 mb-1.5">Buscar</label>
@@ -631,7 +777,7 @@ export default function Documents() {
</select> </select>
</div> </div>
{/* Contribuyente combobox */} {/* Contribuyente combobox */}
<div className="flex flex-col relative"> <div className="relative flex flex-col">
<label className="text-xs font-semibold text-gray-700 mb-1.5">Contribuyente</label> <label className="text-xs font-semibold text-gray-700 mb-1.5">Contribuyente</label>
<input <input
type="text" type="text"
@@ -649,7 +795,7 @@ export default function Documents() {
/> />
{/* Dropdown de sugerencias */} {/* Dropdown de sugerencias */}
{contribuyenteInput && ( {contribuyenteInput && (
<div className="absolute top-16 left-0 right-0 bg-white border border-gray-200 rounded-xl shadow-2xl z-50 max-h-40 overflow-auto"> <div className="absolute left-0 right-0 z-50 overflow-auto bg-white border border-gray-200 shadow-2xl top-16 rounded-xl max-h-40">
{contribuyentes.filter(c => c.toLowerCase().includes(contribuyenteInput.toLowerCase())).length === 0 ? ( {contribuyentes.filter(c => c.toLowerCase().includes(contribuyenteInput.toLowerCase())).length === 0 ? (
<div className="px-3 py-2 text-sm text-gray-500">Sin coincidencias</div> <div className="px-3 py-2 text-sm text-gray-500">Sin coincidencias</div>
) : ( ) : (
@@ -657,7 +803,7 @@ export default function Documents() {
<button <button
key={c} key={c}
type="button" type="button"
className="w-full text-left px-3 py-2 hover:bg-blue-50 text-sm transition-colors duration-200 first:rounded-t-xl last:rounded-b-xl" className="w-full px-3 py-2 text-sm text-left transition-colors duration-200 hover:bg-blue-50 first:rounded-t-xl last:rounded-b-xl"
onClick={() => { onClick={() => {
setContribuyenteFilter(c); setContribuyenteFilter(c);
setContribuyenteInput(''); setContribuyenteInput('');
@@ -738,11 +884,11 @@ export default function Documents() {
{/* Área de acciones para documentos seleccionados */} {/* Área de acciones para documentos seleccionados */}
{selectedDocuments.length > 0 && ( {selectedDocuments.length > 0 && (
<div className="mb-4 bg-gradient-to-r from-blue-50 to-indigo-50 border border-blue-200 rounded-2xl overflow-hidden"> <div className="mb-4 overflow-hidden border border-blue-200 bg-gradient-to-r from-blue-50 to-indigo-50 rounded-2xl">
<div className="px-6 py-4 border-b border-blue-200"> <div className="px-6 py-4 border-b border-blue-200">
<div className="flex items-center justify-between"> <div className="flex items-center justify-between">
<div className="flex items-center gap-3"> <div className="flex items-center gap-3">
<div className="bg-blue-100 rounded-full p-2"> <div className="p-2 bg-blue-100 rounded-full">
<svg className="w-5 h-5 text-blue-600" fill="none" stroke="currentColor" viewBox="0 0 24 24"> <svg className="w-5 h-5 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" /> <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> </svg>
@@ -759,7 +905,7 @@ export default function Documents() {
setSelectedDocuments([]); setSelectedDocuments([]);
setIsSelectAll(false); setIsSelectAll(false);
}} }}
className="text-gray-400 hover:text-gray-600 transition-colors" className="text-gray-400 transition-colors hover:text-gray-600"
title="Limpiar selección" title="Limpiar selección"
> >
<svg className="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24"> <svg className="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24">
@@ -772,7 +918,7 @@ export default function Documents() {
<div className="flex flex-wrap gap-3"> <div className="flex flex-wrap gap-3">
<button <button
onClick={handleDeleteSelected} onClick={handleDeleteSelected}
className="inline-flex items-center px-4 py-2 bg-red-600 hover:bg-red-700 text-white font-medium rounded-lg transition-colors duration-200 shadow-sm hover:shadow-md" className="inline-flex items-center px-4 py-2 font-medium text-white transition-colors duration-200 bg-red-600 rounded-lg shadow-sm hover:bg-red-700 hover:shadow-md"
> >
<svg className="w-5 h-5 mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24"> <svg className="w-5 h-5 mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 00-1-1h-4a1 1 0 00-1 1v3M4 7h16" /> <path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 00-1-1h-4a1 1 0 00-1 1v3M4 7h16" />
@@ -784,16 +930,16 @@ export default function Documents() {
</div> </div>
)} )}
<div className="flex flex-col sm:flex-row items-start sm:items-center justify-between gap-4"> <div className="flex flex-col items-start justify-between gap-4 sm:flex-row sm:items-center">
<div className="flex items-center gap-3"> <div className="flex items-center gap-3">
<span className="inline-flex items-center text-xs text-blue-600 bg-blue-50 px-3 py-2 rounded-full font-medium"> <span className="inline-flex items-center px-3 py-2 text-xs font-medium text-blue-600 rounded-full bg-blue-50">
<svg className="w-4 h-4 mr-2 animate-pulse" fill="none" stroke="currentColor" viewBox="0 0 24 24"> <svg className="w-4 h-4 mr-2 animate-pulse" 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" /> <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> </svg>
Actualización automática cada 30s Actualización automática cada 30s
</span> </span>
{loading && ( {loading && (
<span className="inline-flex items-center text-xs text-orange-600 bg-orange-50 px-3 py-2 rounded-full font-medium"> <span className="inline-flex items-center px-3 py-2 text-xs font-medium text-orange-600 rounded-full bg-orange-50">
<svg className="w-4 h-4 mr-2 animate-spin" fill="none" viewBox="0 0 24 24"> <svg className="w-4 h-4 mr-2 animate-spin" fill="none" viewBox="0 0 24 24">
<circle className="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" strokeWidth="4"></circle> <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> <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>
@@ -826,21 +972,21 @@ export default function Documents() {
Actualizar Ahora Actualizar Ahora
</button> </button>
<button <button
onClick={() => {}} onClick={() => {handleDownloadSelected()}}
className="inline-flex items-center px-4 py-2.5 border border-transparent text-sm font-medium rounded-xl text-white bg-gradient-to-r from-purple-600 to-purple-700 hover:from-purple-700 hover:to-purple-800 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-purple-500 transition-all duration-200 transform hover:scale-105 shadow-lg" className="inline-flex items-center px-4 py-2.5 border border-transparent text-sm font-medium rounded-xl text-white bg-gradient-to-r from-purple-600 to-purple-700 hover:from-purple-700 hover:to-purple-800 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-purple-500 transition-all duration-200 transform hover:scale-105 shadow-lg"
> >
<svg className="w-4 h-4 mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24"> <svg className="w-4 h-4 mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M4 16v1a3 3 0 003 3h10a3 3 0 003-3v-1m-4-4l-4 4m0 0l-4-4m4 4V4" /> <path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M4 16v1a3 3 0 003 3h10a3 3 0 003-3v-1m-4-4l-4 4m0 0l-4-4m4 4V4" />
</svg> </svg>
Descargar Todos {hasActiveFilters ? 'Descargar por Filtro' : 'Descargar Todos'}
</button> </button>
</div> </div>
</div> </div>
{success && ( {success && (
<div className="mt-4 bg-gradient-to-r from-green-50 to-emerald-50 border border-green-200 rounded-xl p-4 shadow-sm"> <div className="p-4 mt-4 border border-green-200 shadow-sm bg-gradient-to-r from-green-50 to-emerald-50 rounded-xl">
<div className="flex"> <div className="flex">
<div className="flex-shrink-0"> <div className="flex-shrink-0">
<svg className="h-5 w-5 text-green-500" fill="none" stroke="currentColor" viewBox="0 0 24 24"> <svg className="w-5 h-5 text-green-500" 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"></path> <path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z"></path>
</svg> </svg>
</div> </div>
@@ -855,30 +1001,30 @@ export default function Documents() {
<div className="overflow-hidden"> <div className="overflow-hidden">
{/* Vista de tabla para pantallas grandes */} {/* Vista de tabla para pantallas grandes */}
<div className="hidden lg:block"> <div className="hidden lg:block">
<div className="overflow-x-auto shadow ring-1 ring-black ring-opacity-5 rounded-lg"> <div className="overflow-x-auto rounded-lg shadow ring-1 ring-black ring-opacity-5">
<table className="min-w-full divide-y divide-gray-300" style={{ minWidth: '1200px' }}> <table className="min-w-full divide-y divide-gray-300" style={{ minWidth: '1200px' }}>
<thead className="bg-gray-50"> <thead className="bg-gray-50">
<tr> <tr>
<th scope="col" className="px-4 py-2 text-left text-xs font-medium text-gray-500 uppercase tracking-wider"> <th scope="col" className="px-4 py-2 text-xs font-medium tracking-wider text-left text-gray-500 uppercase">
<input <input
type="checkbox" type="checkbox"
checked={isSelectAll} checked={isSelectAll}
onChange={handleSelectAll} onChange={handleSelectAll}
className="h-4 w-4 text-blue-600 focus:ring-blue-500 border-gray-300 rounded" className="w-4 h-4 text-blue-600 border-gray-300 rounded focus:ring-blue-500"
/> />
</th> </th>
<th scope="col" className="px-4 py-2 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Pedimento</th> <th scope="col" className="px-4 py-2 text-xs font-medium tracking-wider text-left text-gray-500 uppercase">Pedimento</th>
<th scope="col" className="px-4 py-2 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Fecha Pago</th> <th scope="col" className="px-4 py-2 text-xs font-medium tracking-wider text-left text-gray-500 uppercase">Fecha Pago</th>
<th scope="col" className="px-4 py-2 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Contribuyente</th> <th scope="col" className="px-4 py-2 text-xs font-medium tracking-wider text-left text-gray-500 uppercase">Contribuyente</th>
<th scope="col" className="px-4 py-2 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">CURP Apod.</th> <th scope="col" className="px-4 py-2 text-xs font-medium tracking-wider text-left text-gray-500 uppercase">CURP Apod.</th>
<th scope="col" className="px-4 py-2 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Partidas</th> <th scope="col" className="px-4 py-2 text-xs font-medium tracking-wider text-left text-gray-500 uppercase">Partidas</th>
<th scope="col" className="px-4 py-2 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">F. Carga</th> <th scope="col" className="px-4 py-2 text-xs font-medium tracking-wider text-left text-gray-500 uppercase">F. Carga</th>
<th scope="col" className="px-4 py-2 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Tipo Op.</th> <th scope="col" className="px-4 py-2 text-xs font-medium tracking-wider text-left text-gray-500 uppercase">Tipo Op.</th>
<th scope="col" className="px-4 py-2 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Clave</th> <th scope="col" className="px-4 py-2 text-xs font-medium tracking-wider text-left text-gray-500 uppercase">Clave</th>
<th scope="col" className="px-4 py-2 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Archivos</th> <th scope="col" className="px-4 py-2 text-xs font-medium tracking-wider text-left text-gray-500 uppercase">Archivos</th>
<th scope="col" className="px-4 py-2 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Peso Total</th> <th scope="col" className="px-4 py-2 text-xs font-medium tracking-wider text-left text-gray-500 uppercase">Peso Total</th>
<th scope="col" className="px-4 py-2 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Expediente</th> <th scope="col" className="px-4 py-2 text-xs font-medium tracking-wider text-left text-gray-500 uppercase">Expediente</th>
<th scope="col" className="px-4 py-2 text-center text-xs font-medium text-gray-500 uppercase tracking-wider">Acciones</th> <th scope="col" className="px-4 py-2 text-xs font-medium tracking-wider text-center text-gray-500 uppercase">Acciones</th>
</tr> </tr>
</thead> </thead>
<tbody className="bg-white divide-y divide-gray-200"> <tbody className="bg-white divide-y divide-gray-200">
@@ -886,8 +1032,8 @@ export default function Documents() {
<tr> <tr>
<td colSpan={12} className="px-6 py-12 text-center"> <td colSpan={12} className="px-6 py-12 text-center">
<div className="flex flex-col items-center"> <div className="flex flex-col items-center">
<div className="animate-spin rounded-full h-12 w-12 border-b-2 border-blue-600 mb-4"></div> <div className="w-12 h-12 mb-4 border-b-2 border-blue-600 rounded-full animate-spin"></div>
<span className="text-gray-500 text-lg font-medium">Cargando expedientes...</span> <span className="text-lg font-medium text-gray-500">Cargando expedientes...</span>
</div> </div>
</td> </td>
</tr> </tr>
@@ -895,12 +1041,12 @@ export default function Documents() {
<tr> <tr>
<td colSpan={12} className="px-6 py-12 text-center"> <td colSpan={12} className="px-6 py-12 text-center">
<div className="flex flex-col items-center"> <div className="flex flex-col items-center">
<div className="bg-red-100 rounded-full p-3 mb-4"> <div className="p-3 mb-4 bg-red-100 rounded-full">
<svg className="h-8 w-8 text-red-600" fill="none" stroke="currentColor" viewBox="0 0 24 24"> <svg className="w-8 h-8 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"></path> <path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M12 8v4m0 4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z"></path>
</svg> </svg>
</div> </div>
<span className="text-red-600 text-lg font-medium">Error: {error.message || 'Error al cargar expedientes'}</span> <span className="text-lg font-medium text-red-600">Error: {error.message || 'Error al cargar expedientes'}</span>
</div> </div>
</td> </td>
</tr> </tr>
@@ -912,22 +1058,22 @@ export default function Documents() {
type="checkbox" type="checkbox"
checked={selectedDocuments.includes(ped.id)} checked={selectedDocuments.includes(ped.id)}
onChange={(e) => handleSelectDocument(ped.id, e.target.checked)} onChange={(e) => handleSelectDocument(ped.id, e.target.checked)}
className="h-4 w-4 text-blue-600 focus:ring-blue-500 border-gray-300 rounded" className="w-4 h-4 text-blue-600 border-gray-300 rounded focus:ring-blue-500"
/> />
</td> </td>
<td className="px-4 py-3 whitespace-nowrap"> <td className="px-4 py-3 whitespace-nowrap">
<Link <Link
to={`/expedientes/pedimento/${ped.id}`} to={`/expedientes/pedimento/${ped.id}`}
className="text-blue-600 hover:text-blue-800 font-semibold transition-colors duration-200 text-xs" className="text-xs font-semibold text-blue-600 transition-colors duration-200 hover:text-blue-800"
> >
{ped.pedimento_app} {ped.pedimento_app}
</Link> </Link>
</td> </td>
<td className="px-4 py-3 whitespace-nowrap text-xs text-gray-900">{ped.fecha_pago}</td> <td className="px-4 py-3 text-xs text-gray-900 whitespace-nowrap">{ped.fecha_pago}</td>
<td className="px-4 py-3 text-xs text-gray-900 max-w-xs truncate" title={ped.contribuyente}>{ped.contribuyente}</td> <td className="max-w-xs px-4 py-3 text-xs text-gray-900 truncate" title={ped.contribuyente}>{ped.contribuyente}</td>
<td className="px-4 py-3 whitespace-nowrap text-xs text-gray-900">{ped.curp_apoderado}</td> <td className="px-4 py-3 text-xs text-gray-900 whitespace-nowrap">{ped.curp_apoderado}</td>
<td className="px-4 py-3 whitespace-nowrap text-xs text-gray-900">{ped.numero_partidas}</td> <td className="px-4 py-3 text-xs text-gray-900 whitespace-nowrap">{ped.numero_partidas}</td>
<td className="px-4 py-3 whitespace-nowrap text-xs text-gray-900">{ped.created_at ? ped.created_at.slice(0, 10) : ''}</td> <td className="px-4 py-3 text-xs text-gray-900 whitespace-nowrap">{ped.created_at ? ped.created_at.slice(0, 10) : ''}</td>
<td className="px-4 py-3 whitespace-nowrap"> <td className="px-4 py-3 whitespace-nowrap">
<span className="inline-flex items-center px-2 py-0.5 rounded-full text-xs font-medium bg-blue-100 text-blue-800"> <span className="inline-flex items-center px-2 py-0.5 rounded-full text-xs font-medium bg-blue-100 text-blue-800">
{ped.tipo_operacion === 1 {ped.tipo_operacion === 1
@@ -937,9 +1083,9 @@ export default function Documents() {
: ped.tipo_operacion} : ped.tipo_operacion}
</span> </span>
</td> </td>
<td className="px-4 py-3 whitespace-nowrap text-xs text-gray-900">{ped.clave_pedimento}</td> <td className="px-4 py-3 text-xs text-gray-900 whitespace-nowrap">{ped.clave_pedimento}</td>
<td className="px-4 py-3 whitespace-nowrap text-xs text-gray-900">{ped.documentos_count || 0}</td> <td className="px-4 py-3 text-xs text-gray-900 whitespace-nowrap">{ped.documentos_count || 0}</td>
<td className="px-4 py-3 whitespace-nowrap text-xs text-gray-900 min-w-0"> <td className="min-w-0 px-4 py-3 text-xs text-gray-900 whitespace-nowrap">
<div className="truncate" title={typeof ped.documentos_peso_total === 'number' ? (ped.documentos_peso_total / (1024 * 1024)).toFixed(2) + ' MB' : ped.documentos_peso_total}> <div className="truncate" title={typeof ped.documentos_peso_total === 'number' ? (ped.documentos_peso_total / (1024 * 1024)).toFixed(2) + ' MB' : ped.documentos_peso_total}>
{typeof ped.documentos_peso_total === 'number' {typeof ped.documentos_peso_total === 'number'
? (ped.documentos_peso_total / (1024 * 1024)).toFixed(2) + ' MB' ? (ped.documentos_peso_total / (1024 * 1024)).toFixed(2) + ' MB'
@@ -969,13 +1115,13 @@ export default function Documents() {
)} )}
</span> </span>
</td> </td>
<td className="px-4 py-3 whitespace-nowrap text-center"> <td className="px-4 py-3 text-center whitespace-nowrap">
<button <button
className="p-2 text-blue-600 hover:text-blue-800 hover:bg-blue-50 rounded-full focus:outline-none focus:ring-2 focus:ring-blue-500 focus:ring-offset-2 transition-colors duration-200" className="p-2 text-blue-600 transition-colors duration-200 rounded-full hover:text-blue-800 hover:bg-blue-50 focus:outline-none focus:ring-2 focus:ring-blue-500 focus:ring-offset-2"
onClick={() => {}} onClick={() => handleDownloadTodoElExpediente(ped.id, ped.pedimento_app)}
title="Descargar" title="Descargar"
> >
<svg className="h-5 w-5" fill="none" stroke="currentColor" viewBox="0 0 24 24"> <svg className="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M4 16v1a3 3 0 003 3h10a3 3 0 003-3v-1m-4-4l-4 4m0 0l-4-4m4 4V4" /> <path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M4 16v1a3 3 0 003 3h10a3 3 0 003-3v-1m-4-4l-4 4m0 0l-4-4m4 4V4" />
</svg> </svg>
</button> </button>
@@ -986,12 +1132,12 @@ export default function Documents() {
<tr> <tr>
<td colSpan={12} className="px-6 py-12 text-center"> <td colSpan={12} className="px-6 py-12 text-center">
<div className="flex flex-col items-center"> <div className="flex flex-col items-center">
<div className="bg-gray-100 rounded-full p-4 mb-4"> <div className="p-4 mb-4 bg-gray-100 rounded-full">
<svg className="h-8 w-8 text-gray-400" fill="none" stroke="currentColor" viewBox="0 0 24 24"> <svg className="w-8 h-8 text-gray-400" 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" /> <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> </svg>
</div> </div>
<h3 className="text-lg font-semibold text-gray-900 mb-2">No hay expedientes</h3> <h3 className="mb-2 text-lg font-semibold text-gray-900">No hay expedientes</h3>
<p className="text-gray-500">No se encontraron expedientes con los filtros aplicados.</p> <p className="text-gray-500">No se encontraron expedientes con los filtros aplicados.</p>
</div> </div>
</td> </td>
@@ -1003,37 +1149,37 @@ export default function Documents() {
</div> </div>
{/* Vista de tarjetas para pantallas pequeñas y medianas */} {/* Vista de tarjetas para pantallas pequeñas y medianas */}
<div className="lg:hidden space-y-4 p-4"> <div className="p-4 space-y-4 lg:hidden">
{loading ? ( {loading ? (
<div className="flex flex-col items-center py-12"> <div className="flex flex-col items-center py-12">
<div className="animate-spin rounded-full h-12 w-12 border-b-2 border-blue-600 mb-4"></div> <div className="w-12 h-12 mb-4 border-b-2 border-blue-600 rounded-full animate-spin"></div>
<span className="text-gray-500 text-lg font-medium">Cargando expedientes...</span> <span className="text-lg font-medium text-gray-500">Cargando expedientes...</span>
</div> </div>
) : error ? ( ) : error ? (
<div className="flex flex-col items-center py-12"> <div className="flex flex-col items-center py-12">
<div className="bg-red-100 rounded-full p-3 mb-4"> <div className="p-3 mb-4 bg-red-100 rounded-full">
<svg className="h-8 w-8 text-red-600" fill="none" stroke="currentColor" viewBox="0 0 24 24"> <svg className="w-8 h-8 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"></path> <path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M12 8v4m0 4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z"></path>
</svg> </svg>
</div> </div>
<span className="text-red-600 text-lg font-medium">Error: {error.message || 'Error al cargar expedientes'}</span> <span className="text-lg font-medium text-red-600">Error: {error.message || 'Error al cargar expedientes'}</span>
</div> </div>
) : currentDocuments.length > 0 ? ( ) : currentDocuments.length > 0 ? (
currentDocuments.map(ped => ( currentDocuments.map(ped => (
<div key={ped.id} className="bg-white rounded-2xl shadow-lg border border-gray-200 p-4 hover:shadow-xl transition-all duration-300 relative"> <div key={ped.id} className="relative p-4 transition-all duration-300 bg-white border border-gray-200 shadow-lg rounded-2xl hover:shadow-xl">
{/* Checkbox en la esquina superior derecha */} {/* Checkbox en la esquina superior derecha */}
<div className="absolute top-4 right-4"> <div className="absolute top-4 right-4">
<input <input
type="checkbox" type="checkbox"
checked={selectedDocuments.includes(ped.id)} checked={selectedDocuments.includes(ped.id)}
onChange={(e) => handleSelectDocument(ped.id, e.target.checked)} onChange={(e) => handleSelectDocument(ped.id, e.target.checked)}
className="h-5 w-5 text-blue-600 focus:ring-blue-500 border-gray-300 rounded" className="w-5 h-5 text-blue-600 border-gray-300 rounded focus:ring-blue-500"
/> />
</div> </div>
<div className="flex items-start justify-between mb-4 pr-8">{/* Agregamos pr-8 para dar espacio al checkbox */} <div className="flex items-start justify-between pr-8 mb-4">{/* Agregamos pr-8 para dar espacio al checkbox */}
<div className="flex items-center gap-3"> <div className="flex items-center gap-3">
<div className="bg-blue-100 rounded-xl p-2 flex-shrink-0"> <div className="flex-shrink-0 p-2 bg-blue-100 rounded-xl">
<svg className="w-5 h-5 text-blue-600" fill="none" stroke="currentColor" viewBox="0 0 24 24"> <svg className="w-5 h-5 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" /> <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> </svg>
@@ -1041,7 +1187,7 @@ export default function Documents() {
<div className="flex-1"> <div className="flex-1">
<Link <Link
to={`/expedientes/pedimento/${ped.id}`} to={`/expedientes/pedimento/${ped.id}`}
className="text-lg font-semibold text-blue-600 hover:text-blue-800 transition-colors duration-200" className="text-lg font-semibold text-blue-600 transition-colors duration-200 hover:text-blue-800"
> >
{ped.pedimento_app} {ped.pedimento_app}
</Link> </Link>
@@ -1057,7 +1203,7 @@ export default function Documents() {
</div> </div>
</div> </div>
<div className="space-y-3 mb-4"> <div className="mb-4 space-y-3">
<div className="flex items-center justify-between"> <div className="flex items-center justify-between">
<span className="text-sm font-medium text-gray-600">Contribuyente:</span> <span className="text-sm font-medium text-gray-600">Contribuyente:</span>
<span className="text-sm text-gray-900 text-right max-w-[60%] truncate" title={ped.contribuyente}> <span className="text-sm text-gray-900 text-right max-w-[60%] truncate" title={ped.contribuyente}>
@@ -1071,27 +1217,27 @@ export default function Documents() {
</div> </div>
)} )}
<div className="grid grid-cols-1 gap-2"> <div className="grid grid-cols-1 gap-2">
<div className="flex items-center justify-between bg-green-50 rounded-lg p-2"> <div className="flex items-center justify-between p-2 rounded-lg bg-green-50">
<span className="text-sm font-medium text-green-700">Tipo Operacion</span> <span className="text-sm font-medium text-green-700">Tipo Operacion</span>
<span className="text-sm font-bold text-green-800">{ped.tipo_operacion}</span> <span className="text-sm font-bold text-green-800">{ped.tipo_operacion}</span>
</div> </div>
<div className="flex items-center justify-between bg-green-50 rounded-lg p-2"> <div className="flex items-center justify-between p-2 rounded-lg bg-green-50">
<span className="text-sm font-medium text-green-700">Partidas</span> <span className="text-sm font-medium text-green-700">Partidas</span>
<span className="text-sm font-bold text-green-800">{ped.numero_partidas}</span> <span className="text-sm font-bold text-green-800">{ped.numero_partidas}</span>
</div> </div>
<div className="flex items-center justify-between bg-blue-50 rounded-lg p-2"> <div className="flex items-center justify-between p-2 rounded-lg bg-blue-50">
<span className="text-sm font-medium text-blue-700">Fecha de Carga</span> <span className="text-sm font-medium text-blue-700">Fecha de Carga</span>
<span className="text-sm font-bold text-blue-800">{ped.created_at ? ped.created_at.slice(0, 10) : ''}</span> <span className="text-sm font-bold text-blue-800">{ped.created_at ? ped.created_at.slice(0, 10) : ''}</span>
</div> </div>
<div className="flex items-center justify-between bg-gray-50 rounded-lg p-2"> <div className="flex items-center justify-between p-2 rounded-lg bg-gray-50">
<span className="text-sm font-medium text-gray-700">Clave</span> <span className="text-sm font-medium text-gray-700">Clave</span>
<span className="text-sm font-bold text-gray-800">{ped.clave_pedimento}</span> <span className="text-sm font-bold text-gray-800">{ped.clave_pedimento}</span>
</div> </div>
<div className="flex items-center justify-between bg-gray-50 rounded-lg p-2"> <div className="flex items-center justify-between p-2 rounded-lg bg-gray-50">
<span className="text-sm font-medium text-gray-700">No. Archivos</span> <span className="text-sm font-medium text-gray-700">No. Archivos</span>
<span className="text-sm font-bold text-gray-800">{ped.documentos_count || 0}</span> <span className="text-sm font-bold text-gray-800">{ped.documentos_count || 0}</span>
</div> </div>
<div className="flex items-center justify-between bg-gray-50 rounded-lg p-2"> <div className="flex items-center justify-between p-2 rounded-lg bg-gray-50">
<span className="text-sm font-medium text-gray-700">Peso Total</span> <span className="text-sm font-medium text-gray-700">Peso Total</span>
<span className="text-sm font-bold text-gray-800"> <span className="text-sm font-bold text-gray-800">
{typeof ped.documentos_peso_total === 'number' {typeof ped.documentos_peso_total === 'number'
@@ -1104,20 +1250,20 @@ export default function Documents() {
</div> </div>
)) ))
) : ( ) : (
<div className="bg-gray-50 rounded-2xl p-8 text-center"> <div className="p-8 text-center bg-gray-50 rounded-2xl">
<div className="bg-gray-100 rounded-full p-4 w-16 h-16 mx-auto mb-4 flex items-center justify-center"> <div className="flex items-center justify-center w-16 h-16 p-4 mx-auto mb-4 bg-gray-100 rounded-full">
<svg className="w-8 h-8 text-gray-400" fill="none" stroke="currentColor" viewBox="0 0 24 24"> <svg className="w-8 h-8 text-gray-400" 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" /> <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> </svg>
</div> </div>
<p className="text-gray-500 font-medium">No hay expedientes disponibles</p> <p className="font-medium text-gray-500">No hay expedientes disponibles</p>
<p className="text-gray-400 text-sm mt-1">Intenta ajustar los filtros de búsqueda</p> <p className="mt-1 text-sm text-gray-400">Intenta ajustar los filtros de búsqueda</p>
</div> </div>
)} )}
</div> </div>
{/* Paginación moderna y responsiva */} {/* Paginación moderna y responsiva */}
{totalDocuments > 0 && ( {totalDocuments > 0 && (
<div className="bg-gradient-to-r from-gray-50 to-blue-50/30 px-4 sm:px-6 py-4 flex flex-col sm:flex-row items-center justify-between border-t border-gray-200"> <div className="flex flex-col items-center justify-between px-4 py-4 border-t border-gray-200 bg-gradient-to-r from-gray-50 to-blue-50/30 sm:px-6 sm:flex-row">
{(() => { {(() => {
const totalPages = Math.max(1, Math.ceil(totalDocuments / itemsPerPage)); const totalPages = Math.max(1, Math.ceil(totalDocuments / itemsPerPage));
const maxPagesToShow = 5; const maxPagesToShow = 5;
@@ -1132,21 +1278,21 @@ export default function Documents() {
pageNumbers.push(i); pageNumbers.push(i);
} }
return ( return (
<div className="flex flex-col sm:flex-row sm:items-center w-full gap-4"> <div className="flex flex-col w-full gap-4 sm:flex-row sm:items-center">
<div className="flex items-center gap-3"> <div className="flex items-center gap-3">
<label htmlFor="itemsPerPage" className="text-xs font-semibold text-gray-700">Registros por página:</label> <label htmlFor="itemsPerPage" className="text-xs font-semibold text-gray-700">Registros por página:</label>
<select <select
id="itemsPerPage" id="itemsPerPage"
value={itemsPerPage} value={itemsPerPage}
onChange={e => handleItemsPerPageChange(Number(e.target.value))} onChange={e => handleItemsPerPageChange(Number(e.target.value))}
className="border border-gray-300 rounded-lg px-3 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-blue-500 bg-white shadow-sm" className="px-3 py-2 text-sm bg-white border border-gray-300 rounded-lg shadow-sm focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-blue-500"
> >
{[5, 10, 20, 50, 100, 200, 400, 800, 1200, 2400, 10000].map(size => ( {[5, 10, 20, 50, 100, 200, 400, 800, 1200, 2400, 10000].map(size => (
<option key={size} value={size}>{size}</option> <option key={size} value={size}>{size}</option>
))} ))}
</select> </select>
</div> </div>
<div className="flex items-center justify-center sm:justify-end flex-1 gap-1"> <div className="flex items-center justify-center flex-1 gap-1 sm:justify-end">
<button <button
type="button" type="button"
onClick={e => handlePageChange(1, e)} onClick={e => handlePageChange(1, e)}
@@ -1163,7 +1309,7 @@ export default function Documents() {
> >
</button> </button>
<div className="hidden sm:flex items-center gap-1"> <div className="items-center hidden gap-1 sm:flex">
{pageNumbers.map(num => ( {pageNumbers.map(num => (
<button <button
type="button" type="button"
@@ -1176,7 +1322,7 @@ export default function Documents() {
</button> </button>
))} ))}
</div> </div>
<div className="sm:hidden flex items-center px-3 py-2 bg-white border border-gray-200 rounded-lg shadow-sm"> <div className="flex items-center px-3 py-2 bg-white border border-gray-200 rounded-lg shadow-sm sm:hidden">
<span className="text-sm font-semibold text-gray-700"> <span className="text-sm font-semibold text-gray-700">
{currentPage} / {totalPages} {currentPage} / {totalPages}
</span> </span>
@@ -1199,7 +1345,7 @@ export default function Documents() {
</button> </button>
</div> </div>
<div className="text-center sm:text-right"> <div className="text-center sm:text-right">
<span className="text-xs text-gray-600 bg-white px-3 py-2 rounded-lg border border-gray-200 shadow-sm"> <span className="px-3 py-2 text-xs text-gray-600 bg-white border border-gray-200 rounded-lg shadow-sm">
Mostrando <span className="font-bold text-blue-600">{((currentPage - 1) * itemsPerPage) + 1}</span> a <span className="font-bold text-blue-600">{Math.min(currentPage * itemsPerPage, totalDocuments)}</span> de <span className="font-bold text-blue-600">{totalDocuments}</span> registros Mostrando <span className="font-bold text-blue-600">{((currentPage - 1) * itemsPerPage) + 1}</span> a <span className="font-bold text-blue-600">{Math.min(currentPage * itemsPerPage, totalDocuments)}</span> de <span className="font-bold text-blue-600">{totalDocuments}</span> registros
</span> </span>
</div> </div>
@@ -1213,13 +1359,13 @@ export default function Documents() {
{/* Modal de subida de expedientes */} {/* Modal de subida de expedientes */}
{showUploadModal && ( {showUploadModal && (
<div className="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center p-4 z-50"> <div className="fixed inset-0 z-50 flex items-center justify-center p-4 bg-black bg-opacity-50">
<div className="bg-white rounded-2xl shadow-2xl max-w-lg w-full mx-4 transform transition-all duration-300 scale-100 max-h-[90vh] flex flex-col"> <div className="bg-white rounded-2xl shadow-2xl max-w-lg w-full mx-4 transform transition-all duration-300 scale-100 max-h-[90vh] flex flex-col">
{/* Header del modal */} {/* Header del modal */}
<div className="px-6 py-4 border-b border-gray-200 flex-shrink-0"> <div className="flex-shrink-0 px-6 py-4 border-b border-gray-200">
<div className="flex items-center justify-between"> <div className="flex items-center justify-between">
<div className="flex items-center gap-3"> <div className="flex items-center gap-3">
<div className="bg-blue-100 rounded-full p-3"> <div className="p-3 bg-blue-100 rounded-full">
<svg className="w-6 h-6 text-blue-600" fill="none" stroke="currentColor" viewBox="0 0 24 24"> <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="M7 16a4 4 0 01-.88-7.903A5 5 0 1115.9 6L16 6a5 5 0 011 9.9M15 13l-3-3m0 0l-3 3m3-3v12" /> <path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M7 16a4 4 0 01-.88-7.903A5 5 0 1115.9 6L16 6a5 5 0 011 9.9M15 13l-3-3m0 0l-3 3m3-3v12" />
</svg> </svg>
@@ -1237,7 +1383,7 @@ export default function Documents() {
setIsUploading(false); setIsUploading(false);
setValidationErrors([]); setValidationErrors([]);
}} }}
className="text-gray-400 hover:text-gray-600 transition-colors duration-200" className="text-gray-400 transition-colors duration-200 hover:text-gray-600"
> >
<svg className="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24"> <svg className="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M6 18L18 6M6 6l12 12" /> <path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M6 18L18 6M6 6l12 12" />
@@ -1247,10 +1393,10 @@ export default function Documents() {
</div> </div>
{/* Contenido del modal - con scroll */} {/* Contenido del modal - con scroll */}
<div className="flex-1 overflow-y-auto px-6 py-4 min-h-0"> <div className="flex-1 min-h-0 px-6 py-4 overflow-y-auto">
{/* Selector de tipo de subida */} {/* Selector de tipo de subida */}
<div className="mb-4"> <div className="mb-4">
<label className="block text-sm font-medium text-gray-700 mb-2"> <label className="block mb-2 text-sm font-medium text-gray-700">
Tipo de subida Tipo de subida
</label> </label>
<div className="grid grid-cols-3 gap-2"> <div className="grid grid-cols-3 gap-2">
@@ -1298,7 +1444,7 @@ export default function Documents() {
{/* Área de selección de archivos */} {/* Área de selección de archivos */}
<div className="mb-4"> <div className="mb-4">
<label className="block text-sm font-medium text-gray-700 mb-2"> <label className="block mb-2 text-sm font-medium text-gray-700">
{uploadType === 'folders' && 'Seleccionar carpetas'} {uploadType === 'folders' && 'Seleccionar carpetas'}
{uploadType === 'zip' && 'Seleccionar archivos ZIP'} {uploadType === 'zip' && 'Seleccionar archivos ZIP'}
{uploadType === 'rar' && 'Seleccionar archivos RAR'} {uploadType === 'rar' && 'Seleccionar archivos RAR'}
@@ -1306,11 +1452,11 @@ export default function Documents() {
{uploadType === 'folders' ? ( {uploadType === 'folders' ? (
<div className="space-y-3"> <div className="space-y-3">
<div className="border-2 border-dashed border-gray-300 rounded-lg p-6 text-center hover:border-gray-400 transition-colors duration-200"> <div className="p-6 text-center transition-colors duration-200 border-2 border-gray-300 border-dashed rounded-lg hover:border-gray-400">
<svg className="w-12 h-12 text-gray-400 mx-auto mb-4" fill="none" stroke="currentColor" viewBox="0 0 24 24"> <svg className="w-12 h-12 mx-auto mb-4 text-gray-400" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M3 7v10a2 2 0 002 2h14a2 2 0 002-2V9a2 2 0 00-2-2h-6l-2-2H5a2 2 0 00-2 2z" /> <path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M3 7v10a2 2 0 002 2h14a2 2 0 002-2V9a2 2 0 00-2-2h-6l-2-2H5a2 2 0 00-2 2z" />
</svg> </svg>
<p className="text-sm text-gray-600 mb-3"> <p className="mb-3 text-sm text-gray-600">
Selecciona una carpeta. Después puedes hacer clic nuevamente para agregar más carpetas. Selecciona una carpeta. Después puedes hacer clic nuevamente para agregar más carpetas.
</p> </p>
<input <input
@@ -1323,14 +1469,14 @@ export default function Documents() {
/> />
<button <button
onClick={() => fileInputRef.current?.click()} onClick={() => fileInputRef.current?.click()}
className="inline-flex items-center px-4 py-2 border border-transparent text-sm font-medium rounded-md text-white bg-blue-600 hover:bg-blue-700 transition-colors duration-200" className="inline-flex items-center px-4 py-2 text-sm font-medium text-white transition-colors duration-200 bg-blue-600 border border-transparent rounded-md hover:bg-blue-700"
> >
<svg className="w-4 h-4 mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24"> <svg className="w-4 h-4 mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M12 6v6m0 0v6m0-6h6m-6 0H6" /> <path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M12 6v6m0 0v6m0-6h6m-6 0H6" />
</svg> </svg>
Agregar Carpeta Agregar Carpeta
{selectedFiles.length > 0 && ( {selectedFiles.length > 0 && (
<span className="ml-2 bg-blue-500 text-white text-xs px-2 py-1 rounded-full"> <span className="px-2 py-1 ml-2 text-xs text-white bg-blue-500 rounded-full">
{[...new Set(selectedFiles.map(file => file.webkitRelativePath.split('/')[0]))].length} {[...new Set(selectedFiles.map(file => file.webkitRelativePath.split('/')[0]))].length}
</span> </span>
)} )}
@@ -1339,13 +1485,13 @@ export default function Documents() {
{/* Lista de carpetas seleccionadas - con altura máxima y scroll */} {/* Lista de carpetas seleccionadas - con altura máxima y scroll */}
{selectedFiles.length > 0 && ( {selectedFiles.length > 0 && (
<div className="bg-gray-50 border border-gray-200 rounded-lg p-4"> <div className="p-4 border border-gray-200 rounded-lg bg-gray-50">
<h4 className="text-sm font-medium text-gray-700 mb-2">Carpetas seleccionadas:</h4> <h4 className="mb-2 text-sm font-medium text-gray-700">Carpetas seleccionadas:</h4>
<div className="max-h-40 overflow-y-auto space-y-2"> <div className="space-y-2 overflow-y-auto max-h-40">
{[...new Set(selectedFiles.map(file => file.webkitRelativePath.split('/')[0]))].map((folderName, index) => ( {[...new Set(selectedFiles.map(file => file.webkitRelativePath.split('/')[0]))].map((folderName, index) => (
<div key={index} className="flex items-center justify-between bg-white p-3 rounded border"> <div key={index} className="flex items-center justify-between p-3 bg-white border rounded">
<div className="flex items-center"> <div className="flex items-center">
<svg className="w-4 h-4 text-blue-500 mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24"> <svg className="w-4 h-4 mr-2 text-blue-500" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M3 7v10a2 2 0 002 2h14a2 2 0 002-2V9a2 2 0 00-2-2h-6l-2-2H5a2 2 0 00-2 2z" /> <path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M3 7v10a2 2 0 002 2h14a2 2 0 002-2V9a2 2 0 00-2-2h-6l-2-2H5a2 2 0 00-2 2z" />
</svg> </svg>
<span className="text-sm font-medium">{folderName}</span> <span className="text-sm font-medium">{folderName}</span>
@@ -1356,7 +1502,7 @@ export default function Documents() {
</span> </span>
<button <button
onClick={() => removeFolderFromSelection(folderName)} onClick={() => removeFolderFromSelection(folderName)}
className="text-red-500 hover:text-red-700 p-1" className="p-1 text-red-500 hover:text-red-700"
title="Eliminar carpeta" title="Eliminar carpeta"
> >
<svg className="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24"> <svg className="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
@@ -1378,11 +1524,11 @@ export default function Documents() {
</div> </div>
) : ( ) : (
<div className="space-y-3"> <div className="space-y-3">
<div className="border-2 border-dashed border-gray-300 rounded-lg p-6 text-center hover:border-gray-400 transition-colors duration-200"> <div className="p-6 text-center transition-colors duration-200 border-2 border-gray-300 border-dashed rounded-lg hover:border-gray-400">
<svg className="w-12 h-12 text-gray-400 mx-auto mb-4" fill="none" stroke="currentColor" viewBox="0 0 24 24"> <svg className="w-12 h-12 mx-auto mb-4 text-gray-400" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M7 16a4 4 0 01-.88-7.903A5 5 0 1115.9 6L16 6a5 5 0 011 9.9M15 13l-3-3m0 0l-3 3m3-3v12" /> <path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M7 16a4 4 0 01-.88-7.903A5 5 0 1115.9 6L16 6a5 5 0 011 9.9M15 13l-3-3m0 0l-3 3m3-3v12" />
</svg> </svg>
<p className="text-sm text-gray-600 mb-3"> <p className="mb-3 text-sm text-gray-600">
{uploadType === 'zip' && 'Selecciona archivos ZIP. Puedes hacer clic nuevamente para agregar más archivos.'} {uploadType === 'zip' && 'Selecciona archivos ZIP. Puedes hacer clic nuevamente para agregar más archivos.'}
{uploadType === 'rar' && 'Selecciona archivos RAR. Puedes hacer clic nuevamente para agregar más archivos.'} {uploadType === 'rar' && 'Selecciona archivos RAR. Puedes hacer clic nuevamente para agregar más archivos.'}
</p> </p>
@@ -1396,14 +1542,14 @@ export default function Documents() {
/> />
<button <button
onClick={() => fileInputRef.current?.click()} onClick={() => fileInputRef.current?.click()}
className="inline-flex items-center px-4 py-2 border border-transparent text-sm font-medium rounded-md text-white bg-blue-600 hover:bg-blue-700 transition-colors duration-200" className="inline-flex items-center px-4 py-2 text-sm font-medium text-white transition-colors duration-200 bg-blue-600 border border-transparent rounded-md hover:bg-blue-700"
> >
<svg className="w-4 h-4 mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24"> <svg className="w-4 h-4 mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M12 6v6m0 0v6m0-6h6m-6 0H6" /> <path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M12 6v6m0 0v6m0-6h6m-6 0H6" />
</svg> </svg>
Agregar {uploadType === 'zip' ? 'ZIP' : 'RAR'} Agregar {uploadType === 'zip' ? 'ZIP' : 'RAR'}
{selectedFiles.length > 0 && ( {selectedFiles.length > 0 && (
<span className="ml-2 bg-blue-500 text-white text-xs px-2 py-1 rounded-full"> <span className="px-2 py-1 ml-2 text-xs text-white bg-blue-500 rounded-full">
{selectedFiles.length} {selectedFiles.length}
</span> </span>
)} )}
@@ -1412,13 +1558,13 @@ export default function Documents() {
{/* Lista de archivos seleccionados */} {/* Lista de archivos seleccionados */}
{selectedFiles.length > 0 && ( {selectedFiles.length > 0 && (
<div className="bg-gray-50 border border-gray-200 rounded-lg p-4"> <div className="p-4 border border-gray-200 rounded-lg bg-gray-50">
<h4 className="text-sm font-medium text-gray-700 mb-2">Archivos seleccionados:</h4> <h4 className="mb-2 text-sm font-medium text-gray-700">Archivos seleccionados:</h4>
<div className="max-h-40 overflow-y-auto space-y-2"> <div className="space-y-2 overflow-y-auto max-h-40">
{selectedFiles.map((file, index) => ( {selectedFiles.map((file, index) => (
<div key={index} className="flex items-center justify-between bg-white p-3 rounded border"> <div key={index} className="flex items-center justify-between p-3 bg-white border rounded">
<div className="flex items-center"> <div className="flex items-center">
<svg className="w-4 h-4 text-blue-500 mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24"> <svg className="w-4 h-4 mr-2 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" /> <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> </svg>
<span className="text-sm font-medium">{file.name}</span> <span className="text-sm font-medium">{file.name}</span>
@@ -1429,7 +1575,7 @@ export default function Documents() {
</span> </span>
<button <button
onClick={() => removeFileFromSelection(index)} onClick={() => removeFileFromSelection(index)}
className="text-red-500 hover:text-red-700 p-1" className="p-1 text-red-500 hover:text-red-700"
title="Eliminar archivo" title="Eliminar archivo"
> >
<svg className="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24"> <svg className="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
@@ -1454,7 +1600,7 @@ export default function Documents() {
{/* Selector de contribuyente */} {/* Selector de contribuyente */}
<div className="mb-4"> <div className="mb-4">
<label className="block text-sm font-medium text-gray-700 mb-2"> <label className="block mb-2 text-sm font-medium text-gray-700">
Contribuyente Contribuyente
</label> </label>
<select <select
@@ -1471,20 +1617,20 @@ export default function Documents() {
))} ))}
</select> </select>
{loadingContributors && ( {loadingContributors && (
<p className="text-sm text-gray-500 mt-1">Cargando contribuyentes...</p> <p className="mt-1 text-sm text-gray-500">Cargando contribuyentes...</p>
)} )}
</div> </div>
{/* Errores de validación */} {/* Errores de validación */}
{validationErrors.length > 0 && ( {validationErrors.length > 0 && (
<div className="mb-4 p-3 bg-red-50 border border-red-200 rounded-md"> <div className="p-3 mb-4 border border-red-200 rounded-md bg-red-50">
<div className="flex items-center gap-2 mb-2"> <div className="flex items-center gap-2 mb-2">
<svg className="w-5 h-5 text-red-500" fill="none" stroke="currentColor" viewBox="0 0 24 24"> <svg className="w-5 h-5 text-red-500" 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" /> <path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M12 8v4m0 4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z" />
</svg> </svg>
<span className="text-sm font-medium text-red-800">Errores de validación:</span> <span className="text-sm font-medium text-red-800">Errores de validación:</span>
</div> </div>
<ul className="text-sm text-red-700 space-y-1"> <ul className="space-y-1 text-sm text-red-700">
{validationErrors.map((error, index) => ( {validationErrors.map((error, index) => (
<li key={index}> {error}</li> <li key={index}> {error}</li>
))} ))}
@@ -1494,7 +1640,7 @@ export default function Documents() {
{/* Información de nomenclatura */} {/* Información de nomenclatura */}
{uploadType === 'folders' && ( {uploadType === 'folders' && (
<div className="mb-4 p-3 bg-blue-50 border border-blue-200 rounded-md"> <div className="p-3 mb-4 border border-blue-200 rounded-md bg-blue-50">
<div className="flex items-center gap-2 mb-2"> <div className="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"> <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="M13 16h-1v-4h-1m1-4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z" /> <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" />
@@ -1524,9 +1670,9 @@ export default function Documents() {
<span className="text-sm font-medium text-gray-700">Comprimiendo carpetas...</span> <span className="text-sm font-medium text-gray-700">Comprimiendo carpetas...</span>
<span className="text-sm text-gray-500">{compressionProgress}%</span> <span className="text-sm text-gray-500">{compressionProgress}%</span>
</div> </div>
<div className="w-full bg-gray-200 rounded-full h-2"> <div className="w-full h-2 bg-gray-200 rounded-full">
<div <div
className="bg-green-600 h-2 rounded-full transition-all duration-300" className="h-2 transition-all duration-300 bg-green-600 rounded-full"
style={{ width: `${compressionProgress}%` }} style={{ width: `${compressionProgress}%` }}
></div> ></div>
</div> </div>
@@ -1539,9 +1685,9 @@ export default function Documents() {
<span className="text-sm font-medium text-gray-700">Subiendo...</span> <span className="text-sm font-medium text-gray-700">Subiendo...</span>
<span className="text-sm text-gray-500">{uploadProgress}%</span> <span className="text-sm text-gray-500">{uploadProgress}%</span>
</div> </div>
<div className="w-full bg-gray-200 rounded-full h-2"> <div className="w-full h-2 bg-gray-200 rounded-full">
<div <div
className="bg-blue-600 h-2 rounded-full transition-all duration-300" className="h-2 transition-all duration-300 bg-blue-600 rounded-full"
style={{ width: `${uploadProgress}%` }} style={{ width: `${uploadProgress}%` }}
></div> ></div>
</div> </div>
@@ -1550,7 +1696,7 @@ export default function Documents() {
</div> </div>
{/* Footer del modal - fijo */} {/* Footer del modal - fijo */}
<div className="flex-shrink-0 px-6 py-4 border-t border-gray-200 flex gap-3 justify-end"> <div className="flex justify-end flex-shrink-0 gap-3 px-6 py-4 border-t border-gray-200">
<button <button
onClick={() => { onClick={() => {
setShowUploadModal(false); setShowUploadModal(false);
@@ -1562,14 +1708,14 @@ export default function Documents() {
setValidationErrors([]); setValidationErrors([]);
}} }}
disabled={isUploading || isCompressing} disabled={isUploading || isCompressing}
className="px-4 py-2 text-sm font-medium text-gray-700 bg-white border border-gray-300 rounded-md hover:bg-gray-50 transition-colors duration-200 disabled:opacity-50 disabled:cursor-not-allowed" className="px-4 py-2 text-sm font-medium text-gray-700 transition-colors duration-200 bg-white border border-gray-300 rounded-md hover:bg-gray-50 disabled:opacity-50 disabled:cursor-not-allowed"
> >
Cancelar Cancelar
</button> </button>
<button <button
onClick={handleUploadFiles} onClick={handleUploadFiles}
disabled={isUploading || isCompressing || selectedFiles.length === 0 || !selectedContributor || validationErrors.length > 0} disabled={isUploading || isCompressing || selectedFiles.length === 0 || !selectedContributor || validationErrors.length > 0}
className="px-4 py-2 text-sm font-medium text-white bg-blue-600 border border-transparent rounded-md hover:bg-blue-700 transition-colors duration-200 disabled:opacity-50 disabled:cursor-not-allowed" className="px-4 py-2 text-sm font-medium text-white transition-colors duration-200 bg-blue-600 border border-transparent rounded-md hover:bg-blue-700 disabled:opacity-50 disabled:cursor-not-allowed"
> >
{isCompressing ? 'Comprimiendo...' : isUploading ? 'Subiendo...' : 'Subir expedientes'} {isCompressing ? 'Comprimiendo...' : isUploading ? 'Subiendo...' : 'Subir expedientes'}
</button> </button>
@@ -1580,12 +1726,12 @@ export default function Documents() {
{/* Modal de confirmación para eliminación */} {/* Modal de confirmación para eliminación */}
{showDeleteModal && ( {showDeleteModal && (
<div className="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center p-4 z-50"> <div className="fixed inset-0 z-50 flex items-center justify-center p-4 bg-black bg-opacity-50">
<div className="bg-white rounded-2xl shadow-2xl max-w-md w-full mx-4 transform transition-all duration-300 scale-100"> <div className="w-full max-w-md mx-4 transition-all duration-300 transform scale-100 bg-white shadow-2xl rounded-2xl">
{/* Header del modal */} {/* Header del modal */}
<div className="px-6 py-4 border-b border-gray-200"> <div className="px-6 py-4 border-b border-gray-200">
<div className="flex items-center gap-3"> <div className="flex items-center gap-3">
<div className="bg-red-100 rounded-full p-3"> <div className="p-3 bg-red-100 rounded-full">
<svg className="w-6 h-6 text-red-600" fill="none" stroke="currentColor" viewBox="0 0 24 24"> <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="M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 00-1-1h-4a1 1 0 00-1 1v3M4 7h16" /> <path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 00-1-1h-4a1 1 0 00-1 1v3M4 7h16" />
</svg> </svg>
@@ -1602,7 +1748,7 @@ export default function Documents() {
{/* Contenido del modal */} {/* Contenido del modal */}
<div className="px-6 py-4"> <div className="px-6 py-4">
<div className="mb-4"> <div className="mb-4">
<p className="text-gray-700 mb-3"> <p className="mb-3 text-gray-700">
¿Estás seguro de que deseas eliminar{' '} ¿Estás seguro de que deseas eliminar{' '}
<span className="font-semibold text-red-600"> <span className="font-semibold text-red-600">
{selectedDocuments.length} documento{selectedDocuments.length !== 1 ? 's' : ''} {selectedDocuments.length} documento{selectedDocuments.length !== 1 ? 's' : ''}
@@ -1610,14 +1756,14 @@ export default function Documents() {
? ?
</p> </p>
<div className="bg-red-50 border border-red-200 rounded-lg p-3"> <div className="p-3 border border-red-200 rounded-lg bg-red-50">
<div className="flex items-start gap-2"> <div className="flex items-start gap-2">
<svg className="w-5 h-5 text-red-500 mt-0.5 flex-shrink-0" fill="none" stroke="currentColor" viewBox="0 0 24 24"> <svg className="w-5 h-5 text-red-500 mt-0.5 flex-shrink-0" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-2.5L13.732 4c-.77-.833-1.964-.833-2.732 0L3.732 16.5c-.77.833.192 2.5 1.732 2.5z" /> <path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-2.5L13.732 4c-.77-.833-1.964-.833-2.732 0L3.732 16.5c-.77.833.192 2.5 1.732 2.5z" />
</svg> </svg>
<div> <div>
<p className="text-sm font-medium text-red-800">Advertencia importante</p> <p className="text-sm font-medium text-red-800">Advertencia importante</p>
<p className="text-sm text-red-700 mt-1"> <p className="mt-1 text-sm text-red-700">
Los documentos eliminados no podrán ser recuperados. Asegúrate de que realmente deseas proceder con esta acción. Los documentos eliminados no podrán ser recuperados. Asegúrate de que realmente deseas proceder con esta acción.
</p> </p>
</div> </div>
@@ -1627,16 +1773,16 @@ export default function Documents() {
</div> </div>
{/* Botones del modal */} {/* Botones del modal */}
<div className="px-6 py-4 border-t border-gray-200 flex justify-end gap-3"> <div className="flex justify-end gap-3 px-6 py-4 border-t border-gray-200">
<button <button
onClick={() => setShowDeleteModal(false)} onClick={() => setShowDeleteModal(false)}
className="px-4 py-2 text-sm font-medium text-gray-700 bg-white border border-gray-300 rounded-lg hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500 transition-colors duration-200" className="px-4 py-2 text-sm font-medium text-gray-700 transition-colors duration-200 bg-white border border-gray-300 rounded-lg hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500"
> >
Cancelar Cancelar
</button> </button>
<button <button
onClick={confirmDelete} onClick={confirmDelete}
className="px-4 py-2 text-sm font-medium text-white bg-red-600 border border-transparent rounded-lg hover:bg-red-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-red-500 transition-colors duration-200 flex items-center gap-2" className="flex items-center gap-2 px-4 py-2 text-sm font-medium text-white transition-colors duration-200 bg-red-600 border border-transparent rounded-lg hover:bg-red-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-red-500"
> >
<svg className="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24"> <svg className="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 00-1-1h-4a1 1 0 00-1 1v3M4 7h16" /> <path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 00-1-1h-4a1 1 0 00-1 1v3M4 7h16" />

View File

@@ -13,6 +13,7 @@ export default function LandingAnimated() {
message: '' message: ''
}); });
const currentYear = new Date().getFullYear();
const observerRef = useRef(null); const observerRef = useRef(null);
const sectionsRef = useRef({}); const sectionsRef = useRef({});
@@ -101,7 +102,7 @@ export default function LandingAnimated() {
// Estadísticas animadas // Estadísticas animadas
const stats = [ const stats = [
{ number: '500+', label: 'Agentes Aduanales', icon: '🏢' }, { number: '350+', label: 'Clientes', icon: '🏢' },
{ number: '15,000+', label: 'Pedimentos Procesados', icon: '📋' }, { number: '15,000+', label: 'Pedimentos Procesados', icon: '📋' },
{ number: '99.9%', label: 'Uptime Garantizado', icon: '⚡' }, { number: '99.9%', label: 'Uptime Garantizado', icon: '⚡' },
{ number: '24/7', label: 'Soporte Especializado', icon: '🛡️' } { number: '24/7', label: 'Soporte Especializado', icon: '🛡️' }
@@ -115,24 +116,31 @@ export default function LandingAnimated() {
? 'bg-white/95 backdrop-blur-md shadow-lg border-b border-gray-200' ? 'bg-white/95 backdrop-blur-md shadow-lg border-b border-gray-200'
: 'bg-transparent' : 'bg-transparent'
}`}> }`}>
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8"> <div className="px-4 mx-auto max-w-7xl sm:px-6 lg:px-8">
<div className="flex justify-between items-center py-4"> <div className="flex items-center justify-between py-4">
<div className="flex items-center"> <div className="flex items-center">
<div className="flex-shrink-0"> <div className="flex-shrink-0">
<h1 className="text-2xl font-bold"> <h1 className="text-2xl font-bold">
<span <span
className="bg-clip-text text-transparent" className="text-transparent bg-clip-text"
style={{ style={{
background: `linear-gradient(to right, #1B2A41, #4DA6FF)`, color: isScrolled
WebkitBackgroundClip: 'text', ? 'transparent'
WebkitTextFillColor: 'transparent' : '#FFFFFF',
background: isScrolled
? 'linear-gradient(to right, #1B2A41, #4DA6FF)'
: 'none',
WebkitBackgroundClip: isScrolled ? 'text' : 'unset',
WebkitTextFillColor: isScrolled ? 'transparent' : '#FFFFFF',
backgroundClip: isScrolled ? 'text' : 'unset'
}} }}
> >
EFC EFC
</span> </span>
</h1> </h1>
</div> </div>
<nav className="hidden md:flex ml-10 space-x-8"> <nav className="hidden ml-10 space-x-8 md:flex">
{[ {[
{ id: 'inicio', label: 'Inicio' }, { id: 'inicio', label: 'Inicio' },
{ id: 'estadisticas', label: 'Confianza' }, { id: 'estadisticas', label: 'Confianza' },
@@ -146,20 +154,32 @@ export default function LandingAnimated() {
onClick={() => scrollToSection(item.id)} onClick={() => scrollToSection(item.id)}
className={`relative text-sm font-medium transition-all duration-300 hover:scale-105 group`} className={`relative text-sm font-medium transition-all duration-300 hover:scale-105 group`}
style={{ style={{
color: activeSection === item.id color: item.id === 'inicio'
? (isScrolled
? (activeSection === 'inicio' ? '#1B2A41' : '#333333')
: (activeSection === 'inicio' ? 'white' : 'white'))
: (activeSection === item.id
? '#1B2A41' ? '#1B2A41'
: isScrolled : isScrolled
? '#333333' ? '#333333'
: 'white' : 'white')
}} }}
onMouseEnter={(e) => { onMouseEnter={(e) => {
if (activeSection !== item.id) { if (activeSection !== item.id) {
if (item.id === 'inicio') {
e.target.style.color = isScrolled ? '#1B2A41' : '#4DA6FF'; e.target.style.color = isScrolled ? '#1B2A41' : '#4DA6FF';
} else {
e.target.style.color = isScrolled ? '#1B2A41' : '#4DA6FF';
}
} }
}} }}
onMouseLeave={(e) => { onMouseLeave={(e) => {
if (activeSection !== item.id) { if (activeSection !== item.id) {
if (item.id === 'inicio') {
e.target.style.color = isScrolled ? '#333333' : 'white'; e.target.style.color = isScrolled ? '#333333' : 'white';
} else {
e.target.style.color = isScrolled ? '#333333' : 'white';
}
} }
}} }}
> >
@@ -168,7 +188,11 @@ export default function LandingAnimated() {
className={`absolute -bottom-1 left-0 h-0.5 transition-all duration-300 ${ className={`absolute -bottom-1 left-0 h-0.5 transition-all duration-300 ${
activeSection === item.id ? 'w-full' : 'w-0 group-hover:w-full' activeSection === item.id ? 'w-full' : 'w-0 group-hover:w-full'
}`} }`}
style={{ backgroundColor: '#1B2A41' }} style={{
backgroundColor: item.id === 'inicio' && !isScrolled && activeSection === 'inicio'
? 'white'
: '#1B2A41'
}}
></span> ></span>
</button> </button>
))} ))}
@@ -196,21 +220,33 @@ export default function LandingAnimated() {
</header> </header>
{/* Hero Section con efectos de gradiente animado */} {/* Hero Section con efectos de gradiente animado */}
<section id="inicio" className="relative min-h-screen flex items-center overflow-hidden"> <section id="inicio" className="relative flex items-center min-h-screen overflow-hidden">
{/* Background con gradientes animados */} {/* Background con imagen */}
<div className="absolute inset-0">
{/* Imagen de fondo */}
<div
className="absolute inset-0 bg-center bg-no-repeat bg-cover"
style={{
backgroundImage: 'url("images/empresaria001.webp")', // Cambia esta ruta
backgroundColor: '#1B2A41', // Color de respaldo
}}
>
{/* Overlay para oscurecer la imagen y mejorar legibilidad */}
<div <div
className="absolute inset-0" className="absolute inset-0"
style={{ style={{
background: 'linear-gradient(135deg, #1B2A41 0%, #263549 50%, #1976D2 100%)' background: 'linear-gradient(to bottom, rgba(27, 42, 65, 0.85), rgba(27, 42, 65, 0.7))',
}} }}
> ></div>
<div className="absolute inset-0 bg-gradient-to-t from-black/20 to-transparent"></div> </div>
{/* Efectos adicionales */}
<div className="absolute inset-0 opacity-20"> <div className="absolute inset-0 opacity-20">
<div className="absolute inset-0 bg-gradient-to-r from-transparent via-white/5 to-transparent animate-pulse"></div> <div className="absolute inset-0 bg-gradient-to-r from-transparent via-white/5 to-transparent animate-pulse"></div>
</div> </div>
</div> </div>
<div className="relative max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-32 text-center"> <div className="relative px-4 py-32 mx-auto text-center max-w-7xl sm:px-6 lg:px-8">
<div className="space-y-8"> <div className="space-y-8">
<h1 <h1
data-animate="hero-title" data-animate="hero-title"
@@ -222,7 +258,7 @@ export default function LandingAnimated() {
> >
<span className="block"> <span className="block">
<span <span
className="bg-clip-text text-transparent" className="text-transparent bg-clip-text"
style={{ style={{
background: 'linear-gradient(to right, white, #64B5F6)', background: 'linear-gradient(to right, white, #64B5F6)',
WebkitBackgroundClip: 'text', WebkitBackgroundClip: 'text',
@@ -233,7 +269,7 @@ export default function LandingAnimated() {
</span> </span>
</span> </span>
<span <span
className="block text-3xl sm:text-4xl md:text-5xl mt-4 bg-clip-text text-transparent" className="block mt-4 text-3xl text-transparent sm:text-4xl md:text-5xl bg-clip-text"
style={{ style={{
background: 'linear-gradient(to right, #64B5F6, white)', background: 'linear-gradient(to right, #64B5F6, white)',
WebkitBackgroundClip: 'text', WebkitBackgroundClip: 'text',
@@ -243,7 +279,7 @@ export default function LandingAnimated() {
Para Agentes Aduanales Para Agentes Aduanales
</span> </span>
<span <span
className="block text-3xl sm:text-4xl md:text-5xl bg-clip-text text-transparent" className="block text-3xl text-transparent sm:text-4xl md:text-5xl bg-clip-text"
style={{ style={{
background: 'linear-gradient(to right, white, #64B5F6)', background: 'linear-gradient(to right, white, #64B5F6)',
WebkitBackgroundClip: 'text', WebkitBackgroundClip: 'text',
@@ -264,8 +300,8 @@ export default function LandingAnimated() {
style={{ color: '#64B5F6' }} style={{ color: '#64B5F6' }}
> >
La plataforma líder desarrollada por La plataforma líder desarrollada por
<span className="font-bold text-white"> @AduanaSoft</span> para <span className="font-bold text-white"> Aduanasoft®</span> para
<span className="font-semibold" style={{ color: '#FF9800' }}> digitalizar y optimizar</span> <span className="font-semibold" style={{ color: '#FFFFFF' }}> digitalizar y optimizar</span>
{' '}todos tus procesos de comercio exterior con tecnología de vanguardia {' '}todos tus procesos de comercio exterior con tecnología de vanguardia
</p> </p>
@@ -279,7 +315,7 @@ export default function LandingAnimated() {
> >
<Link <Link
to="/login" to="/login"
className="group inline-flex items-center px-8 py-4 text-lg font-semibold rounded-full transition-all duration-300 shadow-2xl hover:shadow-3xl transform hover:-translate-y-1 hover:scale-105" className="inline-flex items-center px-8 py-4 text-lg font-semibold transition-all duration-300 transform rounded-full shadow-2xl group hover:shadow-3xl hover:-translate-y-1 hover:scale-105"
style={{ style={{
color: '#1B2A41', color: '#1B2A41',
background: 'linear-gradient(to right, white, #F2F4F7)' background: 'linear-gradient(to right, white, #F2F4F7)'
@@ -292,15 +328,15 @@ export default function LandingAnimated() {
}} }}
> >
<span>Comenzar Ahora</span> <span>Comenzar Ahora</span>
<svg className="ml-2 w-5 h-5 group-hover:translate-x-1 transition-transform duration-200" fill="currentColor" viewBox="0 0 20 20"> <svg className="w-5 h-5 ml-2 transition-transform duration-200 group-hover:translate-x-1" fill="currentColor" viewBox="0 0 20 20">
<path fillRule="evenodd" d="M10.293 3.293a1 1 0 011.414 0l6 6a1 1 0 010 1.414l-6 6a1 1 0 01-1.414-1.414L14.586 11H3a1 1 0 110-2h11.586l-4.293-4.293a1 1 0 010-1.414z" clipRule="evenodd" /> <path fillRule="evenodd" d="M10.293 3.293a1 1 0 011.414 0l6 6a1 1 0 010 1.414l-6 6a1 1 0 01-1.414-1.414L14.586 11H3a1 1 0 110-2h11.586l-4.293-4.293a1 1 0 010-1.414z" clipRule="evenodd" />
</svg> </svg>
</Link> </Link>
<button <button
onClick={() => scrollToSection('caracteristicas')} onClick={() => scrollToSection('caracteristicas')}
className="group inline-flex items-center px-8 py-4 text-lg font-semibold rounded-full text-white bg-transparent border-2 border-white/30 hover:border-white hover:bg-white/10 transition-all duration-300 backdrop-blur-sm" className="inline-flex items-center px-8 py-4 text-lg font-semibold text-white transition-all duration-300 bg-transparent border-2 rounded-full group border-white/30 hover:border-white hover:bg-white/10 backdrop-blur-sm"
> >
<svg className="mr-2 w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24"> <svg className="w-5 h-5 mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M14.828 14.828a4 4 0 01-5.656 0M9 10h1m4 0h1m-6 4h.01M19 10a9 9 0 11-18 0 9 9 0 0118 0z" /> <path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M14.828 14.828a4 4 0 01-5.656 0M9 10h1m4 0h1m-6 4h.01M19 10a9 9 0 11-18 0 9 9 0 0118 0z" />
</svg> </svg>
<span>Ver Demo</span> <span>Ver Demo</span>
@@ -308,11 +344,11 @@ export default function LandingAnimated() {
</div> </div>
{/* Floating cards con efectos */} {/* Floating cards con efectos */}
<div className="grid grid-cols-1 md:grid-cols-3 gap-6 max-w-4xl mx-auto"> <div className="grid max-w-4xl grid-cols-1 gap-6 mx-auto md:grid-cols-3">
{[ {[
{ icon: '🚀', title: 'Rápido', desc: 'Procesamiento instantáneo' }, { icon: '🚀', title: 'Rápido', desc: 'Procesamiento instantáneo' },
{ icon: '🔒', title: 'Seguro', desc: 'Cifrado de nivel bancario' }, { icon: '🔒', title: 'Seguro', desc: 'Cifrado de nivel bancario' },
{ icon: '📊', title: 'Inteligente', desc: 'IA para optimización' } { icon: '📊', title: 'Inteligente', desc: 'IA para optimización' },
].map((feature, index) => ( ].map((feature, index) => (
<div <div
key={index} key={index}
@@ -324,8 +360,8 @@ export default function LandingAnimated() {
}`} }`}
style={{ transitionDelay: `${700 + index * 200}ms` }} style={{ transitionDelay: `${700 + index * 200}ms` }}
> >
<div className="text-4xl mb-3">{feature.icon}</div> <div className="mb-3 text-4xl">{feature.icon}</div>
<h3 className="text-white font-semibold text-lg mb-2">{feature.title}</h3> <h3 className="mb-2 text-lg font-semibold text-white">{feature.title}</h3>
<p className="text-sm" style={{ color: '#64B5F6' }}>{feature.desc}</p> <p className="text-sm" style={{ color: '#64B5F6' }}>{feature.desc}</p>
</div> </div>
))} ))}
@@ -334,10 +370,10 @@ export default function LandingAnimated() {
</div> </div>
{/* Scroll indicator animado */} {/* Scroll indicator animado */}
<div className="absolute bottom-8 left-1/2 transform -translate-x-1/2 animate-bounce"> <div className="absolute transform -translate-x-1/2 bottom-8 left-1/2 animate-bounce">
<button <button
onClick={() => scrollToSection('estadisticas')} onClick={() => scrollToSection('estadisticas')}
className="text-white/70 hover:text-white transition-colors duration-200" className="transition-colors duration-200 text-white/70 hover:text-white"
> >
<svg className="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24"> <svg className="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M19 14l-7 7m0 0l-7-7m7 7V3" /> <path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M19 14l-7 7m0 0l-7-7m7 7V3" />
@@ -348,7 +384,7 @@ export default function LandingAnimated() {
{/* Sección de Estadísticas y Confianza */} {/* Sección de Estadísticas y Confianza */}
<section id="estadisticas" className="py-20" style={{ background: 'linear-gradient(to right, #F2F4F7, white)' }}> <section id="estadisticas" className="py-20" style={{ background: 'linear-gradient(to right, #F2F4F7, white)' }}>
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8"> <div className="px-4 mx-auto max-w-7xl sm:px-6 lg:px-8">
<div <div
data-animate="stats-header" data-animate="stats-header"
className={`text-center mb-16 transition-all duration-1000 ${ className={`text-center mb-16 transition-all duration-1000 ${
@@ -357,17 +393,17 @@ export default function LandingAnimated() {
: 'opacity-0 translate-y-10' : 'opacity-0 translate-y-10'
}`} }`}
> >
<h2 className="text-4xl font-extrabold mb-4" style={{ color: '#333333' }}> <h2 className="mb-4 text-4xl font-extrabold" style={{ color: '#333333' }}>
Más de <span style={{ color: '#1B2A41' }}>500 empresas</span> confían en nosotros Más de <span style={{ color: '#1B2A41' }}>350 empresas</span> confían en nosotros
</h2> </h2>
<p className="text-xl max-w-3xl mx-auto" style={{ color: '#7A7A7A' }}> <p className="max-w-3xl mx-auto text-xl" style={{ color: '#7A7A7A' }}>
Desarrollado por <span className="font-bold" style={{ color: '#1B2A41' }}>@AduanaSoft</span>, Desarrollado por <span className="font-bold" style={{ color: '#1B2A41' }}>Aduanasoft®</span>,
líderes en tecnología aduanal con más de 10 años de experiencia líderes en tecnología aduanal con más de 29 años de experiencia
</p> </p>
</div> </div>
{/* Stats con animaciones */} {/* Stats con animaciones */}
<div className="grid grid-cols-2 lg:grid-cols-4 gap-8 mb-16"> <div className="grid grid-cols-2 gap-8 mb-16 lg:grid-cols-4">
{stats.map((stat, index) => ( {stats.map((stat, index) => (
<div <div
key={index} key={index}
@@ -379,8 +415,8 @@ export default function LandingAnimated() {
}`} }`}
style={{ transitionDelay: `${index * 200}ms` }} style={{ transitionDelay: `${index * 200}ms` }}
> >
<div className="text-4xl mb-4 animate-pulse">{stat.icon}</div> <div className="mb-4 text-4xl animate-pulse">{stat.icon}</div>
<div className="text-3xl font-bold mb-2" style={{ color: '#1B2A41' }}>{stat.number}</div> <div className="mb-2 text-3xl font-bold" style={{ color: '#1B2A41' }}>{stat.number}</div>
<div className="font-medium" style={{ color: '#7A7A7A' }}>{stat.label}</div> <div className="font-medium" style={{ color: '#7A7A7A' }}>{stat.label}</div>
</div> </div>
))} ))}
@@ -396,18 +432,18 @@ export default function LandingAnimated() {
}`} }`}
style={{ background: 'linear-gradient(to right, #1B2A41, #263549)' }} style={{ background: 'linear-gradient(to right, #1B2A41, #263549)' }}
> >
<div className="grid md:grid-cols-2 gap-8 items-center"> <div className="grid items-center gap-8 md:grid-cols-2">
<div> <div>
<h3 className="text-3xl font-bold mb-6">Acerca de AduanaSoft</h3> <h3 className="mb-6 text-3xl font-bold">Acerca de Aduanasoft</h3>
<div className="space-y-4 text-indigo-100"> <div className="space-y-4 text-indigo-100">
{[ {[
"10+ años especializados en software aduanal", "29+ años especializados en software aduanal",
"Equipo experto en comercio exterior y tecnología", "Equipo experto en comercio exterior y tecnología",
"Certificación SAT y cumplimiento normativo total", "Certificación SAT y cumplimiento normativo total",
"Soporte 24/7 con especialistas aduanales" "Soporte 24/7 con especialistas aduanales"
].map((item, idx) => ( ].map((item, idx) => (
<div key={idx} className="flex items-start space-x-3"> <div key={idx} className="flex items-start space-x-3">
<div className="flex-shrink-0 w-6 h-6 bg-white/20 rounded-full flex items-center justify-center mt-1"> <div className="flex items-center justify-center flex-shrink-0 w-6 h-6 mt-1 rounded-full bg-white/20">
<svg className="w-3 h-3" fill="currentColor" viewBox="0 0 20 20"> <svg className="w-3 h-3" fill="currentColor" viewBox="0 0 20 20">
<path fillRule="evenodd" d="M16.707 5.293a1 1 0 010 1.414l-8 8a1 1 0 01-1.414 0l-4-4a1 1 0 011.414-1.414L8 12.586l7.293-7.293a1 1 0 011.414 0z" clipRule="evenodd" /> <path fillRule="evenodd" d="M16.707 5.293a1 1 0 010 1.414l-8 8a1 1 0 01-1.414 0l-4-4a1 1 0 011.414-1.414L8 12.586l7.293-7.293a1 1 0 011.414 0z" clipRule="evenodd" />
</svg> </svg>
@@ -418,9 +454,9 @@ export default function LandingAnimated() {
</div> </div>
</div> </div>
<div className="text-center"> <div className="text-center">
<div className="bg-white/10 backdrop-blur-md rounded-2xl p-8 border border-white/20"> <div className="p-8 border bg-white/10 backdrop-blur-md rounded-2xl border-white/20">
<div className="text-6xl mb-4">🏆</div> <div className="mb-4 text-6xl">🏆</div>
<h4 className="text-2xl font-bold mb-2">Líder del Mercado</h4> <h4 className="mb-2 text-2xl font-bold">Líder del Mercado</h4>
<p className="text-indigo-100"> <p className="text-indigo-100">
Reconocidos como la mejor solución tecnológica para agentes aduanales en México Reconocidos como la mejor solución tecnológica para agentes aduanales en México
</p> </p>
@@ -433,7 +469,7 @@ export default function LandingAnimated() {
{/* Características con efectos interactivos */} {/* Características con efectos interactivos */}
<section id="caracteristicas" className="py-20 bg-white"> <section id="caracteristicas" className="py-20 bg-white">
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8"> <div className="px-4 mx-auto max-w-7xl sm:px-6 lg:px-8">
<div <div
data-animate="features-header" data-animate="features-header"
className={`text-center mb-16 transition-all duration-1000 ${ className={`text-center mb-16 transition-all duration-1000 ${
@@ -442,7 +478,7 @@ export default function LandingAnimated() {
: 'opacity-0 translate-y-10' : 'opacity-0 translate-y-10'
}`} }`}
> >
<h2 className="text-4xl font-extrabold text-gray-900 mb-4"> <h2 className="mb-4 text-4xl font-extrabold text-gray-900">
Soluciones Especializadas para Comercio Exterior Soluciones Especializadas para Comercio Exterior
</h2> </h2>
<p className="text-xl text-gray-600"> <p className="text-xl text-gray-600">
@@ -450,7 +486,7 @@ export default function LandingAnimated() {
</p> </p>
</div> </div>
<div className="grid grid-cols-1 lg:grid-cols-3 gap-8"> <div className="grid grid-cols-1 gap-8 lg:grid-cols-3">
{[ {[
{ {
icon: '📋', icon: '📋',
@@ -492,7 +528,7 @@ export default function LandingAnimated() {
}`}> }`}>
{feature.icon} {feature.icon}
</div> </div>
<h3 className="text-2xl font-bold mb-4 transition-colors duration-300" style={{ <h3 className="mb-4 text-2xl font-bold transition-colors duration-300" style={{
color: visibleElements.has(`feature-${index}`) ? '#1B2A41' : '#333333' color: visibleElements.has(`feature-${index}`) ? '#1B2A41' : '#333333'
}}> }}>
{feature.title} {feature.title}
@@ -503,7 +539,7 @@ export default function LandingAnimated() {
<ul className="space-y-2"> <ul className="space-y-2">
{feature.features.map((item, idx) => ( {feature.features.map((item, idx) => (
<li key={idx} className="flex items-center text-sm" style={{ color: '#7A7A7A' }}> <li key={idx} className="flex items-center text-sm" style={{ color: '#7A7A7A' }}>
<svg className="w-4 h-4 mr-2 flex-shrink-0" style={{ color: '#2E7D32' }} fill="currentColor" viewBox="0 0 20 20"> <svg className="flex-shrink-0 w-4 h-4 mr-2" style={{ color: '#2E7D32' }} fill="currentColor" viewBox="0 0 20 20">
<path fillRule="evenodd" d="M16.707 5.293a1 1 0 010 1.414l-8 8a1 1 0 01-1.414 0l-4-4a1 1 0 011.414-1.414L8 12.586l7.293-7.293a1 1 0 011.414 0z" clipRule="evenodd" /> <path fillRule="evenodd" d="M16.707 5.293a1 1 0 010 1.414l-8 8a1 1 0 01-1.414 0l-4-4a1 1 0 011.414-1.414L8 12.586l7.293-7.293a1 1 0 011.414 0z" clipRule="evenodd" />
</svg> </svg>
{item} {item}
@@ -514,7 +550,7 @@ export default function LandingAnimated() {
<div className="p-4 transition-colors duration-300" style={{ <div className="p-4 transition-colors duration-300" style={{
background: 'linear-gradient(to right, #F2F4F7, #FFFFFF)' background: 'linear-gradient(to right, #F2F4F7, #FFFFFF)'
}}> }}>
<button className="font-semibold text-sm transition-colors duration-200" style={{ <button className="text-sm font-semibold transition-colors duration-200" style={{
color: '#1B2A41' color: '#1B2A41'
}}> }}>
Conocer más Conocer más
@@ -528,7 +564,7 @@ export default function LandingAnimated() {
{/* Precios */} {/* Precios */}
<section id="precios" className="py-20 bg-white"> <section id="precios" className="py-20 bg-white">
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8"> <div className="px-4 mx-auto max-w-7xl sm:px-6 lg:px-8">
<div <div
data-animate="pricing-header" data-animate="pricing-header"
className={`text-center mb-16 transition-all duration-1000 ${ className={`text-center mb-16 transition-all duration-1000 ${
@@ -537,7 +573,7 @@ export default function LandingAnimated() {
: 'opacity-0 translate-y-10' : 'opacity-0 translate-y-10'
}`} }`}
> >
<h2 className="text-4xl font-extrabold text-gray-900 mb-4"> <h2 className="mb-4 text-4xl font-extrabold text-gray-900">
Planes diseñados para tu crecimiento Planes diseñados para tu crecimiento
</h2> </h2>
<p className="text-xl text-gray-600"> <p className="text-xl text-gray-600">
@@ -545,7 +581,7 @@ export default function LandingAnimated() {
</p> </p>
</div> </div>
<div className="grid grid-cols-1 md:grid-cols-3 gap-8"> <div className="grid grid-cols-1 gap-8 md:grid-cols-3">
{[ {[
{ {
name: 'Starter', name: 'Starter',
@@ -614,8 +650,8 @@ export default function LandingAnimated() {
}} }}
> >
{plan.popular && ( {plan.popular && (
<div className="absolute -top-4 left-1/2 transform -translate-x-1/2"> <div className="absolute transform -translate-x-1/2 -top-4 left-1/2">
<span className="text-white px-4 py-2 rounded-full text-sm font-semibold animate-pulse" style={{ <span className="px-4 py-2 text-sm font-semibold text-white rounded-full animate-pulse" style={{
background: 'linear-gradient(to right, #1B2A41, #263549)' background: 'linear-gradient(to right, #1B2A41, #263549)'
}}> }}>
Más Popular Más Popular
@@ -623,8 +659,8 @@ export default function LandingAnimated() {
</div> </div>
)} )}
<div className="text-center mb-8"> <div className="mb-8 text-center">
<h3 className="text-2xl font-bold mb-2" style={{ color: '#333333' }}>{plan.name}</h3> <h3 className="mb-2 text-2xl font-bold" style={{ color: '#333333' }}>{plan.name}</h3>
<p className="mb-4" style={{ color: '#7A7A7A' }}>{plan.description}</p> <p className="mb-4" style={{ color: '#7A7A7A' }}>{plan.description}</p>
{/* {/*
<div className="mb-4"> <div className="mb-4">
@@ -634,10 +670,10 @@ export default function LandingAnimated() {
*/} */}
</div> </div>
<ul className="space-y-3 mb-8"> <ul className="mb-8 space-y-3">
{plan.features.map((feature, idx) => ( {plan.features.map((feature, idx) => (
<li key={idx} className="flex items-center"> <li key={idx} className="flex items-center">
<svg className="w-5 h-5 text-green-500 mr-3" fill="currentColor" viewBox="0 0 20 20"> <svg className="w-5 h-5 mr-3 text-green-500" fill="currentColor" viewBox="0 0 20 20">
<path fillRule="evenodd" d="M16.707 5.293a1 1 0 010 1.414l-8 8a1 1 0 01-1.414 0l-4-4a1 1 0 011.414-1.414L8 12.586l7.293-7.293a1 1 0 011.414 0z" clipRule="evenodd" /> <path fillRule="evenodd" d="M16.707 5.293a1 1 0 010 1.414l-8 8a1 1 0 01-1.414 0l-4-4a1 1 0 011.414-1.414L8 12.586l7.293-7.293a1 1 0 011.414 0z" clipRule="evenodd" />
</svg> </svg>
<span className="text-gray-700">{feature}</span> <span className="text-gray-700">{feature}</span>
@@ -662,32 +698,50 @@ export default function LandingAnimated() {
</section> </section>
{/* Contacto */} {/* Contacto */}
<section id="contacto" className="relative py-20 overflow-hidden" style={{ <section id="contacto" className="relative py-20 overflow-hidden">
background: 'linear-gradient(135deg, #1B2A41 0%, #263549 50%, #1B2A41 100%)' {/* Background con imagen */}
}}> <div className="absolute inset-0">
{/* Misma imagen de fondo que la sección inicio */}
<div
className="absolute inset-0 bg-center bg-no-repeat bg-cover"
style={{
backgroundImage: 'url("images/sistemagestion002.webp")',
backgroundColor: '#1B2A41',
}}
>
{/* Overlay más oscuro para mejor legibilidad en formulario */}
<div
className="absolute inset-0"
style={{
background: 'linear-gradient(to bottom, rgba(27, 42, 65, 0.85), rgba(27, 42, 65, 0.7))',
}}
></div>
</div>
{/* Background Pattern */} {/* Background Pattern */}
<div className="absolute inset-0 opacity-10"> <div className="absolute inset-0 opacity-10">
<div className="absolute inset-0" style={{ <div className="absolute inset-0" style={{
backgroundImage: `url("data:image/svg+xml,%3Csvg width='60' height='60' viewBox='0 0 60 60' xmlns='http://www.w3.org/2000/svg'%3E%3Cg fill='none' fillRule='evenodd'%3E%3Cg fill='%234DA6FF' fillOpacity='0.3'%3E%3Ccircle cx='30' cy='30' r='2'/%3E%3C/g%3E%3C/g%3E%3C/svg%3E")` backgroundImage: `url("data:image/svg+xml,%3Csvg width='60' height='60' viewBox='0 0 60 60' xmlns='http://www.w3.org/2000/svg'%3E%3Cg fill='none' fillRule='evenodd'%3E%3Cg fill='%234DA6FF' fillOpacity='0.3'%3E%3Ccircle cx='30' cy='30' r='2'/%3E%3C/g%3E%3C/g%3E%3C/svg%3E")`
}}></div> }}></div>
</div> </div>
</div>
{/* Floating elements */} {/* Floating elements */}
<div className="absolute top-10 left-10 w-20 h-20 rounded-full opacity-20" style={{ backgroundColor: '#4DA6FF' }}></div> {/* <div className="absolute w-20 h-20 rounded-full top-10 left-10 opacity-20" style={{ backgroundColor: '#4DA6FF' }}></div>
<div className="absolute bottom-10 right-10 w-32 h-32 rounded-full opacity-15" style={{ backgroundColor: '#F57C00' }}></div> <div className="absolute w-32 h-32 rounded-full bottom-10 right-10 opacity-15" style={{ backgroundColor: '#F57C00' }}></div>
<div className="absolute top-1/2 left-1/4 w-16 h-16 rounded-full opacity-10" style={{ backgroundColor: '#2E7D32' }}></div> <div className="absolute w-16 h-16 rounded-full top-1/2 left-1/4 opacity-10" style={{ backgroundColor: '#2E7D32' }}></div> */}
<div className="relative max-w-7xl mx-auto px-4 sm:px-6 lg:px-8"> <div className="relative px-4 mx-auto max-w-7xl sm:px-6 lg:px-8">
<div className="text-center mb-16"> <div className="mb-16 text-center">
<h2 className="text-4xl md:text-5xl font-extrabold text-white mb-6"> <h2 className="mb-6 text-4xl font-extrabold text-white md:text-5xl">
¿Listo para <span style={{ color: '#4DA6FF' }}>transformar</span> tu operación aduanal? ¿Listo para <span style={{ color: '#4DA6FF' }}>transformar</span> tu operación aduanal?
</h2> </h2>
<p className="text-xl md:text-2xl max-w-3xl mx-auto" style={{ color: '#64B5F6' }}> <p className="max-w-3xl mx-auto text-xl md:text-2xl" style={{ color: '#64B5F6' }}>
Únete a más de 500 empresas que ya optimizaron sus procesos con EFC Únete a más de 350 empresas que ya optimizaron sus procesos con EFC
</p> </p>
</div> </div>
<div className="grid grid-cols-1 lg:grid-cols-2 gap-12 items-start"> <div className="grid items-start grid-cols-1 gap-12 lg:grid-cols-2">
<div <div
data-animate="contact-info" data-animate="contact-info"
className={`transition-all duration-1000 ${ className={`transition-all duration-1000 ${
@@ -697,9 +751,9 @@ export default function LandingAnimated() {
}`} }`}
> >
{/* Card de información de contacto */} {/* Card de información de contacto */}
<div className="bg-white/10 backdrop-blur-md rounded-3xl p-8 border border-white/20"> <div className="p-8 border bg-white/10 backdrop-blur-md rounded-3xl border-white/20">
<div className="mb-8"> <div className="mb-8">
<h3 className="text-2xl font-bold text-white mb-4"> <h3 className="mb-4 text-2xl font-bold text-white">
Hablemos de tu proyecto Hablemos de tu proyecto
</h3> </h3>
<p className="text-lg" style={{ color: '#64B5F6' }}> <p className="text-lg" style={{ color: '#64B5F6' }}>
@@ -728,15 +782,15 @@ export default function LandingAnimated() {
subtitle: 'Visitas con cita previa' subtitle: 'Visitas con cita previa'
} }
].map((contact, idx) => ( ].map((contact, idx) => (
<div key={idx} className="flex items-start space-x-4 p-4 rounded-xl bg-white/5 hover:bg-white/10 transition-all duration-300"> <div key={idx} className="flex items-start p-4 space-x-4 transition-all duration-300 rounded-xl bg-white/5 hover:bg-white/10">
<div className="w-14 h-14 flex items-center justify-center rounded-full" style={{ backgroundColor: '#4DA6FF' }}> <div className="flex items-center justify-center rounded-full w-14 h-14" style={{ backgroundColor: '#4DA6FF' }}>
<svg className="w-7 h-7 text-white" fill="none" stroke="currentColor" viewBox="0 0 24 24"> <svg className="text-white w-7 h-7" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d={contact.icon} /> <path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d={contact.icon} />
</svg> </svg>
</div> </div>
<div className="flex-1"> <div className="flex-1">
<h4 className="font-bold text-white text-lg">{contact.title}</h4> <h4 className="text-lg font-bold text-white">{contact.title}</h4>
<p className="font-semibold mb-1" style={{ color: '#4DA6FF' }}>{contact.info}</p> <p className="mb-1 font-semibold" style={{ color: '#4DA6FF' }}>{contact.info}</p>
<p className="text-sm" style={{ color: '#64B5F6' }}>{contact.subtitle}</p> <p className="text-sm" style={{ color: '#64B5F6' }}>{contact.subtitle}</p>
</div> </div>
</div> </div>
@@ -744,10 +798,10 @@ export default function LandingAnimated() {
</div> </div>
{/* Botón adicional para WhatsApp */} {/* Botón adicional para WhatsApp */}
<div className="mt-8 pt-6 border-t border-white/20"> <div className="pt-6 mt-8 border-t border-white/20">
<a <a
href="#" href="#"
className="flex items-center justify-center w-full py-4 px-6 rounded-xl font-semibold text-white transition-all duration-300 transform hover:scale-105" className="flex items-center justify-center w-full px-6 py-4 font-semibold text-white transition-all duration-300 transform rounded-xl hover:scale-105"
style={{ style={{
background: 'linear-gradient(45deg, #25D366, #128C7E)' background: 'linear-gradient(45deg, #25D366, #128C7E)'
}} }}
@@ -769,10 +823,10 @@ export default function LandingAnimated() {
: 'opacity-0 translate-x-10' : 'opacity-0 translate-x-10'
}`} }`}
> >
<h3 className="text-2xl font-bold mb-6" style={{ color: '#333333' }}>Solicita una demostración</h3> <h3 className="mb-6 text-2xl font-bold" style={{ color: '#333333' }}>Solicita una demostración</h3>
<form onSubmit={handleContactSubmit} className="space-y-4"> <form onSubmit={handleContactSubmit} className="space-y-4">
<div> <div>
<label className="block text-sm font-semibold mb-2" style={{ color: '#333333' }}> <label className="block mb-2 text-sm font-semibold" style={{ color: '#333333' }}>
Nombre completo Nombre completo
</label> </label>
<input <input
@@ -780,7 +834,7 @@ export default function LandingAnimated() {
required required
value={contactForm.name} value={contactForm.name}
onChange={(e) => setContactForm({...contactForm, name: e.target.value})} onChange={(e) => setContactForm({...contactForm, name: e.target.value})}
className="w-full px-4 py-3 border border-gray-300 rounded-lg focus:ring-2 focus:border-transparent transition-all duration-200" className="w-full px-4 py-3 transition-all duration-200 border border-gray-300 rounded-lg focus:ring-2 focus:border-transparent"
style={{ style={{
focusRingColor: '#4DA6FF', focusRingColor: '#4DA6FF',
outline: 'none' outline: 'none'
@@ -792,7 +846,7 @@ export default function LandingAnimated() {
</div> </div>
<div> <div>
<label className="block text-sm font-semibold mb-2" style={{ color: '#333333' }}> <label className="block mb-2 text-sm font-semibold" style={{ color: '#333333' }}>
Email corporativo Email corporativo
</label> </label>
<input <input
@@ -800,7 +854,7 @@ export default function LandingAnimated() {
required required
value={contactForm.email} value={contactForm.email}
onChange={(e) => setContactForm({...contactForm, email: e.target.value})} onChange={(e) => setContactForm({...contactForm, email: e.target.value})}
className="w-full px-4 py-3 border border-gray-300 rounded-lg focus:ring-2 focus:border-transparent transition-all duration-200" className="w-full px-4 py-3 transition-all duration-200 border border-gray-300 rounded-lg focus:ring-2 focus:border-transparent"
onFocus={(e) => e.target.style.borderColor = '#4DA6FF'} onFocus={(e) => e.target.style.borderColor = '#4DA6FF'}
onBlur={(e) => e.target.style.borderColor = '#d1d5db'} onBlur={(e) => e.target.style.borderColor = '#d1d5db'}
placeholder="tu@empresa.com" placeholder="tu@empresa.com"
@@ -808,7 +862,7 @@ export default function LandingAnimated() {
</div> </div>
<div> <div>
<label className="block text-sm font-semibold mb-2" style={{ color: '#333333' }}> <label className="block mb-2 text-sm font-semibold" style={{ color: '#333333' }}>
Empresa Empresa
</label> </label>
<input <input
@@ -816,7 +870,7 @@ export default function LandingAnimated() {
required required
value={contactForm.company} value={contactForm.company}
onChange={(e) => setContactForm({...contactForm, company: e.target.value})} onChange={(e) => setContactForm({...contactForm, company: e.target.value})}
className="w-full px-4 py-3 border border-gray-300 rounded-lg focus:ring-2 focus:border-transparent transition-all duration-200" className="w-full px-4 py-3 transition-all duration-200 border border-gray-300 rounded-lg focus:ring-2 focus:border-transparent"
onFocus={(e) => e.target.style.borderColor = '#4DA6FF'} onFocus={(e) => e.target.style.borderColor = '#4DA6FF'}
onBlur={(e) => e.target.style.borderColor = '#d1d5db'} onBlur={(e) => e.target.style.borderColor = '#d1d5db'}
placeholder="Nombre de tu empresa" placeholder="Nombre de tu empresa"
@@ -824,14 +878,14 @@ export default function LandingAnimated() {
</div> </div>
<div> <div>
<label className="block text-sm font-semibold mb-2" style={{ color: '#333333' }}> <label className="block mb-2 text-sm font-semibold" style={{ color: '#333333' }}>
Mensaje Mensaje
</label> </label>
<textarea <textarea
rows="4" rows="4"
value={contactForm.message} value={contactForm.message}
onChange={(e) => setContactForm({...contactForm, message: e.target.value})} onChange={(e) => setContactForm({...contactForm, message: e.target.value})}
className="w-full px-4 py-3 border border-gray-300 rounded-lg focus:ring-2 focus:border-transparent transition-all duration-200" className="w-full px-4 py-3 transition-all duration-200 border border-gray-300 rounded-lg focus:ring-2 focus:border-transparent"
onFocus={(e) => e.target.style.borderColor = '#4DA6FF'} onFocus={(e) => e.target.style.borderColor = '#4DA6FF'}
onBlur={(e) => e.target.style.borderColor = '#d1d5db'} onBlur={(e) => e.target.style.borderColor = '#d1d5db'}
placeholder="Cuéntanos sobre tu operación aduanal..." placeholder="Cuéntanos sobre tu operación aduanal..."
@@ -860,11 +914,11 @@ export default function LandingAnimated() {
</section> </section>
{/* Footer */} {/* Footer */}
<footer className="text-white py-12" style={{ backgroundColor: '#1B2A41' }}> <footer className="py-12 text-white" style={{ backgroundColor: '#1B2A41' }}>
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8"> <div className="px-4 mx-auto max-w-7xl sm:px-6 lg:px-8">
<div className="grid grid-cols-1 md:grid-cols-4 gap-8"> <div className="grid grid-cols-1 gap-8 md:grid-cols-4">
<div className="col-span-1 md:col-span-2"> <div className="col-span-1 md:col-span-2">
<h3 className="text-2xl font-bold mb-4"> <h3 className="mb-4 text-2xl font-bold">
<span style={{ <span style={{
background: 'linear-gradient(to right, #4DA6FF, #64B5F6)', background: 'linear-gradient(to right, #4DA6FF, #64B5F6)',
WebkitBackgroundClip: 'text', WebkitBackgroundClip: 'text',
@@ -873,13 +927,13 @@ export default function LandingAnimated() {
EFC EFC
</span> </span>
</h3> </h3>
<p className="mb-2 max-w-md" style={{ color: '#7A7A7A' }}> {/* <p className="max-w-md mb-2" style={{ color: '#7A7A7A' }}>
Uso correcto <span className="font-semibold" style={{ color: '#4DA6FF' }}>Aduanasoft®</span> Uso correcto <span className="font-semibold" style={{ color: '#4DA6FF' }}>Aduanasoft®</span>
</p> </p> */}
<ul className="mb-4 text-sm text-gray-300 space-y-1"> {/* <ul className="mb-4 space-y-1 text-sm text-gray-300">
<li><span className="font-bold text-white">+350</span> clientes en todo el país</li> <li><span className="font-bold text-white">+350</span> clientes en todo el país</li>
<li><span className="font-bold text-white">29 años</span> de experiencia</li> <li><span className="font-bold text-white">29 años</span> de experiencia</li>
</ul> </ul> */}
<div className="flex space-x-4"> <div className="flex space-x-4">
<a href="https://www.facebook.com/AduanaSoftMX" target="_blank" rel="noopener noreferrer" className="transition-colors duration-200" style={{ color: '#7A7A7A' }} onMouseEnter={e => e.target.style.color = 'white'} onMouseLeave={e => e.target.style.color = '#7A7A7A'} title="Facebook"> <a href="https://www.facebook.com/AduanaSoftMX" target="_blank" rel="noopener noreferrer" className="transition-colors duration-200" style={{ color: '#7A7A7A' }} onMouseEnter={e => e.target.style.color = 'white'} onMouseLeave={e => e.target.style.color = '#7A7A7A'} title="Facebook">
<svg className="w-6 h-6" fill="currentColor" viewBox="0 0 24 24"><path d="M22.675 0h-21.35C.595 0 0 .592 0 1.326v21.348C0 23.408.595 24 1.325 24h11.495v-9.294H9.692v-3.622h3.128V8.413c0-3.1 1.893-4.788 4.659-4.788 1.325 0 2.463.099 2.797.143v3.24l-1.918.001c-1.504 0-1.797.715-1.797 1.763v2.313h3.587l-.467 3.622h-3.12V24h6.116C23.406 24 24 23.408 24 22.674V1.326C24 .592 23.406 0 22.675 0"/></svg> <svg className="w-6 h-6" fill="currentColor" viewBox="0 0 24 24"><path d="M22.675 0h-21.35C.595 0 0 .592 0 1.326v21.348C0 23.408.595 24 1.325 24h11.495v-9.294H9.692v-3.622h3.128V8.413c0-3.1 1.893-4.788 4.659-4.788 1.325 0 2.463.099 2.797.143v3.24l-1.918.001c-1.504 0-1.797.715-1.797 1.763v2.313h3.587l-.467 3.622h-3.12V24h6.116C23.406 24 24 23.408 24 22.674V1.326C24 .592 23.406 0 22.675 0"/></svg>
@@ -903,29 +957,29 @@ export default function LandingAnimated() {
</div> </div>
<div> <div>
<h4 className="font-semibold mb-4">Producto</h4> <h4 className="mb-4 font-semibold">Producto</h4>
<ul className="space-y-2 text-gray-400"> <ul className="space-y-2 text-gray-400">
<li><button onClick={() => scrollToSection('caracteristicas')} className="hover:text-white transition-colors duration-200">Características</button></li> <li><button onClick={() => scrollToSection('caracteristicas')} className="transition-colors duration-200 hover:text-white">Características</button></li>
<li><button onClick={() => scrollToSection('precios')} className="hover:text-white transition-colors duration-200">Precios</button></li> <li><button onClick={() => scrollToSection('precios')} className="transition-colors duration-200 hover:text-white">Precios</button></li>
<li><a href="#" className="hover:text-white transition-colors duration-200">Integraciónes</a></li> <li><a href="#" className="transition-colors duration-200 hover:text-white">Integraciónes</a></li>
<li><a href="#" className="hover:text-white transition-colors duration-200">API</a></li> <li><a href="#" className="transition-colors duration-200 hover:text-white">API</a></li>
</ul> </ul>
</div> </div>
<div> <div>
<h4 className="font-semibold mb-4">Soporte</h4> <h4 className="mb-4 font-semibold">Soporte</h4>
<ul className="space-y-2 text-gray-400"> <ul className="space-y-2 text-gray-400">
<li><button onClick={() => scrollToSection('contacto')} className="hover:text-white transition-colors duration-200">Contacto</button></li> <li><button onClick={() => scrollToSection('contacto')} className="transition-colors duration-200 hover:text-white">Contacto</button></li>
<li><a href="#" className="hover:text-white transition-colors duration-200">Documentación</a></li> <li><a href="#" className="transition-colors duration-200 hover:text-white">Documentación</a></li>
<li><a href="#" className="hover:text-white transition-colors duration-200">Centro de Ayuda</a></li> <li><a href="#" className="transition-colors duration-200 hover:text-white">Centro de Ayuda</a></li>
<li><a href="#" className="hover:text-white transition-colors duration-200">Status</a></li> <li><a href="#" className="transition-colors duration-200 hover:text-white">Status</a></li>
</ul> </ul>
</div> </div>
</div> </div>
<div className="border-t border-gray-800 mt-8 pt-8 text-center text-gray-400"> <div className="pt-8 mt-8 text-center text-gray-400 border-t border-gray-800">
<p> <p>
&copy; 2025 EFC by <span className="font-semibold text-indigo-400">@AduanaSoft</span>. &copy; {currentYear} EFC by <span className="font-semibold text-white">Aduanasoft®</span>.
Todos los derechos reservados. | Solución especializada para Agentes Aduanales e Importadores. Todos los derechos reservados. | Solución especializada para Agentes Aduanales e Importadores.
</p> </p>
</div> </div>

File diff suppressed because it is too large Load Diff

View File

@@ -81,6 +81,7 @@ export default function Reports() {
} }
fetchOrgId(); fetchOrgId();
}, []); }, []);
// Handler for Generar Reporte in Cumplimiento tab // Handler for Generar Reporte in Cumplimiento tab
const handleGenerarReporteCumplimiento = async () => { const handleGenerarReporteCumplimiento = async () => {
if (!organizacionId) { if (!organizacionId) {
@@ -196,6 +197,7 @@ export default function Reports() {
const [tourStep, setTourStep] = useState(0); const [tourStep, setTourStep] = useState(0);
const [organizaciones, setOrganizaciones] = useState([]); const [organizaciones, setOrganizaciones] = useState([]);
const [importadores, setImportadores] = useState([]);
useEffect(() => { useEffect(() => {
const fetchOrganizaciones = async () => { const fetchOrganizaciones = async () => {
@@ -213,6 +215,23 @@ export default function Reports() {
fetchOrganizaciones(); fetchOrganizaciones();
}, []); }, []);
useEffect(() => {
const fetchImportadores = async () => {
try {
const res = await fetch(
`${import.meta.env.VITE_EFC_API_URL}/customs/importadores/`,
{ method: 'GET', headers: { 'Authorization': `Bearer ${localStorage.getItem('access')}` }
});
const data = await res.json();
setImportadores(data);
} catch {
console.error('Error fetching importadores:', err);
setImportadores([]);
}
};
fetchImportadores();
}, []);
const [globalFilters, setGlobalFilters] = useState({ const [globalFilters, setGlobalFilters] = useState({
rfc: '', rfc: '',
fecha_pago_desde: '', fecha_pago_desde: '',
@@ -240,13 +259,45 @@ export default function Reports() {
</div> </div>
<div className="p-4"> <div className="p-4">
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-4"> <div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-4">
{/* Filtro por Organización */}
<div className="group">
<label className="block text-sm font-medium text-gray-700 mb-1">
Organización
</label>
<div className="relative rounded-lg shadow-sm">
<select
value={globalFilters.organizacion || ''}
onChange={(e) => setGlobalFilters(prev => ({
...prev,
organizacion: e.target.value
}))}
className="block w-full rounded-lg border-gray-300 pl-3 pr-10 py-2.5 text-gray-900 placeholder-gray-500
focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-blue-500 sm:text-sm
transition-all duration-200 bg-white appearance-none"
>
<option value="">Todas las organizaciones</option>
{organizaciones.results && organizaciones.results.map(org => (
<option key={org.id} value={org.id}>
{org.nombre} {/* Usar el campo 'nombre' que sí existe */}
</option>
))}
</select>
<div className="absolute inset-y-0 right-0 flex items-center pr-3 pointer-events-none">
<svg className="h-5 w-5 text-gray-400 group-focus-within:text-blue-500" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M19 9l-7 7-7-7" />
</svg>
</div>
</div>
</div>
{/* Filtro por RFC */} {/* Filtro por RFC */}
<div className="group"> <div className="group">
<label className="block text-sm font-medium text-gray-700 mb-1"> <label className="block text-sm font-medium text-gray-700 mb-1">
RFC RFC
</label> </label>
{/* modificar de input a select */}
<div className="relative rounded-lg shadow-sm"> <div className="relative rounded-lg shadow-sm">
<input {/* <input
type="text" type="text"
value={globalFilters.rfc || ''} value={globalFilters.rfc || ''}
onChange={(e) => setGlobalFilters(prev => ({ onChange={(e) => setGlobalFilters(prev => ({
@@ -257,7 +308,25 @@ export default function Reports() {
focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-blue-500 sm:text-sm focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-blue-500 sm:text-sm
transition-all duration-200 bg-white" transition-all duration-200 bg-white"
placeholder="Ej: ABC123456789" placeholder="Ej: ABC123456789"
/> /> */}
<select
name="rfc"
value={globalFilters.rfc || ''}
onChange={(e) => setGlobalFilters(prev => ({
...prev,
rfc: e.target.value
}))}
className="w-full px-3 py-2 border border-green-300 rounded-md shadow-sm focus:ring-2 focus:ring-green-500 focus:border-green-500 transition-all duration-200 bg-white text-slate-900 text-sm font-mono uppercase"
style={{ textTransform: 'uppercase' }}
>
<option value="" >Selecciona un RFC</option>
{importadores.filter(imp => {
if (!globalFilters.organizacion) return true;
return imp.organizacion === globalFilters.organizacion;
}).map(imp => (
<option key={imp.rfc} value={imp.rfc}>{imp.rfc}</option>
))}
</select>
<div className="absolute inset-y-0 right-0 flex items-center pr-3 pointer-events-none"> <div className="absolute inset-y-0 right-0 flex items-center pr-3 pointer-events-none">
<svg className="h-5 w-5 text-gray-400 group-focus-within:text-blue-500" fill="none" viewBox="0 0 24 24" stroke="currentColor"> <svg className="h-5 w-5 text-gray-400 group-focus-within:text-blue-500" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z" /> <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z" />
@@ -367,37 +436,6 @@ export default function Reports() {
</div> </div>
</div> </div>
</div> </div>
{/* Filtro por Organización */}
<div className="group">
<label className="block text-sm font-medium text-gray-700 mb-1">
Organización
</label>
<div className="relative rounded-lg shadow-sm">
<select
value={globalFilters.organizacion || ''}
onChange={(e) => setGlobalFilters(prev => ({
...prev,
organizacion: e.target.value
}))}
className="block w-full rounded-lg border-gray-300 pl-3 pr-10 py-2.5 text-gray-900 placeholder-gray-500
focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-blue-500 sm:text-sm
transition-all duration-200 bg-white appearance-none"
>
<option value="">Todas las organizaciones</option>
{organizaciones.results && organizaciones.results.map(org => (
<option key={org.id} value={org.id}>
{org.nombre} {/* Usar el campo 'nombre' que sí existe */}
</option>
))}
</select>
<div className="absolute inset-y-0 right-0 flex items-center pr-3 pointer-events-none">
<svg className="h-5 w-5 text-gray-400 group-focus-within:text-blue-500" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M19 9l-7 7-7-7" />
</svg>
</div>
</div>
</div>
</div> </div>
{/* Botón para limpiar filtros globales */} {/* Botón para limpiar filtros globales */}

View File

@@ -202,10 +202,13 @@ function RelacionarImportadoresModal({ open, onClose, vucem }) {
} }
import React, { useEffect, useState } from 'react'; import React, { useEffect, useState } from 'react';
import { fetchWithAuth, postWithAuth, putWithAuth, deleteWithAuth, putFormDataWithAuth, postFormDataWithAuth, patchWithAuth } from '../fetchWithAuth'; import { fetchWithAuth, postWithAuth, putWithAuth, deleteWithAuth, putFormDataWithAuth, postFormDataWithAuth, patchWithAuth } from '../fetchWithAuth';
import { useUser } from '../context/UserContext';
const API_URL = import.meta.env.VITE_EFC_API_URL; const API_URL = import.meta.env.VITE_EFC_API_URL;
export default function Vucem() { export default function Vucem() {
// Estado para modal de relacionar importadores // Estado para modal de relacionar importadores
const isDebugMode = import.meta.env.VITE_DEBUG_MODE === 'true';
const [showRelacionarModal, setShowRelacionarModal] = useState(false); const [showRelacionarModal, setShowRelacionarModal] = useState(false);
const [selectedVucem, setSelectedVucem] = useState(null); const [selectedVucem, setSelectedVucem] = useState(null);
const [vucemList, setVucemList] = useState([]); const [vucemList, setVucemList] = useState([]);
@@ -216,10 +219,13 @@ export default function Vucem() {
const [editVucem, setEditVucem] = useState(null); const [editVucem, setEditVucem] = useState(null);
const [showDeleteModal, setShowDeleteModal] = useState(false); const [showDeleteModal, setShowDeleteModal] = useState(false);
const [deleteVucem, setDeleteVucem] = useState(null); const [deleteVucem, setDeleteVucem] = useState(null);
const [organizaciones, setOrganizaciones] = useState([]);
const { user: currentUser } = useUser();
// Estado para formulario de creación/edición // Estado para formulario de creación/edición
const initialForm = { const initialForm = {
usuario: '', usuario: '',
organizacion: '',
password: '', password: '',
patente: '', patente: '',
efirma: '', efirma: '',
@@ -260,6 +266,22 @@ export default function Vucem() {
})); }));
}; };
useEffect(() => {
const fetchOrganizaciones = async () => {
try {
const url = `${import.meta.env.VITE_EFC_API_URL}/organization/organizaciones/`;
const res = await fetchWithAuth(url); // ← USA fetchWithAuth
if (!res.ok) throw new Error('Error al obtener las organizaciones');
const data = await res.json();
setOrganizaciones(data);
} catch (err) {
console.error('Error fetching organizaciones:', err);
setOrganizaciones([]); // ← Asegurar que siempre sea un array
}
};
fetchOrganizaciones();
}, []);
// Funciones para alternar visibilidad en modal de edición // Funciones para alternar visibilidad en modal de edición
const toggleEditPasswordVisibility = () => { const toggleEditPasswordVisibility = () => {
setShowEditPassword(!showEditPassword); setShowEditPassword(!showEditPassword);
@@ -358,6 +380,7 @@ export default function Vucem() {
const res = await fetchWithAuth(`${API_URL}/vucem/vucem/`); const res = await fetchWithAuth(`${API_URL}/vucem/vucem/`);
if (!res.ok) throw new Error('Error al cargar Ventanilla Unica'); if (!res.ok) throw new Error('Error al cargar Ventanilla Unica');
const data = await res.json(); const data = await res.json();
// console.log('data > ', data);
setVucemList(data); setVucemList(data);
setError(null); setError(null);
} catch (err) { } catch (err) {
@@ -1272,12 +1295,13 @@ export default function Vucem() {
formData.append('password', form.password); formData.append('password', form.password);
formData.append('patente', form.patente); formData.append('patente', form.patente);
formData.append('efirma', form.efirma); 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('is_importador', form.is_importador);
formData.append('is_active', form.is_active);
formData.append('acusecove', form.acusecove); formData.append('acusecove', form.acusecove);
formData.append('acuseedocument', form.acuseedocument); formData.append('acuseedocument', form.acuseedocument);
formData.append('is_active', form.is_active); formData.append('organizacion_id', form.organizacion);
if (form.key) formData.append('key', form.key);
if (form.cer) formData.append('cer', form.cer);
try { try {
const res = await postFormDataWithAuth(`${API_URL}/vucem/vucem/`, formData); const res = await postFormDataWithAuth(`${API_URL}/vucem/vucem/`, formData);
if (!res.ok) throw new Error('Error al crear'); if (!res.ok) throw new Error('Error al crear');
@@ -1553,6 +1577,54 @@ export default function Vucem() {
</div> </div>
</div> </div>
{/* Seccion de organizacion para super_users */}
{currentUser.is_superuser ? (
<div className="bg-slate-50 rounded-lg p-4 border border-slate-200">
<div className="flex items-center mb-3 pb-2 border-b border-slate-300">
<div className="bg-amber-600 rounded-lg p-2 mr-3 shadow-sm">
<svg className="w-4 h-4 text-white" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M10.325 4.317c.426-1.756 2.924-1.756 3.35 0a1.724 1.724 0 002.573 1.066c1.543-.94 3.31.826 2.37 2.37a1.724 1.724 0 001.065 2.572c1.756.426 1.756 2.924 0 3.35a1.724 1.724 0 00-1.066 2.573c.94 1.543-.826 3.31-2.37 2.37a1.724 1.724 0 00-2.572 1.065c-.426 1.756-2.924 1.756-3.35 0a1.724 1.724 0 00-2.573-1.066c-1.543.94-3.31-.826-2.37-2.37a1.724 1.724 0 00-1.065-2.572c-1.756-.426-1.756-2.924 0-3.35a1.724 1.724 0 001.066-2.573c-.94-1.543.826-3.31 2.37-2.37.996.608 2.296.07 2.572-1.065z" />
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M15 12a3 3 0 11-6 0 3 3 0 016 0z" />
</svg>
</div>
<div>
<h4 className="text-sm font-semibold text-slate-800">Configuracion de Organizaciones</h4>
<p className="text-xs text-slate-600">Asignar la Organizacion a la que pertenece.</p>
</div>
</div>
<div className="grid grid-cols-1 lg:grid-cols-1 gap-4">
<div className="space-y-1">
<label className="block text-xs font-semibold text-slate-700">
Seleccionar Organizacion <span className="text-red-600">*</span>
</label>
<div className="relative rounded-lg shadow-sm">
<select
name="organizacion"
value={form.organizacion || ''}
onChange={handleInputChange}
className="block w-full rounded-lg border-gray-300 pl-3 pr-10 py-2.5 text-gray-900 placeholder-gray-500
focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-blue-500 sm:text-sm
transition-all duration-200 bg-white appearance-none"
>
<option value="">Todas las organizaciones</option>
{organizaciones.results && organizaciones.results.map(org => (
<option key={org.id} value={org.id}>
{org.nombre}
</option>
))}
</select>
<div className="absolute inset-y-0 right-0 flex items-center pr-3 pointer-events-none">
<svg className="h-5 w-5 text-gray-400 group-focus-within:text-blue-500" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M19 9l-7 7-7-7" />
</svg>
</div>
</div>
</div>
</div>
</div>
) : (
<div className="bg-slate-50 rounded-lg p-4 border border-slate-200"></div>
)}
{/* Botones de acción */} {/* Botones de acción */}
<div className="flex flex-col sm:flex-row justify-end space-y-2 sm:space-y-0 sm:space-x-3 pt-4 border-t border-slate-200"> <div className="flex flex-col sm:flex-row justify-end space-y-2 sm:space-y-0 sm:space-x-3 pt-4 border-t border-slate-200">
<button <button