Vinculaciones multi-agencia

This commit is contained in:
2025-06-25 12:38:50 -06:00
parent a830e3a84a
commit c5c698dbe4
62 changed files with 5107 additions and 1454 deletions

View File

@@ -1,4 +1,33 @@
-- 1. Tabla de Agencias Aduanales
Sistemas -- Administrador General
INSERT INTO [Importaciones_HC].[dbo].[usuarios_sistema]
([nombre], [email], [password_hash], [tipo_usuario], [activo], [creado_en], [dos_factores], [notificaciones], [notificaciones_extra])
VALUES (
'4a77Tj9XtOCnvkUrxuxLCg==', -- nombre encriptado
'QVaqkaWSvUtgAfKN51JNDkoyUiZGVQs3+ARpoGtdD/I=', -- email encriptado
'$2y$10$.8CI5zuJ6MICQRRGqksYjOyuIYnln4xgUKJiQkZdEpLWfvUpg1f9m', -- password hash bcrypt
'super_admin', 1, GETDATE(), 0, 0, 0
);
CREATE TABLE solicitudes_agencias(
[request_id] [int] IDENTITY(1,1) NOT NULL,
[agencia_name] [varchar](255) NULL,
[rfc] [varchar](100) NULL,
[email] [varchar](255) NULL,
[phone] [varchar](20) NULL,
[direccion] [varchar](100) NULL,
[opinion_file] [varchar](500) NULL,
[admin_name] [varchar](255) NULL,
[admin_email] [varchar](255) NULL,
[request_status] [varchar](20) NULL,
[request_date] [datetime] NULL,
[approval_date] [datetime] NULL,
[approved_by] [int] NULL,
[timestamp_request] [timestamp] NOT NULL
);
-- Tabla de Agencias Aduanales
CREATE TABLE [dbo].[agencias_aduanales](
[id_agencia] [int] IDENTITY(1,1) NOT NULL,
@@ -15,15 +44,33 @@ CREATE TABLE [dbo].[agencias_aduanales](
CONSTRAINT [PK_agencias_aduanales] PRIMARY KEY CLUSTERED ([id_agencia] ASC)
);
-- Agregar foreign key para administrador de agencia
ALTER TABLE [dbo].[agencias_aduanales]
ADD CONSTRAINT [FK_agencias_administrador]
FOREIGN KEY ([id_administrador]) REFERENCES [dbo].[usuarios_sistema]([id_usuario]);
CREATE TABLE [dbo].[credenciales_db_agencias] (
[id_credencial] INT IDENTITY(1,1) NOT NULL,
[id_agencia] INT NOT NULL,
[db_instance] NVARCHAR(255) NOT NULL,
[db_ip] NVARCHAR(50) NOT NULL,
[db_user] NVARCHAR(100) NOT NULL,
[db_password] NVARCHAR(255) NOT NULL, -- Considera encriptar o manejar de forma segura
[db_port] INT NOT NULL DEFAULT 1433, -- Default SQL Server port
[creado_en] DATETIME NOT NULL DEFAULT GETDATE(),
[actualizado_en] DATETIME NULL,
-- 2. Actualizar tipos de usuario válidos
CONSTRAINT [PK_credenciales_db_agencias] PRIMARY KEY CLUSTERED ([id_credencial] ASC),
CONSTRAINT [FK_credenciales_db_agencia]
FOREIGN KEY ([id_agencia]) REFERENCES [dbo].[agencias_aduanales]([id_agencia])
);
CREATE UNIQUE INDEX [IX_credenciales_db_agencias_id_agencia]
ON [dbo].[credenciales_db_agencias]([id_agencia]);
-- Actualizar tipos de usuario válidos
-- Los tipos de usuario ahora serán:
@@ -34,11 +81,10 @@ FOREIGN KEY ([id_administrador]) REFERENCES [dbo].[usuarios_sistema]([id_usuario
-- Campos de auditoría para usuarios_sistema
ALTER TABLE [dbo].[usuarios_sistema]
ADD [actualizado_en] [datetime] NULL,
[creado_por] [int] NULL;
ADD [creado_por] [int] NULL;
-- 3. Tabla de relación importador-agencia (many-to-many)
-- Tabla de relación importador-agencia (many-to-many)
CREATE TABLE [dbo].[importador_agencia](
[id_relacion] [int] IDENTITY(1,1) NOT NULL,
@@ -68,7 +114,7 @@ ON [dbo].[importador_agencia] ([id_importador], [id_agencia])
WHERE [activo] = 1;
-- 4. Tabla de relación agente-agencia
-- Tabla de relación agente-agencia
CREATE TABLE [dbo].[agente_agencia](
[id_relacion] INT IDENTITY(1,1) NOT NULL,
@@ -81,11 +127,15 @@ CREATE TABLE [dbo].[agente_agencia](
CONSTRAINT [FK_agente_agencia_usuario]
FOREIGN KEY ([id_agente]) REFERENCES [dbo].[usuarios_sistema]([id_usuario]),
CONSTRAINT [FK_agente_agencia_agencia]
FOREIGN KEY ([id_agencia]) REFERENCES [dbo].[agencias_aduanales]([id_agencia]),
CONSTRAINT [UQ_agente_activo]
UNIQUE ([id_agente]) WHERE [activo] = 1 -- evita duplicados activos
FOREIGN KEY ([id_agencia]) REFERENCES [dbo].[agencias_aduanales]([id_agencia])
);
-- Luego creas el índice filtrado:
CREATE UNIQUE INDEX [UQ_agente_activo]
ON [dbo].[agente_agencia]([id_agente])
WHERE [activo] = 1;
-- Índice único para evitar duplicados activos
CREATE UNIQUE INDEX [IX_agente_agencia_unique]
@@ -93,7 +143,7 @@ ON [dbo].[agente_agencia] ([id_agente], [id_agencia])
WHERE [activo] = 1;
-- 5. Tabla para solicitudes de vinculación de importadores
-- Tabla para solicitudes de vinculación de importadores
CREATE TABLE [dbo].[solicitudes_vinculacion](
[id_solicitud] [int] IDENTITY(1,1) NOT NULL,
@@ -115,25 +165,32 @@ CREATE TABLE [dbo].[solicitudes_vinculacion](
);
-- 6. Tabla para auditoría de cambios de agencia
-- Tabla para auditoría de cambios de agencia
CREATE TABLE [dbo].[historial_agencia](
[id_historial] [int] IDENTITY(1,1) NOT NULL,
[id_importador] [int] NULL,
[id_agente] [int] NULL,
[id_agencia_anterior] [int] NULL,
[id_agencia_nueva] [int] NULL,
[accion] [varchar](50) NOT NULL, -- 'VINCULACION', 'DESVINCULACION'
[motivo] [nvarchar](500) NULL,
CREATE TABLE [dbo].[bitacora_agencias](
[id_bitacora] [int] IDENTITY(1,1) NOT NULL,
[id_agencia] [int] NULL,
[nombre] [varchar](100) NULL,
[accion] [varchar](50) NOT NULL, -- 'CREACION', 'DESACTIVACION'
[realizado_por] [int] NOT NULL,
[fecha_cambio] [datetime] NOT NULL DEFAULT GETDATE(),
[datos_adicionales] [nvarchar](max) NULL, -- JSON con información adicional
CONSTRAINT [PK_historial_agencia_cambios] PRIMARY KEY CLUSTERED ([id_historial] ASC)
[fecha] [datetime] NOT NULL DEFAULT GETDATE(),
CONSTRAINT [PK_bitacora_agenciaS] PRIMARY KEY CLUSTERED ([id_bitacora] ASC)
);
-- Índices adicionales para mejorar rendimiento
ALTER TABLE [dbo].[bitacora_agencias]
ADD CONSTRAINT [FK_bitacora_agencias_agencia]
FOREIGN KEY ([id_agencia]) REFERENCES [dbo].[agencias_aduanales]([id_agencia]);
CREATE INDEX [IX_usuarios_tipo_agencia] ON [dbo].[usuarios_sistema] ([tipo_usuario], [id_agencia]);
ALTER TABLE [dbo].[bitacora_agencias]
ADD CONSTRAINT [FK_bitacora_agencias_administrador]
FOREIGN KEY ([realizado_por]) REFERENCES [dbo].[usuarios_sistema]([id_usuario]);
ALTER TABLE [dbo].[bitacora_agencias]
ADD CONSTRAINT [CK_bitacora_agencias_accion]
CHECK ([accion] IN ('CREACION', 'DESACTIVACION'));
-- Índices adicionales para mejorar rendimiento
CREATE INDEX [IX_importador_agencia_importador] ON [dbo].[importador_agencia] ([id_importador]);

File diff suppressed because it is too large Load Diff

View File

@@ -14,32 +14,206 @@ loadEnv();
function dashboard()
{
if (!isset($_SESSION['usuario_id']) || $_SESSION['tipo_usuario'] !== 'agente_aduanal') {
if (!isset($_SESSION['usuario_id']) || $_SESSION['tipo_usuario'] !== 'admin_agencia') {
header("Location: /IMPORTADORES/login");
exit;
}
$nombreAgencia = $_SESSION['nombre_agencia'];
include __DIR__ . '/../../views/agencias/dashboard_agencias.php';
}
function solicitudesVinculacion()
{
include __DIR__ . '/../../views/agencias/solicitudes_vinculacion.php';
}
function alta()
{
include __DIR__ . '/../../views/agencias/.php';
if (!isset($_SESSION['usuario_id']) || $_SESSION['tipo_usuario'] !== 'admin_agencia') {
header('Location: /IMPORTADORES/login');
exit;
}
include __DIR__ . '/../../views/agencias/alta_agentes.php';
}
function guardarAgente()
{
if (!isset($_SESSION['usuario_id']) || $_SESSION['tipo_usuario'] !== 'admin_agencia') {
header("Location: /IMPORTADORES/agencias/alta?error=unauthorized");
exit;
}
function activos()
{
include __DIR__ . '/../../views/agencias/usuarios_activos.php';
$conn = getConnection();
try {
if (!sqlsrv_begin_transaction($conn)) {
error_log("ERROR: No se pudo iniciar la transacción");
throw new Exception("Error al iniciar transacción");
}
function bitacora()
{
include __DIR__ . '/../../views/agencias/dashboard_agencias.php';
error_log("Transacción iniciada correctamente");
// 1. Capturar y validar datos
$nombre = trim($_POST['nombre'] ?? '');
$email = trim($_POST['email'] ?? '');
$password = $_POST['password'] ?? '';
$tipo = $_POST['tipo_usuario'] ?? '';
if (empty($nombre) || empty($email) || empty($password) || empty($tipo)) {
header("Location: /IMPORTADORES/agencias/alta?error=invalid_data");
exit;
}
if ($tipo !== 'agente_aduanal') {
error_log("DEBUG 5: Tipo de usuario inválido: $tipo");
header("Location: /IMPORTADORES/agencias/alta?error=invalid_user_type");
exit;
}
// 2. Encriptar datos sensibles
$nombre_encrypted = encrypt($nombre);
$email_encrypted = encrypt($email);
$password_hash = password_hash($password, PASSWORD_DEFAULT);
// 3. Validar duplicado por email encriptado
$sqlCheck = "SELECT COUNT(*) AS total FROM usuarios_sistema WHERE email = ?";
$stmtCheck = sqlsrv_query($conn, $sqlCheck, [$email_encrypted]);
$rowCheck = sqlsrv_fetch_array($stmtCheck, SQLSRV_FETCH_ASSOC);
if ($rowCheck['total'] > 0) {
header("Location: /IMPORTADORES/agencias/alta?error=email_exists");
exit;
}
// 4. Insertar usuario
$sqlInsert = "
INSERT INTO usuarios_sistema (nombre, email, password_hash, tipo_usuario, activo, creado_en, dos_factores, creado_por)
OUTPUT INSERTED.id_usuario
VALUES (?, ?, ?, ?, 1, GETDATE(), 0, ?)";
$params = [$nombre_encrypted, $email_encrypted, $password_hash, $tipo, $_SESSION['usuario_id']];
$stmtInsert = sqlsrv_query($conn, $sqlInsert, $params);
if ($stmtInsert === false) {
error_log("ERROR: Fallo al insertar usuario");
error_log(print_r(sqlsrv_errors(), true));
throw new Exception("Error al insertar usuario");
}
$idUsuarioRow = sqlsrv_fetch_array($stmtInsert, SQLSRV_FETCH_ASSOC);
$id_usuario = $idUsuarioRow['id_usuario'] ?? null;
if (!$id_usuario) {
error_log("ERROR: No se obtuvo id_usuario tras el insert");
throw new Exception("Error al obtener ID del nuevo usuario");
}
// 6. Obtener la agencia del administrador actual
$sqlAgencia = "SELECT id_agencia FROM agencias_aduanales WHERE id_administrador = ?";
$stmtAgencia = sqlsrv_query($conn, $sqlAgencia, [$_SESSION['usuario_id']]);
$agenciaRow = sqlsrv_fetch_array($stmtAgencia, SQLSRV_FETCH_ASSOC);
$id_agencia = $agenciaRow['id_agencia'] ?? null;
if (!$id_agencia) {
error_log("ERROR: No se encontró agencia para el admin_agencia con id_usuario=" . $_SESSION['usuario_id']);
throw new Exception("No se encontró agencia asociada al administrador");
}
// 7. Crear la relación agente-agencia
$sqlRelacion = "INSERT INTO agente_agencia (id_agente, id_agencia, fecha_asignacion, activo, asignado_por)
VALUES (?, ?, GETDATE(), 1, ?)";
$paramsRelacion = [$id_usuario, $id_agencia, $_SESSION['usuario_id']];
$stmtRelacion = sqlsrv_query($conn, $sqlRelacion, $paramsRelacion);
if ($stmtRelacion === false) {
error_log("ERROR: No se pudo insertar la relación agente-agencia");
error_log(print_r(sqlsrv_errors(), true));
throw new Exception("Error al guardar relación agente-agencia");
}
// 8. CONFIRMAR TRANSACCIÓN ✅
if (!sqlsrv_commit($conn)) {
error_log("ERROR: No se pudo confirmar la transacción");
throw new Exception("Error al confirmar transacción");
}
// 9. Enviar correo de bienvenida
if (class_exists('PHPMailer\PHPMailer\PHPMailer')) {
$mail = new PHPMailer(true);
try {
$mail->isSMTP();
$mail->Host = 'secure.emailsrvr.com';
$mail->SMTPAuth = true;
$mail->Username = 'noreply@aduanasoft.com.mx';
$mail->Password = $_ENV['SMTP_PASS'] ?? '';
$mail->SMTPSecure = PHPMailer::ENCRYPTION_STARTTLS;
$mail->Port = 587;
$mail->setFrom('noreply@aduanasoft.com.mx', 'SIIH | Sistema Integral para Importadores de Hidrocarburos');
$mail->addAddress($email); // Email sin encriptar para el envío
$mail->CharSet = 'UTF-8';
$mail->isHTML(true);
$mail->Subject = 'Tu acceso a la plataforma SIIH ha sido creado';
// Determinar el tipo de usuario para el mensaje
$tipoUsuarioTexto = match($tipo) {
'agente_aduanal' => 'agente aduanal',
default => 'usuario'
};
$mail->Body = "
<div style='font-family: Segoe UI, sans-serif; background-color: #f4f6f9; padding: 40px;'>
<div style='max-width: 600px; margin: auto; background: #fff; border: 1px solid #ddd; border-radius: 10px; overflow: hidden;'>
<div style='background: linear-gradient(to right, #003366, #0055A5); padding: 20px; text-align: center;'>
<h2 style='color: white;'>¡Bienvenido a SIIH!</h2>
</div>
<div style='padding: 30px; color: #333; font-size: 16px;'>
<p>Hola <strong>$nombre</strong>,</p>
<p>Se ha creado tu cuenta en la plataforma SIIH con el rol de <strong>$tipoUsuarioTexto</strong>.</p>
<p>Aquí tienes tus credenciales de acceso:</p>
<div style='background: #f8f9fa; padding: 15px; border-radius: 5px; margin: 20px 0;'>
<p><strong>Correo:</strong> $email</p>
<p><strong>Contraseña:</strong> $password</p>
</div>
<p>📌 <strong>Importante:</strong> Te recomendamos cambiar tu contraseña una vez que ingreses al sistema por primera vez.</p>
<p style='text-align: center; margin-top: 30px;'>
<a href='http://siih.aduanasoft.com/IMPORTADORES/login'
style='background: #0055A5; color: white; padding: 12px 25px; text-decoration: none; border-radius: 5px; display: inline-block;'>
Acceder al Sistema
</a>
</p>
</div>
<div style='background: #e9ecef; text-align: center; padding: 15px; font-size: 13px; color: #666;'>
&copy; " . date('Y') . " SIIH · Sistema Integral para Importadores de Hidrocarburos
</div>
</div>
</div>";
$mail->send();
} catch (Exception $e) {
error_log("Error al enviar correo: " . $e->getMessage());
}
}
// 10. Redirigir con mensaje de éxito
header("Location: /IMPORTADORES/agencias/alta?success=created");
exit;
} catch (Exception $e) {
error_log("ERROR CAPTURADO: " . $e->getMessage());
if (!sqlsrv_rollback($conn)) {
error_log("ERROR: No se pudo revertir la transacción");
} else {
error_log("Transacción revertida exitosamente");
}
error_log("=== FIN CON ERROR guardar_agencia ===");
header("Location: /IMPORTADORES/agencias/alta?error=save_failed");
exit;
} finally {
if ($conn) {
sqlsrv_close($conn);
error_log("Conexión cerrada");
}
}
}

View File

@@ -60,7 +60,6 @@ function solicitudes_pendientes() {
WHERE request_status = 'pending'
ORDER BY request_date DESC";
$stmt = sqlsrv_query($conn, $sql);
$solicitudes = [];
while ($row = sqlsrv_fetch_array($stmt, SQLSRV_FETCH_ASSOC)) {

View File

@@ -1,47 +1,45 @@
<?php
require_once __DIR__ . '/../../config/database.php';
require_once __DIR__ . '/../helpers/crypto.php';
session_start();
require_once __DIR__ . '/../helpers/session.php';
require_once __DIR__ . '/../helpers/bitacoras.php';
function index()
{
include __DIR__ . '/../../views/bitacoras/dashboard_bitacoras.php';
}
function lista()
function sistema()
{
$conn = getConnection();
if (!($_SESSION['usuario_id'] ?? false)) {
header('Location: /IMPORTADORES/login');
exit;
include __DIR__ . '/../../views/bitacoras/bitacora_sistema.php';
}
$usuario_id = $_SESSION['usuario_id'];
$sql = "
SELECT u.id_usuario, u.nombre, b.email, b.ip, b.fecha, b.exito, b.detalle
FROM dbo.bitacora_login b
JOIN dbo.usuarios_sistema u ON u.id_usuario = b.id_usuario
WHERE u.id_usuario = ?
ORDER BY b.fecha DESC
";
$stmt = sqlsrv_query($conn, $sql, [$usuario_id]);
if ($stmt === false) {
die("Error en lista(): " . print_r(sqlsrv_errors(), true));
}
$bitacoras = [];
while ($row = sqlsrv_fetch_array($stmt, SQLSRV_FETCH_ASSOC)) {
$row['nombre'] = decrypt($row['nombre']); // Desencripta aquí
$bitacoras[] = $row;
}
include __DIR__ . '/../../views/bitacoras/bitacora_login.php';
function sistemaAgencia()
{
include __DIR__ . '/../../views/bitacoras/sistema_agencia.php';
}
function usuarios()
{
include __DIR__ . '/../../views/bitacoras/bitacora_usuarios.php';
}
function vinculaciones()
{
include __DIR__ . '/../../views/bitacoras/bitacora_vinculaciones.php';
}
function vinculacionesUsuario()
{
include __DIR__ . '/../../views/bitacoras/vinculaciones_usuario.php';
}
function agencias()
{
include __DIR__ . '/../../views/bitacoras/bitacora_agencias.php';
}
function miAcceso()
{
if (!isset($_SESSION['usuario_id'])) {
die("⚠️ No autorizado.");
@@ -57,5 +55,5 @@ function usuarios()
$cambios[] = $row;
}
include __DIR__ . '/../../views/bitacoras/bitacora_usuarios.php';
include __DIR__ . '/../../views/bitacoras/bitacora_usuario.php';
}

View File

@@ -54,42 +54,8 @@ function validar()
$_SESSION['usuario_email'] = $email;
$_SESSION['tipo_usuario'] = $row['tipo_usuario'];
// Buscar RFC y teléfono desde solicitudes_importadores
$sqlSolicitudes = "SELECT rfc, phone FROM solicitudes_importadores WHERE email = ?";
$stmtSolicitudes = sqlsrv_query($conn, $sqlSolicitudes, [$email]);
$rfc = null;
$telefono = null;
if ($stmtSolicitudes && $solRow = sqlsrv_fetch_array($stmtSolicitudes, SQLSRV_FETCH_ASSOC)) {
// Desencriptar RFC
$rfc = strtoupper(decrypt(trim($solRow['rfc'])));
// Validar teléfono (ej. 123 4567890)
$telefono = trim($solRow['phone']);
if (!preg_match('/^\d{3}\s\d{7}$/', $telefono)) {
$telefono = null;
}
}
$nombreUsuario = decrypt($row['nombre']);
// Actualizar/insertar información general
$sqlCheck = "SELECT COUNT(*) AS total FROM informacion_general WHERE id_usuario = ?";
$stmtCheck = sqlsrv_query($conn, $sqlCheck, [$row['id_usuario']]);
$checkRow = sqlsrv_fetch_array($stmtCheck, SQLSRV_FETCH_ASSOC);
if ($checkRow['total'] == 0) {
$sqlInsert = "INSERT INTO informacion_general (id_usuario, nombre, rfc, telefono, correo)
VALUES (?, ?, ?, ?, ?)";
$paramsInsert = [$row['id_usuario'], $nombreUsuario, $rfc, $telefono, $email];
sqlsrv_query($conn, $sqlInsert, $paramsInsert);
} else {
$sqlUpdate = "UPDATE informacion_general SET rfc = ?, telefono = ? WHERE id_usuario = ?";
$paramsUpdate = [$rfc, $telefono, $row['id_usuario']];
sqlsrv_query($conn, $sqlUpdate, $paramsUpdate);
}
// **LÓGICA DE DOS FACTORES**
$dos_factores_activo = (int)$row['dos_factores'] === 1;
@@ -128,9 +94,11 @@ function validar()
registrarBitacora($conn, $row['id_usuario'], $email, $ip, 1, 'Login exitoso - Sin 2FA');
// Redirección directa por rol
redirectByRole($row['tipo_usuario']);
exit;
// Obtener el tipo de usuario para redirección
$tipoUsuario = $_SESSION['tipo_usuario'] ?? 'importador';
// Redirigir según el rol
redirectByRole($tipoUsuario);
}
} else {
@@ -152,15 +120,26 @@ function validar()
function redirectByRole($tipoUsuario)
{
switch ($tipoUsuario) {
case 'importador':
header('Location: /IMPORTADORES/importadores/dashboard');
break;
case 'super_admin':
header('Location: /IMPORTADORES/administrador/dashboard');
exit;
case 'admin_agencia':
header('Location: /IMPORTADORES/agencias/dashboard');
exit;
case 'agente_aduanal':
header('Location: /IMPORTADORES/agentes/dashboard');
break;
default:
exit;
case 'importador':
header('Location: /IMPORTADORES/importadores/dashboard');
break;
exit;
default:
// Fallback para tipos de usuario no reconocidos
header('Location: /IMPORTADORES/login');
exit;
}
}

View File

@@ -1,30 +1,30 @@
<?php
require_once __DIR__ . '/../../vendor/autoload.php';
use PHPMailer\PHPMailer\PHPMailer;
use PHPMailer\PHPMailer\Exception;
require_once __DIR__ . '/../../config/database.php';
require_once __DIR__ . '/../helpers/session.php';
require_once __DIR__ . '/../helpers/env.php';
require_once __DIR__ . '/../helpers/crypto.php';
use PHPMailer\PHPMailer\PHPMailer;
use PHPMailer\PHPMailer\Exception;
loadEnv();
function index() {
function index()
{
include __DIR__ . '/../../views/registro/form.php';
}
function enviar() {
// Validar reCAPTCHA
$captchaResponse = $_POST['g-recaptcha-response'] ?? '';
// Función auxiliar para validar reCAPTCHA
function validarRecaptcha($captchaResponse)
{
if (!$captchaResponse) {
die("❌ Debes completar el reCAPTCHA.");
}
$secretKey = $_ENV['RECAPTCHA_SECRET'];
$verifyUrl = "https://www.google.com/recaptcha/api/siteverify";
$data = [
'secret' => $secretKey,
'response' => $captchaResponse
@@ -45,52 +45,92 @@ function enviar() {
if (!$response->success) {
die("❌ Error de verificación reCAPTCHA.");
}
return true;
}
$conn = getConnection();
$empresa = encrypt(trim($_POST['company_name'] ?? ''));
$rfc = strtoupper(encrypt(trim($_POST['rfc'] ?? '')));
$email = trim($_POST['email'] ?? '');
$telefono = trim($_POST['phone'] ?? '');
$archivo = $_FILES['opinion_file'];
// Validar RFC (12 o 13 caracteres con formato correcto)
if (!preg_match('/^[A-ZÑ&]{3,4}\d{6}[A-Z0-9]{3}$/', strtoupper($_POST['rfc'] ?? ''))) {
// Función auxiliar para validaciones
function validarDatos($rfc, $email, $telefono)
{
// Validar RFC
if (!preg_match('/^[A-ZÑ&]{3,4}\d{6}[A-Z0-9]{3}$/', strtoupper($rfc))) {
die("❌ El RFC no tiene un formato válido.");
}
// Validar email (solo minúsculas, sin caracteres especiales fuera del estándar)
if (!preg_match('/^[a-z0-9._+-]+@[a-z0-9.-]+\.[a-z]{2,}$/', $email)) {
// Validar email
if (!preg_match('/^[a-zA-Z0-9._+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$/', strtolower($email))) {
die("❌ No es una dirección de correo válida.");
}
// Validar teléfono (formato nacional: 3 dígitos + espacio + 7 dígitos)
// Validar teléfono
if (!preg_match('/^\d{3}\s\d{7}$/', $telefono)) {
die("❌ El número de teléfono no tiene un formato válido. Ejemplo: 123 4567890");
}
// Validar archivo
if ($archivo['error'] !== 0 || pathinfo($archivo['name'], PATHINFO_EXTENSION) !== 'pdf') {
die("❌ Archivo inválido. Solo se permiten PDFs.");
}
// Guardar archivo
// Función auxiliar para manejar archivos
function procesarArchivo($archivo)
{
if ($archivo['error'] !== UPLOAD_ERR_OK) {
die("❌ Error al subir el archivo.");
}
// Validar tipo MIME real del archivo
$finfo = finfo_open(FILEINFO_MIME_TYPE);
$mimeType = finfo_file($finfo, $archivo['tmp_name']);
finfo_close($finfo);
if ($mimeType !== 'application/pdf') {
die("❌ El archivo debe ser un PDF válido.");
}
// Validar extensión
if (pathinfo($archivo['name'], PATHINFO_EXTENSION) !== 'pdf') {
die("❌ Solo se permiten archivos PDF.");
}
// Validar tamaño (5MB máximo)
if ($archivo['size'] > 5 * 1024 * 1024) {
die("❌ El archivo no debe exceder 5MB.");
}
// Guardar archivo con nombre único
$nombreArchivo = uniqid() . '_' . basename($archivo['name']);
$rutaDestino = __DIR__ . '/../../storage/opiniones/' . $nombreArchivo;
move_uploaded_file($archivo['tmp_name'], $rutaDestino);
// Insertar en la base de datos
$sql = "INSERT INTO solicitudes_importadores
(company_name, rfc, email, phone, opinion_file, request_status, request_date)
VALUES (?, ?, ?, ?, ?, 'pending', GETDATE())";
$params = [$empresa, $rfc, $email, $telefono, $nombreArchivo];
$stmt = sqlsrv_query($conn, $sql, $params);
if ($stmt === false) {
die(print_r(sqlsrv_errors(), true));
if (!move_uploaded_file($archivo['tmp_name'], $rutaDestino)) {
die("❌ Error al guardar el archivo.");
}
return $nombreArchivo;
}
// Función auxiliar para obtener configuración
function obtenerConfiguracion($conn)
{
$sqlConf = "SELECT TOP 1 * FROM configuracion_sistema";
$stmtConf = sqlsrv_query($conn, $sqlConf);
if ($stmtConf === false) {
error_log("Error al obtener configuración: " . print_r(sqlsrv_errors(), true));
return [
'nombre_plataforma' => 'Sistema Integral para Importadores de Hidrocarburos',
'siglas' => 'SIIH',
'logo_url' => 'assets/img/logo_siih.png',
'colores_primarios' => '#003366,#0055A5'
];
}
return sqlsrv_fetch_array($stmtConf, SQLSRV_FETCH_ASSOC) ?: [
'nombre_plataforma' => 'Sistema Integral para Importadores de Hidrocarburos',
'siglas' => 'SIIH',
'logo_url' => 'assets/img/logo_siih.png',
'colores_primarios' => '#003366,#0055A5'
];
}
// Función auxiliar para enviar email
function enviarEmailConfirmacion($destinatario, $datosEmpresa, $esAgencia = false)
{
$mail = new PHPMailer(true);
try {
@@ -99,31 +139,31 @@ function enviar() {
$mail->Host = 'secure.emailsrvr.com';
$mail->SMTPAuth = true;
$mail->Username = 'noreply@aduanasoft.com.mx';
$mail->Password = $_ENV['SMTP_PASS']; // desde el .env
$mail->Password = $_ENV['SMTP_PASS'];
$mail->SMTPSecure = PHPMailer::ENCRYPTION_STARTTLS;
$mail->Port = 587;
// Correo remitente y destinatario
// Configurar remitente y destinatario
$mail->setFrom('noreply@aduanasoft.com.mx', 'SIIH | AduanaSoft');
$mail->addAddress($email); // destinatario principal
$mail->addAddress($destinatario);
// Formato y contenido
// Obtener configuración
$conn = getConnection();
$conf = obtenerConfiguracion($conn);
$nombrePlataforma = $conf['nombre_plataforma'];
$siglas = $conf['siglas'];
$logoUrl = $conf['logo_url'];
$color2 = explode(',', $conf['colores_primarios'])[1];
// Contenido del email
$mail->CharSet = 'UTF-8';
$mail->isHTML(true);
$mail->Subject = 'Confirmación de solicitud de registro | SIIH';
$fechaRegistro = date('d/m/Y H:i');
// Consulta configuración institucional
$sqlConf = "SELECT TOP 1 * FROM configuracion_sistema";
$stmtConf = sqlsrv_query($conn, $sqlConf);
$conf = sqlsrv_fetch_array($stmtConf, SQLSRV_FETCH_ASSOC);
$nombrePlataforma = $conf['nombre_plataforma'] ?? 'Sistema Integral para Importadores de Hidrocarburos';
$siglas = $conf['siglas'] ?? 'SIIH';
$logoUrl = $conf['logo_url'] ?? 'assets/img/logo_siih.png';
$color1 = explode(',', $conf['colores_primarios'] ?? '#003366,#0055A5')[0];
$color2 = explode(',', $conf['colores_primarios'] ?? '#003366,#0055A5')[1];
$tipoRegistro = $esAgencia ? 'Agencia' : 'Empresa';
$nombreEntidad = $esAgencia ? $datosEmpresa['agencia'] : $datosEmpresa['empresa'];
$mail->Body = "
<div style='font-family: Segoe UI, sans-serif; background-color: #f4f6f9; padding: 40px;'>
@@ -138,28 +178,141 @@ function enviar() {
<hr style='margin: 20px 0;'>
<p><strong>📋 Empresa:</strong> " . htmlspecialchars($_POST['company_name']) . "</p>
<p><strong>📞 Teléfono:</strong> " . htmlspecialchars($telefono) . "</p>
<p><strong>📋 $tipoRegistro:</strong> " . htmlspecialchars($nombreEntidad) . "</p>
<p><strong>📞 Teléfono:</strong> " . htmlspecialchars($datosEmpresa['telefono']) . "</p>
<p><strong>🕓 Fecha de registro:</strong> $fechaRegistro</p>
<hr style='margin: 20px 0;'>
<p style='font-size: 14px;'>Un agente aduanal revisará tu información y te notificará por este medio cuando tu solicitud sea aprobada.</p>
<p style='font-size: 14px;'>Un administrador revisará tu información y te notificará por este medio cuando tu solicitud sea aprobada.</p>
<p style='font-size: 14px;'>Por favor, mantente atento a tu correo (y revisa también tu carpeta de spam o promociones).</p>
<p style='margin-top: 30px; font-size: 13px; color: #888;'>Este es un mensaje automático enviado por el sistema de registro de importadores.</p>
</div>
<div style='background: #f1f1f1; text-align: center; padding: 15px; font-size: 12px; color: #666;'>
&copy; " . date('Y') . " $siglas · Desarrollado por AduanaSoft
&copy; " . date('Y') . " $siglas · $nombrePlataforma
</div>
</div>
</div>
";
</div>";
$mail->send();
return true;
} catch (Exception $e) {
error_log("Error al enviar correo: {$mail->ErrorInfo}");
return false;
}
}
include __DIR__ . '/../../views/registro/gracias.php';
function enviarSoliImportador()
{
try {
// Validar reCAPTCHA
validarRecaptcha($_POST['g-recaptcha-response'] ?? '');
$conn = getConnection();
// Sanitizar y obtener datos
$empresa = trim($_POST['company_name'] ?? '');
$rfc = trim($_POST['rfc'] ?? '');
$email = trim($_POST['email'] ?? '');
$telefono = trim($_POST['phone'] ?? '');
// Validar datos
validarDatos($rfc, $email, $telefono);
// Procesar archivo
$nombreArchivo = procesarArchivo($_FILES['opinion_file']);
// Encriptar datos sensibles
$empresaEncriptada = encrypt($empresa);
$rfcEncriptado = encrypt(strtoupper($rfc));
// Insertar en base de datos
$sql = "INSERT INTO solicitudes_importadores
(company_name, rfc, email, phone, opinion_file, request_status, request_date)
VALUES (?, ?, ?, ?, ?, 'pending', GETDATE())";
$params = [$empresaEncriptada, $rfcEncriptado, $email, $telefono, $nombreArchivo];
$stmt = sqlsrv_query($conn, $sql, $params);
if ($stmt === false) {
error_log("Error en BD: " . print_r(sqlsrv_errors(), true));
die("❌ Error al procesar la solicitud.");
}
// Enviar email de confirmación
$datosEmpresa = [
'empresa' => $empresa,
'telefono' => $telefono
];
enviarEmailConfirmacion($email, $datosEmpresa, false);
include __DIR__ . '/../../views/registro/gracias.php';
} catch (Exception $e) {
error_log("Error en enviarSoliImportador: " . $e->getMessage());
die("❌ Error interno del servidor.");
}
}
function enviarSoliAgencia()
{
try {
// Validar reCAPTCHA
validarRecaptcha($_POST['g-recaptcha-response'] ?? '');
$conn = getConnection();
// Sanitizar y obtener datos
$agencia = trim($_POST['nombre_agencia'] ?? '');
$rfc = trim($_POST['rfc'] ?? '');
$correo = trim($_POST['correo'] ?? '');
$telefono = trim($_POST['telefono'] ?? '');
$direccion = trim($_POST['direccion'] ?? '');
$admin = trim($_POST['nombre_admin'] ?? '');
$admin_correo = trim($_POST['correo_admin'] ?? '');
// Validar datos principales
validarDatos($rfc, $correo, $telefono);
// Validar correo del administrador
if (!preg_match('/^[a-zA-Z0-9._+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$/', strtolower($admin_correo))) {
die("❌ El correo del administrador no es válido.");
}
// Procesar archivo
$nombreArchivo = procesarArchivo($_FILES['opinion_file']);
// Encriptar datos sensibles
$agenciaEncriptada = encrypt($agencia);
$rfcEncriptado = encrypt(strtoupper($rfc));
$adminEncriptado = encrypt($admin);
// Insertar en base de datos
$sql = "INSERT INTO solicitudes_agencias
(agencia_name, rfc, email, phone, direccion, opinion_file, admin_name, admin_email, request_status, request_date)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, 'pending', GETDATE())";
$params = [$agenciaEncriptada, $rfcEncriptado, $correo, $telefono, $direccion, $nombreArchivo, $adminEncriptado, $admin_correo];
$stmt = sqlsrv_query($conn, $sql, $params);
if ($stmt === false) {
error_log("Error en BD: " . print_r(sqlsrv_errors(), true));
die("❌ Error al procesar la solicitud.");
}
// Enviar email de confirmación
$datosAgencia = [
'agencia' => $agencia,
'telefono' => $telefono
];
enviarEmailConfirmacion($correo, $datosAgencia, true);
include __DIR__ . '/../../views/registro/gracias.php';
} catch (Exception $e) {
error_log("Error en enviarSoliAgencia: " . $e->getMessage());
die("❌ Error interno del servidor.");
}
}
?>

View File

@@ -2,286 +2,15 @@
require_once __DIR__ . '/../../config/database.php';
require_once __DIR__ . '/../helpers/env.php';
require_once __DIR__ . '/../helpers/crypto.php';
use PHPMailer\PHPMailer\PHPMailer;
use PHPMailer\PHPMailer\Exception;
require_once __DIR__ . '/../helpers/session.php';
loadEnv();
session_start();
function index() {
header("Location: /IMPORTADORES/sistemas/login");
exit;
}
function login() {
// Si es GET, mostrar el formulario
if ($_SERVER['REQUEST_METHOD'] === 'GET') {
include __DIR__ . '/../../views/admin/login_sistemas.php';
return;
}
// Si es POST, procesar login
$email = $_POST['email'] ?? '';
$clave = $_POST['clave'] ?? '';
if ($email === 'sistemas@aduanasoft.com.mx' && $clave === 'rootSecure2025!') {
$_SESSION['usuario_sistemas'] = true;
header("Location: /IMPORTADORES/sistemas/alta_usuarios");
} else {
echo "❌ Acceso denegado.";
}
}
function alta_usuarios() {
if (!($_SESSION['usuario_sistemas'] ?? false)) {
die("⚠️ No autorizado.");
}
$conn = getConnection();
$sql = "SELECT * FROM usuarios_sistema ORDER BY creado_en DESC";
$stmt = sqlsrv_query($conn, $sql);
$usuarios = [];
while ($row = sqlsrv_fetch_array($stmt, SQLSRV_FETCH_ASSOC)) {
$usuarios[] = $row;
}
include __DIR__ . '/../../views/admin/alta_usuarios.php';
}
function guardar_usuario()
function logout()
{
if (!($_SESSION['usuario_sistemas'] ?? false)) {
die("⚠️ No autorizado.");
}
$conn = getConnection();
// 1. Capturar y validar datos
$nombre = trim($_POST['nombre'] ?? '');
$email = trim($_POST['email'] ?? '');
$password = $_POST['password'] ?? '';
$tipo = $_POST['tipo_usuario'] ?? '';
if (empty($nombre) || empty($email) || empty($password) || empty($tipo)) {
die("❌ Todos los campos son obligatorios.");
}
if (!in_array($tipo, ['importador', 'agente_aduanal'])) {
die("❌ Tipo de usuario inválido.");
}
// 2. Encriptar datos sensibles
$nombre_encrypted = encrypt($nombre);
$email_encrypted = encrypt($email);
$password_hash = password_hash($password, PASSWORD_DEFAULT);
// 3. Validar duplicado por email encriptado
$sqlCheck = "SELECT COUNT(*) AS total FROM usuarios_sistema WHERE email = ?";
$stmtCheck = sqlsrv_query($conn, $sqlCheck, [$email_encrypted]);
$rowCheck = sqlsrv_fetch_array($stmtCheck, SQLSRV_FETCH_ASSOC);
if ($rowCheck['total'] > 0) {
die("❌ Este correo ya está registrado.");
}
// 4. Insertar usuario
$sqlInsert = "INSERT INTO usuarios_sistema (nombre, email, password_hash, tipo_usuario, activo, creado_en)
VALUES (?, ?, ?, ?, 1, GETDATE())";
$params = [$nombre_encrypted, $email_encrypted, $password_hash, $tipo];
$stmtInsert = sqlsrv_query($conn, $sqlInsert, $params);
if ($stmtInsert === false) {
die("❌ Error al guardar: " . print_r(sqlsrv_errors(), true));
}
// 5. Redirigir
header("Location: /IMPORTADORES/sistemas/alta_usuarios");
exit;
}
function agencias_activas()
{
}
function logout() {
session_start();
session_unset(); // Limpia variables de sesión
session_destroy(); // Destruye la sesión
header("Location: /IMPORTADORES/login");
exit;
}
function toggle_estado() {
session_start();
if (!($_SESSION['usuario_sistemas'] ?? false)) {
die("⚠️ No autorizado.");
}
$conn = getConnection();
$id = $_GET['id'] ?? null;
if (!$id || !is_numeric($id)) {
die("❌ ID inválido.");
}
// Obtener el estado actual
$sqlEstado = "SELECT activo FROM usuarios_sistema WHERE id_usuario = ?";
$stmtEstado = sqlsrv_query($conn, $sqlEstado, [$id]);
if (!$stmtEstado || !($row = sqlsrv_fetch_array($stmtEstado, SQLSRV_FETCH_ASSOC))) {
die("❌ Usuario no encontrado.");
}
$nuevoEstado = $row['activo'] ? 0 : 1;
// Actualizar estado
$sqlUpdate = "UPDATE usuarios_sistema SET activo = ? WHERE id_usuario = ?";
$stmtUpdate = sqlsrv_query($conn, $sqlUpdate, [$nuevoEstado, $id]);
if (!$stmtUpdate) {
die("❌ Error al actualizar: " . print_r(sqlsrv_errors(), true));
}
header("Location: /IMPORTADORES/sistemas/alta_usuarios");
exit;
}
function reset_password() {
session_start();
if (!($_SESSION['usuario_sistemas'] ?? false)) {
die("⚠️ No autorizado.");
}
$conn = getConnection();
$id = $_GET['id'] ?? null;
if (!$id || !is_numeric($id)) {
die("❌ ID inválido.");
}
// 🔐 Generar contraseña aleatoria de 10 caracteres
$randomPassword = bin2hex(random_bytes(5)); // genera algo como 'a8c4f1b92d'
$passwordHash = password_hash($randomPassword, PASSWORD_DEFAULT);
$sqlEmail = "SELECT email FROM usuarios_sistema WHERE id_usuario = ?";
$stmtEmail = sqlsrv_query($conn, $sqlEmail, [$id]);
$row = sqlsrv_fetch_array($stmtEmail, SQLSRV_FETCH_ASSOC);
$sql = "UPDATE usuarios_sistema SET password_hash = ? WHERE id_usuario = ?";
$stmt = sqlsrv_query($conn, $sql, [$passwordHash, $id]);
if (!$stmt) {
die("❌ Error al actualizar: " . print_r(sqlsrv_errors(), true));
}
// 🔁 Redirigir pasando la contraseña como parámetro temporal (solo visible para el admin)
header("Location: /IMPORTADORES/sistemas/alta_usuarios?reset=ok&pass=" . urlencode($randomPassword) . "&email=" . urlencode(decrypt($row['email'])));
exit;
}
function enviar_password() {
$email = $_GET['email'] ?? '';
$pass = $_GET['pass'] ?? '';
if (!$email || !$pass) {
http_response_code(400);
echo "Datos incompletos.";
return;
}
require_once __DIR__ . '/../../vendor/autoload.php';
$mail = new PHPMailer(true);
try {
$mail->isSMTP();
$mail->Host = 'secure.emailsrvr.com';
$mail->SMTPAuth = true;
$mail->Username = 'noreply@aduanasoft.com.mx';
$mail->Password = $_ENV['SMTP_PASS'];
$mail->SMTPSecure = PHPMailer::ENCRYPTION_STARTTLS;
$mail->Port = 587;
$mail->setFrom('noreply@aduanasoft.com.mx', 'SIIH | AduanaSoft');
$mail->addAddress($email);
$mail->isHTML(true);
$mail->Subject = 'Nueva contraseña de acceso';
$mail->CharSet = 'UTF-8';
$logoURL='http://siih.aduanasoft.com/IMPORTADORES/public/assets/img/logo_siih.png';
$mail->Body = "
<div style='font-family: Segoe UI, sans-serif; background-color: #f4f6f9; padding: 40px;'>
<div style='max-width: 600px; margin: auto; background: #fff; border: 1px solid #ddd; border-radius: 10px; overflow: hidden;'>
<div style='background: linear-gradient(to right, #003366, #0055A5); padding: 20px; text-align: center;'>
<img src='$logoURL' alt='Logo $siglas' style='height: 80px; width: 80px; margin-bottom: 10px;'>
<h2 style='color: white;'>Contraseña restablecida</h2>
</div>
<div style='padding: 30px; color: #333; font-size: 16px;'>
<p>Tu contraseña de acceso al <strong>$nombreSistema</strong> ha sido restablecida por el administrador.</p>
<p style='margin-top: 20px; font-size: 18px;'>
<strong>Nueva contraseña:</strong><br>
<span style='background-color: #f0f0f0; padding: 10px 15px; border-radius: 5px; display: inline-block; font-family: monospace;'>$pass</span>
</p>
<p style='margin-top: 20px;'>Por favor, cambia esta contraseña una vez que inicies sesión.</p>
<hr style='margin: 30px 0;'>
<p style='font-size: 14px; color: #888;'>Este es un mensaje automático generado por el sistema. Si no solicitaste esta acción, contacta al administrador.</p>
</div>
<div style='background: #e9ecef; text-align: center; padding: 15px; font-size: 13px; color: #666;'>
&copy; " . date('Y') . " $siglas · Sistema Integral para Importadores de Hidrocarburos
</div>
</div>
</div>
";
$mail->send();
echo "Correo enviado";
} catch (Exception $e) {
http_response_code(500);
echo "Error al enviar correo: {$mail->ErrorInfo}";
}
}
function bitacora_login() {
if (!($_SESSION['usuario_sistemas'] ?? false)) {
die("⚠️ No autorizado.");
}
$conn = getConnection();
$sql = "SELECT * FROM bitacora_login ORDER BY fecha DESC";
$stmt = sqlsrv_query($conn, $sql);
$registros = [];
while ($row = sqlsrv_fetch_array($stmt, SQLSRV_FETCH_ASSOC)) {
$registros[] = $row;
}
include __DIR__ . '/../../views/admin/bitacora_login.php';
}
function bitacora_usuarios() {
if (!($_SESSION['usuario_sistemas'] ?? false)) {
die("⚠️ No autorizado.");
}
$conn = getConnection();
$sql = "SELECT * FROM bitacora_usuarios ORDER BY fecha DESC";
$stmt = sqlsrv_query($conn, $sql);
$registros = [];
while ($row = sqlsrv_fetch_array($stmt, SQLSRV_FETCH_ASSOC)) {
$registros[] = $row;
}
include __DIR__ . '/../../views/admin/bitacora_usuarios.php';
}

View File

@@ -0,0 +1,648 @@
<?php
require_once __DIR__ . '/../helpers/session.php';
require_once __DIR__ . '/../../config/database.php';
require_once __DIR__ . '/../helpers/crypto.php';
function vinculacionesUsuario()
{
if (!isset($_SESSION['usuario_id']) || $_SESSION['tipo_usuario'] !== 'importador') {
header('Location: /IMPORTADORES/login');
exit;
}
$conn = getConnection();
$id_importador = $_SESSION['usuario_id'];
$sql = "
SELECT
ia.*,
aa.id_agencia, aa.nombre_agencia, aa.rfc_agencia, aa.telefono, aa.direccion
FROM importador_agencia ia
INNER JOIN agencias_aduanales aa ON ia.id_agencia = aa.id_agencia
WHERE ia.id_importador = ?
AND ia.activo = 1
AND ia.estado = 'APROBADO'
ORDER BY ia.fecha_vinculacion DESC
";
$stmt = sqlsrv_query($conn, $sql, [$id_importador]);
$vinculaciones = [];
if ($stmt !== false) {
while ($row = sqlsrv_fetch_array($stmt, SQLSRV_FETCH_ASSOC)) {
$vinculaciones[] = $row;
}
sqlsrv_free_stmt($stmt);
} else {
// Manejo de errores
$errors = sqlsrv_errors();
error_log("Error en consulta vinculaciones: " . print_r($errors, true));
}
sqlsrv_close($conn);
include __DIR__ . '/../../views/vinculaciones/vinculaciones_importador.php';
}
function usuariosVinculados()
{
if (!isset($_SESSION['usuario_id']) || $_SESSION['tipo_usuario'] !== 'admin_agencia') {
header('Location: /IMPORTADORES/login');
exit;
}
$conn = getConnection();
// DEBUG: Verificar conexión
if (!$conn) {
die("Error de conexión: " . print_r(sqlsrv_errors(), true));
}
// Obtener la agencia del administrador actual
$sqlAgencia = "SELECT id_agencia FROM agencias_aduanales WHERE id_administrador = ?";
$stmtAgencia = sqlsrv_query($conn, $sqlAgencia, [$_SESSION['usuario_id']]);
// DEBUG: Verificar query de agencia
if ($stmtAgencia === false) {
die("Error en consulta de agencia: " . print_r(sqlsrv_errors(), true));
}
$rowAgencia = sqlsrv_fetch_array($stmtAgencia, SQLSRV_FETCH_ASSOC);
// DEBUG: Verificar si se encontró la agencia
if (!$rowAgencia) {
die("No se encontró agencia para el administrador ID: " . $_SESSION['usuario_id']);
}
$id_agencia = $rowAgencia['id_agencia'];
// Primero, verificar si hay registros en la tabla importador_agencia
$sqlCount = "SELECT COUNT(*) as total FROM importador_agencia WHERE id_agencia = ?";
$stmtCount = sqlsrv_query($conn, $sqlCount, [$id_agencia]);
$rowCount = sqlsrv_fetch_array($stmtCount, SQLSRV_FETCH_ASSOC);
// Verificar registros activos y aprobados
$sqlCountActive = "SELECT COUNT(*) as total FROM importador_agencia WHERE id_agencia = ? AND activo = 1 AND estado = 'APROBADO'";
$stmtCountActive = sqlsrv_query($conn, $sqlCountActive, [$id_agencia]);
$rowCountActive = sqlsrv_fetch_array($stmtCountActive, SQLSRV_FETCH_ASSOC);
// Consulta corregida: solo usuarios vinculados ACTIVOS a MI agencia
$sql = "
SELECT
ia.*,
u.nombre as importador_nombre,
ig.rfc,
ig.telefono
FROM importador_agencia ia
INNER JOIN usuarios_sistema u ON ia.id_importador = u.id_usuario
LEFT JOIN informacion_general ig ON u.id_usuario = ig.id_usuario
WHERE ia.id_agencia = ? AND ia.activo = 1 AND ia.estado = 'APROBADO'
ORDER BY ia.fecha_vinculacion DESC
";
$stmt = sqlsrv_query($conn, $sql, [$id_agencia]);
// DEBUG: Verificar query principal
if ($stmt === false) {
die("Error en consulta principal: " . print_r(sqlsrv_errors(), true));
}
$vinculados = [];
if ($stmt !== false) {
while ($row = sqlsrv_fetch_array($stmt, SQLSRV_FETCH_ASSOC)) {
$vinculados[] = $row;
}
}
include __DIR__ . '/../../views/agencias/usuarios_vinculados.php';
}
function nuevaVinculacion()
{
if (!isset($_SESSION['usuario_id']) || $_SESSION['tipo_usuario'] !== 'importador') {
header('Location: /IMPORTADORES/login');
exit;
}
$conn = getConnection();
$sql = "
SELECT
a.*,
s.estado AS estado_solicitud
FROM agencias_aduanales a
LEFT JOIN solicitudes_vinculacion s
ON s.id_agencia = a.id_agencia AND s.id_importador = ?
AND s.estado = 'PENDIENTE'
WHERE NOT EXISTS (
SELECT 1
FROM importador_agencia ia
WHERE ia.id_agencia = a.id_agencia
AND ia.id_importador = ?
AND ia.activo = 1
)
ORDER BY a.creado_en DESC
";
$params = [$_SESSION['usuario_id'], $_SESSION['usuario_id']];
$stmt = sqlsrv_query($conn, $sql, $params);
$agencias = [];
while ($row = sqlsrv_fetch_array($stmt, SQLSRV_FETCH_ASSOC)) {
$agencias[] = $row;
}
include __DIR__ . '/../../views/vinculaciones/nueva.php';
}
function vincular()
{
if (!isset($_SESSION['usuario_id']) || $_SESSION['tipo_usuario'] !== 'importador') {
header('Location: /IMPORTADORES/login');
exit;
}
$conn = getConnection();
$id_importador = $_SESSION['usuario_id'];
$id_agencia = (int) $_GET['id'];
// Verificar si ya existe una solicitud pendiente
$checkSql = "SELECT COUNT(*) AS total FROM solicitudes_vinculacion
WHERE id_importador = ? AND id_agencia = ? AND estado = 'PENDIENTE'";
$checkParams = [$id_importador, $id_agencia];
$checkStmt = sqlsrv_query($conn, $checkSql, $checkParams);
if ($checkStmt && $row = sqlsrv_fetch_array($checkStmt, SQLSRV_FETCH_ASSOC)) {
if ($row['total'] > 0) {
header('Location: /IMPORTADORES/vinculaciones/nuevaVinculacion?error=pending_request');
exit;
}
}
// Insertar la nueva solicitud
$sql = "INSERT INTO solicitudes_vinculacion (id_importador, id_agencia, mensaje, estado, fecha_solicitud) VALUES (?, ?, 'Solicitud de vinculación', 'PENDIENTE', GETDATE())";
$params = [$id_importador, $id_agencia];
$stmt = sqlsrv_prepare($conn, $sql, $params);
if ($stmt && sqlsrv_execute($stmt)) {
header('Location: /IMPORTADORES/vinculaciones/nuevaVinculacion?success=sended_request');
exit;
} else {
header('Location: /IMPORTADORES/vinculaciones/nuevaVinculacion?error=failed_request');
exit;
}
}
function desvincular()
{
// ✅ CORREGIR: Debe ser admin_agencia, no importador
if (!isset($_SESSION['usuario_id']) || $_SESSION['tipo_usuario'] !== 'admin_agencia') {
header('Location: /IMPORTADORES/login');
exit;
}
// Verificar que se recibió el ID de la relación
$id_relacion = $_GET['id'] ?? null;
if (!$id_relacion || !is_numeric($id_relacion)) {
header('Location: /IMPORTADORES/vinculaciones/usuariosVinculados?error=invalid_id');
exit;
}
$conn = getConnection();
try {
// Iniciar transacción
sqlsrv_begin_transaction($conn);
// 1. Verificar que la relación existe y pertenece a la agencia del admin
$sqlVerificar = "
SELECT
ia.*,
aa.id_administrador,
u.nombre as importador_nombre
FROM importador_agencia ia
INNER JOIN agencias_aduanales aa ON ia.id_agencia = aa.id_agencia
INNER JOIN usuarios_sistema u ON ia.id_importador = u.id_usuario
WHERE ia.id_relacion = ? AND aa.id_administrador = ?
";
$stmtVerificar = sqlsrv_query($conn, $sqlVerificar, [$id_relacion, $_SESSION['usuario_id']]);
$relacion = sqlsrv_fetch_array($stmtVerificar, SQLSRV_FETCH_ASSOC);
if (!$relacion) {
throw new Exception('Relación no encontrada o no tienes permisos para desvincularla');
}
if ($relacion['activo'] == 0) {
throw new Exception('Esta relación ya está inactiva');
}
// 2. Desactivar la relación (no eliminar, mantener historial)
$sqlDesactivar = "
UPDATE importador_agencia
SET activo = 0,
fecha_desvinculacion = GETDATE(),
estado = 'DESVINCULADO'
WHERE id_relacion = ?
";
$stmtDesactivar = sqlsrv_query($conn, $sqlDesactivar, [$id_relacion]);
if (!$stmtDesactivar) {
throw new Exception('Error al desactivar la relación');
}
// Confirmar transacción
sqlsrv_commit($conn);
header('Location: /IMPORTADORES/vinculaciones/usuariosVinculados?success=unlinked');
exit;
} catch (Exception $e) {
// Revertir transacción en caso de error
sqlsrv_rollback($conn);
header('Location: /IMPORTADORES/vinculaciones/usuariosVinculados?error=unlinked_failed');
exit;
}
}
function cancelar()
{
if (!isset($_SESSION['usuario_id']) || $_SESSION['tipo_usuario'] !== 'importador') {
header('Location: /IMPORTADORES/login');
exit;
}
$conn = getConnection();
$id_importador = $_SESSION['usuario_id'];
$id_agencia = (int) $_GET['id'];
$sql = "UPDATE solicitudes_vinculacion
SET estado = 'CANCELADA', fecha_respuesta = GETDATE()
WHERE id_importador = ? AND id_agencia = ? AND estado = 'PENDIENTE'";
$params = [$id_importador, $id_agencia];
$stmt = sqlsrv_prepare($conn, $sql, $params);
if ($stmt && sqlsrv_execute($stmt)) {
header('Location: /IMPORTADORES/vinculaciones/nuevaVinculacion?success=cancelled');
exit;
}
}
function solicitudesVinculacion()
{
if (!($_SESSION['usuario_id'] ?? false) || $_SESSION['tipo_usuario'] !== 'admin_agencia') {
header('Location: /IMPORTADORES/login');
exit;
}
$conn = getConnection();
// Obtener agencia...
$sqlAgencia = "SELECT id_agencia FROM agencias_aduanales WHERE id_administrador = ?";
$params = [$_SESSION['usuario_id']];
$stmtAgencia = sqlsrv_query($conn, $sqlAgencia, $params);
$rowAgencia = sqlsrv_fetch_array($stmtAgencia, SQLSRV_FETCH_ASSOC);
if (!$rowAgencia) {
$solicitudes = [];
include __DIR__ . '/../../views/agencias/solicitudes_vinculacion.php';
return;
}
$id_agencia = $rowAgencia['id_agencia'];
// Verificar solicitudes básicas
$debugSql = "SELECT COUNT(*) as total FROM solicitudes_vinculacion WHERE id_agencia = ?";
$debugStmt = sqlsrv_query($conn, $debugSql, [$id_agencia]);
$debugRow = sqlsrv_fetch_array($debugStmt, SQLSRV_FETCH_ASSOC);
// Verificar pendientes
$debugSql2 = "SELECT COUNT(*) as total FROM solicitudes_vinculacion WHERE estado = 'PENDIENTE' AND id_agencia = ?";
$debugStmt2 = sqlsrv_query($conn, $debugSql2, [$id_agencia]);
$debugRow2 = sqlsrv_fetch_array($debugStmt2, SQLSRV_FETCH_ASSOC);
// Consulta principal
$sql = "
SELECT
sv.id_solicitud,
u.nombre AS importador_nombre,
ig.rfc,
sv.mensaje,
sv.fecha_solicitud,
si.opinion_file
FROM solicitudes_vinculacion sv
INNER JOIN usuarios_sistema u ON sv.id_importador = u.id_usuario
LEFT JOIN informacion_general ig ON u.id_usuario = ig.id_usuario
LEFT JOIN solicitudes_importadores si ON LTRIM(RTRIM(LOWER(si.company_name))) = LTRIM(RTRIM(LOWER(u.nombre)))
WHERE sv.estado = 'PENDIENTE' AND sv.id_agencia = ?
ORDER BY sv.fecha_solicitud DESC
";
$params = [$id_agencia];
$stmt = sqlsrv_query($conn, $sql, $params);
// ✅ INICIALIZAR EL ARRAY DE SOLICITUDES
$solicitudes = [];
// ✅ VERIFICAR SI LA CONSULTA FUNCIONÓ
if ($stmt === false) {
// Usar consulta simplificada como backup
$sqlSimple = "
SELECT
sv.id_solicitud,
u.nombre AS importador_nombre,
sv.mensaje,
sv.fecha_solicitud
FROM solicitudes_vinculacion sv
INNER JOIN usuarios_sistema u ON sv.id_importador = u.id_usuario
WHERE sv.estado = 'PENDIENTE' AND sv.id_agencia = ?
ORDER BY sv.fecha_solicitud DESC
";
$stmt = sqlsrv_query($conn, $sqlSimple, $params);
}
// ✅ PROCESAR LOS RESULTADOS Y LLENAR EL ARRAY
if ($stmt !== false) {
while ($row = sqlsrv_fetch_array($stmt, SQLSRV_FETCH_ASSOC)) {
$solicitudes[] = $row;
}
}
include __DIR__ . '/../../views/agencias/solicitudes_vinculacion.php';
}
function aprobarVinculacion()
{
// Verificar autenticación y permisos
if (!($_SESSION['usuario_id'] ?? false) || $_SESSION['tipo_usuario'] !== 'admin_agencia') {
header('Location: /IMPORTADORES/login');
exit;
}
// Verificar que se recibió el ID de la solicitud
$id_solicitud = $_GET['id'] ?? null;
if (!$id_solicitud || !is_numeric($id_solicitud)) {
header('Location: /IMPORTADORES/vinculaciones/solicitudesVinculacion?error=request_id_invalid');
exit;
}
$conn = getConnection();
try {
// Iniciar transacción
sqlsrv_begin_transaction($conn);
// 1. Obtener datos de la solicitud y verificar que pertenece a la agencia del admin
$sqlSolicitud = "
SELECT
sv.id_solicitud,
sv.id_importador,
sv.id_agencia,
sv.estado,
aa.id_administrador
FROM solicitudes_vinculacion sv
INNER JOIN agencias_aduanales aa ON sv.id_agencia = aa.id_agencia
WHERE sv.id_solicitud = ? AND aa.id_administrador = ?
";
$stmtSolicitud = sqlsrv_query($conn, $sqlSolicitud, [$id_solicitud, $_SESSION['usuario_id']]);
$solicitud = sqlsrv_fetch_array($stmtSolicitud, SQLSRV_FETCH_ASSOC);
if (!$solicitud) {
throw new Exception('Solicitud no encontrada o no tienes permisos para aprobarla');
}
if ($solicitud['estado'] !== 'PENDIENTE') {
throw new Exception('Esta solicitud ya ha sido procesada');
}
// 2. Verificar si ya existe una relación activa entre importador y agencia
$sqlVerificar = "
SELECT id_relacion
FROM importador_agencia
WHERE id_importador = ? AND id_agencia = ? AND activo = 1
";
$stmtVerificar = sqlsrv_query($conn, $sqlVerificar, [$solicitud['id_importador'], $solicitud['id_agencia']]);
$relacionExistente = sqlsrv_fetch_array($stmtVerificar, SQLSRV_FETCH_ASSOC);
if ($relacionExistente) {
throw new Exception('Ya existe una vinculación activa entre este importador y la agencia');
}
// 3. Actualizar el estado de la solicitud a APROBADO
$sqlActualizar = "
UPDATE solicitudes_vinculacion
SET estado = 'APROBADO',
fecha_respuesta = GETDATE(),
respondido_por = ?,
comentarios_respuesta = 'Vinculación aprobada'
WHERE id_solicitud = ?
";
$stmtActualizar = sqlsrv_query($conn, $sqlActualizar, [$_SESSION['usuario_id'], $id_solicitud]);
if (!$stmtActualizar) {
throw new Exception('Error al actualizar la solicitud');
}
// 4. Crear la relación en importador_agencia
$sqlRelacion = "
INSERT INTO importador_agencia (
id_importador,
id_agencia,
activo,
fecha_vinculacion,
creado_por,
aprobado_por,
estado
) VALUES (?, ?, 1, GETDATE(), ?, ?, 'APROBADO')
";
$stmtRelacion = sqlsrv_query($conn, $sqlRelacion, [
$solicitud['id_importador'],
$solicitud['id_agencia'],
$solicitud['id_importador'], // creado_por (el importador que solicitó)
$_SESSION['usuario_id'] // aprobado_por (el admin de agencia)
]);
if (!$stmtRelacion) {
throw new Exception('Error al crear la relación importador-agencia');
}
// Confirmar transacción
sqlsrv_commit($conn);
header('Location: /IMPORTADORES/vinculaciones/solicitudesVinculacion?success=approved');
exit;
} catch (Exception $e) {
// Revertir transacción en caso de error
sqlsrv_rollback($conn);
header('Location: /IMPORTADORES/vinculaciones/solicitudesVinculacion?error=denied');
exit;
}
}
function denegarVinculacion()
{
// Verificar autenticación y permisos
if (!($_SESSION['usuario_id'] ?? false) || $_SESSION['tipo_usuario'] !== 'admin_agencia') {
header('Location: /IMPORTADORES/login');
exit;
}
// Verificar que se recibió el ID de la solicitud
$id_solicitud = $_GET['id'] ?? null;
if (!$id_solicitud || !is_numeric($id_solicitud)) {
header('Location: /IMPORTADORES/vinculaciones/solicitudesVinculacion?error=request_id_invalid');
exit;
}
$conn = getConnection();
try {
// Iniciar transacción
sqlsrv_begin_transaction($conn);
// 1. Obtener datos de la solicitud y verificar que pertenece a la agencia del admin
$sqlSolicitud = "
SELECT
sv.id_solicitud,
sv.id_importador,
sv.id_agencia,
sv.estado,
aa.id_administrador
FROM solicitudes_vinculacion sv
INNER JOIN agencias_aduanales aa ON sv.id_agencia = aa.id_agencia
WHERE sv.id_solicitud = ? AND aa.id_administrador = ?
";
$stmtSolicitud = sqlsrv_query($conn, $sqlSolicitud, [$id_solicitud, $_SESSION['usuario_id']]);
$solicitud = sqlsrv_fetch_array($stmtSolicitud, SQLSRV_FETCH_ASSOC);
if (!$solicitud) {
throw new Exception('Solicitud no encontrada o no tienes permisos para denegarla');
}
if ($solicitud['estado'] !== 'PENDIENTE') {
throw new Exception('Esta solicitud ya ha sido procesada');
}
// 2. Actualizar el estado de la solicitud a DENEGADO
$sqlActualizar = "
UPDATE solicitudes_vinculacion
SET estado = 'DENEGADO',
fecha_respuesta = GETDATE(),
respondido_por = ?
WHERE id_solicitud = ?
";
$stmtActualizar = sqlsrv_query($conn, $sqlActualizar, [$_SESSION['usuario_id'], $id_solicitud]);
if (!$stmtActualizar) {
throw new Exception('Error al actualizar la solicitud');
}
if (!$stmtRelacion) {
throw new Exception('Error al registrar el rechazo en el historial');
}
// Confirmar transacción
sqlsrv_commit($conn);
header('Location: /IMPORTADORES/vinculaciones/solicitudesVinculacion?success=denied');
exit;
} catch (Exception $e) {
// Revertir transacción en caso de error
sqlsrv_rollback($conn);
header('Location: /IMPORTADORES/vinculaciones/solicitudesVinculacion?error=denied_error');
exit;
}
}
function desvincularUsuario()
{
// ✅ CORREGIR: Debe ser admin_agencia, no importador
if (!isset($_SESSION['usuario_id']) || $_SESSION['tipo_usuario'] !== 'importador') {
header('Location: /IMPORTADORES/login');
exit;
}
// Verificar que se recibió el ID de la relación
$id_relacion = $_GET['id'] ?? null;
if (!$id_relacion || !is_numeric($id_relacion)) {
header('Location: /IMPORTADORES/vinculaciones/vinculacionesUsuario?error=invalid_id');
exit;
}
$conn = getConnection();
try {
// Iniciar transacción
sqlsrv_begin_transaction($conn);
// 1. Verificar que la relación existe y pertenece a la agencia del admin
$sqlVerificar = "
SELECT
ia.*,
u.id_usuario, u.nombre as importador_nombre, aa.nombre_agencia
FROM importador_agencia ia
INNER JOIN usuarios_sistema u ON ia.id_importador = u.id_usuario
INNER JOIN agencias_aduanales aa ON ia.id_agencia = aa.id_agencia
WHERE ia.id_relacion = ? AND ia.id_importador = ?
";
$stmtVerificar = sqlsrv_query($conn, $sqlVerificar, [$id_relacion, $_SESSION['usuario_id']]);
// Verificar si la consulta fue exitosa
if ($stmtVerificar === false) {
$errors = sqlsrv_errors();
error_log("Error en consulta verificar: " . print_r($errors, true));
throw new Exception('Error en la consulta de verificación');
}
$relacion = sqlsrv_fetch_array($stmtVerificar, SQLSRV_FETCH_ASSOC);
if (!$relacion) {
throw new Exception('Relación no encontrada o no tienes permisos para desvincularla');
}
if ($relacion['activo'] == 0) {
throw new Exception('Esta relación ya está inactiva');
}
// 2. Desactivar la relación (no eliminar, mantener historial)
$sqlDesactivar = "
UPDATE importador_agencia
SET activo = 0,
fecha_desvinculacion = GETDATE(),
estado = 'DESVINCULADO'
WHERE id_relacion = ? AND id_importador = ?
";
$stmtDesactivar = sqlsrv_query($conn, $sqlDesactivar, [$id_relacion, $_SESSION['usuario_id']]);
if ($stmtDesactivar === false) {
$errors = sqlsrv_errors();
error_log("Error en actualización: " . print_r($errors, true));
throw new Exception('Error al desactivar la relación');
}
// Verificar que se actualizó al menos una fila
$filasAfectadas = sqlsrv_rows_affected($stmtDesactivar);
if ($filasAfectadas === 0) {
throw new Exception('No se pudo actualizar la relación');
}
// Limpiar statements
sqlsrv_free_stmt($stmtVerificar);
sqlsrv_free_stmt($stmtDesactivar);
// Confirmar transacción
sqlsrv_commit($conn);
sqlsrv_close($conn);
header('Location: /IMPORTADORES/vinculaciones/vinculacionesUsuario?success=unlinked');
exit;
} catch (Exception $e) {
// Revertir transacción en caso de error
sqlsrv_rollback($conn);
sqlsrv_close($conn);
header('Location: /IMPORTADORES/vinculaciones/vinculacionesUsuario?error=unlinked_failed&msg=' . urlencode($e->getMessage()));
exit;
}
}

View File

@@ -17,4 +17,12 @@ function registrar_bitacora_usuario($usuarioId, $accion, $descripcion) {
sqlsrv_query($conn, $sql, $params);
}
function registrar_bitacora_agencia($agenciaId, $nombreAgencia, $accion, $idAdmin) {
$conn = getConnection();
$sql = "INSERT INTO bitacora_agencias (id_agencia, nombre, accion, realizado_por)
VALUES (?, ?, ?, ?)";
$params = [$usuarioId, $nombre, $accion, $idAdmin];
sqlsrv_query($conn, $sql, $params);
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 278 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 84 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 273 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 MiB

View File

@@ -1,11 +1,17 @@
<?php include __DIR__ . '/../partials/sidebar_sistemas.php'; ?>
<?php include __DIR__ . '/../partials/sidebar_administrador.php'; ?>
<html>
<!DOCTYPE html>
<html lang="es">
<head>
<title>Dashboard | Agencia Aduanal</title>
<meta charset="UTF-8">
<title>Dashboard | Administrador</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>
body { font-family: 'Segoe UI', sans-serif; background-color: #f4f6f9; }
@@ -27,12 +33,89 @@
.card { border-radius: 12px; }
.table thead th { background: #343a40; color: #fff; }
</style>
</head>
<body>
<div class="content">
<h4>✅ Agencias Activas</h4>
<h4 class="mb-4">✅ Agencias Activas</h4>
<!-- TABLA DE AGENCIAS -->
<div class="card p-3 shadow-sm">
<div class="table-responsive">
<table class="table table-striped table-hover align-middle" id="tabla-agencias-activas">
<thead>
<tr>
<th>#</th>
<th>Nombre</th>
<th>RFC</th>
<th>Correo</th>
<th>Teléfono</th>
<th>Dirección</th>
<th>Fecha</th>
<th>Administrador</th>
<th>Acciones</th>
</tr>
</thead>
<tbody>
<?php if (!empty($agencias)): ?>
<?php foreach ($agencias as $ag): ?>
<tr>
<td><?= $ag['id_agencia'] ?></td>
<td><?= htmlspecialchars(decrypt($ag['nombre_agencia'])) ?></td>
<td><?= htmlspecialchars($ag['rfc_agencia']) ?></td>
<td><?= htmlspecialchars(decrypt($ag['email'])) ?></td>
<td><?= htmlspecialchars($ag['telefono']) ?></td>
<td><?= htmlspecialchars($ag['direccion']) ?></td>
<td><?= $ag['creado_en'] ? $ag['creado_en']->format('Y-m-d H:i') : '' ?></td>
<td><?= htmlspecialchars($ag['id_administrador']) ?></td>
<td>
<?php if (isset($ag['activo']) && $ag['activo'] == 1): ?>
<a href="/IMPORTADORES/administrador/suspenderAgencia?id=<?= $ag['id_agencia'] ?>&success=1" class="btn btn-sm btn-danger">Suspender</a>
<?php endif; ?>
</td>
</tr>
<?php endforeach; ?>
<?php else: ?>
<tr>
<td colspan="8" class="text-center">No hay agencias registradas aún.</td>
</tr>
<?php endif; ?>
</tbody>
</table>
</div>
</div>
</div>
<script>
$(document).ready(function () {
$('#tabla-agencias-activas').DataTable({
order: [],
language: {
url: 'https://cdn.datatables.net/plug-ins/1.13.4/i18n/es-ES.json'
}
});
});
</script>
<?php if (isset($_GET['success'])): ?>
<script>
<?php if ($_GET['success'] === 'status_deactivated'): ?>
Swal.fire({ icon: 'success', title: 'Usuario desactivado', text: 'El usuario ha sido desactivado correctamente.', confirmButtonColor: '#198754' });
<?php endif; ?>
</script>
<?php endif; ?>
<?php if (isset($_GET['error'])): ?>
<script>
<?php if ($_GET['error'] === 'unauthorized'): ?>
Swal.fire({ icon: 'error', title: 'No autorizado', text: 'No tienes permisos para realizar esta acción.', confirmButtonColor: '#dc3545' });
<?php elseif ($_GET['error'] === 'invalid_id'): ?>
Swal.fire({ icon: 'error', title: 'ID inválido', text: 'El identificador del usuario no es válido.', confirmButtonColor: '#dc3545' });
<?php elseif ($_GET['error'] === 'agencia_not_found'): ?>
Swal.fire({ icon: 'error', title: 'Usuario no encontrado', text: 'No se encontró el usuario especificado.', confirmButtonColor: '#dc3545' });
<?php elseif ($_GET['error'] === 'update_failed'): ?>
Swal.fire({ icon: 'error', title: 'Error al actualizar', text: 'No se pudo actualizar el estado del usuario.', confirmButtonColor: '#dc3545' });
<?php endif; ?>
</script>
<?php endif; ?>
</body>
</html>

View File

@@ -0,0 +1,202 @@
<?php include __DIR__ . '/../partials/sidebar_administrador.php'; ?>
<!DOCTYPE html>
<html lang="es">
<head>
<meta charset="UTF-8">
<title>Dashboard | Administrador</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>
<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: normal; color: white; transition: all 0.3s ease; }
.sidebar .nav-link:hover,
.sidebar .nav-link.active { background-color: #495057; color: #fff; }
.content { margin-top: 56px; padding: 40px 20px; position: relative; z-index: 1; background-color: #f4f6f9; transition: margin-left 0.3s ease; }
.navbar { position: fixed; top: 0; width: 100%; z-index: 1050; }
/* A partir de dispositivos medianos (>=768px), deja espacio lateral */
@media (min-width: 768px) { .content { margin-left: 250px; /* Ancho del sidebar */ } }
/* En móviles, sin margen lateral */
@media (max-width: 767.98px) {
.content { margin-left: 0; }
.sidebar .nav-link { font-weight: normal; color: #343a40; background-color: transparent; }
.sidebar .nav-link:hover,
.sidebar .nav-link.active { background-color: #e9ecef; color: #212529; }
}
.card { border-radius: 12px; }
.table thead th { background: #343a40; color: #fff; }
</style>
</head>
<body>
<!-- 📄 CONTENIDO -->
<div class="content">
<h4 class="mb-4">🏪 Alta de Agencias Aduanales</h4>
<!-- FORMULARIO -->
<div class="card p-4 mb-5 shadow-sm">
<form action="/IMPORTADORES/administrador/guardar_agencia" method="POST" id="formAltaAgencias">
<h5 class="mb-3"> Nueva Agencia Aduanal</h5>
<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>
<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>
</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>
</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>
</div>
<div class="col-md-12">
<label for="direccion" class="form-label">Dirección *</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>
</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>
</div>
<div class="text-end mt-4">
<button class="btn btn-success px-4">Registrar</button>
</div>
</form>
</div>
</div>
<script>
// Registro de Agencia
document.getElementById('formAltaAgencias').addEventListener('submit', function(e) {
e.preventDefault();
const nombre = document.getElementById('nombre_agencia').value.trim();
const rfc_agencia = document.getElementById('rfc_agencia').value.trim().toUpperCase();
const correo = document.getElementById('correo').value.trim();
const telefono = document.getElementById('telefono').value.trim();
const direccion = document.getElementById('direccion').value.trim();
const nombre_admin = document.getElementById('nombre_admin').value.trim();
const correo_admin = document.getElementById('correo_admin').value.trim();
// 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();
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();
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();
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();
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();
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;
}
// Si todas las validaciones pasan, el formulario se envía.
this.submit();
});
</script>
<?php if (isset($_GET['success'])): ?>
<script>
<?php if ($_GET['success'] === 'created'): ?>
Swal.fire({ icon: 'success', title: 'Agencia registrada',
text: 'La agencia adunal se ha registrada correctamente y se ha enviado un correo con las credenciales de acceso al administrador.',
confirmButtonColor: '#198754' });
<?php endif; ?>
</script>
<?php endif; ?>
<?php if (isset($_GET['error'])): ?>
<script>
<?php if ($_GET['error'] === 'unauthorized'): ?>
Swal.fire({ icon: 'error', title: 'No autorizado', text: 'No tienes permisos para realizar esta acción.', confirmButtonColor: '#dc3545' });
<?php elseif ($_GET['error'] === 'invalid_id_agencia'): ?>
Swal.fire({ icon: 'error', title: 'ID de agencia inválido', text: 'El identificador la agencia no es válido.', confirmButtonColor: '#dc3545' });
<?php elseif ($_GET['error'] === 'invalid_id'): ?>
Swal.fire({ icon: 'error', title: 'ID inválido', text: 'El identificador del usuario no es válido.', confirmButtonColor: '#dc3545' });
<?php elseif ($_GET['error'] === 'save_agencia_failed'): ?>
Swal.fire({ icon: 'error', title: 'Error al guardar agencia', text: 'No se pudo guardar la agencia. Inténtalo nuevamente.', confirmButtonColor: '#dc3545' });
<?php elseif ($_GET['error'] === 'update_failed'): ?>
Swal.fire({ icon: 'error', title: 'Error al actualizar', text: 'No se pudo actualizar el estado del usuario.', confirmButtonColor: '#dc3545' });
<?php elseif ($_GET['error'] === 'invalid_data'): ?>
Swal.fire({ icon: 'error', title: 'Datos inválidos', text: 'Por favor, completa todos los campos requeridos correctamente.', confirmButtonColor: '#dc3545' });
<?php elseif ($_GET['error'] === 'save_failed'): ?>
Swal.fire({ icon: 'error', title: 'Error al guardar', text: 'No se pudo guardar el usuario. Inténtalo nuevamente.', confirmButtonColor: '#dc3545' });
<?php endif; ?>
</script>
<?php endif; ?>
</body>
</html>

View File

@@ -1,37 +1,63 @@
<?php include __DIR__ . '/../partials/sidebar_sistemas.php'; ?>
<?php include __DIR__ . '/../partials/sidebar_administrador.php'; ?>
<!DOCTYPE html>
<html lang="es">
<head>
<meta charset="UTF-8">
<title>Panel de Administración - Usuarios</title>
<title>Dashboard | Administrador</title>
<meta name="viewport" content="width=device-width, initial-scale=1">
<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>
body { font-family: 'Segoe UI', sans-serif; background-color: #f4f6f9; }
.sidebar { width: 220px; height: 100vh; background-color: #343a40; position: fixed; top: 0; left: 0; padding-top: 56px; z-index: 1040; }
.sidebar .nav-link { font-weight: normal; color: white; transition: all 0.3s ease; }
.sidebar .nav-link:hover,
.sidebar .nav-link.active { background-color: #495057; color: #fff; }
.content { margin-top: 56px; padding: 40px 20px; position: relative; z-index: 1; background-color: #f4f6f9; transition: margin-left 0.3s ease; }
.navbar { position: fixed; top: 0; width: 100%; z-index: 1050; }
/* A partir de dispositivos medianos (>=768px), deja espacio lateral */
@media (min-width: 768px) { .content { margin-left: 250px; /* Ancho del sidebar */ } }
/* En móviles, sin margen lateral */
@media (max-width: 767.98px) {
.content { margin-left: 0; }
.sidebar .nav-link { font-weight: normal; color: #343a40; background-color: transparent; }
.sidebar .nav-link:hover,
.sidebar .nav-link.active { background-color: #e9ecef; color: #212529; }
}
.card { border-radius: 12px; }
table.dataTable thead th { background:#343a40; color:#fff; }
</style>
</head>
<body>
<!-- 📄 CONTENIDO -->
<div class="content px-4">
<h3 class="mb-4">Administración de Usuarios</h3>
<div class="content">
<h4 class="mb-4">👥 Alta de Usuarios</h4>
<!-- FORMULARIO -->
<form action="/IMPORTADORES/sistemas/guardar_usuario" method="POST" class="card p-4 mb-5 shadow-sm">
<div class="card p-4 mb-4 shadow-sm bg-white">
<form action="/IMPORTADORES/administrador/guardar_usuario" method="POST">
<h5 class="mb-3"> Nuevo Usuario</h5>
<div class="row g-3">
<div class="col-md-4">
<input name="nombre" class="form-control" placeholder="Nombre completo" required>
<input name="nombre" type="text" class="form-control" id="nombre" placeholder="Nombre completo" required>
</div>
<div class="col-md-4">
<input name="email" type="email" class="form-control" placeholder="Correo electrónico" required>
<input name="email" type="email" class="form-control" id="email" placeholder="Correo electrónico" required>
</div>
<div class="col-md-2">
<input name="password" type="password" class="form-control" placeholder="Contraseña" required>
<input name="password" type="password" class="form-control" id="password" placeholder="Contraseña" required>
</div>
<div class="col-md-2">
<select name="tipo_usuario" class="form-select" required>
<option value="">Tipo</option>
<option value="agente_aduanal">Agente Aduanal</option>
<option value="super_admin">Administrador General</option>
<option value="importador">Importador</option>
</select>
</div>
@@ -40,89 +66,88 @@
<button class="btn btn-success px-4">Registrar</button>
</div>
</form>
</div>
<!-- TABLA DE USUARIOS -->
<div class="card p-4 shadow-sm">
<h5 class="mb-3">👁️ Usuarios Registrados</h5>
<h4 class="mb-4">✅ Usuarios Activos</h4>
<div class="card p-3 shadow-sm">
<div class="table-responsive">
<table class="table table-striped table-hover align-middle">
<thead>
<table class="table table-striped table-hover align-middle" id="tabla-activos">
<thead class="table-dark">
<tr>
<th>#</th>
<th>Nombre</th>
<th>Correo</th>
<th>Tipo</th>
<th>Estado</th>
<th>Creado por</th>
<th>Fecha Registro</th>
<th>Acciones</th>
</tr>
</thead>
<tbody>
<?php if (!empty($usuarios)): ?>
<?php foreach ($usuarios as $u): ?>
<tr>
<td><?= $u['id_usuario'] ?></td>
<td><?= decrypt($u['nombre']) ?></td>
<td><?= decrypt($u['email']) ?></td>
<td><?= ucfirst($u['tipo_usuario']) ?></td>
<td><?= htmlspecialchars(decrypt($u['nombre'])) ?></td>
<td><?= htmlspecialchars(decrypt($u['email'])) ?></td>
<td><?= htmlspecialchars($u['tipo_usuario']) ?></td>
<td><?= htmlspecialchars($u['creado_por']) ?></td>
<td><?= isset($u['creado_en']) && $u['creado_en'] instanceof DateTime ? $u['creado_en']->format('Y-m-d H:i') : '' ?></td>
<td>
<span class="badge <?= $u['activo'] ? 'bg-success' : 'bg-danger' ?>">
<?= $u['activo'] ? 'Activo' : 'Inactivo' ?>
</span>
</td>
<td>
<a href="/IMPORTADORES/sistemas/toggle_estado?id=<?= $u['id_usuario'] ?>" class="btn btn-sm btn-warning">Toggle</a>
<a href="/IMPORTADORES/sistemas/reset_password?id=<?= $u['id_usuario'] ?>" class="btn btn-sm btn-secondary">Reset</a>
<?php if (isset($u['activo']) && $u['activo'] == 1): ?>
<a href="/IMPORTADORES/administrador/suspender?id=<?= $u['id_usuario'] ?>&success=1" class="btn btn-sm btn-danger">Suspender</a>
<?php endif; ?>
</td>
</tr>
<?php endforeach; ?>
<?php else: ?>
<tr>
<td colspan="6" class="text-center">No hay usuarios registrados aún.</td>
</tr>
<?php endif; ?>
</tbody>
</table>
</div>
</div>
</div>
<script src="https://cdn.jsdelivr.net/npm/sweetalert2@11"></script>
<?php if (isset($_GET['reset'], $_GET['pass'], $_GET['email']) && $_GET['reset'] === 'ok'): ?>
<script>
const nuevaPassword = "<?= htmlspecialchars($_GET["pass"]) ?>";
const emailUsuario = "<?= htmlspecialchars($_GET["email"]) ?>";
Swal.fire({
icon: 'success',
title: 'Contraseña restablecida',
html: `
Nueva contraseña generada:<br>
<strong style="font-size:18px;">${nuevaPassword}</strong>
<div class="mt-3">
<button class="btn btn-outline-primary btn-sm" onclick="copiarPassword()">📋 Copiar</button>
<button class="btn btn-outline-success btn-sm" onclick="enviarPorCorreo()">📧 Enviar por correo</button>
</div>
`,
showConfirmButton: false,
});
function copiarPassword() {
navigator.clipboard.writeText(nuevaPassword).then(() => {
Swal.fire('Copiado', 'La contraseña fue copiada al portapapeles.', 'success');
});
$(document).ready(function () {
$('#tabla-activos').DataTable({
order: [],
language: {
url: 'https://cdn.datatables.net/plug-ins/1.13.4/i18n/es-ES.json'
}
function enviarPorCorreo() {
fetch(`/IMPORTADORES/sistemas/enviar_password?email=${encodeURIComponent(emailUsuario)}&pass=${encodeURIComponent(nuevaPassword)}`)
.then(response => response.text())
.then(data => {
Swal.fire('Enviado', 'La contraseña fue enviada al correo del usuario.', 'success');
})
.catch(() => {
Swal.fire('Error', 'No se pudo enviar el correo.', 'error');
});
}
});
</script>
<?php if (isset($_GET['success'])): ?>
<script>
<?php if ($_GET['success'] === 'created'): ?>
Swal.fire({ icon: 'success', title: 'Usuario creado',
text: 'El usuario se ha creado correctamente y se ha enviado un correo con las credenciales.',
confirmButtonColor: '#198754' });
<?php elseif ($_GET['success'] === 'status_deactivated'): ?>
Swal.fire({ icon: 'success', title: 'Usuario desactivado', text: 'El usuario ha sido desactivado correctamente.', confirmButtonColor: '#198754' });
<?php endif; ?>
</script>
<?php endif; ?>
<?php if (isset($_GET['error'])): ?>
<script>
<?php if ($_GET['error'] === 'unauthorized'): ?>
Swal.fire({ icon: 'error', title: 'No autorizado', text: 'No tienes permisos para realizar esta acción.', confirmButtonColor: '#dc3545' });
<?php elseif ($_GET['error'] === 'invalid_id'): ?>
Swal.fire({ icon: 'error', title: 'ID inválido', text: 'El identificador del usuario no es válido.', confirmButtonColor: '#dc3545' });
<?php elseif ($_GET['error'] === 'user_not_found'): ?>
Swal.fire({ icon: 'error', title: 'Usuario no encontrado', text: 'No se encontró el usuario especificado.', confirmButtonColor: '#dc3545' });
<?php elseif ($_GET['error'] === 'update_failed'): ?>
Swal.fire({ icon: 'error', title: 'Error al actualizar', text: 'No se pudo actualizar el estado del usuario.', confirmButtonColor: '#dc3545' });
<?php elseif ($_GET['error'] === 'invalid_data'): ?>
Swal.fire({ icon: 'error', title: 'Datos inválidos', text: 'Por favor, completa todos los campos requeridos correctamente.', confirmButtonColor: '#dc3545' });
<?php elseif ($_GET['error'] === 'invalid_user_type'): ?>
Swal.fire({ icon: 'error', title: 'Tipo de usuario inválido', text: 'El tipo de usuario seleccionado no es válido.', confirmButtonColor: '#dc3545' });
<?php elseif ($_GET['error'] === 'email_exists'): ?>
Swal.fire({ icon: 'error', title: 'Correo ya registrado', text: 'Este correo electrónico ya está registrado en el sistema.', confirmButtonColor: '#dc3545' });
<?php elseif ($_GET['error'] === 'save_failed'): ?>
Swal.fire({ icon: 'error', title: 'Error al guardar', text: 'No se pudo guardar el usuario. Inténtalo nuevamente.', confirmButtonColor: '#dc3545' });
<?php endif; ?>
</script>
<?php endif; ?>

View File

@@ -0,0 +1,91 @@
<?php include __DIR__ . '/../partials/sidebar_administrador.php'; ?>
<!DOCTYPE html>
<html lang="es">
<head>
<meta charset="UTF-8">
<title>Dashboard | Administrador</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>
<style>
body { font-family: 'Segoe UI', sans-serif; background-color: #f4f6f9; }
.sidebar { width: 220px; height: 100vh; background-color: #343a40; position: fixed; top: 0; left: 0; padding-top: 56px; z-index: 1040; }
.sidebar .nav-link { font-weight: normal; color: white; transition: all 0.3s ease; }
.sidebar .nav-link:hover,
.sidebar .nav-link.active { background-color: #495057; color: #fff; }
.content { margin-top: 56px; padding: 40px 20px; position: relative; z-index: 1; background-color: #f4f6f9; transition: margin-left 0.3s ease; }
.navbar { position: fixed; top: 0; width: 100%; z-index: 1050; }
/* A partir de dispositivos medianos (>=768px), deja espacio lateral */
@media (min-width: 768px) { .content { margin-left: 250px; /* Ancho del sidebar */ } }
/* En móviles, sin margen lateral */
@media (max-width: 767.98px) {
.content { margin-left: 0; }
.sidebar .nav-link { font-weight: normal; color: #343a40; background-color: transparent; }
.sidebar .nav-link:hover,
.sidebar .nav-link.active { background-color: #e9ecef; color: #212529; }
}
.card { border-radius: 12px; }
.table thead th { background: #343a40; color: #fff; }
</style>
</head>
<body>
<!-- 📄 CONTENIDO -->
<div class="content">
<h4 class="mb-4">✅ Aprobación de Agencias Aduanales</h4>
<!-- TABLA DE AGENCIAS -->
<div class="card p-3 shadow-sm">
<h5 class="mb-3">🏪 Agencias Solicitantes</h5>
<div class="table-responsive">
<table class="table table-striped table-hover align-middle" id="tabla-agencias-pendientes">
<thead>
<tr>
<th>#</th>
<th>Nombre</th>
<th>RFC</th>
<th>Correo</th>
<th>Teléfono</th>
<th>Dirección</th>
<th>Fecha</th>
<th>Archivo SAT</th>
<th>Acciones</th>
</tr>
</thead>
<tbody>
<?php if (!empty($solicitudes)): ?>
<?php foreach ($solicitudes as $s): ?>
<tr>
<td><?= $s['request_id'] ?></td>
<td><?= htmlspecialchars(decrypt($s['agencia_name'])) ?></td>
<td><?= htmlspecialchars(decrypt($s['rfc'])) ?></td>
<td><?= htmlspecialchars($s['email']) ?></td>
<td><?= htmlspecialchars($s['phone']) ?></td>
<td><?= htmlspecialchars($s['direccion']) ?></td>
<td><?= $s['request_date'] ? $s['request_date']->format('Y-m-d H:i') : '' ?></td>
<td>
<?php if (!empty($s['opinion_file'])): ?>
<a href="/IMPORTADORES/ver_opinion.php?file=<?= urlencode($s['opinion_file']) ?>" target="_blank" class="btn btn-sm btn-outline-secondary">Ver PDF</a>
<?php else: ?>
<span class="text-muted">No adjunto</span>
<?php endif; ?>
</td>
<td>
<a href="/IMPORTADORES/administrador/aprobar_agencia?id=<?= $s['request_id'] ?>" class="btn btn-sm btn-success">Aprobar</a>
</td>
</tr>
<?php endforeach; ?>
<?php else: ?>
<tr>
<td colspan="8" class="text-center">No hay agencias registradas aún.</td>
</tr>
<?php endif; ?>
</tbody>
</table>
</div>
</div>
</div>
</body>
</html>

View File

@@ -0,0 +1,119 @@
<?php include __DIR__ . '/../partials/sidebar_administrador.php'; ?>
<!DOCTYPE html>
<html lang="es">
<head>
<meta charset="UTF-8">
<title>Dashboard | Administrador</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>
<style>
body { font-family: 'Segoe UI', sans-serif; background-color: #f4f6f9; }
.sidebar { width: 220px; height: 100vh; background-color: #343a40; position: fixed; top: 0; left: 0; padding-top: 56px; z-index: 1040; }
.sidebar .nav-link { font-weight: normal; color: white; transition: all 0.3s ease; }
.sidebar .nav-link:hover,
.sidebar .nav-link.active { background-color: #495057; color: #fff; }
.content { margin-top: 56px; padding: 40px 20px; position: relative; z-index: 1; background-color: #f4f6f9; transition: margin-left 0.3s ease; }
.navbar { position: fixed; top: 0; width: 100%; z-index: 1050; }
/* A partir de dispositivos medianos (>=768px), deja espacio lateral */
@media (min-width: 768px) { .content { margin-left: 250px; /* Ancho del sidebar */ } }
/* En móviles, sin margen lateral */
@media (max-width: 767.98px) {
.content { margin-left: 0; }
.sidebar .nav-link { font-weight: normal; color: #343a40; background-color: transparent; }
.sidebar .nav-link:hover,
.sidebar .nav-link.active { background-color: #e9ecef; color: #212529; }
}
.card { border-radius: 12px; }
.table thead th { background: #343a40; color: #fff; }
</style>
</head>
<body>
<!-- 📄 CONTENIDO -->
<div class="content">
<h4 class="mb-4">✅ Aprobación de Usuarios</h4>
<!-- TABLA DE USUARIOS -->
<div class="card p-3 shadow-sm">
<h5 class="mb-3">👥 Usuarios Solicitantes</h5>
<div class="table-responsive">
<table class="table table-striped table-hover align-middle" id="tabla-usuarios-pendientes">
<thead>
<tr>
<th>#</th>
<th>Empresa</th>
<th>RFC</th>
<th>Correo</th>
<th>Teléfono</th>
<th>Fecha</th>
<th>Archivo SAT</th>
<th>Acciones</th>
</tr>
</thead>
<tbody>
<?php if (!empty($solicitudes)): ?>
<?php foreach ($solicitudes as $s): ?>
<tr>
<td><?= $s['request_id'] ?></td>
<td><?= htmlspecialchars(decrypt($s['company_name'])) ?></td>
<td><?= htmlspecialchars(decrypt($s['rfc'])) ?></td>
<td><?= htmlspecialchars($s['email']) ?></td>
<td><?= htmlspecialchars($s['phone']) ?></td>
<td><?= $s['request_date'] ? $s['request_date']->format('Y-m-d H:i') : '' ?></td>
<td>
<?php if (!empty($s['opinion_file'])): ?>
<a href="/IMPORTADORES/ver_opinion.php?file=<?= urlencode($s['opinion_file']) ?>" target="_blank" class="btn btn-sm btn-outline-secondary">Ver PDF</a>
<?php else: ?>
<span class="text-muted">No adjunto</span>
<?php endif; ?>
</td>
<td>
<a href="/IMPORTADORES/administrador/aprobar_usuario?id=<?= $s['request_id'] ?>" class="btn btn-sm btn-success">Aprobar</a>
</td>
</tr>
<?php endforeach; ?>
<?php else: ?>
<tr>
<td colspan="8" class="text-center">No hay solicitudes registradas aún.</td>
</tr>
<?php endif; ?>
</tbody>
</table>
</div>
</div>
</div>
<?php if (isset($_GET['success'])): ?>
<script>
<?php if ($_GET['success'] === 'approved'): ?>
Swal.fire({ icon: 'success', title: 'Usuario aprobado',
text: 'El usuario ha sido aprobado correctamente y se ha enviado un correo con las credenciales.',
confirmButtonColor: '#198754' });
<?php endif; ?>
</script>
<?php endif; ?>
<?php if (isset($_GET['error'])): ?>
<script>
<?php if ($_GET['error'] === 'invalid_id'): ?>
Swal.fire({ icon: 'error', title: 'ID inválido', text: 'El identificador del usuario no es válido.', confirmButtonColor: '#dc3545' });
<?php elseif ($_GET['error'] === 'already_approved'): ?>
Swal.fire({ icon: 'warning', title: 'Ya aprobado', text: 'Esta solicitud ya fue aprobada anteriormente.', confirmButtonColor: '#ffc107' });
<?php elseif ($_GET['error'] === 'request_not_found'): ?>
Swal.fire({ icon: 'error', title: 'Solicitud no encontrada', text: 'No se encontró la solicitud especificada.', confirmButtonColor: '#dc3545' });
<?php elseif ($_GET['error'] === 'save_failed'): ?>
Swal.fire({ icon: 'error', title: 'Error al guardar', text: 'No se pudo guardar el usuario. Inténtalo nuevamente.', confirmButtonColor: '#dc3545' });
<?php elseif ($_GET['error'] === 'update_failed'): ?>
Swal.fire({ icon: 'error', title: 'Error al actualizar', text: 'No se pudo actualizar el estado del usuario.', confirmButtonColor: '#dc3545' });
<?php elseif ($_GET['error'] === 'failed'): ?>
Swal.fire({ icon: 'error', title: 'Error inesperado', text: 'Ha ocurrido un error inesperado. Por favor, inténtalo nuevamente.', confirmButtonColor: '#dc3545' })
<?php else: ?>
Swal.fire({ icon: 'error', title: 'Error', text: 'Ha ocurrido un error inesperado. Por favor, inténtalo nuevamente.', confirmButtonColor: '#dc3545' });
<?php endif; ?>
</script>
<?php endif; ?>
</body>
</html>

View File

@@ -1,50 +0,0 @@
<?php include __DIR__ . '/../partials/sidebar_sistemas.php'; ?>
<!DOCTYPE html>
<html lang="es">
<head>
<meta charset="UTF-8">
<title>Panel de Administración - Usuarios</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>
</head>
<body>
<div class="content px-4 pt-5 mt-4">
<h4>🕵️ Bitácora de Accesos</h4>
<table class="table table-hover mt-3 align-middle">
<thead class="table-dark">
<tr>
<th>#</th>
<th>ID Usuario</th>
<th>Correo</th>
<th>IP</th>
<th>Fecha</th>
<th>Éxito</th>
<th>Detalle</th>
</tr>
</thead>
<tbody>
<?php foreach ($registros as $r): ?>
<tr>
<td><?= $r['id'] ?></td>
<td><?= $r['id_usuario'] ?></td>
<td><?= htmlspecialchars($r['email']) ?></td>
<td><?= htmlspecialchars($r['ip']) ?></td>
<td><?= $r['fecha'] instanceof DateTime ? $r['fecha']->format('Y-m-d H:i') : htmlspecialchars($r['fecha']) ?></td>
<td>
<span class="badge <?= $r['exito'] ? 'bg-success' : 'bg-danger' ?>">
<?= $r['exito'] ? 'Sí' : 'No' ?>
</span>
</td>
<td><?= htmlspecialchars($r['detalle']) ?></td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
</div>
</body>
</html>

View File

@@ -1,42 +0,0 @@
<?php include __DIR__ . '/../partials/sidebar_sistemas.php'; ?>
<!DOCTYPE html>
<html lang="es">
<head>
<meta charset="UTF-8">
<title>Panel de Administración - Usuarios</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>
</head>
<body>
<div class="content px-4 pt-5 mt-4">
<h4>🛠️ Bitácora de Cambios de Usuario</h4>
<table class="table table-hover mt-3 align-middle">
<thead class="table-dark">
<tr>
<th>#</th>
<th>ID Usuario</th>
<th>Acción</th>
<th>Descripción</th>
<th>Fecha</th>
</tr>
</thead>
<tbody>
<?php foreach ($registros as $r): ?>
<tr>
<td><?= $r['id_bitacora'] ?></td>
<td><?= $r['usuario_id'] ?></td>
<td><strong><?= htmlspecialchars($r['accion']) ?></strong></td>
<td><?= nl2br(htmlspecialchars($r['descripcion'])) ?></td>
<td><?= $r['fecha'] instanceof DateTime ? $r['fecha']->format('Y-m-d H:i') : htmlspecialchars($r['fecha']) ?></td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
</div>
</body>
</html>

View File

@@ -0,0 +1,113 @@
<?php include __DIR__ . '/../partials/sidebar_administrador.php'; ?>
<!DOCTYPE html>
<html lang="es">
<head>
<meta charset="UTF-8">
<title>Dashboard | Administrador</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>
<style>
body { font-family: 'Segoe UI', sans-serif; background-color: #f4f6f9; }
.sidebar { width: 220px; height: 100vh; background-color: #343a40; position: fixed; top: 0; left: 0; padding-top: 56px; z-index: 1040; }
.sidebar .nav-link { font-weight: normal; color: white; transition: all 0.3s ease; }
.sidebar .nav-link:hover,
.sidebar .nav-link.active { background-color: #495057; color: #fff; }
.content { margin-top: 56px; padding: 40px 20px; position: relative; z-index: 1; background-color: #f4f6f9; transition: margin-left 0.3s ease; }
.navbar { position: fixed; top: 0; width: 100%; z-index: 1050; }
.btn-indigo { background-color: #6610f2; color: white; }
.btn-indigo:hover { background-color: #520dc2; color: white; }
.text-indigo { color: #520dc2; }
.btn-orange { background-color: orangeRed; color: white; }
.btn-orange:hover { background-color: #ff3600; color: white; }
.text-orange { color: orangeRed; }
.btn-teal { background-color: #20c997; color: white; }
.btn-teal:hover { background-color: #4dd4ac; color: black; }
.text-teal { color: #20c997; }
/* A partir de dispositivos medianos (>=768px), deja espacio lateral */
@media (min-width: 768px) { .content { margin-left: 250px; /* Ancho del sidebar */ } }
/* En móviles, sin margen lateral */
@media (max-width: 767.98px) {
.content { margin-left: 0; }
.sidebar .nav-link { font-weight: normal; color: #343a40; background-color: transparent; }
.sidebar .nav-link:hover,
.sidebar .nav-link.active { background-color: #e9ecef; color: #212529; }
}
.card { border-radius: 12px; }
</style>
</head>
<body>
<!-- 📄 CONTENIDO -->
<div class="content">
<h4 class="mb-4">📦 Panel de Administrador</h4>
<div class="row g-4">
<div class="col-md-4">
<div class="card shadow-sm p-3">
<h5 class="text-primary">Aprobar agencias</h5>
<p>Aprueba las agencias que solicitaron un registro.</p>
<a href="/IMPORTADORES/sistemas/aprobarAgencias"
class="btn btn-primary btn-sm mt-2 <?= $_SERVER['REQUEST_URI'] === '/IMPORTADORES/sistemas/aprobarAgencias' ? 'active' : '' ?>">
Ver bitácora</a>
</div>
</div>
<div class="col-md-4">
<div class="card shadow-sm p-3">
<h5 class="text-success">Alta de agencias</h5>
<p>Da de alta manualmente una agencia.</p>
<a href="/IMPORTADORES/sistemas/altaAgencias"
class="btn btn-success btn-sm mt-2 <?= $_SERVER['REQUEST_URI'] === '/IMPORTADORES/sistemas/altaAgencias' ? 'active' : '' ?>">
Nuevo agente
</a>
</div>
</div>
<div class="col-md-4">
<div class="card shadow-sm p-3">
<h5 class="text-info">Aprobar de usuarios</h5>
<p>Aprueba los usuarios que solicitaron un registro.</p>
<a href="/IMPORTADORES/sistemas/aprobarUsuarios"
class="btn btn-info btn-sm mt-2 <?= $_SERVER['REQUEST_URI'] === '/IMPORTADORES/sistemas/aprobarUsuarios' ? 'active' : '' ?>">
Ver usuarios
</a>
</div>
</div>
<div class="col-md-4">
<div class="card shadow-sm p-3">
<h5 class="text-indigo">Alta de usuarios</h5>
<p>Da de alta manualmente a un usuario.</p>
<a href="/IMPORTADORES/sistemas/altaUsuarios"
class="btn btn-indigo btn-sm mt-2 <?= $_SERVER['REQUEST_URI'] === '/IMPORTADORES/sistemas/altaUsuarios' ? 'active' : '' ?>">
Nuevo agente
</a>
</div>
</div>
<div class="col-md-4">
<div class="card shadow-sm p-3">
<h5 class="text-warning">Configuración</h5>
<p>Administra los datos de tu agencia.</p>
<a href="/IMPORTADORES/sistemas/configuracion"
class="btn btn-warning btn-sm mt-2 <?= $_SERVER['REQUEST_URI'] === '/IMPORTADORES/sistemas/configuracion' ? 'active' : '' ?>">
Configurar
</a>
</div>
</div>
<div class="col-md-4">
<div class="card shadow-sm p-3">
<h5 class="text-danger">Cerrar sesión</h5>
<p>Salir del sistema de forma segura.</p>
<a href="/IMPORTADORES/sistemas/logout" class="btn btn-danger btn-sm mt-2">Logout</a>
</div>
</div>
</div>
</div>
</body>
</html>

View File

@@ -1,28 +0,0 @@
<!DOCTYPE html>
<html lang="es">
<head>
<meta charset="UTF-8">
<title>Login Sistemas</title>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css" rel="stylesheet">
</head>
<body class="bg-light">
<div class="container mt-5" style="max-width: 400px;">
<h4 class="mb-4 text-center">Acceso Administrativo</h4>
<form action="/IMPORTADORES/sistemas/login" method="POST">
<div class="mb-3">
<label class="form-label">Correo</label>
<input type="email" name="email" class="form-control" required>
</div>
<div class="mb-3">
<label class="form-label">Contraseña</label>
<input type="password" name="clave" class="form-control" required>
</div>
<div class="d-grid">
<button class="btn btn-primary">Entrar</button>
</div>
</form>
</div>
</body>
</html>

View File

@@ -0,0 +1,117 @@
<?php include __DIR__ . '/../partials/sidebar_administrador.php'; ?>
<!DOCTYPE html>
<html lang="es">
<head>
<meta charset="UTF-8">
<title>Dashboard | Administrador</title>
<meta name="viewport" content="width=device-width, initial-scale=1">
<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>
body { font-family: 'Segoe UI', sans-serif; background-color: #f4f6f9; }
.sidebar { width: 220px; height: 100vh; background-color: #343a40; position: fixed; top: 0; left: 0; padding-top: 56px; z-index: 1040; }
.sidebar .nav-link { font-weight: normal; color: white; transition: all 0.3s ease; }
.sidebar .nav-link:hover,
.sidebar .nav-link.active { background-color: #495057; color: #fff; }
.content { margin-top: 56px; padding: 40px 20px; position: relative; z-index: 1; background-color: #f4f6f9; transition: margin-left 0.3s ease; }
.navbar { position: fixed; top: 0; width: 100%; z-index: 1050; }
/* A partir de dispositivos medianos (>=768px), deja espacio lateral */
@media (min-width: 768px) { .content { margin-left: 250px; /* Ancho del sidebar */ } }
/* En móviles, sin margen lateral */
@media (max-width: 767.98px) {
.content { margin-left: 0; }
.sidebar .nav-link { font-weight: normal; color: #343a40; background-color: transparent; }
.sidebar .nav-link:hover,
.sidebar .nav-link.active { background-color: #e9ecef; color: #212529; }
}
.card { border-radius: 12px; }
table.dataTable thead th { background:#343a40; color:#fff; }
</style>
</head>
<body>
<!-- 📄 CONTENIDO -->
<div class="content">
<h4 class="mb-4">🚷 Usuarios Inactivos</h4>
<div class="card p-3 shadow-sm">
<div class="table-responsive">
<table class="table table-striped" id="tabla-inactivos">
<thead class="table-dark">
<tr>
<th>#</th>
<th>Nombre</th>
<th>Correo</th>
<th>Tipo</th>
<th>Creado por</th>
<th>Fecha Registro</th>
<th>Acciones</th>
</tr>
</thead>
<tbody>
<?php foreach ($usuarios as $u): ?>
<tr>
<td><?= $u['id_usuario'] ?></td>
<td><?= htmlspecialchars(decrypt($u['nombre'])) ?></td>
<td><?= htmlspecialchars(decrypt($u['email'])) ?></td>
<td><?= htmlspecialchars($u['tipo_usuario']) ?></td>
<td><?= htmlspecialchars($u['creado_por']) ?></td>
<td><?= isset($u['creado_en']) && $u['creado_en'] instanceof DateTime ? $u['creado_en']->format('Y-m-d H:i') : '' ?></td>
<td>
<?php if (isset($u['activo']) && $u['activo'] == 0): ?>
<a href="/IMPORTADORES/administrador/activar?id=<?= $u['id_usuario'] ?>&success=1" class="btn btn-sm btn-success">Activar</a>
<a href="/IMPORTADORES/administrador/reset_password?id=<?= $u['id_usuario'] ?>" class="btn btn-sm btn-secondary">Nueva Contraseña</a>
<?php endif; ?>
</td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
</div>
</div>
</div>
<script>
$(document).ready(function () {
$('#tabla-inactivos').DataTable({
order: [],
language: {
url: 'https://cdn.datatables.net/plug-ins/1.13.4/i18n/es-ES.json'
}
});
});
</script>
<?php if (isset($_GET['success'])): ?>
<script>
<?php if ($_GET['success'] === 'status_activated'): ?>
Swal.fire({ icon: 'success', title: 'Usuario activado', text: 'El usuario ha sido activado correctamente.', confirmButtonColor: '#198754' });
<?php elseif ($_GET['success'] === 'update_success'): ?>
Swal.fire({ icon: 'success', title: 'Contraseña temporal restablecida', text: 'La contraseña de accesso temporal fue restablecida correctamente y se envio al correo del usuario.', confirmButtonColor: '#198754' });
<?php endif; ?>
</script>
<?php endif; ?>
<?php if (isset($_GET['error'])): ?>
<script>
<?php if ($_GET['error'] === 'unauthorized'): ?>
Swal.fire({ icon: 'error', title: 'No autorizado', text: 'No tienes permisos para realizar esta acción.', confirmButtonColor: '#dc3545' });
<?php elseif ($_GET['error'] === 'invalid_id'): ?>
Swal.fire({ icon: 'error', title: 'ID inválido', text: 'El identificador del usuario no es válido.', confirmButtonColor: '#dc3545' });
<?php elseif ($_GET['error'] === 'user_not_found'): ?>
Swal.fire({ icon: 'error', title: 'Usuario no encontrado', text: 'No se encontró el usuario especificado.', confirmButtonColor: '#dc3545' });
<?php elseif ($_GET['error'] === 'update_failed'): ?>
Swal.fire({ icon: 'error', title: 'Error al actualizar', text: 'No se pudo actualizar el estado del usuario.', confirmButtonColor: '#dc3545' });
<?php endif; ?>
</script>
<?php endif; ?>
</body>
</html>

View File

@@ -9,6 +9,7 @@
<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">
<script src="https://cdn.jsdelivr.net/npm/sweetalert2@11"></script>
<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>
<style>
@@ -39,7 +40,7 @@
<h3 class="mb-4">Administración de Agentes</h3>
<!-- FORMULARIO -->
<form action="/IMPORTADORES/agencias/alta_agente" method="POST" class="card p-4 mb-5 shadow-sm">
<form action="/IMPORTADORES/agencias/guardarAgente" method="POST" class="card p-4 mb-5 shadow-sm">
<h5 class="mb-3"> Nuevo Agente</h5>
<div class="row g-3">
<div class="col-md-4">
@@ -55,7 +56,6 @@
<select name="tipo_usuario" class="form-select" required>
<option value="">Tipo</option>
<option value="agente_aduanal">Agente Aduanal</option>
<option value="importador">Importador</option>
</select>
</div>
</div>
@@ -65,5 +65,39 @@
</form>
</div>
<?php if (isset($_GET['success'])): ?>
<script>
<?php if ($_GET['success'] === 'created'): ?>
Swal.fire({ icon: 'success', title: 'Usuario creado',
text: 'El usuario se ha creado correctamente y se ha enviado un correo con las credenciales.',
confirmButtonColor: '#198754' });
<?php elseif ($_GET['success'] === 'status_deactivated'): ?>
Swal.fire({ icon: 'success', title: 'Usuario desactivado', text: 'El usuario ha sido desactivado correctamente.', confirmButtonColor: '#198754' });
<?php endif; ?>
</script>
<?php endif; ?>
<?php if (isset($_GET['error'])): ?>
<script>
<?php if ($_GET['error'] === 'unauthorized'): ?>
Swal.fire({ icon: 'error', title: 'No autorizado', text: 'No tienes permisos para realizar esta acción.', confirmButtonColor: '#dc3545' });
<?php elseif ($_GET['error'] === 'invalid_id'): ?>
Swal.fire({ icon: 'error', title: 'ID inválido', text: 'El identificador del usuario no es válido.', confirmButtonColor: '#dc3545' });
<?php elseif ($_GET['error'] === 'user_not_found'): ?>
Swal.fire({ icon: 'error', title: 'Usuario no encontrado', text: 'No se encontró el usuario especificado.', confirmButtonColor: '#dc3545' });
<?php elseif ($_GET['error'] === 'update_failed'): ?>
Swal.fire({ icon: 'error', title: 'Error al actualizar', text: 'No se pudo actualizar el estado del usuario.', confirmButtonColor: '#dc3545' });
<?php elseif ($_GET['error'] === 'invalid_data'): ?>
Swal.fire({ icon: 'error', title: 'Datos inválidos', text: 'Por favor, completa todos los campos requeridos correctamente.', confirmButtonColor: '#dc3545' });
<?php elseif ($_GET['error'] === 'invalid_user_type'): ?>
Swal.fire({ icon: 'error', title: 'Tipo de usuario inválido', text: 'El tipo de usuario seleccionado no es válido.', confirmButtonColor: '#dc3545' });
<?php elseif ($_GET['error'] === 'email_exists'): ?>
Swal.fire({ icon: 'error', title: 'Correo ya registrado', text: 'Este correo electrónico ya está registrado en el sistema.', confirmButtonColor: '#dc3545' });
<?php elseif ($_GET['error'] === 'save_failed'): ?>
Swal.fire({ icon: 'error', title: 'Error al guardar', text: 'No se pudo guardar el usuario. Inténtalo nuevamente.', confirmButtonColor: '#dc3545' });
<?php endif; ?>
</script>
<?php endif; ?>
</body>
</html>

View File

@@ -35,30 +35,50 @@
<!-- 📄 CONTENIDO -->
<div class="content">
<h4 class="mb-4">📦 Panel del Agencia Aduanal</h4>
<h4 class="mb-4">📦 Panel de Agencia Aduanal</h4>
<div class="row g-4">
<div class="col-md-4">
<div class="card shadow-sm p-3">
<h5 class="text-primary">Usuarios activos</h5>
<h5 class="text-primary">Solicitudes de vinculación</h5>
<p>Aprueba las solicitudes de vinculación de los importadores.</p>
<a href="/IMPORTADORES/agencias/solicitudesVinculacion"
class="btn btn-primary btn-sm mt-2 <?= $_SERVER['REQUEST_URI'] === '/IMPORTADORES/agencias/solicitudesVinculacion' ? 'active' : '' ?>">
Ver solicitudes
</a>
</div>
</div>
<div class="col-md-4">
<div class="card shadow-sm p-3">
<h5 class="text-success">Usuarios vinculados</h5>
<p>Consulta los que ya fueron autorizados.</p>
<a href="/IMPORTADORES/agencias/activos" class="btn btn-primary btn-sm mt-2">Ver usuarios</a>
<a href="/IMPORTADORES/agencias/activos"
class="btn btn-success btn-sm mt-2 <?= $_SERVER['REQUEST_URI'] === '/IMPORTADORES/agencias/activos' ? 'active' : '' ?>">
Ver usuarios
</a>
</div>
</div>
<div class="col-md-4">
<div class="card shadow-sm p-3">
<h5 class="text-success">Alta de agentes</h5>
<p>Da de alta a tus agentes aduanales</p>
<a href="/IMPORTADORES/agencias/alta" class="btn btn-success btn-sm mt-2">Nuevo agente</a>
<h5 class="text-info">Alta de agentes</h5>
<p>Da de alta a tus agentes aduanales.</p>
<a href="/IMPORTADORES/agencias/alta"
class="btn btn-info btn-sm mt-2 <?= $_SERVER['REQUEST_URI'] === '/IMPORTADORES/agencias/alta' ? 'active' : '' ?>">
Nuevo agente
</a>
</div>
</div>
<div class="col-md-4">
<div class="card shadow-sm p-3">
<h5 class="text-warning">Bitácora del sistema</h5>
<p>Revisa los accesos y acciones recientes.</p>
<a href="/IMPORTADORES/agencias/bitacora" class="btn btn-warning btn-sm mt-2">Ver bitácora</a>
<h5 class="text-warning">Configuración</h5>
<p>Administra tus datos y preferencias.</p>
<a href="/IMPORTADORES/configuracion"
class="btn btn-warning btn-sm mt-2 <?= $_SERVER['REQUEST_URI'] === '/IMPORTADORES/agencias/configuracion' ? 'active' : '' ?>">
Configurar
</a>
</div>
</div>

View File

@@ -0,0 +1,119 @@
<?php include __DIR__ . '/../partials/sidebar_agencia.php'; ?>
<html>
<head>
<title>Dashboard | Agencia Aduanal</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">
<script src="https://cdn.jsdelivr.net/npm/sweetalert2@11"></script>
<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>
<style>
body { font-family: 'Segoe UI', sans-serif; background-color: #f4f6f9; }
.sidebar { width: 220px; height: 100vh; background-color: #343a40; position: fixed; top: 0; left: 0; padding-top: 56px; z-index: 1040; }
.sidebar .nav-link { font-weight: normal; color: white; transition: all 0.3s ease; }
.sidebar .nav-link:hover,
.sidebar .nav-link.active { background-color: #495057; color: #fff; }
.content { margin-top: 56px; padding: 40px 20px; position: relative; z-index: 1; background-color: #f4f6f9; transition: margin-left 0.3s ease; }
.navbar { position: fixed; top: 0; width: 100%; z-index: 1050; }
/* A partir de dispositivos medianos (>=768px), deja espacio lateral */
@media (min-width: 768px) { .content { margin-left: 250px; /* Ancho del sidebar */ } }
/* En móviles, sin margen lateral */
@media (max-width: 767.98px) {
.content { margin-left: 0; }
.sidebar .nav-link { font-weight: normal; color: #343a40; background-color: transparent; }
.sidebar .nav-link:hover,
.sidebar .nav-link.active { background-color: #e9ecef; color: #212529; }
}
.card { border-radius: 12px; }
.table thead th { background: #343a40; color: #fff; }
</style>
</head>
<body>
<div class="content">
<h4 class="mb-4">🔗 Solicitudes de Vinculación</h4>
<!-- TABLA DE SOLICITUDES DE VINCULACIÓN -->
<div class="card p-3 shadow-sm">
<h5 class="mb-3">👥 Usuarios Solicitantes</h5>
<div class="table-responsive">
<table class="table table-striped table-hover align-middle" id="tabla-vinculaciones-pendientes">
<thead>
<tr>
<th>#</th>
<th>Nombre</th>
<th>RFC</th>
<th>Mensaje</th>
<th>Fecha</th>
<th>Archivo SAT</th>
<th>Acciones</th>
</tr>
</thead>
<tbody>
<?php if (!empty($solicitudes)): ?>
<?php foreach ($solicitudes as $s): ?>
<tr>
<td><?= htmlspecialchars($s['id_solicitud'] ?? '') ?></td>
<td><?= htmlspecialchars(decrypt($s['importador_nombre'] ?? '')) ?></td>
<td><?= htmlspecialchars($s['rfc'] ?? '') ?></td>
<td><?= htmlspecialchars($s['mensaje'] ?? '') ?></td>
<td><?= !empty($s['fecha_solicitud']) ? $s['fecha_solicitud']->format('Y-m-d H:i') : '' ?></td>
<td>
<?php if (!empty($s['opinion_file'])): ?>
<a href="/IMPORTADORES/ver_opinion.php?file=<?= urlencode($s['opinion_file']) ?>" target="_blank" class="btn btn-sm btn-outline-secondary">Ver PDF</a>
<?php else: ?>
<span class="text-muted">No adjunto</span>
<?php endif; ?>
</td>
<td>
<a href="/IMPORTADORES/vinculaciones/aprobarVinculacion?id=<?= $s['id_solicitud'] ?>" class="btn btn-sm btn-success">Aprobar</a>
<a href="/IMPORTADORES/vinculaciones/denegarVinculacion?id=<?= $s['id_solicitud'] ?>" class="btn btn-sm btn-danger">Denegar</a>
</td>
</tr>
<?php endforeach; ?>
<?php endif; ?>
</tbody>
</table>
</div>
</div>
</div>
<script>
$(document).ready(function () {
$('#tabla-vinculaciones-pendientes').DataTable({
order: [],
language: {
url: 'https://cdn.datatables.net/plug-ins/1.13.4/i18n/es-ES.json'
}
});
});
</script>
<?php if (isset($_GET['success'])): ?>
<script>
<?php if ($_GET['success'] === 'approved'): ?>
Swal.fire({ icon: 'success', title: 'Usuario vinculado',
text: 'El usuario se ha vinculado correctamente, ahora puede visualizar tus catálogos.',
confirmButtonColor: '#198754' });
<?php elseif ($_GET['success'] === 'denied'): ?>
Swal.fire({ icon: 'success', title: 'Solicitud denegada', text: 'La solicitud fue denegada correctamente.', confirmButtonColor: '#198754' });
<?php endif; ?>
</script>
<?php endif; ?>
<?php if (isset($_GET['error'])): ?>
<script>
<?php if ($_GET['error'] === 'request_id_invalid'): ?>
Swal.fire({ icon: 'error', title: 'ID inválido', text: 'El identificador de la solicitud no es válido.', confirmButtonColor: '#dc3545' });
<?php elseif ($_GET['error'] === 'denied'): ?>
Swal.fire({ icon: 'error', title: 'Error de vinculación', text: 'Error al aprobar la vinculación.', confirmButtonColor: '#dc3545' });
<?php elseif ($_GET['error'] === 'denied_error'): ?>
Swal.fire({ icon: 'error', title: 'Error al denegar', text: 'No se pudo rechazar la solicitud.', confirmButtonColor: '#dc3545' });
<?php endif; ?>
</script>
<?php endif; ?>
</body>
</html>

View File

@@ -0,0 +1,105 @@
<?php include __DIR__ . '/../partials/sidebar_agencia.php'; ?>
<html>
<head>
<title>Dashboard | Agencia Aduanal</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">
<script src="https://cdn.jsdelivr.net/npm/sweetalert2@11"></script>
<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>
<style>
body { font-family: 'Segoe UI', sans-serif; background-color: #f4f6f9; }
.sidebar { width: 220px; height: 100vh; background-color: #343a40; position: fixed; top: 0; left: 0; padding-top: 56px; z-index: 1040; }
.sidebar .nav-link { font-weight: normal; color: white; transition: all 0.3s ease; }
.sidebar .nav-link:hover,
.sidebar .nav-link.active { background-color: #495057; color: #fff; }
.content { margin-top: 56px; padding: 40px 20px; position: relative; z-index: 1; background-color: #f4f6f9; transition: margin-left 0.3s ease; }
.navbar { position: fixed; top: 0; width: 100%; z-index: 1050; }
/* A partir de dispositivos medianos (>=768px), deja espacio lateral */
@media (min-width: 768px) { .content { margin-left: 250px; /* Ancho del sidebar */ } }
/* En móviles, sin margen lateral */
@media (max-width: 767.98px) {
.content { margin-left: 0; }
.sidebar .nav-link { font-weight: normal; color: #343a40; background-color: transparent; }
.sidebar .nav-link:hover,
.sidebar .nav-link.active { background-color: #e9ecef; color: #212529; }
}
.card { border-radius: 12px; }
.table thead th { background: #343a40; color: #fff; }
</style>
</head>
<body>
<div class="content">
<h4 class="mb-4">👥 Usuarios Vinculados</h4>
<!-- TABLA DE VINCULACIÓN AGENCIA -->
<div class="card p-3 shadow-sm">
<div class="table-responsive">
<table class="table table-striped table-hover align-middle" id="tabla-vinculaciones-agencia">
<thead>
<tr>
<th>#</th>
<th>Nombre</th>
<th>RFC</th>
<th>Teléfono</th>
<th>Fecha de Vinculación</th>
<th>Acciones</th>
</tr>
</thead>
<tbody>
<?php if (!empty($vinculados)): ?>
<?php foreach ($vinculados as $v): ?>
<tr>
<td><?= htmlspecialchars($v['id_relacion'] ?? '') ?></td>
<td><?= htmlspecialchars(decrypt($v['importador_nombre'] ?? '')) ?></td>
<td><?= htmlspecialchars($v['rfc'] ?? '') ?></td>
<td><?= htmlspecialchars($v['telefono'] ?? '') ?></td>
<td><?= !empty($v['fecha_vinculacion']) ? $v['fecha_vinculacion']->format('Y-m-d H:i') : '' ?></td>
<td>
<a href="/IMPORTADORES/vinculaciones/desvincular?id=<?= $v['id_relacion'] ?>" class="btn btn-sm btn-danger">Desvincular</a>
</td>
</tr>
<?php endforeach; ?>
<?php endif; ?>
</tbody>
</table>
</div>
</div>
</div>
<script>
$(document).ready(function () {
$('#tabla-vinculaciones-agencia').DataTable({
order: [],
language: {
url: 'https://cdn.datatables.net/plug-ins/1.13.4/i18n/es-ES.json'
}
});
});
</script>
<?php if (isset($_GET['success'])): ?>
<script>
<?php if ($_GET['success'] === 'unlinked'): ?>
Swal.fire({ icon: 'success', title: 'Usuario desvinculado',
text: 'El usuario se ha desvinculado correctamente.',
confirmButtonColor: '#198754' });
<?php endif; ?>
</script>
<?php endif; ?>
<?php if (isset($_GET['error'])): ?>
<script>
<?php if ($_GET['error'] === 'unlinked_failed'): ?>
Swal.fire({ icon: 'error', title: 'Desvinculación fallida', text: 'No se ha podido desvincular el usuario.', confirmButtonColor: '#dc3545' });
<?php elseif ($_GET['error'] === 'invalid_id'): ?>
Swal.fire({ icon: 'error', title: 'ID inválido', text: 'El identificador de la relación no es válido.', confirmButtonColor: '#dc3545' });
<?php endif; ?>
</script>
<?php endif; ?>
</body>
</html>

View File

@@ -1,86 +0,0 @@
<?php include __DIR__ . '/../partials/sidebar_agente.php'; ?>
<!DOCTYPE html>
<html lang="es">
<head>
<meta charset="UTF-8">
<title>Dashboard | Agente Aduanal</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">
<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>
<style>
body { font-family: 'Segoe UI', sans-serif; background-color: #f4f6f9; }
.sidebar { width: 220px; height: 100vh; background-color: #343a40; position: fixed; top: 0; left: 0; padding-top: 56px; z-index: 1040; }
.sidebar .nav-link { font-weight: normal; color: white; transition: all 0.3s ease; }
.sidebar .nav-link:hover,
.sidebar .nav-link.active { background-color: #495057; color: #fff; }
.content { margin-top: 56px; padding: 40px 20px; position: relative; z-index: 1; background-color: #f4f6f9; transition: margin-left 0.3s ease; }
.navbar { position: fixed; top: 0; width: 100%; z-index: 1050; }
/* A partir de dispositivos medianos (>=768px), deja espacio lateral */
@media (min-width: 768px) { .content { margin-left: 250px; /* Ancho del sidebar */ } }
/* En móviles, sin margen lateral */
@media (max-width: 767.98px) {
.content { margin-left: 0; }
.sidebar .nav-link { font-weight: normal; color: #343a40; background-color: transparent; }
.sidebar .nav-link:hover,
.sidebar .nav-link.active { background-color: #e9ecef; color: #212529; }
}
.card { border-radius: 12px; }
table.dataTable thead th { background: #343a40; color: #fff; }
</style>
</head>
<body>
<div class="content">
<h4 class="mb-4">👥 Acceso de Usuarios</h4>
<div class="card p-3 shadow-sm">
<div class="table-responsive">
<table class="table table-striped" id="tabla-logins">
<thead>
<tr>
<th>ID</th>
<th>Nombre</th>
<th>Correo</th>
<th>IP</th>
<th>Fecha</th>
<th>Estatus</th>
<th>Detalles</th>
</tr>
</thead>
<tbody>
<?php foreach ($bitacoras as $b): ?>
<tr>
<td><?= htmlspecialchars($b['id_usuario']) ?></td>
<td><?= htmlspecialchars($b['nombre']) ?></td>
<td><?= htmlspecialchars($b['email']) ?></td>
<td><?= htmlspecialchars($b['ip']) ?></td>
<td><?= $b['fecha']->format('Y-m-d H:i:s') ?></td>
<td>
<?= $b['exito'] == 1 ? 'Éxito' : 'Fallido' ?>
</td>
<td><?= htmlspecialchars($b['detalle']) ?></td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
</div>
</div>
</div>
<script>
$(document).ready(function () {
$('#tabla-logins').DataTable({
order: [[4, 'desc']], // Índice de columna 4 = Fecha
language: {
url: '//cdn.datatables.net/plug-ins/1.13.4/i18n/es-ES.json'
}
});
});
</script>
</body>
</html>

View File

@@ -45,7 +45,10 @@ include __DIR__ . '/../partials/sidebar_agente.php';
<div class="card shadow-sm p-3">
<h5 class="text-primary">Importadores activos</h5>
<p>Consulta los que ya fueron autorizados.</p>
<a href="/IMPORTADORES/agentes/activos" class="btn btn-primary btn-sm mt-2">Ver importadores</a>
<a href="/IMPORTADORES/agentes/activos"
class="btn btn-primary btn-sm mt-2 <?= $_SERVER['REQUEST_URI'] === '/IMPORTADORES/agentes/activos' ? 'active' : '' ?>">
Ver importadores
</a>
</div>
</div>
@@ -53,15 +56,21 @@ include __DIR__ . '/../partials/sidebar_agente.php';
<div class="card shadow-sm p-3">
<h5 class="text-success">Solicitudes pendientes</h5>
<p>Valida nuevas solicitudes de registro.</p>
<a href="/IMPORTADORES/agentes/solicitudes_pendientes" class="btn btn-success btn-sm mt-2">Ver solicitudes</a>
<a href="/IMPORTADORES/agentes/solicitudes_pendientes"
class="btn btn-success btn-sm mt-2 <?= $_SERVER['REQUEST_URI'] === '/IMPORTADORES/agentes/solicitudes_pendientes' ? 'active' : '' ?>">
Ver solicitudes
</a>
</div>
</div>
<div class="col-md-4">
<div class="card shadow-sm p-3">
<h5 class="text-warning">Bitácora del sistema</h5>
<h5 class="text-info">Bitácora del sistema</h5>
<p>Revisa los accesos y acciones recientes.</p>
<a href="/IMPORTADORES/agentes/bitacora" class="btn btn-warning btn-sm mt-2">Ver bitácora</a>
<a href="/IMPORTADORES/agentes/bitacora"
class="btn btn-info btn-sm mt-2 <?= $_SERVER['REQUEST_URI'] === '/IMPORTADORES/agentes/bitacora' ? 'active' : '' ?>">
Ver bitácora
</a>
</div>
</div>
@@ -69,7 +78,21 @@ include __DIR__ . '/../partials/sidebar_agente.php';
<div class="card shadow-sm p-3">
<h5 class="text-orange">Locaciones</h5>
<p>Gestiona la locaciones validas para nuevos registros.</p>
<a href="/IMPORTADORES/agentes/lista" class="btn btn-orange btn-sm mt-2">Gestionar locaciones</a>
<a href="/IMPORTADORES/agentes/lista"
class="btn btn-orange btn-sm mt-2 <?= $_SERVER['REQUEST_URI'] === '/IMPORTADORES/agentes/lista' ? 'active' : '' ?>">
Gestionar locaciones
</a>
</div>
</div>
<div class="col-md-4">
<div class="card shadow-sm p-3">
<h5 class="text-warning">Configuración</h5>
<p>Administra tus datos y preferencias.</p>
<a href="/IMPORTADORES/configuracion"
class="btn btn-warning btn-sm mt-2 <?= $_SERVER['REQUEST_URI'] === '/IMPORTADORES/agentes/configuracion' ? 'active' : '' ?>">
Configurar
</a>
</div>
</div>

View File

View File

@@ -1,4 +1,30 @@
<?php include __DIR__ . '/../partials/sidebar_agencia.php'; ?>
<?php
// Aseguramos que el usuario esté autenticado
if (!isset($_SESSION['usuario_id'])) {
header('Location: /IMPORTADORES/login');
exit;
}
// Determinar el tipo de usuario
$tipoUsuario = $_SESSION['tipo_usuario'] ?? 'importador';
// Incluir el sidebar correspondiente
switch ($tipoUsuario) {
case 'super_admin':
include __DIR__ . '/../partials/sidebar_administrador.php';
break;
case 'admin_agencia':
include __DIR__ . '/../partials/sidebar_agencia.php';
break;
case 'agente_aduanal':
include __DIR__ . '/../partials/sidebar_agente.php';
break;
case 'importador':
default:
include __DIR__ . '/../partials/sidebar_importador.php';
break;
}
?>
<!DOCTYPE html>
<html lang="es">

View File

@@ -1,12 +1,13 @@
<?php include __DIR__ . '/../partials/sidebar_agencia.php'; ?>
<?php include __DIR__ . '/../partials/sidebar_administrador.php'; ?>
<html>
<!DOCTYPE html>
<html lang="es">
<head>
<title>Dashboard | Agencia Aduanal</title>
<meta charset="UTF-8">
<title>Dashboard | Administrador</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>
<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; }
@@ -30,8 +31,31 @@
</head>
<body>
<div class="content">
<h4> Usuarios Activos</h4>
<div class="content px-4 pt-5 mt-4">
<h4>🛠️ Bitácora de Cambios de Usuario</h4>
<table class="table table-hover mt-3 align-middle">
<thead class="table-dark">
<tr>
<th>#</th>
<th>ID Usuario</th>
<th>Acción</th>
<th>Descripción</th>
<th>Fecha</th>
</tr>
</thead>
<tbody>
<?php foreach ($registros as $r): ?>
<tr>
<td><?= $r['id_bitacora'] ?></td>
<td><?= $r['usuario_id'] ?></td>
<td><strong><?= htmlspecialchars($r['accion']) ?></strong></td>
<td><?= nl2br(htmlspecialchars($r['descripcion'])) ?></td>
<td><?= $r['fecha'] instanceof DateTime ? $r['fecha']->format('Y-m-d H:i') : htmlspecialchars($r['fecha']) ?></td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
</div>
</body>

View File

@@ -34,18 +34,130 @@
<h4 class="mb-4">📖 Bitácoras</h4>
<div class="row g-4">
<?php if ($_SESSION['tipo_usuario'] === 'super_admin'): ?>
<div class="col-md-4">
<div class="card shadow-sm p-3">
<h5 class="text-primary">Registro de Accesos</h5>
<h5 class="text-primary">Registro de accesos</h5>
<p>Ver actividad de los usuarios.</p>
<a href="/IMPORTADORES/bitacoras/lista"
class="btn btn-primary btn-sm mt-2 <?= $_SERVER['REQUEST_URI'] === '/IMPORTADORES/bitacoras/lista' ? 'active' : '' ?>">
Ver Accesos
<a href="/IMPORTADORES/bitacoras/sistema"
class="btn btn-primary btn-sm mt-2 <?= $_SERVER['REQUEST_URI'] === '/IMPORTADORES/bitacoras/sistema' ? 'active' : '' ?>">
Ver actividad
</a>
</div>
</div>
<div class="col-md-4">
<div class="card shadow-sm p-3">
<h5 class="text-primary">Registro de usuarios</h5>
<p>Ver registros de los usuarios.</p>
<a href="/IMPORTADORES/bitacoras/usuarios"
class="btn btn-primary btn-sm mt-2 <?= $_SERVER['REQUEST_URI'] === '/IMPORTADORES/bitacoras/usuarios' ? 'active' : '' ?>">
Ver registro de usuarios
</a>
</div>
</div>
<div class="col-md-4">
<div class="card shadow-sm p-3">
<h5 class="text-primary">Registro de agencias</h5>
<p>Ver registros de las agencias.</p>
<a href="/IMPORTADORES/bitacoras/agencias"
class="btn btn-primary btn-sm mt-2 <?= $_SERVER['REQUEST_URI'] === '/IMPORTADORES/bitacoras/agencias' ? 'active' : '' ?>">
Ver registro de agencias
</a>
</div>
</div>
<div class="col-md-4">
<div class="card shadow-sm p-3">
<h5 class="text-primary">Mi actividad</h5>
<p>Ver mi actividad en la plataforma.</p>
<a href="/IMPORTADORES/bitacoras/miAcceso"
class="btn btn-primary btn-sm mt-2 <?= $_SERVER['REQUEST_URI'] === '/IMPORTADORES/bitacoras/miAcceso' ? 'active' : '' ?>">
Ver mis accesos
</a>
</div>
</div>
<?php endif; ?>
<?php if ($_SESSION['tipo_usuario'] === 'admin_agencia'): ?>
<div class="col-md-4">
<div class="card shadow-sm p-3">
<h5 class="text-primary">Registro de accesos</h5>
<p>Ver actividad de los usuarios.</p>
<a href="/IMPORTADORES/bitacoras/sistemaAgencia"
class="btn btn-primary btn-sm mt-2 <?= $_SERVER['REQUEST_URI'] === '/IMPORTADORES/bitacoras/sistemaAgencia' ? 'active' : '' ?>">
Ver actividad
</a>
</div>
</div>
<div class="col-md-4">
<div class="card shadow-sm p-3">
<h5 class="text-primary">Registro de vinculaciones</h5>
<p>Ver las vinculaciones de mi agencia.</p>
<a href="/IMPORTADORES/bitacoras/vinculaciones"
class="btn btn-primary btn-sm mt-2 <?= $_SERVER['REQUEST_URI'] === '/IMPORTADORES/bitacoras/vinculaciones' ? 'active' : '' ?>">
Ver actividad
</a>
</div>
</div>
<div class="col-md-4">
<div class="card shadow-sm p-3">
<h5 class="text-primary">Mi actividad</h5>
<p>Ver mi actividad en la plataforma.</p>
<a href="/IMPORTADORES/bitacoras/miAcceso"
class="btn btn-primary btn-sm mt-2 <?= $_SERVER['REQUEST_URI'] === '/IMPORTADORES/bitacoras/miAcceso' ? 'active' : '' ?>">
Ver mis accesos
</a>
</div>
</div>
<?php endif; ?>
<?php if ($_SESSION['tipo_usuario'] === 'agente_aduanal'): ?>
<div class="col-md-4">
<div class="card shadow-sm p-3">
<h5 class="text-primary">Registro de accesos</h5>
<p>Ver actividad de los usuarios.</p>
<a href="/IMPORTADORES/bitacoras/sistemaAgencia"
class="btn btn-primary btn-sm mt-2 <?= $_SERVER['REQUEST_URI'] === '/IMPORTADORES/bitacoras/sistemaAgencia' ? 'active' : '' ?>">
Ver actividad
</a>
</div>
</div>
<div class="col-md-4">
<div class="card shadow-sm p-3">
<h5 class="text-primary">Mi actividad</h5>
<p>Ver mi actividad en la plataforma.</p>
<a href="/IMPORTADORES/bitacoras/miAcceso"
class="btn btn-primary btn-sm mt-2 <?= $_SERVER['REQUEST_URI'] === '/IMPORTADORES/bitacoras/miAcceso' ? 'active' : '' ?>">
Ver mis accesos
</a>
</div>
</div>
<?php endif; ?>
<?php if ($_SESSION['tipo_usuario'] === 'importador'): ?>
<div class="col-md-4">
<div class="card shadow-sm p-3">
<h5 class="text-primary">Mis vinculaciones</h5>
<p>Ver mi registro de vinculaciones.</p>
<a href="/IMPORTADORES/bitacoras/vinculacionesUsuario"
class="btn btn-primary btn-sm mt-2 <?= $_SERVER['REQUEST_URI'] === '/IMPORTADORES/bitacoras/vinculacionesUsuario' ? 'active' : '' ?>">
Ver mis vinculaciones
</a>
</div>
</div>
<div class="col-md-4">
<div class="card shadow-sm p-3">
<h5 class="text-primary">Mi actividad</h5>
<p>Ver mi actividad en la plataforma.</p>
<a href="/IMPORTADORES/bitacoras/miAcceso"
class="btn btn-primary btn-sm mt-2 <?= $_SERVER['REQUEST_URI'] === '/IMPORTADORES/bitacoras/miAcceso' ? 'active' : '' ?>">
Ver mis accesos
</a>
</div>
</div>
<?php endif; ?>
</div>
</div>
</body>
</html>

View File

View File

@@ -119,27 +119,27 @@
// VALIDAR CAMPOS OBLIGATORIOS PRIMERO
if (!transportista_id) {
Swal.fire({ icon: 'error', title: 'Transportista requerido', text: 'Por favor selecciona un transportista.', confirmButtonColor: '#dc3545' });
Swal.fire({ icon: 'warning', title: 'Transportista requerido', text: 'Por favor selecciona un transportista.', confirmButtonColor: '#dc3545' });
document.getElementById('transportista_id').focus();
return;
}
if (!nombre) {
Swal.fire({ icon: 'error', title: 'Nombre requerido', text: 'Por favor ingresa el nombre.', confirmButtonColor: '#dc3545' });
Swal.fire({ icon: 'warning', title: 'Nombre requerido', text: 'Por favor ingresa el nombre.', confirmButtonColor: '#dc3545' });
document.getElementById('nombre').focus();
return;
}
if (!apellido) {
Swal.fire({ icon: 'error', title: 'Apellido requerido', text: 'Por favor ingresa el apellido.', confirmButtonColor: '#dc3545' });
Swal.fire({ icon: 'warning', title: 'Apellido requerido', text: 'Por favor ingresa el apellido.', confirmButtonColor: '#dc3545' });
document.getElementById('apellido').focus();
return;
}
if (!numero_licencia) {
Swal.fire({ icon: 'error', title: 'Número de Licencia requerido', text: 'Por favor ingresa el número de licencia.', confirmButtonColor: '#dc3545' });
Swal.fire({ icon: 'warning', title: 'Número de Licencia requerido', text: 'Por favor ingresa el número de licencia.', confirmButtonColor: '#dc3545' });
document.getElementById('numero_licencia').focus();
return;
}
if (!numero_gafete) {
Swal.fire({ icon: 'error', title: 'Número de Gafete requerido', text: 'Por favor ingresa el número de gafete.', confirmButtonColor: '#dc3545' });
Swal.fire({ icon: 'warning', title: 'Número de Gafete requerido', text: 'Por favor ingresa el número de gafete.', confirmButtonColor: '#dc3545' });
document.getElementById('numero_gafete').focus();
return;
}

View File

@@ -158,27 +158,27 @@
// VALIDAR CAMPOS OBLIGATORIOS PRIMERO
if (!transportista_id) {
Swal.fire({ icon: 'error', title: 'Transportista requerido', text: 'Por favor selecciona un transportista.', confirmButtonColor: '#dc3545' });
Swal.fire({ icon: 'warning', title: 'Transportista requerido', text: 'Por favor selecciona un transportista.', confirmButtonColor: '#dc3545' });
document.getElementById('transportista_id').focus();
return;
}
if (!nombre) {
Swal.fire({ icon: 'error', title: 'Nombre requerido', text: 'Por favor ingresa el nombre.', confirmButtonColor: '#dc3545' });
Swal.fire({ icon: 'warning', title: 'Nombre requerido', text: 'Por favor ingresa el nombre.', confirmButtonColor: '#dc3545' });
document.getElementById('nombre').focus();
return;
}
if (!apellido) {
Swal.fire({ icon: 'error', title: 'Apellido requerido', text: 'Por favor ingresa el apellido.', confirmButtonColor: '#dc3545' });
Swal.fire({ icon: 'warning', title: 'Apellido requerido', text: 'Por favor ingresa el apellido.', confirmButtonColor: '#dc3545' });
document.getElementById('apellido').focus();
return;
}
if (!numero_licencia) {
Swal.fire({ icon: 'error', title: 'Número de Licencia requerido', text: 'Por favor ingresa el número de licencia.', confirmButtonColor: '#dc3545' });
Swal.fire({ icon: 'warning', title: 'Número de Licencia requerido', text: 'Por favor ingresa el número de licencia.', confirmButtonColor: '#dc3545' });
document.getElementById('numero_licencia').focus();
return;
}
if (!numero_gafete) {
Swal.fire({ icon: 'error', title: 'Número de Gafete requerido', text: 'Por favor ingresa el número de gafete.', confirmButtonColor: '#dc3545' });
Swal.fire({ icon: 'warning', title: 'Número de Gafete requerido', text: 'Por favor ingresa el número de gafete.', confirmButtonColor: '#dc3545' });
document.getElementById('numero_gafete').focus();
return;
}

View File

@@ -11,17 +11,20 @@
<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 { font-weight: normal; color: white; transition: all 0.3s ease; }
.sidebar .nav-link:hover,
.sidebar .nav-link.active { background-color: #495057; color: #fff; }
.content { margin-top: 56px; padding: 40px 20px; position: relative; z-index: 1; background-color: #f4f6f9; transition: margin-left 0.3s ease; }
.navbar { position: fixed; top: 0; width: 100%; z-index: 1050; }
.btn-indigo { background-color: #6610f2; color: white;}
.btn-indigo:hover { background-color: #520dc2; color: white;}
.text-indigo { color: orangeRed;}
.text-indigo { color: #520dc2;}
.btn-orange { background-color: orangeRed; color: white;}
.btn-orange:hover { background-color: #ff3600; color: white;}
.text-orange { color: orangeRed;}
.btn-teal { background-color: #20c997; color: white; }
.btn-teal:hover { background-color: #4dd4ac; color: black; }
.text-teal { color: #20c997; }
/* A partir de dispositivos medianos (>=768px), deja espacio lateral */
@media (min-width: 768px) { .content { margin-left: 250px; /* Ancho del sidebar */ } }
/* En móviles, sin margen lateral */
@@ -106,6 +109,17 @@
</div>
</div>
<div class="col-md-4">
<div class="card shadow-sm p-3">
<h5 class="text-teal">Vinculaciones</h5>
<p>Visualiza o establece nuevas vinculaciones con agencias aduanales.</p>
<a href="/IMPORTADORES/importadores/vinculaciones"
class="btn btn-teal btn-sm mt-2 <?= str_contains($_SERVER['REQUEST_URI'], '/importadores/vinculaciones') ? 'active' : '' ?>">
Ver vinculaciones
</a>
</div>
</div>
<div class="col-md-4">
<div class="card shadow-sm p-3">
<h5 class="text-danger">Cerrar sesión</h5>

View File

@@ -38,7 +38,7 @@
<div class="content">
<h4 class="mb-4">🌍 Locaciones</h4>
<a href="/IMPORTADORES/agentes/alta" class="btn btn-success mb-3"> Agregar Locación</a>
<a href="/IMPORTADORES/locaciones/alta" class="btn btn-success mb-3"> Agregar Locación</a>
<div class="card p-3 shadow-sm">
<div class="table-responsive">
<table class="table table-striped" id="tabla-locaciones">

View File

@@ -0,0 +1,179 @@
<style>
.nav .nav-link { font-weight: normal; color: white; transition: all 0.3s ease; }
.nav .nav-link:hover,
.nav .nav-link.active { background-color: #495057; color: #fff; }
/* Estilo inverso solo para pantallas pequeñas (sidebar tipo offcanvas) */
@media (max-width: 767.98px) {
.nav .nav-link { font-weight: normal; color: #343a40; background-color: transparent; }
.nav .nav-link:hover,
.nav .nav-link.active { background-color: #e9ecef; color: #212529; }
}
</style>
<!-- 🔷 NAVBAR -->
<nav class="navbar navbar-dark bg-dark fixed-top">
<div class="container-fluid">
<!-- Botón de menú para móviles -->
<button class="btn btn-outline-light d-md-none me-2" type="button" data-bs-toggle="offcanvas" data-bs-target="#sidebarMenu">
</button>
<span class="navbar-brand">SIIH | Administrador</span>
<div class="d-flex ms-auto text-white">
<?php
// Verifica si estás en el dashboard principal del administrador
$esDashboard = str_contains($_SERVER['REQUEST_URI'], '/administrador/dashboard');
if ($esDashboard): ?>
Bienvenido, <?= htmlspecialchars($_SESSION['usuario_nombre']) ?>
<?php else: ?>
<a href="/IMPORTADORES/sistemas/logout" class="btn btn-outline-light btn-sm">Cerrar Sesión</a>
<?php endif; ?>
</div>
</div>
</nav>
<!-- SIDEBAR FIJA (escritorio) -->
<div class="d-none d-md-block bg-dark text-white position-fixed h-100 pt-5" style="width: 250px;">
<nav class="nav flex-column">
<!-- INICIO -->
<a href="/IMPORTADORES/administrador/dashboard"
class="nav-link px-3 py-2 <?= str_contains($_SERVER['REQUEST_URI'], '/IMPORTADORES/administrador/dashboard') ? 'active' : '' ?>">
🏠 Inicio
</a>
<!-- AGENCIAS -->
<a class="nav-link px-3 py-2 d-flex justify-content-between align-items-center
<?= str_contains($_SERVER['REQUEST_URI'], '/IMPORTADORES/administrador') ? 'active' : '' ?>"
data-bs-toggle="collapse" href="#submenuAgencias" role="button" aria-expanded="false">
🏪 Agencias
<span class="badge bg-secondary">3</span>
</a>
<div class="collapse <?= str_contains($_SERVER['REQUEST_URI'], '/administrador') ? 'show' : '' ?>" id="submenuAgencias">
<nav class="nav flex-column ms-3">
<a href="/IMPORTADORES/administrador/aprobarAgencias"
class="nav-link px-3 py-1 <?= str_contains($_SERVER['REQUEST_URI'], '/IMPORTADORES/administrador/aprobarAgencias') ? 'active' : '' ?>">
• Aprobar agencias
</a>
<a href="/IMPORTADORES/administrador/altaAgencias"
class="nav-link px-3 py-1 <?= str_contains($_SERVER['REQUEST_URI'], '/IMPORTADORES/administrador/altaAgencias') ? 'active' : '' ?>">
• Alta de agencia
</a>
<a href="/IMPORTADORES/administrador/agenciasActivas"
class="nav-link px-3 py-1 <?= str_contains($_SERVER['REQUEST_URI'], '/IMPORTADORES/administrador/agenciasActivas') ? 'active' : '' ?>">
• Ver agencias
</a>
</nav>
</div>
<!-- USUARIOS -->
<a class="nav-link px-3 py-2 d-flex justify-content-between align-items-center
<?= str_contains($_SERVER['REQUEST_URI'], '/IMPORTADORES/administrador') ? 'active' : '' ?>"
data-bs-toggle="collapse" href="#submenuUsuarios" role="button" aria-expanded="false">
👥 Usuarios
<span class="badge bg-secondary">3</span>
</a>
<div class="collapse <?= str_contains($_SERVER['REQUEST_URI'], '/administrador') ? 'show' : '' ?>" id="submenuUsuarios">
<nav class="nav flex-column ms-3">
<a href="/IMPORTADORES/administrador/aprobarUsuarios"
class="nav-link px-3 py-1 <?= str_contains($_SERVER['REQUEST_URI'], '/IMPORTADORES/administrador/aprobarUsuarios') ? 'active' : '' ?>">
• Aprobar usuarios
</a>
<a href="/IMPORTADORES/administrador/altaUsuarios"
class="nav-link px-3 py-1 <?= str_contains($_SERVER['REQUEST_URI'], '/IMPORTADORES/administrador/altaUsuarios') ? 'active' : '' ?>">
• Ver usuarios
</a>
<a href="/IMPORTADORES/administrador/usuariosInactivos"
class="nav-link px-3 py-1 <?= str_contains($_SERVER['REQUEST_URI'], '/IMPORTADORES/administrador/usuariosInactivos') ? 'active' : '' ?>">
• Usuarios inactivos
</a>
</nav>
</div>
<!-- CONFIGURACIÓN -->
<a href="/IMPORTADORES/administrador/configuracion"
class="nav-link px-3 py-2 <?= str_contains($_SERVER['REQUEST_URI'], '/IMPORTADORES/administrador/configuracion') ? 'active' : '' ?>">
⚙️ Configuración
</a>
</nav>
</div>
<!-- OFFCANVAS PARA MÓVILES -->
<div class="offcanvas offcanvas-start d-md-none" tabindex="-1" id="sidebarMenu" style="top: 75px;">
<div class="offcanvas-header">
<h5 class="offcanvas-title">Menú</h5>
<button type="button" class="btn-close" data-bs-dismiss="offcanvas"></button>
</div>
<div class="offcanvas-body">
<!-- INICIO -->
<a href="/IMPORTADORES/administrador/dashboard"
class="nav-link px-3 py-2 <?= str_contains($_SERVER['REQUEST_URI'], '/IMPORTADORES/administrador/dashboard') ? 'active' : '' ?>">
🏠 Inicio
</a>
<!-- AGENCIAS -->
<a class="nav-link px-3 py-2 d-flex justify-content-between align-items-center
<?= str_contains($_SERVER['REQUEST_URI'], '/IMPORTADORES/administrador') ? 'active' : '' ?>"
data-bs-toggle="collapse" href="#submenuAgencias" role="button"
aria-expanded="<?= str_contains($_SERVER['REQUEST_URI'], '/IMPORTADORES/administrador') ? 'true' : 'false' ?>"
aria-controls="submenuAgencias">
🏪 Agencias
<span class="badge bg-secondary">3</span>
</a>
<div class="collapse <?= str_contains($_SERVER['REQUEST_URI'], '/administrador') ? 'show' : '' ?>" id="submenuAgencias">
<nav class="nav flex-column ms-3">
<a href="/IMPORTADORES/administrador/aprobarAgencias"
class="nav-link px-3 py-1 <?= str_contains($_SERVER['REQUEST_URI'], '/IMPORTADORES/administrador/aprobarAgencias') ? 'active' : '' ?>">
• Aprobar agencias
</a>
<a href="/IMPORTADORES/administrador/altaAgencias"
class="nav-link px-3 py-1 <?= str_contains($_SERVER['REQUEST_URI'], '/IMPORTADORES/administrador/altaAgencias') ? 'active' : '' ?>">
• Alta de agencia
</a>
<a href="/IMPORTADORES/administrador/agenciasActivas"
class="nav-link px-3 py-1 <?= str_contains($_SERVER['REQUEST_URI'], '/IMPORTADORES/administrador/agenciasActivas') ? 'active' : '' ?>">
• Ver agencias
</a>
</nav>
</div>
<!-- USUARIOS -->
<a class="nav-link px-3 py-2 d-flex justify-content-between align-items-center
<?= str_contains($_SERVER['REQUEST_URI'], '/IMPORTADORES/administrador') ? 'active' : '' ?>"
data-bs-toggle="collapse" href="#submenuUsuarios" role="button"
aria-expanded="<?= str_contains($_SERVER['REQUEST_URI'], '/IMPORTADORES/administrador') ? 'true' : 'false' ?>"
aria-controls="submenuUsuarios">
👥 Usuarios
<span class="badge bg-secondary">4</span>
</a>
<div class="collapse <?= str_contains($_SERVER['REQUEST_URI'], '/administrador') ? 'show' : '' ?>" id="submenuUsuarios">
<nav class="nav flex-column ms-3">
<a href="/IMPORTADORES/administrador/aprobarUsuarios"
class="nav-link px-3 py-1 <?= str_contains($_SERVER['REQUEST_URI'], '/IMPORTADORES/administrador/aprobarUsuarios') ? 'active' : '' ?>">
• Aprobar usuarios
</a>
<a href="/IMPORTADORES/administrador/altaUsuarios"
class="nav-link px-3 py-1 <?= str_contains($_SERVER['REQUEST_URI'], '/IMPORTADORES/administrador/altaUsuarios') ? 'active' : '' ?>">
• Alta de usuario
</a>
<a href="/IMPORTADORES/administrador/usuariosActivos"
class="nav-link px-3 py-1 <?= str_contains($_SERVER['REQUEST_URI'], '/IMPORTADORES/administrador/usuariosActivos') ? 'active' : '' ?>">
• Ver usuarios
</a>
<a href="/IMPORTADORES/administrador/usuariosInactivos"
class="nav-link px-3 py-1 <?= str_contains($_SERVER['REQUEST_URI'], '/IMPORTADORES/administrador/usuariosInactivos') ? 'active' : '' ?>">
• Usuarios inactivos
</a>
</nav>
</div>
<!-- CONFIGURACIÓN -->
<a href="/IMPORTADORES/administrador/configuracion"
class="nav-link px-3 py-2 <?= str_contains($_SERVER['REQUEST_URI'], '/IMPORTADORES/administrador/configuracion') ? 'active' : '' ?>">
⚙️ Configuración
</a>
</div>
</div>

View File

@@ -38,27 +38,34 @@
<!-- INICIO -->
<a href="/IMPORTADORES/agencias/dashboard"
class="nav-link px-3 py-2 <?= $_SERVER['REQUEST_URI'] === '/IMPORTADORES/agencias/dashboard' ? 'active' : '' ?>">
class="nav-link px-3 py-2 <?= str_contains($_SERVER['REQUEST_URI'], '/IMPORTADORES/agencias/dashboard') ? 'active' : '' ?>">
🏠 Inicio
</a>
<!-- SOLICITUDES VINCULACIÓN -->
<a href="/IMPORTADORES/vinculaciones/solicitudesVinculacion"
class="nav-link px-3 py-2 <?= str_contains($_SERVER['REQUEST_URI'], '/IMPORTADORES/vinculaciones/solicitudesVinculacion') ? 'active' : '' ?>">
🔗 Vinculación
</a>
<!-- USUARIOS ACTIVOS -->
<a href="/IMPORTADORES/agencias/activos"
class="nav-link px-3 py-2 <?= $_SERVER['REQUEST_URI'] === '/IMPORTADORES/agencias/activos' ? 'active' : '' ?>">
Usuarios Activos
<a href="/IMPORTADORES/vinculaciones/usuariosVinculados"
class="nav-link px-3 py-2 <?= str_contains($_SERVER['REQUEST_URI'], '/IMPORTADORES/vinculaciones/usuariosVinculados') ? 'active' : '' ?>">
👥 Usuarios Vinculados
</a>
<!-- ALTA DE AGENTES -->
<a href="/IMPORTADORES/agencias/alta"
class="nav-link px-3 py-2 <?= $_SERVER['REQUEST_URI'] === '/IMPORTADORES/agencias/alta' ? 'active' : '' ?>">
class="nav-link px-3 py-2 <?= str_contains($_SERVER['REQUEST_URI'], '/IMPORTADORES/agencias/alta') ? 'active' : '' ?>">
📥 Alta de Agentes
</a>
<!-- BITÁCORA -->
<a href="/IMPORTADORES/agencias/bitacora"
class="nav-link px-3 py-2 <?= $_SERVER['REQUEST_URI'] === '/IMPORTADORES/agencias/bitacora' ? 'active' : '' ?>">
🕓 Bitácora
<!-- CONFIGURACIÓN -->
<a href="/IMPORTADORES/configuracion"
class="nav-link px-3 py-2 <?= str_contains($_SERVER['REQUEST_URI'], '/configuracion') ? 'active' : '' ?>">
⚙️ Configuración
</a>
</nav>
</div>
@@ -72,26 +79,32 @@
<!-- INICIO -->
<a href="/IMPORTADORES/agencias/dashboard"
class="nav-link px-3 py-2 <?= $_SERVER['REQUEST_URI'] === '/IMPORTADORES/agencias/dashboard' ? 'active' : '' ?>">
class="nav-link px-3 py-2 <?= str_contains($_SERVER['REQUEST_URI'], '/IMPORTADORES/agencias/dashboard') ? 'active' : '' ?>">
🏠 Inicio
</a>
<!-- SOLICITUDES VINCULACIÓN -->
<a href="/IMPORTADORES/agencias/solicitudesVinculacion"
class="nav-link px-3 py-2 <?= str_contains($_SERVER['REQUEST_URI'], '/IMPORTADORES/agencias/solicitudesVinculacion') ? 'active' : '' ?>">
🔗 Vinculación
</a>
<!-- USUARIOS ACTIVOS -->
<a href="/IMPORTADORES/agencias/activos"
class="nav-link px-3 py-2 <?= $_SERVER['REQUEST_URI'] === '/IMPORTADORES/agencias/activos' ? 'active' : '' ?>">
✅ Usuarios Activos
<a href="/IMPORTADORES/vinculaciones/usuariosVinculados"
class="nav-link px-3 py-2 <?= str_contains($_SERVER['REQUEST_URI'], '/IMPORTADORES/vinculaciones/usuariosVinculados') ? 'active' : '' ?>">
✅ Usuarios Vinculados
</a>
<!-- ALTA DE AGENTES -->
<a href="/IMPORTADORES/agencias/alta"
class="nav-link px-3 py-2 <?= $_SERVER['REQUEST_URI'] === '/IMPORTADORES/agencias/alta' ? 'active' : '' ?>">
class="nav-link px-3 py-2 <?= str_contains($_SERVER['REQUEST_URI'], '/IMPORTADORES/agencias/alta') ? 'active' : '' ?>">
📥 Alta de Agentes
</a>
<!-- BITÁCORA -->
<a href="/IMPORTADORES/agencias/bitacora"
class="nav-link px-3 py-2 <?= $_SERVER['REQUEST_URI'] === '/IMPORTADORES/agencias/bitacora' ? 'active' : '' ?>">
🕓 Bitácora
<!-- CONFIGURACIÓN -->
<a href="/IMPORTADORES/configuracion"
class="nav-link px-3 py-2 <?= str_contains($_SERVER['REQUEST_URI'], '/configuracion') ? 'active' : '' ?>">
⚙️ Configuración
</a>
</div>

View File

@@ -1,10 +1,3 @@
<?php
if (!isset($_SESSION['usuario_id']) || $_SESSION['tipo_usuario'] !== 'agente_aduanal') {
header('Location: /IMPORTADORES/login');
exit;
}
?>
<style>
.nav .nav-link { font-weight: normal; color: white; transition: all 0.3s ease; }
.nav .nav-link:hover,
@@ -58,55 +51,12 @@ if (!isset($_SESSION['usuario_id']) || $_SESSION['tipo_usuario'] !== 'agente_adu
🏠 Inicio
</a>
<!-- IMPORTADORES ACTIVOS -->
<!-- IMPORTADORES VINCULADOS -->
<a href="/IMPORTADORES/agentes/activos"
class="nav-link px-3 py-2 <?= $_SERVER['REQUEST_URI'] === '/IMPORTADORES/agentes/activos' ? 'active' : '' ?>">
✅ Importadores Activos
🔗 Vinculados
</a>
<!-- SOLICITUDES PENDIENTES -->
<a href="/IMPORTADORES/agentes/solicitudes_pendientes"
class="nav-link px-3 py-2 <?= $_SERVER['REQUEST_URI'] === '/IMPORTADORES/agentes/solicitudes_pendientes' ? 'active' : '' ?>">
📥 Solicitudes de Registro
</a>
<!-- BITÁCORA -->
<a href="/IMPORTADORES/agentes/bitacora"
class="nav-link px-3 py-2 <?= $_SERVER['REQUEST_URI'] === '/IMPORTADORES/agentes/bitacora' ? 'active' : '' ?>">
🕓 Bitácora
</a>
<!-- LOCACIONES -->
<?php if ($esVistaLocaciones): ?>
<?php $enDashboardLocaciones = $_SERVER['REQUEST_URI'] === '/IMPORTADORES/locaciones/lista'; ?>
<a class="nav-link px-3 py-2 d-flex justify-content-between align-items-center <?= $esVistaLocaciones ? 'active' : '' ?>"
href="<?= $enDashboardLocaciones ? '#submenuLocaciones' : '/IMPORTADORES/locaciones/lista' ?>"
<?= $enDashboardLocaciones ? 'data-bs-toggle="collapse"' : '' ?>
role="button" aria-expanded="true" aria-controls="submenuLocaciones"
onclick="<?= $enDashboardLocaciones ? '' : 'event.preventDefault(); window.location.href = \'/IMPORTADORES/locaciones/lista\';' ?>">
🗺️ Locaciones
<span class="badge bg-secondary">2</span>
</a>
<div class="collapse show" id="submenuLocaciones">
<nav class="nav flex-column ms-3">
<a href="/IMPORTADORES/locaciones/lista"
class="nav-link px-3 py-1 <?= $_SERVER['REQUEST_URI'] === '/IMPORTADORES/locaciones/lista' ? 'active' : '' ?>">
• Ver Locaciones
</a>
<a href="/IMPORTADORES/locaciones/alta"
class="nav-link px-3 py-1 <?= str_contains($_SERVER['REQUEST_URI'], '/locaciones/alta') ? 'active' : '' ?>">
• Agregar Locaciones
</a>
</nav>
</div>
<?php elseif ($esVistaAgentes || $esVistaPatente): ?>
<a href="/IMPORTADORES/locaciones/lista"
class="nav-link px-3 py-2 d-flex justify-content-between align-items-center">
🗺️ Locaciones
<span class="badge bg-secondary">2</span>
</a>
<?php endif; ?>
<!-- Patentes -->
<?php if ($esVistaPatente): ?>
<?php $enDashboardPatente = $_SERVER['REQUEST_URI'] === '/IMPORTADORES/patente/dashboard'; ?>
@@ -138,6 +88,43 @@ if (!isset($_SESSION['usuario_id']) || $_SESSION['tipo_usuario'] !== 'agente_adu
</a>
<?php endif; ?>
<!-- LOCACIONES -->
<?php if ($esVistaLocaciones): ?>
<?php $enDashboardLocaciones = $_SERVER['REQUEST_URI'] === '/IMPORTADORES/locaciones/lista'; ?>
<a class="nav-link px-3 py-2 d-flex justify-content-between align-items-center <?= $esVistaLocaciones ? 'active' : '' ?>"
href="<?= $enDashboardLocaciones ? '#submenuLocaciones' : '/IMPORTADORES/locaciones/lista' ?>"
<?= $enDashboardLocaciones ? 'data-bs-toggle="collapse"' : '' ?>
role="button" aria-expanded="true" aria-controls="submenuLocaciones"
onclick="<?= $enDashboardLocaciones ? '' : 'event.preventDefault(); window.location.href = \'/IMPORTADORES/locaciones/lista\';' ?>">
🗺️ Locaciones
<span class="badge bg-secondary">2</span>
</a>
<div class="collapse show" id="submenuLocaciones">
<nav class="nav flex-column ms-3">
<a href="/IMPORTADORES/locaciones/lista"
class="nav-link px-3 py-1 <?= $_SERVER['REQUEST_URI'] === '/IMPORTADORES/locaciones/lista' ? 'active' : '' ?>">
• Ver Locaciones
</a>
<a href="/IMPORTADORES/locaciones/alta"
class="nav-link px-3 py-1 <?= str_contains($_SERVER['REQUEST_URI'], '/locaciones/alta') ? 'active' : '' ?>">
• Agregar Locaciones
</a>
</nav>
</div>
<?php elseif ($esVistaAgentes || $esVistaPatente): ?>
<a href="/IMPORTADORES/locaciones/lista"
class="nav-link px-3 py-2 d-flex justify-content-between align-items-center">
🗺️ Locaciones
<span class="badge bg-secondary">2</span>
</a>
<?php endif; ?>
<!-- CONFIGURACIÓN -->
<a href="/IMPORTADORES/configuracion"
class="nav-link px-3 py-2 <?= str_contains($_SERVER['REQUEST_URI'], '/configuracion') ? 'active' : '' ?>">
⚙️ Configuración
</a>
</nav>
</div>
@@ -161,12 +148,6 @@ if (!isset($_SESSION['usuario_id']) || $_SESSION['tipo_usuario'] !== 'agente_adu
✅ Importadores Activos
</a>
<!-- SOLICITUDES PENDIENTES -->
<a href="/IMPORTADORES/agentes/solicitudes_pendientes"
class="nav-link px-3 py-2 <?= $_SERVER['REQUEST_URI'] === '/IMPORTADORES/agentes/solicitudes_pendientes' ? 'active' : '' ?>">
📥 Solicitudes de Registro
</a>
<!-- BITÁCORA -->
<a href="/IMPORTADORES/agentes/bitacora"
class="nav-link px-3 py-2 <?= $_SERVER['REQUEST_URI'] === '/IMPORTADORES/agentes/bitacora' ? 'active' : '' ?>">
@@ -235,5 +216,11 @@ if (!isset($_SESSION['usuario_id']) || $_SESSION['tipo_usuario'] !== 'agente_adu
</a>
<?php endif; ?>
<!-- CONFIGURACIÓN -->
<a href="/IMPORTADORES/configuracion"
class="nav-link px-3 py-2 <?= str_contains($_SERVER['REQUEST_URI'], '/configuracion') ? 'active' : '' ?>">
⚙️ Configuración
</a>
</div>
</div>

View File

@@ -1,9 +1,130 @@
<!-- views/partials/sidebar_configuracion.php -->
<?php
if (!isset($_SESSION['usuario_id']) || $_SESSION['tipo_usuario'] !== 'importador') {
// Verificar que el usuario esté autenticado
if (!isset($_SESSION['usuario_id'])) {
header('Location: /IMPORTADORES/login');
exit;
}
// Obtener información del usuario
$tipoUsuario = $_SESSION['tipo_usuario'] ?? '';
$agenciaId = $_SESSION['agencia_id'] ?? null;
$usuarioId = $_SESSION['usuario_id'] ?? null;
// Definir permisos por tipo de usuario
$permisos = [
'super_admin' => [
'informacion_general' => true,
'automatizaciones' => true,
'preferencias' => true,
'seguridad' => true,
'bitacoras' => [
'registro_accesos' => true,
'registro_usuarios' => true,
'registro_agencias' => true,
'mi_actividad' => true
],
],
'admin_agencia' => [
'informacion_general' => true,
'automatizaciones' => true,
'preferencias' => true,
'seguridad' => true,
'bitacoras' => [
'acceso_usuarios_agencia' => true,
'registro_vinculaciones' => true,
'mi_actividad' => true
],
],
'agente_aduanal' => [
'informacion_general' => true,
'automatizaciones' => true,
'preferencias' => true,
'seguridad' => true,
'bitacoras' => [
'acceso_usuarios_agencia' => true,
'mi_actividad' => true
],
],
'importador' => [
'informacion_general' => true,
'automatizaciones' => true,
'preferencias' => true,
'seguridad' => true,
'bitacoras' => [
'vinculaciones_usuario' => true,
'mi_actividad' => true
],
],
];
// Obtener permisos del usuario actual
$permisosUsuario = $permisos[$tipoUsuario] ?? $permisos['importador'];
$permisosBitacoras = $permisosUsuario['bitacoras'] ?? [];
// Función para verificar si el usuario tiene acceso a bitácoras
function tieneAccesoBitacoras($permisosBitacoras) {
if (is_bool($permisosBitacoras)) {
return $permisosBitacoras;
}
return !empty($permisosBitacoras) && array_filter($permisosBitacoras);
}
// Contar bitácoras disponibles según permisos
function contarBitacorasDisponibles($permisosBitacoras) {
if (!is_array($permisosBitacoras)) {
return is_bool($permisosBitacoras) && $permisosBitacoras ? 1 : 0;
}
return count(array_filter($permisosBitacoras));
}
$cantidadBitacoras = contarBitacorasDisponibles($permisosBitacoras);
// Función para obtener el texto descriptivo según el permiso
function obtenerTextoPermiso($permiso, $tipoUsuario) {
$textos = [
'registro_accesos' => ($tipoUsuario === 'super_admin') ? 'Registro de accesos' : null,
'registro_usuarios' => ($tipoUsuario === 'super_admin') ? 'Registro de usuarios' : null,
'registro_agencias' => ($tipoUsuario === 'super_admin') ? 'Registro de agencias' : null,
'acceso_usuarios_agencias' => ($tipoUsuario === 'admin_agencia' || $tipoUsuario === 'agente_aduanal') ? 'Registros de accesos' : null,
'registro_vinculaciones' => ($tipoUsuario === 'admin_agencia' || $tipoUsuario === 'agente_aduanal') ? 'Vinculaciones (Agencia)' : null,
'mi_actividad' => in_array($tipoUsuario, ['super_admin', 'admin_agencia', 'agente_aduanal', 'importador']) ? 'Mi actividad' : null,
'vinculaciones_usuario' => ($tipoUsuario === 'importador') ? 'Mis vinculaciones' : null,
];
return $textos[$permiso] ?? ucfirst(str_replace('_', ' ', $permiso));
}
// Función para obtener el icono según el permiso
function obtenerIconoPermiso($permiso) {
$iconos = [
'registro_accesos' => '🌐',
'registro_usuarios' => '👥',
'registro_agencias' => '🏪',
'acceso_usuarios_agencia' => '👥',
'registro_vinculaciones' => '🔗',
'mi_actividad' => '👤',
'vinculaciones_usuario' => '🔗'
];
return $iconos[$permiso] ?? '📋';
}
// Función para obtener la URL según el permiso
function obtenerUrlPermiso($permiso) {
$urls = [
'registro_accesos' => '/IMPORTADORES/bitacoras/sistema',
'registro_usuarios' => '/IMPORTADORES/bitacoras/usuarios',
'registro_agencias' => '/IMPORTADORES/bitacoras/agencias',
'acceso_usuarios_agencia' => '/IMPORTADORES/bitacoras/sistemaAgencia',
'registro_vinculaciones' => '/IMPORTADORES/bitacoras/vinculaciones',
'mis_vinculaciones' => '/IMPORTADORES/bitacoras/misVinculaciones',
'mi_actividad' => '/IMPORTADORES/bitacoras/miAcceso',
'vinculaciones_usuario' => '/IMPORTADORES/bitacoras/vinculacionesUsuario'
];
return $urls[$permiso] ?? '/IMPORTADORES/bitacoras/index';
}
?>
<style>
@@ -15,7 +136,14 @@ if (!isset($_SESSION['usuario_id']) || $_SESSION['tipo_usuario'] !== 'importador
.nav .nav-link { font-weight: normal; color: #343a40; background-color: transparent; }
.nav .nav-link:hover,
.nav .nav-link.active { background-color: #e9ecef; color: #212529; }
.permission-scope { background-color: rgba(108, 117, 125, 0.2); }
.submenu-item:hover { background-color: rgba(233, 236, 239, 0.8) !important; }
}
.user-badge { font-size: 0.75rem; padding: 2px 6px; border-radius: 3px; }
.permission-indicator { font-size: 0.7rem; color: #28a745; margin-left: 5px; }
.permission-scope { font-size: 0.65rem; color: #6c757d; margin-left: 5px; padding: 1px 4px; background-color: rgba(108, 117, 125, 0.1); border-radius: 2px; }
.submenu-item { font-size: 0.9rem; padding-left: 1rem !important; }
.submenu-item:hover { background-color: rgba(73, 80, 87, 0.7) !important; }
</style>
<!-- NAVBAR -->
@@ -25,9 +153,22 @@ if (!isset($_SESSION['usuario_id']) || $_SESSION['tipo_usuario'] !== 'importador
<button class="btn btn-outline-light d-md-none me-2" type="button" data-bs-toggle="offcanvas" data-bs-target="#sidebarMenu">
</button>
<span class="navbar-brand">SIIH | Configuración</span>
<span class="navbar-brand">
SIIH | Configuración
<span class="user-badge bg-info text-dark ms-2"><?= strtoupper(str_replace('_', ' ', $tipoUsuario)) ?></span>
</span>
<div class="d-flex ms-auto">
<a href="/IMPORTADORES/importadores/dashboard" class="btn btn-outline-light btn-sm" style="margin: 0 0 0 25px;">
<?php
// Definir la URL de regreso según el tipo de usuario
$urlRegreso = match($tipoUsuario) {
'super_admin' => '/IMPORTADORES/administrador/dashboard',
'admin_agencia' => '/IMPORTADORES/agencias/dashboard',
'agente_aduanal' => '/IMPORTADORES/agentes/dashboard',
'importador' => '/IMPORTADORES/importadores/dashboard',
default => '/IMPORTADORES/importadores/dashboard'
};
?>
<a href="<?= $urlRegreso ?>" class="btn btn-outline-light btn-sm" style="margin: 0 0 0 25px;">
← Panel Principal
</a>
</div>
@@ -42,7 +183,7 @@ if (!isset($_SESSION['usuario_id']) || $_SESSION['tipo_usuario'] !== 'importador
<?php
// Detecta si estamos en alguna parte de configuración
$esConfiguracion = str_contains($_SERVER['REQUEST_URI'], '/configuracion');
// Detecta si estamos en alguna ruta de bitácoras
// Detecta si estamos en alguna parte de bitácoras
$esVistaBitacoras = str_contains($_SERVER['REQUEST_URI'], '/bitacoras');
// Detecta si estamos en alguna parta de seguridad
$esVistaSeguridad = str_contains($_SERVER['REQUEST_URI'], '/seguridad');
@@ -53,12 +194,15 @@ if (!isset($_SESSION['usuario_id']) || $_SESSION['tipo_usuario'] !== 'importador
?>
<!-- INFORMACIÓN GENERAL -->
<?php if ($permisosUsuario['informacion_general']): ?>
<a href="/IMPORTADORES/configuracion/index"
class="nav-link px-3 py-2 <?= str_contains($_SERVER['REQUEST_URI'], '/configuracion/dashboard_configuracion') ? 'active' : '' ?>">
Información General
</a>
<?php endif; ?>
<!-- AUTOMATIZACIONES -->
<?php if ($permisosUsuario['automatizaciones']): ?>
<?php if ($esVistaAutomatizaciones): ?>
<?php $enDashboardAutomatizaciones = $_SERVER['REQUEST_URI'] === '/IMPORTADORES/automatizaciones/index'; ?>
<a href="/IMPORTADORES/automatizaciones/index"
@@ -71,8 +215,10 @@ if (!isset($_SESSION['usuario_id']) || $_SESSION['tipo_usuario'] !== 'importador
⚙️ Automatizaciones
</a>
<?php endif; ?>
<?php endif; ?>
<!-- PREFERENCIAS -->
<?php if ($permisosUsuario['preferencias']): ?>
<?php if ($esVistaPreferencias): ?>
<?php $enDashboardPreferencias = $_SERVER['REQUEST_URI'] === '/IMPORTADORES/preferencias/index'; ?>
<a class="nav-link px-3 py-2 d-flex justify-content-between align-items-center <?= $esVistaPreferencias ? 'active' : '' ?>"
@@ -98,8 +244,10 @@ if (!isset($_SESSION['usuario_id']) || $_SESSION['tipo_usuario'] !== 'importador
<span class="badge bg-secondary">1</span>
</a>
<?php endif; ?>
<?php endif; ?>
<!-- SEGURIDAD -->
<?php if ($permisosUsuario['seguridad']): ?>
<?php if ($esVistaSeguridad): ?>
<?php $enDashboardSeguridad = $_SERVER['REQUEST_URI'] === '/IMPORTADORES/seguridad/index'; ?>
<a class="nav-link px-3 py-2 d-flex justify-content-between align-items-center <?= $esVistaSeguridad ? 'active' : '' ?>"
@@ -125,34 +273,57 @@ if (!isset($_SESSION['usuario_id']) || $_SESSION['tipo_usuario'] !== 'importador
<span class="badge bg-secondary">1</span>
</a>
<?php endif; ?>
<?php endif; ?>
<!-- BITÁCORAS -->
<?php if ($esVistaBitacoras): ?>
<!-- BITÁCORAS CON PERMISOS ESPECÍFICOS -->
<?php if (tieneAccesoBitacoras($permisosBitacoras)): ?>
<?php $enDashboardBitacoras = $_SERVER['REQUEST_URI'] === '/IMPORTADORES/bitacoras/index'; ?>
<!-- Estamos en cualquier página relacionada a bitácoras -->
<a class="nav-link px-3 py-2 d-flex justify-content-between align-items-center <?= $esVistaBitacoras ? 'active' : '' ?>"
href="<?= $enDashboardBitacoras ? '#submenuBitacoras' : '/IMPORTADORES/bitacoras/index' ?>"
<?= $enDashboardBitacoras ? 'data-bs-toggle="collapse"' : '' ?>
role="button" aria-expanded="true" aria-controls="submenuBitacoras"
onclick="<?= $enDashboardBitacoras ? '' : 'event.preventDefault(); window.location.href = \'/IMPORTADORES/bitacoras/index\';' ?>">
📖 Bitácoras
<span class="badge bg-secondary">1</span>
<span class="badge bg-secondary"><?= $cantidadBitacoras ?></span>
</a>
<div class="collapse show" id="submenuBitacoras">
<div class="collapse <?= $esVistaBitacoras ? 'show' : '' ?>" id="submenuBitacoras">
<nav class="nav flex-column ms-3">
<a href="/IMPORTADORES/bitacoras/lista"
class="nav-link px-3 py-1 <?= $_SERVER['REQUEST_URI'] === '/IMPORTADORES/bitacoras/lista' ? 'active' : '' ?>">
• Registro de Accesos
<?php foreach ($permisosBitacoras as $permiso => $tienePermiso): ?>
<?php if ($tienePermiso): ?>
<?php
$url = obtenerUrlPermiso($permiso);
$texto = obtenerTextoPermiso($permiso, $tipoUsuario);
$icono = obtenerIconoPermiso($permiso);
$esActivo = $_SERVER['REQUEST_URI'] === $url;
// Scope según dashboard
$scope = '';
if ($tipoUsuario === 'super_admin') {
$scope = 'Global';
} elseif ($tipoUsuario === 'admin_agencia' || $tipoUsuario === 'agente_aduanal') {
if (in_array($permiso, ['acceso_usuarios_agencia', 'registro_vinculaciones'])) {
$scope = 'Agencia';
} elseif (in_array($permiso, ['mis_vinculaciones', 'mi_actividad'])) {
$scope = 'Personal';
}
} elseif ($tipoUsuario === 'importador') {
$scope = 'Personal';
}
?>
<a href="<?= $url ?>"
class="nav-link submenu-item px-3 py-2 d-flex justify-content-between align-items-center <?= $esActivo ? 'active' : '' ?>">
<span>
<?= $icono ?> <?= htmlspecialchars($texto) ?>
<?php if ($scope): ?>
<span class="permission-scope"><?= $scope ?></span>
<?php endif; ?>
</span>
</a>
<?php endif; ?>
<?php endforeach; ?>
</nav>
</div>
<?php elseif ($esConfiguracion || $esVistaAutomatizaciones || $esVistaSeguridad || $esVistaPreferencias): ?>
<!-- Si estamos en configuración, pero no en bitácoras -->
<a href="/IMPORTADORES/bitacoras/index"
class="nav-link px-3 py-2 d-flex justify-content-between align-items-center">
📖 Bitácoras
<span class="badge bg-secondary">1</span>
</a>
<?php endif; ?>
</nav>
@@ -167,12 +338,15 @@ if (!isset($_SESSION['usuario_id']) || $_SESSION['tipo_usuario'] !== 'importador
<div class="offcanvas-body">
<!-- INFORMACIÓN GENERAL -->
<?php if ($permisosUsuario['informacion_general']): ?>
<a href="/IMPORTADORES/configuracion/index"
class="nav-link px-3 py-2 <?= str_contains($_SERVER['REQUEST_URI'], '/configuracion/index') ? 'active' : '' ?>">
class="nav-link px-3 py-2 <?= str_contains($_SERVER['REQUEST_URI'], '/configuracion/dashboard_configuracion') ? 'active' : '' ?>">
Información General
</a>
<?php endif; ?>
<!-- AUTOMATIZACIONES -->
<?php if ($permisosUsuario['automatizaciones']): ?>
<?php if ($esVistaAutomatizaciones): ?>
<?php $enDashboardAutomatizaciones = $_SERVER['REQUEST_URI'] === '/IMPORTADORES/automatizaciones/index'; ?>
<a href="/IMPORTADORES/automatizaciones/index"
@@ -185,8 +359,10 @@ if (!isset($_SESSION['usuario_id']) || $_SESSION['tipo_usuario'] !== 'importador
⚙️ Automatizaciones
</a>
<?php endif; ?>
<?php endif; ?>
<!-- PREFERENCIAS -->
<?php if ($permisosUsuario['preferencias']): ?>
<?php if ($esVistaPreferencias): ?>
<?php $enDashboardPreferencias = $_SERVER['REQUEST_URI'] === '/IMPORTADORES/preferencias/index'; ?>
<a class="nav-link px-3 py-2 d-flex justify-content-between align-items-center <?= $esVistaPreferencias ? 'active' : '' ?>"
@@ -212,8 +388,10 @@ if (!isset($_SESSION['usuario_id']) || $_SESSION['tipo_usuario'] !== 'importador
<span class="badge bg-secondary">1</span>
</a>
<?php endif; ?>
<?php endif; ?>
<!-- SEGURIDAD -->
<?php if ($permisosUsuario['seguridad']): ?>
<?php if ($esVistaSeguridad): ?>
<?php $enDashboardSeguridad = $_SERVER['REQUEST_URI'] === '/IMPORTADORES/seguridad/index'; ?>
<a class="nav-link px-3 py-2 d-flex justify-content-between align-items-center <?= $esVistaSeguridad ? 'active' : '' ?>"
@@ -239,34 +417,57 @@ if (!isset($_SESSION['usuario_id']) || $_SESSION['tipo_usuario'] !== 'importador
<span class="badge bg-secondary">1</span>
</a>
<?php endif; ?>
<?php endif; ?>
<!-- BITÁCORAS -->
<?php if ($esVistaBitacoras): ?>
<!-- BITÁCORAS CON PERMISOS ESPECÍFICOS -->
<?php if (tieneAccesoBitacoras($permisosBitacoras)): ?>
<?php $enDashboardBitacoras = $_SERVER['REQUEST_URI'] === '/IMPORTADORES/bitacoras/index'; ?>
<!-- Estamos en cualquier página relacionada a bitácoras -->
<a class="nav-link px-3 py-2 d-flex justify-content-between align-items-center <?= $esVistaBitacoras ? 'active' : '' ?>"
href="<?= $enDashboardBitacoras ? '#submenuBitacoras' : '/IMPORTADORES/bitacoras/index' ?>"
<?= $enDashboardBitacoras ? 'data-bs-toggle="collapse"' : '' ?>
role="button" aria-expanded="true" aria-controls="submenuBitacoras"
onclick="<?= $enDashboardBitacoras ? '' : 'event.preventDefault(); window.location.href = \'/IMPORTADORES/bitacoras/index\';' ?>">
📖 Bitácoras
<span class="badge bg-secondary">1</span>
<span class="badge bg-secondary"><?= $cantidadBitacoras ?></span>
</a>
<div class="collapse show" id="submenuBitacoras">
<div class="collapse <?= $esVistaBitacoras ? 'show' : '' ?>" id="submenuBitacoras">
<nav class="nav flex-column ms-3">
<a href="/IMPORTADORES/bitacoras/lista"
class="nav-link px-3 py-1 <?= $_SERVER['REQUEST_URI'] === '/IMPORTADORES/bitacoras/lista' ? 'active' : '' ?>">
• Registro de Accesos
<?php foreach ($permisosBitacoras as $permiso => $tienePermiso): ?>
<?php if ($tienePermiso): ?>
<?php
$url = obtenerUrlPermiso($permiso);
$texto = obtenerTextoPermiso($permiso, $tipoUsuario);
$icono = obtenerIconoPermiso($permiso);
$esActivo = $_SERVER['REQUEST_URI'] === $url;
// Scope según dashboard
$scope = '';
if ($tipoUsuario === 'super_admin') {
$scope = 'Global';
} elseif ($tipoUsuario === 'admin_agencia' || $tipoUsuario === 'agente_aduanal') {
if (in_array($permiso, ['acceso_usuarios_agencia', 'registro_vinculaciones'])) {
$scope = 'Agencia';
} elseif (in_array($permiso, ['mis_vinculaciones', 'mi_actividad'])) {
$scope = 'Personal';
}
} elseif ($tipoUsuario === 'importador') {
$scope = 'Personal';
}
?>
<a href="<?= $url ?>"
class="nav-link submenu-item px-3 py-2 d-flex justify-content-between align-items-center <?= $esActivo ? 'active' : '' ?>">
<span>
<?= $icono ?> <?= htmlspecialchars($texto) ?>
<?php if ($scope): ?>
<span class="permission-scope"><?= $scope ?></span>
<?php endif; ?>
</span>
</a>
<?php endif; ?>
<?php endforeach; ?>
</nav>
</div>
<?php elseif ($esConfiguracion || $esVistaAutomatizaciones || $esVistaPreferencias || $esVistaSeguridad): ?>
<!-- Si estamos en configuración, pero no en bitácoras -->
<a href="/IMPORTADORES/bitacoras/index"
class="nav-link px-3 py-2 d-flex justify-content-between align-items-center">
📖 Bitácoras
<span class="badge bg-secondary">1</span>
</a>
<?php endif; ?>
</div>

View File

@@ -1,11 +1,3 @@
<!-- views/partials/sidebar_importador.php -->
<?php
if (!isset($_SESSION['usuario_id']) || $_SESSION['tipo_usuario'] !== 'importador') {
header('Location: /IMPORTADORES/login');
exit;
}
?>
<style>
.nav .nav-link { font-weight: normal; color: white; transition: all 0.3s ease; }
.nav .nav-link:hover,
@@ -46,35 +38,56 @@ if (!isset($_SESSION['usuario_id']) || $_SESSION['tipo_usuario'] !== 'importador
<!-- INICIO -->
<a href="/IMPORTADORES/importadores/dashboard"
class="nav-link px-3 py-2 <?= $_SERVER['REQUEST_URI'] === '/IMPORTADORES/importadores/dashboard' ? 'active' : '' ?>">
class="nav-link px-3 py-2 <?= str_contains($_SERVER['REQUEST_URI'], '/IMPORTADORES/importadores/dashboard') ? 'active' : '' ?>">
🏠 Inicio
</a>
<!-- LOCACIONES -->
<a href="/IMPORTADORES/importadores/lista"
class="nav-link px-3 py-2 <?= $_SERVER['REQUEST_URI'] === '/IMPORTADORES/importadores/lista' ? 'active' : '' ?>">
class="nav-link px-3 py-2 <?= str_contains($_SERVER['REQUEST_URI'], '/IMPORTADORES/importadores/lista') ? 'active' : '' ?>">
🗺️ Locaciones
</a>
<!-- VINCULACIONES -->
<a class="nav-link px-3 py-2 d-flex justify-content-between align-items-center
<?= str_contains($_SERVER['REQUEST_URI'], '/IMPORTADORES/vinculaciones') ? 'active' : '' ?>"
data-bs-toggle="collapse" href="#submenuVinculaciones" role="button" aria-expanded="false">
🔗 Vinculación
<span class="badge bg-secondary">2</span>
</a>
<div class="collapse <?= str_contains($_SERVER['REQUEST_URI'], '/IMPORTADORES/vinculaciones') ? 'show' : '' ?>" id="submenuVinculaciones">
<nav class="nav flex-column ms-3">
<a href="/IMPORTADORES/vinculaciones/vinculacionesUsuario"
class="nav-link px-3 py-2 <?= str_contains($_SERVER['REQUEST_URI'], '/IMPORTADORES/vinculaciones/vinculacionesUsuario') ? 'active' : '' ?>">
• Mis vinculaciones
</a>
<a href="/IMPORTADORES/vinculaciones/nuevaVinculacion"
class="nav-link px-3 py-2 <?= str_contains($_SERVER['REQUEST_URI'], '/IMPORTADORES/vinculaciones/nuevaVinculacion') ? 'active' : '' ?>">
• Nueva vinculación
</a>
</nav>
</div>
<!-- TRANSPORTISTAS -->
<a class="nav-link px-3 py-2 d-flex justify-content-between align-items-center
<?= str_contains($_SERVER['REQUEST_URI'], '/transportistas') ? 'active' : '' ?>"
<?= str_contains($_SERVER['REQUEST_URI'], '/IMPORTADORES/transportistas') ? 'active' : '' ?>"
data-bs-toggle="collapse" href="#submenuTransportistas" role="button" aria-expanded="false">
👨‍✈️ Transportistas
<span class="badge bg-secondary">3</span>
</a>
<div class="collapse <?= str_contains($_SERVER['REQUEST_URI'], '/transportistas') ? 'show' : '' ?>" id="submenuTransportistas">
<div class="collapse <?= str_contains($_SERVER['REQUEST_URI'], '/IMPORTADORES/transportistas') ? 'show' : '' ?>" id="submenuTransportistas">
<nav class="nav flex-column ms-3">
<a href="/IMPORTADORES/transportistas/lista"
class="nav-link px-3 py-1 <?= $_SERVER['REQUEST_URI'] === '/IMPORTADORES/transportistas/lista' ? 'active' : '' ?>">
class="nav-link px-3 py-1 <?= str_contains($_SERVER['REQUEST_URI'], '/IMPORTADORES/transportistas/lista') ? 'active' : '' ?>">
• Mis transportistas
</a>
<a href="/IMPORTADORES/transportistas/alta"
class="nav-link px-3 py-1 <?= str_contains($_SERVER['REQUEST_URI'], '/transportistas/alta') ? 'active' : '' ?>">
class="nav-link px-3 py-1 <?= str_contains($_SERVER['REQUEST_URI'], '/IMPORTADORES/transportistas/alta') ? 'active' : '' ?>">
• Alta de transportista
</a>
<a href="/IMPORTADORES/transportistas/bulk_upload"
class="nav-link px-3 py-1 <?= str_contains($_SERVER['REQUEST_URI'], '/transportistas/bulk_upload') ? 'active' : '' ?>">
class="nav-link px-3 py-1 <?= str_contains($_SERVER['REQUEST_URI'], '/IMPORTADORES/transportistas/bulk_upload') ? 'active' : '' ?>">
• Carga masiva
</a>
</nav>
@@ -82,23 +95,23 @@ if (!isset($_SESSION['usuario_id']) || $_SESSION['tipo_usuario'] !== 'importador
<!-- TRANSPORTES -->
<a class="nav-link px-3 py-2 d-flex justify-content-between align-items-center
<?= str_contains($_SERVER['REQUEST_URI'], '/transportes') ? 'active' : '' ?>"
<?= str_contains($_SERVER['REQUEST_URI'], '/IMPORTADORES/transportes') ? 'active' : '' ?>"
data-bs-toggle="collapse" href="#submenuTransportes" role="button" aria-expanded="false">
🚚 Transportes
<span class="badge bg-secondary">3</span>
</a>
<div class="collapse <?= str_contains($_SERVER['REQUEST_URI'], '/transportes') ? 'show' : '' ?>" id="submenuTransportes">
<div class="collapse <?= str_contains($_SERVER['REQUEST_URI'], '/IMPORTADORES/transportes') ? 'show' : '' ?>" id="submenuTransportes">
<nav class="nav flex-column ms-3">
<a href="/IMPORTADORES/transportes/lista"
class="nav-link px-3 py-1 <?= $_SERVER['REQUEST_URI'] === '/IMPORTADORES/transportes/lista' ? 'active' : '' ?>">
class="nav-link px-3 py-1 <?= str_contains($_SERVER['REQUEST_URI'], '/IMPORTADORES/transportes/lista') ? 'active' : '' ?>">
• Mis transportes
</a>
<a href="/IMPORTADORES/transportes/crear"
class="nav-link px-3 py-1 <?= str_contains($_SERVER['REQUEST_URI'], '/transportes/crear') ? 'active' : '' ?>">
class="nav-link px-3 py-1 <?= str_contains($_SERVER['REQUEST_URI'], '/IMPORTADORES/transportes/crear') ? 'active' : '' ?>">
• Nuevo transporte
</a>
<a href="/IMPORTADORES/transportes/masivo"
class="nav-link px-3 py-1 <?= str_contains($_SERVER['REQUEST_URI'], '/transportes/masivo') ? 'active' : '' ?>">
class="nav-link px-3 py-1 <?= str_contains($_SERVER['REQUEST_URI'], '/IMPORTADORES/transportes/masivo') ? 'active' : '' ?>">
• Carga masiva
</a>
</nav>
@@ -111,29 +124,31 @@ if (!isset($_SESSION['usuario_id']) || $_SESSION['tipo_usuario'] !== 'importador
🚛 Choferes
<span class="badge bg-secondary">2</span>
</a>
<div class="collapse <?= str_contains($_SERVER['REQUEST_URI'], '/choferes') ? 'show' : '' ?>" id="submenuChoferes">
<div class="collapse <?= str_contains($_SERVER['REQUEST_URI'], '/IMPORTADORES/choferes') ? 'show' : '' ?>" id="submenuChoferes">
<nav class="nav flex-column ms-3">
<a href="/IMPORTADORES/choferes/lista"
class="nav-link px-3 py-1 <?= $_SERVER['REQUEST_URI'] === '/IMPORTADORES/choferes/lista' ? 'active' : '' ?>">
class="nav-link px-3 py-1 <?= str_contains($_SERVER['REQUEST_URI'], '/IMPORTADORES/choferes/lista') ? 'active' : '' ?>">
• Mis choferes
</a>
<a href="/IMPORTADORES/choferes/crear"
class="nav-link px-3 py-1 <?= str_contains($_SERVER['REQUEST_URI'], '/choferes/crear') ? 'active' : '' ?>">
class="nav-link px-3 py-1 <?= str_contains($_SERVER['REQUEST_URI'], '/IMPORTADORES/choferes/crear') ? 'active' : '' ?>">
• Nuevo chofer
</a>
</nav>
</div>
<!-- PROVEEDORES -->
<a class="nav-link px-3 py-2 d-flex justify-content-between align-items-center <?= str_contains($_SERVER['REQUEST_URI'], '/IMPORTADORES/proveedores') ? 'active' : '' ?>"
data-bs-toggle="collapse" href="#submenuProveedores" role="button" aria-expanded="<?= str_contains($_SERVER['REQUEST_URI'], '/IMPORTADORES/proveedores') ? 'true' : 'false' ?>"
<a class="nav-link px-3 py-2 d-flex justify-content-between align-items-center
<?= str_contains($_SERVER['REQUEST_URI'], '/IMPORTADORES/proveedores') ? 'active' : '' ?>"
data-bs-toggle="collapse" href="#submenuProveedores" role="button"
aria-expanded="<?= str_contains($_SERVER['REQUEST_URI'], '/IMPORTADORES/proveedores') ? 'true' : 'false' ?>"
aria-controls="submenuProveedores">
🏭 Proveedores
<span class="badge bg-secondary">2</span>
</a>
<div class="collapse <?= str_contains($_SERVER['REQUEST_URI'], '/IMPORTADORES/proveedores') ? 'show' : '' ?>" id="submenuProveedores">
<nav class="nav flex-column ms-3">
<a href="/IMPORTADORES/proveedores" class="nav-link px-3 py-1 <?= $_SERVER['REQUEST_URI'] === '/IMPORTADORES/proveedores' ? 'active' : '' ?>">
<a href="/IMPORTADORES/proveedores" class="nav-link px-3 py-1 <?= str_contains($_SERVER['REQUEST_URI'], '/IMPORTADORES/proveedores') ? 'active' : '' ?>">
• Lista de Proveedores
</a>
<a href="/IMPORTADORES/proveedores/crear" class="nav-link px-3 py-1 <?= str_contains($_SERVER['REQUEST_URI'], '/IMPORTADORES/proveedores/crear') ? 'active' : '' ?>">
@@ -149,14 +164,14 @@ if (!isset($_SESSION['usuario_id']) || $_SESSION['tipo_usuario'] !== 'importador
📄 Solicitudes Importación
<span class="badge bg-secondary">2</span>
</a>
<div class="collapse <?= str_contains($_SERVER['REQUEST_URI'], '/solicitud_importacion') ? 'show' : '' ?>" id="submenuSolicitudes">
<div class="collapse <?= str_contains($_SERVER['REQUEST_URI'], '/IMPORTADORES/solicitud_importacion') ? 'show' : '' ?>" id="submenuSolicitudes">
<nav class="nav flex-column ms-3">
<a href="/IMPORTADORES/solicitud_importacion/lista"
class="nav-link px-3 py-1 <?= $_SERVER['REQUEST_URI'] === '/IMPORTADORES/solicitud_importacion/lista' ? 'active' : '' ?>">
• Mis solicitudes
</a>
<a href="/IMPORTADORES/solicitud_importacion/crear"
class="nav-link px-3 py-1 <?= str_contains($_SERVER['REQUEST_URI'], '/solicitud_importacion/crear') ? 'active' : '' ?>">
class="nav-link px-3 py-1 <?= str_contains($_SERVER['REQUEST_URI'], '/IMPORTADORES/solicitud_importacion/crear') ? 'active' : '' ?>">
• Nueva solicitud
</a>
</nav>
@@ -164,12 +179,12 @@ if (!isset($_SESSION['usuario_id']) || $_SESSION['tipo_usuario'] !== 'importador
<!-- EXPEDIENTE ELECTRÓNICO -->
<a class="nav-link px-3 py-2 d-flex justify-content-between align-items-center
<?= str_contains($_SERVER['REQUEST_URI'], '/expediente') ? 'active' : '' ?>"
<?= str_contains($_SERVER['REQUEST_URI'], '/IMPORTADORES/expediente') ? 'active' : '' ?>"
data-bs-toggle="collapse" href="#submenuExpediente" role="button" aria-expanded="false">
📁 Expediente Electrónico
<span class="badge bg-secondary">1</span>
</a>
<div class="collapse <?= str_contains($_SERVER['REQUEST_URI'], '/expediente') ? 'show' : '' ?>" id="submenuExpediente">
<div class="collapse <?= str_contains($_SERVER['REQUEST_URI'], '/IMPORTADORES/expediente') ? 'show' : '' ?>" id="submenuExpediente">
<nav class="nav flex-column ms-3">
<a href="/IMPORTADORES/expediente/index"
class="nav-link px-3 py-1 <?= $_SERVER['REQUEST_URI'] === '/IMPORTADORES/expediente/consultar' ? 'active' : '' ?>">
@@ -180,7 +195,7 @@ if (!isset($_SESSION['usuario_id']) || $_SESSION['tipo_usuario'] !== 'importador
<!-- CONFIGURACIÓN -->
<a href="/IMPORTADORES/configuracion"
class="nav-link px-3 py-2 <?= str_contains($_SERVER['REQUEST_URI'], '/configuracion') ? 'active' : '' ?>">
class="nav-link px-3 py-2 <?= str_contains($_SERVER['REQUEST_URI'], '/IMPORTADORES/configuracion') ? 'active' : '' ?>">
⚙️ Configuración
</a>
@@ -194,37 +209,59 @@ if (!isset($_SESSION['usuario_id']) || $_SESSION['tipo_usuario'] !== 'importador
<button type="button" class="btn-close" data-bs-dismiss="offcanvas"></button>
</div>
<div class="offcanvas-body">
<!-- INICIO -->
<a href="/IMPORTADORES/importadores/dashboard"
class="nav-link px-3 py-2 <?= $_SERVER['REQUEST_URI'] === '/IMPORTADORES/importadores/dashboard' ? 'active' : '' ?>">
class="nav-link px-3 py-2 <?= str_contains($_SERVER['REQUEST_URI'], '/IMPORTADORES/importadores/dashboard') ? 'active' : '' ?>">
🏠 Inicio
</a>
<!-- LOCACIONES -->
<a href="/IMPORTADORES/importadores/lista"
class="nav-link px-3 py-2 <?= $_SERVER['REQUEST_URI'] === '/IMPORTADORES/importadores/lista' ? 'active' : '' ?>">
class="nav-link px-3 py-2 <?= str_contains($_SERVER['REQUEST_URI'], '/IMPORTADORES/importadores/lista') ? 'active' : '' ?>">
🗺️ Locaciones
</a>
<!-- VINCULACIONES -->
<a class="nav-link px-3 py-2 d-flex justify-content-between align-items-center
<?= str_contains($_SERVER['REQUEST_URI'], '/IMPORTADORES/vinculaciones') ? 'active' : '' ?>"
data-bs-toggle="collapse" href="#submenuVinculaciones" role="button" aria-expanded="false">
🔗 Vinculación
<span class="badge bg-secondary">2</span>
</a>
<div class="collapse <?= str_contains($_SERVER['REQUEST_URI'], '/IMPORTADORES/vinculaciones') ? 'show' : '' ?>" id="submenuVinculaciones">
<nav class="nav flex-column ms-3">
<a href="/IMPORTADORES/vinculaciones/vinculacionesUsuario"
class="nav-link px-3 py-2 <?= str_contains($_SERVER['REQUEST_URI'], '/IMPORTADORES/vinculaciones/vinculacionesUsuario') ? 'active' : '' ?>">
• Mis vinculaciones
</a>
<a href="/IMPORTADORES/vinculaciones/nuevaVinculacion"
class="nav-link px-3 py-2 <?= str_contains($_SERVER['REQUEST_URI'], '/IMPORTADORES/vinculaciones/nuevaVinculacion') ? 'active' : '' ?>">
• Nueva vinculación
</a>
</nav>
</div>
<!-- TRANSPORTISTAS -->
<a class="nav-link px-3 py-2 d-flex justify-content-between align-items-center
<?= str_contains($_SERVER['REQUEST_URI'], '/transportistas') ? 'active' : '' ?>"
<?= str_contains($_SERVER['REQUEST_URI'], '/IMPORTADORES/transportistas') ? 'active' : '' ?>"
data-bs-toggle="collapse" href="#submenuTransportistas" role="button" aria-expanded="false">
👨‍✈️ Transportistas
<span class="badge bg-secondary">3</span>
</a>
<div class="collapse <?= str_contains($_SERVER['REQUEST_URI'], '/transportistas') ? 'show' : '' ?>" id="submenuTransportistas">
<div class="collapse <?= str_contains($_SERVER['REQUEST_URI'], '/IMPORTADORES/transportistas') ? 'show' : '' ?>" id="submenuTransportistas">
<nav class="nav flex-column ms-3">
<a href="/IMPORTADORES/transportistas/lista"
class="nav-link px-3 py-1 <?= $_SERVER['REQUEST_URI'] === '/IMPORTADORES/transportistas/lista' ? 'active' : '' ?>">
class="nav-link px-3 py-1 <?= str_contains($_SERVER['REQUEST_URI'], '/IMPORTADORES/transportistas/lista') ? 'active' : '' ?>">
• Mis transportistas
</a>
<a href="/IMPORTADORES/transportistas/alta"
class="nav-link px-3 py-1 <?= str_contains($_SERVER['REQUEST_URI'], '/transportistas/alta') ? 'active' : '' ?>">
class="nav-link px-3 py-1 <?= str_contains($_SERVER['REQUEST_URI'], '/IMPORTADORES/transportistas/alta') ? 'active' : '' ?>">
• Alta de transportista
</a>
<a href="/IMPORTADORES/transportistas/bulk_upload"
class="nav-link px-3 py-1 <?= str_contains($_SERVER['REQUEST_URI'], '/transportistas/bulk_upload') ? 'active' : '' ?>">
class="nav-link px-3 py-1 <?= str_contains($_SERVER['REQUEST_URI'], '/IMPORTADORES/transportistas/bulk_upload') ? 'active' : '' ?>">
• Carga masiva
</a>
</nav>
@@ -232,23 +269,23 @@ if (!isset($_SESSION['usuario_id']) || $_SESSION['tipo_usuario'] !== 'importador
<!-- TRANSPORTES -->
<a class="nav-link px-3 py-2 d-flex justify-content-between align-items-center
<?= str_contains($_SERVER['REQUEST_URI'], '/transportes') ? 'active' : '' ?>"
<?= str_contains($_SERVER['REQUEST_URI'], '/IMPORTADORES/transportes') ? 'active' : '' ?>"
data-bs-toggle="collapse" href="#submenuTransportes" role="button" aria-expanded="false">
🚚 Transportes
<span class="badge bg-secondary">3</span>
</a>
<div class="collapse <?= str_contains($_SERVER['REQUEST_URI'], '/transportes') ? 'show' : '' ?>" id="submenuTransportes">
<div class="collapse <?= str_contains($_SERVER['REQUEST_URI'], '/IMPORTADORES/transportes') ? 'show' : '' ?>" id="submenuTransportes">
<nav class="nav flex-column ms-3">
<a href="/IMPORTADORES/transportes/lista"
class="nav-link px-3 py-1 <?= $_SERVER['REQUEST_URI'] === '/IMPORTADORES/transportes/lista' ? 'active' : '' ?>">
class="nav-link px-3 py-1 <?= str_contains($_SERVER['REQUEST_URI'], '/IMPORTADORES/transportes/lista') ? 'active' : '' ?>">
• Mis transportes
</a>
<a href="/IMPORTADORES/transportes/crear"
class="nav-link px-3 py-1 <?= str_contains($_SERVER['REQUEST_URI'], '/transportes/crear') ? 'active' : '' ?>">
class="nav-link px-3 py-1 <?= str_contains($_SERVER['REQUEST_URI'], '/IMPORTADORES/transportes/crear') ? 'active' : '' ?>">
• Nuevo transporte
</a>
<a href="/IMPORTADORES/transportes/masivo"
class="nav-link px-3 py-1 <?= str_contains($_SERVER['REQUEST_URI'], '/transportes/masivo') ? 'active' : '' ?>">
class="nav-link px-3 py-1 <?= str_contains($_SERVER['REQUEST_URI'], '/IMPORTADORES/transportes/masivo') ? 'active' : '' ?>">
• Carga masiva
</a>
</nav>
@@ -261,29 +298,31 @@ if (!isset($_SESSION['usuario_id']) || $_SESSION['tipo_usuario'] !== 'importador
🚛 Choferes
<span class="badge bg-secondary">2</span>
</a>
<div class="collapse <?= str_contains($_SERVER['REQUEST_URI'], '/choferes') ? 'show' : '' ?>" id="submenuChoferes">
<div class="collapse <?= str_contains($_SERVER['REQUEST_URI'], '/IMPORTADORES/choferes') ? 'show' : '' ?>" id="submenuChoferes">
<nav class="nav flex-column ms-3">
<a href="/IMPORTADORES/choferes/lista"
class="nav-link px-3 py-1 <?= $_SERVER['REQUEST_URI'] === '/IMPORTADORES/choferes/lista' ? 'active' : '' ?>">
class="nav-link px-3 py-1 <?= str_contains($_SERVER['REQUEST_URI'], '/IMPORTADORES/choferes/lista') ? 'active' : '' ?>">
• Mis choferes
</a>
<a href="/IMPORTADORES/choferes/crear"
class="nav-link px-3 py-1 <?= str_contains($_SERVER['REQUEST_URI'], '/choferes/crear') ? 'active' : '' ?>">
class="nav-link px-3 py-1 <?= str_contains($_SERVER['REQUEST_URI'], '/IMPORTADORES/choferes/crear') ? 'active' : '' ?>">
• Nuevo chofer
</a>
</nav>
</div>
<!-- PROVEEDORES -->
<a class="nav-link px-3 py-2 d-flex justify-content-between align-items-center <?= str_contains($_SERVER['REQUEST_URI'], '/IMPORTADORES/proveedores') ? 'active' : '' ?>"
data-bs-toggle="collapse" href="#submenuProveedores" role="button" aria-expanded="<?= str_contains($_SERVER['REQUEST_URI'], '/IMPORTADORES/proveedores') ? 'true' : 'false' ?>"
<a class="nav-link px-3 py-2 d-flex justify-content-between align-items-center
<?= str_contains($_SERVER['REQUEST_URI'], '/IMPORTADORES/proveedores') ? 'active' : '' ?>"
data-bs-toggle="collapse" href="#submenuProveedores" role="button"
aria-expanded="<?= str_contains($_SERVER['REQUEST_URI'], '/IMPORTADORES/proveedores') ? 'true' : 'false' ?>"
aria-controls="submenuProveedores">
🏭 Proveedores
<span class="badge bg-secondary">2</span>
</a>
<div class="collapse <?= str_contains($_SERVER['REQUEST_URI'], '/IMPORTADORES/proveedores') ? 'show' : '' ?>" id="submenuProveedores">
<nav class="nav flex-column ms-3">
<a href="/IMPORTADORES/proveedores" class="nav-link px-3 py-1 <?= $_SERVER['REQUEST_URI'] === '/IMPORTADORES/proveedores' ? 'active' : '' ?>">
<a href="/IMPORTADORES/proveedores" class="nav-link px-3 py-1 <?= str_contains($_SERVER['REQUEST_URI'], '/IMPORTADORES/proveedores') ? 'active' : '' ?>">
• Lista de Proveedores
</a>
<a href="/IMPORTADORES/proveedores/crear" class="nav-link px-3 py-1 <?= str_contains($_SERVER['REQUEST_URI'], '/IMPORTADORES/proveedores/crear') ? 'active' : '' ?>">
@@ -299,14 +338,14 @@ if (!isset($_SESSION['usuario_id']) || $_SESSION['tipo_usuario'] !== 'importador
📄 Solicitudes Importación
<span class="badge bg-secondary">2</span>
</a>
<div class="collapse <?= str_contains($_SERVER['REQUEST_URI'], '/solicitud_importacion') ? 'show' : '' ?>" id="submenuSolicitudes">
<div class="collapse <?= str_contains($_SERVER['REQUEST_URI'], '/IMPORTADORES/solicitud_importacion') ? 'show' : '' ?>" id="submenuSolicitudes">
<nav class="nav flex-column ms-3">
<a href="/IMPORTADORES/solicitud_importacion/lista"
class="nav-link px-3 py-1 <?= $_SERVER['REQUEST_URI'] === '/IMPORTADORES/solicitud_importacion/lista' ? 'active' : '' ?>">
• Mis solicitudes
</a>
<a href="/IMPORTADORES/solicitud_importacion/crear"
class="nav-link px-3 py-1 <?= str_contains($_SERVER['REQUEST_URI'], '/solicitud_importacion/crear') ? 'active' : '' ?>">
class="nav-link px-3 py-1 <?= str_contains($_SERVER['REQUEST_URI'], '/IMPORTADORES/solicitud_importacion/crear') ? 'active' : '' ?>">
• Nueva solicitud
</a>
</nav>
@@ -314,12 +353,12 @@ if (!isset($_SESSION['usuario_id']) || $_SESSION['tipo_usuario'] !== 'importador
<!-- EXPEDIENTE ELECTRÓNICO -->
<a class="nav-link px-3 py-2 d-flex justify-content-between align-items-center
<?= str_contains($_SERVER['REQUEST_URI'], '/expediente') ? 'active' : '' ?>"
<?= str_contains($_SERVER['REQUEST_URI'], '/IMPORTADORES/expediente') ? 'active' : '' ?>"
data-bs-toggle="collapse" href="#submenuExpediente" role="button" aria-expanded="false">
📁 Expediente Electrónico
<span class="badge bg-secondary">1</span>
</a>
<div class="collapse <?= str_contains($_SERVER['REQUEST_URI'], '/expediente') ? 'show' : '' ?>" id="submenuExpediente">
<div class="collapse <?= str_contains($_SERVER['REQUEST_URI'], '/IMPORTADORES/expediente') ? 'show' : '' ?>" id="submenuExpediente">
<nav class="nav flex-column ms-3">
<a href="/IMPORTADORES/expediente/index"
class="nav-link px-3 py-1 <?= $_SERVER['REQUEST_URI'] === '/IMPORTADORES/expediente/consultar' ? 'active' : '' ?>">
@@ -330,7 +369,7 @@ if (!isset($_SESSION['usuario_id']) || $_SESSION['tipo_usuario'] !== 'importador
<!-- CONFIGURACIÓN -->
<a href="/IMPORTADORES/configuracion"
class="nav-link px-3 py-2 <?= str_contains($_SERVER['REQUEST_URI'], '/configuracion') ? 'active' : '' ?>">
class="nav-link px-3 py-2 <?= str_contains($_SERVER['REQUEST_URI'], '/IMPORTADORES/configuracion') ? 'active' : '' ?>">
⚙️ Configuración
</a>

View File

@@ -1,61 +0,0 @@
<style>
body { font-family: 'Segoe UI', sans-serif; }
.table td, .table th { vertical-align: middle; }
.sidebar { width: 220px; background-color: #343a40; position: fixed; top: 56px; bottom: 0; left: 0; z-index: 1045; padding-top: 1rem; height: 100vh; overflow-y: auto; border-right: 1px solid #dee2e6; }
.sidebar a { color: white; text-decoration: none; }
.sidebar .nav-link.active,
.sidebar .nav-link:hover { background-color: #495057; }
.content { margin-left: 230px; margin-top: 70px; }
@media (max-width: 991.98px) {
.sidebar { position: relative; width: 100%; height: auto; border-right: none; }
.content { margin-left: 0; margin-top: 20px; }
}
</style>
<!-- 🔷 NAVBAR -->
<nav class="navbar navbar-dark bg-dark fixed-top">
<div class="container-fluid">
<a class="navbar-brand" href="#">SIIH | Admin</a>
</div>
</nav>
<!-- SIDEBAR FIJA (escritorio) -->
<div class="d-none d-md-block bg-dark text-white position-fixed h-100 pt-5" style="width: 250px;">
<nav class="nav flex-column">
<a href="/IMPORTADORES/sistemas/alta_usuarios" class="nav-link px-3 py-2 <?= $_GET['url'] === 'sistemas/alta_usuarios' ? 'active' : '' ?>">👥 Usuarios</a>
<a href="/IMPORTADORES/sistemas/agencias_activas" class="nav-link px-3 py-2 <?= $_GET['url'] === 'sistemas/agencias_activas' ? 'active' : '' ?>">🏪 Agencias</a>
<a href="/IMPORTADORES/sistemas/bitacora_login" class="nav-link px-3 py-2 <?= $_GET['url'] === 'sistemas/bitacora_login' ? 'active' : '' ?>">🕵️ Bitácora Accesos</a>
<a href="/IMPORTADORES/sistemas/bitacora_usuarios" class="nav-link px-3 py-2 <?= $_GET['url'] === 'sistemas/bitacora_usuarios' ? 'active' : '' ?>">🛠️ Bitácora Usuarios</a>
<a href="#" class="nav-link px-3 py-2">📦 Configuración</a>
<a href="/IMPORTADORES/sistemas/logout" class="nav-link px-3 py-2 text-danger">🚪 Cerrar sesión</a>
</nav>
</div>
<!-- OFFCANVAS PARA MÓVILES -->
<div class="offcanvas offcanvas-start d-md-none" tabindex="-1" id="sidebarMenu" style="top: 56px;">
<div class="offcanvas-header">
<h5 class="offcanvas-title">Menú</h5>
<button type="button" class="btn-close" data-bs-dismiss="offcanvas"></button>
</div>
<div class="offcanvas-body">
<!-- INICIO -->
<a href="/IMPORTADORES/sistemas/alta_usuarios" class="nav-link px-3 py-2 <?= $_GET['url'] === 'sistemas/alta_usuarios' ? 'active' : '' ?>">👥 Usuarios</a>
<a href="/IMPORTADORES/sistemas/agencias_activas" class="nav-link px-3 py-2 <?= $_GET['url'] === 'sistemas/agencias_activas' ? 'active' : '' ?>">🏪 Agencias</a>
<a href="/IMPORTADORES/sistemas/bitacora_login" class="nav-link px-3 py-2 <?= $_GET['url'] === 'sistemas/bitacora_login' ? 'active' : '' ?>">🕵️ Bitácora Accesos</a>
<a href="/IMPORTADORES/sistemas/bitacora_usuarios" class="nav-link px-3 py-2 <?= $_GET['url'] === 'sistemas/bitacora_usuarios' ? 'active' : '' ?>">🛠️ Bitácora Usuarios</a>
<a href="#" class="nav-link px-3 py-2">📦 Configuración</a>
<a href="/IMPORTADORES/sistemas/logout" class="nav-link px-3 py-2 text-danger">🚪 Cerrar sesión</a>
</div>
</div>

View File

@@ -276,7 +276,7 @@
const soloNumerosRegex = /^[0-9]+$/;
if (!aduana) {
Swal.fire({ icon: 'error', title: 'Campo requerido', text: 'La aduana es obligatoria.', confirmButtonColor: '#dc3545' });
Swal.fire({ icon: 'warning', title: 'Campo requerido', text: 'La aduana es obligatoria.', confirmButtonColor: '#dc3545' });
document.getElementById('aduana').focus();
return;
}
@@ -286,7 +286,7 @@
return;
}
if (!patente) {
Swal.fire({ icon: 'error', title: 'Campo requerido', text: 'La patente es obligatoria.', confirmButtonColor: '#dc3545' });
Swal.fire({ icon: 'warning', title: 'Campo requerido', text: 'La patente es obligatoria.', confirmButtonColor: '#dc3545' });
document.getElementById('patente').focus();
return;
}
@@ -296,12 +296,12 @@
return;
}
if (!agente_aduanal) {
Swal.fire({ icon: 'error', title: 'Campo requerido', text: 'El agente aduanal es obligatorio.', confirmButtonColor: '#dc3545' });
Swal.fire({ icon: 'warning', title: 'Campo requerido', text: 'El agente aduanal es obligatorio.', confirmButtonColor: '#dc3545' });
document.getElementById('agente_aduanal').focus();
return;
}
if (!rfc) {
Swal.fire({ icon: 'error', title: 'Campo requerido', text: 'El RFC es obligatorio.', confirmButtonColor: '#dc3545' });
Swal.fire({ icon: 'warning', title: 'Campo requerido', text: 'El RFC es obligatorio.', confirmButtonColor: '#dc3545' });
document.getElementById('rfc').focus();
return;
}
@@ -311,7 +311,7 @@
return;
}
if (!curp) {
Swal.fire({ icon: 'error', title: 'Campo requerido', text: 'El CURP es obligatorio.', confirmButtonColor: '#dc3545' });
Swal.fire({ icon: 'warning', title: 'Campo requerido', text: 'El CURP es obligatorio.', confirmButtonColor: '#dc3545' });
document.getElementById('curp').focus();
return;
}
@@ -322,7 +322,7 @@
return;
}
if (!razon_social) {
Swal.fire({ icon: 'error', title: 'Campo requerido', text: 'La razón social es obligatoria.', confirmButtonColor: '#dc3545' });
Swal.fire({ icon: 'warning', title: 'Campo requerido', text: 'La razón social es obligatoria.', confirmButtonColor: '#dc3545' });
document.getElementById('razon_social').focus();
return;
}

View File

@@ -299,7 +299,7 @@
const soloNumerosRegex = /^[0-9]+$/;
if (!aduana) {
Swal.fire({ icon: 'error', title: 'Campo requerido', text: 'La aduana es obligatoria.', confirmButtonColor: '#dc3545' });
Swal.fire({ icon: 'warning', title: 'Campo requerido', text: 'La aduana es obligatoria.', confirmButtonColor: '#dc3545' });
document.getElementById('aduana').focus();
return;
}
@@ -309,7 +309,7 @@
return;
}
if (!patente) {
Swal.fire({ icon: 'error', title: 'Campo requerido', text: 'La patente es obligatoria.', confirmButtonColor: '#dc3545' });
Swal.fire({ icon: 'warning', title: 'Campo requerido', text: 'La patente es obligatoria.', confirmButtonColor: '#dc3545' });
document.getElementById('patente').focus();
return;
}
@@ -319,12 +319,12 @@
return;
}
if (!agente_aduanal) {
Swal.fire({ icon: 'error', title: 'Campo requerido', text: 'El agente aduanal es obligatorio.', confirmButtonColor: '#dc3545' });
Swal.fire({ icon: 'warning', title: 'Campo requerido', text: 'El agente aduanal es obligatorio.', confirmButtonColor: '#dc3545' });
document.getElementById('agente_aduanal').focus();
return;
}
if (!rfc) {
Swal.fire({ icon: 'error', title: 'Campo requerido', text: 'El RFC es obligatorio.', confirmButtonColor: '#dc3545' });
Swal.fire({ icon: 'warning', title: 'Campo requerido', text: 'El RFC es obligatorio.', confirmButtonColor: '#dc3545' });
document.getElementById('rfc').focus();
return;
}
@@ -334,7 +334,7 @@
return;
}
if (!curp) {
Swal.fire({ icon: 'error', title: 'Campo requerido', text: 'El CURP es obligatorio.', confirmButtonColor: '#dc3545' });
Swal.fire({ icon: 'warning', title: 'Campo requerido', text: 'El CURP es obligatorio.', confirmButtonColor: '#dc3545' });
document.getElementById('curp').focus();
return;
}
@@ -345,7 +345,7 @@
return;
}
if (!razon_social) {
Swal.fire({ icon: 'error', title: 'Campo requerido', text: 'La razón social es obligatoria.', confirmButtonColor: '#dc3545' });
Swal.fire({ icon: 'warning', title: 'Campo requerido', text: 'La razón social es obligatoria.', confirmButtonColor: '#dc3545' });
document.getElementById('razon_social').focus();
return;
}

View File

@@ -123,7 +123,7 @@
// VALIDAR CAMPOS OBLIGATORIOS
if (!clave) {
Swal.fire({ icon: 'error', title: 'Clave requerida', text: 'Por favor ingresa la clave del proveedor.', confirmButtonColor: '#dc3545' });
Swal.fire({ icon: 'warning', title: 'Clave requerida', text: 'Por favor ingresa la clave del proveedor.', confirmButtonColor: '#dc3545' });
document.getElementById('Clave').focus();
return;
}
@@ -139,7 +139,7 @@
return;
}
if (!nombre) {
Swal.fire({ icon: 'error', title: 'Nombre requerido', text: 'Por favor ingresa el nombre del proveedor.', confirmButtonColor: '#dc3545' });
Swal.fire({ icon: 'warning', title: 'Nombre requerido', text: 'Por favor ingresa el nombre del proveedor.', confirmButtonColor: '#dc3545' });
document.getElementById('Nombre').focus();
return;
}
@@ -149,7 +149,7 @@
return;
}
if (!rfc) {
Swal.fire({ icon: 'error', title: 'RFC requerido', text: 'Por favor ingresa el RFC del proveedor.', confirmButtonColor: '#dc3545' });
Swal.fire({ icon: 'warning', title: 'RFC requerido', text: 'Por favor ingresa el RFC del proveedor.', confirmButtonColor: '#dc3545' });
document.getElementById('RFC').focus();
return;
}
@@ -159,7 +159,7 @@
return;
}
if (!ciudad) {
Swal.fire({ icon: 'error', title: 'Ciudad requerida', text: 'Por favor ingresa la ciudad del proveedor.', confirmButtonColor: '#dc3545' });
Swal.fire({ icon: 'warning', title: 'Ciudad requerida', text: 'Por favor ingresa la ciudad del proveedor.', confirmButtonColor: '#dc3545' });
document.getElementById('Ciudad').focus();
return;
}

View File

@@ -42,6 +42,12 @@ $color2 = explode(',', $config['colores_primarios'] ?? '#003366,#0055A5')
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; }
</style>
</head>
<body>
@@ -72,28 +78,37 @@ $color2 = explode(',', $config['colores_primarios'] ?? '#003366,#0055A5')
</div>
</nav>
<!-- Contenedor del formulario -->
<div class="container card-form">
<form action="/IMPORTADORES/registro/enviar" method="POST" enctype="multipart/form-data" id="formRegistro">
<!-- Contenedor de formularios -->
<div class="container">
<div class="card-form">
<!-- Botón para alternar entre formularios -->
<div class="form-toggle">
<button type="button" class="btn btn-white" id="btnToggle" onclick="alternarFormulario()">🔄 Registro de Agencia Aduanal</button>
</div>
<!-- Formulario de Importador -->
<div id="formImportadorContainer" class="form-container active transition-fade">
<h3 class="form-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="col-md-6">
<label for="company_name" class="form-label">Nombre de la empresa *</label>
<input type="text" class="form-control" name="company_name" required>
<input type="text" class="form-control" name="company_name" id="company_name" 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" required>
<input type="text" class="form-control" name="rfc" maxlength="13" id="rfc" 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" required>
<input type="email" class="form-control" name="email" id="email" required>
</div>
<div class="col-md-6">
<label for="phone" class="form-label">Teléfono *</label>
<input type="text" class="form-control" name="phone" required>
<input type="text" class="form-control" name="phone" id="phone" required>
</div>
<div class="col-md-12">
@@ -112,6 +127,62 @@ $color2 = explode(',', $config['colores_primarios'] ?? '#003366,#0055A5')
</form>
</div>
<!-- Formulario de Agencia Aduanal -->
<div id="formAgenciaContainer" class="form-container transition-fade">
<h3 class="form-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>
<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>
</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>
</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>
</div>
<div class="col-md-12">
<label for="direccion" class="form-label">Dirección *</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>
</div>
<div class="col-md-12">
<label for="opinion_file" class="form-label">Padrón de importadores o encargo conferido (PDF) *</label>
<input type="file" class="form-control" name="opinion_file" id="opinion_file" 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>
</div>
<div class="col-md-12 text-center recaptcha">
<div class="g-recaptcha" 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>
</div>
</form>
</div>
</div>
</div>
<div class="container ">
<div class="alert alert-warning" role="alert">
Una vez que envíes tu solicitud, será revisada manualmente por la agencia aduanal.
@@ -126,59 +197,214 @@ $color2 = explode(',', $config['colores_primarios'] ?? '#003366,#0055A5')
<!-- Validación JS -->
<script>
let formularioActual = 'importador';
function alternarFormulario() {
const formImportador = document.getElementById('formImportadorContainer');
const formAgencia = document.getElementById('formAgenciaContainer');
const btnToggle = document.getElementById('btnToggle');
if (formularioActual === 'importador') {
// Cambiar a formulario de agencia
formImportador.classList.remove('active');
formAgencia.classList.add('active');
btnToggle.textContent = '🔄 Registro de Importador';
btnToggle.classList.remove('btn-white');
btnToggle.classList.add('btn-white');
formularioActual = 'agencia';
} else {
// Cambiar a formulario de importador
formAgencia.classList.remove('active');
formImportador.classList.add('active');
btnToggle.textContent = '🔄 Registro de Agencia Aduanal';
btnToggle.classList.remove('btn-white');
btnToggle.classList.add('btn-white');
formularioActual = 'importador';
}
// Resetear reCAPTCHA si está disponible
if (typeof grecaptcha !== 'undefined') {
grecaptcha.reset();
}
}
// 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
document.getElementById('formRegistro').addEventListener('submit', function(e) {
const rfcInput = document.querySelector('input[name="rfc"]');
const emailInput = document.querySelector('input[name="email"]');
const phoneInput = document.querySelector('input[name="phone"]');
e.preventDefault();
const nombre = document.getElementById('company_name').value.trim();
const rfc = document.getElementById('rfc').value.trim().toUpperCase();
const email = document.getElementById('email').value.trim();
const phone = document.getElementById('phone').value.trim();
const archivo = document.getElementById('opinion_file').files[0];
const recaptcha = grecaptcha.getResponse();
const rfc = rfcInput.value.trim().toUpperCase();
const email = emailInput.value.trim();
const phone = phoneInput.value.trim();
// Expresiones regulares
const emailRegex = /^[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 phoneRegex = /^\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('company_name').focus();
return;
}
// Validación de correo
if (!email) {
Swal.fire({ icon: 'warning', title: 'Campo requerido', text: 'El correo es obligatorio.', confirmButtonColor: '#dc3545' });
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' });
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' });
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();
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('direccion').focus();
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.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
document.getElementById('formRegistroAgencia').addEventListener('submit', function(e) {
e.preventDefault();
const nombre = document.getElementById('nombre_agencia').value.trim();
const rfc_agencia = document.getElementById('rfc_agencia').value.trim().toUpperCase();
const correo = document.getElementById('correo').value.trim();
const telefono = document.getElementById('telefono').value.trim();
const direccion = document.getElementById('direccion').value.trim();
const archivo = document.getElementById('opinion_file').files[0];
const nombre_admin = document.getElementById('nombre_admin').value.trim();
const correo_admin = document.getElementById('correo_admin').value.trim();
const recaptcha = grecaptcha.getResponse();
// Expresiones regulares
const correoRegex = /^[a-z0-9._+-]+@[a-z0-9.-]+\.[a-z]{2,}$/;
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();
return;
}
// Validación de correo
if (!correoRegex.test(email)) {
e.preventDefault();
Swal.fire("Correo inválido", "No es una dirección de correo válida. Solo se permiten minúsculas.", "error");
emailInput.focus();
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 (!rfcRegex.test(rfc)) {
e.preventDefault();
Swal.fire("RFC inválido", "El RFC debe tener 12 o 13 caracteres con el formato correcto (ej. ABC123456XYZ).", "error");
rfcInput.focus();
if (!rfc_agencia) {
Swal.fire({ icon: 'warning', title: 'Campo requerido', text: 'El RFC es obligatorio.', confirmButtonColor: '#dc3545' });
document.getElementById('rfc_agencia').focus();
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 (!telefonoRegex.test(phone)) {
e.preventDefault();
Swal.fire("Teléfono inválido", "Debe tener 10 dígitos en el formato: 3 dígitos (LADA), espacio, y 7 dígitos. Ejemplo: 123 4567890", "error");
phoneInput.focus();
if (!telefono) {
Swal.fire({ icon: 'warning', title: 'Campo requerido', text: 'El teléfono es obligatorio.', confirmButtonColor: '#dc3545' });
document.getElementById('telefono').focus();
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();
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();
return;
}
// Validación de archivo PDF
if (!archivo || archivo.type !== 'application/pdf') {
e.preventDefault();
Swal.fire("Error", "Solo se permite subir archivos PDF.", "error");
if (!archivo) {
Swal.fire({ icon: 'warning', title: 'Campo requerido', text: 'El padrón de importadores es obligatorio.', confirmButtonColor: '#dc3545' });
document.getElementById('direccion').focus();
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.length === 0) {
e.preventDefault();
Swal.fire("Verificación requerida", "Por favor completa el reCAPTCHA.", "warning");
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

@@ -38,7 +38,7 @@ $color2 = explode(',', $config['colores_primarios'] ?? '#003366,#0055A5')
Swal.fire({
icon: 'success',
title: '¡Gracias por tu solicitud!',
html: `<p>Tu información fue enviada correctamente.<br>Un agente aduanal revisará tu expediente y te contactará por correo.</p>`,
html: `<p>Tu información fue enviada correctamente.<br>Un administrador revisará tu expediente y te contactará por correo.</p>`,
confirmButtonText: 'Volver al inicio',
confirmButtonColor: '<?= $color1 ?>',
allowOutsideClick: false,

View File

@@ -128,22 +128,22 @@
// Validaciones de campos obligatorios
if (!vehiculo) {
Swal.fire({ icon:'error', title:'Contenedor requerido', text:'Por favor ingresa el número económico del vehículo.', confirmButtonColor: '#dc3545'});
Swal.fire({ icon: 'warning', title: 'Contenedor requerido', text: 'Por favor ingresa el número económico del vehículo.', confirmButtonColor: '#dc3545'});
document.getElementById('vehiculo').focus();
return;
}
if (!ident_fiscal) {
Swal.fire({ icon:'error', title:'Identificación fiscal requerida', text:'Por favor ingresa la identificación fiscal.', confirmButtonColor: '#dc3545' });
Swal.fire({ icon: 'warning', title: 'Identificación fiscal requerida', text: 'Por favor ingresa la identificación fiscal.', confirmButtonColor: '#dc3545' });
document.getElementById('ident_fiscal').focus();
return;
}
if (!transportista) {
Swal.fire({ icon:'error', title:'Transportista no seleccionado', text:'Debes elegir un transportista.', confirmButtonColor: '#dc3545' });
Swal.fire({ icon: 'warning', title: 'Transportista no seleccionado', text: 'Debes elegir un transportista.', confirmButtonColor: '#dc3545' });
document.getElementById('transportista').focus();
return;
}
if (fotoF && fotoF.size > 2 * 1024 * 1024) { // 2 MB
return Swal.fire({ icon:'error', title:'Foto demasiado grande', text:'La imagen no debe exceder 2 MB.' });
return Swal.fire({ icon: 'error', title: 'Foto demasiado grande', text: 'La imagen no debe exceder 2 MB.' confirmButtonColor: '#dc3545' });
}
// Si todas las validaciones pasan, el formulario se envía.
this.submit();

View File

@@ -131,22 +131,22 @@
// Validaciones de campos obligatorios
if (!veh) {
Swal.fire({ icon:'error', title:'Contenedor requerido', text:'Por favor ingresa el número económico del vehículo.', confirmButtonColor: '#dc3545'});
Swal.fire({ icon: 'warning', title: 'Contenedor requerido', text: 'Por favor ingresa el número económico del vehículo.', confirmButtonColor: '#dc3545'});
document.getElementById('vehiculo').focus();
return;
}
if (!fisc) {
Swal.fire({ icon:'error', title:'Identificación fiscal requerida', text:'Por favor ingresa la identificación fiscal.', confirmButtonColor: '#dc3545' });
Swal.fire({ icon: 'warning', title: 'Identificación fiscal requerida', text: 'Por favor ingresa la identificación fiscal.', confirmButtonColor: '#dc3545' });
document.getElementById('identificador_fiscal').focus();
return;
}
if (!trans) {
Swal.fire({ icon:'error', title:'Transportista no seleccionado', text:'Debes elegir un transportista.', confirmButtonColor: '#dc3545' });
Swal.fire({ icon: 'warning', title: 'Transportista no seleccionado', text: 'Debes elegir un transportista.', confirmButtonColor: '#dc3545' });
document.getElementById('transportista').focus();
return;
}
if (fotoF && fotoF.size > 2 * 1024 * 1024) { // 2 MB
return Swal.fire({ icon:'error', title:'Foto demasiado grande', text:'La imagen no debe exceder 2 MB.' });
return Swal.fire({ icon: 'error', title: 'Foto demasiado grande', text: 'La imagen no debe exceder 2 MB.' confirmButtonColor: '#dc3545' });
}
// Si todas las validaciones pasan, el formulario se envía.
this.submit();

View File

@@ -132,60 +132,74 @@
const telefonoRegex = /^\d{3}\s\d{7}$/;
if (!clave) {
Swal.fire({ icon:'error',title:'Campo requerido',text:'La clave es obligatoria.',confirmButtonColor:'#dc3545'});
document.getElementById('clave').focus();return;
Swal.fire({ icon: 'warning', title: 'Campo requerido', text: 'La clave es obligatoria.', confirmButtonColor: '#dc3545'});
document.getElementById('clave').focus();
return;
}
if (clave.length>8) {
Swal.fire({ icon: 'error', title: 'Clave inválida', text: 'La clave no puede tener más de 8 caracteres.', confirmButtonColor: '#dc3545'});
document.getElementById('clave').focus();return;
document.getElementById('clave').focus();
return;
}
if (!nombre) {
Swal.fire({ icon:'error',title:'Campo requerido',text:'El nombre es obligatorio.',confirmButtonColor:'#dc3545'});
document.getElementById('nombre').focus();return;
Swal.fire({ icon: 'warning', title: 'Campo requerido', text: 'El nombre es obligatorio.', confirmButtonColor: '#dc3545'});
document.getElementById('nombre').focus();
return;
}
if (!rfc) {
Swal.fire({ icon:'error',title:'Campo requerido',text:'El RFC es obligatorio.',confirmButtonColor:'#dc3545'});
document.getElementById('rfc').focus();return;
Swal.fire({ icon: 'warning', title: 'Campo requerido', text: 'El RFC es obligatorio.', confirmButtonColor: '#dc3545'});
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;
document.getElementById('rfc').focus();
return;
}
if (curp && !curpRegex.test(curp)) {
Swal.fire({ icon: 'error', title: 'CURP inválido', text: 'El CURP no tiene el formato correcto.', confirmButtonColor: '#dc3545'});
document.getElementById('curp').focus();return;
document.getElementById('curp').focus();
return;
}
if (!telefono) {
Swal.fire({ icon:'error',title:'Campo requerido',text:'El teléfono es obligatorio.',confirmButtonColor:'#dc3545'});
document.getElementById('telefono').focus();return;
Swal.fire({ icon: 'warning', title: 'Campo requerido', text: 'El teléfono es obligatorio.', confirmButtonColor: '#dc3545'});
document.getElementById('telefono').focus();
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. Ejemplo: 123 4567890',confirmButtonColor:'#dc3545'});
document.getElementById('telefono').focus();return;
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).', onfirmButtonColor:'#dc3545'});
document.getElementById('telefono').focus();
return;
}
if (telefono.length>11) {
Swal.fire({ icon: 'error', title: 'Teléfono inválido', text: 'El teléfono no puede tener más de 11 caracteres.', confirmButtonColor: '#dc3545'});
document.getElementById('telefono').focus();return;
document.getElementById('telefono').focus();
return;
}
if (!caat) {
Swal.fire({ icon:'error',title:'Campo requerido',text:'El código caat es obligatorio.',confirmButtonColor:'#dc3545'});
document.getElementById('caat').focus();return;
Swal.fire({ icon: 'warning', title: 'Campo requerido', text: 'El código caat es obligatorio.', confirmButtonColor: '#dc3545'});
document.getElementById('caat').focus();
return;
}
if (!pais) {
Swal.fire({ icon:'error',title:'Campo requerido',text:'Debe seleccionar un país.',confirmButtonColor:'#dc3545'});
document.getElementById('pais').focus();return;
Swal.fire({ icon: 'warning', title: 'Campo requerido', text: 'Debe seleccionar un país.', confirmButtonColor: '#dc3545'});
document.getElementById('pais').focus();
return;
}
if (!entidad) {
Swal.fire({ icon:'error',title:'Campo requerido',text:'Debe seleccionar un estado.',confirmButtonColor:'#dc3545'});
document.getElementById('entidad').focus();return;
Swal.fire({ icon: 'warning', title: 'Campo requerido', text: 'Debe seleccionar un estado.', confirmButtonColor: '#dc3545'});
document.getElementById('entidad').focus();
return;
}
if (!ciudad) {
Swal.fire({ icon:'error',title:'Campo requerido',text:'Debe seleccionar una ciudad.',confirmButtonColor:'#dc3545'});
document.getElementById('ciudad').focus();return;
Swal.fire({ icon: 'warning', title: 'Campo requerido', text: 'Debe seleccionar una ciudad.', confirmButtonColor: '#dc3545'});
document.getElementById('ciudad').focus();
return;
}
if (!domicilio) {
Swal.fire({ icon:'error',title:'Campo requerido',text:'El domicilio es obligatorio.',confirmButtonColor:'#dc3545'});
document.getElementById('domicilio').focus();return;
Swal.fire({ icon: 'warning', title: 'Campo requerido', text: 'El domicilio es obligatorio.', confirmButtonColor: '#dc3545'});
document.getElementById('domicilio').focus();
return;
}
document.getElementById('rfc').value = rfc;
document.getElementById('curp').value = curp;
@@ -252,7 +266,6 @@
hidden.value = '0';
}
});
</script>
</body>

View File

@@ -143,7 +143,7 @@
// Validaciones de campos obligatorios
if (!clave) {
Swal.fire({ icon: 'error', title: 'Campo requerido', text: 'La clave es obligatoria.', confirmButtonColor: '#dc3545' });
Swal.fire({ icon: 'warning', title: 'Campo requerido', text: 'La clave es obligatoria.', confirmButtonColor: '#dc3545' });
document.getElementById('clave').focus();
return;
}
@@ -153,12 +153,12 @@
return;
}
if (!nombre) {
Swal.fire({ icon: 'error', title: 'Campo requerido', text: 'El nombre es obligatorio.', confirmButtonColor: '#dc3545' });
Swal.fire({ icon: 'warning', title: 'Campo requerido', text: 'El nombre es obligatorio.', confirmButtonColor: '#dc3545' });
document.getElementById('nombre').focus();
return;
}
if (!rfc) {
Swal.fire({ icon: 'error', title: 'Campo requerido', text: 'El RFC es obligatorio.', confirmButtonColor: '#dc3545' });
Swal.fire({ icon: 'warning', title: 'Campo requerido', text: 'El RFC es obligatorio.', confirmButtonColor: '#dc3545' });
document.getElementById('rfc').focus();
return;
}
@@ -169,13 +169,12 @@
}
// Validación de CURP (solo si se ingresó)
if (curp && !curpRegex.test(curp)) {
Swal.fire({ icon: 'error', title: 'CURP inválido', text: 'El CURP no tiene el formato correcto.', confirmButtonColor: '#dc3545'
});
Swal.fire({ icon: 'error', title: 'CURP inválido', text: 'El CURP no tiene el formato correcto.', confirmButtonColor: '#dc3545' });
document.getElementById('curp').focus();
return;
}
if (!telefono) {
Swal.fire({ icon: 'error', title: 'Campo requerido', text: 'El teléfono es obligatorio.', confirmButtonColor: '#dc3545' });
Swal.fire({ icon: 'warning', title: 'Campo requerido', text: 'El teléfono es obligatorio.', confirmButtonColor: '#dc3545' });
document.getElementById('telefono').focus();
return;
}
@@ -190,27 +189,27 @@
return;
}
if (!caat) {
Swal.fire({ icon: 'error', title: 'Campo requerido', text: 'El código caat es obligatorio.', confirmButtonColor: '#dc3545' });
Swal.fire({ icon: 'warning', title: 'Campo requerido', text: 'El código caat es obligatorio.', confirmButtonColor: '#dc3545' });
document.getElementById('caat').focus();
return;
}
if (!pais) {
Swal.fire({ icon: 'error', title: 'Campo requerido', text: 'Debe seleccionar un país.', confirmButtonColor: '#dc3545' });
Swal.fire({ icon: 'warning', title: 'Campo requerido', text: 'Debe seleccionar un país.', confirmButtonColor: '#dc3545' });
document.getElementById('pais').focus();
return;
}
if (!entidad) {
Swal.fire({ icon: 'error', title: 'Campo requerido', text: 'Debe seleccionar un estado.', confirmButtonColor: '#dc3545' });
Swal.fire({ icon: 'warning', title: 'Campo requerido', text: 'Debe seleccionar un estado.', confirmButtonColor: '#dc3545' });
document.getElementById('entidad').focus();
return;
}
if (!ciudad) {
Swal.fire({ icon: 'error', title: 'Campo requerido', text: 'Debe seleccionar una ciudad.', confirmButtonColor: '#dc3545' });
Swal.fire({ icon: 'warning', title: 'Campo requerido', text: 'Debe seleccionar una ciudad.', confirmButtonColor: '#dc3545' });
document.getElementById('ciudad').focus();
return;
}
if (!domicilio) {
Swal.fire({ icon: 'error', title: 'Campo requerido', text: 'El domicilio es obligatorio.', confirmButtonColor: '#dc3545' });
Swal.fire({ icon: 'warning', title: 'Campo requerido', text: 'El domicilio es obligatorio.', confirmButtonColor: '#dc3545' });
document.getElementById('domicilio').focus();
return;
}

View File

@@ -0,0 +1,123 @@
<?php include __DIR__ . '/../partials/sidebar_importador.php'; ?>
<!DOCTYPE html>
<html lang="es">
<head>
<meta charset="UTF-8">
<title>Dashboard | Administrador</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">
<script src="https://cdn.jsdelivr.net/npm/sweetalert2@11"></script>
<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>
<style>
body { font-family: 'Segoe UI', sans-serif; background-color: #f4f6f9; }
.sidebar { width: 220px; height: 100vh; background-color: #343a40; position: fixed; top: 0; left: 0; padding-top: 56px; z-index: 1040; }
.sidebar .nav-link { font-weight: normal; color: white; transition: all 0.3s ease; }
.sidebar .nav-link:hover,
.sidebar .nav-link.active { background-color: #495057; color: #fff; }
.content { margin-top: 56px; padding: 40px 20px; position: relative; z-index: 1; background-color: #f4f6f9; transition: margin-left 0.3s ease; }
.navbar { position: fixed; top: 0; width: 100%; z-index: 1050; }
/* A partir de dispositivos medianos (>=768px), deja espacio lateral */
@media (min-width: 768px) { .content { margin-left: 250px; /* Ancho del sidebar */ } }
/* En móviles, sin margen lateral */
@media (max-width: 767.98px) {
.content { margin-left: 0; }
.sidebar .nav-link { font-weight: normal; color: #343a40; background-color: transparent; }
.sidebar .nav-link:hover,
.sidebar .nav-link.active { background-color: #e9ecef; color: #212529; }
}
.card { border-radius: 12px; }
.table thead th { background: #343a40; color: #fff; }
</style>
</head>
<body>
<!-- 📄 CONTENIDO -->
<div class="content">
<h4 class="mb-4"> Nueva Vinculación</h4>
<!-- TABLA DE AGENCIAS -->
<div class="card p-3 shadow-sm">
<h5 class="mb-3">🔗 Solicitar Vinculación con Agencias Aduanales</h5>
<div class="table-responsive">
<table class="table table-striped table-hover align-middle" id="tabla-solicitar-vinculaciones">
<thead>
<tr>
<th>#</th>
<th>Nombre</th>
<th>RFC</th>
<th>Correo</th>
<th>Teléfono</th>
<th>Dirección</th>
<th>Fecha de Registro</th>
<th>Acciones</th>
</tr>
</thead>
<tbody>
<?php if (!empty($agencias)): ?>
<?php foreach ($agencias as $ag): ?>
<tr>
<td><?= $ag['id_agencia'] ?></td>
<td><?= htmlspecialchars(decrypt($ag['nombre_agencia'])) ?></td>
<td><?= htmlspecialchars($ag['rfc_agencia']) ?></td>
<td><?= htmlspecialchars(decrypt($ag['email'])) ?></td>
<td><?= htmlspecialchars($ag['telefono']) ?></td>
<td><?= htmlspecialchars($ag['direccion']) ?></td>
<td><?= $ag['creado_en'] ? $ag['creado_en']->format('Y-m-d H:i') : '' ?></td>
<td>
<?php if (!empty($ag['estado_solicitud']) && $ag['estado_solicitud'] === 'PENDIENTE'): ?>
<a href="/IMPORTADORES/vinculaciones/cancelar?id=<?= $ag['id_agencia'] ?>" class="btn btn-sm btn-danger">Cancelar solicitud</a>
<?php else: ?>
<a href="/IMPORTADORES/vinculaciones/vincular?id=<?= $ag['id_agencia'] ?>" class="btn btn-sm btn-success">Vincular</a>
<?php endif; ?>
</td>
</tr>
<?php endforeach; ?>
<?php else: ?>
<tr>
<td colspan="8" class="text-center">No hay agencias registradas aún.</td>
</tr>
<?php endif; ?>
</tbody>
</table>
</div>
</div>
</div>
<script>
$(document).ready(function () {
$('#tabla-solicitar-vinculaciones').DataTable({
order: [],
language: {
url: 'https://cdn.datatables.net/plug-ins/1.13.4/i18n/es-ES.json'
}
});
});
</script>
<?php if (isset($_GET['success'])): ?>
<script>
<?php if ($_GET['success'] === 'sended_request'): ?>
Swal.fire({ icon: 'success', title: 'Solicitud enviada',
text: 'La solicitud de vinculación fue enviada correctamente.',
confirmButtonColor: '#198754' });
<?php elseif ($_GET['success'] === 'cancelled'): ?>
Swal.fire({ icon: 'success', title: 'Solicitud cancelada', text: 'La solicitud fue cancelada correctamente.', confirmButtonColor: '#198754' });
<?php endif; ?>
</script>
<?php endif; ?>
<?php if (isset($_GET['error'])): ?>
<script>
<?php if ($_GET['error'] === 'failed_request'): ?>
Swal.fire({ icon: 'error', title: 'Solicitud fallida', text: 'No se pudo enviar la solicitud.', confirmButtonColor: '#dc3545' });
<?php elseif ($_GET['error'] === 'pending_request'): ?>
Swal.fire({ icon: 'error', title: 'Solicitud pendiente', text: 'Ya tienes una solicitud pendiente.', confirmButtonColor: '#dc3545' });
<?php endif; ?>
</script>
<?php endif; ?>
</body>
</html>

View File

@@ -0,0 +1,111 @@
<?php include __DIR__ . '/../partials/sidebar_importador.php'; ?>
<!DOCTYPE html>
<html lang="es">
<head>
<meta charset="UTF-8">
<title>Dashboard | Administrador</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">
<script src="https://cdn.jsdelivr.net/npm/sweetalert2@11"></script>
<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>
<style>
body { font-family: 'Segoe UI', sans-serif; background-color: #f4f6f9; }
.sidebar { width: 220px; height: 100vh; background-color: #343a40; position: fixed; top: 0; left: 0; padding-top: 56px; z-index: 1040; }
.sidebar .nav-link { font-weight: normal; color: white; transition: all 0.3s ease; }
.sidebar .nav-link:hover,
.sidebar .nav-link.active { background-color: #495057; color: #fff; }
.content { margin-top: 56px; padding: 40px 20px; position: relative; z-index: 1; background-color: #f4f6f9; transition: margin-left 0.3s ease; }
.navbar { position: fixed; top: 0; width: 100%; z-index: 1050; }
/* A partir de dispositivos medianos (>=768px), deja espacio lateral */
@media (min-width: 768px) { .content { margin-left: 250px; /* Ancho del sidebar */ } }
/* En móviles, sin margen lateral */
@media (max-width: 767.98px) {
.content { margin-left: 0; }
.sidebar .nav-link { font-weight: normal; color: #343a40; background-color: transparent; }
.sidebar .nav-link:hover,
.sidebar .nav-link.active { background-color: #e9ecef; color: #212529; }
}
.card { border-radius: 12px; }
.table thead th { background: #343a40; color: #fff; }
</style>
</head>
<body>
<!-- 📄 CONTENIDO -->
<div class="content">
<h4 class="mb-4">🔗 Mis vinculaciones</h4>
<!-- TABLA DE VINCULACIÓN USUARIO -->
<div class="card p-3 shadow-sm">
<h5 class="mb-3">🏪 Agencia Vinculadas</h5>
<div class="table-responsive">
<table class="table table-striped table-hover align-middle" id="tabla-vinculaciones-usuario">
<thead>
<tr>
<th>#</th>
<th>Nombre</th>
<th>RFC</th>
<th>Teléfono</th>
<th>Dirección</th>
<th>Fecha de Vinculación</th>
<th>Acciones</th>
</tr>
</thead>
<tbody>
<?php if (!empty($vinculaciones)): ?>
<?php foreach ($vinculaciones as $v): ?>
<tr>
<td><?= htmlspecialchars($v['id_relacion'] ?? '') ?></td>
<td><?= htmlspecialchars(decrypt($v['nombre_agencia'] ?? '')) ?></td>
<td><?= htmlspecialchars($v['rfc_agencia'] ?? '') ?></td>
<td><?= htmlspecialchars($v['telefono'] ?? '') ?></td>
<td><?= htmlspecialchars($v['direccion'] ?? '') ?></td>
<td><?= !empty($v['fecha_vinculacion']) ? $v['fecha_vinculacion']->format('Y-m-d H:i') : '' ?></td>
<td>
<a href="/IMPORTADORES/vinculaciones/desvincularUsuario?id=<?= $v['id_relacion'] ?>" class="btn btn-sm btn-danger">Desvincular</a>
</td>
</tr>
<?php endforeach; ?>
<?php endif; ?>
</tbody>
</table>
</div>
</div>
</div>
<script>
$(document).ready(function () {
$('#tabla-vinculaciones-usuario').DataTable({
order: [],
language: {
url: 'https://cdn.datatables.net/plug-ins/1.13.4/i18n/es-ES.json'
}
});
});
</script>
<?php if (isset($_GET['success'])): ?>
<script>
<?php if ($_GET['success'] === 'unlinked'): ?>
Swal.fire({ icon: 'success', title: 'Usuario desvinculado',
text: 'El usuario se ha desvinculado correctamente.',
confirmButtonColor: '#198754' });
<?php endif; ?>
</script>
<?php endif; ?>
<?php if (isset($_GET['error'])): ?>
<script>
<?php if ($_GET['error'] === 'unlinked_failed'): ?>
Swal.fire({ icon: 'error', title: 'Desvinculación fallida', text: 'No se ha podido desvincular el usuario.', confirmButtonColor: '#dc3545' });
<?php elseif ($_GET['error'] === 'invalid_id'): ?>
Swal.fire({ icon: 'error', title: 'ID inválido', text: 'El identificador de la relación no es válido.', confirmButtonColor: '#dc3545' });
<?php endif; ?>
</script>
<?php endif; ?>
</body>
</html>