Preparando proyecto para migración a repositorio MVE

This commit is contained in:
2025-10-23 08:48:00 -06:00
parent b1b9e4a77c
commit d0af739ce2
75 changed files with 15875 additions and 4022 deletions

View File

@@ -27,36 +27,190 @@ if (empty($claves_pedimentos)) {
<html lang="es">
<head>
<meta charset="UTF-8">
<title>📋 Nuevo Pedimento de Importación</title>
<title>Nuevo Pedimento de Importación</title>
<meta name="viewport" content="width=device-width, initial-scale=1">
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css" rel="stylesheet">
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/js/bootstrap.bundle.min.js"></script>
<!-- Font Awesome -->
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet">
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&display=swap" rel="stylesheet">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0/css/all.min.css">
<!-- Animate.css -->
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/animate.css/4.1.1/animate.min.css">
<style>
body { font-family: 'Segoe UI', sans-serif; background-color: #f4f6f9; }
.sidebar { width: 220px; height: 100vh; background-color: #343a40; position: fixed; top: 0; left: 0; padding-top: 56px; z-index: 1040; }
.sidebar .nav-link { font-weight: bold; color: white; transition: all 0.3s ease; }
.sidebar .nav-link:hover,
.sidebar .nav-link.active { background-color: #495057; color: #fff; }
.content { margin-top: 56px; padding: 40px 20px; position: relative; z-index: 1; background-color: #f4f6f9; transition: margin-left 0.3s ease;}
.navbar { position: fixed; top: 0; width: 100%; z-index: 1050; }
/* A partir de dispositivos medianos (>=768px), deja espacio lateral */
@media (min-width: 768px) { .content { margin-left: 250px; } }
/* En móviles, sin margen lateral */
@media (max-width: 767.98px) {
.content { margin-left: 0; }
.sidebar .nav-link { font-weight: normal; color: #343a40; background-color: transparent; }
.sidebar .nav-link:hover,
.sidebar .nav-link.active { background-color: #e9ecef; color: #212529; }
:root {
--primary: #2563eb;
--primary-dark: #1d4ed8;
--secondary: #64748b;
--success: #059669;
--warning: #d97706;
--danger: #dc2626;
--info: #0891b2;
--light: #f8fafc;
--dark: #0f172a;
--border-color: #e2e8f0;
--text-muted: #64748b;
--bg-body: #f8fafc;
}
.card { border-radius: 12px; }
.fade-in-up { animation: fadeInUp 0.8s ease-out; }
@keyframes fadeInUp {
from { opacity: 0; transform: translateY(30px); }
to { opacity: 1; transform: translateY(0); }
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: 'Inter', -apple-system, BlinkMacSystemFont, sans-serif;
background-color: var(--bg-body);
color: var(--dark);
line-height: 1.6;
}
.main-content {
margin-left: 280px;
padding: 2rem;
min-height: 100vh;
transition: margin-left 0.3s ease;
}
@media (max-width: 768px) {
.main-content {
margin-left: 0;
padding: 1rem;
}
}
.page-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 2rem;
flex-wrap: wrap;
gap: 1rem;
}
.page-title {
color: var(--dark);
font-weight: 600;
font-size: 1.75rem;
margin: 0;
}
.btn-modern {
background: var(--primary);
color: white;
border: none;
padding: 0.625rem 1.25rem;
border-radius: 6px;
font-weight: 500;
font-size: 0.875rem;
transition: all 0.2s ease;
text-decoration: none;
display: inline-flex;
align-items: center;
gap: 0.5rem;
}
.btn-modern:hover {
background: var(--primary-dark);
color: white;
transform: translateY(-1px);
text-decoration: none;
}
.btn-modern.success {
background: var(--success);
}
.btn-modern.success:hover {
background: #047857;
}
.btn-modern.secondary {
background: var(--secondary);
}
.btn-modern.secondary:hover {
background: #475569;
}
.form-card {
background: white;
border: 1px solid var(--border-color);
border-radius: 12px;
padding: 2rem;
margin-bottom: 1.5rem;
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
}
.form-section-title {
color: var(--dark);
font-weight: 600;
font-size: 1.1rem;
margin-bottom: 1.5rem;
padding-bottom: 0.75rem;
border-bottom: 1px solid var(--border-color);
display: flex;
align-items: center;
gap: 0.5rem;
}
.form-section-icon {
width: 24px;
height: 24px;
background: var(--primary);
color: white;
border-radius: 4px;
display: flex;
align-items: center;
justify-content: center;
font-size: 0.75rem;
}
.form-label {
color: var(--dark);
font-weight: 500;
font-size: 0.875rem;
margin-bottom: 0.5rem;
}
.form-control, .form-select {
border: 1px solid var(--border-color);
border-radius: 6px;
padding: 0.625rem 0.75rem;
font-size: 0.875rem;
transition: all 0.2s ease;
}
.form-control:focus, .form-select:focus {
border-color: var(--primary);
box-shadow: 0 0 0 0.2rem rgba(37, 99, 235, 0.1);
}
.alert-modern {
border: none;
border-radius: 8px;
padding: 1rem 1.25rem;
margin-bottom: 1.5rem;
display: flex;
align-items: center;
gap: 0.75rem;
}
.alert-modern.warning {
background: rgba(217, 119, 6, 0.1);
color: #92400e;
border-left: 4px solid var(--warning);
}
.alert-modern.info {
background: rgba(8, 145, 178, 0.1);
color: #0e7490;
border-left: 4px solid var(--info);
}
.fade-in {
animation: fadeIn 0.5s ease-out;
}
@keyframes fadeIn {
from { opacity: 0; transform: translateY(10px); }
to { opacity: 1; transform: translateY(0); }
}
.title-glow { text-shadow: 0 0 10px rgba(0, 123, 255, 0.3); }
/* Efecto para botones */
@@ -69,25 +223,28 @@ if (empty($claves_pedimentos)) {
</head>
<body>
<div class="content">
<div class="d-flex justify-content-between align-items-center mb-4">
<h4 class="animate__animated animate__fadeInDown title-glow">📋 Nuevo Pedimento de Importación</h4>
<a href="/IMPORTADORES/catalogo_pedimentos/lista" class="btn btn-secondary btn-animated">
<i class="fas fa-arrow-left"></i> Regresar a Lista
<div class="main-content">
<!-- Page Header -->
<div class="page-header">
<h1 class="page-title">Nuevo Pedimento de Importación</h1>
<a href="/IMPORTADORES/catalogo_pedimentos/lista" class="btn-modern secondary">
<i class="fas fa-arrow-left"></i>
Regresar a Lista
</a>
</div>
<div class="card fade-in-up">
<div class="card-body">
<form action="/IMPORTADORES/catalogo_pedimentos/guardar" method="POST" id="formPedimento">
<div class="row">
<!-- Información Básica -->
<div class="col-md-6">
<div class="card mb-3">
<div class="card-header">
<h5 class="mb-0"><i class="fas fa-info-circle"></i> Información del Pedimento</h5>
</div>
<div class="card-body">
<!-- Form Container -->
<form action="/IMPORTADORES/catalogo_pedimentos/guardar" method="POST" id="formPedimento" class="fade-in">
<div class="row">
<!-- Información del Pedimento -->
<div class="col-lg-6">
<div class="form-card">
<h3 class="form-section-title">
<div class="form-section-icon">
<i class="fas fa-info-circle"></i>
</div>
Información del Pedimento
</h3>
<div class="mb-3">
<label for="pedimento" class="form-label">Número de Pedimento <span class="text-danger">*</span></label>
<input type="text" class="form-control" id="pedimento" name="pedimento" required
@@ -280,11 +437,13 @@ if (empty($claves_pedimentos)) {
<div class="row">
<div class="col-md-12">
<div class="d-flex justify-content-end gap-2">
<a href="/IMPORTADORES/catalogo_pedimentos/lista" class="btn btn-secondary btn-animated">
<i class="fas fa-times"></i> Cancelar
<a href="/IMPORTADORES/catalogo_pedimentos/lista" class="btn-modern secondary">
<i class="fas fa-times"></i>
Cancelar
</a>
<button type="submit" class="btn btn-success btn-animated" <?= empty($claves_pedimentos) ? 'disabled' : '' ?>>
<i class="fas fa-save"></i> Guardar Pedimento
<button type="submit" class="btn-modern success" <?= empty($claves_pedimentos) ? 'disabled' : '' ?>>
<i class="fas fa-save"></i>
Guardar Pedimento
</button>
</div>
</div>
@@ -294,9 +453,8 @@ if (empty($claves_pedimentos)) {
</div>
</div>
<!-- jQuery -->
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js"></script>
<script src="https://code.jquery.com/jquery-3.6.0.min.js"></script>
<!-- SweetAlert2 -->
<script src="https://cdn.jsdelivr.net/npm/sweetalert2@11"></script>
<script>

View File

@@ -32,22 +32,169 @@ if (!empty($previo['FechaFinal'])) {
<html lang="es">
<head>
<meta charset="UTF-8">
<title>📋 Editar Pedimento</title>
<title>Editar Pedimento</title>
<meta name="viewport" content="width=device-width, initial-scale=1">
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css" rel="stylesheet">
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/js/bootstrap.bundle.min.js"></script>
<!-- Font Awesome -->
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet">
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&display=swap" rel="stylesheet">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0/css/all.min.css">
<!-- Animate.css -->
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/animate.css/4.1.1/animate.min.css">
<style>
body { font-family: 'Segoe UI', sans-serif; background-color: #f4f6f9; }
.sidebar { width: 220px; height: 100vh; background-color: #343a40; position: fixed; top: 0; left: 0; padding-top: 56px; z-index: 1040; }
.sidebar .nav-link { font-weight: bold; color: white; transition: all 0.3s ease; }
.sidebar .nav-link:hover,
.sidebar .nav-link.active { background-color: #495057; color: #fff; }
.content { margin-top: 56px; padding: 40px 20px; position: relative; z-index: 1; background-color: #f4f6f9; transition: margin-left 0.3s ease;}
.navbar { position: fixed; top: 0; width: 100%; z-index: 1050; }
:root {
--primary: #2563eb;
--primary-dark: #1d4ed8;
--secondary: #64748b;
--success: #059669;
--warning: #d97706;
--danger: #dc2626;
--info: #0891b2;
--light: #f8fafc;
--dark: #0f172a;
--border-color: #e2e8f0;
--text-muted: #64748b;
--bg-body: #f8fafc;
}
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: 'Inter', -apple-system, BlinkMacSystemFont, sans-serif;
background-color: var(--bg-body);
color: var(--dark);
line-height: 1.6;
}
.main-content {
margin-left: 280px;
padding: 2rem;
min-height: 100vh;
transition: margin-left 0.3s ease;
}
@media (max-width: 768px) {
.main-content {
margin-left: 0;
padding: 1rem;
}
}
.page-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 2rem;
flex-wrap: wrap;
gap: 1rem;
}
.page-title {
color: var(--dark);
font-weight: 600;
font-size: 1.75rem;
margin: 0;
}
.btn-modern {
background: var(--primary);
color: white;
border: none;
padding: 0.625rem 1.25rem;
border-radius: 6px;
font-weight: 500;
font-size: 0.875rem;
transition: all 0.2s ease;
text-decoration: none;
display: inline-flex;
align-items: center;
gap: 0.5rem;
}
.btn-modern:hover {
background: var(--primary-dark);
color: white;
transform: translateY(-1px);
text-decoration: none;
}
.btn-modern.success {
background: var(--success);
}
.btn-modern.success:hover {
background: #047857;
}
.btn-modern.secondary {
background: var(--secondary);
}
.btn-modern.secondary:hover {
background: #475569;
}
.form-card {
background: white;
border: 1px solid var(--border-color);
border-radius: 12px;
padding: 2rem;
margin-bottom: 1.5rem;
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
}
.form-section-title {
color: var(--dark);
font-weight: 600;
font-size: 1.1rem;
margin-bottom: 1.5rem;
padding-bottom: 0.75rem;
border-bottom: 1px solid var(--border-color);
display: flex;
align-items: center;
gap: 0.5rem;
}
.form-section-icon {
width: 24px;
height: 24px;
background: var(--primary);
color: white;
border-radius: 4px;
display: flex;
align-items: center;
justify-content: center;
font-size: 0.75rem;
}
.form-label {
color: var(--dark);
font-weight: 500;
font-size: 0.875rem;
margin-bottom: 0.5rem;
}
.form-control, .form-select {
border: 1px solid var(--border-color);
border-radius: 6px;
padding: 0.625rem 0.75rem;
font-size: 0.875rem;
transition: all 0.2s ease;
}
.form-control:focus, .form-select:focus {
border-color: var(--primary);
box-shadow: 0 0 0 0.2rem rgba(37, 99, 235, 0.1);
}
.fade-in {
animation: fadeIn 0.5s ease-out;
}
@keyframes fadeIn {
from { opacity: 0; transform: translateY(10px); }
to { opacity: 1; transform: translateY(0); }
}
/* A partir de dispositivos medianos (>=768px), deja espacio lateral */
@media (min-width: 768px) { .content { margin-left: 250px; } }
/* En móviles, sin margen lateral */
@@ -77,20 +224,21 @@ if (!empty($previo['FechaFinal'])) {
</head>
<body>
<div class="content">
<div class="d-flex justify-content-between align-items-center mb-4">
<h4 class="animate__animated animate__fadeInDown title-glow">📋 Editar Pedimento #<?= htmlspecialchars($previo['IdPrevio']) ?></h4>
<a href="/IMPORTADORES/catalogo_pedimentos/lista" class="btn btn-secondary btn-animated">
<i class="fas fa-arrow-left"></i> Regresar a Lista
<div class="main-content">
<!-- Page Header -->
<div class="page-header">
<h1 class="page-title">Editar Pedimento #<?= htmlspecialchars($previo['IdPrevio']) ?></h1>
<a href="/IMPORTADORES/catalogo_pedimentos/lista" class="btn-modern secondary">
<i class="fas fa-arrow-left"></i>
Regresar a Lista
</a>
</div>
<div class="card fade-in-up">
<div class="card-body">
<form action="/IMPORTADORES/catalogo_pedimentos/actualizar" method="POST" id="formPedimento">
<input type="hidden" name="id_previo" value="<?= htmlspecialchars($previo['IdPrevio']) ?>">
<div class="row">
<!-- Form Container -->
<form action="/IMPORTADORES/catalogo_pedimentos/actualizar" method="POST" id="formPedimento" class="fade-in">
<input type="hidden" name="id_previo" value="<?= htmlspecialchars($previo['IdPrevio']) ?>">
<div class="row">
<!-- Información Básica -->
<div class="col-md-6">
<div class="card mb-3">
@@ -310,14 +458,11 @@ if (!empty($previo['FechaFinal'])) {
</div>
</div>
</div>
</form>
</div>
</div>
</form>
</div>
<!-- jQuery -->
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js"></script>
<script src="https://code.jquery.com/jquery-3.6.0.min.js"></script>
<!-- SweetAlert2 -->
<script src="https://cdn.jsdelivr.net/npm/sweetalert2@11"></script>
<script>

View File

@@ -4,160 +4,837 @@
<html lang="es">
<head>
<meta charset="UTF-8">
<title>📋 Catálogo de Pedimentos</title>
<title>Catálogo de Pedimentos</title>
<meta name="viewport" content="width=device-width, initial-scale=1">
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css" rel="stylesheet">
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/js/bootstrap.bundle.min.js"></script>
<!-- Font Awesome -->
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet">
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&display=swap" rel="stylesheet">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0/css/all.min.css">
<!-- Animate.css -->
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/animate.css/4.1.1/animate.min.css">
<style>
body { font-family: 'Segoe UI', sans-serif; background-color: #f4f6f9; }
.sidebar { width: 220px; height: 100vh; background-color: #343a40; position: fixed; top: 0; left: 0; padding-top: 56px; z-index: 1040; }
.sidebar .nav-link { font-weight: bold; color: white; transition: all 0.3s ease; }
.sidebar .nav-link:hover,
.sidebar .nav-link.active { background-color: #495057; color: #fff; }
.content { margin-top: 56px; padding: 40px 20px; position: relative; z-index: 1; background-color: #f4f6f9; transition: margin-left 0.3s ease;}
.navbar { position: fixed; top: 0; width: 100%; z-index: 1050; }
/* A partir de dispositivos medianos (>=768px), deja espacio lateral */
@media (min-width: 768px) { .content { margin-left: 250px; } }
/* En móviles, sin margen lateral */
@media (max-width: 767.98px) {
.content { margin-left: 0; }
.sidebar .nav-link { font-weight: normal; color: #343a40; background-color: transparent; }
.sidebar .nav-link:hover,
.sidebar .nav-link.active { background-color: #e9ecef; color: #212529; }
:root {
--primary: #2563eb;
--primary-dark: #1d4ed8;
--secondary: #64748b;
--success: #059669;
--warning: #d97706;
--danger: #dc2626;
--info: #0891b2;
--light: #f8fafc;
--dark: #0f172a;
--border-color: #e2e8f0;
--text-muted: #64748b;
--bg-body: #f8fafc;
}
.card { border-radius: 12px; }
/* Animaciones personalizadas */
.card-hover { transition: all 0.3s ease; cursor: pointer; }
.card-hover:hover { transform: translateY(-8px) scale(1.02); box-shadow: 0 1rem 2rem rgba(0, 0, 0, 0.15) !important; }
.btn-pulse { animation: pulse 2s infinite; }
@keyframes pulse {
0% { transform: scale(1); }
50% { transform: scale(1.05); }
100% { transform: scale(1); }
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
.fade-in-up { animation: fadeInUp 0.8s ease-out; }
@keyframes fadeInUp {
from { opacity: 0; transform: translateY(30px); }
to { opacity: 1; transform: translateY(0); }
body {
font-family: 'Inter', -apple-system, BlinkMacSystemFont, sans-serif;
background-color: var(--bg-body);
color: var(--dark);
line-height: 1.6;
}
.main-content {
margin-left: 280px;
padding: 2rem;
min-height: 100vh;
transition: margin-left 0.3s ease;
}
@media (max-width: 768px) {
.main-content {
margin-left: 0;
padding: 1rem;
}
}
.page-header {
margin-bottom: 2rem;
}
.page-title {
color: var(--dark);
font-weight: 600;
font-size: 1.75rem;
margin-bottom: 0.5rem;
}
.page-subtitle {
color: var(--text-muted);
font-weight: 400;
font-size: 0.95rem;
}
.feature-card {
background: white;
border: 1px solid var(--border-color);
border-radius: 12px;
padding: 1.5rem;
height: 100%;
transition: all 0.2s ease;
cursor: pointer;
position: relative;
overflow: hidden;
}
.feature-card:hover {
transform: translateY(-4px);
box-shadow: 0 8px 25px rgba(0, 0, 0, 0.1);
border-color: var(--primary);
}
.feature-icon {
width: 48px;
height: 48px;
border-radius: 8px;
display: flex;
align-items: center;
justify-content: center;
margin-bottom: 1rem;
font-size: 1.25rem;
color: white;
}
.feature-icon.primary { background: var(--primary); }
.feature-icon.success { background: var(--success); }
.feature-icon.info { background: var(--info); }
.feature-title {
font-weight: 600;
font-size: 1.1rem;
color: var(--dark);
margin-bottom: 0.5rem;
}
.feature-description {
color: var(--text-muted);
font-size: 0.9rem;
margin-bottom: 1.5rem;
line-height: 1.5;
}
.btn-feature {
background: var(--primary);
color: white;
border: none;
padding: 0.625rem 1.25rem;
border-radius: 6px;
font-weight: 500;
font-size: 0.875rem;
transition: all 0.2s ease;
width: 100%;
text-decoration: none;
display: inline-block;
text-align: center;
}
.btn-feature:hover {
background: var(--primary-dark);
color: white;
transform: translateY(-1px);
text-decoration: none;
}
.btn-feature.success {
background: var(--success);
}
.btn-feature.success:hover {
background: #047857;
color: white;
}
.btn-feature.info {
background: var(--info);
}
.btn-feature.info:hover {
background: #0e7490;
color: white;
}
.info-card {
background: white;
border: 1px solid var(--border-color);
border-radius: 12px;
padding: 1.5rem;
margin-top: 2rem;
}
.info-card-header {
display: flex;
align-items: center;
margin-bottom: 1rem;
padding-bottom: 0.75rem;
border-bottom: 1px solid var(--border-color);
}
.info-icon {
width: 32px;
height: 32px;
border-radius: 6px;
background: rgba(37, 99, 235, 0.1);
color: var(--primary);
display: flex;
align-items: center;
justify-content: center;
margin-right: 0.75rem;
}
.info-title {
font-weight: 600;
font-size: 1.1rem;
color: var(--dark);
margin: 0;
}
.info-text {
color: var(--text-muted);
font-size: 0.95rem;
margin-bottom: 1rem;
}
.feature-list {
list-style: none;
padding: 0;
}
.feature-list li {
display: flex;
align-items: center;
padding: 0.5rem 0;
color: var(--text-muted);
font-size: 0.9rem;
}
.feature-list li i {
color: var(--success);
margin-right: 0.75rem;
width: 16px;
}
.fade-in {
animation: fadeIn 0.5s ease-out;
}
@keyframes fadeIn {
from { opacity: 0; transform: translateY(10px); }
to { opacity: 1; transform: translateY(0); }
}
/* Responsive animations */
@media (max-width: 768px) { .card-hover:hover { transform: translateY(-4px) scale(1.01); } }
/* Efecto de glow para elementos activos */
.btn.active { box-shadow: 0 0 20px rgba(var(--bs-primary-rgb), 0.5); }
/* Animación para el título */
.title-glow { text-shadow: 0 0 10px rgba(0, 123, 255, 0.3); }
/* Efecto para botones */
.btn-animated { transition: all 0.3s ease; position: relative; overflow: hidden; }
.btn-animated:hover { transform: translateY(-2px); box-shadow: 0 5px 15px rgba(0, 0, 0, 0.2); }
.btn-animated::before { content: ''; position: absolute; top: 0; left: -100%; width: 100%; height: 100%;
background: linear-gradient(90deg, transparent, rgba(255, 255, 255, 0.2), transparent); transition: left 0.5s; }
.btn-animated:hover::before { left: 100%; }
</style>
</head>
<body>
<div class="content">
<h4 class="mb-4 animate__animated animate__fadeInDown title-glow">📋 Catálogo de Pedimentos</h4>
<div class="row mb-4">
<div class="col-md-6 col-lg-4 mb-3">
<div class="card bg-primary text-white card-hover fade-in-up">
<div class="card-body">
<div class="d-flex justify-content-between">
<div>
<h5 class="card-title">Lista de Pedimentos</h5>
<p class="card-text">Visualizar y gestionar todos los pedimentos registrados</p>
</div>
<div class="align-self-center">
<i class="fas fa-list fa-2x"></i>
</div>
</div>
<a href="/IMPORTADORES/catalogo_pedimentos/lista" class="btn btn-outline-light btn-sm btn-animated">
Ver Lista <i class="fas fa-arrow-right"></i>
</a>
<div class="main-content">
<!-- Page Header -->
<div class="page-header">
<h1 class="page-title">Catálogo de Pedimentos</h1>
<p class="page-subtitle">Gestiona y administra todos los pedimentos de importación del sistema</p>
</div>
<!-- Features Grid -->
<div class="row g-4 mb-4">
<div class="col-lg-4 col-md-6">
<div class="feature-card fade-in" onclick="window.location.href='/IMPORTADORES/catalogo_pedimentos/lista'">
<div class="feature-icon primary">
<i class="fas fa-list"></i>
</div>
<h3 class="feature-title">Lista de Pedimentos</h3>
<p class="feature-description">Visualiza y gestiona todos los pedimentos registrados en el sistema con herramientas de búsqueda y filtrado avanzado.</p>
<a href="/IMPORTADORES/catalogo_pedimentos/lista" class="btn-feature">
Ver Lista
<i class="fas fa-arrow-right ms-2"></i>
</a>
</div>
</div>
<div class="col-md-6 col-lg-4 mb-3">
<div class="card bg-success text-white card-hover fade-in-up">
<div class="card-body">
<div class="d-flex justify-content-between">
<div>
<h5 class="card-title">Nuevo Pedimento</h5>
<p class="card-text">Registrar un nuevo pedimento en el sistema</p>
</div>
<div class="align-self-center">
<i class="fas fa-plus-circle fa-2x"></i>
</div>
</div>
<a href="/IMPORTADORES/catalogo_pedimentos/crear" class="btn btn-outline-light btn-sm btn-animated">
Crear Nuevo <i class="fas fa-plus"></i>
</a>
<div class="col-lg-4 col-md-6">
<div class="feature-card fade-in" onclick="window.location.href='/IMPORTADORES/catalogo_pedimentos/crear'">
<div class="feature-icon success">
<i class="fas fa-plus-circle"></i>
</div>
<h3 class="feature-title">Nuevo Pedimento</h3>
<p class="feature-description">Registra un nuevo pedimento con toda la información requerida y documentación necesaria para el proceso de importación.</p>
<a href="/IMPORTADORES/catalogo_pedimentos/crear" class="btn-feature success">
Crear Nuevo
<i class="fas fa-plus ms-2"></i>
</a>
</div>
</div>
<div class="col-md-6 col-lg-4 mb-3">
<div class="card bg-info text-white card-hover fade-in-up">
<div class="card-body">
<div class="d-flex justify-content-between">
<div>
<h5 class="card-title">Reportes</h5>
<p class="card-text">Generar reportes y estadísticas de pedimentos</p>
</div>
<div class="align-self-center">
<i class="fas fa-chart-bar fa-2x"></i>
</div>
</div>
<button class="btn btn-outline-light btn-sm btn-animated" onclick="proximamente()">
Ver Reportes <i class="fas fa-chart-line"></i>
<div class="col-lg-4 col-md-6">
<div class="feature-card fade-in">
<div class="feature-icon info">
<i class="fas fa-upload"></i>
</div>
<h3 class="feature-title">Importar Archivo</h3>
<p class="feature-description">Carga masiva de pedimentos desde archivo de texto con formato específico del sistema aduanero.</p>
<button class="btn-feature info" onclick="importarPedimentos()">
Importar Datos
<i class="fas fa-file-import ms-2"></i>
</button>
<div class="mt-2">
<button class="btn btn-sm btn-outline-secondary" onclick="verificarPedimentosExistentes()" style="border-radius: 6px; font-size: 0.8rem;">
<i class="fas fa-search me-1"></i>Ver Existentes
</button>
<button class="btn btn-sm btn-outline-danger" onclick="limpiarBaseDatos()" style="border-radius: 6px; font-size: 0.8rem;">
<i class="fas fa-trash me-1"></i>Limpiar BD
</button>
</div>
</div>
</div>
<div class="col-lg-4 col-md-6">
<div class="feature-card fade-in">
<div class="feature-icon warning" style="background: var(--warning);">
<i class="fas fa-chart-bar"></i>
</div>
<h3 class="feature-title">Reportes</h3>
<p class="feature-description">Genera reportes detallados y estadísticas de pedimentos para análisis y seguimiento de operaciones.</p>
<button class="btn-feature" style="background: var(--warning);" onclick="proximamente()">
Ver Reportes
<i class="fas fa-chart-line ms-2"></i>
</button>
</div>
</div>
</div>
<div class="row">
<div class="col-12">
<div class="card fade-in-up">
<div class="card-header">
<h5 class="mb-0"><i class="fas fa-info-circle"></i> Información del Módulo</h5>
</div>
<div class="card-body">
<p class="text-muted">
El catálogo de pedimentos le permite gestionar de manera eficiente todos los pedimentos
de importación. Desde aquí puede:
</p>
<ul class="list-unstyled">
<li><i class="fas fa-check text-success"></i> Registrar nuevos pedimentos con toda la información requerida</li>
<li><i class="fas fa-check text-success"></i> Consultar y editar pedimentos existentes</li>
<li><i class="fas fa-check text-success"></i> Realizar búsquedas avanzadas por diversos criterios</li>
<li><i class="fas fa-check text-success"></i> Exportar información para reportes</li>
<li><i class="fas fa-check text-success"></i> Mantener un histórico completo de operaciones</li>
</ul>
<!-- Information Card -->
<div class="info-card">
<div class="info-card-header">
<div class="info-icon">
<i class="fas fa-info-circle"></i>
</div>
<h3 class="info-title">Información del Módulo</h3>
</div>
<p class="info-text">
El catálogo de pedimentos te permite gestionar de manera eficiente todos los pedimentos
de importación. Desde aquí puedes realizar las siguientes acciones:
</p>
<ul class="feature-list">
<li>
<i class="fas fa-check"></i>
Registrar nuevos pedimentos con toda la información requerida
</li>
<li>
<i class="fas fa-check"></i>
Consultar y editar pedimentos existentes
</li>
<li>
<i class="fas fa-check"></i>
Realizar búsquedas avanzadas por diversos criterios
</li>
<li>
<i class="fas fa-check"></i>
Exportar información para reportes y análisis
</li>
<li>
<i class="fas fa-check"></i>
Mantener un histórico completo de operaciones
</li>
</ul>
</div>
</div>
<!-- Modal para Importar Archivo -->
<div class="modal fade" id="modalImportarPedimentos" tabindex="-1" aria-labelledby="modalImportarLabel" aria-hidden="true">
<div class="modal-dialog modal-lg">
<div class="modal-content" style="border-radius: 12px; border: none;">
<div class="modal-header" style="background: linear-gradient(135deg, var(--info) 0%, #0e7490 100%); color: white; border-radius: 12px 12px 0 0;">
<h5 class="modal-title" id="modalImportarLabel">
<i class="fas fa-upload me-2"></i>
Importar Pedimentos desde Archivo
</h5>
<button type="button" class="btn-close btn-close-white" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<div class="modal-body p-4">
<div class="alert alert-info d-flex align-items-start" style="border: none; border-radius: 8px; background: rgba(8, 145, 178, 0.1); border-left: 4px solid var(--info);">
<i class="fas fa-info-circle me-2 mt-1" style="color: var(--info);"></i>
<div>
<strong>Formato del archivo:</strong>
<p class="mb-1">El sistema reconoce archivos con los siguientes códigos:</p>
<ul class="mb-2 small">
<li><strong>501:</strong> Datos del pedimento (número, RFC, fechas)</li>
<li><strong>505:</strong> Información de facturas</li>
<li><strong>551:</strong> Partidas de mercancías</li>
</ul>
<p class="mb-0 small text-muted">
<i class="fas fa-calendar-day me-1"></i>
<strong>Archivos julianos:</strong> Se aceptan extensiones de día del año (.001 a .366)
</p>
</div>
</div>
<form id="formImportarPedimentos" enctype="multipart/form-data">
<div class="mb-3">
<label for="archivoImportar" class="form-label fw-semibold">
<i class="fas fa-file-text me-1"></i>
Seleccionar archivos
</label>
<input type="file" class="form-control" id="archivoImportar" name="archivos[]" multiple required style="border-radius: 8px; border: 1px solid var(--border-color);">
<div class="form-text">Formatos soportados: .txt, .dat, archivos julianos (.001-.366). Puedes seleccionar varios archivos.</div>
</div>
<div class="mb-3">
<div class="form-check">
<input class="form-check-input" type="checkbox" id="validarDuplicados" name="validar_duplicados" checked>
<label class="form-check-label fw-medium" for="validarDuplicados">
Validar pedimentos duplicados
</label>
<div class="form-text">Evita importar pedimentos que ya existen en el sistema</div>
</div>
</div>
<div id="previewContainer" style="display: none;" class="mb-3">
<h6 class="fw-semibold mb-2">
<i class="fas fa-eye me-1"></i>
Vista previa del archivo
</h6>
<div id="previewContent" class="p-3" style="background: #f8fafc; border-radius: 8px; border: 1px solid var(--border-color); max-height: 200px; overflow-y: auto; font-family: monospace; font-size: 0.85rem;"></div>
</div>
</form>
</div>
<div class="modal-footer" style="border-top: 1px solid var(--border-color); border-radius: 0 0 12px 12px;">
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal" style="border-radius: 8px;">
<i class="fas fa-times me-1"></i>
Cancelar
</button>
<button type="button" class="btn btn-primary" id="btnProcesarArchivo" style="border-radius: 8px; background: var(--info); border-color: var(--info);" disabled>
<i class="fas fa-cogs me-1"></i>
Procesar Archivos
</button>
</div>
</div>
</div>
</div>
<!-- jQuery -->
<script src="https://code.jquery.com/jquery-3.6.0.min.js"></script>
<!-- SweetAlert2 -->
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/sweetalert2@11"></script>
<script>
// Toast helper (Bootstrap 5): muestra un toast en la esquina superior derecha
function ensureToastContainer() {
let container = document.getElementById('toastContainer');
if (!container) {
container = document.createElement('div');
container.id = 'toastContainer';
container.className = 'position-fixed';
container.style.top = 'calc(var(--navbar-height, 70px) + 10px)';
container.style.right = '16px';
container.style.zIndex = '11000';
document.body.appendChild(container);
}
return container;
}
function mostrarToastImportacion(estadisticas) {
const ped = Number(estadisticas.pedimentos_procesados || 0);
const fac = Number(estadisticas.facturas_procesadas || 0);
const par = Number(estadisticas.partidas_procesadas || 0);
const dup = Number(estadisticas.duplicados || 0);
const err = Number(estadisticas.errores || 0);
const variant = err > 0 ? 'danger' : (dup > 0 ? 'warning' : 'success');
const title = err > 0 ? 'Importación con advertencias' : 'Importación completada';
const container = ensureToastContainer();
const toast = document.createElement('div');
toast.className = `toast align-items-center text-bg-${variant} border-0 shadow`;
toast.setAttribute('role', 'alert');
toast.setAttribute('aria-live', 'assertive');
toast.setAttribute('aria-atomic', 'true');
toast.innerHTML = `
<div class="d-flex">
<div class="toast-body">
<div class="fw-semibold mb-1"><i class="fas ${variant==='success' ? 'fa-check-circle' : (variant==='warning' ? 'fa-exclamation-triangle' : 'fa-times-circle')} me-1"></i>${title}</div>
<div class="small">
<span class="me-2">Pedimentos <span class="badge bg-light text-dark">${ped}</span></span>
<span class="me-2">Facturas <span class="badge bg-light text-dark">${fac}</span></span>
<span class="me-2">Partidas <span class="badge bg-light text-dark">${par}</span></span>
${dup > 0 ? `<span class=\"me-2\">Duplicados <span class=\"badge bg-dark\">${dup}</span></span>` : ''}
${err > 0 ? `<span class=\"me-2\">Errores <span class=\"badge bg-dark\">${err}</span></span>` : ''}
</div>
</div>
<button type="button" class="btn-close btn-close-white me-2 m-auto" data-bs-dismiss="toast" aria-label="Close"></button>
</div>`;
container.appendChild(toast);
const bsToast = new bootstrap.Toast(toast, { autohide: true, delay: 7000 });
bsToast.show();
toast.addEventListener('hidden.bs.toast', () => toast.remove());
}
// Base URL del proyecto (generada desde PHP)
const BASE_URL = '<?php echo "http" . (isset($_SERVER["HTTPS"]) && $_SERVER["HTTPS"] === "on" ? "s" : "") . "://" . $_SERVER["HTTP_HOST"] . "/IMPORTADORES"; ?>';
console.log('Base URL:', BASE_URL);
// Initialize page animations
document.addEventListener('DOMContentLoaded', function() {
const fadeElements = document.querySelectorAll('.fade-in');
fadeElements.forEach((element, index) => {
element.style.animationDelay = (index * 0.1) + 's';
});
// Add click handlers for cards
const cards = document.querySelectorAll('.feature-card');
cards.forEach(card => {
card.addEventListener('mouseenter', function() {
this.style.transform = 'translateY(-6px)';
});
card.addEventListener('mouseleave', function() {
this.style.transform = 'translateY(0)';
});
});
// Configurar eventos del modal de importación
configurarModalImportacion();
});
// Funciones para las acciones de las cards
function irListaPedimentos() {
window.location.href = 'lista.php';
}
function irCrearPedimento() {
window.location.href = 'crear.php';
}
function importarPedimentos() {
const modal = new bootstrap.Modal(document.getElementById('modalImportarPedimentos'));
modal.show();
}
function proximamente() {
Swal.fire({
icon: 'info',
title: 'Próximamente',
text: 'Esta función estará disponible próximamente.',
confirmButtonColor: '#0d6efd'
confirmButtonColor: '#2563eb',
customClass: {
popup: 'rounded-3'
}
});
}
// Configuración del modal de importación
function configurarModalImportacion() {
const archivoInput = document.getElementById('archivoImportar');
const previewContainer = document.getElementById('previewContainer');
const previewContent = document.getElementById('previewContent');
const btnProcesar = document.getElementById('btnProcesarArchivo');
if (!archivoInput) return; // Si no existe el elemento, salir
// Manejar selección de archivos (múltiples)
archivoInput.addEventListener('change', function(e) {
const archivos = Array.from(e.target.files || []);
if (archivos.length > 0) {
// Validar extensiones de todos los archivos seleccionados
const extensionesValidas = ['txt', 'dat'];
for (const file of archivos) {
const nombreArchivo = file.name.toLowerCase();
const extension = nombreArchivo.split('.').pop();
const esJuliana = /^\d{3}$/.test(extension); // 3 dígitos exactos
if (!extensionesValidas.includes(extension) && !esJuliana) {
Swal.fire({
icon: 'error',
title: 'Archivo no válido',
text: `"${file.name}" no es válido. Usa .txt, .dat o extensión juliana (.001-.366)`,
confirmButtonColor: '#dc2626',
customClass: { popup: 'rounded-3' }
});
archivoInput.value = '';
previewContainer.style.display = 'none';
btnProcesar.disabled = true;
return;
}
}
// Mostrar vista previa del PRIMER archivo y el total seleccionado
const reader = new FileReader();
reader.onload = function(e) {
const contenido = e.target.result;
mostrarVistaPrevia(contenido, archivos.length);
btnProcesar.disabled = false;
};
reader.readAsText(archivos[0]);
} else {
previewContainer.style.display = 'none';
btnProcesar.disabled = true;
}
});
// Manejar procesamiento del archivo
btnProcesar.addEventListener('click', function() {
procesarArchivoImportacion();
});
}
function mostrarVistaPrevia(contenido, totalArchivos = 1) {
const previewContainer = document.getElementById('previewContainer');
const previewContent = document.getElementById('previewContent');
const lineas = contenido.split('\n').slice(0, 10); // Mostrar solo las primeras 10 líneas
const preview = lineas.map(linea => {
// Resaltar diferentes tipos de registros
let clase = '';
if (linea.startsWith('501')) {
clase = 'style="color: #059669; font-weight: bold;"'; // Verde para pedimentos
} else if (linea.startsWith('505')) {
clase = 'style="color: #d97706; font-weight: bold;"'; // Naranja para facturas
} else if (linea.startsWith('551')) {
clase = 'style="color: #2563eb; font-weight: bold;"'; // Azul para partidas
}
return `<div ${clase}>${linea}</div>`;
}).join('');
let extra = '';
if (contenido.split('\n').length > 10) {
extra += '<div style="color: #6b7280; font-style: italic;">... y más líneas</div>';
}
if (totalArchivos > 1) {
const restantes = totalArchivos - 1;
extra += `<div class="mt-2" style="color: #374151; font-weight: 600;">+ ${restantes} archivo${restantes === 1 ? '' : 's'} más seleccionado${restantes === 1 ? '' : 's'}</div>`;
}
previewContent.innerHTML = preview + extra;
previewContainer.style.display = 'block';
}
function procesarArchivoImportacion() {
const formData = new FormData(document.getElementById('formImportarPedimentos'));
// Mostrar loading en el botón
const btnProcesar = document.getElementById('btnProcesarArchivo');
const textoOriginal = btnProcesar.innerHTML;
btnProcesar.innerHTML = '<i class="fas fa-spinner fa-spin me-1"></i>Procesando...';
btnProcesar.disabled = true;
// Llamada AJAX al backend
const url = `${BASE_URL}/public/importar_pedimentos.php`;
console.log('Attempting to fetch:', url);
fetch(url, {
method: 'POST',
body: formData
})
.then(response => {
console.log('Response status:', response.status);
console.log('Response headers:', response.headers);
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
return response.json();
})
.then(data => {
console.log('Response data:', data);
// Restaurar botón
btnProcesar.innerHTML = textoOriginal;
btnProcesar.disabled = false;
if (data.success) {
// Verificar si es respuesta de prueba o de importación real
if (data.estadisticas) {
// Respuesta de importación real
const estadisticas = data.estadisticas;
// Mostrar toast superior derecho en lugar del modal
mostrarToastImportacion(estadisticas);
} else if (data.diagnostico) {
// Respuesta de diagnóstico
let mensaje = `🔍 Diagnóstico del Sistema:\n\n`;
// Mostrar checks
if (data.checks) {
mensaje += `📊 Estado de Componentes:\n`;
mensaje += `• Sesión: ${data.checks.sesion ? '✅' : '❌'}\n`;
mensaje += `• Usuario ID: ${data.checks.usuario_id || 'N/A'}\n`;
mensaje += `• Tipo Usuario: ${data.checks.tipo_usuario || 'N/A'}\n`;
mensaje += `• Método POST: ${data.checks.metodo_post ? '✅' : '❌'}\n`;
mensaje += `• Archivo recibido: ${data.checks.archivo_recibido ? '✅' : '❌'}\n`;
mensaje += `• Conexión DB: ${data.checks.conexion_db ? '✅' : '❌'}\n`;
mensaje += `• Dir. temporal: ${data.checks.directorio_temp_existe ? '✅' : '❌'}\n\n`;
}
// Mostrar errores potenciales
if (data.errores_potenciales && data.errores_potenciales.length > 0) {
mensaje += `⚠️ Problemas detectados:\n`;
data.errores_potenciales.forEach(error => {
mensaje += `• ${error}\n`;
});
mensaje += `\n`;
}
// Información del archivo
if (data.checks?.archivo_recibido) {
mensaje += `📁 Archivo: ${data.checks.archivo_nombre}\n`;
if (data.total_lineas) {
mensaje += `📏 Total líneas: ${data.total_lineas}\n`;
}
}
const icono = data.errores_potenciales.length > 0 ? 'warning' : 'success';
const titulo = data.errores_potenciales.length > 0 ? 'Problemas Detectados' : 'Sistema OK';
Swal.fire({
icon: icono,
title: titulo,
text: mensaje,
confirmButtonColor: icono === 'warning' ? '#d97706' : '#059669',
customClass: {
popup: 'rounded-3'
}
});
} else {
// Respuesta de prueba genérica
let mensaje = `✅ Conexión exitosa!\n\n`;
mensaje += `📁 Archivo: ${data.files_data?.nombre || 'No detectado'}\n`;
mensaje += `📏 Tamaño: ${data.files_data?.tamaño || 'N/A'} bytes\n`;
mensaje += `🕒 Timestamp: ${data.timestamp}\n`;
mensaje += `👤 Usuario: ${data.usuario}\n\n`;
mensaje += ` Esto es una prueba de conexión.`;
Swal.fire({
icon: 'info',
title: 'Prueba de Conexión',
text: mensaje,
confirmButtonColor: '#2563eb',
customClass: {
popup: 'rounded-3'
}
});
}
} else {
// Mostrar error
Swal.fire({
icon: 'error',
title: 'Error en la Importación',
text: data.message || 'Ocurrió un error al procesar el archivo',
confirmButtonColor: '#dc2626',
customClass: {
popup: 'rounded-3'
}
});
}
// Cerrar modal solo si fue exitoso
if (data.success) {
const modal = bootstrap.Modal.getInstance(document.getElementById('modalImportarPedimentos'));
modal.hide();
// Limpiar formulario
document.getElementById('formImportarPedimentos').reset();
document.getElementById('previewContainer').style.display = 'none';
}
})
.catch(error => {
// Restaurar botón en caso de error
btnProcesar.innerHTML = textoOriginal;
btnProcesar.disabled = false;
console.error('Error:', error);
Swal.fire({
icon: 'error',
title: 'Error de Conexión',
text: 'No se pudo conectar con el servidor. Verifique su conexión e intente nuevamente.',
confirmButtonColor: '#dc2626',
customClass: {
popup: 'rounded-3'
}
});
});
}
// Funciones para gestión de datos
function verificarPedimentosExistentes() {
fetch(`${BASE_URL}/public/verificar_pedimentos.php`)
.then(response => response.json())
.then(data => {
if (data.success) {
const stats = data.estadisticas;
let mensaje = `📊 Pedimentos en Base de Datos:\n\n`;
mensaje += `• Total: ${stats.total_pedimentos}\n`;
mensaje += `• Usuarios: ${stats.usuarios_distintos}\n`;
if (stats.total_pedimentos > 0) {
mensaje += `• Primer pedimento: ${stats.primer_pedimento?.date || 'N/A'}\n`;
mensaje += `• Último pedimento: ${stats.ultimo_pedimento?.date || 'N/A'}\n\n`;
if (data.pedimentos_existentes.length > 0) {
mensaje += `🔍 Últimos 5 pedimentos:\n`;
data.pedimentos_existentes.slice(0, 5).forEach(p => {
mensaje += `• ${p.numero_pedimento} (${p.fecha_creacion?.date || 'N/A'})\n`;
});
}
}
Swal.fire({
icon: 'info',
title: 'Pedimentos Existentes',
text: mensaje,
confirmButtonColor: '#2563eb'
});
}
})
.catch(error => console.error('Error:', error));
}
function limpiarBaseDatos() {
Swal.fire({
title: '⚠️ Confirmar Limpieza',
text: 'Esto eliminará TODOS los pedimentos, facturas y partidas. ¿Estás seguro?',
icon: 'warning',
showCancelButton: true,
confirmButtonColor: '#dc2626',
cancelButtonColor: '#6b7280',
confirmButtonText: 'Sí, limpiar',
cancelButtonText: 'Cancelar'
}).then((result) => {
if (result.isConfirmed) {
fetch(`${BASE_URL}/public/limpiar_pedimentos.php`, {
method: 'POST'
})
.then(response => response.json())
.then(data => {
if (data.success) {
Swal.fire({
icon: 'success',
title: 'Base de Datos Limpiada',
text: 'Todos los registros han sido eliminados. Ahora puedes importar pedimentos sin duplicados.',
confirmButtonColor: '#059669'
});
} else {
Swal.fire({
icon: 'error',
title: 'Error al Limpiar',
text: data.message || 'No se pudo limpiar la base de datos',
confirmButtonColor: '#dc2626'
});
}
})
.catch(error => {
Swal.fire({
icon: 'error',
title: 'Error de Conexión',
text: 'No se pudo conectar al servidor para limpiar la BD',
confirmButtonColor: '#dc2626'
});
});
}
});
}
// Limpiar modal al cerrar
const modal = document.getElementById('modalImportarPedimentos');
if (modal) {
modal.addEventListener('hidden.bs.modal', function() {
document.getElementById('formImportarPedimentos').reset();
document.getElementById('previewContainer').style.display = 'none';
document.getElementById('btnProcesarArchivo').disabled = true;
});
}
</script>

View File

@@ -4,80 +4,316 @@
<html lang="es">
<head>
<meta charset="UTF-8">
<title>📋 Catálogo de Pedimentos</title>
<title>Lista de Pedimentos</title>
<meta name="viewport" content="width=device-width, initial-scale=1">
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css" rel="stylesheet">
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/js/bootstrap.bundle.min.js"></script>
<link href="https://cdn.datatables.net/1.13.4/css/jquery.dataTables.min.css" rel="stylesheet">
<!-- Font Awesome -->
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet">
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&display=swap" rel="stylesheet">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0/css/all.min.css">
<!-- Animate.css -->
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/animate.css/4.1.1/animate.min.css">
<link href="https://cdn.datatables.net/1.13.4/css/dataTables.bootstrap5.min.css" rel="stylesheet">
<style>
table.dataTable thead th { background:#343a40; color:#fff; }
body { font-family: 'Segoe UI', sans-serif; background-color: #f4f6f9; }
.sidebar { width: 220px; height: 100vh; background-color: #343a40; position: fixed; top: 0; left: 0; padding-top: 56px; z-index: 1040; }
.sidebar .nav-link { font-weight: bold; color: white; transition: all 0.3s ease; }
.sidebar .nav-link:hover,
.sidebar .nav-link.active { background-color: #495057; color: #fff; }
.content { margin-top: 56px; padding: 40px 20px; position: relative; z-index: 1; background-color: #f4f6f9; transition: margin-left 0.3s ease;}
.navbar { position: fixed; top: 0; width: 100%; z-index: 1050; }
/* A partir de dispositivos medianos (>=768px), deja espacio lateral */
@media (min-width: 768px) { .content { margin-left: 250px; } }
/* En móviles, sin margen lateral */
@media (max-width: 767.98px) {
.content { margin-left: 0; }
.sidebar .nav-link { font-weight: normal; color: #343a40; background-color: transparent; }
.sidebar .nav-link:hover,
.sidebar .nav-link.active { background-color: #e9ecef; color: #212529; }
:root {
--primary: #2563eb;
--primary-dark: #1d4ed8;
--secondary: #64748b;
--success: #059669;
--warning: #d97706;
--danger: #dc2626;
--info: #0891b2;
--light: #f8fafc;
--dark: #0f172a;
--border-color: #e2e8f0;
--text-muted: #64748b;
--bg-body: #f8fafc;
}
.card { border-radius: 12px; }
/* Animaciones personalizadas */
.card-hover { transition: all 0.3s ease; cursor: pointer; }
.card-hover:hover { transform: translateY(-8px) scale(1.02); box-shadow: 0 1rem 2rem rgba(0, 0, 0, 0.15) !important; }
.btn-pulse { animation: pulse 2s infinite; }
@keyframes pulse {
0% { transform: scale(1); }
50% { transform: scale(1.05); }
100% { transform: scale(1); }
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
.fade-in-up { animation: fadeInUp 0.8s ease-out; }
@keyframes fadeInUp {
from { opacity: 0; transform: translateY(30px); }
to { opacity: 1; transform: translateY(0); }
body {
font-family: 'Inter', -apple-system, BlinkMacSystemFont, sans-serif;
background-color: var(--bg-body);
color: var(--dark);
line-height: 1.6;
}
/* Responsive animations */
@media (max-width: 768px) { .card-hover:hover { transform: translateY(-4px) scale(1.01); } }
/* Efecto de glow para elementos activos */
.btn.active { box-shadow: 0 0 20px rgba(var(--bs-primary-rgb), 0.5); }
/* Animación para el título */
.title-glow { text-shadow: 0 0 10px rgba(0, 123, 255, 0.3); }
/* Efecto para botones */
.btn-animated { transition: all 0.3s ease; position: relative; overflow: hidden; }
.btn-animated:hover { transform: translateY(-2px); box-shadow: 0 5px 15px rgba(0, 0, 0, 0.2); }
.btn-animated::before { content: ''; position: absolute; top: 0; left: -100%; width: 100%; height: 100%;
background: linear-gradient(90deg, transparent, rgba(255, 255, 255, 0.2), transparent); transition: left 0.5s; }
.btn-animated:hover::before { left: 100%; }
.main-content {
margin-left: 280px;
padding: 2rem;
min-height: 100vh;
transition: margin-left 0.3s ease;
}
@media (max-width: 768px) {
.main-content {
margin-left: 0;
padding: 1rem;
}
}
.page-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 2rem;
flex-wrap: wrap;
gap: 1rem;
}
.page-title {
color: var(--dark);
font-weight: 600;
font-size: 1.75rem;
margin: 0;
}
.btn-modern {
background: var(--primary);
color: white;
border: none;
padding: 0.625rem 1.25rem;
border-radius: 6px;
font-weight: 500;
font-size: 0.875rem;
transition: all 0.2s ease;
text-decoration: none;
display: inline-flex;
align-items: center;
gap: 0.5rem;
}
.btn-modern:hover {
background: var(--primary-dark);
color: white;
transform: translateY(-1px);
text-decoration: none;
}
.btn-modern.success {
background: var(--success);
}
.btn-modern.success:hover {
background: #047857;
}
.btn-modern.secondary {
background: var(--secondary);
}
.btn-modern.secondary:hover {
background: #475569;
}
.btn-modern.danger {
background: var(--danger);
}
.btn-modern.danger:hover {
background: #b91c1c;
}
.data-card {
background: white;
border: 1px solid var(--border-color);
border-radius: 12px;
padding: 0;
overflow: hidden;
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.08);
}
.table-container {
border-radius: 12px;
overflow: hidden;
}
.table {
margin-bottom: 0;
font-size: 0.9rem;
}
.table thead th {
background: linear-gradient(135deg, var(--primary) 0%, var(--primary-dark) 100%);
color: white;
border: none;
padding: 1rem 0.75rem;
font-weight: 600;
font-size: 0.875rem;
text-transform: uppercase;
letter-spacing: 0.025em;
border-bottom: 3px solid rgba(255, 255, 255, 0.1);
}
.table tbody td {
padding: 0.875rem 0.75rem;
border-color: var(--border-color);
vertical-align: middle;
}
.table tbody td:first-child {
font-weight: 600;
color: var(--primary);
font-family: 'JetBrains Mono', 'Courier New', monospace;
}
.table tbody tr {
transition: all 0.2s ease;
}
.table tbody tr:hover {
background-color: rgba(37, 99, 235, 0.05);
transform: translateY(-1px);
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
}
.badge-modern {
padding: 0.4rem 0.8rem;
border-radius: 20px;
font-size: 0.75rem;
font-weight: 600;
text-transform: uppercase;
letter-spacing: 0.025em;
display: inline-flex;
align-items: center;
gap: 0.25rem;
}
.badge-success {
background: linear-gradient(135deg, var(--success), #047857);
color: white;
box-shadow: 0 2px 4px rgba(5, 150, 105, 0.2);
}
.badge-secondary {
background: linear-gradient(135deg, var(--secondary), #475569);
color: white;
box-shadow: 0 2px 4px rgba(100, 116, 139, 0.2);
}
.btn-action {
padding: 0.5rem 0.75rem;
border-radius: 8px;
font-size: 0.75rem;
margin: 0 0.25rem;
transition: all 0.2s ease;
border: 1px solid transparent;
display: inline-flex;
align-items: center;
gap: 0.25rem;
}
.btn-action:hover {
transform: translateY(-2px);
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
}
.btn-action.edit {
background: var(--primary);
color: white;
}
.btn-action.edit:hover {
background: var(--primary-dark);
color: white;
}
.btn-action.delete {
background: var(--danger);
color: white;
}
.btn-action.delete:hover {
background: #b91c1c;
color: white;
}
/* DataTables customization */
.dataTables_wrapper .dataTables_length,
.dataTables_wrapper .dataTables_filter,
.dataTables_wrapper .dataTables_info,
.dataTables_wrapper .dataTables_paginate {
margin: 1rem;
}
.dataTables_wrapper .dataTables_length select,
.dataTables_wrapper .dataTables_filter input {
border: 1px solid var(--border-color);
border-radius: 6px;
padding: 0.375rem 0.75rem;
}
.dataTables_wrapper .dataTables_paginate .paginate_button {
padding: 0.375rem 0.75rem !important;
margin: 0 0.125rem !important;
border-radius: 6px !important;
border: 1px solid var(--border-color) !important;
color: var(--dark) !important;
}
.dataTables_wrapper .dataTables_paginate .paginate_button.current {
background: var(--primary) !important;
color: white !important;
border-color: var(--primary) !important;
}
.dataTables_wrapper .dataTables_paginate .paginate_button:hover {
background: var(--primary) !important;
color: white !important;
border-color: var(--primary) !important;
}
.fade-in {
animation: fadeIn 0.5s ease-out;
}
@keyframes fadeIn {
from { opacity: 0; transform: translateY(10px); }
to { opacity: 1; transform: translateY(0); }
}
/* Drawer lateral */
.drawer { position: fixed; inset: 0; z-index: 99999; display: none; }
.drawer.open { display: block; }
.drawer-overlay { position: absolute; inset: 0; background: rgba(0,0,0,0.35); z-index: 99998; }
.drawer-panel { position: absolute; top: 0; right: -520px; width: 520px; max-width: 94vw; height: 100%; background: #fff; border-left: 1px solid var(--border-color); box-shadow: -8px 0 24px rgba(0,0,0,0.1); transition: right 0.3s ease; display: flex; flex-direction: column; z-index: 99999; }
.drawer.open .drawer-panel { right: 0; }
.drawer-header { padding: 1rem 1rem; border-bottom: 1px solid var(--border-color); display: flex; justify-content: space-between; align-items: center; }
.drawer-title { font-weight: 600; font-size: 1.1rem; }
.drawer-subtitle { color: var(--text-muted); font-size: 0.9rem; }
.page-subtitle { color: var(--text-muted); font-weight: 500; font-size: 1rem; margin-left: 0.75rem; }
</style>
</head>
<body>
<div class="content">
<div class="d-flex justify-content-between align-items-center mb-4">
<h4 class="animate__animated animate__fadeInDown title-glow">📋 Lista de Pedimentos</h4>
<div>
<a href="/IMPORTADORES/catalogo_pedimentos/crear" class="btn btn-success btn-animated me-2">
<i class="fas fa-plus"></i> Nuevo Pedimento
<div class="main-content">
<!-- Page Header -->
<div class="page-header">
<h1 class="page-title">Lista de Pedimentos <span id="headerPedimentoActual" class="page-subtitle"></span></h1>
<div class="d-flex gap-2">
<a href="/IMPORTADORES/catalogo_pedimentos/crear" class="btn-modern success">
<i class="fas fa-plus"></i>
Nuevo Pedimento
</a>
<a href="/IMPORTADORES/catalogo_pedimentos" class="btn btn-secondary btn-animated">
<i class="fas fa-arrow-left"></i> Regresar
<button type="button" class="btn-modern" style="background: var(--info);" onclick="abrirModalImportar()">
<i class="fas fa-upload"></i>
Importar Archivos
</button>
<button type="button" id="btnImportarWinsaai" class="btn-modern secondary" onclick="importarDesdeWinsaai()" title="Requiere configuración en Automatizaciones (WINSAAI)">
<i class="fas fa-cloud-download-alt"></i>
Importar desde WINSAAI
</button>
<a href="/IMPORTADORES/catalogo_pedimentos" class="btn-modern secondary">
<i class="fas fa-arrow-left"></i>
Regresar
</a>
</div>
</div>
<div class="card p-3 shadow-sm card-hover position-relative h-auto fade-in-up">
<div class="table-responsive">
<table id="tablaPedimentos" class="display nowrap" style="width:100%">
<!-- Data Table -->
<div class="data-card fade-in">
<div class="table-container">
<table id="tablaPedimentos" class="table table-hover">
<thead>
<tr>
<th>ID</th>
@@ -97,16 +333,152 @@
</div>
</div>
<!-- jQuery -->
<!-- Modal para Importar Archivos -->
<div class="modal fade" id="modalImportarPedimentos" tabindex="-1" aria-labelledby="modalImportarLabel" aria-hidden="true">
<div class="modal-dialog modal-lg">
<div class="modal-content" style="border-radius: 12px; border: none;">
<div class="modal-header" style="background: linear-gradient(135deg, var(--info) 0%, #0e7490 100%); color: white; border-radius: 12px 12px 0 0;">
<h5 class="modal-title" id="modalImportarLabel">
<i class="fas fa-upload me-2"></i>
Importar Pedimentos desde Archivos
</h5>
<button type="button" class="btn-close btn-close-white" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<div class="modal-body p-4">
<div class="alert alert-info d-flex align-items-start" style="border: none; border-radius: 8px; background: rgba(8, 145, 178, 0.1); border-left: 4px solid var(--info);">
<i class="fas fa-info-circle me-2 mt-1" style="color: var(--info);"></i>
<div>
<strong>Formato del archivo:</strong>
<p class="mb-1">El sistema reconoce archivos con los siguientes códigos:</p>
<ul class="mb-2 small">
<li><strong>501:</strong> Datos del pedimento (número, RFC, fechas)</li>
<li><strong>505:</strong> Información de facturas</li>
<li><strong>551:</strong> Partidas de mercancías</li>
</ul>
<p class="mb-0 small text-muted">
<i class="fas fa-calendar-day me-1"></i>
<strong>Archivos julianos:</strong> Se aceptan extensiones de día del año (.001 a .366)
</p>
</div>
</div>
<form id="formImportarPedimentos" enctype="multipart/form-data">
<div class="mb-3">
<label for="archivoImportar" class="form-label fw-semibold">
<i class="fas fa-file-text me-1"></i>
Seleccionar archivos
</label>
<input type="file" class="form-control" id="archivoImportar" name="archivos[]" multiple required style="border-radius: 8px; border: 1px solid var(--border-color);">
<div class="form-text">Formatos soportados: .txt, .dat, archivos julianos (.001-.366). Puedes seleccionar varios archivos.</div>
</div>
<div class="mb-3">
<div class="form-check">
<input class="form-check-input" type="checkbox" id="validarDuplicados" name="validar_duplicados" checked>
<label class="form-check-label fw-medium" for="validarDuplicados">
Validar pedimentos duplicados
</label>
<div class="form-text">Evita importar pedimentos que ya existen en el sistema</div>
</div>
</div>
<div id="previewContainer" style="display: none;" class="mb-3">
<h6 class="fw-semibold mb-2">
<i class="fas fa-eye me-1"></i>
Vista previa del archivo
</h6>
<div id="previewContent" class="p-3" style="background: #f8fafc; border-radius: 8px; border: 1px solid var(--border-color); max-height: 200px; overflow-y: auto; font-family: monospace; font-size: 0.85rem;"></div>
</div>
</form>
</div>
<div class="modal-footer" style="border-top: 1px solid var(--border-color); border-radius: 0 0 12px 12px;">
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal" style="border-radius: 8px;">
<i class="fas fa-times me-1"></i>
Cancelar
</button>
<button type="button" class="btn btn-primary" id="btnProcesarArchivo" style="border-radius: 8px; background: var(--info); border-color: var(--info);" disabled>
<i class="fas fa-cogs me-1"></i>
Procesar Archivos
</button>
</div>
</div>
</div>
</div>
<!-- Drawer lateral de detalle -->
<div id="drawerDetalle" class="drawer" aria-hidden="true">
<div class="drawer-overlay" onclick="cerrarDetalle()"></div>
<div class="drawer-panel">
<div class="drawer-header">
<div>
<div class="drawer-title">Pedimento <span id="drawerNumero"></span></div>
<div class="drawer-subtitle" id="drawerSubtitulo"></div>
</div>
<button type="button" class="btn btn-outline-secondary btn-sm" onclick="cerrarDetalle()" aria-label="Cerrar">&times;</button>
</div>
<div class="drawer-body p-3 overflow-auto">
<ul class="nav nav-tabs" id="detalleTabs" role="tablist">
<li class="nav-item" role="presentation">
<button class="nav-link active" id="tab-facturas" data-bs-toggle="tab" data-bs-target="#pane-facturas" type="button" role="tab">Facturas</button>
</li>
<li class="nav-item" role="presentation">
<button class="nav-link" id="tab-partidas" data-bs-toggle="tab" data-bs-target="#pane-partidas" type="button" role="tab">Partidas</button>
</li>
</ul>
<div class="tab-content pt-3">
<div class="tab-pane fade show active" id="pane-facturas" role="tabpanel">
<div class="table-responsive">
<table class="table table-sm table-striped align-middle">
<thead>
<tr>
<th>#</th>
<th>Número</th>
<th>Fecha</th>
<th>Proveedor</th>
<th>Moneda</th>
<th>Total</th>
</tr>
</thead>
<tbody id="tbodyFacturas"></tbody>
</table>
</div>
</div>
<div class="tab-pane fade" id="pane-partidas" role="tabpanel">
<div class="table-responsive">
<table class="table table-sm table-striped align-middle">
<thead>
<tr>
<th>#</th>
<th>Fracción</th>
<th>Descripción</th>
<th>Cantidad</th>
<th>UM</th>
<th>Valor Aduana</th>
</tr>
</thead>
<tbody id="tbodyPartidas"></tbody>
</table>
</div>
</div>
</div>
</div>
</div>
</div>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js"></script>
<script src="https://code.jquery.com/jquery-3.6.0.min.js"></script>
<!-- DataTables JS -->
<script src="https://cdn.datatables.net/1.13.4/js/jquery.dataTables.min.js"></script>
<!-- SweetAlert2 -->
<script src="https://cdn.datatables.net/1.13.4/js/dataTables.bootstrap5.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/sweetalert2@11"></script>
<script>
$(document).ready(function() {
// Inicializar DataTable
// Base URL del proyecto (igual que en index)
const BASE_URL = '<?php echo "http" . (isset($_SERVER["HTTPS"]) && $_SERVER["HTTPS"] === "on" ? "s" : "") . "://" . $_SERVER["HTTP_HOST"] . "/IMPORTADORES"; ?>';
let hasWinsaaiConfig = false;
$(document).ready(function() {
// Initialize DataTable with professional styling
$('#tablaPedimentos').DataTable({
"processing": true,
"serverSide": true,
@@ -127,9 +499,9 @@
"name": "Status",
"render": function(data, type, row) {
if (data === 'Activo') {
return '<span class="badge bg-success">Activo</span>';
return '<span class="badge-modern badge-success"><i class="fas fa-check-circle"></i>Activo</span>';
} else {
return '<span class="badge bg-secondary">Inactivo</span>';
return '<span class="badge-modern badge-secondary"><i class="fas fa-pause-circle"></i>Inactivo</span>';
}
}
},
@@ -140,12 +512,20 @@
"render": function(data, type, row) {
const id = row[0];
return `
<a href="/IMPORTADORES/catalogo_pedimentos/editar?id=${id}" class="btn btn-sm btn-primary btn-animated" title="Editar">
<i class="fas fa-edit"></i>
</a>
<button type="button" class="btn btn-sm btn-danger btn-animated" onclick="confirmarEliminar(${id})" title="Eliminar">
<i class="fas fa-trash"></i>
</button>
<div class="d-flex justify-content-center gap-1">
<button type="button" class="btn-action view" onclick="abrirDetalle(${id})" title="Ver detalle">
<i class="fas fa-list"></i>
<span>Detalle</span>
</button>
<a href="/IMPORTADORES/catalogo_pedimentos/editar?id=${id}" class="btn-action edit" title="Editar pedimento">
<i class="fas fa-edit"></i>
<span>Editar</span>
</a>
<button type="button" class="btn-action delete" onclick="confirmarEliminar(${id})" title="Eliminar pedimento">
<i class="fas fa-trash-alt"></i>
<span>Eliminar</span>
</button>
</div>
`;
}
}
@@ -156,8 +536,23 @@
"responsive": true,
"order": [[4, "desc"]],
"pageLength": 25,
"lengthMenu": [[10, 25, 50, 100], [10, 25, 50, 100]]
"lengthMenu": [[10, 25, 50, 100], [10, 25, 50, 100]],
"dom": "<'row'<'col-sm-6'l><'col-sm-6'f>>" +
"<'row'<'col-sm-12'tr>>" +
"<'row'<'col-sm-5'i><'col-sm-7'p>>"
});
// Add fade-in animation
setTimeout(() => {
document.querySelector('.fade-in').style.opacity = '1';
document.querySelector('.fade-in').style.transform = 'translateY(0)';
}, 100);
// Configurar modal importar
configurarModalImportacion();
// Cargar estado de configuración WINSAAI
cargarConfigWinsaai();
});
function confirmarEliminar(id) {
@@ -168,7 +563,11 @@
showCancelButton: true,
confirmButtonText: 'Sí, eliminar',
cancelButtonText: 'Cancelar',
confirmButtonColor: '#d33'
confirmButtonColor: '#dc2626',
cancelButtonColor: '#64748b',
customClass: {
popup: 'rounded-3'
}
}).then(result => {
if (result.isConfirmed) {
window.location = `/IMPORTADORES/catalogo_pedimentos/eliminar?id=${id}`;
@@ -176,18 +575,378 @@
});
}
// Mostrar alertas de éxito/error
// Success/Error alerts
<?php if (isset($_GET['created'])): ?>
Swal.fire({ icon: 'success', title: 'Pedimento creado', text: 'El pedimento se creó correctamente.', confirmButtonColor: '#198754' });
Swal.fire({
icon: 'success',
title: 'Pedimento creado',
text: 'El pedimento se creó correctamente.',
confirmButtonColor: '#059669',
customClass: { popup: 'rounded-3' }
});
<?php endif; ?>
<?php if (isset($_GET['updated'])): ?>
Swal.fire({ icon: 'success', title: 'Pedimento actualizado', text: 'Los datos fueron modificados correctamente.', confirmButtonColor: '#198754' });
Swal.fire({
icon: 'success',
title: 'Pedimento actualizado',
text: 'Los datos fueron modificados correctamente.',
confirmButtonColor: '#059669',
customClass: { popup: 'rounded-3' }
});
<?php endif; ?>
<?php if (isset($_GET['deleted'])): ?>
Swal.fire({ icon: 'success', title: 'Pedimento eliminado', text: 'El pedimento fue eliminado correctamente.', confirmButtonColor: '#198754' });
Swal.fire({
icon: 'success',
title: 'Pedimento eliminado',
text: 'El pedimento fue eliminado correctamente.',
confirmButtonColor: '#059669',
customClass: { popup: 'rounded-3' }
});
<?php endif; ?>
// --- Importación de pedimentos (múltiples) ---
async function cargarConfigWinsaai() {
try {
const resp = await fetch(`${BASE_URL}/winsaai/get_config`);
const data = await resp.json();
hasWinsaaiConfig = !!(data && data.success && data.data);
} catch (e) {
hasWinsaaiConfig = false;
}
// Ajustar estilo/tooltip del botón según estado
const btn = document.getElementById('btnImportarWinsaai');
if (!btn) return;
if (hasWinsaaiConfig) {
btn.classList.remove('secondary');
btn.style.background = 'var(--success)';
btn.title = 'Configuración detectada: listo para importar (próximamente)';
} else {
btn.classList.add('secondary');
btn.style.background = 'var(--secondary)';
btn.title = 'Requiere configurar WINSAAI en Automatizaciones';
}
}
function importarDesdeWinsaai() {
if (!hasWinsaaiConfig) {
Swal.fire({
icon: 'warning',
title: 'Configura WINSAAI primero',
text: 'Para importar desde WINSAAI necesitas configurar la conexión en la sección Automatizaciones.',
showCancelButton: true,
confirmButtonText: 'Ir a Automatizaciones',
cancelButtonText: 'Cancelar',
confirmButtonColor: '#2563eb',
cancelButtonColor: '#64748b',
customClass: { popup: 'rounded-3' }
}).then(r => {
if (r.isConfirmed) {
window.location.href = `${BASE_URL}/automatizaciones`;
}
});
return;
}
// Placeholder: funcionalidad próxima
Swal.fire({
icon: 'info',
title: 'Próximamente',
text: 'La importación directa desde WINSAAI estará disponible pronto.',
confirmButtonColor: '#2563eb',
customClass: { popup: 'rounded-3' }
});
}
function abrirModalImportar() {
const modal = new bootstrap.Modal(document.getElementById('modalImportarPedimentos'));
modal.show();
}
function configurarModalImportacion() {
const archivoInput = document.getElementById('archivoImportar');
const previewContainer = document.getElementById('previewContainer');
const previewContent = document.getElementById('previewContent');
const btnProcesar = document.getElementById('btnProcesarArchivo');
if (!archivoInput) return;
archivoInput.addEventListener('change', function(e) {
const archivos = Array.from(e.target.files || []);
if (archivos.length > 0) {
const extensionesValidas = ['txt', 'dat'];
for (const file of archivos) {
const nombreArchivo = file.name.toLowerCase();
const extension = nombreArchivo.split('.').pop();
const esJuliana = /^\d{3}$/.test(extension);
if (!extensionesValidas.includes(extension) && !esJuliana) {
Swal.fire({
icon: 'error',
title: 'Archivo no válido',
text: `"${file.name}" no es válido. Usa .txt, .dat o extensión juliana (.001-.366)`,
confirmButtonColor: '#dc2626',
customClass: { popup: 'rounded-3' }
});
archivoInput.value = '';
previewContainer.style.display = 'none';
btnProcesar.disabled = true;
return;
}
}
const reader = new FileReader();
reader.onload = function(e) {
const contenido = e.target.result;
mostrarVistaPrevia(contenido, archivos.length);
btnProcesar.disabled = false;
};
reader.readAsText(archivos[0]);
} else {
previewContainer.style.display = 'none';
btnProcesar.disabled = true;
}
});
btnProcesar.addEventListener('click', function() {
procesarArchivoImportacion();
});
}
function mostrarVistaPrevia(contenido, totalArchivos = 1) {
const previewContainer = document.getElementById('previewContainer');
const previewContent = document.getElementById('previewContent');
const lineas = contenido.split('\n').slice(0, 10);
const preview = lineas.map(linea => {
let clase = '';
if (linea.startsWith('501')) clase = 'style="color: #059669; font-weight: bold;"';
else if (linea.startsWith('505')) clase = 'style="color: #d97706; font-weight: bold;"';
else if (linea.startsWith('551')) clase = 'style="color: #2563eb; font-weight: bold;"';
return `<div ${clase}>${linea}</div>`;
}).join('');
let extra = '';
if (contenido.split('\n').length > 10) {
extra += '<div style="color: #6b7280; font-style: italic;">... y más líneas</div>';
}
if (totalArchivos > 1) {
const restantes = totalArchivos - 1;
extra += `<div class="mt-2" style="color: #374151; font-weight: 600;">+ ${restantes} archivo${restantes === 1 ? '' : 's'} más seleccionado${restantes === 1 ? '' : 's'}</div>`;
}
previewContent.innerHTML = preview + extra;
previewContainer.style.display = 'block';
}
function procesarArchivoImportacion() {
const formData = new FormData(document.getElementById('formImportarPedimentos'));
const btnProcesar = document.getElementById('btnProcesarArchivo');
const textoOriginal = btnProcesar.innerHTML;
btnProcesar.innerHTML = '<i class="fas fa-spinner fa-spin me-1"></i>Procesando...';
btnProcesar.disabled = true;
const url = `${BASE_URL}/public/importar_pedimentos.php`;
fetch(url, {
method: 'POST',
body: formData
})
.then(async response => {
let data;
try { data = await response.json(); } catch (_) { data = null; }
if (!response.ok) {
const msg = (data && data.message) ? data.message : `HTTP ${response.status}`;
throw new Error(msg);
}
return data;
})
.then(data => {
btnProcesar.innerHTML = textoOriginal;
btnProcesar.disabled = false;
if (data.success) {
const estadisticas = data.estadisticas || {};
let mensaje = `Se procesaron ${estadisticas.pedimentos_procesados || 0} pedimentos exitosamente.\n\n`;
mensaje += `• Pedimentos: ${estadisticas.pedimentos_procesados || 0}\n`;
mensaje += `• Facturas: ${estadisticas.facturas_procesadas || 0}\n`;
mensaje += `• Partidas: ${estadisticas.partidas_procesadas || 0}\n`;
if ((estadisticas.duplicados || 0) > 0) mensaje += `• Duplicados omitidos: ${estadisticas.duplicados}\n`;
if ((estadisticas.errores || 0) > 0) mensaje += `• Errores: ${estadisticas.errores}`;
Swal.fire({
icon: 'success',
title: 'Importación Completada',
text: mensaje,
confirmButtonColor: '#059669',
customClass: { popup: 'rounded-3' }
});
const modal = bootstrap.Modal.getInstance(document.getElementById('modalImportarPedimentos'));
modal.hide();
document.getElementById('formImportarPedimentos').reset();
document.getElementById('previewContainer').style.display = 'none';
// Recargar la tabla para ver nuevos pedimentos
$('#tablaPedimentos').DataTable().ajax.reload(null, false);
} else {
Swal.fire({
icon: 'error',
title: 'Error en la Importación',
text: data.message || 'Ocurrió un error al procesar los archivos',
confirmButtonColor: '#dc2626',
customClass: { popup: 'rounded-3' }
});
}
})
.catch(error => {
btnProcesar.innerHTML = textoOriginal;
btnProcesar.disabled = false;
Swal.fire({
icon: 'error',
title: 'Error de Conexión',
text: error.message || 'No se pudo conectar con el servidor.',
confirmButtonColor: '#dc2626',
customClass: { popup: 'rounded-3' }
});
});
}
// --- Drawer de detalle de pedimento ---
async function abrirDetalle(id) {
const drawer = document.getElementById('drawerDetalle');
const numeroEl = document.getElementById('drawerNumero');
const subEl = document.getElementById('drawerSubtitulo');
const tbodyF = document.getElementById('tbodyFacturas');
const tbodyP = document.getElementById('tbodyPartidas');
const headerPed = document.getElementById('headerPedimentoActual');
if (!drawer) return;
// Limpia contenidos previos
numeroEl.textContent = '';
subEl.textContent = '';
tbodyF.innerHTML = '<tr><td colspan="6" class="text-center text-muted">Cargando facturas...</td></tr>';
tbodyP.innerHTML = '<tr><td colspan="6" class="text-center text-muted">Cargando partidas...</td></tr>';
// Abre el drawer inmediatamente para mejor UX
drawer.classList.add('open');
drawer.setAttribute('aria-hidden', 'false');
try {
// Encabezado del pedimento
const rPed = await fetch(`${BASE_URL}/catalogo_pedimentos/ajax_pedimento?id=${encodeURIComponent(id)}`);
const dPed = await rPed.json();
if (!dPed || !dPed.success || !dPed.data) throw new Error(dPed.message || 'No se encontró el pedimento');
const ped = dPed.data;
const numeroPed = ped.numero_pedimento || ped.numero || ped.id || id;
numeroEl.textContent = numeroPed;
const nombre = ped.nombre_importador || ped.importador || '';
const rfc = ped.rfc_importador || ped.rfc || '';
const fecha = (ped.fecha_creacion || ped.fecha || '').toString().substring(0, 10);
subEl.textContent = `${nombre}${nombre && rfc ? ' • ' : ''}${rfc}${(nombre||rfc) && fecha ? ' • ' : ''}${fecha}`;
if (headerPed) {
headerPed.textContent = `• Pedimento ${numeroPed}`;
}
// Carga datos de detalle
await Promise.all([
cargarFacturas(id),
cargarPartidas(id)
]);
} catch (err) {
Swal.fire({
icon: 'error',
title: 'No se pudo cargar el detalle',
text: err.message || 'Error desconocido',
confirmButtonColor: '#dc2626',
customClass: { popup: 'rounded-3' }
});
cerrarDetalle();
}
}
function cerrarDetalle() {
const drawer = document.getElementById('drawerDetalle');
const headerPed = document.getElementById('headerPedimentoActual');
if (!drawer) return;
drawer.classList.remove('open');
drawer.setAttribute('aria-hidden', 'true');
if (headerPed) headerPed.textContent = '';
}
async function cargarFacturas(pedimentoId) {
const tbody = document.getElementById('tbodyFacturas');
try {
const r = await fetch(`${BASE_URL}/catalogo_pedimentos/ajax_facturas?pedimento_id=${encodeURIComponent(pedimentoId)}`);
const d = await r.json();
if (!d || !d.success) throw new Error(d.message || 'Error al cargar facturas');
const arr = Array.isArray(d.data) ? d.data : [];
if (arr.length === 0) {
tbody.innerHTML = '<tr><td colspan="6" class="text-center text-muted">Sin facturas</td></tr>';
return;
}
const rows = arr.map((f, idx) => {
const numero = f.numero_factura || f.factura || f.numero || '';
const fecha = (f.fecha || f.fecha_factura || '').toString().substring(0,10);
const proveedor = f.proveedor || f.nombre_proveedor || f.vendedor || '';
const moneda = f.moneda || '';
const totalRaw = f.total ?? f.importe_total ?? f.subtotal ?? f.valor_factura;
const total = (typeof totalRaw === 'number') ? totalRaw.toLocaleString(undefined, {minimumFractionDigits: 2, maximumFractionDigits: 2}) : (totalRaw || '');
return `<tr>
<td>${idx+1}</td>
<td>${numero || '-'}</td>
<td>${fecha || '-'}</td>
<td>${proveedor || '-'}</td>
<td>${moneda || '-'}</td>
<td class="text-end">${total || '-'}</td>
</tr>`;
}).join('');
tbody.innerHTML = rows;
} catch (e) {
tbody.innerHTML = '<tr><td colspan="6" class="text-center text-danger">Error cargando facturas</td></tr>';
}
}
async function cargarPartidas(pedimentoId) {
const tbody = document.getElementById('tbodyPartidas');
try {
const r = await fetch(`${BASE_URL}/catalogo_pedimentos/ajax_partidas?pedimento_id=${encodeURIComponent(pedimentoId)}`);
const d = await r.json();
if (!d || !d.success) throw new Error(d.message || 'Error al cargar partidas');
const arr = Array.isArray(d.data) ? d.data : [];
if (arr.length === 0) {
tbody.innerHTML = '<tr><td colspan="6" class="text-center text-muted">Sin partidas</td></tr>';
return;
}
const rows = arr.map((p, idx) => {
const fraccion = p.fraccion || p.fraccion_arancelaria || '';
const descripcion = p.descripcion || p.descripcion_mercancia || '';
const cantidad = p.cantidad ?? '';
const um = p.um || p.unidad || '';
const valorRaw = p.valor_aduana ?? p.valor_comercial ?? p.valor ?? '';
const valor = (typeof valorRaw === 'number') ? valorRaw.toLocaleString(undefined, {minimumFractionDigits: 2, maximumFractionDigits: 2}) : (valorRaw || '');
return `<tr>
<td>${idx+1}</td>
<td>${fraccion || '-'}</td>
<td>${descripcion || '-'}</td>
<td class="text-end">${cantidad || '-'}</td>
<td>${um || '-'}</td>
<td class="text-end">${valor || '-'}</td>
</tr>`;
}).join('');
tbody.innerHTML = rows;
} catch (e) {
tbody.innerHTML = '<tr><td colspan="6" class="text-center text-danger">Error cargando partidas</td></tr>';
}
}
// Cerrar con Escape
document.addEventListener('keyup', (ev) => {
if (ev.key === 'Escape') {
cerrarDetalle();
}
});
</script>
</body>

View File

@@ -9,92 +9,142 @@
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css" rel="stylesheet">
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/js/bootstrap.bundle.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/sweetalert2@11"></script>
<!-- Font Awesome -->
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.0/css/all.min.css">
<!-- Animate.css para animaciones adicionales -->
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/animate.css/4.1.1/animate.min.css">
<style>
body { font-family: 'Segoe UI', sans-serif; background-color: #f4f6f9; }
.sidebar { width: 220px; height: 100vh; background-color: #343a40; position: fixed; top: 0; left: 0; padding-top: 56px; z-index: 1040; }
.sidebar .nav-link { font-weight: bold; color: white; transition: all 0.3s ease; }
.sidebar .nav-link:hover, .sidebar .nav-link.active { background-color: #495057; color: #fff; }
.content { margin-top: 56px; padding: 40px 20px; position: relative; z-index: 1; background-color: #f4f6f9; transition: margin-left 0.3s ease; }
@media (min-width: 768px) { .content { margin-left: 250px; } }
@media (max-width: 767.98px) {
.content { margin-left: 0; }
.sidebar .nav-link { font-weight: normal; color: #343a40; background-color: transparent; }
.sidebar .nav-link:hover, .sidebar .nav-link.active { background-color: #e9ecef; color: #212529; }
}
.card { border-radius: 12px; }
/* Animaciones personalizadas */
.card-hover { transition: all 0.3s ease; cursor: pointer; }
.card-hover:hover { transform: translateY(-8px) scale(1.02); box-shadow: 0 1rem 2rem rgba(0, 0, 0, 0.15) !important; }
.btn-pulse { animation: pulse 2s infinite; }
@keyframes pulse {
0% { transform: scale(1); }
50% { transform: scale(1.05); }
100% { transform: scale(1); }
}
.fade-in-up { animation: fadeInUp 0.8s ease-out; }
@keyframes fadeInUp {
from { opacity: 0; transform: translateY(30px); }
to { opacity: 1; transform: translateY(0); }
}
/* Responsive animations */
@media (max-width: 768px) { .card-hover:hover { transform: translateY(-4px) scale(1.01); } }
/* Efecto de glow para elementos activos */
.btn.active { box-shadow: 0 0 20px rgba(var(--bs-primary-rgb), 0.5); }
/* Animación para el título */
.title-glow { text-shadow: 0 0 10px rgba(0, 123, 255, 0.3); }
/* Efecto para botones */
.btn-animated { transition: all 0.3s ease; position: relative; overflow: hidden; }
.btn-animated:hover { transform: translateY(-2px); box-shadow: 0 5px 15px rgba(0, 0, 0, 0.2); }
.btn-animated::before { content: ''; position: absolute; top: 0; left: -100%; width: 100%; height: 100%;
background: linear-gradient(90deg, transparent, rgba(255, 255, 255, 0.2), transparent); transition: left 0.5s; }
.btn-animated:hover::before { left: 100%; }
.table th, .table td { vertical-align: middle; }
.folder-icon { color: #f0ad4e; font-size: 1.2rem; }
body { font-family: 'Segoe UI', sans-serif; background-color: #f6f8fb; }
.content { margin-top: 56px; padding: 32px 20px; background-color: #f6f8fb; }
@media (min-width: 768px) { .content { margin-left: var(--sidebar-width, 280px); } }
.title { font-weight: 700; }
.kpi-card { border: 1px solid #e9ecef; border-radius: 12px; background: #fff; }
.kpi-value { font-size: 1.25rem; font-weight: 700; }
.search-wrap { gap: .75rem; }
.card-exp { border: 1px solid #eef1f5; border-radius: 14px; transition: box-shadow .2s ease, transform .2s ease; }
.card-exp:hover { box-shadow: 0 .75rem 1.5rem rgba(0,0,0,.08); transform: translateY(-2px); }
.chip { display: inline-flex; align-items: center; gap: .35rem; padding: .15rem .5rem; border-radius: 999px; font-size: .78rem; border: 1px solid #e5e7eb; background: #f8fafc; color: #334155; }
.chip i { font-size: .85rem; }
.muted { color: #6b7280; }
.ped-code { font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, 'Liberation Mono', 'Courier New', monospace; letter-spacing: .5px; }
.actions .btn { border-radius: 10px; }
.empty-state { border: 2px dashed #cbd5e1; border-radius: 14px; padding: 32px; background: #fff; }
</style>
</head>
<body>
<div class="content">
<h4 class="mb-4 animate__animated animate__fadeInDown title_glow">📁 Expedientes Electrónicos</h4>
<div class="card p-3 shadow-sm card-hover position-relative h-auto fade-in-up">
<div class="table-responsive">
<table class="table table-hover table-bordered">
<thead class="table-dark">
<tr>
<th>Pedimento</th>
<th>Fecha Factura</th>
<th>Aduana</th>
<th>Proveedor</th>
<th>Archivos</th>
<th>Tamaño Total</th>
<th>Acciones</th>
</tr>
</thead>
<tbody>
<?php foreach ($expedientes as $exp): ?>
<tr>
<td><i class="fas fa-folder folder-icon me-2"></i><?= htmlspecialchars($exp['numero_pedimento']) ?></td>
<td><?= is_a($exp['fecha_factura'], 'DateTime') ? $exp['fecha_factura']->format('Y-m-d') : htmlspecialchars($exp['fecha_factura']) ?></td>
<?php
$totalPed = count($expedientes ?? []);
$totalArch = 0; $totalSize = 0;
foreach(($expedientes ?? []) as $e){ $totalArch += (int)($e['total_archivos'] ?? 0); $totalSize += (float)($e['total_tamano'] ?? 0); }
?>
<td><?= htmlspecialchars($exp['aduana']) ?></td>
<td><?= htmlspecialchars($exp['proveedor_clave']) ?></td>
<td><?= $exp['total_archivos'] ?> archivos</td>
<td><?= number_format($exp['total_tamano'], 2) ?> KB</td>
<td>
<a href="/IMPORTADORES/expediente/ver/<?= $exp['id_solicitud'] ?>" class="btn btn-outline-primary btn-sm mt-auto w-auto btn-animated">📂 Ver</a>
<a href="/IMPORTADORES/expediente/subir/<?= $exp['id_solicitud'] ?>" class="btn btn-outline-success btn-sm mt-auto w-auto btn-animated">⬆ Subir</a>
</td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
<div class="content">
<div class="d-flex flex-wrap align-items-end justify-content-between mb-3">
<div>
<h3 class="title mb-1">📁 Expediente electrónico</h3>
<div class="text-muted">Consulta y organiza los documentos por pedimento</div>
</div>
<div class="search-wrap d-flex align-items-center">
<input id="searchExp" type="search" class="form-control" placeholder="Buscar pedimento, proveedor o aduana">
<select id="filterArch" class="form-select">
<option value="all">Todos</option>
<option value="with">Con archivos</option>
<option value="without">Sin archivos</option>
</select>
</div>
</div>
<div class="row g-3 mb-4">
<div class="col-6 col-md-3">
<div class="kpi-card p-3">
<div class="muted">Pedimentos</div>
<div class="kpi-value"><?= (int)$totalPed ?></div>
</div>
</div>
<div class="col-6 col-md-3">
<div class="kpi-card p-3">
<div class="muted">Archivos</div>
<div class="kpi-value"><?= (int)$totalArch ?></div>
</div>
</div>
<div class="col-12 col-md-6">
<div class="kpi-card p-3 d-flex align-items-center justify-content-between">
<div>
<div class="muted">Tamaño total</div>
<div class="kpi-value"><?= number_format($totalSize, 2) ?> KB</div>
</div>
<div class="text-muted small">Incluye todos los archivos subidos a los pedimentos</div>
</div>
</div>
</div>
<?php if (empty($expedientes)): ?>
<div class="empty-state text-center">
<div class="mb-2">Aún no hay pedimentos listados.</div>
<div class="text-muted">Cuando importes pedimentos, podrás cargar y consultar documentos desde aquí.</div>
</div>
<?php else: ?>
<div class="row" id="gridExp">
<?php foreach ($expedientes as $exp):
$ff = $exp['fecha_factura'] ?? null;
$fecha = '-';
if ($ff instanceof DateTime) { $fecha = $ff->format('Y-m-d'); }
else if (is_array($ff) && isset($ff['date'])) { $fecha = substr($ff['date'],0,10); }
elseif ($ff) { $fecha = htmlspecialchars((string)$ff); }
$archCount = (int)($exp['total_archivos'] ?? 0);
$hasFiles = $archCount > 0;
?>
<div class="col-12 col-md-6 col-xl-4 exp-card"
data-key="<?= htmlspecialchars(($exp['pedimento_display'] ?? $exp['numero_pedimento'] ?? '').' '.($exp['proveedor'] ?? '').' '.($exp['aduana'] ?? '')) ?>"
data-files="<?= $hasFiles ? 'with' : 'without' ?>">
<div class="card-exp bg-white p-3 h-100">
<div class="d-flex align-items-center justify-content-between mb-2">
<div class="ped-code h5 mb-0"><?= htmlspecialchars($exp['pedimento_display'] ?? ($exp['numero_pedimento'] ?? '')) ?></div>
<span class="chip <?= $hasFiles ? 'border-success text-success' : ''?>">
<i class="fa-solid fa-paperclip"></i><?= $archCount ?> archivos
</span>
</div>
<div class="d-flex flex-wrap gap-2 mb-2">
<span class="chip"><i class="fa-solid fa-user-tie"></i><?= htmlspecialchars($exp['proveedor'] ?? '-') ?></span>
<span class="chip"><i class="fa-regular fa-calendar"></i><?= $fecha ?></span>
<span class="chip"><i class="fa-solid fa-warehouse"></i><?= htmlspecialchars($exp['aduana'] ?? '-') ?></span>
<span class="chip"><i class="fa-id-badge"></i><?= htmlspecialchars($exp['patente'] ?? '-') ?></span>
</div>
<div class="muted small mb-3">Tamaño: <?= number_format((float)($exp['total_tamano'] ?? 0), 2) ?> KB</div>
<div class="actions d-flex gap-2">
<a href="/IMPORTADORES/expediente/ver_pedimento/<?= (int)$exp['pedimento_id'] ?>" class="btn btn-outline-primary btn-sm flex-fill">
<i class="fa-regular fa-folder-open me-1"></i> Ver expediente
</a>
<a href="/IMPORTADORES/expediente/subir_pedimento/<?= (int)$exp['pedimento_id'] ?>" class="btn btn-outline-success btn-sm">
<i class="fa-solid fa-upload"></i>
</a>
</div>
</div>
</div>
<?php endforeach; ?>
</div>
<?php endif; ?>
</div>
<script>
(function(){
const q = document.getElementById('searchExp');
const f = document.getElementById('filterArch');
const cards = Array.from(document.querySelectorAll('#gridExp .exp-card'));
function apply(){
const term = (q?.value || '').toLowerCase();
const filt = f?.value || 'all';
cards.forEach(c => {
const key = (c.getAttribute('data-key')||'').toLowerCase();
const has = c.getAttribute('data-files');
const match = !term || key.includes(term);
const byFiles = (filt==='all') || (filt==='with' && has==='with') || (filt==='without' && has==='without');
c.style.display = (match && byFiles) ? '' : 'none';
});
}
q?.addEventListener('input', apply);
f?.addEventListener('change', apply);
apply();
})();
</script>
</body>
</html>

View File

@@ -8,64 +8,67 @@
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css" rel="stylesheet">
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/js/bootstrap.bundle.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/sweetalert2@11"></script>
<!-- Font Awesome -->
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0/css/all.min.css">
<!-- Animate.css para animaciones adicionales -->
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/animate.css/4.1.1/animate.min.css">
<style>
body { font-family: 'Segoe UI', sans-serif; background-color: #f4f6f9; }
.content { margin-top: 56px; padding: 40px 20px; background-color: #f4f6f9; }
@media (min-width: 768px) { .content { margin-left: 250px; } }
body { font-family: 'Segoe UI', sans-serif; background-color: #f6f8fb; }
.content { margin-top: 56px; padding: 32px 20px; background-color: #f6f8fb; }
@media (min-width: 768px) { .content { margin-left: var(--sidebar-width, 280px); } }
.card { border-radius: 12px; }
/* Animaciones personalizadas */
.card-hover { transition: all 0.3s ease; cursor: pointer; }
.card-hover:hover { transform: translateY(-8px) scale(1.02); box-shadow: 0 1rem 2rem rgba(0, 0, 0, 0.15) !important; }
.btn-pulse { animation: pulse 2s infinite; }
@keyframes pulse {
0% { transform: scale(1); }
50% { transform: scale(1.05); }
100% { transform: scale(1); }
}
.fade-in-up { animation: fadeInUp 0.8s ease-out; }
@keyframes fadeInUp {
from { opacity: 0; transform: translateY(30px); }
to { opacity: 1; transform: translateY(0); }
}
/* Responsive animations */
@media (max-width: 768px) { .card-hover:hover { transform: translateY(-4px) scale(1.01); } }
/* Efecto de glow para elementos activos */
.btn.active { box-shadow: 0 0 20px rgba(var(--bs-primary-rgb), 0.5); }
/* Animación para el título */
.title-glow { text-shadow: 0 0 10px rgba(0, 123, 255, 0.3); }
/* Efecto para botones */
.btn-animated { transition: all 0.3s ease; position: relative; overflow: hidden; }
.btn-animated:hover { transform: translateY(-2px); box-shadow: 0 5px 15px rgba(0, 0, 0, 0.2); }
.btn-animated::before { content: ''; position: absolute; top: 0; left: -100%; width: 100%; height: 100%;
background: linear-gradient(90deg, transparent, rgba(255, 255, 255, 0.2), transparent); transition: left 0.5s; }
.btn-animated:hover::before { left: 100%; }
.table th, .table td { vertical-align: middle; }
.folder-icon { color: #f0ad4e; font-size: 1.2rem; }
.dropzone { border: 2px dashed #cbd5e1; border-radius: 12px; background: #fff; padding: 32px; text-align: center; cursor: pointer; transition: background .15s ease, border-color .15s ease; }
.dropzone.dragover { background: #f1f5f9; border-color: #94a3b8; }
</style>
</head>
<body>
<div class="content">
<h4 class="mb-4 animate__animated animate__fadeInDown title_glow">📤 Subir Archivos al Expediente</h4>
<div class="card p-4 bg-white shadow-sm card-hover position-relative h-auto fade-in-up">
<form action="/IMPORTADORES/expediente/subir_handler" method="POST" enctype="multipart/form-data">
<input type="hidden" name="id_solicitud" value="<?= $id_solicitud ?>">
<h4 class="mb-4">📤 Subir archivos al expediente</h4>
<div class="card p-4 bg-white shadow-sm">
<form action="/IMPORTADORES/expediente/subir_handler" method="POST" enctype="multipart/form-data" id="formUpload">
<?php if (isset($pedimento_id)): ?>
<input type="hidden" name="pedimento_id" value="<?= (int)$pedimento_id ?>">
<?php else: ?>
<input type="hidden" name="id_solicitud" value="<?= $id_solicitud ?>">
<?php endif; ?>
<div class="mb-3">
<label for="archivo" class="form-label">Selecciona archivo(s)</label>
<input class="form-control" type="file" name="archivos[]" id="archivo" multiple required>
<div id="dz" class="dropzone">
<i class="fa-solid fa-cloud-arrow-up fa-2xl mb-2"></i>
<div class="mb-1">Arrastra y suelta los archivos aquí</div>
<div class="text-muted small">o haz clic para seleccionar</div>
<input class="form-control d-none" type="file" name="archivos[]" id="archivo" multiple required>
</div>
<div id="fileList" class="mt-3 small text-muted"></div>
</div>
<button type="submit" class="btn btn-success mt-auto w-auto btn-animated">Subir</button>
<a href="/IMPORTADORES/expediente/index" class="btn btn-secondary ms-2 mt-auto w-auto btn-animated">Cancelar</a>
<div class="d-flex gap-2">
<button type="submit" class="btn btn-success">Subir</button>
<a href="/IMPORTADORES/expediente/index" class="btn btn-secondary">Cancelar</a>
</div>
</form>
</div>
</div>
<script>
(function(){
const dz = document.getElementById('dz');
const input = document.getElementById('archivo');
const list = document.getElementById('fileList');
function openPicker(){ input.click(); }
function updateList(){
const files = Array.from(input.files || []);
if (!files.length) { list.textContent = ''; return; }
list.innerHTML = files.map(f => `• ${f.name} (${(f.size/1024).toFixed(1)} KB)`).join('<br>');
}
dz.addEventListener('click', openPicker);
dz.addEventListener('dragover', (e)=>{ e.preventDefault(); dz.classList.add('dragover'); });
dz.addEventListener('dragleave', ()=> dz.classList.remove('dragover'));
dz.addEventListener('drop', (e)=>{
e.preventDefault(); dz.classList.remove('dragover');
if (e.dataTransfer?.files?.length){ input.files = e.dataTransfer.files; updateList(); }
});
input.addEventListener('change', updateList);
})();
</script>
</body>
</html>

View File

@@ -7,104 +7,123 @@
<title>📂 Ver Expediente</title>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css" rel="stylesheet">
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/js/bootstrap.bundle.min.js"></script>
<!-- Font Awesome -->
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0/css/all.min.css">
<!-- Animate.css para animaciones adicionales -->
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/animate.css/4.1.1/animate.min.css">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.0/css/all.min.css">
<script src="https://cdn.jsdelivr.net/npm/sweetalert2@11"></script>
<style>
body { font-family: 'Segoe UI', sans-serif; background-color: #f4f6f9; }
.content { margin-top: 56px; padding: 40px 20px; background-color: #f4f6f9; }
@media (min-width: 768px) { .content { margin-left: 250px; } }
.file-icon { margin-right: 8px; }
.card { border-radius: 12px; }
/* Animaciones personalizadas */
.card-hover { transition: all 0.3s ease; cursor: pointer; }
.card-hover:hover { transform: translateY(-8px) scale(1.02); box-shadow: 0 1rem 2rem rgba(0, 0, 0, 0.15) !important; }
.btn-pulse { animation: pulse 2s infinite; }
@keyframes pulse {
0% { transform: scale(1); }
50% { transform: scale(1.05); }
100% { transform: scale(1); }
}
.fade-in-up { animation: fadeInUp 0.8s ease-out; }
@keyframes fadeInUp {
from { opacity: 0; transform: translateY(30px); }
to { opacity: 1; transform: translateY(0); }
}
/* Responsive animations */
@media (max-width: 768px) { .card-hover:hover { transform: translateY(-4px) scale(1.01); } }
/* Efecto de glow para elementos activos */
.btn.active { box-shadow: 0 0 20px rgba(var(--bs-primary-rgb), 0.5); }
/* Animación para el título */
.title-glow { text-shadow: 0 0 10px rgba(0, 123, 255, 0.3); }
/* Efecto para botones */
.btn-animated { transition: all 0.3s ease; position: relative; overflow: hidden; }
.btn-animated:hover { transform: translateY(-2px); box-shadow: 0 5px 15px rgba(0, 0, 0, 0.2); }
.btn-animated::before { content: ''; position: absolute; top: 0; left: -100%; width: 100%; height: 100%;
background: linear-gradient(90deg, transparent, rgba(255, 255, 255, 0.2), transparent); transition: left 0.5s; }
.btn-animated:hover::before { left: 100%; }
.table th, .table td { vertical-align: middle; }
.folder-icon { color: #f0ad4e; font-size: 1.2rem; }
body { font-family: 'Segoe UI', sans-serif; background-color: #f6f8fb; }
.content { margin-top: 56px; padding: 32px 20px; background-color: #f6f8fb; }
@media (min-width: 768px) { .content { margin-left: var(--sidebar-width, 280px); } }
.ped-code { font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, 'Liberation Mono', 'Courier New', monospace; letter-spacing: .5px; }
.chip { display: inline-flex; align-items: center; gap: .35rem; padding: .15rem .5rem; border-radius: 999px; font-size: .78rem; border: 1px solid #e5e7eb; background: #f8fafc; color: #334155; }
.kpi-card { border: 1px solid #e9ecef; border-radius: 12px; background: #fff; }
.file-card { border: 1px solid #eef1f5; border-radius: 12px; background:#fff; transition: box-shadow .2s ease, transform .2s ease; }
.file-card:hover { box-shadow: 0 .75rem 1.5rem rgba(0,0,0,.08); transform: translateY(-2px); }
.file-icon { width: 36px; height: 36px; display: inline-flex; align-items: center; justify-content: center; border-radius: 8px; background: #f1f5f9; }
.toolbar .btn { border-radius: 10px; }
</style>
</head>
<body>
<div class="content">
<h4 class="mb-4 animate__animated animate__fadeInDown title_glow">📂 Archivos del Expediente</h4>
<div class="card p-4 bg-white shadow-sm card-hover position-relative h-auto fade-in-up">
<?php if (empty($archivos)): ?>
<p>No hay archivos subidos para esta solicitud.</p>
<a href="/IMPORTADORES/expediente/subir/<?= $id_solicitud ?>" class="btn btn-success mt-auto w-auto btn-animated"> Subir archivos</a>
<?php
$archCount = count($archivos ?? []);
$totalSize = 0; foreach(($archivos ?? []) as $a){ $totalSize += (float)($a['tamano_archivo'] ?? 0); }
$displayCode = $pedimento['pedimento_display'] ?? ($pedimento_id ?? '');
?>
<?php else: ?>
<div class="mb-3">
<a href="/IMPORTADORES/expediente/descargar_zip/<?= $id_solicitud ?>" class="btn btn-outline-dark mt-auto w-auto btn-animated">📦 Descargar todo en ZIP</a>
<div class="content">
<div class="d-flex flex-wrap align-items-center justify-content-between mb-3">
<div>
<div class="text-muted small">Pedimento</div>
<h3 class="ped-code mb-1"><?= htmlspecialchars($displayCode) ?></h3>
<div class="d-flex flex-wrap gap-2">
<span class="chip"><i class="fa-solid fa-warehouse"></i><?= htmlspecialchars($pedimento['aduana'] ?? '-') ?></span>
<span class="chip"><i class="fa-id-badge"></i><?= htmlspecialchars($pedimento['patente'] ?? '-') ?></span>
<span class="chip"><i class="fa-regular fa-calendar"></i><?= htmlspecialchars($pedimento['anio'] ?? '-') ?></span>
<?php if (!empty($pedimento['rfc_importador'])): ?>
<span class="chip"><i class="fa-solid fa-building"></i><?= htmlspecialchars($pedimento['rfc_importador']) ?></span>
<?php endif; ?>
</div>
<table class="table table-hover table-bordered">
<thead class="table-light">
<tr>
<th>Archivo </th>
<th>Tipo</th>
<th>Tamaño (KB)</th>
<th>Subido por</th>
<th>Fecha</th>
<th>Acciones</th>
</tr>
</thead>
<tbody>
<?php foreach ($archivos as $file): ?>
<tr>
<td>
<?php
$icon = 'fa-file';
$ext = strtolower(pathinfo($file['nombre_archivo'], PATHINFO_EXTENSION));
if (in_array($ext, ['pdf'])) $icon = 'fa-file-pdf text-danger';
elseif (in_array($ext, ['jpg', 'jpeg', 'png'])) $icon = 'fa-file-image text-info';
elseif (in_array($ext, ['doc', 'docx'])) $icon = 'fa-file-word text-primary';
elseif (in_array($ext, ['xls', 'xlsx'])) $icon = 'fa-file-excel text-success';
elseif (in_array($ext, ['zip', 'rar'])) $icon = 'fa-file-archive text-warning';
?>
<i class="fas <?= $icon ?> file-icon"></i>
<?= htmlspecialchars($file['nombre_archivo']) ?>
</td>
<td><?= htmlspecialchars($file['tipo_archivo']) ?></td>
<td><?= number_format($file['tamano_archivo'], 2) ?></td>
<td><?= htmlspecialchars($file['creado_por']) ?></td>
<td><?= $file['creado_en']->format('Y-m-d H:i') ?></td>
<td class="d-flex gap-2">
<a href="/IMPORTADORES/expediente/ver_archivo/<?= $file['id'] ?>" class="btn btn-sm btn-outline-secondary" target="_blank">👁 Ver</a>
<a href="/IMPORTADORES/<?= $file['ruta_archivo'] ?>" download class="btn btn-sm btn-outline-primary">⬇ Descargar</a>
</td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
<?php endif; ?>
<a href="/IMPORTADORES/expediente/index" class="btn btn-secondary mt-3">← Volver</a>
</div>
<div class="toolbar d-flex flex-wrap gap-2 align-items-center">
<input id="searchFiles" type="search" class="form-control" placeholder="Buscar por nombre o tipo">
<?php if (isset($pedimento_id)): ?>
<a href="/IMPORTADORES/expediente/subir_pedimento/<?= (int)$pedimento_id ?>" class="btn btn-success"><i class="fa-solid fa-upload me-1"></i> Subir</a>
<a href="/IMPORTADORES/expediente/descargar_zip_pedimento/<?= (int)$pedimento_id ?>" class="btn btn-outline-dark"><i class="fa-solid fa-box-archive me-1"></i> ZIP</a>
<?php else: ?>
<a href="/IMPORTADORES/expediente/subir/<?= $id_solicitud ?>" class="btn btn-success"><i class="fa-solid fa-upload me-1"></i> Subir</a>
<a href="/IMPORTADORES/expediente/descargar_zip/<?= $id_solicitud ?>" class="btn btn-outline-dark"><i class="fa-solid fa-box-archive me-1"></i> ZIP</a>
<?php endif; ?>
<a href="/IMPORTADORES/expediente/index" class="btn btn-secondary">← Volver</a>
</div>
</div>
<div class="row g-3 mb-4">
<div class="col-6 col-md-3">
<div class="kpi-card p-3">
<div class="text-muted">Archivos</div>
<div class="h5 mb-0"><?= (int)$archCount ?></div>
</div>
</div>
<div class="col-6 col-md-3">
<div class="kpi-card p-3">
<div class="text-muted">Tamaño total</div>
<div class="h5 mb-0"><?= number_format($totalSize, 2) ?> KB</div>
</div>
</div>
</div>
<?php if (empty($archivos)): ?>
<div class="p-4 bg-white border rounded-3 text-center">
No hay archivos subidos para este expediente.
</div>
<?php else: ?>
<div class="row" id="gridFiles">
<?php foreach ($archivos as $file):
$icon = 'fa-file';
$ext = strtolower(pathinfo($file['nombre_archivo'], PATHINFO_EXTENSION));
if (in_array($ext, ['pdf'])) $icon = 'fa-file-pdf text-danger';
elseif (in_array($ext, ['jpg', 'jpeg', 'png'])) $icon = 'fa-file-image text-info';
elseif (in_array($ext, ['doc', 'docx'])) $icon = 'fa-file-word text-primary';
elseif (in_array($ext, ['xls', 'xlsx'])) $icon = 'fa-file-excel text-success';
elseif (in_array($ext, ['zip', 'rar'])) $icon = 'fa-file-archive text-warning';
$fecha = $file['creado_en'] instanceof DateTime ? $file['creado_en']->format('Y-m-d H:i') : '';
?>
<div class="col-12 col-md-6 col-xl-4 file-item" data-key="<?= htmlspecialchars(($file['nombre_archivo'] ?? '').' '.($file['tipo_archivo'] ?? '')) ?>">
<div class="file-card p-3 h-100">
<div class="d-flex align-items-start gap-3 mb-2">
<div class="file-icon"><i class="fas <?= $icon ?>"></i></div>
<div class="flex-grow-1">
<div class="fw-semibold"><?= htmlspecialchars($file['nombre_archivo']) ?></div>
<div class="text-muted small">Tipo: <?= htmlspecialchars($file['tipo_archivo']) ?> · <?= number_format($file['tamano_archivo'], 2) ?> KB</div>
<div class="text-muted small">Subido por <?= htmlspecialchars($file['creado_por']) ?> · <?= $fecha ?></div>
</div>
</div>
<div class="d-flex gap-2">
<a href="/IMPORTADORES/expediente/ver_archivo/<?= $file['id'] ?>" class="btn btn-sm btn-outline-secondary" target="_blank"><i class="fa-regular fa-eye"></i></a>
<a href="/IMPORTADORES/<?= $file['ruta_archivo'] ?>" download class="btn btn-sm btn-outline-primary"><i class="fa-solid fa-download"></i></a>
</div>
</div>
</div>
<?php endforeach; ?>
</div>
<?php endif; ?>
</div>
<script>
(function(){
const q = document.getElementById('searchFiles');
const items = Array.from(document.querySelectorAll('#gridFiles .file-item'));
function apply(){
const term = (q?.value || '').toLowerCase();
items.forEach(it => {
const key = (it.getAttribute('data-key')||'').toLowerCase();
it.style.display = !term || key.includes(term) ? '' : 'none';
});
}
q?.addEventListener('input', apply);
apply();
})();
</script>
</body>
</html>

View File

@@ -355,10 +355,10 @@ $mensajeMantenimiento = $config['mensaje_mantenimiento'] ?? 'El sistema se encue
<div class="row align-items-center">
<div class="col-lg-6">
<div class="hero-content">
<h1>Gestión Moderna de <span style="color: #10b981;">Importaciones</span></h1>
<h1>Manifestación de Valor <span style="color: #10b981;">Electrónica</span></h1>
<p class="lead">
Simplifica tus procesos aduaneros con nuestra plataforma integral.
Desde pedimentos hasta seguimiento, todo en un solo lugar.
Simplifica tus trámites de MVE con nuestra plataforma especializada.
Desde la solicitud hasta la respuesta del SAT, gestiona todo digitalmente.
</p>
<div class="d-flex flex-column flex-md-row gap-3">
<a href="/IMPORTADORES/registro" class="btn-modern btn-primary-modern">
@@ -392,7 +392,7 @@ $mensajeMantenimiento = $config['mensaje_mantenimiento'] ?? 'El sistema se encue
<div class="col-6 col-md-3">
<div class="stat-item">
<span class="stat-number">25K+</span>
<div class="stat-label">Trámites</div>
<div class="stat-label">MVE Procesadas</div>
</div>
</div>
<div class="col-6 col-md-3">
@@ -414,20 +414,20 @@ $mensajeMantenimiento = $config['mensaje_mantenimiento'] ?? 'El sistema se encue
<!-- FEATURES -->
<section class="section-modern" id="features">
<div class="container">
<h2 class="section-title">Todo lo que necesitas</h2>
<h2 class="section-title">MVE Digital Completa</h2>
<p class="section-subtitle">
Herramientas profesionales diseñadas para optimizar cada etapa del proceso de importación
Herramientas especializadas para gestionar eficientemente tu Manifestación de Valor Electrónica
</p>
<div class="row g-4">
<div class="col-lg-4 col-md-6">
<div class="feature-card">
<div class="feature-icon">
<i class="fas fa-file-invoice"></i>
<i class="fas fa-file-contract"></i>
</div>
<h5 class="feature-title">Gestión de Pedimentos</h5>
<h5 class="feature-title">Solicitud MVE Automática</h5>
<p class="feature-description">
Crea, gestiona y da seguimiento a tus pedimentos de importación con validación automática de datos.
Genera automáticamente tu Manifestación de Valor Electrónica con base en los datos del pedimento y facturas.
</p>
</div>
</div>
@@ -435,11 +435,11 @@ $mensajeMantenimiento = $config['mensaje_mantenimiento'] ?? 'El sistema se encue
<div class="col-lg-4 col-md-6">
<div class="feature-card">
<div class="feature-icon">
<i class="fas fa-shield-check"></i>
<i class="fas fa-clock"></i>
</div>
<h5 class="feature-title">Cumplimiento Automatizado</h5>
<h5 class="feature-title">Seguimiento en Tiempo Real</h5>
<p class="feature-description">
Mantente al día con regulaciones aduaneras y recibe alertas sobre cambios normativos relevantes.
Monitorea el estatus de tus MVE desde el envío hasta la respuesta del SAT con notificaciones automáticas.
</p>
</div>
</div>
@@ -447,11 +447,11 @@ $mensajeMantenimiento = $config['mensaje_mantenimiento'] ?? 'El sistema se encue
<div class="col-lg-4 col-md-6">
<div class="feature-card">
<div class="feature-icon">
<i class="fas fa-analytics"></i>
<i class="fas fa-file-signature"></i>
</div>
<h5 class="feature-title">Reportes Inteligentes</h5>
<h5 class="feature-title">Firma Digital Integrada</h5>
<p class="feature-description">
Analiza tus operaciones con dashboards interactivos y reportes personalizables en tiempo real.
Firma electrónicamente tus MVE con validación biométrica y cumplimiento normativo SAT.
</p>
</div>
</div>
@@ -459,11 +459,11 @@ $mensajeMantenimiento = $config['mensaje_mantenimiento'] ?? 'El sistema se encue
<div class="col-lg-4 col-md-6">
<div class="feature-card">
<div class="feature-icon">
<i class="fas fa-users"></i>
<i class="fas fa-database"></i>
</div>
<h5 class="feature-title">Colaboración</h5>
<h5 class="feature-title">Historial Completo</h5>
<p class="feature-description">
Conecta con agentes aduanales, transportistas y proveedores en un ecosistema colaborativo.
Mantén un registro detallado de todas tus MVE con expediente digital y documentación respaldatoria.
</p>
</div>
</div>
@@ -471,11 +471,11 @@ $mensajeMantenimiento = $config['mensaje_mantenimiento'] ?? 'El sistema se encue
<div class="col-lg-4 col-md-6">
<div class="feature-card">
<div class="feature-icon">
<i class="fas fa-mobile-alt"></i>
<i class="fas fa-calculator"></i>
</div>
<h5 class="feature-title">Acceso Universal</h5>
<h5 class="feature-title">Validación Automática</h5>
<p class="feature-description">
Accede desde cualquier dispositivo con nuestra aplicación web responsiva y segura.
Valida automáticamente precios, cantidades y valores con base de datos actualizada del SAT.
</p>
</div>
</div>
@@ -483,11 +483,11 @@ $mensajeMantenimiento = $config['mensaje_mantenimiento'] ?? 'El sistema se encue
<div class="col-lg-4 col-md-6">
<div class="feature-card">
<div class="feature-icon">
<i class="fas fa-lock"></i>
<i class="fas fa-shield-alt"></i>
</div>
<h5 class="feature-title">Seguridad Total</h5>
<h5 class="feature-title">Cumplimiento Normativo</h5>
<p class="feature-description">
Protección enterprise con encriptación avanzada, backups automáticos y certificaciones de seguridad.
Garantiza el cumplimiento total con las disposiciones del SAT para MVE según la normatividad vigente.
</p>
</div>
</div>
@@ -498,9 +498,9 @@ $mensajeMantenimiento = $config['mensaje_mantenimiento'] ?? 'El sistema se encue
<!-- CTA -->
<section class="cta-section">
<div class="container">
<h2 class="section-title" style="margin-bottom: 1rem;">¿Listo para optimizar tus importaciones?</h2>
<h2 class="section-title" style="margin-bottom: 1rem;">¿Listo para digitalizar tus MVE?</h2>
<p class="section-subtitle" style="margin-bottom: 3rem;">
Únete a más de 1,200 importadores que ya confían en nuestra plataforma
Únete a más de 1,200 importadores que ya gestionan sus Manifestaciones de Valor Electrónica digitalmente
</p>
<a href="/IMPORTADORES/registro" class="btn-modern btn-primary-modern">
<i class="fas fa-arrow-right"></i>

View File

@@ -6,209 +6,357 @@
<meta charset="UTF-8">
<title>Dashboard | Importador</title>
<meta name="viewport" content="width=device-width, initial-scale=1">
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css" rel="stylesheet">
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/js/bootstrap.bundle.min.js"></script>
<!-- Font Awesome -->
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet">
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&display=swap" rel="stylesheet">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0/css/all.min.css">
<!-- Animate.css para animaciones adicionales -->
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/animate.css/4.1.1/animate.min.css">
<style>
body { font-family: 'Segoe UI', sans-serif; background-color: #f4f6f9; }
.sidebar { width: 220px; height: 100vh; background-color: #343a40; position: fixed; top: 0; left: 0; padding-top: 56px; z-index: 1040; }
.sidebar .nav-link { font-weight: normal; color: white; transition: all 0.3s ease; }
.sidebar .nav-link:hover,
.sidebar .nav-link.active { background-color: #495057; color: #fff; }
.content { margin-top: 56px; padding: 40px 20px; position: relative; z-index: 1; background-color: #f4f6f9; transition: margin-left 0.3s ease; }
.navbar { position: fixed; top: 0; width: 100%; z-index: 1050; }
.btn-indigo { background-color: #6610f2; color: white; }
.btn-indigo:hover { background-color: #520dc2; color: white; }
.text-indigo { color: #520dc2; }
.btn-orange { background-color: orangeRed; color: white; }
.btn-orange:hover { background-color: #ff3600; color: white; }
.text-orange { color: orangeRed; }
.btn-teal { background-color: #4dd4ac ; color: white; }
.btn-teal:hover { background-color: #20c997; color: black; }
.text-teal { color: #4dd4ac; }
.btn-lime { background-color: #84cc16; color: white; }
.text-lime { color: #84cc16; }
.btn-lime:hover { background-color: #65a30d; color: black; }
.btn-cyan { background-color: #06b6d4; color: white; }
.text-cyan { color: #06b6d4; }
.btn-cyan:hover { background-color: #0891b2; color: white; }
.btn-violet { background-color: #8b5cf6; color: white; }
.text-violet { color: #8b5cf6; }
.btn-violet:hover { background-color: #7c3aed; color: white; }
.btn-lila { background-color: #c4b5fd; /* violeta claro */ color: white; border: none; }
.btn-lila:hover { background-color: #a78bfa; /* tono ligeramente más fuerte */ color: white; }
.text-lila { color: #c4b5fd; }
/* A partir de dispositivos medianos (>=768px), deja espacio lateral */
@media (min-width: 768px) { .content { margin-left: 250px; /* Ancho del sidebar */ } }
/* En móviles, sin margen lateral */
@media (max-width: 767.98px) {
.content { margin-left: 0; }
.sidebar .nav-link { font-weight: normal; color: #343a40; background-color: transparent; }
.sidebar .nav-link:hover,
.sidebar .nav-link.active { background-color: #e9ecef; color: #212529; }
:root {
--primary: #2563eb;
--primary-dark: #1d4ed8;
--secondary: #64748b;
--success: #059669;
--warning: #d97706;
--danger: #dc2626;
--info: #0891b2;
--light: #f8fafc;
--dark: #0f172a;
--border-color: #e2e8f0;
--text-muted: #64748b;
--bg-body: #f8fafc;
}
.card { border-radius: 12px; }
/* Animaciones personalizadas */
.card-hover { transition: all 0.3s ease; cursor: pointer; }
.card-hover:hover { transform: translateY(-8px) saqcle(1.02); box-shadow: 0 1rem 2rem rgba(0, 0, 0, 0.15) !important; }
.btn-pulse { animation: pulse 2s infinite; }
@keyframes pulse {
0% { transform: scale(1); }
50% { transform: scale(1.05); }
100% { transform: scale(1); }
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
.fade-in-up { animation: fadeInUp 0.8s ease-out; }
@keyframes fadeInUp {
from { opacity: 0; transform: translateY(30px); }
to { opacity: 1; transform: translateY(0); }
body {
font-family: 'Inter', -apple-system, BlinkMacSystemFont, sans-serif;
background-color: var(--bg-body);
color: var(--dark);
line-height: 1.6;
}
.loading-spinner { display: none; }
/* Botón con efecto de loading */
.btn-loading { position: relative; color: transparent !important; pointer-events: none; }
.btn-loading::after { content: ''; position: absolute; width: 20px; height: 20px; top: 50%; left: 50%; margin-left: -10px;
margin-top: -10px; border: 2px solid #fff; border-radius: 50%; border-top-color: transparent; animation: spin 1s linear infinite; }
.btn-loading .loading-spinner { display: inline-block; }
.btn-loading .btn-text { display: none; }
@keyframes spin { to { transform: rotate(360deg); } }
/* Responsive animations */
@media (max-width: 768px) { .card-hover:hover { transform: translateY(-4px) scale(1.01); } }
/* Efecto de glow para elementos activos */
.btn.active { box-shadow: 0 0 20px rgba(var(--bs-primary-rgb), 0.5); }
/* Animación para el título */
.title-glow { text-shadow: 0 0 10px rgba(0, 123, 255, 0.3); }
/* Efecto para botones */
.btn-animated { transition: all 0.3s ease; position: relative; overflow: hidden; }
.btn-animated:hover { transform: translateY(-2px); box-shadow: 0 5px 15px rgba(0, 0, 0, 0.2); }
.btn-animated::before { content: ''; position: absolute; top: 0; left: -100%; width: 100%; height: 100%;
background: linear-gradient(90deg, transparent, rgba(255, 255, 255, 0.2), transparent); transition: left 0.5s; }
.btn-animated:hover::before { left: 100%; }
/* Animación para alertas */
.alert-animated { animation: slideInDown 0.5s ease-out; }
@keyframes slideInDown {
from { transform: translateY(-100%); opacity: 0; }
to { transform: translateY(0); opacity: 1; }
.main-content {
margin-left: 280px;
padding: 2rem;
min-height: 100vh;
transition: margin-left 0.3s ease;
}
/* Efecto de parpadeo para elementos obligatorios */
.border-warning-animated { animation: borderGlow 2s ease-in-out infinite alternate; }
@keyframes borderGlow {
from { border-color: #ffc107; box-shadow: 0 0 5px rgba(255, 193, 7, 0.5); }
to { border-color: #ffcd39; box-shadow: 0 0 20px rgba(255, 193, 7, 0.8); }
@media (max-width: 768px) {
.main-content {
margin-left: 0;
padding: 1rem;
}
}
.page-header {
margin-bottom: 2rem;
}
.page-title {
color: var(--dark);
font-weight: 600;
font-size: 1.75rem;
margin-bottom: 0.5rem;
}
.page-subtitle {
color: var(--text-muted);
font-weight: 400;
font-size: 0.95rem;
}
.dashboard-card {
background: white;
border: 1px solid var(--border-color);
border-radius: 12px;
padding: 1.5rem;
height: 100%;
transition: all 0.2s ease;
cursor: pointer;
position: relative;
overflow: hidden;
}
.dashboard-card:hover {
transform: translateY(-2px);
box-shadow: 0 8px 25px rgba(0, 0, 0, 0.1);
border-color: var(--primary);
}
.card-icon {
width: 48px;
height: 48px;
border-radius: 8px;
display: flex;
align-items: center;
justify-content: center;
margin-bottom: 1rem;
font-size: 1.25rem;
color: white;
}
.card-icon.primary { background: var(--primary); }
.card-icon.success { background: var(--success); }
.card-icon.warning { background: var(--warning); }
.card-icon.info { background: var(--info); }
.card-icon.danger { background: var(--danger); }
.card-title {
font-weight: 600;
font-size: 1.1rem;
color: var(--dark);
margin-bottom: 0.5rem;
}
.card-description {
color: var(--text-muted);
font-size: 0.9rem;
margin-bottom: 1.5rem;
line-height: 1.5;
}
.card-action {
margin-top: auto;
}
.btn-card {
background: var(--primary);
color: white;
border: none;
padding: 0.625rem 1.25rem;
border-radius: 6px;
font-weight: 500;
font-size: 0.875rem;
transition: all 0.2s ease;
width: 100%;
position: relative;
overflow: hidden;
}
.btn-card:hover {
background: var(--primary-dark);
color: white;
transform: translateY(-1px);
}
.btn-card.success {
background: var(--success);
}
.btn-card.success:hover {
background: #047857;
}
.btn-card.warning {
background: var(--warning);
}
.btn-card.warning:hover {
background: #b45309;
}
.btn-card.info {
background: var(--info);
}
.btn-card.info:hover {
background: #0e7490;
}
.btn-card.danger {
background: var(--danger);
}
.btn-card.danger:hover {
background: #b91c1c;
}
.badge-required {
position: absolute;
top: 1rem;
right: 1rem;
background: var(--warning);
color: white;
font-size: 0.75rem;
padding: 0.25rem 0.5rem;
border-radius: 4px;
font-weight: 500;
}
.alert-professional {
background: white;
border: 1px solid var(--border-color);
border-left: 4px solid var(--info);
border-radius: 8px;
padding: 1.5rem;
margin-bottom: 2rem;
}
.alert-professional.warning {
border-left-color: var(--warning);
}
.alert-icon {
width: 40px;
height: 40px;
border-radius: 8px;
display: flex;
align-items: center;
justify-content: center;
margin-right: 1rem;
font-size: 1.1rem;
}
.alert-icon.info {
background: rgba(8, 145, 178, 0.1);
color: var(--info);
}
.alert-icon.warning {
background: rgba(217, 119, 6, 0.1);
color: var(--warning);
}
.alert-title {
font-weight: 600;
font-size: 1rem;
color: var(--dark);
margin-bottom: 0.5rem;
}
.alert-text {
color: var(--text-muted);
font-size: 0.9rem;
margin-bottom: 1rem;
}
.btn-loading {
opacity: 0.7;
pointer-events: none;
}
.btn-loading::after {
content: '';
position: absolute;
top: 50%;
left: 50%;
width: 16px;
height: 16px;
margin-left: -8px;
margin-top: -8px;
border: 2px solid transparent;
border-top: 2px solid currentColor;
border-radius: 50%;
animation: spin 1s linear infinite;
}
@keyframes spin {
to { transform: rotate(360deg); }
}
.fade-in {
animation: fadeIn 0.5s ease-out;
}
@keyframes fadeIn {
from { opacity: 0; transform: translateY(10px); }
to { opacity: 1; transform: translateY(0); }
}
</style>
</head>
<body>
<div class="content">
<h4 class="mb-4 animate__animated animate__fadeInDown title_glow">📦 Panel del Importador</h4>
<div class="main-content">
<!-- Page Header -->
<div class="page-header">
<h1 class="page-title">Dashboard Importador</h1>
<p class="page-subtitle">Gestiona tus catálogos y accede a las funcionalidades del sistema</p>
</div>
<?php if ($catalogosData['catalogos_activos']): ?>
<div class="row g-4">
<?php
$delay = 0;
foreach ($catalogosData['catalogos'] as $catalogo):
$delay += 0.1; // Incrementar el retraso para cada catálogo
?>
<div class="col-md-4 animate__animated animate__fadeInUp" style="animation-delay: <?= $delay ?>s;">
<div class="card shadow-sm p-3 card-hover <?= $catalogo['obligatorio'] ? 'border-warning border-warning-animated' : '' ?>">
<div class="card-body p-0 d-flex flex-column">
<h5 class="text-<?= $catalogo['color'] ?> mb-3">
<i class="<?= obtenerIconoCatalogo($catalogo['nombre']) ?> me-2"></i><?= $catalogo['nombre'] ?>
</h5>
<p class="text-muted small flex-grow-1 mb-4"><?= $catalogo['descripcion'] ?></p>
<div class="mt-auto">
<button
class="btn btn-<?= $catalogo['color'] ?> btn-sm mt-auto w-100 btn-animated <?= $_SERVER['REQUEST_URI'] === $catalogo['ruta'] ? 'active' : '' ?>"
onclick="navigateWithAnimation(this, '<?= $catalogo['ruta'] ?>')"
data-url="<?= $catalogo['ruta'] ?>">
<span class="loading-spinner spinner-border spinner-border-sm me-2" role="status" aria-hidden="true"></span>
<span class="btn-text"><?= $catalogo['accion'] ?></span>
</button>
</div>
<?php if ($catalogo['obligatorio']): ?>
<small class="text-warning mt-2 animate__animated animate__pulse animate__infinite">
<i class="fas fa-exclamation-triangle me-1"></i>
Obligatorio
</small>
<?php endif; ?>
<!-- Catálogos Grid -->
<div class="row g-4 mb-4">
<?php foreach ($catalogosData['catalogos'] as $catalogo): ?>
<div class="col-lg-4 col-md-6 fade-in">
<div class="dashboard-card" onclick="navigateWithAnimation(this, '<?= $catalogo['ruta'] ?>')">
<?php if ($catalogo['obligatorio']): ?>
<div class="badge-required">Obligatorio</div>
<?php endif; ?>
<div class="card-icon <?= getCardColorClass($catalogo['color']) ?>">
<i class="<?= obtenerIconoCatalogo($catalogo['nombre']) ?>"></i>
</div>
<h3 class="card-title"><?= $catalogo['nombre'] ?></h3>
<p class="card-description"><?= $catalogo['descripcion'] ?></p>
<div class="card-action">
<button class="btn-card <?= $catalogo['color'] ?>">
<?= $catalogo['accion'] ?>
</button>
</div>
</div>
</div>
<?php endforeach; ?>
</div>
<?php if (count($catalogosData['catalogos']) === 2): // Solo obligatorios ?>
<div class="row mt-4">
<div class="col-12">
<div class="alert alert-info alert-animated">
<div class="d-flex align-items-center">
<div class="me-3">
<i class="fas fa-lightbulb fa-2x text-info animate__animated animate__pulse animate__infinite"></i>
</div>
<div class="flex-grow-1">
<h6 class="alert-heading">💡 Consejo</h6>
<p class="mb-2">Parece que no tienes catálogos activados. Puedes activar los que necesites desde tus preferencias.</p>
<button class="btn btn-primary btn-sm btn-pulse" onclick="navigateWithAnimation(this, '/IMPORTADORES/preferencias/catalogos')">
<span class="loading-spinner spinner-border spinner-border-sm me-2" role="status" aria-hidden="true"></span>
<span class="btn-text">Configurar Catálogos</span>
</button>
</div>
</div>
<?php if (count($catalogosData['catalogos']) === 2): ?>
<!-- Info Alert -->
<div class="alert-professional">
<div class="d-flex align-items-start">
<div class="alert-icon info">
<i class="fas fa-lightbulb"></i>
</div>
<div>
<h4 class="alert-title">Consejo del Sistema</h4>
<p class="alert-text">Tienes disponibles solo los catálogos obligatorios. Puedes activar catálogos adicionales desde tu panel de preferencias para acceder a más funcionalidades.</p>
<button class="btn-card info" onclick="navigateWithAnimation(this, '/IMPORTADORES/preferencias/catalogos')">
Configurar Catálogos
</button>
</div>
</div>
</div>
<?php endif; ?>
<?php else: ?>
<!-- Si los catálogos están completamente desactivados -->
<div class="row g-4">
<div class="col-12">
<div class="alert alert-warning alert-animated">
<div class="d-flex align-items-center">
<div class="me-3">
<i class="fas fa-lock fa-2x text-warning animate__animated animate__swing animate__infinite"></i>
</div>
<div class="flex-grow-1">
<h5 class="alert-heading">🔒 Catálogos Desactivados</h5>
<p class="mb-3">Los catálogos están desactivados en tus preferencias. Solo tienes acceso a las funciones básicas del sistema.</p>
<button class="btn btn-primary btn-sm btn-pulse" onclick="navigateWithAnimation(this, '/IMPORTADORES/preferencias/catalogos')">
<span class="loading-spinner spinner-border spinner-border-sm me-2" role="status" aria-hidden="true"></span>
<span class="btn-text">Activar Catálogos</span>
</button>
</div>
</div>
<!-- Warning Alert -->
<div class="alert-professional warning mb-4">
<div class="d-flex align-items-start">
<div class="alert-icon warning">
<i class="fas fa-exclamation-triangle"></i>
</div>
<div>
<h4 class="alert-title">Catálogos Desactivados</h4>
<p class="alert-text">Los catálogos están desactivados en tu configuración. Solo tienes acceso a las funciones básicas del sistema.</p>
<button class="btn-card warning" onclick="navigateWithAnimation(this, '/IMPORTADORES/preferencias/catalogos')">
Activar Catálogos
</button>
</div>
</div>
<!-- Mostrar solo catálogos obligatorios -->
<?php
$delay = 0;
foreach ($catalogosData['catalogos'] as $catalogo):
$delay += 0.1;
?>
<div class="col-md-4 animate__animated animate__fadeInUp" style="animation-delay: <?= $delay ?>s;">
<div class="card shadow-sm p-3 card-hover border-warning border-warning-animated h-100">
<div class="card-body p-0 d-flex flex-column">
<h5 class="text-<?= $catalogo['color'] ?> mb-3">
<i class="<?= obtenerIconoCatalogo($catalogo['nombre']) ?> me-2"></i>
<?= $catalogo['nombre'] ?>
</h5>
<p class="text-muted small flex-grow-1 mb-4"><?= $catalogo['descripcion'] ?></p>
<div class="mt-auto">
<button
class="btn btn-<?= $catalogo['color'] ?> btn-sm mt-auto w-100 btn-animated <?= $_SERVER['REQUEST_URI'] === $catalogo['ruta'] ? 'active' : '' ?>"
onclick="navigateWithAnimation(this, '<?= $catalogo['ruta'] ?>')"
data-url="<?= $catalogo['ruta'] ?>">
<span class="loading-spinner spinner-border spinner-border-sm me-2" role="status" aria-hidden="true"></span>
<span class="btn-text"><?= $catalogo['accion'] ?></span>
</button>
</div>
<small class="text-warning mt-2 d-flex align-items-center">
<i class="fas fa-lock me-1 animate__animated animate__pulse animate__infinite"></i>
Función básica
</small>
</div>
<!-- Basic Functions Grid -->
<div class="row g-4">
<?php foreach ($catalogosData['catalogos'] as $catalogo): ?>
<div class="col-lg-4 col-md-6 fade-in">
<div class="dashboard-card" onclick="navigateWithAnimation(this, '<?= $catalogo['ruta'] ?>')">
<div class="badge-required">Básico</div>
<div class="card-icon <?= getCardColorClass($catalogo['color']) ?>">
<i class="<?= obtenerIconoCatalogo($catalogo['nombre']) ?>"></i>
</div>
<h3 class="card-title"><?= $catalogo['nombre'] ?></h3>
<p class="card-description"><?= $catalogo['descripcion'] ?></p>
<div class="card-action">
<button class="btn-card <?= $catalogo['color'] ?>">
<?= $catalogo['accion'] ?>
</button>
</div>
</div>
</div>
@@ -217,79 +365,74 @@
<?php endif; ?>
</div>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js"></script>
<script>
// Función para navegación con animación
function navigateWithAnimation(button, url) {
// Prevenir múltiples clics
// Navigation function with professional loading animation
function navigateWithAnimation(element, url) {
// Find the button within the card
const button = element.querySelector('.btn-card') || element;
// Prevent double clicks
if (button.classList.contains('btn-loading')) {
return;
}
// Añadir estado de carga
// Add loading state
button.classList.add('btn-loading');
button.disabled = true;
// Añadir efecto de salida a la tarjeta padre
const card = button.closest('.card');
if (card) {
card.style.transform = 'scale(0.95)';
card.style.opacity = '0.7';
card.style.transition = 'all 0.3s ease';
}
// Simular carga y navegar
setTimeout(function() {
// Add subtle card animation
const card = element.closest('.dashboard-card') || element;
card.style.transform = 'scale(0.98)';
card.style.opacity = '0.8';
// Navigate after short delay
setTimeout(() => {
window.location.href = url;
}, 1000);
}, 600);
}
// Animación de entrada escalonada para las tarjetas
// Initialize page animations
document.addEventListener('DOMContentLoaded', function() {
const cards = document.querySelectorAll('.card-hover');
// Efecto de revelado progresivo
cards.forEach((card, index) => {
card.style.animationDelay = (index * 0.1) + 's';
// Add staggered fade-in animation to cards
const fadeElements = document.querySelectorAll('.fade-in');
fadeElements.forEach((element, index) => {
element.style.animationDelay = (index * 0.1) + 's';
});
// Añadir efecto de hover mejorado
// Add smooth hover effects to cards
const cards = document.querySelectorAll('.dashboard-card');
cards.forEach(card => {
card.addEventListener('mouseenter', function() {
this.style.transform = 'translateY(-5px) scale(1.02)';
this.style.transform = 'translateY(-4px)';
});
card.addEventListener('mouseleave', function() {
this.style.transform = 'translateY(0) scale(1)';
this.style.transform = 'translateY(0)';
});
});
});
// Efecto de ondas en los botones
document.querySelectorAll('.btn-animated').forEach(button => {
button.addEventListener('click', function(e) {
let ripple = document.createElement('span');
ripple.classList.add('ripple');
this.appendChild(ripple);
let x = e.clientX - e.target.offsetLeft;
let y = e.clientY - e.target.offsetTop;
ripple.style.left = x + 'px';
ripple.style.top = y + 'px';
setTimeout(() => {
ripple.remove();
}, 1000);
});
});
// Animación para alertas que se pueden cerrar
document.querySelectorAll('.alert').forEach(alert => {
alert.addEventListener('closed.bs.alert', function() {
this.style.animation = 'slideOutUp 0.5s ease-in';
});
});
</script>
<?php
// Helper functions for the dashboard
function getCardColorClass($color) {
$colorMap = [
'indigo' => 'primary',
'teal' => 'success',
'orange' => 'warning',
'cyan' => 'info',
'lime' => 'success',
'violet' => 'primary',
'lila' => 'primary'
];
return $colorMap[$color] ?? 'primary';
}
?>
</body>
</html>

File diff suppressed because it is too large Load Diff

View File

@@ -1,91 +1,618 @@
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet">
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&display=swap" rel="stylesheet">
<style>
/* Estilos inspirados en Tailwind */
.sidebar-container { background-color: #f9fafb; border-right: 1px solid #e5e7eb; }
.sidebar-dark { background-color: #1f2937; border-right: 1px solid #374151; }
.sidebar-nav .nav-link { color: #374151; font-weight: 500; padding: 0.5rem 0.75rem; margin-bottom: 0.125rem;
border-radius: 0.5rem; transition: all 0.15s ease-in-out; display: flex; align-items: center; }
.sidebar-nav .nav-link:hover { background-color: #f3f4f6; color: #111827; }
.sidebar-nav .nav-link.active { background-color: #f3f4f6; color: #111827; }
.sidebar-nav .nav-link .icon { width: 1.25rem; height: 1.25rem; margin-right: 0.75rem; color: #6b7280; transition: color 0.15s ease-in-out; flex-shrink: 0; }
.sidebar-nav .nav-link:hover .icon,
.sidebar-nav .nav-link.active .icon { color: #374151; }
.sidebar-nav .badge { background-color: #f3f4f6; color: #374151; font-size: 0.75rem; font-weight: 500; padding: 0.25rem 0.5rem; border-radius: 9999px; margin-left: auto; }
.sidebar-nav .badge.badge-primary { background-color: #dbeafe; color: #1e40af; }
.submenu .nav-link { padding-left: 2.75rem; color: #6b7280; font-weight: 400; }
.submenu .nav-link:hover { background-color: #f3f4f6; color: #374151; }
.submenu .nav-link.active { background-color: #f3f4f6; color: #374151; }
.collapse-icon { width: 0.75rem; height: 0.75rem; margin-left: auto; transition: transform 0.15s ease-in-out; }
.nav-link[aria-expanded="true"] .collapse-icon { transform: rotate(180deg); }
/* Estilos para modo oscuro */
.sidebar-dark .sidebar-nav .nav-link { color: #d1d5db; }
.sidebar-dark .sidebar-nav .nav-link:hover { background-color: #374151; color: #ffffff; }
.sidebar-dark .sidebar-nav .nav-link.active { background-color: #374151; color: #ffffff; }
.sidebar-dark .sidebar-nav .nav-link .icon { color: #9ca3af; }
.sidebar-dark .sidebar-nav .nav-link:hover .icon,
.sidebar-dark .sidebar-nav .nav-link.active .icon { color: #ffffff; }
.sidebar-dark .sidebar-nav .badge { background-color: #374151; color: #d1d5db; }
.sidebar-dark .sidebar-nav .badge.badge-primary { background-color: #1e3a8a; color: #93c5fd; }
.sidebar-dark .submenu .nav-link { color: #9ca3af; }
.sidebar-dark .submenu .nav-link:hover { background-color: #374151; color: #d1d5db; }
.sidebar-dark .submenu .nav-link.active { background-color: #374151; color: #d1d5db; }
/* Navbar */
.navbar-brand { font-weight: 600; font-size: 1.125rem; }
.menu-toggle { border: 1px solid #6b7280; border-radius: 0.5rem; padding: 0.5rem; transition: all 0.15s ease-in-out; }
.menu-toggle:hover { background-color: #f3f4f6; }
/* Offcanvas para móviles */
.offcanvas { background-color: #ffffff; }
.offcanvas-header { border-bottom: 1px solid #e5e7eb; padding: 1rem 1.5rem; }
.offcanvas-title { font-weight: 600; font-size: 1.125rem; color: #111827; }
.offcanvas-body { padding: 1rem 1.5rem; }
/* Responsive adjustments */
/* Variables CSS para un diseño profesional limpio */
:root {
--sidebar-bg: #ffffff;
--sidebar-width: 280px;
--navbar-height: 70px;
--primary-color: #3b82f6;
--primary-dark: #2563eb;
--text-primary: #1f2937;
--text-secondary: #6b7280;
--text-muted: #9ca3af;
--text-light: #ffffff;
--bg-hover: #f8fafc;
--bg-active: #f1f5f9;
--bg-selected: #eff6ff;
--border-color: #e5e7eb;
--border-light: #f3f4f6;
--shadow-sm: 0 1px 2px rgba(0, 0, 0, 0.05);
--shadow-md: 0 4px 6px rgba(0, 0, 0, 0.07);
--shadow-lg: 0 10px 25px rgba(0, 0, 0, 0.08);
--transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
--navbar-bg: linear-gradient(135deg, var(--primary, #0f172a) 0%, var(--secondary, #334155) 100%);
}
/* Reset y fuentes base */
* {
font-family: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
}
/* Asegurar espacio para navbar fijo */
body {
padding-top: var(--navbar-height) !important;
margin-top: 0 !important;
}
/* Prevenir que otros estilos interfieran con el navbar */
html, body {
position: relative;
}
/* Reset específico para el navbar */
.navbar-professional * {
box-sizing: border-box;
}
/* Asegurar que ningún elemento padre afecte el posicionamiento */
.navbar-professional {
transform: none !important;
will-change: auto !important;
}
/* Estilos del contenedor principal del sidebar */
.sidebar-container {
background: var(--sidebar-bg);
border-right: 1px solid var(--border-color);
box-shadow: var(--shadow-lg);
position: relative;
overflow: hidden;
}
.sidebar-container::before {
content: '';
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: linear-gradient(180deg, transparent 0%, rgba(0, 0, 0, 0.01) 100%);
pointer-events: none;
}
/* Estilos de navegación mejorados */
.sidebar-nav {
position: relative;
z-index: 1;
}
.sidebar-nav .nav-item {
margin-bottom: 4px;
}
.sidebar-nav .nav-link {
color: var(--text-primary);
font-weight: 500;
font-size: 0.95rem;
padding: 14px 20px;
border-radius: 12px;
transition: var(--transition);
display: flex;
align-items: center;
text-decoration: none;
position: relative;
margin: 0 12px;
border: 1px solid transparent;
}
.sidebar-nav .nav-link::before {
content: '';
position: absolute;
left: 0;
top: 50%;
transform: translateY(-50%);
width: 4px;
height: 0;
background: var(--primary-color);
border-radius: 2px;
transition: height 0.3s ease;
}
.sidebar-nav .nav-link:hover {
background: var(--bg-hover);
border-color: var(--border-light);
transform: translateY(-1px);
box-shadow: var(--shadow-sm);
color: var(--text-primary);
}
.sidebar-nav .nav-link:hover::before {
height: 24px;
}
.sidebar-nav .nav-link.active {
background: var(--bg-selected);
border-color: var(--primary-color);
border-color: rgba(59, 130, 246, 0.2);
box-shadow: var(--shadow-sm);
font-weight: 600;
color: var(--primary-color);
}
.sidebar-nav .nav-link.active::before {
height: 28px;
}
/* Iconos mejorados */
.sidebar-nav .nav-link .icon {
width: 22px;
height: 22px;
margin-right: 16px;
color: var(--text-secondary);
transition: var(--transition);
flex-shrink: 0;
}
.sidebar-nav .nav-link:hover .icon {
color: var(--text-primary);
transform: scale(1.05);
}
.sidebar-nav .nav-link.active .icon {
color: var(--primary-color);
transform: scale(1.05);
}
/* Badges modernos */
.sidebar-nav .badge {
background: var(--border-light);
color: var(--text-secondary);
font-size: 0.75rem;
font-weight: 600;
padding: 6px 10px;
border-radius: 20px;
margin-left: auto;
border: 1px solid var(--border-color);
}
.sidebar-nav .badge.badge-primary {
background: rgba(59, 130, 246, 0.1);
color: var(--primary-color);
border-color: rgba(59, 130, 246, 0.2);
}
/* Submenús mejorados */
.submenu {
padding-left: 0;
margin-top: 8px;
border-left: 2px solid var(--border-color);
margin-left: 32px;
}
.submenu .nav-link {
padding: 10px 20px 10px 24px;
font-size: 0.875rem;
font-weight: 400;
color: var(--text-secondary);
margin: 0 12px 0 0;
border-radius: 0 10px 10px 0;
}
.submenu .nav-link::before {
display: none;
}
.submenu .nav-link:hover {
color: var(--text-primary);
background: var(--bg-hover);
}
.submenu .nav-link.active {
color: var(--primary-color);
background: var(--bg-selected);
}
/* Iconos de colapso mejorados */
.collapse-icon {
width: 16px;
height: 16px;
margin-left: auto;
transition: transform 0.4s cubic-bezier(0.4, 0, 0.2, 1);
color: var(--text-muted);
}
.nav-link:hover .collapse-icon {
color: var(--text-secondary);
}
.nav-link[aria-expanded="true"] .collapse-icon {
transform: rotate(180deg);
color: var(--primary-color);
}
/* Navbar profesional con posicionamiento reforzado */
.navbar-professional {
background: var(--navbar-bg) !important;
height: var(--navbar-height) !important;
box-shadow: var(--shadow-md) !important;
border: none !important;
backdrop-filter: blur(10px);
position: fixed !important;
top: 0 !important;
left: 0 !important;
right: 0 !important;
width: 100% !important;
z-index: 9999 !important;
overflow: hidden;
margin: 0 !important;
padding: 0 !important;
}
/* Forzar que el navbar siempre esté en la parte superior */
.navbar-professional.fixed-top {
position: fixed !important;
top: 0 !important;
left: 0 !important;
right: 0 !important;
z-index: 9999 !important;
}
.navbar-brand {
font-weight: 700;
font-size: 1.4rem;
color: var(--text-light) !important;
letter-spacing: -0.5px;
margin-bottom: 0 !important;
padding: 0 !important;
}
.navbar-brand .highlight {
color: #ffd700;
font-weight: 300;
}
/* Contenedor del navbar reforzado */
.navbar-professional .container-fluid {
padding-left: 1rem !important;
padding-right: 1rem !important;
margin: 0 !important;
width: 100% !important;
max-width: none !important;
}
/* Botón de menú mejorado */
.menu-toggle {
border: 2px solid rgba(255, 255, 255, 0.3);
border-radius: 10px;
padding: 10px 12px;
background: rgba(255, 255, 255, 0.1);
color: var(--text-light);
font-size: 1.2rem;
transition: var(--transition);
backdrop-filter: blur(5px);
}
.menu-toggle:hover {
background: rgba(255, 255, 255, 0.2);
border-color: rgba(255, 255, 255, 0.5);
transform: scale(1.05);
}
/* Botones de acción mejorados */
.btn-professional {
padding: 10px 20px;
border-radius: 10px;
font-weight: 500;
font-size: 0.9rem;
transition: var(--transition);
border: 2px solid transparent;
}
.btn-outline-light-professional {
background: rgba(255, 255, 255, 0.1);
border-color: rgba(255, 255, 255, 0.3);
color: var(--text-light);
backdrop-filter: blur(5px);
}
.btn-outline-light-professional:hover {
background: rgba(255, 255, 255, 0.2);
border-color: rgba(255, 255, 255, 0.5);
color: var(--text-light);
transform: translateY(-2px);
box-shadow: var(--shadow-md);
}
/* Estilos para el contenido de bienvenida */
.welcome-container {
flex-shrink: 0;
display: flex !important;
align-items: center;
height: 100%;
position: relative;
z-index: 1;
}
.welcome-text {
font-size: 0.95rem;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
max-width: 200px;
color: white !important;
margin: 0;
line-height: 1;
}
#navbar-content {
display: flex !important;
align-items: center;
height: 100%;
position: relative;
z-index: 1;
}
/* Offcanvas mejorado */
.offcanvas-professional {
background: var(--sidebar-bg);
border: none;
border-left: 1px solid var(--border-color);
}
.offcanvas-header {
border-bottom: 1px solid var(--border-color);
padding: 1.5rem;
background: var(--bg-hover);
}
.offcanvas-title {
font-weight: 700;
font-size: 1.3rem;
color: var(--text-primary);
letter-spacing: -0.3px;
}
.btn-close-white-professional {
background: var(--border-light);
border-radius: 8px;
padding: 8px;
transition: var(--transition);
opacity: 0.8;
}
.btn-close-white-professional:hover {
background: var(--border-color);
transform: scale(1.1);
opacity: 1;
}
.offcanvas-body {
padding: 1.5rem;
}
/* Efectos de scroll personalizado */
.sidebar-scroll::-webkit-scrollbar {
width: 6px;
}
.sidebar-scroll::-webkit-scrollbar-track {
background: var(--border-light);
border-radius: 3px;
}
.sidebar-scroll::-webkit-scrollbar-thumb {
background: var(--border-color);
border-radius: 3px;
}
.sidebar-scroll::-webkit-scrollbar-thumb:hover {
background: var(--text-muted);
}
/* Separadores visuales */
.nav-separator {
height: 1px;
background: var(--border-color);
margin: 16px 20px;
position: relative;
}
.nav-separator::after {
content: '';
position: absolute;
left: 50%;
top: 50%;
transform: translate(-50%, -50%);
width: 40px;
height: 1px;
background: var(--text-muted);
opacity: 0.3;
}
/* Animaciones mejoradas */
@keyframes slideIn {
from {
opacity: 0;
transform: translateX(-20px);
}
to {
opacity: 1;
transform: translateX(0);
}
}
.nav-item {
animation: slideIn 0.3s ease-out;
}
.nav-item:nth-child(1) { animation-delay: 0.05s; }
.nav-item:nth-child(2) { animation-delay: 0.1s; }
.nav-item:nth-child(3) { animation-delay: 0.15s; }
.nav-item:nth-child(4) { animation-delay: 0.2s; }
.nav-item:nth-child(5) { animation-delay: 0.25s; }
.nav-item:nth-child(6) { animation-delay: 0.3s; }
.nav-item:nth-child(7) { animation-delay: 0.35s; }
.nav-item:nth-child(8) { animation-delay: 0.4s; }
.nav-item:nth-child(9) { animation-delay: 0.45s; }
.nav-item:nth-child(10) { animation-delay: 0.5s; }
/* Responsive mejorado */
@media (max-width: 767.98px) {
.sidebar-nav .nav-link { color: #374151; }
.sidebar-nav .nav-link:hover { background-color: #f3f4f6; color: #111827; }
.sidebar-nav .nav-link.active { background-color: #f3f4f6; color: #111827; }
:root {
--navbar-height: 60px;
}
.navbar-professional {
height: 60px;
padding-left: 1rem;
padding-right: 1rem;
}
.navbar-professional .container-fluid {
padding-left: 0.5rem;
padding-right: 0.5rem;
}
.navbar-brand {
font-size: 1.0rem;
flex-shrink: 1;
min-width: 0;
}
.navbar-brand span:first-child {
display: none;
}
.menu-toggle {
padding: 6px 8px;
margin-right: 0.5rem;
flex-shrink: 0;
}
.welcome-text {
font-size: 0.85rem;
max-width: 150px;
}
.welcome-container {
flex-shrink: 1;
min-width: 0;
}
.sidebar-nav .nav-link {
margin: 0 8px;
padding: 12px 16px;
font-size: 0.9rem;
}
.offcanvas-professional {
width: 280px !important;
}
#navbar-content {
flex-shrink: 1;
min-width: 0;
overflow: hidden;
}
}
/* Para pantallas muy pequeñas */
@media (max-width: 575.98px) {
.navbar-brand {
font-size: 0.9rem;
}
.welcome-text {
font-size: 0.8rem;
max-width: 120px;
}
.navbar-professional .container-fluid {
padding-left: 0.25rem;
padding-right: 0.25rem;
}
}
/* Estados especiales */
.nav-link.loading {
opacity: 0.6;
pointer-events: none;
}
.nav-link.loading .icon {
animation: spin 1s linear infinite;
}
@keyframes spin {
from { transform: rotate(0deg); }
to { transform: rotate(360deg); }
}
</style>
<!-- NAVBAR -->
<nav class="navbar navbar-dark bg-dark fixed-top">
<div class="container-fluid">
<!-- Botón de menú para móviles -->
<button class="menu-toggle d-md-none me-3" type="button" data-bs-toggle="offcanvas" data-bs-target="#sidebarMenu">
</button>
<span class="navbar-brand">SIIH | Administrador</span>
<div class="d-flex ms-auto text-white">
<div id="navbar-content">
<!-- NAVBAR PROFESIONAL -->
<nav class="navbar navbar-professional fixed-top navbar-dark">
<div class="container-fluid px-4 h-100">
<div class="d-flex align-items-center justify-content-between w-100 h-100">
<!-- Lado izquierdo: Menú + Brand -->
<div class="d-flex align-items-center h-100">
<!-- Botón de menú para móviles -->
<button class="menu-toggle d-md-none me-3" type="button" data-bs-toggle="offcanvas" data-bs-target="#sidebarMenu">
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<line x1="3" y1="6" x2="21" y2="6"></line>
<line x1="3" y1="12" x2="21" y2="12"></line>
<line x1="3" y1="18" x2="21" y2="18"></line>
</svg>
</button>
<!-- Brand mejorado -->
<div class="navbar-brand d-flex align-items-center mb-0">
<svg class="me-2" width="32" height="32" viewBox="0 0 24 24" fill="currentColor">
<path d="M12 2L2 7l10 5 10-5-10-5zM2 17l10 5 10-5M2 12l10 5 10-5"/>
</svg>
<span>SIIH <span class="highlight">|</span> Administrador</span>
</div>
</div>
<!-- Lado derecho: Contenido del navbar -->
<div class="d-flex align-items-center h-100">
<?php
// Verifica si estás en el dashboard principal del administrador
$esDashboard = str_contains($_SERVER['REQUEST_URI'], '/administrador/dashboard');
if ($esDashboard): ?>
<span id="welcome-text">Bienvenido, <?= htmlspecialchars($_SESSION['usuario_nombre']) ?></span>
<div class="welcome-container">
<svg class="me-2 d-none d-sm-block" width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<path d="M20 21v-2a4 4 0 0 0-4-4H8a4 4 0 0 0-4 4v2"></path>
<circle cx="12" cy="7" r="4"></circle>
</svg>
<span id="welcome-text" class="welcome-text">Bienvenido, <?= htmlspecialchars($_SESSION['usuario_nombre']) ?></span>
</div>
<?php else: ?>
<a href="/IMPORTADORES/sistemas/logout" class="btn btn-outline-light btn-sm ms-2 W-auto btn-animated" id="logout-btn">Cerrar Sesión</a>
<a href="/IMPORTADORES/sistemas/logout"
class="btn btn-outline-light-professional btn-professional d-flex align-items-center"
id="logout-btn">
<svg class="me-2" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<path d="M9 21H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h4"></path>
<polyline points="16,17 21,12 16,7"></polyline>
<line x1="21" y1="12" x2="9" y2="12"></line>
</svg>
Cerrar Sesión
</a>
<?php endif; ?>
</div>
</div>
</div>
</nav>
<!-- SIDEBAR FIJA (escritorio) -->
<div class="d-none d-md-block sidebar-container sidebar-dark position-fixed h-100" style="width: 250px; top: 0; padding-top: 4rem; z-index: 1000;">
<!-- SIDEBAR PROFESIONAL (escritorio) -->
<div class="d-none d-md-block sidebar-container position-fixed h-100 sidebar-scroll" style="width: var(--sidebar-width); top: 0; padding-top: var(--navbar-height); z-index: 1000;">
<div class="px-3 py-4 h-100 overflow-y-auto">
<nav class="sidebar-nav">
<ul class="nav flex-column">
<!-- INICIO -->
<li class="nav-item">
<a href="/IMPORTADORES/administrador/dashboard" class="nav-link active">
<svg class="icon" fill="currentColor" viewBox="0 0 22 21">
<path d="M16.975 11H10V4.025a1 1 0 0 0-1.066-.998 8.5 8.5 0 1 0 9.039 9.039.999.999 0 0 0-1-1.066h.002Z"/>
<path d="M12.5 0c-.157 0-.311.01-.565.027A1 1 0 0 0 11 1.02V10h8.975a1 1 0 0 0 1-.935c.013-.188.028-.374.028-.565A8.51 8.51 0 0 0 12.5 0Z"/>
<svg class="icon" fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24">
<path d="M3 9l9-7 9 7v11a2 2 0 01-2 2H5a2 2 0 01-2-2z"/>
<polyline points="9,22 9,12 15,12 15,22"/>
</svg>
<span>Inicio</span>
</a>
</li>
<div class="nav-separator"></div>
<!-- AGENCIAS -->
<li class="nav-item">
<a class="nav-link" data-bs-toggle="collapse" href="#submenuAgencias" role="button" aria-expanded="false">

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,386 @@
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet">
<style>
/* Estilos inspirados en Tailwind */
.sidebar-container { background-color: #f9fafb; border-right: 1px solid #e5e7eb; }
.sidebar-dark { background-color: #1f2937; border-right: 1px solid #374151; }
.sidebar-nav .nav-link { color: #374151; font-weight: 500; padding: 0.5rem 0.75rem; margin-bottom: 0.125rem;
border-radius: 0.5rem; transition: all 0.15s ease-in-out; display: flex; align-items: center; }
.sidebar-nav .nav-link:hover { background-color: #f3f4f6; color: #111827; }
.sidebar-nav .nav-link.active { background-color: #f3f4f6; color: #111827; }
.sidebar-nav .nav-link .icon { width: 1.25rem; height: 1.25rem; margin-right: 0.75rem; color: #6b7280; transition: color 0.15s ease-in-out; flex-shrink: 0; }
.sidebar-nav .nav-link:hover .icon,
.sidebar-nav .nav-link.active .icon { color: #374151; }
.sidebar-nav .badge { background-color: #f3f4f6; color: #374151; font-size: 0.75rem; font-weight: 500; padding: 0.25rem 0.5rem; border-radius: 9999px; margin-left: auto; }
.sidebar-nav .badge.badge-primary { background-color: #dbeafe; color: #1e40af; }
.submenu .nav-link { padding-left: 2.75rem; color: #6b7280; font-weight: 400; }
.submenu .nav-link:hover { background-color: #f3f4f6; color: #374151; }
.submenu .nav-link.active { background-color: #f3f4f6; color: #374151; }
.collapse-icon { width: 0.75rem; height: 0.75rem; margin-left: auto; transition: transform 0.15s ease-in-out; }
.nav-link[aria-expanded="true"] .collapse-icon { transform: rotate(180deg); }
/* Estilos para modo oscuro */
.sidebar-dark .sidebar-nav .nav-link { color: #d1d5db; }
.sidebar-dark .sidebar-nav .nav-link:hover { background-color: #374151; color: #ffffff; }
.sidebar-dark .sidebar-nav .nav-link.active { background-color: #374151; color: #ffffff; }
.sidebar-dark .sidebar-nav .nav-link .icon { color: #9ca3af; }
.sidebar-dark .sidebar-nav .nav-link:hover .icon,
.sidebar-dark .sidebar-nav .nav-link.active .icon { color: #ffffff; }
.sidebar-dark .sidebar-nav .badge { background-color: #374151; color: #d1d5db; }
.sidebar-dark .sidebar-nav .badge.badge-primary { background-color: #1e3a8a; color: #93c5fd; }
.sidebar-dark .submenu .nav-link { color: #9ca3af; }
.sidebar-dark .submenu .nav-link:hover { background-color: #374151; color: #d1d5db; }
.sidebar-dark .submenu .nav-link.active { background-color: #374151; color: #d1d5db; }
/* Navbar */
.navbar-brand { font-weight: 600; font-size: 1.125rem; }
.menu-toggle { border: 1px solid #6b7280; border-radius: 0.5rem; padding: 0.5rem; transition: all 0.15s ease-in-out; }
.menu-toggle:hover { background-color: #f3f4f6; }
/* Offcanvas para móviles */
.offcanvas { background-color: #ffffff; }
.offcanvas-header { border-bottom: 1px solid #e5e7eb; padding: 1rem 1.5rem; }
.offcanvas-title { font-weight: 600; font-size: 1.125rem; color: #111827; }
.offcanvas-body { padding: 1rem 1.5rem; }
/* Responsive adjustments */
@media (max-width: 767.98px) {
.sidebar-nav .nav-link { color: #374151; }
.sidebar-nav .nav-link:hover { background-color: #f3f4f6; color: #111827; }
.sidebar-nav .nav-link.active { background-color: #f3f4f6; color: #111827; }
}
</style>
<!-- 🔷 NAVBAR -->
<nav class="navbar navbar-dark bg-dark fixed-top">
<div class="container-fluid">
<!-- Botón de menú para móviles -->
<button class="menu-toggle d-md-none me-3" type="button" data-bs-toggle="offcanvas" data-bs-target="#sidebarMenu">
</button>
<span class="navbar-brand">SIIH | Agencia Aduanal</span>
<div class="d-flex ms-auto text-white">
<div class="navbar-content">
<?php
// Verifica si estás en el dashboard principal del importador
$esDashboard = str_contains($_SERVER['REQUEST_URI'], '/IMPORTADORES/agencias/dashboard');
if ($esDashboard): ?>
<span id="welcome-text">Bienvenido, <?= htmlspecialchars($_SESSION['usuario_nombre']) ?></span>
<?php else: ?>
<a href="/IMPORTADORES/sistemas/logout" class="btn btn-outline-light btn-sm ms-2 W-auto btn-animated">Cerrar Sesión</a>
<?php endif; ?>
</div>
</div>
</div>
</nav>
<!-- SIDEBAR FIJA (escritorio) -->
<div class="d-none d-md-block sidebar-container sidebar-dark position-fixed h-100" style="width: 250px; top: 0; padding-top: 4rem; z-index: 1000;">
<div class="px-3 py-4 h-100 overflow-y-auto">
<nav class="sidebar-nav">
<ul class="nav flex-column">
<!-- INICIO -->
<li class="nav-item">
<a href="/IMPORTADORES/agencias/dashboard" class="nav-link active">
<svg class="icon" fill="currentColor" viewBox="0 0 22 21">
<path d="M16.975 11H10V4.025a1 1 0 0 0-1.066-.998 8.5 8.5 0 1 0 9.039 9.039.999.999 0 0 0-1-1.066h.002Z"/>
<path d="M12.5 0c-.157 0-.311.01-.565.027A1 1 0 0 0 11 1.02V10h8.975a1 1 0 0 0 1-.935c.013-.188.028-.374.028-.565A8.51 8.51 0 0 0 12.5 0Z"/>
</svg>
<span>Inicio</span>
</a>
</li>
<!-- SOLICITUDES VINCULACIÓN -->
<li class="nav-item">
<a href="/IMPORTADORES/vinculaciones/solicitudesVinculacion" class="nav-link">
<svg class="icon" fill="currentColor" viewBox="0 0 20 20">
<path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 4 4 0 005.656 0l3-3a4 4 0 00-5.656-5.656l-1.5 1.5a1 1 0 101.414 1.414l1.5-1.5z"/>
<path d="M7.414 15.414a2 2 0 01-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 4 4 0 00-5.656 0l-3 3a4 4 0 105.656 5.656l1.5-1.5a1 1 0 10-1.414-1.414l-1.5 1.5z"/>
</svg>
<span>Vinculación</span>
</a>
</li>
<!-- USUARIOS VINCULADOS -->
<li class="nav-item">
<a href="/IMPORTADORES/vinculaciones/vinculacionesAgencia" class="nav-link">
<svg class="icon" fill="currentColor" viewBox="0 0 20 20">
<path d="M14 2a3.963 3.963 0 0 0-1.4.267 6.439 6.439 0 0 1-1.331 6.638A4 4 0 1 0 14 2Zm1 9h-1.264A6.957 6.957 0 0 1 15 15v2a2.97 2.97 0 0 1-.184 1H19a1 1 0 0 0 1-1v-1a5.006 5.006 0 0 0-5-5ZM6.5 9a4.5 4.5 0 1 0 0-9 4.5 4.5 0 0 0 0 9ZM8 10H5a5.006 5.006 0 0 0-5 5v2a1 1 0 0 0 1 1h11a1 1 0 0 0 1-1v-2a5.006 5.006 0 0 0-5-5Z"/>
</svg>
<span>Usuarios Vinculados</span>
</a>
</li>
<!-- Patentes -->
<li class="nav-item">
<a class="nav-link" data-bs-toggle="collapse" href="#submenuPatentesDesktop" role="button" aria-expanded="false" aria-controls="submenuPatentesDesktop">
<svg class="icon" fill="currentColor" viewBox="0 0 20 20">
<path fill-rule="evenodd" d="M4 4a2 2 0 012-2h4.586A2 2 0 0112 2.586L15.414 6A2 2 0 0116 7.414V16a2 2 0 01-2 2H6a2 2 0 01-2-2V4zm2 6a1 1 0 011-1h6a1 1 0 110 2H7a1 1 0 01-1-1zm1 3a1 1 0 100 2h6a1 1 0 100-2H7z" clip-rule="evenodd"/>
</svg>
<span>Patentes</span>
<svg class="collapse-icon" fill="none" stroke="currentColor" viewBox="0 0 10 6">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="m1 1 4 4 4-4"/>
</svg>
</a>
<div class="collapse" id="submenuPatentesDesktop">
<ul class="nav flex-column submenu">
<li class="nav-item">
<a href="/IMPORTADORES/patente/lista" class="nav-link">
Ver Patentes
</a>
</li>
<li class="nav-item">
<a href="/IMPORTADORES/patente/alta" class="nav-link">
Nueva Patente
</a>
</li>
</ul>
</div>
</li>
<!-- LOCACIONES -->
<li class="nav-item">
<a class="nav-link" data-bs-toggle="collapse" href="#submenuLocacionesDesktop" role="button" aria-expanded="false" aria-controls="submenuLocacionesDesktop">
<svg class="icon" fill="currentColor" viewBox="0 0 20 20">
<path d="M10 2C6.686 2 4 4.686 4 8c0 5.25 6 12 6 12s6-6.75 6-12c0-3.314-2.686-6-6-6zm0 8c-1.105 0-2-.895-2-2s.895-2 2-2 2 .895 2 2-.895 2-2 2z"/>
</svg>
<span>Locaciones</span>
<svg class="collapse-icon" fill="none" stroke="currentColor" viewBox="0 0 10 6">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="m1 1 4 4 4-4"/>
</svg>
</a>
<div class="collapse" id="submenuLocacionesDesktop">
<ul class="nav flex-column submenu">
<li class="nav-item">
<a href="/IMPORTADORES/locaciones/lista" class="nav-link">
Ver Locaciones
</a>
</li>
<li class="nav-item">
<a href="/IMPORTADORES/locaciones/alta" class="nav-link">
Agregar Locaciones
</a>
</li>
</ul>
</div>
</li>
<!-- ALTA DE AGENTES -->
<li class="nav-item">
<a href="/IMPORTADORES/agencias/alta" class="nav-link">
<svg class="icon" fill="currentColor" viewBox="0 0 20 20">
<path fill-rule="evenodd" d="M10 9a3 3 0 100-6 3 3 0 000 6zm-7 9a7 7 0 1114 0H3z" clip-rule="evenodd"/>
</svg>
<span>Alta de agentes</span>
</a>
</li>
<!-- CONFIGURACIÓN -->
<li class="nav-item">
<a href="/IMPORTADORES/configuracion" class="nav-link">
<svg class="icon" fill="currentColor" viewBox="0 0 20 20">
<path fill-rule="evenodd" d="M11.983 1.034a1 1 0 00-1.966 0l-.26 1.857a6.996 6.996 0 00-1.223.51l-1.7-1.04a1 1 0 00-1.366.366l-1 1.732a1 1 0 00.366 1.366l1.7 1.04a6.984 6.984 0 000 1.02l-1.7 1.04a1 1 0 00-.366 1.366l1 1.732a1 1 0 001.366.366l1.7-1.04c.392.212.803.386 1.223.51l.26 1.857a1 1 0 001.966 0l.26-1.857c.42-.124.831-.298 1.223-.51l1.7 1.04a1 1 0 001.366-.366l1-1.732a1 1 0 00-.366-1.366l-1.7-1.04a6.984 6.984 0 000-1.02l1.7-1.04a1 1 0 00.366-1.366l-1-1.732a1 1 0 00-1.366-.366l-1.7 1.04a6.996 6.996 0 00-1.223-.51l-.26-1.857zM10 13a3 3 0 100-6 3 3 0 000 6z" clip-rule="evenodd"/>
</svg>
<span>Configuración</span>
</a>
</li>
</ul>
</nav>
</div>
</div>
<!-- OFFCANVAS PARA MÓVILES -->
<div class="offcanvas offcanvas-start d-md-none" tabindex="-1" id="sidebarMenu" style="top: 0; width: 250px;">
<div class="offcanvas-header">
<h5 class="offcanvas-title">Menú</h5>
<button type="button" class="btn-close" data-bs-dismiss="offcanvas"></button>
</div>
<div class="offcanvas-body">
<nav class="sidebar-nav">
<ul class="nav flex-column">
<!-- INICIO -->
<li class="nav-item">
<a href="/IMPORTADORES/agentes/dashboard" class="nav-link active">
<svg class="icon" fill="currentColor" viewBox="0 0 22 21">
<path d="M16.975 11H10V4.025a1 1 0 0 0-1.066-.998 8.5 8.5 0 1 0 9.039 9.039.999.999 0 0 0-1-1.066h.002Z"/>
<path d="M12.5 0c-.157 0-.311.01-.565.027A1 1 0 0 0 11 1.02V10h8.975a1 1 0 0 0 1-.935c.013-.188.028-.374.028-.565A8.51 8.51 0 0 0 12.5 0Z"/>
</svg>
<span>Inicio</span>
</a>
</li>
<!-- SOLICITUDES VINCULACIÓN -->
<li class="nav-item">
<a href="/IMPORTADORES/vinculaciones/solicitudesVinculacion" class="nav-link">
<svg class="icon" fill="currentColor" viewBox="0 0 20 20">
<path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 4 4 0 005.656 0l3-3a4 4 0 00-5.656-5.656l-1.5 1.5a1 1 0 101.414 1.414l1.5-1.5z"/>
<path d="M7.414 15.414a2 2 0 01-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 4 4 0 00-5.656 0l-3 3a4 4 0 105.656 5.656l1.5-1.5a1 1 0 10-1.414-1.414l-1.5 1.5z"/>
</svg>
<span>Vinculación</span>
</a>
</li>
<!-- USUARIOS VINCULADOS -->
<li class="nav-item">
<a href="/IMPORTADORES/vinculaciones/vinculacionesAgencia" class="nav-link">
<svg class="icon" fill="currentColor" viewBox="0 0 20 20">
<path d="M14 2a3.963 3.963 0 0 0-1.4.267 6.439 6.439 0 0 1-1.331 6.638A4 4 0 1 0 14 2Zm1 9h-1.264A6.957 6.957 0 0 1 15 15v2a2.97 2.97 0 0 1-.184 1H19a1 1 0 0 0 1-1v-1a5.006 5.006 0 0 0-5-5ZM6.5 9a4.5 4.5 0 1 0 0-9 4.5 4.5 0 0 0 0 9ZM8 10H5a5.006 5.006 0 0 0-5 5v2a1 1 0 0 0 1 1h11a1 1 0 0 0 1-1v-2a5.006 5.006 0 0 0-5-5Z"/>
</svg>
<span>Usuarios Vinculados</span>
</a>
</li>
<!-- Patentes -->
<li class="nav-item">
<a class="nav-link" data-bs-toggle="collapse" href="#submenuPatentesMobile" role="button" aria-expanded="false" aria-controls="submenuPatentesMobile">
<svg class="icon" fill="currentColor" viewBox="0 0 20 20">
<path fill-rule="evenodd" d="M4 4a2 2 0 012-2h4.586A2 2 0 0112 2.586L15.414 6A2 2 0 0116 7.414V16a2 2 0 01-2 2H6a2 2 0 01-2-2V4zm2 6a1 1 0 011-1h6a1 1 0 110 2H7a1 1 0 01-1-1zm1 3a1 1 0 100 2h6a1 1 0 100-2H7z" clip-rule="evenodd"/>
</svg>
<span>Patentes</span>
<svg class="collapse-icon" fill="none" stroke="currentColor" viewBox="0 0 10 6">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="m1 1 4 4 4-4"/>
</svg>
</a>
<div class="collapse" id="submenuPatentesMobile">
<ul class="nav flex-column submenu">
<li class="nav-item">
<a href="/IMPORTADORES/patente/lista" class="nav-link">
Ver Patentes
</a>
</li>
<li class="nav-item">
<a href="/IMPORTADORES/patente/alta" class="nav-link">
Nueva Patente
</a>
</li>
</ul>
</div>
</li>
<!-- LOCACIONES -->
<li class="nav-item">
<a class="nav-link" data-bs-toggle="collapse" href="#submenuLocacionesMobile" role="button" aria-expanded="false" aria-controls="submenuLocacionesMobile">
<svg class="icon" fill="currentColor" viewBox="0 0 20 20">
<path d="M10 2C6.686 2 4 4.686 4 8c0 5.25 6 12 6 12s6-6.75 6-12c0-3.314-2.686-6-6-6zm0 8c-1.105 0-2-.895-2-2s.895-2 2-2 2 .895 2 2-.895 2-2 2z"/>
</svg>
<span>Locaciones</span>
<svg class="collapse-icon" fill="none" stroke="currentColor" viewBox="0 0 10 6">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="m1 1 4 4 4-4"/>
</svg>
</a>
<div class="collapse" id="submenuLocacionesMobile">
<ul class="nav flex-column submenu">
<li class="nav-item">
<a href="/IMPORTADORES/locaciones/lista" class="nav-link">
Ver Locaciones
</a>
</li>
<li class="nav-item">
<a href="/IMPORTADORES/locaciones/alta" class="nav-link">
Agregar Locaciones
</a>
</li>
</ul>
</div>
</li>
<!-- ALTA DE AGENTES -->
<li class="nav-item">
<a href="/IMPORTADORES/agencias/alta" class="nav-link">
<svg class="icon" fill="currentColor" viewBox="0 0 20 20">
<path fill-rule="evenodd" d="M10 9a3 3 0 100-6 3 3 0 000 6zm-7 9a7 7 0 1114 0H3z" clip-rule="evenodd"/>
</svg>
<span>Alta de agentes</span>
</a>
</li>
<!-- CONFIGURACIÓN -->
<li class="nav-item">
<a href="/IMPORTADORES/configuracion" class="nav-link">
<svg class="icon" fill="currentColor" viewBox="0 0 20 20">
<path fill-rule="evenodd" d="M11.983 1.034a1 1 0 00-1.966 0l-.26 1.857a6.996 6.996 0 00-1.223.51l-1.7-1.04a1 1 0 00-1.366.366l-1 1.732a1 1 0 00.366 1.366l1.7 1.04a6.984 6.984 0 000 1.02l-1.7 1.04a1 1 0 00-.366 1.366l1 1.732a1 1 0 001.366.366l1.7-1.04c.392.212.803.386 1.223.51l.26 1.857a1 1 0 001.966 0l.26-1.857c.42-.124.831-.298 1.223-.51l1.7 1.04a1 1 0 001.366-.366l1-1.732a1 1 0 00-.366-1.366l-1.7-1.04a6.984 6.984 0 000-1.02l1.7-1.04a1 1 0 00.366-1.366l-1-1.732a1 1 0 00-1.366-.366l-1.7 1.04a6.996 6.996 0 00-1.223-.51l-.26-1.857zM10 13a3 3 0 100-6 3 3 0 000 6z" clip-rule="evenodd"/>
</svg>
<span>Configuración</span>
</a>
</li>
</ul>
</nav>
</div>
</div>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js"></script>
<script>
// Simular lógica de dashboard
document.addEventListener('DOMContentLoaded', function() {
const esDashboard = window.location.pathname.includes('/agencias/dashboard');
const welcomeText = document.getElementById('welcome-text');
const logoutBtn = document.getElementById('logout-btn');
if (!esDashboard) {
if (welcomeText) {
welcomeText.style.display = 'none';
}
if (logoutBtn) {
logoutBtn.style.display = 'inline-block';
}
}
// Manejar estados activos basados en URL actual
const currentPath = window.location.pathname;
const navLinks = document.querySelectorAll('.nav-link');
navLinks.forEach(link => {
const href = link.getAttribute('href');
if (href && currentPath.includes(href)) {
link.classList.add('active');
}
});
document.querySelectorAll('[data-bs-toggle="collapse"]').forEach(trigger => {
// Desactivar Bootstrap
trigger.removeAttribute('data-bs-toggle');
// Inicializar estado correcto
const target = document.querySelector(trigger.getAttribute('href'));
if (target) {
target.style.transition = 'height 0.3s ease, opacity 0.2s ease';
target.style.overflow = 'hidden';
if (!target.classList.contains('show')) {
target.style.height = '0';
target.style.opacity = '0';
}
}
trigger.addEventListener('click', function(e) {
e.preventDefault();
const target = document.querySelector(this.getAttribute('href'));
const icon = this.querySelector('.collapse-icon');
if (!target) return;
const isExpanding = this.getAttribute('aria-expanded') !== 'true';
if (isExpanding) {
// Abrir
target.classList.add('show');
target.style.height = '0';
target.style.opacity = '0';
target.style.display = 'block';
// Forzar reflow
void target.offsetHeight;
target.style.height = target.scrollHeight + 'px';
target.style.opacity = '1';
this.setAttribute('aria-expanded', 'true');
if (icon) icon.style.transform = 'rotate(180deg)';
} else {
// Cerrar
target.style.height = target.scrollHeight + 'px';
target.style.opacity = '1';
// Forzar reflow
void target.offsetHeight;
target.style.height = '0';
target.style.opacity = '0';
setTimeout(() => {
target.classList.remove('show');
target.style.display = '';
}, 300);
this.setAttribute('aria-expanded', 'false');
if (icon) icon.style.transform = 'rotate(0deg)';
}
});
});
});
</script>

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,315 @@
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet">
<style>
/* Estilos inspirados en Tailwind */
.sidebar-container { background-color: #f9fafb; border-right: 1px solid #e5e7eb; }
.sidebar-dark { background-color: #1f2937; border-right: 1px solid #374151; }
.sidebar-nav .nav-link { color: #374151; font-weight: 500; padding: 0.5rem 0.75rem; margin-bottom: 0.125rem;
border-radius: 0.5rem; transition: all 0.15s ease-in-out; display: flex; align-items: center; }
.sidebar-nav .nav-link:hover { background-color: #f3f4f6; color: #111827; }
.sidebar-nav .nav-link.active { background-color: #f3f4f6; color: #111827; }
.sidebar-nav .nav-link .icon { width: 1.25rem; height: 1.25rem; margin-right: 0.75rem; color: #6b7280; transition: color 0.15s ease-in-out; flex-shrink: 0; }
.sidebar-nav .nav-link:hover .icon,
.sidebar-nav .nav-link.active .icon { color: #374151; }
.sidebar-nav .badge { background-color: #f3f4f6; color: #374151; font-size: 0.75rem; font-weight: 500; padding: 0.25rem 0.5rem; border-radius: 9999px; margin-left: auto; }
.sidebar-nav .badge.badge-primary { background-color: #dbeafe; color: #1e40af; }
.submenu .nav-link { padding-left: 2.75rem; color: #6b7280; font-weight: 400; }
.submenu .nav-link:hover { background-color: #f3f4f6; color: #374151; }
.submenu .nav-link.active { background-color: #f3f4f6; color: #374151; }
.collapse-icon { width: 0.75rem; height: 0.75rem; margin-left: auto; transition: transform 0.15s ease-in-out; }
.nav-link[aria-expanded="true"] .collapse-icon { transform: rotate(180deg); }
/* Estilos para modo oscuro */
.sidebar-dark .sidebar-nav .nav-link { color: #d1d5db; }
.sidebar-dark .sidebar-nav .nav-link:hover { background-color: #374151; color: #ffffff; }
.sidebar-dark .sidebar-nav .nav-link.active { background-color: #374151; color: #ffffff; }
.sidebar-dark .sidebar-nav .nav-link .icon { color: #9ca3af; }
.sidebar-dark .sidebar-nav .nav-link:hover .icon,
.sidebar-dark .sidebar-nav .nav-link.active .icon { color: #ffffff; }
.sidebar-dark .sidebar-nav .badge { background-color: #374151; color: #d1d5db; }
.sidebar-dark .sidebar-nav .badge.badge-primary { background-color: #1e3a8a; color: #93c5fd; }
.sidebar-dark .submenu .nav-link { color: #9ca3af; }
.sidebar-dark .submenu .nav-link:hover { background-color: #374151; color: #d1d5db; }
.sidebar-dark .submenu .nav-link.active { background-color: #374151; color: #d1d5db; }
/* Navbar */
.navbar-brand { font-weight: 600; font-size: 1.125rem; }
.menu-toggle { border: 1px solid #6b7280; border-radius: 0.5rem; padding: 0.5rem; transition: all 0.15s ease-in-out; }
.menu-toggle:hover { background-color: #f3f4f6; }
/* Offcanvas para móviles */
.offcanvas { background-color: #ffffff; }
.offcanvas-header { border-bottom: 1px solid #e5e7eb; padding: 1rem 1.5rem; }
.offcanvas-title { font-weight: 600; font-size: 1.125rem; color: #111827; }
.offcanvas-body { padding: 1rem 1.5rem; }
/* Responsive adjustments */
@media (max-width: 767.98px) {
.sidebar-nav .nav-link { color: #374151; }
.sidebar-nav .nav-link:hover { background-color: #f3f4f6; color: #111827; }
.sidebar-nav .nav-link.active { background-color: #f3f4f6; color: #111827; }
}
</style>
<!-- 🔷 NAVBAR -->
<nav class="navbar navbar-dark bg-dark fixed-top">
<div class="container-fluid">
<!-- Botón de menú para móviles -->
<button class="menu-toggle d-md-none me-3" type="button" data-bs-toggle="offcanvas" data-bs-target="#sidebarMenu">
</button>
<span class="navbar-brand">SIIH | Agente Aduanal</span>
<div class="d-flex ms-auto text-white">
<div class="navbar-content">
<?php
// Verifica si estás en el dashboard principal del importador
$esDashboard = str_contains($_SERVER['REQUEST_URI'], '/agentes/dashboard');
if ($esDashboard): ?>
<span id="welcome-text">Bienvenido, <?= htmlspecialchars($_SESSION['usuario_nombre']) ?></span>
<?php else: ?>
<a href="/IMPORTADORES/sistemas/logout" class="btn btn-outline-light btn-sm ms-2 W-auto btn-animated">Cerrar Sesión</a>
<?php endif; ?>
</div>
</div>
</div>
</nav>
<!-- SIDEBAR FIJA (escritorio) -->
<div class="d-none d-md-block sidebar-container sidebar-dark position-fixed h-100" style="width: 250px; top: 0; padding-top: 4rem; z-index: 1000;">
<div class="px-3 py-4 h-100 overflow-y-auto">
<nav class="sidebar-nav">
<ul class="nav flex-column">
<!-- INICIO -->
<li class="nav-item">
<a href="/IMPORTADORES/agentes/dashboard" class="nav-link active">
<svg class="icon" fill="currentColor" viewBox="0 0 22 21">
<path d="M16.975 11H10V4.025a1 1 0 0 0-1.066-.998 8.5 8.5 0 1 0 9.039 9.039.999.999 0 0 0-1-1.066h.002Z"/>
<path d="M12.5 0c-.157 0-.311.01-.565.027A1 1 0 0 0 11 1.02V10h8.975a1 1 0 0 0 1-.935c.013-.188.028-.374.028-.565A8.51 8.51 0 0 0 12.5 0Z"/>
</svg>
<span>Inicio</span>
</a>
</li>
<!-- IMPORTADORES VINCULADOS -->
<li class="nav-item">
<a href="/IMPORTADORES/agentes/vinculados" class="nav-link">
<svg class="icon" fill="currentColor" viewBox="0 0 20 20">
<path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 4 4 0 005.656 0l3-3a4 4 0 00-5.656-5.656l-1.5 1.5a1 1 0 101.414 1.414l1.5-1.5z"/>
<path d="M7.414 15.414a2 2 0 01-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 4 4 0 00-5.656 0l-3 3a4 4 0 105.656 5.656l1.5-1.5a1 1 0 10-1.414-1.414l-1.5 1.5z"/>
</svg>
<span>Vinculación</span>
</a>
</li>
<!-- Patentes -->
<li class="nav-item">
<a class="nav-link" data-bs-toggle="collapse" href="#submenuPatentesDesktop" role="button" aria-expanded="false" aria-controls="submenuPatentesDesktop">
<svg class="icon" fill="currentColor" viewBox="0 0 20 20">
<path fill-rule="evenodd" d="M4 4a2 2 0 012-2h4.586A2 2 0 0112 2.586L15.414 6A2 2 0 0116 7.414V16a2 2 0 01-2 2H6a2 2 0 01-2-2V4zm2 6a1 1 0 011-1h6a1 1 0 110 2H7a1 1 0 01-1-1zm1 3a1 1 0 100 2h6a1 1 0 100-2H7z" clip-rule="evenodd"/>
</svg>
<span>Patentes</span>
<svg class="collapse-icon" fill="none" stroke="currentColor" viewBox="0 0 10 6">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="m1 1 4 4 4-4"/>
</svg>
</a>
<div class="collapse" id="submenuPatentesDesktop">
<ul class="nav flex-column submenu">
<li class="nav-item">
<a href="/IMPORTADORES/patente/lista" class="nav-link">
Ver Patentes
</a>
</li>
<li class="nav-item">
<a href="/IMPORTADORES/patente/alta" class="nav-link">
Nueva Patente
</a>
</li>
</ul>
</div>
</li>
<!-- LOCACIONES -->
<li class="nav-item">
<a class="nav-link" data-bs-toggle="collapse" href="#submenuLocacionesDesktop" role="button" aria-expanded="false" aria-controls="submenuLocacionesDesktop">
<svg class="icon" fill="currentColor" viewBox="0 0 20 20">
<path d="M10 2C6.686 2 4 4.686 4 8c0 5.25 6 12 6 12s6-6.75 6-12c0-3.314-2.686-6-6-6zm0 8c-1.105 0-2-.895-2-2s.895-2 2-2 2 .895 2 2-.895 2-2 2z"/>
</svg>
<span>Locaciones</span>
<svg class="collapse-icon" fill="none" stroke="currentColor" viewBox="0 0 10 6">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="m1 1 4 4 4-4"/>
</svg>
</a>
<div class="collapse" id="submenuLocacionesDesktop">
<ul class="nav flex-column submenu">
<li class="nav-item">
<a href="/IMPORTADORES/locaciones/lista" class="nav-link">
Ver Locaciones
</a>
</li>
<li class="nav-item">
<a href="/IMPORTADORES/locaciones/alta" class="nav-link">
Agregar Locaciones
</a>
</li>
</ul>
</div>
</li>
<!-- CONFIGURACIÓN -->
<li class="nav-item">
<a href="/IMPORTADORES/configuracion" class="nav-link">
<svg class="icon" fill="currentColor" viewBox="0 0 20 20">
<path fill-rule="evenodd" d="M11.983 1.034a1 1 0 00-1.966 0l-.26 1.857a6.996 6.996 0 00-1.223.51l-1.7-1.04a1 1 0 00-1.366.366l-1 1.732a1 1 0 00.366 1.366l1.7 1.04a6.984 6.984 0 000 1.02l-1.7 1.04a1 1 0 00-.366 1.366l1 1.732a1 1 0 001.366.366l1.7-1.04c.392.212.803.386 1.223.51l.26 1.857a1 1 0 001.966 0l.26-1.857c.42-.124.831-.298 1.223-.51l1.7 1.04a1 1 0 001.366-.366l1-1.732a1 1 0 00-.366-1.366l-1.7-1.04a6.984 6.984 0 000-1.02l1.7-1.04a1 1 0 00.366-1.366l-1-1.732a1 1 0 00-1.366-.366l-1.7 1.04a6.996 6.996 0 00-1.223-.51l-.26-1.857zM10 13a3 3 0 100-6 3 3 0 000 6z" clip-rule="evenodd"/>
</svg>
<span>Configuración</span>
</a>
</li>
</ul>
</nav>
</div>
</div>
<!-- OFFCANVAS PARA MÓVILES -->
<div class="offcanvas offcanvas-start d-md-none" tabindex="-1" id="sidebarMenu" style="top: 0; width: 250px;">
<div class="offcanvas-header">
<h5 class="offcanvas-title">Menú</h5>
<button type="button" class="btn-close" data-bs-dismiss="offcanvas"></button>
</div>
<div class="offcanvas-body">
<nav class="sidebar-nav">
<ul class="nav flex-column">
<!-- INICIO -->
<li class="nav-item">
<a href="/IMPORTADORES/agentes/dashboard" class="nav-link active">
<svg class="icon" fill="currentColor" viewBox="0 0 22 21">
<path d="M16.975 11H10V4.025a1 1 0 0 0-1.066-.998 8.5 8.5 0 1 0 9.039 9.039.999.999 0 0 0-1-1.066h.002Z"/>
<path d="M12.5 0c-.157 0-.311.01-.565.027A1 1 0 0 0 11 1.02V10h8.975a1 1 0 0 0 1-.935c.013-.188.028-.374.028-.565A8.51 8.51 0 0 0 12.5 0Z"/>
</svg>
<span>Inicio</span>
</a>
</li>
<!-- IMPORTADORES VINCULADOS -->
<li class="nav-item">
<a href="/IMPORTADORES/agentes/vinculados" class="nav-link">
<svg class="icon" fill="currentColor" viewBox="0 0 20 20">
<path d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 4 4 0 005.656 0l3-3a4 4 0 00-5.656-5.656l-1.5 1.5a1 1 0 101.414 1.414l1.5-1.5z"/>
<path d="M7.414 15.414a2 2 0 01-2.828-2.828l3-3a2 2 0 012.828 0 1 1 0 001.414-1.414 4 4 0 00-5.656 0l-3 3a4 4 0 105.656 5.656l1.5-1.5a1 1 0 10-1.414-1.414l-1.5 1.5z"/>
</svg>
<span>Vinculación</span>
</a>
</li>
<!-- Patentes -->
<li class="nav-item">
<a class="nav-link" data-bs-toggle="collapse" href="#submenuPatentesMobile" role="button" aria-expanded="false" aria-controls="submenuPatentesMobile">
<svg class="icon" fill="currentColor" viewBox="0 0 20 20">
<path fill-rule="evenodd" d="M4 4a2 2 0 012-2h4.586A2 2 0 0112 2.586L15.414 6A2 2 0 0116 7.414V16a2 2 0 01-2 2H6a2 2 0 01-2-2V4zm2 6a1 1 0 011-1h6a1 1 0 110 2H7a1 1 0 01-1-1zm1 3a1 1 0 100 2h6a1 1 0 100-2H7z" clip-rule="evenodd"/>
</svg>
<span>Patentes</span>
<svg class="collapse-icon" fill="none" stroke="currentColor" viewBox="0 0 10 6">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="m1 1 4 4 4-4"/>
</svg>
</a>
<div class="collapse" id="submenuPatentesMobile">
<ul class="nav flex-column submenu">
<li class="nav-item">
<a href="/IMPORTADORES/patente/lista" class="nav-link">
Ver Patentes
</a>
</li>
<li class="nav-item">
<a href="/IMPORTADORES/patente/alta" class="nav-link">
Nueva Patente
</a>
</li>
</ul>
</div>
</li>
<!-- LOCACIONES -->
<li class="nav-item">
<a class="nav-link" data-bs-toggle="collapse" href="#submenuLocacionesMobile" role="button" aria-expanded="false" aria-controls="submenuLocacionesMobile">
<svg class="icon" fill="currentColor" viewBox="0 0 20 20">
<path d="M10 2C6.686 2 4 4.686 4 8c0 5.25 6 12 6 12s6-6.75 6-12c0-3.314-2.686-6-6-6zm0 8c-1.105 0-2-.895-2-2s.895-2 2-2 2 .895 2 2-.895 2-2 2z"/>
</svg>
<span>Locaciones</span>
<svg class="collapse-icon" fill="none" stroke="currentColor" viewBox="0 0 10 6">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="m1 1 4 4 4-4"/>
</svg>
</a>
<div class="collapse" id="submenuLocacionesMobile">
<ul class="nav flex-column submenu">
<li class="nav-item">
<a href="/IMPORTADORES/locaciones/lista" class="nav-link">
Ver Locaciones
</a>
</li>
<li class="nav-item">
<a href="/IMPORTADORES/locaciones/alta" class="nav-link">
Agregar Locaciones
</a>
</li>
</ul>
</div>
</li>
<!-- CONFIGURACIÓN -->
<li class="nav-item">
<a href="/IMPORTADORES/configuracion" class="nav-link">
<svg class="icon" fill="currentColor" viewBox="0 0 20 20">
<path fill-rule="evenodd" d="M11.983 1.034a1 1 0 00-1.966 0l-.26 1.857a6.996 6.996 0 00-1.223.51l-1.7-1.04a1 1 0 00-1.366.366l-1 1.732a1 1 0 00.366 1.366l1.7 1.04a6.984 6.984 0 000 1.02l-1.7 1.04a1 1 0 00-.366 1.366l1 1.732a1 1 0 001.366.366l1.7-1.04c.392.212.803.386 1.223.51l.26 1.857a1 1 0 001.966 0l.26-1.857c.42-.124.831-.298 1.223-.51l1.7 1.04a1 1 0 001.366-.366l1-1.732a1 1 0 00-.366-1.366l-1.7-1.04a6.984 6.984 0 000-1.02l1.7-1.04a1 1 0 00.366-1.366l-1-1.732a1 1 0 00-1.366-.366l-1.7 1.04a6.996 6.996 0 00-1.223-.51l-.26-1.857zM10 13a3 3 0 100-6 3 3 0 000 6z" clip-rule="evenodd"/>
</svg>
<span>Configuración</span>
</a>
</li>
</ul>
</nav>
</div>
</div>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js"></script>
<script>
// Simular lógica de dashboard
document.addEventListener('DOMContentLoaded', function() {
const esDashboard = window.location.pathname.includes('/agentes/dashboard');
const welcomeText = document.getElementById('welcome-text');
const logoutBtn = document.getElementById('logout-btn');
if (!esDashboard) {
if (welcomeText) {
welcomeText.style.display = 'none';
}
if (logoutBtn) {
logoutBtn.style.display = 'inline-block';
}
}
// Manejar estados activos basados en URL actual
const currentPath = window.location.pathname;
const navLinks = document.querySelectorAll('.nav-link');
navLinks.forEach(link => {
const href = link.getAttribute('href');
if (href && currentPath.includes(href)) {
link.classList.add('active');
}
});
// Manejar collapse manualmente
document.querySelectorAll('[data-bs-toggle="collapse"]').forEach(trigger => {
trigger.addEventListener('click', function(e) {
e.preventDefault();
e.stopPropagation();
const targetId = this.getAttribute('href');
const targetElement = document.querySelector(targetId);
const isExpanded = this.getAttribute('aria-expanded') === 'true';
if (targetElement) {
if (isExpanded) {
// Cerrar
targetElement.classList.remove('show');
this.setAttribute('aria-expanded', 'false');
} else {
// Abrir
targetElement.classList.add('show');
this.setAttribute('aria-expanded', 'true');
}
}
});
// Remover el atributo data-bs-toggle para evitar conflictos
trigger.removeAttribute('data-bs-toggle');
});
});
</script>

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,594 @@
<!-- views/partials/sidebar_configuracion.php -->
<?php
// Verificar que el usuario esté autenticado
if (!isset($_SESSION['usuario_id'])) {
header('Location: /IMPORTADORES/login');
exit;
}
// Obtener información del usuario
$tipoUsuario = $_SESSION['tipo_usuario'] ?? '';
$usuarioId = $_SESSION['usuario_id'] ?? null;
// Definir permisos por tipo de usuario
$permisos = [
'super_admin' => [
'informacion_general' => true,
'automatizaciones' => true,
'preferencias' => true,
'seguridad' => true,
'bitacoras' => [
'registro_accesos' => true,
'registro_cambios' => true,
'registro_usuarios' => true,
'registro_agencias' => true,
'mi_actividad' => true
],
],
'admin_agencia' => [
'informacion_general' => true,
'automatizaciones' => true,
'preferencias' => true,
'seguridad' => true,
'bitacoras' => [
'acceso_usuarios_agencia' => true,
'registro_vinculaciones' => true,
'mi_actividad' => true
],
],
'agente_aduanal' => [
'informacion_general' => true,
'automatizaciones' => true,
'preferencias' => true,
'seguridad' => true,
'bitacoras' => [
'acceso_usuarios_agencia' => true,
'registro_vinculaciones' => true,
'mi_actividad' => true
],
],
'importador' => [
'informacion_general' => true,
'automatizaciones' => true,
'preferencias' => true,
'seguridad' => true,
'bitacoras' => [
'vinculaciones_usuario' => true,
'mi_actividad' => true
],
],
];
// Obtener permisos del usuario actual
$permisosUsuario = $permisos[$tipoUsuario] ?? $permisos['importador'];
$permisosBitacoras = $permisosUsuario['bitacoras'] ?? [];
// Función para verificar si el usuario tiene acceso a bitácoras
function tieneAccesoBitacoras($permisosBitacoras) {
if (is_bool($permisosBitacoras)) {
return $permisosBitacoras;
}
return !empty($permisosBitacoras) && array_filter($permisosBitacoras);
}
// Contar bitácoras disponibles según permisos
function contarBitacorasDisponibles($permisosBitacoras) {
if (!is_array($permisosBitacoras)) {
return is_bool($permisosBitacoras) && $permisosBitacoras ? 1 : 0;
}
return count(array_filter($permisosBitacoras));
}
$cantidadBitacoras = contarBitacorasDisponibles($permisosBitacoras);
// Variables para detectar las vistas actuales
$currentPath = $_SERVER['REQUEST_URI'];
$esConfiguracion = $currentPath === '/IMPORTADORES/configuracion/index' || $currentPath === '/IMPORTADORES/configuracion';
$esVistaAutomatizaciones = strpos($currentPath, '/IMPORTADORES/automatizaciones/') === 0;
$esVistaPreferencias = strpos($currentPath, '/IMPORTADORES/preferencias/') === 0;
$esVistaSeguridad = strpos($currentPath, '/IMPORTADORES/seguridad/') === 0;
$esVistaBitacoras = strpos($currentPath, '/IMPORTADORES/bitacoras/') === 0 && $currentPath !== '/IMPORTADORES/configuracion';
$esVistaReset = strpos($currentPath, '/IMPORTADORES/reset/') === 0;
// Función para obtener el texto descriptivo según el permiso
function obtenerTextoPermiso($permiso, $tipoUsuario) {
$textos = [
'registro_accesos' => ($tipoUsuario === 'super_admin') ? 'Registro de accesos' : null,
'registro_cambios' => ($tipoUsuario === 'super_admin') ? 'Cambios de usuarios' : null,
'registro_usuarios' => ($tipoUsuario === 'super_admin') ? 'Registro de usuarios' : null,
'registro_agencias' => ($tipoUsuario === 'super_admin') ? 'Registro de agencias' : null,
'acceso_usuarios_agencia' => ($tipoUsuario === 'admin_agencia' || $tipoUsuario === 'agente_aduanal') ? 'Registro de accesos' : null,
'registro_vinculaciones' => ($tipoUsuario === 'admin_agencia' || $tipoUsuario === 'agente_aduanal') ? 'Vinculaciones (Agencia)' : null,
'mi_actividad' => in_array($tipoUsuario, ['super_admin', 'admin_agencia', 'agente_aduanal', 'importador']) ? 'Mi actividad' : null,
'vinculaciones_usuario' => ($tipoUsuario === 'importador') ? 'Mis vinculaciones' : null,
];
return $textos[$permiso] ?? ucfirst(str_replace('_', ' ', $permiso));
}
// Función para obtener la URL según el permiso
function obtenerUrlPermiso($permiso) {
$urls = [
'registro_accesos' => '/IMPORTADORES/bitacoras/sistema', // ✅
'registro_cambios' => '/IMPORTADORES/bitacoras/cambios', // ✅
'registro_usuarios' => '/IMPORTADORES/bitacoras/usuarios', //
'registro_agencias' => '/IMPORTADORES/bitacoras/agencias', // ✅
'acceso_usuarios_agencia' => '/IMPORTADORES/bitacoras/sistemaAgencia', // ✅
'registro_vinculaciones' => '/IMPORTADORES/bitacoras/vinculaciones', // ✅
'mi_actividad' => '/IMPORTADORES/bitacoras/miAcceso', // ✅
'vinculaciones_usuario' => '/IMPORTADORES/bitacoras/vinculacionesUsuario' // ✅
];
return $urls[$permiso] ?? '/IMPORTADORES/bitacoras/index';
}
?>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet">
<style>
/* Estilos inspirados en Tailwind */
.sidebar-container { background-color: #f9fafb; border-right: 1px solid #e5e7eb; }
.sidebar-dark { background-color: #1f2937; border-right: 1px solid #374151; }
.sidebar-nav .nav-link { color: #374151; font-weight: 500; padding: 0.5rem 0.75rem; margin-bottom: 0.125rem;
border-radius: 0.5rem; transition: all 0.15s ease-in-out; display: flex; align-items: center; }
.sidebar-nav .nav-link:hover { background-color: #f3f4f6; color: #111827; }
.sidebar-nav .nav-link.active { background-color: #f3f4f6; color: #111827; }
.sidebar-nav .nav-link .icon { width: 1.25rem; height: 1.25rem; margin-right: 0.75rem; color: #6b7280; transition: color 0.15s ease-in-out; flex-shrink: 0; }
.sidebar-nav .nav-link:hover .icon,
.sidebar-nav .nav-link.active .icon { color: #374151; }
.sidebar-nav .badge { background-color: #f3f4f6; color: #374151; font-size: 0.75rem; font-weight: 500; padding: 0.25rem 0.5rem; border-radius: 9999px; margin-left: auto; }
.sidebar-nav .badge.badge-primary { background-color: #dbeafe; color: #1e40af; }
.submenu .nav-link { padding-left: 2.75rem; color: #6b7280; font-weight: 400; }
.submenu .nav-link:hover { background-color: #f3f4f6; color: #374151; }
.submenu .nav-link.active { background-color: #f3f4f6; color: #374151; }
.collapse-icon { width: 0.75rem; height: 0.75rem; margin-left: auto; transition: transform 0.15s ease-in-out; }
.nav-link[aria-expanded="true"] .collapse-icon { transform: rotate(180deg); }
/* Estilos para modo oscuro */
.sidebar-dark .sidebar-nav .nav-link { color: #d1d5db; }
.sidebar-dark .sidebar-nav .nav-link:hover { background-color: #374151; color: #ffffff; }
.sidebar-dark .sidebar-nav .nav-link.active { background-color: #374151; color: #ffffff; }
.sidebar-dark .sidebar-nav .nav-link .icon { color: #9ca3af; }
.sidebar-dark .sidebar-nav .nav-link:hover .icon,
.sidebar-dark .sidebar-nav .nav-link.active .icon { color: #ffffff; }
.sidebar-dark .sidebar-nav .badge { background-color: #374151; color: #d1d5db; }
.sidebar-dark .sidebar-nav .badge.badge-primary { background-color: #1e3a8a; color: #93c5fd; }
.sidebar-dark .submenu .nav-link { color: #9ca3af; }
.sidebar-dark .submenu .nav-link:hover { background-color: #374151; color: #d1d5db; }
.sidebar-dark .submenu .nav-link.active { background-color: #374151; color: #d1d5db; }
/* Navbar */
.navbar-brand { font-weight: 600; font-size: 1.125rem; }
.menu-toggle { border: 1px solid #6b7280; border-radius: 0.5rem; padding: 0.5rem; transition: all 0.15s ease-in-out; }
.menu-toggle:hover { background-color: #f3f4f6; }
/* Offcanvas para móviles */
.offcanvas { background-color: #ffffff; }
.offcanvas-header { border-bottom: 1px solid #e5e7eb; padding: 1rem 1.5rem; }
.offcanvas-title { font-weight: 600; font-size: 1.125rem; color: #111827; }
.offcanvas-body { padding: 1rem 1.5rem; }
/* Responsive adjustments */
@media (max-width: 767.98px) {
.sidebar-nav .nav-link { color: #374151; }
.sidebar-nav .nav-link:hover { background-color: #f3f4f6; color: #111827; }
.sidebar-nav .nav-link.active { background-color: #f3f4f6; color: #111827; }
.permission-scope { background-color: rgba(108, 117, 125, 0.2); }
.submenu-item:hover { background-color: rgba(233, 236, 239, 0.8) !important; }
}
.user-badge { font-size: 0.75rem; padding: 2px 6px; border-radius: 3px; }
.permission-indicator { font-size: 0.7rem; color: #28a745; margin-left: 5px; }
.permission-scope { font-size: 0.65rem; color: #6c757d; margin-left: 5px; padding: 1px 4px; background-color: rgba(108, 117, 125, 0.1); border-radius: 2px; }
.submenu-item { font-size: 0.9rem; padding-left: 1rem !important; }
.submenu-item:hover { background-color: rgba(73, 80, 87, 0.7) !important; }
/* Transiciones para collapse manual */
.collapse {
overflow: hidden;
transition: height 0.3s ease;
}
/* Estilo para íconos */
.collapse-icon {
transition: transform 0.3s ease;
margin-left: auto;
}
</style>
<!-- NAVBAR -->
<nav class="navbar navbar-dark bg-dark fixed-top">
<div class="container-fluid">
<!-- Botón de menú para móviles -->
<button class="menu-toggle d-md-none me-3" type="button" data-bs-toggle="offcanvas" data-bs-target="#sidebarMenu">
</button>
<span class="navbar-brand">
SIIH | Configuración
<span class="user-badge bg-info text-dark ms-2"><?= strtoupper(str_replace('_', ' ', $tipoUsuario)) ?></span>
</span>
<div class="d-flex ms-auto">
<?php
// Definir la URL de regreso según el tipo de usuario
$urlRegreso = match($tipoUsuario) {
'super_admin' => '/IMPORTADORES/administrador/dashboard',
'admin_agencia' => '/IMPORTADORES/agencias/dashboard',
'agente_aduanal' => '/IMPORTADORES/agentes/dashboard',
'importador' => '/IMPORTADORES/importadores/dashboard',
default => '/IMPORTADORES/importadores/dashboard'
};
?>
<a href="<?= $urlRegreso ?>" class="btn btn-outline-light btn-sm ms-2 W-auto btn-animated" style="margin: 0 0 0 25px;">
← Panel Principal
</a>
</div>
</div>
</nav>
<!-- SIDEBAR FIJA (escritorio) -->
<div class="d-none d-md-block sidebar-container sidebar-dark position-fixed h-100" style="width: 250px; top: 0; padding-top: 4rem; z-index: 1000;">
<div class="px-3 py-4 h-100 overflow-y-auto">
<nav class="sidebar-nav">
<ul class="nav flex-column">
<!-- INFORMACIÓN GENERAL -->
<li class="nav-item">
<?php if ($permisosUsuario['informacion_general']): ?>
<a href="/IMPORTADORES/configuracion/index" class="nav-link">
<svg class="icon" fill="currentColor" viewBox="0 0 20 20">
<path fill-rule="evenodd" d="M18 10c0 4.418-3.582 8-8 8s-8-3.582-8-8 3.582-8 8-8 8 3.582 8 8zm-8-4a1 1 0 100 2 1 1 0 000-2zm-1 4a1 1 0 000 2h1v3a1 1 0 102 0v-4a1 1 0 00-1-1h-2z" clip-rule="evenodd" />
</svg>
<span>Información general</span>
</a>
<?php endif; ?>
</li>
<!-- AUTOMATIZACIONES -->
<li class="nav-item">
<?php if ($permisosUsuario['automatizaciones']): ?>
<a href="/IMPORTADORES/automatizaciones/index" class="nav-link">
<svg class="icon" fill="currentColor" viewBox="0 0 20 20">
<path d="M13 7H7v6h6V7z" />
<path fill-rule="evenodd" d="M5 3a2 2 0 00-2 2v10a2 2 0 002 2h10a2 2 0 002-2V9.586a1 1 0 00-.293-.707l-4.586-4.586A1 1 0 0012.414 4H5zm7 1.414L16.586 10H13a1 1 0 01-1-1V4.414z" clip-rule="evenodd"/>
</svg>
<span>Automatizaciones</span>
</a>
<?php endif; ?>
</li>
<!-- PREFERENCIAS -->
<li class="nav-item">
<?php if ($permisosUsuario['preferencias']): ?>
<?php $enDashboardPreferencias = $_SERVER['REQUEST_URI'] === '/IMPORTADORES/preferencias/index'; ?>
<a class="nav-link <?= $esVistaPreferencias ? 'active' : '' ?>"
href="<?= $enDashboardPreferencias ? '#submenuPreferenciasDesktop' : '/IMPORTADORES/preferencias/index' ?>"
<?= $enDashboardPreferencias ? 'data-bs-toggle="collapse"' : '' ?>
role="button" aria-expanded="false" aria-controls="submenuPreferenciasDesktop"
onclick="<?= $enDashboardPreferencias ? 'return true;' : 'event.preventDefault(); window.location.href = \'/IMPORTADORES/preferencias/index\';' ?>">
<svg class="icon" fill="currentColor" viewBox="0 0 20 20">
<path fill-rule="evenodd" d="M11.3 1.046a1 1 0 00-2.6 0L7.528 3H4a1 1 0 000 2h1v10H4a1 1 0 000 2h3.528l1.172 1.954a1 1 0 001.6 0L12.472 17H16a1 1 0 000-2h-1V5h1a1 1 0 100-2h-3.528L11.3 1.046zM9 6a1 1 0 011 1v6a1 1 0 11-2 0V7a1 1 0 011-1z" clip-rule="evenodd"/>
</svg>
<span>Preferencias</span>
<svg class="collapse-icon" fill="none" stroke="currentColor" viewBox="0 0 10 6">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="m1 1 4 4 4-4"/>
</svg>
</a>
<div class="collapse" id="submenuPreferenciasDesktop">
<ul class="nav flex-column submenu">
<li class="nav-item">
<a href="/IMPORTADORES/preferencias/notificaciones" class="nav-link">
Gestión de Notificaciones
</a>
</li>
<li class="nav-item">
<a href="/IMPORTADORES/preferencias/catalogos" class="nav-link">
Visualización de Catálogos
</a>
</li>
</ul>
</div>
<?php endif; ?>
</li>
<!-- SEGURIDAD -->
<li class="nav-item">
<?php if ($permisosUsuario['seguridad']): ?>
<?php $enDashboardSeguridad = $_SERVER['REQUEST_URI'] === '/IMPORTADORES/seguridad/index'; ?>
<a class="nav-link <?= $esVistaSeguridad ? 'active' : '' ?>"
href="<?= $enDashboardSeguridad ? '#submenuSeguridadDesktop' : '/IMPORTADORES/seguridad/index' ?>"
<?= $enDashboardSeguridad ? 'data-bs-toggle="collapse"' : '' ?>
role="button" aria-expanded="false" aria-controls="submenuSeguridadDesktop"
onclick="<?= $enDashboardSeguridad ? 'return true;' : 'event.preventDefault(); window.location.href = \'/IMPORTADORES/seguridad/index\';' ?>">
<svg class="icon" fill="currentColor" viewBox="0 0 20 20">
<path fill-rule="evenodd" d="M10 2a4 4 0 00-4 4v2H5a2 2 0 00-2 2v6a2 2 0 002 2h10a2 2 0 002-2v-6a2 2 0 00-2-2h-1V6a4 4 0 00-4-4zm2 6V6a2 2 0 10-4 0v2h4z" clip-rule="evenodd"/>
</svg>
<span>Seguridad</span>
<svg class="collapse-icon" fill="none" stroke="currentColor" viewBox="0 0 10 6">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="m1 1 4 4 4-4"/>
</svg>
</a>
<div class="collapse" id="submenuSeguridadDesktop">
<nav class="nav flex-column submenu">
<li class="nav-item">
<a href="/IMPORTADORES/seguridad/opciones" class="nav-link">
Opciones de Seguridad
</a>
</li>
</nav>
</div>
<?php endif; ?>
</li>
<!-- BITÁCORAS CON PERMISOS ESPECÍFICOS -->
<li class="nav-item">
<?php if (tieneAccesoBitacoras($permisosBitacoras)): ?>
<?php $enDashboardBitacoras = $_SERVER['REQUEST_URI'] === '/IMPORTADORES/bitacoras/index'; ?>
<a class="nav-link <?= $esVistaBitacoras ? 'active' : '' ?>"
href="<?= $enDashboardBitacoras ? '#submenuBitacorasDesktop' : '/IMPORTADORES/bitacoras/index' ?>"
<?= $enDashboardBitacoras ? 'data-bs-toggle="collapse"' : '' ?>
role="button" aria-expanded="<?= $enDashboardBitacoras ? 'false' : 'false' ?>" aria-controls="submenuBitacorasDesktop"
onclick="<?= $enDashboardBitacoras ? 'return true;' : 'event.preventDefault(); window.location.href = \'/IMPORTADORES/bitacoras/index\';' ?>">
<svg class="icon" fill="currentColor" viewBox="0 0 20 20">
<path d="M4 3a2 2 0 00-2 2v10a2 2 0 002 2h12a1 1 0 001-1V4a1 1 0 00-1-1H4zm0 2h11v10H4V5z"/>
<path d="M6 7h5v1H6V7zm0 3h8v1H6v-1z"/>
</svg>
<span>Bitácoras</span>
<svg class="collapse-icon" fill="none" stroke="currentColor" viewBox="0 0 10 6">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="m1 1 4 4 4-4"/>
</svg>
</a>
<div class="collapse" id="submenuBitacorasDesktop">
<nav class="nav flex-column submenu">
<?php foreach ($permisosBitacoras as $permiso => $tienePermiso): ?>
<li class="nav-item">
<?php if ($tienePermiso): ?>
<?php
$url = obtenerUrlPermiso($permiso);
$texto = obtenerTextoPermiso($permiso, $tipoUsuario);
$esActivo = $_SERVER['REQUEST_URI'] === $url;
// Scope según dashboard
$scope = '';
if ($tipoUsuario === 'super_admin') {
$scope = 'Global';
} elseif ($tipoUsuario === 'admin_agencia' || $tipoUsuario === 'agente_aduanal') {
if (in_array($permiso, ['acceso_usuarios_agencia', 'registro_vinculaciones'])) {
$scope = 'Agencia';
} elseif (in_array($permiso, ['mis_vinculaciones', 'mi_actividad'])) {
$scope = 'Personal';
}
} elseif ($tipoUsuario === 'importador') {
$scope = 'Personal';
}
?>
<a href="<?= $url ?>"
class="nav-link submenu-item px-3 py-2 d-flex justify-content-between align-items-center <?= $esActivo ? 'active' : '' ?>">
<span>
<?= htmlspecialchars($texto) ?>
<?php if ($scope): ?>
<span class="permission-scope"><?= $scope ?></span>
<?php endif; ?>
</span>
</a>
<?php endif; ?>
</li>
<?php endforeach; ?>
</nav>
</div>
<?php endif; ?>
</li>
</ul>
</nav>
</div>
</div>
<!-- OFFCANVAS PARA MÓVILES -->
<div class="offcanvas offcanvas-start d-md-none" tabindex="-1" id="sidebarMenu" style="top: 0; width: 250px;">
<div class="offcanvas-header">
<h5 class="offcanvas-title">Menú</h5>
<button type="button" class="btn-close" data-bs-dismiss="offcanvas"></button>
</div>
<div class="offcanvas-body">
<nav class="sidebar-nav">
<ul class="nav flex-column">
<!-- INFORMACIÓN GENERAL -->
<li class="nav-item">
<?php if ($permisosUsuario['informacion_general']): ?>
<a href="/IMPORTADORES/configuracion/index" class="nav-link">
<svg class="icon" fill="currentColor" viewBox="0 0 20 20">
<path fill-rule="evenodd" d="M18 10c0 4.418-3.582 8-8 8s-8-3.582-8-8 3.582-8 8-8 8 3.582 8 8zm-8-4a1 1 0 100 2 1 1 0 000-2zm-1 4a1 1 0 000 2h1v3a1 1 0 102 0v-4a1 1 0 00-1-1h-2z" clip-rule="evenodd" />
</svg>
<span>Información general</span>
</a>
<?php endif; ?>
</li>
<!-- AUTOMATIZACIONES -->
<li class="nav-item">
<?php if ($permisosUsuario['automatizaciones']): ?>
<a href="/IMPORTADORES/automatizaciones/index" class="nav-link">
<svg class="icon" fill="currentColor" viewBox="0 0 20 20">
<path d="M13 7H7v6h6V7z" />
<path fill-rule="evenodd" d="M5 3a2 2 0 00-2 2v10a2 2 0 002 2h10a2 2 0 002-2V9.586a1 1 0 00-.293-.707l-4.586-4.586A1 1 0 0012.414 4H5zm7 1.414L16.586 10H13a1 1 0 01-1-1V4.414z" clip-rule="evenodd"/>
</svg>
<span>Automatizaciones</span>
</a>
<?php endif; ?>
</li>
<!-- PREFERENCIAS -->
<li class="nav-item">
<?php if ($permisosUsuario['preferencias']): ?>
<?php $enDashboardPreferencias = $_SERVER['REQUEST_URI'] === '/IMPORTADORES/preferencias/index'; ?>
<a class="nav-link <?= $esVistaPreferencias ? 'active' : '' ?>"
href="<?= $enDashboardPreferencias ? '#submenuPreferenciasMobile' : '/IMPORTADORES/preferencias/index' ?>"
<?= $enDashboardPreferencias ? 'data-bs-toggle="collapse"' : '' ?>
role="button" aria-expanded="false" aria-controls="submenuPreferenciasMobile"
onclick="<?= $enDashboardPreferencias ? 'return true;' : 'event.preventDefault(); window.location.href = \'/IMPORTADORES/preferencias/index\';' ?>">
<svg class="icon" fill="currentColor" viewBox="0 0 20 20">
<path fill-rule="evenodd" d="M11.3 1.046a1 1 0 00-2.6 0L7.528 3H4a1 1 0 000 2h1v10H4a1 1 0 000 2h3.528l1.172 1.954a1 1 0 001.6 0L12.472 17H16a1 1 0 000-2h-1V5h1a1 1 0 100-2h-3.528L11.3 1.046zM9 6a1 1 0 011 1v6a1 1 0 11-2 0V7a1 1 0 011-1z" clip-rule="evenodd"/>
</svg>
<span>Preferencias</span>
<svg class="collapse-icon" fill="none" stroke="currentColor" viewBox="0 0 10 6">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="m1 1 4 4 4-4"/>
</svg>
</a>
<div class="collapse" id="submenuPreferenciasMobile">
<ul class="nav flex-column submenu">
<li class="nav-item">
<a href="/IMPORTADORES/preferencias/notificaciones" class="nav-link">
Gestión de Notificaciones
</a>
</li>
<li class="nav-item">
<a href="/IMPORTADORES/preferencias/catalogos" class="nav-link">
Visualización de Catálogos
</a>
</li>
</ul>
</div>
<?php endif; ?>
</li>
<!-- SEGURIDAD -->
<li class="nav-item">
<?php if ($permisosUsuario['seguridad']): ?>
<?php $enDashboardSeguridad = $_SERVER['REQUEST_URI'] === '/IMPORTADORES/seguridad/index'; ?>
<a class="nav-link <?= $esVistaSeguridad ? 'active' : '' ?>"
href="<?= $enDashboardSeguridad ? '#submenuSeguridadMobile' : '/IMPORTADORES/seguridad/index' ?>"
<?= $enDashboardSeguridad ? 'data-bs-toggle="collapse"' : '' ?>
role="button" aria-expanded="false" aria-controls="submenuSeguridadMobile"
onclick="<?= $enDashboardSeguridad ? 'return true;' : 'event.preventDefault(); window.location.href = \'/IMPORTADORES/seguridad/index\';' ?>">
<svg class="icon" fill="currentColor" viewBox="0 0 20 20">
<path fill-rule="evenodd" d="M10 2a4 4 0 00-4 4v2H5a2 2 0 00-2 2v6a2 2 0 002 2h10a2 2 0 002-2v-6a2 2 0 00-2-2h-1V6a4 4 0 00-4-4zm2 6V6a2 2 0 10-4 0v2h4z" clip-rule="evenodd"/>
</svg>
<span>Seguridad</span>
<svg class="collapse-icon" fill="none" stroke="currentColor" viewBox="0 0 10 6">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="m1 1 4 4 4-4"/>
</svg>
</a>
<div class="collapse" id="submenuSeguridadMobile">
<nav class="nav flex-column submenu">
<li class="nav-item">
<a href="/IMPORTADORES/seguridad/opciones" class="nav-link">
Opciones de Seguridad
</a>
</li>
</nav>
</div>
<?php endif; ?>
</li>
<!-- BITÁCORAS CON PERMISOS ESPECÍFICOS -->
<li class="nav-item">
<?php if (tieneAccesoBitacoras($permisosBitacoras)): ?>
<?php $enDashboardBitacoras = $_SERVER['REQUEST_URI'] === '/IMPORTADORES/bitacoras/index'; ?>
<a class="nav-link <?= $esVistaBitacoras ? 'active' : '' ?>"
href="<?= $enDashboardBitacoras ? '#submenuBitacorasMobile' : '/IMPORTADORES/bitacoras/index' ?>"
<?= $enDashboardBitacoras ? 'data-bs-toggle="collapse"' : '' ?>
role="button" aria-expanded="<?= $enDashboardBitacoras ? 'false' : 'false' ?>" aria-controls="submenuBitacorasMobile"
onclick="<?= $enDashboardBitacoras ? 'return true;' : 'event.preventDefault(); window.location.href = \'/IMPORTADORES/bitacoras/index\';' ?>">
<svg class="icon" fill="currentColor" viewBox="0 0 20 20">
<path d="M4 3a2 2 0 00-2 2v10a2 2 0 002 2h12a1 1 0 001-1V4a1 1 0 00-1-1H4zm0 2h11v10H4V5z"/>
<path d="M6 7h5v1H6V7zm0 3h8v1H6v-1z"/>
</svg>
<span>Bitácoras</span>
<svg class="collapse-icon" fill="none" stroke="currentColor" viewBox="0 0 10 6">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="m1 1 4 4 4-4"/>
</svg>
</a>
<div class="collapse" id="submenuBitacorasMobile">
<nav class="nav flex-column submenu">
<?php foreach ($permisosBitacoras as $permiso => $tienePermiso): ?>
<li class="nav-item">
<?php if ($tienePermiso): ?>
<?php
$url = obtenerUrlPermiso($permiso);
$texto = obtenerTextoPermiso($permiso, $tipoUsuario);
$esActivo = $_SERVER['REQUEST_URI'] === $url;
// Scope según dashboard
$scope = '';
if ($tipoUsuario === 'super_admin') {
$scope = 'Global';
} elseif ($tipoUsuario === 'admin_agencia' || $tipoUsuario === 'agente_aduanal') {
if (in_array($permiso, ['acceso_usuarios_agencia', 'registro_vinculaciones'])) {
$scope = 'Agencia';
} elseif (in_array($permiso, ['mis_vinculaciones', 'mi_actividad'])) {
$scope = 'Personal';
}
} elseif ($tipoUsuario === 'importador') {
$scope = 'Personal';
}
?>
<a href="<?= $url ?>"
class="nav-link submenu-item <?= $esActivo ? 'active' : '' ?>">
<span>
<?= htmlspecialchars($texto) ?>
<?php if ($scope): ?>
<span class="permission-scope"><?= $scope ?></span>
<?php endif; ?>
</span>
</a>
<?php endif; ?>
</li>
<?php endforeach; ?>
</nav>
</div>
<?php endif; ?>
</li>
</ul>
</nav>
</div>
</div>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js"></script>
<script>
document.addEventListener('DOMContentLoaded', function() {
const currentPath = window.location.pathname.replace(/\/$/, '');
// Inicializar todos los collapses
document.querySelectorAll('[data-bs-toggle="collapse"]').forEach(trigger => {
const targetId = trigger.getAttribute('data-bs-target') || trigger.getAttribute('href');
const target = document.querySelector(targetId);
const icon = trigger.querySelector('.collapse-icon');
if (!target) return;
// 1. Inicialización automática basada en la ruta
const shouldExpand = Array.from(target.querySelectorAll('a')).some(link => {
const href = link.getAttribute('href')?.replace(/\/$/, '');
return href && currentPath.startsWith(href);
});
// 2. Configurar el estado inicial
if (shouldExpand) {
target.classList.add('show');
trigger.setAttribute('aria-expanded', 'true');
if (icon) icon.style.transform = 'rotate(180deg)';
}
// 3. Manejar clicks manuales
trigger.addEventListener('click', function(e) {
// Solo prevenir el comportamiento por defecto si es necesario
if (this.getAttribute('href') === '#' || this.getAttribute('data-bs-target') === '#') {
e.preventDefault();
}
const isExpanded = this.getAttribute('aria-expanded') === 'true';
// Rotar el ícono inmediatamente
if (icon) {
icon.style.transform = isExpanded ? 'rotate(0deg)' : 'rotate(180deg)';
}
// Actualizar atributo ARIA
this.setAttribute('aria-expanded', !isExpanded);
});
// 4. Manejar eventos de Bootstrap para sincronización
target.addEventListener('show.bs.collapse', () => {
trigger.setAttribute('aria-expanded', 'true');
if (icon) icon.style.transform = 'rotate(180deg)';
});
target.addEventListener('hide.bs.collapse', () => {
trigger.setAttribute('aria-expanded', 'false');
if (icon) icon.style.transform = 'rotate(0deg)';
});
});
// Marcar enlaces activos
document.querySelectorAll('.nav-link').forEach(link => {
const href = link.getAttribute('href')?.replace(/\/$/, '');
if (href && (currentPath === href || currentPath.startsWith(href + '/'))) {
link.classList.add('active');
}
});
});
</script>

File diff suppressed because it is too large Load Diff

View File

@@ -19,16 +19,10 @@
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/animate.css/4.1.1/animate.min.css">
<style>
body { font-family: 'Segoe UI', sans-serif; background-color: #f4f6f9; }
.sidebar { width: 220px; height: 100vh; background-color: #343a40; position: fixed; top: 0; left: 0; padding-top: 56px; z-index: 1040; }
.sidebar .nav-link { font-weight: bold; color: white; transition: all 0.3s ease; }
.sidebar .nav-link:hover, .sidebar .nav-link.active { background-color: #495057; color: #fff; }
.content { margin-top: 56px; padding: 40px 20px; position: relative; z-index: 1; background-color: #f4f6f9; transition: margin-left 0.3s ease; }
@media (min-width: 768px) { .content { margin-left: 250px; } }
@media (max-width: 767.98px) {
.content { margin-left: 0; }
.sidebar .nav-link { font-weight: normal; color: #343a40; background-color: transparent; }
.sidebar .nav-link:hover, .sidebar .nav-link.active { background-color: #e9ecef; color: #212529; }
}
/* Contenido principal: sin solaparse con sidebar/navbar */
.content { margin-top: 0; padding: 24px 20px; position: relative; z-index: 1; background-color: #f4f6f9; transition: margin-left 0.3s ease; }
@media (min-width: 768px) { .content { margin-left: var(--sidebar-width, 280px); } }
@media (max-width: 767.98px) { .content { margin-left: 0; } }
.card { border-radius: 12px; }
/* Animaciones personalizadas */
.card-hover { transition: all 0.3s ease; cursor: pointer; }
@@ -57,7 +51,7 @@
background: linear-gradient(90deg, transparent, rgba(255, 255, 255, 0.2), transparent); transition: left 0.5s; }
.btn-animated:hover::before { left: 100%; }
.hide { display: none !important; }
.alert { top: 75px; left: 275px; right: 50px; position: fixed; z-index: 1050; width: calc(100% - 285px); }
.alert { top: calc(var(--navbar-height, 70px) + 5px); left: calc(var(--sidebar-width, 280px) + 15px); right: 15px; position: fixed; z-index: 1050; width: calc(100% - var(--sidebar-width, 280px) - 30px); }
/* ========== ANIMACIONES PARA INPUTS TEXTO ========== */
.form-control:not(.no-animation) { transition: all 0.3s ease; border: 2px solid #dee2e6; position: relative; }
.form-control:not(.no-animation).shake { animation: shake 0.5s ease-in-out; }
@@ -241,7 +235,7 @@
/* Indicador de progreso */
.progress-indicator {
position: fixed;
top: 60px;
top: calc(var(--navbar-height, 70px) + 10px);
right: 20px;
background: white;
border-radius: 8px;
@@ -293,7 +287,7 @@
/* Auto-save indicator */
.auto-save-indicator {
position: fixed;
top: 60px;
top: calc(var(--navbar-height, 70px) + 10px);
left: 50%;
transform: translateX(-50%);
background: #28a745;
@@ -308,7 +302,7 @@
.auto-save-indicator.show {
opacity: 1;
top: 80px;
top: calc(var(--navbar-height, 70px) + 30px);
}
/* Templates rápidos */
@@ -926,7 +920,7 @@
<!-- Panel de referencia (columna derecha) - MEJORADO -->
<div class="col-lg-3">
<!-- Card de Pedimentos -->
<div class="card bg-light shadow-sm fade-in-up mb-3" style="animation-delay: 0.2s; position: sticky; top: 20px;">
<div class="card bg-light shadow-sm fade-in-up mb-3" style="animation-delay: 0.2s; position: sticky; top: calc(var(--navbar-height, 70px) + 20px);">
<div class="card-header bg-primary text-white py-2">
<h6 class="mb-0"><i class="fas fa-list-ul me-1"></i> Pedimentos Disponibles</h6>
</div>
@@ -1161,7 +1155,6 @@
<!-- Choices.js JS -->
<script src="https://cdn.jsdelivr.net/npm/choices.js/public/assets/scripts/choices.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/js/bootstrap.bundle.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/select2@4.1.0/dist/js/select2.min.js"></script>
<script>