This commit is contained in:
2025-10-21 11:41:30 -06:00
parent ed2ef4e01b
commit b1b9e4a77c
49 changed files with 14632 additions and 1736 deletions

View File

@@ -61,8 +61,718 @@
<body>
<div class="content">
<h4 class="mb-4 animate__animated animate__fadeInDown title_glow">⚙️ Automatizaciones</h4>
<h4 class="mb-4 animate__animated animate__fadeInDown title-glow">⚙️ Automatizaciones</h4>
<!-- Card de conexión WINSAAI -->
<div class="row mb-4 fade-in-up">
<div class="col-12">
<div class="card shadow-sm card-hover">
<div class="card-header bg-primary text-white d-flex align-items-center justify-content-between">
<div class="d-flex align-items-center">
<i class="fas fa-plug me-2"></i>
<h5 class="mb-0">Conexión con WINSAAI</h5>
</div>
<div id="connection_status_header">
<span class="badge bg-secondary">Verificando...</span>
</div>
</div>
<div class="card-body">
<!-- Estado cuando NO hay configuración -->
<div id="no_config_section">
<p class="text-muted mb-3">
<i class="fas fa-info-circle me-2"></i>
Configura la conexión con el sistema WINSAAI para importar automáticamente pedimentos y COVES.
</p>
<!-- Botón para conectar -->
<button class="btn btn-success btn-animated btn-pulse" type="button"
onclick="showConfigForm()">
<i class="fas fa-wifi me-2"></i>
Conectar con WINSAAI
</button>
</div>
<!-- Estado cuando SÍ hay configuración -->
<div id="existing_config_section" style="display: none;">
<div class="alert alert-success border-0" role="alert">
<div class="d-flex align-items-center mb-3">
<i class="fas fa-check-circle me-2 text-success fs-5"></i>
<h6 class="mb-0 fw-bold">¡Estás configurado para conectarte a WINSAAI!</h6>
</div>
<!-- Información de la configuración actual -->
<div class="row g-3 mb-3">
<div class="col-md-6">
<div class="d-flex align-items-center">
<i class="fas fa-server me-2 text-primary"></i>
<div>
<small class="text-muted d-block">Servidor</small>
<span id="current_server" class="fw-semibold">-</span>
</div>
</div>
</div>
<div class="col-md-6">
<div class="d-flex align-items-center">
<i class="fas fa-user me-2 text-info"></i>
<div>
<small class="text-muted d-block">Usuario</small>
<span id="current_user" class="fw-semibold">-</span>
</div>
</div>
</div>
<div class="col-md-6">
<div class="d-flex align-items-center">
<i class="fas fa-shield-alt me-2 text-warning"></i>
<div>
<small class="text-muted d-block">Protocolo</small>
<span id="current_protocol" class="fw-semibold">-</span>
</div>
</div>
</div>
<div class="col-md-6">
<div class="d-flex align-items-center">
<i class="fas fa-clock me-2 text-secondary"></i>
<div>
<small class="text-muted d-block">Última sincronización</small>
<span id="last_sync_display" class="fw-semibold">Nunca</span>
</div>
</div>
</div>
</div>
<!-- Opciones de sincronización activas -->
<div class="mb-3">
<small class="text-muted d-block mb-2">Datos configurados para sincronizar:</small>
<div class="d-flex gap-2">
<span id="sync_pedimentos_badge" class="badge bg-primary" style="display: none;">
<i class="fas fa-file-alt me-1"></i> Pedimentos
</span>
<span id="sync_coves_badge" class="badge bg-info" style="display: none;">
<i class="fas fa-ship me-1"></i> COVES
</span>
</div>
</div>
<!-- Botones de acción -->
<div class="d-flex gap-2 flex-wrap">
<button type="button" class="btn btn-outline-primary btn-animated btn-sm"
onclick="showConfigForm(true)">
<i class="fas fa-edit me-2"></i>
Editar Conexión
</button>
<button type="button" class="btn btn-outline-warning btn-animated btn-sm"
onclick="toggleConfigStatus()">
<i class="fas fa-pause me-2"></i>
<span id="toggle_status_text">Desactivar</span>
</button>
<button type="button" class="btn btn-outline-secondary btn-animated btn-sm"
onclick="testExistingConnection()">
<i class="fas fa-vial me-2"></i>
Probar Conexión
</button>
<button type="button" class="btn btn-outline-success btn-animated btn-sm"
onclick="syncData()">
<i class="fas fa-sync-alt me-2"></i>
Sincronizar Ahora
</button>
<button type="button" class="btn btn-outline-danger btn-animated btn-sm"
onclick="deleteConfig()">
<i class="fas fa-trash me-2"></i>
Eliminar
</button>
</div>
</div>
</div>
<!-- Formulario de configuración (inicialmente oculto) -->
<div class="collapse mt-4" id="winsaaiConfig">
<div class="card border-secondary">
<div class="card-header bg-light d-flex justify-content-between align-items-center">
<h6 class="mb-0">
<i class="fas fa-cog me-2"></i>
<span id="form_title">Configuración de Conexión API</span>
</h6>
<button type="button" class="btn btn-sm btn-outline-secondary"
onclick="hideConfigForm()">
<i class="fas fa-times"></i>
</button>
</div>
<div class="card-body">
<form id="winsaaiForm">
<div class="row">
<!-- Dirección IP/DNS -->
<div class="col-md-6 mb-3">
<label for="winsaai_host" class="form-label">
<i class="fas fa-server me-1"></i>
Dirección IP / DNS
</label>
<input type="text" class="form-control" id="winsaai_host"
placeholder="192.168.1.100 o api.winsaai.com" required>
<div class="form-text">
Ingresa la dirección IP o nombre de dominio del servidor WINSAAI
</div>
</div>
<!-- Puerto -->
<div class="col-md-6 mb-3">
<label for="winsaai_port" class="form-label">
<i class="fas fa-network-wired me-1"></i>
Puerto
</label>
<input type="number" class="form-control" id="winsaai_port"
placeholder="8080" min="1" max="65535" required>
<div class="form-text">
Puerto del servicio API (ejemplo: 8080, 80, 443)
</div>
</div>
</div>
<div class="row">
<!-- Usuario -->
<div class="col-md-6 mb-3">
<label for="winsaai_usuario" class="form-label">
<i class="fas fa-user me-1"></i>
Usuario
</label>
<input type="text" class="form-control" id="winsaai_usuario"
placeholder="Tu usuario de WINSAAI" required>
<div class="form-text">
Nombre de usuario proporcionado por WINSAAI
</div>
</div>
<!-- Contraseña -->
<div class="col-md-6 mb-3">
<label for="winsaai_password" class="form-label">
<i class="fas fa-lock me-1"></i>
Contraseña
</label>
<input type="password" class="form-control" id="winsaai_password"
placeholder="Tu contraseña" required>
<div class="form-text">
Contraseña de acceso al sistema WINSAAI
</div>
</div>
</div>
<div class="row">
<!-- Protocolo -->
<div class="col-md-6 mb-3">
<label for="winsaai_protocol" class="form-label">
<i class="fas fa-shield-alt me-1"></i>
Protocolo
</label>
<select class="form-select" id="winsaai_protocol" required>
<option value="">Seleccionar protocolo</option>
<option value="http">HTTP</option>
<option value="https">HTTPS (Recomendado)</option>
</select>
</div>
<!-- Estado Actual -->
<div class="col-md-6 mb-3">
<label class="form-label">
<i class="fas fa-info-circle me-1"></i>
Estado Actual
</label>
<div id="connection_status_display" class="form-control-plaintext">
<span class="badge bg-secondary">No configurado</span>
</div>
</div>
</div>
<!-- Opciones de sincronización -->
<div class="row">
<div class="col-12 mb-3">
<label class="form-label">
<i class="fas fa-sync me-1"></i>
Datos a sincronizar
</label>
<div class="form-check">
<input class="form-check-input" type="checkbox" id="sync_pedimentos" checked>
<label class="form-check-label" for="sync_pedimentos">
<i class="fas fa-file-alt me-1 text-primary"></i>
Pedimentos
</label>
</div>
<div class="form-check">
<input class="form-check-input" type="checkbox" id="sync_coves" checked>
<label class="form-check-label" for="sync_coves">
<i class="fas fa-ship me-1 text-info"></i>
COVES
</label>
</div>
</div>
</div>
<!-- Botones de acción -->
<div class="d-flex gap-2 flex-wrap">
<button type="button" class="btn btn-outline-secondary btn-animated"
onclick="testConnection()">
<i class="fas fa-vial me-2"></i>
Probar Conexión
</button>
<button type="submit" class="btn btn-primary btn-animated">
<i class="fas fa-save me-2"></i>
Guardar Configuración
</button>
<button type="button" class="btn btn-warning btn-animated"
onclick="syncData()">
<i class="fas fa-sync-alt me-2"></i>
Sincronizar Ahora
</button>
</div>
</form>
</div>
</div>
</div>
<!-- Estado de conexión -->
<div id="connectionStatus" class="mt-3" style="display: none;">
<div class="alert alert-info">
<i class="fas fa-info-circle me-2"></i>
<span id="statusMessage">Verificando conexión...</span>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<!-- Scripts para funcionalidad WINSAAI -->
<script>
let currentConfig = null;
// Cargar configuración al inicio
document.addEventListener('DOMContentLoaded', function() {
loadExistingConfig();
});
// Cargar configuración existente
async function loadExistingConfig() {
try {
const response = await fetch('/IMPORTADORES/winsaai/get_config');
const result = await response.json();
if (result.success && result.data) {
currentConfig = result.data;
showExistingConfig(result.data);
} else {
showNoConfig();
}
} catch (error) {
console.error('Error cargando configuración:', error);
showNoConfig();
}
}
// Mostrar estado cuando NO hay configuración
function showNoConfig() {
document.getElementById('no_config_section').style.display = 'block';
document.getElementById('existing_config_section').style.display = 'none';
document.getElementById('connection_status_header').innerHTML = '<span class="badge bg-secondary">Sin configurar</span>';
}
// Mostrar estado cuando SÍ hay configuración
function showExistingConfig(config) {
// Ocultar sección de "no config" y mostrar sección existente
document.getElementById('no_config_section').style.display = 'none';
document.getElementById('existing_config_section').style.display = 'block';
// Actualizar información mostrada
document.getElementById('current_server').textContent = `${config.protocol}://${config.host}:${config.port}`;
document.getElementById('current_user').textContent = config.usuario;
document.getElementById('current_protocol').textContent = config.protocol.toUpperCase();
// Mostrar última sincronización
if (config.last_sync) {
const lastSyncDate = new Date(config.last_sync);
document.getElementById('last_sync_display').textContent = lastSyncDate.toLocaleString();
} else {
document.getElementById('last_sync_display').textContent = 'Nunca';
}
// Mostrar badges de sincronización
const pedimentosBadge = document.getElementById('sync_pedimentos_badge');
const covesBadge = document.getElementById('sync_coves_badge');
if (config.sync_pedimentos) {
pedimentosBadge.style.display = 'inline-block';
} else {
pedimentosBadge.style.display = 'none';
}
if (config.sync_coves) {
covesBadge.style.display = 'inline-block';
} else {
covesBadge.style.display = 'none';
}
// Actualizar header según estado
updateHeaderStatus(config.status);
// Actualizar botón de toggle según estado
updateToggleButton(config.status);
// Llenar formulario con datos existentes para edición
document.getElementById('winsaai_host').value = config.host || '';
document.getElementById('winsaai_port').value = config.port || '';
document.getElementById('winsaai_protocol').value = config.protocol || '';
document.getElementById('winsaai_usuario').value = config.usuario || '';
document.getElementById('sync_pedimentos').checked = config.sync_pedimentos == 1;
document.getElementById('sync_coves').checked = config.sync_coves == 1;
}
// Actualizar estado en el header
function updateHeaderStatus(status) {
const headerStatus = document.getElementById('connection_status_header');
let badgeHtml = '';
switch(status) {
case 'activo':
badgeHtml = '<span class="badge bg-success"><i class="fas fa-check-circle me-1"></i>Conectado</span>';
break;
case 'inactivo':
badgeHtml = '<span class="badge bg-warning"><i class="fas fa-pause me-1"></i>Inactivo</span>';
break;
case 'error':
badgeHtml = '<span class="badge bg-danger"><i class="fas fa-exclamation-triangle me-1"></i>Error</span>';
break;
default:
badgeHtml = '<span class="badge bg-secondary">Sin configurar</span>';
}
headerStatus.innerHTML = badgeHtml;
}
// Actualizar botón de toggle según estado
function updateToggleButton(status) {
const toggleBtn = document.querySelector('button[onclick="toggleConfigStatus()"]');
const toggleText = document.getElementById('toggle_status_text');
const icon = toggleBtn.querySelector('i');
if (status === 'activo') {
toggleText.textContent = 'Desactivar';
icon.className = 'fas fa-pause me-2';
toggleBtn.className = 'btn btn-outline-warning btn-animated btn-sm';
} else {
toggleText.textContent = 'Activar';
icon.className = 'fas fa-play me-2';
toggleBtn.className = 'btn btn-outline-success btn-animated btn-sm';
}
}
// Mostrar formulario de configuración
function showConfigForm(isEdit = false) {
const formTitle = document.getElementById('form_title');
const configCollapse = new bootstrap.Collapse(document.getElementById('winsaaiConfig'), { show: true });
if (isEdit) {
formTitle.textContent = 'Editar Configuración de WINSAAI';
} else {
formTitle.textContent = 'Nueva Configuración de WINSAAI';
// Limpiar formulario para nueva configuración
document.getElementById('winsaaiForm').reset();
}
}
// Ocultar formulario de configuración
function hideConfigForm() {
const configCollapse = new bootstrap.Collapse(document.getElementById('winsaaiConfig'), { hide: true });
}
// Probar conexión con configuración existente
async function testExistingConnection() {
if (!currentConfig) {
showStatus('error', 'No hay configuración para probar');
return;
}
showStatus('info', 'Probando conexión con configuración actual...');
try {
const response = await fetch('/IMPORTADORES/winsaai/test_connection', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
host: currentConfig.host,
port: currentConfig.port,
protocol: currentConfig.protocol,
usuario: currentConfig.usuario,
password: 'existing_config' // Indicador para usar configuración guardada
})
});
const result = await response.json();
if (result.success) {
showStatus('success', result.message);
updateHeaderStatus('activo');
} else {
showStatus('error', result.message);
updateHeaderStatus('error');
}
} catch (error) {
showStatus('error', 'Error de red: ' + error.message);
updateHeaderStatus('error');
}
}
// Activar/Desactivar configuración
async function toggleConfigStatus() {
if (!currentConfig) {
showStatus('error', 'No hay configuración para modificar');
return;
}
const currentStatus = currentConfig.status;
const newStatus = currentStatus === 'activo' ? 'inactivo' : 'activo';
const action = newStatus === 'activo' ? 'activar' : 'desactivar';
if (confirm(`¿Estás seguro de que deseas ${action} la configuración de WINSAAI?`)) {
showStatus('info', `${action === 'activar' ? 'Activando' : 'Desactivando'} configuración...`);
try {
// Aquí harías la llamada al servidor para cambiar el estado
// Por ahora simularemos el cambio
currentConfig.status = newStatus;
updateHeaderStatus(newStatus);
updateToggleButton(newStatus);
showStatus('success', `Configuración ${action === 'activar' ? 'activada' : 'desactivada'} correctamente`);
} catch (error) {
showStatus('error', `Error al ${action} configuración: ` + error.message);
}
}
}
// Eliminar configuración
async function deleteConfig() {
if (!currentConfig) {
showStatus('error', 'No hay configuración para eliminar');
return;
}
if (confirm('¿Estás seguro de que deseas eliminar completamente la configuración de WINSAAI? Esta acción no se puede deshacer.')) {
showStatus('info', 'Eliminando configuración...');
try {
// Aquí harías la llamada al servidor para eliminar
// Por ahora simularemos la eliminación
currentConfig = null;
showNoConfig();
hideConfigForm();
showStatus('success', 'Configuración eliminada correctamente');
} catch (error) {
showStatus('error', 'Error al eliminar configuración: ' + error.message);
}
}
}
// Función para probar la conexión (desde formulario)
async function testConnection() {
const host = document.getElementById('winsaai_host').value;
const port = document.getElementById('winsaai_port').value;
const protocol = document.getElementById('winsaai_protocol').value;
const usuario = document.getElementById('winsaai_usuario').value;
const password = document.getElementById('winsaai_password').value;
if (!host || !port || !protocol || !usuario || !password) {
showStatus('error', 'Por favor completa todos los campos obligatorios');
return;
}
showStatus('info', 'Probando conexión con WINSAAI...');
try {
const response = await fetch('/IMPORTADORES/winsaai/test_connection', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
host: host,
port: parseInt(port),
protocol: protocol,
usuario: usuario,
password: password
})
});
const result = await response.json();
if (result.success) {
showStatus('success', result.message);
} else {
showStatus('error', result.message);
}
} catch (error) {
showStatus('error', 'Error de red: ' + error.message);
}
}
// Función para sincronizar datos
async function syncData() {
let pedimentos, coves;
if (currentConfig) {
// Usar configuración existente
pedimentos = currentConfig.sync_pedimentos;
coves = currentConfig.sync_coves;
} else {
// Usar valores del formulario
pedimentos = document.getElementById('sync_pedimentos').checked;
coves = document.getElementById('sync_coves').checked;
}
if (!pedimentos && !coves) {
showStatus('warning', 'No hay datos configurados para sincronizar');
return;
}
showStatus('info', 'Iniciando sincronización de datos...');
let syncType = 'both';
if (pedimentos && !coves) syncType = 'pedimentos';
else if (!pedimentos && coves) syncType = 'coves';
try {
const response = await fetch('/IMPORTADORES/winsaai/sync_data', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
sync_type: syncType
})
});
const result = await response.json();
if (result.success) {
let message = 'Sincronización completada: ';
if (result.data.pedimentos) {
message += `${result.data.pedimentos.processed} pedimentos `;
}
if (result.data.coves) {
message += `${result.data.coves.processed} COVES `;
}
showStatus('success', message);
// Actualizar última sincronización si hay configuración
if (currentConfig) {
currentConfig.last_sync = new Date().toISOString();
document.getElementById('last_sync_display').textContent = new Date().toLocaleString();
}
} else {
showStatus('error', result.message);
}
} catch (error) {
showStatus('error', 'Error de red: ' + error.message);
}
}
// Función para mostrar estado
function showStatus(type, message) {
const statusDiv = document.getElementById('connectionStatus');
const statusMessage = document.getElementById('statusMessage');
const alertDiv = statusDiv.querySelector('.alert');
// Remover clases anteriores
alertDiv.className = 'alert';
// Agregar nueva clase según el tipo
switch(type) {
case 'success':
alertDiv.classList.add('alert-success');
statusMessage.innerHTML = `<i class="fas fa-check-circle me-2"></i>${message}`;
break;
case 'error':
alertDiv.classList.add('alert-danger');
statusMessage.innerHTML = `<i class="fas fa-exclamation-circle me-2"></i>${message}`;
break;
case 'warning':
alertDiv.classList.add('alert-warning');
statusMessage.innerHTML = `<i class="fas fa-exclamation-triangle me-2"></i>${message}`;
break;
default:
alertDiv.classList.add('alert-info');
statusMessage.innerHTML = `<i class="fas fa-info-circle me-2"></i>${message}`;
}
statusDiv.style.display = 'block';
// Auto-ocultar después de 5 segundos para mensajes de éxito
if (type === 'success') {
setTimeout(() => {
statusDiv.style.display = 'none';
}, 5000);
}
}
// Manejar envío del formulario
document.getElementById('winsaaiForm').addEventListener('submit', async function(e) {
e.preventDefault();
const config = {
host: document.getElementById('winsaai_host').value,
port: parseInt(document.getElementById('winsaai_port').value),
protocol: document.getElementById('winsaai_protocol').value,
usuario: document.getElementById('winsaai_usuario').value,
password: document.getElementById('winsaai_password').value,
sync_pedimentos: document.getElementById('sync_pedimentos').checked,
sync_coves: document.getElementById('sync_coves').checked
};
// Validar campos obligatorios
if (!config.host || !config.port || !config.protocol || !config.usuario || !config.password) {
showStatus('error', 'Por favor completa todos los campos obligatorios');
return;
}
showStatus('info', 'Guardando configuración...');
try {
const response = await fetch('/IMPORTADORES/winsaai/save_config', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(config)
});
const result = await response.json();
if (result.success) {
showStatus('success', result.message);
// Recargar configuración para mostrar el nuevo estado
await loadExistingConfig();
hideConfigForm();
} else {
showStatus('error', result.message);
}
} catch (error) {
console.error('Error completo:', error);
showStatus('error', 'Error de red: ' + error.message);
}
});
// Animación suave para el collapse
document.getElementById('winsaaiConfig').addEventListener('show.bs.collapse', function() {
this.style.opacity = '0';
this.style.transform = 'translateY(-20px)';
setTimeout(() => {
this.style.transition = 'all 0.3s ease';
this.style.opacity = '1';
this.style.transform = 'translateY(0)';
}, 50);
});
</script>
</body>
</html>

View File

@@ -0,0 +1,407 @@
<?php
include __DIR__ . '/../partials/sidebar_importador.php';
// Verificar que el importador tenga información configurada
if (!$importador) {
echo '<div class="alert alert-warning">
<i class="fas fa-exclamation-triangle"></i>
<strong>Configuración requerida:</strong>
Debe configurar su información general antes de crear pedimentos.
<a href="/IMPORTADORES/configuracion" class="btn btn-sm btn-primary ms-2">Ir a Configuración</a>
</div>';
return;
}
// Verificar que tenga claves de pedimentos configuradas
if (empty($claves_pedimentos)) {
echo '<div class="alert alert-info">
<i class="fas fa-info-circle"></i>
<strong>Claves de pedimentos:</strong>
No tiene claves de pedimentos de importación configuradas.
<a href="/IMPORTADORES/claves_pedimentos" class="btn btn-sm btn-success ms-2">Configurar Claves</a>
</div>';
}
?>
<!DOCTYPE html>
<html lang="es">
<head>
<meta charset="UTF-8">
<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 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; }
}
.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); }
}
.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">
<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
</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">
<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
placeholder="Ej: 23 47 3807 8001234"
pattern="[0-9\s]+" title="Solo números y espacios">
<div class="form-text">Formato estándar: AA AA AAAA AAAAAAA (año aduana sección número)</div>
</div>
<div class="mb-3">
<label for="clave_ped" class="form-label">Clave de Pedimento <span class="text-danger">*</span></label>
<select class="form-select" id="clave_ped" name="clave_ped" required>
<option value="">Seleccionar clave...</option>
<?php foreach ($claves_pedimentos as $clave): ?>
<option value="<?= htmlspecialchars($clave['codigo']) ?>">
<?= htmlspecialchars($clave['codigo']) ?> - <?= htmlspecialchars($clave['descripcion']) ?>
</option>
<?php endforeach; ?>
</select>
<?php if (empty($claves_pedimentos)): ?>
<div class="form-text text-warning">
<i class="fas fa-exclamation-triangle"></i>
No tiene claves configuradas.
<a href="/IMPORTADORES/claves_pedimentos/inicializar_claves_usuario">Inicializar claves por defecto</a>
</div>
<?php else: ?>
<div class="form-text">
Seleccione la clave que corresponda al tipo de importación
</div>
<?php endif; ?>
</div>
<div class="row">
<div class="col-md-6">
<div class="mb-3">
<label for="tipo_operacion" class="form-label">Tipo de Operación</label>
<select class="form-select" id="tipo_operacion" name="tipo_operacion">
<option value="1" selected>Importación Definitiva</option>
<option value="3">Importación Temporal</option>
</select>
</div>
</div>
<div class="col-md-6">
<div class="mb-3">
<label for="tipo_pedimento" class="form-label">Tipo de Pedimento</label>
<select class="form-select" id="tipo_pedimento" name="tipo_pedimento">
<option value="1" selected>Normal</option>
<option value="2">Consolidado</option>
<option value="3">Rectificación</option>
</select>
</div>
</div>
</div>
<div class="row">
<div class="col-md-6">
<div class="mb-3">
<label for="regimen" class="form-label">Régimen</label>
<input type="text" class="form-control" id="regimen" name="regimen"
placeholder="Ej: IMD" maxlength="10">
</div>
</div>
<div class="col-md-6">
<div class="mb-3">
<label for="destino" class="form-label">Destino</label>
<input type="text" class="form-control" id="destino" name="destino"
placeholder="Código de destino" maxlength="10">
</div>
</div>
</div>
</div>
</div>
</div>
<!-- Información del Importador (Automática) -->
<div class="col-md-6">
<div class="card mb-3 bg-light">
<div class="card-header">
<h5 class="mb-0"><i class="fas fa-building"></i> Información del Importador</h5>
</div>
<div class="card-body">
<?php if ($importador): ?>
<div class="mb-2">
<strong>RFC:</strong>
<span class="badge bg-primary"><?= htmlspecialchars($importador['rfc']) ?></span>
</div>
<div class="mb-2">
<strong>Nombre/Razón Social:</strong><br>
<span class="text-muted"><?= htmlspecialchars($importador['nombre']) ?></span>
</div>
<?php if (!empty($importador['correo'])): ?>
<div class="mb-2">
<strong>Correo:</strong><br>
<span class="text-muted"><?= htmlspecialchars($importador['correo']) ?></span>
</div>
<?php endif; ?>
<hr>
<div class="alert alert-success alert-sm">
<i class="fas fa-check-circle"></i>
Esta información se incluirá automáticamente en el pedimento
</div>
<?php else: ?>
<div class="alert alert-warning">
<i class="fas fa-exclamation-triangle"></i>
Configure su información general primero
</div>
<?php endif; ?>
</div>
</div>
<!-- Fechas -->
<div class="card mb-3">
<div class="card-header">
<h5 class="mb-0"><i class="fas fa-calendar"></i> Fechas</h5>
</div>
<div class="card-body">
<div class="mb-3">
<label for="fecha_pedimento" class="form-label">Fecha del Pedimento</label>
<input type="date" class="form-control" id="fecha_pedimento" name="fecha_pedimento"
value="<?= date('Y-m-d') ?>">
</div>
<div class="row">
<div class="col-md-6">
<div class="mb-3">
<label for="fecha_inicio" class="form-label">Fecha de Inicio</label>
<input type="date" class="form-control" id="fecha_inicio" name="fecha_inicio">
</div>
</div>
<div class="col-md-6">
<div class="mb-3">
<label for="fecha_final" class="form-label">Fecha Final</label>
<input type="date" class="form-control" id="fecha_final" name="fecha_final">
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<!-- Información Adicional -->
<div class="row">
<div class="col-md-12">
<div class="card mb-3">
<div class="card-header">
<h5 class="mb-0"><i class="fas fa-cogs"></i> Información Adicional</h5>
</div>
<div class="card-body">
<div class="row">
<div class="col-md-6">
<div class="mb-3">
<label for="archivo_final_previo" class="form-label">Archivo Final Previo</label>
<input type="text" class="form-control" id="archivo_final_previo" name="archivo_final_previo"
placeholder="Nombre del archivo" maxlength="100">
</div>
</div>
<div class="col-md-6">
<div class="mb-3">
<label for="acuse_cons" class="form-label">Acuse de Recibo</label>
<input type="text" class="form-control" id="acuse_cons" name="acuse_cons"
placeholder="Número de acuse" maxlength="20">
</div>
</div>
</div>
<div class="row">
<div class="col-md-6">
<div class="mb-3">
<label for="tipo" class="form-label">Tipo Adicional</label>
<input type="text" class="form-control" id="tipo" name="tipo"
placeholder="Información adicional" maxlength="10">
</div>
</div>
<div class="col-md-6">
<div class="mb-3">
<label for="status" class="form-label">Estado</label>
<select class="form-select" id="status" name="status">
<option value="1" selected>Activo</option>
<option value="0">Inactivo</option>
</select>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<!-- Botones -->
<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>
<button type="submit" class="btn btn-success btn-animated" <?= empty($claves_pedimentos) ? 'disabled' : '' ?>>
<i class="fas fa-save"></i> Guardar Pedimento
</button>
</div>
</div>
</div>
</form>
</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/sweetalert2@11"></script>
<script>
$(document).ready(function() {
// Validación del formulario
$('#formPedimento').on('submit', function(e) {
var pedimento = $('#pedimento').val().trim();
var clavePed = $('#clave_ped').val();
if (!pedimento) {
e.preventDefault();
Swal.fire({
icon: 'warning',
title: 'Campo obligatorio',
text: 'El número de pedimento es obligatorio',
confirmButtonColor: '#ffc107'
});
$('#pedimento').focus();
return false;
}
if (!clavePed) {
e.preventDefault();
Swal.fire({
icon: 'warning',
title: 'Campo obligatorio',
text: 'Debe seleccionar una clave de pedimento',
confirmButtonColor: '#ffc107'
});
$('#clave_ped').focus();
return false;
}
// Validación básica del formato de pedimento
var pedimentoLimpio = pedimento.replace(/\s+/g, '');
if (pedimentoLimpio.length < 13) {
e.preventDefault();
Swal.fire({
icon: 'error',
title: 'Formato inválido',
text: 'El número de pedimento debe tener al menos 13 dígitos',
confirmButtonColor: '#dc3545'
});
$('#pedimento').focus();
return false;
}
});
// Formateo automático del número de pedimento
$('#pedimento').on('input', function() {
var value = this.value.replace(/[^\d]/g, ''); // Solo números
var formatted = '';
if (value.length >= 2) {
formatted += value.substr(0, 2) + ' ';
if (value.length >= 4) {
formatted += value.substr(2, 2) + ' ';
if (value.length >= 8) {
formatted += value.substr(4, 4) + ' ';
if (value.length > 8) {
formatted += value.substr(8);
}
} else if (value.length > 4) {
formatted += value.substr(4);
}
} else if (value.length > 2) {
formatted += value.substr(2);
}
} else {
formatted = value;
}
this.value = formatted;
});
// Validación de fechas
$('#fecha_inicio, #fecha_final').on('change', function() {
var fechaInicio = $('#fecha_inicio').val();
var fechaFinal = $('#fecha_final').val();
if (fechaInicio && fechaFinal && fechaInicio > fechaFinal) {
Swal.fire({
icon: 'warning',
title: 'Fechas incorrectas',
text: 'La fecha de inicio no puede ser mayor que la fecha final',
confirmButtonColor: '#ffc107'
});
$(this).val('');
}
});
// Información adicional sobre la clave seleccionada
$('#clave_ped').on('change', function() {
var selectedText = $(this).find('option:selected').text();
if (selectedText && selectedText !== 'Seleccionar clave...') {
var descripcion = selectedText.split(' - ')[1];
if (descripcion) {
$(this).next('.form-text').html('<i class="fas fa-info-circle text-primary"></i> ' + descripcion);
}
} else {
$(this).next('.form-text').html('Seleccione la clave que corresponda al tipo de importación');
}
});
});
</script>
</body>
</html>

View File

@@ -0,0 +1,380 @@
<?php
include __DIR__ . '/../partials/sidebar_importador.php';
// Convertir fechas de formato int a date si están presentes
$fecha_pedimento_display = '';
$fecha_inicio_display = '';
$fecha_final_display = '';
if (!empty($previo['FechaPedimento'])) {
$fecha_str = (string)$previo['FechaPedimento'];
if (strlen($fecha_str) === 8) {
$fecha_pedimento_display = substr($fecha_str, 0, 4) . '-' . substr($fecha_str, 4, 2) . '-' . substr($fecha_str, 6, 2);
}
}
if (!empty($previo['FechaInicio'])) {
$fecha_str = (string)$previo['FechaInicio'];
if (strlen($fecha_str) === 8) {
$fecha_inicio_display = substr($fecha_str, 0, 4) . '-' . substr($fecha_str, 4, 2) . '-' . substr($fecha_str, 6, 2);
}
}
if (!empty($previo['FechaFinal'])) {
$fecha_str = (string)$previo['FechaFinal'];
if (strlen($fecha_str) === 8) {
$fecha_final_display = substr($fecha_str, 0, 4) . '-' . substr($fecha_str, 4, 2) . '-' . substr($fecha_str, 6, 2);
}
}
?>
<!DOCTYPE html>
<html lang="es">
<head>
<meta charset="UTF-8">
<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 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; }
}
.card { border-radius: 12px; }
/* Animaciones personalizadas */
.card-hover { transition: all 0.3s ease; }
.fade-in-up { animation: fadeInUp 0.8s ease-out; }
@keyframes fadeInUp {
from { opacity: 0; transform: translateY(30px); }
to { opacity: 1; transform: translateY(0); }
}
/* 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">
<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
</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">
<!-- 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 Básica</h5>
</div>
<div class="card-body">
<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
value="<?= htmlspecialchars($previo['Pedimento'] ?? '') ?>"
placeholder="Ej: 23 47 3807 8001234">
</div>
<div class="row">
<div class="col-md-6">
<div class="mb-3">
<label for="tipo_operacion" class="form-label">Tipo de Operación</label>
<select class="form-select" id="tipo_operacion" name="tipo_operacion">
<option value="">Seleccionar...</option>
<option value="1" <?= ($previo['TipoOperacion'] == 1) ? 'selected' : '' ?>>Importación Definitiva</option>
<option value="2" <?= ($previo['TipoOperacion'] == 2) ? 'selected' : '' ?>>Exportación Definitiva</option>
<option value="3" <?= ($previo['TipoOperacion'] == 3) ? 'selected' : '' ?>>Importación Temporal</option>
<option value="4" <?= ($previo['TipoOperacion'] == 4) ? 'selected' : '' ?>>Exportación Temporal</option>
<option value="5" <?= ($previo['TipoOperacion'] == 5) ? 'selected' : '' ?>>Tránsito</option>
</select>
</div>
</div>
<div class="col-md-6">
<div class="mb-3">
<label for="tipo_pedimento" class="form-label">Tipo de Pedimento</label>
<select class="form-select" id="tipo_pedimento" name="tipo_pedimento">
<option value="">Seleccionar...</option>
<option value="1" <?= ($previo['TipoPedimento'] == 1) ? 'selected' : '' ?>>Normal</option>
<option value="2" <?= ($previo['TipoPedimento'] == 2) ? 'selected' : '' ?>>Consolidado</option>
<option value="3" <?= ($previo['TipoPedimento'] == 3) ? 'selected' : '' ?>>Rectificación</option>
</select>
</div>
</div>
</div>
<!-- ...existing form fields... -->
<div class="row">
<div class="col-md-6">
<div class="mb-3">
<label for="clave_ped" class="form-label">Clave de Pedimento</label>
<input type="text" class="form-control" id="clave_ped" name="clave_ped"
value="<?= htmlspecialchars($previo['ClavePed'] ?? '') ?>"
placeholder="Ej: A1" maxlength="10">
</div>
</div>
<div class="col-md-6">
<div class="mb-3">
<label for="regimen" class="form-label">Régimen</label>
<input type="text" class="form-control" id="regimen" name="regimen"
value="<?= htmlspecialchars($previo['Regimen'] ?? '') ?>"
placeholder="Ej: IMD" maxlength="10">
</div>
</div>
</div>
<div class="mb-3">
<label for="destino" class="form-label">Destino</label>
<input type="text" class="form-control" id="destino" name="destino"
value="<?= htmlspecialchars($previo['Destino'] ?? '') ?>"
placeholder="Código de destino" maxlength="10">
</div>
</div>
</div>
</div>
<!-- Información del Cliente -->
<div class="col-md-6">
<div class="card mb-3">
<div class="card-header">
<h5 class="mb-0"><i class="fas fa-user"></i> Información del Cliente</h5>
</div>
<div class="card-body">
<div class="mb-3">
<label for="cliente_rfc" class="form-label">RFC del Cliente <span class="text-danger">*</span></label>
<input type="text" class="form-control" id="cliente_rfc" name="cliente_rfc" required
value="<?= htmlspecialchars($previo['ClienteRFC'] ?? '') ?>"
placeholder="Ej: ABC123456789" maxlength="20" style="text-transform: uppercase;">
</div>
<div class="mb-3">
<label for="cliente_nombre" class="form-label">Nombre/Razón Social del Cliente <span class="text-danger">*</span></label>
<textarea class="form-control" id="cliente_nombre" name="cliente_nombre" rows="3" required
placeholder="Nombre completo o razón social" maxlength="500"><?= htmlspecialchars($previo['ClienteNombre'] ?? '') ?></textarea>
</div>
</div>
</div>
<!-- Fechas -->
<div class="card mb-3">
<div class="card-header">
<h5 class="mb-0"><i class="fas fa-calendar"></i> Fechas</h5>
</div>
<div class="card-body">
<div class="mb-3">
<label for="fecha_pedimento" class="form-label">Fecha del Pedimento</label>
<input type="date" class="form-control" id="fecha_pedimento" name="fecha_pedimento"
value="<?= $fecha_pedimento_display ?>">
</div>
<div class="row">
<div class="col-md-6">
<div class="mb-3">
<label for="fecha_inicio" class="form-label">Fecha de Inicio</label>
<input type="date" class="form-control" id="fecha_inicio" name="fecha_inicio"
value="<?= $fecha_inicio_display ?>">
</div>
</div>
<div class="col-md-6">
<div class="mb-3">
<label for="fecha_final" class="form-label">Fecha Final</label>
<input type="date" class="form-control" id="fecha_final" name="fecha_final"
value="<?= $fecha_final_display ?>">
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<!-- Información Adicional -->
<div class="row">
<div class="col-md-12">
<div class="card mb-3">
<div class="card-header">
<h5 class="mb-0"><i class="fas fa-cogs"></i> Información Adicional</h5>
</div>
<div class="card-body">
<div class="row">
<div class="col-md-6">
<div class="mb-3">
<label for="archivo_final_previo" class="form-label">Archivo Final Previo</label>
<input type="text" class="form-control" id="archivo_final_previo" name="archivo_final_previo"
value="<?= htmlspecialchars($previo['ArchivoFinalPrevio'] ?? '') ?>"
placeholder="Nombre del archivo" maxlength="100">
</div>
</div>
<div class="col-md-6">
<div class="mb-3">
<label for="acuse_cons" class="form-label">Acuse de Recibo</label>
<input type="text" class="form-control" id="acuse_cons" name="acuse_cons"
value="<?= htmlspecialchars($previo['AcuseCons'] ?? '') ?>"
placeholder="Número de acuse" maxlength="20">
</div>
</div>
</div>
<div class="row">
<div class="col-md-6">
<div class="mb-3">
<label for="tipo" class="form-label">Tipo</label>
<input type="text" class="form-control" id="tipo" name="tipo"
value="<?= htmlspecialchars(trim($previo['Tipo'] ?? '')) ?>"
placeholder="Tipo de pedimento" maxlength="10">
</div>
</div>
<div class="col-md-6">
<div class="mb-3">
<label for="status" class="form-label">Estado</label>
<select class="form-select" id="status" name="status">
<option value="1" <?= ($previo['Status'] == 1) ? 'selected' : '' ?>>Activo</option>
<option value="0" <?= ($previo['Status'] == 0) ? 'selected' : '' ?>>Inactivo</option>
</select>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<!-- Información de Auditoría -->
<div class="row">
<div class="col-md-12">
<div class="card mb-3 bg-light">
<div class="card-header">
<h5 class="mb-0"><i class="fas fa-info"></i> Información de Registro</h5>
</div>
<div class="card-body">
<div class="row">
<div class="col-md-6">
<p class="mb-1"><strong>ID del Registro:</strong> <?= htmlspecialchars($previo['IdPrevio']) ?></p>
</div>
<div class="col-md-6">
<p class="mb-1"><strong>Fecha de Registro:</strong>
<?php
if ($previo['Timestamp'] instanceof DateTime) {
echo $previo['Timestamp']->format('d/m/Y H:i:s');
} else {
echo 'No disponible';
}
?>
</p>
</div>
</div>
</div>
</div>
</div>
</div>
<!-- Botones -->
<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>
<button type="submit" class="btn btn-primary btn-animated">
<i class="fas fa-save"></i> Actualizar Pedimento
</button>
</div>
</div>
</div>
</form>
</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/sweetalert2@11"></script>
<script>
$(document).ready(function() {
// Validación del formulario
$('#formPedimento').on('submit', function(e) {
var pedimento = $('#pedimento').val().trim();
var clienteRfc = $('#cliente_rfc').val().trim();
var clienteNombre = $('#cliente_nombre').val().trim();
if (!pedimento || !clienteRfc || !clienteNombre) {
e.preventDefault();
Swal.fire({
icon: 'warning',
title: 'Campos obligatorios',
text: 'Por favor complete todos los campos obligatorios (*)',
confirmButtonColor: '#ffc107'
});
return false;
}
// Validación básica de RFC
if (clienteRfc.length < 12) {
e.preventDefault();
Swal.fire({
icon: 'error',
title: 'RFC inválido',
text: 'El RFC debe tener al menos 12 caracteres',
confirmButtonColor: '#dc3545'
});
$('#cliente_rfc').focus();
return false;
}
});
// Convertir RFC a mayúsculas automáticamente
$('#cliente_rfc').on('input', function() {
this.value = this.value.toUpperCase();
});
// Validación de fechas
$('#fecha_inicio, #fecha_final').on('change', function() {
var fechaInicio = $('#fecha_inicio').val();
var fechaFinal = $('#fecha_final').val();
if (fechaInicio && fechaFinal && fechaInicio > fechaFinal) {
Swal.fire({
icon: 'warning',
title: 'Fechas incorrectas',
text: 'La fecha de inicio no puede ser mayor que la fecha final',
confirmButtonColor: '#ffc107'
});
$(this).val('');
}
});
});
</script>
</body>
</html>

View File

@@ -0,0 +1,166 @@
<?php include __DIR__ . '/../partials/sidebar_importador.php'; ?>
<!DOCTYPE html>
<html lang="es">
<head>
<meta charset="UTF-8">
<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 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; }
}
.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%; }
</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>
</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>
</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>
</button>
</div>
</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>
</div>
</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/sweetalert2@11"></script>
<script>
function proximamente() {
Swal.fire({
icon: 'info',
title: 'Próximamente',
text: 'Esta función estará disponible próximamente.',
confirmButtonColor: '#0d6efd'
});
}
</script>
</body>
</html>

View File

@@ -0,0 +1,194 @@
<?php include __DIR__ . '/../partials/sidebar_importador.php'; ?>
<!DOCTYPE html>
<html lang="es">
<head>
<meta charset="UTF-8">
<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>
<link href="https://cdn.datatables.net/1.13.4/css/jquery.dataTables.min.css" rel="stylesheet">
<!-- Font Awesome -->
<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>
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; }
}
.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%; }
</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
</a>
<a href="/IMPORTADORES/catalogo_pedimentos" class="btn btn-secondary btn-animated">
<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%">
<thead>
<tr>
<th>ID</th>
<th>Pedimento</th>
<th>RFC Cliente</th>
<th>Nombre Cliente</th>
<th>Fecha Registro</th>
<th>Estado</th>
<th>Acciones</th>
</tr>
</thead>
<tbody>
<!-- Los datos se cargan via AJAX -->
</tbody>
</table>
</div>
</div>
</div>
<!-- jQuery -->
<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.jsdelivr.net/npm/sweetalert2@11"></script>
<script>
$(document).ready(function() {
// Inicializar DataTable
$('#tablaPedimentos').DataTable({
"processing": true,
"serverSide": true,
"searchDelay": 500,
"deferRender": true,
"ajax": {
"url": "/IMPORTADORES/catalogo_pedimentos/ajax_lista",
"type": "GET"
},
"columns": [
{ "data": 0, "name": "IdPrevio" },
{ "data": 1, "name": "Pedimento" },
{ "data": 2, "name": "ClienteRFC" },
{ "data": 3, "name": "ClienteNombre" },
{ "data": 4, "name": "Timestamp" },
{
"data": 5,
"name": "Status",
"render": function(data, type, row) {
if (data === 'Activo') {
return '<span class="badge bg-success">Activo</span>';
} else {
return '<span class="badge bg-secondary">Inactivo</span>';
}
}
},
{
"data": null,
"orderable": false,
"searchable": false,
"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>
`;
}
}
],
"language": {
"url": "https://cdn.datatables.net/plug-ins/1.13.4/i18n/es-ES.json"
},
"responsive": true,
"order": [[4, "desc"]],
"pageLength": 25,
"lengthMenu": [[10, 25, 50, 100], [10, 25, 50, 100]]
});
});
function confirmarEliminar(id) {
Swal.fire({
title: '¿Eliminar pedimento?',
text: '¿Está seguro que desea eliminar este pedimento?',
icon: 'warning',
showCancelButton: true,
confirmButtonText: 'Sí, eliminar',
cancelButtonText: 'Cancelar',
confirmButtonColor: '#d33'
}).then(result => {
if (result.isConfirmed) {
window.location = `/IMPORTADORES/catalogo_pedimentos/eliminar?id=${id}`;
}
});
}
// Mostrar alertas de éxito/error
<?php if (isset($_GET['created'])): ?>
Swal.fire({ icon: 'success', title: 'Pedimento creado', text: 'El pedimento se creó correctamente.', confirmButtonColor: '#198754' });
<?php endif; ?>
<?php if (isset($_GET['updated'])): ?>
Swal.fire({ icon: 'success', title: 'Pedimento actualizado', text: 'Los datos fueron modificados correctamente.', confirmButtonColor: '#198754' });
<?php endif; ?>
<?php if (isset($_GET['deleted'])): ?>
Swal.fire({ icon: 'success', title: 'Pedimento eliminado', text: 'El pedimento fue eliminado correctamente.', confirmButtonColor: '#198754' });
<?php endif; ?>
</script>
</body>
</html>

View File

@@ -0,0 +1,230 @@
<?php include __DIR__ . '/../partials/sidebar_importador.php'; ?>
<!DOCTYPE html>
<html lang="es">
<head>
<meta charset="UTF-8">
<title>🔑 Nueva Clave de 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 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; }
}
.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); }
}
.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">
<div class="d-flex justify-content-between align-items-center mb-4">
<h4 class="animate__animated animate__fadeInDown title-glow">🔑 Nueva Clave de Pedimento</h4>
<a href="/IMPORTADORES/claves_pedimentos/lista" class="btn btn-secondary btn-animated">
<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/claves_pedimentos/guardar" method="POST" id="formClave">
<div class="row">
<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 de la Clave</h5>
</div>
<div class="card-body">
<div class="mb-3">
<label for="codigo" class="form-label">Código de la Clave <span class="text-danger">*</span></label>
<input type="text" class="form-control" id="codigo" name="codigo" required
placeholder="Ej: A1, B3, G1" maxlength="10" style="text-transform: uppercase;">
<div class="form-text">Máximo 10 caracteres. Se convertirá automáticamente a mayúsculas.</div>
</div>
<div class="mb-3">
<label for="descripcion" class="form-label">Descripción <span class="text-danger">*</span></label>
<textarea class="form-control" id="descripcion" name="descripcion" rows="4" required
placeholder="Descripción detallada de la clave de pedimento" maxlength="255"></textarea>
<div class="form-text">Describe claramente el tipo de operación que representa esta clave.</div>
</div>
<div class="mb-3">
<label for="tipo_operacion" class="form-label">Tipo de Operación</label>
<select class="form-select" id="tipo_operacion" name="tipo_operacion">
<option value="importacion" selected>Importación</option>
<option value="exportacion">Exportación</option>
<option value="transito">Tránsito</option>
<option value="otros">Otros</option>
</select>
</div>
<div class="mb-3">
<div class="form-check form-switch">
<input class="form-check-input" type="checkbox" id="activo" name="activo" checked>
<label class="form-check-label" for="activo">
<i class="fas fa-toggle-on text-success"></i> Clave activa
</label>
<div class="form-text">Las claves inactivas no aparecerán en los formularios de pedimentos.</div>
</div>
</div>
</div>
</div>
</div>
<div class="col-md-6">
<div class="card mb-3 bg-light">
<div class="card-header">
<h5 class="mb-0"><i class="fas fa-lightbulb"></i> Ejemplos de Claves Comunes</h5>
</div>
<div class="card-body">
<div class="table-responsive">
<table class="table table-sm">
<thead>
<tr>
<th>Código</th>
<th>Descripción</th>
</tr>
</thead>
<tbody>
<tr><td><strong>A1</strong></td><td>Importación definitiva de mercancías</td></tr>
<tr><td><strong>A3</strong></td><td>Importación definitiva de vehículos usados</td></tr>
<tr><td><strong>A4</strong></td><td>Importación definitiva de vehículos nuevos</td></tr>
<tr><td><strong>B1</strong></td><td>Importación temporal para elaborar</td></tr>
<tr><td><strong>C1</strong></td><td>Importación definitiva de mercancías donadas</td></tr>
<tr><td><strong>G1</strong></td><td>Importación con Programa IMMEX</td></tr>
<tr><td><strong>I1</strong></td><td>Importación definitiva exenta</td></tr>
<tr><td><strong>L1</strong></td><td>Importación con TLC</td></tr>
</tbody>
</table>
</div>
<div class="alert alert-info mt-3">
<i class="fas fa-info-circle"></i>
<strong>Consejo:</strong> Puedes crear tus propias claves personalizadas o usar las estándar del SAT.
</div>
</div>
</div>
</div>
</div>
<!-- Botones -->
<div class="row">
<div class="col-md-12">
<div class="d-flex justify-content-end gap-2">
<a href="/IMPORTADORES/claves_pedimentos/lista" class="btn btn-secondary btn-animated">
<i class="fas fa-times"></i> Cancelar
</a>
<button type="submit" class="btn btn-success btn-animated">
<i class="fas fa-save"></i> Guardar Clave
</button>
</div>
</div>
</div>
</form>
</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/sweetalert2@11"></script>
<script>
$(document).ready(function() {
// Validación del formulario
$('#formClave').on('submit', function(e) {
var codigo = $('#codigo').val().trim();
var descripcion = $('#descripcion').val().trim();
if (!codigo || !descripcion) {
e.preventDefault();
Swal.fire({
icon: 'warning',
title: 'Campos obligatorios',
text: 'Por favor complete todos los campos obligatorios (*)',
confirmButtonColor: '#ffc107'
});
return false;
}
// Validación del código
if (codigo.length > 10) {
e.preventDefault();
Swal.fire({
icon: 'error',
title: 'Código muy largo',
text: 'El código no puede tener más de 10 caracteres',
confirmButtonColor: '#dc3545'
});
$('#codigo').focus();
return false;
}
// Validar que el código no tenga caracteres especiales
var regex = /^[A-Z0-9]+$/;
if (!regex.test(codigo)) {
e.preventDefault();
Swal.fire({
icon: 'error',
title: 'Código inválido',
text: 'El código solo puede contener letras y números',
confirmButtonColor: '#dc3545'
});
$('#codigo').focus();
return false;
}
});
// Convertir código a mayúsculas automáticamente
$('#codigo').on('input', function() {
this.value = this.value.toUpperCase();
});
// Contador de caracteres para descripción
$('#descripcion').on('input', function() {
var current = $(this).val().length;
var max = 255;
var remaining = max - current;
if (remaining < 20) {
$(this).next('.form-text').text(`Caracteres restantes: ${remaining}`).addClass('text-warning');
} else {
$(this).next('.form-text').text('Describe claramente el tipo de operación que representa esta clave.').removeClass('text-warning');
}
});
});
</script>
</body>
</html>

View File

@@ -0,0 +1,280 @@
<?php
include __DIR__ . '/../partials/sidebar_importador.php';
?>
<!DOCTYPE html>
<html lang="es">
<head>
<meta charset="UTF-8">
<title>🔑 Editar Clave de 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 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; }
}
.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); }
}
.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">
<div class="d-flex justify-content-between align-items-center mb-4">
<h4 class="animate__animated animate__fadeInDown title-glow">🔑 Editar Clave #<?= htmlspecialchars($clave['id_clave_pedimento']) ?></h4>
<a href="/IMPORTADORES/claves_pedimentos/lista" class="btn btn-secondary btn-animated">
<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/claves_pedimentos/actualizar" method="POST" id="formClave">
<input type="hidden" name="id_clave_pedimento" value="<?= htmlspecialchars($clave['id_clave_pedimento']) ?>">
<div class="row">
<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 de la Clave</h5>
</div>
<div class="card-body">
<div class="mb-3">
<label for="codigo" class="form-label">Código de la Clave <span class="text-danger">*</span></label>
<input type="text" class="form-control" id="codigo" name="codigo" required
value="<?= htmlspecialchars($clave['codigo'] ?? '') ?>"
placeholder="Ej: A1, B3, G1" maxlength="10" style="text-transform: uppercase;">
<div class="form-text">Máximo 10 caracteres. Se convertirá automáticamente a mayúsculas.</div>
</div>
<div class="mb-3">
<label for="descripcion" class="form-label">Descripción <span class="text-danger">*</span></label>
<textarea class="form-control" id="descripcion" name="descripcion" rows="4" required
placeholder="Descripción detallada de la clave de pedimento" maxlength="255"><?= htmlspecialchars($clave['descripcion'] ?? '') ?></textarea>
<div class="form-text">Describe claramente el tipo de operación que representa esta clave.</div>
</div>
<div class="mb-3">
<label for="tipo_operacion" class="form-label">Tipo de Operación</label>
<select class="form-select" id="tipo_operacion" name="tipo_operacion">
<option value="importacion" <?= ($clave['tipo_operacion'] === 'importacion') ? 'selected' : '' ?>>Importación</option>
<option value="exportacion" <?= ($clave['tipo_operacion'] === 'exportacion') ? 'selected' : '' ?>>Exportación</option>
<option value="transito" <?= ($clave['tipo_operacion'] === 'transito') ? 'selected' : '' ?>>Tránsito</option>
<option value="otros" <?= ($clave['tipo_operacion'] === 'otros') ? 'selected' : '' ?>>Otros</option>
</select>
</div>
<div class="mb-3">
<div class="form-check form-switch">
<input class="form-check-input" type="checkbox" id="activo" name="activo"
<?= ($clave['activo'] == 1) ? 'checked' : '' ?>>
<label class="form-check-label" for="activo">
<i class="fas fa-toggle-on text-success"></i> Clave activa
</label>
<div class="form-text">Las claves inactivas no aparecerán en los formularios de pedimentos.</div>
</div>
</div>
</div>
</div>
</div>
<div class="col-md-6">
<!-- Información de Auditoría -->
<div class="card mb-3 bg-light">
<div class="card-header">
<h5 class="mb-0"><i class="fas fa-info"></i> Información del Registro</h5>
</div>
<div class="card-body">
<div class="row">
<div class="col-md-6">
<p class="mb-1"><strong>ID:</strong> <?= htmlspecialchars($clave['id_clave_pedimento']) ?></p>
</div>
<div class="col-md-6">
<p class="mb-1"><strong>Código Actual:</strong>
<span class="badge bg-primary"><?= htmlspecialchars($clave['codigo']) ?></span>
</p>
</div>
</div>
<div class="row">
<div class="col-md-6">
<p class="mb-1"><strong>Fecha Creación:</strong>
<?php
if ($clave['fecha_creacion'] instanceof DateTime) {
echo $clave['fecha_creacion']->format('d/m/Y H:i:s');
} else {
echo 'No disponible';
}
?>
</p>
</div>
<div class="col-md-6">
<p class="mb-1"><strong>Última Modificación:</strong>
<?php
if ($clave['fecha_modificacion'] instanceof DateTime) {
echo $clave['fecha_modificacion']->format('d/m/Y H:i:s');
} else {
echo 'No disponible';
}
?>
</p>
</div>
</div>
<div class="row">
<div class="col-md-12">
<p class="mb-1"><strong>Estado:</strong>
<?php if ($clave['activo'] == 1): ?>
<span class="badge bg-success">Activo</span>
<?php else: ?>
<span class="badge bg-secondary">Inactivo</span>
<?php endif; ?>
</p>
</div>
</div>
</div>
</div>
<!-- Referencias -->
<div class="card mb-3 bg-info text-white">
<div class="card-header">
<h5 class="mb-0"><i class="fas fa-lightbulb"></i> Ejemplos de Claves Comunes</h5>
</div>
<div class="card-body">
<div class="row">
<div class="col-6">
<small><strong>A1:</strong> Importación definitiva</small><br>
<small><strong>A3:</strong> Vehículos usados</small><br>
<small><strong>B1:</strong> Temporal elaborar</small><br>
<small><strong>G1:</strong> Programa IMMEX</small>
</div>
<div class="col-6">
<small><strong>I1:</strong> Definitiva exenta</small><br>
<small><strong>L1:</strong> Con TLC</small><br>
<small><strong>C1:</strong> Mercancías donadas</small><br>
<small><strong>J1:</strong> Temporal reexportación</small>
</div>
</div>
</div>
</div>
</div>
</div>
<!-- Botones -->
<div class="row">
<div class="col-md-12">
<div class="d-flex justify-content-end gap-2">
<a href="/IMPORTADORES/claves_pedimentos/lista" class="btn btn-secondary btn-animated">
<i class="fas fa-times"></i> Cancelar
</a>
<button type="submit" class="btn btn-primary btn-animated">
<i class="fas fa-save"></i> Actualizar Clave
</button>
</div>
</div>
</div>
</form>
</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/sweetalert2@11"></script>
<script>
$(document).ready(function() {
// Validación del formulario
$('#formClave').on('submit', function(e) {
var codigo = $('#codigo').val().trim();
var descripcion = $('#descripcion').val().trim();
if (!codigo || !descripcion) {
e.preventDefault();
Swal.fire({
icon: 'warning',
title: 'Campos obligatorios',
text: 'Por favor complete todos los campos obligatorios (*)',
confirmButtonColor: '#ffc107'
});
return false;
}
// Validación del código
if (codigo.length > 10) {
e.preventDefault();
Swal.fire({
icon: 'error',
title: 'Código muy largo',
text: 'El código no puede tener más de 10 caracteres',
confirmButtonColor: '#dc3545'
});
$('#codigo').focus();
return false;
}
// Validar que el código no tenga caracteres especiales
var regex = /^[A-Z0-9]+$/;
if (!regex.test(codigo)) {
e.preventDefault();
Swal.fire({
icon: 'error',
title: 'Código inválido',
text: 'El código solo puede contener letras y números',
confirmButtonColor: '#dc3545'
});
$('#codigo').focus();
return false;
}
});
// Convertir código a mayúsculas automáticamente
$('#codigo').on('input', function() {
this.value = this.value.toUpperCase();
});
// Contador de caracteres para descripción
$('#descripcion').on('input', function() {
var current = $(this).val().length;
var max = 255;
var remaining = max - current;
if (remaining < 20) {
$(this).next('.form-text').text(`Caracteres restantes: ${remaining}`).addClass('text-warning');
} else {
$(this).next('.form-text').text('Describe claramente el tipo de operación que representa esta clave.').removeClass('text-warning');
}
});
});
</script>
</body>
</html>

View File

@@ -0,0 +1,226 @@
<?php include __DIR__ . '/../partials/sidebar_importador.php'; ?>
<!DOCTYPE html>
<html lang="es">
<head>
<meta charset="UTF-8">
<title>📤 Importar Claves CSV</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 rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0/css/all.min.css">
<style>
body { font-family: 'Segoe UI', sans-serif; background-color: #f4f6f9; }
.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; } }
.card { border-radius: 12px; }
.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); }
.file-drop-area {
border: 2px dashed #007bff;
border-radius: 10px;
padding: 40px;
text-align: center;
background-color: #f8f9ff;
transition: all 0.3s ease;
cursor: pointer;
}
.file-drop-area:hover {
border-color: #0056b3;
background-color: #e7f1ff;
}
.file-drop-area.dragover {
border-color: #28a745;
background-color: #d4edda;
}
.file-info {
background-color: #e9ecef;
border-radius: 8px;
padding: 15px;
margin-top: 15px;
}
</style>
</head>
<body>
<div class="content">
<div class="d-flex justify-content-between align-items-center mb-4">
<h4><i class="fas fa-upload text-primary"></i> Importar Claves de Pedimentos por CSV</h4>
<div>
<a href="/IMPORTADORES/public/downloads/claves_pedimentos_template.csv" class="btn btn-outline-info btn-animated me-2" download>
<i class="fas fa-download"></i> Descargar Plantilla
</a>
<a href="/IMPORTADORES/claves_pedimentos/lista" class="btn btn-secondary btn-animated">
<i class="fas fa-arrow-left"></i> Regresar
</a>
</div>
</div>
<div class="row">
<div class="col-md-8">
<div class="card shadow-sm">
<div class="card-header bg-primary text-white">
<h5 class="mb-0"><i class="fas fa-file-csv"></i> Cargar Archivo CSV</h5>
</div>
<div class="card-body">
<form action="/IMPORTADORES/claves_pedimentos/procesar_csv" method="POST" enctype="multipart/form-data" id="csvForm">
<div class="file-drop-area" id="fileDropArea">
<i class="fas fa-cloud-upload-alt fa-3x text-primary mb-3"></i>
<h5>Arrastra tu archivo CSV aquí</h5>
<p class="text-muted">o haz clic para seleccionar archivo</p>
<input type="file" name="csv_file" id="csvFile" accept=".csv" style="display: none;" required>
</div>
<div id="fileInfo" class="file-info" style="display: none;">
<h6><i class="fas fa-file-check text-success"></i> Archivo seleccionado:</h6>
<p id="fileName" class="mb-1"></p>
<small id="fileSize" class="text-muted"></small>
</div>
<div class="mt-4">
<h6><i class="fas fa-cogs"></i> Opciones de importación:</h6>
<div class="form-check">
<input class="form-check-input" type="checkbox" name="omitir_duplicados" id="omitirDuplicados" checked>
<label class="form-check-label" for="omitirDuplicados">
Omitir códigos duplicados (no reemplazar los existentes)
</label>
</div>
<div class="form-check">
<input class="form-check-input" type="checkbox" name="activar_todas" id="activarTodas" checked>
<label class="form-check-label" for="activarTodas">
Activar todas las claves importadas
</label>
</div>
</div>
<div class="mt-4 text-center">
<button type="submit" class="btn btn-success btn-lg btn-animated" id="submitBtn" disabled>
<i class="fas fa-upload"></i> Procesar Archivo CSV
</button>
</div>
</form>
</div>
</div>
</div>
<div class="col-md-4">
<div class="card shadow-sm">
<div class="card-header bg-info text-white">
<h5 class="mb-0"><i class="fas fa-info-circle"></i> Instrucciones</h5>
</div>
<div class="card-body">
<h6>📋 Formato del archivo CSV:</h6>
<ul class="small">
<li><strong>Separador:</strong> Coma (,)</li>
<li><strong>Codificación:</strong> UTF-8</li>
<li><strong>Encabezados:</strong> Obligatorios</li>
</ul>
<h6>🏷️ Columnas requeridas:</h6>
<ul class="small">
<li><strong>codigo:</strong> Código de la clave (ej: A1, B2)</li>
<li><strong>descripcion:</strong> Descripción detallada</li>
<li><strong>tipo_operacion:</strong> importacion o exportacion</li>
<li><strong>activo:</strong> 1 (activo) o 0 (inactivo)</li>
</ul>
<div class="alert alert-warning small mt-3">
<i class="fas fa-exclamation-triangle"></i>
<strong>Importante:</strong> Los códigos deben ser únicos. Si existe un código duplicado, se omitirá según la configuración seleccionada.
</div>
<div class="mt-3">
<a href="/IMPORTADORES/public/downloads/claves_pedimentos_template.csv" class="btn btn-sm btn-outline-primary w-100" download>
<i class="fas fa-download"></i> Descargar plantilla de ejemplo
</a>
</div>
</div>
</div>
</div>
</div>
</div>
<script src="https://cdn.jsdelivr.net/npm/sweetalert2@11"></script>
<script>
const fileDropArea = document.getElementById('fileDropArea');
const csvFile = document.getElementById('csvFile');
const fileInfo = document.getElementById('fileInfo');
const fileName = document.getElementById('fileName');
const fileSize = document.getElementById('fileSize');
const submitBtn = document.getElementById('submitBtn');
// Eventos de drag & drop
fileDropArea.addEventListener('click', () => csvFile.click());
fileDropArea.addEventListener('dragover', handleDragOver);
fileDropArea.addEventListener('dragleave', handleDragLeave);
fileDropArea.addEventListener('drop', handleDrop);
csvFile.addEventListener('change', handleFileSelect);
function handleDragOver(e) {
e.preventDefault();
fileDropArea.classList.add('dragover');
}
function handleDragLeave(e) {
e.preventDefault();
fileDropArea.classList.remove('dragover');
}
function handleDrop(e) {
e.preventDefault();
fileDropArea.classList.remove('dragover');
const files = e.dataTransfer.files;
if (files.length > 0) {
csvFile.files = files;
handleFileSelect();
}
}
function handleFileSelect() {
const file = csvFile.files[0];
if (file) {
// Validar que sea CSV
if (!file.name.toLowerCase().endsWith('.csv')) {
Swal.fire({
icon: 'error',
title: 'Archivo inválido',
text: 'Por favor selecciona un archivo CSV válido.'
});
csvFile.value = '';
return;
}
// Mostrar información del archivo
fileName.textContent = file.name;
fileSize.textContent = `Tamaño: ${(file.size / 1024).toFixed(1)} KB`;
fileInfo.style.display = 'block';
submitBtn.disabled = false;
}
}
// Validar formulario antes de enviar
document.getElementById('csvForm').addEventListener('submit', function(e) {
const file = csvFile.files[0];
if (!file) {
e.preventDefault();
Swal.fire({
icon: 'warning',
title: 'Archivo requerido',
text: 'Por favor selecciona un archivo CSV.'
});
return;
}
// Mostrar indicador de carga
submitBtn.innerHTML = '<i class="fas fa-spinner fa-spin"></i> Procesando...';
submitBtn.disabled = true;
});
</script>
</body>
</html>

View File

@@ -0,0 +1,170 @@
<?php include __DIR__ . '/../partials/sidebar_importador.php'; ?>
<!DOCTYPE html>
<html lang="es">
<head>
<meta charset="UTF-8">
<title>🔑 Claves 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 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; }
}
.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%; }
</style>
</head>
<body>
<div class="content">
<h4 class="mb-4 animate__animated animate__fadeInDown title-glow">🔑 Configuración de Claves 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">Mis Claves</h5>
<p class="card-text">Ver y gestionar tus claves de pedimentos configuradas</p>
</div>
<div class="align-self-center">
<i class="fas fa-list fa-2x"></i>
</div>
</div>
<a href="/IMPORTADORES/claves_pedimentos/lista" class="btn btn-outline-light btn-sm btn-animated">
Ver Lista <i class="fas fa-arrow-right"></i>
</a>
</div>
</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">Nueva Clave</h5>
<p class="card-text">Agregar una nueva clave de pedimento personalizada</p>
</div>
<div class="align-self-center">
<i class="fas fa-plus-circle fa-2x"></i>
</div>
</div>
<a href="/IMPORTADORES/claves_pedimentos/crear" class="btn btn-outline-light btn-sm btn-animated">
Crear Nueva <i class="fas fa-plus"></i>
</a>
</div>
</div>
</div>
<div class="col-md-6 col-lg-4 mb-3">
<div class="card bg-warning text-white card-hover fade-in-up">
<div class="card-body">
<div class="d-flex justify-content-between">
<div>
<h5 class="card-title">Inicializar</h5>
<p class="card-text">Cargar claves de pedimentos por defecto</p>
</div>
<div class="align-self-center">
<i class="fas fa-download fa-2x"></i>
</div>
</div>
<a href="/IMPORTADORES/claves_pedimentos/inicializar_claves_usuario"
class="btn btn-outline-light btn-sm btn-animated"
onclick="return confirm('¿Desea cargar las claves de pedimentos por defecto? Solo funciona si no tiene claves configuradas.')">
Inicializar <i class="fas fa-magic"></i>
</a>
</div>
</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">
Las claves de pedimentos son códigos que identifican el tipo de operación de comercio exterior.
Este módulo te permite personalizar y gestionar las claves que utilizas frecuentemente.
</p>
<div class="row">
<div class="col-md-6">
<h6><i class="fas fa-check text-success"></i> Funcionalidades:</h6>
<ul class="list-unstyled">
<li><i class="fas fa-dot-circle text-primary"></i> Crear claves personalizadas</li>
<li><i class="fas fa-dot-circle text-primary"></i> Modificar claves existentes</li>
<li><i class="fas fa-dot-circle text-primary"></i> Activar/desactivar claves</li>
<li><i class="fas fa-dot-circle text-primary"></i> Eliminar claves no utilizadas</li>
</ul>
</div>
<div class="col-md-6">
<h6><i class="fas fa-lightbulb text-warning"></i> Claves Comunes de Importación:</h6>
<ul class="list-unstyled">
<li><i class="fas fa-dot-circle text-info"></i> <strong>A1:</strong> Importación definitiva</li>
<li><i class="fas fa-dot-circle text-info"></i> <strong>B1:</strong> Importación temporal</li>
<li><i class="fas fa-dot-circle text-info"></i> <strong>G1:</strong> Programa IMMEX</li>
<li><i class="fas fa-dot-circle text-info"></i> <strong>L1:</strong> TLC (Tratados)</li>
</ul>
</div>
</div>
</div>
</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/sweetalert2@11"></script>
</body>
</html>

View File

@@ -0,0 +1,204 @@
<?php include __DIR__ . '/../partials/sidebar_importador.php'; ?>
<!DOCTYPE html>
<html lang="es">
<head>
<meta charset="UTF-8">
<title>🔑 Lista de Claves 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 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>
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; }
}
.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; }
.fade-in-up { animation: fadeInUp 0.8s ease-out; }
@keyframes fadeInUp {
from { opacity: 0; transform: translateY(30px); }
to { opacity: 1; transform: translateY(0); }
}
.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">
<div class="d-flex justify-content-between align-items-center mb-4">
<h4 class="animate__animated animate__fadeInDown title-glow">🔑 Mis Claves de Pedimentos</h4>
<div>
<a href="/IMPORTADORES/claves_pedimentos/crear" class="btn btn-success btn-animated me-2">
<i class="fas fa-plus"></i> Nueva Clave
</a>
<a href="/IMPORTADORES/claves_pedimentos/importar_csv" class="btn btn-info btn-animated me-2">
<i class="fas fa-upload"></i> Importar CSV
</a>
<a href="/IMPORTADORES/claves_pedimentos" class="btn btn-secondary btn-animated">
<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="tablaClaves" class="display nowrap" style="width:100%">
<thead>
<tr>
<th>ID</th>
<th>Código</th>
<th>Descripción</th>
<th>Tipo</th>
<th>Estado</th>
<th>Fecha Creación</th>
<th>Acciones</th>
</tr>
</thead>
<tbody>
<!-- Los datos se cargan via AJAX -->
</tbody>
</table>
</div>
</div>
</div>
<!-- jQuery -->
<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.jsdelivr.net/npm/sweetalert2@11"></script>
<script>
$(document).ready(function() {
// Inicializar DataTable
$('#tablaClaves').DataTable({
"processing": true,
"serverSide": true,
"searchDelay": 500,
"deferRender": true,
"ajax": {
"url": "/IMPORTADORES/claves_pedimentos/ajax_lista",
"type": "GET"
},
"columns": [
{ "data": 0, "name": "id_clave_pedimento" },
{ "data": 1, "name": "codigo" },
{ "data": 2, "name": "descripcion" },
{ "data": 3, "name": "tipo_operacion" },
{
"data": 4,
"name": "activo",
"render": function(data, type, row) {
if (data === 'Activo') {
return '<span class="badge bg-success">Activo</span>';
} else {
return '<span class="badge bg-secondary">Inactivo</span>';
}
}
},
{ "data": 5, "name": "fecha_creacion" },
{
"data": null,
"orderable": false,
"searchable": false,
"render": function(data, type, row) {
const id = row[0];
return `
<a href="/IMPORTADORES/claves_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>
`;
}
}
],
"language": {
"url": "https://cdn.datatables.net/plug-ins/1.13.4/i18n/es-ES.json"
},
"responsive": true,
"order": [[1, "asc"]], // Ordenar por código
"pageLength": 25,
"lengthMenu": [[10, 25, 50, 100], [10, 25, 50, 100]]
});
});
function confirmarEliminar(id) {
Swal.fire({
title: '¿Eliminar clave de pedimento?',
text: '¿Está seguro que desea eliminar esta clave de pedimento?',
icon: 'warning',
showCancelButton: true,
confirmButtonText: 'Sí, eliminar',
cancelButtonText: 'Cancelar',
confirmButtonColor: '#d33'
}).then(result => {
if (result.isConfirmed) {
window.location = `/IMPORTADORES/claves_pedimentos/eliminar?id=${id}`;
}
});
}
// Mostrar alertas de éxito/error
<?php if (isset($_GET['created'])): ?>
<?php if ($_GET['created'] === 'ok'): ?>
Swal.fire({ icon: 'success', title: 'Clave creada', text: 'La clave de pedimento se creó correctamente.', confirmButtonColor: '#198754' });
<?php elseif ($_GET['created'] === 'initialized'): ?>
Swal.fire({ icon: 'success', title: 'Claves inicializadas', text: 'Se cargaron las claves de pedimento por defecto.', confirmButtonColor: '#198754' });
<?php endif; ?>
<?php endif; ?>
<?php if (isset($_GET['updated'])): ?>
Swal.fire({ icon: 'success', title: 'Clave actualizada', text: 'Los datos fueron modificados correctamente.', confirmButtonColor: '#198754' });
<?php endif; ?>
<?php if (isset($_GET['deleted'])): ?>
Swal.fire({ icon: 'success', title: 'Clave eliminada', text: 'La clave de pedimento fue eliminada correctamente.', confirmButtonColor: '#198754' });
<?php endif; ?>
<?php if (isset($_GET['info']) && $_GET['info'] === 'already_initialized'): ?>
Swal.fire({ icon: 'info', title: 'Ya inicializado', text: 'Ya tienes claves de pedimentos configuradas.', confirmButtonColor: '#0d6efd' });
<?php endif; ?>
<?php if (isset($_GET['imported']) && $_GET['imported'] === 'ok' && isset($_GET['message'])): ?>
Swal.fire({
icon: 'success',
title: 'Importación completada',
html: '<?= urldecode($_GET['message']) ?>',
confirmButtonColor: '#198754',
width: 600
});
<?php endif; ?>
</script>
</body>
</html>

View File

@@ -15,12 +15,12 @@ if (!$stmt || !sqlsrv_execute($stmt)) {
}
$config = sqlsrv_fetch_array($stmt, SQLSRV_FETCH_ASSOC);
// Variables con valores por defecto
$nombre = $config['nombre_plataforma'] ?? 'Sistema Integral para Importadores de Hidrocarburos';
$siglas = $config['siglas'] ?? 'SIIH';
$logo = $config['logo_url'] ?? 'assets/img/logo_siih.png';
$color1 = explode(',', $config['colores_primarios'] ?? '#003366,#0055A5')[0];
$color2 = explode(',', $config['colores_primarios'] ?? '#003366,#0055A5')[1];
// Variables con valores por defecto - Branding más sutil
$nombre = $config['nombre_plataforma'] ?? 'Sistema de Gestión de Importaciones';
$siglas = $config['siglas'] ?? 'AduanaSoft';
$logo = $config['logo_url'] ?? 'assets/img/logo_aduanasoft.png';
$color1 = explode(',', $config['colores_primarios'] ?? '#0f172a,#334155')[0]; // Negro suave
$color2 = explode(',', $config['colores_primarios'] ?? '#0f172a,#334155')[1]; // Gris slate
$mantenimiento = $config['modo_mantenimiento'] ?? 0;
$mensajeMantenimiento = $config['mensaje_mantenimiento'] ?? 'El sistema se encuentra en mantenimiento temporal. Por favor, vuelve más tarde.';
?>
@@ -29,23 +29,288 @@ $mensajeMantenimiento = $config['mensaje_mantenimiento'] ?? 'El sistema se encue
<html lang="es">
<head>
<meta charset="UTF-8">
<title><?= htmlspecialchars($siglas) ?> | Inicio</title>
<title><?= htmlspecialchars($siglas) ?> | Gestión de Importaciones</title>
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta name="description" content="Plataforma integral para la gestión de importaciones, trámites aduaneros y control de mercancías">
<!-- Bootstrap -->
<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>
<!-- Iconos -->
<link href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.0/css/all.min.css" rel="stylesheet">
<!-- Google Fonts -->
<link href="https://fonts.googleapis.com/css2?family=Poppins:wght@300;400;500;600;700&display=swap" rel="stylesheet">
<style>
body { background-color: #f4f6f9; font-family: 'Segoe UI', sans-serif; }
.navbar { background-color: #fff; }
.navbar .nav-link, .navbar-brand { color:<?= $color1 ?> !important; font-weight: 500; }
.navbar .nav-link.active { background-color: #F9F9F9; border-radius: 5px; }
.logo-siih { height: 90px; margin-right: 10px; }
.hero { background: linear-gradient(to right, <?= $color1 ?>, <?= $color2 ?>); color: white; padding: 80px 20px; text-align: center; }
.features { background-color: white; padding: 50px 0; }
.features i { font-size: 40px; color: <?= $color1 ?>; margin-bottom: 10px; }
footer { background-color: #e9ecef; padding: 20px; text-align: center; font-size: 14px; color: #666; }
:root {
--primary: <?= $color1 ?>;
--secondary: <?= $color2 ?>;
--accent: #10b981;
--warning: #f59e0b;
--surface: #ffffff;
--background: #f8fafc;
--text: #1e293b;
--text-muted: #64748b;
--border: #e2e8f0;
}
* {
box-sizing: border-box;
}
body {
background-color: var(--background);
font-family: 'Poppins', sans-serif;
color: var(--text);
line-height: 1.7;
margin: 0;
font-weight: 400;
}
.navbar {
background: rgba(255, 255, 255, 0.95);
backdrop-filter: blur(20px);
border-bottom: 1px solid var(--border);
padding: 1rem 0;
position: sticky;
top: 0;
z-index: 1000;
}
.navbar-brand {
font-weight: 600;
font-size: 1.25rem;
color: var(--primary) !important;
}
.nav-link {
color: var(--text-muted) !important;
font-weight: 500;
padding: 0.5rem 1rem !important;
border-radius: 8px;
transition: all 0.2s ease;
}
.nav-link:hover, .nav-link.active {
color: var(--primary) !important;
background-color: #f1f5f9;
}
.hero {
background: linear-gradient(135deg, var(--primary) 0%, var(--secondary) 100%);
min-height: 90vh;
display: flex;
align-items: center;
position: relative;
overflow: hidden;
}
.hero::before {
content: '';
position: absolute;
width: 200%;
height: 200%;
background: url('data:image/svg+xml,<svg xmlns="http://www.w3.org/2000/svg" width="60" height="60" viewBox="0 0 60 60"><g fill="none" stroke="rgba(255,255,255,0.1)" stroke-width="1"><circle cx="30" cy="30" r="1"/></g></svg>') repeat;
animation: float 20s ease-in-out infinite;
}
@keyframes float {
0%, 100% { transform: translate(0, 0) rotate(0deg); }
50% { transform: translate(-20px, -20px) rotate(180deg); }
}
.hero-content {
position: relative;
z-index: 2;
}
.hero h1 {
font-size: clamp(2.5rem, 5vw, 4rem);
font-weight: 700;
margin-bottom: 1.5rem;
color: white;
line-height: 1.2;
}
.hero .lead {
font-size: 1.2rem;
color: rgba(255, 255, 255, 0.9);
margin-bottom: 3rem;
max-width: 600px;
}
.btn-modern {
padding: 12px 28px;
border-radius: 12px;
font-weight: 600;
text-decoration: none;
display: inline-flex;
align-items: center;
gap: 8px;
transition: all 0.3s ease;
border: none;
cursor: pointer;
}
.btn-primary-modern {
background: white;
color: var(--primary);
box-shadow: 0 4px 15px rgba(0, 0, 0, 0.1);
}
.btn-primary-modern:hover {
color: var(--primary);
transform: translateY(-2px);
box-shadow: 0 8px 25px rgba(0, 0, 0, 0.15);
}
.btn-outline-modern {
background: transparent;
color: white;
border: 2px solid rgba(255, 255, 255, 0.3);
}
.btn-outline-modern:hover {
background: rgba(255, 255, 255, 0.1);
color: white;
border-color: white;
}
.stats {
background: var(--surface);
margin-top: -4rem;
position: relative;
z-index: 3;
border-radius: 20px;
box-shadow: 0 10px 30px rgba(0, 0, 0, 0.1);
padding: 3rem 0;
}
.stat-item {
text-align: center;
padding: 1rem;
}
.stat-number {
font-size: 2.5rem;
font-weight: 700;
color: var(--primary);
display: block;
line-height: 1;
}
.stat-label {
color: var(--text-muted);
font-size: 0.9rem;
font-weight: 500;
margin-top: 0.5rem;
}
.section-modern {
padding: 5rem 0;
}
.section-title {
font-size: 2.5rem;
font-weight: 700;
color: var(--text);
margin-bottom: 1rem;
text-align: center;
}
.section-subtitle {
color: var(--text-muted);
font-size: 1.1rem;
text-align: center;
max-width: 600px;
margin: 0 auto 4rem;
}
.feature-card {
background: var(--surface);
border-radius: 16px;
padding: 2.5rem 2rem;
text-align: center;
border: 1px solid var(--border);
transition: all 0.3s ease;
height: 100%;
}
.feature-card:hover {
transform: translateY(-8px);
box-shadow: 0 20px 40px rgba(0, 0, 0, 0.08);
border-color: var(--accent);
}
.feature-icon {
width: 70px;
height: 70px;
background: linear-gradient(135deg, var(--accent), #059669);
border-radius: 16px;
display: flex;
align-items: center;
justify-content: center;
margin: 0 auto 1.5rem;
color: white;
font-size: 1.5rem;
}
.feature-title {
font-size: 1.25rem;
font-weight: 600;
color: var(--text);
margin-bottom: 1rem;
}
.feature-description {
color: var(--text-muted);
line-height: 1.6;
}
.cta-section {
background: linear-gradient(135deg, #f1f5f9 0%, #e2e8f0 100%);
text-align: center;
padding: 5rem 0;
}
footer {
background: var(--text);
color: #94a3b8;
padding: 2.5rem 0 1.5rem;
text-align: center;
}
.footer-brand {
font-size: 1.25rem;
font-weight: 600;
color: white;
margin-bottom: 0.5rem;
}
.footer-text {
font-size: 0.9rem;
line-height: 1.5;
}
@media (max-width: 768px) {
.hero {
min-height: 70vh;
text-align: center;
}
.stats {
margin-top: -2rem;
padding: 2rem 0;
}
.btn-modern {
width: 100%;
justify-content: center;
margin-bottom: 1rem;
}
.section-modern {
padding: 3rem 0;
}
}
</style>
</head>
<body>
@@ -53,8 +318,8 @@ $mensajeMantenimiento = $config['mensaje_mantenimiento'] ?? 'El sistema se encue
<!-- NAVBAR -->
<nav class="navbar navbar-expand-lg">
<div class="container">
<a class="navbar-brand d-flex align-items-center" href="#">
<img src="/IMPORTADORES/public/<?= htmlspecialchars($logo) ?>" alt="Logo" class="logo-siih">
<a class="navbar-brand" href="#">
<i class="fas fa-shipping-fast me-2"></i><?= htmlspecialchars($siglas) ?>
</a>
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#nav">
<span class="navbar-toggler-icon"></span>
@@ -68,68 +333,191 @@ $mensajeMantenimiento = $config['mensaje_mantenimiento'] ?? 'El sistema se encue
<a class="nav-link" href="/IMPORTADORES/registro">Registro</a>
</li>
<li class="nav-item">
<a class="nav-link" href="/IMPORTADORES/login">Inicio sesión</a>
<a class="nav-link" href="/IMPORTADORES/login">Iniciar Sesión</a>
</li>
</ul>
</div>
</div>
</nav>
<!-- ALERTA DE MANTENIMIENTO -->
<!-- MANTENIMIENTO -->
<?php if ($mantenimiento): ?>
<div id="bloqueo-mantenimiento" style="
position: fixed;
top: 0; left: 0;
width: 100%; height: 100%;
background: rgba(255, 255, 255, 0.95);
z-index: 9999;
display: flex;
align-items: center;
justify-content: center;
flex-direction: column;
text-align: center;
font-family: sans-serif;
">
<i class="fas fa-tools fa-3x mb-3 text-danger"></i>
<h2>Sistema en Mantenimiento</h2>
<p><?= htmlspecialchars($mensajeMantenimiento) ?></p>
<div style="position: fixed; top: 0; left: 0; width: 100%; height: 100%; background: rgba(255, 255, 255, 0.98); z-index: 9999; display: flex; align-items: center; justify-content: center; flex-direction: column; text-align: center;">
<i class="fas fa-tools fa-4x mb-4" style="color: var(--accent);"></i>
<h2 style="color: var(--text); margin-bottom: 1rem;">Sistema en Mantenimiento</h2>
<p style="color: var(--text-muted); max-width: 500px;"><?= htmlspecialchars($mensajeMantenimiento) ?></p>
</div>
<?php endif; ?>
<!-- HERO -->
<section class="hero">
<div class="container">
<h1 class="display-5 mb-3"><?= htmlspecialchars($nombre) ?></h1>
<p class="lead">Una plataforma confiable para gestionar el registro y control de importadores de hidrocarburos en México.</p>
</div>
</section>
<!-- FEATURES -->
<section class="features text-center">
<div class="container">
<div class="row">
<div class="col-md-4">
<i class="fas fa-file-signature"></i>
<h5 class="mt-3">Registro en línea</h5>
<p>Los importadores pueden enviar su solicitud y documentación de forma digital.</p>
<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>
<p class="lead">
Simplifica tus procesos aduaneros con nuestra plataforma integral.
Desde pedimentos hasta seguimiento, todo en un solo lugar.
</p>
<div class="d-flex flex-column flex-md-row gap-3">
<a href="/IMPORTADORES/registro" class="btn-modern btn-primary-modern">
<i class="fas fa-rocket"></i>
Comenzar Gratis
</a>
<a href="#features" class="btn-modern btn-outline-modern">
<i class="fas fa-play"></i>
Ver Demo
</a>
</div>
</div>
</div>
<div class="col-md-4">
<i class="fas fa-user-shield"></i>
<h5 class="mt-3">Autorización segura</h5>
<p>Las agencias aduanales validan manualmente cada solicitud antes de otorgar acceso.</p>
</div>
<div class="col-md-4">
<i class="fas fa-database"></i>
<h5 class="mt-3">Información centralizada</h5>
<p>Todas las solicitudes y datos se almacenan de forma segura y organizada.</p>
<div class="col-lg-6 d-none d-lg-block text-center">
<i class="fas fa-chart-line" style="font-size: 15rem; color: rgba(255,255,255,0.1);"></i>
</div>
</div>
</div>
</section>
<!-- STATS -->
<div class="container">
<div class="stats">
<div class="row">
<div class="col-6 col-md-3">
<div class="stat-item">
<span class="stat-number">1,200+</span>
<div class="stat-label">Importadores</div>
</div>
</div>
<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>
</div>
<div class="col-6 col-md-3">
<div class="stat-item">
<span class="stat-number">98%</span>
<div class="stat-label">Satisfacción</div>
</div>
</div>
<div class="col-6 col-md-3">
<div class="stat-item">
<span class="stat-number">24/7</span>
<div class="stat-label">Soporte</div>
</div>
</div>
</div>
</div>
</div>
<!-- FEATURES -->
<section class="section-modern" id="features">
<div class="container">
<h2 class="section-title">Todo lo que necesitas</h2>
<p class="section-subtitle">
Herramientas profesionales diseñadas para optimizar cada etapa del proceso de importación
</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>
</div>
<h5 class="feature-title">Gestión de Pedimentos</h5>
<p class="feature-description">
Crea, gestiona y da seguimiento a tus pedimentos de importación con validación automática de datos.
</p>
</div>
</div>
<div class="col-lg-4 col-md-6">
<div class="feature-card">
<div class="feature-icon">
<i class="fas fa-shield-check"></i>
</div>
<h5 class="feature-title">Cumplimiento Automatizado</h5>
<p class="feature-description">
Mantente al día con regulaciones aduaneras y recibe alertas sobre cambios normativos relevantes.
</p>
</div>
</div>
<div class="col-lg-4 col-md-6">
<div class="feature-card">
<div class="feature-icon">
<i class="fas fa-analytics"></i>
</div>
<h5 class="feature-title">Reportes Inteligentes</h5>
<p class="feature-description">
Analiza tus operaciones con dashboards interactivos y reportes personalizables en tiempo real.
</p>
</div>
</div>
<div class="col-lg-4 col-md-6">
<div class="feature-card">
<div class="feature-icon">
<i class="fas fa-users"></i>
</div>
<h5 class="feature-title">Colaboración</h5>
<p class="feature-description">
Conecta con agentes aduanales, transportistas y proveedores en un ecosistema colaborativo.
</p>
</div>
</div>
<div class="col-lg-4 col-md-6">
<div class="feature-card">
<div class="feature-icon">
<i class="fas fa-mobile-alt"></i>
</div>
<h5 class="feature-title">Acceso Universal</h5>
<p class="feature-description">
Accede desde cualquier dispositivo con nuestra aplicación web responsiva y segura.
</p>
</div>
</div>
<div class="col-lg-4 col-md-6">
<div class="feature-card">
<div class="feature-icon">
<i class="fas fa-lock"></i>
</div>
<h5 class="feature-title">Seguridad Total</h5>
<p class="feature-description">
Protección enterprise con encriptación avanzada, backups automáticos y certificaciones de seguridad.
</p>
</div>
</div>
</div>
</div>
</section>
<!-- CTA -->
<section class="cta-section">
<div class="container">
<h2 class="section-title" style="margin-bottom: 1rem;">¿Listo para optimizar tus importaciones?</h2>
<p class="section-subtitle" style="margin-bottom: 3rem;">
Únete a más de 1,200 importadores que ya confían en nuestra plataforma
</p>
<a href="/IMPORTADORES/registro" class="btn-modern btn-primary-modern">
<i class="fas fa-arrow-right"></i>
Comenzar Ahora
</a>
</div>
</section>
<!-- FOOTER -->
<footer class="fixed-bottom">
&copy; <?= date('Y') ?> <?= htmlspecialchars($siglas) ?> · <?= htmlspecialchars($nombre) ?>
<footer>
<div class="container">
<div class="footer-brand"><?= htmlspecialchars($siglas) ?></div>
<div class="footer-text">
&copy; <?= date('Y') ?> Sistema de Gestión de Importaciones<br>
Optimizando el comercio internacional
</div>
</div>
</footer>
</body>

View File

@@ -1,15 +1,22 @@
<?php
$nombre = $config['nombre_plataforma'] ?? 'Sistema Integral para Importadores de Hidrocarburos';
$siglas = $config['siglas'] ?? 'SIIH';
$logo = $config['logo_url'] ?? 'assets/img/logo_siih.png';
$color1 = explode(',', $config['colores_primarios'] ?? '#003366,#0055A5')[0];
$color2 = explode(',', $config['colores_primarios'] ?? '#003366,#0055A5')[1];
$mantenimiento = $config['modo_mantenimiento'] ?? 0;
$mensajeMantenimiento = $config['mensaje_mantenimiento'] ?? 'El sistema se encuentra en mantenimiento temporal. Por favor, vuelve más tarde.';
require_once __DIR__ . '/../../config/database.php';
$conn = getConnection();
if (!isset($_SESSION['recuperacion_autorizada'])) {
header('Location: /IMPORTADORES/login');
exit;
$sql = "SELECT TOP 1 * FROM configuracion_sistema";
$stmt = sqlsrv_query($conn, $sql);
$config = sqlsrv_fetch_array($stmt, SQLSRV_FETCH_ASSOC);
// Variables con valores por defecto - Branding moderno
$nombre = $config['nombre_plataforma'] ?? 'Sistema de Gestión de Importaciones';
$siglas = $config['siglas'] ?? 'AduanaSoft';
$logo = $config['logo_url'] ?? 'assets/img/logo_aduanasoft.png';
$color1 = explode(',', $config['colores_primarios'] ?? '#0f172a,#334155')[0];
$color2 = explode(',', $config['colores_primarios'] ?? '#0f172a,#334155')[1];
// Verificar que el usuario tenga permiso para cambiar contraseña
if (!isset($_SESSION['codigo_verificado']) || !isset($_SESSION['email_recuperacion'])) {
header('Location: /IMPORTADORES/login/recuperar');
exit;
}
?>
@@ -17,31 +24,304 @@ if (!isset($_SESSION['recuperacion_autorizada'])) {
<html lang="es">
<head>
<meta charset="UTF-8">
<title><?= htmlspecialchars($siglas) ?> | Recuperación de Contraseña</title>
<title><?= htmlspecialchars($siglas) ?> | Nueva Contraseña</title>
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta name="description" content="Establece tu nueva contraseña para acceder a tu cuenta">
<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://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.0/css/all.min.css" rel="stylesheet">
<link href="https://fonts.googleapis.com/css2?family=Poppins:wght@300;400;500;600;700&display=swap" rel="stylesheet">
<style>
body { background-color: #f4f6f9; font-family: 'Segoe UI', sans-serif; position: relative; height: 100vh; }
.navbar { background-color: #fff; }
.navbar .nav-link, .navbar-brand { color: <?= $color1 ?> !important; font-weight: 500; }
.logo-siih { height: 90px; margin-right: 10px; }
.login-container { max-width: 420px; margin: 80px auto; background: white; padding: 40px; border-radius: 12px; box-shadow: 0 10px 25px rgba(0, 0, 0, 0.08); z-index: 10; position: relative; }
.form-control { border-radius: 6px; }
footer { background-color: #e9ecef; padding: 20px; text-align: center; font-size: 14px; color: #666; margin-top: 60px; }
/* Contenedor de fondo semitransparente para las vistas */
.overlay { position: absolute; top: 0; left: 0; width: 100%; height: 100%; background: rgba(0, 0, 0, 0.5); z-index: 5; }
:root {
--primary: <?= $color1 ?>;
--secondary: <?= $color2 ?>;
--accent: #10b981;
--surface: #ffffff;
--background: #f8fafc;
--text: #1e293b;
--text-muted: #64748b;
--border: #e2e8f0;
--error: #ef4444;
--warning: #f59e0b;
}
* {
box-sizing: border-box;
}
body {
background: linear-gradient(135deg, var(--background) 0%, #e2e8f0 100%);
font-family: 'Poppins', sans-serif;
color: var(--text);
line-height: 1.6;
margin: 0;
min-height: 100vh;
}
.navbar {
background: rgba(255, 255, 255, 0.95);
backdrop-filter: blur(20px);
border-bottom: 1px solid var(--border);
padding: 1rem 0;
}
.navbar-brand {
font-weight: 600;
font-size: 1.25rem;
color: var(--primary) !important;
}
.nav-link {
color: var(--text-muted) !important;
font-weight: 500;
padding: 0.5rem 1rem !important;
border-radius: 8px;
transition: all 0.2s ease;
}
.nav-link:hover {
color: var(--primary) !important;
background-color: #f1f5f9;
}
.password-container {
max-width: 550px;
margin: 3rem auto;
background: var(--surface);
border-radius: 20px;
box-shadow: 0 20px 40px rgba(0, 0, 0, 0.08);
padding: 3rem;
border: 1px solid var(--border);
position: relative;
overflow: hidden;
}
.password-container::before {
content: '';
position: absolute;
top: 0;
left: 0;
right: 0;
height: 4px;
background: linear-gradient(90deg, var(--accent), var(--primary));
}
.password-header {
text-align: center;
margin-bottom: 2.5rem;
}
.password-icon {
width: 80px;
height: 80px;
background: linear-gradient(135deg, var(--accent), #059669);
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
margin: 0 auto 1.5rem;
color: white;
font-size: 2rem;
}
.password-title {
font-size: 1.75rem;
font-weight: 700;
color: var(--text);
margin-bottom: 0.5rem;
}
.password-subtitle {
color: var(--text-muted);
font-size: 0.95rem;
line-height: 1.5;
}
.form-label {
font-weight: 500;
color: var(--text);
margin-bottom: 0.5rem;
display: flex;
align-items: center;
gap: 0.5rem;
}
.form-control {
border: 2px solid var(--border);
border-radius: 12px;
padding: 0.75rem 1rem;
font-size: 0.95rem;
transition: all 0.2s ease;
background: var(--surface);
}
.form-control:focus {
border-color: var(--accent);
box-shadow: 0 0 0 3px rgba(16, 185, 129, 0.1);
background: var(--surface);
}
.password-input-group {
position: relative;
}
.password-toggle {
position: absolute;
right: 12px;
top: 50%;
transform: translateY(-50%);
background: none;
border: none;
color: var(--text-muted);
cursor: pointer;
padding: 0.25rem;
border-radius: 4px;
transition: color 0.2s ease;
}
.password-toggle:hover {
color: var(--accent);
}
.password-strength {
margin-top: 0.5rem;
}
.strength-bar {
height: 4px;
background: var(--border);
border-radius: 2px;
overflow: hidden;
margin-bottom: 0.5rem;
}
.strength-fill {
height: 100%;
transition: all 0.3s ease;
border-radius: 2px;
}
.strength-weak { background: #ef4444; width: 25%; }
.strength-medium { background: var(--warning); width: 60%; }
.strength-strong { background: var(--accent); width: 100%; }
.strength-text {
font-size: 0.85rem;
font-weight: 500;
}
.password-requirements {
background: linear-gradient(135deg, #f8fafc, #f1f5f9);
border: 1px solid var(--border);
border-radius: 12px;
padding: 1rem;
margin: 1rem 0;
}
.requirement {
font-size: 0.85rem;
color: var(--text-muted);
margin-bottom: 0.25rem;
display: flex;
align-items: center;
gap: 0.5rem;
}
.requirement.met {
color: var(--accent);
}
.requirement i {
width: 16px;
font-size: 0.75rem;
}
.btn-modern {
padding: 12px 28px;
border-radius: 12px;
font-weight: 600;
text-decoration: none;
display: inline-flex;
align-items: center;
justify-content: center;
gap: 8px;
transition: all 0.3s ease;
border: none;
cursor: pointer;
width: 100%;
}
.btn-primary-modern {
background: linear-gradient(135deg, var(--accent), #059669);
color: white;
box-shadow: 0 4px 15px rgba(16, 185, 129, 0.3);
}
.btn-primary-modern:hover {
transform: translateY(-2px);
box-shadow: 0 8px 25px rgba(16, 185, 129, 0.4);
color: white;
}
.btn-primary-modern:disabled {
opacity: 0.6;
transform: none;
box-shadow: none;
cursor: not-allowed;
}
.alert-modern {
border-radius: 12px;
border: none;
padding: 1rem 1.25rem;
margin-bottom: 1.5rem;
font-weight: 500;
}
.alert-danger {
background: linear-gradient(135deg, #fef2f2, #fee2e2);
color: #dc2626;
border: 1px solid #fecaca;
}
.alert-success {
background: linear-gradient(135deg, #f0fdf4, #dcfce7);
color: #16a34a;
border: 1px solid #bbf7d0;
}
footer {
background: var(--text);
color: #94a3b8;
padding: 2rem 0;
text-align: center;
margin-top: auto;
}
.footer-text {
font-size: 0.9rem;
line-height: 1.5;
}
@media (max-width: 768px) {
.password-container {
margin: 2rem 1rem;
padding: 2rem 1.5rem;
}
.password-title {
font-size: 1.5rem;
}
}
</style>
</head>
<body>
<!-- Navbar institucional -->
<!-- NAVBAR -->
<nav class="navbar navbar-expand-lg">
<div class="container">
<a class="navbar-brand d-flex align-items-center text-dark" href="#">
<img src="/IMPORTADORES/public/<?= htmlspecialchars($logo) ?>" alt="Logo" class="logo-siih">
| Inicio de Importador
<a class="navbar-brand" href="/IMPORTADORES/">
<i class="fas fa-shipping-fast me-2"></i><?= htmlspecialchars($siglas) ?>
</a>
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarNav">
<span class="navbar-toggler-icon"></span>
@@ -52,68 +332,291 @@ if (!isset($_SESSION['recuperacion_autorizada'])) {
<a class="nav-link" href="/IMPORTADORES/">Inicio</a>
</li>
<li class="nav-item">
<a class="nav-link " aria-current="page" href="/IMPORTADORES/registro">Registro</a>
<a class="nav-link" href="/IMPORTADORES/registro">Registro</a>
</li>
<li class="nav-item">
<a class="nav-link active" href="/IMPORTADORES/login">Inicio sesión</a>
<a class="nav-link" href="/IMPORTADORES/login">Iniciar Sesión</a>
</li>
</ul>
</div>
</div>
</nav>
<?php if ($mantenimiento): ?>
<div class="container mt-4">
<div class="alert alert-danger text-center" role="alert">
<i class="fas fa-triangle-exclamation me-2"></i>
<?= htmlspecialchars($mensajeMantenimiento) ?>
<!-- CONTENEDOR DE NUEVA CONTRASEÑA -->
<div class="password-container">
<div class="password-header">
<div class="password-icon">
<i class="fas fa-lock"></i>
</div>
<h1 class="password-title">Nueva Contraseña</h1>
<p class="password-subtitle">
Crea una contraseña segura para proteger tu cuenta de importaciones
</p>
</div>
<?php endif; ?>
<!-- Overlay (fondo semitransparente) que cubre la pantalla -->
<div class="overlay"></div>
<!-- MENSAJES -->
<?php if (isset($_SESSION['error_message'])): ?>
<div class="alert-modern alert-danger">
<i class="fas fa-exclamation-triangle me-2"></i>
<?= $_SESSION['error_message'] ?>
</div>
<?php unset($_SESSION['error_message']); ?>
<?php endif; ?>
<!-- Formulario de cambio de contraseña -->
<div class="login-container">
<h4 class="mb-4 text-center" style="color:<?= $color1 ?>">Cambiar contraseña</h4>
<form id="formNueva" action="/IMPORTADORES/login/cambiarPassword" method="POST">
<form action="/IMPORTADORES/login/actualizar_password" method="POST" id="passwordForm">
<div class="mb-3">
<label class="form-label">Nueva contraseña:</label>
<input type="password" name="password" required class="form-control" minlength="6" autocomplete="new-password">
<label for="password" class="form-label">
<i class="fas fa-key text-muted"></i>
Nueva contraseña
</label>
<div class="password-input-group">
<input type="password" name="password" id="password" class="form-control"
required minlength="8"
placeholder="Tu nueva contraseña segura">
<button type="button" class="password-toggle" onclick="togglePassword('password')">
<i class="fas fa-eye" id="password-icon"></i>
</button>
</div>
<div class="password-strength">
<div class="strength-bar">
<div class="strength-fill" id="strengthBar"></div>
</div>
<div class="strength-text" id="strengthText">Ingresa una contraseña</div>
</div>
</div>
<div class="mb-3">
<label class="form-label">Confirmar contraseña:</label>
<input type="password" name="confirmar" required class="form-control">
<div class="mb-4">
<label for="confirm_password" class="form-label">
<i class="fas fa-shield-alt text-muted"></i>
Confirmar contraseña
</label>
<div class="password-input-group">
<input type="password" name="confirm_password" id="confirm_password" class="form-control"
required minlength="8"
placeholder="Repite tu nueva contraseña">
<button type="button" class="password-toggle" onclick="togglePassword('confirm_password')">
<i class="fas fa-eye" id="confirm_password-icon"></i>
</button>
</div>
<div id="passwordMatch" class="mt-2" style="font-size: 0.85rem;"></div>
</div>
<button type="submit" class="btn btn-warning w-100">Actualizar contraseña</button>
<div class="password-requirements">
<div class="mb-2" style="font-weight: 600; color: var(--text);">
<i class="fas fa-info-circle me-2"></i>Requisitos de seguridad:
</div>
<div class="requirement" id="req-length">
<i class="fas fa-circle"></i>
Mínimo 8 caracteres
</div>
<div class="requirement" id="req-uppercase">
<i class="fas fa-circle"></i>
Al menos una mayúscula (A-Z)
</div>
<div class="requirement" id="req-lowercase">
<i class="fas fa-circle"></i>
Al menos una minúscula (a-z)
</div>
<div class="requirement" id="req-number">
<i class="fas fa-circle"></i>
Al menos un número (0-9)
</div>
<div class="requirement" id="req-special">
<i class="fas fa-circle"></i>
Al menos un carácter especial (!@#$%^&*)
</div>
</div>
<button type="submit" class="btn-modern btn-primary-modern" id="submitBtn" disabled>
<i class="fas fa-save"></i>
Guardar Nueva Contraseña
</button>
</form>
<?php if (isset($_SESSION['cambio_error'])): ?>
<div class="text-danger mt-3"><?= $_SESSION['cambio_error'] ?></div>
<?php unset($_SESSION['cambio_error']); ?>
<?php endif; ?>
<?php if (isset($_SESSION['cambio_exito'])): ?>
<div class="text-success mt-3"><?= $_SESSION['cambio_exito'] ?></div>
<?php unset($_SESSION['cambio_exito']); ?>
<?php endif; ?>
</div>
<!-- FOOTER -->
<footer class="fixed-bottom">
&copy; <?= date('Y') ?> <?= htmlspecialchars($siglas) ?> · <?= htmlspecialchars($nombre) ?>
<footer>
<div class="container text-center">
<div class="footer-text">
&copy; <?= date('Y') ?> <?= htmlspecialchars($siglas) ?> - <?= htmlspecialchars($nombre) ?><br>
Seguridad avanzada para tu cuenta
</div>
</div>
</footer>
<script>
document.getElementById('formNueva').addEventListener('submit', function(e) {
const pass = this.password.value;
const confirm = this.confirmar.value;
if (pass !== confirm) {
e.preventDefault();
alert('❌ Las contraseñas no coinciden.');
function togglePassword(inputId) {
const input = document.getElementById(inputId);
const icon = document.getElementById(inputId + '-icon');
if (input.type === 'password') {
input.type = 'text';
icon.classList.remove('fa-eye');
icon.classList.add('fa-eye-slash');
} else {
input.type = 'password';
icon.classList.remove('fa-eye-slash');
icon.classList.add('fa-eye');
}
}
function checkPasswordStrength(password) {
const requirements = {
length: password.length >= 8,
uppercase: /[A-Z]/.test(password),
lowercase: /[a-z]/.test(password),
number: /[0-9]/.test(password),
special: /[!@#$%^&*()_+\-=\[\]{};':"\\|,.<>\/?]/.test(password)
};
// Actualizar indicadores visuales
Object.keys(requirements).forEach(req => {
const element = document.getElementById(`req-${req}`);
const icon = element.querySelector('i');
if (requirements[req]) {
element.classList.add('met');
icon.classList.remove('fa-circle');
icon.classList.add('fa-check-circle');
} else {
element.classList.remove('met');
icon.classList.remove('fa-check-circle');
icon.classList.add('fa-circle');
}
});
// Calcular fuerza
const metCount = Object.values(requirements).filter(Boolean).length;
const strengthBar = document.getElementById('strengthBar');
const strengthText = document.getElementById('strengthText');
strengthBar.className = 'strength-fill';
if (metCount < 3) {
strengthBar.classList.add('strength-weak');
strengthText.textContent = 'Contraseña débil';
strengthText.style.color = '#ef4444';
} else if (metCount < 5) {
strengthBar.classList.add('strength-medium');
strengthText.textContent = 'Contraseña moderada';
strengthText.style.color = '#f59e0b';
} else {
strengthBar.classList.add('strength-strong');
strengthText.textContent = 'Contraseña fuerte';
strengthText.style.color = '#10b981';
}
return metCount >= 4; // Requiere al menos 4 de 5 criterios
}
function checkPasswordMatch() {
const password = document.getElementById('password').value;
const confirmPassword = document.getElementById('confirm_password').value;
const matchDiv = document.getElementById('passwordMatch');
if (confirmPassword === '') {
matchDiv.textContent = '';
return false;
}
if (password === confirmPassword) {
matchDiv.innerHTML = '<i class="fas fa-check-circle me-1" style="color: #10b981;"></i>Las contraseñas coinciden';
matchDiv.style.color = '#10b981';
return true;
} else {
matchDiv.innerHTML = '<i class="fas fa-times-circle me-1" style="color: #ef4444;"></i>Las contraseñas no coinciden';
matchDiv.style.color = '#ef4444';
return false;
}
}
function validateForm() {
const password = document.getElementById('password').value;
const isStrong = checkPasswordStrength(password);
const isMatching = checkPasswordMatch();
const submitBtn = document.getElementById('submitBtn');
submitBtn.disabled = !(isStrong && isMatching && password.length >= 8);
}
// Event listeners
document.getElementById('password').addEventListener('input', validateForm);
document.getElementById('confirm_password').addEventListener('input', validateForm);
// Validación del formulario
document.getElementById('passwordForm').addEventListener('submit', function(e) {
e.preventDefault();
const password = document.getElementById('password').value;
const confirmPassword = document.getElementById('confirm_password').value;
const submitBtn = document.getElementById('submitBtn');
if (password !== confirmPassword) {
showAlert('error', 'Las contraseñas no coinciden');
return;
}
if (!checkPasswordStrength(password)) {
showAlert('error', 'La contraseña no cumple con los requisitos de seguridad');
return;
}
// Deshabilitar botón y mostrar loading
submitBtn.disabled = true;
submitBtn.innerHTML = '<i class="fas fa-spinner fa-spin"></i> Guardando...';
// Enviar formulario normalmente (el método devuelve HTML)
const formData = new FormData();
formData.append('password', password);
formData.append('confirmar', confirmPassword);
fetch('/IMPORTADORES/login/actualizar_password', {
method: 'POST',
body: formData
})
.then(response => response.text())
.then(html => {
// Verificar si contiene mensaje de éxito
if (html.includes('✅ ¡Contraseña actualizada!')) {
// Mostrar la respuesta HTML del servidor
document.body.innerHTML = html;
} else {
// Es un mensaje de error
showAlert('error', html.replace(/<[^>]*>/g, '').trim());
submitBtn.disabled = false;
submitBtn.innerHTML = '<i class="fas fa-save"></i> Guardar Nueva Contraseña';
}
})
.catch(error => {
console.error('Error:', error);
showAlert('error', 'Error de conexión. Intenta nuevamente.');
submitBtn.disabled = false;
submitBtn.innerHTML = '<i class="fas fa-save"></i> Guardar Nueva Contraseña';
});
});
function showAlert(type, message) {
// Remover alertas anteriores
const existingAlerts = document.querySelectorAll('.alert-modern');
existingAlerts.forEach(alert => alert.remove());
// Crear nueva alerta
const alertDiv = document.createElement('div');
alertDiv.className = `alert-modern alert-${type === 'success' ? 'success' : 'danger'}`;
alertDiv.innerHTML = `
<i class="fas fa-${type === 'success' ? 'check-circle' : 'exclamation-triangle'} me-2"></i>
${message}
`;
// Insertar antes del formulario
const form = document.getElementById('passwordForm');
form.parentNode.insertBefore(alertDiv, form);
}
// Focus automático
document.addEventListener('DOMContentLoaded', function() {
document.getElementById('password').focus();
});
</script>

View File

@@ -1,44 +1,273 @@
<?php
$nombre = $config['nombre_plataforma'] ?? 'Sistema Integral para Importadores de Hidrocarburos';
$siglas = $config['siglas'] ?? 'SIIH';
$logo = $config['logo_url'] ?? 'assets/img/logo_siih.png';
$color1 = explode(',', $config['colores_primarios'] ?? '#003366,#0055A5')[0];
$color2 = explode(',', $config['colores_primarios'] ?? '#003366,#0055A5')[1];
$mantenimiento = $config['modo_mantenimiento'] ?? 0;
$mensajeMantenimiento = $config['mensaje_mantenimiento'] ?? 'El sistema se encuentra en mantenimiento temporal. Por favor, vuelve más tarde.';
require_once __DIR__ . '/../../config/database.php';
$conn = getConnection();
$sql = "SELECT TOP 1 * FROM configuracion_sistema";
$stmt = sqlsrv_query($conn, $sql);
$config = sqlsrv_fetch_array($stmt, SQLSRV_FETCH_ASSOC);
// Variables con valores por defecto - Branding moderno
$nombre = $config['nombre_plataforma'] ?? 'Sistema de Gestión de Importaciones';
$siglas = $config['siglas'] ?? 'AduanaSoft';
$logo = $config['logo_url'] ?? 'assets/img/logo_aduanasoft.png';
$color1 = explode(',', $config['colores_primarios'] ?? '#0f172a,#334155')[0];
$color2 = explode(',', $config['colores_primarios'] ?? '#0f172a,#334155')[1];
// Obtener tipo de confirmación y mensaje
$tipo = $_GET['tipo'] ?? 'success';
$mensaje = $_GET['mensaje'] ?? 'Operación completada exitosamente';
?>
<!DOCTYPE html>
<html lang="es">
<head>
<meta charset="UTF-8">
<title><?= htmlspecialchars($siglas) ?> | Recuperación de Contraseña</title>
<title><?= htmlspecialchars($siglas) ?> | Confirmación</title>
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta name="description" content="Confirmación de operación en tu cuenta de importaciones">
<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://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.0/css/all.min.css" rel="stylesheet">
<link href="https://fonts.googleapis.com/css2?family=Poppins:wght@300;400;500;600;700&display=swap" rel="stylesheet">
<style>
body { background-color: #f4f6f9; font-family: 'Segoe UI', sans-serif; position: relative; min-height: 100vh; }
.navbar { background-color: #fff; }
.navbar .nav-link, .navbar-brand { color: <?= $color1 ?> !important; font-weight: 500; }
.logo-siih { height: 90px; margin-right: 10px; }
.login-container { max-width: 420px; margin: 80px auto; background: white; padding: 40px; border-radius: 12px; box-shadow: 0 10px 25px rgba(0, 0, 0, 0.08); z-index: 10; position: relative; }
.form-control { border-radius: 6px; }
footer { background-color: #e9ecef; padding: 20px; text-align: center; font-size: 14px; color: #666; margin-top: 60px; }
/* Contenedor de fondo semitransparente para las vistas */
.overlay { position: fixed; top: 0; left: 0; width: 100%; height: 100%; background: rgba(0, 0, 0, 0.5); z-index: 5; }
#mensaje { transition: opacity 0.3s ease; }
@media (max-height: 600px) { footer { position: static !important; } }
:root {
--primary: <?= $color1 ?>;
--secondary: <?= $color2 ?>;
--accent: #10b981;
--surface: #ffffff;
--background: #f8fafc;
--text: #1e293b;
--text-muted: #64748b;
--border: #e2e8f0;
--success: #16a34a;
--error: #ef4444;
--warning: #f59e0b;
}
* {
box-sizing: border-box;
}
body {
background: linear-gradient(135deg, var(--background) 0%, #e2e8f0 100%);
font-family: 'Poppins', sans-serif;
color: var(--text);
line-height: 1.6;
margin: 0;
min-height: 100vh;
display: flex;
flex-direction: column;
}
.navbar {
background: rgba(255, 255, 255, 0.95);
backdrop-filter: blur(20px);
border-bottom: 1px solid var(--border);
padding: 1rem 0;
}
.navbar-brand {
font-weight: 600;
font-size: 1.25rem;
color: var(--primary) !important;
}
.nav-link {
color: var(--text-muted) !important;
font-weight: 500;
padding: 0.5rem 1rem !important;
border-radius: 8px;
transition: all 0.2s ease;
}
.nav-link:hover {
color: var(--primary) !important;
background-color: #f1f5f9;
}
.confirmation-container {
max-width: 500px;
margin: 3rem auto;
background: var(--surface);
border-radius: 20px;
box-shadow: 0 20px 40px rgba(0, 0, 0, 0.08);
padding: 3rem;
border: 1px solid var(--border);
position: relative;
overflow: hidden;
text-align: center;
}
.confirmation-container::before {
content: '';
position: absolute;
top: 0;
left: 0;
right: 0;
height: 4px;
background: linear-gradient(90deg, var(--accent), var(--primary));
}
.confirmation-icon {
width: 100px;
height: 100px;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
margin: 0 auto 2rem;
color: white;
font-size: 2.5rem;
position: relative;
animation: pulse 2s infinite;
}
.icon-success {
background: linear-gradient(135deg, var(--success), #059669);
}
.icon-error {
background: linear-gradient(135deg, var(--error), #dc2626);
}
.icon-warning {
background: linear-gradient(135deg, var(--warning), #d97706);
}
.icon-info {
background: linear-gradient(135deg, #3b82f6, #1d4ed8);
}
@keyframes pulse {
0% {
box-shadow: 0 0 0 0 rgba(16, 185, 129, 0.4);
}
70% {
box-shadow: 0 0 0 10px rgba(16, 185, 129, 0);
}
100% {
box-shadow: 0 0 0 0 rgba(16, 185, 129, 0);
}
}
.confirmation-title {
font-size: 2rem;
font-weight: 700;
color: var(--text);
margin-bottom: 1rem;
}
.confirmation-message {
color: var(--text-muted);
font-size: 1.1rem;
line-height: 1.6;
margin-bottom: 2.5rem;
max-width: 400px;
margin-left: auto;
margin-right: auto;
}
.action-buttons {
display: flex;
flex-direction: column;
gap: 1rem;
margin-top: 2rem;
}
.btn-modern {
padding: 12px 28px;
border-radius: 12px;
font-weight: 600;
text-decoration: none;
display: inline-flex;
align-items: center;
justify-content: center;
gap: 8px;
transition: all 0.3s ease;
border: none;
cursor: pointer;
}
.btn-primary-modern {
background: linear-gradient(135deg, var(--accent), #059669);
color: white;
box-shadow: 0 4px 15px rgba(16, 185, 129, 0.3);
}
.btn-primary-modern:hover {
transform: translateY(-2px);
box-shadow: 0 8px 25px rgba(16, 185, 129, 0.4);
color: white;
}
.btn-outline-modern {
background: transparent;
color: var(--text-muted);
border: 2px solid var(--border);
}
.btn-outline-modern:hover {
background: var(--background);
color: var(--text);
border-color: var(--accent);
text-decoration: none;
}
.additional-info {
background: linear-gradient(135deg, #f0f9ff, #e0f2fe);
border: 1px solid #38bdf8;
border-radius: 12px;
padding: 1.5rem;
margin-top: 2rem;
color: #0c4a6e;
text-align: left;
}
.countdown {
font-weight: 600;
color: var(--accent);
margin-top: 1rem;
font-size: 0.9rem;
}
footer {
background: var(--text);
color: #94a3b8;
padding: 2rem 0;
text-align: center;
margin-top: auto;
}
.footer-text {
font-size: 0.9rem;
line-height: 1.5;
}
@media (max-width: 768px) {
.confirmation-container {
margin: 2rem 1rem;
padding: 2rem 1.5rem;
}
.confirmation-title {
font-size: 1.5rem;
}
.confirmation-icon {
width: 80px;
height: 80px;
font-size: 2rem;
}
}
</style>
</head>
<body>
<!-- Navbar institucional -->
<!-- NAVBAR -->
<nav class="navbar navbar-expand-lg">
<div class="container">
<a class="navbar-brand d-flex align-items-center text-dark" href="#">
<img src="/IMPORTADORES/public/<?= htmlspecialchars($logo) ?>" alt="Logo" class="logo-siih">
| Inicio de Importador
<a class="navbar-brand" href="/IMPORTADORES/">
<i class="fas fa-shipping-fast me-2"></i><?= htmlspecialchars($siglas) ?>
</a>
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarNav">
<span class="navbar-toggler-icon"></span>
@@ -49,75 +278,206 @@ $mensajeMantenimiento = $config['mensaje_mantenimiento'] ?? 'El sistema se encue
<a class="nav-link" href="/IMPORTADORES/">Inicio</a>
</li>
<li class="nav-item">
<a class="nav-link " aria-current="page" href="/IMPORTADORES/registro">Registro</a>
<a class="nav-link" href="/IMPORTADORES/registro">Registro</a>
</li>
<li class="nav-item">
<a class="nav-link active" href="/IMPORTADORES/login">Inicio sesión</a>
<a class="nav-link" href="/IMPORTADORES/login">Iniciar Sesión</a>
</li>
</ul>
</div>
</div>
</nav>
<?php if ($mantenimiento): ?>
<div class="container mt-4">
<div class="alert alert-danger text-center" role="alert">
<i class="fas fa-triangle-exclamation me-2"></i>
<?= htmlspecialchars($mensajeMantenimiento) ?>
</div>
<!-- CONTENEDOR DE CONFIRMACIÓN -->
<div class="confirmation-container">
<?php
// Determinar icono y títulos según el tipo
$iconClass = 'icon-success';
$iconSymbol = 'fa-check';
$title = 'Operación Exitosa';
switch($tipo) {
case 'password_changed':
$iconClass = 'icon-success';
$iconSymbol = 'fa-check';
$title = '¡Contraseña Actualizada!';
$mensaje = 'Tu contraseña ha sido cambiada exitosamente. Ya puedes iniciar sesión con tu nueva contraseña.';
break;
case 'code_sent':
$iconClass = 'icon-info';
$iconSymbol = 'fa-envelope';
$title = '¡Código Enviado!';
$mensaje = 'Hemos enviado un código de verificación a tu correo electrónico. Revisa tu bandeja de entrada y spam.';
break;
case 'registration_sent':
$iconClass = 'icon-success';
$iconSymbol = 'fa-paper-plane';
$title = '¡Solicitud Enviada!';
$mensaje = 'Tu solicitud de registro ha sido enviada correctamente. Recibirás una notificación por correo cuando sea aprobada.';
break;
case 'error':
$iconClass = 'icon-error';
$iconSymbol = 'fa-times';
$title = 'Error en la Operación';
break;
case 'warning':
$iconClass = 'icon-warning';
$iconSymbol = 'fa-exclamation-triangle';
$title = 'Atención Requerida';
break;
default:
$iconClass = 'icon-success';
$iconSymbol = 'fa-check';
$title = 'Operación Completada';
}
?>
<div class="confirmation-icon <?= $iconClass ?>">
<i class="fas <?= $iconSymbol ?>"></i>
</div>
<?php endif; ?>
<!-- Overlay (fondo semitransparente) que cubre la pantalla -->
<div class="overlay"></div>
<h1 class="confirmation-title"><?= htmlspecialchars($title) ?></h1>
<p class="confirmation-message">
<?= htmlspecialchars($mensaje) ?>
</p>
<!-- Formulario de confirmación de acceso -->
<div class="login-container">
<h4 class="mb-4 text-center text-success">Confirma el Acceso</h4>
<!-- Formulario para verificar el código -->
<form id="formConfirmacion" action="/IMPORTADORES/login/confirmarAcceso" method="POST">
<div class="mb-3">
<label class="form-label" for="codigo">Código recibido por correo</label>
<input type="text" id="codigo" name="codigo" class="form-control" required pattern="\d{6}" maxlength="6" placeholder="Ej. 123456">
</div>
<button type="submit" class="btn btn-primary w-100">Verificar código</button>
</form>
<div id="mensaje" class="mt-3 text-center fw-bold">
<?php if (isset($_SESSION['codigo_error'])): ?>
<div class="text-danger"><?= $_SESSION['codigo_error'] ?></div>
<?php unset($_SESSION['codigo_error']); ?>
<?php elseif (isset($_SESSION['codigo_exito'])): ?>
<div class="text-success"><?= $_SESSION['codigo_exito'] ?></div>
<?php unset($_SESSION['codigo_exito']); ?>
<div class="action-buttons">
<?php if ($tipo === 'password_changed'): ?>
<a href="/IMPORTADORES/login" class="btn-modern btn-primary-modern">
<i class="fas fa-sign-in-alt"></i>
Iniciar Sesión
</a>
<?php elseif ($tipo === 'code_sent'): ?>
<a href="/IMPORTADORES/login/verificar_codigo" class="btn-modern btn-primary-modern">
<i class="fas fa-key"></i>
Verificar Código
</a>
<a href="/IMPORTADORES/login/recuperar" class="btn-modern btn-outline-modern">
<i class="fas fa-arrow-left"></i>
Volver
</a>
<?php elseif ($tipo === 'registration_sent'): ?>
<a href="/IMPORTADORES/" class="btn-modern btn-primary-modern">
<i class="fas fa-home"></i>
Ir al Inicio
</a>
<a href="/IMPORTADORES/login" class="btn-modern btn-outline-modern">
<i class="fas fa-sign-in-alt"></i>
Iniciar Sesión
</a>
<?php else: ?>
<a href="/IMPORTADORES/login" class="btn-modern btn-primary-modern">
<i class="fas fa-sign-in-alt"></i>
Iniciar Sesión
</a>
<a href="/IMPORTADORES/" class="btn-modern btn-outline-modern">
<i class="fas fa-home"></i>
Ir al Inicio
</a>
<?php endif; ?>
</div>
</div>
<?php
$redirigirA = $_SESSION['redirigir_a'] ?? null;
$delayMs = $_SESSION['delay_redireccion'] ?? null;
unset($_SESSION['redirigir_a'], $_SESSION['delay_redireccion']);
?>
<?php if ($tipo === 'code_sent'): ?>
<div class="additional-info">
<div class="d-flex align-items-start">
<i class="fas fa-info-circle me-2 mt-1"></i>
<div>
<strong>¿No recibiste el código?</strong><br>
<small>
• Revisa tu carpeta de spam o correo no deseado<br>
• El código es válido por 15 minutos<br>
• Puedes solicitar un nuevo código si es necesario
</small>
</div>
</div>
</div>
<?php endif; ?>
<?php if ($tipo === 'registration_sent'): ?>
<div class="additional-info">
<div class="d-flex align-items-start">
<i class="fas fa-clock me-2 mt-1"></i>
<div>
<strong>Proceso de Revisión</strong><br>
<small>
• Nuestro equipo revisará tu solicitud en las próximas 24-48 horas<br>
• Recibirás un correo con el resultado de la evaluación<br>
• Asegúrate de revisar tu bandeja de entrada regularmente
</small>
</div>
</div>
</div>
<?php endif; ?>
<?php if (in_array($tipo, ['password_changed', 'registration_sent'])): ?>
<div class="countdown" id="countdown">
Serás redirigido automáticamente en <span id="timer">10</span> segundos...
</div>
<?php endif; ?>
</div>
<!-- FOOTER -->
<footer>
<div class="container text-center">
<div class="footer-text">
&copy; <?= date('Y') ?> <?= htmlspecialchars($siglas) ?> - <?= htmlspecialchars($nombre) ?><br>
Plataforma segura de gestión de importaciones
</div>
</div>
</footer>
<script>
document.addEventListener('DOMContentLoaded', function () {
const redirigirA = <?= json_encode($redirigirA) ?>;
const delay = <?= json_encode($delayMs) ?>;
if (redirigirA && delay) {
setTimeout(() => {
window.location.href = redirigirA;
}, parseInt(delay));
// Redirección automática para ciertos tipos
<?php if (in_array($tipo, ['password_changed', 'registration_sent'])): ?>
let timeLeft = 10;
const timerElement = document.getElementById('timer');
const countdownElement = document.getElementById('countdown');
const countdown = setInterval(() => {
timeLeft--;
timerElement.textContent = timeLeft;
if (timeLeft <= 0) {
clearInterval(countdown);
<?php if ($tipo === 'password_changed'): ?>
window.location.href = '/IMPORTADORES/login';
<?php elseif ($tipo === 'registration_sent'): ?>
window.location.href = '/IMPORTADORES/';
<?php endif; ?>
}
}, 1000);
// Permitir cancelar la redirección automática
document.addEventListener('click', () => {
if (timeLeft > 0) {
clearInterval(countdown);
countdownElement.style.display = 'none';
}
});
<?php endif; ?>
// Animación de entrada
document.addEventListener('DOMContentLoaded', function() {
const container = document.querySelector('.confirmation-container');
container.style.opacity = '0';
container.style.transform = 'translateY(20px)';
setTimeout(() => {
container.style.transition = 'all 0.5s ease';
container.style.opacity = '1';
container.style.transform = 'translateY(0)';
}, 100);
});
</script>
<!-- FOOTER -->
<footer class="fixed-bottom">
&copy; <?= date('Y') ?> <?= htmlspecialchars($siglas) ?> · <?= htmlspecialchars($nombre) ?>
</footer>
</body>
</html>

View File

@@ -6,11 +6,12 @@ $sql = "SELECT TOP 1 * FROM configuracion_sistema";
$stmt = sqlsrv_query($conn, $sql);
$config = sqlsrv_fetch_array($stmt, SQLSRV_FETCH_ASSOC);
$nombre = $config['nombre_plataforma'] ?? 'Sistema Integral para Importadores de Hidrocarburos';
$siglas = $config['siglas'] ?? 'SIIH';
$logo = $config['logo_url'] ?? 'assets/img/logo_siih.png';
$color1 = explode(',', $config['colores_primarios'] ?? '#003366,#0055A5')[0];
$color2 = explode(',', $config['colores_primarios'] ?? '#003366,#0055A5')[1];
// Variables con valores por defecto - Branding moderno
$nombre = $config['nombre_plataforma'] ?? 'Sistema de Gestión de Importaciones';
$siglas = $config['siglas'] ?? 'AduanaSoft';
$logo = $config['logo_url'] ?? 'assets/img/logo_aduanasoft.png';
$color1 = explode(',', $config['colores_primarios'] ?? '#0f172a,#334155')[0];
$color2 = explode(',', $config['colores_primarios'] ?? '#0f172a,#334155')[1];
$mantenimiento = $config['modo_mantenimiento'] ?? 0;
$mensajeMantenimiento = $config['mensaje_mantenimiento'] ?? 'El sistema se encuentra en mantenimiento temporal. Por favor, vuelve más tarde.';
?>
@@ -19,40 +20,238 @@ $mensajeMantenimiento = $config['mensaje_mantenimiento'] ?? 'El sistema se encue
<html lang="es">
<head>
<meta charset="UTF-8">
<title><?= htmlspecialchars($siglas) ?> | Iniciar sesión</title>
<title><?= htmlspecialchars($siglas) ?> | Iniciar Sesión</title>
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta name="description" content="Accede a tu cuenta en la plataforma de gestión de importaciones">
<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://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.0/css/all.min.css" rel="stylesheet">
<link href="https://fonts.googleapis.com/css2?family=Poppins:wght@300;400;500;600;700&display=swap" rel="stylesheet">
<style>
body { background-color: #f4f6f9; font-family: 'Segoe UI', sans-serif; }
.navbar { background-color: #fff; }
.navbar .nav-link, .navbar-brand { color:<?= $color1 ?> !important; font-weight: 500; }
.navbar .nav-link.active { background-color: #F9F9F9; border-radius: 5px; }
.logo-siih { height: 90px; margin-right: 10px; }
.login-container { max-width: 420px; margin: 80px auto; background: white; padding: 40px; border-radius: 12px; box-shadow: 0 10px 25px rgba(0, 0, 0, 0.08); }
.form-control { border-radius: 6px; }
footer { background-color: #e9ecef; padding: 20px; text-align: center; font-size: 14px; color: #666; margin-top: 60px; }
#mensaje { transition: opacity 0.3s ease; }
@media (max-height: 600px) { footer { position: static !important; } }
/* Efecto de glow para elementos activos */
.btn.active { box-shadow: 0 0 20px rgba(var(--bs-primary-rgb), 0.5); }
/* 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%; }
:root {
--primary: <?= $color1 ?>;
--secondary: <?= $color2 ?>;
--accent: #10b981;
--surface: #ffffff;
--background: #f8fafc;
--text: #1e293b;
--text-muted: #64748b;
--border: #e2e8f0;
--error: #ef4444;
}
* {
box-sizing: border-box;
}
body {
background: linear-gradient(135deg, var(--background) 0%, #e2e8f0 100%);
font-family: 'Poppins', sans-serif;
color: var(--text);
line-height: 1.6;
margin: 0;
min-height: 100vh;
}
.navbar {
background: rgba(255, 255, 255, 0.95);
backdrop-filter: blur(20px);
border-bottom: 1px solid var(--border);
padding: 1rem 0;
}
.navbar-brand {
font-weight: 600;
font-size: 1.25rem;
color: var(--primary) !important;
}
.nav-link {
color: var(--text-muted) !important;
font-weight: 500;
padding: 0.5rem 1rem !important;
border-radius: 8px;
transition: all 0.2s ease;
}
.nav-link:hover, .nav-link.active {
color: var(--primary) !important;
background-color: #f1f5f9;
}
.login-container {
max-width: 450px;
margin: 3rem auto;
background: var(--surface);
border-radius: 20px;
box-shadow: 0 20px 40px rgba(0, 0, 0, 0.08);
padding: 3rem;
border: 1px solid var(--border);
position: relative;
overflow: hidden;
}
.login-container::before {
content: '';
position: absolute;
top: 0;
left: 0;
right: 0;
height: 4px;
background: linear-gradient(90deg, var(--accent), var(--primary));
}
.login-header {
text-align: center;
margin-bottom: 2.5rem;
}
.login-title {
font-size: 1.75rem;
font-weight: 700;
color: var(--text);
margin-bottom: 0.5rem;
}
.login-subtitle {
color: var(--text-muted);
font-size: 0.95rem;
}
.form-label {
font-weight: 500;
color: var(--text);
margin-bottom: 0.5rem;
display: flex;
align-items: center;
gap: 0.5rem;
}
.form-control {
border: 2px solid var(--border);
border-radius: 12px;
padding: 0.75rem 1rem;
font-size: 0.95rem;
transition: all 0.2s ease;
background: var(--surface);
}
.form-control:focus {
border-color: var(--accent);
box-shadow: 0 0 0 3px rgba(16, 185, 129, 0.1);
background: var(--surface);
}
.btn-modern {
padding: 12px 28px;
border-radius: 12px;
font-weight: 600;
text-decoration: none;
display: inline-flex;
align-items: center;
justify-content: center;
gap: 8px;
transition: all 0.3s ease;
border: none;
cursor: pointer;
width: 100%;
}
.btn-primary-modern {
background: linear-gradient(135deg, var(--accent), #059669);
color: white;
box-shadow: 0 4px 15px rgba(16, 185, 129, 0.3);
}
.btn-primary-modern:hover {
transform: translateY(-2px);
box-shadow: 0 8px 25px rgba(16, 185, 129, 0.4);
color: white;
}
.forgot-password {
text-align: center;
margin-top: 1.5rem;
}
.forgot-password a {
color: var(--accent);
text-decoration: none;
font-weight: 500;
transition: color 0.2s ease;
}
.forgot-password a:hover {
color: #059669;
}
.alert-modern {
border-radius: 12px;
border: none;
padding: 1rem 1.25rem;
margin-bottom: 1.5rem;
font-weight: 500;
}
.alert-danger {
background: linear-gradient(135deg, #fef2f2, #fee2e2);
color: #dc2626;
border: 1px solid #fecaca;
}
.alert-warning {
background: linear-gradient(135deg, #fef3c7, #fde68a);
color: #92400e;
border: 1px solid #f59e0b;
}
footer {
background: var(--text);
color: #94a3b8;
padding: 2rem 0;
text-align: center;
margin-top: auto;
}
.footer-text {
font-size: 0.9rem;
line-height: 1.5;
}
@media (max-width: 768px) {
.login-container {
margin: 2rem 1rem;
padding: 2rem 1.5rem;
}
.login-title {
font-size: 1.5rem;
}
}
.maintenance-overlay {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: rgba(255, 255, 255, 0.98);
z-index: 9999;
display: flex;
align-items: center;
justify-content: center;
flex-direction: column;
text-align: center;
}
</style>
</head>
<body>
<!-- Navbar institucional -->
<!-- NAVBAR -->
<nav class="navbar navbar-expand-lg">
<div class="container">
<a class="navbar-brand d-flex align-items-center text-dark" href="#">
<img src="/IMPORTADORES/public/<?= htmlspecialchars($logo) ?>" alt="Logo" class="logo-siih">
| Inicio de Importador
<a class="navbar-brand" href="/IMPORTADORES/">
<i class="fas fa-shipping-fast me-2"></i><?= htmlspecialchars($siglas) ?>
</a>
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarNav">
<span class="navbar-toggler-icon"></span>
@@ -63,60 +262,82 @@ $mensajeMantenimiento = $config['mensaje_mantenimiento'] ?? 'El sistema se encue
<a class="nav-link" href="/IMPORTADORES/">Inicio</a>
</li>
<li class="nav-item">
<a class="nav-link " aria-current="page" href="/IMPORTADORES/registro">Registro</a>
<a class="nav-link" href="/IMPORTADORES/registro">Registro</a>
</li>
<li class="nav-item">
<a class="nav-link active" href="/IMPORTADORES/login">Inicio sesión</a>
<a class="nav-link active" href="/IMPORTADORES/login">Iniciar Sesión</a>
</li>
</ul>
</div>
</div>
</nav>
<!-- MANTENIMIENTO -->
<?php if ($mantenimiento): ?>
<div class="container mt-4">
<div class="alert alert-danger text-center" role="alert">
<i class="fas fa-triangle-exclamation me-2"></i>
<?= htmlspecialchars($mensajeMantenimiento) ?>
</div>
<div class="maintenance-overlay">
<i class="fas fa-tools fa-4x mb-4" style="color: var(--accent);"></i>
<h2 style="color: var(--text); margin-bottom: 1rem;">Sistema en Mantenimiento</h2>
<p style="color: var(--text-muted); max-width: 500px;"><?= htmlspecialchars($mensajeMantenimiento) ?></p>
</div>
<?php endif; ?>
<!-- CONTENEDOR DE LOGIN -->
<div class="login-container">
<h4 class="mb-4 text-center text-dark">Inicio de Sesión </h4>
<form action="/IMPORTADORES/login/validar" method="POST">
<div class="mb-3">
<label class="form-label">Correo electrónico</label>
<input type="email" name="email" class="form-control" required autocomplete="username" value="">
</div>
<div class="mb-3">
<label class="form-label">Contraseña</label>
<input type="password" name="password" class="form-control" required autocomplete="current-password" value="">
</div>
<div class="d-grid">
<button type="submit" class="btn btn-primary btn-sm mt-auto m-100 btn-animated">Iniciar sesión</button>
</div>
<div class="mb-3 text-end">
<div style="display: flex; justify-content: center;">
<a href="/IMPORTADORES/login/recuperar" class="text-decoration-none">¿Olvidaste tu contraseña?</a>
</div>
</div>
</form>
<div id="mensaje" class="mt-3 text-center fw-bold">
<?php if (isset($_SESSION['login_error'])): ?>
<div class="text-danger"><?= $_SESSION['login_error'] ?></div>
<?php unset($_SESSION['login_error']); ?>
<?php endif; ?>
<div class="login-header">
<h1 class="login-title">Bienvenido de Vuelta</h1>
<p class="login-subtitle">Ingresa a tu cuenta para gestionar tus importaciones</p>
</div>
<!-- MENSAJES DE ERROR -->
<?php if (isset($_SESSION['login_error'])): ?>
<div class="alert-modern alert-danger">
<i class="fas fa-exclamation-triangle me-2"></i>
<?= $_SESSION['login_error'] ?>
</div>
<?php unset($_SESSION['login_error']); ?>
<?php endif; ?>
<form action="/IMPORTADORES/login/validar" method="POST">
<div class="mb-3">
<label for="email" class="form-label">
<i class="fas fa-envelope text-muted"></i>
Correo electrónico
</label>
<input type="email" name="email" id="email" class="form-control"
required autocomplete="username"
placeholder="tu@empresa.com">
</div>
<div class="mb-4">
<label for="password" class="form-label">
<i class="fas fa-lock text-muted"></i>
Contraseña
</label>
<input type="password" name="password" id="password" class="form-control"
required autocomplete="current-password"
placeholder="Tu contraseña segura">
</div>
<button type="submit" class="btn-modern btn-primary-modern">
<i class="fas fa-sign-in-alt"></i>
Iniciar Sesión
</button>
<div class="forgot-password">
<a href="/IMPORTADORES/login/recuperar">¿Olvidaste tu contraseña?</a>
</div>
</form>
</div>
<!-- FOOTER -->
<footer class="fixed-bottom">
&copy; <?= date('Y') ?> <?= htmlspecialchars($siglas) ?> · <?= htmlspecialchars($nombre) ?>
<div class="container text-center ">
<div class="footer-text">
&copy; <?= date('Y') ?> <?= htmlspecialchars($siglas) ?> - <?= htmlspecialchars($nombre) ?><br>
Acceso seguro a tu plataforma de importaciones
</div>
</div>
</footer>
<!-- I.C.M -->
</body>
</html>

View File

@@ -1,42 +1,259 @@
<?php
$nombre = $config['nombre_plataforma'] ?? 'Sistema Integral para Importadores de Hidrocarburos';
$siglas = $config['siglas'] ?? 'SIIH';
$logo = $config['logo_url'] ?? 'assets/img/logo_siih.png';
$color1 = explode(',', $config['colores_primarios'] ?? '#003366,#0055A5')[0];
$color2 = explode(',', $config['colores_primarios'] ?? '#003366,#0055A5')[1];
$mantenimiento = $config['modo_mantenimiento'] ?? 0;
$mensajeMantenimiento = $config['mensaje_mantenimiento'] ?? 'El sistema se encuentra en mantenimiento temporal. Por favor, vuelve más tarde.';
require_once __DIR__ . '/../../config/database.php';
$conn = getConnection();
$sql = "SELECT TOP 1 * FROM configuracion_sistema";
$stmt = sqlsrv_query($conn, $sql);
$config = sqlsrv_fetch_array($stmt, SQLSRV_FETCH_ASSOC);
// Variables con valores por defecto - Branding moderno
$nombre = $config['nombre_plataforma'] ?? 'Sistema de Gestión de Importaciones';
$siglas = $config['siglas'] ?? 'AduanaSoft';
$logo = $config['logo_url'] ?? 'assets/img/logo_aduanasoft.png';
$color1 = explode(',', $config['colores_primarios'] ?? '#0f172a,#334155')[0];
$color2 = explode(',', $config['colores_primarios'] ?? '#0f172a,#334155')[1];
?>
<!DOCTYPE html>
<html lang="es">
<head>
<meta charset="UTF-8">
<title><?= htmlspecialchars($siglas) ?> | Recuperación de Contraseña</title>
<title><?= htmlspecialchars($siglas) ?> | Recuperar Contraseña</title>
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta name="description" content="Recupera tu contraseña para acceder a tu cuenta de importaciones">
<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://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.0/css/all.min.css" rel="stylesheet">
<link href="https://fonts.googleapis.com/css2?family=Poppins:wght@300;400;500;600;700&display=swap" rel="stylesheet">
<style>
body { background-color: #f4f6f9; font-family: 'Segoe UI', sans-serif; position: relative; height: 100vh; }
.navbar { background-color: #fff; }
.navbar .nav-link, .navbar-brand { color: <?= $color1 ?> !important; font-weight: 500; }
.logo-siih { height: 90px; margin-right: 10px; }
.login-container { max-width: 420px; margin: 80px auto; background: white; padding: 40px; border-radius: 12px; box-shadow: 0 10px 25px rgba(0, 0, 0, 0.08); z-index: 10; position: relative; }
.form-control { border-radius: 6px; }
footer { background-color: #e9ecef; padding: 20px; text-align: center; font-size: 14px; color: #666; margin-top: 60px; }
/* Contenedor de fondo semitransparente para las vistas */
.overlay { position: absolute; top: 0; left: 0; width: 100%; height: 100%; background: rgba(0, 0, 0, 0.5); z-index: 5; }
:root {
--primary: <?= $color1 ?>;
--secondary: <?= $color2 ?>;
--accent: #10b981;
--surface: #ffffff;
--background: #f8fafc;
--text: #1e293b;
--text-muted: #64748b;
--border: #e2e8f0;
--error: #ef4444;
}
* {
box-sizing: border-box;
}
body {
background: linear-gradient(135deg, var(--background) 0%, #e2e8f0 100%);
font-family: 'Poppins', sans-serif;
color: var(--text);
line-height: 1.6;
margin: 0;
min-height: 100vh;
}
.navbar {
background: rgba(255, 255, 255, 0.95);
backdrop-filter: blur(20px);
border-bottom: 1px solid var(--border);
padding: 1rem 0;
}
.navbar-brand {
font-weight: 600;
font-size: 1.25rem;
color: var(--primary) !important;
}
.nav-link {
color: var(--text-muted) !important;
font-weight: 500;
padding: 0.5rem 1rem !important;
border-radius: 8px;
transition: all 0.2s ease;
}
.nav-link:hover, .nav-link.active {
color: var(--primary) !important;
background-color: #f1f5f9;
}
.recovery-container {
max-width: 500px;
margin: 3rem auto;
background: var(--surface);
border-radius: 20px;
box-shadow: 0 20px 40px rgba(0, 0, 0, 0.08);
padding: 3rem;
border: 1px solid var(--border);
position: relative;
overflow: hidden;
}
.recovery-container::before {
content: '';
position: absolute;
top: 0;
left: 0;
right: 0;
height: 4px;
background: linear-gradient(90deg, var(--accent), var(--primary));
}
.recovery-header {
text-align: center;
margin-bottom: 2.5rem;
}
.recovery-icon {
width: 80px;
height: 80px;
background: linear-gradient(135deg, var(--accent), #059669);
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
margin: 0 auto 1.5rem;
color: white;
font-size: 2rem;
}
.recovery-title {
font-size: 1.75rem;
font-weight: 700;
color: var(--text);
margin-bottom: 0.5rem;
}
.recovery-subtitle {
color: var(--text-muted);
font-size: 0.95rem;
}
.form-label {
font-weight: 500;
color: var(--text);
margin-bottom: 0.5rem;
display: flex;
align-items: center;
gap: 0.5rem;
}
.form-control {
border: 2px solid var(--border);
border-radius: 12px;
padding: 0.75rem 1rem;
font-size: 0.95rem;
transition: all 0.2s ease;
background: var(--surface);
}
.form-control:focus {
border-color: var(--accent);
box-shadow: 0 0 0 3px rgba(16, 185, 129, 0.1);
background: var(--surface);
}
.btn-modern {
padding: 12px 28px;
border-radius: 12px;
font-weight: 600;
text-decoration: none;
display: inline-flex;
align-items: center;
justify-content: center;
gap: 8px;
transition: all 0.3s ease;
border: none;
cursor: pointer;
width: 100%;
margin-bottom: 1rem;
}
.btn-primary-modern {
background: linear-gradient(135deg, var(--accent), #059669);
color: white;
box-shadow: 0 4px 15px rgba(16, 185, 129, 0.3);
}
.btn-primary-modern:hover {
transform: translateY(-2px);
box-shadow: 0 8px 25px rgba(16, 185, 129, 0.4);
color: white;
}
.btn-outline-modern {
background: transparent;
color: var(--text-muted);
border: 2px solid var(--border);
}
.btn-outline-modern:hover {
background: var(--background);
color: var(--text);
border-color: var(--accent);
}
.alert-modern {
border-radius: 12px;
border: none;
padding: 1rem 1.25rem;
margin-bottom: 1.5rem;
font-weight: 500;
}
.alert-danger {
background: linear-gradient(135deg, #fef2f2, #fee2e2);
color: #dc2626;
border: 1px solid #fecaca;
}
.alert-success {
background: linear-gradient(135deg, #f0fdf4, #dcfce7);
color: #16a34a;
border: 1px solid #bbf7d0;
}
.info-box {
background: linear-gradient(135deg, #f0f9ff, #e0f2fe);
border: 1px solid #38bdf8;
border-radius: 12px;
padding: 1.5rem;
margin-top: 1.5rem;
color: #0c4a6e;
}
footer {
background: var(--text);
color: #94a3b8;
padding: 2rem 0;
text-align: center;
margin-top: auto;
}
.footer-text {
font-size: 0.9rem;
line-height: 1.5;
}
@media (max-width: 768px) {
.recovery-container {
margin: 2rem 1rem;
padding: 2rem 1.5rem;
}
.recovery-title {
font-size: 1.5rem;
}
}
</style>
</head>
<body>
<!-- Navbar institucional -->
<!-- NAVBAR -->
<nav class="navbar navbar-expand-lg">
<div class="container">
<a class="navbar-brand d-flex align-items-center text-dark" href="#">
<img src="/IMPORTADORES/public/<?= htmlspecialchars($logo) ?>" alt="Logo" class="logo-siih">
| Inicio de Importador
<a class="navbar-brand" href="/IMPORTADORES/">
<i class="fas fa-shipping-fast me-2"></i><?= htmlspecialchars($siglas) ?>
</a>
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarNav">
<span class="navbar-toggler-icon"></span>
@@ -47,112 +264,166 @@ $mensajeMantenimiento = $config['mensaje_mantenimiento'] ?? 'El sistema se encue
<a class="nav-link" href="/IMPORTADORES/">Inicio</a>
</li>
<li class="nav-item">
<a class="nav-link " aria-current="page" href="/IMPORTADORES/registro">Registro</a>
<a class="nav-link" href="/IMPORTADORES/registro">Registro</a>
</li>
<li class="nav-item">
<a class="nav-link active" href="/IMPORTADORES/login">Inicio sesión</a>
<a class="nav-link" href="/IMPORTADORES/login">Iniciar Sesión</a>
</li>
</ul>
</div>
</div>
</nav>
<?php if ($mantenimiento): ?>
<div class="container mt-4">
<div class="alert alert-danger text-center" role="alert">
<i class="fas fa-triangle-exclamation me-2"></i>
<?= htmlspecialchars($mensajeMantenimiento) ?>
<!-- CONTENEDOR DE RECUPERACIÓN -->
<div class="recovery-container">
<div class="recovery-header">
<div class="recovery-icon">
<i class="fas fa-key"></i>
</div>
<h1 class="recovery-title">Recuperar Contraseña</h1>
<p class="recovery-subtitle">Ingresa tu correo electrónico y te enviaremos un código de recuperación</p>
</div>
<?php endif; ?>
<!-- Overlay (fondo semitransparente) que cubre la pantalla -->
<div class="overlay"></div>
<!-- Formulario de recuperación de contraseña -->
<div class="login-container">
<h4 class="mb-4 text-center text-dark">Recuperar Contraseña</h4>
<form id="formRecuperar" novalidate>
<div class="mb-3">
<label class="form-label" for="emailInput">Ingresa tu correo</label>
<input type="email" id="emailInput" name="email" class="form-control" required>
<!-- MENSAJES -->
<?php if (isset($_SESSION['error_message'])): ?>
<div class="alert-modern alert-danger">
<i class="fas fa-exclamation-triangle me-2"></i>
<?= $_SESSION['error_message'] ?>
</div>
<button type="submit" class="btn btn-primary w-100">Enviar código</button>
<?php unset($_SESSION['error_message']); ?>
<?php endif; ?>
<?php if (isset($_SESSION['success_message'])): ?>
<div class="alert-modern alert-success">
<i class="fas fa-check-circle me-2"></i>
<?= $_SESSION['success_message'] ?>
</div>
<?php unset($_SESSION['success_message']); ?>
<?php endif; ?>
<form action="/IMPORTADORES/login/enviar_codigo" method="POST" id="recoveryForm">
<div class="mb-4">
<label for="email" class="form-label">
<i class="fas fa-envelope text-muted"></i>
Correo electrónico registrado
</label>
<input type="email" name="email" id="email" class="form-control"
required autocomplete="username"
placeholder="tu@empresa.com">
</div>
<button type="submit" class="btn-modern btn-primary-modern" id="submitBtn">
<i class="fas fa-paper-plane"></i>
Enviar Código de Recuperación
</button>
<a href="/IMPORTADORES/login" class="btn-modern btn-outline-modern">
<i class="fas fa-arrow-left"></i>
Volver al Login
</a>
</form>
<?php if (isset($_SESSION['recuperar_error'])): ?>
<div class="text-danger mt-3 text-center"><?= htmlspecialchars($_SESSION['recuperar_error']) ?></div>
<?php unset($_SESSION['recuperar_error']); ?>
<?php endif; ?>
<?php if (isset($_SESSION['recuperar_exito'])): ?>
<div class="text-success mt-3 text-center"><?= htmlspecialchars($_SESSION['recuperar_exito']) ?></div>
<?php unset($_SESSION['recuperar_exito']); ?>
<?php endif; ?>
<div id="mensaje" class="mt-3 text-center fw-bold"></div>
<div class="info-box">
<div class="d-flex align-items-start">
<i class="fas fa-info-circle me-2 mt-1"></i>
<div>
<strong>¿Cómo funciona?</strong><br>
<small>
1. Ingresa tu correo electrónico registrado<br>
2. Revisa tu bandeja de entrada (y spam)<br>
3. Usa el código para crear una nueva contraseña<br>
4. Accede con tu nueva contraseña
</small>
</div>
</div>
</div>
</div>
<!-- FOOTER -->
<footer class="fixed-bottom">
&copy; <?= date('Y') ?> <?= htmlspecialchars($siglas) ?> · <?= htmlspecialchars($nombre) ?>
<footer>
<div class="container text-center">
<div class="footer-text">
&copy; <?= date('Y') ?> <?= htmlspecialchars($siglas) ?> - <?= htmlspecialchars($nombre) ?><br>
Recuperación segura de contraseña
</div>
</div>
</footer>
<script>
document.addEventListener('DOMContentLoaded', function () {
const form = document.getElementById('formRecuperar');
const mensaje = document.getElementById('mensaje');
const inputCorreo = form.querySelector('input[name="email"]');
inputCorreo.addEventListener('input', () => {
mensaje.innerHTML = '';
mensaje.classList.remove("text-danger", "text-success");
});
form.addEventListener('submit', function(e) {
e.preventDefault();
const emailValue = inputCorreo.value.trim();
if (!emailValue) {
mensaje.textContent = "❌ Por favor, ingresa un correo válido.";
mensaje.classList.add("text-danger");
return;
document.getElementById('recoveryForm').addEventListener('submit', function(e) {
e.preventDefault();
const submitBtn = document.getElementById('submitBtn');
const email = document.getElementById('email').value.trim();
// Validar email
const emailRegex = /^[a-zA-Z0-9._+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$/;
if (!email || !emailRegex.test(email)) {
showAlert('error', 'Por favor ingresa un correo electrónico válido');
return;
}
// Deshabilitar botón y mostrar loading
submitBtn.disabled = true;
submitBtn.innerHTML = '<i class="fas fa-spinner fa-spin"></i> Enviando...';
// Enviar solicitud AJAX
fetch('/IMPORTADORES/login/enviar_codigo', {
method: 'POST',
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
},
body: 'email=' + encodeURIComponent(email)
})
.then(response => response.json())
.then(data => {
if (data.success) {
// Éxito - redirigir a verificar código
showAlert('success', data.message);
setTimeout(() => {
window.location.href = '/IMPORTADORES/login/verificar_codigo';
}, 2000);
} else {
// Error - mostrar mensaje
showAlert('error', data.message);
// Rehabilitar botón
submitBtn.disabled = false;
submitBtn.innerHTML = '<i class="fas fa-paper-plane"></i> Enviar Código de Recuperación';
}
const formData = new FormData();
formData.append('email', emailValue);
fetch('/IMPORTADORES/login/enviarCodigo', {
method: 'POST',
body: formData
})
.then(response => response.json())
.then(data => {
// Esperamos que el backend devuelva JSON con { success: true/false, message: "..." }
if (data.success) {
mensaje.textContent = data.message;
mensaje.classList.remove("text-danger");
mensaje.classList.add("text-success");
// Opcional: redirigir a la vista de verificación de código
setTimeout(() => {
window.location.href = '/IMPORTADORES/login/verificarCodigoVista';
}, 2000);
} else {
mensaje.textContent = "❌ " + data.message;
mensaje.classList.remove("text-success");
mensaje.classList.add("text-danger");
}
})
.catch(() => {
mensaje.textContent = "❌ Error de red.";
mensaje.classList.remove("text-success");
mensaje.classList.add("text-danger");
});
})
.catch(error => {
console.error('Error:', error);
showAlert('error', 'Error de conexión. Intenta nuevamente.');
// Rehabilitar botón
submitBtn.disabled = false;
submitBtn.innerHTML = '<i class="fas fa-paper-plane"></i> Enviar Código de Recuperación';
});
});
function showAlert(type, message) {
// Remover alertas anteriores
const existingAlerts = document.querySelectorAll('.alert-modern');
existingAlerts.forEach(alert => alert.remove());
// Crear nueva alerta
const alertDiv = document.createElement('div');
alertDiv.className = `alert-modern alert-${type === 'success' ? 'success' : 'danger'}`;
alertDiv.innerHTML = `
<i class="fas fa-${type === 'success' ? 'check-circle' : 'exclamation-triangle'} me-2"></i>
${message}
`;
// Insertar antes del formulario
const form = document.getElementById('recoveryForm');
form.parentNode.insertBefore(alertDiv, form);
// Auto-remover después de 5 segundos si es error
if (type === 'error') {
setTimeout(() => {
alertDiv.remove();
}, 5000);
}
}
</script>
</body>
</html>

View File

@@ -1,11 +1,17 @@
<?php
$nombre = $config['nombre_plataforma'] ?? 'Sistema Integral para Importadores de Hidrocarburos';
$siglas = $config['siglas'] ?? 'SIIH';
$logo = $config['logo_url'] ?? 'assets/img/logo_siih.png';
$color1 = explode(',', $config['colores_primarios'] ?? '#003366,#0055A5')[0];
$color2 = explode(',', $config['colores_primarios'] ?? '#003366,#0055A5')[1];
$mantenimiento = $config['modo_mantenimiento'] ?? 0;
$mensajeMantenimiento = $config['mensaje_mantenimiento'] ?? 'El sistema se encuentra en mantenimiento temporal. Por favor, vuelve más tarde.';
require_once __DIR__ . '/../../config/database.php';
$conn = getConnection();
$sql = "SELECT TOP 1 * FROM configuracion_sistema";
$stmt = sqlsrv_query($conn, $sql);
$config = sqlsrv_fetch_array($stmt, SQLSRV_FETCH_ASSOC);
// Variables con valores por defecto - Branding moderno
$nombre = $config['nombre_plataforma'] ?? 'Sistema de Gestión de Importaciones';
$siglas = $config['siglas'] ?? 'AduanaSoft';
$logo = $config['logo_url'] ?? 'assets/img/logo_aduanasoft.png';
$color1 = explode(',', $config['colores_primarios'] ?? '#0f172a,#334155')[0];
$color2 = explode(',', $config['colores_primarios'] ?? '#0f172a,#334155')[1];
if (!isset($_SESSION['email_recuperacion'])) {
// Si no hay correo en sesión, redirige a solicitar código
@@ -18,60 +24,271 @@ if (!isset($_SESSION['email_recuperacion'])) {
<html lang="es">
<head>
<meta charset="UTF-8">
<title><?= htmlspecialchars($siglas) ?> | Recuperación de Contraseña</title>
<title><?= htmlspecialchars($siglas) ?> | Verificar Código</title>
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta name="description" content="Verifica el código de recuperación enviado a tu correo">
<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://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.0/css/all.min.css" rel="stylesheet">
<link href="https://fonts.googleapis.com/css2?family=Poppins:wght@300;400;500;600;700&display=swap" rel="stylesheet">
<style>
body { background-color: #f4f6f9; font-family: 'Segoe UI', sans-serif; position: relative; min-height: 100vh; }
.navbar { background-color: #fff; }
.navbar .nav-link, .navbar-brand { color: <?= $color1 ?> !important; font-weight: 500; }
.logo-siih { height: 90px; margin-right: 10px; }
.login-container { max-width: 420px; margin: 80px auto; background: white; padding: 40px; border-radius: 12px; box-shadow: 0 10px 25px rgba(0, 0, 0, 0.08); z-index: 10; position: relative; }
.form-control { border-radius: 6px; transition: all 0.3s ease; }
.form-control.error { border-color: #dc3545; background-color: #fff5f5; animation: shake 0.5s ease-in-out; }
.form-control.success { border-color: #28a745; background-color: #f8fff9; }
footer { background-color: #e9ecef; padding: 20px; text-align: center; font-size: 14px; color: #666; margin-top: 60px; }
/* Contenedor de fondo semitransparente para las vistas */
.overlay { position: fixed; top: 0; left: 0; width: 100%; height: 100%; background: rgba(0, 0, 0, 0.5); z-index: 5; }
#mensaje { transition: all 0.3s ease; min-height: 24px; }
#btnReenviarRespaldo { background: none; border: none; color: <?= $color1 ?>; text-decoration: underline; cursor: pointer; font-size: 14px; padding: 0; transition: color 0.3s ease; }
#btnReenviarRespaldo:hover { color: <?= $color2 ?>; }
#btnReenviarRespaldo:disabled { color: #ccc; cursor: not-allowed; text-decoration: none; }
.btn:disabled { opacity: 0.6; cursor: not-allowed; }
.loading { position: relative; pointer-events: none; }
.loading::after { content: ''; position: absolute; top: 50%; left: 50%; width: 20px; height: 20px; border: 2px solid transparent; border-top: 2px solid #fff; border-radius: 50%; animation: spin 1s linear infinite; transform: translate(-50%, -50%); }
@keyframes shake {
0%, 100% { transform: translateX(0); }
25% { transform: translateX(-5px); }
75% { transform: translateX(5px); }
:root {
--primary: <?= $color1 ?>;
--secondary: <?= $color2 ?>;
--accent: #10b981;
--surface: #ffffff;
--background: #f8fafc;
--text: #1e293b;
--text-muted: #64748b;
--border: #e2e8f0;
--error: #ef4444;
}
@keyframes spin {
0% { transform: translate(-50%, -50%) rotate(0deg); }
100% { transform: translate(-50%, -50%) rotate(360deg); }
* {
box-sizing: border-box;
}
@keyframes fadeIn {
from { opacity: 0; transform: translateY(-10px); }
to { opacity: 1; transform: translateY(0); }
body {
background: linear-gradient(135deg, var(--background) 0%, #e2e8f0 100%);
font-family: 'Poppins', sans-serif;
color: var(--text);
line-height: 1.6;
margin: 0;
min-height: 100vh;
}
.navbar {
background: rgba(255, 255, 255, 0.95);
backdrop-filter: blur(20px);
border-bottom: 1px solid var(--border);
padding: 1rem 0;
}
.navbar-brand {
font-weight: 600;
font-size: 1.25rem;
color: var(--primary) !important;
}
.nav-link {
color: var(--text-muted) !important;
font-weight: 500;
padding: 0.5rem 1rem !important;
border-radius: 8px;
transition: all 0.2s ease;
}
.nav-link:hover {
color: var(--primary) !important;
background-color: #f1f5f9;
}
.verification-container {
max-width: 500px;
margin: 3rem auto;
background: var(--surface);
border-radius: 20px;
box-shadow: 0 20px 40px rgba(0, 0, 0, 0.08);
padding: 3rem;
border: 1px solid var(--border);
position: relative;
overflow: hidden;
}
.verification-container::before {
content: '';
position: absolute;
top: 0;
left: 0;
right: 0;
height: 4px;
background: linear-gradient(90deg, var(--accent), var(--primary));
}
.verification-header {
text-align: center;
margin-bottom: 2.5rem;
}
.verification-icon {
width: 80px;
height: 80px;
background: linear-gradient(135deg, #3b82f6, #1d4ed8);
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
margin: 0 auto 1.5rem;
color: white;
font-size: 2rem;
}
.verification-title {
font-size: 1.75rem;
font-weight: 700;
color: var(--text);
margin-bottom: 0.5rem;
}
.verification-subtitle {
color: var(--text-muted);
font-size: 0.95rem;
line-height: 1.5;
}
.form-label {
font-weight: 500;
color: var(--text);
margin-bottom: 0.5rem;
display: flex;
align-items: center;
gap: 0.5rem;
}
.form-control {
border: 2px solid var(--border);
border-radius: 12px;
padding: 0.75rem 1rem;
font-size: 0.95rem;
transition: all 0.2s ease;
background: var(--surface);
}
.form-control:focus {
border-color: var(--accent);
box-shadow: 0 0 0 3px rgba(16, 185, 129, 0.1);
background: var(--surface);
}
.code-input {
text-align: center;
font-size: 1.5rem;
font-weight: 600;
letter-spacing: 0.5rem;
font-family: 'Courier New', monospace;
}
.btn-modern {
padding: 12px 28px;
border-radius: 12px;
font-weight: 600;
text-decoration: none;
display: inline-flex;
align-items: center;
justify-content: center;
gap: 8px;
transition: all 0.3s ease;
border: none;
cursor: pointer;
width: 100%;
margin-bottom: 1rem;
}
.btn-primary-modern {
background: linear-gradient(135deg, var(--accent), #059669);
color: white;
box-shadow: 0 4px 15px rgba(16, 185, 129, 0.3);
}
.btn-primary-modern:hover {
transform: translateY(-2px);
box-shadow: 0 8px 25px rgba(16, 185, 129, 0.4);
color: white;
}
.btn-outline-modern {
background: transparent;
color: var(--text-muted);
border: 2px solid var(--border);
}
.btn-outline-modern:hover {
background: var(--background);
color: var(--text);
border-color: var(--accent);
}
.alert-modern {
border-radius: 12px;
border: none;
padding: 1rem 1.25rem;
margin-bottom: 1.5rem;
font-weight: 500;
}
.alert-danger {
background: linear-gradient(135deg, #fef2f2, #fee2e2);
color: #dc2626;
border: 1px solid #fecaca;
}
.alert-success {
background: linear-gradient(135deg, #f0fdf4, #dcfce7);
color: #16a34a;
border: 1px solid #bbf7d0;
}
.resend-section {
text-align: center;
margin-top: 1.5rem;
padding-top: 1.5rem;
border-top: 2px solid var(--border);
}
.resend-text {
color: var(--text-muted);
font-size: 0.9rem;
margin-bottom: 1rem;
}
.resend-link {
color: var(--accent);
text-decoration: none;
font-weight: 500;
transition: color 0.2s ease;
}
.resend-link:hover {
color: #059669;
}
footer {
background: var(--text);
color: #94a3b8;
padding: 2rem 0;
text-align: center;
margin-top: auto;
}
.footer-text {
font-size: 0.9rem;
line-height: 1.5;
}
@media (max-width: 768px) {
.verification-container {
margin: 2rem 1rem;
padding: 2rem 1.5rem;
}
.verification-title {
font-size: 1.5rem;
}
.code-input {
font-size: 1.25rem;
letter-spacing: 0.25rem;
}
}
.fade-in { animation: fadeIn 0.3s ease-out; }
@media (max-height: 600px) { footer { position: static !important; } }
/* Estilos para input de código */
#codigo { font-size: 18px; letter-spacing: 3px; text-align: center; font-weight: bold; }
.input-group { position: relative; }
.clear-input { position: absolute; right: 10px; top: 50%; transform: translateY(-50%); background: none; border: none; color: #ccc; cursor: pointer; z-index: 5; display: none; }
.clear-input:hover { color: #666; }
</style>
</head>
<body>
<!-- Navbar institucional -->
<!-- NAVBAR -->
<nav class="navbar navbar-expand-lg">
<div class="container">
<a class="navbar-brand d-flex align-items-center text-dark" href="#">
<img src="/IMPORTADORES/public/<?= htmlspecialchars($logo) ?>" alt="Logo" class="logo-siih">
| Inicio de Importador
<a class="navbar-brand" href="/IMPORTADORES/">
<i class="fas fa-shipping-fast me-2"></i><?= htmlspecialchars($siglas) ?>
</a>
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarNav">
<span class="navbar-toggler-icon"></span>
@@ -82,387 +299,229 @@ if (!isset($_SESSION['email_recuperacion'])) {
<a class="nav-link" href="/IMPORTADORES/">Inicio</a>
</li>
<li class="nav-item">
<a class="nav-link " aria-current="page" href="/IMPORTADORES/registro">Registro</a>
<a class="nav-link" href="/IMPORTADORES/registro">Registro</a>
</li>
<li class="nav-item">
<a class="nav-link active" href="/IMPORTADORES/login">Inicio sesión</a>
<a class="nav-link" href="/IMPORTADORES/login">Iniciar Sesión</a>
</li>
</ul>
</div>
</div>
</nav>
<?php if ($mantenimiento): ?>
<div class="container mt-4">
<div class="alert alert-danger text-center" role="alert">
<i class="fas fa-triangle-exclamation me-2"></i>
<?= htmlspecialchars($mensajeMantenimiento) ?>
<!-- CONTENEDOR DE VERIFICACIÓN -->
<div class="verification-container">
<div class="verification-header">
<div class="verification-icon">
<i class="fas fa-shield-alt"></i>
</div>
<h1 class="verification-title">Verificar Código</h1>
<p class="verification-subtitle">
Hemos enviado un código de 6 dígitos a tu correo electrónico.<br>
Ingresa el código para continuar con la recuperación.
</p>
</div>
<?php endif; ?>
<!-- Overlay (fondo semitransparente) que cubre la pantalla -->
<div class="overlay"></div>
<!-- MENSAJES -->
<?php if (isset($_SESSION['error_message'])): ?>
<div class="alert-modern alert-danger">
<i class="fas fa-exclamation-triangle me-2"></i>
<?= $_SESSION['error_message'] ?>
</div>
<?php unset($_SESSION['error_message']); ?>
<?php endif; ?>
<!-- Formulario de validación de código -->
<div class="login-container">
<h4 class="mb-4 text-center text-dark">Verifica tu código</h4>
<p class="text-muted text-center mb-4">
<i class="fas fa-envelope me-2"></i>
Hemos enviado un código de 6 dígitos a tu correo electrónico
</p>
<!-- Formulario para verificar el código -->
<form id="formCodigo" action="/IMPORTADORES/login/verificarCodigo" method="POST">
<div class="mb-3">
<label class="form-label" for="codigo">
<i class="fas fa-key me-2"></i>Código de verificación
<?php if (isset($_SESSION['success_message'])): ?>
<div class="alert-modern alert-success">
<i class="fas fa-check-circle me-2"></i>
<?= $_SESSION['success_message'] ?>
</div>
<?php unset($_SESSION['success_message']); ?>
<?php endif; ?>
<form action="/IMPORTADORES/login/verificar_codigo_recuperacion" method="POST" id="verificationForm">
<div class="mb-4">
<label for="codigo" class="form-label">
<i class="fas fa-key text-muted"></i>
Código de verificación
</label>
<div class="input-group">
<input type="text" id="codigo" name="codigo" class="form-control" required pattern="\d{6}" maxlength="6" placeholder="000000" autocomplete="off" inputmode="numeric">
<button type="button" class="clear-input" id="clearCode" title="Limpiar código">
<i class="fas fa-times"></i>
</button>
</div>
<div class="form-text">
<input type="text" name="codigo" id="codigo" class="form-control code-input"
required maxlength="6" minlength="6"
placeholder="000000"
pattern="[0-9]{6}"
autocomplete="one-time-code">
<small class="form-text text-muted mt-2">
<i class="fas fa-info-circle me-1"></i>
Ingresa el código de 6 dígitos que recibiste por correo
</div>
</small>
</div>
<button type="submit" id="btnVerificar" class="btn btn-success w-100">
<i class="fas fa-check me-2"></i>Verificar código
<button type="submit" class="btn-modern btn-primary-modern" id="submitBtn">
<i class="fas fa-check"></i>
Verificar Código
</button>
<!-- Botón para enviar el código al correo de respaldo-->
<div class="text-center mt-3">
<button type="button" id="btnReenviarRespaldo" title="Enviar código al correo de respaldo">
<i class="fas fa-paper-plane me-1"></i>
¿No tienes acceso al correo? Reenviar al correo de respaldo
</button>
</div>
<a href="/IMPORTADORES/login/recuperar" class="btn-modern btn-outline-modern">
<i class="fas fa-arrow-left"></i>
Volver
</a>
</form>
<!-- Mostrar mensajes -->
<div id="mensaje" class="mt-3 text-center fw-bold">
<?php if (isset($_SESSION['codigo_error'])): ?>
<div class="text-danger fade-in">
<i class="fas fa-exclamation-triangle me-2"></i>
<?= $_SESSION['codigo_error'] ?>
</div>
<?php unset($_SESSION['codigo_error']); ?>
<?php elseif (isset($_SESSION['codigo_exito'])): ?>
<div class="text-success fade-in">
<i class="fas fa-check-circle me-2"></i>
<?= $_SESSION['codigo_exito'] ?>
</div>
<?php unset($_SESSION['codigo_exito']); ?>
<?php endif; ?>
</div>
<!-- Contador de intentos -->
<div id="intentosInfo" class="mt-2 text-center text-muted" style="font-size: 12px;">
<!-- Se llenará dinámicamente -->
<div class="resend-section">
<div class="resend-text">
¿No recibiste el código?
</div>
<a href="#" class="resend-link" id="resendLink">
<i class="fas fa-redo me-1"></i>
Reenviar código
</a>
</div>
</div>
<!-- FOOTER -->
<footer class="fixed-bottom">
&copy; <?= date('Y') ?> <?= htmlspecialchars($siglas) ?> · <?= htmlspecialchars($nombre) ?>
<footer>
<div class="container text-center">
<div class="footer-text">
&copy; <?= date('Y') ?> <?= htmlspecialchars($siglas) ?> - <?= htmlspecialchars($nombre) ?><br>
Verificación segura de identidad
</div>
</div>
</footer>
<script>
document.addEventListener('DOMContentLoaded', function () {
const form = document.getElementById('formCodigo');
const mensaje = document.getElementById('mensaje');
const inputCodigo = document.getElementById('codigo');
const btnVerificar = document.getElementById('btnVerificar');
const btnReenviar = document.getElementById('btnReenviarRespaldo');
const clearBtn = document.getElementById('clearCode');
const intentosInfo = document.getElementById('intentosInfo');
// Auto-format del código
document.getElementById('codigo').addEventListener('input', function(e) {
let value = e.target.value.replace(/\D/g, ''); // Solo números
if (value.length > 6) value = value.substring(0, 6);
e.target.value = value;
});
let intentosRealizados = 0;
const maxIntentos = 5;
// Focus automático al cargar
document.addEventListener('DOMContentLoaded', function() {
document.getElementById('codigo').focus();
});
// Auto-focus en el input al cargar
inputCodigo.focus();
// Mostrar/ocultar botón de limpiar
inputCodigo.addEventListener('input', function() {
clearBtn.style.display = this.value.length > 0 ? 'block' : 'none';
// Limpiar mensajes previos
limpiarMensajes();
// Remover clases de error/éxito
inputCodigo.classList.remove('error', 'success');
// Solo permitir números
this.value = this.value.replace(/[^0-9]/g, '');
// Actualizar contador visual
actualizarContadorVisual();
});
// Limpiar input
clearBtn.addEventListener('click', function() {
inputCodigo.value = '';
inputCodigo.focus();
clearBtn.style.display = 'none';
limpiarMensajes();
inputCodigo.classList.remove('error', 'success');
});
// Permitir solo números en tiempo real
inputCodigo.addEventListener('keypress', function(e) {
if (!/[0-9]/.test(e.key) && !['Backspace', 'Delete', 'Tab', 'Enter'].includes(e.key)) {
e.preventDefault();
}
});
// Auto-envío cuando se completen 6 dígitos (opcional)
inputCodigo.addEventListener('input', function() {
if (this.value.length === 6) {
// Opcional: enviar automáticamente después de un breve delay
// setTimeout(() => form.dispatchEvent(new Event('submit')), 500);
}
});
// Manejo del formulario
form.addEventListener('submit', function(e) {
e.preventDefault();
const codigo = inputCodigo.value.trim();
if (!validarCodigo(codigo)) return;
enviarCodigo(codigo);
});
// Validación del código
function validarCodigo(codigo) {
if (!codigo) {
mostrarError("❌ Por favor, ingresa el código.");
return false;
}
if (codigo.length !== 6) {
mostrarError("❌ El código debe tener exactamente 6 dígitos.");
inputCodigo.classList.add('error');
return false;
}
if (!/^\d{6}$/.test(codigo)) {
mostrarError("❌ El código solo debe contener números.");
inputCodigo.classList.add('error');
return false;
}
return true;
// Manejar envío del formulario
document.getElementById('verificationForm').addEventListener('submit', function(e) {
e.preventDefault();
const submitBtn = document.getElementById('submitBtn');
const codigo = document.getElementById('codigo').value.trim();
// Validar código
if (!codigo || codigo.length !== 6 || !/^\d{6}$/.test(codigo)) {
showAlert('error', 'El código debe tener exactamente 6 dígitos');
return;
}
// Enviar código para verificación
function enviarCodigo(codigo) {
// Mostrar estado de carga
btnVerificar.disabled = true;
btnVerificar.classList.add('loading');
btnVerificar.innerHTML = '<span style="opacity: 0;">Verificando...</span>';
const formData = new FormData();
formData.append('codigo', codigo);
fetch('/IMPORTADORES/login/verificarCodigo', {
method: 'POST',
body: formData,
credentials: 'same-origin'
})
.then(response => response.json())
.then(data => {
if (data.blocked) {
mostrarError(data.message); // <-- función personalizada con un div bonito
setTimeout(() => {
window.location.href = data.redirect;
}, 4000);
}
if (data.success) {
// Código correcto
mostrarExito(data.message);
inputCodigo.classList.add('success');
inputCodigo.disabled = true;
// Redirigir al formulario de cambio de contraseña
setTimeout(() => {
window.location.href = '/IMPORTADORES/login/cambiarPasswordVista';
}, 1500);
} else {
// Código incorrecto
manejarCodigoIncorrecto(data);
}
})
.catch(error => {
console.error('Error:', error);
mostrarError("❌ Error de conexión. Intenta nuevamente.");
inputCodigo.classList.add('error');
limpiarYEnfocarInput();
})
.finally(() => {
// Restaurar botón
btnVerificar.disabled = false;
btnVerificar.classList.remove('loading');
btnVerificar.innerHTML = '<i class="fas fa-check me-2"></i>Verificar código';
});
}
// Mostrar error de cuenta bloqueada
function mostrarError(mensaje) {
const mensajeDiv = document.getElementById('mensaje');
mensajeDiv.innerHTML = `
<div class="text-danger fade-in">
<i class="fas fa-exclamation-triangle me-2"></i> ${mensaje}
</div>
`;
}
// Manejar código incorrecto
function manejarCodigoIncorrecto(data) {
intentosRealizados++;
mostrarError(data.message);
inputCodigo.classList.add('error');
// Limpiar input y enfocar para nuevo intento
limpiarYEnfocarInput();
// Actualizar contador de intentos
actualizarContadorIntentos();
// Si se bloqueó el código
if (data.blocked) {
bloquearFormulario();
}
}
// Limpiar input y enfocar
function limpiarYEnfocarInput() {
setTimeout(() => {
inputCodigo.value = '';
inputCodigo.classList.remove('error');
inputCodigo.focus();
clearBtn.style.display = 'none';
}, 1500); // Esperar 1.5 segundos antes de limpiar
}
// Actualizar contador visual
function actualizarContadorVisual() {
const longitud = inputCodigo.value.length;
if (longitud > 0) {
inputCodigo.setAttribute('placeholder', '0'.repeat(6 - longitud) + inputCodigo.value);
} else {
inputCodigo.setAttribute('placeholder', '000000');
}
}
// Actualizar contador de intentos
function actualizarContadorIntentos() {
if (intentosRealizados > 0) {
const restantes = maxIntentos - intentosRealizados;
intentosInfo.innerHTML = `
<i class="fas fa-exclamation-triangle text-warning me-1"></i>
Intentos restantes: <strong>${restantes}</strong> de ${maxIntentos}
`;
intentosInfo.classList.add('fade-in');
}
}
// Bloquear formulario cuando se exceden intentos
function bloquearFormulario() {
inputCodigo.disabled = true;
btnVerificar.disabled = true;
btnReenviar.disabled = true;
intentosInfo.innerHTML = `
<i class="fas fa-ban text-danger me-1"></i>
<span class="text-danger">Código bloqueado por seguridad</span>
`;
// Redirigir a solicitar nuevo código después de 5 segundos
setTimeout(() => {
window.location.href = '/IMPORTADORES/login/recuperar';
}, 5000);
}
// Funciones de utilidad para mensajes
function mostrarMensaje(texto, tipo) {
mensaje.innerHTML = `<div class="${tipo} fade-in">${texto}</div>`;
mensaje.scrollIntoView({ behavior: 'smooth', block: 'nearest' });
}
function mostrarError(texto) {
mostrarMensaje(`<i class="fas fa-exclamation-triangle me-2"></i>${texto}`, 'text-danger');
}
function mostrarExito(texto) {
mostrarMensaje(`<i class="fas fa-check-circle me-2"></i>${texto}`, 'text-success');
}
function mostrarInfo(texto) {
mostrarMensaje(`<i class="fas fa-info-circle me-2"></i>${texto}`, 'text-info');
}
function limpiarMensajes() {
mensaje.innerHTML = '';
}
// Manejo del botón de reenvío
btnReenviar.addEventListener("click", function() {
const btnOriginalText = this.innerHTML;
// Deshabilitar botón temporalmente
this.disabled = true;
this.innerHTML = '<i class="fas fa-spinner fa-spin me-1"></i>Enviando...';
fetch("/IMPORTADORES/login/reenviarCodigo", {
method: "POST",
headers: {'Content-Type': 'application/x-www-form-urlencoded'},
body: "accion=reenviarCodigo"
})
.then(response => response.json())
.then(data => {
mostrarInfo(data.message);
// Habilitar botón después de 30 segundos
// Deshabilitar botón y mostrar loading
submitBtn.disabled = true;
submitBtn.innerHTML = '<i class="fas fa-spinner fa-spin"></i> Verificando...';
// Enviar solicitud AJAX
fetch('/IMPORTADORES/login/verificar_codigo_recuperacion', {
method: 'POST',
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
},
body: 'codigo=' + encodeURIComponent(codigo)
})
.then(response => response.json())
.then(data => {
if (data.success) {
// Éxito - redirigir a cambiar contraseña
showAlert('success', data.message);
setTimeout(() => {
this.disabled = false;
this.innerHTML = btnOriginalText;
}, 30000);
})
.catch(error => {
console.error('Error:', error);
mostrarError("❌ Error al reenviar código.");
window.location.href = '/IMPORTADORES/login/cambiar_password';
}, 1500);
} else {
// Error - mostrar mensaje
showAlert('error', data.message);
// Rehabilitar botón
submitBtn.disabled = false;
submitBtn.innerHTML = '<i class="fas fa-check"></i> Verificar Código';
// Restaurar botón en caso de error
this.disabled = false;
this.innerHTML = btnOriginalText;
});
});
// Manejar tecla Enter en cualquier parte del formulario
document.addEventListener('keypress', function(e) {
if (e.key === 'Enter' && !btnVerificar.disabled) {
form.dispatchEvent(new Event('submit'));
// Si hay bloqueo, redirigir
if (data.blocked) {
setTimeout(() => {
window.location.href = data.redirect || '/IMPORTADORES/login';
}, 3000);
}
}
});
// Prevenir paste de contenido no numérico
inputCodigo.addEventListener('paste', function(e) {
e.preventDefault();
const paste = (e.clipboardData || window.clipboardData).getData('text');
const numericPaste = paste.replace(/[^0-9]/g, '').substring(0, 6);
this.value = numericPaste;
this.dispatchEvent(new Event('input'));
})
.catch(error => {
console.error('Error:', error);
showAlert('error', 'Error de conexión. Intenta nuevamente.');
// Rehabilitar botón
submitBtn.disabled = false;
submitBtn.innerHTML = '<i class="fas fa-check"></i> Verificar Código';
});
});
// Manejar reenvío de código
document.getElementById('resendLink').addEventListener('click', function(e) {
e.preventDefault();
const link = this;
const originalText = link.innerHTML;
// Mostrar loading
link.innerHTML = '<i class="fas fa-spinner fa-spin me-1"></i> Reenviando...';
link.style.pointerEvents = 'none';
// Enviar solicitud AJAX
fetch('/IMPORTADORES/login/reenviar_codigo', {
method: 'POST',
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
}
})
.then(response => response.json())
.then(data => {
if (data.success) {
showAlert('success', data.message);
} else {
showAlert('error', data.message);
}
// Restaurar link
link.innerHTML = originalText;
link.style.pointerEvents = 'auto';
})
.catch(error => {
console.error('Error:', error);
showAlert('error', 'Error al reenviar código. Intenta nuevamente.');
// Restaurar link
link.innerHTML = originalText;
link.style.pointerEvents = 'auto';
});
});
function showAlert(type, message) {
// Remover alertas anteriores
const existingAlerts = document.querySelectorAll('.alert-modern');
existingAlerts.forEach(alert => alert.remove());
// Crear nueva alerta
const alertDiv = document.createElement('div');
alertDiv.className = `alert-modern alert-${type === 'success' ? 'success' : 'danger'}`;
alertDiv.innerHTML = `
<i class="fas fa-${type === 'success' ? 'check-circle' : 'exclamation-triangle'} me-2"></i>
${message}
`;
// Insertar antes del formulario
const form = document.getElementById('verificationForm');
form.parentNode.insertBefore(alertDiv, form);
// Auto-remover después de 5 segundos si es error
if (type === 'error') {
setTimeout(() => {
alertDiv.remove();
}, 5000);
}
}
</script>
</body>

701
views/mve/lista.php Normal file
View File

@@ -0,0 +1,701 @@
<?php include __DIR__ . '/../partials/sidebar_importador.php'; ?>
<!DOCTYPE html>
<html lang="es">
<head>
<meta charset="UTF-8">
<title>📄 Manifestación de Valor - 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">
<link href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0/css/all.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">
<script src="https://code.jquery.com/jquery-3.7.0.min.js"></script>
<script src="https://cdn.datatables.net/1.13.4/js/jquery.dataTables.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/sweetalert2@11"></script>
<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;}
@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; }
.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; }
.fade-in-up { animation: fadeInUp 0.8s ease-out; }
@keyframes fadeInUp {
from { opacity: 0; transform: translateY(30px); }
to { opacity: 1; transform: translateY(0); }
}
.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%; }
/* Estilos para facturas seleccionables */
.factura-item {
border: 1px solid #dee2e6;
border-radius: 8px;
padding: 12px;
margin-bottom: 8px;
cursor: pointer;
transition: all 0.3s ease;
background-color: #fff;
}
.factura-item:hover {
border-color: #0d6efd;
box-shadow: 0 2px 8px rgba(13, 110, 253, 0.15);
transform: translateY(-2px);
}
.factura-item.selected {
border-color: #198754;
background-color: #d1e7dd;
box-shadow: 0 3px 10px rgba(25, 135, 84, 0.2);
}
.factura-numero {
font-weight: bold;
color: #0d6efd;
font-size: 1.1em;
}
.factura-item.selected .factura-numero {
color: #198754;
}
.factura-details {
font-size: 0.9em;
color: #6c757d;
margin-top: 4px;
}
.factura-item.selected .factura-details {
color: #495057;
}
</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">📄 Manifestación de Valor - Pedimentos</h4>
</div>
<div class="card p-3 shadow-sm card-hover position-relative h-auto fade-in-up">
<div class="table-responsive">
<table id="tablaMvePedimentos" class="display nowrap" style="width:100%">
<thead>
<tr>
<th>ID</th>
<th>Pedimento</th>
<th>RFC Cliente</th>
<th>Nombre Cliente</th>
<th>Fecha Registro</th>
<th>Estado</th>
<th>Acción</th>
</tr>
</thead>
<tbody>
<!-- Los datos se cargan via AJAX -->
</tbody>
</table>
</div>
</div>
</div>
<!-- Modal para facturas relacionadas -->
<div class="modal fade" id="modalFacturasMVE" tabindex="-1" aria-labelledby="modalFacturasMVELabel" aria-hidden="true">
<div class="modal-dialog modal-xl">
<div class="modal-content">
<div class="modal-header bg-primary text-white">
<h5 class="modal-title" id="modalFacturasMVELabel">Manifestación de Valor - Pedimento <span id="modalPedimento"></span></h5>
<button type="button" class="btn-close btn-close-white" data-bs-dismiss="modal" aria-label="Cerrar"></button>
</div>
<div class="modal-body">
<div class="row">
<div class="col-md-4">
<div class="card">
<div class="card-header bg-info text-white">
<h6 class="mb-0">Facturas del Pedimento</h6>
</div>
<div class="card-body" style="max-height: 400px; overflow-y: auto;">
<div id="facturasRelacionadasContainer">
<!-- Aquí se cargan las facturas -->
</div>
</div>
</div>
</div>
<div class="col-md-8">
<div class="card">
<div class="card-header bg-success text-white d-flex justify-content-between align-items-center">
<h6 class="mb-0">Catálogo COVE - Factura <span id="facturaSeleccionada">Seleccionar factura</span></h6>
<div>
<button class="btn btn-sm btn-outline-light" id="btnArt65">Art. 65 - Incrementables</button>
<button class="btn btn-sm btn-light" id="btnArt66">Art. 66 - Decrementables</button>
</div>
</div>
<div class="card-body" style="max-height: 500px; overflow-y: auto;">
<!-- Campos Art. 65 - Incrementables -->
<div id="art65Section">
<h6 class="text-success fw-bold mb-3">Artículo 65 - Incrementables</h6>
<!-- Gastos realizados por cuenta del Importador -->
<div class="row mb-3">
<div class="col-12">
<label class="form-label fw-bold">Gastos de transporte, seguros y conexos (carga y descarga) erogados con posterioridad:</label>
</div>
<div class="col-md-4">
<label class="form-label">Fecha de erogación:</label>
<input type="date" class="form-control" name="fecha_transporte" id="fecha_transporte">
</div>
<div class="col-md-4">
<label class="form-label">Importe (MN):</label>
<input type="number" step="0.01" class="form-control" name="importe_transporte" id="importe_transporte">
</div>
</div>
<!-- Información de descuentos especiales -->
<div class="row mb-3">
<div class="col-12">
<label class="form-label fw-bold">Información de descuentos especiales para aplicar en el momento de la compraventa:</label>
</div>
<div class="col-md-4">
<label class="form-label">Fecha de erogación:</label>
<input type="date" class="form-control" name="fecha_descuentos" id="fecha_descuentos">
</div>
<div class="col-md-4">
<label class="form-label">Importe (MN):</label>
<input type="number" step="0.01" class="form-control" name="importe_descuentos" id="importe_descuentos">
</div>
</div>
<!-- Gastos posteriores a la importación -->
<div class="row mb-3">
<div class="col-12">
<label class="form-label fw-bold">Gastos posteriores a la importación por construcción, instalación, armado, montaje, mantenimiento o asistencia técnica:</label>
</div>
<div class="col-md-4">
<label class="form-label">Fecha de erogación:</label>
<input type="date" class="form-control" name="fecha_posteriores" id="fecha_posteriores">
</div>
<div class="col-md-4">
<label class="form-label">Importe (MN):</label>
<input type="number" step="0.01" class="form-control" name="importe_posteriores" id="importe_posteriores">
</div>
</div>
<!-- Contribuciones y cuotas compensatorias -->
<div class="row mb-3">
<div class="col-12">
<label class="form-label fw-bold">Contribuciones y cuotas compensatorias aplicables en territorio nacional:</label>
</div>
<div class="col-md-4">
<label class="form-label">Fecha de erogación:</label>
<input type="date" class="form-control" name="fecha_contribuciones" id="fecha_contribuciones">
</div>
<div class="col-md-4">
<label class="form-label">Importe (MN):</label>
<input type="number" step="0.01" class="form-control" name="importe_contribuciones" id="importe_contribuciones">
</div>
</div>
<!-- Pagos de importador al vendedor -->
<div class="row mb-3">
<div class="col-12">
<label class="form-label fw-bold">Pagos de importador al vendedor por dividendos u otros que no guarden relación directa con las mercancías importadas:</label>
</div>
<div class="col-md-4">
<label class="form-label">Fecha de erogación:</label>
<input type="date" class="form-control" name="fecha_pagos_vendedor" id="fecha_pagos_vendedor">
</div>
<div class="col-md-4">
<label class="form-label">Importe (MN):</label>
<input type="number" step="0.01" class="form-control" name="importe_pagos_vendedor" id="importe_pagos_vendedor">
</div>
</div>
</div>
<!-- Campos Art. 66 - Decrementables -->
<div id="art66Section" style="display: none;">
<h6 class="text-primary fw-bold mb-3">Artículo 66 - Decrementables</h6>
<!-- Comisiones y gastos de corretaje -->
<div class="row mb-3">
<div class="col-12">
<label class="form-label fw-bold">Comisiones y gastos de corretaje, excepto comisiones de compra:</label>
</div>
<div class="col-md-3">
<label class="form-label">Fecha de erogación:</label>
<input type="date" class="form-control" name="fecha_comisiones" id="fecha_comisiones">
</div>
<div class="col-md-3">
<label class="form-label">Importe (MN):</label>
<input type="number" step="0.01" class="form-control" name="importe_comisiones" id="importe_comisiones">
</div>
<div class="col-md-3">
<label class="form-label">¿Está a cargo del importador?:</label>
<div class="form-check form-check-inline">
<input class="form-check-input" type="radio" name="cargo_comisiones" id="cargo_comisiones_si" value="Si">
<label class="form-check-label" for="cargo_comisiones_si">Sí</label>
</div>
<div class="form-check form-check-inline">
<input class="form-check-input" type="radio" name="cargo_comisiones" id="cargo_comisiones_no" value="No">
<label class="form-check-label" for="cargo_comisiones_no">No</label>
</div>
</div>
</div>
<!-- Costos de envases -->
<div class="row mb-3">
<div class="col-12">
<label class="form-label fw-bold">Costo de los envases o embalajes que formen un todo con las mercancías:</label>
</div>
<div class="col-md-3">
<label class="form-label">Fecha de erogación:</label>
<input type="date" class="form-control" name="fecha_envases" id="fecha_envases">
</div>
<div class="col-md-3">
<label class="form-label">Importe (MN):</label>
<input type="number" step="0.01" class="form-control" name="importe_envases" id="importe_envases">
</div>
<div class="col-md-3">
<label class="form-label">¿Está a cargo del importador?:</label>
<div class="form-check form-check-inline">
<input class="form-check-input" type="radio" name="cargo_envases" id="cargo_envases_si" value="Si">
<label class="form-check-label" for="cargo_envases_si">Sí</label>
</div>
<div class="form-check form-check-inline">
<input class="form-check-input" type="radio" name="cargo_envases" id="cargo_envases_no" value="No">
<label class="form-check-label" for="cargo_envases_no">No</label>
</div>
</div>
</div>
<!-- Gastos de embalaje -->
<div class="row mb-3">
<div class="col-12">
<label class="form-label fw-bold">Gastos de embalaje (mano de obra y/o materiales):</label>
</div>
<div class="col-md-3">
<label class="form-label">Fecha de erogación:</label>
<input type="date" class="form-control" name="fecha_embalaje" id="fecha_embalaje">
</div>
<div class="col-md-3">
<label class="form-label">Importe (MN):</label>
<input type="number" step="0.01" class="form-control" name="importe_embalaje" id="importe_embalaje">
</div>
<div class="col-md-3">
<label class="form-label">¿Está a cargo del importador?:</label>
<div class="form-check form-check-inline">
<input class="form-check-input" type="radio" name="cargo_embalaje" id="cargo_embalaje_si" value="Si">
<label class="form-check-label" for="cargo_embalaje_si">Sí</label>
</div>
<div class="form-check form-check-inline">
<input class="form-check-input" type="radio" name="cargo_embalaje" id="cargo_embalaje_no" value="No">
<label class="form-check-label" for="cargo_embalaje_no">No</label>
</div>
</div>
</div>
<!-- Gastos de transporte, seguros y conexos -->
<div class="row mb-3">
<div class="col-12">
<label class="form-label fw-bold">Gastos de transporte, seguros y conexos (como manejo, carga y descarga) erogados antes de los momentos señalados en el artículo 56, fracción I de la Ley:</label>
</div>
<div class="col-md-3">
<label class="form-label">Fecha de erogación:</label>
<input type="date" class="form-control" name="fecha_transporte_dec" id="fecha_transporte_dec">
</div>
<div class="col-md-3">
<label class="form-label">Importe (MN):</label>
<input type="number" step="0.01" class="form-control" name="importe_transporte_dec" id="importe_transporte_dec">
</div>
<div class="col-md-3">
<label class="form-label">¿Está a cargo del importador?:</label>
<div class="form-check form-check-inline">
<input class="form-check-input" type="radio" name="cargo_transporte_dec" id="cargo_transporte_dec_si" value="Si">
<label class="form-check-label" for="cargo_transporte_dec_si">Sí</label>
</div>
<div class="form-check form-check-inline">
<input class="form-check-input" type="radio" name="cargo_transporte_dec" id="cargo_transporte_dec_no" value="No">
<label class="form-check-label" for="cargo_transporte_dec_no">No</label>
</div>
</div>
</div>
<!-- Trabajos de ingeniería -->
<div class="row mb-3">
<div class="col-12">
<label class="form-label fw-bold">Trabajos de ingeniería, creación y perfeccionamiento, artísticos, diseños, planos y croquis realizados fuera del territorio nacional que sean necesarios para la producción de las mercancías importadas:</label>
</div>
<div class="col-md-3">
<label class="form-label">Fecha de erogación:</label>
<input type="date" class="form-control" name="fecha_ingenieria" id="fecha_ingenieria">
</div>
<div class="col-md-3">
<label class="form-label">Importe (MN):</label>
<input type="number" step="0.01" class="form-control" name="importe_ingenieria" id="importe_ingenieria">
</div>
<div class="col-md-3">
<label class="form-label">¿Está a cargo del importador?:</label>
<div class="form-check form-check-inline">
<input class="form-check-input" type="radio" name="cargo_ingenieria" id="cargo_ingenieria_si" value="Si">
<label class="form-check-label" for="cargo_ingenieria_si">Sí</label>
</div>
<div class="form-check form-check-inline">
<input class="form-check-input" type="radio" name="cargo_ingenieria" id="cargo_ingenieria_no" value="No">
<label class="form-check-label" for="cargo_ingenieria_no">No</label>
</div>
</div>
</div>
<!-- Regalías y derechos de licencia -->
<div class="row mb-3">
<div class="col-12">
<label class="form-label fw-bold">Las regalías y derechos de licencia a pagar como condición de la venta de las mercancías a importar, que no estén incluidos en el precio pagado:</label>
</div>
<div class="col-md-3">
<label class="form-label">Fecha de erogación:</label>
<input type="date" class="form-control" name="fecha_regalias" id="fecha_regalias">
</div>
<div class="col-md-3">
<label class="form-label">Importe (MN):</label>
<input type="number" step="0.01" class="form-control" name="importe_regalias" id="importe_regalias">
</div>
<div class="col-md-3">
<label class="form-label">¿Está a cargo del importador?:</label>
<div class="form-check form-check-inline">
<input class="form-check-input" type="radio" name="cargo_regalias" id="cargo_regalias_si" value="Si">
<label class="form-check-label" for="cargo_regalias_si">Sí</label>
</div>
<div class="form-check form-check-inline">
<input class="form-check-input" type="radio" name="cargo_regalias" id="cargo_regalias_no" value="No">
<label class="form-check-label" for="cargo_regalias_no">No</label>
</div>
</div>
</div>
<!-- Valor de cualquier parte del producto -->
<div class="row mb-3">
<div class="col-12">
<label class="form-label fw-bold">Valor de cualquier parte del producto de la enajenación, cesión o utilización, posteriores a la importación, que se revierta directa o indirectamente al vendedor:</label>
</div>
<div class="col-md-3">
<label class="form-label">Fecha de erogación:</label>
<input type="date" class="form-control" name="fecha_producto" id="fecha_producto">
</div>
<div class="col-md-3">
<label class="form-label">Importe (MN):</label>
<input type="number" step="0.01" class="form-control" name="importe_producto" id="importe_producto">
</div>
<div class="col-md-3">
<label class="form-label">¿Está a cargo del importador?:</label>
<div class="form-check form-check-inline">
<input class="form-check-input" type="radio" name="cargo_producto" id="cargo_producto_si" value="Si">
<label class="form-check-label" for="cargo_producto_si">Sí</label>
</div>
<div class="form-check form-check-inline">
<input class="form-check-input" type="radio" name="cargo_producto" id="cargo_producto_no" value="No">
<label class="form-check-label" for="cargo_producto_no">No</label>
</div>
</div>
</div>
</div>
<div class="mt-3">
<button type="button" class="btn btn-primary" id="btnGuardarDatosMVE">
<i class="fas fa-save me-1"></i>Guardar datos MVE
</button>
</div>
</div>
</div>
</div>
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Cancelar</button>
<button type="button" class="btn btn-success" id="btnConfirmarMVE">Solicitar Manifestación ahora</button>
</div>
</div>
</div>
</div>
<script>
$(document).ready(function() {
var pedimentoSeleccionado = null;
var facturaSeleccionadaId = null;
var tabla = $('#tablaMvePedimentos').DataTable({
"processing": true,
"serverSide": true,
"searchDelay": 500,
"deferRender": true,
"ajax": {
"url": "/IMPORTADORES/catalogo_pedimentos/ajax_lista",
"type": "GET"
},
"columns": [
{ "data": 0, "name": "IdPrevio" },
{ "data": 1, "name": "Pedimento" },
{ "data": 2, "name": "ClienteRFC" },
{ "data": 3, "name": "ClienteNombre" },
{ "data": 4, "name": "Timestamp" },
{
"data": 5,
"name": "Status",
"render": function(data, type, row) {
if (data === 'Activo') {
return '<span class="badge bg-success">Activo</span>';
} else {
return '<span class="badge bg-secondary">Inactivo</span>';
}
}
},
{
"data": null,
"orderable": false,
"searchable": false,
"render": function(data, type, row) {
const id = row[0];
const pedimento = row[1];
return `<button class='btn btn-info btn-sm btn-animated' onclick='abrirModalFacturasMVE(${JSON.stringify(id)}, ${JSON.stringify(pedimento)})'>
<i class='fas fa-file-invoice-dollar me-1'></i>Solicitar MVE
</button>`;
}
}
],
"language": {
"url": "https://cdn.datatables.net/plug-ins/1.13.4/i18n/es-ES.json"
},
"responsive": true,
"order": [[4, "desc"]],
"pageLength": 25,
"lengthMenu": [[10, 25, 50, 100], [10, 25, 50, 100]]
});
window.abrirModalFacturasMVE = function(id, pedimento) {
pedimentoSeleccionado = id;
$('#modalPedimento').text(pedimento);
$('#facturasRelacionadasContainer').html('<div class="text-center py-3"><div class="spinner-border text-primary"></div><div class="mt-2">Cargando facturas...</div></div>');
$.ajax({
url: '/IMPORTADORES/solicitud_importacion/ajax_facturas_por_pedimento',
method: 'GET',
data: { id_pedimento: id },
dataType: 'json',
success: function(response) {
if (response.success && response.facturas && response.facturas.length > 0) {
var html = '';
response.facturas.forEach(function(f) {
html += `<div class="factura-item" onclick="seleccionarFactura('${f.numero_factura}', ${f.id_factura})">
<div class="factura-numero">${f.numero_factura}</div>
<div class="factura-details">Fecha: ${f.fecha} | Monto: $${parseFloat(f.monto).toLocaleString()}</div>
</div>`;
});
$('#facturasRelacionadasContainer').html(html);
} else {
$('#facturasRelacionadasContainer').html('<div class="alert alert-warning">No hay facturas relacionadas a este pedimento.</div>');
}
},
error: function() {
$('#facturasRelacionadasContainer').html('<div class="alert alert-danger">Error al cargar facturas.</div>');
}
});
var modal = new bootstrap.Modal(document.getElementById('modalFacturasMVE'));
modal.show();
};
window.seleccionarFactura = function(numeroFactura, idFactura) {
if (!idFactura || idFactura === 'undefined' || idFactura === 'null') {
Swal.fire({
icon: 'error',
title: 'Error',
text: 'ID de factura no válido.',
confirmButtonText: 'OK'
});
return;
}
facturaSeleccionadaId = parseInt(idFactura);
$('#facturaSeleccionada').text(numeroFactura);
$('.factura-item').removeClass('selected');
$(`.factura-item:contains(${numeroFactura})`).addClass('selected');
$('#facturaSeleccionada').removeClass('text-muted').addClass('text-success fw-bold');
cargarDatosMVEFactura(facturaSeleccionadaId);
};
$('#btnArt65').on('click', function() {
$('#art65Section').show();
$('#art66Section').hide();
$('#btnArt65').removeClass('btn-outline-light').addClass('btn-light');
$('#btnArt66').removeClass('btn-light').addClass('btn-outline-light');
});
$('#btnArt66').on('click', function() {
$('#art65Section').hide();
$('#art66Section').show();
$('#btnArt65').removeClass('btn-light').addClass('btn-outline-light');
$('#btnArt66').removeClass('btn-outline-light').addClass('btn-light');
});
function cargarDatosMVEFactura(idFactura) {
$.ajax({
url: '/IMPORTADORES/mve/ajax_obtener_datos_factura',
method: 'GET',
data: { id_factura: idFactura },
dataType: 'json',
success: function(response) {
if (response.success && response.datos) {
if (response.datos.art65) {
const art65 = response.datos.art65;
$('#fecha_transporte').val(art65.fecha_transporte || '');
$('#importe_transporte').val(art65.importe_transporte || '');
$('#fecha_descuentos').val(art65.fecha_descuentos || '');
$('#importe_descuentos').val(art65.importe_descuentos || '');
$('#fecha_posteriores').val(art65.fecha_posteriores || '');
$('#importe_posteriores').val(art65.importe_posteriores || '');
$('#fecha_contribuciones').val(art65.fecha_contribuciones || '');
$('#importe_contribuciones').val(art65.importe_contribuciones || '');
$('#fecha_pagos_vendedor').val(art65.fecha_pagos_vendedor || '');
$('#importe_pagos_vendedor').val(art65.importe_pagos_vendedor || '');
}
if (response.datos.art66) {
const art66 = response.datos.art66;
$('#fecha_comisiones').val(art66.fecha_comisiones || '');
$('#importe_comisiones').val(art66.importe_comisiones || '');
$('input[name="cargo_comisiones"][value="' + (art66.cargo_comisiones || '') + '"]').prop('checked', true);
$('#fecha_envases').val(art66.fecha_envases || '');
$('#importe_envases').val(art66.importe_envases || '');
$('input[name="cargo_envases"][value="' + (art66.cargo_envases || '') + '"]').prop('checked', true);
$('#fecha_embalaje').val(art66.fecha_embalaje || '');
$('#importe_embalaje').val(art66.importe_embalaje || '');
$('input[name="cargo_embalaje"][value="' + (art66.cargo_embalaje || '') + '"]').prop('checked', true);
$('#fecha_transporte_dec').val(art66.fecha_transporte_dec || '');
$('#importe_transporte_dec').val(art66.importe_transporte_dec || '');
$('input[name="cargo_transporte_dec"][value="' + (art66.cargo_transporte_dec || '') + '"]').prop('checked', true);
$('#fecha_ingenieria').val(art66.fecha_ingenieria || '');
$('#importe_ingenieria').val(art66.importe_ingenieria || '');
$('input[name="cargo_ingenieria"][value="' + (art66.cargo_ingenieria || '') + '"]').prop('checked', true);
$('#fecha_regalias').val(art66.fecha_regalias || '');
$('#importe_regalias').val(art66.importe_regalias || '');
$('input[name="cargo_regalias"][value="' + (art66.cargo_regalias || '') + '"]').prop('checked', true);
$('#fecha_producto').val(art66.fecha_producto || '');
$('#importe_producto').val(art66.importe_producto || '');
$('input[name="cargo_producto"][value="' + (art66.cargo_producto || '') + '"]').prop('checked', true);
}
}
}
});
}
$('#btnGuardarDatosMVE').on('click', function() {
if (!facturaSeleccionadaId) {
Swal.fire({
icon: 'warning',
title: 'Seleccionar factura',
text: 'Por favor selecciona una factura primero.',
confirmButtonText: 'OK'
});
return;
}
if (!pedimentoSeleccionado) {
Swal.fire({
icon: 'warning',
title: 'Error',
text: 'No se ha seleccionado un pedimento válido.',
confirmButtonText: 'OK'
});
return;
}
const datosArt65 = {
fecha_transporte: $('#fecha_transporte').val(),
importe_transporte: $('#importe_transporte').val(),
fecha_descuentos: $('#fecha_descuentos').val(),
importe_descuentos: $('#importe_descuentos').val(),
fecha_posteriores: $('#fecha_posteriores').val(),
importe_posteriores: $('#importe_posteriores').val(),
fecha_contribuciones: $('#fecha_contribuciones').val(),
importe_contribuciones: $('#importe_contribuciones').val(),
fecha_pagos_vendedor: $('#fecha_pagos_vendedor').val(),
importe_pagos_vendedor: $('#importe_pagos_vendedor').val()
};
const datosArt66 = {
fecha_comisiones: $('#fecha_comisiones').val(),
importe_comisiones: $('#importe_comisiones').val(),
cargo_comisiones: $('input[name="cargo_comisiones"]:checked').val(),
fecha_envases: $('#fecha_envases').val(),
importe_envases: $('#importe_envases').val(),
cargo_envases: $('input[name="cargo_envases"]:checked').val(),
fecha_embalaje: $('#fecha_embalaje').val(),
importe_embalaje: $('#importe_embalaje').val(),
cargo_embalaje: $('input[name="cargo_embalaje"]:checked').val(),
fecha_transporte_dec: $('#fecha_transporte_dec').val(),
importe_transporte_dec: $('#importe_transporte_dec').val(),
cargo_transporte_dec: $('input[name="cargo_transporte_dec"]:checked').val(),
fecha_ingenieria: $('#fecha_ingenieria').val(),
importe_ingenieria: $('#importe_ingenieria').val(),
cargo_ingenieria: $('input[name="cargo_ingenieria"]:checked').val(),
fecha_regalias: $('#fecha_regalias').val(),
importe_regalias: $('#importe_regalias').val(),
cargo_regalias: $('input[name="cargo_regalias"]:checked').val(),
fecha_producto: $('#fecha_producto').val(),
importe_producto: $('#importe_producto').val(),
cargo_producto: $('input[name="cargo_producto"]:checked').val()
};
$.ajax({
url: '/IMPORTADORES/mve/ajax_guardar_datos_factura',
method: 'POST',
data: {
id_factura: facturaSeleccionadaId,
id_pedimento: pedimentoSeleccionado,
datos_art65: JSON.stringify(datosArt65),
datos_art66: JSON.stringify(datosArt66)
},
dataType: 'json',
success: function(response) {
if (response.success) {
Swal.fire({
icon: 'success',
title: 'Datos guardados',
text: 'Los datos de la Manifestación de Valor han sido guardados correctamente.',
confirmButtonText: 'OK'
});
} else {
Swal.fire({
icon: 'error',
title: 'Error',
text: response.message || 'Error al guardar los datos.',
confirmButtonText: 'OK'
});
}
},
error: function() {
Swal.fire({
icon: 'error',
title: 'Error',
text: 'Error de conexión al guardar los datos.',
confirmButtonText: 'OK'
});
}
});
});
$('#btnConfirmarMVE').on('click', function() {
$('#modalFacturasMVE').modal('hide');
Swal.fire({
icon: 'success',
title: 'Solicitud enviada',
text: 'Se ha solicitado la Manifestación de Valor para el pedimento ' + $('#modalPedimento').text(),
confirmButtonText: 'OK'
});
});
});
</script>
</body>
</html>

View File

@@ -86,6 +86,71 @@
<span>Inicio</span>
</a>
</li>
<!-- CATÁLOGO DE PEDIMENTOS -->
<li class="nav-item">
<a class="nav-link" data-bs-toggle="collapse" href="#submenuCatalogoPedimentosDesktop" role="button" aria-expanded="false" aria-controls="submenuCatalogoPedimentosDesktop">
<svg class="icon" fill="currentColor" viewBox="0 0 20 20">
<path fill-rule="evenodd" d="M3 4a1 1 0 011-1h12a1 1 0 011 1v2a1 1 0 01-1 1H4a1 1 0 01-1-1V4zM3 10a1 1 0 011-1h6a1 1 0 011 1v6a1 1 0 01-1 1H4a1 1 0 01-1-1v-6zM14 9a1 1 0 00-1 1v6a1 1 0 001 1h2a1 1 0 001-1v-6a1 1 0 00-1-1h-2z" clip-rule="evenodd"/>
</svg>
<span>Catálogo de Pedimentos</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="submenuCatalogoPedimentosDesktop">
<ul class="nav flex-column submenu">
<li class="nav-item">
<a href="/IMPORTADORES/catalogo_pedimentos/lista" class="nav-link">
Ver catálogo
</a>
</li>
<li class="nav-item">
<a href="/IMPORTADORES/catalogo_pedimentos/buscar" class="nav-link">
Buscar pedimentos
</a>
</li>
</ul>
</div>
</li>
<!-- CLAVES DE PEDIMENTOS -->
<li class="nav-item">
<a class="nav-link" data-bs-toggle="collapse" href="#submenuClavesPedimentosDesktop" role="button" aria-expanded="false" aria-controls="submenuClavesPedimentosDesktop">
<svg class="icon" fill="currentColor" viewBox="0 0 20 20">
<path fill-rule="evenodd" d="M18 8A6 6 0 006 8c0 7-3 9-3 9h18s-3-2-3-9zM13.73 21a2 2 0 01-3.46 0" clip-rule="evenodd"/>
</svg>
<span>Claves de Pedimentos</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="submenuClavesPedimentosDesktop">
<ul class="nav flex-column submenu">
<li class="nav-item">
<a href="/IMPORTADORES/claves_pedimentos/lista" class="nav-link">
Mis claves
</a>
</li>
<li class="nav-item">
<a href="/IMPORTADORES/claves_pedimentos/crear" class="nav-link">
Nueva clave
</a>
</li>
<li class="nav-item">
<a href="/IMPORTADORES/claves_pedimentos/importar_csv" class="nav-link">
Importar CSV
</a>
</li>
<li class="nav-item">
<a href="/IMPORTADORES/claves_pedimentos/inicializar_claves_usuario" class="nav-link">
Inicializar claves
</a>
</li>
</ul>
</div>
</li>
<!-- LOCACIONES -->
<li class="nav-item">
<a href="/IMPORTADORES/importadores/lista" class="nav-link">
@@ -269,13 +334,13 @@
</ul>
</div>
</li>
<!-- SOLICITUDES DE IMPORTACIÓN -->
<!-- FACTURAS -->
<li class="nav-item">
<a class="nav-link" data-bs-toggle="collapse" href="#submenuSolicitudesDesktop" role="button" aria-expanded="false" aria-controls="submenuSolicitudesDesktop">
<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>Solicitudes Importación</span>
<span>Facturas</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>
@@ -284,17 +349,28 @@
<ul class="nav flex-column submenu">
<li class="nav-item">
<a href="/IMPORTADORES/solicitud_importacion/lista" class="nav-link">
Mis solicitudes
Mis facturas
</a>
</li>
<li class="nav-item">
<a href="/IMPORTADORES/solicitud_importacion/crear" class="nav-link">
Nueva solicitud
Nueva factura
</a>
</li>
</ul>
</div>
</li>
<!-- MVE (Manifestación de Valor Electrónica) -->
<li class="nav-item">
<a href="/IMPORTADORES/mve" class="nav-link">
<svg class="icon" fill="currentColor" viewBox="0 0 20 20">
<path fill-rule="evenodd" d="M4 2a2 2 0 00-2 2v12a2 2 0 002 2h8.586A2 2 0 0014 17.414L17.414 14A2 2 0 0018 12.586V4a2 2 0 00-2-2H4zm8 14H4V4h12v8h-2a2 2 0 00-2 2v2z" clip-rule="evenodd"/>
</svg>
<span>Manifestación Valor (MVE)</span>
</a>
</li>
<!-- EXPEDIENTE ELECTRÓNICO -->
<li class="nav-item">
<a class="nav-link" data-bs-toggle="collapse" href="#submenuExpedienteDesktop" role="button" aria-expanded="false" aria-controls="submenuExpedienteDesktop">
@@ -317,6 +393,7 @@
</ul>
</div>
</li>
<!-- CONFIGURACIÓN -->
<li class="nav-item">
<a href="/IMPORTADORES/configuracion" class="nav-link">
@@ -350,6 +427,34 @@
<span>Inicio</span>
</a>
</li>
<!-- CATÁLOGO DE PEDIMENTOS -->
<li class="nav-item">
<a class="nav-link" data-bs-toggle="collapse" href="#submenuCatalogoPedimentosMobile" role="button" aria-expanded="false" aria-controls="submenuCatalogoPedimentosMobile">
<svg class="icon" fill="currentColor" viewBox="0 0 20 20">
<path fill-rule="evenodd" d="M3 4a1 1 0 011-1h12a1 1 0 011 1v2a1 1 0 01-1 1H4a1 1 0 01-1-1V4zM3 10a1 1 0 011-1h6a1 1 0 011 1v6a1 1 0 01-1 1H4a1 1 0 01-1-1v-6zM14 9a1 1 0 00-1 1v6a1 1 0 001 1h2a1 1 0 001-1v-6a1 1 0 00-1-1h-2z" clip-rule="evenodd"/>
</svg>
<span>Catálogo de Pedimentos</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="submenuCatalogoPedimentosMobile">
<ul class="nav flex-column submenu">
<li class="nav-item">
<a href="/IMPORTADORES/catalogo_pedimentos/lista" class="nav-link">
Ver catálogo
</a>
</li>
<li class="nav-item">
<a href="/IMPORTADORES/catalogo_pedimentos/buscar" class="nav-link">
Buscar pedimentos
</a>
</li>
</ul>
</div>
</li>
<!-- LOCACIONES -->
<li class="nav-item">
<a href="/IMPORTADORES/importadores/lista" class="nav-link">
@@ -533,13 +638,13 @@
</ul>
</div>
</li>
<!-- SOLICITUDES DE IMPORTACIÓN -->
<!-- FACTURAS -->
<li class="nav-item">
<a class="nav-link" data-bs-toggle="collapse" href="#submenuSolicitudesMobile" role="button" aria-expanded="false" aria-controls="submenuSolicitudesMobile">
<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>Solicitudes Importación</span>
<span>Facturas</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>
@@ -548,17 +653,28 @@
<ul class="nav flex-column submenu">
<li class="nav-item">
<a href="/IMPORTADORES/solicitud_importacion/lista" class="nav-link">
Mis solicitudes
Mis facturas
</a>
</li>
<li class="nav-item">
<a href="/IMPORTADORES/solicitud_importacion/crear" class="nav-link">
Nueva solicitud
Nueva factura
</a>
</li>
</ul>
</div>
</li>
<!-- MVE (Manifestación de Valor Electrónica) -->
<li class="nav-item">
<a href="/IMPORTADORES/mve" class="nav-link">
<svg class="icon" fill="currentColor" viewBox="0 0 20 20">
<path fill-rule="evenodd" d="M4 2a2 2 0 00-2 2v12a2 2 0 002 2h8.586A2 2 0 0014 17.414L17.414 14A2 2 0 0018 12.586V4a2 2 0 00-2-2H4zm8 14H4V4h12v8h-2a2 2 0 00-2 2v2z" clip-rule="evenodd"/>
</svg>
<span>Manifestación Valor (MVE)</span>
</a>
</li>
<!-- EXPEDIENTE ELECTRÓNICO -->
<li class="nav-item">
<a class="nav-link" data-bs-toggle="collapse" href="#submenuExpedienteMobile" role="button" aria-expanded="false" aria-controls="submenuExpedienteMobile">

View File

@@ -8,20 +8,21 @@ $sql = "SELECT TOP 1 * FROM configuracion_sistema";
$stmt = sqlsrv_query($conn, $sql);
$config = sqlsrv_fetch_array($stmt, SQLSRV_FETCH_ASSOC);
// Variables con valores por defecto
$nombre = $config['nombre_plataforma'] ?? 'Sistema Integral para Importadores de Hidrocarburos';
$siglas = $config['siglas'] ?? 'SIIH';
$logo = $config['logo_url'] ?? 'assets/img/logo_siih.png';
$color1 = explode(',', $config['colores_primarios'] ?? '#003366,#0055A5')[0];
$color2 = explode(',', $config['colores_primarios'] ?? '#003366,#0055A5')[1];
// Variables con valores por defecto - Branding moderno
$nombre = $config['nombre_plataforma'] ?? 'Sistema de Gestión de Importaciones';
$siglas = $config['siglas'] ?? 'AduanaSoft';
$logo = $config['logo_url'] ?? 'assets/img/logo_aduanasoft.png';
$color1 = explode(',', $config['colores_primarios'] ?? '#0f172a,#334155')[0];
$color2 = explode(',', $config['colores_primarios'] ?? '#0f172a,#334155')[1];
?>
<!DOCTYPE html>
<html lang="es">
<head>
<meta charset="UTF-8">
<title><?= htmlspecialchars($siglas) ?> | Registro de Importador</title>
<title><?= htmlspecialchars($siglas) ?> | Registro</title>
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta name="description" content="Registro de importadores y agencias aduanales en la plataforma integral de gestión">
<!-- Bootstrap 5 -->
<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>
@@ -29,36 +30,289 @@ $color2 = explode(',', $config['colores_primarios'] ?? '#003366,#0055A5')
<script src="https://cdn.jsdelivr.net/npm/sweetalert2@11"></script>
<!-- Google reCAPTCHA v2 -->
<script src="https://www.google.com/recaptcha/api.js" async defer></script>
<!-- Google Fonts -->
<link href="https://fonts.googleapis.com/css2?family=Poppins:wght@300;400;500;600;700&display=swap" rel="stylesheet">
<style>
body { background-color: #f4f6f9; font-family: 'Segoe UI', sans-serif; }
.navbar { background-color: #fff; }
.navbar .nav-link,
.navbar-brand { color: <?= $color1 ?> !important; font-weight: 500; }
.navbar .nav-link.active { background-color: #F9F9F9; border-radius: 5px; }
.card-form { max-width: 900px; margin: 50px auto; padding: 40px; background: #fff; border-radius: 10px; box-shadow: 0 10px 30px rgba(0, 0, 0, 0.08); }
.form-label { font-weight: 500; }
.form-control,
.form-select { border-radius: 6px; }
footer { background-color: #e9ecef; padding: 20px; text-align: center; font-size: 14px; color: #666; }
.recaptcha { margin-top: 20px; }
.logo-siih { height: 90px; margin-right: 10px; }
.form-toggle { margin-bottom: 2rem; text-align: center; }
.form-toggle .btn { margin: 0 0.5rem; min-width: 150px; }
.form-container { display: none; }
.form-container.active { display: block; }
.form-title { text-align: center; margin-bottom: 2rem; color: #333; }
.transition-fade { transition: opacity 0.3s ease-in-out; }
.alert { bottom: 30px; }
:root {
--primary: <?= $color1 ?>;
--secondary: <?= $color2 ?>;
--accent: #10b981;
--surface: #ffffff;
--background: #f8fafc;
--text: #1e293b;
--text-muted: #64748b;
--border: #e2e8f0;
}
* {
box-sizing: border-box;
}
body {
background: linear-gradient(135deg, var(--background) 0%, #e2e8f0 100%);
font-family: 'Poppins', sans-serif;
color: var(--text);
line-height: 1.6;
margin: 0;
min-height: 100vh;
}
.navbar {
background: rgba(255, 255, 255, 0.95);
backdrop-filter: blur(20px);
border-bottom: 1px solid var(--border);
padding: 1rem 0;
}
.navbar-brand {
font-weight: 600;
font-size: 1.25rem;
color: var(--primary) !important;
}
.nav-link {
color: var(--text-muted) !important;
font-weight: 500;
padding: 0.5rem 1rem !important;
border-radius: 8px;
transition: all 0.2s ease;
}
.nav-link:hover, .nav-link.active {
color: var(--primary) !important;
background-color: #f1f5f9;
}
.registration-container {
max-width: 1000px;
margin: 2rem auto;
padding: 0 1rem;
}
.card-form {
background: var(--surface);
border-radius: 20px;
box-shadow: 0 20px 40px rgba(0, 0, 0, 0.08);
padding: 3rem;
border: 1px solid var(--border);
position: relative;
overflow: hidden;
}
.card-form::before {
content: '';
position: absolute;
top: 0;
left: 0;
right: 0;
height: 4px;
background: linear-gradient(90deg, var(--accent), var(--primary));
}
.page-header {
text-align: center;
margin-bottom: 3rem;
}
.page-title {
font-size: 2.5rem;
font-weight: 700;
color: var(--text);
margin-bottom: 0.5rem;
}
.page-subtitle {
color: var(--text-muted);
font-size: 1.1rem;
}
.form-toggle {
display: flex;
justify-content: center;
margin-bottom: 3rem;
background: var(--background);
border-radius: 12px;
padding: 0.5rem;
gap: 0.5rem;
}
.toggle-btn {
flex: 1;
max-width: 200px;
padding: 1rem 1.5rem;
border: none;
border-radius: 8px;
font-weight: 600;
transition: all 0.3s ease;
background: transparent;
color: var(--text-muted);
cursor: pointer;
}
.toggle-btn.active {
background: var(--surface);
color: var(--primary);
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
}
.form-container {
display: none;
animation: fadeIn 0.3s ease-in-out;
}
.form-container.active {
display: block;
}
@keyframes fadeIn {
from { opacity: 0; transform: translateY(20px); }
to { opacity: 1; transform: translateY(0); }
}
.form-section-title {
font-size: 1.5rem;
font-weight: 600;
color: var(--text);
margin-bottom: 2rem;
text-align: center;
position: relative;
}
.form-section-title::after {
content: '';
position: absolute;
bottom: -8px;
left: 50%;
transform: translateX(-50%);
width: 50px;
height: 3px;
background: var(--accent);
border-radius: 2px;
}
.form-label {
font-weight: 500;
color: var(--text);
margin-bottom: 0.5rem;
}
.form-control, .form-select {
border: 2px solid var(--border);
border-radius: 12px;
padding: 0.75rem 1rem;
font-size: 0.95rem;
transition: all 0.2s ease;
background: var(--surface);
}
.form-control:focus, .form-select:focus {
border-color: var(--accent);
box-shadow: 0 0 0 3px rgba(16, 185, 129, 0.1);
background: var(--surface);
}
.section-divider {
border: none;
height: 2px;
background: linear-gradient(90deg, transparent, var(--border), transparent);
margin: 2.5rem 0;
}
.section-subtitle {
font-weight: 600;
color: var(--text);
font-size: 1.1rem;
margin-bottom: 1.5rem;
display: flex;
align-items: center;
gap: 0.5rem;
}
.recaptcha {
margin: 2rem 0;
text-align: center;
}
.btn-modern {
padding: 12px 28px;
border-radius: 12px;
font-weight: 600;
text-decoration: none;
display: inline-flex;
align-items: center;
gap: 8px;
transition: all 0.3s ease;
border: none;
cursor: pointer;
}
.btn-primary-modern {
background: linear-gradient(135deg, var(--accent), #059669);
color: white;
box-shadow: 0 4px 15px rgba(16, 185, 129, 0.3);
}
.btn-primary-modern:hover {
transform: translateY(-2px);
box-shadow: 0 8px 25px rgba(16, 185, 129, 0.4);
color: white;
}
.info-alert {
background: linear-gradient(135deg, #fef3c7, #fde68a);
border: 1px solid #f59e0b;
border-radius: 12px;
padding: 1.5rem;
margin-top: 2rem;
color: #92400e;
}
.info-alert .alert-icon {
color: #f59e0b;
margin-right: 0.5rem;
}
footer {
background: var(--text);
color: #94a3b8;
padding: 2rem 0;
text-align: center;
margin-top: 3rem;
}
.footer-text {
font-size: 0.9rem;
line-height: 1.5;
}
@media (max-width: 768px) {
.card-form {
margin: 1rem;
padding: 2rem 1.5rem;
}
.page-title {
font-size: 2rem;
}
.form-toggle {
flex-direction: column;
gap: 0.5rem;
}
.toggle-btn {
max-width: none;
}
}
</style>
</head>
<body>
<!-- Navbar institucional -->
<!-- NAVBAR -->
<nav class="navbar navbar-expand-lg">
<div class="container">
<a class="navbar-brand d-flex align-items-center text-dark" href="#">
<img src="/IMPORTADORES/public/<?= htmlspecialchars($logo) ?>" alt="Logo" class="logo-siih">
| Registro de Importador
<a class="navbar-brand" href="/IMPORTADORES/">
<i class="fas fa-shipping-fast me-2"></i><?= htmlspecialchars($siglas) ?>
</a>
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarNav">
<span class="navbar-toggler-icon"></span>
@@ -72,166 +326,242 @@ $color2 = explode(',', $config['colores_primarios'] ?? '#003366,#0055A5')
<a class="nav-link active" aria-current="page" href="/IMPORTADORES/registro">Registro</a>
</li>
<li class="nav-item">
<a class="nav-link" href="/IMPORTADORES/login">Inicio sesión</a>
<a class="nav-link" href="/IMPORTADORES/login">Iniciar Sesión</a>
</li>
</ul>
</div>
</div>
</nav>
<!-- Contenedor de formularios -->
<div class="container">
<!-- CONTENEDOR PRINCIPAL -->
<div class="registration-container">
<div class="page-header">
<h1 class="page-title">Únete a Nuestra Plataforma</h1>
<p class="page-subtitle">Registra tu empresa o agencia aduanal para comenzar a gestionar importaciones</p>
</div>
<div class="card-form">
<!-- Botón para alternar entre formularios -->
<!-- SELECTOR DE TIPO DE REGISTRO -->
<div class="form-toggle">
<button type="button" class="btn btn-white" id="btnToggle" onclick="alternarFormulario()">🔄 Registro de Agencia Aduanal</button>
<button type="button" class="toggle-btn active" id="btnImportador" onclick="mostrarFormulario('importador')">
<i class="fas fa-building me-2"></i>Importador
</button>
<button type="button" class="toggle-btn" id="btnAgencia" onclick="mostrarFormulario('agencia')">
<i class="fas fa-handshake me-2"></i>Agencia Aduanal
</button>
</div>
<!-- Formulario de Importador -->
<div id="formImportadorContainer" class="form-container active transition-fade">
<h3 class="form-title">Registro de Importador</h3>
<!-- FORMULARIO DE IMPORTADOR -->
<div id="formImportadorContainer" class="form-container active">
<h3 class="form-section-title">Registro de Importador</h3>
<form action="/IMPORTADORES/registro/enviarSoliImportador" method="POST" enctype="multipart/form-data" id="formRegistro">
<div class="row g-3">
<div class="row g-4">
<div class="col-md-6">
<label for="company_name" class="form-label">Nombre de la empresa *</label>
<input type="text" class="form-control" name="company_name" id="company_name" required>
<label for="company_name" class="form-label">
<i class="fas fa-building text-muted me-2"></i>Nombre de la empresa *
</label>
<input type="text" class="form-control" name="company_name" id="company_name" placeholder="Ingresa el nombre de tu empresa" required>
</div>
<div class="col-md-6">
<label for="rfc" class="form-label">RFC *</label>
<input type="text" class="form-control" name="rfc" maxlength="13" id="rfc" required>
<label for="rfc" class="form-label">
<i class="fas fa-id-card text-muted me-2"></i>RFC *
</label>
<input type="text" class="form-control" name="rfc" maxlength="13" id="rfc" placeholder="RFC de la empresa" required>
</div>
<div class="col-md-6">
<label for="email" class="form-label">Correo electrónico *</label>
<input type="email" class="form-control" name="email" id="email" required>
<label for="email" class="form-label">
<i class="fas fa-envelope text-muted me-2"></i>Correo electrónico *
</label>
<input type="email" class="form-control" name="email" id="email" placeholder="correo@empresa.com" required>
</div>
<div class="col-md-6">
<label for="phone" class="form-label">Teléfono *</label>
<input type="text" class="form-control" name="phone" id="phone" required>
<label for="phone" class="form-label">
<i class="fas fa-phone text-muted me-2"></i>Teléfono *
</label>
<input type="text" class="form-control" name="phone" id="phone" placeholder="123 4567890" required>
</div>
<div class="col-md-12">
<label for="opinion_file_importador" class="form-label">Padrón de importadores o encargo conferido (PDF) *</label>
<div class="col-12">
<label for="opinion_file_importador" class="form-label">
<i class="fas fa-file-pdf text-muted me-2"></i>Padrón de Importadores o Encargo Conferido (PDF) *
</label>
<input type="file" class="form-control" name="opinion_file" id="opinion_file_importador" accept=".pdf" required>
<div class="form-text">Sube tu documento oficial que acredite tu actividad como importador.</div>
</div>
<div class="col-md-12 text-center recaptcha">
<div class="col-12 recaptcha">
<div class="g-recaptcha" id="recaptcha-importador" data-sitekey="6LfkMB8rAAAAAH-BEC1cMYij1HEhw4sTaEOl-UCA"></div>
</div>
<div class="col-md-12 text-end mt-4">
<button type="submit" class="btn btn-primary px-4 py-2">Enviar solicitud</button>
<div class="col-12 text-center">
<button type="submit" class="btn-modern btn-primary-modern">
<i class="fas fa-paper-plane me-2"></i>
Enviar Solicitud
</button>
</div>
</div>
</form>
</div>
<!-- Formulario de Agencia Aduanal -->
<div id="formAgenciaContainer" class="form-container transition-fade">
<h3 class="form-title">Registro de Agencia Aduanal</h3>
<!-- FORMULARIO DE AGENCIA ADUANAL -->
<div id="formAgenciaContainer" class="form-container">
<h3 class="form-section-title">Registro de Agencia Aduanal</h3>
<form action="/IMPORTADORES/registro/enviarSoliAgencia" method="POST" enctype="multipart/form-data" id="formRegistroAgencia">
<div class="row g-3">
<div class="col-md-6">
<label for="nombre_agencia" class="form-label">Nombre de la agencia *</label>
<input type="text" class="form-control" name="nombre_agencia" id="nombre_agencia" required>
<div class="row g-4">
<!-- Información de la Agencia -->
<div class="col-12">
<div class="section-subtitle">
<i class="fas fa-handshake text-primary"></i>
Información de la Agencia
</div>
</div>
<div class="col-md-6">
<label for="rfc_agencia" class="form-label">RFC *</label>
<input type="text" class="form-control" name="rfc_agencia" maxlength="13" id="rfc_agencia" required>
<label for="nombre_agencia" class="form-label">
<i class="fas fa-building text-muted me-2"></i>Nombre de la agencia *
</label>
<input type="text" class="form-control" name="nombre_agencia" id="nombre_agencia" placeholder="Nombre de la agencia aduanal" required>
</div>
<div class="col-md-6">
<label for="correo" class="form-label">Correo electrónico *</label>
<input type="email" class="form-control" name="correo" id="correo" required>
<label for="rfc_agencia" class="form-label">
<i class="fas fa-id-card text-muted me-2"></i>RFC *
</label>
<input type="text" class="form-control" name="rfc_agencia" maxlength="13" id="rfc_agencia" placeholder="RFC de la agencia" required>
</div>
<div class="col-md-6">
<label for="telefono" class="form-label">Teléfono *</label>
<input type="text" class="form-control" name="telefono" maxlength="11" id="telefono" required>
<label for="correo" class="form-label">
<i class="fas fa-envelope text-muted me-2"></i>Correo electrónico *
</label>
<input type="email" class="form-control" name="correo" id="correo" placeholder="contacto@agencia.com" required>
</div>
<div class="col-md-12">
<label for="direccion" class="form-label">Dirección *</label>
<div class="col-md-6">
<label for="telefono" class="form-label">
<i class="fas fa-phone text-muted me-2"></i>Teléfono *
</label>
<input type="text" class="form-control" name="telefono" maxlength="11" id="telefono" placeholder="123 4567890" required>
</div>
<div class="col-12">
<label for="direccion" class="form-label">
<i class="fas fa-map-marker-alt text-muted me-2"></i>Dirección completa *
</label>
<input type="text" class="form-control" name="direccion" id="direccion"
placeholder="Ej: Calle, num.externo, num.interno, colonia, municipio, código postal, estado" required>
placeholder="Calle, num.externo, num.interno, colonia, municipio, código postal, estado" required>
</div>
<div class="col-md-12">
<label for="opinion_file_agencia" class="form-label">Padrón de importadores o encargo conferido (PDF) *</label>
<div class="col-12">
<label for="opinion_file_agencia" class="form-label">
<i class="fas fa-file-pdf text-muted me-2"></i>Padrón de Importadores o Encargo Conferido (PDF) *
</label>
<input type="file" class="form-control" name="opinion_file" id="opinion_file_agencia" accept=".pdf" required>
</div>
<hr>
<p><strong>Administrador de la Agencia</strong></p>
<div class="col-md-6">
<label for="nombre_admin" class="form-label">Nombre *</label>
<input type="text" class="form-control" name="nombre_admin" id="nombre_admin" required>
</div>
<div class="col-md-6">
<label for="correo_admin" class="form-label">Correo Administrador *</label>
<input type="email" class="form-control" name="correo_admin" id="correo_admin" required>
<hr class="section-divider">
<!-- Información del Administrador -->
<div class="col-12">
<div class="section-subtitle">
<i class="fas fa-user-tie text-primary"></i>
Administrador de la Agencia
</div>
</div>
<div class="col-md-12 text-center recaptcha">
<div class="col-md-6">
<label for="nombre_admin" class="form-label">
<i class="fas fa-user text-muted me-2"></i>Nombre completo *
</label>
<input type="text" class="form-control" name="nombre_admin" id="nombre_admin" placeholder="Nombre del administrador" required>
</div>
<div class="col-md-6">
<label for="correo_admin" class="form-label">
<i class="fas fa-envelope text-muted me-2"></i>Correo del administrador *
</label>
<input type="email" class="form-control" name="correo_admin" id="correo_admin" placeholder="admin@agencia.com" required>
</div>
<div class="col-12 recaptcha">
<div class="g-recaptcha" id="recaptcha-agencia" data-sitekey="6LfkMB8rAAAAAH-BEC1cMYij1HEhw4sTaEOl-UCA"></div>
</div>
<div class="col-md-12 text-end mt-4">
<button type="submit" class="btn btn-primary px-4 py-2">Enviar solicitud</button>
<div class="col-12 text-center">
<button type="submit" class="btn-modern btn-primary-modern">
<i class="fas fa-paper-plane me-2"></i>
Enviar Solicitud
</button>
</div>
</div>
</form>
</div>
</div>
</div>
<div class="container mb-4">
<div class="alert alert-warning" role="alert">
Una vez que envíes tu solicitud, será revisada manualmente por la agencia aduanal.
Recibirás una notificación por correo electrónico cuando tu registro sea aprobado, junto con las instrucciones para activar tu cuenta e iniciar sesión en la plataforma.
<!-- INFORMACIÓN ADICIONAL -->
<div class="info-alert">
<div class="d-flex align-items-start">
<i class="fas fa-info-circle alert-icon"></i>
<div>
<strong>Proceso de Aprobación</strong><br>
Una vez enviada tu solicitud, será revisada manualmente por nuestro equipo.
Recibirás una notificación por correo electrónico cuando tu registro sea aprobado,
junto con las instrucciones para activar tu cuenta e iniciar sesión en la plataforma.
</div>
</div>
</div>
</div>
<!-- FOOTER -->
<footer class="fixed-bottom text-center py-3">
&copy; <?= date('Y') ?> <?= htmlspecialchars($siglas) ?> · <?= htmlspecialchars($nombre) ?>
<footer>
<div class="container text-center">
<div class="footer-text">
&copy; <?= date('Y') ?> <?= htmlspecialchars($siglas) ?> - <?= htmlspecialchars($nombre) ?><br>
Optimizando el comercio internacional
</div>
</div>
</footer>
<!-- Validación JS -->
<!-- SCRIPTS -->
<script>
let formularioActual = 'importador';
function alternarFormulario() {
function mostrarFormulario(tipo) {
const formImportador = document.getElementById('formImportadorContainer');
const formAgencia = document.getElementById('formAgenciaContainer');
const btnToggle = document.getElementById('btnToggle');
const btnImportador = document.getElementById('btnImportador');
const btnAgencia = document.getElementById('btnAgencia');
if (formularioActual === 'importador') {
// Cambiar a formulario de agencia
formImportador.classList.remove('active');
formAgencia.classList.add('active');
btnToggle.textContent = '🔄 Registro de Importador';
formularioActual = 'agencia';
} else {
// Cambiar a formulario de importador
formAgencia.classList.remove('active');
// Resetear clases
formImportador.classList.remove('active');
formAgencia.classList.remove('active');
btnImportador.classList.remove('active');
btnAgencia.classList.remove('active');
if (tipo === 'importador') {
formImportador.classList.add('active');
btnToggle.textContent = '🔄 Registro de Agencia Aduanal';
formularioActual = 'importador';
btnImportador.classList.add('active');
formularioActual = 'importador';
} else {
formAgencia.classList.add('active');
btnAgencia.classList.add('active');
formularioActual = 'agencia';
}
// Resetear reCAPTCHA si está disponible
// Resetear reCAPTCHA
if (typeof grecaptcha !== 'undefined') {
try{
grecaptcha.reset(0); // Reset primer reCAPTCHA
grecaptcha.reset(1); // Reset primer reCAPTCHA
grecaptcha.reset(0);
grecaptcha.reset(1);
} catch (e) {
console.log('Error resetting reCAPTCHA:', e);
}
}
}
// Función para obtener la respuesta del reCAPTCHA activo
function getActiveRecaptchaResponse() {
if (typeof grecaptcha !== 'undefined') {
try {
@@ -248,14 +578,7 @@ $color2 = explode(',', $config['colores_primarios'] ?? '#003366,#0055A5')
return '';
}
// Inicializar al cargar la página
document.addEventListener('DOMContentLoaded', function() {
// Por defecto mostrar el formulario de importador
const formImportador = document.getElementById('formImportadorContainer');
formImportador.classList.add('active');
});
// Registro de Importador
// Validación para formulario de importador
document.getElementById('formRegistro').addEventListener('submit', function(e) {
e.preventDefault();
@@ -271,65 +594,75 @@ $color2 = explode(',', $config['colores_primarios'] ?? '#003366,#0055A5')
const rfcRegex = /^[A-ZÑ&]{3,4}\d{6}[A-Z0-9]{3}$/;
const phoneRegex = /^\d{3}\s\d{7}$/;
// Validación de nombre
// Validaciones
if (!nombre) {
Swal.fire({ icon: 'warning', title: 'Campo requerido', text: 'El nombre es obligatorio.', confirmButtonColor: '#dc3545' });
Swal.fire({
icon: 'warning',
title: 'Campo requerido',
text: 'El nombre de la empresa es obligatorio.',
confirmButtonColor: '#10b981'
});
document.getElementById('company_name').focus();
return;
}
// Validación de correo
if (!email) {
Swal.fire({ icon: 'warning', title: 'Campo requerido', text: 'El correo es obligatorio.', confirmButtonColor: '#dc3545' });
if (!email || !emailRegex.test(email)) {
Swal.fire({
icon: 'error',
title: 'Correo inválido',
text: 'Ingresa una dirección de correo válida.',
confirmButtonColor: '#10b981'
});
document.getElementById('email').focus();
return;
}
if (!emailRegex.test(email)) {
Swal.fire({ icon: 'error', title: 'Correo inválido', text: 'No es una dirección de correo válida.', confirmButtonColor: '#dc3545' });
document.getElementById('email').focus();
return;
}
// Validación de RFC
if (!rfc) {
Swal.fire({ icon: 'warning', title: 'Campo requerido', text: 'El RFC es obligatorio.', confirmButtonColor: '#dc3545' });
if (!rfc || !rfcRegex.test(rfc)) {
Swal.fire({
icon: 'error',
title: 'RFC inválido',
text: 'El RFC debe tener el formato correcto (ej. ABC123456XYZ).',
confirmButtonColor: '#10b981'
});
document.getElementById('rfc').focus();
return;
}
if (!rfcRegex.test(rfc)) {
Swal.fire({ icon: 'error', title: 'RFC inválido', text: 'El RFC debe tener 12 o 13 caracteres con el formato correcto (ej. ABC123456XYZ).', confirmButtonColor: '#dc3545' });
document.getElementById('rfc').focus();
return;
}
// Validación de teléfono
if (!phone) {
Swal.fire({ icon: 'warning', title: 'Campo requerido', text: 'El teléfono es obligatorio.', confirmButtonColor: '#dc3545' });
if (!phone || !phoneRegex.test(phone)) {
Swal.fire({
icon: 'error',
title: 'Teléfono inválido',
text: 'Formato: 3 dígitos (LADA), espacio, y 7 dígitos. Ejemplo: 123 4567890',
confirmButtonColor: '#10b981'
});
document.getElementById('phone').focus();
return;
}
if (!phoneRegex.test(phone)) {
Swal.fire({ icon: 'error', title: 'Teléfono inválido', text: 'Debe tener 10 dígitos en el formato: 3 dígitos (LADA), espacio, y 7 dígitos. Ejemplo: 123 4567890', confirmButtonColor: '#dc3545' });
document.getElementById('phone').focus();
if (!archivo || archivo.type !== 'application/pdf') {
Swal.fire({
icon: 'error',
title: 'Archivo requerido',
text: 'Sube el padrón de importadores en formato PDF.',
confirmButtonColor: '#10b981'
});
return;
}
// Validación de archivo PDF
if (!archivo) {
Swal.fire({ icon: 'warning', title: 'Campo requerido', text: 'El padrón de importadores es obligatorio.', confirmButtonColor: '#dc3545' });
document.getElementById('opinion_file_importador').focus();
if (!recaptcha || recaptcha.length === 0) {
Swal.fire({
icon: 'warning',
title: 'Verificación requerida',
text: 'Por favor completa la verificación reCAPTCHA.',
confirmButtonColor: '#10b981'
});
return;
}
if (!archivo || archivo.type !== 'application/pdf') {
Swal.fire({ icon: 'error', title: 'Formato de documento inválido', text: 'Solo se permite subir archivos PDF.', confirmButtonColor: '#dc3545' });
return;
}
// Validación de reCAPTCHA
if (!recaptcha || recaptcha.length === 0) {
Swal.fire({ icon: 'warning', title: 'Verificación requerida', text: 'Por favor completa el reCAPTCHA.', confirmButtonColor: '#dc3545' });
return;
}
// Si todas las validaciones pasan, el formulario se envía.
this.submit();
});
// Registro de Agencia
// Validación para formulario de agencia
document.getElementById('formRegistroAgencia').addEventListener('submit', function(e) {
e.preventDefault();
@@ -343,88 +676,71 @@ $color2 = explode(',', $config['colores_primarios'] ?? '#003366,#0055A5')
const correo_admin = document.getElementById('correo_admin').value.trim();
const recaptcha = getActiveRecaptchaResponse();
// Expresiones regulares
const correoRegex = /^[a-zA-Z0-9._+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$/;
const rfcRegex = /^[A-ZÑ&]{3,4}\d{6}[A-Z0-9]{3}$/;
const telefonoRegex = /^\d{3}\s\d{7}$/;
// Validación de nombre
if (!nombre) {
Swal.fire({ icon: 'warning', title: 'Campo requerido', text: 'El nombre es obligatorio.', confirmButtonColor: '#dc3545' });
document.getElementById('nombre_agencia').focus();
// Validaciones básicas
if (!nombre || !correo || !rfc_agencia || !telefono || !direccion || !nombre_admin || !correo_admin) {
Swal.fire({
icon: 'warning',
title: 'Campos incompletos',
text: 'Todos los campos son obligatorios.',
confirmButtonColor: '#10b981'
});
return;
}
// Validación de correo
if (!correo) {
Swal.fire({ icon: 'warning', title: 'Campo requerido', text: 'El correo es obligatorio.', confirmButtonColor: '#dc3545' });
document.getElementById('correo').focus();
return;
}
if (!correoRegex.test(correo)) {
Swal.fire({ icon: 'error', title: 'Correo inválido', text: 'No es una dirección de correo válida.', confirmButtonColor: '#dc3545' });
document.getElementById('correo').focus();
return;
}
// Validación de RFC
if (!rfc_agencia) {
Swal.fire({ icon: 'warning', title: 'Campo requerido', text: 'El RFC es obligatorio.', confirmButtonColor: '#dc3545' });
document.getElementById('rfc_agencia').focus();
if (!correoRegex.test(correo) || !correoRegex.test(correo_admin)) {
Swal.fire({
icon: 'error',
title: 'Correos inválidos',
text: 'Verifica que los correos tengan formato válido.',
confirmButtonColor: '#10b981'
});
return;
}
if (!rfcRegex.test(rfc_agencia)) {
Swal.fire({ icon: 'error', title: 'RFC inválido', text: 'El RFC debe tener 12 o 13 caracteres con el formato correcto (ej. ABC123456XYZ).', confirmButtonColor: '#dc3545' });
document.getElementById('rfc_agencia').focus();
return;
}
// Validación de teléfono
if (!telefono) {
Swal.fire({ icon: 'warning', title: 'Campo requerido', text: 'El teléfono es obligatorio.', confirmButtonColor: '#dc3545' });
document.getElementById('telefono').focus();
Swal.fire({
icon: 'error',
title: 'RFC inválido',
text: 'El RFC debe tener el formato correcto.',
confirmButtonColor: '#10b981'
});
return;
}
if (!telefonoRegex.test(telefono)) {
Swal.fire({ icon: 'error', title: 'Teléfono inválido', text: 'Debe tener 10 dígitos en el formato: 3 dígitos (LADA), espacio, y 7 dígitos (ej. 123 4567890).', confirmButtonColor: '#dc3545' });
document.getElementById('telefono').focus();
Swal.fire({
icon: 'error',
title: 'Teléfono inválido',
text: 'Formato: 3 dígitos (LADA), espacio, y 7 dígitos.',
confirmButtonColor: '#10b981'
});
return;
}
// Validación de dirección
if (!direccion) {
Swal.fire({ icon: 'warning', title: 'Campo requerido', text: 'La dirección es obligatoria.', confirmButtonColor: '#dc3545' });
document.getElementById('direccion').focus();
if (!archivo || archivo.type !== 'application/pdf') {
Swal.fire({
icon: 'error',
title: 'Archivo requerido',
text: 'Sube el documento en formato PDF.',
confirmButtonColor: '#10b981'
});
return;
}
// Validación de archivo PDF
if (!archivo) {
Swal.fire({ icon: 'warning', title: 'Campo requerido', text: 'El padrón de importadores es obligatorio.', confirmButtonColor: '#dc3545' });
document.getElementById('opinion_file_agencia').focus();
if (!recaptcha || recaptcha.length === 0) {
Swal.fire({
icon: 'warning',
title: 'Verificación requerida',
text: 'Por favor completa la verificación reCAPTCHA.',
confirmButtonColor: '#10b981'
});
return;
}
if (!archivo || archivo.type !== 'application/pdf') {
Swal.fire({ icon: 'error', title: 'Formato de documento inválido', text: 'Solo se permite subir archivos PDF.', confirmButtonColor: '#dc3545' });
return; }
// Validación de administrador
if (!nombre_admin) {
Swal.fire({ icon: 'warning', title: 'Campo requerido', text: 'El nombre del administrador es obligatorio.', confirmButtonColor: '#dc3545' });
document.getElementById('nombre_admin').focus();
return;
}
// Validación de correo de administrador
if (!correo_admin) {
Swal.fire({ icon: 'warning', title: 'Campo requerido', text: 'El correo de administrador es obligatorio.', confirmButtonColor: '#dc3545' });
document.getElementById('correo_admin').focus();
return;
}
if (!correoRegex.test(correo_admin)) {
Swal.fire({ icon: 'error', title: 'Correo inválido', text: 'No es una dirección de correo válida.', confirmButtonColor: '#dc3545' });
document.getElementById('correo_admin').focus();
return;
}
// Validación de reCAPTCHA
if (!recaptcha || recaptcha.length === 0) {
Swal.fire({ icon: 'warning', title: 'Verificación requerida', text: 'Por favor completa el reCAPTCHA.', confirmButtonColor: '#dc3545' });
return;
}
// Si todas las validaciones pasan, el formulario se envía.
this.submit();
});
</script>

View File

@@ -121,6 +121,25 @@
</div>
</div>
</div>
<!-- Nueva card para Ventanilla Única -->
<div class="col-md-6 mb-4">
<div class="card h-100 card-hover">
<div class="card-body text-center">
<div class="security-icon mb-3">
<i class="fas fa-university text-warning"></i>
</div>
<h5 class="card-title">Ventanilla Única</h5>
<p class="card-text text-muted">
Configurar ejecutable y certificados para transmisiones de Manifestación de Valor Electrónica
</p>
<a href="/IMPORTADORES/seguridad/ventanillaUnica" class="btn btn-outline-warning btn-animated">
<i class="fas fa-cogs me-2"></i>Configurar VU
</a>
</div>
</div>
</div>
</div>
</div>

View File

@@ -0,0 +1,349 @@
<?php include __DIR__ . '/../partials/sidebar_configuracion.php'; ?>
<!DOCTYPE html>
<html lang="es">
<head>
<meta charset="UTF-8">
<title>🔐 Configuración Ventanilla Única</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 rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.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; }
.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; box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1); }
.btn-animated { transition: all 0.3s ease; }
.btn-animated:hover { transform: translateY(-2px); box-shadow: 0 5px 15px rgba(0, 0, 0, 0.2); }
.form-floating { position: relative; }
.form-floating > .form-control:focus ~ label { color: #0d6efd; }
.security-header { background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); }
.status-indicator { position: absolute; right: 10px; top: 50%; transform: translateY(-50%); }
.path-input { font-family: 'Courier New', monospace; font-size: 0.9em; }
.config-section { border-left: 4px solid #0d6efd; padding-left: 20px; margin-bottom: 30px; }
</style>
</head>
<body>
<div class="content">
<!-- Header -->
<div class="security-header text-white p-4 rounded-3 mb-4">
<div class="d-flex align-items-center justify-content-between">
<div>
<h4 class="mb-1"><i class="fas fa-university me-2"></i>Configuración Ventanilla Única</h4>
<p class="mb-0 opacity-75">Configuración para transmisiones de Manifestación de Valor Electrónica</p>
</div>
<div class="text-end">
<a href="/IMPORTADORES/seguridad/index" class="btn btn-light btn-sm">
<i class="fas fa-arrow-left me-1"></i>Regresar a Seguridad
</a>
</div>
</div>
</div>
<!-- Mensajes de éxito/error -->
<?php if (isset($_SESSION['config_success'])): ?>
<div class="alert alert-success alert-dismissible fade show" role="alert">
<i class="fas fa-check-circle me-2"></i>
<?= htmlspecialchars($_SESSION['config_success']) ?>
<button type="button" class="btn-close" data-bs-dismiss="alert"></button>
</div>
<?php unset($_SESSION['config_success']); ?>
<?php endif; ?>
<?php if (isset($_SESSION['config_error'])): ?>
<div class="alert alert-danger alert-dismissible fade show" role="alert">
<i class="fas fa-exclamation-triangle me-2"></i>
<?= htmlspecialchars($_SESSION['config_error']) ?>
<button type="button" class="btn-close" data-bs-dismiss="alert"></button>
</div>
<?php unset($_SESSION['config_error']); ?>
<?php endif; ?>
<div class="row">
<div class="col-12">
<div class="card">
<div class="card-header bg-primary text-white">
<h5 class="mb-0"><i class="fas fa-cogs me-2"></i>Configuración de Ventanilla Única</h5>
</div>
<div class="card-body">
<form action="/IMPORTADORES/seguridad/guardarConfiguracionVU" method="POST" id="configForm" enctype="multipart/form-data">
<!-- Sección 1: Certificados FIEL -->
<div class="config-section">
<h6 class="text-success mb-3">
<i class="fas fa-certificate me-2"></i>Certificados FIEL
</h6>
<div class="row">
<div class="col-md-6">
<div class="mb-3">
<label for="archivo_key" class="form-label">
<i class="fas fa-key me-1"></i>Archivo KEY *
</label>
<input type="file"
class="form-control"
id="archivo_key"
name="archivo_key"
accept=".key"
<?= empty($configuracion_vu['ruta_archivo_key']) ? 'required' : '' ?>>
<?php if (!empty($configuracion_vu['ruta_archivo_key'])): ?>
<div class="mt-2 p-2 bg-light rounded">
<small class="text-success">
<i class="fas fa-check me-1"></i>
Archivo actual: <?= basename($configuracion_vu['ruta_archivo_key']) ?>
</small>
<input type="hidden" name="ruta_archivo_key_actual" value="<?= htmlspecialchars($configuracion_vu['ruta_archivo_key']) ?>">
</div>
<?php endif; ?>
<small class="text-muted d-block mt-1">
Solo archivos .key permitidos
</small>
</div>
</div>
<div class="col-md-6">
<div class="mb-3">
<label for="archivo_cer" class="form-label">
<i class="fas fa-certificate me-1"></i>Archivo CER *
</label>
<input type="file"
class="form-control"
id="archivo_cer"
name="archivo_cer"
accept=".cer"
<?= empty($configuracion_vu['ruta_archivo_cer']) ? 'required' : '' ?>>
<?php if (!empty($configuracion_vu['ruta_archivo_cer'])): ?>
<div class="mt-2 p-2 bg-light rounded">
<small class="text-success">
<i class="fas fa-check me-1"></i>
Archivo actual: <?= basename($configuracion_vu['ruta_archivo_cer']) ?>
</small>
<input type="hidden" name="ruta_archivo_cer_actual" value="<?= htmlspecialchars($configuracion_vu['ruta_archivo_cer']) ?>">
</div>
<?php endif; ?>
<small class="text-muted d-block mt-1">
Solo archivos .cer permitidos
</small>
</div>
</div>
</div>
<div class="row">
<div class="col-12">
<div class="form-floating mb-3">
<input type="password"
class="form-control"
id="clave_fiel"
name="clave_fiel"
value="<?= htmlspecialchars($configuracion_vu['clave_fiel']) ?>"
placeholder="Contraseña FIEL"
required>
<label for="clave_fiel">
<i class="fas fa-lock me-1"></i>Clave de Acceso FIEL *
</label>
<button type="button"
class="btn btn-outline-secondary position-absolute end-0 top-50 translate-middle-y me-3"
style="z-index: 10;"
onclick="togglePassword('clave_fiel')">
<i class="fas fa-eye" id="clave_fiel_icon"></i>
</button>
</div>
<small class="text-muted">
<i class="fas fa-shield-alt me-1"></i>
Esta contraseña se almacena de forma encriptada
</small>
</div>
</div>
</div>
<!-- Sección 2: Acceso Ventanilla Única -->
<div class="config-section">
<h6 class="text-warning mb-3">
<i class="fas fa-user-cog me-2"></i>Acceso a Ventanilla Única
</h6>
<div class="row">
<div class="col-md-6">
<div class="form-floating mb-3">
<input type="text"
class="form-control"
id="rfc_usuario_vu"
name="rfc_usuario_vu"
value="<?= htmlspecialchars($configuracion_vu['rfc_usuario_vu']) ?>"
placeholder="RFC12345678901"
maxlength="13"
style="text-transform: uppercase;"
required>
<label for="rfc_usuario_vu">
<i class="fas fa-id-card me-1"></i>RFC Usuario VU *
</label>
</div>
</div>
<div class="col-md-6">
<div class="form-floating mb-3">
<input type="password"
class="form-control"
id="clave_webservice"
name="clave_webservice"
value="<?= htmlspecialchars($configuracion_vu['clave_webservice']) ?>"
placeholder="Contraseña Web Service">
<label for="clave_webservice">
<i class="fas fa-globe me-1"></i>Clave de Acceso Web Service (Opcional)
</label>
<button type="button"
class="btn btn-outline-secondary position-absolute end-0 top-50 translate-middle-y me-3"
style="z-index: 10;"
onclick="togglePassword('clave_webservice')">
<i class="fas fa-eye" id="clave_webservice_icon"></i>
</button>
</div>
</div>
</div>
<small class="text-muted">
<i class="fas fa-info-circle me-1"></i>
Las contraseñas se almacenan de forma encriptada por seguridad
</small>
</div>
<!-- Información adicional -->
<?php if ($configuracion_vu['fecha_creacion'] || $configuracion_vu['fecha_actualizacion']): ?>
<div class="alert alert-info">
<i class="fas fa-info-circle me-2"></i>
<strong>Información de configuración:</strong><br>
<?php if ($configuracion_vu['fecha_creacion']): ?>
Creada: <?= $configuracion_vu['fecha_creacion']->format('d/m/Y H:i') ?><br>
<?php endif; ?>
<?php if ($configuracion_vu['fecha_actualizacion']): ?>
Última actualización: <?= $configuracion_vu['fecha_actualizacion']->format('d/m/Y H:i') ?>
<?php endif; ?>
</div>
<?php endif; ?>
<!-- Botones de acción -->
<div class="d-flex justify-content-between align-items-center">
<button type="button"
class="btn btn-outline-info btn-animated"
onclick="probarConfiguracion()">
<i class="fas fa-vial me-2"></i>Probar Configuración
</button>
<div>
<button type="button"
class="btn btn-outline-secondary me-2"
onclick="limpiarFormulario()">
<i class="fas fa-eraser me-1"></i>Limpiar
</button>
<button type="submit"
class="btn btn-primary btn-animated">
<i class="fas fa-save me-2"></i>Guardar Configuración
</button>
</div>
</div>
</form>
</div>
</div>
</div>
</div>
</div>
<script>
// Función para mostrar/ocultar contraseñas
function togglePassword(fieldId) {
const field = document.getElementById(fieldId);
const icon = document.getElementById(fieldId + '_icon');
if (field.type === 'password') {
field.type = 'text';
icon.classList.remove('fa-eye');
icon.classList.add('fa-eye-slash');
} else {
field.type = 'password';
icon.classList.remove('fa-eye-slash');
icon.classList.add('fa-eye');
}
}
// Función para limpiar el formulario
function limpiarFormulario() {
if (confirm('¿Estás seguro de que deseas limpiar todos los campos?')) {
document.getElementById('configForm').reset();
}
}
// Función para probar la configuración
function probarConfiguracion() {
const btn = event.target;
const originalHtml = btn.innerHTML;
btn.innerHTML = '<i class="fas fa-spinner fa-spin me-2"></i>Probando...';
btn.disabled = true;
fetch('/IMPORTADORES/seguridad/probarConexionVU', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
}
})
.then(response => response.json())
.then(data => {
if (data.success) {
Swal.fire({
icon: 'success',
title: 'Configuración Válida',
text: data.message,
confirmButtonColor: '#28a745'
});
} else {
Swal.fire({
icon: 'error',
title: 'Error en Configuración',
text: data.message,
confirmButtonColor: '#dc3545'
});
}
})
.catch(error => {
Swal.fire({
icon: 'error',
title: 'Error de Conexión',
text: 'No se pudo probar la configuración. Verifica tu conexión.',
confirmButtonColor: '#dc3545'
});
})
.finally(() => {
btn.innerHTML = originalHtml;
btn.disabled = false;
});
}
// Validación en tiempo real de rutas de archivos
document.addEventListener('DOMContentLoaded', function() {
const pathInputs = document.querySelectorAll('.path-input');
pathInputs.forEach(input => {
input.addEventListener('blur', function() {
const statusId = this.id.replace('ruta_', '') + '-status';
const statusElement = document.getElementById(statusId);
if (this.value.trim()) {
// Aquí podrías agregar validación adicional
statusElement.innerHTML = '<i class="fas fa-check text-success"></i>';
} else {
statusElement.innerHTML = '';
}
});
});
// Convertir RFC a mayúsculas automáticamente
document.getElementById('rfc_usuario_vu').addEventListener('input', function() {
this.value = this.value.toUpperCase();
});
});
</script>
</body>
</html>

File diff suppressed because it is too large Load Diff

View File

@@ -96,7 +96,7 @@
<body>
<div class="content">
<h4 class="mb-4 animate__animated animate__fadeInDown title_glow">✏️ Editar Solicitud #<?= (int)$factura['id_solicitud'] ?></h4>
<h4 class="mb-4 animate__animated animate__fadeInDown title_glow">✏️ Editar factura #<?= (int)$factura['id_solicitud'] ?></h4>
<div class="card p-4 bg-white shadow-sm card-hover position-relative h-auto fade-in-up">
<form id="solicitudForm" action="/IMPORTADORES/solicitud_importacion/actualizar" method="POST" enctype="multipart/form-data">
<input type="hidden" name="id_solicitud" value="<?= (int)$factura['id_solicitud'] ?>">

View File

@@ -0,0 +1,790 @@
<?php include __DIR__ . '/../partials/sidebar_importador.php'; ?>
<!DOCTYPE html>
<html lang="es">
<head>
<meta charset="UTF-8">
<title> Nuevo Template Rápido</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">
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/choices.js/public/assets/styles/choices.min.css"/>
<script src="https://cdn.jsdelivr.net/npm/sweetalert2@11"></script>
<script src="https://code.jquery.com/jquery-3.7.1.min.js"></script>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0/css/all.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;
box-shadow: 0 4px 12px rgba(0,0,0,0.1);
border: none;
}
.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);
}
.form-control:focus, .form-select:focus {
border-color: #0d6efd;
box-shadow: 0 0 0 0.2rem rgba(13, 110, 253, 0.25);
}
.emoji-picker {
display: grid;
grid-template-columns: repeat(8, 1fr);
gap: 8px;
padding: 15px;
max-height: 250px;
overflow-y: auto;
border: 1px solid #dee2e6;
border-radius: 8px;
background: white;
box-shadow: 0 2px 10px rgba(0,0,0,0.1);
}
.emoji-item {
padding: 10px;
text-align: center;
cursor: pointer;
border-radius: 6px;
transition: all 0.2s ease;
font-size: 20px;
border: 2px solid transparent;
display: flex;
align-items: center;
justify-content: center;
min-height: 40px;
}
.emoji-item:hover {
background-color: #e3f2fd;
border-color: #2196f3;
transform: scale(1.1);
}
.emoji-item.selected {
background-color: #1976d2;
color: white;
border-color: #0d47a1;
}
.preview-card {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
color: white;
border-radius: 12px;
padding: 20px;
margin-top: 10px;
box-shadow: 0 4px 15px rgba(0,0,0,0.2);
}
.template-preview-item {
background: rgba(255,255,255,0.2);
border: 1px solid rgba(255,255,255,0.3);
color: white;
padding: 8px 14px;
border-radius: 25px;
font-size: 11px;
font-weight: 500;
margin: 3px;
display: inline-block;
backdrop-filter: blur(10px);
}
.section-header {
background: linear-gradient(135deg, #f8f9fa 0%, #e9ecef 100%);
padding: 15px 20px;
margin: -15px -15px 20px -15px;
border-radius: 8px 8px 0 0;
border-bottom: 1px solid #dee2e6;
}
.section-header h6 {
margin: 0;
color: #495057;
font-weight: 600;
}
.icon-preview {
font-size: 24px;
margin-right: 8px;
display: inline-flex;
align-items: center;
justify-content: center;
width: 40px;
height: 40px;
background: rgba(255,255,255,0.1);
border-radius: 8px;
border: 1px solid rgba(255,255,255,0.2);
}
.form-label {
font-weight: 600;
color: #495057;
margin-bottom: 8px;
}
.choices__inner {
min-height: 42px !important;
border: 1px solid #ced4da !important;
border-radius: 6px !important;
}
.choices__inner:focus {
border-color: #0d6efd !important;
box-shadow: 0 0 0 0.2rem rgba(13, 110, 253, 0.25) !important;
}
.loading-spinner {
display: inline-block;
width: 20px;
height: 20px;
border: 3px solid rgba(255,255,255,.3);
border-radius: 50%;
border-top-color: #fff;
animation: spin 1s ease-in-out infinite;
}
@keyframes spin {
to { transform: rotate(360deg); }
}
.tips-section {
background: linear-gradient(135deg, #e3f2fd 0%, #bbdefb 100%);
border-radius: 8px;
padding: 15px;
margin-top: 15px;
}
.tip-item {
display: flex;
align-items: center;
margin-bottom: 8px;
font-size: 13px;
color: #1565c0;
}
.tip-item:last-child {
margin-bottom: 0;
}
.tip-item i {
margin-right: 8px;
width: 16px;
}
</style>
</head>
<body>
<div class="content">
<div class="d-flex justify-content-between align-items-center mb-4">
<h4 class="mb-0"> Nuevo Template Rápido</h4>
<a href="/IMPORTADORES/templates_rapidos/lista" class="btn btn-secondary btn-animated">
<i class="fas fa-arrow-left me-1"></i>Volver a Lista
</a>
</div>
<div class="row">
<!-- Formulario Principal -->
<div class="col-lg-8">
<div class="card">
<div class="card-body p-4">
<form id="templateForm" action="/IMPORTADORES/templates_rapidos/guardar" method="POST">
<!-- Información Básica -->
<div class="section-header">
<h6 class="mb-0"><i class="fas fa-info-circle me-2"></i>Información Básica</h6>
</div>
<div class="row mb-4">
<div class="col-md-6">
<label for="nombre" class="form-label">Nombre del Template *</label>
<input id="nombre" name="nombre" type="text" class="form-control" required
placeholder="ej. Importación China" maxlength="100">
</div>
<div class="col-md-6">
<label for="icono" class="form-label">Icono</label>
<div class="input-group">
<span class="input-group-text" id="icon-display" style="font-size: 20px;">🏢</span>
<input id="icono" name="icono" type="text" class="form-control" value="🏢" readonly>
<button type="button" class="btn btn-outline-secondary dropdown-toggle"
data-bs-toggle="dropdown" aria-expanded="false">
<i class="fas fa-palette"></i>
</button>
<div class="dropdown-menu dropdown-menu-end p-0" style="width: 320px;">
<div class="p-2">
<h6 class="dropdown-header">🌍 Países</h6>
<div class="emoji-picker" style="max-height: 120px;">
<div class="emoji-item" data-emoji="🇨🇳">🇨🇳</div>
<div class="emoji-item" data-emoji="🇺🇸">🇺🇸</div>
<div class="emoji-item" data-emoji="🇪🇺">🇪🇺</div>
<div class="emoji-item" data-emoji="🇩🇪">🇩🇪</div>
<div class="emoji-item" data-emoji="🇯🇵">🇯🇵</div>
<div class="emoji-item" data-emoji="🇰🇷">🇰🇷</div>
<div class="emoji-item" data-emoji="🇮🇹">🇮🇹</div>
<div class="emoji-item" data-emoji="🇫🇷">🇫🇷</div>
<div class="emoji-item" data-emoji="🇨🇦">🇨🇦</div>
<div class="emoji-item" data-emoji="🇧🇷">🇧🇷</div>
<div class="emoji-item" data-emoji="🇮🇳">🇮🇳</div>
<div class="emoji-item" data-emoji="🇹🇼">🇹🇼</div>
</div>
<h6 class="dropdown-header mt-3">🏢 Empresas & Logística</h6>
<div class="emoji-picker" style="max-height: 120px;">
<div class="emoji-item" data-emoji="🏢">🏢</div>
<div class="emoji-item" data-emoji="🏭">🏭</div>
<div class="emoji-item" data-emoji="🏪">🏪</div>
<div class="emoji-item" data-emoji="🏬">🏬</div>
<div class="emoji-item" data-emoji="🚢">🚢</div>
<div class="emoji-item" data-emoji="✈️">✈️</div>
<div class="emoji-item" data-emoji="🚛">🚛</div>
<div class="emoji-item" data-emoji="🚚">🚚</div>
<div class="emoji-item" data-emoji="📦">📦</div>
<div class="emoji-item" data-emoji="📋">📋</div>
<div class="emoji-item" data-emoji="💼">💼</div>
<div class="emoji-item" data-emoji="📊">📊</div>
</div>
<h6 class="dropdown-header mt-3">⭐ Destacados</h6>
<div class="emoji-picker" style="max-height: 80px;">
<div class="emoji-item" data-emoji="⚡">⚡</div>
<div class="emoji-item" data-emoji="🔥">🔥</div>
<div class="emoji-item" data-emoji="⭐">⭐</div>
<div class="emoji-item" data-emoji="🎯">🎯</div>
<div class="emoji-item" data-emoji="🚀">🚀</div>
<div class="emoji-item" data-emoji="💎">💎</div>
<div class="emoji-item" data-emoji="🔧">🔧</div>
<div class="emoji-item" data-emoji="⚙️">⚙️</div>
</div>
</div>
</div>
</div>
</div>
</div>
<div class="row mb-4">
<div class="col-md-12">
<label for="descripcion" class="form-label">Descripción</label>
<textarea id="descripcion" name="descripcion" class="form-control" rows="2"
placeholder="Descripción breve del template (opcional)" maxlength="255"></textarea>
</div>
</div>
<!-- Configuración Principal -->
<div class="section-header">
<h6 class="mb-0"><i class="fas fa-cog me-2"></i>Configuración Principal</h6>
</div>
<div class="row mb-3">
<div class="col-md-3">
<label for="tipo_moneda" class="form-label">Moneda</label>
<select id="tipo_moneda" name="tipo_moneda" class="form-select searchable">
<option value="">-- Sin especificar --</option>
<option value="MXN">Peso Mexicano (MXN)</option>
<option value="USD">Dólar USD (USD)</option>
<option value="EUR">Euro (EUR)</option>
<option value="CNY">Yuan (CNY)</option>
<option value="GBP">Libra GBP (GBP)</option>
<option value="JPY">Yen (JPY)</option>
</select>
</div>
<div class="col-md-3">
<label for="incoterm" class="form-label">INCOTERM</label>
<select id="incoterm" name="incoterm" class="form-select searchable">
<option value="">-- Sin especificar --</option>
<?php foreach($incoterms as $inc): ?>
<option value="<?= htmlspecialchars($inc['INCOTERM']) ?>">
<?= htmlspecialchars($inc['INCOTERM'].' - '.$inc['DESCESPANOL']) ?>
</option>
<?php endforeach; ?>
</select>
</div>
<div class="col-md-3">
<label for="vinculacion" class="form-label">Vinculación</label>
<select id="vinculacion" name="vinculacion" class="form-select">
<option value="">-- Sin especificar --</option>
<option value="0">No existe</option>
<option value="1">Existe, no afecta</option>
<option value="2">Existe y afecta</option>
</select>
</div>
<div class="col-md-3">
<label for="tasa_preferencial" class="form-label">Tasa Preferencial</label>
<select id="tasa_preferencial" name="tasa_preferencial" class="form-select">
<option value="">-- Sin especificar --</option>
<option value="General">General</option>
<option value="TLC">TLC</option>
<option value="PROSEC">PROSEC</option>
<option value="ALADI">ALADI</option>
<option value="COMERCIALIZADORA">COMERCIALIZADORA</option>
</select>
</div>
</div>
<div class="row mb-3">
<div class="col-md-6">
<label for="pais_proveedor" class="form-label">País Proveedor</label>
<select id="pais_proveedor" name="pais_proveedor" class="form-select searchable">
<option value="">-- Sin especificar --</option>
<?php foreach($paises as $p): ?>
<option value="<?= htmlspecialchars($p['id_pais']) ?>"><?= htmlspecialchars($p['nombre']) ?></option>
<?php endforeach; ?>
</select>
<input type="hidden" id="pais_proveedor_texto" name="pais_proveedor_texto">
</div>
<div class="col-md-6">
<label for="unidad_comercial_id" class="form-label">Unidad de Medida</label>
<select id="unidad_comercial_id" name="unidad_comercial_id" class="form-select searchable">
<option value="">-- Sin especificar --</option>
<?php foreach($unidades_medida as $um): ?>
<option value="<?= htmlspecialchars($um['id']) ?>"><?= htmlspecialchars($um['descripcion']) ?></option>
<?php endforeach; ?>
</select>
</div>
</div>
<!-- Configuración Avanzada -->
<div class="section-header">
<h6 class="mb-0"><i class="fas fa-tools me-2"></i>Configuración Avanzada</h6>
</div>
<div class="row mb-3">
<div class="col-md-6">
<label for="anexo22_apendice" class="form-label">Aduana</label>
<select id="anexo22_apendice" name="anexo22_apendice" class="form-select searchable">
<option value="">-- Sin especificar --</option>
<?php foreach($aduanas as $a): ?>
<option value="<?= htmlspecialchars($a['aduana_seccion']) ?>">
<?= htmlspecialchars($a['aduana_seccion'].' '.$a['nombre']) ?>
</option>
<?php endforeach; ?>
</select>
</div>
<div class="col-md-6">
<label for="patente" class="form-label">Patente Aduanal</label>
<select id="patente" name="patente" class="form-select searchable">
<option value="">-- Sin especificar --</option>
<?php foreach($patentes as $pt): ?>
<option value="<?= htmlspecialchars($pt['id_agente']) ?>">
<?= htmlspecialchars($pt['patente'].' - '.$pt['agente_aduanal']) ?>
</option>
<?php endforeach; ?>
</select>
</div>
</div>
<div class="row mb-4">
<div class="col-md-6">
<label for="transportista_id" class="form-label">Transportista</label>
<select id="transportista_id" name="transportista_id" class="form-select searchable">
<option value="">-- Sin especificar --</option>
<?php foreach($transportistas as $t): ?>
<option value="<?= htmlspecialchars($t['id_transportista']) ?>">
<?= htmlspecialchars($t['clave_identificador'] . ' - ' . $t['nombre']) ?>
</option>
<?php endforeach; ?>
</select>
</div>
<div class="col-md-6">
<label for="chofer_id" class="form-label">Chofer</label>
<select id="chofer_id" name="chofer_id" class="form-select searchable">
<option value="">-- Sin especificar --</option>
<?php foreach($choferes as $c): ?>
<option value="<?= htmlspecialchars($c['id_chofer']) ?>">
<?= htmlspecialchars($c['nombre']) ?>
</option>
<?php endforeach; ?>
</select>
</div>
</div>
<!-- Botones -->
<div class="text-end">
<button type="submit" class="btn btn-success btn-animated me-2">
<i class="fas fa-save me-1"></i>Guardar Template
</button>
<a href="/IMPORTADORES/templates_rapidos/lista" class="btn btn-secondary btn-animated">
<i class="fas fa-times me-1"></i>Cancelar
</a>
</div>
</form>
</div>
</div>
</div>
<!-- Vista Previa -->
<div class="col-lg-4">
<div class="card sticky-top" style="top: 20px;">
<div class="card-header bg-primary text-white">
<h6 class="mb-0"><i class="fas fa-eye me-2"></i>Vista Previa</h6>
</div>
<div class="card-body">
<div id="template-preview">
<div class="preview-card">
<div class="d-flex align-items-center mb-2">
<span id="preview-icono" class="fs-4 me-2">🏢</span>
<span id="preview-nombre" class="fw-bold">Nuevo Template</span>
</div>
<div id="preview-descripcion" class="small mb-3 opacity-75">
Descripción del template...
</div>
<div id="preview-config" class="d-flex flex-wrap">
<!-- Configuración se mostrará aquí dinámicamente -->
</div>
</div>
</div>
<div class="tips-section">
<h6 class="small text-primary mb-3 fw-bold">💡 Consejos para Templates Efectivos</h6>
<div class="tip-item">
<i class="fas fa-lightbulb text-warning"></i>
<span>Usa nombres descriptivos que identifiquen fácilmente el template</span>
</div>
<div class="tip-item">
<i class="fas fa-flag text-info"></i>
<span>Elige iconos de países o símbolos representativos</span>
</div>
<div class="tip-item">
<i class="fas fa-filter text-success"></i>
<span>Solo llena campos que sean comunes en todas las importaciones</span>
</div>
<div class="tip-item">
<i class="fas fa-clock text-danger"></i>
<span>Deja vacíos los campos que cambien frecuentemente</span>
</div>
<div class="tip-item">
<i class="fas fa-save text-primary"></i>
<span>Los templates te ahorrarán tiempo en futuras importaciones</span>
</div>
</div>
<!-- Botón adicional para resetear -->
<div class="d-grid mt-3">
<button type="button" class="btn btn-outline-secondary btn-sm" onclick="resetForm()">
<i class="fas fa-refresh me-1"></i>Limpiar Formulario
</button>
</div>
</div>
</div>
</div>
</div>
</div>
<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>
// Variables globales
let choicesInstances = {};
// Inicializar cuando el DOM esté listo
document.addEventListener('DOMContentLoaded', function() {
initializeChoices();
initializeEmojiPicker();
initializePreview();
initializeValidation();
initializeDynamicUpdates();
});
// Inicializar Choices.js para selects con búsqueda
function initializeChoices() {
document.querySelectorAll('.searchable').forEach(element => {
choicesInstances[element.id] = new Choices(element, {
searchEnabled: true,
itemSelectText: '',
shouldSort: false,
searchPlaceholderValue: 'Buscar...',
noResultsText: 'No se encontraron resultados',
noChoicesText: 'No hay opciones disponibles'
});
});
}
// Inicializar selector de emojis
function initializeEmojiPicker() {
// Manejar clicks en emojis
document.addEventListener('click', function(e) {
if (e.target.classList.contains('emoji-item')) {
const emoji = e.target.getAttribute('data-emoji') || e.target.textContent;
selectEmoji(emoji);
// Cerrar dropdown
const dropdown = bootstrap.Dropdown.getInstance(document.querySelector('[data-bs-toggle="dropdown"]'));
if (dropdown) {
dropdown.hide();
}
}
});
// Mostrar emoji seleccionado visualmente
document.addEventListener('mouseenter', function(e) {
if (e.target.classList.contains('emoji-item')) {
// Quitar selección anterior
document.querySelectorAll('.emoji-item.selected').forEach(item => {
item.classList.remove('selected');
});
// Agregar efecto hover mejorado
e.target.style.transform = 'scale(1.2)';
e.target.style.background = 'linear-gradient(135deg, #4CAF50, #45a049)';
e.target.style.color = 'white';
e.target.style.borderRadius = '8px';
}
});
document.addEventListener('mouseleave', function(e) {
if (e.target.classList.contains('emoji-item')) {
e.target.style.transform = '';
e.target.style.background = '';
e.target.style.color = '';
e.target.style.borderRadius = '';
}
});
}
// Función para seleccionar emoji (mejorada)
function selectEmoji(emoji) {
const iconInput = document.getElementById('icono');
const iconDisplay = document.getElementById('icon-display');
// Actualizar valor e interfaz
iconInput.value = emoji;
iconDisplay.textContent = emoji;
// Efecto visual en el display
iconDisplay.style.transform = 'scale(1.3)';
iconDisplay.style.transition = 'transform 0.3s ease';
setTimeout(() => {
iconDisplay.style.transform = 'scale(1)';
}, 300);
updatePreview();
// Mostrar feedback visual
showToast('✅ Icono actualizado', 'success');
}
// Inicializar vista previa
function initializePreview() {
updatePreview();
// Eventos para actualizar vista previa en tiempo real
document.querySelectorAll('#templateForm input, #templateForm select, #templateForm textarea').forEach(element => {
element.addEventListener('input', debounce(updatePreview, 300));
element.addEventListener('change', updatePreview);
});
}
// Función para actualizar vista previa (mejorada)
function updatePreview() {
const nombre = document.getElementById('nombre').value.trim() || 'Nuevo Template';
const descripcion = document.getElementById('descripcion').value.trim() || 'Descripción del template...';
const icono = document.getElementById('icono').value;
// Actualizar elementos de vista previa con animación
const previewNombre = document.getElementById('preview-nombre');
const previewDescripcion = document.getElementById('preview-descripcion');
const previewIcono = document.getElementById('preview-icono');
if (previewNombre.textContent !== nombre) {
previewNombre.style.opacity = '0.5';
setTimeout(() => {
previewNombre.textContent = nombre;
previewNombre.style.opacity = '1';
}, 150);
}
if (previewDescripcion.textContent !== descripcion) {
previewDescripcion.style.opacity = '0.5';
setTimeout(() => {
previewDescripcion.textContent = descripcion;
previewDescripcion.style.opacity = '1';
}, 150);
}
previewIcono.textContent = icono;
// Actualizar configuración con más campos
const config = [];
const fields = [
{ id: 'tipo_moneda', label: '💰 Moneda' },
{ id: 'incoterm', label: '📋 INCOTERM', getValue: (el) => el.value ? el.options[el.selectedIndex].text.split(' - ')[0] : '' },
{ id: 'vinculacion', label: '🔗 Vinculación', map: {'0':'Sin Vinc.', '1':'No Afecta', '2':'Afecta'} },
{ id: 'tasa_preferencial', label: '📊 Tasa' },
{ id: 'pais_proveedor', label: '🌍 País', getValue: (el) => el.value ? el.options[el.selectedIndex].text : '' },
{ id: 'anexo22_apendice', label: '🏢 Aduana', getValue: (el) => el.value ? el.options[el.selectedIndex].text.split(' ')[0] : '' },
{ id: 'transportista_id', label: '🚛 Transportista', getValue: (el) => el.value ? el.options[el.selectedIndex].text.split(' - ')[0] : '' }
];
fields.forEach(field => {
const element = document.getElementById(field.id);
let value = '';
if (field.getValue) {
value = field.getValue(element);
} else {
value = element.value;
}
if (value) {
const displayValue = field.map && field.map[value] ? field.map[value] : value;
config.push(`<span class="template-preview-item">${field.label}: ${displayValue}</span>`);
}
});
const configContainer = document.getElementById('preview-config');
configContainer.style.opacity = '0.5';
setTimeout(() => {
configContainer.innerHTML = config.join('');
configContainer.style.opacity = '1';
}, 150);
}
// Inicializar actualizaciones dinámicas
function initializeDynamicUpdates() {
// Actualizar texto del país cuando cambie
const paisSelect = document.getElementById('pais_proveedor');
if (paisSelect && choicesInstances[paisSelect.id]) {
paisSelect.addEventListener('change', function() {
const selectedOption = this.options[this.selectedIndex];
document.getElementById('pais_proveedor_texto').value = selectedOption ? selectedOption.text : '';
updatePreview();
});
}
// Filtrar choferes según transportista seleccionado
const transportistaSelect = document.getElementById('transportista_id');
const choferSelect = document.getElementById('chofer_id');
if (transportistaSelect && choferSelect) {
transportistaSelect.addEventListener('change', function() {
const transportistaId = this.value;
const choferChoices = choicesInstances[choferSelect.id];
if (choferChoices) {
// Aquí podrías hacer una llamada AJAX para filtrar choferes
// Por ahora simplemente reseteamos la selección
choferChoices.setChoiceByValue('');
}
});
}
}
// Inicializar validación del formulario
function initializeValidation() {
const form = document.getElementById('templateForm');
form.addEventListener('submit', function(e) {
if (!validateForm()) {
e.preventDefault();
return false;
}
// Mostrar loading mejorado
Swal.fire({
title: 'Guardando template...',
html: '<div class="loading-spinner"></div><br>Por favor espera...',
allowOutsideClick: false,
allowEscapeKey: false,
showConfirmButton: false,
customClass: {
popup: 'swal2-noanimation'
}
});
});
// Validación en tiempo real del nombre
const nombreInput = document.getElementById('nombre');
nombreInput.addEventListener('input', function() {
validateField(this);
});
}
// Función de validación del formulario
function validateForm() {
const nombre = document.getElementById('nombre').value.trim();
if (!nombre) {
showErrorAlert('Campo requerido', 'El nombre del template es obligatorio');
document.getElementById('nombre').focus();
return false;
}
if (nombre.length > 100) {
showErrorAlert('Nombre muy largo', 'El nombre no puede exceder 100 caracteres');
document.getElementById('nombre').focus();
return false;
}
return true;
}
// Validar campo individual
function validateField(field) {
const value = field.value.trim();
if (field.id === 'nombre') {
if (value.length > 100) {
field.classList.add('is-invalid');
field.classList.remove('is-valid');
} else if (value.length > 0) {
field.classList.add('is-valid');
field.classList.remove('is-invalid');
} else {
field.classList.remove('is-valid', 'is-invalid');
}
}
}
// Funciones de utilidad
function debounce(func, wait) {
let timeout;
return function executedFunction(...args) {
const later = () => {
clearTimeout(timeout);
func(...args);
};
clearTimeout(timeout);
timeout = setTimeout(later, wait);
};
}
function showErrorAlert(title, text) {
Swal.fire({
icon: 'error',
title: title,
text: text,
confirmButtonColor: '#dc3545',
confirmButtonText: 'Entendido'
});
}
function showToast(message, type = 'info') {
const Toast = Swal.mixin({
toast: true,
position: 'top-end',
showConfirmButton: false,
timer: 2000,
timerProgressBar: true
});
Toast.fire({
icon: type,
title: message
});
}
// Función para resetear el formulario
function resetForm() {
document.getElementById('templateForm').reset();
// Resetear Choices.js
Object.values(choicesInstances).forEach(choice => {
choice.setChoiceByValue('');
});
// Resetear icono
document.getElementById('icono').value = '🏢';
document.getElementById('icon-display').textContent = '🏢';
updatePreview();
showToast('Formulario reiniciado', 'info');
}
</script>
</body>
</html>

View File

@@ -0,0 +1,494 @@
<?php include __DIR__ . '/../partials/sidebar_importador.php'; ?>
<!DOCTYPE html>
<html lang="es">
<head>
<meta charset="UTF-8">
<title>✏️ Editar Template Rápido</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">
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/choices.js/public/assets/styles/choices.min.css"/>
<script src="https://cdn.jsdelivr.net/npm/sweetalert2@11"></script>
<script src="https://code.jquery.com/jquery-3.7.1.min.js"></script>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0/css/all.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; box-shadow: 0 4px 12px rgba(0,0,0,0.1); }
.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); }
.form-control:focus, .form-select:focus {
border-color: #0d6efd;
box-shadow: 0 0 0 0.2rem rgba(13, 110, 253, 0.25);
}
.emoji-picker {
display: grid;
grid-template-columns: repeat(8, 1fr);
gap: 5px;
padding: 10px;
max-height: 200px;
overflow-y: auto;
border: 1px solid #dee2e6;
border-radius: 5px;
background: white;
}
.emoji-item {
padding: 8px;
text-align: center;
cursor: pointer;
border-radius: 3px;
transition: background-color 0.2s;
}
.emoji-item:hover {
background-color: #f8f9fa;
}
.preview-card {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
color: white;
border-radius: 8px;
padding: 15px;
margin-top: 10px;
}
.template-preview-item {
background: rgba(255,255,255,0.2);
border: 1px solid rgba(255,255,255,0.3);
color: white;
padding: 6px 12px;
border-radius: 20px;
font-size: 12px;
margin: 2px;
display: inline-block;
}
.choices.required .choices__inner { border: 1px solid #ced4da; }
.choices.is-invalid .choices__inner { border: 1px solid #dc3545; background-color: #fff5f5; }
.choices.is-valid .choices__inner { border: 1px solid #28a745; background-color: #f5fff5; }
.section-header {
background: linear-gradient(135deg, #f8f9fa 0%, #e9ecef 100%);
padding: 15px;
margin: -15px -15px 15px -15px;
border-radius: 8px 8px 0 0;
border-bottom: 1px solid #dee2e6;
}
</style>
</head>
<body>
<div class="content">
<div class="d-flex justify-content-between align-items-center mb-4">
<h4 class="mb-0">✏️ Editar Template: <?= htmlspecialchars($template['icono']) ?> <?= htmlspecialchars($template['nombre']) ?></h4>
<div>
<a href="/IMPORTADORES/templates_rapidos/lista" class="btn btn-secondary btn-animated me-2">
<i class="fas fa-list me-1"></i>Volver a Lista
</a>
<a href="/IMPORTADORES/templates_rapidos/crear" class="btn btn-success btn-animated">
<i class="fas fa-plus me-1"></i>Nuevo Template
</a>
</div>
</div>
<!-- Info del template -->
<div class="row mb-3">
<div class="col-md-12">
<div class="card bg-info text-white">
<div class="card-body py-3">
<div class="row align-items-center">
<div class="col-md-8">
<h6 class="mb-1"><i class="fas fa-info-circle me-2"></i>Información del Template</h6>
<p class="mb-0 small">
Creado el: <?= $template['fecha_creacion'] ? $template['fecha_creacion']->format('d/m/Y H:i') : 'N/A' ?> |
Usado: <?= $template['veces_usado'] ?> veces |
Última modificación: <?= $template['fecha_modificacion'] ? $template['fecha_modificacion']->format('d/m/Y H:i') : 'N/A' ?>
</p>
</div>
<div class="col-md-4 text-end">
<span class="h4"><?= htmlspecialchars($template['icono']) ?></span>
</div>
</div>
</div>
</div>
</div>
</div>
<div class="row">
<!-- Formulario Principal -->
<div class="col-lg-8">
<div class="card">
<div class="card-body p-4">
<form id="templateForm" action="/IMPORTADORES/templates_rapidos/actualizar" method="POST">
<input type="hidden" name="id" value="<?= htmlspecialchars($template['id']) ?>">
<!-- Información Básica -->
<div class="section-header">
<h6 class="mb-0"><i class="fas fa-info-circle me-2"></i>Información Básica</h6>
</div>
<div class="row mb-4">
<div class="col-md-6">
<label for="nombre" class="form-label">Nombre del Template *</label>
<input id="nombre" name="nombre" type="text" class="form-control" required
placeholder="ej. Importación China" maxlength="100"
value="<?= htmlspecialchars($template['nombre']) ?>">
</div>
<div class="col-md-6">
<label for="icono" class="form-label">Icono</label>
<div class="input-group">
<input id="icono" name="icono" type="text" class="form-control" readonly
value="<?= htmlspecialchars($template['icono']) ?>">
<button type="button" class="btn btn-outline-secondary" data-bs-toggle="dropdown">
<i class="fas fa-smile"></i>
</button>
<ul class="dropdown-menu dropdown-menu-end p-0" style="width: 300px;">
<li>
<div class="emoji-picker">
<div class="emoji-item" onclick="selectEmoji('🇨🇳')">🇨🇳</div>
<div class="emoji-item" onclick="selectEmoji('🇺🇸')">🇺🇸</div>
<div class="emoji-item" onclick="selectEmoji('🇪🇺')">🇪🇺</div>
<div class="emoji-item" onclick="selectEmoji('🇩🇪')">🇩🇪</div>
<div class="emoji-item" onclick="selectEmoji('🇯🇵')">🇯🇵</div>
<div class="emoji-item" onclick="selectEmoji('🇰🇷')">🇰🇷</div>
<div class="emoji-item" onclick="selectEmoji('🇮🇹')">🇮🇹</div>
<div class="emoji-item" onclick="selectEmoji('🇫🇷')">🇫🇷</div>
<div class="emoji-item" onclick="selectEmoji('🏢')">🏢</div>
<div class="emoji-item" onclick="selectEmoji('🏭')">🏭</div>
<div class="emoji-item" onclick="selectEmoji('🚢')">🚢</div>
<div class="emoji-item" onclick="selectEmoji('✈️')">✈️</div>
<div class="emoji-item" onclick="selectEmoji('🚛')">🚛</div>
<div class="emoji-item" onclick="selectEmoji('📦')">📦</div>
<div class="emoji-item" onclick="selectEmoji('💼')">💼</div>
<div class="emoji-item" onclick="selectEmoji('⚡')">⚡</div>
<div class="emoji-item" onclick="selectEmoji('🔥')">🔥</div>
<div class="emoji-item" onclick="selectEmoji('⭐')">⭐</div>
<div class="emoji-item" onclick="selectEmoji('🎯')">🎯</div>
<div class="emoji-item" onclick="selectEmoji('🚀')">🚀</div>
</div>
</li>
</ul>
</div>
</div>
</div>
<div class="row mb-4">
<div class="col-md-12">
<label for="descripcion" class="form-label">Descripción</label>
<textarea id="descripcion" name="descripcion" class="form-control" rows="2"
placeholder="Descripción breve del template (opcional)" maxlength="255"><?= htmlspecialchars($template['descripcion']) ?></textarea>
</div>
</div>
<!-- Configuración Principal -->
<div class="section-header">
<h6 class="mb-0"><i class="fas fa-cog me-2"></i>Configuración Principal</h6>
</div>
<?php $config = $template['config']; ?>
<div class="row mb-3">
<div class="col-md-3">
<label for="tipo_moneda" class="form-label">Moneda</label>
<select id="tipo_moneda" name="tipo_moneda" class="form-select searchable">
<option value="">-- Sin especificar --</option>
<option value="MXN" <?= ($config['tipo_moneda'] ?? '') === 'MXN' ? 'selected' : '' ?>>Peso Mexicano (MXN)</option>
<option value="USD" <?= ($config['tipo_moneda'] ?? '') === 'USD' ? 'selected' : '' ?>>Dólar USD (USD)</option>
<option value="EUR" <?= ($config['tipo_moneda'] ?? '') === 'EUR' ? 'selected' : '' ?>>Euro (EUR)</option>
<option value="CNY" <?= ($config['tipo_moneda'] ?? '') === 'CNY' ? 'selected' : '' ?>>Yuan (CNY)</option>
<option value="GBP" <?= ($config['tipo_moneda'] ?? '') === 'GBP' ? 'selected' : '' ?>>Libra GBP (GBP)</option>
<option value="JPY" <?= ($config['tipo_moneda'] ?? '') === 'JPY' ? 'selected' : '' ?>>Yen (JPY)</option>
</select>
</div>
<div class="col-md-3">
<label for="incoterm" class="form-label">INCOTERM</label>
<select id="incoterm" name="incoterm" class="form-select searchable">
<option value="">-- Sin especificar --</option>
<?php foreach($incoterms as $inc): ?>
<option value="<?= htmlspecialchars($inc['INCOTERM']) ?>"
<?= ($config['incoterm'] ?? '') === $inc['INCOTERM'] ? 'selected' : '' ?>>
<?= htmlspecialchars($inc['INCOTERM'].' - '.$inc['DESCESPANOL']) ?>
</option>
<?php endforeach; ?>
</select>
</div>
<div class="col-md-3">
<label for="vinculacion" class="form-label">Vinculación</label>
<select id="vinculacion" name="vinculacion" class="form-select">
<option value="">-- Sin especificar --</option>
<option value="0" <?= ($config['vinculacion'] ?? '') === '0' ? 'selected' : '' ?>>No existe</option>
<option value="1" <?= ($config['vinculacion'] ?? '') === '1' ? 'selected' : '' ?>>Existe, no afecta</option>
<option value="2" <?= ($config['vinculacion'] ?? '') === '2' ? 'selected' : '' ?>>Existe y afecta</option>
</select>
</div>
<div class="col-md-3">
<label for="tasa_preferencial" class="form-label">Tasa Preferencial</label>
<select id="tasa_preferencial" name="tasa_preferencial" class="form-select">
<option value="">-- Sin especificar --</option>
<option value="General" <?= ($config['tasa_preferencial'] ?? '') === 'General' ? 'selected' : '' ?>>General</option>
<option value="TLC" <?= ($config['tasa_preferencial'] ?? '') === 'TLC' ? 'selected' : '' ?>>TLC</option>
<option value="PROSEC" <?= ($config['tasa_preferencial'] ?? '') === 'PROSEC' ? 'selected' : '' ?>>PROSEC</option>
<option value="ALADI" <?= ($config['tasa_preferencial'] ?? '') === 'ALADI' ? 'selected' : '' ?>>ALADI</option>
<option value="COMERCIALIZADORA" <?= ($config['tasa_preferencial'] ?? '') === 'COMERCIALIZADORA' ? 'selected' : '' ?>>COMERCIALIZADORA</option>
</select>
</div>
</div>
<div class="row mb-3">
<div class="col-md-6">
<label for="pais_proveedor" class="form-label">País Proveedor</label>
<select id="pais_proveedor" name="pais_proveedor" class="form-select searchable">
<option value="">-- Sin especificar --</option>
<?php foreach($paises as $p): ?>
<option value="<?= htmlspecialchars($p['id_pais']) ?>"
<?= ($config['pais_proveedor'] ?? '') === $p['id_pais'] ? 'selected' : '' ?>>
<?= htmlspecialchars($p['nombre']) ?>
</option>
<?php endforeach; ?>
</select>
<input type="hidden" id="pais_proveedor_texto" name="pais_proveedor_texto"
value="<?= htmlspecialchars($config['pais_proveedor_texto'] ?? '') ?>">
</div>
<div class="col-md-6">
<label for="unidad_comercial_id" class="form-label">Unidad de Medida</label>
<select id="unidad_comercial_id" name="unidad_comercial_id" class="form-select searchable">
<option value="">-- Sin especificar --</option>
<?php foreach($unidades_medida as $um): ?>
<option value="<?= htmlspecialchars($um['id']) ?>"
<?= ($config['unidad_comercial_id'] ?? '') === $um['id'] ? 'selected' : '' ?>>
<?= htmlspecialchars($um['descripcion']) ?>
</option>
<?php endforeach; ?>
</select>
</div>
</div>
<!-- Configuración Avanzada -->
<div class="section-header">
<h6 class="mb-0"><i class="fas fa-tools me-2"></i>Configuración Avanzada</h6>
</div>
<div class="row mb-3">
<div class="col-md-6">
<label for="anexo22_apendice" class="form-label">Aduana</label>
<select id="anexo22_apendice" name="anexo22_apendice" class="form-select searchable">
<option value="">-- Sin especificar --</option>
<?php foreach($aduanas as $a): ?>
<option value="<?= htmlspecialchars($a['aduana_seccion']) ?>"
<?= ($config['anexo22_apendice'] ?? '') === $a['aduana_seccion'] ? 'selected' : '' ?>>
<?= htmlspecialchars($a['aduana_seccion'].' '.$a['nombre']) ?>
</option>
<?php endforeach; ?>
</select>
</div>
<div class="col-md-6">
<label for="patente" class="form-label">Patente Aduanal</label>
<select id="patente" name="patente" class="form-select searchable">
<option value="">-- Sin especificar --</option>
<?php foreach($patentes as $pt): ?>
<option value="<?= htmlspecialchars($pt['id_agente']) ?>"
<?= ($config['patente'] ?? '') === $pt['id_agente'] ? 'selected' : '' ?>>
<?= htmlspecialchars($pt['patente'].' - '.$pt['agente_aduanal']) ?>
</option>
<?php endforeach; ?>
</select>
</div>
</div>
<div class="row mb-4">
<div class="col-md-6">
<label for="transportista_id" class="form-label">Transportista</label>
<select id="transportista_id" name="transportista_id" class="form-select searchable">
<option value="">-- Sin especificar --</option>
<?php foreach($transportistas as $t): ?>
<option value="<?= htmlspecialchars($t['id_transportista']) ?>"
<?= ($config['transportista_id'] ?? '') === $t['id_transportista'] ? 'selected' : '' ?>>
<?= htmlspecialchars($t['clave_identificador'] . ' - ' . $t['nombre']) ?>
</option>
<?php endforeach; ?>
</select>
</div>
<div class="col-md-6">
<label for="chofer_id" class="form-label">Chofer</label>
<select id="chofer_id" name="chofer_id" class="form-select searchable">
<option value="">-- Sin especificar --</option>
<?php foreach($choferes as $c): ?>
<option value="<?= htmlspecialchars($c['id_chofer']) ?>"
<?= ($config['chofer_id'] ?? '') === $c['id_chofer'] ? 'selected' : '' ?>>
<?= htmlspecialchars($c['nombre']) ?>
</option>
<?php endforeach; ?>
</select>
</div>
</div>
<!-- Botones -->
<div class="text-end">
<button type="submit" class="btn btn-primary btn-animated me-2">
<i class="fas fa-save me-1"></i>Actualizar Template
</button>
<a href="/IMPORTADORES/templates_rapidos/lista" class="btn btn-secondary btn-animated">
<i class="fas fa-times me-1"></i>Cancelar
</a>
</div>
</form>
</div>
</div>
</div>
<!-- Vista Previa -->
<div class="col-lg-4">
<div class="card sticky-top" style="top: 20px;">
<div class="card-header bg-warning text-dark">
<h6 class="mb-0"><i class="fas fa-eye me-2"></i>Vista Previa</h6>
</div>
<div class="card-body">
<div id="template-preview">
<div class="preview-card">
<div class="d-flex align-items-center mb-2">
<span id="preview-icono" class="fs-4 me-2"><?= htmlspecialchars($template['icono']) ?></span>
<span id="preview-nombre" class="fw-bold"><?= htmlspecialchars($template['nombre']) ?></span>
</div>
<div id="preview-descripcion" class="small mb-3 opacity-75">
<?= htmlspecialchars($template['descripcion']) ?>
</div>
<div id="preview-config" class="d-flex flex-wrap">
<!-- Configuración se mostrará aquí dinámicamente -->
</div>
</div>
</div>
<div class="mt-3">
<h6 class="small text-muted mb-2">📊 Estadísticas de Uso:</h6>
<div class="small text-muted">
<div><i class="fas fa-mouse-pointer text-primary me-1"></i>Usado: <?= $template['veces_usado'] ?> veces</div>
<div><i class="fas fa-clock text-info me-1"></i>Última vez: <?= $template['ultima_vez_usado'] ? $template['ultima_vez_usado']->format('d/m/Y H:i') : 'Nunca' ?></div>
</div>
</div>
<div class="mt-3">
<h6 class="small text-muted mb-2">💡 Consejos:</h6>
<ul class="list-unstyled small text-muted">
<li><i class="fas fa-check text-success me-1"></i> Usa nombres descriptivos</li>
<li><i class="fas fa-check text-success me-1"></i> Elige iconos representativos</li>
<li><i class="fas fa-check text-success me-1"></i> Solo llena campos comunes</li>
<li><i class="fas fa-check text-success me-1"></i> Deja vacío lo que varía</li>
</ul>
</div>
</div>
</div>
</div>
</div>
</div>
<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>
// Inicializar Choices.js
document.addEventListener('DOMContentLoaded', function() {
document.querySelectorAll('.searchable').forEach(element => {
new Choices(element, {
searchEnabled: true,
itemSelectText: '',
shouldSort: false
});
});
// Inicializar vista previa
updatePreview();
// Eventos para actualizar vista previa
document.querySelectorAll('#templateForm input, #templateForm select, #templateForm textarea').forEach(element => {
element.addEventListener('input', updatePreview);
element.addEventListener('change', updatePreview);
});
});
// Función para seleccionar emoji
function selectEmoji(emoji) {
document.getElementById('icono').value = emoji;
updatePreview();
}
// Función para actualizar vista previa
function updatePreview() {
const nombre = document.getElementById('nombre').value || 'Template sin nombre';
const descripcion = document.getElementById('descripcion').value || 'Sin descripción';
const icono = document.getElementById('icono').value;
document.getElementById('preview-nombre').textContent = nombre;
document.getElementById('preview-descripcion').textContent = descripcion;
document.getElementById('preview-icono').textContent = icono;
// Actualizar configuración
const config = [];
const fields = [
{ id: 'tipo_moneda', label: 'Moneda' },
{ id: 'incoterm', label: 'INCOTERM' },
{ id: 'vinculacion', label: 'Vinculación', map: {'0':'Sin Vinc.', '1':'No Afecta', '2':'Afecta'} },
{ id: 'tasa_preferencial', label: 'Tasa' }
];
fields.forEach(field => {
const value = document.getElementById(field.id).value;
if (value) {
const displayValue = field.map && field.map[value] ? field.map[value] : value;
config.push(`<span class="template-preview-item">${field.label}: ${displayValue}</span>`);
}
});
document.getElementById('preview-config').innerHTML = config.join('');
}
// Actualizar texto del país cuando cambie
document.getElementById('pais_proveedor').addEventListener('change', function() {
const selectedOption = this.options[this.selectedIndex];
document.getElementById('pais_proveedor_texto').value = selectedOption ? selectedOption.text : '';
updatePreview();
});
// Validación del formulario
document.getElementById('templateForm').addEventListener('submit', function(e) {
const nombre = document.getElementById('nombre').value.trim();
if (!nombre) {
e.preventDefault();
Swal.fire({
icon: 'error',
title: 'Campo requerido',
text: 'El nombre del template es obligatorio',
confirmButtonColor: '#3085d6'
});
document.getElementById('nombre').focus();
return false;
}
if (nombre.length > 100) {
e.preventDefault();
Swal.fire({
icon: 'error',
title: 'Nombre muy largo',
text: 'El nombre no puede exceder 100 caracteres',
confirmButtonColor: '#3085d6'
});
document.getElementById('nombre').focus();
return false;
}
// Mostrar loading
Swal.fire({
title: 'Actualizando template...',
allowOutsideClick: false,
didOpen: () => {
Swal.showLoading();
}
});
});
</script>
</body>
</html>

View File

@@ -0,0 +1,948 @@
<?php include __DIR__ . '/../partials/sidebar_importador.php'; ?>
<!DOCTYPE html>
<html lang="es">
<head>
<meta charset="UTF-8">
<title>📋 Templates Rápidos</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">
<link href="https://cdn.datatables.net/1.13.4/css/jquery.dataTables.min.css" rel="stylesheet">
<script src="https://cdn.jsdelivr.net/npm/sweetalert2@11"></script>
<script src="https://code.jquery.com/jquery-3.7.1.min.js"></script>
<script src="https://cdn.datatables.net/1.13.4/js/jquery.dataTables.min.js"></script>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0/css/all.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; box-shadow: 0 4px 12px rgba(0,0,0,0.1); }
.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); }
.table-responsive { border-radius: 8px; overflow: hidden; }
.dataTables_wrapper .dataTables_paginate .paginate_button { padding: 0.375rem 0.75rem; }
.alert { border-radius: 8px; }
.fade-in { animation: fadeIn 0.5s ease-in; }
@keyframes fadeIn { from { opacity: 0; transform: translateY(20px); } to { opacity: 1; transform: translateY(0); } }
/* ✅ NUEVO: Estilos para filtros de templates */
.template-filters {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
border-radius: 12px;
padding: 20px;
margin-bottom: 25px;
color: white;
}
.filter-btn {
background: rgba(255,255,255,0.1);
border: 1px solid rgba(255,255,255,0.2);
color: white;
border-radius: 20px;
padding: 8px 16px;
margin: 0 5px 10px 0;
transition: all 0.3s ease;
font-size: 13px;
cursor: pointer;
}
.filter-btn:hover, .filter-btn.active {
background: rgba(255,255,255,0.2);
border-color: rgba(255,255,255,0.4);
transform: translateY(-2px);
color: white;
}
.filter-btn.active {
background: rgba(255,255,255,0.25);
font-weight: bold;
}
/* ✅ NUEVO: Botón flotante de ayuda */
.help-float-btn {
position: fixed;
bottom: 30px;
right: 30px;
width: 60px;
height: 60px;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
border: none;
border-radius: 50%;
color: white;
font-size: 24px;
cursor: pointer;
box-shadow: 0 8px 25px rgba(102, 126, 234, 0.4);
transition: all 0.3s ease;
z-index: 1060;
display: flex;
align-items: center;
justify-content: center;
}
.help-float-btn:hover {
transform: translateY(-3px) scale(1.1);
box-shadow: 0 12px 35px rgba(102, 126, 234, 0.6);
color: white;
}
.help-float-btn:active {
transform: translateY(-1px) scale(1.05);
}
/* ✅ NUEVO: Mini-modal de ayuda */
.shortcuts-modal {
position: fixed;
bottom: 100px;
right: 30px;
width: 320px;
background: white;
border-radius: 12px;
box-shadow: 0 15px 40px rgba(0,0,0,0.2);
z-index: 1070;
display: none;
animation: slideInUp 0.3s ease;
}
@keyframes slideInUp {
from { opacity: 0; transform: translateY(20px); }
to { opacity: 1; transform: translateY(0); }
}
.shortcuts-header {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
color: white;
padding: 15px 20px;
border-radius: 12px 12px 0 0;
font-weight: bold;
}
.shortcuts-content {
padding: 20px;
max-height: 400px;
overflow-y: auto;
}
.shortcut-item {
display: flex;
justify-content: space-between;
align-items: center;
padding: 10px 0;
border-bottom: 1px solid #eee;
}
.shortcut-item:last-child {
border-bottom: none;
}
.shortcut-key {
background: #f1f3f4;
border: 1px solid #dadce0;
border-radius: 4px;
padding: 4px 8px;
font-size: 12px;
font-weight: bold;
color: #5f6368;
min-width: 50px;
text-align: center;
}
.shortcut-desc {
flex: 1;
margin-right: 10px;
font-size: 13px;
color: #333;
}
/* ✅ NUEVO: Templates colapsados por defecto */
.templates-section {
margin-bottom: 25px;
}
.section-header {
background: white;
border: 1px solid #dee2e6;
border-radius: 8px 8px 0 0;
padding: 15px 20px;
cursor: pointer;
transition: all 0.2s ease;
display: flex;
justify-content: space-between;
align-items: center;
}
.section-header:hover {
background: #f8f9fa;
}
.section-content {
border: 1px solid #dee2e6;
border-top: none;
border-radius: 0 0 8px 8px;
display: none;
}
.section-content.show {
display: block;
}
.toggle-icon {
transition: transform 0.2s ease;
}
.section-header.active .toggle-icon {
transform: rotate(180deg);
}
/* Mejorar badges */
.scope-badge {
font-size: 11px;
padding: 4px 8px;
border-radius: 12px;
}
</style>
</head>
<body>
<div class="content">
<div class="d-flex justify-content-between align-items-center mb-4">
<h4 class="mb-0">📋 Templates Rápidos</h4>
<div>
<a href="/IMPORTADORES/templates_rapidos/crear" class="btn btn-success btn-animated">
<i class="fas fa-plus me-1"></i>Nuevo Template
</a>
<a href="/IMPORTADORES/home" class="btn btn-secondary btn-animated ms-2">
<i class="fas fa-arrow-left me-1"></i>Volver
</a>
</div>
</div>
<!-- Alertas -->
<?php if (isset($_GET['created'])): ?>
<div class="alert alert-success alert-dismissible fade show fade-in" role="alert">
<i class="fas fa-check-circle me-2"></i>
<strong>¡Éxito!</strong> Template creado correctamente.
<button type="button" class="btn-close" data-bs-dismiss="alert"></button>
</div>
<?php endif; ?>
<?php if (isset($_GET['updated'])): ?>
<div class="alert alert-info alert-dismissible fade show fade-in" role="alert">
<i class="fas fa-info-circle me-2"></i>
<strong>¡Actualizado!</strong> Template modificado correctamente.
<button type="button" class="btn-close" data-bs-dismiss="alert"></button>
</div>
<?php endif; ?>
<?php if (isset($_GET['deleted'])): ?>
<div class="alert alert-warning alert-dismissible fade show fade-in" role="alert">
<i class="fas fa-exclamation-circle me-2"></i>
<strong>¡Eliminado!</strong> Template desactivado correctamente.
<button type="button" class="btn-close" data-bs-dismiss="alert"></button>
</div>
<?php endif; ?>
<!-- ✅ NUEVO: Filtros mejorados -->
<div class="template-filters">
<div class="d-flex justify-content-between align-items-center mb-3">
<h6 class="mb-0"><i class="fas fa-filter me-2"></i>Filtrar Templates</h6>
<small id="template-count" class="opacity-75">Cargando...</small>
</div>
<div class="d-flex flex-wrap">
<button class="filter-btn active" data-filter="all">
<i class="fas fa-th-list me-1"></i>Todos
</button>
<button class="filter-btn" data-filter="personal">
<i class="fas fa-user me-1"></i>Mis Templates
</button>
<button class="filter-btn" data-filter="agencia">
<i class="fas fa-building me-1"></i>De mi Agencia
</button>
<button class="filter-btn" data-filter="mas-usados">
<i class="fas fa-fire me-1"></i>Más Usados
</button>
<button class="filter-btn" data-filter="recientes">
<i class="fas fa-clock me-1"></i>Recientes
</button>
</div>
</div>
<!-- ✅ MODIFICADO: Templates organizados por secciones colapsables -->
<div class="templates-section">
<div class="section-header" data-toggle="personal">
<div>
<h6 class="mb-0"><i class="fas fa-user text-primary me-2"></i>Mis Templates Personalizados</h6>
<small class="text-muted">Templates que has creado</small>
</div>
<i class="fas fa-chevron-down toggle-icon"></i>
</div>
<div class="section-content show" id="personal-templates">
<div class="card-body p-0">
<div class="table-responsive">
<table id="personalTemplatesTable" class="table table-hover mb-0" style="width:100%">
<thead class="bg-light">
<tr>
<th width="60">ID</th>
<th width="200">Nombre</th>
<th>Descripción</th>
<th width="80">Usos</th>
<th width="100">Creado</th>
<th width="120">Acciones</th>
</tr>
</thead>
<tbody></tbody>
</table>
</div>
</div>
</div>
</div>
<div class="templates-section">
<div class="section-header" data-toggle="agencia">
<div>
<h6 class="mb-0"><i class="fas fa-building text-info me-2"></i>Templates de Agencia</h6>
<small class="text-muted">Compartidos en tu agencia</small>
</div>
<i class="fas fa-chevron-down toggle-icon"></i>
</div>
<div class="section-content" id="agencia-templates">
<div class="card-body p-0">
<div class="table-responsive">
<table id="agenciaTemplatesTable" class="table table-hover mb-0" style="width:100%">
<thead class="bg-light">
<tr>
<th width="60">ID</th>
<th width="200">Nombre</th>
<th>Descripción</th>
<th width="80">Usos</th>
<th width="100">Creado</th>
<th width="120">Acciones</th>
</tr>
</thead>
<tbody></tbody>
</table>
</div>
</div>
</div>
</div>
<!-- Templates globales solo se muestran si se solicita explícitamente -->
<div class="templates-section" style="display: none;" id="global-section">
<div class="section-header" data-toggle="global">
<div>
<h6 class="mb-0"><i class="fas fa-globe text-secondary me-2"></i>Templates Globales</h6>
<small class="text-muted">Predeterminados del sistema</small>
</div>
<i class="fas fa-chevron-down toggle-icon"></i>
</div>
<div class="section-content" id="global-templates">
<div class="card-body p-0">
<div class="table-responsive">
<table id="globalTemplatesTable" class="table table-hover mb-0" style="width:100%">
<thead class="bg-light">
<tr>
<th width="60">ID</th>
<th width="200">Nombre</th>
<th>Descripción</th>
<th width="80">Usos</th>
<th width="100">Creado</th>
<th width="80">Ver</th>
</tr>
</thead>
<tbody></tbody>
</table>
</div>
</div>
</div>
</div>
</div>
<!-- ✅ NUEVO: Botón flotante de ayuda -->
<button class="help-float-btn" id="helpBtn" title="Atajos rápidos">
<i class="fas fa-question"></i>
</button>
<!-- ✅ NUEVO: Mini-modal de atajos -->
<div class="shortcuts-modal" id="shortcutsModal">
<div class="shortcuts-header">
<div class="d-flex justify-content-between align-items-center">
<span><i class="fas fa-keyboard me-2"></i>Atajos Rápidos</span>
<button type="button" class="btn-close btn-close-white" id="closeShortcuts"></button>
</div>
</div>
<div class="shortcuts-content">
<div class="mb-3">
<strong class="text-primary">🎯 Templates</strong>
</div>
<div class="shortcut-item">
<div class="shortcut-desc">Crear nuevo template</div>
<div class="shortcut-key">Ctrl+N</div>
</div>
<div class="shortcut-item">
<div class="shortcut-desc">Filtrar personal</div>
<div class="shortcut-key">Alt+P</div>
</div>
<div class="shortcut-item">
<div class="shortcut-desc">Filtrar agencia</div>
<div class="shortcut-key">Alt+A</div>
</div>
<div class="shortcut-item">
<div class="shortcut-desc">Más usados</div>
<div class="shortcut-key">Alt+U</div>
</div>
<div class="mt-4 mb-3">
<strong class="text-primary">⚡ Navegación</strong>
</div>
<div class="shortcut-item">
<div class="shortcut-desc">Buscar en tabla</div>
<div class="shortcut-key">Ctrl+F</div>
</div>
<div class="shortcut-item">
<div class="shortcut-desc">Actualizar datos</div>
<div class="shortcut-key">F5</div>
</div>
<div class="shortcut-item">
<div class="shortcut-desc">Expandir/Colapsar</div>
<div class="shortcut-key">Space</div>
</div>
<div class="mt-4 mb-3">
<strong class="text-primary">📋 Pedimentos</strong>
</div>
<div class="shortcut-item">
<div class="shortcut-desc">Nueva solicitud</div>
<div class="shortcut-key">Ctrl+I</div>
</div>
<div class="shortcut-item">
<div class="shortcut-desc">Aplicar template</div>
<div class="shortcut-key">Enter</div>
</div>
<div class="shortcut-item">
<div class="shortcut-desc">Guardar rápido</div>
<div class="shortcut-key">Ctrl+S</div>
</div>
<div class="mt-3 text-center">
<small class="text-muted">
<i class="fas fa-info-circle me-1"></i>
Presiona <kbd>F1</kbd> para mostrar/ocultar esta ayuda
</small>
</div>
</div>
</div>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/js/bootstrap.bundle.min.js"></script>
<script>
$(document).ready(function() {
// ✅ CONFIGURAR DATATABLES POR SECCIÓN
const personalTable = initDataTable('#personalTemplatesTable', 'personal');
const agenciaTable = initDataTable('#agenciaTemplatesTable', 'agencia');
const globalTable = initDataTable('#globalTemplatesTable', 'global');
// ✅ MANEJO DE SECCIONES COLAPSABLES
$('.section-header').click(function() {
const $this = $(this);
const $content = $this.next('.section-content');
const $icon = $this.find('.toggle-icon');
$content.slideToggle(300);
$this.toggleClass('active');
// Si se está abriendo y la tabla no se ha cargado
if ($content.is(':visible') && !$content.data('loaded')) {
const section = $this.data('toggle');
loadSectionData(section);
$content.data('loaded', true);
}
});
// ✅ FILTROS MEJORADOS
$('.filter-btn').click(function() {
$('.filter-btn').removeClass('active');
$(this).addClass('active');
const filter = $(this).data('filter');
applyFilter(filter);
});
// ✅ BOTÓN FLOTANTE DE AYUDA
$('#helpBtn').click(function() {
$('#shortcutsModal').toggle();
});
$('#closeShortcuts').click(function() {
$('#shortcutsModal').hide();
});
// Cerrar modal al hacer clic fuera
$(document).click(function(e) {
if (!$(e.target).closest('#shortcutsModal, #helpBtn').length) {
$('#shortcutsModal').hide();
}
});
// ✅ ATAJOS DE TECLADO
$(document).keydown(function(e) {
// Ctrl+N: Nuevo template
if (e.ctrlKey && e.key === 'n') {
e.preventDefault();
window.location.href = '/IMPORTADORES/templates_rapidos/crear';
}
// Ctrl+I: Nueva solicitud
if (e.ctrlKey && e.key === 'i') {
e.preventDefault();
window.location.href = '/IMPORTADORES/solicitud_importacion/crear';
}
// Alt+P: Filtrar personal
if (e.altKey && e.key === 'p') {
e.preventDefault();
$('.filter-btn[data-filter="personal"]').click();
}
// Alt+A: Filtrar agencia
if (e.altKey && e.key === 'a') {
e.preventDefault();
$('.filter-btn[data-filter="agencia"]').click();
}
// Alt+U: Más usados
if (e.altKey && e.key === 'u') {
e.preventDefault();
$('.filter-btn[data-filter="mas-usados"]').click();
}
// F1: Toggle ayuda
if (e.key === 'F1') {
e.preventDefault();
$('#shortcutsModal').toggle();
}
// F5: Actualizar
if (e.key === 'F5') {
e.preventDefault();
location.reload();
}
// Space: Toggle sección activa
if (e.key === ' ' && !$(e.target).is('input, textarea, select')) {
e.preventDefault();
$('.section-header.active').click();
}
});
// Auto-refresh cada 30 segundos
setInterval(() => {
refreshActiveTables();
}, 30000);
// Auto-dismiss alerts
setTimeout(() => {
$('.alert').fadeOut('slow');
}, 5000);
// Cargar sección personal por defecto
loadSectionData('personal');
$('#personal-templates').data('loaded', true);
});
// ✅ FUNCIÓN PARA INICIALIZAR DATATABLES
function initDataTable(selector, scope) {
return $(selector).DataTable({
processing: true,
serverSide: false,
data: [],
columns: [
{ data: 0 }, // ID
{ data: 1 }, // Nombre con icono
{ data: 2 }, // Descripción
{ data: 3 }, // Veces usado
{ data: 4 }, // Fecha creación
{ data: 5, orderable: false } // Acciones
],
order: [[3, 'desc']], // Ordenar por usos descendente
language: {
processing: "Procesando...",
search: "Buscar:",
lengthMenu: "Mostrar _MENU_ registros",
info: "Mostrando de _START_ a _END_ de _TOTAL_ registros",
infoEmpty: "Mostrando 0 a 0 de 0 registros",
infoFiltered: "(filtrado de _MAX_ registros totales)",
loadingRecords: "Cargando...",
zeroRecords: "No se encontraron templates " + (scope === 'personal' ? 'personalizados' : scope === 'agencia' ? 'de agencia' : 'globales'),
emptyTable: "No hay templates " + (scope === 'personal' ? 'personalizados' : scope === 'agencia' ? 'de agencia' : 'globales'),
paginate: {
first: "Primero",
previous: "Anterior",
next: "Siguiente",
last: "Último"
}
},
pageLength: 10,
responsive: true,
drawCallback: function() {
$(selector + ' tbody tr').addClass('fade-in');
}
});
}
// ✅ FUNCIÓN PARA CARGAR DATOS POR SECCIÓN
function loadSectionData(section) {
$.ajax({
url: '/IMPORTADORES/templates_rapidos/ajax_lista_por_seccion',
method: 'GET',
data: { seccion: section },
dataType: 'json',
success: function(response) {
let table;
switch(section) {
case 'personal':
table = $('#personalTemplatesTable').DataTable();
break;
case 'agencia':
table = $('#agenciaTemplatesTable').DataTable();
break;
case 'global':
table = $('#globalTemplatesTable').DataTable();
break;
}
if (table) {
table.clear().rows.add(response.data || []).draw();
}
updateTemplateCount();
},
error: function() {
console.error('Error cargando templates de sección:', section);
}
});
}
// ✅ FUNCIÓN PARA APLICAR FILTROS
function applyFilter(filter) {
switch(filter) {
case 'all':
$('.templates-section').show();
$('.section-content.show').each(function() {
if (!$(this).data('loaded')) {
const section = $(this).prev().data('toggle');
loadSectionData(section);
$(this).data('loaded', true);
}
});
break;
case 'personal':
$('.templates-section').hide();
$('.templates-section').first().show();
if (!$('#personal-templates').hasClass('show')) {
$('.section-header').first().click();
}
break;
case 'agencia':
$('.templates-section').hide();
$('.templates-section').eq(1).show();
if (!$('#agencia-templates').hasClass('show')) {
$('.section-header').eq(1).click();
}
break;
case 'mas-usados':
// Mostrar todos pero filtrar por más de 5 usos
$('.templates-section').show();
$('.section-content.show').each(function() {
const table = $(this).find('table').DataTable();
table.search('').columns().search('').draw();
table.column(3).search('^([5-9]|[1-9][0-9]+)$', true, false).draw();
});
break;
case 'recientes':
// Mostrar todos pero filtrar por últimos 7 días
$('.templates-section').show();
const weekAgo = new Date();
weekAgo.setDate(weekAgo.getDate() - 7);
$('.section-content.show').each(function() {
const table = $(this).find('table').DataTable();
table.search('').columns().search('').draw();
// Filtrar por fecha en columna 4
});
break;
}
updateTemplateCount();
}
// ✅ FUNCIÓN PARA ACTUALIZAR CONTADOR DE TEMPLATES
function updateTemplateCount() {
let total = 0;
$('.section-content:visible').each(function() {
const table = $(this).find('table').DataTable();
if (table) {
total += table.rows({search: 'applied'}).count();
}
});
$('#template-count').text(`${total} templates disponibles`);
}
// ✅ FUNCIÓN PARA REFRESCAR TABLAS ACTIVAS
function refreshActiveTables() {
$('.section-content:visible').each(function() {
if ($(this).data('loaded')) {
const section = $(this).prev().data('toggle');
loadSectionData(section);
}
});
}
// ✅ FUNCIONES DE ACCIONES
function aplicarTemplate(id, nombre) {
Swal.fire({
title: '🚀 Aplicar Template',
text: `¿Crear nueva solicitud con el template "${nombre}"?`,
icon: 'question',
showCancelButton: true,
confirmButtonText: '<i class="fas fa-rocket me-1"></i>Aplicar',
cancelButtonText: 'Cancelar',
confirmButtonColor: '#28a745',
cancelButtonColor: '#6c757d'
}).then((result) => {
if (result.isConfirmed) {
window.location.href = `/IMPORTADORES/solicitud_importacion/crear?template=${id}`;
}
});
}
function editarTemplate(id) {
window.location.href = `/IMPORTADORES/templates_rapidos/editar/${id}`;
}
function duplicarTemplate(id, nombre) {
Swal.fire({
title: '📋 Duplicar Template',
text: `¿Crear una copia de "${nombre}"?`,
icon: 'question',
showCancelButton: true,
confirmButtonText: '<i class="fas fa-copy me-1"></i>Duplicar',
cancelButtonText: 'Cancelar',
confirmButtonColor: '#17a2b8',
cancelButtonColor: '#6c757d'
}).then((result) => {
if (result.isConfirmed) {
$.ajax({
url: '/IMPORTADORES/templates_rapidos/duplicar',
method: 'POST',
data: { id: id },
success: function(response) {
if (response.success) {
Swal.fire({
title: '✅ ¡Duplicado!',
text: 'Template duplicado exitosamente',
icon: 'success',
timer: 2000,
showConfirmButton: false
}).then(() => {
location.reload();
});
} else {
Swal.fire('Error', response.message || 'No se pudo duplicar', 'error');
}
},
error: function() {
Swal.fire('Error', 'Error de conexión', 'error');
}
});
}
});
}
function eliminarTemplate(id, nombre) {
Swal.fire({
title: '⚠️ Eliminar Template',
html: `¿Estás seguro de eliminar "<strong>${nombre}</strong>"?<br><small class="text-muted">Esta acción no se puede deshacer</small>`,
icon: 'warning',
showCancelButton: true,
confirmButtonText: '<i class="fas fa-trash me-1"></i>Eliminar',
cancelButtonText: 'Cancelar',
confirmButtonColor: '#dc3545',
cancelButtonColor: '#6c757d',
focusCancel: true
}).then((result) => {
if (result.isConfirmed) {
$.ajax({
url: '/IMPORTADORES/templates_rapidos/eliminar',
method: 'POST',
data: { id: id },
success: function(response) {
if (response.success) {
Swal.fire({
title: '✅ ¡Eliminado!',
text: 'Template eliminado exitosamente',
icon: 'success',
timer: 2000,
showConfirmButton: false
}).then(() => {
location.reload();
});
} else {
Swal.fire('Error', response.message || 'No se pudo eliminar', 'error');
}
},
error: function() {
Swal.fire('Error', 'Error de conexión', 'error');
}
});
}
});
}
function verTemplate(id) {
// Abrir modal con detalles del template
$.ajax({
url: `/IMPORTADORES/templates_rapidos/ver/${id}`,
method: 'GET',
success: function(response) {
Swal.fire({
title: '📄 Detalles del Template',
html: response,
width: '80%',
showCloseButton: true,
showConfirmButton: false,
customClass: {
container: 'template-preview-modal'
}
});
},
error: function() {
Swal.fire('Error', 'No se pudo cargar el template', 'error');
}
});
}
// ✅ MANEJO DE EVENTOS GLOBALES
$(document).on('click', '.btn-aplicar', function(e) {
e.preventDefault();
const id = $(this).data('id');
const nombre = $(this).data('nombre');
aplicarTemplate(id, nombre);
});
$(document).on('click', '.btn-editar', function(e) {
e.preventDefault();
const id = $(this).data('id');
editarTemplate(id);
});
$(document).on('click', '.btn-duplicar', function(e) {
e.preventDefault();
const id = $(this).data('id');
const nombre = $(this).data('nombre');
duplicarTemplate(id, nombre);
});
$(document).on('click', '.btn-eliminar', function(e) {
e.preventDefault();
const id = $(this).data('id');
const nombre = $(this).data('nombre');
eliminarTemplate(id, nombre);
});
$(document).on('click', '.btn-ver', function(e) {
e.preventDefault();
const id = $(this).data('id');
verTemplate(id);
});
// ✅ TOOLTIPS MEJORADOS
$(document).on('mouseenter', '[title]', function() {
$(this).tooltip('show');
});
// ✅ ANIMACIONES DE HOVER EN FILAS
$(document).on('mouseenter', 'tbody tr', function() {
$(this).addClass('table-active');
}).on('mouseleave', 'tbody tr', function() {
$(this).removeClass('table-active');
});
// ✅ NOTIFICACIONES DE ACTUALIZACIÓN
function showUpdateNotification() {
const toast = $(`
<div class="toast-notification">
<div class="d-flex align-items-center">
<i class="fas fa-sync-alt me-2 text-primary"></i>
<span>Templates actualizados</span>
<button type="button" class="btn-close btn-close-sm ms-auto"></button>
</div>
</div>
`);
$('body').append(toast);
toast.fadeIn();
setTimeout(() => {
toast.fadeOut(() => toast.remove());
}, 3000);
}
</script>
<style>
/* ✅ ESTILOS ADICIONALES */
.toast-notification {
position: fixed;
bottom: 100px;
left: 30px;
background: white;
border: 1px solid #dee2e6;
border-radius: 8px;
padding: 15px;
box-shadow: 0 4px 12px rgba(0,0,0,0.15);
z-index: 1050;
display: none;
min-width: 250px;
}
.template-preview-modal {
font-family: inherit;
}
.template-preview-modal .swal2-html-container {
max-height: 70vh;
overflow-y: auto;
text-align: left;
}
/* Mejorar responsive de tablas */
@media (max-width: 768px) {
.template-filters {
padding: 15px;
}
.filter-btn {
font-size: 12px;
padding: 6px 12px;
margin-bottom: 8px;
}
.help-float-btn {
width: 50px;
height: 50px;
font-size: 20px;
bottom: 20px;
right: 20px;
}
.shortcuts-modal {
width: 280px;
right: 20px;
bottom: 80px;
}
}
/* Estados de carga */
.loading-row {
background: linear-gradient(90deg, #f0f0f0 25%, #e0e0e0 50%, #f0f0f0 75%);
background-size: 200% 100%;
animation: shimmer 1.5s infinite;
}
@keyframes shimmer {
0% { background-position: -200% 0; }
100% { background-position: 200% 0; }
}
</style>
</body>
</html>