grales
This commit is contained in:
558
app/controllers/catalogo_pedimentos.php
Normal file
558
app/controllers/catalogo_pedimentos.php
Normal file
@@ -0,0 +1,558 @@
|
||||
<?php
|
||||
require_once __DIR__ . '/../helpers/session.php';
|
||||
require_once __DIR__ . '/../../config/database.php';
|
||||
require_once __DIR__ . '/../helpers/crypto.php';
|
||||
|
||||
function index()
|
||||
{
|
||||
if (!($_SESSION['usuario_id'] ?? false)) {
|
||||
header('Location: /IMPORTADORES/login');
|
||||
exit;
|
||||
}
|
||||
|
||||
include __DIR__ . '/../../views/catalogo_pedimentos/index.php';
|
||||
}
|
||||
|
||||
function lista()
|
||||
{
|
||||
if (!($_SESSION['usuario_id'] ?? false)) {
|
||||
header('Location: /IMPORTADORES/login');
|
||||
exit;
|
||||
}
|
||||
|
||||
include __DIR__ . '/../../views/catalogo_pedimentos/lista.php';
|
||||
}
|
||||
|
||||
function crear()
|
||||
{
|
||||
if (!($_SESSION['usuario_id'] ?? false)) {
|
||||
header('Location: /IMPORTADORES/login');
|
||||
exit;
|
||||
}
|
||||
|
||||
$conn = getConnection();
|
||||
$id_usuario = $_SESSION['usuario_id'];
|
||||
|
||||
// Obtener información del importador
|
||||
$sqlImportador = "SELECT rfc, nombre, correo FROM informacion_general WHERE id_usuario = ?";
|
||||
$stmtImportador = sqlsrv_query($conn, $sqlImportador, [$id_usuario]);
|
||||
|
||||
if ($stmtImportador === false) {
|
||||
die("❌ Error al consultar información del importador: " . print_r(sqlsrv_errors(), true));
|
||||
}
|
||||
|
||||
$importador = sqlsrv_fetch_array($stmtImportador, SQLSRV_FETCH_ASSOC);
|
||||
|
||||
// Obtener claves de pedimentos activas del usuario
|
||||
$sqlClaves = "SELECT codigo, descripcion FROM claves_pedimentos_usuario
|
||||
WHERE id_usuario = ? AND activo = 1 AND tipo_operacion = 'importacion'
|
||||
ORDER BY codigo ASC";
|
||||
$stmtClaves = sqlsrv_query($conn, $sqlClaves, [$id_usuario]);
|
||||
|
||||
$claves_pedimentos = [];
|
||||
if ($stmtClaves !== false) {
|
||||
while ($row = sqlsrv_fetch_array($stmtClaves, SQLSRV_FETCH_ASSOC)) {
|
||||
$claves_pedimentos[] = $row;
|
||||
}
|
||||
}
|
||||
|
||||
include __DIR__ . '/../../views/catalogo_pedimentos/crear.php';
|
||||
}
|
||||
|
||||
function guardar()
|
||||
{
|
||||
if (!($_SESSION['usuario_id'] ?? false)) {
|
||||
die("⚠️ No autorizado.");
|
||||
}
|
||||
|
||||
$conn = getConnection();
|
||||
$id_usuario = $_SESSION['usuario_id'];
|
||||
|
||||
// Obtener información del importador automáticamente
|
||||
$sqlImportador = "SELECT rfc, nombre FROM informacion_general WHERE id_usuario = ?";
|
||||
$stmtImportador = sqlsrv_query($conn, $sqlImportador, [$id_usuario]);
|
||||
|
||||
if ($stmtImportador === false) {
|
||||
die("❌ Error al consultar información del importador: " . print_r(sqlsrv_errors(), true));
|
||||
}
|
||||
|
||||
$importador = sqlsrv_fetch_array($stmtImportador, SQLSRV_FETCH_ASSOC);
|
||||
|
||||
if (!$importador) {
|
||||
die("❌ Error: No se encontró información del importador. Configure su información general primero.");
|
||||
}
|
||||
|
||||
// Obtener datos del formulario
|
||||
$pedimento = trim($_POST['pedimento'] ?? '');
|
||||
$clave_ped = trim($_POST['clave_ped'] ?? '');
|
||||
$tipo_operacion = $_POST['tipo_operacion'] ?? 1; // Por defecto importación
|
||||
$tipo_pedimento = $_POST['tipo_pedimento'] ?? 1; // Por defecto normal
|
||||
$regimen = trim($_POST['regimen'] ?? '');
|
||||
$destino = trim($_POST['destino'] ?? '');
|
||||
|
||||
// Fechas
|
||||
$fecha_pedimento = $_POST['fecha_pedimento'] ?? null;
|
||||
$fecha_inicio = $_POST['fecha_inicio'] ?? null;
|
||||
$fecha_final = $_POST['fecha_final'] ?? null;
|
||||
|
||||
// Información adicional
|
||||
$archivo_final_previo = trim($_POST['archivo_final_previo'] ?? '');
|
||||
$acuse_cons = trim($_POST['acuse_cons'] ?? '');
|
||||
$tipo = trim($_POST['tipo'] ?? '');
|
||||
$status = isset($_POST['status']) ? (int)$_POST['status'] : 1;
|
||||
|
||||
// Validaciones
|
||||
if (empty($pedimento)) {
|
||||
die("❌ El número de pedimento es obligatorio.");
|
||||
}
|
||||
|
||||
if (empty($clave_ped)) {
|
||||
die("❌ La clave de pedimento es obligatoria.");
|
||||
}
|
||||
|
||||
// Convertir fechas a formato YYYYMMDD si están presentes
|
||||
$fecha_pedimento_int = null;
|
||||
$fecha_inicio_int = null;
|
||||
$fecha_final_int = null;
|
||||
|
||||
if ($fecha_pedimento) {
|
||||
$fecha_pedimento_int = (int)str_replace('-', '', $fecha_pedimento);
|
||||
}
|
||||
if ($fecha_inicio) {
|
||||
$fecha_inicio_int = (int)str_replace('-', '', $fecha_inicio);
|
||||
}
|
||||
if ($fecha_final) {
|
||||
$fecha_final_int = (int)str_replace('-', '', $fecha_final);
|
||||
}
|
||||
|
||||
$sql = "INSERT INTO PREVIOS_COMPARTIDOS_WS
|
||||
(Pedimento, ClienteRFC, ClienteNombre, ClavePed, TipoOperacion, TipoPedimento,
|
||||
Regimen, Destino, FechaPedimento, FechaInicio, FechaFinal,
|
||||
ArchivoFinalPrevio, AcuseCons, Tipo, Status, Timestamp, id_usuario)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, GETDATE(), ?)";
|
||||
|
||||
$params = [
|
||||
$pedimento,
|
||||
$importador['rfc'],
|
||||
$importador['nombre'],
|
||||
$clave_ped,
|
||||
$tipo_operacion,
|
||||
$tipo_pedimento,
|
||||
$regimen,
|
||||
$destino,
|
||||
$fecha_pedimento_int,
|
||||
$fecha_inicio_int,
|
||||
$fecha_final_int,
|
||||
$archivo_final_previo,
|
||||
$acuse_cons,
|
||||
$tipo,
|
||||
$status,
|
||||
$id_usuario
|
||||
];
|
||||
|
||||
$stmt = sqlsrv_query($conn, $sql, $params);
|
||||
|
||||
if ($stmt === false) {
|
||||
die("❌ Error al guardar: " . print_r(sqlsrv_errors(), true));
|
||||
}
|
||||
|
||||
header('Location: /IMPORTADORES/catalogo_pedimentos/lista?created=ok');
|
||||
exit;
|
||||
}
|
||||
|
||||
function editar()
|
||||
{
|
||||
if (!($_SESSION['usuario_id'] ?? false)) {
|
||||
header('Location: /IMPORTADORES/login');
|
||||
exit;
|
||||
}
|
||||
|
||||
$id = $_GET['id'] ?? null;
|
||||
|
||||
if (!$id || !is_numeric($id)) {
|
||||
die("❌ ID inválido.");
|
||||
}
|
||||
|
||||
$conn = getConnection();
|
||||
$id_usuario = $_SESSION['usuario_id'];
|
||||
|
||||
// Obtener información del importador
|
||||
$sqlImportador = "SELECT rfc, nombre FROM informacion_general WHERE id_usuario = ?";
|
||||
$stmtImportador = sqlsrv_query($conn, $sqlImportador, [$id_usuario]);
|
||||
|
||||
if ($stmtImportador === false) {
|
||||
die("❌ Error al consultar información del importador: " . print_r(sqlsrv_errors(), true));
|
||||
}
|
||||
|
||||
$importador = sqlsrv_fetch_array($stmtImportador, SQLSRV_FETCH_ASSOC);
|
||||
|
||||
// Obtener claves de pedimentos activas del usuario
|
||||
$sqlClaves = "SELECT codigo, descripcion FROM claves_pedimentos_usuario
|
||||
WHERE id_usuario = ? AND activo = 1 AND tipo_operacion = 'importacion'
|
||||
ORDER BY codigo ASC";
|
||||
$stmtClaves = sqlsrv_query($conn, $sqlClaves, [$id_usuario]);
|
||||
|
||||
$claves_pedimentos = [];
|
||||
if ($stmtClaves !== false) {
|
||||
while ($row = sqlsrv_fetch_array($stmtClaves, SQLSRV_FETCH_ASSOC)) {
|
||||
$claves_pedimentos[] = $row;
|
||||
}
|
||||
}
|
||||
|
||||
// Obtener datos del pedimento
|
||||
$sql = "SELECT * FROM PREVIOS_COMPARTIDOS_WS WHERE IdPrevio = ?";
|
||||
$stmt = sqlsrv_query($conn, $sql, [$id]);
|
||||
|
||||
if ($stmt === false) {
|
||||
die("❌ Error en consulta: " . print_r(sqlsrv_errors(), true));
|
||||
}
|
||||
|
||||
$previo = sqlsrv_fetch_array($stmt, SQLSRV_FETCH_ASSOC);
|
||||
|
||||
if (!$previo) {
|
||||
die("❌ Pedimento no encontrado.");
|
||||
}
|
||||
|
||||
include __DIR__ . '/../../views/catalogo_pedimentos/editar.php';
|
||||
}
|
||||
|
||||
function actualizar()
|
||||
{
|
||||
if (!($_SESSION['usuario_id'] ?? false)) {
|
||||
die("⚠️ No autorizado.");
|
||||
}
|
||||
|
||||
$conn = getConnection();
|
||||
$id_usuario = $_SESSION['usuario_id'];
|
||||
|
||||
// Obtener información del importador automáticamente
|
||||
$sqlImportador = "SELECT rfc, nombre FROM informacion_general WHERE id_usuario = ?";
|
||||
$stmtImportador = sqlsrv_query($conn, $sqlImportador, [$id_usuario]);
|
||||
|
||||
if ($stmtImportador === false) {
|
||||
die("❌ Error al consultar información del importador: " . print_r(sqlsrv_errors(), true));
|
||||
}
|
||||
|
||||
$importador = sqlsrv_fetch_array($stmtImportador, SQLSRV_FETCH_ASSOC);
|
||||
|
||||
if (!$importador) {
|
||||
die("❌ Error: No se encontró información del importador.");
|
||||
}
|
||||
|
||||
$id_previo = $_POST['id_previo'] ?? null;
|
||||
$pedimento = trim($_POST['pedimento'] ?? '');
|
||||
$clave_ped = trim($_POST['clave_ped'] ?? '');
|
||||
$tipo_operacion = $_POST['tipo_operacion'] ?? 1;
|
||||
$tipo_pedimento = $_POST['tipo_pedimento'] ?? 1;
|
||||
$regimen = trim($_POST['regimen'] ?? '');
|
||||
$destino = trim($_POST['destino'] ?? '');
|
||||
|
||||
// Fechas
|
||||
$fecha_pedimento = $_POST['fecha_pedimento'] ?? null;
|
||||
$fecha_inicio = $_POST['fecha_inicio'] ?? null;
|
||||
$fecha_final = $_POST['fecha_final'] ?? null;
|
||||
|
||||
// Información adicional
|
||||
$archivo_final_previo = trim($_POST['archivo_final_previo'] ?? '');
|
||||
$acuse_cons = trim($_POST['acuse_cons'] ?? '');
|
||||
$tipo = trim($_POST['tipo'] ?? '');
|
||||
$status = isset($_POST['status']) ? (int)$_POST['status'] : 1;
|
||||
|
||||
if (!$id_previo || !is_numeric($id_previo)) {
|
||||
die("❌ ID inválido.");
|
||||
}
|
||||
|
||||
// Validaciones
|
||||
if (empty($pedimento)) {
|
||||
die("❌ El número de pedimento es obligatorio.");
|
||||
}
|
||||
|
||||
if (empty($clave_ped)) {
|
||||
die("❌ La clave de pedimento es obligatoria.");
|
||||
}
|
||||
|
||||
// Convertir fechas a formato YYYYMMDD si están presentes
|
||||
$fecha_pedimento_int = null;
|
||||
$fecha_inicio_int = null;
|
||||
$fecha_final_int = null;
|
||||
|
||||
if ($fecha_pedimento) {
|
||||
$fecha_pedimento_int = (int)str_replace('-', '', $fecha_pedimento);
|
||||
}
|
||||
if ($fecha_inicio) {
|
||||
$fecha_inicio_int = (int)str_replace('-', '', $fecha_inicio);
|
||||
}
|
||||
if ($fecha_final) {
|
||||
$fecha_final_int = (int)str_replace('-', '', $fecha_final);
|
||||
}
|
||||
|
||||
$sql = "UPDATE PREVIOS_COMPARTIDOS_WS SET
|
||||
Pedimento = ?, ClienteRFC = ?, ClienteNombre = ?, ClavePed = ?,
|
||||
TipoOperacion = ?, TipoPedimento = ?, Regimen = ?, Destino = ?,
|
||||
FechaPedimento = ?, FechaInicio = ?, FechaFinal = ?,
|
||||
ArchivoFinalPrevio = ?, AcuseCons = ?, Tipo = ?, Status = ?
|
||||
WHERE IdPrevio = ?";
|
||||
|
||||
$params = [
|
||||
$pedimento,
|
||||
$importador['rfc'],
|
||||
$importador['nombre'],
|
||||
$clave_ped,
|
||||
$tipo_operacion,
|
||||
$tipo_pedimento,
|
||||
$regimen,
|
||||
$destino,
|
||||
$fecha_pedimento_int,
|
||||
$fecha_inicio_int,
|
||||
$fecha_final_int,
|
||||
$archivo_final_previo,
|
||||
$acuse_cons,
|
||||
$tipo,
|
||||
$status,
|
||||
$id_previo
|
||||
];
|
||||
|
||||
$stmt = sqlsrv_query($conn, $sql, $params);
|
||||
|
||||
if ($stmt === false) {
|
||||
die("❌ Error al actualizar: " . print_r(sqlsrv_errors(), true));
|
||||
}
|
||||
|
||||
header('Location: /IMPORTADORES/catalogo_pedimentos/lista?updated=ok');
|
||||
exit;
|
||||
}
|
||||
|
||||
function eliminar()
|
||||
{
|
||||
if (!($_SESSION['usuario_id'] ?? false)) {
|
||||
header('Location: /IMPORTADORES/login');
|
||||
exit;
|
||||
}
|
||||
|
||||
$id = $_GET['id'] ?? null;
|
||||
|
||||
if (!$id || !is_numeric($id)) {
|
||||
die("❌ ID inválido.");
|
||||
}
|
||||
|
||||
$conn = getConnection();
|
||||
|
||||
$sql = "DELETE FROM PREVIOS_COMPARTIDOS_WS WHERE IdPrevio = ?";
|
||||
$stmt = sqlsrv_query($conn, $sql, [$id]);
|
||||
|
||||
if ($stmt === false) {
|
||||
die("❌ Error al eliminar: " . print_r(sqlsrv_errors(), true));
|
||||
}
|
||||
|
||||
header('Location: /IMPORTADORES/catalogo_pedimentos/lista?deleted=ok');
|
||||
exit;
|
||||
}
|
||||
|
||||
function ajax_lista()
|
||||
{
|
||||
if (!($_SESSION['usuario_id'] ?? false)) {
|
||||
http_response_code(403);
|
||||
echo json_encode([]);
|
||||
exit;
|
||||
}
|
||||
|
||||
$id_usuario = $_SESSION['usuario_id'];
|
||||
$conn = getConnection();
|
||||
|
||||
// Obtener RFC del importador para filtrar solo sus pedimentos
|
||||
$sqlImportador = "SELECT rfc FROM informacion_general WHERE id_usuario = ?";
|
||||
$stmtImportador = sqlsrv_query($conn, $sqlImportador, [$id_usuario]);
|
||||
|
||||
if ($stmtImportador === false) {
|
||||
echo json_encode([
|
||||
"draw" => intval($_GET['draw'] ?? 0),
|
||||
"recordsTotal" => 0,
|
||||
"recordsFiltered" => 0,
|
||||
"data" => [],
|
||||
"error" => "Error al consultar información del importador"
|
||||
]);
|
||||
exit;
|
||||
}
|
||||
|
||||
$importador = sqlsrv_fetch_array($stmtImportador, SQLSRV_FETCH_ASSOC);
|
||||
|
||||
if (!$importador) {
|
||||
echo json_encode([
|
||||
"draw" => intval($_GET['draw'] ?? 0),
|
||||
"recordsTotal" => 0,
|
||||
"recordsFiltered" => 0,
|
||||
"data" => []
|
||||
]);
|
||||
exit;
|
||||
}
|
||||
|
||||
// Parámetros de DataTables
|
||||
$draw = intval($_GET['draw'] ?? 0);
|
||||
$start = intval($_GET['start'] ?? 0);
|
||||
$length = intval($_GET['length'] ?? 10);
|
||||
$search = $_GET['search']['value'] ?? '';
|
||||
|
||||
// Total registros sin filtro
|
||||
$sqlTotal = "SELECT COUNT(*) AS total FROM PREVIOS_COMPARTIDOS_WS WHERE ClienteRFC = ?";
|
||||
$stmt = sqlsrv_query($conn, $sqlTotal, [$importador['rfc']]);
|
||||
|
||||
if ($stmt === false) {
|
||||
echo json_encode([
|
||||
"draw" => $draw,
|
||||
"recordsTotal" => 0,
|
||||
"recordsFiltered" => 0,
|
||||
"data" => [],
|
||||
"error" => "Error al contar registros totales"
|
||||
]);
|
||||
exit;
|
||||
}
|
||||
|
||||
$row = sqlsrv_fetch_array($stmt, SQLSRV_FETCH_ASSOC);
|
||||
$recordsTotal = (int)($row['total'] ?? 0);
|
||||
|
||||
// Construir condiciones de filtro
|
||||
$where = "ClienteRFC = ?";
|
||||
$params = [$importador['rfc']];
|
||||
|
||||
if ($search !== '') {
|
||||
$where .= " AND (Pedimento LIKE ? OR ClienteNombre LIKE ? OR ClavePed LIKE ?)";
|
||||
$like = "%{$search}%";
|
||||
$params = array_merge($params, [$like, $like, $like]);
|
||||
}
|
||||
|
||||
// Total registros filtrados
|
||||
$sqlFiltered = "SELECT COUNT(*) AS total FROM PREVIOS_COMPARTIDOS_WS WHERE $where";
|
||||
$stmtF = sqlsrv_query($conn, $sqlFiltered, $params);
|
||||
|
||||
if ($stmtF === false) {
|
||||
echo json_encode([
|
||||
"draw" => $draw,
|
||||
"recordsTotal" => $recordsTotal,
|
||||
"recordsFiltered" => 0,
|
||||
"data" => [],
|
||||
"error" => "Error al contar registros filtrados"
|
||||
]);
|
||||
exit;
|
||||
}
|
||||
|
||||
$rowF = sqlsrv_fetch_array($stmtF, SQLSRV_FETCH_ASSOC);
|
||||
$recordsFiltered = (int)($rowF['total'] ?? 0);
|
||||
|
||||
// Datos de la página
|
||||
$sqlData = "SELECT IdPrevio, Pedimento, ClienteRFC, ClienteNombre, Timestamp, Status
|
||||
FROM PREVIOS_COMPARTIDOS_WS
|
||||
WHERE $where
|
||||
ORDER BY Timestamp DESC
|
||||
OFFSET ? ROWS FETCH NEXT ? ROWS ONLY";
|
||||
$params[] = $start;
|
||||
$params[] = $length;
|
||||
|
||||
$stmtD = sqlsrv_query($conn, $sqlData, $params);
|
||||
|
||||
$data = [];
|
||||
if ($stmtD !== false) {
|
||||
while ($r = sqlsrv_fetch_array($stmtD, SQLSRV_FETCH_ASSOC)) {
|
||||
$timestamp = $r['Timestamp'] instanceof DateTime ? $r['Timestamp']->format('Y-m-d H:i:s') : '';
|
||||
$status_text = $r['Status'] == 1 ? 'Activo' : 'Inactivo';
|
||||
|
||||
$data[] = [
|
||||
$r['IdPrevio'],
|
||||
$r['Pedimento'],
|
||||
$r['ClienteRFC'],
|
||||
$r['ClienteNombre'],
|
||||
$timestamp,
|
||||
$status_text
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
$response = [
|
||||
"draw" => $draw,
|
||||
"recordsTotal" => $recordsTotal,
|
||||
"recordsFiltered" => $recordsFiltered,
|
||||
"data" => $data
|
||||
];
|
||||
|
||||
header('Content-Type: application/json; charset=UTF-8');
|
||||
echo json_encode($response, JSON_UNESCAPED_UNICODE);
|
||||
exit;
|
||||
}
|
||||
|
||||
function buscar_pedimentos()
|
||||
{
|
||||
if (!($_SESSION['usuario_id'] ?? false)) {
|
||||
http_response_code(403);
|
||||
echo json_encode([]);
|
||||
exit;
|
||||
}
|
||||
|
||||
$id_usuario = $_SESSION['usuario_id'];
|
||||
$query = trim($_GET['q'] ?? '');
|
||||
$limit = intval($_GET['limit'] ?? 10); // ✅ NUEVO: Parámetro limit con valor por defecto
|
||||
|
||||
// ✅ MODIFICADO: Para el panel de referencia, si no hay query, traer los más recientes
|
||||
if (empty($query)) {
|
||||
// Si no hay query, obtener los pedimentos más recientes para el panel de referencia
|
||||
$whereCondition = "ClienteRFC = ? AND Status = 1";
|
||||
$searchParams = [];
|
||||
} else {
|
||||
// Si hay query, mantener la lógica original de búsqueda
|
||||
if (strlen($query) < 3) {
|
||||
echo json_encode([]);
|
||||
exit;
|
||||
}
|
||||
$whereCondition = "ClienteRFC = ? AND (Pedimento LIKE ? OR ClienteNombre LIKE ? OR ClavePed LIKE ?) AND Status = 1";
|
||||
$like = "%{$query}%";
|
||||
$searchParams = [$like, $like, $like];
|
||||
}
|
||||
|
||||
$conn = getConnection();
|
||||
|
||||
// Obtener RFC del usuario para filtrar solo sus pedimentos
|
||||
$sqlImportador = "SELECT rfc FROM informacion_general WHERE id_usuario = ?";
|
||||
$stmtImportador = sqlsrv_query($conn, $sqlImportador, [$id_usuario]);
|
||||
|
||||
if ($stmtImportador === false) {
|
||||
echo json_encode([]);
|
||||
exit;
|
||||
}
|
||||
|
||||
$importador = sqlsrv_fetch_array($stmtImportador, SQLSRV_FETCH_ASSOC);
|
||||
|
||||
if (!$importador) {
|
||||
echo json_encode([]);
|
||||
exit;
|
||||
}
|
||||
|
||||
// ✅ MODIFICADO: Query dinámico con límite configurable
|
||||
$sql = "SELECT TOP {$limit} IdPrevio, Pedimento, ClienteRFC, ClienteNombre, ClavePed, Timestamp
|
||||
FROM PREVIOS_COMPARTIDOS_WS
|
||||
WHERE {$whereCondition}
|
||||
ORDER BY Timestamp DESC";
|
||||
|
||||
$params = array_merge([$importador['rfc']], $searchParams);
|
||||
$stmt = sqlsrv_query($conn, $sql, $params);
|
||||
|
||||
$pedimentos = [];
|
||||
if ($stmt !== false) {
|
||||
while ($row = sqlsrv_fetch_array($stmt, SQLSRV_FETCH_ASSOC)) {
|
||||
$fecha_formateada = '';
|
||||
if ($row['Timestamp'] instanceof DateTime) {
|
||||
$fecha_formateada = $row['Timestamp']->format('d/m/Y');
|
||||
}
|
||||
|
||||
$pedimentos[] = [
|
||||
'IdPrevio' => $row['IdPrevio'],
|
||||
'Pedimento' => $row['Pedimento'],
|
||||
'ClienteRFC' => $row['ClienteRFC'],
|
||||
'ClienteNombre' => $row['ClienteNombre'],
|
||||
'ClavePed' => $row['ClavePed'],
|
||||
'fecha_formateada' => $fecha_formateada
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
header('Content-Type: application/json; charset=UTF-8');
|
||||
echo json_encode($pedimentos, JSON_UNESCAPED_UNICODE);
|
||||
exit;
|
||||
}
|
||||
553
app/controllers/claves_pedimentos.php
Normal file
553
app/controllers/claves_pedimentos.php
Normal file
@@ -0,0 +1,553 @@
|
||||
<?php
|
||||
require_once __DIR__ . '/../helpers/session.php';
|
||||
require_once __DIR__ . '/../../config/database.php';
|
||||
require_once __DIR__ . '/../helpers/crypto.php';
|
||||
|
||||
function index()
|
||||
{
|
||||
if (!($_SESSION['usuario_id'] ?? false)) {
|
||||
header('Location: /IMPORTADORES/login');
|
||||
exit;
|
||||
}
|
||||
|
||||
include __DIR__ . '/../../views/claves_pedimentos/index.php';
|
||||
}
|
||||
|
||||
function lista()
|
||||
{
|
||||
if (!($_SESSION['usuario_id'] ?? false)) {
|
||||
header('Location: /IMPORTADORES/login');
|
||||
exit;
|
||||
}
|
||||
|
||||
include __DIR__ . '/../../views/claves_pedimentos/lista.php';
|
||||
}
|
||||
|
||||
function crear()
|
||||
{
|
||||
if (!($_SESSION['usuario_id'] ?? false)) {
|
||||
header('Location: /IMPORTADORES/login');
|
||||
exit;
|
||||
}
|
||||
|
||||
include __DIR__ . '/../../views/claves_pedimentos/crear.php';
|
||||
}
|
||||
|
||||
function guardar()
|
||||
{
|
||||
if (!($_SESSION['usuario_id'] ?? false)) {
|
||||
die("⚠️ No autorizado.");
|
||||
}
|
||||
|
||||
$conn = getConnection();
|
||||
$id_usuario = $_SESSION['usuario_id'];
|
||||
|
||||
$codigo = strtoupper(trim($_POST['codigo'] ?? ''));
|
||||
$descripcion = trim($_POST['descripcion'] ?? '');
|
||||
$tipo_operacion = $_POST['tipo_operacion'] ?? 'importacion';
|
||||
$activo = isset($_POST['activo']) ? 1 : 0;
|
||||
|
||||
// Validaciones
|
||||
if (empty($codigo) || empty($descripcion)) {
|
||||
die("❌ El código y descripción son obligatorios.");
|
||||
}
|
||||
|
||||
if (strlen($codigo) > 10) {
|
||||
die("❌ El código no puede tener más de 10 caracteres.");
|
||||
}
|
||||
|
||||
if (!preg_match('/^[A-Z0-9]+$/', $codigo)) {
|
||||
die("❌ El código solo puede contener letras y números.");
|
||||
}
|
||||
|
||||
// Verificar que no exista el código para este usuario
|
||||
$sqlCheck = "SELECT COUNT(*) as count FROM claves_pedimentos_usuario WHERE id_usuario = ? AND codigo = ?";
|
||||
$stmtCheck = sqlsrv_query($conn, $sqlCheck, [$id_usuario, $codigo]);
|
||||
|
||||
if ($stmtCheck === false) {
|
||||
die("❌ Error al verificar código existente: " . print_r(sqlsrv_errors(), true));
|
||||
}
|
||||
|
||||
$result = sqlsrv_fetch_array($stmtCheck, SQLSRV_FETCH_ASSOC);
|
||||
|
||||
if ($result['count'] > 0) {
|
||||
die("❌ Ya existe una clave con el código '{$codigo}' para este usuario.");
|
||||
}
|
||||
|
||||
$sql = "INSERT INTO claves_pedimentos_usuario
|
||||
(id_usuario, codigo, descripcion, tipo_operacion, activo, fecha_creacion, fecha_modificacion)
|
||||
VALUES (?, ?, ?, ?, ?, GETDATE(), GETDATE())";
|
||||
|
||||
$params = [$id_usuario, $codigo, $descripcion, $tipo_operacion, $activo];
|
||||
|
||||
$stmt = sqlsrv_query($conn, $sql, $params);
|
||||
|
||||
if ($stmt === false) {
|
||||
die("❌ Error al guardar: " . print_r(sqlsrv_errors(), true));
|
||||
}
|
||||
|
||||
header('Location: /IMPORTADORES/claves_pedimentos/lista?created=ok');
|
||||
exit;
|
||||
}
|
||||
|
||||
function editar()
|
||||
{
|
||||
if (!($_SESSION['usuario_id'] ?? false)) {
|
||||
header('Location: /IMPORTADORES/login');
|
||||
exit;
|
||||
}
|
||||
|
||||
$id = $_GET['id'] ?? null;
|
||||
$id_usuario = $_SESSION['usuario_id'];
|
||||
|
||||
if (!$id || !is_numeric($id)) {
|
||||
die("❌ ID inválido.");
|
||||
}
|
||||
|
||||
$conn = getConnection();
|
||||
|
||||
$sql = "SELECT * FROM claves_pedimentos_usuario WHERE id_clave_pedimento = ? AND id_usuario = ?";
|
||||
$stmt = sqlsrv_query($conn, $sql, [$id, $id_usuario]);
|
||||
|
||||
if ($stmt === false) {
|
||||
die("❌ Error en consulta: " . print_r(sqlsrv_errors(), true));
|
||||
}
|
||||
|
||||
$clave = sqlsrv_fetch_array($stmt, SQLSRV_FETCH_ASSOC);
|
||||
|
||||
if (!$clave) {
|
||||
die("❌ Clave de pedimento no encontrada.");
|
||||
}
|
||||
|
||||
include __DIR__ . '/../../views/claves_pedimentos/editar.php';
|
||||
}
|
||||
|
||||
function actualizar()
|
||||
{
|
||||
if (!($_SESSION['usuario_id'] ?? false)) {
|
||||
die("⚠️ No autorizado.");
|
||||
}
|
||||
|
||||
$conn = getConnection();
|
||||
$id_usuario = $_SESSION['usuario_id'];
|
||||
|
||||
$id_clave_pedimento = $_POST['id_clave_pedimento'] ?? null;
|
||||
$codigo = strtoupper(trim($_POST['codigo'] ?? ''));
|
||||
$descripcion = trim($_POST['descripcion'] ?? '');
|
||||
$tipo_operacion = $_POST['tipo_operacion'] ?? 'importacion';
|
||||
$activo = isset($_POST['activo']) ? 1 : 0;
|
||||
|
||||
if (!$id_clave_pedimento || !is_numeric($id_clave_pedimento)) {
|
||||
die("❌ ID inválido.");
|
||||
}
|
||||
|
||||
// Validaciones
|
||||
if (empty($codigo) || empty($descripcion)) {
|
||||
die("❌ El código y descripción son obligatorios.");
|
||||
}
|
||||
|
||||
if (strlen($codigo) > 10) {
|
||||
die("❌ El código no puede tener más de 10 caracteres.");
|
||||
}
|
||||
|
||||
if (!preg_match('/^[A-Z0-9]+$/', $codigo)) {
|
||||
die("❌ El código solo puede contener letras y números.");
|
||||
}
|
||||
|
||||
// Verificar que no exista otro código igual (excepto el actual)
|
||||
$sqlCheck = "SELECT COUNT(*) as count FROM claves_pedimentos_usuario
|
||||
WHERE id_usuario = ? AND codigo = ? AND id_clave_pedimento != ?";
|
||||
$stmtCheck = sqlsrv_query($conn, $sqlCheck, [$id_usuario, $codigo, $id_clave_pedimento]);
|
||||
|
||||
if ($stmtCheck === false) {
|
||||
die("❌ Error al verificar código existente: " . print_r(sqlsrv_errors(), true));
|
||||
}
|
||||
|
||||
$result = sqlsrv_fetch_array($stmtCheck, SQLSRV_FETCH_ASSOC);
|
||||
|
||||
if ($result['count'] > 0) {
|
||||
die("❌ Ya existe otra clave con el código '{$codigo}' para este usuario.");
|
||||
}
|
||||
|
||||
$sql = "UPDATE claves_pedimentos_usuario SET
|
||||
codigo = ?, descripcion = ?, tipo_operacion = ?, activo = ?, fecha_modificacion = GETDATE()
|
||||
WHERE id_clave_pedimento = ? AND id_usuario = ?";
|
||||
|
||||
$params = [$codigo, $descripcion, $tipo_operacion, $activo, $id_clave_pedimento, $id_usuario];
|
||||
|
||||
$stmt = sqlsrv_query($conn, $sql, $params);
|
||||
|
||||
if ($stmt === false) {
|
||||
die("❌ Error al actualizar: " . print_r(sqlsrv_errors(), true));
|
||||
}
|
||||
|
||||
header('Location: /IMPORTADORES/claves_pedimentos/lista?updated=ok');
|
||||
exit;
|
||||
}
|
||||
|
||||
function eliminar()
|
||||
{
|
||||
if (!($_SESSION['usuario_id'] ?? false)) {
|
||||
header('Location: /IMPORTADORES/login');
|
||||
exit;
|
||||
}
|
||||
|
||||
$id = $_GET['id'] ?? null;
|
||||
$id_usuario = $_SESSION['usuario_id'];
|
||||
|
||||
if (!$id || !is_numeric($id)) {
|
||||
die("❌ ID inválido.");
|
||||
}
|
||||
|
||||
$conn = getConnection();
|
||||
|
||||
$sql = "DELETE FROM claves_pedimentos_usuario WHERE id_clave_pedimento = ? AND id_usuario = ?";
|
||||
$stmt = sqlsrv_query($conn, $sql, [$id, $id_usuario]);
|
||||
|
||||
if ($stmt === false) {
|
||||
die("❌ Error al eliminar: " . print_r(sqlsrv_errors(), true));
|
||||
}
|
||||
|
||||
header('Location: /IMPORTADORES/claves_pedimentos/lista?deleted=ok');
|
||||
exit;
|
||||
}
|
||||
|
||||
function ajax_lista()
|
||||
{
|
||||
if (!($_SESSION['usuario_id'] ?? false)) {
|
||||
http_response_code(403);
|
||||
echo json_encode([]);
|
||||
exit;
|
||||
}
|
||||
|
||||
$id_usuario = $_SESSION['usuario_id'];
|
||||
$conn = getConnection();
|
||||
|
||||
// Parámetros de DataTables
|
||||
$draw = intval($_GET['draw'] ?? 0);
|
||||
$start = intval($_GET['start'] ?? 0);
|
||||
$length = intval($_GET['length'] ?? 10);
|
||||
$search = $_GET['search']['value'] ?? '';
|
||||
|
||||
// Total registros sin filtro
|
||||
$sqlTotal = "SELECT COUNT(*) AS total FROM claves_pedimentos_usuario WHERE id_usuario = ?";
|
||||
$stmt = sqlsrv_query($conn, $sqlTotal, [$id_usuario]);
|
||||
|
||||
if ($stmt === false) {
|
||||
echo json_encode([
|
||||
"draw" => $draw,
|
||||
"recordsTotal" => 0,
|
||||
"recordsFiltered" => 0,
|
||||
"data" => [],
|
||||
"error" => "Error al contar registros totales"
|
||||
]);
|
||||
exit;
|
||||
}
|
||||
|
||||
$row = sqlsrv_fetch_array($stmt, SQLSRV_FETCH_ASSOC);
|
||||
$recordsTotal = (int)($row['total'] ?? 0);
|
||||
|
||||
// Construir condiciones de filtro
|
||||
$where = "id_usuario = ?";
|
||||
$params = [$id_usuario];
|
||||
|
||||
if ($search !== '') {
|
||||
$where .= " AND (codigo LIKE ? OR descripcion LIKE ? OR tipo_operacion LIKE ?)";
|
||||
$like = "%{$search}%";
|
||||
$params = array_merge($params, [$like, $like, $like]);
|
||||
}
|
||||
|
||||
// Total registros filtrados
|
||||
$sqlFiltered = "SELECT COUNT(*) AS total FROM claves_pedimentos_usuario WHERE $where";
|
||||
$stmtF = sqlsrv_query($conn, $sqlFiltered, $params);
|
||||
|
||||
if ($stmtF === false) {
|
||||
echo json_encode([
|
||||
"draw" => $draw,
|
||||
"recordsTotal" => $recordsTotal,
|
||||
"recordsFiltered" => 0,
|
||||
"data" => [],
|
||||
"error" => "Error al contar registros filtrados"
|
||||
]);
|
||||
exit;
|
||||
}
|
||||
|
||||
$rowF = sqlsrv_fetch_array($stmtF, SQLSRV_FETCH_ASSOC);
|
||||
$recordsFiltered = (int)($rowF['total'] ?? 0);
|
||||
|
||||
// Datos de la página
|
||||
$sqlData = "SELECT id_clave_pedimento, codigo, descripcion, tipo_operacion,
|
||||
CASE WHEN activo = 1 THEN 'Activo' ELSE 'Inactivo' END as estado,
|
||||
FORMAT(fecha_creacion, 'dd/MM/yyyy HH:mm') as fecha_creacion
|
||||
FROM claves_pedimentos_usuario
|
||||
WHERE $where
|
||||
ORDER BY codigo ASC
|
||||
OFFSET ? ROWS FETCH NEXT ? ROWS ONLY";
|
||||
$params[] = $start;
|
||||
$params[] = $length;
|
||||
|
||||
$stmtD = sqlsrv_query($conn, $sqlData, $params);
|
||||
|
||||
$data = [];
|
||||
if ($stmtD !== false) {
|
||||
while ($r = sqlsrv_fetch_array($stmtD, SQLSRV_FETCH_ASSOC)) {
|
||||
$data[] = [
|
||||
$r['id_clave_pedimento'],
|
||||
$r['codigo'],
|
||||
$r['descripcion'],
|
||||
ucfirst($r['tipo_operacion']),
|
||||
$r['estado'],
|
||||
$r['fecha_creacion']
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
$response = [
|
||||
"draw" => $draw,
|
||||
"recordsTotal" => $recordsTotal,
|
||||
"recordsFiltered" => $recordsFiltered,
|
||||
"data" => $data
|
||||
];
|
||||
|
||||
header('Content-Type: application/json; charset=UTF-8');
|
||||
echo json_encode($response, JSON_UNESCAPED_UNICODE);
|
||||
exit;
|
||||
}
|
||||
|
||||
function inicializar_claves_usuario()
|
||||
{
|
||||
if (!($_SESSION['usuario_id'] ?? false)) {
|
||||
header('Location: /IMPORTADORES/login');
|
||||
exit;
|
||||
}
|
||||
|
||||
$conn = getConnection();
|
||||
$id_usuario = $_SESSION['usuario_id'];
|
||||
|
||||
// Verificar si ya tiene claves configuradas
|
||||
$sqlCheck = "SELECT COUNT(*) as count FROM claves_pedimentos_usuario WHERE id_usuario = ?";
|
||||
$stmtCheck = sqlsrv_query($conn, $sqlCheck, [$id_usuario]);
|
||||
|
||||
if ($stmtCheck === false) {
|
||||
die("❌ Error al verificar claves existentes: " . print_r(sqlsrv_errors(), true));
|
||||
}
|
||||
|
||||
$result = sqlsrv_fetch_array($stmtCheck, SQLSRV_FETCH_ASSOC);
|
||||
|
||||
if ($result['count'] > 0) {
|
||||
header('Location: /IMPORTADORES/claves_pedimentos/lista?info=already_initialized');
|
||||
exit;
|
||||
}
|
||||
|
||||
// Claves de pedimentos por defecto para importación
|
||||
$claves_default = [
|
||||
['A1', 'Importación definitiva de mercancías', 'importacion'],
|
||||
['A3', 'Importación definitiva de vehículos usados', 'importacion'],
|
||||
['A4', 'Importación definitiva de vehículos nuevos', 'importacion'],
|
||||
['B1', 'Importación temporal para elaborar, transformar o reparar', 'importacion'],
|
||||
['C1', 'Importación definitiva de mercancías donadas', 'importacion'],
|
||||
['G1', 'Importación de mercancías con Programa IMMEX', 'importacion'],
|
||||
['I1', 'Importación definitiva exenta', 'importacion'],
|
||||
['J1', 'Importación temporal para reexportación en el mismo estado', 'importacion'],
|
||||
['L1', 'Importación definitiva con franquicia arancelaria con TLC', 'importacion'],
|
||||
['M1', 'Importación de menajes de casa', 'importacion'],
|
||||
['N1', 'Importación de equipaje', 'importacion'],
|
||||
['P1', 'Importación temporal de remolques y semirremolques', 'importacion'],
|
||||
['R1', 'Importación temporal de contenedores', 'importacion'],
|
||||
['S1', 'Importación temporal de vehículos', 'importacion'],
|
||||
['T1', 'Importación temporal de enseres de tripulantes', 'importacion'],
|
||||
['V1', 'Importación temporal de mercancías para exposición', 'importacion']
|
||||
];
|
||||
|
||||
$sql = "INSERT INTO claves_pedimentos_usuario
|
||||
(id_usuario, codigo, descripcion, tipo_operacion, activo, fecha_creacion, fecha_modificacion)
|
||||
VALUES (?, ?, ?, ?, 1, GETDATE(), GETDATE())";
|
||||
|
||||
$insertadas = 0;
|
||||
foreach ($claves_default as $clave) {
|
||||
$params = [$id_usuario, $clave[0], $clave[1], $clave[2]];
|
||||
$stmt = sqlsrv_query($conn, $sql, $params);
|
||||
|
||||
if ($stmt !== false) {
|
||||
$insertadas++;
|
||||
}
|
||||
}
|
||||
|
||||
if ($insertadas > 0) {
|
||||
header('Location: /IMPORTADORES/claves_pedimentos/lista?created=initialized');
|
||||
} else {
|
||||
die("❌ Error al inicializar las claves de pedimentos.");
|
||||
}
|
||||
exit;
|
||||
}
|
||||
|
||||
function importar_csv()
|
||||
{
|
||||
if (!($_SESSION['usuario_id'] ?? false)) {
|
||||
header('Location: /IMPORTADORES/login');
|
||||
exit;
|
||||
}
|
||||
|
||||
include __DIR__ . '/../../views/claves_pedimentos/importar_csv.php';
|
||||
}
|
||||
|
||||
function procesar_csv()
|
||||
{
|
||||
if (!($_SESSION['usuario_id'] ?? false)) {
|
||||
die("⚠️ No autorizado.");
|
||||
}
|
||||
|
||||
$conn = getConnection();
|
||||
$id_usuario = $_SESSION['usuario_id'];
|
||||
|
||||
// Verificar que se subió un archivo
|
||||
if (!isset($_FILES['csv_file']) || $_FILES['csv_file']['error'] !== UPLOAD_ERR_OK) {
|
||||
die("❌ Error: No se pudo cargar el archivo CSV.");
|
||||
}
|
||||
|
||||
$archivo_csv = $_FILES['csv_file']['tmp_name'];
|
||||
$nombre_archivo = $_FILES['csv_file']['name'];
|
||||
|
||||
// Validar extensión
|
||||
if (!str_ends_with(strtolower($nombre_archivo), '.csv')) {
|
||||
die("❌ Error: El archivo debe tener extensión .csv");
|
||||
}
|
||||
|
||||
// Opciones de importación
|
||||
$omitir_duplicados = isset($_POST['omitir_duplicados']);
|
||||
$activar_todas = isset($_POST['activar_todas']);
|
||||
|
||||
try {
|
||||
// Leer archivo CSV
|
||||
$archivo = fopen($archivo_csv, 'r');
|
||||
if (!$archivo) {
|
||||
die("❌ Error: No se pudo abrir el archivo CSV.");
|
||||
}
|
||||
|
||||
// Leer encabezados
|
||||
$encabezados = fgetcsv($archivo, 1000, ',');
|
||||
if (!$encabezados) {
|
||||
fclose($archivo);
|
||||
die("❌ Error: El archivo CSV está vacío o no tiene el formato correcto.");
|
||||
}
|
||||
|
||||
// Validar encabezados requeridos
|
||||
$encabezados_requeridos = ['codigo', 'descripcion', 'tipo_operacion', 'activo'];
|
||||
$encabezados_faltantes = array_diff($encabezados_requeridos, $encabezados);
|
||||
|
||||
if (!empty($encabezados_faltantes)) {
|
||||
fclose($archivo);
|
||||
die("❌ Error: Faltan las siguientes columnas: " . implode(', ', $encabezados_faltantes));
|
||||
}
|
||||
|
||||
$insertadas = 0;
|
||||
$omitidas = 0;
|
||||
$errores = [];
|
||||
$fila_numero = 1;
|
||||
|
||||
// SQL para verificar códigos existentes
|
||||
$sqlCheck = "SELECT COUNT(*) as count FROM claves_pedimentos_usuario WHERE id_usuario = ? AND codigo = ?";
|
||||
|
||||
// SQL para insertar
|
||||
$sqlInsert = "INSERT INTO claves_pedimentos_usuario
|
||||
(id_usuario, codigo, descripcion, tipo_operacion, activo, fecha_creacion, fecha_modificacion)
|
||||
VALUES (?, ?, ?, ?, ?, GETDATE(), GETDATE())";
|
||||
|
||||
// Procesar cada fila
|
||||
while (($fila = fgetcsv($archivo, 1000, ',')) !== FALSE) {
|
||||
$fila_numero++;
|
||||
|
||||
if (count($fila) < count($encabezados_requeridos)) {
|
||||
$errores[] = "Fila $fila_numero: Datos insuficientes";
|
||||
continue;
|
||||
}
|
||||
|
||||
// Crear array asociativo
|
||||
$datos = array_combine($encabezados, $fila);
|
||||
|
||||
// Validar datos
|
||||
$codigo = strtoupper(trim($datos['codigo']));
|
||||
$descripcion = trim($datos['descripcion']);
|
||||
$tipo_operacion = trim($datos['tipo_operacion']);
|
||||
$activo = $activar_todas ? 1 : (int)($datos['activo'] ?? 1);
|
||||
|
||||
// Validaciones
|
||||
if (empty($codigo)) {
|
||||
$errores[] = "Fila $fila_numero: Código vacío";
|
||||
continue;
|
||||
}
|
||||
|
||||
if (empty($descripcion)) {
|
||||
$errores[] = "Fila $fila_numero: Descripción vacía";
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!in_array($tipo_operacion, ['importacion', 'exportacion'])) {
|
||||
$errores[] = "Fila $fila_numero: Tipo de operación inválido ($tipo_operacion)";
|
||||
continue;
|
||||
}
|
||||
|
||||
if (strlen($codigo) > 10) {
|
||||
$errores[] = "Fila $fila_numero: Código muy largo (máximo 10 caracteres)";
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!preg_match('/^[A-Z0-9]+$/', $codigo)) {
|
||||
$errores[] = "Fila $fila_numero: Código inválido (solo letras y números)";
|
||||
continue;
|
||||
}
|
||||
|
||||
// Verificar si ya existe
|
||||
if ($omitir_duplicados) {
|
||||
$stmtCheck = sqlsrv_query($conn, $sqlCheck, [$id_usuario, $codigo]);
|
||||
|
||||
if ($stmtCheck === false) {
|
||||
$errores[] = "Fila $fila_numero: Error al verificar código existente";
|
||||
continue;
|
||||
}
|
||||
|
||||
$result = sqlsrv_fetch_array($stmtCheck, SQLSRV_FETCH_ASSOC);
|
||||
|
||||
if ($result['count'] > 0) {
|
||||
$omitidas++;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
// Insertar registro
|
||||
$params = [$id_usuario, $codigo, $descripcion, $tipo_operacion, $activo];
|
||||
$stmt = sqlsrv_query($conn, $sqlInsert, $params);
|
||||
|
||||
if ($stmt === false) {
|
||||
$sql_errors = sqlsrv_errors();
|
||||
$errores[] = "Fila $fila_numero: Error al insertar - " . $sql_errors[0]['message'];
|
||||
} else {
|
||||
$insertadas++;
|
||||
}
|
||||
}
|
||||
|
||||
fclose($archivo);
|
||||
|
||||
// Preparar mensaje de resultado
|
||||
$mensaje = "✅ Proceso completado:";
|
||||
$mensaje .= "<br>• Registros insertados: $insertadas";
|
||||
if ($omitidas > 0) {
|
||||
$mensaje .= "<br>• Registros omitidos (duplicados): $omitidas";
|
||||
}
|
||||
if (!empty($errores)) {
|
||||
$mensaje .= "<br>• Errores encontrados: " . count($errores);
|
||||
$mensaje .= "<br><br>Detalle de errores:<br>" . implode("<br>", array_slice($errores, 0, 10));
|
||||
if (count($errores) > 10) {
|
||||
$mensaje .= "<br>... y " . (count($errores) - 10) . " errores más.";
|
||||
}
|
||||
}
|
||||
|
||||
// Redirigir con resultado
|
||||
$encoded_message = urlencode($mensaje);
|
||||
header("Location: /IMPORTADORES/claves_pedimentos/lista?imported=ok&message=" . $encoded_message);
|
||||
exit;
|
||||
|
||||
} catch (Exception $e) {
|
||||
die("❌ Error inesperado: " . $e->getMessage());
|
||||
}
|
||||
}
|
||||
@@ -1112,4 +1112,38 @@ function cambiarPassword()
|
||||
<script>
|
||||
setTimeout(() => { window.location.href = '/IMPORTADORES/login'; }, 4000);
|
||||
</script>";
|
||||
}
|
||||
}
|
||||
|
||||
// MÉTODOS ADICIONALES CON NOMBRES DE RUTA COMPATIBLES
|
||||
|
||||
function enviar_codigo()
|
||||
{
|
||||
// Redirigir al método camelCase existente
|
||||
return enviarCodigo();
|
||||
}
|
||||
|
||||
function verificar_codigo_recuperacion()
|
||||
{
|
||||
// Redirigir al método camelCase existente
|
||||
return verificarCodigo();
|
||||
}
|
||||
|
||||
function reenviar_codigo()
|
||||
{
|
||||
// Redirigir al método camelCase existente
|
||||
return reenviarCodigo();
|
||||
}
|
||||
|
||||
function actualizar_password()
|
||||
{
|
||||
// Redirigir al método camelCase existente
|
||||
return cambiarPassword();
|
||||
}
|
||||
|
||||
function verificar_codigo()
|
||||
{
|
||||
// Mostrar vista de verificar código
|
||||
return verificarCodigoVista();
|
||||
}
|
||||
|
||||
?>
|
||||
190
app/controllers/mve.php
Normal file
190
app/controllers/mve.php
Normal file
@@ -0,0 +1,190 @@
|
||||
<?php
|
||||
require_once __DIR__ . '/../../config/database.php';
|
||||
require_once __DIR__ . '/../helpers/session.php';
|
||||
|
||||
function index() {
|
||||
include __DIR__ . '/../../views/mve/lista.php';
|
||||
}
|
||||
|
||||
function ajax_guardar_datos_factura() {
|
||||
try {
|
||||
// Validar que el usuario esté autenticado
|
||||
if (!isset($_SESSION['user_id'])) {
|
||||
echo json_encode(['success' => false, 'message' => 'Usuario no autenticado']);
|
||||
return;
|
||||
}
|
||||
|
||||
$id_factura = $_POST['id_factura'] ?? null;
|
||||
$id_pedimento = $_POST['id_pedimento'] ?? null;
|
||||
$datos_art65 = json_decode($_POST['datos_art65'] ?? '{}', true);
|
||||
$datos_art66 = json_decode($_POST['datos_art66'] ?? '{}', true);
|
||||
|
||||
if (!$id_factura || !$id_pedimento) {
|
||||
echo json_encode(['success' => false, 'message' => 'Faltan datos requeridos']);
|
||||
return;
|
||||
}
|
||||
|
||||
$db = getDB();
|
||||
|
||||
// Verificar si ya existen datos para esta factura
|
||||
$stmt = $db->prepare("SELECT id FROM mve_facturas_datos WHERE id_pedimento = ? AND id_factura = ?");
|
||||
$stmt->execute([$id_pedimento, $id_factura]);
|
||||
$existe = $stmt->fetch();
|
||||
|
||||
if ($existe) {
|
||||
// Actualizar registro existente
|
||||
$sql = "UPDATE mve_facturas_datos SET
|
||||
art65_fecha_transporte = ?, art65_importe_transporte = ?,
|
||||
art65_fecha_descuentos = ?, art65_importe_descuentos = ?,
|
||||
art65_fecha_posteriores = ?, art65_importe_posteriores = ?,
|
||||
art65_fecha_contribuciones = ?, art65_importe_contribuciones = ?,
|
||||
art65_fecha_pagos_vendedor = ?, art65_importe_pagos_vendedor = ?,
|
||||
|
||||
art66_fecha_comisiones = ?, art66_importe_comisiones = ?, art66_cargo_comisiones = ?,
|
||||
art66_fecha_envases = ?, art66_importe_envases = ?, art66_cargo_envases = ?,
|
||||
art66_fecha_embalaje = ?, art66_importe_embalaje = ?, art66_cargo_embalaje = ?,
|
||||
art66_fecha_transporte_dec = ?, art66_importe_transporte_dec = ?, art66_cargo_transporte_dec = ?,
|
||||
art66_fecha_ingenieria = ?, art66_importe_ingenieria = ?, art66_cargo_ingenieria = ?,
|
||||
art66_fecha_regalias = ?, art66_importe_regalias = ?, art66_cargo_regalias = ?,
|
||||
art66_fecha_producto = ?, art66_importe_producto = ?, art66_cargo_producto = ?,
|
||||
|
||||
fecha_actualizacion = NOW()
|
||||
WHERE id_pedimento = ? AND id_factura = ?";
|
||||
|
||||
$params = [
|
||||
$datos_art65['fecha_transporte'] ?: null, $datos_art65['importe_transporte'] ?: null,
|
||||
$datos_art65['fecha_descuentos'] ?: null, $datos_art65['importe_descuentos'] ?: null,
|
||||
$datos_art65['fecha_posteriores'] ?: null, $datos_art65['importe_posteriores'] ?: null,
|
||||
$datos_art65['fecha_contribuciones'] ?: null, $datos_art65['importe_contribuciones'] ?: null,
|
||||
$datos_art65['fecha_pagos_vendedor'] ?: null, $datos_art65['importe_pagos_vendedor'] ?: null,
|
||||
|
||||
$datos_art66['fecha_comisiones'] ?: null, $datos_art66['importe_comisiones'] ?: null, $datos_art66['cargo_comisiones'] ?: null,
|
||||
$datos_art66['fecha_envases'] ?: null, $datos_art66['importe_envases'] ?: null, $datos_art66['cargo_envases'] ?: null,
|
||||
$datos_art66['fecha_embalaje'] ?: null, $datos_art66['importe_embalaje'] ?: null, $datos_art66['cargo_embalaje'] ?: null,
|
||||
$datos_art66['fecha_transporte_dec'] ?: null, $datos_art66['importe_transporte_dec'] ?: null, $datos_art66['cargo_transporte_dec'] ?: null,
|
||||
$datos_art66['fecha_ingenieria'] ?: null, $datos_art66['importe_ingenieria'] ?: null, $datos_art66['cargo_ingenieria'] ?: null,
|
||||
$datos_art66['fecha_regalias'] ?: null, $datos_art66['importe_regalias'] ?: null, $datos_art66['cargo_regalias'] ?: null,
|
||||
$datos_art66['fecha_producto'] ?: null, $datos_art66['importe_producto'] ?: null, $datos_art66['cargo_producto'] ?: null,
|
||||
|
||||
$id_pedimento, $id_factura
|
||||
];
|
||||
} else {
|
||||
// Crear nuevo registro
|
||||
$sql = "INSERT INTO mve_facturas_datos (
|
||||
id_pedimento, id_factura, numero_factura,
|
||||
art65_fecha_transporte, art65_importe_transporte,
|
||||
art65_fecha_descuentos, art65_importe_descuentos,
|
||||
art65_fecha_posteriores, art65_importe_posteriores,
|
||||
art65_fecha_contribuciones, art65_importe_contribuciones,
|
||||
art65_fecha_pagos_vendedor, art65_importe_pagos_vendedor,
|
||||
|
||||
art66_fecha_comisiones, art66_importe_comisiones, art66_cargo_comisiones,
|
||||
art66_fecha_envases, art66_importe_envases, art66_cargo_envases,
|
||||
art66_fecha_embalaje, art66_importe_embalaje, art66_cargo_embalaje,
|
||||
art66_fecha_transporte_dec, art66_importe_transporte_dec, art66_cargo_transporte_dec,
|
||||
art66_fecha_ingenieria, art66_importe_ingenieria, art66_cargo_ingenieria,
|
||||
art66_fecha_regalias, art66_importe_regalias, art66_cargo_regalias,
|
||||
art66_fecha_producto, art66_importe_producto, art66_cargo_producto,
|
||||
|
||||
usuario_creacion
|
||||
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)";
|
||||
|
||||
$params = [
|
||||
$id_pedimento, $id_factura, "FACTURA-$id_factura",
|
||||
$datos_art65['fecha_transporte'] ?: null, $datos_art65['importe_transporte'] ?: null,
|
||||
$datos_art65['fecha_descuentos'] ?: null, $datos_art65['importe_descuentos'] ?: null,
|
||||
$datos_art65['fecha_posteriores'] ?: null, $datos_art65['importe_posteriores'] ?: null,
|
||||
$datos_art65['fecha_contribuciones'] ?: null, $datos_art65['importe_contribuciones'] ?: null,
|
||||
$datos_art65['fecha_pagos_vendedor'] ?: null, $datos_art65['importe_pagos_vendedor'] ?: null,
|
||||
|
||||
$datos_art66['fecha_comisiones'] ?: null, $datos_art66['importe_comisiones'] ?: null, $datos_art66['cargo_comisiones'] ?: null,
|
||||
$datos_art66['fecha_envases'] ?: null, $datos_art66['importe_envases'] ?: null, $datos_art66['cargo_envases'] ?: null,
|
||||
$datos_art66['fecha_embalaje'] ?: null, $datos_art66['importe_embalaje'] ?: null, $datos_art66['cargo_embalaje'] ?: null,
|
||||
$datos_art66['fecha_transporte_dec'] ?: null, $datos_art66['importe_transporte_dec'] ?: null, $datos_art66['cargo_transporte_dec'] ?: null,
|
||||
$datos_art66['fecha_ingenieria'] ?: null, $datos_art66['importe_ingenieria'] ?: null, $datos_art66['cargo_ingenieria'] ?: null,
|
||||
$datos_art66['fecha_regalias'] ?: null, $datos_art66['importe_regalias'] ?: null, $datos_art66['cargo_regalias'] ?: null,
|
||||
$datos_art66['fecha_producto'] ?: null, $datos_art66['importe_producto'] ?: null, $datos_art66['cargo_producto'] ?: null,
|
||||
|
||||
$_SESSION['user_id']
|
||||
];
|
||||
}
|
||||
|
||||
$stmt = $db->prepare($sql);
|
||||
$resultado = $stmt->execute($params);
|
||||
|
||||
if ($resultado) {
|
||||
echo json_encode(['success' => true, 'message' => 'Datos guardados correctamente']);
|
||||
} else {
|
||||
echo json_encode(['success' => false, 'message' => 'Error al guardar los datos']);
|
||||
}
|
||||
|
||||
} catch (Exception $e) {
|
||||
echo json_encode(['success' => false, 'message' => 'Error interno: ' . $e->getMessage()]);
|
||||
}
|
||||
}
|
||||
|
||||
function ajax_obtener_datos_factura() {
|
||||
try {
|
||||
$id_factura = $_GET['id_factura'] ?? null;
|
||||
|
||||
if (!$id_factura) {
|
||||
echo json_encode(['success' => false, 'message' => 'ID de factura requerido']);
|
||||
return;
|
||||
}
|
||||
|
||||
$db = getDB();
|
||||
$stmt = $db->prepare("SELECT * FROM mve_facturas_datos WHERE id_factura = ?");
|
||||
$stmt->execute([$id_factura]);
|
||||
$datos = $stmt->fetch(PDO::FETCH_ASSOC);
|
||||
|
||||
if ($datos) {
|
||||
// Estructurar datos para el frontend
|
||||
$datosEstructurados = [
|
||||
'art65' => [
|
||||
'fecha_transporte' => $datos['art65_fecha_transporte'],
|
||||
'importe_transporte' => $datos['art65_importe_transporte'],
|
||||
'fecha_descuentos' => $datos['art65_fecha_descuentos'],
|
||||
'importe_descuentos' => $datos['art65_importe_descuentos'],
|
||||
'fecha_posteriores' => $datos['art65_fecha_posteriores'],
|
||||
'importe_posteriores' => $datos['art65_importe_posteriores'],
|
||||
'fecha_contribuciones' => $datos['art65_fecha_contribuciones'],
|
||||
'importe_contribuciones' => $datos['art65_importe_contribuciones'],
|
||||
'fecha_pagos_vendedor' => $datos['art65_fecha_pagos_vendedor'],
|
||||
'importe_pagos_vendedor' => $datos['art65_importe_pagos_vendedor']
|
||||
],
|
||||
'art66' => [
|
||||
'fecha_comisiones' => $datos['art66_fecha_comisiones'],
|
||||
'importe_comisiones' => $datos['art66_importe_comisiones'],
|
||||
'cargo_comisiones' => $datos['art66_cargo_comisiones'],
|
||||
'fecha_envases' => $datos['art66_fecha_envases'],
|
||||
'importe_envases' => $datos['art66_importe_envases'],
|
||||
'cargo_envases' => $datos['art66_cargo_envases'],
|
||||
'fecha_embalaje' => $datos['art66_fecha_embalaje'],
|
||||
'importe_embalaje' => $datos['art66_importe_embalaje'],
|
||||
'cargo_embalaje' => $datos['art66_cargo_embalaje'],
|
||||
'fecha_transporte_dec' => $datos['art66_fecha_transporte_dec'],
|
||||
'importe_transporte_dec' => $datos['art66_importe_transporte_dec'],
|
||||
'cargo_transporte_dec' => $datos['art66_cargo_transporte_dec'],
|
||||
'fecha_ingenieria' => $datos['art66_fecha_ingenieria'],
|
||||
'importe_ingenieria' => $datos['art66_importe_ingenieria'],
|
||||
'cargo_ingenieria' => $datos['art66_cargo_ingenieria'],
|
||||
'fecha_regalias' => $datos['art66_fecha_regalias'],
|
||||
'importe_regalias' => $datos['art66_importe_regalias'],
|
||||
'cargo_regalias' => $datos['art66_cargo_regalias'],
|
||||
'fecha_producto' => $datos['art66_fecha_producto'],
|
||||
'importe_producto' => $datos['art66_importe_producto'],
|
||||
'cargo_producto' => $datos['art66_cargo_producto']
|
||||
]
|
||||
];
|
||||
|
||||
echo json_encode(['success' => true, 'datos' => $datosEstructurados]);
|
||||
} else {
|
||||
echo json_encode(['success' => true, 'datos' => null]);
|
||||
}
|
||||
|
||||
} catch (Exception $e) {
|
||||
echo json_encode(['success' => false, 'message' => 'Error interno: ' . $e->getMessage()]);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -342,4 +342,281 @@ function obtenerCorreos($conn, $id_usuario)
|
||||
}
|
||||
|
||||
return $correos;
|
||||
}
|
||||
|
||||
function ventanillaUnica()
|
||||
{
|
||||
$conn = getConnection();
|
||||
|
||||
$id_usuario = $_SESSION['usuario_id'] ?? null;
|
||||
|
||||
// Asegúrate de que el usuario está autenticado
|
||||
if (!$id_usuario || ($_SESSION['pendiente_confirmacion'] ?? false)) {
|
||||
die("Usuario no autenticado.");
|
||||
}
|
||||
|
||||
// Obtener configuración actual de ventanilla única
|
||||
$configuracion_vu = obtenerConfiguracionVU($conn, $id_usuario);
|
||||
|
||||
include __DIR__ . '/../../views/seguridad/ventanilla_unica.php';
|
||||
}
|
||||
|
||||
function guardarConfiguracionVU()
|
||||
{
|
||||
// Solo ejecutar si es una petición POST
|
||||
if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
|
||||
header('Location: /IMPORTADORES/seguridad/ventanillaUnica');
|
||||
exit;
|
||||
}
|
||||
|
||||
$conn = getConnection();
|
||||
|
||||
$id_usuario = $_SESSION['usuario_id'] ?? null;
|
||||
if (!$id_usuario || ($_SESSION['pendiente_confirmacion'] ?? false)) {
|
||||
die("No autorizado - Sesión inválida");
|
||||
}
|
||||
|
||||
// Obtener datos del formulario (sin ruta_ejecutable)
|
||||
$clave_fiel = trim($_POST['clave_fiel'] ?? '');
|
||||
$rfc_usuario_vu = trim($_POST['rfc_usuario_vu'] ?? '');
|
||||
$clave_webservice = trim($_POST['clave_webservice'] ?? '');
|
||||
|
||||
// Validar campos obligatorios (sin ruta_ejecutable)
|
||||
if (empty($clave_fiel) || empty($rfc_usuario_vu)) {
|
||||
$_SESSION['config_error'] = 'Los campos Clave FIEL y RFC Usuario VU son obligatorios.';
|
||||
header('Location: /IMPORTADORES/seguridad/ventanillaUnica');
|
||||
exit;
|
||||
}
|
||||
|
||||
// Obtener configuración actual para conservar archivos existentes
|
||||
$configuracion_actual = obtenerConfiguracionVU($conn, $id_usuario);
|
||||
$ruta_archivo_key = $configuracion_actual['ruta_archivo_key'];
|
||||
$ruta_archivo_cer = $configuracion_actual['ruta_archivo_cer'];
|
||||
|
||||
// Directorio para archivos de certificados
|
||||
$upload_dir = __DIR__ . '/../../storage/certificados/';
|
||||
if (!is_dir($upload_dir)) {
|
||||
mkdir($upload_dir, 0755, true);
|
||||
}
|
||||
|
||||
// Procesar archivo KEY
|
||||
if (!empty($_FILES['archivo_key']['tmp_name']) && $_FILES['archivo_key']['error'] === UPLOAD_ERR_OK) {
|
||||
$key_extension = pathinfo($_FILES['archivo_key']['name'], PATHINFO_EXTENSION);
|
||||
|
||||
if (strtolower($key_extension) !== 'key') {
|
||||
$_SESSION['config_error'] = 'El archivo KEY debe tener extensión .key';
|
||||
header('Location: /IMPORTADORES/seguridad/ventanillaUnica');
|
||||
exit;
|
||||
}
|
||||
|
||||
$key_filename = 'key_' . $id_usuario . '_' . time() . '.key';
|
||||
$key_destination = $upload_dir . $key_filename;
|
||||
|
||||
if (move_uploaded_file($_FILES['archivo_key']['tmp_name'], $key_destination)) {
|
||||
// Eliminar archivo anterior si existe
|
||||
if ($ruta_archivo_key && file_exists($ruta_archivo_key)) {
|
||||
unlink($ruta_archivo_key);
|
||||
}
|
||||
$ruta_archivo_key = $key_destination;
|
||||
} else {
|
||||
$_SESSION['config_error'] = 'Error al subir el archivo KEY.';
|
||||
header('Location: /IMPORTADORES/seguridad/ventanillaUnica');
|
||||
exit;
|
||||
}
|
||||
}
|
||||
|
||||
// Procesar archivo CER
|
||||
if (!empty($_FILES['archivo_cer']['tmp_name']) && $_FILES['archivo_cer']['error'] === UPLOAD_ERR_OK) {
|
||||
$cer_extension = pathinfo($_FILES['archivo_cer']['name'], PATHINFO_EXTENSION);
|
||||
|
||||
if (strtolower($cer_extension) !== 'cer') {
|
||||
$_SESSION['config_error'] = 'El archivo CER debe tener extensión .cer';
|
||||
header('Location: /IMPORTADORES/seguridad/ventanillaUnica');
|
||||
exit;
|
||||
}
|
||||
|
||||
$cer_filename = 'cer_' . $id_usuario . '_' . time() . '.cer';
|
||||
$cer_destination = $upload_dir . $cer_filename;
|
||||
|
||||
if (move_uploaded_file($_FILES['archivo_cer']['tmp_name'], $cer_destination)) {
|
||||
// Eliminar archivo anterior si existe
|
||||
if ($ruta_archivo_cer && file_exists($ruta_archivo_cer)) {
|
||||
unlink($ruta_archivo_cer);
|
||||
}
|
||||
$ruta_archivo_cer = $cer_destination;
|
||||
} else {
|
||||
$_SESSION['config_error'] = 'Error al subir el archivo CER.';
|
||||
header('Location: /IMPORTADORES/seguridad/ventanillaUnica');
|
||||
exit;
|
||||
}
|
||||
}
|
||||
|
||||
// Validar que se hayan subido los certificados (obligatorios para nueva configuración)
|
||||
if (empty($ruta_archivo_key) || empty($ruta_archivo_cer)) {
|
||||
$_SESSION['config_error'] = 'Los archivos CER y KEY son obligatorios.';
|
||||
header('Location: /IMPORTADORES/seguridad/ventanillaUnica');
|
||||
exit;
|
||||
}
|
||||
|
||||
// Encriptar las contraseñas sensibles
|
||||
$clave_fiel_encrypted = encrypt($clave_fiel);
|
||||
$clave_webservice_encrypted = !empty($clave_webservice) ? encrypt($clave_webservice) : '';
|
||||
|
||||
// Verificar si ya existe configuración - CORREGIR ERROR SQL
|
||||
$sql_check = "SELECT COUNT(*) as total FROM configuracion_ventanilla_unica WHERE id_usuario = ?";
|
||||
$stmt_check = sqlsrv_query($conn, $sql_check, [$id_usuario]);
|
||||
|
||||
if ($stmt_check === false) {
|
||||
$_SESSION['config_error'] = 'Error en la consulta de configuración: ' . print_r(sqlsrv_errors(), true);
|
||||
header('Location: /IMPORTADORES/seguridad/ventanillaUnica');
|
||||
exit;
|
||||
}
|
||||
|
||||
$row = sqlsrv_fetch_array($stmt_check, SQLSRV_FETCH_ASSOC);
|
||||
|
||||
if ($row['total'] > 0) {
|
||||
// Actualizar configuración existente (sin ruta_ejecutable)
|
||||
$sql = "UPDATE configuracion_ventanilla_unica SET
|
||||
ruta_archivo_key = ?,
|
||||
ruta_archivo_cer = ?,
|
||||
clave_fiel = ?,
|
||||
rfc_usuario_vu = ?,
|
||||
clave_webservice = ?,
|
||||
fecha_actualizacion = GETDATE()
|
||||
WHERE id_usuario = ?";
|
||||
$params = [
|
||||
$ruta_archivo_key,
|
||||
$ruta_archivo_cer,
|
||||
$clave_fiel_encrypted,
|
||||
$rfc_usuario_vu,
|
||||
$clave_webservice_encrypted,
|
||||
$id_usuario
|
||||
];
|
||||
} else {
|
||||
// Insertar nueva configuración (sin ruta_ejecutable)
|
||||
$sql = "INSERT INTO configuracion_ventanilla_unica
|
||||
(id_usuario, ruta_archivo_key, ruta_archivo_cer,
|
||||
clave_fiel, rfc_usuario_vu, clave_webservice, fecha_creacion)
|
||||
VALUES (?, ?, ?, ?, ?, ?, GETDATE())";
|
||||
$params = [
|
||||
$id_usuario,
|
||||
$ruta_archivo_key,
|
||||
$ruta_archivo_cer,
|
||||
$clave_fiel_encrypted,
|
||||
$rfc_usuario_vu,
|
||||
$clave_webservice_encrypted
|
||||
];
|
||||
}
|
||||
|
||||
$stmt = sqlsrv_prepare($conn, $sql, $params);
|
||||
|
||||
if (!$stmt) {
|
||||
$_SESSION['config_error'] = "Error en la preparación: " . print_r(sqlsrv_errors(), true);
|
||||
header('Location: /IMPORTADORES/seguridad/ventanillaUnica');
|
||||
exit;
|
||||
}
|
||||
|
||||
$result = sqlsrv_execute($stmt);
|
||||
|
||||
if ($result === false) {
|
||||
$_SESSION['config_error'] = "Error al guardar configuración: " . print_r(sqlsrv_errors(), true);
|
||||
header('Location: /IMPORTADORES/seguridad/ventanillaUnica');
|
||||
exit;
|
||||
}
|
||||
|
||||
sqlsrv_free_stmt($stmt);
|
||||
sqlsrv_close($conn);
|
||||
|
||||
$_SESSION['config_success'] = 'Configuración de Ventanilla Única guardada correctamente.';
|
||||
header('Location: /IMPORTADORES/seguridad/ventanillaUnica');
|
||||
exit;
|
||||
}
|
||||
|
||||
function probarConexionVU()
|
||||
{
|
||||
header('Content-Type: application/json');
|
||||
|
||||
$id_usuario = $_SESSION['usuario_id'] ?? null;
|
||||
if (!$id_usuario || ($_SESSION['pendiente_confirmacion'] ?? false)) {
|
||||
echo json_encode(['success' => false, 'message' => 'Usuario no autenticado']);
|
||||
exit;
|
||||
}
|
||||
|
||||
$conn = getConnection();
|
||||
$configuracion = obtenerConfiguracionVU($conn, $id_usuario);
|
||||
|
||||
// Verificar que todos los campos requeridos estén configurados (sin ruta_ejecutable)
|
||||
if (empty($configuracion['rfc_usuario_vu']) || empty($configuracion['clave_fiel'])) {
|
||||
echo json_encode([
|
||||
'success' => false,
|
||||
'message' => 'Configuración incompleta. Verifica que RFC Usuario VU y Clave FIEL estén configurados.'
|
||||
]);
|
||||
exit;
|
||||
}
|
||||
|
||||
// Verificar archivos de certificados
|
||||
if (!empty($configuracion['ruta_archivo_cer']) && !file_exists($configuracion['ruta_archivo_cer'])) {
|
||||
echo json_encode([
|
||||
'success' => false,
|
||||
'message' => 'El archivo CER no existe en la ruta especificada.'
|
||||
]);
|
||||
exit;
|
||||
}
|
||||
|
||||
if (!empty($configuracion['ruta_archivo_key']) && !file_exists($configuracion['ruta_archivo_key'])) {
|
||||
echo json_encode([
|
||||
'success' => false,
|
||||
'message' => 'El archivo KEY no existe en la ruta especificada.'
|
||||
]);
|
||||
exit;
|
||||
}
|
||||
|
||||
// Verificar que existan ambos archivos de certificados
|
||||
if (empty($configuracion['ruta_archivo_cer']) || empty($configuracion['ruta_archivo_key'])) {
|
||||
echo json_encode([
|
||||
'success' => false,
|
||||
'message' => 'Los archivos CER y KEY son obligatorios para la configuración.'
|
||||
]);
|
||||
exit;
|
||||
}
|
||||
|
||||
// Si llegamos aquí, la configuración es válida
|
||||
echo json_encode([
|
||||
'success' => true,
|
||||
'message' => 'Configuración válida. Los archivos de certificados existen y todos los campos obligatorios están completos.'
|
||||
]);
|
||||
exit;
|
||||
}
|
||||
|
||||
function obtenerConfiguracionVU($conn, $id_usuario)
|
||||
{
|
||||
$configuracion = [
|
||||
'ruta_archivo_key' => '',
|
||||
'ruta_archivo_cer' => '',
|
||||
'clave_fiel' => '',
|
||||
'rfc_usuario_vu' => '',
|
||||
'clave_webservice' => '',
|
||||
'fecha_creacion' => null,
|
||||
'fecha_actualizacion' => null
|
||||
];
|
||||
|
||||
$sql = "SELECT * FROM configuracion_ventanilla_unica WHERE id_usuario = ?";
|
||||
$stmt = sqlsrv_prepare($conn, $sql, [$id_usuario]);
|
||||
|
||||
if ($stmt && sqlsrv_execute($stmt)) {
|
||||
if ($row = sqlsrv_fetch_array($stmt, SQLSRV_FETCH_ASSOC)) {
|
||||
$configuracion['ruta_archivo_key'] = $row['ruta_archivo_key'] ?? '';
|
||||
$configuracion['ruta_archivo_cer'] = $row['ruta_archivo_cer'] ?? '';
|
||||
$configuracion['rfc_usuario_vu'] = $row['rfc_usuario_vu'] ?? '';
|
||||
|
||||
// Desencriptar contraseñas
|
||||
$configuracion['clave_fiel'] = !empty($row['clave_fiel']) ? decrypt($row['clave_fiel']) : '';
|
||||
$configuracion['clave_webservice'] = !empty($row['clave_webservice']) ? decrypt($row['clave_webservice']) : '';
|
||||
|
||||
$configuracion['fecha_creacion'] = $row['fecha_creacion'];
|
||||
$configuracion['fecha_actualizacion'] = $row['fecha_actualizacion'];
|
||||
}
|
||||
}
|
||||
|
||||
return $configuracion;
|
||||
}
|
||||
@@ -410,6 +410,7 @@ function guardar()
|
||||
$aduana_seccion = $_POST['anexo22_apendice'] ?? null;
|
||||
$num_factura = trim($_POST['numero_factura'] ?? '');
|
||||
$fecha = $_POST['fecha_factura'] ?? null;
|
||||
$pedimento = trim($_POST['pedimento'] ?? ''); // ✅ NUEVO CAMPO
|
||||
$incoterm = $_POST['incoterm'] ?? null;
|
||||
$pais_proveedor = $_POST['pais_proveedor'] ?? null;
|
||||
$tipo_moneda = $_POST['tipo_moneda'] ?? null;
|
||||
@@ -462,15 +463,16 @@ function guardar()
|
||||
$fotoUrl,
|
||||
$status,
|
||||
$proveedor_clave,
|
||||
$patente_id ? (int)$patente_id : null
|
||||
$patente_id ? (int)$patente_id : null,
|
||||
$pedimento ?: null // ✅ CORREGIDO: Campo pedimento_vinculado al final
|
||||
];
|
||||
$sql = "INSERT INTO dbo.solicitud_importacion_factura
|
||||
(id_importador, id_agencia, aduana, anexo22_apendice, numero_factura,
|
||||
fecha_factura, numero_pedimento, incoterm, pais_proveedor, tipo_moneda,
|
||||
fecha_factura, incoterm, pais_proveedor, tipo_moneda,
|
||||
valor_factura, vinculacion, transportista_id, chofer_id,
|
||||
foto_solicitud_url, status, proveedor_clave, patente_id)
|
||||
foto_solicitud_url, status, proveedor_clave, patente_id, pedimento_vinculado)
|
||||
OUTPUT INSERTED.id_solicitud
|
||||
VALUES(?, ?, ?, ?, ?, ?, NULL, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
||||
VALUES(?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
||||
";
|
||||
$stmt = sqlsrv_query($conn, $sql, $params, ['Scrollable' => SQLSRV_CURSOR_KEYSET]);
|
||||
|
||||
@@ -947,8 +949,8 @@ function actualizar()
|
||||
} else {
|
||||
// INSERTAR nueva partida (asegurarse que no tenga id_partida o sea 0)
|
||||
$sql = "INSERT INTO dbo.solicitud_importacion_partidas
|
||||
(id_solicitud, descripcion, cantidad_comercial, cantidad_tarifa,
|
||||
valor_factura, peso_bruto, unidad_comercial_id, tasa_preferencial)
|
||||
(id_solicitud, descripcion, cantidad_comercial, cantidad_tarifa, valor_factura,
|
||||
peso_bruto, unidad_comercial_id, tasa_preferencial)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?)
|
||||
";
|
||||
$params = [ $id_solicitud, $desc, $cantCom, $cantTar, $valPart, $peso, $umId, $tasaPref ];
|
||||
@@ -1968,7 +1970,7 @@ function generarHTMLPDF($solicitud, $partidas, $configuracion, $proveedor_info =
|
||||
$unidades = ['', 'uno', 'dos', 'tres', 'cuatro', 'cinco', 'seis', 'siete', 'ocho', 'nueve'];
|
||||
$decenas = ['', '', 'veinte', 'treinta', 'cuarenta', 'cincuenta', 'sesenta', 'setenta', 'ochenta', 'noventa'];
|
||||
$especiales = ['diez', 'once', 'doce', 'trece', 'catorce', 'quince', 'dieciséis', 'diecisiete', 'dieciocho', 'diecinueve'];
|
||||
$centenas = ['', 'ciento', 'doscientos', 'trescientos', 'cuatrocientos', 'quinientos', 'seiscientos', 'setecientos', 'ochocientos', 'novecientos'];
|
||||
$centenas = ['', 'ciento', 'doscientos', 'trescientos', 'cuatrocientos', 'quinientos', 'seiscientos', 'setecientos', 'ochocientos'];
|
||||
|
||||
if ($numero == 0) return 'cero';
|
||||
if ($numero == 100) return 'cien';
|
||||
@@ -2212,4 +2214,89 @@ function generarHTMLPDF($solicitud, $partidas, $configuracion, $proveedor_info =
|
||||
';
|
||||
|
||||
return $html;
|
||||
}
|
||||
|
||||
// NUEVO ENDPOINT: Pedimentos de catálogo activos del importador para panel de referencia
|
||||
function ajax_pedimentos_catalogo()
|
||||
{
|
||||
header('Content-Type: application/json; charset=utf-8');
|
||||
if (!($_SESSION['usuario_id'] ?? false)) {
|
||||
http_response_code(401);
|
||||
echo json_encode(['data' => []]);
|
||||
exit;
|
||||
}
|
||||
$id_usuario = $_SESSION['usuario_id'];
|
||||
$limit = isset($_GET['limit']) ? intval($_GET['limit']) : 10;
|
||||
$conn = getConnection();
|
||||
// Obtener RFC del importador
|
||||
$sqlImportador = "SELECT rfc FROM informacion_general WHERE id_usuario = ?";
|
||||
$stmtImportador = sqlsrv_query($conn, $sqlImportador, [$id_usuario]);
|
||||
if ($stmtImportador === false) {
|
||||
echo json_encode(['data' => []]);
|
||||
exit;
|
||||
}
|
||||
$importador = sqlsrv_fetch_array($stmtImportador, SQLSRV_FETCH_ASSOC);
|
||||
if (!$importador) {
|
||||
echo json_encode(['data' => []]);
|
||||
exit;
|
||||
}
|
||||
// Obtener pedimentos activos del catálogo
|
||||
$sql = "SELECT TOP {$limit} IdPrevio, Pedimento, ClienteNombre, ClavePed, Timestamp
|
||||
FROM PREVIOS_COMPARTIDOS_WS
|
||||
WHERE ClienteRFC = ? AND Status = 1
|
||||
ORDER BY Timestamp DESC";
|
||||
$stmt = sqlsrv_query($conn, $sql, [$importador['rfc']]);
|
||||
$pedimentos = [];
|
||||
if ($stmt !== false) {
|
||||
while ($row = sqlsrv_fetch_array($stmt, SQLSRV_FETCH_ASSOC)) {
|
||||
$fecha = '';
|
||||
if ($row['Timestamp'] instanceof DateTime) {
|
||||
$fecha = $row['Timestamp']->format('d/m/Y');
|
||||
}
|
||||
$pedimentos[] = [
|
||||
'numero' => $row['Pedimento'],
|
||||
'cliente' => $row['ClienteNombre'],
|
||||
'clave' => $row['ClavePed'],
|
||||
'fecha' => $fecha
|
||||
];
|
||||
}
|
||||
}
|
||||
echo json_encode(['data' => $pedimentos]);
|
||||
exit;
|
||||
}
|
||||
|
||||
function ajax_facturas_por_pedimento() {
|
||||
header('Content-Type: application/json');
|
||||
$id_previo = isset($_GET['id_pedimento']) ? trim($_GET['id_pedimento']) : '';
|
||||
if (!$id_previo) {
|
||||
echo json_encode(['success' => false, 'error' => 'ID de pedimento no válido']);
|
||||
return;
|
||||
}
|
||||
$conn = getConnection();
|
||||
// 1. Buscar el número de pedimento real en PREVIOS_COMPARTIDOS_WS
|
||||
$stmt = sqlsrv_query($conn, "SELECT Pedimento FROM PREVIOS_COMPARTIDOS_WS WHERE IdPrevio = ?", [$id_previo]);
|
||||
if ($stmt === false || !($row = sqlsrv_fetch_array($stmt, SQLSRV_FETCH_ASSOC))) {
|
||||
echo json_encode(['success' => false, 'error' => 'No se encontró el pedimento en PREVIOS_COMPARTIDOS_WS']);
|
||||
return;
|
||||
}
|
||||
$numero_pedimento = $row['Pedimento'];
|
||||
|
||||
// 2. Buscar las facturas en solicitud_importacion_factura usando el número de pedimento
|
||||
// ✅ CORREGIDO: Incluir id_solicitud como id_factura
|
||||
$stmt2 = sqlsrv_query($conn, "SELECT id_solicitud, numero_factura, fecha_factura, valor_factura FROM solicitud_importacion_factura WHERE numero_pedimento = ?", [$numero_pedimento]);
|
||||
if ($stmt2 === false) {
|
||||
echo json_encode(['success' => false, 'error' => 'Error en la consulta de facturas']);
|
||||
return;
|
||||
}
|
||||
|
||||
$facturas = [];
|
||||
while ($row2 = sqlsrv_fetch_array($stmt2, SQLSRV_FETCH_ASSOC)) {
|
||||
$facturas[] = [
|
||||
'id_factura' => $row2['id_solicitud'], // ✅ AGREGADO: Usar id_solicitud como id_factura
|
||||
'numero_factura' => $row2['numero_factura'],
|
||||
'fecha' => ($row2['fecha_factura'] instanceof DateTime) ? $row2['fecha_factura']->format('Y-m-d') : $row2['fecha_factura'],
|
||||
'monto' => $row2['valor_factura']
|
||||
];
|
||||
}
|
||||
echo json_encode(['success' => true, 'facturas' => $facturas]);
|
||||
}
|
||||
913
app/controllers/templates_rapidos.php
Normal file
913
app/controllers/templates_rapidos.php
Normal file
@@ -0,0 +1,913 @@
|
||||
<?php
|
||||
require_once __DIR__ . '/../helpers/session.php';
|
||||
require_once __DIR__ . '/../../config/database.php';
|
||||
|
||||
function index()
|
||||
{
|
||||
if (!($_SESSION['usuario_id'] ?? false)) {
|
||||
header('Location: /IMPORTADORES/login');
|
||||
exit;
|
||||
}
|
||||
|
||||
include __DIR__ . '/../../views/templates_rapidos/index.php';
|
||||
}
|
||||
|
||||
function lista()
|
||||
{
|
||||
if (!($_SESSION['usuario_id'] ?? false)) {
|
||||
header('Location: /IMPORTADORES/login');
|
||||
exit;
|
||||
}
|
||||
|
||||
include __DIR__ . '/../../views/templates_rapidos/lista.php';
|
||||
}
|
||||
|
||||
function crear()
|
||||
{
|
||||
if (!($_SESSION['usuario_id'] ?? false)) {
|
||||
header('Location: /IMPORTADORES/login');
|
||||
exit;
|
||||
}
|
||||
|
||||
$conn = getConnection();
|
||||
|
||||
// Obtener datos para los selects
|
||||
$aduanas = obtenerAduanas($conn);
|
||||
$patentes = obtenerPatentes($conn);
|
||||
$incoterms = obtenerIncoterms($conn);
|
||||
$paises = obtenerPaises($conn);
|
||||
$transportistas = obtenerTransportistas($conn);
|
||||
$choferes = obtenerChoferes($conn);
|
||||
$unidades_medida = obtenerUnidadesMedida($conn);
|
||||
|
||||
include __DIR__ . '/../../views/templates_rapidos/crear.php';
|
||||
}
|
||||
|
||||
function guardar()
|
||||
{
|
||||
if (!($_SESSION['usuario_id'] ?? false)) {
|
||||
die("⚠️ No autorizado.");
|
||||
}
|
||||
|
||||
$conn = getConnection();
|
||||
$id_usuario = $_SESSION['usuario_id'];
|
||||
$id_agencia = $_SESSION['id_agencia_en_uso'] ?? null;
|
||||
|
||||
// Obtener datos del formulario
|
||||
$nombre = trim($_POST['nombre'] ?? '');
|
||||
$descripcion = trim($_POST['descripcion'] ?? '');
|
||||
$icono = trim($_POST['icono'] ?? '🏢');
|
||||
|
||||
// Configuración del template
|
||||
$config = [
|
||||
'tipo_moneda' => $_POST['tipo_moneda'] ?? '',
|
||||
'incoterm' => $_POST['incoterm'] ?? '',
|
||||
'vinculacion' => $_POST['vinculacion'] ?? '',
|
||||
'pais_proveedor' => $_POST['pais_proveedor'] ?? '',
|
||||
'pais_proveedor_texto' => $_POST['pais_proveedor_texto'] ?? '',
|
||||
'anexo22_apendice' => $_POST['anexo22_apendice'] ?? '',
|
||||
'patente' => $_POST['patente'] ?? '',
|
||||
'transportista_id' => $_POST['transportista_id'] ?? '',
|
||||
'chofer_id' => $_POST['chofer_id'] ?? '',
|
||||
'tasa_preferencial' => $_POST['tasa_preferencial'] ?? '',
|
||||
'unidad_comercial_id' => $_POST['unidad_comercial_id'] ?? ''
|
||||
];
|
||||
|
||||
$config_json = json_encode($config, JSON_UNESCAPED_UNICODE);
|
||||
|
||||
// Validaciones
|
||||
if (empty($nombre)) {
|
||||
die("❌ El nombre del template es obligatorio.");
|
||||
}
|
||||
|
||||
if (strlen($nombre) > 100) {
|
||||
die("❌ El nombre del template es muy largo (máximo 100 caracteres).");
|
||||
}
|
||||
|
||||
$sql = "INSERT INTO dbo.templates_rapidos
|
||||
(nombre, descripcion, icono, config_json, tipo_moneda, incoterm,
|
||||
vinculacion, pais_proveedor, tasa_preferencial, id_agencia, id_usuario_creador)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)";
|
||||
|
||||
$params = [
|
||||
$nombre,
|
||||
$descripcion,
|
||||
$icono,
|
||||
$config_json,
|
||||
$config['tipo_moneda'],
|
||||
$config['incoterm'],
|
||||
$config['vinculacion'] ?: null,
|
||||
$config['pais_proveedor'],
|
||||
$config['tasa_preferencial'],
|
||||
$id_agencia,
|
||||
$id_usuario
|
||||
];
|
||||
|
||||
$stmt = sqlsrv_query($conn, $sql, $params);
|
||||
|
||||
if ($stmt === false) {
|
||||
die("❌ Error al guardar template: " . print_r(sqlsrv_errors(), true));
|
||||
}
|
||||
|
||||
header('Location: /IMPORTADORES/templates_rapidos/lista?created=ok');
|
||||
exit;
|
||||
}
|
||||
|
||||
function editar()
|
||||
{
|
||||
if (!($_SESSION['usuario_id'] ?? false)) {
|
||||
header('Location: /IMPORTADORES/login');
|
||||
exit;
|
||||
}
|
||||
|
||||
$id = $_GET['id'] ?? null;
|
||||
|
||||
if (!$id || !is_numeric($id)) {
|
||||
die("❌ ID inválido.");
|
||||
}
|
||||
|
||||
$conn = getConnection();
|
||||
$id_usuario = $_SESSION['usuario_id'];
|
||||
$id_agencia = $_SESSION['id_agencia_en_uso'] ?? null;
|
||||
|
||||
// Obtener el template
|
||||
$sql = "SELECT * FROM dbo.templates_rapidos
|
||||
WHERE id = ? AND (id_usuario_creador = ? OR id_agencia = ? OR id_agencia IS NULL)";
|
||||
$stmt = sqlsrv_query($conn, $sql, [$id, $id_usuario, $id_agencia]);
|
||||
|
||||
if ($stmt === false) {
|
||||
die("❌ Error en consulta: " . print_r(sqlsrv_errors(), true));
|
||||
}
|
||||
|
||||
$template = sqlsrv_fetch_array($stmt, SQLSRV_FETCH_ASSOC);
|
||||
|
||||
if (!$template) {
|
||||
die("❌ Template no encontrado o sin permisos.");
|
||||
}
|
||||
|
||||
// Decodificar configuración JSON
|
||||
$template['config'] = json_decode($template['config_json'], true) ?: [];
|
||||
|
||||
// Obtener datos para los selects
|
||||
$conn = getConnection();
|
||||
$aduanas = obtenerAduanas($conn);
|
||||
$patentes = obtenerPatentes($conn);
|
||||
$incoterms = obtenerIncoterms($conn);
|
||||
$paises = obtenerPaises($conn);
|
||||
$transportistas = obtenerTransportistas($conn);
|
||||
$choferes = obtenerChoferes($conn);
|
||||
$unidades_medida = obtenerUnidadesMedida($conn);
|
||||
|
||||
include __DIR__ . '/../../views/templates_rapidos/editar.php';
|
||||
}
|
||||
|
||||
function actualizar()
|
||||
{
|
||||
if (!($_SESSION['usuario_id'] ?? false)) {
|
||||
die("⚠️ No autorizado.");
|
||||
}
|
||||
|
||||
$conn = getConnection();
|
||||
$id_usuario = $_SESSION['usuario_id'];
|
||||
$id_agencia = $_SESSION['id_agencia_en_uso'] ?? null;
|
||||
|
||||
$id = $_POST['id'] ?? null;
|
||||
$nombre = trim($_POST['nombre'] ?? '');
|
||||
$descripcion = trim($_POST['descripcion'] ?? '');
|
||||
$icono = trim($_POST['icono'] ?? '🏢');
|
||||
|
||||
if (!$id || !is_numeric($id)) {
|
||||
die("❌ ID inválido.");
|
||||
}
|
||||
|
||||
// Configuración del template
|
||||
$config = [
|
||||
'tipo_moneda' => $_POST['tipo_moneda'] ?? '',
|
||||
'incoterm' => $_POST['incoterm'] ?? '',
|
||||
'vinculacion' => $_POST['vinculacion'] ?? '',
|
||||
'pais_proveedor' => $_POST['pais_proveedor'] ?? '',
|
||||
'pais_proveedor_texto' => $_POST['pais_proveedor_texto'] ?? '',
|
||||
'anexo22_apendice' => $_POST['anexo22_apendice'] ?? '',
|
||||
'patente' => $_POST['patente'] ?? '',
|
||||
'transportista_id' => $_POST['transportista_id'] ?? '',
|
||||
'chofer_id' => $_POST['chofer_id'] ?? '',
|
||||
'tasa_preferencial' => $_POST['tasa_preferencial'] ?? '',
|
||||
'unidad_comercial_id' => $_POST['unidad_comercial_id'] ?? ''
|
||||
];
|
||||
|
||||
$config_json = json_encode($config, JSON_UNESCAPED_UNICODE);
|
||||
|
||||
// Validaciones
|
||||
if (empty($nombre)) {
|
||||
die("❌ El nombre del template es obligatorio.");
|
||||
}
|
||||
|
||||
// Verificar permisos
|
||||
$sqlCheck = "SELECT id FROM dbo.templates_rapidos
|
||||
WHERE id = ? AND (id_usuario_creador = ? OR id_agencia = ?)";
|
||||
$stmtCheck = sqlsrv_query($conn, $sqlCheck, [$id, $id_usuario, $id_agencia]);
|
||||
$exists = sqlsrv_fetch_array($stmtCheck, SQLSRV_FETCH_ASSOC);
|
||||
|
||||
if (!$exists) {
|
||||
die("❌ Template no encontrado o sin permisos para editarlo.");
|
||||
}
|
||||
|
||||
$sql = "UPDATE dbo.templates_rapidos SET
|
||||
nombre = ?, descripcion = ?, icono = ?, config_json = ?,
|
||||
tipo_moneda = ?, incoterm = ?, vinculacion = ?,
|
||||
pais_proveedor = ?, tasa_preferencial = ?, fecha_modificacion = GETDATE()
|
||||
WHERE id = ?";
|
||||
|
||||
$params = [
|
||||
$nombre,
|
||||
$descripcion,
|
||||
$icono,
|
||||
$config_json,
|
||||
$config['tipo_moneda'],
|
||||
$config['incoterm'],
|
||||
$config['vinculacion'] ?: null,
|
||||
$config['pais_proveedor'],
|
||||
$config['tasa_preferencial'],
|
||||
$id
|
||||
];
|
||||
|
||||
$stmt = sqlsrv_query($conn, $sql, $params);
|
||||
|
||||
if ($stmt === false) {
|
||||
die("❌ Error al actualizar template: " . print_r(sqlsrv_errors(), true));
|
||||
}
|
||||
|
||||
header('Location: /IMPORTADORES/templates_rapidos/lista?updated=ok');
|
||||
exit;
|
||||
}
|
||||
|
||||
function eliminar()
|
||||
{
|
||||
if (!($_SESSION['usuario_id'] ?? false)) {
|
||||
header('Location: /IMPORTADORES/login');
|
||||
exit;
|
||||
}
|
||||
|
||||
$id = $_GET['id'] ?? null;
|
||||
|
||||
if (!$id || !is_numeric($id)) {
|
||||
die("❌ ID inválido.");
|
||||
}
|
||||
|
||||
$conn = getConnection();
|
||||
$id_usuario = $_SESSION['usuario_id'];
|
||||
$id_agencia = $_SESSION['id_agencia_en_uso'] ?? null;
|
||||
|
||||
// Verificar permisos
|
||||
$sqlCheck = "SELECT id FROM dbo.templates_rapidos
|
||||
WHERE id = ? AND (id_usuario_creador = ? OR id_agencia = ?)";
|
||||
$stmtCheck = sqlsrv_query($conn, $sqlCheck, [$id, $id_usuario, $id_agencia]);
|
||||
$exists = sqlsrv_fetch_array($stmtCheck, SQLSRV_FETCH_ASSOC);
|
||||
|
||||
if (!$exists) {
|
||||
die("❌ Template no encontrado o sin permisos para eliminarlo.");
|
||||
}
|
||||
|
||||
$sql = "UPDATE dbo.templates_rapidos SET activo = 0 WHERE id = ?";
|
||||
$stmt = sqlsrv_query($conn, $sql, [$id]);
|
||||
|
||||
if ($stmt === false) {
|
||||
die("❌ Error al eliminar template: " . print_r(sqlsrv_errors(), true));
|
||||
}
|
||||
|
||||
header('Location: /IMPORTADORES/templates_rapidos/lista?deleted=ok');
|
||||
exit;
|
||||
}
|
||||
|
||||
function ajax_lista()
|
||||
{
|
||||
if (!($_SESSION['usuario_id'] ?? false)) {
|
||||
http_response_code(403);
|
||||
echo json_encode([]);
|
||||
exit;
|
||||
}
|
||||
|
||||
$id_usuario = $_SESSION['usuario_id'];
|
||||
$id_agencia = $_SESSION['id_agencia_en_uso'] ?? null;
|
||||
$conn = getConnection();
|
||||
|
||||
// Parámetros de DataTables
|
||||
$draw = intval($_GET['draw'] ?? 0);
|
||||
$start = intval($_GET['start'] ?? 0);
|
||||
$length = intval($_GET['length'] ?? 10);
|
||||
$search = $_GET['search']['value'] ?? '';
|
||||
|
||||
// Construir condiciones de filtro
|
||||
$where = "activo = 1 AND (id_agencia IS NULL OR id_agencia = ? OR id_usuario_creador = ?)";
|
||||
$params = [$id_agencia, $id_usuario];
|
||||
|
||||
if ($search !== '') {
|
||||
$where .= " AND (nombre LIKE ? OR descripcion LIKE ?)";
|
||||
$like = "%{$search}%";
|
||||
$params = array_merge($params, [$like, $like]);
|
||||
}
|
||||
|
||||
// Total registros filtrados
|
||||
$sqlFiltered = "SELECT COUNT(*) AS total FROM dbo.templates_rapidos WHERE $where";
|
||||
$stmtF = sqlsrv_query($conn, $sqlFiltered, $params);
|
||||
$rowF = sqlsrv_fetch_array($stmtF, SQLSRV_FETCH_ASSOC);
|
||||
$recordsFiltered = (int)($rowF['total'] ?? 0);
|
||||
|
||||
// Total registros sin filtro
|
||||
$sqlTotal = "SELECT COUNT(*) AS total FROM dbo.templates_rapidos WHERE activo = 1 AND (id_agencia IS NULL OR id_agencia = ? OR id_usuario_creador = ?)";
|
||||
$stmtT = sqlsrv_query($conn, $sqlTotal, [$id_agencia, $id_usuario]);
|
||||
$rowT = sqlsrv_fetch_array($stmtT, SQLSRV_FETCH_ASSOC);
|
||||
$recordsTotal = (int)($rowT['total'] ?? 0);
|
||||
|
||||
// Consulta principal con paginación
|
||||
$sql = "SELECT t.id, t.nombre, t.descripcion, t.icono, t.tipo_moneda, t.incoterm,
|
||||
t.vinculacion, t.pais_proveedor, t.tasa_preferencial, t.fecha_creacion,
|
||||
u.nombre_usuario AS usuario_creador,
|
||||
CASE WHEN t.id_agencia IS NULL THEN 'Sistema' ELSE a.nombre END AS ambito
|
||||
FROM dbo.templates_rapidos t
|
||||
LEFT JOIN dbo.usuarios u ON t.id_usuario_creador = u.id
|
||||
LEFT JOIN dbo.agencias a ON t.id_agencia = a.id
|
||||
WHERE $where
|
||||
ORDER BY t.fecha_creacion DESC
|
||||
OFFSET ? ROWS FETCH NEXT ? ROWS ONLY";
|
||||
|
||||
$paramsData = array_merge($params, [$start, $length]);
|
||||
$stmt = sqlsrv_query($conn, $sql, $paramsData);
|
||||
|
||||
$data = [];
|
||||
while ($row = sqlsrv_fetch_array($stmt, SQLSRV_FETCH_ASSOC)) {
|
||||
$fecha = $row['fecha_creacion'] instanceof DateTime
|
||||
? $row['fecha_creacion']->format('d/m/Y H:i')
|
||||
: 'N/A';
|
||||
|
||||
$data[] = [
|
||||
'id' => $row['id'],
|
||||
'icono' => htmlspecialchars($row['icono'] ?? '🏢'),
|
||||
'nombre' => htmlspecialchars($row['nombre']),
|
||||
'descripcion' => htmlspecialchars($row['descripcion'] ?? ''),
|
||||
'tipo_moneda' => htmlspecialchars($row['tipo_moneda'] ?? ''),
|
||||
'incoterm' => htmlspecialchars($row['incoterm'] ?? ''),
|
||||
'pais_proveedor' => htmlspecialchars($row['pais_proveedor'] ?? ''),
|
||||
'ambito' => htmlspecialchars($row['ambito'] ?? ''),
|
||||
'usuario_creador' => htmlspecialchars($row['usuario_creador'] ?? ''),
|
||||
'fecha_creacion' => $fecha
|
||||
];
|
||||
}
|
||||
|
||||
echo json_encode([
|
||||
'draw' => $draw,
|
||||
'recordsTotal' => $recordsTotal,
|
||||
'recordsFiltered' => $recordsFiltered,
|
||||
'data' => $data
|
||||
]);
|
||||
}
|
||||
|
||||
function ajax_obtener_templates_debug()
|
||||
{
|
||||
header('Content-Type: application/json; charset=UTF-8');
|
||||
|
||||
if (!($_SESSION['usuario_id'] ?? false)) {
|
||||
echo json_encode(['error' => 'Usuario no autenticado']);
|
||||
exit;
|
||||
}
|
||||
|
||||
$id_usuario = $_SESSION['usuario_id'];
|
||||
$id_agencia = $_SESSION['id_agencia_en_uso'] ?? null;
|
||||
|
||||
try {
|
||||
$conn = getConnection();
|
||||
|
||||
// ✅ CONSULTA SIN FILTROS para debug
|
||||
$sql = "SELECT id, nombre, descripcion, icono, config_json,
|
||||
ISNULL(veces_usado, 0) as veces_usado,
|
||||
id_usuario_creador, id_agencia, activo,
|
||||
fecha_creacion
|
||||
FROM dbo.templates_rapidos
|
||||
ORDER BY fecha_creacion DESC";
|
||||
|
||||
$stmt = sqlsrv_query($conn, $sql);
|
||||
|
||||
if ($stmt === false) {
|
||||
$errors = sqlsrv_errors();
|
||||
echo json_encode([
|
||||
'error' => 'Error en consulta SQL',
|
||||
'message' => $errors[0]['message'] ?? 'Error desconocido',
|
||||
'sql_errors' => $errors
|
||||
]);
|
||||
exit;
|
||||
}
|
||||
|
||||
$templates = [];
|
||||
$debug_info = [
|
||||
'usuario_actual' => $id_usuario,
|
||||
'agencia_actual' => $id_agencia,
|
||||
'todos_los_templates' => []
|
||||
];
|
||||
|
||||
while ($row = sqlsrv_fetch_array($stmt, SQLSRV_FETCH_ASSOC)) {
|
||||
// Info completa para debug
|
||||
$template_debug = [
|
||||
'id' => $row['id'],
|
||||
'nombre' => $row['nombre'],
|
||||
'id_usuario_creador' => $row['id_usuario_creador'],
|
||||
'id_agencia' => $row['id_agencia'],
|
||||
'activo' => $row['activo'],
|
||||
'fecha_creacion' => $row['fecha_creacion'] instanceof DateTime
|
||||
? $row['fecha_creacion']->format('Y-m-d H:i:s')
|
||||
: $row['fecha_creacion'],
|
||||
'es_del_usuario_actual' => ($row['id_usuario_creador'] == $id_usuario),
|
||||
'es_de_la_agencia' => ($row['id_agencia'] == $id_agencia),
|
||||
'deberia_mostrarse' => (
|
||||
$row['activo'] == 1 && (
|
||||
$row['id_agencia'] === null ||
|
||||
$row['id_agencia'] == $id_agencia ||
|
||||
$row['id_usuario_creador'] == $id_usuario
|
||||
)
|
||||
)
|
||||
];
|
||||
|
||||
$debug_info['todos_los_templates'][] = $template_debug;
|
||||
|
||||
// Solo agregar a templates para mostrar si cumple condiciones
|
||||
if ($template_debug['deberia_mostrarse']) {
|
||||
$config = [];
|
||||
if (!empty($row['config_json'])) {
|
||||
$decoded = json_decode($row['config_json'], true);
|
||||
$config = is_array($decoded) ? $decoded : [];
|
||||
}
|
||||
|
||||
$templates[] = [
|
||||
'id' => (int)$row['id'],
|
||||
'nombre' => $row['nombre'],
|
||||
'descripcion' => $row['descripcion'] ?? '',
|
||||
'icono' => $row['icono'] ?? '📋',
|
||||
'config' => $config,
|
||||
'veces_usado' => (int)$row['veces_usado'],
|
||||
'id_usuario_creador' => $row['id_usuario_creador'],
|
||||
'es_mio' => ($row['id_usuario_creador'] == $id_usuario)
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
echo json_encode([
|
||||
'success' => true,
|
||||
'count' => count($templates),
|
||||
'templates' => $templates,
|
||||
'debug_info' => $debug_info
|
||||
], JSON_UNESCAPED_UNICODE);
|
||||
|
||||
} catch (Exception $e) {
|
||||
echo json_encode([
|
||||
'error' => 'Error interno del servidor',
|
||||
'message' => $e->getMessage()
|
||||
]);
|
||||
}
|
||||
|
||||
exit;
|
||||
}
|
||||
|
||||
function ajax_obtener_templates()
|
||||
{
|
||||
// ✅ VERSIÓN CORREGIDA: Función simplificada sin filtros complejos
|
||||
|
||||
header('Content-Type: application/json; charset=UTF-8');
|
||||
|
||||
if (!($_SESSION['usuario_id'] ?? false)) {
|
||||
http_response_code(403);
|
||||
echo json_encode(['error' => 'Usuario no autenticado', 'templates' => []]);
|
||||
exit;
|
||||
}
|
||||
|
||||
$id_usuario = $_SESSION['usuario_id'];
|
||||
$id_agencia = $_SESSION['id_agencia_en_uso'] ?? null;
|
||||
|
||||
try {
|
||||
$conn = getConnection();
|
||||
|
||||
// ✅ CONSULTA SIMPLIFICADA: Mostrar todos los templates activos del usuario o agencia
|
||||
$sql = "SELECT id, nombre, descripcion, icono, config_json,
|
||||
ISNULL(veces_usado, 0) as veces_usado,
|
||||
id_usuario_creador, id_agencia
|
||||
FROM dbo.templates_rapidos
|
||||
WHERE activo = 1
|
||||
AND (id_usuario_creador = ? OR id_agencia = ? OR id_agencia IS NULL)
|
||||
ORDER BY
|
||||
CASE WHEN id_usuario_creador = ? THEN 0 ELSE 1 END,
|
||||
veces_usado DESC,
|
||||
nombre ASC";
|
||||
|
||||
$params = [$id_usuario, $id_agencia, $id_usuario];
|
||||
$stmt = sqlsrv_query($conn, $sql, $params);
|
||||
|
||||
if ($stmt === false) {
|
||||
$errors = sqlsrv_errors();
|
||||
echo json_encode([
|
||||
'error' => 'Error en consulta SQL',
|
||||
'message' => $errors[0]['message'] ?? 'Error desconocido',
|
||||
'templates' => []
|
||||
]);
|
||||
exit;
|
||||
}
|
||||
|
||||
$templates = [];
|
||||
$templates_personales = [];
|
||||
$templates_otros = [];
|
||||
|
||||
while ($row = sqlsrv_fetch_array($stmt, SQLSRV_FETCH_ASSOC)) {
|
||||
// Decodificar JSON con manejo de errores
|
||||
$config = [];
|
||||
if (!empty($row['config_json'])) {
|
||||
$decoded = json_decode($row['config_json'], true);
|
||||
$config = is_array($decoded) ? $decoded : [];
|
||||
}
|
||||
|
||||
$template = [
|
||||
'id' => (int)$row['id'],
|
||||
'nombre' => $row['nombre'],
|
||||
'descripcion' => $row['descripcion'] ?? '',
|
||||
'icono' => $row['icono'] ?? '📋',
|
||||
'config' => $config,
|
||||
'veces_usado' => (int)$row['veces_usado'],
|
||||
'id_usuario_creador' => $row['id_usuario_creador'],
|
||||
'id_agencia' => $row['id_agencia'],
|
||||
'es_mio' => ($row['id_usuario_creador'] == $id_usuario),
|
||||
'ambito' => ($row['id_usuario_creador'] == $id_usuario) ? 'personal' :
|
||||
(($row['id_agencia'] == $id_agencia) ? 'agencia' : 'sistema')
|
||||
];
|
||||
|
||||
// Separar templates personales de otros
|
||||
if ($template['es_mio']) {
|
||||
$templates_personales[] = $template;
|
||||
} else {
|
||||
$templates_otros[] = $template;
|
||||
}
|
||||
|
||||
$templates[] = $template;
|
||||
}
|
||||
|
||||
// Respuesta con información detallada
|
||||
echo json_encode([
|
||||
'success' => true,
|
||||
'count' => count($templates),
|
||||
'count_personales' => count($templates_personales),
|
||||
'count_otros' => count($templates_otros),
|
||||
'usuario_id' => $id_usuario,
|
||||
'agencia_id' => $id_agencia,
|
||||
'templates' => $templates,
|
||||
'templates_personales' => $templates_personales,
|
||||
'templates_otros' => $templates_otros
|
||||
], JSON_UNESCAPED_UNICODE);
|
||||
|
||||
} catch (Exception $e) {
|
||||
echo json_encode([
|
||||
'error' => 'Error interno del servidor',
|
||||
'message' => $e->getMessage(),
|
||||
'templates' => []
|
||||
]);
|
||||
}
|
||||
|
||||
exit;
|
||||
}
|
||||
|
||||
function ajax_lista_por_seccion()
|
||||
{
|
||||
if (!($_SESSION['usuario_id'] ?? false)) {
|
||||
die(json_encode(['error' => 'No autorizado']));
|
||||
}
|
||||
|
||||
$conn = getConnection();
|
||||
$id_usuario = $_SESSION['usuario_id'];
|
||||
$id_agencia = $_SESSION['id_agencia_en_uso'] ?? null;
|
||||
$seccion = $_GET['seccion'] ?? 'personal';
|
||||
|
||||
$data = [];
|
||||
|
||||
try {
|
||||
switch($seccion) {
|
||||
case 'personal':
|
||||
// Solo templates creados por el usuario actual
|
||||
$sql = "SELECT id, nombre, descripcion, icono, config_json,
|
||||
veces_usado, fecha_creacion, id_usuario_creador
|
||||
FROM dbo.templates_rapidos
|
||||
WHERE id_usuario_creador = ? AND estado = 1
|
||||
ORDER BY veces_usado DESC, fecha_creacion DESC";
|
||||
$params = [$id_usuario];
|
||||
break;
|
||||
|
||||
case 'agencia':
|
||||
// Templates de la agencia (excluyendo los personales ya mostrados)
|
||||
$sql = "SELECT id, nombre, descripcion, icono, config_json,
|
||||
veces_usado, fecha_creacion, id_usuario_creador
|
||||
FROM dbo.templates_rapidos
|
||||
WHERE id_agencia = ? AND id_usuario_creador != ? AND estado = 1
|
||||
ORDER BY veces_usado DESC, fecha_creacion DESC";
|
||||
$params = [$id_agencia, $id_usuario];
|
||||
break;
|
||||
|
||||
case 'global':
|
||||
// Templates globales del sistema (solo si se solicitan explícitamente)
|
||||
$sql = "SELECT id, nombre, descripcion, icono, config_json,
|
||||
veces_usado, fecha_creacion, id_usuario_creador
|
||||
FROM dbo.templates_rapidos
|
||||
WHERE id_agencia IS NULL AND id_usuario_creador IS NULL AND estado = 1
|
||||
ORDER BY veces_usado DESC, fecha_creacion DESC";
|
||||
$params = [];
|
||||
break;
|
||||
|
||||
default:
|
||||
throw new Exception('Sección inválida');
|
||||
}
|
||||
|
||||
$stmt = sqlsrv_query($conn, $sql, $params);
|
||||
|
||||
if ($stmt === false) {
|
||||
throw new Exception('Error en consulta: ' . print_r(sqlsrv_errors(), true));
|
||||
}
|
||||
|
||||
while ($row = sqlsrv_fetch_array($stmt, SQLSRV_FETCH_ASSOC)) {
|
||||
// Formatear fecha
|
||||
$fecha = $row['fecha_creacion'] instanceof DateTime
|
||||
? $row['fecha_creacion']->format('d/m/Y')
|
||||
: date('d/m/Y', strtotime($row['fecha_creacion']));
|
||||
|
||||
// Determinar acciones según el tipo de template
|
||||
$acciones = '';
|
||||
$esPropio = ($row['id_usuario_creador'] == $id_usuario);
|
||||
$esAgencia = ($seccion === 'agencia');
|
||||
$esGlobal = ($seccion === 'global');
|
||||
|
||||
if ($esPropio) {
|
||||
$acciones = '
|
||||
<div class="btn-group" role="group">
|
||||
<a href="/IMPORTADORES/templates_rapidos/editar?id=' . $row['id'] . '"
|
||||
class="btn btn-sm btn-outline-primary" title="Editar">
|
||||
<i class="fas fa-edit"></i>
|
||||
</a>
|
||||
<button onclick="duplicarTemplate(' . $row['id'] . ')"
|
||||
class="btn btn-sm btn-outline-info" title="Duplicar">
|
||||
<i class="fas fa-copy"></i>
|
||||
</button>
|
||||
<button onclick="eliminarTemplate(' . $row['id'] . ')"
|
||||
class="btn btn-sm btn-outline-danger" title="Eliminar">
|
||||
<i class="fas fa-trash"></i>
|
||||
</button>
|
||||
</div>';
|
||||
} elseif ($esAgencia) {
|
||||
$acciones = '
|
||||
<div class="btn-group" role="group">
|
||||
<button onclick="mostrarVistaPrevia(' . $row['id'] . ')"
|
||||
class="btn btn-sm btn-outline-info" title="Ver">
|
||||
<i class="fas fa-eye"></i>
|
||||
</button>
|
||||
<button onclick="duplicarTemplate(' . $row['id'] . ')"
|
||||
class="btn btn-sm btn-outline-success" title="Duplicar">
|
||||
<i class="fas fa-copy"></i>
|
||||
</button>
|
||||
</div>';
|
||||
} else { // Global
|
||||
$acciones = '
|
||||
<button onclick="mostrarVistaPrevia(' . $row['id'] . ')"
|
||||
class="btn btn-sm btn-outline-info" title="Ver detalles">
|
||||
<i class="fas fa-eye"></i>
|
||||
</button>';
|
||||
}
|
||||
|
||||
// Construir nombre con icono
|
||||
$nombreConIcono = '<span class="d-flex align-items-center">
|
||||
<span class="me-2" style="font-size: 18px;">' . htmlspecialchars($row['icono']) . '</span>
|
||||
<div>
|
||||
<strong>' . htmlspecialchars($row['nombre']) . '</strong>';
|
||||
|
||||
// Agregar badge según el tipo
|
||||
if ($esPropio) {
|
||||
$nombreConIcono .= ' <span class="badge bg-primary scope-badge ms-2">Mío</span>';
|
||||
} elseif ($esAgencia) {
|
||||
$nombreConIcono .= ' <span class="badge bg-info scope-badge ms-2">Agencia</span>';
|
||||
} else {
|
||||
$nombreConIcono .= ' <span class="badge bg-secondary scope-badge ms-2">Global</span>';
|
||||
}
|
||||
|
||||
$nombreConIcono .= '</div></span>';
|
||||
|
||||
$data[] = [
|
||||
$row['id'],
|
||||
$nombreConIcono,
|
||||
htmlspecialchars($row['descripcion'] ?? 'Sin descripción'),
|
||||
'<span class="badge bg-success">' . ($row['veces_usado'] ?? 0) . '</span>',
|
||||
$fecha,
|
||||
$acciones
|
||||
];
|
||||
}
|
||||
|
||||
echo json_encode([
|
||||
'success' => true,
|
||||
'data' => $data,
|
||||
'count' => count($data)
|
||||
]);
|
||||
|
||||
} catch (Exception $e) {
|
||||
echo json_encode([
|
||||
'success' => false,
|
||||
'error' => $e->getMessage(),
|
||||
'data' => []
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
function duplicar()
|
||||
{
|
||||
if (!($_SESSION['usuario_id'] ?? false)) {
|
||||
die("⚠️ No autorizado.");
|
||||
}
|
||||
|
||||
$conn = getConnection();
|
||||
$id_usuario = $_SESSION['usuario_id'];
|
||||
$id_agencia = $_SESSION['id_agencia_en_uso'] ?? null;
|
||||
|
||||
$template_id = $_POST['template_id'] ?? null;
|
||||
|
||||
if (!$template_id || !is_numeric($template_id)) {
|
||||
die("❌ ID de template inválido.");
|
||||
}
|
||||
|
||||
// Obtener template original
|
||||
$sql = "SELECT * FROM dbo.templates_rapidos WHERE id = ?";
|
||||
$stmt = sqlsrv_query($conn, $sql, [$template_id]);
|
||||
|
||||
if ($stmt === false) {
|
||||
die("❌ Error al buscar template: " . print_r(sqlsrv_errors(), true));
|
||||
}
|
||||
|
||||
$original = sqlsrv_fetch_array($stmt, SQLSRV_FETCH_ASSOC);
|
||||
|
||||
if (!$original) {
|
||||
die("❌ Template no encontrado.");
|
||||
}
|
||||
|
||||
// Crear duplicado con nuevo nombre
|
||||
$nuevo_nombre = $original['nombre'] . ' (Copia)';
|
||||
|
||||
$sql_insert = "INSERT INTO dbo.templates_rapidos
|
||||
(nombre, descripcion, icono, config_json, tipo_moneda, incoterm,
|
||||
vinculacion, pais_proveedor, tasa_preferencial, id_agencia, id_usuario_creador)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)";
|
||||
|
||||
$params_insert = [
|
||||
$nuevo_nombre,
|
||||
$original['descripcion'],
|
||||
$original['icono'],
|
||||
$original['config_json'],
|
||||
$original['tipo_moneda'],
|
||||
$original['incoterm'],
|
||||
$original['vinculacion'],
|
||||
$original['pais_proveedor'],
|
||||
$original['tasa_preferencial'],
|
||||
$id_agencia,
|
||||
$id_usuario
|
||||
];
|
||||
|
||||
$stmt_insert = sqlsrv_query($conn, $sql_insert, $params_insert);
|
||||
|
||||
if ($stmt_insert === false) {
|
||||
die("❌ Error al duplicar: " . print_r(sqlsrv_errors(), true));
|
||||
}
|
||||
|
||||
echo "✅ Template duplicado correctamente como '{$nuevo_nombre}'.";
|
||||
}
|
||||
|
||||
function ajax_usar_template()
|
||||
{
|
||||
if (!($_SESSION['usuario_id'] ?? false)) {
|
||||
http_response_code(403);
|
||||
echo json_encode(['success' => false, 'message' => 'No autorizado']);
|
||||
exit;
|
||||
}
|
||||
|
||||
$id_template = intval($_POST['id_template'] ?? 0);
|
||||
$id_usuario = $_SESSION['usuario_id'];
|
||||
|
||||
if ($id_template <= 0) {
|
||||
echo json_encode(['success' => false, 'message' => 'ID de template inválido']);
|
||||
exit;
|
||||
}
|
||||
|
||||
$conn = getConnection();
|
||||
|
||||
// Incrementar contador de uso
|
||||
$sql = "UPDATE dbo.templates_rapidos
|
||||
SET veces_usado = veces_usado + 1, ultima_vez_usado = GETDATE()
|
||||
WHERE id = ? AND activo = 1";
|
||||
$stmt = sqlsrv_query($conn, $sql, [$id_template]);
|
||||
|
||||
if ($stmt === false) {
|
||||
echo json_encode(['success' => false, 'message' => 'Error al actualizar estadísticas']);
|
||||
exit;
|
||||
}
|
||||
|
||||
echo json_encode(['success' => true]);
|
||||
exit;
|
||||
}
|
||||
|
||||
// Funciones auxiliares
|
||||
function obtenerAduanas($conn) {
|
||||
$sql = "SELECT DISTINCT RIGHT(REPLICATE('0', 3) + CAST(aduana_seccion AS VARCHAR), 3) AS aduana_seccion, nombre FROM dbo.aduanas ORDER BY aduana_seccion";
|
||||
$stmt = sqlsrv_query($conn, $sql);
|
||||
$result = [];
|
||||
if ($stmt !== false) {
|
||||
while ($row = sqlsrv_fetch_array($stmt, SQLSRV_FETCH_ASSOC)) {
|
||||
$result[] = $row;
|
||||
}
|
||||
}
|
||||
return $result;
|
||||
}
|
||||
|
||||
function obtenerPatentes($conn) {
|
||||
$id_agencia = $_SESSION['id_agencia_en_uso'] ?? null;
|
||||
if (!$id_agencia) {
|
||||
return [];
|
||||
}
|
||||
|
||||
$sql = "SELECT id_agente, patente, agente_aduanal FROM dbo.agentes_aduanales WHERE id_agencia = ? AND activo = 1 ORDER BY patente";
|
||||
$stmt = sqlsrv_query($conn, $sql, [$id_agencia]);
|
||||
$result = [];
|
||||
if ($stmt !== false) {
|
||||
while ($row = sqlsrv_fetch_array($stmt, SQLSRV_FETCH_ASSOC)) {
|
||||
$result[] = $row;
|
||||
}
|
||||
}
|
||||
return $result;
|
||||
}
|
||||
|
||||
function obtenerIncoterms($conn) {
|
||||
$sql = "SELECT INCOTERM, DESCESPANOL FROM dbo.gIncoterms ORDER BY INCOTERM";
|
||||
$stmt = sqlsrv_query($conn, $sql);
|
||||
$result = [];
|
||||
if ($stmt !== false) {
|
||||
while ($row = sqlsrv_fetch_array($stmt, SQLSRV_FETCH_ASSOC)) {
|
||||
$result[] = $row;
|
||||
}
|
||||
}
|
||||
return $result;
|
||||
}
|
||||
|
||||
function obtenerPaises($conn) {
|
||||
$sql = "SELECT id_pais, nombre FROM dbo.paises ORDER BY nombre";
|
||||
$stmt = sqlsrv_query($conn, $sql);
|
||||
$result = [];
|
||||
if ($stmt !== false) {
|
||||
while ($row = sqlsrv_fetch_array($stmt, SQLSRV_FETCH_ASSOC)) {
|
||||
$result[] = $row;
|
||||
}
|
||||
}
|
||||
return $result;
|
||||
}
|
||||
|
||||
function obtenerTransportistas($conn) {
|
||||
$id_usuario = $_SESSION['usuario_id'] ?? null;
|
||||
if (!$id_usuario) {
|
||||
return [];
|
||||
}
|
||||
|
||||
$sql = "SELECT id_transportista, clave_identificador, nombre FROM dbo.transportistas WHERE id_usuario = ? AND activo = 1 ORDER BY nombre";
|
||||
$stmt = sqlsrv_query($conn, $sql, [$id_usuario]);
|
||||
$result = [];
|
||||
if ($stmt !== false) {
|
||||
while ($row = sqlsrv_fetch_array($stmt, SQLSRV_FETCH_ASSOC)) {
|
||||
$result[] = $row;
|
||||
}
|
||||
}
|
||||
return $result;
|
||||
}
|
||||
|
||||
function obtenerChoferes($conn) {
|
||||
$id_usuario = $_SESSION['usuario_id'] ?? null;
|
||||
if (!$id_usuario) {
|
||||
return [];
|
||||
}
|
||||
|
||||
$sql = "SELECT c.id_chofer, c.nombre+' '+c.apellido AS nombre, c.transportista_id
|
||||
FROM dbo.choferes c
|
||||
JOIN dbo.transportistas t ON c.transportista_id = t.id_transportista
|
||||
WHERE t.id_usuario = ? AND c.status = 1
|
||||
ORDER BY c.nombre";
|
||||
$stmt = sqlsrv_query($conn, $sql, [$id_usuario]);
|
||||
$result = [];
|
||||
if ($stmt !== false) {
|
||||
while ($row = sqlsrv_fetch_array($stmt, SQLSRV_FETCH_ASSOC)) {
|
||||
$result[] = $row;
|
||||
}
|
||||
}
|
||||
return $result;
|
||||
}
|
||||
|
||||
function obtenerUnidadesMedida($conn) {
|
||||
$sql = "SELECT id, descripcion FROM dbo.unidades_medida_apendice7 ORDER BY id";
|
||||
$stmt = sqlsrv_query($conn, $sql);
|
||||
$result = [];
|
||||
if ($stmt !== false) {
|
||||
while ($row = sqlsrv_fetch_array($stmt, SQLSRV_FETCH_ASSOC)) {
|
||||
$result[] = $row;
|
||||
}
|
||||
}
|
||||
return $result;
|
||||
}
|
||||
237
app/controllers/winsaai.php
Normal file
237
app/controllers/winsaai.php
Normal file
@@ -0,0 +1,237 @@
|
||||
<?php
|
||||
require_once __DIR__ . '/../helpers/session.php';
|
||||
require_once __DIR__ . '/../../config/database.php';
|
||||
require_once __DIR__ . '/../helpers/crypto.php';
|
||||
require_once __DIR__ . '/../helpers/env.php';
|
||||
|
||||
loadEnv();
|
||||
|
||||
function save_config() {
|
||||
$conn = getConnection();
|
||||
$userId = $_SESSION['usuario_id'] ?? null;
|
||||
|
||||
if (!$userId) {
|
||||
http_response_code(401);
|
||||
header('Content-Type: application/json');
|
||||
echo json_encode(['success' => false, 'message' => 'Sesión expirada']);
|
||||
exit;
|
||||
}
|
||||
|
||||
if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
|
||||
http_response_code(405);
|
||||
header('Content-Type: application/json');
|
||||
echo json_encode(['success' => false, 'message' => 'Método no permitido']);
|
||||
exit;
|
||||
}
|
||||
|
||||
$input = json_decode(file_get_contents('php://input'), true);
|
||||
|
||||
if (json_last_error() !== JSON_ERROR_NONE) {
|
||||
http_response_code(400);
|
||||
header('Content-Type: application/json');
|
||||
echo json_encode(['success' => false, 'message' => 'JSON inválido']);
|
||||
exit;
|
||||
}
|
||||
|
||||
$host = trim($input['host'] ?? '');
|
||||
$port = intval($input['port'] ?? 80);
|
||||
$protocol = $input['protocol'] ?? 'https';
|
||||
$usuario = trim($input['usuario'] ?? '');
|
||||
$password = $input['password'] ?? '';
|
||||
$sync_pedimentos = $input['sync_pedimentos'] ?? true;
|
||||
$sync_coves = $input['sync_coves'] ?? true;
|
||||
|
||||
// Validaciones
|
||||
if (empty($host) || empty($usuario) || empty($password)) {
|
||||
http_response_code(400);
|
||||
header('Content-Type: application/json');
|
||||
echo json_encode(['success' => false, 'message' => 'Todos los campos son obligatorios']);
|
||||
exit;
|
||||
}
|
||||
|
||||
// Encriptar contraseña
|
||||
$encryptedPassword = encrypt($password);
|
||||
|
||||
// Verificar si existe configuración
|
||||
$sqlCheck = "SELECT id FROM winsaai_config WHERE id_usuario = ?";
|
||||
$stmtCheck = sqlsrv_query($conn, $sqlCheck, [$userId]);
|
||||
|
||||
if ($stmtCheck === false) {
|
||||
http_response_code(500);
|
||||
header('Content-Type: application/json');
|
||||
echo json_encode(['success' => false, 'message' => 'Error en base de datos']);
|
||||
exit;
|
||||
}
|
||||
|
||||
$existingConfig = sqlsrv_fetch_array($stmtCheck, SQLSRV_FETCH_ASSOC);
|
||||
sqlsrv_free_stmt($stmtCheck);
|
||||
|
||||
if ($existingConfig) {
|
||||
// Actualizar
|
||||
$sql = "UPDATE winsaai_config
|
||||
SET host = ?, port = ?, protocol = ?, usuario = ?, password = ?,
|
||||
sync_pedimentos = ?, sync_coves = ?, updated_at = GETDATE()
|
||||
WHERE id_usuario = ?";
|
||||
$params = [$host, $port, $protocol, $usuario, $encryptedPassword,
|
||||
$sync_pedimentos ? 1 : 0, $sync_coves ? 1 : 0, $userId];
|
||||
} else {
|
||||
// Insertar
|
||||
$sql = "INSERT INTO winsaai_config (id_usuario, host, port, protocol, usuario, password, sync_pedimentos, sync_coves)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?)";
|
||||
$params = [$userId, $host, $port, $protocol, $usuario, $encryptedPassword,
|
||||
$sync_pedimentos ? 1 : 0, $sync_coves ? 1 : 0];
|
||||
}
|
||||
|
||||
$stmt = sqlsrv_query($conn, $sql, $params);
|
||||
|
||||
if ($stmt === false) {
|
||||
http_response_code(500);
|
||||
header('Content-Type: application/json');
|
||||
echo json_encode(['success' => false, 'message' => 'Error al guardar configuración']);
|
||||
exit;
|
||||
}
|
||||
|
||||
sqlsrv_free_stmt($stmt);
|
||||
|
||||
header('Content-Type: application/json');
|
||||
echo json_encode(['success' => true, 'message' => 'Configuración guardada correctamente']);
|
||||
}
|
||||
|
||||
function get_config() {
|
||||
$conn = getConnection();
|
||||
$userId = $_SESSION['usuario_id'] ?? null;
|
||||
|
||||
if (!$userId) {
|
||||
http_response_code(401);
|
||||
header('Content-Type: application/json');
|
||||
echo json_encode(['success' => false, 'message' => 'Sesión expirada']);
|
||||
exit;
|
||||
}
|
||||
|
||||
$sql = "SELECT * FROM winsaai_config WHERE id_usuario = ?";
|
||||
$stmt = sqlsrv_query($conn, $sql, [$userId]);
|
||||
|
||||
if ($stmt === false) {
|
||||
http_response_code(500);
|
||||
header('Content-Type: application/json');
|
||||
echo json_encode(['success' => false, 'message' => 'Error en base de datos']);
|
||||
exit;
|
||||
}
|
||||
|
||||
$config = sqlsrv_fetch_array($stmt, SQLSRV_FETCH_ASSOC);
|
||||
sqlsrv_free_stmt($stmt);
|
||||
|
||||
if ($config) {
|
||||
// No enviar contraseña por seguridad
|
||||
unset($config['password']);
|
||||
// Convertir BIT a boolean
|
||||
$config['sync_pedimentos'] = (bool)$config['sync_pedimentos'];
|
||||
$config['sync_coves'] = (bool)$config['sync_coves'];
|
||||
|
||||
header('Content-Type: application/json');
|
||||
echo json_encode(['success' => true, 'data' => $config]);
|
||||
} else {
|
||||
header('Content-Type: application/json');
|
||||
echo json_encode(['success' => false, 'message' => 'No hay configuración']);
|
||||
}
|
||||
}
|
||||
|
||||
function test_connection() {
|
||||
$conn = getConnection();
|
||||
$userId = $_SESSION['usuario_id'] ?? null;
|
||||
|
||||
if (!$userId) {
|
||||
http_response_code(401);
|
||||
header('Content-Type: application/json');
|
||||
echo json_encode(['success' => false, 'message' => 'Sesión expirada']);
|
||||
exit;
|
||||
}
|
||||
|
||||
$input = json_decode(file_get_contents('php://input'), true);
|
||||
|
||||
$host = trim($input['host'] ?? '');
|
||||
$port = intval($input['port'] ?? 80);
|
||||
$protocol = $input['protocol'] ?? 'https';
|
||||
$usuario = trim($input['usuario'] ?? '');
|
||||
$password = $input['password'] ?? '';
|
||||
|
||||
if (empty($host) || empty($usuario) || empty($password)) {
|
||||
http_response_code(400);
|
||||
header('Content-Type: application/json');
|
||||
echo json_encode(['success' => false, 'message' => 'Faltan datos para probar conexión']);
|
||||
exit;
|
||||
}
|
||||
|
||||
$url = "{$protocol}://{$host}:{$port}/api/test";
|
||||
|
||||
// Simular prueba de conexión (aquí pondrías la lógica real)
|
||||
header('Content-Type: application/json');
|
||||
echo json_encode(['success' => true, 'message' => "Conexión exitosa con {$protocol}://{$host}:{$port}"]);
|
||||
}
|
||||
|
||||
function sync_data() {
|
||||
$conn = getConnection();
|
||||
$userId = $_SESSION['usuario_id'] ?? null;
|
||||
|
||||
if (!$userId) {
|
||||
http_response_code(401);
|
||||
header('Content-Type: application/json');
|
||||
echo json_encode(['success' => false, 'message' => 'Sesión expirada']);
|
||||
exit;
|
||||
}
|
||||
|
||||
// Obtener configuración del usuario
|
||||
$sql = "SELECT * FROM winsaai_config WHERE id_usuario = ?";
|
||||
$stmt = sqlsrv_query($conn, $sql, [$userId]);
|
||||
|
||||
if ($stmt === false) {
|
||||
http_response_code(500);
|
||||
header('Content-Type: application/json');
|
||||
echo json_encode(['success' => false, 'message' => 'Error en base de datos']);
|
||||
exit;
|
||||
}
|
||||
|
||||
$config = sqlsrv_fetch_array($stmt, SQLSRV_FETCH_ASSOC);
|
||||
sqlsrv_free_stmt($stmt);
|
||||
|
||||
if (!$config) {
|
||||
http_response_code(400);
|
||||
header('Content-Type: application/json');
|
||||
echo json_encode(['success' => false, 'message' => 'No hay configuración de WINSAAI']);
|
||||
exit;
|
||||
}
|
||||
|
||||
$input = json_decode(file_get_contents('php://input'), true);
|
||||
$syncType = $input['sync_type'] ?? 'both';
|
||||
|
||||
// Simular sincronización
|
||||
$results = [];
|
||||
if ($syncType === 'pedimentos' || $syncType === 'both') {
|
||||
$results['pedimentos'] = ['total' => 10, 'processed' => 10];
|
||||
}
|
||||
if ($syncType === 'coves' || $syncType === 'both') {
|
||||
$results['coves'] = ['total' => 5, 'processed' => 5];
|
||||
}
|
||||
|
||||
// Actualizar última sincronización
|
||||
$sqlUpdate = "UPDATE winsaai_config SET last_sync = GETDATE() WHERE id_usuario = ?";
|
||||
sqlsrv_query($conn, $sqlUpdate, [$userId]);
|
||||
|
||||
header('Content-Type: application/json');
|
||||
echo json_encode(['success' => true, 'message' => 'Sincronización completada', 'data' => $results]);
|
||||
}
|
||||
|
||||
function index() {
|
||||
header('Content-Type: application/json');
|
||||
echo json_encode([
|
||||
'success' => true,
|
||||
'message' => 'API WINSAAI disponible',
|
||||
'endpoints' => [
|
||||
'save_config' => '/IMPORTADORES/winsaai/save_config',
|
||||
'test_connection' => '/IMPORTADORES/winsaai/test_connection',
|
||||
'sync_data' => '/IMPORTADORES/winsaai/sync_data',
|
||||
'get_config' => '/IMPORTADORES/winsaai/get_config'
|
||||
]
|
||||
]);
|
||||
}
|
||||
?>
|
||||
21
claves_pedimentos_tabla.sql
Normal file
21
claves_pedimentos_tabla.sql
Normal file
@@ -0,0 +1,21 @@
|
||||
-- Tabla para claves de pedimentos configurables por usuario
|
||||
CREATE TABLE claves_pedimentos_usuario (
|
||||
id_clave_pedimento INT PRIMARY KEY IDENTITY,
|
||||
id_usuario INT NOT NULL,
|
||||
codigo VARCHAR(10) NOT NULL,
|
||||
descripcion NVARCHAR(255) NOT NULL,
|
||||
tipo_operacion VARCHAR(50), -- 'importacion', 'exportacion', etc.
|
||||
activo BIT DEFAULT 1,
|
||||
fecha_creacion DATETIME DEFAULT GETDATE(),
|
||||
fecha_modificacion DATETIME DEFAULT GETDATE(),
|
||||
FOREIGN KEY (id_usuario) REFERENCES usuarios_sistema(id_usuario),
|
||||
UNIQUE(id_usuario, codigo) -- Un usuario no puede tener códigos duplicados
|
||||
);
|
||||
|
||||
-- Índices para optimizar consultas
|
||||
CREATE INDEX IX_claves_pedimentos_usuario_id ON claves_pedimentos_usuario(id_usuario);
|
||||
CREATE INDEX IX_claves_pedimentos_activo ON claves_pedimentos_usuario(activo);
|
||||
|
||||
-- NOTA: Los datos de ejemplo se omiten porque requieren usuarios existentes
|
||||
-- Las claves se insertarán automáticamente cuando el usuario use la función
|
||||
-- "inicializar_claves_usuario()" desde la aplicación web
|
||||
97
configuracion_ventanilla_unica.sql
Normal file
97
configuracion_ventanilla_unica.sql
Normal file
@@ -0,0 +1,97 @@
|
||||
-- Tabla para configuración de Ventanilla Única
|
||||
-- Script de creación para SQL Server
|
||||
|
||||
IF NOT EXISTS (SELECT * FROM sysobjects WHERE name='configuracion_ventanilla_unica' AND xtype='U')
|
||||
BEGIN
|
||||
CREATE TABLE configuracion_ventanilla_unica (
|
||||
id_configuracion INT IDENTITY(1,1) PRIMARY KEY,
|
||||
id_usuario INT NOT NULL,
|
||||
ruta_ejecutable NVARCHAR(500) NOT NULL,
|
||||
ruta_archivo_key NVARCHAR(500) NULL,
|
||||
ruta_archivo_cer NVARCHAR(500) NULL,
|
||||
clave_fiel NVARCHAR(MAX) NULL, -- Encriptado
|
||||
rfc_usuario_vu NVARCHAR(13) NOT NULL,
|
||||
clave_webservice NVARCHAR(MAX) NULL, -- Encriptado
|
||||
fecha_creacion DATETIME2 DEFAULT GETDATE(),
|
||||
fecha_actualizacion DATETIME2 NULL,
|
||||
activo BIT DEFAULT 1,
|
||||
|
||||
-- Constraints
|
||||
CONSTRAINT FK_configuracion_vu_usuario
|
||||
FOREIGN KEY (id_usuario) REFERENCES usuarios_sistema(id_usuario)
|
||||
ON DELETE CASCADE,
|
||||
|
||||
CONSTRAINT UQ_configuracion_vu_usuario
|
||||
UNIQUE (id_usuario)
|
||||
);
|
||||
|
||||
PRINT 'Tabla configuracion_ventanilla_unica creada exitosamente';
|
||||
END
|
||||
ELSE
|
||||
BEGIN
|
||||
PRINT 'La tabla configuracion_ventanilla_unica ya existe';
|
||||
END
|
||||
|
||||
-- Crear índices para mejorar rendimiento
|
||||
IF NOT EXISTS (SELECT * FROM sys.indexes WHERE name='IX_configuracion_vu_usuario' AND object_id = OBJECT_ID('configuracion_ventanilla_unica'))
|
||||
BEGIN
|
||||
CREATE INDEX IX_configuracion_vu_usuario ON configuracion_ventanilla_unica(id_usuario);
|
||||
PRINT 'Índice IX_configuracion_vu_usuario creado';
|
||||
END
|
||||
|
||||
IF NOT EXISTS (SELECT * FROM sys.indexes WHERE name='IX_configuracion_vu_rfc' AND object_id = OBJECT_ID('configuracion_ventanilla_unica'))
|
||||
BEGIN
|
||||
CREATE INDEX IX_configuracion_vu_rfc ON configuracion_ventanilla_unica(rfc_usuario_vu);
|
||||
PRINT 'Índice IX_configuracion_vu_rfc creado';
|
||||
END
|
||||
|
||||
-- Agregar comentarios descriptivos
|
||||
EXEC sp_addextendedproperty
|
||||
@name = N'MS_Description',
|
||||
@value = N'Configuración de Ventanilla Única para transmisiones de Manifestación de Valor Electrónica',
|
||||
@level0type = N'SCHEMA', @level0name = N'dbo',
|
||||
@level1type = N'TABLE', @level1name = N'configuracion_ventanilla_unica';
|
||||
|
||||
EXEC sp_addextendedproperty
|
||||
@name = N'MS_Description',
|
||||
@value = N'Ruta completa al archivo ejecutable de Ventanilla Única',
|
||||
@level0type = N'SCHEMA', @level0name = N'dbo',
|
||||
@level1type = N'TABLE', @level1name = N'configuracion_ventanilla_unica',
|
||||
@level2type = N'COLUMN', @level2name = N'ruta_ejecutable';
|
||||
|
||||
EXEC sp_addextendedproperty
|
||||
@name = N'MS_Description',
|
||||
@value = N'Ruta al archivo KEY del certificado FIEL',
|
||||
@level0type = N'SCHEMA', @level0name = N'dbo',
|
||||
@level1type = N'TABLE', @level1name = N'configuracion_ventanilla_unica',
|
||||
@level2type = N'COLUMN', @level2name = N'ruta_archivo_key';
|
||||
|
||||
EXEC sp_addextendedproperty
|
||||
@name = N'MS_Description',
|
||||
@value = N'Ruta al archivo CER del certificado FIEL',
|
||||
@level0type = N'SCHEMA', @level0name = N'dbo',
|
||||
@level1type = N'TABLE', @level1name = N'configuracion_ventanilla_unica',
|
||||
@level2type = N'COLUMN', @level2name = N'ruta_archivo_cer';
|
||||
|
||||
EXEC sp_addextendedproperty
|
||||
@name = N'MS_Description',
|
||||
@value = N'Contraseña FIEL encriptada',
|
||||
@level0type = N'SCHEMA', @level0name = N'dbo',
|
||||
@level1type = N'TABLE', @level1name = N'configuracion_ventanilla_unica',
|
||||
@level2type = N'COLUMN', @level2name = N'clave_fiel';
|
||||
|
||||
EXEC sp_addextendedproperty
|
||||
@name = N'MS_Description',
|
||||
@value = N'RFC del usuario para acceso a Ventanilla Única',
|
||||
@level0type = N'SCHEMA', @level0name = N'dbo',
|
||||
@level1type = N'TABLE', @level1name = N'configuracion_ventanilla_unica',
|
||||
@level2type = N'COLUMN', @level2name = N'rfc_usuario_vu';
|
||||
|
||||
EXEC sp_addextendedproperty
|
||||
@name = N'MS_Description',
|
||||
@value = N'Contraseña del Web Service encriptada',
|
||||
@level0type = N'SCHEMA', @level0name = N'dbo',
|
||||
@level1type = N'TABLE', @level1name = N'configuracion_ventanilla_unica',
|
||||
@level2type = N'COLUMN', @level2name = N'clave_webservice';
|
||||
|
||||
PRINT 'Script de configuración de Ventanilla Única completado exitosamente';
|
||||
52
create_table_temp.php
Normal file
52
create_table_temp.php
Normal file
@@ -0,0 +1,52 @@
|
||||
<?php
|
||||
// Script temporal para crear la tabla claves_pedimentos_usuario
|
||||
require_once __DIR__ . '/config/database.php';
|
||||
|
||||
try {
|
||||
$conn = getConnection();
|
||||
echo "✅ Conexión establecida exitosamente\n";
|
||||
|
||||
// Leer el script SQL
|
||||
$sqlScript = file_get_contents(__DIR__ . '/claves_pedimentos_tabla.sql');
|
||||
|
||||
if (!$sqlScript) {
|
||||
die("❌ No se pudo leer el archivo SQL\n");
|
||||
}
|
||||
|
||||
// Dividir el script en declaraciones individuales
|
||||
$statements = explode(';', $sqlScript);
|
||||
|
||||
$executed = 0;
|
||||
foreach ($statements as $statement) {
|
||||
$statement = trim($statement);
|
||||
|
||||
// Saltar declaraciones vacías y comentarios
|
||||
if (empty($statement) || strpos($statement, '--') === 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
echo "Ejecutando: " . substr($statement, 0, 50) . "...\n";
|
||||
|
||||
$result = sqlsrv_query($conn, $statement);
|
||||
|
||||
if ($result === false) {
|
||||
$errors = sqlsrv_errors();
|
||||
// Si el error es que la tabla ya existe, continuamos
|
||||
if (isset($errors[0]['code']) && $errors[0]['code'] == 2714) {
|
||||
echo "⚠️ La tabla ya existe, continuando...\n";
|
||||
continue;
|
||||
}
|
||||
echo "❌ Error: " . print_r($errors, true) . "\n";
|
||||
} else {
|
||||
$executed++;
|
||||
echo "✅ Ejecutado exitosamente\n";
|
||||
}
|
||||
}
|
||||
|
||||
echo "\n🎉 Proceso completado. Se ejecutaron $executed declaraciones SQL.\n";
|
||||
echo "La tabla 'claves_pedimentos_usuario' debería estar creada ahora.\n";
|
||||
|
||||
} catch (Exception $e) {
|
||||
echo "❌ Error: " . $e->getMessage() . "\n";
|
||||
}
|
||||
?>
|
||||
196
debug_templates.php
Normal file
196
debug_templates.php
Normal file
@@ -0,0 +1,196 @@
|
||||
<?php
|
||||
// Script de debug simple para templates
|
||||
session_start();
|
||||
|
||||
// Para debugging, vamos a simular una sesión válida
|
||||
if (!isset($_SESSION['usuario_id'])) {
|
||||
$_SESSION['usuario_id'] = 1; // Cambia este valor por tu ID de usuario real
|
||||
$_SESSION['id_agencia_en_uso'] = 1; // Cambia por tu ID de agencia real
|
||||
}
|
||||
|
||||
require_once __DIR__ . '/config/database.php';
|
||||
|
||||
echo "<h2>🔍 Debug Simple de Templates</h2>";
|
||||
echo "<p><strong>Usuario actual:</strong> " . $_SESSION['usuario_id'] . "</p>";
|
||||
echo "<p><strong>Agencia actual:</strong> " . ($_SESSION['id_agencia_en_uso'] ?? 'NULL') . "</p>";
|
||||
|
||||
try {
|
||||
$conn = getConnection();
|
||||
|
||||
// 1. Ver todos los templates sin filtros
|
||||
echo "<h3>1. Todos los templates en la base de datos:</h3>";
|
||||
$sql = "SELECT id, nombre, descripcion, activo, id_usuario_creador, id_agencia,
|
||||
fecha_creacion, config_json
|
||||
FROM dbo.templates_rapidos
|
||||
ORDER BY fecha_creacion DESC";
|
||||
|
||||
$stmt = sqlsrv_query($conn, $sql);
|
||||
|
||||
if ($stmt === false) {
|
||||
$errors = sqlsrv_errors();
|
||||
echo "<div style='color: red;'>❌ Error en consulta: " . print_r($errors, true) . "</div>";
|
||||
} else {
|
||||
$count = 0;
|
||||
echo "<table border='1' style='border-collapse: collapse; width: 100%;'>";
|
||||
echo "<tr style='background: #f0f0f0;'>
|
||||
<th>ID</th>
|
||||
<th>Nombre</th>
|
||||
<th>Descripción</th>
|
||||
<th>Activo</th>
|
||||
<th>Usuario Creador</th>
|
||||
<th>Agencia</th>
|
||||
<th>Fecha Creación</th>
|
||||
<th>Tiene Config</th>
|
||||
</tr>";
|
||||
|
||||
while ($row = sqlsrv_fetch_array($stmt, SQLSRV_FETCH_ASSOC)) {
|
||||
$count++;
|
||||
$fecha = $row['fecha_creacion'] instanceof DateTime
|
||||
? $row['fecha_creacion']->format('Y-m-d H:i:s')
|
||||
: $row['fecha_creacion'];
|
||||
|
||||
$tieneConfig = !empty($row['config_json']) ? 'Sí' : 'No';
|
||||
|
||||
echo "<tr>";
|
||||
echo "<td>" . $row['id'] . "</td>";
|
||||
echo "<td><strong>" . htmlspecialchars($row['nombre']) . "</strong></td>";
|
||||
echo "<td>" . htmlspecialchars($row['descripcion'] ?? '') . "</td>";
|
||||
echo "<td>" . ($row['activo'] ? '✅' : '❌') . "</td>";
|
||||
echo "<td>" . ($row['id_usuario_creador'] ?? 'NULL') . "</td>";
|
||||
echo "<td>" . ($row['id_agencia'] ?? 'NULL') . "</td>";
|
||||
echo "<td>" . $fecha . "</td>";
|
||||
echo "<td>" . $tieneConfig . "</td>";
|
||||
echo "</tr>";
|
||||
}
|
||||
echo "</table>";
|
||||
|
||||
echo "<p><strong>Total de templates encontrados: $count</strong></p>";
|
||||
}
|
||||
|
||||
// 2. Probar la consulta exacta del endpoint
|
||||
echo "<h3>2. Probando consulta del endpoint (con filtros):</h3>";
|
||||
$id_usuario = $_SESSION['usuario_id'];
|
||||
$id_agencia = $_SESSION['id_agencia_en_uso'];
|
||||
|
||||
$sql_endpoint = "SELECT id, nombre, descripcion, icono, config_json,
|
||||
ISNULL(veces_usado, 0) as veces_usado,
|
||||
id_usuario_creador, id_agencia
|
||||
FROM dbo.templates_rapidos
|
||||
WHERE activo = 1
|
||||
AND (id_usuario_creador = ? OR id_agencia = ? OR id_agencia IS NULL)
|
||||
ORDER BY
|
||||
CASE WHEN id_usuario_creador = ? THEN 0 ELSE 1 END,
|
||||
veces_usado DESC,
|
||||
nombre ASC";
|
||||
|
||||
$stmt_endpoint = sqlsrv_query($conn, $sql_endpoint, [$id_usuario, $id_agencia, $id_usuario]);
|
||||
|
||||
if ($stmt_endpoint === false) {
|
||||
$errors = sqlsrv_errors();
|
||||
echo "<div style='color: red;'>❌ Error en consulta endpoint: " . print_r($errors, true) . "</div>";
|
||||
} else {
|
||||
$count_endpoint = 0;
|
||||
echo "<table border='1' style='border-collapse: collapse; width: 100%;'>";
|
||||
echo "<tr style='background: #e3f2fd;'>
|
||||
<th>ID</th>
|
||||
<th>Nombre</th>
|
||||
<th>Es Mío</th>
|
||||
<th>Usuario Creador</th>
|
||||
<th>Agencia</th>
|
||||
<th>Debería Aparecer</th>
|
||||
</tr>";
|
||||
|
||||
while ($row = sqlsrv_fetch_array($stmt_endpoint, SQLSRV_FETCH_ASSOC)) {
|
||||
$count_endpoint++;
|
||||
$esMio = ($row['id_usuario_creador'] == $id_usuario) ? 'SÍ' : 'NO';
|
||||
|
||||
echo "<tr>";
|
||||
echo "<td>" . $row['id'] . "</td>";
|
||||
echo "<td><strong>" . htmlspecialchars($row['nombre']) . "</strong></td>";
|
||||
echo "<td style='color: " . ($esMio === 'SÍ' ? 'green' : 'blue') . ";'><strong>$esMio</strong></td>";
|
||||
echo "<td>" . ($row['id_usuario_creador'] ?? 'NULL') . "</td>";
|
||||
echo "<td>" . ($row['id_agencia'] ?? 'NULL') . "</td>";
|
||||
echo "<td>✅ SÍ</td>";
|
||||
echo "</tr>";
|
||||
}
|
||||
echo "</table>";
|
||||
|
||||
echo "<p><strong>Templates que deberían aparecer en el formulario: $count_endpoint</strong></p>";
|
||||
}
|
||||
|
||||
// 3. Probar el endpoint AJAX directamente
|
||||
echo "<h3>3. Prueba del endpoint AJAX:</h3>";
|
||||
echo "<p><a href='/IMPORTADORES/templates_rapidos/ajax_obtener_templates' target='_blank' style='background: #007bff; color: white; padding: 10px 15px; text-decoration: none; border-radius: 5px;'>🔗 Abrir endpoint AJAX en nueva pestaña</a></p>";
|
||||
|
||||
// 4. Verificar la sesión
|
||||
echo "<h3>4. Estado de la sesión:</h3>";
|
||||
echo "<pre>";
|
||||
echo "SESSION:\n";
|
||||
foreach ($_SESSION as $key => $value) {
|
||||
if (is_string($value) || is_numeric($value)) {
|
||||
echo " $key: $value\n";
|
||||
}
|
||||
}
|
||||
echo "</pre>";
|
||||
|
||||
} catch (Exception $e) {
|
||||
echo "<div style='color: red;'>❌ <strong>Error:</strong> " . $e->getMessage() . "</div>";
|
||||
}
|
||||
?>
|
||||
|
||||
<script>
|
||||
// Script para probar el AJAX desde aquí mismo
|
||||
function probarAjax() {
|
||||
console.log('🔄 Probando AJAX...');
|
||||
|
||||
fetch('/IMPORTADORES/templates_rapidos/ajax_obtener_templates', {
|
||||
method: 'GET',
|
||||
headers: {
|
||||
'Accept': 'application/json',
|
||||
'X-Requested-With': 'XMLHttpRequest'
|
||||
}
|
||||
})
|
||||
.then(response => {
|
||||
console.log('📦 Status:', response.status);
|
||||
return response.text();
|
||||
})
|
||||
.then(data => {
|
||||
console.log('📄 Respuesta cruda:', data);
|
||||
try {
|
||||
const json = JSON.parse(data);
|
||||
console.log('✅ JSON parseado:', json);
|
||||
|
||||
document.getElementById('ajax-result').innerHTML = `
|
||||
<h4>Resultado del AJAX:</h4>
|
||||
<pre style="background: #f8f9fa; padding: 15px; border-radius: 5px; overflow-x: auto;">${JSON.stringify(json, null, 2)}</pre>
|
||||
`;
|
||||
} catch (e) {
|
||||
console.error('❌ Error parseando JSON:', e);
|
||||
document.getElementById('ajax-result').innerHTML = `
|
||||
<h4>Respuesta del servidor (no es JSON válido):</h4>
|
||||
<pre style="background: #fff3cd; padding: 15px; border-radius: 5px; overflow-x: auto;">${data}</pre>
|
||||
`;
|
||||
}
|
||||
})
|
||||
.catch(error => {
|
||||
console.error('❌ Error AJAX:', error);
|
||||
document.getElementById('ajax-result').innerHTML = `
|
||||
<h4>Error en la petición:</h4>
|
||||
<pre style="background: #f8d7da; padding: 15px; border-radius: 5px;">${error.message}</pre>
|
||||
`;
|
||||
});
|
||||
}
|
||||
</script>
|
||||
|
||||
<h3>5. Prueba AJAX en tiempo real:</h3>
|
||||
<button onclick="probarAjax()" style="background: #28a745; color: white; padding: 10px 20px; border: none; border-radius: 5px; cursor: pointer;">
|
||||
🧪 Probar AJAX ahora
|
||||
</button>
|
||||
<div id="ajax-result" style="margin-top: 15px;"></div>
|
||||
|
||||
<hr>
|
||||
<p><small><strong>💡 Instrucciones:</strong><br>
|
||||
1. Revisa los templates en la tabla de arriba<br>
|
||||
2. Verifica que tu usuario_id y agencia_id sean correctos<br>
|
||||
3. Haz clic en "Probar AJAX ahora" para ver la respuesta en tiempo real<br>
|
||||
4. Abre la consola del navegador (F12) para ver logs detallados</small></p>
|
||||
90
mve_incrementales_decrementales.sql
Normal file
90
mve_incrementales_decrementales.sql
Normal file
@@ -0,0 +1,90 @@
|
||||
-- Tabla para almacenar datos de Manifestación de Valor por factura
|
||||
CREATE TABLE mve_facturas_datos (
|
||||
id INT IDENTITY(1,1) PRIMARY KEY,
|
||||
id_pedimento INT NOT NULL,
|
||||
id_factura INT NOT NULL,
|
||||
numero_factura NVARCHAR(100),
|
||||
|
||||
-- Campos Art. 65 - Incrementables
|
||||
art65_fecha_transporte DATE NULL,
|
||||
art65_importe_transporte DECIMAL(15,2) NULL,
|
||||
art65_fecha_descuentos DATE NULL,
|
||||
art65_importe_descuentos DECIMAL(15,2) NULL,
|
||||
art65_fecha_posteriores DATE NULL,
|
||||
art65_importe_posteriores DECIMAL(15,2) NULL,
|
||||
art65_fecha_contribuciones DATE NULL,
|
||||
art65_importe_contribuciones DECIMAL(15,2) NULL,
|
||||
art65_fecha_pagos_vendedor DATE NULL,
|
||||
art65_importe_pagos_vendedor DECIMAL(15,2) NULL,
|
||||
|
||||
-- Campos Art. 66 - Decrementables
|
||||
art66_fecha_comisiones DATE NULL,
|
||||
art66_importe_comisiones DECIMAL(15,2) NULL,
|
||||
art66_cargo_comisiones NVARCHAR(10) CHECK (art66_cargo_comisiones IN ('Si', 'No')) NULL,
|
||||
|
||||
art66_fecha_envases DATE NULL,
|
||||
art66_importe_envases DECIMAL(15,2) NULL,
|
||||
art66_cargo_envases NVARCHAR(10) CHECK (art66_cargo_envases IN ('Si', 'No')) NULL,
|
||||
|
||||
art66_fecha_embalaje DATE NULL,
|
||||
art66_importe_embalaje DECIMAL(15,2) NULL,
|
||||
art66_cargo_embalaje NVARCHAR(10) CHECK (art66_cargo_embalaje IN ('Si', 'No')) NULL,
|
||||
|
||||
art66_fecha_transporte_dec DATE NULL,
|
||||
art66_importe_transporte_dec DECIMAL(15,2) NULL,
|
||||
art66_cargo_transporte_dec NVARCHAR(10) CHECK (art66_cargo_transporte_dec IN ('Si', 'No')) NULL,
|
||||
|
||||
art66_fecha_ingenieria DATE NULL,
|
||||
art66_importe_ingenieria DECIMAL(15,2) NULL,
|
||||
art66_cargo_ingenieria NVARCHAR(10) CHECK (art66_cargo_ingenieria IN ('Si', 'No')) NULL,
|
||||
|
||||
art66_fecha_regalias DATE NULL,
|
||||
art66_importe_regalias DECIMAL(15,2) NULL,
|
||||
art66_cargo_regalias NVARCHAR(10) CHECK (art66_cargo_regalias IN ('Si', 'No')) NULL,
|
||||
|
||||
art66_fecha_producto DATE NULL,
|
||||
art66_importe_producto DECIMAL(15,2) NULL,
|
||||
art66_cargo_producto NVARCHAR(10) CHECK (art66_cargo_producto IN ('Si', 'No')) NULL,
|
||||
|
||||
-- Campos de control
|
||||
fecha_creacion DATETIME2 DEFAULT GETDATE(),
|
||||
fecha_actualizacion DATETIME2 DEFAULT GETDATE(),
|
||||
usuario_creacion NVARCHAR(100),
|
||||
|
||||
-- Índices y restricciones
|
||||
CONSTRAINT UQ_mve_facturas_datos_pedimento_factura UNIQUE (id_pedimento, id_factura)
|
||||
);
|
||||
|
||||
-- Crear índices separadamente
|
||||
CREATE INDEX IX_mve_facturas_datos_pedimento ON mve_facturas_datos (id_pedimento);
|
||||
CREATE INDEX IX_mve_facturas_datos_factura ON mve_facturas_datos (id_factura);
|
||||
|
||||
-- Tabla para el historial de solicitudes MVE
|
||||
CREATE TABLE mve_solicitudes (
|
||||
id INT IDENTITY(1,1) PRIMARY KEY,
|
||||
id_pedimento INT NOT NULL,
|
||||
numero_pedimento NVARCHAR(50),
|
||||
estado NVARCHAR(20) CHECK (estado IN ('Pendiente', 'En_Proceso', 'Completada', 'Rechazada')) DEFAULT 'Pendiente',
|
||||
fecha_solicitud DATETIME2 DEFAULT GETDATE(),
|
||||
fecha_respuesta DATETIME2 NULL,
|
||||
observaciones NTEXT,
|
||||
usuario_solicitud NVARCHAR(100)
|
||||
);
|
||||
|
||||
-- Crear índices para mve_solicitudes
|
||||
CREATE INDEX IX_mve_solicitudes_pedimento ON mve_solicitudes (id_pedimento);
|
||||
CREATE INDEX IX_mve_solicitudes_estado ON mve_solicitudes (estado);
|
||||
CREATE INDEX IX_mve_solicitudes_fecha_solicitud ON mve_solicitudes (fecha_solicitud);
|
||||
|
||||
-- Crear trigger para actualizar fecha_actualizacion automáticamente
|
||||
CREATE TRIGGER TR_mve_facturas_datos_update
|
||||
ON mve_facturas_datos
|
||||
AFTER UPDATE
|
||||
AS
|
||||
BEGIN
|
||||
SET NOCOUNT ON;
|
||||
UPDATE mve_facturas_datos
|
||||
SET fecha_actualizacion = GETDATE()
|
||||
FROM mve_facturas_datos m
|
||||
INNER JOIN inserted i ON m.id = i.id;
|
||||
END;
|
||||
58
previos_tabla.sql
Normal file
58
previos_tabla.sql
Normal file
@@ -0,0 +1,58 @@
|
||||
-- Tabla para el catálogo de pedimentos (previos)
|
||||
-- Esta tabla almacena los pedimentos registrados por los importadores
|
||||
|
||||
CREATE TABLE [dbo].[previos] (
|
||||
[IdPrevio] [int] IDENTITY(1,1) NOT NULL,
|
||||
[Pedimento] [nvarchar](50) NOT NULL,
|
||||
[ClienteRFC] [nvarchar](13) NOT NULL,
|
||||
[ClienteNombre] [nvarchar](255) NOT NULL,
|
||||
[ClavePed] [nvarchar](10) NOT NULL,
|
||||
[TipoOperacion] [int] DEFAULT 1, -- 1=importación, 2=exportación
|
||||
[TipoPedimento] [int] DEFAULT 1, -- 1=normal, 2=complementario, etc.
|
||||
[Regimen] [nvarchar](100) NULL,
|
||||
[Destino] [nvarchar](100) NULL,
|
||||
[FechaPedimento] [int] NULL, -- Formato YYYYMMDD
|
||||
[FechaInicio] [int] NULL, -- Formato YYYYMMDD
|
||||
[FechaFinal] [int] NULL, -- Formato YYYYMMDD
|
||||
[ArchivoFinalPrevio] [nvarchar](255) NULL,
|
||||
[AcuseCons] [nvarchar](100) NULL,
|
||||
[Tipo] [nvarchar](50) NULL,
|
||||
[Status] [int] DEFAULT 1, -- 1=activo, 0=inactivo
|
||||
[Timestamp] [datetime] DEFAULT GETDATE(),
|
||||
|
||||
CONSTRAINT [PK_previos] PRIMARY KEY CLUSTERED ([IdPrevio] ASC)
|
||||
);
|
||||
|
||||
-- Índices para mejorar el rendimiento
|
||||
CREATE INDEX [IX_previos_cliente] ON [dbo].[previos] ([ClienteRFC]);
|
||||
CREATE INDEX [IX_previos_pedimento] ON [dbo].[previos] ([Pedimento]);
|
||||
CREATE INDEX [IX_previos_status] ON [dbo].[previos] ([Status]);
|
||||
CREATE INDEX [IX_previos_timestamp] ON [dbo].[previos] ([Timestamp] DESC);
|
||||
|
||||
-- Comentarios para documentación
|
||||
EXEC sys.sp_addextendedproperty
|
||||
@name=N'MS_Description',
|
||||
@value=N'Tabla principal para el catálogo de pedimentos de importadores',
|
||||
@level0type=N'SCHEMA', @level0name=N'dbo',
|
||||
@level1type=N'TABLE', @level1name=N'previos';
|
||||
|
||||
EXEC sys.sp_addextendedproperty
|
||||
@name=N'MS_Description',
|
||||
@value=N'Número de pedimento aduanero',
|
||||
@level0type=N'SCHEMA', @level0name=N'dbo',
|
||||
@level1type=N'TABLE', @level1name=N'previos',
|
||||
@level2type=N'COLUMN', @level2name=N'Pedimento';
|
||||
|
||||
EXEC sys.sp_addextendedproperty
|
||||
@name=N'MS_Description',
|
||||
@value=N'RFC del cliente/importador',
|
||||
@level0type=N'SCHEMA', @level0name=N'dbo',
|
||||
@level1type=N'TABLE', @level1name=N'previos',
|
||||
@level2type=N'COLUMN', @level2name=N'ClienteRFC';
|
||||
|
||||
EXEC sys.sp_addextendedproperty
|
||||
@name=N'MS_Description',
|
||||
@value=N'Clave de pedimento utilizada',
|
||||
@level0type=N'SCHEMA', @level0name=N'dbo',
|
||||
@level1type=N'TABLE', @level1name=N'previos',
|
||||
@level2type=N'COLUMN', @level2name=N'ClavePed';
|
||||
6
public/downloads/claves_pedimentos_template.csv
Normal file
6
public/downloads/claves_pedimentos_template.csv
Normal file
@@ -0,0 +1,6 @@
|
||||
codigo,descripcion,tipo_operacion,activo
|
||||
A1,Importación definitiva de mercancías,importacion,1
|
||||
A3,Importación definitiva de vehículos usados,importacion,1
|
||||
B1,Importación temporal para elaborar transformar o reparar,importacion,1
|
||||
C1,Importación definitiva de mercancías donadas,importacion,1
|
||||
G1,Importación de mercancías con Programa IMMEX,importacion,1
|
||||
|
BIN
storage/certificados/cer_46_1760569052.cer
Normal file
BIN
storage/certificados/cer_46_1760569052.cer
Normal file
Binary file not shown.
BIN
storage/certificados/cer_46_1760569397.cer
Normal file
BIN
storage/certificados/cer_46_1760569397.cer
Normal file
Binary file not shown.
BIN
storage/certificados/key_46_1760569052.key
Normal file
BIN
storage/certificados/key_46_1760569052.key
Normal file
Binary file not shown.
BIN
storage/certificados/key_46_1760569397.key
Normal file
BIN
storage/certificados/key_46_1760569397.key
Normal file
Binary file not shown.
83
templates_rapidos.sql
Normal file
83
templates_rapidos.sql
Normal file
@@ -0,0 +1,83 @@
|
||||
-- Tabla para Templates Rápidos Configurables
|
||||
CREATE TABLE dbo.templates_rapidos (
|
||||
id INT IDENTITY(1,1) PRIMARY KEY,
|
||||
nombre VARCHAR(100) NOT NULL,
|
||||
descripcion VARCHAR(255),
|
||||
icono VARCHAR(50) DEFAULT '🏢',
|
||||
activo BIT DEFAULT 1,
|
||||
|
||||
-- Configuración del template
|
||||
config_json NVARCHAR(MAX), -- JSON con la configuración completa
|
||||
|
||||
-- Campos específicos más usados (para facilitar consultas)
|
||||
tipo_moneda VARCHAR(3),
|
||||
incoterm VARCHAR(10),
|
||||
vinculacion TINYINT,
|
||||
pais_proveedor VARCHAR(10),
|
||||
tasa_preferencial VARCHAR(20),
|
||||
|
||||
-- Metadatos
|
||||
id_agencia INT,
|
||||
id_usuario_creador INT,
|
||||
fecha_creacion DATETIME DEFAULT GETDATE(),
|
||||
fecha_modificacion DATETIME DEFAULT GETDATE(),
|
||||
|
||||
-- Estadísticas de uso
|
||||
veces_usado INT DEFAULT 0,
|
||||
ultima_vez_usado DATETIME,
|
||||
|
||||
-- Índices
|
||||
INDEX IX_templates_rapidos_agencia (id_agencia, activo),
|
||||
INDEX IX_templates_rapidos_usuario (id_usuario_creador),
|
||||
INDEX IX_templates_rapidos_uso (veces_usado DESC)
|
||||
);
|
||||
|
||||
-- Insertar algunos templates por defecto
|
||||
INSERT INTO dbo.templates_rapidos (nombre, descripcion, icono, config_json, tipo_moneda, incoterm, vinculacion, pais_proveedor, tasa_preferencial, id_agencia, id_usuario_creador) VALUES
|
||||
('Importación China', 'FOB, CNY, General, Sin vinculación', '🇨🇳', '{"tipo_moneda":"CNY","incoterm":"FOB","vinculacion":"0","pais_proveedor_texto":"China","tasa_preferencial":"General"}', 'CNY', 'FOB', 0, NULL, 'General', NULL, NULL),
|
||||
('Importación USA', 'FOB, USD, TLC, Sin vinculación', '🇺🇸', '{"tipo_moneda":"USD","incoterm":"FOB","vinculacion":"0","pais_proveedor_texto":"Estados Unidos","tasa_preferencial":"TLC"}', 'USD', 'FOB', 0, NULL, 'TLC', NULL, NULL),
|
||||
('Comercializadora', 'FOB, USD, COMERCIALIZADORA, Con vinculación', '🏢', '{"tipo_moneda":"USD","incoterm":"FOB","vinculacion":"2","pais_proveedor_texto":"Estados Unidos","tasa_preferencial":"COMERCIALIZADORA"}', 'USD', 'FOB', 2, NULL, 'COMERCIALIZADORA', NULL, NULL),
|
||||
('Importación Europa', 'FOB, EUR, General, Sin vinculación', '🇪🇺', '{"tipo_moneda":"EUR","incoterm":"FOB","vinculacion":"0","pais_proveedor_texto":"Alemania","tasa_preferencial":"General"}', 'EUR', 'FOB', 0, NULL, 'General', NULL, NULL),
|
||||
('PROSEC México', 'FOB, USD, PROSEC, Sin vinculación', '🏭', '{"tipo_moneda":"USD","incoterm":"FOB","vinculacion":"0","pais_proveedor_texto":"Estados Unidos","tasa_preferencial":"PROSEC"}', 'USD', 'FOB', 0, NULL, 'PROSEC', NULL, NULL);
|
||||
|
||||
-- Crear procedimiento almacenado para obtener templates
|
||||
GO
|
||||
CREATE PROCEDURE sp_obtener_templates_rapidos
|
||||
@id_usuario INT,
|
||||
@id_agencia INT = NULL
|
||||
AS
|
||||
BEGIN
|
||||
SELECT
|
||||
id,
|
||||
nombre,
|
||||
descripcion,
|
||||
icono,
|
||||
config_json,
|
||||
veces_usado,
|
||||
ultima_vez_usado
|
||||
FROM dbo.templates_rapidos
|
||||
WHERE activo = 1
|
||||
AND (
|
||||
id_agencia IS NULL -- Templates globales
|
||||
OR id_agencia = @id_agencia -- Templates de la agencia
|
||||
OR id_usuario_creador = @id_usuario -- Templates del usuario
|
||||
)
|
||||
ORDER BY veces_usado DESC, nombre ASC;
|
||||
END
|
||||
|
||||
-- Crear procedimiento para incrementar uso de template
|
||||
GO
|
||||
CREATE PROCEDURE sp_usar_template_rapido
|
||||
@id_template INT,
|
||||
@id_usuario INT
|
||||
AS
|
||||
BEGIN
|
||||
UPDATE dbo.templates_rapidos
|
||||
SET veces_usado = veces_usado + 1,
|
||||
ultima_vez_usado = GETDATE()
|
||||
WHERE id = @id_template;
|
||||
|
||||
-- Opcional: Registrar en bitácora de uso
|
||||
INSERT INTO dbo.bitacoras (id_usuario, accion, tabla_afectada, id_registro, detalles, fecha_accion)
|
||||
VALUES (@id_usuario, 'USAR_TEMPLATE', 'templates_rapidos', @id_template, 'Template rápido utilizado', GETDATE());
|
||||
END
|
||||
157
test_templates.php
Normal file
157
test_templates.php
Normal file
@@ -0,0 +1,157 @@
|
||||
<?php
|
||||
// Script de diagnóstico para templates
|
||||
session_start();
|
||||
|
||||
// Simular una sesión válida para testing
|
||||
$_SESSION['usuario_id'] = 1; // Ajusta según tu usuario
|
||||
$_SESSION['id_agencia_en_uso'] = 1; // Ajusta según tu agencia
|
||||
|
||||
require_once __DIR__ . '/config/database.php';
|
||||
|
||||
echo "<h2>🔍 Diagnóstico de Templates</h2>";
|
||||
|
||||
try {
|
||||
$conn = getConnection();
|
||||
echo "✅ <strong>Conexión a base de datos:</strong> OK<br><br>";
|
||||
|
||||
// 1. Verificar si existe la tabla
|
||||
echo "<h3>1. Verificando tabla templates_rapidos:</h3>";
|
||||
$sql_check = "SELECT TABLE_NAME FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_NAME = 'templates_rapidos'";
|
||||
$stmt = sqlsrv_query($conn, $sql_check);
|
||||
|
||||
if ($stmt && sqlsrv_fetch_array($stmt)) {
|
||||
echo "✅ La tabla <code>templates_rapidos</code> existe<br><br>";
|
||||
|
||||
// 2. Verificar estructura de la tabla
|
||||
echo "<h3>2. Estructura de la tabla:</h3>";
|
||||
$sql_columns = "SELECT COLUMN_NAME, DATA_TYPE, IS_NULLABLE
|
||||
FROM INFORMATION_SCHEMA.COLUMNS
|
||||
WHERE TABLE_NAME = 'templates_rapidos'
|
||||
ORDER BY ORDINAL_POSITION";
|
||||
$stmt_cols = sqlsrv_query($conn, $sql_columns);
|
||||
|
||||
echo "<table border='1' style='border-collapse: collapse; margin-bottom: 20px;'>";
|
||||
echo "<tr><th>Columna</th><th>Tipo</th><th>Nullable</th></tr>";
|
||||
|
||||
$columnas_encontradas = [];
|
||||
while ($col = sqlsrv_fetch_array($stmt_cols, SQLSRV_FETCH_ASSOC)) {
|
||||
$columnas_encontradas[] = $col['COLUMN_NAME'];
|
||||
echo "<tr>";
|
||||
echo "<td>" . $col['COLUMN_NAME'] . "</td>";
|
||||
echo "<td>" . $col['DATA_TYPE'] . "</td>";
|
||||
echo "<td>" . $col['IS_NULLABLE'] . "</td>";
|
||||
echo "</tr>";
|
||||
}
|
||||
echo "</table>";
|
||||
|
||||
// 3. Verificar columnas específicas que usa el código
|
||||
echo "<h3>3. Verificando columnas requeridas:</h3>";
|
||||
$columnas_requeridas = ['id', 'nombre', 'descripcion', 'icono', 'config_json', 'veces_usado', 'activo', 'id_usuario_creador', 'id_agencia'];
|
||||
|
||||
foreach ($columnas_requeridas as $col) {
|
||||
if (in_array($col, $columnas_encontradas)) {
|
||||
echo "✅ Columna <code>$col</code>: Existe<br>";
|
||||
} else {
|
||||
echo "❌ Columna <code>$col</code>: <strong>NO EXISTE</strong><br>";
|
||||
}
|
||||
}
|
||||
|
||||
echo "<br>";
|
||||
|
||||
// 4. Contar registros
|
||||
echo "<h3>4. Contando registros:</h3>";
|
||||
$sql_count = "SELECT COUNT(*) as total FROM dbo.templates_rapidos";
|
||||
$stmt_count = sqlsrv_query($conn, $sql_count);
|
||||
|
||||
if ($stmt_count && $row = sqlsrv_fetch_array($stmt_count, SQLSRV_FETCH_ASSOC)) {
|
||||
echo "📊 Total de registros en la tabla: <strong>" . $row['total'] . "</strong><br>";
|
||||
|
||||
// 4.1 Contar activos
|
||||
$sql_active = "SELECT COUNT(*) as activos FROM dbo.templates_rapidos WHERE activo = 1";
|
||||
$stmt_active = sqlsrv_query($conn, $sql_active);
|
||||
if ($stmt_active && $row_active = sqlsrv_fetch_array($stmt_active, SQLSRV_FETCH_ASSOC)) {
|
||||
echo "✅ Registros activos: <strong>" . $row_active['activos'] . "</strong><br>";
|
||||
}
|
||||
}
|
||||
|
||||
echo "<br>";
|
||||
|
||||
// 5. Probar la consulta exacta del controlador
|
||||
echo "<h3>5. Probando consulta del controlador:</h3>";
|
||||
$id_usuario = $_SESSION['usuario_id'];
|
||||
$id_agencia = $_SESSION['id_agencia_en_uso'];
|
||||
|
||||
echo "👤 ID Usuario: $id_usuario<br>";
|
||||
echo "🏢 ID Agencia: $id_agencia<br><br>";
|
||||
|
||||
$sql_controller = "SELECT id, nombre, descripcion, icono, config_json, veces_usado
|
||||
FROM dbo.templates_rapidos
|
||||
WHERE activo = 1
|
||||
AND (id_agencia IS NULL OR id_agencia = ? OR id_usuario_creador = ?)
|
||||
ORDER BY veces_usado DESC, nombre ASC";
|
||||
|
||||
echo "<strong>SQL:</strong><br>";
|
||||
echo "<code>" . str_replace('?', "'$id_agencia', '$id_usuario'", $sql_controller) . "</code><br><br>";
|
||||
|
||||
$stmt_test = sqlsrv_query($conn, $sql_controller, [$id_agencia, $id_usuario]);
|
||||
|
||||
if ($stmt_test === false) {
|
||||
echo "❌ <strong>Error en la consulta:</strong><br>";
|
||||
$errors = sqlsrv_errors();
|
||||
foreach ($errors as $error) {
|
||||
echo "- " . $error['message'] . "<br>";
|
||||
}
|
||||
} else {
|
||||
$templates = [];
|
||||
$count = 0;
|
||||
|
||||
while ($row = sqlsrv_fetch_array($stmt_test, SQLSRV_FETCH_ASSOC)) {
|
||||
$count++;
|
||||
$templates[] = $row;
|
||||
if ($count <= 3) { // Mostrar solo los primeros 3 para no saturar
|
||||
echo "📋 Template $count: <strong>" . htmlspecialchars($row['nombre']) . "</strong><br>";
|
||||
}
|
||||
}
|
||||
|
||||
echo "<br>✅ <strong>Consulta exitosa. Total encontrados: $count templates</strong><br>";
|
||||
|
||||
if ($count === 0) {
|
||||
echo "<br>⚠️ <strong>No se encontraron templates. Posibles causas:</strong><br>";
|
||||
echo "1. No hay templates creados<br>";
|
||||
echo "2. Todos los templates están inactivos (activo = 0)<br>";
|
||||
echo "3. Los templates no pertenecen a tu usuario/agencia<br>";
|
||||
}
|
||||
}
|
||||
|
||||
// 6. Mostrar algunos registros de ejemplo
|
||||
echo "<br><h3>6. Registros de muestra (últimos 5):</h3>";
|
||||
$sql_sample = "SELECT TOP 5 id, nombre, activo, id_usuario_creador, id_agencia
|
||||
FROM dbo.templates_rapidos
|
||||
ORDER BY id DESC";
|
||||
$stmt_sample = sqlsrv_query($conn, $sql_sample);
|
||||
|
||||
if ($stmt_sample) {
|
||||
echo "<table border='1' style='border-collapse: collapse;'>";
|
||||
echo "<tr><th>ID</th><th>Nombre</th><th>Activo</th><th>Usuario</th><th>Agencia</th></tr>";
|
||||
|
||||
while ($sample = sqlsrv_fetch_array($stmt_sample, SQLSRV_FETCH_ASSOC)) {
|
||||
echo "<tr>";
|
||||
echo "<td>" . $sample['id'] . "</td>";
|
||||
echo "<td>" . htmlspecialchars($sample['nombre']) . "</td>";
|
||||
echo "<td>" . ($sample['activo'] ? '✅' : '❌') . "</td>";
|
||||
echo "<td>" . ($sample['id_usuario_creador'] ?? 'NULL') . "</td>";
|
||||
echo "<td>" . ($sample['id_agencia'] ?? 'NULL') . "</td>";
|
||||
echo "</tr>";
|
||||
}
|
||||
echo "</table>";
|
||||
}
|
||||
|
||||
} else {
|
||||
echo "❌ La tabla <code>templates_rapidos</code> <strong>NO EXISTE</strong><br>";
|
||||
echo "🔧 Necesitas crear la tabla primero.";
|
||||
}
|
||||
|
||||
} catch (Exception $e) {
|
||||
echo "❌ <strong>Error:</strong> " . $e->getMessage();
|
||||
}
|
||||
?>
|
||||
@@ -61,8 +61,718 @@
|
||||
<body>
|
||||
|
||||
<div class="content">
|
||||
<h4 class="mb-4 animate__animated animate__fadeInDown title_glow">⚙️ Automatizaciones</h4>
|
||||
<h4 class="mb-4 animate__animated animate__fadeInDown title-glow">⚙️ Automatizaciones</h4>
|
||||
|
||||
<!-- Card de conexión WINSAAI -->
|
||||
<div class="row mb-4 fade-in-up">
|
||||
<div class="col-12">
|
||||
<div class="card shadow-sm card-hover">
|
||||
<div class="card-header bg-primary text-white d-flex align-items-center justify-content-between">
|
||||
<div class="d-flex align-items-center">
|
||||
<i class="fas fa-plug me-2"></i>
|
||||
<h5 class="mb-0">Conexión con WINSAAI</h5>
|
||||
</div>
|
||||
<div id="connection_status_header">
|
||||
<span class="badge bg-secondary">Verificando...</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
|
||||
<!-- Estado cuando NO hay configuración -->
|
||||
<div id="no_config_section">
|
||||
<p class="text-muted mb-3">
|
||||
<i class="fas fa-info-circle me-2"></i>
|
||||
Configura la conexión con el sistema WINSAAI para importar automáticamente pedimentos y COVES.
|
||||
</p>
|
||||
|
||||
<!-- Botón para conectar -->
|
||||
<button class="btn btn-success btn-animated btn-pulse" type="button"
|
||||
onclick="showConfigForm()">
|
||||
<i class="fas fa-wifi me-2"></i>
|
||||
Conectar con WINSAAI
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<!-- Estado cuando SÍ hay configuración -->
|
||||
<div id="existing_config_section" style="display: none;">
|
||||
<div class="alert alert-success border-0" role="alert">
|
||||
<div class="d-flex align-items-center mb-3">
|
||||
<i class="fas fa-check-circle me-2 text-success fs-5"></i>
|
||||
<h6 class="mb-0 fw-bold">¡Estás configurado para conectarte a WINSAAI!</h6>
|
||||
</div>
|
||||
|
||||
<!-- Información de la configuración actual -->
|
||||
<div class="row g-3 mb-3">
|
||||
<div class="col-md-6">
|
||||
<div class="d-flex align-items-center">
|
||||
<i class="fas fa-server me-2 text-primary"></i>
|
||||
<div>
|
||||
<small class="text-muted d-block">Servidor</small>
|
||||
<span id="current_server" class="fw-semibold">-</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<div class="d-flex align-items-center">
|
||||
<i class="fas fa-user me-2 text-info"></i>
|
||||
<div>
|
||||
<small class="text-muted d-block">Usuario</small>
|
||||
<span id="current_user" class="fw-semibold">-</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<div class="d-flex align-items-center">
|
||||
<i class="fas fa-shield-alt me-2 text-warning"></i>
|
||||
<div>
|
||||
<small class="text-muted d-block">Protocolo</small>
|
||||
<span id="current_protocol" class="fw-semibold">-</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<div class="d-flex align-items-center">
|
||||
<i class="fas fa-clock me-2 text-secondary"></i>
|
||||
<div>
|
||||
<small class="text-muted d-block">Última sincronización</small>
|
||||
<span id="last_sync_display" class="fw-semibold">Nunca</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Opciones de sincronización activas -->
|
||||
<div class="mb-3">
|
||||
<small class="text-muted d-block mb-2">Datos configurados para sincronizar:</small>
|
||||
<div class="d-flex gap-2">
|
||||
<span id="sync_pedimentos_badge" class="badge bg-primary" style="display: none;">
|
||||
<i class="fas fa-file-alt me-1"></i> Pedimentos
|
||||
</span>
|
||||
<span id="sync_coves_badge" class="badge bg-info" style="display: none;">
|
||||
<i class="fas fa-ship me-1"></i> COVES
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Botones de acción -->
|
||||
<div class="d-flex gap-2 flex-wrap">
|
||||
<button type="button" class="btn btn-outline-primary btn-animated btn-sm"
|
||||
onclick="showConfigForm(true)">
|
||||
<i class="fas fa-edit me-2"></i>
|
||||
Editar Conexión
|
||||
</button>
|
||||
<button type="button" class="btn btn-outline-warning btn-animated btn-sm"
|
||||
onclick="toggleConfigStatus()">
|
||||
<i class="fas fa-pause me-2"></i>
|
||||
<span id="toggle_status_text">Desactivar</span>
|
||||
</button>
|
||||
<button type="button" class="btn btn-outline-secondary btn-animated btn-sm"
|
||||
onclick="testExistingConnection()">
|
||||
<i class="fas fa-vial me-2"></i>
|
||||
Probar Conexión
|
||||
</button>
|
||||
<button type="button" class="btn btn-outline-success btn-animated btn-sm"
|
||||
onclick="syncData()">
|
||||
<i class="fas fa-sync-alt me-2"></i>
|
||||
Sincronizar Ahora
|
||||
</button>
|
||||
<button type="button" class="btn btn-outline-danger btn-animated btn-sm"
|
||||
onclick="deleteConfig()">
|
||||
<i class="fas fa-trash me-2"></i>
|
||||
Eliminar
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Formulario de configuración (inicialmente oculto) -->
|
||||
<div class="collapse mt-4" id="winsaaiConfig">
|
||||
<div class="card border-secondary">
|
||||
<div class="card-header bg-light d-flex justify-content-between align-items-center">
|
||||
<h6 class="mb-0">
|
||||
<i class="fas fa-cog me-2"></i>
|
||||
<span id="form_title">Configuración de Conexión API</span>
|
||||
</h6>
|
||||
<button type="button" class="btn btn-sm btn-outline-secondary"
|
||||
onclick="hideConfigForm()">
|
||||
<i class="fas fa-times"></i>
|
||||
</button>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<form id="winsaaiForm">
|
||||
<div class="row">
|
||||
<!-- Dirección IP/DNS -->
|
||||
<div class="col-md-6 mb-3">
|
||||
<label for="winsaai_host" class="form-label">
|
||||
<i class="fas fa-server me-1"></i>
|
||||
Dirección IP / DNS
|
||||
</label>
|
||||
<input type="text" class="form-control" id="winsaai_host"
|
||||
placeholder="192.168.1.100 o api.winsaai.com" required>
|
||||
<div class="form-text">
|
||||
Ingresa la dirección IP o nombre de dominio del servidor WINSAAI
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Puerto -->
|
||||
<div class="col-md-6 mb-3">
|
||||
<label for="winsaai_port" class="form-label">
|
||||
<i class="fas fa-network-wired me-1"></i>
|
||||
Puerto
|
||||
</label>
|
||||
<input type="number" class="form-control" id="winsaai_port"
|
||||
placeholder="8080" min="1" max="65535" required>
|
||||
<div class="form-text">
|
||||
Puerto del servicio API (ejemplo: 8080, 80, 443)
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<!-- Usuario -->
|
||||
<div class="col-md-6 mb-3">
|
||||
<label for="winsaai_usuario" class="form-label">
|
||||
<i class="fas fa-user me-1"></i>
|
||||
Usuario
|
||||
</label>
|
||||
<input type="text" class="form-control" id="winsaai_usuario"
|
||||
placeholder="Tu usuario de WINSAAI" required>
|
||||
<div class="form-text">
|
||||
Nombre de usuario proporcionado por WINSAAI
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Contraseña -->
|
||||
<div class="col-md-6 mb-3">
|
||||
<label for="winsaai_password" class="form-label">
|
||||
<i class="fas fa-lock me-1"></i>
|
||||
Contraseña
|
||||
</label>
|
||||
<input type="password" class="form-control" id="winsaai_password"
|
||||
placeholder="Tu contraseña" required>
|
||||
<div class="form-text">
|
||||
Contraseña de acceso al sistema WINSAAI
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<!-- Protocolo -->
|
||||
<div class="col-md-6 mb-3">
|
||||
<label for="winsaai_protocol" class="form-label">
|
||||
<i class="fas fa-shield-alt me-1"></i>
|
||||
Protocolo
|
||||
</label>
|
||||
<select class="form-select" id="winsaai_protocol" required>
|
||||
<option value="">Seleccionar protocolo</option>
|
||||
<option value="http">HTTP</option>
|
||||
<option value="https">HTTPS (Recomendado)</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<!-- Estado Actual -->
|
||||
<div class="col-md-6 mb-3">
|
||||
<label class="form-label">
|
||||
<i class="fas fa-info-circle me-1"></i>
|
||||
Estado Actual
|
||||
</label>
|
||||
<div id="connection_status_display" class="form-control-plaintext">
|
||||
<span class="badge bg-secondary">No configurado</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Opciones de sincronización -->
|
||||
<div class="row">
|
||||
<div class="col-12 mb-3">
|
||||
<label class="form-label">
|
||||
<i class="fas fa-sync me-1"></i>
|
||||
Datos a sincronizar
|
||||
</label>
|
||||
<div class="form-check">
|
||||
<input class="form-check-input" type="checkbox" id="sync_pedimentos" checked>
|
||||
<label class="form-check-label" for="sync_pedimentos">
|
||||
<i class="fas fa-file-alt me-1 text-primary"></i>
|
||||
Pedimentos
|
||||
</label>
|
||||
</div>
|
||||
<div class="form-check">
|
||||
<input class="form-check-input" type="checkbox" id="sync_coves" checked>
|
||||
<label class="form-check-label" for="sync_coves">
|
||||
<i class="fas fa-ship me-1 text-info"></i>
|
||||
COVES
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Botones de acción -->
|
||||
<div class="d-flex gap-2 flex-wrap">
|
||||
<button type="button" class="btn btn-outline-secondary btn-animated"
|
||||
onclick="testConnection()">
|
||||
<i class="fas fa-vial me-2"></i>
|
||||
Probar Conexión
|
||||
</button>
|
||||
<button type="submit" class="btn btn-primary btn-animated">
|
||||
<i class="fas fa-save me-2"></i>
|
||||
Guardar Configuración
|
||||
</button>
|
||||
<button type="button" class="btn btn-warning btn-animated"
|
||||
onclick="syncData()">
|
||||
<i class="fas fa-sync-alt me-2"></i>
|
||||
Sincronizar Ahora
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Estado de conexión -->
|
||||
<div id="connectionStatus" class="mt-3" style="display: none;">
|
||||
<div class="alert alert-info">
|
||||
<i class="fas fa-info-circle me-2"></i>
|
||||
<span id="statusMessage">Verificando conexión...</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<!-- Scripts para funcionalidad WINSAAI -->
|
||||
<script>
|
||||
let currentConfig = null;
|
||||
|
||||
// Cargar configuración al inicio
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
loadExistingConfig();
|
||||
});
|
||||
|
||||
// Cargar configuración existente
|
||||
async function loadExistingConfig() {
|
||||
try {
|
||||
const response = await fetch('/IMPORTADORES/winsaai/get_config');
|
||||
const result = await response.json();
|
||||
|
||||
if (result.success && result.data) {
|
||||
currentConfig = result.data;
|
||||
showExistingConfig(result.data);
|
||||
} else {
|
||||
showNoConfig();
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error cargando configuración:', error);
|
||||
showNoConfig();
|
||||
}
|
||||
}
|
||||
|
||||
// Mostrar estado cuando NO hay configuración
|
||||
function showNoConfig() {
|
||||
document.getElementById('no_config_section').style.display = 'block';
|
||||
document.getElementById('existing_config_section').style.display = 'none';
|
||||
document.getElementById('connection_status_header').innerHTML = '<span class="badge bg-secondary">Sin configurar</span>';
|
||||
}
|
||||
|
||||
// Mostrar estado cuando SÍ hay configuración
|
||||
function showExistingConfig(config) {
|
||||
// Ocultar sección de "no config" y mostrar sección existente
|
||||
document.getElementById('no_config_section').style.display = 'none';
|
||||
document.getElementById('existing_config_section').style.display = 'block';
|
||||
|
||||
// Actualizar información mostrada
|
||||
document.getElementById('current_server').textContent = `${config.protocol}://${config.host}:${config.port}`;
|
||||
document.getElementById('current_user').textContent = config.usuario;
|
||||
document.getElementById('current_protocol').textContent = config.protocol.toUpperCase();
|
||||
|
||||
// Mostrar última sincronización
|
||||
if (config.last_sync) {
|
||||
const lastSyncDate = new Date(config.last_sync);
|
||||
document.getElementById('last_sync_display').textContent = lastSyncDate.toLocaleString();
|
||||
} else {
|
||||
document.getElementById('last_sync_display').textContent = 'Nunca';
|
||||
}
|
||||
|
||||
// Mostrar badges de sincronización
|
||||
const pedimentosBadge = document.getElementById('sync_pedimentos_badge');
|
||||
const covesBadge = document.getElementById('sync_coves_badge');
|
||||
|
||||
if (config.sync_pedimentos) {
|
||||
pedimentosBadge.style.display = 'inline-block';
|
||||
} else {
|
||||
pedimentosBadge.style.display = 'none';
|
||||
}
|
||||
|
||||
if (config.sync_coves) {
|
||||
covesBadge.style.display = 'inline-block';
|
||||
} else {
|
||||
covesBadge.style.display = 'none';
|
||||
}
|
||||
|
||||
// Actualizar header según estado
|
||||
updateHeaderStatus(config.status);
|
||||
|
||||
// Actualizar botón de toggle según estado
|
||||
updateToggleButton(config.status);
|
||||
|
||||
// Llenar formulario con datos existentes para edición
|
||||
document.getElementById('winsaai_host').value = config.host || '';
|
||||
document.getElementById('winsaai_port').value = config.port || '';
|
||||
document.getElementById('winsaai_protocol').value = config.protocol || '';
|
||||
document.getElementById('winsaai_usuario').value = config.usuario || '';
|
||||
document.getElementById('sync_pedimentos').checked = config.sync_pedimentos == 1;
|
||||
document.getElementById('sync_coves').checked = config.sync_coves == 1;
|
||||
}
|
||||
|
||||
// Actualizar estado en el header
|
||||
function updateHeaderStatus(status) {
|
||||
const headerStatus = document.getElementById('connection_status_header');
|
||||
let badgeHtml = '';
|
||||
|
||||
switch(status) {
|
||||
case 'activo':
|
||||
badgeHtml = '<span class="badge bg-success"><i class="fas fa-check-circle me-1"></i>Conectado</span>';
|
||||
break;
|
||||
case 'inactivo':
|
||||
badgeHtml = '<span class="badge bg-warning"><i class="fas fa-pause me-1"></i>Inactivo</span>';
|
||||
break;
|
||||
case 'error':
|
||||
badgeHtml = '<span class="badge bg-danger"><i class="fas fa-exclamation-triangle me-1"></i>Error</span>';
|
||||
break;
|
||||
default:
|
||||
badgeHtml = '<span class="badge bg-secondary">Sin configurar</span>';
|
||||
}
|
||||
|
||||
headerStatus.innerHTML = badgeHtml;
|
||||
}
|
||||
|
||||
// Actualizar botón de toggle según estado
|
||||
function updateToggleButton(status) {
|
||||
const toggleBtn = document.querySelector('button[onclick="toggleConfigStatus()"]');
|
||||
const toggleText = document.getElementById('toggle_status_text');
|
||||
const icon = toggleBtn.querySelector('i');
|
||||
|
||||
if (status === 'activo') {
|
||||
toggleText.textContent = 'Desactivar';
|
||||
icon.className = 'fas fa-pause me-2';
|
||||
toggleBtn.className = 'btn btn-outline-warning btn-animated btn-sm';
|
||||
} else {
|
||||
toggleText.textContent = 'Activar';
|
||||
icon.className = 'fas fa-play me-2';
|
||||
toggleBtn.className = 'btn btn-outline-success btn-animated btn-sm';
|
||||
}
|
||||
}
|
||||
|
||||
// Mostrar formulario de configuración
|
||||
function showConfigForm(isEdit = false) {
|
||||
const formTitle = document.getElementById('form_title');
|
||||
const configCollapse = new bootstrap.Collapse(document.getElementById('winsaaiConfig'), { show: true });
|
||||
|
||||
if (isEdit) {
|
||||
formTitle.textContent = 'Editar Configuración de WINSAAI';
|
||||
} else {
|
||||
formTitle.textContent = 'Nueva Configuración de WINSAAI';
|
||||
// Limpiar formulario para nueva configuración
|
||||
document.getElementById('winsaaiForm').reset();
|
||||
}
|
||||
}
|
||||
|
||||
// Ocultar formulario de configuración
|
||||
function hideConfigForm() {
|
||||
const configCollapse = new bootstrap.Collapse(document.getElementById('winsaaiConfig'), { hide: true });
|
||||
}
|
||||
|
||||
// Probar conexión con configuración existente
|
||||
async function testExistingConnection() {
|
||||
if (!currentConfig) {
|
||||
showStatus('error', 'No hay configuración para probar');
|
||||
return;
|
||||
}
|
||||
|
||||
showStatus('info', 'Probando conexión con configuración actual...');
|
||||
|
||||
try {
|
||||
const response = await fetch('/IMPORTADORES/winsaai/test_connection', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify({
|
||||
host: currentConfig.host,
|
||||
port: currentConfig.port,
|
||||
protocol: currentConfig.protocol,
|
||||
usuario: currentConfig.usuario,
|
||||
password: 'existing_config' // Indicador para usar configuración guardada
|
||||
})
|
||||
});
|
||||
|
||||
const result = await response.json();
|
||||
|
||||
if (result.success) {
|
||||
showStatus('success', result.message);
|
||||
updateHeaderStatus('activo');
|
||||
} else {
|
||||
showStatus('error', result.message);
|
||||
updateHeaderStatus('error');
|
||||
}
|
||||
} catch (error) {
|
||||
showStatus('error', 'Error de red: ' + error.message);
|
||||
updateHeaderStatus('error');
|
||||
}
|
||||
}
|
||||
|
||||
// Activar/Desactivar configuración
|
||||
async function toggleConfigStatus() {
|
||||
if (!currentConfig) {
|
||||
showStatus('error', 'No hay configuración para modificar');
|
||||
return;
|
||||
}
|
||||
|
||||
const currentStatus = currentConfig.status;
|
||||
const newStatus = currentStatus === 'activo' ? 'inactivo' : 'activo';
|
||||
const action = newStatus === 'activo' ? 'activar' : 'desactivar';
|
||||
|
||||
if (confirm(`¿Estás seguro de que deseas ${action} la configuración de WINSAAI?`)) {
|
||||
showStatus('info', `${action === 'activar' ? 'Activando' : 'Desactivando'} configuración...`);
|
||||
|
||||
try {
|
||||
// Aquí harías la llamada al servidor para cambiar el estado
|
||||
// Por ahora simularemos el cambio
|
||||
currentConfig.status = newStatus;
|
||||
updateHeaderStatus(newStatus);
|
||||
updateToggleButton(newStatus);
|
||||
showStatus('success', `Configuración ${action === 'activar' ? 'activada' : 'desactivada'} correctamente`);
|
||||
} catch (error) {
|
||||
showStatus('error', `Error al ${action} configuración: ` + error.message);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Eliminar configuración
|
||||
async function deleteConfig() {
|
||||
if (!currentConfig) {
|
||||
showStatus('error', 'No hay configuración para eliminar');
|
||||
return;
|
||||
}
|
||||
|
||||
if (confirm('¿Estás seguro de que deseas eliminar completamente la configuración de WINSAAI? Esta acción no se puede deshacer.')) {
|
||||
showStatus('info', 'Eliminando configuración...');
|
||||
|
||||
try {
|
||||
// Aquí harías la llamada al servidor para eliminar
|
||||
// Por ahora simularemos la eliminación
|
||||
currentConfig = null;
|
||||
showNoConfig();
|
||||
hideConfigForm();
|
||||
showStatus('success', 'Configuración eliminada correctamente');
|
||||
} catch (error) {
|
||||
showStatus('error', 'Error al eliminar configuración: ' + error.message);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Función para probar la conexión (desde formulario)
|
||||
async function testConnection() {
|
||||
const host = document.getElementById('winsaai_host').value;
|
||||
const port = document.getElementById('winsaai_port').value;
|
||||
const protocol = document.getElementById('winsaai_protocol').value;
|
||||
const usuario = document.getElementById('winsaai_usuario').value;
|
||||
const password = document.getElementById('winsaai_password').value;
|
||||
|
||||
if (!host || !port || !protocol || !usuario || !password) {
|
||||
showStatus('error', 'Por favor completa todos los campos obligatorios');
|
||||
return;
|
||||
}
|
||||
|
||||
showStatus('info', 'Probando conexión con WINSAAI...');
|
||||
|
||||
try {
|
||||
const response = await fetch('/IMPORTADORES/winsaai/test_connection', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify({
|
||||
host: host,
|
||||
port: parseInt(port),
|
||||
protocol: protocol,
|
||||
usuario: usuario,
|
||||
password: password
|
||||
})
|
||||
});
|
||||
|
||||
const result = await response.json();
|
||||
|
||||
if (result.success) {
|
||||
showStatus('success', result.message);
|
||||
} else {
|
||||
showStatus('error', result.message);
|
||||
}
|
||||
} catch (error) {
|
||||
showStatus('error', 'Error de red: ' + error.message);
|
||||
}
|
||||
}
|
||||
|
||||
// Función para sincronizar datos
|
||||
async function syncData() {
|
||||
let pedimentos, coves;
|
||||
|
||||
if (currentConfig) {
|
||||
// Usar configuración existente
|
||||
pedimentos = currentConfig.sync_pedimentos;
|
||||
coves = currentConfig.sync_coves;
|
||||
} else {
|
||||
// Usar valores del formulario
|
||||
pedimentos = document.getElementById('sync_pedimentos').checked;
|
||||
coves = document.getElementById('sync_coves').checked;
|
||||
}
|
||||
|
||||
if (!pedimentos && !coves) {
|
||||
showStatus('warning', 'No hay datos configurados para sincronizar');
|
||||
return;
|
||||
}
|
||||
|
||||
showStatus('info', 'Iniciando sincronización de datos...');
|
||||
|
||||
let syncType = 'both';
|
||||
if (pedimentos && !coves) syncType = 'pedimentos';
|
||||
else if (!pedimentos && coves) syncType = 'coves';
|
||||
|
||||
try {
|
||||
const response = await fetch('/IMPORTADORES/winsaai/sync_data', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify({
|
||||
sync_type: syncType
|
||||
})
|
||||
});
|
||||
|
||||
const result = await response.json();
|
||||
|
||||
if (result.success) {
|
||||
let message = 'Sincronización completada: ';
|
||||
if (result.data.pedimentos) {
|
||||
message += `${result.data.pedimentos.processed} pedimentos `;
|
||||
}
|
||||
if (result.data.coves) {
|
||||
message += `${result.data.coves.processed} COVES `;
|
||||
}
|
||||
showStatus('success', message);
|
||||
|
||||
// Actualizar última sincronización si hay configuración
|
||||
if (currentConfig) {
|
||||
currentConfig.last_sync = new Date().toISOString();
|
||||
document.getElementById('last_sync_display').textContent = new Date().toLocaleString();
|
||||
}
|
||||
} else {
|
||||
showStatus('error', result.message);
|
||||
}
|
||||
} catch (error) {
|
||||
showStatus('error', 'Error de red: ' + error.message);
|
||||
}
|
||||
}
|
||||
|
||||
// Función para mostrar estado
|
||||
function showStatus(type, message) {
|
||||
const statusDiv = document.getElementById('connectionStatus');
|
||||
const statusMessage = document.getElementById('statusMessage');
|
||||
const alertDiv = statusDiv.querySelector('.alert');
|
||||
|
||||
// Remover clases anteriores
|
||||
alertDiv.className = 'alert';
|
||||
|
||||
// Agregar nueva clase según el tipo
|
||||
switch(type) {
|
||||
case 'success':
|
||||
alertDiv.classList.add('alert-success');
|
||||
statusMessage.innerHTML = `<i class="fas fa-check-circle me-2"></i>${message}`;
|
||||
break;
|
||||
case 'error':
|
||||
alertDiv.classList.add('alert-danger');
|
||||
statusMessage.innerHTML = `<i class="fas fa-exclamation-circle me-2"></i>${message}`;
|
||||
break;
|
||||
case 'warning':
|
||||
alertDiv.classList.add('alert-warning');
|
||||
statusMessage.innerHTML = `<i class="fas fa-exclamation-triangle me-2"></i>${message}`;
|
||||
break;
|
||||
default:
|
||||
alertDiv.classList.add('alert-info');
|
||||
statusMessage.innerHTML = `<i class="fas fa-info-circle me-2"></i>${message}`;
|
||||
}
|
||||
|
||||
statusDiv.style.display = 'block';
|
||||
|
||||
// Auto-ocultar después de 5 segundos para mensajes de éxito
|
||||
if (type === 'success') {
|
||||
setTimeout(() => {
|
||||
statusDiv.style.display = 'none';
|
||||
}, 5000);
|
||||
}
|
||||
}
|
||||
|
||||
// Manejar envío del formulario
|
||||
document.getElementById('winsaaiForm').addEventListener('submit', async function(e) {
|
||||
e.preventDefault();
|
||||
|
||||
const config = {
|
||||
host: document.getElementById('winsaai_host').value,
|
||||
port: parseInt(document.getElementById('winsaai_port').value),
|
||||
protocol: document.getElementById('winsaai_protocol').value,
|
||||
usuario: document.getElementById('winsaai_usuario').value,
|
||||
password: document.getElementById('winsaai_password').value,
|
||||
sync_pedimentos: document.getElementById('sync_pedimentos').checked,
|
||||
sync_coves: document.getElementById('sync_coves').checked
|
||||
};
|
||||
|
||||
// Validar campos obligatorios
|
||||
if (!config.host || !config.port || !config.protocol || !config.usuario || !config.password) {
|
||||
showStatus('error', 'Por favor completa todos los campos obligatorios');
|
||||
return;
|
||||
}
|
||||
|
||||
showStatus('info', 'Guardando configuración...');
|
||||
|
||||
try {
|
||||
const response = await fetch('/IMPORTADORES/winsaai/save_config', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify(config)
|
||||
});
|
||||
|
||||
const result = await response.json();
|
||||
|
||||
if (result.success) {
|
||||
showStatus('success', result.message);
|
||||
// Recargar configuración para mostrar el nuevo estado
|
||||
await loadExistingConfig();
|
||||
hideConfigForm();
|
||||
} else {
|
||||
showStatus('error', result.message);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error completo:', error);
|
||||
showStatus('error', 'Error de red: ' + error.message);
|
||||
}
|
||||
});
|
||||
|
||||
// Animación suave para el collapse
|
||||
document.getElementById('winsaaiConfig').addEventListener('show.bs.collapse', function() {
|
||||
this.style.opacity = '0';
|
||||
this.style.transform = 'translateY(-20px)';
|
||||
setTimeout(() => {
|
||||
this.style.transition = 'all 0.3s ease';
|
||||
this.style.opacity = '1';
|
||||
this.style.transform = 'translateY(0)';
|
||||
}, 50);
|
||||
});
|
||||
</script>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
407
views/catalogo_pedimentos/crear.php
Normal file
407
views/catalogo_pedimentos/crear.php
Normal file
@@ -0,0 +1,407 @@
|
||||
<?php
|
||||
include __DIR__ . '/../partials/sidebar_importador.php';
|
||||
|
||||
// Verificar que el importador tenga información configurada
|
||||
if (!$importador) {
|
||||
echo '<div class="alert alert-warning">
|
||||
<i class="fas fa-exclamation-triangle"></i>
|
||||
<strong>Configuración requerida:</strong>
|
||||
Debe configurar su información general antes de crear pedimentos.
|
||||
<a href="/IMPORTADORES/configuracion" class="btn btn-sm btn-primary ms-2">Ir a Configuración</a>
|
||||
</div>';
|
||||
return;
|
||||
}
|
||||
|
||||
// Verificar que tenga claves de pedimentos configuradas
|
||||
if (empty($claves_pedimentos)) {
|
||||
echo '<div class="alert alert-info">
|
||||
<i class="fas fa-info-circle"></i>
|
||||
<strong>Claves de pedimentos:</strong>
|
||||
No tiene claves de pedimentos de importación configuradas.
|
||||
<a href="/IMPORTADORES/claves_pedimentos" class="btn btn-sm btn-success ms-2">Configurar Claves</a>
|
||||
</div>';
|
||||
}
|
||||
?>
|
||||
|
||||
<!DOCTYPE html>
|
||||
<html lang="es">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>📋 Nuevo Pedimento de Importación</title>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css" rel="stylesheet">
|
||||
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/js/bootstrap.bundle.min.js"></script>
|
||||
<!-- Font Awesome -->
|
||||
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0/css/all.min.css">
|
||||
<!-- Animate.css -->
|
||||
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/animate.css/4.1.1/animate.min.css">
|
||||
<style>
|
||||
body { font-family: 'Segoe UI', sans-serif; background-color: #f4f6f9; }
|
||||
.sidebar { width: 220px; height: 100vh; background-color: #343a40; position: fixed; top: 0; left: 0; padding-top: 56px; z-index: 1040; }
|
||||
.sidebar .nav-link { font-weight: bold; color: white; transition: all 0.3s ease; }
|
||||
.sidebar .nav-link:hover,
|
||||
.sidebar .nav-link.active { background-color: #495057; color: #fff; }
|
||||
.content { margin-top: 56px; padding: 40px 20px; position: relative; z-index: 1; background-color: #f4f6f9; transition: margin-left 0.3s ease;}
|
||||
.navbar { position: fixed; top: 0; width: 100%; z-index: 1050; }
|
||||
/* A partir de dispositivos medianos (>=768px), deja espacio lateral */
|
||||
@media (min-width: 768px) { .content { margin-left: 250px; } }
|
||||
/* En móviles, sin margen lateral */
|
||||
@media (max-width: 767.98px) {
|
||||
.content { margin-left: 0; }
|
||||
.sidebar .nav-link { font-weight: normal; color: #343a40; background-color: transparent; }
|
||||
.sidebar .nav-link:hover,
|
||||
.sidebar .nav-link.active { background-color: #e9ecef; color: #212529; }
|
||||
}
|
||||
.card { border-radius: 12px; }
|
||||
.fade-in-up { animation: fadeInUp 0.8s ease-out; }
|
||||
@keyframes fadeInUp {
|
||||
from { opacity: 0; transform: translateY(30px); }
|
||||
to { opacity: 1; transform: translateY(0); }
|
||||
}
|
||||
.title-glow { text-shadow: 0 0 10px rgba(0, 123, 255, 0.3); }
|
||||
/* Efecto para botones */
|
||||
.btn-animated { transition: all 0.3s ease; position: relative; overflow: hidden; }
|
||||
.btn-animated:hover { transform: translateY(-2px); box-shadow: 0 5px 15px rgba(0, 0, 0, 0.2); }
|
||||
.btn-animated::before { content: ''; position: absolute; top: 0; left: -100%; width: 100%; height: 100%;
|
||||
background: linear-gradient(90deg, transparent, rgba(255, 255, 255, 0.2), transparent); transition: left 0.5s; }
|
||||
.btn-animated:hover::before { left: 100%; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<div class="content">
|
||||
<div class="d-flex justify-content-between align-items-center mb-4">
|
||||
<h4 class="animate__animated animate__fadeInDown title-glow">📋 Nuevo Pedimento de Importación</h4>
|
||||
<a href="/IMPORTADORES/catalogo_pedimentos/lista" class="btn btn-secondary btn-animated">
|
||||
<i class="fas fa-arrow-left"></i> Regresar a Lista
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<div class="card fade-in-up">
|
||||
<div class="card-body">
|
||||
<form action="/IMPORTADORES/catalogo_pedimentos/guardar" method="POST" id="formPedimento">
|
||||
<div class="row">
|
||||
<!-- Información Básica -->
|
||||
<div class="col-md-6">
|
||||
<div class="card mb-3">
|
||||
<div class="card-header">
|
||||
<h5 class="mb-0"><i class="fas fa-info-circle"></i> Información del Pedimento</h5>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<div class="mb-3">
|
||||
<label for="pedimento" class="form-label">Número de Pedimento <span class="text-danger">*</span></label>
|
||||
<input type="text" class="form-control" id="pedimento" name="pedimento" required
|
||||
placeholder="Ej: 23 47 3807 8001234"
|
||||
pattern="[0-9\s]+" title="Solo números y espacios">
|
||||
<div class="form-text">Formato estándar: AA AA AAAA AAAAAAA (año aduana sección número)</div>
|
||||
</div>
|
||||
|
||||
<div class="mb-3">
|
||||
<label for="clave_ped" class="form-label">Clave de Pedimento <span class="text-danger">*</span></label>
|
||||
<select class="form-select" id="clave_ped" name="clave_ped" required>
|
||||
<option value="">Seleccionar clave...</option>
|
||||
<?php foreach ($claves_pedimentos as $clave): ?>
|
||||
<option value="<?= htmlspecialchars($clave['codigo']) ?>">
|
||||
<?= htmlspecialchars($clave['codigo']) ?> - <?= htmlspecialchars($clave['descripcion']) ?>
|
||||
</option>
|
||||
<?php endforeach; ?>
|
||||
</select>
|
||||
<?php if (empty($claves_pedimentos)): ?>
|
||||
<div class="form-text text-warning">
|
||||
<i class="fas fa-exclamation-triangle"></i>
|
||||
No tiene claves configuradas.
|
||||
<a href="/IMPORTADORES/claves_pedimentos/inicializar_claves_usuario">Inicializar claves por defecto</a>
|
||||
</div>
|
||||
<?php else: ?>
|
||||
<div class="form-text">
|
||||
Seleccione la clave que corresponda al tipo de importación
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<div class="col-md-6">
|
||||
<div class="mb-3">
|
||||
<label for="tipo_operacion" class="form-label">Tipo de Operación</label>
|
||||
<select class="form-select" id="tipo_operacion" name="tipo_operacion">
|
||||
<option value="1" selected>Importación Definitiva</option>
|
||||
<option value="3">Importación Temporal</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<div class="mb-3">
|
||||
<label for="tipo_pedimento" class="form-label">Tipo de Pedimento</label>
|
||||
<select class="form-select" id="tipo_pedimento" name="tipo_pedimento">
|
||||
<option value="1" selected>Normal</option>
|
||||
<option value="2">Consolidado</option>
|
||||
<option value="3">Rectificación</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<div class="col-md-6">
|
||||
<div class="mb-3">
|
||||
<label for="regimen" class="form-label">Régimen</label>
|
||||
<input type="text" class="form-control" id="regimen" name="regimen"
|
||||
placeholder="Ej: IMD" maxlength="10">
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<div class="mb-3">
|
||||
<label for="destino" class="form-label">Destino</label>
|
||||
<input type="text" class="form-control" id="destino" name="destino"
|
||||
placeholder="Código de destino" maxlength="10">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Información del Importador (Automática) -->
|
||||
<div class="col-md-6">
|
||||
<div class="card mb-3 bg-light">
|
||||
<div class="card-header">
|
||||
<h5 class="mb-0"><i class="fas fa-building"></i> Información del Importador</h5>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<?php if ($importador): ?>
|
||||
<div class="mb-2">
|
||||
<strong>RFC:</strong>
|
||||
<span class="badge bg-primary"><?= htmlspecialchars($importador['rfc']) ?></span>
|
||||
</div>
|
||||
<div class="mb-2">
|
||||
<strong>Nombre/Razón Social:</strong><br>
|
||||
<span class="text-muted"><?= htmlspecialchars($importador['nombre']) ?></span>
|
||||
</div>
|
||||
<?php if (!empty($importador['correo'])): ?>
|
||||
<div class="mb-2">
|
||||
<strong>Correo:</strong><br>
|
||||
<span class="text-muted"><?= htmlspecialchars($importador['correo']) ?></span>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
<hr>
|
||||
<div class="alert alert-success alert-sm">
|
||||
<i class="fas fa-check-circle"></i>
|
||||
Esta información se incluirá automáticamente en el pedimento
|
||||
</div>
|
||||
<?php else: ?>
|
||||
<div class="alert alert-warning">
|
||||
<i class="fas fa-exclamation-triangle"></i>
|
||||
Configure su información general primero
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Fechas -->
|
||||
<div class="card mb-3">
|
||||
<div class="card-header">
|
||||
<h5 class="mb-0"><i class="fas fa-calendar"></i> Fechas</h5>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<div class="mb-3">
|
||||
<label for="fecha_pedimento" class="form-label">Fecha del Pedimento</label>
|
||||
<input type="date" class="form-control" id="fecha_pedimento" name="fecha_pedimento"
|
||||
value="<?= date('Y-m-d') ?>">
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<div class="col-md-6">
|
||||
<div class="mb-3">
|
||||
<label for="fecha_inicio" class="form-label">Fecha de Inicio</label>
|
||||
<input type="date" class="form-control" id="fecha_inicio" name="fecha_inicio">
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<div class="mb-3">
|
||||
<label for="fecha_final" class="form-label">Fecha Final</label>
|
||||
<input type="date" class="form-control" id="fecha_final" name="fecha_final">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Información Adicional -->
|
||||
<div class="row">
|
||||
<div class="col-md-12">
|
||||
<div class="card mb-3">
|
||||
<div class="card-header">
|
||||
<h5 class="mb-0"><i class="fas fa-cogs"></i> Información Adicional</h5>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<div class="row">
|
||||
<div class="col-md-6">
|
||||
<div class="mb-3">
|
||||
<label for="archivo_final_previo" class="form-label">Archivo Final Previo</label>
|
||||
<input type="text" class="form-control" id="archivo_final_previo" name="archivo_final_previo"
|
||||
placeholder="Nombre del archivo" maxlength="100">
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<div class="mb-3">
|
||||
<label for="acuse_cons" class="form-label">Acuse de Recibo</label>
|
||||
<input type="text" class="form-control" id="acuse_cons" name="acuse_cons"
|
||||
placeholder="Número de acuse" maxlength="20">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<div class="col-md-6">
|
||||
<div class="mb-3">
|
||||
<label for="tipo" class="form-label">Tipo Adicional</label>
|
||||
<input type="text" class="form-control" id="tipo" name="tipo"
|
||||
placeholder="Información adicional" maxlength="10">
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<div class="mb-3">
|
||||
<label for="status" class="form-label">Estado</label>
|
||||
<select class="form-select" id="status" name="status">
|
||||
<option value="1" selected>Activo</option>
|
||||
<option value="0">Inactivo</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Botones -->
|
||||
<div class="row">
|
||||
<div class="col-md-12">
|
||||
<div class="d-flex justify-content-end gap-2">
|
||||
<a href="/IMPORTADORES/catalogo_pedimentos/lista" class="btn btn-secondary btn-animated">
|
||||
<i class="fas fa-times"></i> Cancelar
|
||||
</a>
|
||||
<button type="submit" class="btn btn-success btn-animated" <?= empty($claves_pedimentos) ? 'disabled' : '' ?>>
|
||||
<i class="fas fa-save"></i> Guardar Pedimento
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- jQuery -->
|
||||
<script src="https://code.jquery.com/jquery-3.6.0.min.js"></script>
|
||||
<!-- SweetAlert2 -->
|
||||
<script src="https://cdn.jsdelivr.net/npm/sweetalert2@11"></script>
|
||||
|
||||
<script>
|
||||
$(document).ready(function() {
|
||||
// Validación del formulario
|
||||
$('#formPedimento').on('submit', function(e) {
|
||||
var pedimento = $('#pedimento').val().trim();
|
||||
var clavePed = $('#clave_ped').val();
|
||||
|
||||
if (!pedimento) {
|
||||
e.preventDefault();
|
||||
Swal.fire({
|
||||
icon: 'warning',
|
||||
title: 'Campo obligatorio',
|
||||
text: 'El número de pedimento es obligatorio',
|
||||
confirmButtonColor: '#ffc107'
|
||||
});
|
||||
$('#pedimento').focus();
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!clavePed) {
|
||||
e.preventDefault();
|
||||
Swal.fire({
|
||||
icon: 'warning',
|
||||
title: 'Campo obligatorio',
|
||||
text: 'Debe seleccionar una clave de pedimento',
|
||||
confirmButtonColor: '#ffc107'
|
||||
});
|
||||
$('#clave_ped').focus();
|
||||
return false;
|
||||
}
|
||||
|
||||
// Validación básica del formato de pedimento
|
||||
var pedimentoLimpio = pedimento.replace(/\s+/g, '');
|
||||
if (pedimentoLimpio.length < 13) {
|
||||
e.preventDefault();
|
||||
Swal.fire({
|
||||
icon: 'error',
|
||||
title: 'Formato inválido',
|
||||
text: 'El número de pedimento debe tener al menos 13 dígitos',
|
||||
confirmButtonColor: '#dc3545'
|
||||
});
|
||||
$('#pedimento').focus();
|
||||
return false;
|
||||
}
|
||||
});
|
||||
|
||||
// Formateo automático del número de pedimento
|
||||
$('#pedimento').on('input', function() {
|
||||
var value = this.value.replace(/[^\d]/g, ''); // Solo números
|
||||
var formatted = '';
|
||||
|
||||
if (value.length >= 2) {
|
||||
formatted += value.substr(0, 2) + ' ';
|
||||
if (value.length >= 4) {
|
||||
formatted += value.substr(2, 2) + ' ';
|
||||
if (value.length >= 8) {
|
||||
formatted += value.substr(4, 4) + ' ';
|
||||
if (value.length > 8) {
|
||||
formatted += value.substr(8);
|
||||
}
|
||||
} else if (value.length > 4) {
|
||||
formatted += value.substr(4);
|
||||
}
|
||||
} else if (value.length > 2) {
|
||||
formatted += value.substr(2);
|
||||
}
|
||||
} else {
|
||||
formatted = value;
|
||||
}
|
||||
|
||||
this.value = formatted;
|
||||
});
|
||||
|
||||
// Validación de fechas
|
||||
$('#fecha_inicio, #fecha_final').on('change', function() {
|
||||
var fechaInicio = $('#fecha_inicio').val();
|
||||
var fechaFinal = $('#fecha_final').val();
|
||||
|
||||
if (fechaInicio && fechaFinal && fechaInicio > fechaFinal) {
|
||||
Swal.fire({
|
||||
icon: 'warning',
|
||||
title: 'Fechas incorrectas',
|
||||
text: 'La fecha de inicio no puede ser mayor que la fecha final',
|
||||
confirmButtonColor: '#ffc107'
|
||||
});
|
||||
$(this).val('');
|
||||
}
|
||||
});
|
||||
|
||||
// Información adicional sobre la clave seleccionada
|
||||
$('#clave_ped').on('change', function() {
|
||||
var selectedText = $(this).find('option:selected').text();
|
||||
if (selectedText && selectedText !== 'Seleccionar clave...') {
|
||||
var descripcion = selectedText.split(' - ')[1];
|
||||
if (descripcion) {
|
||||
$(this).next('.form-text').html('<i class="fas fa-info-circle text-primary"></i> ' + descripcion);
|
||||
}
|
||||
} else {
|
||||
$(this).next('.form-text').html('Seleccione la clave que corresponda al tipo de importación');
|
||||
}
|
||||
});
|
||||
});
|
||||
</script>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
380
views/catalogo_pedimentos/editar.php
Normal file
380
views/catalogo_pedimentos/editar.php
Normal file
@@ -0,0 +1,380 @@
|
||||
<?php
|
||||
include __DIR__ . '/../partials/sidebar_importador.php';
|
||||
|
||||
// Convertir fechas de formato int a date si están presentes
|
||||
$fecha_pedimento_display = '';
|
||||
$fecha_inicio_display = '';
|
||||
$fecha_final_display = '';
|
||||
|
||||
if (!empty($previo['FechaPedimento'])) {
|
||||
$fecha_str = (string)$previo['FechaPedimento'];
|
||||
if (strlen($fecha_str) === 8) {
|
||||
$fecha_pedimento_display = substr($fecha_str, 0, 4) . '-' . substr($fecha_str, 4, 2) . '-' . substr($fecha_str, 6, 2);
|
||||
}
|
||||
}
|
||||
|
||||
if (!empty($previo['FechaInicio'])) {
|
||||
$fecha_str = (string)$previo['FechaInicio'];
|
||||
if (strlen($fecha_str) === 8) {
|
||||
$fecha_inicio_display = substr($fecha_str, 0, 4) . '-' . substr($fecha_str, 4, 2) . '-' . substr($fecha_str, 6, 2);
|
||||
}
|
||||
}
|
||||
|
||||
if (!empty($previo['FechaFinal'])) {
|
||||
$fecha_str = (string)$previo['FechaFinal'];
|
||||
if (strlen($fecha_str) === 8) {
|
||||
$fecha_final_display = substr($fecha_str, 0, 4) . '-' . substr($fecha_str, 4, 2) . '-' . substr($fecha_str, 6, 2);
|
||||
}
|
||||
}
|
||||
?>
|
||||
|
||||
<!DOCTYPE html>
|
||||
<html lang="es">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>📋 Editar Pedimento</title>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css" rel="stylesheet">
|
||||
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/js/bootstrap.bundle.min.js"></script>
|
||||
<!-- Font Awesome -->
|
||||
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0/css/all.min.css">
|
||||
<!-- Animate.css -->
|
||||
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/animate.css/4.1.1/animate.min.css">
|
||||
<style>
|
||||
body { font-family: 'Segoe UI', sans-serif; background-color: #f4f6f9; }
|
||||
.sidebar { width: 220px; height: 100vh; background-color: #343a40; position: fixed; top: 0; left: 0; padding-top: 56px; z-index: 1040; }
|
||||
.sidebar .nav-link { font-weight: bold; color: white; transition: all 0.3s ease; }
|
||||
.sidebar .nav-link:hover,
|
||||
.sidebar .nav-link.active { background-color: #495057; color: #fff; }
|
||||
.content { margin-top: 56px; padding: 40px 20px; position: relative; z-index: 1; background-color: #f4f6f9; transition: margin-left 0.3s ease;}
|
||||
.navbar { position: fixed; top: 0; width: 100%; z-index: 1050; }
|
||||
/* A partir de dispositivos medianos (>=768px), deja espacio lateral */
|
||||
@media (min-width: 768px) { .content { margin-left: 250px; } }
|
||||
/* En móviles, sin margen lateral */
|
||||
@media (max-width: 767.98px) {
|
||||
.content { margin-left: 0; }
|
||||
.sidebar .nav-link { font-weight: normal; color: #343a40; background-color: transparent; }
|
||||
.sidebar .nav-link:hover,
|
||||
.sidebar .nav-link.active { background-color: #e9ecef; color: #212529; }
|
||||
}
|
||||
.card { border-radius: 12px; }
|
||||
/* Animaciones personalizadas */
|
||||
.card-hover { transition: all 0.3s ease; }
|
||||
.fade-in-up { animation: fadeInUp 0.8s ease-out; }
|
||||
@keyframes fadeInUp {
|
||||
from { opacity: 0; transform: translateY(30px); }
|
||||
to { opacity: 1; transform: translateY(0); }
|
||||
}
|
||||
/* Animación para el título */
|
||||
.title-glow { text-shadow: 0 0 10px rgba(0, 123, 255, 0.3); }
|
||||
/* Efecto para botones */
|
||||
.btn-animated { transition: all 0.3s ease; position: relative; overflow: hidden; }
|
||||
.btn-animated:hover { transform: translateY(-2px); box-shadow: 0 5px 15px rgba(0, 0, 0, 0.2); }
|
||||
.btn-animated::before { content: ''; position: absolute; top: 0; left: -100%; width: 100%; height: 100%;
|
||||
background: linear-gradient(90deg, transparent, rgba(255, 255, 255, 0.2), transparent); transition: left 0.5s; }
|
||||
.btn-animated:hover::before { left: 100%; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<div class="content">
|
||||
<div class="d-flex justify-content-between align-items-center mb-4">
|
||||
<h4 class="animate__animated animate__fadeInDown title-glow">📋 Editar Pedimento #<?= htmlspecialchars($previo['IdPrevio']) ?></h4>
|
||||
<a href="/IMPORTADORES/catalogo_pedimentos/lista" class="btn btn-secondary btn-animated">
|
||||
<i class="fas fa-arrow-left"></i> Regresar a Lista
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<div class="card fade-in-up">
|
||||
<div class="card-body">
|
||||
<form action="/IMPORTADORES/catalogo_pedimentos/actualizar" method="POST" id="formPedimento">
|
||||
<input type="hidden" name="id_previo" value="<?= htmlspecialchars($previo['IdPrevio']) ?>">
|
||||
|
||||
<div class="row">
|
||||
<!-- Información Básica -->
|
||||
<div class="col-md-6">
|
||||
<div class="card mb-3">
|
||||
<div class="card-header">
|
||||
<h5 class="mb-0"><i class="fas fa-info-circle"></i> Información Básica</h5>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<div class="mb-3">
|
||||
<label for="pedimento" class="form-label">Número de Pedimento <span class="text-danger">*</span></label>
|
||||
<input type="text" class="form-control" id="pedimento" name="pedimento" required
|
||||
value="<?= htmlspecialchars($previo['Pedimento'] ?? '') ?>"
|
||||
placeholder="Ej: 23 47 3807 8001234">
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<div class="col-md-6">
|
||||
<div class="mb-3">
|
||||
<label for="tipo_operacion" class="form-label">Tipo de Operación</label>
|
||||
<select class="form-select" id="tipo_operacion" name="tipo_operacion">
|
||||
<option value="">Seleccionar...</option>
|
||||
<option value="1" <?= ($previo['TipoOperacion'] == 1) ? 'selected' : '' ?>>Importación Definitiva</option>
|
||||
<option value="2" <?= ($previo['TipoOperacion'] == 2) ? 'selected' : '' ?>>Exportación Definitiva</option>
|
||||
<option value="3" <?= ($previo['TipoOperacion'] == 3) ? 'selected' : '' ?>>Importación Temporal</option>
|
||||
<option value="4" <?= ($previo['TipoOperacion'] == 4) ? 'selected' : '' ?>>Exportación Temporal</option>
|
||||
<option value="5" <?= ($previo['TipoOperacion'] == 5) ? 'selected' : '' ?>>Tránsito</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<div class="mb-3">
|
||||
<label for="tipo_pedimento" class="form-label">Tipo de Pedimento</label>
|
||||
<select class="form-select" id="tipo_pedimento" name="tipo_pedimento">
|
||||
<option value="">Seleccionar...</option>
|
||||
<option value="1" <?= ($previo['TipoPedimento'] == 1) ? 'selected' : '' ?>>Normal</option>
|
||||
<option value="2" <?= ($previo['TipoPedimento'] == 2) ? 'selected' : '' ?>>Consolidado</option>
|
||||
<option value="3" <?= ($previo['TipoPedimento'] == 3) ? 'selected' : '' ?>>Rectificación</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- ...existing form fields... -->
|
||||
<div class="row">
|
||||
<div class="col-md-6">
|
||||
<div class="mb-3">
|
||||
<label for="clave_ped" class="form-label">Clave de Pedimento</label>
|
||||
<input type="text" class="form-control" id="clave_ped" name="clave_ped"
|
||||
value="<?= htmlspecialchars($previo['ClavePed'] ?? '') ?>"
|
||||
placeholder="Ej: A1" maxlength="10">
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<div class="mb-3">
|
||||
<label for="regimen" class="form-label">Régimen</label>
|
||||
<input type="text" class="form-control" id="regimen" name="regimen"
|
||||
value="<?= htmlspecialchars($previo['Regimen'] ?? '') ?>"
|
||||
placeholder="Ej: IMD" maxlength="10">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mb-3">
|
||||
<label for="destino" class="form-label">Destino</label>
|
||||
<input type="text" class="form-control" id="destino" name="destino"
|
||||
value="<?= htmlspecialchars($previo['Destino'] ?? '') ?>"
|
||||
placeholder="Código de destino" maxlength="10">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Información del Cliente -->
|
||||
<div class="col-md-6">
|
||||
<div class="card mb-3">
|
||||
<div class="card-header">
|
||||
<h5 class="mb-0"><i class="fas fa-user"></i> Información del Cliente</h5>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<div class="mb-3">
|
||||
<label for="cliente_rfc" class="form-label">RFC del Cliente <span class="text-danger">*</span></label>
|
||||
<input type="text" class="form-control" id="cliente_rfc" name="cliente_rfc" required
|
||||
value="<?= htmlspecialchars($previo['ClienteRFC'] ?? '') ?>"
|
||||
placeholder="Ej: ABC123456789" maxlength="20" style="text-transform: uppercase;">
|
||||
</div>
|
||||
|
||||
<div class="mb-3">
|
||||
<label for="cliente_nombre" class="form-label">Nombre/Razón Social del Cliente <span class="text-danger">*</span></label>
|
||||
<textarea class="form-control" id="cliente_nombre" name="cliente_nombre" rows="3" required
|
||||
placeholder="Nombre completo o razón social" maxlength="500"><?= htmlspecialchars($previo['ClienteNombre'] ?? '') ?></textarea>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Fechas -->
|
||||
<div class="card mb-3">
|
||||
<div class="card-header">
|
||||
<h5 class="mb-0"><i class="fas fa-calendar"></i> Fechas</h5>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<div class="mb-3">
|
||||
<label for="fecha_pedimento" class="form-label">Fecha del Pedimento</label>
|
||||
<input type="date" class="form-control" id="fecha_pedimento" name="fecha_pedimento"
|
||||
value="<?= $fecha_pedimento_display ?>">
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<div class="col-md-6">
|
||||
<div class="mb-3">
|
||||
<label for="fecha_inicio" class="form-label">Fecha de Inicio</label>
|
||||
<input type="date" class="form-control" id="fecha_inicio" name="fecha_inicio"
|
||||
value="<?= $fecha_inicio_display ?>">
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<div class="mb-3">
|
||||
<label for="fecha_final" class="form-label">Fecha Final</label>
|
||||
<input type="date" class="form-control" id="fecha_final" name="fecha_final"
|
||||
value="<?= $fecha_final_display ?>">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Información Adicional -->
|
||||
<div class="row">
|
||||
<div class="col-md-12">
|
||||
<div class="card mb-3">
|
||||
<div class="card-header">
|
||||
<h5 class="mb-0"><i class="fas fa-cogs"></i> Información Adicional</h5>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<div class="row">
|
||||
<div class="col-md-6">
|
||||
<div class="mb-3">
|
||||
<label for="archivo_final_previo" class="form-label">Archivo Final Previo</label>
|
||||
<input type="text" class="form-control" id="archivo_final_previo" name="archivo_final_previo"
|
||||
value="<?= htmlspecialchars($previo['ArchivoFinalPrevio'] ?? '') ?>"
|
||||
placeholder="Nombre del archivo" maxlength="100">
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<div class="mb-3">
|
||||
<label for="acuse_cons" class="form-label">Acuse de Recibo</label>
|
||||
<input type="text" class="form-control" id="acuse_cons" name="acuse_cons"
|
||||
value="<?= htmlspecialchars($previo['AcuseCons'] ?? '') ?>"
|
||||
placeholder="Número de acuse" maxlength="20">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<div class="col-md-6">
|
||||
<div class="mb-3">
|
||||
<label for="tipo" class="form-label">Tipo</label>
|
||||
<input type="text" class="form-control" id="tipo" name="tipo"
|
||||
value="<?= htmlspecialchars(trim($previo['Tipo'] ?? '')) ?>"
|
||||
placeholder="Tipo de pedimento" maxlength="10">
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<div class="mb-3">
|
||||
<label for="status" class="form-label">Estado</label>
|
||||
<select class="form-select" id="status" name="status">
|
||||
<option value="1" <?= ($previo['Status'] == 1) ? 'selected' : '' ?>>Activo</option>
|
||||
<option value="0" <?= ($previo['Status'] == 0) ? 'selected' : '' ?>>Inactivo</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Información de Auditoría -->
|
||||
<div class="row">
|
||||
<div class="col-md-12">
|
||||
<div class="card mb-3 bg-light">
|
||||
<div class="card-header">
|
||||
<h5 class="mb-0"><i class="fas fa-info"></i> Información de Registro</h5>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<div class="row">
|
||||
<div class="col-md-6">
|
||||
<p class="mb-1"><strong>ID del Registro:</strong> <?= htmlspecialchars($previo['IdPrevio']) ?></p>
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<p class="mb-1"><strong>Fecha de Registro:</strong>
|
||||
<?php
|
||||
if ($previo['Timestamp'] instanceof DateTime) {
|
||||
echo $previo['Timestamp']->format('d/m/Y H:i:s');
|
||||
} else {
|
||||
echo 'No disponible';
|
||||
}
|
||||
?>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Botones -->
|
||||
<div class="row">
|
||||
<div class="col-md-12">
|
||||
<div class="d-flex justify-content-end gap-2">
|
||||
<a href="/IMPORTADORES/catalogo_pedimentos/lista" class="btn btn-secondary btn-animated">
|
||||
<i class="fas fa-times"></i> Cancelar
|
||||
</a>
|
||||
<button type="submit" class="btn btn-primary btn-animated">
|
||||
<i class="fas fa-save"></i> Actualizar Pedimento
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- jQuery -->
|
||||
<script src="https://code.jquery.com/jquery-3.6.0.min.js"></script>
|
||||
<!-- SweetAlert2 -->
|
||||
<script src="https://cdn.jsdelivr.net/npm/sweetalert2@11"></script>
|
||||
|
||||
<script>
|
||||
$(document).ready(function() {
|
||||
// Validación del formulario
|
||||
$('#formPedimento').on('submit', function(e) {
|
||||
var pedimento = $('#pedimento').val().trim();
|
||||
var clienteRfc = $('#cliente_rfc').val().trim();
|
||||
var clienteNombre = $('#cliente_nombre').val().trim();
|
||||
|
||||
if (!pedimento || !clienteRfc || !clienteNombre) {
|
||||
e.preventDefault();
|
||||
Swal.fire({
|
||||
icon: 'warning',
|
||||
title: 'Campos obligatorios',
|
||||
text: 'Por favor complete todos los campos obligatorios (*)',
|
||||
confirmButtonColor: '#ffc107'
|
||||
});
|
||||
return false;
|
||||
}
|
||||
|
||||
// Validación básica de RFC
|
||||
if (clienteRfc.length < 12) {
|
||||
e.preventDefault();
|
||||
Swal.fire({
|
||||
icon: 'error',
|
||||
title: 'RFC inválido',
|
||||
text: 'El RFC debe tener al menos 12 caracteres',
|
||||
confirmButtonColor: '#dc3545'
|
||||
});
|
||||
$('#cliente_rfc').focus();
|
||||
return false;
|
||||
}
|
||||
});
|
||||
|
||||
// Convertir RFC a mayúsculas automáticamente
|
||||
$('#cliente_rfc').on('input', function() {
|
||||
this.value = this.value.toUpperCase();
|
||||
});
|
||||
|
||||
// Validación de fechas
|
||||
$('#fecha_inicio, #fecha_final').on('change', function() {
|
||||
var fechaInicio = $('#fecha_inicio').val();
|
||||
var fechaFinal = $('#fecha_final').val();
|
||||
|
||||
if (fechaInicio && fechaFinal && fechaInicio > fechaFinal) {
|
||||
Swal.fire({
|
||||
icon: 'warning',
|
||||
title: 'Fechas incorrectas',
|
||||
text: 'La fecha de inicio no puede ser mayor que la fecha final',
|
||||
confirmButtonColor: '#ffc107'
|
||||
});
|
||||
$(this).val('');
|
||||
}
|
||||
});
|
||||
});
|
||||
</script>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
166
views/catalogo_pedimentos/index.php
Normal file
166
views/catalogo_pedimentos/index.php
Normal file
@@ -0,0 +1,166 @@
|
||||
<?php include __DIR__ . '/../partials/sidebar_importador.php'; ?>
|
||||
|
||||
<!DOCTYPE html>
|
||||
<html lang="es">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>📋 Catálogo de Pedimentos</title>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css" rel="stylesheet">
|
||||
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/js/bootstrap.bundle.min.js"></script>
|
||||
<!-- Font Awesome -->
|
||||
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0/css/all.min.css">
|
||||
<!-- Animate.css -->
|
||||
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/animate.css/4.1.1/animate.min.css">
|
||||
<style>
|
||||
body { font-family: 'Segoe UI', sans-serif; background-color: #f4f6f9; }
|
||||
.sidebar { width: 220px; height: 100vh; background-color: #343a40; position: fixed; top: 0; left: 0; padding-top: 56px; z-index: 1040; }
|
||||
.sidebar .nav-link { font-weight: bold; color: white; transition: all 0.3s ease; }
|
||||
.sidebar .nav-link:hover,
|
||||
.sidebar .nav-link.active { background-color: #495057; color: #fff; }
|
||||
.content { margin-top: 56px; padding: 40px 20px; position: relative; z-index: 1; background-color: #f4f6f9; transition: margin-left 0.3s ease;}
|
||||
.navbar { position: fixed; top: 0; width: 100%; z-index: 1050; }
|
||||
/* A partir de dispositivos medianos (>=768px), deja espacio lateral */
|
||||
@media (min-width: 768px) { .content { margin-left: 250px; } }
|
||||
/* En móviles, sin margen lateral */
|
||||
@media (max-width: 767.98px) {
|
||||
.content { margin-left: 0; }
|
||||
.sidebar .nav-link { font-weight: normal; color: #343a40; background-color: transparent; }
|
||||
.sidebar .nav-link:hover,
|
||||
.sidebar .nav-link.active { background-color: #e9ecef; color: #212529; }
|
||||
}
|
||||
.card { border-radius: 12px; }
|
||||
/* Animaciones personalizadas */
|
||||
.card-hover { transition: all 0.3s ease; cursor: pointer; }
|
||||
.card-hover:hover { transform: translateY(-8px) scale(1.02); box-shadow: 0 1rem 2rem rgba(0, 0, 0, 0.15) !important; }
|
||||
.btn-pulse { animation: pulse 2s infinite; }
|
||||
@keyframes pulse {
|
||||
0% { transform: scale(1); }
|
||||
50% { transform: scale(1.05); }
|
||||
100% { transform: scale(1); }
|
||||
}
|
||||
.fade-in-up { animation: fadeInUp 0.8s ease-out; }
|
||||
@keyframes fadeInUp {
|
||||
from { opacity: 0; transform: translateY(30px); }
|
||||
to { opacity: 1; transform: translateY(0); }
|
||||
}
|
||||
/* Responsive animations */
|
||||
@media (max-width: 768px) { .card-hover:hover { transform: translateY(-4px) scale(1.01); } }
|
||||
/* Efecto de glow para elementos activos */
|
||||
.btn.active { box-shadow: 0 0 20px rgba(var(--bs-primary-rgb), 0.5); }
|
||||
/* Animación para el título */
|
||||
.title-glow { text-shadow: 0 0 10px rgba(0, 123, 255, 0.3); }
|
||||
/* Efecto para botones */
|
||||
.btn-animated { transition: all 0.3s ease; position: relative; overflow: hidden; }
|
||||
.btn-animated:hover { transform: translateY(-2px); box-shadow: 0 5px 15px rgba(0, 0, 0, 0.2); }
|
||||
.btn-animated::before { content: ''; position: absolute; top: 0; left: -100%; width: 100%; height: 100%;
|
||||
background: linear-gradient(90deg, transparent, rgba(255, 255, 255, 0.2), transparent); transition: left 0.5s; }
|
||||
.btn-animated:hover::before { left: 100%; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<div class="content">
|
||||
<h4 class="mb-4 animate__animated animate__fadeInDown title-glow">📋 Catálogo de Pedimentos</h4>
|
||||
|
||||
<div class="row mb-4">
|
||||
<div class="col-md-6 col-lg-4 mb-3">
|
||||
<div class="card bg-primary text-white card-hover fade-in-up">
|
||||
<div class="card-body">
|
||||
<div class="d-flex justify-content-between">
|
||||
<div>
|
||||
<h5 class="card-title">Lista de Pedimentos</h5>
|
||||
<p class="card-text">Visualizar y gestionar todos los pedimentos registrados</p>
|
||||
</div>
|
||||
<div class="align-self-center">
|
||||
<i class="fas fa-list fa-2x"></i>
|
||||
</div>
|
||||
</div>
|
||||
<a href="/IMPORTADORES/catalogo_pedimentos/lista" class="btn btn-outline-light btn-sm btn-animated">
|
||||
Ver Lista <i class="fas fa-arrow-right"></i>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-md-6 col-lg-4 mb-3">
|
||||
<div class="card bg-success text-white card-hover fade-in-up">
|
||||
<div class="card-body">
|
||||
<div class="d-flex justify-content-between">
|
||||
<div>
|
||||
<h5 class="card-title">Nuevo Pedimento</h5>
|
||||
<p class="card-text">Registrar un nuevo pedimento en el sistema</p>
|
||||
</div>
|
||||
<div class="align-self-center">
|
||||
<i class="fas fa-plus-circle fa-2x"></i>
|
||||
</div>
|
||||
</div>
|
||||
<a href="/IMPORTADORES/catalogo_pedimentos/crear" class="btn btn-outline-light btn-sm btn-animated">
|
||||
Crear Nuevo <i class="fas fa-plus"></i>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-md-6 col-lg-4 mb-3">
|
||||
<div class="card bg-info text-white card-hover fade-in-up">
|
||||
<div class="card-body">
|
||||
<div class="d-flex justify-content-between">
|
||||
<div>
|
||||
<h5 class="card-title">Reportes</h5>
|
||||
<p class="card-text">Generar reportes y estadísticas de pedimentos</p>
|
||||
</div>
|
||||
<div class="align-self-center">
|
||||
<i class="fas fa-chart-bar fa-2x"></i>
|
||||
</div>
|
||||
</div>
|
||||
<button class="btn btn-outline-light btn-sm btn-animated" onclick="proximamente()">
|
||||
Ver Reportes <i class="fas fa-chart-line"></i>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<div class="col-12">
|
||||
<div class="card fade-in-up">
|
||||
<div class="card-header">
|
||||
<h5 class="mb-0"><i class="fas fa-info-circle"></i> Información del Módulo</h5>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<p class="text-muted">
|
||||
El catálogo de pedimentos le permite gestionar de manera eficiente todos los pedimentos
|
||||
de importación. Desde aquí puede:
|
||||
</p>
|
||||
<ul class="list-unstyled">
|
||||
<li><i class="fas fa-check text-success"></i> Registrar nuevos pedimentos con toda la información requerida</li>
|
||||
<li><i class="fas fa-check text-success"></i> Consultar y editar pedimentos existentes</li>
|
||||
<li><i class="fas fa-check text-success"></i> Realizar búsquedas avanzadas por diversos criterios</li>
|
||||
<li><i class="fas fa-check text-success"></i> Exportar información para reportes</li>
|
||||
<li><i class="fas fa-check text-success"></i> Mantener un histórico completo de operaciones</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- jQuery -->
|
||||
<script src="https://code.jquery.com/jquery-3.6.0.min.js"></script>
|
||||
<!-- SweetAlert2 -->
|
||||
<script src="https://cdn.jsdelivr.net/npm/sweetalert2@11"></script>
|
||||
|
||||
<script>
|
||||
function proximamente() {
|
||||
Swal.fire({
|
||||
icon: 'info',
|
||||
title: 'Próximamente',
|
||||
text: 'Esta función estará disponible próximamente.',
|
||||
confirmButtonColor: '#0d6efd'
|
||||
});
|
||||
}
|
||||
</script>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
194
views/catalogo_pedimentos/lista.php
Normal file
194
views/catalogo_pedimentos/lista.php
Normal file
@@ -0,0 +1,194 @@
|
||||
<?php include __DIR__ . '/../partials/sidebar_importador.php'; ?>
|
||||
|
||||
<!DOCTYPE html>
|
||||
<html lang="es">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>📋 Catálogo de Pedimentos</title>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css" rel="stylesheet">
|
||||
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/js/bootstrap.bundle.min.js"></script>
|
||||
<link href="https://cdn.datatables.net/1.13.4/css/jquery.dataTables.min.css" rel="stylesheet">
|
||||
<!-- Font Awesome -->
|
||||
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0/css/all.min.css">
|
||||
<!-- Animate.css -->
|
||||
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/animate.css/4.1.1/animate.min.css">
|
||||
<style>
|
||||
table.dataTable thead th { background:#343a40; color:#fff; }
|
||||
body { font-family: 'Segoe UI', sans-serif; background-color: #f4f6f9; }
|
||||
.sidebar { width: 220px; height: 100vh; background-color: #343a40; position: fixed; top: 0; left: 0; padding-top: 56px; z-index: 1040; }
|
||||
.sidebar .nav-link { font-weight: bold; color: white; transition: all 0.3s ease; }
|
||||
.sidebar .nav-link:hover,
|
||||
.sidebar .nav-link.active { background-color: #495057; color: #fff; }
|
||||
.content { margin-top: 56px; padding: 40px 20px; position: relative; z-index: 1; background-color: #f4f6f9; transition: margin-left 0.3s ease;}
|
||||
.navbar { position: fixed; top: 0; width: 100%; z-index: 1050; }
|
||||
/* A partir de dispositivos medianos (>=768px), deja espacio lateral */
|
||||
@media (min-width: 768px) { .content { margin-left: 250px; } }
|
||||
/* En móviles, sin margen lateral */
|
||||
@media (max-width: 767.98px) {
|
||||
.content { margin-left: 0; }
|
||||
.sidebar .nav-link { font-weight: normal; color: #343a40; background-color: transparent; }
|
||||
.sidebar .nav-link:hover,
|
||||
.sidebar .nav-link.active { background-color: #e9ecef; color: #212529; }
|
||||
}
|
||||
.card { border-radius: 12px; }
|
||||
/* Animaciones personalizadas */
|
||||
.card-hover { transition: all 0.3s ease; cursor: pointer; }
|
||||
.card-hover:hover { transform: translateY(-8px) scale(1.02); box-shadow: 0 1rem 2rem rgba(0, 0, 0, 0.15) !important; }
|
||||
.btn-pulse { animation: pulse 2s infinite; }
|
||||
@keyframes pulse {
|
||||
0% { transform: scale(1); }
|
||||
50% { transform: scale(1.05); }
|
||||
100% { transform: scale(1); }
|
||||
}
|
||||
.fade-in-up { animation: fadeInUp 0.8s ease-out; }
|
||||
@keyframes fadeInUp {
|
||||
from { opacity: 0; transform: translateY(30px); }
|
||||
to { opacity: 1; transform: translateY(0); }
|
||||
}
|
||||
/* Responsive animations */
|
||||
@media (max-width: 768px) { .card-hover:hover { transform: translateY(-4px) scale(1.01); } }
|
||||
/* Efecto de glow para elementos activos */
|
||||
.btn.active { box-shadow: 0 0 20px rgba(var(--bs-primary-rgb), 0.5); }
|
||||
/* Animación para el título */
|
||||
.title-glow { text-shadow: 0 0 10px rgba(0, 123, 255, 0.3); }
|
||||
/* Efecto para botones */
|
||||
.btn-animated { transition: all 0.3s ease; position: relative; overflow: hidden; }
|
||||
.btn-animated:hover { transform: translateY(-2px); box-shadow: 0 5px 15px rgba(0, 0, 0, 0.2); }
|
||||
.btn-animated::before { content: ''; position: absolute; top: 0; left: -100%; width: 100%; height: 100%;
|
||||
background: linear-gradient(90deg, transparent, rgba(255, 255, 255, 0.2), transparent); transition: left 0.5s; }
|
||||
.btn-animated:hover::before { left: 100%; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<div class="content">
|
||||
<div class="d-flex justify-content-between align-items-center mb-4">
|
||||
<h4 class="animate__animated animate__fadeInDown title-glow">📋 Lista de Pedimentos</h4>
|
||||
<div>
|
||||
<a href="/IMPORTADORES/catalogo_pedimentos/crear" class="btn btn-success btn-animated me-2">
|
||||
<i class="fas fa-plus"></i> Nuevo Pedimento
|
||||
</a>
|
||||
<a href="/IMPORTADORES/catalogo_pedimentos" class="btn btn-secondary btn-animated">
|
||||
<i class="fas fa-arrow-left"></i> Regresar
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card p-3 shadow-sm card-hover position-relative h-auto fade-in-up">
|
||||
<div class="table-responsive">
|
||||
<table id="tablaPedimentos" class="display nowrap" style="width:100%">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>ID</th>
|
||||
<th>Pedimento</th>
|
||||
<th>RFC Cliente</th>
|
||||
<th>Nombre Cliente</th>
|
||||
<th>Fecha Registro</th>
|
||||
<th>Estado</th>
|
||||
<th>Acciones</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<!-- Los datos se cargan via AJAX -->
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- jQuery -->
|
||||
<script src="https://code.jquery.com/jquery-3.6.0.min.js"></script>
|
||||
<!-- DataTables JS -->
|
||||
<script src="https://cdn.datatables.net/1.13.4/js/jquery.dataTables.min.js"></script>
|
||||
<!-- SweetAlert2 -->
|
||||
<script src="https://cdn.jsdelivr.net/npm/sweetalert2@11"></script>
|
||||
|
||||
<script>
|
||||
$(document).ready(function() {
|
||||
// Inicializar DataTable
|
||||
$('#tablaPedimentos').DataTable({
|
||||
"processing": true,
|
||||
"serverSide": true,
|
||||
"searchDelay": 500,
|
||||
"deferRender": true,
|
||||
"ajax": {
|
||||
"url": "/IMPORTADORES/catalogo_pedimentos/ajax_lista",
|
||||
"type": "GET"
|
||||
},
|
||||
"columns": [
|
||||
{ "data": 0, "name": "IdPrevio" },
|
||||
{ "data": 1, "name": "Pedimento" },
|
||||
{ "data": 2, "name": "ClienteRFC" },
|
||||
{ "data": 3, "name": "ClienteNombre" },
|
||||
{ "data": 4, "name": "Timestamp" },
|
||||
{
|
||||
"data": 5,
|
||||
"name": "Status",
|
||||
"render": function(data, type, row) {
|
||||
if (data === 'Activo') {
|
||||
return '<span class="badge bg-success">Activo</span>';
|
||||
} else {
|
||||
return '<span class="badge bg-secondary">Inactivo</span>';
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"data": null,
|
||||
"orderable": false,
|
||||
"searchable": false,
|
||||
"render": function(data, type, row) {
|
||||
const id = row[0];
|
||||
return `
|
||||
<a href="/IMPORTADORES/catalogo_pedimentos/editar?id=${id}" class="btn btn-sm btn-primary btn-animated" title="Editar">
|
||||
<i class="fas fa-edit"></i>
|
||||
</a>
|
||||
<button type="button" class="btn btn-sm btn-danger btn-animated" onclick="confirmarEliminar(${id})" title="Eliminar">
|
||||
<i class="fas fa-trash"></i>
|
||||
</button>
|
||||
`;
|
||||
}
|
||||
}
|
||||
],
|
||||
"language": {
|
||||
"url": "https://cdn.datatables.net/plug-ins/1.13.4/i18n/es-ES.json"
|
||||
},
|
||||
"responsive": true,
|
||||
"order": [[4, "desc"]],
|
||||
"pageLength": 25,
|
||||
"lengthMenu": [[10, 25, 50, 100], [10, 25, 50, 100]]
|
||||
});
|
||||
});
|
||||
|
||||
function confirmarEliminar(id) {
|
||||
Swal.fire({
|
||||
title: '¿Eliminar pedimento?',
|
||||
text: '¿Está seguro que desea eliminar este pedimento?',
|
||||
icon: 'warning',
|
||||
showCancelButton: true,
|
||||
confirmButtonText: 'Sí, eliminar',
|
||||
cancelButtonText: 'Cancelar',
|
||||
confirmButtonColor: '#d33'
|
||||
}).then(result => {
|
||||
if (result.isConfirmed) {
|
||||
window.location = `/IMPORTADORES/catalogo_pedimentos/eliminar?id=${id}`;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Mostrar alertas de éxito/error
|
||||
<?php if (isset($_GET['created'])): ?>
|
||||
Swal.fire({ icon: 'success', title: 'Pedimento creado', text: 'El pedimento se creó correctamente.', confirmButtonColor: '#198754' });
|
||||
<?php endif; ?>
|
||||
|
||||
<?php if (isset($_GET['updated'])): ?>
|
||||
Swal.fire({ icon: 'success', title: 'Pedimento actualizado', text: 'Los datos fueron modificados correctamente.', confirmButtonColor: '#198754' });
|
||||
<?php endif; ?>
|
||||
|
||||
<?php if (isset($_GET['deleted'])): ?>
|
||||
Swal.fire({ icon: 'success', title: 'Pedimento eliminado', text: 'El pedimento fue eliminado correctamente.', confirmButtonColor: '#198754' });
|
||||
<?php endif; ?>
|
||||
</script>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
230
views/claves_pedimentos/crear.php
Normal file
230
views/claves_pedimentos/crear.php
Normal file
@@ -0,0 +1,230 @@
|
||||
<?php include __DIR__ . '/../partials/sidebar_importador.php'; ?>
|
||||
|
||||
<!DOCTYPE html>
|
||||
<html lang="es">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>🔑 Nueva Clave de Pedimento</title>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css" rel="stylesheet">
|
||||
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/js/bootstrap.bundle.min.js"></script>
|
||||
<!-- Font Awesome -->
|
||||
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0/css/all.min.css">
|
||||
<!-- Animate.css -->
|
||||
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/animate.css/4.1.1/animate.min.css">
|
||||
<style>
|
||||
body { font-family: 'Segoe UI', sans-serif; background-color: #f4f6f9; }
|
||||
.sidebar { width: 220px; height: 100vh; background-color: #343a40; position: fixed; top: 0; left: 0; padding-top: 56px; z-index: 1040; }
|
||||
.sidebar .nav-link { font-weight: bold; color: white; transition: all 0.3s ease; }
|
||||
.sidebar .nav-link:hover,
|
||||
.sidebar .nav-link.active { background-color: #495057; color: #fff; }
|
||||
.content { margin-top: 56px; padding: 40px 20px; position: relative; z-index: 1; background-color: #f4f6f9; transition: margin-left 0.3s ease;}
|
||||
.navbar { position: fixed; top: 0; width: 100%; z-index: 1050; }
|
||||
/* A partir de dispositivos medianos (>=768px), deja espacio lateral */
|
||||
@media (min-width: 768px) { .content { margin-left: 250px; } }
|
||||
/* En móviles, sin margen lateral */
|
||||
@media (max-width: 767.98px) {
|
||||
.content { margin-left: 0; }
|
||||
.sidebar .nav-link { font-weight: normal; color: #343a40; background-color: transparent; }
|
||||
.sidebar .nav-link:hover,
|
||||
.sidebar .nav-link.active { background-color: #e9ecef; color: #212529; }
|
||||
}
|
||||
.card { border-radius: 12px; }
|
||||
.fade-in-up { animation: fadeInUp 0.8s ease-out; }
|
||||
@keyframes fadeInUp {
|
||||
from { opacity: 0; transform: translateY(30px); }
|
||||
to { opacity: 1; transform: translateY(0); }
|
||||
}
|
||||
.title-glow { text-shadow: 0 0 10px rgba(0, 123, 255, 0.3); }
|
||||
/* Efecto para botones */
|
||||
.btn-animated { transition: all 0.3s ease; position: relative; overflow: hidden; }
|
||||
.btn-animated:hover { transform: translateY(-2px); box-shadow: 0 5px 15px rgba(0, 0, 0, 0.2); }
|
||||
.btn-animated::before { content: ''; position: absolute; top: 0; left: -100%; width: 100%; height: 100%;
|
||||
background: linear-gradient(90deg, transparent, rgba(255, 255, 255, 0.2), transparent); transition: left 0.5s; }
|
||||
.btn-animated:hover::before { left: 100%; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<div class="content">
|
||||
<div class="d-flex justify-content-between align-items-center mb-4">
|
||||
<h4 class="animate__animated animate__fadeInDown title-glow">🔑 Nueva Clave de Pedimento</h4>
|
||||
<a href="/IMPORTADORES/claves_pedimentos/lista" class="btn btn-secondary btn-animated">
|
||||
<i class="fas fa-arrow-left"></i> Regresar a Lista
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<div class="card fade-in-up">
|
||||
<div class="card-body">
|
||||
<form action="/IMPORTADORES/claves_pedimentos/guardar" method="POST" id="formClave">
|
||||
<div class="row">
|
||||
<div class="col-md-6">
|
||||
<div class="card mb-3">
|
||||
<div class="card-header">
|
||||
<h5 class="mb-0"><i class="fas fa-info-circle"></i> Información de la Clave</h5>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<div class="mb-3">
|
||||
<label for="codigo" class="form-label">Código de la Clave <span class="text-danger">*</span></label>
|
||||
<input type="text" class="form-control" id="codigo" name="codigo" required
|
||||
placeholder="Ej: A1, B3, G1" maxlength="10" style="text-transform: uppercase;">
|
||||
<div class="form-text">Máximo 10 caracteres. Se convertirá automáticamente a mayúsculas.</div>
|
||||
</div>
|
||||
|
||||
<div class="mb-3">
|
||||
<label for="descripcion" class="form-label">Descripción <span class="text-danger">*</span></label>
|
||||
<textarea class="form-control" id="descripcion" name="descripcion" rows="4" required
|
||||
placeholder="Descripción detallada de la clave de pedimento" maxlength="255"></textarea>
|
||||
<div class="form-text">Describe claramente el tipo de operación que representa esta clave.</div>
|
||||
</div>
|
||||
|
||||
<div class="mb-3">
|
||||
<label for="tipo_operacion" class="form-label">Tipo de Operación</label>
|
||||
<select class="form-select" id="tipo_operacion" name="tipo_operacion">
|
||||
<option value="importacion" selected>Importación</option>
|
||||
<option value="exportacion">Exportación</option>
|
||||
<option value="transito">Tránsito</option>
|
||||
<option value="otros">Otros</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div class="mb-3">
|
||||
<div class="form-check form-switch">
|
||||
<input class="form-check-input" type="checkbox" id="activo" name="activo" checked>
|
||||
<label class="form-check-label" for="activo">
|
||||
<i class="fas fa-toggle-on text-success"></i> Clave activa
|
||||
</label>
|
||||
<div class="form-text">Las claves inactivas no aparecerán en los formularios de pedimentos.</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-md-6">
|
||||
<div class="card mb-3 bg-light">
|
||||
<div class="card-header">
|
||||
<h5 class="mb-0"><i class="fas fa-lightbulb"></i> Ejemplos de Claves Comunes</h5>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<div class="table-responsive">
|
||||
<table class="table table-sm">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Código</th>
|
||||
<th>Descripción</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr><td><strong>A1</strong></td><td>Importación definitiva de mercancías</td></tr>
|
||||
<tr><td><strong>A3</strong></td><td>Importación definitiva de vehículos usados</td></tr>
|
||||
<tr><td><strong>A4</strong></td><td>Importación definitiva de vehículos nuevos</td></tr>
|
||||
<tr><td><strong>B1</strong></td><td>Importación temporal para elaborar</td></tr>
|
||||
<tr><td><strong>C1</strong></td><td>Importación definitiva de mercancías donadas</td></tr>
|
||||
<tr><td><strong>G1</strong></td><td>Importación con Programa IMMEX</td></tr>
|
||||
<tr><td><strong>I1</strong></td><td>Importación definitiva exenta</td></tr>
|
||||
<tr><td><strong>L1</strong></td><td>Importación con TLC</td></tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<div class="alert alert-info mt-3">
|
||||
<i class="fas fa-info-circle"></i>
|
||||
<strong>Consejo:</strong> Puedes crear tus propias claves personalizadas o usar las estándar del SAT.
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Botones -->
|
||||
<div class="row">
|
||||
<div class="col-md-12">
|
||||
<div class="d-flex justify-content-end gap-2">
|
||||
<a href="/IMPORTADORES/claves_pedimentos/lista" class="btn btn-secondary btn-animated">
|
||||
<i class="fas fa-times"></i> Cancelar
|
||||
</a>
|
||||
<button type="submit" class="btn btn-success btn-animated">
|
||||
<i class="fas fa-save"></i> Guardar Clave
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- jQuery -->
|
||||
<script src="https://code.jquery.com/jquery-3.6.0.min.js"></script>
|
||||
<!-- SweetAlert2 -->
|
||||
<script src="https://cdn.jsdelivr.net/npm/sweetalert2@11"></script>
|
||||
|
||||
<script>
|
||||
$(document).ready(function() {
|
||||
// Validación del formulario
|
||||
$('#formClave').on('submit', function(e) {
|
||||
var codigo = $('#codigo').val().trim();
|
||||
var descripcion = $('#descripcion').val().trim();
|
||||
|
||||
if (!codigo || !descripcion) {
|
||||
e.preventDefault();
|
||||
Swal.fire({
|
||||
icon: 'warning',
|
||||
title: 'Campos obligatorios',
|
||||
text: 'Por favor complete todos los campos obligatorios (*)',
|
||||
confirmButtonColor: '#ffc107'
|
||||
});
|
||||
return false;
|
||||
}
|
||||
|
||||
// Validación del código
|
||||
if (codigo.length > 10) {
|
||||
e.preventDefault();
|
||||
Swal.fire({
|
||||
icon: 'error',
|
||||
title: 'Código muy largo',
|
||||
text: 'El código no puede tener más de 10 caracteres',
|
||||
confirmButtonColor: '#dc3545'
|
||||
});
|
||||
$('#codigo').focus();
|
||||
return false;
|
||||
}
|
||||
|
||||
// Validar que el código no tenga caracteres especiales
|
||||
var regex = /^[A-Z0-9]+$/;
|
||||
if (!regex.test(codigo)) {
|
||||
e.preventDefault();
|
||||
Swal.fire({
|
||||
icon: 'error',
|
||||
title: 'Código inválido',
|
||||
text: 'El código solo puede contener letras y números',
|
||||
confirmButtonColor: '#dc3545'
|
||||
});
|
||||
$('#codigo').focus();
|
||||
return false;
|
||||
}
|
||||
});
|
||||
|
||||
// Convertir código a mayúsculas automáticamente
|
||||
$('#codigo').on('input', function() {
|
||||
this.value = this.value.toUpperCase();
|
||||
});
|
||||
|
||||
// Contador de caracteres para descripción
|
||||
$('#descripcion').on('input', function() {
|
||||
var current = $(this).val().length;
|
||||
var max = 255;
|
||||
var remaining = max - current;
|
||||
|
||||
if (remaining < 20) {
|
||||
$(this).next('.form-text').text(`Caracteres restantes: ${remaining}`).addClass('text-warning');
|
||||
} else {
|
||||
$(this).next('.form-text').text('Describe claramente el tipo de operación que representa esta clave.').removeClass('text-warning');
|
||||
}
|
||||
});
|
||||
});
|
||||
</script>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
280
views/claves_pedimentos/editar.php
Normal file
280
views/claves_pedimentos/editar.php
Normal file
@@ -0,0 +1,280 @@
|
||||
<?php
|
||||
include __DIR__ . '/../partials/sidebar_importador.php';
|
||||
?>
|
||||
|
||||
<!DOCTYPE html>
|
||||
<html lang="es">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>🔑 Editar Clave de Pedimento</title>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css" rel="stylesheet">
|
||||
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/js/bootstrap.bundle.min.js"></script>
|
||||
<!-- Font Awesome -->
|
||||
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0/css/all.min.css">
|
||||
<!-- Animate.css -->
|
||||
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/animate.css/4.1.1/animate.min.css">
|
||||
<style>
|
||||
body { font-family: 'Segoe UI', sans-serif; background-color: #f4f6f9; }
|
||||
.sidebar { width: 220px; height: 100vh; background-color: #343a40; position: fixed; top: 0; left: 0; padding-top: 56px; z-index: 1040; }
|
||||
.sidebar .nav-link { font-weight: bold; color: white; transition: all 0.3s ease; }
|
||||
.sidebar .nav-link:hover,
|
||||
.sidebar .nav-link.active { background-color: #495057; color: #fff; }
|
||||
.content { margin-top: 56px; padding: 40px 20px; position: relative; z-index: 1; background-color: #f4f6f9; transition: margin-left 0.3s ease;}
|
||||
.navbar { position: fixed; top: 0; width: 100%; z-index: 1050; }
|
||||
/* A partir de dispositivos medianos (>=768px), deja espacio lateral */
|
||||
@media (min-width: 768px) { .content { margin-left: 250px; } }
|
||||
/* En móviles, sin margen lateral */
|
||||
@media (max-width: 767.98px) {
|
||||
.content { margin-left: 0; }
|
||||
.sidebar .nav-link { font-weight: normal; color: #343a40; background-color: transparent; }
|
||||
.sidebar .nav-link:hover,
|
||||
.sidebar .nav-link.active { background-color: #e9ecef; color: #212529; }
|
||||
}
|
||||
.card { border-radius: 12px; }
|
||||
.fade-in-up { animation: fadeInUp 0.8s ease-out; }
|
||||
@keyframes fadeInUp {
|
||||
from { opacity: 0; transform: translateY(30px); }
|
||||
to { opacity: 1; transform: translateY(0); }
|
||||
}
|
||||
.title-glow { text-shadow: 0 0 10px rgba(0, 123, 255, 0.3); }
|
||||
/* Efecto para botones */
|
||||
.btn-animated { transition: all 0.3s ease; position: relative; overflow: hidden; }
|
||||
.btn-animated:hover { transform: translateY(-2px); box-shadow: 0 5px 15px rgba(0, 0, 0, 0.2); }
|
||||
.btn-animated::before { content: ''; position: absolute; top: 0; left: -100%; width: 100%; height: 100%;
|
||||
background: linear-gradient(90deg, transparent, rgba(255, 255, 255, 0.2), transparent); transition: left 0.5s; }
|
||||
.btn-animated:hover::before { left: 100%; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<div class="content">
|
||||
<div class="d-flex justify-content-between align-items-center mb-4">
|
||||
<h4 class="animate__animated animate__fadeInDown title-glow">🔑 Editar Clave #<?= htmlspecialchars($clave['id_clave_pedimento']) ?></h4>
|
||||
<a href="/IMPORTADORES/claves_pedimentos/lista" class="btn btn-secondary btn-animated">
|
||||
<i class="fas fa-arrow-left"></i> Regresar a Lista
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<div class="card fade-in-up">
|
||||
<div class="card-body">
|
||||
<form action="/IMPORTADORES/claves_pedimentos/actualizar" method="POST" id="formClave">
|
||||
<input type="hidden" name="id_clave_pedimento" value="<?= htmlspecialchars($clave['id_clave_pedimento']) ?>">
|
||||
|
||||
<div class="row">
|
||||
<div class="col-md-6">
|
||||
<div class="card mb-3">
|
||||
<div class="card-header">
|
||||
<h5 class="mb-0"><i class="fas fa-info-circle"></i> Información de la Clave</h5>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<div class="mb-3">
|
||||
<label for="codigo" class="form-label">Código de la Clave <span class="text-danger">*</span></label>
|
||||
<input type="text" class="form-control" id="codigo" name="codigo" required
|
||||
value="<?= htmlspecialchars($clave['codigo'] ?? '') ?>"
|
||||
placeholder="Ej: A1, B3, G1" maxlength="10" style="text-transform: uppercase;">
|
||||
<div class="form-text">Máximo 10 caracteres. Se convertirá automáticamente a mayúsculas.</div>
|
||||
</div>
|
||||
|
||||
<div class="mb-3">
|
||||
<label for="descripcion" class="form-label">Descripción <span class="text-danger">*</span></label>
|
||||
<textarea class="form-control" id="descripcion" name="descripcion" rows="4" required
|
||||
placeholder="Descripción detallada de la clave de pedimento" maxlength="255"><?= htmlspecialchars($clave['descripcion'] ?? '') ?></textarea>
|
||||
<div class="form-text">Describe claramente el tipo de operación que representa esta clave.</div>
|
||||
</div>
|
||||
|
||||
<div class="mb-3">
|
||||
<label for="tipo_operacion" class="form-label">Tipo de Operación</label>
|
||||
<select class="form-select" id="tipo_operacion" name="tipo_operacion">
|
||||
<option value="importacion" <?= ($clave['tipo_operacion'] === 'importacion') ? 'selected' : '' ?>>Importación</option>
|
||||
<option value="exportacion" <?= ($clave['tipo_operacion'] === 'exportacion') ? 'selected' : '' ?>>Exportación</option>
|
||||
<option value="transito" <?= ($clave['tipo_operacion'] === 'transito') ? 'selected' : '' ?>>Tránsito</option>
|
||||
<option value="otros" <?= ($clave['tipo_operacion'] === 'otros') ? 'selected' : '' ?>>Otros</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div class="mb-3">
|
||||
<div class="form-check form-switch">
|
||||
<input class="form-check-input" type="checkbox" id="activo" name="activo"
|
||||
<?= ($clave['activo'] == 1) ? 'checked' : '' ?>>
|
||||
<label class="form-check-label" for="activo">
|
||||
<i class="fas fa-toggle-on text-success"></i> Clave activa
|
||||
</label>
|
||||
<div class="form-text">Las claves inactivas no aparecerán en los formularios de pedimentos.</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-md-6">
|
||||
<!-- Información de Auditoría -->
|
||||
<div class="card mb-3 bg-light">
|
||||
<div class="card-header">
|
||||
<h5 class="mb-0"><i class="fas fa-info"></i> Información del Registro</h5>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<div class="row">
|
||||
<div class="col-md-6">
|
||||
<p class="mb-1"><strong>ID:</strong> <?= htmlspecialchars($clave['id_clave_pedimento']) ?></p>
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<p class="mb-1"><strong>Código Actual:</strong>
|
||||
<span class="badge bg-primary"><?= htmlspecialchars($clave['codigo']) ?></span>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-md-6">
|
||||
<p class="mb-1"><strong>Fecha Creación:</strong>
|
||||
<?php
|
||||
if ($clave['fecha_creacion'] instanceof DateTime) {
|
||||
echo $clave['fecha_creacion']->format('d/m/Y H:i:s');
|
||||
} else {
|
||||
echo 'No disponible';
|
||||
}
|
||||
?>
|
||||
</p>
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<p class="mb-1"><strong>Última Modificación:</strong>
|
||||
<?php
|
||||
if ($clave['fecha_modificacion'] instanceof DateTime) {
|
||||
echo $clave['fecha_modificacion']->format('d/m/Y H:i:s');
|
||||
} else {
|
||||
echo 'No disponible';
|
||||
}
|
||||
?>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-md-12">
|
||||
<p class="mb-1"><strong>Estado:</strong>
|
||||
<?php if ($clave['activo'] == 1): ?>
|
||||
<span class="badge bg-success">Activo</span>
|
||||
<?php else: ?>
|
||||
<span class="badge bg-secondary">Inactivo</span>
|
||||
<?php endif; ?>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Referencias -->
|
||||
<div class="card mb-3 bg-info text-white">
|
||||
<div class="card-header">
|
||||
<h5 class="mb-0"><i class="fas fa-lightbulb"></i> Ejemplos de Claves Comunes</h5>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<div class="row">
|
||||
<div class="col-6">
|
||||
<small><strong>A1:</strong> Importación definitiva</small><br>
|
||||
<small><strong>A3:</strong> Vehículos usados</small><br>
|
||||
<small><strong>B1:</strong> Temporal elaborar</small><br>
|
||||
<small><strong>G1:</strong> Programa IMMEX</small>
|
||||
</div>
|
||||
<div class="col-6">
|
||||
<small><strong>I1:</strong> Definitiva exenta</small><br>
|
||||
<small><strong>L1:</strong> Con TLC</small><br>
|
||||
<small><strong>C1:</strong> Mercancías donadas</small><br>
|
||||
<small><strong>J1:</strong> Temporal reexportación</small>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Botones -->
|
||||
<div class="row">
|
||||
<div class="col-md-12">
|
||||
<div class="d-flex justify-content-end gap-2">
|
||||
<a href="/IMPORTADORES/claves_pedimentos/lista" class="btn btn-secondary btn-animated">
|
||||
<i class="fas fa-times"></i> Cancelar
|
||||
</a>
|
||||
<button type="submit" class="btn btn-primary btn-animated">
|
||||
<i class="fas fa-save"></i> Actualizar Clave
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- jQuery -->
|
||||
<script src="https://code.jquery.com/jquery-3.6.0.min.js"></script>
|
||||
<!-- SweetAlert2 -->
|
||||
<script src="https://cdn.jsdelivr.net/npm/sweetalert2@11"></script>
|
||||
|
||||
<script>
|
||||
$(document).ready(function() {
|
||||
// Validación del formulario
|
||||
$('#formClave').on('submit', function(e) {
|
||||
var codigo = $('#codigo').val().trim();
|
||||
var descripcion = $('#descripcion').val().trim();
|
||||
|
||||
if (!codigo || !descripcion) {
|
||||
e.preventDefault();
|
||||
Swal.fire({
|
||||
icon: 'warning',
|
||||
title: 'Campos obligatorios',
|
||||
text: 'Por favor complete todos los campos obligatorios (*)',
|
||||
confirmButtonColor: '#ffc107'
|
||||
});
|
||||
return false;
|
||||
}
|
||||
|
||||
// Validación del código
|
||||
if (codigo.length > 10) {
|
||||
e.preventDefault();
|
||||
Swal.fire({
|
||||
icon: 'error',
|
||||
title: 'Código muy largo',
|
||||
text: 'El código no puede tener más de 10 caracteres',
|
||||
confirmButtonColor: '#dc3545'
|
||||
});
|
||||
$('#codigo').focus();
|
||||
return false;
|
||||
}
|
||||
|
||||
// Validar que el código no tenga caracteres especiales
|
||||
var regex = /^[A-Z0-9]+$/;
|
||||
if (!regex.test(codigo)) {
|
||||
e.preventDefault();
|
||||
Swal.fire({
|
||||
icon: 'error',
|
||||
title: 'Código inválido',
|
||||
text: 'El código solo puede contener letras y números',
|
||||
confirmButtonColor: '#dc3545'
|
||||
});
|
||||
$('#codigo').focus();
|
||||
return false;
|
||||
}
|
||||
});
|
||||
|
||||
// Convertir código a mayúsculas automáticamente
|
||||
$('#codigo').on('input', function() {
|
||||
this.value = this.value.toUpperCase();
|
||||
});
|
||||
|
||||
// Contador de caracteres para descripción
|
||||
$('#descripcion').on('input', function() {
|
||||
var current = $(this).val().length;
|
||||
var max = 255;
|
||||
var remaining = max - current;
|
||||
|
||||
if (remaining < 20) {
|
||||
$(this).next('.form-text').text(`Caracteres restantes: ${remaining}`).addClass('text-warning');
|
||||
} else {
|
||||
$(this).next('.form-text').text('Describe claramente el tipo de operación que representa esta clave.').removeClass('text-warning');
|
||||
}
|
||||
});
|
||||
});
|
||||
</script>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
226
views/claves_pedimentos/importar_csv.php
Normal file
226
views/claves_pedimentos/importar_csv.php
Normal file
@@ -0,0 +1,226 @@
|
||||
<?php include __DIR__ . '/../partials/sidebar_importador.php'; ?>
|
||||
|
||||
<!DOCTYPE html>
|
||||
<html lang="es">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>📤 Importar Claves CSV</title>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css" rel="stylesheet">
|
||||
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/js/bootstrap.bundle.min.js"></script>
|
||||
<!-- Font Awesome -->
|
||||
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0/css/all.min.css">
|
||||
<style>
|
||||
body { font-family: 'Segoe UI', sans-serif; background-color: #f4f6f9; }
|
||||
.content { margin-top: 56px; padding: 40px 20px; position: relative; z-index: 1; background-color: #f4f6f9; transition: margin-left 0.3s ease;}
|
||||
@media (min-width: 768px) { .content { margin-left: 250px; } }
|
||||
@media (max-width: 767.98px) { .content { margin-left: 0; } }
|
||||
.card { border-radius: 12px; }
|
||||
.btn-animated { transition: all 0.3s ease; position: relative; overflow: hidden; }
|
||||
.btn-animated:hover { transform: translateY(-2px); box-shadow: 0 5px 15px rgba(0, 0, 0, 0.2); }
|
||||
.file-drop-area {
|
||||
border: 2px dashed #007bff;
|
||||
border-radius: 10px;
|
||||
padding: 40px;
|
||||
text-align: center;
|
||||
background-color: #f8f9ff;
|
||||
transition: all 0.3s ease;
|
||||
cursor: pointer;
|
||||
}
|
||||
.file-drop-area:hover {
|
||||
border-color: #0056b3;
|
||||
background-color: #e7f1ff;
|
||||
}
|
||||
.file-drop-area.dragover {
|
||||
border-color: #28a745;
|
||||
background-color: #d4edda;
|
||||
}
|
||||
.file-info {
|
||||
background-color: #e9ecef;
|
||||
border-radius: 8px;
|
||||
padding: 15px;
|
||||
margin-top: 15px;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<div class="content">
|
||||
<div class="d-flex justify-content-between align-items-center mb-4">
|
||||
<h4><i class="fas fa-upload text-primary"></i> Importar Claves de Pedimentos por CSV</h4>
|
||||
<div>
|
||||
<a href="/IMPORTADORES/public/downloads/claves_pedimentos_template.csv" class="btn btn-outline-info btn-animated me-2" download>
|
||||
<i class="fas fa-download"></i> Descargar Plantilla
|
||||
</a>
|
||||
<a href="/IMPORTADORES/claves_pedimentos/lista" class="btn btn-secondary btn-animated">
|
||||
<i class="fas fa-arrow-left"></i> Regresar
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<div class="col-md-8">
|
||||
<div class="card shadow-sm">
|
||||
<div class="card-header bg-primary text-white">
|
||||
<h5 class="mb-0"><i class="fas fa-file-csv"></i> Cargar Archivo CSV</h5>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<form action="/IMPORTADORES/claves_pedimentos/procesar_csv" method="POST" enctype="multipart/form-data" id="csvForm">
|
||||
|
||||
<div class="file-drop-area" id="fileDropArea">
|
||||
<i class="fas fa-cloud-upload-alt fa-3x text-primary mb-3"></i>
|
||||
<h5>Arrastra tu archivo CSV aquí</h5>
|
||||
<p class="text-muted">o haz clic para seleccionar archivo</p>
|
||||
<input type="file" name="csv_file" id="csvFile" accept=".csv" style="display: none;" required>
|
||||
</div>
|
||||
|
||||
<div id="fileInfo" class="file-info" style="display: none;">
|
||||
<h6><i class="fas fa-file-check text-success"></i> Archivo seleccionado:</h6>
|
||||
<p id="fileName" class="mb-1"></p>
|
||||
<small id="fileSize" class="text-muted"></small>
|
||||
</div>
|
||||
|
||||
<div class="mt-4">
|
||||
<h6><i class="fas fa-cogs"></i> Opciones de importación:</h6>
|
||||
<div class="form-check">
|
||||
<input class="form-check-input" type="checkbox" name="omitir_duplicados" id="omitirDuplicados" checked>
|
||||
<label class="form-check-label" for="omitirDuplicados">
|
||||
Omitir códigos duplicados (no reemplazar los existentes)
|
||||
</label>
|
||||
</div>
|
||||
<div class="form-check">
|
||||
<input class="form-check-input" type="checkbox" name="activar_todas" id="activarTodas" checked>
|
||||
<label class="form-check-label" for="activarTodas">
|
||||
Activar todas las claves importadas
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mt-4 text-center">
|
||||
<button type="submit" class="btn btn-success btn-lg btn-animated" id="submitBtn" disabled>
|
||||
<i class="fas fa-upload"></i> Procesar Archivo CSV
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-md-4">
|
||||
<div class="card shadow-sm">
|
||||
<div class="card-header bg-info text-white">
|
||||
<h5 class="mb-0"><i class="fas fa-info-circle"></i> Instrucciones</h5>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<h6>📋 Formato del archivo CSV:</h6>
|
||||
<ul class="small">
|
||||
<li><strong>Separador:</strong> Coma (,)</li>
|
||||
<li><strong>Codificación:</strong> UTF-8</li>
|
||||
<li><strong>Encabezados:</strong> Obligatorios</li>
|
||||
</ul>
|
||||
|
||||
<h6>🏷️ Columnas requeridas:</h6>
|
||||
<ul class="small">
|
||||
<li><strong>codigo:</strong> Código de la clave (ej: A1, B2)</li>
|
||||
<li><strong>descripcion:</strong> Descripción detallada</li>
|
||||
<li><strong>tipo_operacion:</strong> importacion o exportacion</li>
|
||||
<li><strong>activo:</strong> 1 (activo) o 0 (inactivo)</li>
|
||||
</ul>
|
||||
|
||||
<div class="alert alert-warning small mt-3">
|
||||
<i class="fas fa-exclamation-triangle"></i>
|
||||
<strong>Importante:</strong> Los códigos deben ser únicos. Si existe un código duplicado, se omitirá según la configuración seleccionada.
|
||||
</div>
|
||||
|
||||
<div class="mt-3">
|
||||
<a href="/IMPORTADORES/public/downloads/claves_pedimentos_template.csv" class="btn btn-sm btn-outline-primary w-100" download>
|
||||
<i class="fas fa-download"></i> Descargar plantilla de ejemplo
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script src="https://cdn.jsdelivr.net/npm/sweetalert2@11"></script>
|
||||
<script>
|
||||
const fileDropArea = document.getElementById('fileDropArea');
|
||||
const csvFile = document.getElementById('csvFile');
|
||||
const fileInfo = document.getElementById('fileInfo');
|
||||
const fileName = document.getElementById('fileName');
|
||||
const fileSize = document.getElementById('fileSize');
|
||||
const submitBtn = document.getElementById('submitBtn');
|
||||
|
||||
// Eventos de drag & drop
|
||||
fileDropArea.addEventListener('click', () => csvFile.click());
|
||||
fileDropArea.addEventListener('dragover', handleDragOver);
|
||||
fileDropArea.addEventListener('dragleave', handleDragLeave);
|
||||
fileDropArea.addEventListener('drop', handleDrop);
|
||||
csvFile.addEventListener('change', handleFileSelect);
|
||||
|
||||
function handleDragOver(e) {
|
||||
e.preventDefault();
|
||||
fileDropArea.classList.add('dragover');
|
||||
}
|
||||
|
||||
function handleDragLeave(e) {
|
||||
e.preventDefault();
|
||||
fileDropArea.classList.remove('dragover');
|
||||
}
|
||||
|
||||
function handleDrop(e) {
|
||||
e.preventDefault();
|
||||
fileDropArea.classList.remove('dragover');
|
||||
|
||||
const files = e.dataTransfer.files;
|
||||
if (files.length > 0) {
|
||||
csvFile.files = files;
|
||||
handleFileSelect();
|
||||
}
|
||||
}
|
||||
|
||||
function handleFileSelect() {
|
||||
const file = csvFile.files[0];
|
||||
|
||||
if (file) {
|
||||
// Validar que sea CSV
|
||||
if (!file.name.toLowerCase().endsWith('.csv')) {
|
||||
Swal.fire({
|
||||
icon: 'error',
|
||||
title: 'Archivo inválido',
|
||||
text: 'Por favor selecciona un archivo CSV válido.'
|
||||
});
|
||||
csvFile.value = '';
|
||||
return;
|
||||
}
|
||||
|
||||
// Mostrar información del archivo
|
||||
fileName.textContent = file.name;
|
||||
fileSize.textContent = `Tamaño: ${(file.size / 1024).toFixed(1)} KB`;
|
||||
fileInfo.style.display = 'block';
|
||||
submitBtn.disabled = false;
|
||||
}
|
||||
}
|
||||
|
||||
// Validar formulario antes de enviar
|
||||
document.getElementById('csvForm').addEventListener('submit', function(e) {
|
||||
const file = csvFile.files[0];
|
||||
|
||||
if (!file) {
|
||||
e.preventDefault();
|
||||
Swal.fire({
|
||||
icon: 'warning',
|
||||
title: 'Archivo requerido',
|
||||
text: 'Por favor selecciona un archivo CSV.'
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
// Mostrar indicador de carga
|
||||
submitBtn.innerHTML = '<i class="fas fa-spinner fa-spin"></i> Procesando...';
|
||||
submitBtn.disabled = true;
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
170
views/claves_pedimentos/index.php
Normal file
170
views/claves_pedimentos/index.php
Normal file
@@ -0,0 +1,170 @@
|
||||
<?php include __DIR__ . '/../partials/sidebar_importador.php'; ?>
|
||||
|
||||
<!DOCTYPE html>
|
||||
<html lang="es">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>🔑 Claves de Pedimentos</title>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css" rel="stylesheet">
|
||||
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/js/bootstrap.bundle.min.js"></script>
|
||||
<!-- Font Awesome -->
|
||||
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0/css/all.min.css">
|
||||
<!-- Animate.css -->
|
||||
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/animate.css/4.1.1/animate.min.css">
|
||||
<style>
|
||||
body { font-family: 'Segoe UI', sans-serif; background-color: #f4f6f9; }
|
||||
.sidebar { width: 220px; height: 100vh; background-color: #343a40; position: fixed; top: 0; left: 0; padding-top: 56px; z-index: 1040; }
|
||||
.sidebar .nav-link { font-weight: bold; color: white; transition: all 0.3s ease; }
|
||||
.sidebar .nav-link:hover,
|
||||
.sidebar .nav-link.active { background-color: #495057; color: #fff; }
|
||||
.content { margin-top: 56px; padding: 40px 20px; position: relative; z-index: 1; background-color: #f4f6f9; transition: margin-left 0.3s ease;}
|
||||
.navbar { position: fixed; top: 0; width: 100%; z-index: 1050; }
|
||||
/* A partir de dispositivos medianos (>=768px), deja espacio lateral */
|
||||
@media (min-width: 768px) { .content { margin-left: 250px; } }
|
||||
/* En móviles, sin margen lateral */
|
||||
@media (max-width: 767.98px) {
|
||||
.content { margin-left: 0; }
|
||||
.sidebar .nav-link { font-weight: normal; color: #343a40; background-color: transparent; }
|
||||
.sidebar .nav-link:hover,
|
||||
.sidebar .nav-link.active { background-color: #e9ecef; color: #212529; }
|
||||
}
|
||||
.card { border-radius: 12px; }
|
||||
/* Animaciones personalizadas */
|
||||
.card-hover { transition: all 0.3s ease; cursor: pointer; }
|
||||
.card-hover:hover { transform: translateY(-8px) scale(1.02); box-shadow: 0 1rem 2rem rgba(0, 0, 0, 0.15) !important; }
|
||||
.btn-pulse { animation: pulse 2s infinite; }
|
||||
@keyframes pulse {
|
||||
0% { transform: scale(1); }
|
||||
50% { transform: scale(1.05); }
|
||||
100% { transform: scale(1); }
|
||||
}
|
||||
.fade-in-up { animation: fadeInUp 0.8s ease-out; }
|
||||
@keyframes fadeInUp {
|
||||
from { opacity: 0; transform: translateY(30px); }
|
||||
to { opacity: 1; transform: translateY(0); }
|
||||
}
|
||||
/* Responsive animations */
|
||||
@media (max-width: 768px) { .card-hover:hover { transform: translateY(-4px) scale(1.01); } }
|
||||
/* Efecto de glow para elementos activos */
|
||||
.btn.active { box-shadow: 0 0 20px rgba(var(--bs-primary-rgb), 0.5); }
|
||||
/* Animación para el título */
|
||||
.title-glow { text-shadow: 0 0 10px rgba(0, 123, 255, 0.3); }
|
||||
/* Efecto para botones */
|
||||
.btn-animated { transition: all 0.3s ease; position: relative; overflow: hidden; }
|
||||
.btn-animated:hover { transform: translateY(-2px); box-shadow: 0 5px 15px rgba(0, 0, 0, 0.2); }
|
||||
.btn-animated::before { content: ''; position: absolute; top: 0; left: -100%; width: 100%; height: 100%;
|
||||
background: linear-gradient(90deg, transparent, rgba(255, 255, 255, 0.2), transparent); transition: left 0.5s; }
|
||||
.btn-animated:hover::before { left: 100%; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<div class="content">
|
||||
<h4 class="mb-4 animate__animated animate__fadeInDown title-glow">🔑 Configuración de Claves de Pedimentos</h4>
|
||||
|
||||
<div class="row mb-4">
|
||||
<div class="col-md-6 col-lg-4 mb-3">
|
||||
<div class="card bg-primary text-white card-hover fade-in-up">
|
||||
<div class="card-body">
|
||||
<div class="d-flex justify-content-between">
|
||||
<div>
|
||||
<h5 class="card-title">Mis Claves</h5>
|
||||
<p class="card-text">Ver y gestionar tus claves de pedimentos configuradas</p>
|
||||
</div>
|
||||
<div class="align-self-center">
|
||||
<i class="fas fa-list fa-2x"></i>
|
||||
</div>
|
||||
</div>
|
||||
<a href="/IMPORTADORES/claves_pedimentos/lista" class="btn btn-outline-light btn-sm btn-animated">
|
||||
Ver Lista <i class="fas fa-arrow-right"></i>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-md-6 col-lg-4 mb-3">
|
||||
<div class="card bg-success text-white card-hover fade-in-up">
|
||||
<div class="card-body">
|
||||
<div class="d-flex justify-content-between">
|
||||
<div>
|
||||
<h5 class="card-title">Nueva Clave</h5>
|
||||
<p class="card-text">Agregar una nueva clave de pedimento personalizada</p>
|
||||
</div>
|
||||
<div class="align-self-center">
|
||||
<i class="fas fa-plus-circle fa-2x"></i>
|
||||
</div>
|
||||
</div>
|
||||
<a href="/IMPORTADORES/claves_pedimentos/crear" class="btn btn-outline-light btn-sm btn-animated">
|
||||
Crear Nueva <i class="fas fa-plus"></i>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-md-6 col-lg-4 mb-3">
|
||||
<div class="card bg-warning text-white card-hover fade-in-up">
|
||||
<div class="card-body">
|
||||
<div class="d-flex justify-content-between">
|
||||
<div>
|
||||
<h5 class="card-title">Inicializar</h5>
|
||||
<p class="card-text">Cargar claves de pedimentos por defecto</p>
|
||||
</div>
|
||||
<div class="align-self-center">
|
||||
<i class="fas fa-download fa-2x"></i>
|
||||
</div>
|
||||
</div>
|
||||
<a href="/IMPORTADORES/claves_pedimentos/inicializar_claves_usuario"
|
||||
class="btn btn-outline-light btn-sm btn-animated"
|
||||
onclick="return confirm('¿Desea cargar las claves de pedimentos por defecto? Solo funciona si no tiene claves configuradas.')">
|
||||
Inicializar <i class="fas fa-magic"></i>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<div class="col-12">
|
||||
<div class="card fade-in-up">
|
||||
<div class="card-header">
|
||||
<h5 class="mb-0"><i class="fas fa-info-circle"></i> Información del Módulo</h5>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<p class="text-muted">
|
||||
Las claves de pedimentos son códigos que identifican el tipo de operación de comercio exterior.
|
||||
Este módulo te permite personalizar y gestionar las claves que utilizas frecuentemente.
|
||||
</p>
|
||||
<div class="row">
|
||||
<div class="col-md-6">
|
||||
<h6><i class="fas fa-check text-success"></i> Funcionalidades:</h6>
|
||||
<ul class="list-unstyled">
|
||||
<li><i class="fas fa-dot-circle text-primary"></i> Crear claves personalizadas</li>
|
||||
<li><i class="fas fa-dot-circle text-primary"></i> Modificar claves existentes</li>
|
||||
<li><i class="fas fa-dot-circle text-primary"></i> Activar/desactivar claves</li>
|
||||
<li><i class="fas fa-dot-circle text-primary"></i> Eliminar claves no utilizadas</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<h6><i class="fas fa-lightbulb text-warning"></i> Claves Comunes de Importación:</h6>
|
||||
<ul class="list-unstyled">
|
||||
<li><i class="fas fa-dot-circle text-info"></i> <strong>A1:</strong> Importación definitiva</li>
|
||||
<li><i class="fas fa-dot-circle text-info"></i> <strong>B1:</strong> Importación temporal</li>
|
||||
<li><i class="fas fa-dot-circle text-info"></i> <strong>G1:</strong> Programa IMMEX</li>
|
||||
<li><i class="fas fa-dot-circle text-info"></i> <strong>L1:</strong> TLC (Tratados)</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- jQuery -->
|
||||
<script src="https://code.jquery.com/jquery-3.6.0.min.js"></script>
|
||||
<!-- SweetAlert2 -->
|
||||
<script src="https://cdn.jsdelivr.net/npm/sweetalert2@11"></script>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
204
views/claves_pedimentos/lista.php
Normal file
204
views/claves_pedimentos/lista.php
Normal file
@@ -0,0 +1,204 @@
|
||||
<?php include __DIR__ . '/../partials/sidebar_importador.php'; ?>
|
||||
|
||||
<!DOCTYPE html>
|
||||
<html lang="es">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>🔑 Lista de Claves de Pedimentos</title>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css" rel="stylesheet">
|
||||
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/js/bootstrap.bundle.min.js"></script>
|
||||
<link href="https://cdn.datatables.net/1.13.4/css/jquery.dataTables.min.css" rel="stylesheet">
|
||||
<!-- Font Awesome -->
|
||||
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0/css/all.min.css">
|
||||
<!-- Animate.css -->
|
||||
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/animate.css/4.1.1/animate.min.css">
|
||||
<style>
|
||||
table.dataTable thead th { background:#343a40; color:#fff; }
|
||||
body { font-family: 'Segoe UI', sans-serif; background-color: #f4f6f9; }
|
||||
.sidebar { width: 220px; height: 100vh; background-color: #343a40; position: fixed; top: 0; left: 0; padding-top: 56px; z-index: 1040; }
|
||||
.sidebar .nav-link { font-weight: bold; color: white; transition: all 0.3s ease; }
|
||||
.sidebar .nav-link:hover,
|
||||
.sidebar .nav-link.active { background-color: #495057; color: #fff; }
|
||||
.content { margin-top: 56px; padding: 40px 20px; position: relative; z-index: 1; background-color: #f4f6f9; transition: margin-left 0.3s ease;}
|
||||
.navbar { position: fixed; top: 0; width: 100%; z-index: 1050; }
|
||||
/* A partir de dispositivos medianos (>=768px), deja espacio lateral */
|
||||
@media (min-width: 768px) { .content { margin-left: 250px; } }
|
||||
/* En móviles, sin margen lateral */
|
||||
@media (max-width: 767.98px) {
|
||||
.content { margin-left: 0; }
|
||||
.sidebar .nav-link { font-weight: normal; color: #343a40; background-color: transparent; }
|
||||
.sidebar .nav-link:hover,
|
||||
.sidebar .nav-link.active { background-color: #e9ecef; color: #212529; }
|
||||
}
|
||||
.card { border-radius: 12px; }
|
||||
/* Animaciones personalizadas */
|
||||
.card-hover { transition: all 0.3s ease; cursor: pointer; }
|
||||
.card-hover:hover { transform: translateY(-8px) scale(1.02); box-shadow: 0 1rem 2rem rgba(0, 0, 0, 0.15) !important; }
|
||||
.fade-in-up { animation: fadeInUp 0.8s ease-out; }
|
||||
@keyframes fadeInUp {
|
||||
from { opacity: 0; transform: translateY(30px); }
|
||||
to { opacity: 1; transform: translateY(0); }
|
||||
}
|
||||
.title-glow { text-shadow: 0 0 10px rgba(0, 123, 255, 0.3); }
|
||||
/* Efecto para botones */
|
||||
.btn-animated { transition: all 0.3s ease; position: relative; overflow: hidden; }
|
||||
.btn-animated:hover { transform: translateY(-2px); box-shadow: 0 5px 15px rgba(0, 0, 0, 0.2); }
|
||||
.btn-animated::before { content: ''; position: absolute; top: 0; left: -100%; width: 100%; height: 100%;
|
||||
background: linear-gradient(90deg, transparent, rgba(255, 255, 255, 0.2), transparent); transition: left 0.5s; }
|
||||
.btn-animated:hover::before { left: 100%; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<div class="content">
|
||||
<div class="d-flex justify-content-between align-items-center mb-4">
|
||||
<h4 class="animate__animated animate__fadeInDown title-glow">🔑 Mis Claves de Pedimentos</h4>
|
||||
<div>
|
||||
<a href="/IMPORTADORES/claves_pedimentos/crear" class="btn btn-success btn-animated me-2">
|
||||
<i class="fas fa-plus"></i> Nueva Clave
|
||||
</a>
|
||||
<a href="/IMPORTADORES/claves_pedimentos/importar_csv" class="btn btn-info btn-animated me-2">
|
||||
<i class="fas fa-upload"></i> Importar CSV
|
||||
</a>
|
||||
<a href="/IMPORTADORES/claves_pedimentos" class="btn btn-secondary btn-animated">
|
||||
<i class="fas fa-arrow-left"></i> Regresar
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card p-3 shadow-sm card-hover position-relative h-auto fade-in-up">
|
||||
<div class="table-responsive">
|
||||
<table id="tablaClaves" class="display nowrap" style="width:100%">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>ID</th>
|
||||
<th>Código</th>
|
||||
<th>Descripción</th>
|
||||
<th>Tipo</th>
|
||||
<th>Estado</th>
|
||||
<th>Fecha Creación</th>
|
||||
<th>Acciones</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<!-- Los datos se cargan via AJAX -->
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- jQuery -->
|
||||
<script src="https://code.jquery.com/jquery-3.6.0.min.js"></script>
|
||||
<!-- DataTables JS -->
|
||||
<script src="https://cdn.datatables.net/1.13.4/js/jquery.dataTables.min.js"></script>
|
||||
<!-- SweetAlert2 -->
|
||||
<script src="https://cdn.jsdelivr.net/npm/sweetalert2@11"></script>
|
||||
|
||||
<script>
|
||||
$(document).ready(function() {
|
||||
// Inicializar DataTable
|
||||
$('#tablaClaves').DataTable({
|
||||
"processing": true,
|
||||
"serverSide": true,
|
||||
"searchDelay": 500,
|
||||
"deferRender": true,
|
||||
"ajax": {
|
||||
"url": "/IMPORTADORES/claves_pedimentos/ajax_lista",
|
||||
"type": "GET"
|
||||
},
|
||||
"columns": [
|
||||
{ "data": 0, "name": "id_clave_pedimento" },
|
||||
{ "data": 1, "name": "codigo" },
|
||||
{ "data": 2, "name": "descripcion" },
|
||||
{ "data": 3, "name": "tipo_operacion" },
|
||||
{
|
||||
"data": 4,
|
||||
"name": "activo",
|
||||
"render": function(data, type, row) {
|
||||
if (data === 'Activo') {
|
||||
return '<span class="badge bg-success">Activo</span>';
|
||||
} else {
|
||||
return '<span class="badge bg-secondary">Inactivo</span>';
|
||||
}
|
||||
}
|
||||
},
|
||||
{ "data": 5, "name": "fecha_creacion" },
|
||||
{
|
||||
"data": null,
|
||||
"orderable": false,
|
||||
"searchable": false,
|
||||
"render": function(data, type, row) {
|
||||
const id = row[0];
|
||||
return `
|
||||
<a href="/IMPORTADORES/claves_pedimentos/editar?id=${id}" class="btn btn-sm btn-primary btn-animated" title="Editar">
|
||||
<i class="fas fa-edit"></i>
|
||||
</a>
|
||||
<button type="button" class="btn btn-sm btn-danger btn-animated" onclick="confirmarEliminar(${id})" title="Eliminar">
|
||||
<i class="fas fa-trash"></i>
|
||||
</button>
|
||||
`;
|
||||
}
|
||||
}
|
||||
],
|
||||
"language": {
|
||||
"url": "https://cdn.datatables.net/plug-ins/1.13.4/i18n/es-ES.json"
|
||||
},
|
||||
"responsive": true,
|
||||
"order": [[1, "asc"]], // Ordenar por código
|
||||
"pageLength": 25,
|
||||
"lengthMenu": [[10, 25, 50, 100], [10, 25, 50, 100]]
|
||||
});
|
||||
});
|
||||
|
||||
function confirmarEliminar(id) {
|
||||
Swal.fire({
|
||||
title: '¿Eliminar clave de pedimento?',
|
||||
text: '¿Está seguro que desea eliminar esta clave de pedimento?',
|
||||
icon: 'warning',
|
||||
showCancelButton: true,
|
||||
confirmButtonText: 'Sí, eliminar',
|
||||
cancelButtonText: 'Cancelar',
|
||||
confirmButtonColor: '#d33'
|
||||
}).then(result => {
|
||||
if (result.isConfirmed) {
|
||||
window.location = `/IMPORTADORES/claves_pedimentos/eliminar?id=${id}`;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Mostrar alertas de éxito/error
|
||||
<?php if (isset($_GET['created'])): ?>
|
||||
<?php if ($_GET['created'] === 'ok'): ?>
|
||||
Swal.fire({ icon: 'success', title: 'Clave creada', text: 'La clave de pedimento se creó correctamente.', confirmButtonColor: '#198754' });
|
||||
<?php elseif ($_GET['created'] === 'initialized'): ?>
|
||||
Swal.fire({ icon: 'success', title: 'Claves inicializadas', text: 'Se cargaron las claves de pedimento por defecto.', confirmButtonColor: '#198754' });
|
||||
<?php endif; ?>
|
||||
<?php endif; ?>
|
||||
|
||||
<?php if (isset($_GET['updated'])): ?>
|
||||
Swal.fire({ icon: 'success', title: 'Clave actualizada', text: 'Los datos fueron modificados correctamente.', confirmButtonColor: '#198754' });
|
||||
<?php endif; ?>
|
||||
|
||||
<?php if (isset($_GET['deleted'])): ?>
|
||||
Swal.fire({ icon: 'success', title: 'Clave eliminada', text: 'La clave de pedimento fue eliminada correctamente.', confirmButtonColor: '#198754' });
|
||||
<?php endif; ?>
|
||||
|
||||
<?php if (isset($_GET['info']) && $_GET['info'] === 'already_initialized'): ?>
|
||||
Swal.fire({ icon: 'info', title: 'Ya inicializado', text: 'Ya tienes claves de pedimentos configuradas.', confirmButtonColor: '#0d6efd' });
|
||||
<?php endif; ?>
|
||||
|
||||
<?php if (isset($_GET['imported']) && $_GET['imported'] === 'ok' && isset($_GET['message'])): ?>
|
||||
Swal.fire({
|
||||
icon: 'success',
|
||||
title: 'Importación completada',
|
||||
html: '<?= urldecode($_GET['message']) ?>',
|
||||
confirmButtonColor: '#198754',
|
||||
width: 600
|
||||
});
|
||||
<?php endif; ?>
|
||||
</script>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
@@ -15,12 +15,12 @@ if (!$stmt || !sqlsrv_execute($stmt)) {
|
||||
}
|
||||
$config = sqlsrv_fetch_array($stmt, SQLSRV_FETCH_ASSOC);
|
||||
|
||||
// Variables con valores por defecto
|
||||
$nombre = $config['nombre_plataforma'] ?? 'Sistema Integral para Importadores de Hidrocarburos';
|
||||
$siglas = $config['siglas'] ?? 'SIIH';
|
||||
$logo = $config['logo_url'] ?? 'assets/img/logo_siih.png';
|
||||
$color1 = explode(',', $config['colores_primarios'] ?? '#003366,#0055A5')[0];
|
||||
$color2 = explode(',', $config['colores_primarios'] ?? '#003366,#0055A5')[1];
|
||||
// Variables con valores por defecto - Branding más sutil
|
||||
$nombre = $config['nombre_plataforma'] ?? 'Sistema de Gestión de Importaciones';
|
||||
$siglas = $config['siglas'] ?? 'AduanaSoft';
|
||||
$logo = $config['logo_url'] ?? 'assets/img/logo_aduanasoft.png';
|
||||
$color1 = explode(',', $config['colores_primarios'] ?? '#0f172a,#334155')[0]; // Negro suave
|
||||
$color2 = explode(',', $config['colores_primarios'] ?? '#0f172a,#334155')[1]; // Gris slate
|
||||
$mantenimiento = $config['modo_mantenimiento'] ?? 0;
|
||||
$mensajeMantenimiento = $config['mensaje_mantenimiento'] ?? 'El sistema se encuentra en mantenimiento temporal. Por favor, vuelve más tarde.';
|
||||
?>
|
||||
@@ -29,23 +29,288 @@ $mensajeMantenimiento = $config['mensaje_mantenimiento'] ?? 'El sistema se encue
|
||||
<html lang="es">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title><?= htmlspecialchars($siglas) ?> | Inicio</title>
|
||||
<title><?= htmlspecialchars($siglas) ?> | Gestión de Importaciones</title>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<meta name="description" content="Plataforma integral para la gestión de importaciones, trámites aduaneros y control de mercancías">
|
||||
<!-- Bootstrap -->
|
||||
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css" rel="stylesheet">
|
||||
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/js/bootstrap.bundle.min.js"></script>
|
||||
<!-- Iconos -->
|
||||
<link href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.0/css/all.min.css" rel="stylesheet">
|
||||
<!-- Google Fonts -->
|
||||
<link href="https://fonts.googleapis.com/css2?family=Poppins:wght@300;400;500;600;700&display=swap" rel="stylesheet">
|
||||
<style>
|
||||
body { background-color: #f4f6f9; font-family: 'Segoe UI', sans-serif; }
|
||||
.navbar { background-color: #fff; }
|
||||
.navbar .nav-link, .navbar-brand { color:<?= $color1 ?> !important; font-weight: 500; }
|
||||
.navbar .nav-link.active { background-color: #F9F9F9; border-radius: 5px; }
|
||||
.logo-siih { height: 90px; margin-right: 10px; }
|
||||
.hero { background: linear-gradient(to right, <?= $color1 ?>, <?= $color2 ?>); color: white; padding: 80px 20px; text-align: center; }
|
||||
.features { background-color: white; padding: 50px 0; }
|
||||
.features i { font-size: 40px; color: <?= $color1 ?>; margin-bottom: 10px; }
|
||||
footer { background-color: #e9ecef; padding: 20px; text-align: center; font-size: 14px; color: #666; }
|
||||
:root {
|
||||
--primary: <?= $color1 ?>;
|
||||
--secondary: <?= $color2 ?>;
|
||||
--accent: #10b981;
|
||||
--warning: #f59e0b;
|
||||
--surface: #ffffff;
|
||||
--background: #f8fafc;
|
||||
--text: #1e293b;
|
||||
--text-muted: #64748b;
|
||||
--border: #e2e8f0;
|
||||
}
|
||||
|
||||
* {
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
body {
|
||||
background-color: var(--background);
|
||||
font-family: 'Poppins', sans-serif;
|
||||
color: var(--text);
|
||||
line-height: 1.7;
|
||||
margin: 0;
|
||||
font-weight: 400;
|
||||
}
|
||||
|
||||
.navbar {
|
||||
background: rgba(255, 255, 255, 0.95);
|
||||
backdrop-filter: blur(20px);
|
||||
border-bottom: 1px solid var(--border);
|
||||
padding: 1rem 0;
|
||||
position: sticky;
|
||||
top: 0;
|
||||
z-index: 1000;
|
||||
}
|
||||
|
||||
.navbar-brand {
|
||||
font-weight: 600;
|
||||
font-size: 1.25rem;
|
||||
color: var(--primary) !important;
|
||||
}
|
||||
|
||||
.nav-link {
|
||||
color: var(--text-muted) !important;
|
||||
font-weight: 500;
|
||||
padding: 0.5rem 1rem !important;
|
||||
border-radius: 8px;
|
||||
transition: all 0.2s ease;
|
||||
}
|
||||
|
||||
.nav-link:hover, .nav-link.active {
|
||||
color: var(--primary) !important;
|
||||
background-color: #f1f5f9;
|
||||
}
|
||||
|
||||
.hero {
|
||||
background: linear-gradient(135deg, var(--primary) 0%, var(--secondary) 100%);
|
||||
min-height: 90vh;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.hero::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
width: 200%;
|
||||
height: 200%;
|
||||
background: url('data:image/svg+xml,<svg xmlns="http://www.w3.org/2000/svg" width="60" height="60" viewBox="0 0 60 60"><g fill="none" stroke="rgba(255,255,255,0.1)" stroke-width="1"><circle cx="30" cy="30" r="1"/></g></svg>') repeat;
|
||||
animation: float 20s ease-in-out infinite;
|
||||
}
|
||||
|
||||
@keyframes float {
|
||||
0%, 100% { transform: translate(0, 0) rotate(0deg); }
|
||||
50% { transform: translate(-20px, -20px) rotate(180deg); }
|
||||
}
|
||||
|
||||
.hero-content {
|
||||
position: relative;
|
||||
z-index: 2;
|
||||
}
|
||||
|
||||
.hero h1 {
|
||||
font-size: clamp(2.5rem, 5vw, 4rem);
|
||||
font-weight: 700;
|
||||
margin-bottom: 1.5rem;
|
||||
color: white;
|
||||
line-height: 1.2;
|
||||
}
|
||||
|
||||
.hero .lead {
|
||||
font-size: 1.2rem;
|
||||
color: rgba(255, 255, 255, 0.9);
|
||||
margin-bottom: 3rem;
|
||||
max-width: 600px;
|
||||
}
|
||||
|
||||
.btn-modern {
|
||||
padding: 12px 28px;
|
||||
border-radius: 12px;
|
||||
font-weight: 600;
|
||||
text-decoration: none;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
transition: all 0.3s ease;
|
||||
border: none;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.btn-primary-modern {
|
||||
background: white;
|
||||
color: var(--primary);
|
||||
box-shadow: 0 4px 15px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
.btn-primary-modern:hover {
|
||||
color: var(--primary);
|
||||
transform: translateY(-2px);
|
||||
box-shadow: 0 8px 25px rgba(0, 0, 0, 0.15);
|
||||
}
|
||||
|
||||
.btn-outline-modern {
|
||||
background: transparent;
|
||||
color: white;
|
||||
border: 2px solid rgba(255, 255, 255, 0.3);
|
||||
}
|
||||
|
||||
.btn-outline-modern:hover {
|
||||
background: rgba(255, 255, 255, 0.1);
|
||||
color: white;
|
||||
border-color: white;
|
||||
}
|
||||
|
||||
.stats {
|
||||
background: var(--surface);
|
||||
margin-top: -4rem;
|
||||
position: relative;
|
||||
z-index: 3;
|
||||
border-radius: 20px;
|
||||
box-shadow: 0 10px 30px rgba(0, 0, 0, 0.1);
|
||||
padding: 3rem 0;
|
||||
}
|
||||
|
||||
.stat-item {
|
||||
text-align: center;
|
||||
padding: 1rem;
|
||||
}
|
||||
|
||||
.stat-number {
|
||||
font-size: 2.5rem;
|
||||
font-weight: 700;
|
||||
color: var(--primary);
|
||||
display: block;
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
.stat-label {
|
||||
color: var(--text-muted);
|
||||
font-size: 0.9rem;
|
||||
font-weight: 500;
|
||||
margin-top: 0.5rem;
|
||||
}
|
||||
|
||||
.section-modern {
|
||||
padding: 5rem 0;
|
||||
}
|
||||
|
||||
.section-title {
|
||||
font-size: 2.5rem;
|
||||
font-weight: 700;
|
||||
color: var(--text);
|
||||
margin-bottom: 1rem;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.section-subtitle {
|
||||
color: var(--text-muted);
|
||||
font-size: 1.1rem;
|
||||
text-align: center;
|
||||
max-width: 600px;
|
||||
margin: 0 auto 4rem;
|
||||
}
|
||||
|
||||
.feature-card {
|
||||
background: var(--surface);
|
||||
border-radius: 16px;
|
||||
padding: 2.5rem 2rem;
|
||||
text-align: center;
|
||||
border: 1px solid var(--border);
|
||||
transition: all 0.3s ease;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.feature-card:hover {
|
||||
transform: translateY(-8px);
|
||||
box-shadow: 0 20px 40px rgba(0, 0, 0, 0.08);
|
||||
border-color: var(--accent);
|
||||
}
|
||||
|
||||
.feature-icon {
|
||||
width: 70px;
|
||||
height: 70px;
|
||||
background: linear-gradient(135deg, var(--accent), #059669);
|
||||
border-radius: 16px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
margin: 0 auto 1.5rem;
|
||||
color: white;
|
||||
font-size: 1.5rem;
|
||||
}
|
||||
|
||||
.feature-title {
|
||||
font-size: 1.25rem;
|
||||
font-weight: 600;
|
||||
color: var(--text);
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
.feature-description {
|
||||
color: var(--text-muted);
|
||||
line-height: 1.6;
|
||||
}
|
||||
|
||||
.cta-section {
|
||||
background: linear-gradient(135deg, #f1f5f9 0%, #e2e8f0 100%);
|
||||
text-align: center;
|
||||
padding: 5rem 0;
|
||||
}
|
||||
|
||||
footer {
|
||||
background: var(--text);
|
||||
color: #94a3b8;
|
||||
padding: 2.5rem 0 1.5rem;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.footer-brand {
|
||||
font-size: 1.25rem;
|
||||
font-weight: 600;
|
||||
color: white;
|
||||
margin-bottom: 0.5rem;
|
||||
}
|
||||
|
||||
.footer-text {
|
||||
font-size: 0.9rem;
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.hero {
|
||||
min-height: 70vh;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.stats {
|
||||
margin-top: -2rem;
|
||||
padding: 2rem 0;
|
||||
}
|
||||
|
||||
.btn-modern {
|
||||
width: 100%;
|
||||
justify-content: center;
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
.section-modern {
|
||||
padding: 3rem 0;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
@@ -53,8 +318,8 @@ $mensajeMantenimiento = $config['mensaje_mantenimiento'] ?? 'El sistema se encue
|
||||
<!-- NAVBAR -->
|
||||
<nav class="navbar navbar-expand-lg">
|
||||
<div class="container">
|
||||
<a class="navbar-brand d-flex align-items-center" href="#">
|
||||
<img src="/IMPORTADORES/public/<?= htmlspecialchars($logo) ?>" alt="Logo" class="logo-siih">
|
||||
<a class="navbar-brand" href="#">
|
||||
<i class="fas fa-shipping-fast me-2"></i><?= htmlspecialchars($siglas) ?>
|
||||
</a>
|
||||
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#nav">
|
||||
<span class="navbar-toggler-icon"></span>
|
||||
@@ -68,68 +333,191 @@ $mensajeMantenimiento = $config['mensaje_mantenimiento'] ?? 'El sistema se encue
|
||||
<a class="nav-link" href="/IMPORTADORES/registro">Registro</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="/IMPORTADORES/login">Inicio sesión</a>
|
||||
<a class="nav-link" href="/IMPORTADORES/login">Iniciar Sesión</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
|
||||
<!-- ALERTA DE MANTENIMIENTO -->
|
||||
<!-- MANTENIMIENTO -->
|
||||
<?php if ($mantenimiento): ?>
|
||||
<div id="bloqueo-mantenimiento" style="
|
||||
position: fixed;
|
||||
top: 0; left: 0;
|
||||
width: 100%; height: 100%;
|
||||
background: rgba(255, 255, 255, 0.95);
|
||||
z-index: 9999;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
flex-direction: column;
|
||||
text-align: center;
|
||||
font-family: sans-serif;
|
||||
">
|
||||
<i class="fas fa-tools fa-3x mb-3 text-danger"></i>
|
||||
<h2>Sistema en Mantenimiento</h2>
|
||||
<p><?= htmlspecialchars($mensajeMantenimiento) ?></p>
|
||||
<div style="position: fixed; top: 0; left: 0; width: 100%; height: 100%; background: rgba(255, 255, 255, 0.98); z-index: 9999; display: flex; align-items: center; justify-content: center; flex-direction: column; text-align: center;">
|
||||
<i class="fas fa-tools fa-4x mb-4" style="color: var(--accent);"></i>
|
||||
<h2 style="color: var(--text); margin-bottom: 1rem;">Sistema en Mantenimiento</h2>
|
||||
<p style="color: var(--text-muted); max-width: 500px;"><?= htmlspecialchars($mensajeMantenimiento) ?></p>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
|
||||
<!-- HERO -->
|
||||
<section class="hero">
|
||||
<div class="container">
|
||||
<h1 class="display-5 mb-3"><?= htmlspecialchars($nombre) ?></h1>
|
||||
<p class="lead">Una plataforma confiable para gestionar el registro y control de importadores de hidrocarburos en México.</p>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- FEATURES -->
|
||||
<section class="features text-center">
|
||||
<div class="container">
|
||||
<div class="row">
|
||||
<div class="col-md-4">
|
||||
<i class="fas fa-file-signature"></i>
|
||||
<h5 class="mt-3">Registro en línea</h5>
|
||||
<p>Los importadores pueden enviar su solicitud y documentación de forma digital.</p>
|
||||
<div class="row align-items-center">
|
||||
<div class="col-lg-6">
|
||||
<div class="hero-content">
|
||||
<h1>Gestión Moderna de <span style="color: #10b981;">Importaciones</span></h1>
|
||||
<p class="lead">
|
||||
Simplifica tus procesos aduaneros con nuestra plataforma integral.
|
||||
Desde pedimentos hasta seguimiento, todo en un solo lugar.
|
||||
</p>
|
||||
<div class="d-flex flex-column flex-md-row gap-3">
|
||||
<a href="/IMPORTADORES/registro" class="btn-modern btn-primary-modern">
|
||||
<i class="fas fa-rocket"></i>
|
||||
Comenzar Gratis
|
||||
</a>
|
||||
<a href="#features" class="btn-modern btn-outline-modern">
|
||||
<i class="fas fa-play"></i>
|
||||
Ver Demo
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-4">
|
||||
<i class="fas fa-user-shield"></i>
|
||||
<h5 class="mt-3">Autorización segura</h5>
|
||||
<p>Las agencias aduanales validan manualmente cada solicitud antes de otorgar acceso.</p>
|
||||
</div>
|
||||
<div class="col-md-4">
|
||||
<i class="fas fa-database"></i>
|
||||
<h5 class="mt-3">Información centralizada</h5>
|
||||
<p>Todas las solicitudes y datos se almacenan de forma segura y organizada.</p>
|
||||
<div class="col-lg-6 d-none d-lg-block text-center">
|
||||
<i class="fas fa-chart-line" style="font-size: 15rem; color: rgba(255,255,255,0.1);"></i>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- STATS -->
|
||||
<div class="container">
|
||||
<div class="stats">
|
||||
<div class="row">
|
||||
<div class="col-6 col-md-3">
|
||||
<div class="stat-item">
|
||||
<span class="stat-number">1,200+</span>
|
||||
<div class="stat-label">Importadores</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-6 col-md-3">
|
||||
<div class="stat-item">
|
||||
<span class="stat-number">25K+</span>
|
||||
<div class="stat-label">Trámites</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-6 col-md-3">
|
||||
<div class="stat-item">
|
||||
<span class="stat-number">98%</span>
|
||||
<div class="stat-label">Satisfacción</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-6 col-md-3">
|
||||
<div class="stat-item">
|
||||
<span class="stat-number">24/7</span>
|
||||
<div class="stat-label">Soporte</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- FEATURES -->
|
||||
<section class="section-modern" id="features">
|
||||
<div class="container">
|
||||
<h2 class="section-title">Todo lo que necesitas</h2>
|
||||
<p class="section-subtitle">
|
||||
Herramientas profesionales diseñadas para optimizar cada etapa del proceso de importación
|
||||
</p>
|
||||
|
||||
<div class="row g-4">
|
||||
<div class="col-lg-4 col-md-6">
|
||||
<div class="feature-card">
|
||||
<div class="feature-icon">
|
||||
<i class="fas fa-file-invoice"></i>
|
||||
</div>
|
||||
<h5 class="feature-title">Gestión de Pedimentos</h5>
|
||||
<p class="feature-description">
|
||||
Crea, gestiona y da seguimiento a tus pedimentos de importación con validación automática de datos.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-lg-4 col-md-6">
|
||||
<div class="feature-card">
|
||||
<div class="feature-icon">
|
||||
<i class="fas fa-shield-check"></i>
|
||||
</div>
|
||||
<h5 class="feature-title">Cumplimiento Automatizado</h5>
|
||||
<p class="feature-description">
|
||||
Mantente al día con regulaciones aduaneras y recibe alertas sobre cambios normativos relevantes.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-lg-4 col-md-6">
|
||||
<div class="feature-card">
|
||||
<div class="feature-icon">
|
||||
<i class="fas fa-analytics"></i>
|
||||
</div>
|
||||
<h5 class="feature-title">Reportes Inteligentes</h5>
|
||||
<p class="feature-description">
|
||||
Analiza tus operaciones con dashboards interactivos y reportes personalizables en tiempo real.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-lg-4 col-md-6">
|
||||
<div class="feature-card">
|
||||
<div class="feature-icon">
|
||||
<i class="fas fa-users"></i>
|
||||
</div>
|
||||
<h5 class="feature-title">Colaboración</h5>
|
||||
<p class="feature-description">
|
||||
Conecta con agentes aduanales, transportistas y proveedores en un ecosistema colaborativo.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-lg-4 col-md-6">
|
||||
<div class="feature-card">
|
||||
<div class="feature-icon">
|
||||
<i class="fas fa-mobile-alt"></i>
|
||||
</div>
|
||||
<h5 class="feature-title">Acceso Universal</h5>
|
||||
<p class="feature-description">
|
||||
Accede desde cualquier dispositivo con nuestra aplicación web responsiva y segura.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-lg-4 col-md-6">
|
||||
<div class="feature-card">
|
||||
<div class="feature-icon">
|
||||
<i class="fas fa-lock"></i>
|
||||
</div>
|
||||
<h5 class="feature-title">Seguridad Total</h5>
|
||||
<p class="feature-description">
|
||||
Protección enterprise con encriptación avanzada, backups automáticos y certificaciones de seguridad.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- CTA -->
|
||||
<section class="cta-section">
|
||||
<div class="container">
|
||||
<h2 class="section-title" style="margin-bottom: 1rem;">¿Listo para optimizar tus importaciones?</h2>
|
||||
<p class="section-subtitle" style="margin-bottom: 3rem;">
|
||||
Únete a más de 1,200 importadores que ya confían en nuestra plataforma
|
||||
</p>
|
||||
<a href="/IMPORTADORES/registro" class="btn-modern btn-primary-modern">
|
||||
<i class="fas fa-arrow-right"></i>
|
||||
Comenzar Ahora
|
||||
</a>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- FOOTER -->
|
||||
<footer class="fixed-bottom">
|
||||
© <?= date('Y') ?> <?= htmlspecialchars($siglas) ?> · <?= htmlspecialchars($nombre) ?>
|
||||
<footer>
|
||||
<div class="container">
|
||||
<div class="footer-brand"><?= htmlspecialchars($siglas) ?></div>
|
||||
<div class="footer-text">
|
||||
© <?= date('Y') ?> Sistema de Gestión de Importaciones<br>
|
||||
Optimizando el comercio internacional
|
||||
</div>
|
||||
</div>
|
||||
</footer>
|
||||
|
||||
</body>
|
||||
|
||||
@@ -1,15 +1,22 @@
|
||||
<?php
|
||||
$nombre = $config['nombre_plataforma'] ?? 'Sistema Integral para Importadores de Hidrocarburos';
|
||||
$siglas = $config['siglas'] ?? 'SIIH';
|
||||
$logo = $config['logo_url'] ?? 'assets/img/logo_siih.png';
|
||||
$color1 = explode(',', $config['colores_primarios'] ?? '#003366,#0055A5')[0];
|
||||
$color2 = explode(',', $config['colores_primarios'] ?? '#003366,#0055A5')[1];
|
||||
$mantenimiento = $config['modo_mantenimiento'] ?? 0;
|
||||
$mensajeMantenimiento = $config['mensaje_mantenimiento'] ?? 'El sistema se encuentra en mantenimiento temporal. Por favor, vuelve más tarde.';
|
||||
require_once __DIR__ . '/../../config/database.php';
|
||||
$conn = getConnection();
|
||||
|
||||
if (!isset($_SESSION['recuperacion_autorizada'])) {
|
||||
header('Location: /IMPORTADORES/login');
|
||||
exit;
|
||||
$sql = "SELECT TOP 1 * FROM configuracion_sistema";
|
||||
$stmt = sqlsrv_query($conn, $sql);
|
||||
$config = sqlsrv_fetch_array($stmt, SQLSRV_FETCH_ASSOC);
|
||||
|
||||
// Variables con valores por defecto - Branding moderno
|
||||
$nombre = $config['nombre_plataforma'] ?? 'Sistema de Gestión de Importaciones';
|
||||
$siglas = $config['siglas'] ?? 'AduanaSoft';
|
||||
$logo = $config['logo_url'] ?? 'assets/img/logo_aduanasoft.png';
|
||||
$color1 = explode(',', $config['colores_primarios'] ?? '#0f172a,#334155')[0];
|
||||
$color2 = explode(',', $config['colores_primarios'] ?? '#0f172a,#334155')[1];
|
||||
|
||||
// Verificar que el usuario tenga permiso para cambiar contraseña
|
||||
if (!isset($_SESSION['codigo_verificado']) || !isset($_SESSION['email_recuperacion'])) {
|
||||
header('Location: /IMPORTADORES/login/recuperar');
|
||||
exit;
|
||||
}
|
||||
?>
|
||||
|
||||
@@ -17,31 +24,304 @@ if (!isset($_SESSION['recuperacion_autorizada'])) {
|
||||
<html lang="es">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title><?= htmlspecialchars($siglas) ?> | Recuperación de Contraseña</title>
|
||||
<title><?= htmlspecialchars($siglas) ?> | Nueva Contraseña</title>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<meta name="description" content="Establece tu nueva contraseña para acceder a tu cuenta">
|
||||
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css" rel="stylesheet">
|
||||
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/js/bootstrap.bundle.min.js"></script>
|
||||
<link href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.0/css/all.min.css" rel="stylesheet">
|
||||
<link href="https://fonts.googleapis.com/css2?family=Poppins:wght@300;400;500;600;700&display=swap" rel="stylesheet">
|
||||
<style>
|
||||
body { background-color: #f4f6f9; font-family: 'Segoe UI', sans-serif; position: relative; height: 100vh; }
|
||||
.navbar { background-color: #fff; }
|
||||
.navbar .nav-link, .navbar-brand { color: <?= $color1 ?> !important; font-weight: 500; }
|
||||
.logo-siih { height: 90px; margin-right: 10px; }
|
||||
.login-container { max-width: 420px; margin: 80px auto; background: white; padding: 40px; border-radius: 12px; box-shadow: 0 10px 25px rgba(0, 0, 0, 0.08); z-index: 10; position: relative; }
|
||||
.form-control { border-radius: 6px; }
|
||||
footer { background-color: #e9ecef; padding: 20px; text-align: center; font-size: 14px; color: #666; margin-top: 60px; }
|
||||
/* Contenedor de fondo semitransparente para las vistas */
|
||||
.overlay { position: absolute; top: 0; left: 0; width: 100%; height: 100%; background: rgba(0, 0, 0, 0.5); z-index: 5; }
|
||||
:root {
|
||||
--primary: <?= $color1 ?>;
|
||||
--secondary: <?= $color2 ?>;
|
||||
--accent: #10b981;
|
||||
--surface: #ffffff;
|
||||
--background: #f8fafc;
|
||||
--text: #1e293b;
|
||||
--text-muted: #64748b;
|
||||
--border: #e2e8f0;
|
||||
--error: #ef4444;
|
||||
--warning: #f59e0b;
|
||||
}
|
||||
|
||||
* {
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
body {
|
||||
background: linear-gradient(135deg, var(--background) 0%, #e2e8f0 100%);
|
||||
font-family: 'Poppins', sans-serif;
|
||||
color: var(--text);
|
||||
line-height: 1.6;
|
||||
margin: 0;
|
||||
min-height: 100vh;
|
||||
}
|
||||
|
||||
.navbar {
|
||||
background: rgba(255, 255, 255, 0.95);
|
||||
backdrop-filter: blur(20px);
|
||||
border-bottom: 1px solid var(--border);
|
||||
padding: 1rem 0;
|
||||
}
|
||||
|
||||
.navbar-brand {
|
||||
font-weight: 600;
|
||||
font-size: 1.25rem;
|
||||
color: var(--primary) !important;
|
||||
}
|
||||
|
||||
.nav-link {
|
||||
color: var(--text-muted) !important;
|
||||
font-weight: 500;
|
||||
padding: 0.5rem 1rem !important;
|
||||
border-radius: 8px;
|
||||
transition: all 0.2s ease;
|
||||
}
|
||||
|
||||
.nav-link:hover {
|
||||
color: var(--primary) !important;
|
||||
background-color: #f1f5f9;
|
||||
}
|
||||
|
||||
.password-container {
|
||||
max-width: 550px;
|
||||
margin: 3rem auto;
|
||||
background: var(--surface);
|
||||
border-radius: 20px;
|
||||
box-shadow: 0 20px 40px rgba(0, 0, 0, 0.08);
|
||||
padding: 3rem;
|
||||
border: 1px solid var(--border);
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.password-container::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
height: 4px;
|
||||
background: linear-gradient(90deg, var(--accent), var(--primary));
|
||||
}
|
||||
|
||||
.password-header {
|
||||
text-align: center;
|
||||
margin-bottom: 2.5rem;
|
||||
}
|
||||
|
||||
.password-icon {
|
||||
width: 80px;
|
||||
height: 80px;
|
||||
background: linear-gradient(135deg, var(--accent), #059669);
|
||||
border-radius: 50%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
margin: 0 auto 1.5rem;
|
||||
color: white;
|
||||
font-size: 2rem;
|
||||
}
|
||||
|
||||
.password-title {
|
||||
font-size: 1.75rem;
|
||||
font-weight: 700;
|
||||
color: var(--text);
|
||||
margin-bottom: 0.5rem;
|
||||
}
|
||||
|
||||
.password-subtitle {
|
||||
color: var(--text-muted);
|
||||
font-size: 0.95rem;
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
.form-label {
|
||||
font-weight: 500;
|
||||
color: var(--text);
|
||||
margin-bottom: 0.5rem;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.5rem;
|
||||
}
|
||||
|
||||
.form-control {
|
||||
border: 2px solid var(--border);
|
||||
border-radius: 12px;
|
||||
padding: 0.75rem 1rem;
|
||||
font-size: 0.95rem;
|
||||
transition: all 0.2s ease;
|
||||
background: var(--surface);
|
||||
}
|
||||
|
||||
.form-control:focus {
|
||||
border-color: var(--accent);
|
||||
box-shadow: 0 0 0 3px rgba(16, 185, 129, 0.1);
|
||||
background: var(--surface);
|
||||
}
|
||||
|
||||
.password-input-group {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.password-toggle {
|
||||
position: absolute;
|
||||
right: 12px;
|
||||
top: 50%;
|
||||
transform: translateY(-50%);
|
||||
background: none;
|
||||
border: none;
|
||||
color: var(--text-muted);
|
||||
cursor: pointer;
|
||||
padding: 0.25rem;
|
||||
border-radius: 4px;
|
||||
transition: color 0.2s ease;
|
||||
}
|
||||
|
||||
.password-toggle:hover {
|
||||
color: var(--accent);
|
||||
}
|
||||
|
||||
.password-strength {
|
||||
margin-top: 0.5rem;
|
||||
}
|
||||
|
||||
.strength-bar {
|
||||
height: 4px;
|
||||
background: var(--border);
|
||||
border-radius: 2px;
|
||||
overflow: hidden;
|
||||
margin-bottom: 0.5rem;
|
||||
}
|
||||
|
||||
.strength-fill {
|
||||
height: 100%;
|
||||
transition: all 0.3s ease;
|
||||
border-radius: 2px;
|
||||
}
|
||||
|
||||
.strength-weak { background: #ef4444; width: 25%; }
|
||||
.strength-medium { background: var(--warning); width: 60%; }
|
||||
.strength-strong { background: var(--accent); width: 100%; }
|
||||
|
||||
.strength-text {
|
||||
font-size: 0.85rem;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.password-requirements {
|
||||
background: linear-gradient(135deg, #f8fafc, #f1f5f9);
|
||||
border: 1px solid var(--border);
|
||||
border-radius: 12px;
|
||||
padding: 1rem;
|
||||
margin: 1rem 0;
|
||||
}
|
||||
|
||||
.requirement {
|
||||
font-size: 0.85rem;
|
||||
color: var(--text-muted);
|
||||
margin-bottom: 0.25rem;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.5rem;
|
||||
}
|
||||
|
||||
.requirement.met {
|
||||
color: var(--accent);
|
||||
}
|
||||
|
||||
.requirement i {
|
||||
width: 16px;
|
||||
font-size: 0.75rem;
|
||||
}
|
||||
|
||||
.btn-modern {
|
||||
padding: 12px 28px;
|
||||
border-radius: 12px;
|
||||
font-weight: 600;
|
||||
text-decoration: none;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 8px;
|
||||
transition: all 0.3s ease;
|
||||
border: none;
|
||||
cursor: pointer;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.btn-primary-modern {
|
||||
background: linear-gradient(135deg, var(--accent), #059669);
|
||||
color: white;
|
||||
box-shadow: 0 4px 15px rgba(16, 185, 129, 0.3);
|
||||
}
|
||||
|
||||
.btn-primary-modern:hover {
|
||||
transform: translateY(-2px);
|
||||
box-shadow: 0 8px 25px rgba(16, 185, 129, 0.4);
|
||||
color: white;
|
||||
}
|
||||
|
||||
.btn-primary-modern:disabled {
|
||||
opacity: 0.6;
|
||||
transform: none;
|
||||
box-shadow: none;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
.alert-modern {
|
||||
border-radius: 12px;
|
||||
border: none;
|
||||
padding: 1rem 1.25rem;
|
||||
margin-bottom: 1.5rem;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.alert-danger {
|
||||
background: linear-gradient(135deg, #fef2f2, #fee2e2);
|
||||
color: #dc2626;
|
||||
border: 1px solid #fecaca;
|
||||
}
|
||||
|
||||
.alert-success {
|
||||
background: linear-gradient(135deg, #f0fdf4, #dcfce7);
|
||||
color: #16a34a;
|
||||
border: 1px solid #bbf7d0;
|
||||
}
|
||||
|
||||
footer {
|
||||
background: var(--text);
|
||||
color: #94a3b8;
|
||||
padding: 2rem 0;
|
||||
text-align: center;
|
||||
margin-top: auto;
|
||||
}
|
||||
|
||||
.footer-text {
|
||||
font-size: 0.9rem;
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.password-container {
|
||||
margin: 2rem 1rem;
|
||||
padding: 2rem 1.5rem;
|
||||
}
|
||||
|
||||
.password-title {
|
||||
font-size: 1.5rem;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<!-- Navbar institucional -->
|
||||
<!-- NAVBAR -->
|
||||
<nav class="navbar navbar-expand-lg">
|
||||
<div class="container">
|
||||
<a class="navbar-brand d-flex align-items-center text-dark" href="#">
|
||||
<img src="/IMPORTADORES/public/<?= htmlspecialchars($logo) ?>" alt="Logo" class="logo-siih">
|
||||
| Inicio de Importador
|
||||
<a class="navbar-brand" href="/IMPORTADORES/">
|
||||
<i class="fas fa-shipping-fast me-2"></i><?= htmlspecialchars($siglas) ?>
|
||||
</a>
|
||||
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarNav">
|
||||
<span class="navbar-toggler-icon"></span>
|
||||
@@ -52,68 +332,291 @@ if (!isset($_SESSION['recuperacion_autorizada'])) {
|
||||
<a class="nav-link" href="/IMPORTADORES/">Inicio</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link " aria-current="page" href="/IMPORTADORES/registro">Registro</a>
|
||||
<a class="nav-link" href="/IMPORTADORES/registro">Registro</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link active" href="/IMPORTADORES/login">Inicio sesión</a>
|
||||
<a class="nav-link" href="/IMPORTADORES/login">Iniciar Sesión</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
|
||||
<?php if ($mantenimiento): ?>
|
||||
<div class="container mt-4">
|
||||
<div class="alert alert-danger text-center" role="alert">
|
||||
<i class="fas fa-triangle-exclamation me-2"></i>
|
||||
<?= htmlspecialchars($mensajeMantenimiento) ?>
|
||||
<!-- CONTENEDOR DE NUEVA CONTRASEÑA -->
|
||||
<div class="password-container">
|
||||
<div class="password-header">
|
||||
<div class="password-icon">
|
||||
<i class="fas fa-lock"></i>
|
||||
</div>
|
||||
<h1 class="password-title">Nueva Contraseña</h1>
|
||||
<p class="password-subtitle">
|
||||
Crea una contraseña segura para proteger tu cuenta de importaciones
|
||||
</p>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
|
||||
<!-- Overlay (fondo semitransparente) que cubre la pantalla -->
|
||||
<div class="overlay"></div>
|
||||
<!-- MENSAJES -->
|
||||
<?php if (isset($_SESSION['error_message'])): ?>
|
||||
<div class="alert-modern alert-danger">
|
||||
<i class="fas fa-exclamation-triangle me-2"></i>
|
||||
<?= $_SESSION['error_message'] ?>
|
||||
</div>
|
||||
<?php unset($_SESSION['error_message']); ?>
|
||||
<?php endif; ?>
|
||||
|
||||
<!-- Formulario de cambio de contraseña -->
|
||||
<div class="login-container">
|
||||
<h4 class="mb-4 text-center" style="color:<?= $color1 ?>">Cambiar contraseña</h4>
|
||||
<form id="formNueva" action="/IMPORTADORES/login/cambiarPassword" method="POST">
|
||||
<form action="/IMPORTADORES/login/actualizar_password" method="POST" id="passwordForm">
|
||||
<div class="mb-3">
|
||||
<label class="form-label">Nueva contraseña:</label>
|
||||
<input type="password" name="password" required class="form-control" minlength="6" autocomplete="new-password">
|
||||
<label for="password" class="form-label">
|
||||
<i class="fas fa-key text-muted"></i>
|
||||
Nueva contraseña
|
||||
</label>
|
||||
<div class="password-input-group">
|
||||
<input type="password" name="password" id="password" class="form-control"
|
||||
required minlength="8"
|
||||
placeholder="Tu nueva contraseña segura">
|
||||
<button type="button" class="password-toggle" onclick="togglePassword('password')">
|
||||
<i class="fas fa-eye" id="password-icon"></i>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="password-strength">
|
||||
<div class="strength-bar">
|
||||
<div class="strength-fill" id="strengthBar"></div>
|
||||
</div>
|
||||
<div class="strength-text" id="strengthText">Ingresa una contraseña</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label class="form-label">Confirmar contraseña:</label>
|
||||
<input type="password" name="confirmar" required class="form-control">
|
||||
|
||||
<div class="mb-4">
|
||||
<label for="confirm_password" class="form-label">
|
||||
<i class="fas fa-shield-alt text-muted"></i>
|
||||
Confirmar contraseña
|
||||
</label>
|
||||
<div class="password-input-group">
|
||||
<input type="password" name="confirm_password" id="confirm_password" class="form-control"
|
||||
required minlength="8"
|
||||
placeholder="Repite tu nueva contraseña">
|
||||
<button type="button" class="password-toggle" onclick="togglePassword('confirm_password')">
|
||||
<i class="fas fa-eye" id="confirm_password-icon"></i>
|
||||
</button>
|
||||
</div>
|
||||
<div id="passwordMatch" class="mt-2" style="font-size: 0.85rem;"></div>
|
||||
</div>
|
||||
<button type="submit" class="btn btn-warning w-100">Actualizar contraseña</button>
|
||||
|
||||
<div class="password-requirements">
|
||||
<div class="mb-2" style="font-weight: 600; color: var(--text);">
|
||||
<i class="fas fa-info-circle me-2"></i>Requisitos de seguridad:
|
||||
</div>
|
||||
<div class="requirement" id="req-length">
|
||||
<i class="fas fa-circle"></i>
|
||||
Mínimo 8 caracteres
|
||||
</div>
|
||||
<div class="requirement" id="req-uppercase">
|
||||
<i class="fas fa-circle"></i>
|
||||
Al menos una mayúscula (A-Z)
|
||||
</div>
|
||||
<div class="requirement" id="req-lowercase">
|
||||
<i class="fas fa-circle"></i>
|
||||
Al menos una minúscula (a-z)
|
||||
</div>
|
||||
<div class="requirement" id="req-number">
|
||||
<i class="fas fa-circle"></i>
|
||||
Al menos un número (0-9)
|
||||
</div>
|
||||
<div class="requirement" id="req-special">
|
||||
<i class="fas fa-circle"></i>
|
||||
Al menos un carácter especial (!@#$%^&*)
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<button type="submit" class="btn-modern btn-primary-modern" id="submitBtn" disabled>
|
||||
<i class="fas fa-save"></i>
|
||||
Guardar Nueva Contraseña
|
||||
</button>
|
||||
</form>
|
||||
|
||||
<?php if (isset($_SESSION['cambio_error'])): ?>
|
||||
<div class="text-danger mt-3"><?= $_SESSION['cambio_error'] ?></div>
|
||||
<?php unset($_SESSION['cambio_error']); ?>
|
||||
<?php endif; ?>
|
||||
|
||||
<?php if (isset($_SESSION['cambio_exito'])): ?>
|
||||
<div class="text-success mt-3"><?= $_SESSION['cambio_exito'] ?></div>
|
||||
<?php unset($_SESSION['cambio_exito']); ?>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
|
||||
<!-- FOOTER -->
|
||||
<footer class="fixed-bottom">
|
||||
© <?= date('Y') ?> <?= htmlspecialchars($siglas) ?> · <?= htmlspecialchars($nombre) ?>
|
||||
<footer>
|
||||
<div class="container text-center">
|
||||
<div class="footer-text">
|
||||
© <?= date('Y') ?> <?= htmlspecialchars($siglas) ?> - <?= htmlspecialchars($nombre) ?><br>
|
||||
Seguridad avanzada para tu cuenta
|
||||
</div>
|
||||
</div>
|
||||
</footer>
|
||||
|
||||
<script>
|
||||
document.getElementById('formNueva').addEventListener('submit', function(e) {
|
||||
const pass = this.password.value;
|
||||
const confirm = this.confirmar.value;
|
||||
|
||||
if (pass !== confirm) {
|
||||
e.preventDefault();
|
||||
alert('❌ Las contraseñas no coinciden.');
|
||||
function togglePassword(inputId) {
|
||||
const input = document.getElementById(inputId);
|
||||
const icon = document.getElementById(inputId + '-icon');
|
||||
|
||||
if (input.type === 'password') {
|
||||
input.type = 'text';
|
||||
icon.classList.remove('fa-eye');
|
||||
icon.classList.add('fa-eye-slash');
|
||||
} else {
|
||||
input.type = 'password';
|
||||
icon.classList.remove('fa-eye-slash');
|
||||
icon.classList.add('fa-eye');
|
||||
}
|
||||
}
|
||||
|
||||
function checkPasswordStrength(password) {
|
||||
const requirements = {
|
||||
length: password.length >= 8,
|
||||
uppercase: /[A-Z]/.test(password),
|
||||
lowercase: /[a-z]/.test(password),
|
||||
number: /[0-9]/.test(password),
|
||||
special: /[!@#$%^&*()_+\-=\[\]{};':"\\|,.<>\/?]/.test(password)
|
||||
};
|
||||
|
||||
// Actualizar indicadores visuales
|
||||
Object.keys(requirements).forEach(req => {
|
||||
const element = document.getElementById(`req-${req}`);
|
||||
const icon = element.querySelector('i');
|
||||
|
||||
if (requirements[req]) {
|
||||
element.classList.add('met');
|
||||
icon.classList.remove('fa-circle');
|
||||
icon.classList.add('fa-check-circle');
|
||||
} else {
|
||||
element.classList.remove('met');
|
||||
icon.classList.remove('fa-check-circle');
|
||||
icon.classList.add('fa-circle');
|
||||
}
|
||||
});
|
||||
|
||||
// Calcular fuerza
|
||||
const metCount = Object.values(requirements).filter(Boolean).length;
|
||||
const strengthBar = document.getElementById('strengthBar');
|
||||
const strengthText = document.getElementById('strengthText');
|
||||
|
||||
strengthBar.className = 'strength-fill';
|
||||
|
||||
if (metCount < 3) {
|
||||
strengthBar.classList.add('strength-weak');
|
||||
strengthText.textContent = 'Contraseña débil';
|
||||
strengthText.style.color = '#ef4444';
|
||||
} else if (metCount < 5) {
|
||||
strengthBar.classList.add('strength-medium');
|
||||
strengthText.textContent = 'Contraseña moderada';
|
||||
strengthText.style.color = '#f59e0b';
|
||||
} else {
|
||||
strengthBar.classList.add('strength-strong');
|
||||
strengthText.textContent = 'Contraseña fuerte';
|
||||
strengthText.style.color = '#10b981';
|
||||
}
|
||||
|
||||
return metCount >= 4; // Requiere al menos 4 de 5 criterios
|
||||
}
|
||||
|
||||
function checkPasswordMatch() {
|
||||
const password = document.getElementById('password').value;
|
||||
const confirmPassword = document.getElementById('confirm_password').value;
|
||||
const matchDiv = document.getElementById('passwordMatch');
|
||||
|
||||
if (confirmPassword === '') {
|
||||
matchDiv.textContent = '';
|
||||
return false;
|
||||
}
|
||||
|
||||
if (password === confirmPassword) {
|
||||
matchDiv.innerHTML = '<i class="fas fa-check-circle me-1" style="color: #10b981;"></i>Las contraseñas coinciden';
|
||||
matchDiv.style.color = '#10b981';
|
||||
return true;
|
||||
} else {
|
||||
matchDiv.innerHTML = '<i class="fas fa-times-circle me-1" style="color: #ef4444;"></i>Las contraseñas no coinciden';
|
||||
matchDiv.style.color = '#ef4444';
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
function validateForm() {
|
||||
const password = document.getElementById('password').value;
|
||||
const isStrong = checkPasswordStrength(password);
|
||||
const isMatching = checkPasswordMatch();
|
||||
|
||||
const submitBtn = document.getElementById('submitBtn');
|
||||
submitBtn.disabled = !(isStrong && isMatching && password.length >= 8);
|
||||
}
|
||||
|
||||
// Event listeners
|
||||
document.getElementById('password').addEventListener('input', validateForm);
|
||||
document.getElementById('confirm_password').addEventListener('input', validateForm);
|
||||
|
||||
// Validación del formulario
|
||||
document.getElementById('passwordForm').addEventListener('submit', function(e) {
|
||||
e.preventDefault();
|
||||
|
||||
const password = document.getElementById('password').value;
|
||||
const confirmPassword = document.getElementById('confirm_password').value;
|
||||
const submitBtn = document.getElementById('submitBtn');
|
||||
|
||||
if (password !== confirmPassword) {
|
||||
showAlert('error', 'Las contraseñas no coinciden');
|
||||
return;
|
||||
}
|
||||
|
||||
if (!checkPasswordStrength(password)) {
|
||||
showAlert('error', 'La contraseña no cumple con los requisitos de seguridad');
|
||||
return;
|
||||
}
|
||||
|
||||
// Deshabilitar botón y mostrar loading
|
||||
submitBtn.disabled = true;
|
||||
submitBtn.innerHTML = '<i class="fas fa-spinner fa-spin"></i> Guardando...';
|
||||
|
||||
// Enviar formulario normalmente (el método devuelve HTML)
|
||||
const formData = new FormData();
|
||||
formData.append('password', password);
|
||||
formData.append('confirmar', confirmPassword);
|
||||
|
||||
fetch('/IMPORTADORES/login/actualizar_password', {
|
||||
method: 'POST',
|
||||
body: formData
|
||||
})
|
||||
.then(response => response.text())
|
||||
.then(html => {
|
||||
// Verificar si contiene mensaje de éxito
|
||||
if (html.includes('✅ ¡Contraseña actualizada!')) {
|
||||
// Mostrar la respuesta HTML del servidor
|
||||
document.body.innerHTML = html;
|
||||
} else {
|
||||
// Es un mensaje de error
|
||||
showAlert('error', html.replace(/<[^>]*>/g, '').trim());
|
||||
submitBtn.disabled = false;
|
||||
submitBtn.innerHTML = '<i class="fas fa-save"></i> Guardar Nueva Contraseña';
|
||||
}
|
||||
})
|
||||
.catch(error => {
|
||||
console.error('Error:', error);
|
||||
showAlert('error', 'Error de conexión. Intenta nuevamente.');
|
||||
submitBtn.disabled = false;
|
||||
submitBtn.innerHTML = '<i class="fas fa-save"></i> Guardar Nueva Contraseña';
|
||||
});
|
||||
});
|
||||
|
||||
function showAlert(type, message) {
|
||||
// Remover alertas anteriores
|
||||
const existingAlerts = document.querySelectorAll('.alert-modern');
|
||||
existingAlerts.forEach(alert => alert.remove());
|
||||
|
||||
// Crear nueva alerta
|
||||
const alertDiv = document.createElement('div');
|
||||
alertDiv.className = `alert-modern alert-${type === 'success' ? 'success' : 'danger'}`;
|
||||
alertDiv.innerHTML = `
|
||||
<i class="fas fa-${type === 'success' ? 'check-circle' : 'exclamation-triangle'} me-2"></i>
|
||||
${message}
|
||||
`;
|
||||
|
||||
// Insertar antes del formulario
|
||||
const form = document.getElementById('passwordForm');
|
||||
form.parentNode.insertBefore(alertDiv, form);
|
||||
}
|
||||
|
||||
// Focus automático
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
document.getElementById('password').focus();
|
||||
});
|
||||
</script>
|
||||
|
||||
|
||||
@@ -1,44 +1,273 @@
|
||||
<?php
|
||||
$nombre = $config['nombre_plataforma'] ?? 'Sistema Integral para Importadores de Hidrocarburos';
|
||||
$siglas = $config['siglas'] ?? 'SIIH';
|
||||
$logo = $config['logo_url'] ?? 'assets/img/logo_siih.png';
|
||||
$color1 = explode(',', $config['colores_primarios'] ?? '#003366,#0055A5')[0];
|
||||
$color2 = explode(',', $config['colores_primarios'] ?? '#003366,#0055A5')[1];
|
||||
$mantenimiento = $config['modo_mantenimiento'] ?? 0;
|
||||
$mensajeMantenimiento = $config['mensaje_mantenimiento'] ?? 'El sistema se encuentra en mantenimiento temporal. Por favor, vuelve más tarde.';
|
||||
require_once __DIR__ . '/../../config/database.php';
|
||||
$conn = getConnection();
|
||||
|
||||
$sql = "SELECT TOP 1 * FROM configuracion_sistema";
|
||||
$stmt = sqlsrv_query($conn, $sql);
|
||||
$config = sqlsrv_fetch_array($stmt, SQLSRV_FETCH_ASSOC);
|
||||
|
||||
// Variables con valores por defecto - Branding moderno
|
||||
$nombre = $config['nombre_plataforma'] ?? 'Sistema de Gestión de Importaciones';
|
||||
$siglas = $config['siglas'] ?? 'AduanaSoft';
|
||||
$logo = $config['logo_url'] ?? 'assets/img/logo_aduanasoft.png';
|
||||
$color1 = explode(',', $config['colores_primarios'] ?? '#0f172a,#334155')[0];
|
||||
$color2 = explode(',', $config['colores_primarios'] ?? '#0f172a,#334155')[1];
|
||||
|
||||
// Obtener tipo de confirmación y mensaje
|
||||
$tipo = $_GET['tipo'] ?? 'success';
|
||||
$mensaje = $_GET['mensaje'] ?? 'Operación completada exitosamente';
|
||||
?>
|
||||
|
||||
<!DOCTYPE html>
|
||||
<html lang="es">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title><?= htmlspecialchars($siglas) ?> | Recuperación de Contraseña</title>
|
||||
<title><?= htmlspecialchars($siglas) ?> | Confirmación</title>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<meta name="description" content="Confirmación de operación en tu cuenta de importaciones">
|
||||
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css" rel="stylesheet">
|
||||
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/js/bootstrap.bundle.min.js"></script>
|
||||
<link href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.0/css/all.min.css" rel="stylesheet">
|
||||
<link href="https://fonts.googleapis.com/css2?family=Poppins:wght@300;400;500;600;700&display=swap" rel="stylesheet">
|
||||
<style>
|
||||
body { background-color: #f4f6f9; font-family: 'Segoe UI', sans-serif; position: relative; min-height: 100vh; }
|
||||
.navbar { background-color: #fff; }
|
||||
.navbar .nav-link, .navbar-brand { color: <?= $color1 ?> !important; font-weight: 500; }
|
||||
.logo-siih { height: 90px; margin-right: 10px; }
|
||||
.login-container { max-width: 420px; margin: 80px auto; background: white; padding: 40px; border-radius: 12px; box-shadow: 0 10px 25px rgba(0, 0, 0, 0.08); z-index: 10; position: relative; }
|
||||
.form-control { border-radius: 6px; }
|
||||
footer { background-color: #e9ecef; padding: 20px; text-align: center; font-size: 14px; color: #666; margin-top: 60px; }
|
||||
/* Contenedor de fondo semitransparente para las vistas */
|
||||
.overlay { position: fixed; top: 0; left: 0; width: 100%; height: 100%; background: rgba(0, 0, 0, 0.5); z-index: 5; }
|
||||
#mensaje { transition: opacity 0.3s ease; }
|
||||
@media (max-height: 600px) { footer { position: static !important; } }
|
||||
:root {
|
||||
--primary: <?= $color1 ?>;
|
||||
--secondary: <?= $color2 ?>;
|
||||
--accent: #10b981;
|
||||
--surface: #ffffff;
|
||||
--background: #f8fafc;
|
||||
--text: #1e293b;
|
||||
--text-muted: #64748b;
|
||||
--border: #e2e8f0;
|
||||
--success: #16a34a;
|
||||
--error: #ef4444;
|
||||
--warning: #f59e0b;
|
||||
}
|
||||
|
||||
* {
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
body {
|
||||
background: linear-gradient(135deg, var(--background) 0%, #e2e8f0 100%);
|
||||
font-family: 'Poppins', sans-serif;
|
||||
color: var(--text);
|
||||
line-height: 1.6;
|
||||
margin: 0;
|
||||
min-height: 100vh;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.navbar {
|
||||
background: rgba(255, 255, 255, 0.95);
|
||||
backdrop-filter: blur(20px);
|
||||
border-bottom: 1px solid var(--border);
|
||||
padding: 1rem 0;
|
||||
}
|
||||
|
||||
.navbar-brand {
|
||||
font-weight: 600;
|
||||
font-size: 1.25rem;
|
||||
color: var(--primary) !important;
|
||||
}
|
||||
|
||||
.nav-link {
|
||||
color: var(--text-muted) !important;
|
||||
font-weight: 500;
|
||||
padding: 0.5rem 1rem !important;
|
||||
border-radius: 8px;
|
||||
transition: all 0.2s ease;
|
||||
}
|
||||
|
||||
.nav-link:hover {
|
||||
color: var(--primary) !important;
|
||||
background-color: #f1f5f9;
|
||||
}
|
||||
|
||||
.confirmation-container {
|
||||
max-width: 500px;
|
||||
margin: 3rem auto;
|
||||
background: var(--surface);
|
||||
border-radius: 20px;
|
||||
box-shadow: 0 20px 40px rgba(0, 0, 0, 0.08);
|
||||
padding: 3rem;
|
||||
border: 1px solid var(--border);
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.confirmation-container::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
height: 4px;
|
||||
background: linear-gradient(90deg, var(--accent), var(--primary));
|
||||
}
|
||||
|
||||
.confirmation-icon {
|
||||
width: 100px;
|
||||
height: 100px;
|
||||
border-radius: 50%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
margin: 0 auto 2rem;
|
||||
color: white;
|
||||
font-size: 2.5rem;
|
||||
position: relative;
|
||||
animation: pulse 2s infinite;
|
||||
}
|
||||
|
||||
.icon-success {
|
||||
background: linear-gradient(135deg, var(--success), #059669);
|
||||
}
|
||||
|
||||
.icon-error {
|
||||
background: linear-gradient(135deg, var(--error), #dc2626);
|
||||
}
|
||||
|
||||
.icon-warning {
|
||||
background: linear-gradient(135deg, var(--warning), #d97706);
|
||||
}
|
||||
|
||||
.icon-info {
|
||||
background: linear-gradient(135deg, #3b82f6, #1d4ed8);
|
||||
}
|
||||
|
||||
@keyframes pulse {
|
||||
0% {
|
||||
box-shadow: 0 0 0 0 rgba(16, 185, 129, 0.4);
|
||||
}
|
||||
70% {
|
||||
box-shadow: 0 0 0 10px rgba(16, 185, 129, 0);
|
||||
}
|
||||
100% {
|
||||
box-shadow: 0 0 0 0 rgba(16, 185, 129, 0);
|
||||
}
|
||||
}
|
||||
|
||||
.confirmation-title {
|
||||
font-size: 2rem;
|
||||
font-weight: 700;
|
||||
color: var(--text);
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
.confirmation-message {
|
||||
color: var(--text-muted);
|
||||
font-size: 1.1rem;
|
||||
line-height: 1.6;
|
||||
margin-bottom: 2.5rem;
|
||||
max-width: 400px;
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
}
|
||||
|
||||
.action-buttons {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 1rem;
|
||||
margin-top: 2rem;
|
||||
}
|
||||
|
||||
.btn-modern {
|
||||
padding: 12px 28px;
|
||||
border-radius: 12px;
|
||||
font-weight: 600;
|
||||
text-decoration: none;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 8px;
|
||||
transition: all 0.3s ease;
|
||||
border: none;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.btn-primary-modern {
|
||||
background: linear-gradient(135deg, var(--accent), #059669);
|
||||
color: white;
|
||||
box-shadow: 0 4px 15px rgba(16, 185, 129, 0.3);
|
||||
}
|
||||
|
||||
.btn-primary-modern:hover {
|
||||
transform: translateY(-2px);
|
||||
box-shadow: 0 8px 25px rgba(16, 185, 129, 0.4);
|
||||
color: white;
|
||||
}
|
||||
|
||||
.btn-outline-modern {
|
||||
background: transparent;
|
||||
color: var(--text-muted);
|
||||
border: 2px solid var(--border);
|
||||
}
|
||||
|
||||
.btn-outline-modern:hover {
|
||||
background: var(--background);
|
||||
color: var(--text);
|
||||
border-color: var(--accent);
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.additional-info {
|
||||
background: linear-gradient(135deg, #f0f9ff, #e0f2fe);
|
||||
border: 1px solid #38bdf8;
|
||||
border-radius: 12px;
|
||||
padding: 1.5rem;
|
||||
margin-top: 2rem;
|
||||
color: #0c4a6e;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.countdown {
|
||||
font-weight: 600;
|
||||
color: var(--accent);
|
||||
margin-top: 1rem;
|
||||
font-size: 0.9rem;
|
||||
}
|
||||
|
||||
footer {
|
||||
background: var(--text);
|
||||
color: #94a3b8;
|
||||
padding: 2rem 0;
|
||||
text-align: center;
|
||||
margin-top: auto;
|
||||
}
|
||||
|
||||
.footer-text {
|
||||
font-size: 0.9rem;
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.confirmation-container {
|
||||
margin: 2rem 1rem;
|
||||
padding: 2rem 1.5rem;
|
||||
}
|
||||
|
||||
.confirmation-title {
|
||||
font-size: 1.5rem;
|
||||
}
|
||||
|
||||
.confirmation-icon {
|
||||
width: 80px;
|
||||
height: 80px;
|
||||
font-size: 2rem;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<!-- Navbar institucional -->
|
||||
<!-- NAVBAR -->
|
||||
<nav class="navbar navbar-expand-lg">
|
||||
<div class="container">
|
||||
<a class="navbar-brand d-flex align-items-center text-dark" href="#">
|
||||
<img src="/IMPORTADORES/public/<?= htmlspecialchars($logo) ?>" alt="Logo" class="logo-siih">
|
||||
| Inicio de Importador
|
||||
<a class="navbar-brand" href="/IMPORTADORES/">
|
||||
<i class="fas fa-shipping-fast me-2"></i><?= htmlspecialchars($siglas) ?>
|
||||
</a>
|
||||
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarNav">
|
||||
<span class="navbar-toggler-icon"></span>
|
||||
@@ -49,75 +278,206 @@ $mensajeMantenimiento = $config['mensaje_mantenimiento'] ?? 'El sistema se encue
|
||||
<a class="nav-link" href="/IMPORTADORES/">Inicio</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link " aria-current="page" href="/IMPORTADORES/registro">Registro</a>
|
||||
<a class="nav-link" href="/IMPORTADORES/registro">Registro</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link active" href="/IMPORTADORES/login">Inicio sesión</a>
|
||||
<a class="nav-link" href="/IMPORTADORES/login">Iniciar Sesión</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
|
||||
<?php if ($mantenimiento): ?>
|
||||
<div class="container mt-4">
|
||||
<div class="alert alert-danger text-center" role="alert">
|
||||
<i class="fas fa-triangle-exclamation me-2"></i>
|
||||
<?= htmlspecialchars($mensajeMantenimiento) ?>
|
||||
</div>
|
||||
<!-- CONTENEDOR DE CONFIRMACIÓN -->
|
||||
<div class="confirmation-container">
|
||||
<?php
|
||||
// Determinar icono y títulos según el tipo
|
||||
$iconClass = 'icon-success';
|
||||
$iconSymbol = 'fa-check';
|
||||
$title = 'Operación Exitosa';
|
||||
|
||||
switch($tipo) {
|
||||
case 'password_changed':
|
||||
$iconClass = 'icon-success';
|
||||
$iconSymbol = 'fa-check';
|
||||
$title = '¡Contraseña Actualizada!';
|
||||
$mensaje = 'Tu contraseña ha sido cambiada exitosamente. Ya puedes iniciar sesión con tu nueva contraseña.';
|
||||
break;
|
||||
|
||||
case 'code_sent':
|
||||
$iconClass = 'icon-info';
|
||||
$iconSymbol = 'fa-envelope';
|
||||
$title = '¡Código Enviado!';
|
||||
$mensaje = 'Hemos enviado un código de verificación a tu correo electrónico. Revisa tu bandeja de entrada y spam.';
|
||||
break;
|
||||
|
||||
case 'registration_sent':
|
||||
$iconClass = 'icon-success';
|
||||
$iconSymbol = 'fa-paper-plane';
|
||||
$title = '¡Solicitud Enviada!';
|
||||
$mensaje = 'Tu solicitud de registro ha sido enviada correctamente. Recibirás una notificación por correo cuando sea aprobada.';
|
||||
break;
|
||||
|
||||
case 'error':
|
||||
$iconClass = 'icon-error';
|
||||
$iconSymbol = 'fa-times';
|
||||
$title = 'Error en la Operación';
|
||||
break;
|
||||
|
||||
case 'warning':
|
||||
$iconClass = 'icon-warning';
|
||||
$iconSymbol = 'fa-exclamation-triangle';
|
||||
$title = 'Atención Requerida';
|
||||
break;
|
||||
|
||||
default:
|
||||
$iconClass = 'icon-success';
|
||||
$iconSymbol = 'fa-check';
|
||||
$title = 'Operación Completada';
|
||||
}
|
||||
?>
|
||||
|
||||
<div class="confirmation-icon <?= $iconClass ?>">
|
||||
<i class="fas <?= $iconSymbol ?>"></i>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
|
||||
<!-- Overlay (fondo semitransparente) que cubre la pantalla -->
|
||||
<div class="overlay"></div>
|
||||
<h1 class="confirmation-title"><?= htmlspecialchars($title) ?></h1>
|
||||
|
||||
<p class="confirmation-message">
|
||||
<?= htmlspecialchars($mensaje) ?>
|
||||
</p>
|
||||
|
||||
<!-- Formulario de confirmación de acceso -->
|
||||
<div class="login-container">
|
||||
<h4 class="mb-4 text-center text-success">Confirma el Acceso</h4>
|
||||
|
||||
<!-- Formulario para verificar el código -->
|
||||
<form id="formConfirmacion" action="/IMPORTADORES/login/confirmarAcceso" method="POST">
|
||||
<div class="mb-3">
|
||||
<label class="form-label" for="codigo">Código recibido por correo</label>
|
||||
<input type="text" id="codigo" name="codigo" class="form-control" required pattern="\d{6}" maxlength="6" placeholder="Ej. 123456">
|
||||
</div>
|
||||
<button type="submit" class="btn btn-primary w-100">Verificar código</button>
|
||||
</form>
|
||||
|
||||
<div id="mensaje" class="mt-3 text-center fw-bold">
|
||||
<?php if (isset($_SESSION['codigo_error'])): ?>
|
||||
<div class="text-danger"><?= $_SESSION['codigo_error'] ?></div>
|
||||
<?php unset($_SESSION['codigo_error']); ?>
|
||||
<?php elseif (isset($_SESSION['codigo_exito'])): ?>
|
||||
<div class="text-success"><?= $_SESSION['codigo_exito'] ?></div>
|
||||
<?php unset($_SESSION['codigo_exito']); ?>
|
||||
<div class="action-buttons">
|
||||
<?php if ($tipo === 'password_changed'): ?>
|
||||
<a href="/IMPORTADORES/login" class="btn-modern btn-primary-modern">
|
||||
<i class="fas fa-sign-in-alt"></i>
|
||||
Iniciar Sesión
|
||||
</a>
|
||||
|
||||
<?php elseif ($tipo === 'code_sent'): ?>
|
||||
<a href="/IMPORTADORES/login/verificar_codigo" class="btn-modern btn-primary-modern">
|
||||
<i class="fas fa-key"></i>
|
||||
Verificar Código
|
||||
</a>
|
||||
<a href="/IMPORTADORES/login/recuperar" class="btn-modern btn-outline-modern">
|
||||
<i class="fas fa-arrow-left"></i>
|
||||
Volver
|
||||
</a>
|
||||
|
||||
<?php elseif ($tipo === 'registration_sent'): ?>
|
||||
<a href="/IMPORTADORES/" class="btn-modern btn-primary-modern">
|
||||
<i class="fas fa-home"></i>
|
||||
Ir al Inicio
|
||||
</a>
|
||||
<a href="/IMPORTADORES/login" class="btn-modern btn-outline-modern">
|
||||
<i class="fas fa-sign-in-alt"></i>
|
||||
Iniciar Sesión
|
||||
</a>
|
||||
|
||||
<?php else: ?>
|
||||
<a href="/IMPORTADORES/login" class="btn-modern btn-primary-modern">
|
||||
<i class="fas fa-sign-in-alt"></i>
|
||||
Iniciar Sesión
|
||||
</a>
|
||||
<a href="/IMPORTADORES/" class="btn-modern btn-outline-modern">
|
||||
<i class="fas fa-home"></i>
|
||||
Ir al Inicio
|
||||
</a>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<?php
|
||||
$redirigirA = $_SESSION['redirigir_a'] ?? null;
|
||||
$delayMs = $_SESSION['delay_redireccion'] ?? null;
|
||||
unset($_SESSION['redirigir_a'], $_SESSION['delay_redireccion']);
|
||||
?>
|
||||
<?php if ($tipo === 'code_sent'): ?>
|
||||
<div class="additional-info">
|
||||
<div class="d-flex align-items-start">
|
||||
<i class="fas fa-info-circle me-2 mt-1"></i>
|
||||
<div>
|
||||
<strong>¿No recibiste el código?</strong><br>
|
||||
<small>
|
||||
• Revisa tu carpeta de spam o correo no deseado<br>
|
||||
• El código es válido por 15 minutos<br>
|
||||
• Puedes solicitar un nuevo código si es necesario
|
||||
</small>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
|
||||
<?php if ($tipo === 'registration_sent'): ?>
|
||||
<div class="additional-info">
|
||||
<div class="d-flex align-items-start">
|
||||
<i class="fas fa-clock me-2 mt-1"></i>
|
||||
<div>
|
||||
<strong>Proceso de Revisión</strong><br>
|
||||
<small>
|
||||
• Nuestro equipo revisará tu solicitud en las próximas 24-48 horas<br>
|
||||
• Recibirás un correo con el resultado de la evaluación<br>
|
||||
• Asegúrate de revisar tu bandeja de entrada regularmente
|
||||
</small>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
|
||||
<?php if (in_array($tipo, ['password_changed', 'registration_sent'])): ?>
|
||||
<div class="countdown" id="countdown">
|
||||
Serás redirigido automáticamente en <span id="timer">10</span> segundos...
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
|
||||
<!-- FOOTER -->
|
||||
<footer>
|
||||
<div class="container text-center">
|
||||
<div class="footer-text">
|
||||
© <?= date('Y') ?> <?= htmlspecialchars($siglas) ?> - <?= htmlspecialchars($nombre) ?><br>
|
||||
Plataforma segura de gestión de importaciones
|
||||
</div>
|
||||
</div>
|
||||
</footer>
|
||||
|
||||
<script>
|
||||
document.addEventListener('DOMContentLoaded', function () {
|
||||
const redirigirA = <?= json_encode($redirigirA) ?>;
|
||||
const delay = <?= json_encode($delayMs) ?>;
|
||||
|
||||
if (redirigirA && delay) {
|
||||
setTimeout(() => {
|
||||
window.location.href = redirigirA;
|
||||
}, parseInt(delay));
|
||||
// Redirección automática para ciertos tipos
|
||||
<?php if (in_array($tipo, ['password_changed', 'registration_sent'])): ?>
|
||||
let timeLeft = 10;
|
||||
const timerElement = document.getElementById('timer');
|
||||
const countdownElement = document.getElementById('countdown');
|
||||
|
||||
const countdown = setInterval(() => {
|
||||
timeLeft--;
|
||||
timerElement.textContent = timeLeft;
|
||||
|
||||
if (timeLeft <= 0) {
|
||||
clearInterval(countdown);
|
||||
<?php if ($tipo === 'password_changed'): ?>
|
||||
window.location.href = '/IMPORTADORES/login';
|
||||
<?php elseif ($tipo === 'registration_sent'): ?>
|
||||
window.location.href = '/IMPORTADORES/';
|
||||
<?php endif; ?>
|
||||
}
|
||||
}, 1000);
|
||||
|
||||
// Permitir cancelar la redirección automática
|
||||
document.addEventListener('click', () => {
|
||||
if (timeLeft > 0) {
|
||||
clearInterval(countdown);
|
||||
countdownElement.style.display = 'none';
|
||||
}
|
||||
});
|
||||
<?php endif; ?>
|
||||
|
||||
// Animación de entrada
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
const container = document.querySelector('.confirmation-container');
|
||||
container.style.opacity = '0';
|
||||
container.style.transform = 'translateY(20px)';
|
||||
|
||||
setTimeout(() => {
|
||||
container.style.transition = 'all 0.5s ease';
|
||||
container.style.opacity = '1';
|
||||
container.style.transform = 'translateY(0)';
|
||||
}, 100);
|
||||
});
|
||||
</script>
|
||||
|
||||
<!-- FOOTER -->
|
||||
<footer class="fixed-bottom">
|
||||
© <?= date('Y') ?> <?= htmlspecialchars($siglas) ?> · <?= htmlspecialchars($nombre) ?>
|
||||
</footer>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
@@ -6,11 +6,12 @@ $sql = "SELECT TOP 1 * FROM configuracion_sistema";
|
||||
$stmt = sqlsrv_query($conn, $sql);
|
||||
$config = sqlsrv_fetch_array($stmt, SQLSRV_FETCH_ASSOC);
|
||||
|
||||
$nombre = $config['nombre_plataforma'] ?? 'Sistema Integral para Importadores de Hidrocarburos';
|
||||
$siglas = $config['siglas'] ?? 'SIIH';
|
||||
$logo = $config['logo_url'] ?? 'assets/img/logo_siih.png';
|
||||
$color1 = explode(',', $config['colores_primarios'] ?? '#003366,#0055A5')[0];
|
||||
$color2 = explode(',', $config['colores_primarios'] ?? '#003366,#0055A5')[1];
|
||||
// Variables con valores por defecto - Branding moderno
|
||||
$nombre = $config['nombre_plataforma'] ?? 'Sistema de Gestión de Importaciones';
|
||||
$siglas = $config['siglas'] ?? 'AduanaSoft';
|
||||
$logo = $config['logo_url'] ?? 'assets/img/logo_aduanasoft.png';
|
||||
$color1 = explode(',', $config['colores_primarios'] ?? '#0f172a,#334155')[0];
|
||||
$color2 = explode(',', $config['colores_primarios'] ?? '#0f172a,#334155')[1];
|
||||
$mantenimiento = $config['modo_mantenimiento'] ?? 0;
|
||||
$mensajeMantenimiento = $config['mensaje_mantenimiento'] ?? 'El sistema se encuentra en mantenimiento temporal. Por favor, vuelve más tarde.';
|
||||
?>
|
||||
@@ -19,40 +20,238 @@ $mensajeMantenimiento = $config['mensaje_mantenimiento'] ?? 'El sistema se encue
|
||||
<html lang="es">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title><?= htmlspecialchars($siglas) ?> | Iniciar sesión</title>
|
||||
<title><?= htmlspecialchars($siglas) ?> | Iniciar Sesión</title>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<meta name="description" content="Accede a tu cuenta en la plataforma de gestión de importaciones">
|
||||
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css" rel="stylesheet">
|
||||
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/js/bootstrap.bundle.min.js"></script>
|
||||
<link href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.0/css/all.min.css" rel="stylesheet">
|
||||
<link href="https://fonts.googleapis.com/css2?family=Poppins:wght@300;400;500;600;700&display=swap" rel="stylesheet">
|
||||
<style>
|
||||
body { background-color: #f4f6f9; font-family: 'Segoe UI', sans-serif; }
|
||||
.navbar { background-color: #fff; }
|
||||
.navbar .nav-link, .navbar-brand { color:<?= $color1 ?> !important; font-weight: 500; }
|
||||
.navbar .nav-link.active { background-color: #F9F9F9; border-radius: 5px; }
|
||||
.logo-siih { height: 90px; margin-right: 10px; }
|
||||
.login-container { max-width: 420px; margin: 80px auto; background: white; padding: 40px; border-radius: 12px; box-shadow: 0 10px 25px rgba(0, 0, 0, 0.08); }
|
||||
.form-control { border-radius: 6px; }
|
||||
footer { background-color: #e9ecef; padding: 20px; text-align: center; font-size: 14px; color: #666; margin-top: 60px; }
|
||||
#mensaje { transition: opacity 0.3s ease; }
|
||||
@media (max-height: 600px) { footer { position: static !important; } }
|
||||
/* Efecto de glow para elementos activos */
|
||||
.btn.active { box-shadow: 0 0 20px rgba(var(--bs-primary-rgb), 0.5); }
|
||||
/* Efecto para botones */
|
||||
.btn-animated { transition: all 0.3s ease; position: relative; overflow: hidden; }
|
||||
.btn-animated:hover { transform: translateY(-2px); box-shadow: 0 5px 15px rgba(0, 0, 0, 0.2); }
|
||||
.btn-animated::before { content: ''; position: absolute; top: 0; left: -100%; width: 100%; height: 100%;
|
||||
background: linear-gradient(90deg, transparent, rgba(255, 255, 255, 0.2), transparent); transition: left 0.5s; }
|
||||
.btn-animated:hover::before { left: 100%; }
|
||||
:root {
|
||||
--primary: <?= $color1 ?>;
|
||||
--secondary: <?= $color2 ?>;
|
||||
--accent: #10b981;
|
||||
--surface: #ffffff;
|
||||
--background: #f8fafc;
|
||||
--text: #1e293b;
|
||||
--text-muted: #64748b;
|
||||
--border: #e2e8f0;
|
||||
--error: #ef4444;
|
||||
}
|
||||
|
||||
* {
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
body {
|
||||
background: linear-gradient(135deg, var(--background) 0%, #e2e8f0 100%);
|
||||
font-family: 'Poppins', sans-serif;
|
||||
color: var(--text);
|
||||
line-height: 1.6;
|
||||
margin: 0;
|
||||
min-height: 100vh;
|
||||
}
|
||||
|
||||
.navbar {
|
||||
background: rgba(255, 255, 255, 0.95);
|
||||
backdrop-filter: blur(20px);
|
||||
border-bottom: 1px solid var(--border);
|
||||
padding: 1rem 0;
|
||||
}
|
||||
|
||||
.navbar-brand {
|
||||
font-weight: 600;
|
||||
font-size: 1.25rem;
|
||||
color: var(--primary) !important;
|
||||
}
|
||||
|
||||
.nav-link {
|
||||
color: var(--text-muted) !important;
|
||||
font-weight: 500;
|
||||
padding: 0.5rem 1rem !important;
|
||||
border-radius: 8px;
|
||||
transition: all 0.2s ease;
|
||||
}
|
||||
|
||||
.nav-link:hover, .nav-link.active {
|
||||
color: var(--primary) !important;
|
||||
background-color: #f1f5f9;
|
||||
}
|
||||
|
||||
.login-container {
|
||||
max-width: 450px;
|
||||
margin: 3rem auto;
|
||||
background: var(--surface);
|
||||
border-radius: 20px;
|
||||
box-shadow: 0 20px 40px rgba(0, 0, 0, 0.08);
|
||||
padding: 3rem;
|
||||
border: 1px solid var(--border);
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.login-container::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
height: 4px;
|
||||
background: linear-gradient(90deg, var(--accent), var(--primary));
|
||||
}
|
||||
|
||||
.login-header {
|
||||
text-align: center;
|
||||
margin-bottom: 2.5rem;
|
||||
}
|
||||
|
||||
.login-title {
|
||||
font-size: 1.75rem;
|
||||
font-weight: 700;
|
||||
color: var(--text);
|
||||
margin-bottom: 0.5rem;
|
||||
}
|
||||
|
||||
.login-subtitle {
|
||||
color: var(--text-muted);
|
||||
font-size: 0.95rem;
|
||||
}
|
||||
|
||||
.form-label {
|
||||
font-weight: 500;
|
||||
color: var(--text);
|
||||
margin-bottom: 0.5rem;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.5rem;
|
||||
}
|
||||
|
||||
.form-control {
|
||||
border: 2px solid var(--border);
|
||||
border-radius: 12px;
|
||||
padding: 0.75rem 1rem;
|
||||
font-size: 0.95rem;
|
||||
transition: all 0.2s ease;
|
||||
background: var(--surface);
|
||||
}
|
||||
|
||||
.form-control:focus {
|
||||
border-color: var(--accent);
|
||||
box-shadow: 0 0 0 3px rgba(16, 185, 129, 0.1);
|
||||
background: var(--surface);
|
||||
}
|
||||
|
||||
.btn-modern {
|
||||
padding: 12px 28px;
|
||||
border-radius: 12px;
|
||||
font-weight: 600;
|
||||
text-decoration: none;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 8px;
|
||||
transition: all 0.3s ease;
|
||||
border: none;
|
||||
cursor: pointer;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.btn-primary-modern {
|
||||
background: linear-gradient(135deg, var(--accent), #059669);
|
||||
color: white;
|
||||
box-shadow: 0 4px 15px rgba(16, 185, 129, 0.3);
|
||||
}
|
||||
|
||||
.btn-primary-modern:hover {
|
||||
transform: translateY(-2px);
|
||||
box-shadow: 0 8px 25px rgba(16, 185, 129, 0.4);
|
||||
color: white;
|
||||
}
|
||||
|
||||
.forgot-password {
|
||||
text-align: center;
|
||||
margin-top: 1.5rem;
|
||||
}
|
||||
|
||||
.forgot-password a {
|
||||
color: var(--accent);
|
||||
text-decoration: none;
|
||||
font-weight: 500;
|
||||
transition: color 0.2s ease;
|
||||
}
|
||||
|
||||
.forgot-password a:hover {
|
||||
color: #059669;
|
||||
}
|
||||
|
||||
.alert-modern {
|
||||
border-radius: 12px;
|
||||
border: none;
|
||||
padding: 1rem 1.25rem;
|
||||
margin-bottom: 1.5rem;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.alert-danger {
|
||||
background: linear-gradient(135deg, #fef2f2, #fee2e2);
|
||||
color: #dc2626;
|
||||
border: 1px solid #fecaca;
|
||||
}
|
||||
|
||||
.alert-warning {
|
||||
background: linear-gradient(135deg, #fef3c7, #fde68a);
|
||||
color: #92400e;
|
||||
border: 1px solid #f59e0b;
|
||||
}
|
||||
|
||||
footer {
|
||||
background: var(--text);
|
||||
color: #94a3b8;
|
||||
padding: 2rem 0;
|
||||
text-align: center;
|
||||
margin-top: auto;
|
||||
}
|
||||
|
||||
.footer-text {
|
||||
font-size: 0.9rem;
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.login-container {
|
||||
margin: 2rem 1rem;
|
||||
padding: 2rem 1.5rem;
|
||||
}
|
||||
|
||||
.login-title {
|
||||
font-size: 1.5rem;
|
||||
}
|
||||
}
|
||||
|
||||
.maintenance-overlay {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background: rgba(255, 255, 255, 0.98);
|
||||
z-index: 9999;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
flex-direction: column;
|
||||
text-align: center;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<!-- Navbar institucional -->
|
||||
<!-- NAVBAR -->
|
||||
<nav class="navbar navbar-expand-lg">
|
||||
<div class="container">
|
||||
<a class="navbar-brand d-flex align-items-center text-dark" href="#">
|
||||
<img src="/IMPORTADORES/public/<?= htmlspecialchars($logo) ?>" alt="Logo" class="logo-siih">
|
||||
| Inicio de Importador
|
||||
<a class="navbar-brand" href="/IMPORTADORES/">
|
||||
<i class="fas fa-shipping-fast me-2"></i><?= htmlspecialchars($siglas) ?>
|
||||
</a>
|
||||
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarNav">
|
||||
<span class="navbar-toggler-icon"></span>
|
||||
@@ -63,60 +262,82 @@ $mensajeMantenimiento = $config['mensaje_mantenimiento'] ?? 'El sistema se encue
|
||||
<a class="nav-link" href="/IMPORTADORES/">Inicio</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link " aria-current="page" href="/IMPORTADORES/registro">Registro</a>
|
||||
<a class="nav-link" href="/IMPORTADORES/registro">Registro</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link active" href="/IMPORTADORES/login">Inicio sesión</a>
|
||||
<a class="nav-link active" href="/IMPORTADORES/login">Iniciar Sesión</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
|
||||
<!-- MANTENIMIENTO -->
|
||||
<?php if ($mantenimiento): ?>
|
||||
<div class="container mt-4">
|
||||
<div class="alert alert-danger text-center" role="alert">
|
||||
<i class="fas fa-triangle-exclamation me-2"></i>
|
||||
<?= htmlspecialchars($mensajeMantenimiento) ?>
|
||||
</div>
|
||||
<div class="maintenance-overlay">
|
||||
<i class="fas fa-tools fa-4x mb-4" style="color: var(--accent);"></i>
|
||||
<h2 style="color: var(--text); margin-bottom: 1rem;">Sistema en Mantenimiento</h2>
|
||||
<p style="color: var(--text-muted); max-width: 500px;"><?= htmlspecialchars($mensajeMantenimiento) ?></p>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
|
||||
<!-- CONTENEDOR DE LOGIN -->
|
||||
<div class="login-container">
|
||||
<h4 class="mb-4 text-center text-dark">Inicio de Sesión </h4>
|
||||
<form action="/IMPORTADORES/login/validar" method="POST">
|
||||
<div class="mb-3">
|
||||
<label class="form-label">Correo electrónico</label>
|
||||
<input type="email" name="email" class="form-control" required autocomplete="username" value="">
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label class="form-label">Contraseña</label>
|
||||
<input type="password" name="password" class="form-control" required autocomplete="current-password" value="">
|
||||
</div>
|
||||
<div class="d-grid">
|
||||
<button type="submit" class="btn btn-primary btn-sm mt-auto m-100 btn-animated">Iniciar sesión</button>
|
||||
</div>
|
||||
<div class="mb-3 text-end">
|
||||
<div style="display: flex; justify-content: center;">
|
||||
<a href="/IMPORTADORES/login/recuperar" class="text-decoration-none">¿Olvidaste tu contraseña?</a>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
<div id="mensaje" class="mt-3 text-center fw-bold">
|
||||
<?php if (isset($_SESSION['login_error'])): ?>
|
||||
<div class="text-danger"><?= $_SESSION['login_error'] ?></div>
|
||||
<?php unset($_SESSION['login_error']); ?>
|
||||
<?php endif; ?>
|
||||
<div class="login-header">
|
||||
<h1 class="login-title">Bienvenido de Vuelta</h1>
|
||||
<p class="login-subtitle">Ingresa a tu cuenta para gestionar tus importaciones</p>
|
||||
</div>
|
||||
|
||||
<!-- MENSAJES DE ERROR -->
|
||||
<?php if (isset($_SESSION['login_error'])): ?>
|
||||
<div class="alert-modern alert-danger">
|
||||
<i class="fas fa-exclamation-triangle me-2"></i>
|
||||
<?= $_SESSION['login_error'] ?>
|
||||
</div>
|
||||
<?php unset($_SESSION['login_error']); ?>
|
||||
<?php endif; ?>
|
||||
|
||||
<form action="/IMPORTADORES/login/validar" method="POST">
|
||||
<div class="mb-3">
|
||||
<label for="email" class="form-label">
|
||||
<i class="fas fa-envelope text-muted"></i>
|
||||
Correo electrónico
|
||||
</label>
|
||||
<input type="email" name="email" id="email" class="form-control"
|
||||
required autocomplete="username"
|
||||
placeholder="tu@empresa.com">
|
||||
</div>
|
||||
|
||||
<div class="mb-4">
|
||||
<label for="password" class="form-label">
|
||||
<i class="fas fa-lock text-muted"></i>
|
||||
Contraseña
|
||||
</label>
|
||||
<input type="password" name="password" id="password" class="form-control"
|
||||
required autocomplete="current-password"
|
||||
placeholder="Tu contraseña segura">
|
||||
</div>
|
||||
|
||||
<button type="submit" class="btn-modern btn-primary-modern">
|
||||
<i class="fas fa-sign-in-alt"></i>
|
||||
Iniciar Sesión
|
||||
</button>
|
||||
|
||||
<div class="forgot-password">
|
||||
<a href="/IMPORTADORES/login/recuperar">¿Olvidaste tu contraseña?</a>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<!-- FOOTER -->
|
||||
<footer class="fixed-bottom">
|
||||
© <?= date('Y') ?> <?= htmlspecialchars($siglas) ?> · <?= htmlspecialchars($nombre) ?>
|
||||
<div class="container text-center ">
|
||||
<div class="footer-text">
|
||||
© <?= date('Y') ?> <?= htmlspecialchars($siglas) ?> - <?= htmlspecialchars($nombre) ?><br>
|
||||
Acceso seguro a tu plataforma de importaciones
|
||||
</div>
|
||||
</div>
|
||||
</footer>
|
||||
<!-- I.C.M -->
|
||||
|
||||
</body>
|
||||
</html>
|
||||
@@ -1,42 +1,259 @@
|
||||
<?php
|
||||
$nombre = $config['nombre_plataforma'] ?? 'Sistema Integral para Importadores de Hidrocarburos';
|
||||
$siglas = $config['siglas'] ?? 'SIIH';
|
||||
$logo = $config['logo_url'] ?? 'assets/img/logo_siih.png';
|
||||
$color1 = explode(',', $config['colores_primarios'] ?? '#003366,#0055A5')[0];
|
||||
$color2 = explode(',', $config['colores_primarios'] ?? '#003366,#0055A5')[1];
|
||||
$mantenimiento = $config['modo_mantenimiento'] ?? 0;
|
||||
$mensajeMantenimiento = $config['mensaje_mantenimiento'] ?? 'El sistema se encuentra en mantenimiento temporal. Por favor, vuelve más tarde.';
|
||||
require_once __DIR__ . '/../../config/database.php';
|
||||
$conn = getConnection();
|
||||
|
||||
$sql = "SELECT TOP 1 * FROM configuracion_sistema";
|
||||
$stmt = sqlsrv_query($conn, $sql);
|
||||
$config = sqlsrv_fetch_array($stmt, SQLSRV_FETCH_ASSOC);
|
||||
|
||||
// Variables con valores por defecto - Branding moderno
|
||||
$nombre = $config['nombre_plataforma'] ?? 'Sistema de Gestión de Importaciones';
|
||||
$siglas = $config['siglas'] ?? 'AduanaSoft';
|
||||
$logo = $config['logo_url'] ?? 'assets/img/logo_aduanasoft.png';
|
||||
$color1 = explode(',', $config['colores_primarios'] ?? '#0f172a,#334155')[0];
|
||||
$color2 = explode(',', $config['colores_primarios'] ?? '#0f172a,#334155')[1];
|
||||
?>
|
||||
|
||||
<!DOCTYPE html>
|
||||
<html lang="es">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title><?= htmlspecialchars($siglas) ?> | Recuperación de Contraseña</title>
|
||||
<title><?= htmlspecialchars($siglas) ?> | Recuperar Contraseña</title>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<meta name="description" content="Recupera tu contraseña para acceder a tu cuenta de importaciones">
|
||||
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css" rel="stylesheet">
|
||||
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/js/bootstrap.bundle.min.js"></script>
|
||||
<link href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.0/css/all.min.css" rel="stylesheet">
|
||||
<link href="https://fonts.googleapis.com/css2?family=Poppins:wght@300;400;500;600;700&display=swap" rel="stylesheet">
|
||||
<style>
|
||||
body { background-color: #f4f6f9; font-family: 'Segoe UI', sans-serif; position: relative; height: 100vh; }
|
||||
.navbar { background-color: #fff; }
|
||||
.navbar .nav-link, .navbar-brand { color: <?= $color1 ?> !important; font-weight: 500; }
|
||||
.logo-siih { height: 90px; margin-right: 10px; }
|
||||
.login-container { max-width: 420px; margin: 80px auto; background: white; padding: 40px; border-radius: 12px; box-shadow: 0 10px 25px rgba(0, 0, 0, 0.08); z-index: 10; position: relative; }
|
||||
.form-control { border-radius: 6px; }
|
||||
footer { background-color: #e9ecef; padding: 20px; text-align: center; font-size: 14px; color: #666; margin-top: 60px; }
|
||||
/* Contenedor de fondo semitransparente para las vistas */
|
||||
.overlay { position: absolute; top: 0; left: 0; width: 100%; height: 100%; background: rgba(0, 0, 0, 0.5); z-index: 5; }
|
||||
:root {
|
||||
--primary: <?= $color1 ?>;
|
||||
--secondary: <?= $color2 ?>;
|
||||
--accent: #10b981;
|
||||
--surface: #ffffff;
|
||||
--background: #f8fafc;
|
||||
--text: #1e293b;
|
||||
--text-muted: #64748b;
|
||||
--border: #e2e8f0;
|
||||
--error: #ef4444;
|
||||
}
|
||||
|
||||
* {
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
body {
|
||||
background: linear-gradient(135deg, var(--background) 0%, #e2e8f0 100%);
|
||||
font-family: 'Poppins', sans-serif;
|
||||
color: var(--text);
|
||||
line-height: 1.6;
|
||||
margin: 0;
|
||||
min-height: 100vh;
|
||||
}
|
||||
|
||||
.navbar {
|
||||
background: rgba(255, 255, 255, 0.95);
|
||||
backdrop-filter: blur(20px);
|
||||
border-bottom: 1px solid var(--border);
|
||||
padding: 1rem 0;
|
||||
}
|
||||
|
||||
.navbar-brand {
|
||||
font-weight: 600;
|
||||
font-size: 1.25rem;
|
||||
color: var(--primary) !important;
|
||||
}
|
||||
|
||||
.nav-link {
|
||||
color: var(--text-muted) !important;
|
||||
font-weight: 500;
|
||||
padding: 0.5rem 1rem !important;
|
||||
border-radius: 8px;
|
||||
transition: all 0.2s ease;
|
||||
}
|
||||
|
||||
.nav-link:hover, .nav-link.active {
|
||||
color: var(--primary) !important;
|
||||
background-color: #f1f5f9;
|
||||
}
|
||||
|
||||
.recovery-container {
|
||||
max-width: 500px;
|
||||
margin: 3rem auto;
|
||||
background: var(--surface);
|
||||
border-radius: 20px;
|
||||
box-shadow: 0 20px 40px rgba(0, 0, 0, 0.08);
|
||||
padding: 3rem;
|
||||
border: 1px solid var(--border);
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.recovery-container::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
height: 4px;
|
||||
background: linear-gradient(90deg, var(--accent), var(--primary));
|
||||
}
|
||||
|
||||
.recovery-header {
|
||||
text-align: center;
|
||||
margin-bottom: 2.5rem;
|
||||
}
|
||||
|
||||
.recovery-icon {
|
||||
width: 80px;
|
||||
height: 80px;
|
||||
background: linear-gradient(135deg, var(--accent), #059669);
|
||||
border-radius: 50%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
margin: 0 auto 1.5rem;
|
||||
color: white;
|
||||
font-size: 2rem;
|
||||
}
|
||||
|
||||
.recovery-title {
|
||||
font-size: 1.75rem;
|
||||
font-weight: 700;
|
||||
color: var(--text);
|
||||
margin-bottom: 0.5rem;
|
||||
}
|
||||
|
||||
.recovery-subtitle {
|
||||
color: var(--text-muted);
|
||||
font-size: 0.95rem;
|
||||
}
|
||||
|
||||
.form-label {
|
||||
font-weight: 500;
|
||||
color: var(--text);
|
||||
margin-bottom: 0.5rem;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.5rem;
|
||||
}
|
||||
|
||||
.form-control {
|
||||
border: 2px solid var(--border);
|
||||
border-radius: 12px;
|
||||
padding: 0.75rem 1rem;
|
||||
font-size: 0.95rem;
|
||||
transition: all 0.2s ease;
|
||||
background: var(--surface);
|
||||
}
|
||||
|
||||
.form-control:focus {
|
||||
border-color: var(--accent);
|
||||
box-shadow: 0 0 0 3px rgba(16, 185, 129, 0.1);
|
||||
background: var(--surface);
|
||||
}
|
||||
|
||||
.btn-modern {
|
||||
padding: 12px 28px;
|
||||
border-radius: 12px;
|
||||
font-weight: 600;
|
||||
text-decoration: none;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 8px;
|
||||
transition: all 0.3s ease;
|
||||
border: none;
|
||||
cursor: pointer;
|
||||
width: 100%;
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
.btn-primary-modern {
|
||||
background: linear-gradient(135deg, var(--accent), #059669);
|
||||
color: white;
|
||||
box-shadow: 0 4px 15px rgba(16, 185, 129, 0.3);
|
||||
}
|
||||
|
||||
.btn-primary-modern:hover {
|
||||
transform: translateY(-2px);
|
||||
box-shadow: 0 8px 25px rgba(16, 185, 129, 0.4);
|
||||
color: white;
|
||||
}
|
||||
|
||||
.btn-outline-modern {
|
||||
background: transparent;
|
||||
color: var(--text-muted);
|
||||
border: 2px solid var(--border);
|
||||
}
|
||||
|
||||
.btn-outline-modern:hover {
|
||||
background: var(--background);
|
||||
color: var(--text);
|
||||
border-color: var(--accent);
|
||||
}
|
||||
|
||||
.alert-modern {
|
||||
border-radius: 12px;
|
||||
border: none;
|
||||
padding: 1rem 1.25rem;
|
||||
margin-bottom: 1.5rem;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.alert-danger {
|
||||
background: linear-gradient(135deg, #fef2f2, #fee2e2);
|
||||
color: #dc2626;
|
||||
border: 1px solid #fecaca;
|
||||
}
|
||||
|
||||
.alert-success {
|
||||
background: linear-gradient(135deg, #f0fdf4, #dcfce7);
|
||||
color: #16a34a;
|
||||
border: 1px solid #bbf7d0;
|
||||
}
|
||||
|
||||
.info-box {
|
||||
background: linear-gradient(135deg, #f0f9ff, #e0f2fe);
|
||||
border: 1px solid #38bdf8;
|
||||
border-radius: 12px;
|
||||
padding: 1.5rem;
|
||||
margin-top: 1.5rem;
|
||||
color: #0c4a6e;
|
||||
}
|
||||
|
||||
footer {
|
||||
background: var(--text);
|
||||
color: #94a3b8;
|
||||
padding: 2rem 0;
|
||||
text-align: center;
|
||||
margin-top: auto;
|
||||
}
|
||||
|
||||
.footer-text {
|
||||
font-size: 0.9rem;
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.recovery-container {
|
||||
margin: 2rem 1rem;
|
||||
padding: 2rem 1.5rem;
|
||||
}
|
||||
|
||||
.recovery-title {
|
||||
font-size: 1.5rem;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<!-- Navbar institucional -->
|
||||
<!-- NAVBAR -->
|
||||
<nav class="navbar navbar-expand-lg">
|
||||
<div class="container">
|
||||
<a class="navbar-brand d-flex align-items-center text-dark" href="#">
|
||||
<img src="/IMPORTADORES/public/<?= htmlspecialchars($logo) ?>" alt="Logo" class="logo-siih">
|
||||
| Inicio de Importador
|
||||
<a class="navbar-brand" href="/IMPORTADORES/">
|
||||
<i class="fas fa-shipping-fast me-2"></i><?= htmlspecialchars($siglas) ?>
|
||||
</a>
|
||||
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarNav">
|
||||
<span class="navbar-toggler-icon"></span>
|
||||
@@ -47,112 +264,166 @@ $mensajeMantenimiento = $config['mensaje_mantenimiento'] ?? 'El sistema se encue
|
||||
<a class="nav-link" href="/IMPORTADORES/">Inicio</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link " aria-current="page" href="/IMPORTADORES/registro">Registro</a>
|
||||
<a class="nav-link" href="/IMPORTADORES/registro">Registro</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link active" href="/IMPORTADORES/login">Inicio sesión</a>
|
||||
<a class="nav-link" href="/IMPORTADORES/login">Iniciar Sesión</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
|
||||
<?php if ($mantenimiento): ?>
|
||||
<div class="container mt-4">
|
||||
<div class="alert alert-danger text-center" role="alert">
|
||||
<i class="fas fa-triangle-exclamation me-2"></i>
|
||||
<?= htmlspecialchars($mensajeMantenimiento) ?>
|
||||
<!-- CONTENEDOR DE RECUPERACIÓN -->
|
||||
<div class="recovery-container">
|
||||
<div class="recovery-header">
|
||||
<div class="recovery-icon">
|
||||
<i class="fas fa-key"></i>
|
||||
</div>
|
||||
<h1 class="recovery-title">Recuperar Contraseña</h1>
|
||||
<p class="recovery-subtitle">Ingresa tu correo electrónico y te enviaremos un código de recuperación</p>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
|
||||
<!-- Overlay (fondo semitransparente) que cubre la pantalla -->
|
||||
<div class="overlay"></div>
|
||||
|
||||
<!-- Formulario de recuperación de contraseña -->
|
||||
<div class="login-container">
|
||||
<h4 class="mb-4 text-center text-dark">Recuperar Contraseña</h4>
|
||||
<form id="formRecuperar" novalidate>
|
||||
<div class="mb-3">
|
||||
<label class="form-label" for="emailInput">Ingresa tu correo</label>
|
||||
<input type="email" id="emailInput" name="email" class="form-control" required>
|
||||
<!-- MENSAJES -->
|
||||
<?php if (isset($_SESSION['error_message'])): ?>
|
||||
<div class="alert-modern alert-danger">
|
||||
<i class="fas fa-exclamation-triangle me-2"></i>
|
||||
<?= $_SESSION['error_message'] ?>
|
||||
</div>
|
||||
<button type="submit" class="btn btn-primary w-100">Enviar código</button>
|
||||
<?php unset($_SESSION['error_message']); ?>
|
||||
<?php endif; ?>
|
||||
|
||||
<?php if (isset($_SESSION['success_message'])): ?>
|
||||
<div class="alert-modern alert-success">
|
||||
<i class="fas fa-check-circle me-2"></i>
|
||||
<?= $_SESSION['success_message'] ?>
|
||||
</div>
|
||||
<?php unset($_SESSION['success_message']); ?>
|
||||
<?php endif; ?>
|
||||
|
||||
<form action="/IMPORTADORES/login/enviar_codigo" method="POST" id="recoveryForm">
|
||||
<div class="mb-4">
|
||||
<label for="email" class="form-label">
|
||||
<i class="fas fa-envelope text-muted"></i>
|
||||
Correo electrónico registrado
|
||||
</label>
|
||||
<input type="email" name="email" id="email" class="form-control"
|
||||
required autocomplete="username"
|
||||
placeholder="tu@empresa.com">
|
||||
</div>
|
||||
|
||||
<button type="submit" class="btn-modern btn-primary-modern" id="submitBtn">
|
||||
<i class="fas fa-paper-plane"></i>
|
||||
Enviar Código de Recuperación
|
||||
</button>
|
||||
|
||||
<a href="/IMPORTADORES/login" class="btn-modern btn-outline-modern">
|
||||
<i class="fas fa-arrow-left"></i>
|
||||
Volver al Login
|
||||
</a>
|
||||
</form>
|
||||
|
||||
<?php if (isset($_SESSION['recuperar_error'])): ?>
|
||||
<div class="text-danger mt-3 text-center"><?= htmlspecialchars($_SESSION['recuperar_error']) ?></div>
|
||||
<?php unset($_SESSION['recuperar_error']); ?>
|
||||
<?php endif; ?>
|
||||
|
||||
<?php if (isset($_SESSION['recuperar_exito'])): ?>
|
||||
<div class="text-success mt-3 text-center"><?= htmlspecialchars($_SESSION['recuperar_exito']) ?></div>
|
||||
<?php unset($_SESSION['recuperar_exito']); ?>
|
||||
<?php endif; ?>
|
||||
|
||||
<div id="mensaje" class="mt-3 text-center fw-bold"></div>
|
||||
<div class="info-box">
|
||||
<div class="d-flex align-items-start">
|
||||
<i class="fas fa-info-circle me-2 mt-1"></i>
|
||||
<div>
|
||||
<strong>¿Cómo funciona?</strong><br>
|
||||
<small>
|
||||
1. Ingresa tu correo electrónico registrado<br>
|
||||
2. Revisa tu bandeja de entrada (y spam)<br>
|
||||
3. Usa el código para crear una nueva contraseña<br>
|
||||
4. Accede con tu nueva contraseña
|
||||
</small>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- FOOTER -->
|
||||
<footer class="fixed-bottom">
|
||||
© <?= date('Y') ?> <?= htmlspecialchars($siglas) ?> · <?= htmlspecialchars($nombre) ?>
|
||||
<footer>
|
||||
<div class="container text-center">
|
||||
<div class="footer-text">
|
||||
© <?= date('Y') ?> <?= htmlspecialchars($siglas) ?> - <?= htmlspecialchars($nombre) ?><br>
|
||||
Recuperación segura de contraseña
|
||||
</div>
|
||||
</div>
|
||||
</footer>
|
||||
|
||||
<script>
|
||||
document.addEventListener('DOMContentLoaded', function () {
|
||||
const form = document.getElementById('formRecuperar');
|
||||
const mensaje = document.getElementById('mensaje');
|
||||
const inputCorreo = form.querySelector('input[name="email"]');
|
||||
|
||||
inputCorreo.addEventListener('input', () => {
|
||||
mensaje.innerHTML = '';
|
||||
mensaje.classList.remove("text-danger", "text-success");
|
||||
});
|
||||
|
||||
form.addEventListener('submit', function(e) {
|
||||
e.preventDefault();
|
||||
|
||||
const emailValue = inputCorreo.value.trim();
|
||||
if (!emailValue) {
|
||||
mensaje.textContent = "❌ Por favor, ingresa un correo válido.";
|
||||
mensaje.classList.add("text-danger");
|
||||
return;
|
||||
document.getElementById('recoveryForm').addEventListener('submit', function(e) {
|
||||
e.preventDefault();
|
||||
|
||||
const submitBtn = document.getElementById('submitBtn');
|
||||
const email = document.getElementById('email').value.trim();
|
||||
|
||||
// Validar email
|
||||
const emailRegex = /^[a-zA-Z0-9._+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$/;
|
||||
if (!email || !emailRegex.test(email)) {
|
||||
showAlert('error', 'Por favor ingresa un correo electrónico válido');
|
||||
return;
|
||||
}
|
||||
|
||||
// Deshabilitar botón y mostrar loading
|
||||
submitBtn.disabled = true;
|
||||
submitBtn.innerHTML = '<i class="fas fa-spinner fa-spin"></i> Enviando...';
|
||||
|
||||
// Enviar solicitud AJAX
|
||||
fetch('/IMPORTADORES/login/enviar_codigo', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/x-www-form-urlencoded',
|
||||
},
|
||||
body: 'email=' + encodeURIComponent(email)
|
||||
})
|
||||
.then(response => response.json())
|
||||
.then(data => {
|
||||
if (data.success) {
|
||||
// Éxito - redirigir a verificar código
|
||||
showAlert('success', data.message);
|
||||
setTimeout(() => {
|
||||
window.location.href = '/IMPORTADORES/login/verificar_codigo';
|
||||
}, 2000);
|
||||
} else {
|
||||
// Error - mostrar mensaje
|
||||
showAlert('error', data.message);
|
||||
// Rehabilitar botón
|
||||
submitBtn.disabled = false;
|
||||
submitBtn.innerHTML = '<i class="fas fa-paper-plane"></i> Enviar Código de Recuperación';
|
||||
}
|
||||
|
||||
const formData = new FormData();
|
||||
formData.append('email', emailValue);
|
||||
|
||||
fetch('/IMPORTADORES/login/enviarCodigo', {
|
||||
method: 'POST',
|
||||
body: formData
|
||||
})
|
||||
.then(response => response.json())
|
||||
.then(data => {
|
||||
// Esperamos que el backend devuelva JSON con { success: true/false, message: "..." }
|
||||
|
||||
if (data.success) {
|
||||
mensaje.textContent = data.message;
|
||||
mensaje.classList.remove("text-danger");
|
||||
mensaje.classList.add("text-success");
|
||||
|
||||
// Opcional: redirigir a la vista de verificación de código
|
||||
setTimeout(() => {
|
||||
window.location.href = '/IMPORTADORES/login/verificarCodigoVista';
|
||||
}, 2000);
|
||||
} else {
|
||||
mensaje.textContent = "❌ " + data.message;
|
||||
mensaje.classList.remove("text-success");
|
||||
mensaje.classList.add("text-danger");
|
||||
}
|
||||
})
|
||||
.catch(() => {
|
||||
mensaje.textContent = "❌ Error de red.";
|
||||
mensaje.classList.remove("text-success");
|
||||
mensaje.classList.add("text-danger");
|
||||
});
|
||||
})
|
||||
.catch(error => {
|
||||
console.error('Error:', error);
|
||||
showAlert('error', 'Error de conexión. Intenta nuevamente.');
|
||||
// Rehabilitar botón
|
||||
submitBtn.disabled = false;
|
||||
submitBtn.innerHTML = '<i class="fas fa-paper-plane"></i> Enviar Código de Recuperación';
|
||||
});
|
||||
});
|
||||
|
||||
function showAlert(type, message) {
|
||||
// Remover alertas anteriores
|
||||
const existingAlerts = document.querySelectorAll('.alert-modern');
|
||||
existingAlerts.forEach(alert => alert.remove());
|
||||
|
||||
// Crear nueva alerta
|
||||
const alertDiv = document.createElement('div');
|
||||
alertDiv.className = `alert-modern alert-${type === 'success' ? 'success' : 'danger'}`;
|
||||
alertDiv.innerHTML = `
|
||||
<i class="fas fa-${type === 'success' ? 'check-circle' : 'exclamation-triangle'} me-2"></i>
|
||||
${message}
|
||||
`;
|
||||
|
||||
// Insertar antes del formulario
|
||||
const form = document.getElementById('recoveryForm');
|
||||
form.parentNode.insertBefore(alertDiv, form);
|
||||
|
||||
// Auto-remover después de 5 segundos si es error
|
||||
if (type === 'error') {
|
||||
setTimeout(() => {
|
||||
alertDiv.remove();
|
||||
}, 5000);
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
@@ -1,11 +1,17 @@
|
||||
<?php
|
||||
$nombre = $config['nombre_plataforma'] ?? 'Sistema Integral para Importadores de Hidrocarburos';
|
||||
$siglas = $config['siglas'] ?? 'SIIH';
|
||||
$logo = $config['logo_url'] ?? 'assets/img/logo_siih.png';
|
||||
$color1 = explode(',', $config['colores_primarios'] ?? '#003366,#0055A5')[0];
|
||||
$color2 = explode(',', $config['colores_primarios'] ?? '#003366,#0055A5')[1];
|
||||
$mantenimiento = $config['modo_mantenimiento'] ?? 0;
|
||||
$mensajeMantenimiento = $config['mensaje_mantenimiento'] ?? 'El sistema se encuentra en mantenimiento temporal. Por favor, vuelve más tarde.';
|
||||
require_once __DIR__ . '/../../config/database.php';
|
||||
$conn = getConnection();
|
||||
|
||||
$sql = "SELECT TOP 1 * FROM configuracion_sistema";
|
||||
$stmt = sqlsrv_query($conn, $sql);
|
||||
$config = sqlsrv_fetch_array($stmt, SQLSRV_FETCH_ASSOC);
|
||||
|
||||
// Variables con valores por defecto - Branding moderno
|
||||
$nombre = $config['nombre_plataforma'] ?? 'Sistema de Gestión de Importaciones';
|
||||
$siglas = $config['siglas'] ?? 'AduanaSoft';
|
||||
$logo = $config['logo_url'] ?? 'assets/img/logo_aduanasoft.png';
|
||||
$color1 = explode(',', $config['colores_primarios'] ?? '#0f172a,#334155')[0];
|
||||
$color2 = explode(',', $config['colores_primarios'] ?? '#0f172a,#334155')[1];
|
||||
|
||||
if (!isset($_SESSION['email_recuperacion'])) {
|
||||
// Si no hay correo en sesión, redirige a solicitar código
|
||||
@@ -18,60 +24,271 @@ if (!isset($_SESSION['email_recuperacion'])) {
|
||||
<html lang="es">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title><?= htmlspecialchars($siglas) ?> | Recuperación de Contraseña</title>
|
||||
<title><?= htmlspecialchars($siglas) ?> | Verificar Código</title>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<meta name="description" content="Verifica el código de recuperación enviado a tu correo">
|
||||
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css" rel="stylesheet">
|
||||
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/js/bootstrap.bundle.min.js"></script>
|
||||
<link href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.0/css/all.min.css" rel="stylesheet">
|
||||
<link href="https://fonts.googleapis.com/css2?family=Poppins:wght@300;400;500;600;700&display=swap" rel="stylesheet">
|
||||
<style>
|
||||
body { background-color: #f4f6f9; font-family: 'Segoe UI', sans-serif; position: relative; min-height: 100vh; }
|
||||
.navbar { background-color: #fff; }
|
||||
.navbar .nav-link, .navbar-brand { color: <?= $color1 ?> !important; font-weight: 500; }
|
||||
.logo-siih { height: 90px; margin-right: 10px; }
|
||||
.login-container { max-width: 420px; margin: 80px auto; background: white; padding: 40px; border-radius: 12px; box-shadow: 0 10px 25px rgba(0, 0, 0, 0.08); z-index: 10; position: relative; }
|
||||
.form-control { border-radius: 6px; transition: all 0.3s ease; }
|
||||
.form-control.error { border-color: #dc3545; background-color: #fff5f5; animation: shake 0.5s ease-in-out; }
|
||||
.form-control.success { border-color: #28a745; background-color: #f8fff9; }
|
||||
footer { background-color: #e9ecef; padding: 20px; text-align: center; font-size: 14px; color: #666; margin-top: 60px; }
|
||||
/* Contenedor de fondo semitransparente para las vistas */
|
||||
.overlay { position: fixed; top: 0; left: 0; width: 100%; height: 100%; background: rgba(0, 0, 0, 0.5); z-index: 5; }
|
||||
#mensaje { transition: all 0.3s ease; min-height: 24px; }
|
||||
#btnReenviarRespaldo { background: none; border: none; color: <?= $color1 ?>; text-decoration: underline; cursor: pointer; font-size: 14px; padding: 0; transition: color 0.3s ease; }
|
||||
#btnReenviarRespaldo:hover { color: <?= $color2 ?>; }
|
||||
#btnReenviarRespaldo:disabled { color: #ccc; cursor: not-allowed; text-decoration: none; }
|
||||
.btn:disabled { opacity: 0.6; cursor: not-allowed; }
|
||||
.loading { position: relative; pointer-events: none; }
|
||||
.loading::after { content: ''; position: absolute; top: 50%; left: 50%; width: 20px; height: 20px; border: 2px solid transparent; border-top: 2px solid #fff; border-radius: 50%; animation: spin 1s linear infinite; transform: translate(-50%, -50%); }
|
||||
@keyframes shake {
|
||||
0%, 100% { transform: translateX(0); }
|
||||
25% { transform: translateX(-5px); }
|
||||
75% { transform: translateX(5px); }
|
||||
:root {
|
||||
--primary: <?= $color1 ?>;
|
||||
--secondary: <?= $color2 ?>;
|
||||
--accent: #10b981;
|
||||
--surface: #ffffff;
|
||||
--background: #f8fafc;
|
||||
--text: #1e293b;
|
||||
--text-muted: #64748b;
|
||||
--border: #e2e8f0;
|
||||
--error: #ef4444;
|
||||
}
|
||||
@keyframes spin {
|
||||
0% { transform: translate(-50%, -50%) rotate(0deg); }
|
||||
100% { transform: translate(-50%, -50%) rotate(360deg); }
|
||||
|
||||
* {
|
||||
box-sizing: border-box;
|
||||
}
|
||||
@keyframes fadeIn {
|
||||
from { opacity: 0; transform: translateY(-10px); }
|
||||
to { opacity: 1; transform: translateY(0); }
|
||||
|
||||
body {
|
||||
background: linear-gradient(135deg, var(--background) 0%, #e2e8f0 100%);
|
||||
font-family: 'Poppins', sans-serif;
|
||||
color: var(--text);
|
||||
line-height: 1.6;
|
||||
margin: 0;
|
||||
min-height: 100vh;
|
||||
}
|
||||
|
||||
.navbar {
|
||||
background: rgba(255, 255, 255, 0.95);
|
||||
backdrop-filter: blur(20px);
|
||||
border-bottom: 1px solid var(--border);
|
||||
padding: 1rem 0;
|
||||
}
|
||||
|
||||
.navbar-brand {
|
||||
font-weight: 600;
|
||||
font-size: 1.25rem;
|
||||
color: var(--primary) !important;
|
||||
}
|
||||
|
||||
.nav-link {
|
||||
color: var(--text-muted) !important;
|
||||
font-weight: 500;
|
||||
padding: 0.5rem 1rem !important;
|
||||
border-radius: 8px;
|
||||
transition: all 0.2s ease;
|
||||
}
|
||||
|
||||
.nav-link:hover {
|
||||
color: var(--primary) !important;
|
||||
background-color: #f1f5f9;
|
||||
}
|
||||
|
||||
.verification-container {
|
||||
max-width: 500px;
|
||||
margin: 3rem auto;
|
||||
background: var(--surface);
|
||||
border-radius: 20px;
|
||||
box-shadow: 0 20px 40px rgba(0, 0, 0, 0.08);
|
||||
padding: 3rem;
|
||||
border: 1px solid var(--border);
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.verification-container::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
height: 4px;
|
||||
background: linear-gradient(90deg, var(--accent), var(--primary));
|
||||
}
|
||||
|
||||
.verification-header {
|
||||
text-align: center;
|
||||
margin-bottom: 2.5rem;
|
||||
}
|
||||
|
||||
.verification-icon {
|
||||
width: 80px;
|
||||
height: 80px;
|
||||
background: linear-gradient(135deg, #3b82f6, #1d4ed8);
|
||||
border-radius: 50%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
margin: 0 auto 1.5rem;
|
||||
color: white;
|
||||
font-size: 2rem;
|
||||
}
|
||||
|
||||
.verification-title {
|
||||
font-size: 1.75rem;
|
||||
font-weight: 700;
|
||||
color: var(--text);
|
||||
margin-bottom: 0.5rem;
|
||||
}
|
||||
|
||||
.verification-subtitle {
|
||||
color: var(--text-muted);
|
||||
font-size: 0.95rem;
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
.form-label {
|
||||
font-weight: 500;
|
||||
color: var(--text);
|
||||
margin-bottom: 0.5rem;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.5rem;
|
||||
}
|
||||
|
||||
.form-control {
|
||||
border: 2px solid var(--border);
|
||||
border-radius: 12px;
|
||||
padding: 0.75rem 1rem;
|
||||
font-size: 0.95rem;
|
||||
transition: all 0.2s ease;
|
||||
background: var(--surface);
|
||||
}
|
||||
|
||||
.form-control:focus {
|
||||
border-color: var(--accent);
|
||||
box-shadow: 0 0 0 3px rgba(16, 185, 129, 0.1);
|
||||
background: var(--surface);
|
||||
}
|
||||
|
||||
.code-input {
|
||||
text-align: center;
|
||||
font-size: 1.5rem;
|
||||
font-weight: 600;
|
||||
letter-spacing: 0.5rem;
|
||||
font-family: 'Courier New', monospace;
|
||||
}
|
||||
|
||||
.btn-modern {
|
||||
padding: 12px 28px;
|
||||
border-radius: 12px;
|
||||
font-weight: 600;
|
||||
text-decoration: none;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 8px;
|
||||
transition: all 0.3s ease;
|
||||
border: none;
|
||||
cursor: pointer;
|
||||
width: 100%;
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
.btn-primary-modern {
|
||||
background: linear-gradient(135deg, var(--accent), #059669);
|
||||
color: white;
|
||||
box-shadow: 0 4px 15px rgba(16, 185, 129, 0.3);
|
||||
}
|
||||
|
||||
.btn-primary-modern:hover {
|
||||
transform: translateY(-2px);
|
||||
box-shadow: 0 8px 25px rgba(16, 185, 129, 0.4);
|
||||
color: white;
|
||||
}
|
||||
|
||||
.btn-outline-modern {
|
||||
background: transparent;
|
||||
color: var(--text-muted);
|
||||
border: 2px solid var(--border);
|
||||
}
|
||||
|
||||
.btn-outline-modern:hover {
|
||||
background: var(--background);
|
||||
color: var(--text);
|
||||
border-color: var(--accent);
|
||||
}
|
||||
|
||||
.alert-modern {
|
||||
border-radius: 12px;
|
||||
border: none;
|
||||
padding: 1rem 1.25rem;
|
||||
margin-bottom: 1.5rem;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.alert-danger {
|
||||
background: linear-gradient(135deg, #fef2f2, #fee2e2);
|
||||
color: #dc2626;
|
||||
border: 1px solid #fecaca;
|
||||
}
|
||||
|
||||
.alert-success {
|
||||
background: linear-gradient(135deg, #f0fdf4, #dcfce7);
|
||||
color: #16a34a;
|
||||
border: 1px solid #bbf7d0;
|
||||
}
|
||||
|
||||
.resend-section {
|
||||
text-align: center;
|
||||
margin-top: 1.5rem;
|
||||
padding-top: 1.5rem;
|
||||
border-top: 2px solid var(--border);
|
||||
}
|
||||
|
||||
.resend-text {
|
||||
color: var(--text-muted);
|
||||
font-size: 0.9rem;
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
.resend-link {
|
||||
color: var(--accent);
|
||||
text-decoration: none;
|
||||
font-weight: 500;
|
||||
transition: color 0.2s ease;
|
||||
}
|
||||
|
||||
.resend-link:hover {
|
||||
color: #059669;
|
||||
}
|
||||
|
||||
footer {
|
||||
background: var(--text);
|
||||
color: #94a3b8;
|
||||
padding: 2rem 0;
|
||||
text-align: center;
|
||||
margin-top: auto;
|
||||
}
|
||||
|
||||
.footer-text {
|
||||
font-size: 0.9rem;
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.verification-container {
|
||||
margin: 2rem 1rem;
|
||||
padding: 2rem 1.5rem;
|
||||
}
|
||||
|
||||
.verification-title {
|
||||
font-size: 1.5rem;
|
||||
}
|
||||
|
||||
.code-input {
|
||||
font-size: 1.25rem;
|
||||
letter-spacing: 0.25rem;
|
||||
}
|
||||
}
|
||||
.fade-in { animation: fadeIn 0.3s ease-out; }
|
||||
@media (max-height: 600px) { footer { position: static !important; } }
|
||||
/* Estilos para input de código */
|
||||
#codigo { font-size: 18px; letter-spacing: 3px; text-align: center; font-weight: bold; }
|
||||
.input-group { position: relative; }
|
||||
.clear-input { position: absolute; right: 10px; top: 50%; transform: translateY(-50%); background: none; border: none; color: #ccc; cursor: pointer; z-index: 5; display: none; }
|
||||
.clear-input:hover { color: #666; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<!-- Navbar institucional -->
|
||||
<!-- NAVBAR -->
|
||||
<nav class="navbar navbar-expand-lg">
|
||||
<div class="container">
|
||||
<a class="navbar-brand d-flex align-items-center text-dark" href="#">
|
||||
<img src="/IMPORTADORES/public/<?= htmlspecialchars($logo) ?>" alt="Logo" class="logo-siih">
|
||||
| Inicio de Importador
|
||||
<a class="navbar-brand" href="/IMPORTADORES/">
|
||||
<i class="fas fa-shipping-fast me-2"></i><?= htmlspecialchars($siglas) ?>
|
||||
</a>
|
||||
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarNav">
|
||||
<span class="navbar-toggler-icon"></span>
|
||||
@@ -82,387 +299,229 @@ if (!isset($_SESSION['email_recuperacion'])) {
|
||||
<a class="nav-link" href="/IMPORTADORES/">Inicio</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link " aria-current="page" href="/IMPORTADORES/registro">Registro</a>
|
||||
<a class="nav-link" href="/IMPORTADORES/registro">Registro</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link active" href="/IMPORTADORES/login">Inicio sesión</a>
|
||||
<a class="nav-link" href="/IMPORTADORES/login">Iniciar Sesión</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
|
||||
<?php if ($mantenimiento): ?>
|
||||
<div class="container mt-4">
|
||||
<div class="alert alert-danger text-center" role="alert">
|
||||
<i class="fas fa-triangle-exclamation me-2"></i>
|
||||
<?= htmlspecialchars($mensajeMantenimiento) ?>
|
||||
<!-- CONTENEDOR DE VERIFICACIÓN -->
|
||||
<div class="verification-container">
|
||||
<div class="verification-header">
|
||||
<div class="verification-icon">
|
||||
<i class="fas fa-shield-alt"></i>
|
||||
</div>
|
||||
<h1 class="verification-title">Verificar Código</h1>
|
||||
<p class="verification-subtitle">
|
||||
Hemos enviado un código de 6 dígitos a tu correo electrónico.<br>
|
||||
Ingresa el código para continuar con la recuperación.
|
||||
</p>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
|
||||
<!-- Overlay (fondo semitransparente) que cubre la pantalla -->
|
||||
<div class="overlay"></div>
|
||||
<!-- MENSAJES -->
|
||||
<?php if (isset($_SESSION['error_message'])): ?>
|
||||
<div class="alert-modern alert-danger">
|
||||
<i class="fas fa-exclamation-triangle me-2"></i>
|
||||
<?= $_SESSION['error_message'] ?>
|
||||
</div>
|
||||
<?php unset($_SESSION['error_message']); ?>
|
||||
<?php endif; ?>
|
||||
|
||||
<!-- Formulario de validación de código -->
|
||||
<div class="login-container">
|
||||
<h4 class="mb-4 text-center text-dark">Verifica tu código</h4>
|
||||
<p class="text-muted text-center mb-4">
|
||||
<i class="fas fa-envelope me-2"></i>
|
||||
Hemos enviado un código de 6 dígitos a tu correo electrónico
|
||||
</p>
|
||||
|
||||
<!-- Formulario para verificar el código -->
|
||||
<form id="formCodigo" action="/IMPORTADORES/login/verificarCodigo" method="POST">
|
||||
<div class="mb-3">
|
||||
<label class="form-label" for="codigo">
|
||||
<i class="fas fa-key me-2"></i>Código de verificación
|
||||
<?php if (isset($_SESSION['success_message'])): ?>
|
||||
<div class="alert-modern alert-success">
|
||||
<i class="fas fa-check-circle me-2"></i>
|
||||
<?= $_SESSION['success_message'] ?>
|
||||
</div>
|
||||
<?php unset($_SESSION['success_message']); ?>
|
||||
<?php endif; ?>
|
||||
|
||||
<form action="/IMPORTADORES/login/verificar_codigo_recuperacion" method="POST" id="verificationForm">
|
||||
<div class="mb-4">
|
||||
<label for="codigo" class="form-label">
|
||||
<i class="fas fa-key text-muted"></i>
|
||||
Código de verificación
|
||||
</label>
|
||||
<div class="input-group">
|
||||
<input type="text" id="codigo" name="codigo" class="form-control" required pattern="\d{6}" maxlength="6" placeholder="000000" autocomplete="off" inputmode="numeric">
|
||||
<button type="button" class="clear-input" id="clearCode" title="Limpiar código">
|
||||
<i class="fas fa-times"></i>
|
||||
</button>
|
||||
</div>
|
||||
<div class="form-text">
|
||||
<input type="text" name="codigo" id="codigo" class="form-control code-input"
|
||||
required maxlength="6" minlength="6"
|
||||
placeholder="000000"
|
||||
pattern="[0-9]{6}"
|
||||
autocomplete="one-time-code">
|
||||
<small class="form-text text-muted mt-2">
|
||||
<i class="fas fa-info-circle me-1"></i>
|
||||
Ingresa el código de 6 dígitos que recibiste por correo
|
||||
</div>
|
||||
</small>
|
||||
</div>
|
||||
|
||||
<button type="submit" id="btnVerificar" class="btn btn-success w-100">
|
||||
<i class="fas fa-check me-2"></i>Verificar código
|
||||
|
||||
<button type="submit" class="btn-modern btn-primary-modern" id="submitBtn">
|
||||
<i class="fas fa-check"></i>
|
||||
Verificar Código
|
||||
</button>
|
||||
|
||||
<!-- Botón para enviar el código al correo de respaldo-->
|
||||
<div class="text-center mt-3">
|
||||
<button type="button" id="btnReenviarRespaldo" title="Enviar código al correo de respaldo">
|
||||
<i class="fas fa-paper-plane me-1"></i>
|
||||
¿No tienes acceso al correo? Reenviar al correo de respaldo
|
||||
</button>
|
||||
</div>
|
||||
<a href="/IMPORTADORES/login/recuperar" class="btn-modern btn-outline-modern">
|
||||
<i class="fas fa-arrow-left"></i>
|
||||
Volver
|
||||
</a>
|
||||
</form>
|
||||
|
||||
<!-- Mostrar mensajes -->
|
||||
<div id="mensaje" class="mt-3 text-center fw-bold">
|
||||
<?php if (isset($_SESSION['codigo_error'])): ?>
|
||||
<div class="text-danger fade-in">
|
||||
<i class="fas fa-exclamation-triangle me-2"></i>
|
||||
<?= $_SESSION['codigo_error'] ?>
|
||||
</div>
|
||||
<?php unset($_SESSION['codigo_error']); ?>
|
||||
<?php elseif (isset($_SESSION['codigo_exito'])): ?>
|
||||
<div class="text-success fade-in">
|
||||
<i class="fas fa-check-circle me-2"></i>
|
||||
<?= $_SESSION['codigo_exito'] ?>
|
||||
</div>
|
||||
<?php unset($_SESSION['codigo_exito']); ?>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
|
||||
<!-- Contador de intentos -->
|
||||
<div id="intentosInfo" class="mt-2 text-center text-muted" style="font-size: 12px;">
|
||||
<!-- Se llenará dinámicamente -->
|
||||
<div class="resend-section">
|
||||
<div class="resend-text">
|
||||
¿No recibiste el código?
|
||||
</div>
|
||||
<a href="#" class="resend-link" id="resendLink">
|
||||
<i class="fas fa-redo me-1"></i>
|
||||
Reenviar código
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- FOOTER -->
|
||||
<footer class="fixed-bottom">
|
||||
© <?= date('Y') ?> <?= htmlspecialchars($siglas) ?> · <?= htmlspecialchars($nombre) ?>
|
||||
<footer>
|
||||
<div class="container text-center">
|
||||
<div class="footer-text">
|
||||
© <?= date('Y') ?> <?= htmlspecialchars($siglas) ?> - <?= htmlspecialchars($nombre) ?><br>
|
||||
Verificación segura de identidad
|
||||
</div>
|
||||
</div>
|
||||
</footer>
|
||||
|
||||
<script>
|
||||
document.addEventListener('DOMContentLoaded', function () {
|
||||
const form = document.getElementById('formCodigo');
|
||||
const mensaje = document.getElementById('mensaje');
|
||||
const inputCodigo = document.getElementById('codigo');
|
||||
const btnVerificar = document.getElementById('btnVerificar');
|
||||
const btnReenviar = document.getElementById('btnReenviarRespaldo');
|
||||
const clearBtn = document.getElementById('clearCode');
|
||||
const intentosInfo = document.getElementById('intentosInfo');
|
||||
// Auto-format del código
|
||||
document.getElementById('codigo').addEventListener('input', function(e) {
|
||||
let value = e.target.value.replace(/\D/g, ''); // Solo números
|
||||
if (value.length > 6) value = value.substring(0, 6);
|
||||
e.target.value = value;
|
||||
});
|
||||
|
||||
let intentosRealizados = 0;
|
||||
const maxIntentos = 5;
|
||||
// Focus automático al cargar
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
document.getElementById('codigo').focus();
|
||||
});
|
||||
|
||||
// Auto-focus en el input al cargar
|
||||
inputCodigo.focus();
|
||||
|
||||
// Mostrar/ocultar botón de limpiar
|
||||
inputCodigo.addEventListener('input', function() {
|
||||
clearBtn.style.display = this.value.length > 0 ? 'block' : 'none';
|
||||
|
||||
// Limpiar mensajes previos
|
||||
limpiarMensajes();
|
||||
|
||||
// Remover clases de error/éxito
|
||||
inputCodigo.classList.remove('error', 'success');
|
||||
|
||||
// Solo permitir números
|
||||
this.value = this.value.replace(/[^0-9]/g, '');
|
||||
|
||||
// Actualizar contador visual
|
||||
actualizarContadorVisual();
|
||||
});
|
||||
|
||||
// Limpiar input
|
||||
clearBtn.addEventListener('click', function() {
|
||||
inputCodigo.value = '';
|
||||
inputCodigo.focus();
|
||||
clearBtn.style.display = 'none';
|
||||
limpiarMensajes();
|
||||
inputCodigo.classList.remove('error', 'success');
|
||||
});
|
||||
|
||||
// Permitir solo números en tiempo real
|
||||
inputCodigo.addEventListener('keypress', function(e) {
|
||||
if (!/[0-9]/.test(e.key) && !['Backspace', 'Delete', 'Tab', 'Enter'].includes(e.key)) {
|
||||
e.preventDefault();
|
||||
}
|
||||
});
|
||||
|
||||
// Auto-envío cuando se completen 6 dígitos (opcional)
|
||||
inputCodigo.addEventListener('input', function() {
|
||||
if (this.value.length === 6) {
|
||||
// Opcional: enviar automáticamente después de un breve delay
|
||||
// setTimeout(() => form.dispatchEvent(new Event('submit')), 500);
|
||||
}
|
||||
});
|
||||
|
||||
// Manejo del formulario
|
||||
form.addEventListener('submit', function(e) {
|
||||
e.preventDefault();
|
||||
|
||||
const codigo = inputCodigo.value.trim();
|
||||
if (!validarCodigo(codigo)) return;
|
||||
|
||||
enviarCodigo(codigo);
|
||||
});
|
||||
|
||||
// Validación del código
|
||||
function validarCodigo(codigo) {
|
||||
if (!codigo) {
|
||||
mostrarError("❌ Por favor, ingresa el código.");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (codigo.length !== 6) {
|
||||
mostrarError("❌ El código debe tener exactamente 6 dígitos.");
|
||||
inputCodigo.classList.add('error');
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!/^\d{6}$/.test(codigo)) {
|
||||
mostrarError("❌ El código solo debe contener números.");
|
||||
inputCodigo.classList.add('error');
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
// Manejar envío del formulario
|
||||
document.getElementById('verificationForm').addEventListener('submit', function(e) {
|
||||
e.preventDefault();
|
||||
|
||||
const submitBtn = document.getElementById('submitBtn');
|
||||
const codigo = document.getElementById('codigo').value.trim();
|
||||
|
||||
// Validar código
|
||||
if (!codigo || codigo.length !== 6 || !/^\d{6}$/.test(codigo)) {
|
||||
showAlert('error', 'El código debe tener exactamente 6 dígitos');
|
||||
return;
|
||||
}
|
||||
|
||||
// Enviar código para verificación
|
||||
function enviarCodigo(codigo) {
|
||||
// Mostrar estado de carga
|
||||
btnVerificar.disabled = true;
|
||||
btnVerificar.classList.add('loading');
|
||||
btnVerificar.innerHTML = '<span style="opacity: 0;">Verificando...</span>';
|
||||
|
||||
const formData = new FormData();
|
||||
formData.append('codigo', codigo);
|
||||
|
||||
fetch('/IMPORTADORES/login/verificarCodigo', {
|
||||
method: 'POST',
|
||||
body: formData,
|
||||
credentials: 'same-origin'
|
||||
})
|
||||
.then(response => response.json())
|
||||
.then(data => {
|
||||
if (data.blocked) {
|
||||
mostrarError(data.message); // <-- función personalizada con un div bonito
|
||||
setTimeout(() => {
|
||||
window.location.href = data.redirect;
|
||||
}, 4000);
|
||||
}
|
||||
|
||||
if (data.success) {
|
||||
// Código correcto
|
||||
mostrarExito(data.message);
|
||||
inputCodigo.classList.add('success');
|
||||
inputCodigo.disabled = true;
|
||||
|
||||
// Redirigir al formulario de cambio de contraseña
|
||||
setTimeout(() => {
|
||||
window.location.href = '/IMPORTADORES/login/cambiarPasswordVista';
|
||||
}, 1500);
|
||||
|
||||
} else {
|
||||
// Código incorrecto
|
||||
manejarCodigoIncorrecto(data);
|
||||
}
|
||||
})
|
||||
.catch(error => {
|
||||
console.error('Error:', error);
|
||||
mostrarError("❌ Error de conexión. Intenta nuevamente.");
|
||||
inputCodigo.classList.add('error');
|
||||
limpiarYEnfocarInput();
|
||||
})
|
||||
.finally(() => {
|
||||
// Restaurar botón
|
||||
btnVerificar.disabled = false;
|
||||
btnVerificar.classList.remove('loading');
|
||||
btnVerificar.innerHTML = '<i class="fas fa-check me-2"></i>Verificar código';
|
||||
});
|
||||
}
|
||||
|
||||
// Mostrar error de cuenta bloqueada
|
||||
function mostrarError(mensaje) {
|
||||
const mensajeDiv = document.getElementById('mensaje');
|
||||
mensajeDiv.innerHTML = `
|
||||
<div class="text-danger fade-in">
|
||||
<i class="fas fa-exclamation-triangle me-2"></i> ${mensaje}
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
// Manejar código incorrecto
|
||||
function manejarCodigoIncorrecto(data) {
|
||||
intentosRealizados++;
|
||||
|
||||
mostrarError(data.message);
|
||||
inputCodigo.classList.add('error');
|
||||
|
||||
// Limpiar input y enfocar para nuevo intento
|
||||
limpiarYEnfocarInput();
|
||||
|
||||
// Actualizar contador de intentos
|
||||
actualizarContadorIntentos();
|
||||
|
||||
// Si se bloqueó el código
|
||||
if (data.blocked) {
|
||||
bloquearFormulario();
|
||||
}
|
||||
}
|
||||
|
||||
// Limpiar input y enfocar
|
||||
function limpiarYEnfocarInput() {
|
||||
setTimeout(() => {
|
||||
inputCodigo.value = '';
|
||||
inputCodigo.classList.remove('error');
|
||||
inputCodigo.focus();
|
||||
clearBtn.style.display = 'none';
|
||||
}, 1500); // Esperar 1.5 segundos antes de limpiar
|
||||
}
|
||||
|
||||
// Actualizar contador visual
|
||||
function actualizarContadorVisual() {
|
||||
const longitud = inputCodigo.value.length;
|
||||
if (longitud > 0) {
|
||||
inputCodigo.setAttribute('placeholder', '0'.repeat(6 - longitud) + inputCodigo.value);
|
||||
} else {
|
||||
inputCodigo.setAttribute('placeholder', '000000');
|
||||
}
|
||||
}
|
||||
|
||||
// Actualizar contador de intentos
|
||||
function actualizarContadorIntentos() {
|
||||
if (intentosRealizados > 0) {
|
||||
const restantes = maxIntentos - intentosRealizados;
|
||||
intentosInfo.innerHTML = `
|
||||
<i class="fas fa-exclamation-triangle text-warning me-1"></i>
|
||||
Intentos restantes: <strong>${restantes}</strong> de ${maxIntentos}
|
||||
`;
|
||||
intentosInfo.classList.add('fade-in');
|
||||
}
|
||||
}
|
||||
|
||||
// Bloquear formulario cuando se exceden intentos
|
||||
function bloquearFormulario() {
|
||||
inputCodigo.disabled = true;
|
||||
btnVerificar.disabled = true;
|
||||
btnReenviar.disabled = true;
|
||||
|
||||
intentosInfo.innerHTML = `
|
||||
<i class="fas fa-ban text-danger me-1"></i>
|
||||
<span class="text-danger">Código bloqueado por seguridad</span>
|
||||
`;
|
||||
|
||||
// Redirigir a solicitar nuevo código después de 5 segundos
|
||||
setTimeout(() => {
|
||||
window.location.href = '/IMPORTADORES/login/recuperar';
|
||||
}, 5000);
|
||||
}
|
||||
|
||||
// Funciones de utilidad para mensajes
|
||||
function mostrarMensaje(texto, tipo) {
|
||||
mensaje.innerHTML = `<div class="${tipo} fade-in">${texto}</div>`;
|
||||
mensaje.scrollIntoView({ behavior: 'smooth', block: 'nearest' });
|
||||
}
|
||||
|
||||
function mostrarError(texto) {
|
||||
mostrarMensaje(`<i class="fas fa-exclamation-triangle me-2"></i>${texto}`, 'text-danger');
|
||||
}
|
||||
|
||||
function mostrarExito(texto) {
|
||||
mostrarMensaje(`<i class="fas fa-check-circle me-2"></i>${texto}`, 'text-success');
|
||||
}
|
||||
|
||||
function mostrarInfo(texto) {
|
||||
mostrarMensaje(`<i class="fas fa-info-circle me-2"></i>${texto}`, 'text-info');
|
||||
}
|
||||
|
||||
function limpiarMensajes() {
|
||||
mensaje.innerHTML = '';
|
||||
}
|
||||
|
||||
// Manejo del botón de reenvío
|
||||
btnReenviar.addEventListener("click", function() {
|
||||
const btnOriginalText = this.innerHTML;
|
||||
|
||||
// Deshabilitar botón temporalmente
|
||||
this.disabled = true;
|
||||
this.innerHTML = '<i class="fas fa-spinner fa-spin me-1"></i>Enviando...';
|
||||
|
||||
fetch("/IMPORTADORES/login/reenviarCodigo", {
|
||||
method: "POST",
|
||||
headers: {'Content-Type': 'application/x-www-form-urlencoded'},
|
||||
body: "accion=reenviarCodigo"
|
||||
})
|
||||
.then(response => response.json())
|
||||
.then(data => {
|
||||
mostrarInfo(data.message);
|
||||
|
||||
// Habilitar botón después de 30 segundos
|
||||
|
||||
// Deshabilitar botón y mostrar loading
|
||||
submitBtn.disabled = true;
|
||||
submitBtn.innerHTML = '<i class="fas fa-spinner fa-spin"></i> Verificando...';
|
||||
|
||||
// Enviar solicitud AJAX
|
||||
fetch('/IMPORTADORES/login/verificar_codigo_recuperacion', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/x-www-form-urlencoded',
|
||||
},
|
||||
body: 'codigo=' + encodeURIComponent(codigo)
|
||||
})
|
||||
.then(response => response.json())
|
||||
.then(data => {
|
||||
if (data.success) {
|
||||
// Éxito - redirigir a cambiar contraseña
|
||||
showAlert('success', data.message);
|
||||
setTimeout(() => {
|
||||
this.disabled = false;
|
||||
this.innerHTML = btnOriginalText;
|
||||
}, 30000);
|
||||
})
|
||||
.catch(error => {
|
||||
console.error('Error:', error);
|
||||
mostrarError("❌ Error al reenviar código.");
|
||||
window.location.href = '/IMPORTADORES/login/cambiar_password';
|
||||
}, 1500);
|
||||
} else {
|
||||
// Error - mostrar mensaje
|
||||
showAlert('error', data.message);
|
||||
// Rehabilitar botón
|
||||
submitBtn.disabled = false;
|
||||
submitBtn.innerHTML = '<i class="fas fa-check"></i> Verificar Código';
|
||||
|
||||
// Restaurar botón en caso de error
|
||||
this.disabled = false;
|
||||
this.innerHTML = btnOriginalText;
|
||||
});
|
||||
});
|
||||
|
||||
// Manejar tecla Enter en cualquier parte del formulario
|
||||
document.addEventListener('keypress', function(e) {
|
||||
if (e.key === 'Enter' && !btnVerificar.disabled) {
|
||||
form.dispatchEvent(new Event('submit'));
|
||||
// Si hay bloqueo, redirigir
|
||||
if (data.blocked) {
|
||||
setTimeout(() => {
|
||||
window.location.href = data.redirect || '/IMPORTADORES/login';
|
||||
}, 3000);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// Prevenir paste de contenido no numérico
|
||||
inputCodigo.addEventListener('paste', function(e) {
|
||||
e.preventDefault();
|
||||
const paste = (e.clipboardData || window.clipboardData).getData('text');
|
||||
const numericPaste = paste.replace(/[^0-9]/g, '').substring(0, 6);
|
||||
this.value = numericPaste;
|
||||
this.dispatchEvent(new Event('input'));
|
||||
})
|
||||
.catch(error => {
|
||||
console.error('Error:', error);
|
||||
showAlert('error', 'Error de conexión. Intenta nuevamente.');
|
||||
// Rehabilitar botón
|
||||
submitBtn.disabled = false;
|
||||
submitBtn.innerHTML = '<i class="fas fa-check"></i> Verificar Código';
|
||||
});
|
||||
});
|
||||
|
||||
// Manejar reenvío de código
|
||||
document.getElementById('resendLink').addEventListener('click', function(e) {
|
||||
e.preventDefault();
|
||||
|
||||
const link = this;
|
||||
const originalText = link.innerHTML;
|
||||
|
||||
// Mostrar loading
|
||||
link.innerHTML = '<i class="fas fa-spinner fa-spin me-1"></i> Reenviando...';
|
||||
link.style.pointerEvents = 'none';
|
||||
|
||||
// Enviar solicitud AJAX
|
||||
fetch('/IMPORTADORES/login/reenviar_codigo', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/x-www-form-urlencoded',
|
||||
}
|
||||
})
|
||||
.then(response => response.json())
|
||||
.then(data => {
|
||||
if (data.success) {
|
||||
showAlert('success', data.message);
|
||||
} else {
|
||||
showAlert('error', data.message);
|
||||
}
|
||||
|
||||
// Restaurar link
|
||||
link.innerHTML = originalText;
|
||||
link.style.pointerEvents = 'auto';
|
||||
})
|
||||
.catch(error => {
|
||||
console.error('Error:', error);
|
||||
showAlert('error', 'Error al reenviar código. Intenta nuevamente.');
|
||||
|
||||
// Restaurar link
|
||||
link.innerHTML = originalText;
|
||||
link.style.pointerEvents = 'auto';
|
||||
});
|
||||
});
|
||||
|
||||
function showAlert(type, message) {
|
||||
// Remover alertas anteriores
|
||||
const existingAlerts = document.querySelectorAll('.alert-modern');
|
||||
existingAlerts.forEach(alert => alert.remove());
|
||||
|
||||
// Crear nueva alerta
|
||||
const alertDiv = document.createElement('div');
|
||||
alertDiv.className = `alert-modern alert-${type === 'success' ? 'success' : 'danger'}`;
|
||||
alertDiv.innerHTML = `
|
||||
<i class="fas fa-${type === 'success' ? 'check-circle' : 'exclamation-triangle'} me-2"></i>
|
||||
${message}
|
||||
`;
|
||||
|
||||
// Insertar antes del formulario
|
||||
const form = document.getElementById('verificationForm');
|
||||
form.parentNode.insertBefore(alertDiv, form);
|
||||
|
||||
// Auto-remover después de 5 segundos si es error
|
||||
if (type === 'error') {
|
||||
setTimeout(() => {
|
||||
alertDiv.remove();
|
||||
}, 5000);
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
</body>
|
||||
|
||||
701
views/mve/lista.php
Normal file
701
views/mve/lista.php
Normal file
@@ -0,0 +1,701 @@
|
||||
<?php include __DIR__ . '/../partials/sidebar_importador.php'; ?>
|
||||
|
||||
<!DOCTYPE html>
|
||||
<html lang="es">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>📄 Manifestación de Valor - Pedimentos</title>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css" rel="stylesheet">
|
||||
<link href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0/css/all.min.css" rel="stylesheet">
|
||||
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/js/bootstrap.bundle.min.js"></script>
|
||||
<link href="https://cdn.datatables.net/1.13.4/css/jquery.dataTables.min.css" rel="stylesheet">
|
||||
<script src="https://code.jquery.com/jquery-3.7.0.min.js"></script>
|
||||
<script src="https://cdn.datatables.net/1.13.4/js/jquery.dataTables.min.js"></script>
|
||||
<script src="https://cdn.jsdelivr.net/npm/sweetalert2@11"></script>
|
||||
<style>
|
||||
table.dataTable thead th { background:#343a40; color:#fff; }
|
||||
body { font-family: 'Segoe UI', sans-serif; background-color: #f4f6f9; }
|
||||
.sidebar { width: 220px; height: 100vh; background-color: #343a40; position: fixed; top: 0; left: 0; padding-top: 56px; z-index: 1040; }
|
||||
.sidebar .nav-link { font-weight: bold; color: white; transition: all 0.3s ease; }
|
||||
.sidebar .nav-link:hover, .sidebar .nav-link.active { background-color: #495057; color: #fff; }
|
||||
.content { margin-top: 56px; padding: 40px 20px; position: relative; z-index: 1; background-color: #f4f6f9; transition: margin-left 0.3s ease;}
|
||||
@media (min-width: 768px) { .content { margin-left: 250px; } }
|
||||
@media (max-width: 767.98px) {
|
||||
.content { margin-left: 0; }
|
||||
.sidebar .nav-link { font-weight: normal; color: #343a40; background-color: transparent; }
|
||||
.sidebar .nav-link:hover, .sidebar .nav-link.active { background-color: #e9ecef; color: #212529; }
|
||||
}
|
||||
.card { border-radius: 12px; }
|
||||
.card-hover { transition: all 0.3s ease; cursor: pointer; }
|
||||
.card-hover:hover { transform: translateY(-8px) scale(1.02); box-shadow: 0 1rem 2rem rgba(0, 0, 0, 0.15) !important; }
|
||||
.fade-in-up { animation: fadeInUp 0.8s ease-out; }
|
||||
@keyframes fadeInUp {
|
||||
from { opacity: 0; transform: translateY(30px); }
|
||||
to { opacity: 1; transform: translateY(0); }
|
||||
}
|
||||
.btn-animated { transition: all 0.3s ease; position: relative; overflow: hidden; }
|
||||
.btn-animated:hover { transform: translateY(-2px); box-shadow: 0 5px 15px rgba(0, 0, 0, 0.2); }
|
||||
.btn-animated::before { content: ''; position: absolute; top: 0; left: -100%; width: 100%; height: 100%; background: linear-gradient(90deg, transparent, rgba(255, 255, 255, 0.2), transparent); transition: left 0.5s; }
|
||||
.btn-animated:hover::before { left: 100%; }
|
||||
|
||||
/* Estilos para facturas seleccionables */
|
||||
.factura-item {
|
||||
border: 1px solid #dee2e6;
|
||||
border-radius: 8px;
|
||||
padding: 12px;
|
||||
margin-bottom: 8px;
|
||||
cursor: pointer;
|
||||
transition: all 0.3s ease;
|
||||
background-color: #fff;
|
||||
}
|
||||
.factura-item:hover {
|
||||
border-color: #0d6efd;
|
||||
box-shadow: 0 2px 8px rgba(13, 110, 253, 0.15);
|
||||
transform: translateY(-2px);
|
||||
}
|
||||
.factura-item.selected {
|
||||
border-color: #198754;
|
||||
background-color: #d1e7dd;
|
||||
box-shadow: 0 3px 10px rgba(25, 135, 84, 0.2);
|
||||
}
|
||||
.factura-numero {
|
||||
font-weight: bold;
|
||||
color: #0d6efd;
|
||||
font-size: 1.1em;
|
||||
}
|
||||
.factura-item.selected .factura-numero {
|
||||
color: #198754;
|
||||
}
|
||||
.factura-details {
|
||||
font-size: 0.9em;
|
||||
color: #6c757d;
|
||||
margin-top: 4px;
|
||||
}
|
||||
.factura-item.selected .factura-details {
|
||||
color: #495057;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="content">
|
||||
<div class="d-flex justify-content-between align-items-center mb-4">
|
||||
<h4 class="animate__animated animate__fadeInDown title-glow">📄 Manifestación de Valor - Pedimentos</h4>
|
||||
</div>
|
||||
<div class="card p-3 shadow-sm card-hover position-relative h-auto fade-in-up">
|
||||
<div class="table-responsive">
|
||||
<table id="tablaMvePedimentos" class="display nowrap" style="width:100%">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>ID</th>
|
||||
<th>Pedimento</th>
|
||||
<th>RFC Cliente</th>
|
||||
<th>Nombre Cliente</th>
|
||||
<th>Fecha Registro</th>
|
||||
<th>Estado</th>
|
||||
<th>Acción</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<!-- Los datos se cargan via AJAX -->
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Modal para facturas relacionadas -->
|
||||
<div class="modal fade" id="modalFacturasMVE" tabindex="-1" aria-labelledby="modalFacturasMVELabel" aria-hidden="true">
|
||||
<div class="modal-dialog modal-xl">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header bg-primary text-white">
|
||||
<h5 class="modal-title" id="modalFacturasMVELabel">Manifestación de Valor - Pedimento <span id="modalPedimento"></span></h5>
|
||||
<button type="button" class="btn-close btn-close-white" data-bs-dismiss="modal" aria-label="Cerrar"></button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<div class="row">
|
||||
<div class="col-md-4">
|
||||
<div class="card">
|
||||
<div class="card-header bg-info text-white">
|
||||
<h6 class="mb-0">Facturas del Pedimento</h6>
|
||||
</div>
|
||||
<div class="card-body" style="max-height: 400px; overflow-y: auto;">
|
||||
<div id="facturasRelacionadasContainer">
|
||||
<!-- Aquí se cargan las facturas -->
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-8">
|
||||
<div class="card">
|
||||
<div class="card-header bg-success text-white d-flex justify-content-between align-items-center">
|
||||
<h6 class="mb-0">Catálogo COVE - Factura <span id="facturaSeleccionada">Seleccionar factura</span></h6>
|
||||
<div>
|
||||
<button class="btn btn-sm btn-outline-light" id="btnArt65">Art. 65 - Incrementables</button>
|
||||
<button class="btn btn-sm btn-light" id="btnArt66">Art. 66 - Decrementables</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card-body" style="max-height: 500px; overflow-y: auto;">
|
||||
<!-- Campos Art. 65 - Incrementables -->
|
||||
<div id="art65Section">
|
||||
<h6 class="text-success fw-bold mb-3">Artículo 65 - Incrementables</h6>
|
||||
|
||||
<!-- Gastos realizados por cuenta del Importador -->
|
||||
<div class="row mb-3">
|
||||
<div class="col-12">
|
||||
<label class="form-label fw-bold">Gastos de transporte, seguros y conexos (carga y descarga) erogados con posterioridad:</label>
|
||||
</div>
|
||||
<div class="col-md-4">
|
||||
<label class="form-label">Fecha de erogación:</label>
|
||||
<input type="date" class="form-control" name="fecha_transporte" id="fecha_transporte">
|
||||
</div>
|
||||
<div class="col-md-4">
|
||||
<label class="form-label">Importe (MN):</label>
|
||||
<input type="number" step="0.01" class="form-control" name="importe_transporte" id="importe_transporte">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Información de descuentos especiales -->
|
||||
<div class="row mb-3">
|
||||
<div class="col-12">
|
||||
<label class="form-label fw-bold">Información de descuentos especiales para aplicar en el momento de la compraventa:</label>
|
||||
</div>
|
||||
<div class="col-md-4">
|
||||
<label class="form-label">Fecha de erogación:</label>
|
||||
<input type="date" class="form-control" name="fecha_descuentos" id="fecha_descuentos">
|
||||
</div>
|
||||
<div class="col-md-4">
|
||||
<label class="form-label">Importe (MN):</label>
|
||||
<input type="number" step="0.01" class="form-control" name="importe_descuentos" id="importe_descuentos">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Gastos posteriores a la importación -->
|
||||
<div class="row mb-3">
|
||||
<div class="col-12">
|
||||
<label class="form-label fw-bold">Gastos posteriores a la importación por construcción, instalación, armado, montaje, mantenimiento o asistencia técnica:</label>
|
||||
</div>
|
||||
<div class="col-md-4">
|
||||
<label class="form-label">Fecha de erogación:</label>
|
||||
<input type="date" class="form-control" name="fecha_posteriores" id="fecha_posteriores">
|
||||
</div>
|
||||
<div class="col-md-4">
|
||||
<label class="form-label">Importe (MN):</label>
|
||||
<input type="number" step="0.01" class="form-control" name="importe_posteriores" id="importe_posteriores">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Contribuciones y cuotas compensatorias -->
|
||||
<div class="row mb-3">
|
||||
<div class="col-12">
|
||||
<label class="form-label fw-bold">Contribuciones y cuotas compensatorias aplicables en territorio nacional:</label>
|
||||
</div>
|
||||
<div class="col-md-4">
|
||||
<label class="form-label">Fecha de erogación:</label>
|
||||
<input type="date" class="form-control" name="fecha_contribuciones" id="fecha_contribuciones">
|
||||
</div>
|
||||
<div class="col-md-4">
|
||||
<label class="form-label">Importe (MN):</label>
|
||||
<input type="number" step="0.01" class="form-control" name="importe_contribuciones" id="importe_contribuciones">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Pagos de importador al vendedor -->
|
||||
<div class="row mb-3">
|
||||
<div class="col-12">
|
||||
<label class="form-label fw-bold">Pagos de importador al vendedor por dividendos u otros que no guarden relación directa con las mercancías importadas:</label>
|
||||
</div>
|
||||
<div class="col-md-4">
|
||||
<label class="form-label">Fecha de erogación:</label>
|
||||
<input type="date" class="form-control" name="fecha_pagos_vendedor" id="fecha_pagos_vendedor">
|
||||
</div>
|
||||
<div class="col-md-4">
|
||||
<label class="form-label">Importe (MN):</label>
|
||||
<input type="number" step="0.01" class="form-control" name="importe_pagos_vendedor" id="importe_pagos_vendedor">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Campos Art. 66 - Decrementables -->
|
||||
<div id="art66Section" style="display: none;">
|
||||
<h6 class="text-primary fw-bold mb-3">Artículo 66 - Decrementables</h6>
|
||||
|
||||
<!-- Comisiones y gastos de corretaje -->
|
||||
<div class="row mb-3">
|
||||
<div class="col-12">
|
||||
<label class="form-label fw-bold">Comisiones y gastos de corretaje, excepto comisiones de compra:</label>
|
||||
</div>
|
||||
<div class="col-md-3">
|
||||
<label class="form-label">Fecha de erogación:</label>
|
||||
<input type="date" class="form-control" name="fecha_comisiones" id="fecha_comisiones">
|
||||
</div>
|
||||
<div class="col-md-3">
|
||||
<label class="form-label">Importe (MN):</label>
|
||||
<input type="number" step="0.01" class="form-control" name="importe_comisiones" id="importe_comisiones">
|
||||
</div>
|
||||
<div class="col-md-3">
|
||||
<label class="form-label">¿Está a cargo del importador?:</label>
|
||||
<div class="form-check form-check-inline">
|
||||
<input class="form-check-input" type="radio" name="cargo_comisiones" id="cargo_comisiones_si" value="Si">
|
||||
<label class="form-check-label" for="cargo_comisiones_si">Sí</label>
|
||||
</div>
|
||||
<div class="form-check form-check-inline">
|
||||
<input class="form-check-input" type="radio" name="cargo_comisiones" id="cargo_comisiones_no" value="No">
|
||||
<label class="form-check-label" for="cargo_comisiones_no">No</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Costos de envases -->
|
||||
<div class="row mb-3">
|
||||
<div class="col-12">
|
||||
<label class="form-label fw-bold">Costo de los envases o embalajes que formen un todo con las mercancías:</label>
|
||||
</div>
|
||||
<div class="col-md-3">
|
||||
<label class="form-label">Fecha de erogación:</label>
|
||||
<input type="date" class="form-control" name="fecha_envases" id="fecha_envases">
|
||||
</div>
|
||||
<div class="col-md-3">
|
||||
<label class="form-label">Importe (MN):</label>
|
||||
<input type="number" step="0.01" class="form-control" name="importe_envases" id="importe_envases">
|
||||
</div>
|
||||
<div class="col-md-3">
|
||||
<label class="form-label">¿Está a cargo del importador?:</label>
|
||||
<div class="form-check form-check-inline">
|
||||
<input class="form-check-input" type="radio" name="cargo_envases" id="cargo_envases_si" value="Si">
|
||||
<label class="form-check-label" for="cargo_envases_si">Sí</label>
|
||||
</div>
|
||||
<div class="form-check form-check-inline">
|
||||
<input class="form-check-input" type="radio" name="cargo_envases" id="cargo_envases_no" value="No">
|
||||
<label class="form-check-label" for="cargo_envases_no">No</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Gastos de embalaje -->
|
||||
<div class="row mb-3">
|
||||
<div class="col-12">
|
||||
<label class="form-label fw-bold">Gastos de embalaje (mano de obra y/o materiales):</label>
|
||||
</div>
|
||||
<div class="col-md-3">
|
||||
<label class="form-label">Fecha de erogación:</label>
|
||||
<input type="date" class="form-control" name="fecha_embalaje" id="fecha_embalaje">
|
||||
</div>
|
||||
<div class="col-md-3">
|
||||
<label class="form-label">Importe (MN):</label>
|
||||
<input type="number" step="0.01" class="form-control" name="importe_embalaje" id="importe_embalaje">
|
||||
</div>
|
||||
<div class="col-md-3">
|
||||
<label class="form-label">¿Está a cargo del importador?:</label>
|
||||
<div class="form-check form-check-inline">
|
||||
<input class="form-check-input" type="radio" name="cargo_embalaje" id="cargo_embalaje_si" value="Si">
|
||||
<label class="form-check-label" for="cargo_embalaje_si">Sí</label>
|
||||
</div>
|
||||
<div class="form-check form-check-inline">
|
||||
<input class="form-check-input" type="radio" name="cargo_embalaje" id="cargo_embalaje_no" value="No">
|
||||
<label class="form-check-label" for="cargo_embalaje_no">No</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Gastos de transporte, seguros y conexos -->
|
||||
<div class="row mb-3">
|
||||
<div class="col-12">
|
||||
<label class="form-label fw-bold">Gastos de transporte, seguros y conexos (como manejo, carga y descarga) erogados antes de los momentos señalados en el artículo 56, fracción I de la Ley:</label>
|
||||
</div>
|
||||
<div class="col-md-3">
|
||||
<label class="form-label">Fecha de erogación:</label>
|
||||
<input type="date" class="form-control" name="fecha_transporte_dec" id="fecha_transporte_dec">
|
||||
</div>
|
||||
<div class="col-md-3">
|
||||
<label class="form-label">Importe (MN):</label>
|
||||
<input type="number" step="0.01" class="form-control" name="importe_transporte_dec" id="importe_transporte_dec">
|
||||
</div>
|
||||
<div class="col-md-3">
|
||||
<label class="form-label">¿Está a cargo del importador?:</label>
|
||||
<div class="form-check form-check-inline">
|
||||
<input class="form-check-input" type="radio" name="cargo_transporte_dec" id="cargo_transporte_dec_si" value="Si">
|
||||
<label class="form-check-label" for="cargo_transporte_dec_si">Sí</label>
|
||||
</div>
|
||||
<div class="form-check form-check-inline">
|
||||
<input class="form-check-input" type="radio" name="cargo_transporte_dec" id="cargo_transporte_dec_no" value="No">
|
||||
<label class="form-check-label" for="cargo_transporte_dec_no">No</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Trabajos de ingeniería -->
|
||||
<div class="row mb-3">
|
||||
<div class="col-12">
|
||||
<label class="form-label fw-bold">Trabajos de ingeniería, creación y perfeccionamiento, artísticos, diseños, planos y croquis realizados fuera del territorio nacional que sean necesarios para la producción de las mercancías importadas:</label>
|
||||
</div>
|
||||
<div class="col-md-3">
|
||||
<label class="form-label">Fecha de erogación:</label>
|
||||
<input type="date" class="form-control" name="fecha_ingenieria" id="fecha_ingenieria">
|
||||
</div>
|
||||
<div class="col-md-3">
|
||||
<label class="form-label">Importe (MN):</label>
|
||||
<input type="number" step="0.01" class="form-control" name="importe_ingenieria" id="importe_ingenieria">
|
||||
</div>
|
||||
<div class="col-md-3">
|
||||
<label class="form-label">¿Está a cargo del importador?:</label>
|
||||
<div class="form-check form-check-inline">
|
||||
<input class="form-check-input" type="radio" name="cargo_ingenieria" id="cargo_ingenieria_si" value="Si">
|
||||
<label class="form-check-label" for="cargo_ingenieria_si">Sí</label>
|
||||
</div>
|
||||
<div class="form-check form-check-inline">
|
||||
<input class="form-check-input" type="radio" name="cargo_ingenieria" id="cargo_ingenieria_no" value="No">
|
||||
<label class="form-check-label" for="cargo_ingenieria_no">No</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Regalías y derechos de licencia -->
|
||||
<div class="row mb-3">
|
||||
<div class="col-12">
|
||||
<label class="form-label fw-bold">Las regalías y derechos de licencia a pagar como condición de la venta de las mercancías a importar, que no estén incluidos en el precio pagado:</label>
|
||||
</div>
|
||||
<div class="col-md-3">
|
||||
<label class="form-label">Fecha de erogación:</label>
|
||||
<input type="date" class="form-control" name="fecha_regalias" id="fecha_regalias">
|
||||
</div>
|
||||
<div class="col-md-3">
|
||||
<label class="form-label">Importe (MN):</label>
|
||||
<input type="number" step="0.01" class="form-control" name="importe_regalias" id="importe_regalias">
|
||||
</div>
|
||||
<div class="col-md-3">
|
||||
<label class="form-label">¿Está a cargo del importador?:</label>
|
||||
<div class="form-check form-check-inline">
|
||||
<input class="form-check-input" type="radio" name="cargo_regalias" id="cargo_regalias_si" value="Si">
|
||||
<label class="form-check-label" for="cargo_regalias_si">Sí</label>
|
||||
</div>
|
||||
<div class="form-check form-check-inline">
|
||||
<input class="form-check-input" type="radio" name="cargo_regalias" id="cargo_regalias_no" value="No">
|
||||
<label class="form-check-label" for="cargo_regalias_no">No</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Valor de cualquier parte del producto -->
|
||||
<div class="row mb-3">
|
||||
<div class="col-12">
|
||||
<label class="form-label fw-bold">Valor de cualquier parte del producto de la enajenación, cesión o utilización, posteriores a la importación, que se revierta directa o indirectamente al vendedor:</label>
|
||||
</div>
|
||||
<div class="col-md-3">
|
||||
<label class="form-label">Fecha de erogación:</label>
|
||||
<input type="date" class="form-control" name="fecha_producto" id="fecha_producto">
|
||||
</div>
|
||||
<div class="col-md-3">
|
||||
<label class="form-label">Importe (MN):</label>
|
||||
<input type="number" step="0.01" class="form-control" name="importe_producto" id="importe_producto">
|
||||
</div>
|
||||
<div class="col-md-3">
|
||||
<label class="form-label">¿Está a cargo del importador?:</label>
|
||||
<div class="form-check form-check-inline">
|
||||
<input class="form-check-input" type="radio" name="cargo_producto" id="cargo_producto_si" value="Si">
|
||||
<label class="form-check-label" for="cargo_producto_si">Sí</label>
|
||||
</div>
|
||||
<div class="form-check form-check-inline">
|
||||
<input class="form-check-input" type="radio" name="cargo_producto" id="cargo_producto_no" value="No">
|
||||
<label class="form-check-label" for="cargo_producto_no">No</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mt-3">
|
||||
<button type="button" class="btn btn-primary" id="btnGuardarDatosMVE">
|
||||
<i class="fas fa-save me-1"></i>Guardar datos MVE
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Cancelar</button>
|
||||
<button type="button" class="btn btn-success" id="btnConfirmarMVE">Solicitar Manifestación ahora</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<script>
|
||||
$(document).ready(function() {
|
||||
var pedimentoSeleccionado = null;
|
||||
var facturaSeleccionadaId = null;
|
||||
|
||||
var tabla = $('#tablaMvePedimentos').DataTable({
|
||||
"processing": true,
|
||||
"serverSide": true,
|
||||
"searchDelay": 500,
|
||||
"deferRender": true,
|
||||
"ajax": {
|
||||
"url": "/IMPORTADORES/catalogo_pedimentos/ajax_lista",
|
||||
"type": "GET"
|
||||
},
|
||||
"columns": [
|
||||
{ "data": 0, "name": "IdPrevio" },
|
||||
{ "data": 1, "name": "Pedimento" },
|
||||
{ "data": 2, "name": "ClienteRFC" },
|
||||
{ "data": 3, "name": "ClienteNombre" },
|
||||
{ "data": 4, "name": "Timestamp" },
|
||||
{
|
||||
"data": 5,
|
||||
"name": "Status",
|
||||
"render": function(data, type, row) {
|
||||
if (data === 'Activo') {
|
||||
return '<span class="badge bg-success">Activo</span>';
|
||||
} else {
|
||||
return '<span class="badge bg-secondary">Inactivo</span>';
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"data": null,
|
||||
"orderable": false,
|
||||
"searchable": false,
|
||||
"render": function(data, type, row) {
|
||||
const id = row[0];
|
||||
const pedimento = row[1];
|
||||
return `<button class='btn btn-info btn-sm btn-animated' onclick='abrirModalFacturasMVE(${JSON.stringify(id)}, ${JSON.stringify(pedimento)})'>
|
||||
<i class='fas fa-file-invoice-dollar me-1'></i>Solicitar MVE
|
||||
</button>`;
|
||||
}
|
||||
}
|
||||
],
|
||||
"language": {
|
||||
"url": "https://cdn.datatables.net/plug-ins/1.13.4/i18n/es-ES.json"
|
||||
},
|
||||
"responsive": true,
|
||||
"order": [[4, "desc"]],
|
||||
"pageLength": 25,
|
||||
"lengthMenu": [[10, 25, 50, 100], [10, 25, 50, 100]]
|
||||
});
|
||||
|
||||
window.abrirModalFacturasMVE = function(id, pedimento) {
|
||||
pedimentoSeleccionado = id;
|
||||
$('#modalPedimento').text(pedimento);
|
||||
$('#facturasRelacionadasContainer').html('<div class="text-center py-3"><div class="spinner-border text-primary"></div><div class="mt-2">Cargando facturas...</div></div>');
|
||||
$.ajax({
|
||||
url: '/IMPORTADORES/solicitud_importacion/ajax_facturas_por_pedimento',
|
||||
method: 'GET',
|
||||
data: { id_pedimento: id },
|
||||
dataType: 'json',
|
||||
success: function(response) {
|
||||
if (response.success && response.facturas && response.facturas.length > 0) {
|
||||
var html = '';
|
||||
response.facturas.forEach(function(f) {
|
||||
html += `<div class="factura-item" onclick="seleccionarFactura('${f.numero_factura}', ${f.id_factura})">
|
||||
<div class="factura-numero">${f.numero_factura}</div>
|
||||
<div class="factura-details">Fecha: ${f.fecha} | Monto: $${parseFloat(f.monto).toLocaleString()}</div>
|
||||
</div>`;
|
||||
});
|
||||
$('#facturasRelacionadasContainer').html(html);
|
||||
} else {
|
||||
$('#facturasRelacionadasContainer').html('<div class="alert alert-warning">No hay facturas relacionadas a este pedimento.</div>');
|
||||
}
|
||||
},
|
||||
error: function() {
|
||||
$('#facturasRelacionadasContainer').html('<div class="alert alert-danger">Error al cargar facturas.</div>');
|
||||
}
|
||||
});
|
||||
var modal = new bootstrap.Modal(document.getElementById('modalFacturasMVE'));
|
||||
modal.show();
|
||||
};
|
||||
|
||||
window.seleccionarFactura = function(numeroFactura, idFactura) {
|
||||
if (!idFactura || idFactura === 'undefined' || idFactura === 'null') {
|
||||
Swal.fire({
|
||||
icon: 'error',
|
||||
title: 'Error',
|
||||
text: 'ID de factura no válido.',
|
||||
confirmButtonText: 'OK'
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
facturaSeleccionadaId = parseInt(idFactura);
|
||||
$('#facturaSeleccionada').text(numeroFactura);
|
||||
$('.factura-item').removeClass('selected');
|
||||
$(`.factura-item:contains(${numeroFactura})`).addClass('selected');
|
||||
$('#facturaSeleccionada').removeClass('text-muted').addClass('text-success fw-bold');
|
||||
cargarDatosMVEFactura(facturaSeleccionadaId);
|
||||
};
|
||||
|
||||
$('#btnArt65').on('click', function() {
|
||||
$('#art65Section').show();
|
||||
$('#art66Section').hide();
|
||||
$('#btnArt65').removeClass('btn-outline-light').addClass('btn-light');
|
||||
$('#btnArt66').removeClass('btn-light').addClass('btn-outline-light');
|
||||
});
|
||||
|
||||
$('#btnArt66').on('click', function() {
|
||||
$('#art65Section').hide();
|
||||
$('#art66Section').show();
|
||||
$('#btnArt65').removeClass('btn-light').addClass('btn-outline-light');
|
||||
$('#btnArt66').removeClass('btn-outline-light').addClass('btn-light');
|
||||
});
|
||||
|
||||
function cargarDatosMVEFactura(idFactura) {
|
||||
$.ajax({
|
||||
url: '/IMPORTADORES/mve/ajax_obtener_datos_factura',
|
||||
method: 'GET',
|
||||
data: { id_factura: idFactura },
|
||||
dataType: 'json',
|
||||
success: function(response) {
|
||||
if (response.success && response.datos) {
|
||||
if (response.datos.art65) {
|
||||
const art65 = response.datos.art65;
|
||||
$('#fecha_transporte').val(art65.fecha_transporte || '');
|
||||
$('#importe_transporte').val(art65.importe_transporte || '');
|
||||
$('#fecha_descuentos').val(art65.fecha_descuentos || '');
|
||||
$('#importe_descuentos').val(art65.importe_descuentos || '');
|
||||
$('#fecha_posteriores').val(art65.fecha_posteriores || '');
|
||||
$('#importe_posteriores').val(art65.importe_posteriores || '');
|
||||
$('#fecha_contribuciones').val(art65.fecha_contribuciones || '');
|
||||
$('#importe_contribuciones').val(art65.importe_contribuciones || '');
|
||||
$('#fecha_pagos_vendedor').val(art65.fecha_pagos_vendedor || '');
|
||||
$('#importe_pagos_vendedor').val(art65.importe_pagos_vendedor || '');
|
||||
}
|
||||
|
||||
if (response.datos.art66) {
|
||||
const art66 = response.datos.art66;
|
||||
$('#fecha_comisiones').val(art66.fecha_comisiones || '');
|
||||
$('#importe_comisiones').val(art66.importe_comisiones || '');
|
||||
$('input[name="cargo_comisiones"][value="' + (art66.cargo_comisiones || '') + '"]').prop('checked', true);
|
||||
$('#fecha_envases').val(art66.fecha_envases || '');
|
||||
$('#importe_envases').val(art66.importe_envases || '');
|
||||
$('input[name="cargo_envases"][value="' + (art66.cargo_envases || '') + '"]').prop('checked', true);
|
||||
$('#fecha_embalaje').val(art66.fecha_embalaje || '');
|
||||
$('#importe_embalaje').val(art66.importe_embalaje || '');
|
||||
$('input[name="cargo_embalaje"][value="' + (art66.cargo_embalaje || '') + '"]').prop('checked', true);
|
||||
$('#fecha_transporte_dec').val(art66.fecha_transporte_dec || '');
|
||||
$('#importe_transporte_dec').val(art66.importe_transporte_dec || '');
|
||||
$('input[name="cargo_transporte_dec"][value="' + (art66.cargo_transporte_dec || '') + '"]').prop('checked', true);
|
||||
$('#fecha_ingenieria').val(art66.fecha_ingenieria || '');
|
||||
$('#importe_ingenieria').val(art66.importe_ingenieria || '');
|
||||
$('input[name="cargo_ingenieria"][value="' + (art66.cargo_ingenieria || '') + '"]').prop('checked', true);
|
||||
$('#fecha_regalias').val(art66.fecha_regalias || '');
|
||||
$('#importe_regalias').val(art66.importe_regalias || '');
|
||||
$('input[name="cargo_regalias"][value="' + (art66.cargo_regalias || '') + '"]').prop('checked', true);
|
||||
$('#fecha_producto').val(art66.fecha_producto || '');
|
||||
$('#importe_producto').val(art66.importe_producto || '');
|
||||
$('input[name="cargo_producto"][value="' + (art66.cargo_producto || '') + '"]').prop('checked', true);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
$('#btnGuardarDatosMVE').on('click', function() {
|
||||
if (!facturaSeleccionadaId) {
|
||||
Swal.fire({
|
||||
icon: 'warning',
|
||||
title: 'Seleccionar factura',
|
||||
text: 'Por favor selecciona una factura primero.',
|
||||
confirmButtonText: 'OK'
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
if (!pedimentoSeleccionado) {
|
||||
Swal.fire({
|
||||
icon: 'warning',
|
||||
title: 'Error',
|
||||
text: 'No se ha seleccionado un pedimento válido.',
|
||||
confirmButtonText: 'OK'
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
const datosArt65 = {
|
||||
fecha_transporte: $('#fecha_transporte').val(),
|
||||
importe_transporte: $('#importe_transporte').val(),
|
||||
fecha_descuentos: $('#fecha_descuentos').val(),
|
||||
importe_descuentos: $('#importe_descuentos').val(),
|
||||
fecha_posteriores: $('#fecha_posteriores').val(),
|
||||
importe_posteriores: $('#importe_posteriores').val(),
|
||||
fecha_contribuciones: $('#fecha_contribuciones').val(),
|
||||
importe_contribuciones: $('#importe_contribuciones').val(),
|
||||
fecha_pagos_vendedor: $('#fecha_pagos_vendedor').val(),
|
||||
importe_pagos_vendedor: $('#importe_pagos_vendedor').val()
|
||||
};
|
||||
|
||||
const datosArt66 = {
|
||||
fecha_comisiones: $('#fecha_comisiones').val(),
|
||||
importe_comisiones: $('#importe_comisiones').val(),
|
||||
cargo_comisiones: $('input[name="cargo_comisiones"]:checked').val(),
|
||||
fecha_envases: $('#fecha_envases').val(),
|
||||
importe_envases: $('#importe_envases').val(),
|
||||
cargo_envases: $('input[name="cargo_envases"]:checked').val(),
|
||||
fecha_embalaje: $('#fecha_embalaje').val(),
|
||||
importe_embalaje: $('#importe_embalaje').val(),
|
||||
cargo_embalaje: $('input[name="cargo_embalaje"]:checked').val(),
|
||||
fecha_transporte_dec: $('#fecha_transporte_dec').val(),
|
||||
importe_transporte_dec: $('#importe_transporte_dec').val(),
|
||||
cargo_transporte_dec: $('input[name="cargo_transporte_dec"]:checked').val(),
|
||||
fecha_ingenieria: $('#fecha_ingenieria').val(),
|
||||
importe_ingenieria: $('#importe_ingenieria').val(),
|
||||
cargo_ingenieria: $('input[name="cargo_ingenieria"]:checked').val(),
|
||||
fecha_regalias: $('#fecha_regalias').val(),
|
||||
importe_regalias: $('#importe_regalias').val(),
|
||||
cargo_regalias: $('input[name="cargo_regalias"]:checked').val(),
|
||||
fecha_producto: $('#fecha_producto').val(),
|
||||
importe_producto: $('#importe_producto').val(),
|
||||
cargo_producto: $('input[name="cargo_producto"]:checked').val()
|
||||
};
|
||||
|
||||
$.ajax({
|
||||
url: '/IMPORTADORES/mve/ajax_guardar_datos_factura',
|
||||
method: 'POST',
|
||||
data: {
|
||||
id_factura: facturaSeleccionadaId,
|
||||
id_pedimento: pedimentoSeleccionado,
|
||||
datos_art65: JSON.stringify(datosArt65),
|
||||
datos_art66: JSON.stringify(datosArt66)
|
||||
},
|
||||
dataType: 'json',
|
||||
success: function(response) {
|
||||
if (response.success) {
|
||||
Swal.fire({
|
||||
icon: 'success',
|
||||
title: 'Datos guardados',
|
||||
text: 'Los datos de la Manifestación de Valor han sido guardados correctamente.',
|
||||
confirmButtonText: 'OK'
|
||||
});
|
||||
} else {
|
||||
Swal.fire({
|
||||
icon: 'error',
|
||||
title: 'Error',
|
||||
text: response.message || 'Error al guardar los datos.',
|
||||
confirmButtonText: 'OK'
|
||||
});
|
||||
}
|
||||
},
|
||||
error: function() {
|
||||
Swal.fire({
|
||||
icon: 'error',
|
||||
title: 'Error',
|
||||
text: 'Error de conexión al guardar los datos.',
|
||||
confirmButtonText: 'OK'
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
$('#btnConfirmarMVE').on('click', function() {
|
||||
$('#modalFacturasMVE').modal('hide');
|
||||
Swal.fire({
|
||||
icon: 'success',
|
||||
title: 'Solicitud enviada',
|
||||
text: 'Se ha solicitado la Manifestación de Valor para el pedimento ' + $('#modalPedimento').text(),
|
||||
confirmButtonText: 'OK'
|
||||
});
|
||||
});
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -86,6 +86,71 @@
|
||||
<span>Inicio</span>
|
||||
</a>
|
||||
</li>
|
||||
|
||||
<!-- CATÁLOGO DE PEDIMENTOS -->
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" data-bs-toggle="collapse" href="#submenuCatalogoPedimentosDesktop" role="button" aria-expanded="false" aria-controls="submenuCatalogoPedimentosDesktop">
|
||||
<svg class="icon" fill="currentColor" viewBox="0 0 20 20">
|
||||
<path fill-rule="evenodd" d="M3 4a1 1 0 011-1h12a1 1 0 011 1v2a1 1 0 01-1 1H4a1 1 0 01-1-1V4zM3 10a1 1 0 011-1h6a1 1 0 011 1v6a1 1 0 01-1 1H4a1 1 0 01-1-1v-6zM14 9a1 1 0 00-1 1v6a1 1 0 001 1h2a1 1 0 001-1v-6a1 1 0 00-1-1h-2z" clip-rule="evenodd"/>
|
||||
</svg>
|
||||
<span>Catálogo de Pedimentos</span>
|
||||
<svg class="collapse-icon" fill="none" stroke="currentColor" viewBox="0 0 10 6">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="m1 1 4 4 4-4"/>
|
||||
</svg>
|
||||
</a>
|
||||
<div class="collapse" id="submenuCatalogoPedimentosDesktop">
|
||||
<ul class="nav flex-column submenu">
|
||||
<li class="nav-item">
|
||||
<a href="/IMPORTADORES/catalogo_pedimentos/lista" class="nav-link">
|
||||
Ver catálogo
|
||||
</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a href="/IMPORTADORES/catalogo_pedimentos/buscar" class="nav-link">
|
||||
Buscar pedimentos
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</li>
|
||||
|
||||
<!-- CLAVES DE PEDIMENTOS -->
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" data-bs-toggle="collapse" href="#submenuClavesPedimentosDesktop" role="button" aria-expanded="false" aria-controls="submenuClavesPedimentosDesktop">
|
||||
<svg class="icon" fill="currentColor" viewBox="0 0 20 20">
|
||||
<path fill-rule="evenodd" d="M18 8A6 6 0 006 8c0 7-3 9-3 9h18s-3-2-3-9zM13.73 21a2 2 0 01-3.46 0" clip-rule="evenodd"/>
|
||||
</svg>
|
||||
<span>Claves de Pedimentos</span>
|
||||
<svg class="collapse-icon" fill="none" stroke="currentColor" viewBox="0 0 10 6">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="m1 1 4 4 4-4"/>
|
||||
</svg>
|
||||
</a>
|
||||
<div class="collapse" id="submenuClavesPedimentosDesktop">
|
||||
<ul class="nav flex-column submenu">
|
||||
<li class="nav-item">
|
||||
<a href="/IMPORTADORES/claves_pedimentos/lista" class="nav-link">
|
||||
Mis claves
|
||||
</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a href="/IMPORTADORES/claves_pedimentos/crear" class="nav-link">
|
||||
Nueva clave
|
||||
</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a href="/IMPORTADORES/claves_pedimentos/importar_csv" class="nav-link">
|
||||
Importar CSV
|
||||
</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a href="/IMPORTADORES/claves_pedimentos/inicializar_claves_usuario" class="nav-link">
|
||||
Inicializar claves
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</li>
|
||||
|
||||
<!-- LOCACIONES -->
|
||||
<li class="nav-item">
|
||||
<a href="/IMPORTADORES/importadores/lista" class="nav-link">
|
||||
@@ -269,13 +334,13 @@
|
||||
</ul>
|
||||
</div>
|
||||
</li>
|
||||
<!-- SOLICITUDES DE IMPORTACIÓN -->
|
||||
<!-- FACTURAS -->
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" data-bs-toggle="collapse" href="#submenuSolicitudesDesktop" role="button" aria-expanded="false" aria-controls="submenuSolicitudesDesktop">
|
||||
<svg class="icon" fill="currentColor" viewBox="0 0 20 20">
|
||||
<path fill-rule="evenodd" d="M4 4a2 2 0 012-2h4.586A2 2 0 0112 2.586L15.414 6A2 2 0 0116 7.414V16a2 2 0 01-2 2H6a2 2 0 01-2-2V4zm2 6a1 1 0 011-1h6a1 1 0 110 2H7a1 1 0 01-1-1zm1 3a1 1 0 100 2h6a1 1 0 100-2H7z" clip-rule="evenodd"/>
|
||||
</svg>
|
||||
<span>Solicitudes Importación</span>
|
||||
<span>Facturas</span>
|
||||
<svg class="collapse-icon" fill="none" stroke="currentColor" viewBox="0 0 10 6">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="m1 1 4 4 4-4"/>
|
||||
</svg>
|
||||
@@ -284,17 +349,28 @@
|
||||
<ul class="nav flex-column submenu">
|
||||
<li class="nav-item">
|
||||
<a href="/IMPORTADORES/solicitud_importacion/lista" class="nav-link">
|
||||
Mis solicitudes
|
||||
Mis facturas
|
||||
</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a href="/IMPORTADORES/solicitud_importacion/crear" class="nav-link">
|
||||
Nueva solicitud
|
||||
Nueva factura
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</li>
|
||||
|
||||
<!-- MVE (Manifestación de Valor Electrónica) -->
|
||||
<li class="nav-item">
|
||||
<a href="/IMPORTADORES/mve" class="nav-link">
|
||||
<svg class="icon" fill="currentColor" viewBox="0 0 20 20">
|
||||
<path fill-rule="evenodd" d="M4 2a2 2 0 00-2 2v12a2 2 0 002 2h8.586A2 2 0 0014 17.414L17.414 14A2 2 0 0018 12.586V4a2 2 0 00-2-2H4zm8 14H4V4h12v8h-2a2 2 0 00-2 2v2z" clip-rule="evenodd"/>
|
||||
</svg>
|
||||
<span>Manifestación Valor (MVE)</span>
|
||||
</a>
|
||||
</li>
|
||||
|
||||
<!-- EXPEDIENTE ELECTRÓNICO -->
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" data-bs-toggle="collapse" href="#submenuExpedienteDesktop" role="button" aria-expanded="false" aria-controls="submenuExpedienteDesktop">
|
||||
@@ -317,6 +393,7 @@
|
||||
</ul>
|
||||
</div>
|
||||
</li>
|
||||
|
||||
<!-- CONFIGURACIÓN -->
|
||||
<li class="nav-item">
|
||||
<a href="/IMPORTADORES/configuracion" class="nav-link">
|
||||
@@ -350,6 +427,34 @@
|
||||
<span>Inicio</span>
|
||||
</a>
|
||||
</li>
|
||||
|
||||
<!-- CATÁLOGO DE PEDIMENTOS -->
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" data-bs-toggle="collapse" href="#submenuCatalogoPedimentosMobile" role="button" aria-expanded="false" aria-controls="submenuCatalogoPedimentosMobile">
|
||||
<svg class="icon" fill="currentColor" viewBox="0 0 20 20">
|
||||
<path fill-rule="evenodd" d="M3 4a1 1 0 011-1h12a1 1 0 011 1v2a1 1 0 01-1 1H4a1 1 0 01-1-1V4zM3 10a1 1 0 011-1h6a1 1 0 011 1v6a1 1 0 01-1 1H4a1 1 0 01-1-1v-6zM14 9a1 1 0 00-1 1v6a1 1 0 001 1h2a1 1 0 001-1v-6a1 1 0 00-1-1h-2z" clip-rule="evenodd"/>
|
||||
</svg>
|
||||
<span>Catálogo de Pedimentos</span>
|
||||
<svg class="collapse-icon" fill="none" stroke="currentColor" viewBox="0 0 10 6">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="m1 1 4 4 4-4"/>
|
||||
</svg>
|
||||
</a>
|
||||
<div class="collapse" id="submenuCatalogoPedimentosMobile">
|
||||
<ul class="nav flex-column submenu">
|
||||
<li class="nav-item">
|
||||
<a href="/IMPORTADORES/catalogo_pedimentos/lista" class="nav-link">
|
||||
Ver catálogo
|
||||
</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a href="/IMPORTADORES/catalogo_pedimentos/buscar" class="nav-link">
|
||||
Buscar pedimentos
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</li>
|
||||
|
||||
<!-- LOCACIONES -->
|
||||
<li class="nav-item">
|
||||
<a href="/IMPORTADORES/importadores/lista" class="nav-link">
|
||||
@@ -533,13 +638,13 @@
|
||||
</ul>
|
||||
</div>
|
||||
</li>
|
||||
<!-- SOLICITUDES DE IMPORTACIÓN -->
|
||||
<!-- FACTURAS -->
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" data-bs-toggle="collapse" href="#submenuSolicitudesMobile" role="button" aria-expanded="false" aria-controls="submenuSolicitudesMobile">
|
||||
<svg class="icon" fill="currentColor" viewBox="0 0 20 20">
|
||||
<path fill-rule="evenodd" d="M4 4a2 2 0 012-2h4.586A2 2 0 0112 2.586L15.414 6A2 2 0 0116 7.414V16a2 2 0 01-2 2H6a2 2 0 01-2-2V4zm2 6a1 1 0 011-1h6a1 1 0 110 2H7a1 1 0 01-1-1zm1 3a1 1 0 100 2h6a1 1 0 100-2H7z" clip-rule="evenodd"/>
|
||||
</svg>
|
||||
<span>Solicitudes Importación</span>
|
||||
<span>Facturas</span>
|
||||
<svg class="collapse-icon" fill="none" stroke="currentColor" viewBox="0 0 10 6">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="m1 1 4 4 4-4"/>
|
||||
</svg>
|
||||
@@ -548,17 +653,28 @@
|
||||
<ul class="nav flex-column submenu">
|
||||
<li class="nav-item">
|
||||
<a href="/IMPORTADORES/solicitud_importacion/lista" class="nav-link">
|
||||
Mis solicitudes
|
||||
Mis facturas
|
||||
</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a href="/IMPORTADORES/solicitud_importacion/crear" class="nav-link">
|
||||
Nueva solicitud
|
||||
Nueva factura
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</li>
|
||||
|
||||
<!-- MVE (Manifestación de Valor Electrónica) -->
|
||||
<li class="nav-item">
|
||||
<a href="/IMPORTADORES/mve" class="nav-link">
|
||||
<svg class="icon" fill="currentColor" viewBox="0 0 20 20">
|
||||
<path fill-rule="evenodd" d="M4 2a2 2 0 00-2 2v12a2 2 0 002 2h8.586A2 2 0 0014 17.414L17.414 14A2 2 0 0018 12.586V4a2 2 0 00-2-2H4zm8 14H4V4h12v8h-2a2 2 0 00-2 2v2z" clip-rule="evenodd"/>
|
||||
</svg>
|
||||
<span>Manifestación Valor (MVE)</span>
|
||||
</a>
|
||||
</li>
|
||||
|
||||
<!-- EXPEDIENTE ELECTRÓNICO -->
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" data-bs-toggle="collapse" href="#submenuExpedienteMobile" role="button" aria-expanded="false" aria-controls="submenuExpedienteMobile">
|
||||
|
||||
@@ -8,20 +8,21 @@ $sql = "SELECT TOP 1 * FROM configuracion_sistema";
|
||||
$stmt = sqlsrv_query($conn, $sql);
|
||||
$config = sqlsrv_fetch_array($stmt, SQLSRV_FETCH_ASSOC);
|
||||
|
||||
// Variables con valores por defecto
|
||||
$nombre = $config['nombre_plataforma'] ?? 'Sistema Integral para Importadores de Hidrocarburos';
|
||||
$siglas = $config['siglas'] ?? 'SIIH';
|
||||
$logo = $config['logo_url'] ?? 'assets/img/logo_siih.png';
|
||||
$color1 = explode(',', $config['colores_primarios'] ?? '#003366,#0055A5')[0];
|
||||
$color2 = explode(',', $config['colores_primarios'] ?? '#003366,#0055A5')[1];
|
||||
// Variables con valores por defecto - Branding moderno
|
||||
$nombre = $config['nombre_plataforma'] ?? 'Sistema de Gestión de Importaciones';
|
||||
$siglas = $config['siglas'] ?? 'AduanaSoft';
|
||||
$logo = $config['logo_url'] ?? 'assets/img/logo_aduanasoft.png';
|
||||
$color1 = explode(',', $config['colores_primarios'] ?? '#0f172a,#334155')[0];
|
||||
$color2 = explode(',', $config['colores_primarios'] ?? '#0f172a,#334155')[1];
|
||||
?>
|
||||
|
||||
<!DOCTYPE html>
|
||||
<html lang="es">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title><?= htmlspecialchars($siglas) ?> | Registro de Importador</title>
|
||||
<title><?= htmlspecialchars($siglas) ?> | Registro</title>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<meta name="description" content="Registro de importadores y agencias aduanales en la plataforma integral de gestión">
|
||||
<!-- Bootstrap 5 -->
|
||||
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css" rel="stylesheet">
|
||||
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/js/bootstrap.bundle.min.js"></script>
|
||||
@@ -29,36 +30,289 @@ $color2 = explode(',', $config['colores_primarios'] ?? '#003366,#0055A5')
|
||||
<script src="https://cdn.jsdelivr.net/npm/sweetalert2@11"></script>
|
||||
<!-- Google reCAPTCHA v2 -->
|
||||
<script src="https://www.google.com/recaptcha/api.js" async defer></script>
|
||||
<!-- Google Fonts -->
|
||||
<link href="https://fonts.googleapis.com/css2?family=Poppins:wght@300;400;500;600;700&display=swap" rel="stylesheet">
|
||||
<style>
|
||||
body { background-color: #f4f6f9; font-family: 'Segoe UI', sans-serif; }
|
||||
.navbar { background-color: #fff; }
|
||||
.navbar .nav-link,
|
||||
.navbar-brand { color: <?= $color1 ?> !important; font-weight: 500; }
|
||||
.navbar .nav-link.active { background-color: #F9F9F9; border-radius: 5px; }
|
||||
.card-form { max-width: 900px; margin: 50px auto; padding: 40px; background: #fff; border-radius: 10px; box-shadow: 0 10px 30px rgba(0, 0, 0, 0.08); }
|
||||
.form-label { font-weight: 500; }
|
||||
.form-control,
|
||||
.form-select { border-radius: 6px; }
|
||||
footer { background-color: #e9ecef; padding: 20px; text-align: center; font-size: 14px; color: #666; }
|
||||
.recaptcha { margin-top: 20px; }
|
||||
.logo-siih { height: 90px; margin-right: 10px; }
|
||||
.form-toggle { margin-bottom: 2rem; text-align: center; }
|
||||
.form-toggle .btn { margin: 0 0.5rem; min-width: 150px; }
|
||||
.form-container { display: none; }
|
||||
.form-container.active { display: block; }
|
||||
.form-title { text-align: center; margin-bottom: 2rem; color: #333; }
|
||||
.transition-fade { transition: opacity 0.3s ease-in-out; }
|
||||
.alert { bottom: 30px; }
|
||||
:root {
|
||||
--primary: <?= $color1 ?>;
|
||||
--secondary: <?= $color2 ?>;
|
||||
--accent: #10b981;
|
||||
--surface: #ffffff;
|
||||
--background: #f8fafc;
|
||||
--text: #1e293b;
|
||||
--text-muted: #64748b;
|
||||
--border: #e2e8f0;
|
||||
}
|
||||
|
||||
* {
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
body {
|
||||
background: linear-gradient(135deg, var(--background) 0%, #e2e8f0 100%);
|
||||
font-family: 'Poppins', sans-serif;
|
||||
color: var(--text);
|
||||
line-height: 1.6;
|
||||
margin: 0;
|
||||
min-height: 100vh;
|
||||
}
|
||||
|
||||
.navbar {
|
||||
background: rgba(255, 255, 255, 0.95);
|
||||
backdrop-filter: blur(20px);
|
||||
border-bottom: 1px solid var(--border);
|
||||
padding: 1rem 0;
|
||||
}
|
||||
|
||||
.navbar-brand {
|
||||
font-weight: 600;
|
||||
font-size: 1.25rem;
|
||||
color: var(--primary) !important;
|
||||
}
|
||||
|
||||
.nav-link {
|
||||
color: var(--text-muted) !important;
|
||||
font-weight: 500;
|
||||
padding: 0.5rem 1rem !important;
|
||||
border-radius: 8px;
|
||||
transition: all 0.2s ease;
|
||||
}
|
||||
|
||||
.nav-link:hover, .nav-link.active {
|
||||
color: var(--primary) !important;
|
||||
background-color: #f1f5f9;
|
||||
}
|
||||
|
||||
.registration-container {
|
||||
max-width: 1000px;
|
||||
margin: 2rem auto;
|
||||
padding: 0 1rem;
|
||||
}
|
||||
|
||||
.card-form {
|
||||
background: var(--surface);
|
||||
border-radius: 20px;
|
||||
box-shadow: 0 20px 40px rgba(0, 0, 0, 0.08);
|
||||
padding: 3rem;
|
||||
border: 1px solid var(--border);
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.card-form::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
height: 4px;
|
||||
background: linear-gradient(90deg, var(--accent), var(--primary));
|
||||
}
|
||||
|
||||
.page-header {
|
||||
text-align: center;
|
||||
margin-bottom: 3rem;
|
||||
}
|
||||
|
||||
.page-title {
|
||||
font-size: 2.5rem;
|
||||
font-weight: 700;
|
||||
color: var(--text);
|
||||
margin-bottom: 0.5rem;
|
||||
}
|
||||
|
||||
.page-subtitle {
|
||||
color: var(--text-muted);
|
||||
font-size: 1.1rem;
|
||||
}
|
||||
|
||||
.form-toggle {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
margin-bottom: 3rem;
|
||||
background: var(--background);
|
||||
border-radius: 12px;
|
||||
padding: 0.5rem;
|
||||
gap: 0.5rem;
|
||||
}
|
||||
|
||||
.toggle-btn {
|
||||
flex: 1;
|
||||
max-width: 200px;
|
||||
padding: 1rem 1.5rem;
|
||||
border: none;
|
||||
border-radius: 8px;
|
||||
font-weight: 600;
|
||||
transition: all 0.3s ease;
|
||||
background: transparent;
|
||||
color: var(--text-muted);
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.toggle-btn.active {
|
||||
background: var(--surface);
|
||||
color: var(--primary);
|
||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
.form-container {
|
||||
display: none;
|
||||
animation: fadeIn 0.3s ease-in-out;
|
||||
}
|
||||
|
||||
.form-container.active {
|
||||
display: block;
|
||||
}
|
||||
|
||||
@keyframes fadeIn {
|
||||
from { opacity: 0; transform: translateY(20px); }
|
||||
to { opacity: 1; transform: translateY(0); }
|
||||
}
|
||||
|
||||
.form-section-title {
|
||||
font-size: 1.5rem;
|
||||
font-weight: 600;
|
||||
color: var(--text);
|
||||
margin-bottom: 2rem;
|
||||
text-align: center;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.form-section-title::after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
bottom: -8px;
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
width: 50px;
|
||||
height: 3px;
|
||||
background: var(--accent);
|
||||
border-radius: 2px;
|
||||
}
|
||||
|
||||
.form-label {
|
||||
font-weight: 500;
|
||||
color: var(--text);
|
||||
margin-bottom: 0.5rem;
|
||||
}
|
||||
|
||||
.form-control, .form-select {
|
||||
border: 2px solid var(--border);
|
||||
border-radius: 12px;
|
||||
padding: 0.75rem 1rem;
|
||||
font-size: 0.95rem;
|
||||
transition: all 0.2s ease;
|
||||
background: var(--surface);
|
||||
}
|
||||
|
||||
.form-control:focus, .form-select:focus {
|
||||
border-color: var(--accent);
|
||||
box-shadow: 0 0 0 3px rgba(16, 185, 129, 0.1);
|
||||
background: var(--surface);
|
||||
}
|
||||
|
||||
.section-divider {
|
||||
border: none;
|
||||
height: 2px;
|
||||
background: linear-gradient(90deg, transparent, var(--border), transparent);
|
||||
margin: 2.5rem 0;
|
||||
}
|
||||
|
||||
.section-subtitle {
|
||||
font-weight: 600;
|
||||
color: var(--text);
|
||||
font-size: 1.1rem;
|
||||
margin-bottom: 1.5rem;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.5rem;
|
||||
}
|
||||
|
||||
.recaptcha {
|
||||
margin: 2rem 0;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.btn-modern {
|
||||
padding: 12px 28px;
|
||||
border-radius: 12px;
|
||||
font-weight: 600;
|
||||
text-decoration: none;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
transition: all 0.3s ease;
|
||||
border: none;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.btn-primary-modern {
|
||||
background: linear-gradient(135deg, var(--accent), #059669);
|
||||
color: white;
|
||||
box-shadow: 0 4px 15px rgba(16, 185, 129, 0.3);
|
||||
}
|
||||
|
||||
.btn-primary-modern:hover {
|
||||
transform: translateY(-2px);
|
||||
box-shadow: 0 8px 25px rgba(16, 185, 129, 0.4);
|
||||
color: white;
|
||||
}
|
||||
|
||||
.info-alert {
|
||||
background: linear-gradient(135deg, #fef3c7, #fde68a);
|
||||
border: 1px solid #f59e0b;
|
||||
border-radius: 12px;
|
||||
padding: 1.5rem;
|
||||
margin-top: 2rem;
|
||||
color: #92400e;
|
||||
}
|
||||
|
||||
.info-alert .alert-icon {
|
||||
color: #f59e0b;
|
||||
margin-right: 0.5rem;
|
||||
}
|
||||
|
||||
footer {
|
||||
background: var(--text);
|
||||
color: #94a3b8;
|
||||
padding: 2rem 0;
|
||||
text-align: center;
|
||||
margin-top: 3rem;
|
||||
}
|
||||
|
||||
.footer-text {
|
||||
font-size: 0.9rem;
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.card-form {
|
||||
margin: 1rem;
|
||||
padding: 2rem 1.5rem;
|
||||
}
|
||||
|
||||
.page-title {
|
||||
font-size: 2rem;
|
||||
}
|
||||
|
||||
.form-toggle {
|
||||
flex-direction: column;
|
||||
gap: 0.5rem;
|
||||
}
|
||||
|
||||
.toggle-btn {
|
||||
max-width: none;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<!-- Navbar institucional -->
|
||||
<!-- NAVBAR -->
|
||||
<nav class="navbar navbar-expand-lg">
|
||||
<div class="container">
|
||||
<a class="navbar-brand d-flex align-items-center text-dark" href="#">
|
||||
<img src="/IMPORTADORES/public/<?= htmlspecialchars($logo) ?>" alt="Logo" class="logo-siih">
|
||||
| Registro de Importador
|
||||
<a class="navbar-brand" href="/IMPORTADORES/">
|
||||
<i class="fas fa-shipping-fast me-2"></i><?= htmlspecialchars($siglas) ?>
|
||||
</a>
|
||||
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarNav">
|
||||
<span class="navbar-toggler-icon"></span>
|
||||
@@ -72,166 +326,242 @@ $color2 = explode(',', $config['colores_primarios'] ?? '#003366,#0055A5')
|
||||
<a class="nav-link active" aria-current="page" href="/IMPORTADORES/registro">Registro</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="/IMPORTADORES/login">Inicio sesión</a>
|
||||
<a class="nav-link" href="/IMPORTADORES/login">Iniciar Sesión</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
|
||||
<!-- Contenedor de formularios -->
|
||||
<div class="container">
|
||||
<!-- CONTENEDOR PRINCIPAL -->
|
||||
<div class="registration-container">
|
||||
<div class="page-header">
|
||||
<h1 class="page-title">Únete a Nuestra Plataforma</h1>
|
||||
<p class="page-subtitle">Registra tu empresa o agencia aduanal para comenzar a gestionar importaciones</p>
|
||||
</div>
|
||||
|
||||
<div class="card-form">
|
||||
<!-- Botón para alternar entre formularios -->
|
||||
<!-- SELECTOR DE TIPO DE REGISTRO -->
|
||||
<div class="form-toggle">
|
||||
<button type="button" class="btn btn-white" id="btnToggle" onclick="alternarFormulario()">🔄 Registro de Agencia Aduanal</button>
|
||||
<button type="button" class="toggle-btn active" id="btnImportador" onclick="mostrarFormulario('importador')">
|
||||
<i class="fas fa-building me-2"></i>Importador
|
||||
</button>
|
||||
<button type="button" class="toggle-btn" id="btnAgencia" onclick="mostrarFormulario('agencia')">
|
||||
<i class="fas fa-handshake me-2"></i>Agencia Aduanal
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<!-- Formulario de Importador -->
|
||||
<div id="formImportadorContainer" class="form-container active transition-fade">
|
||||
<h3 class="form-title">Registro de Importador</h3>
|
||||
<!-- FORMULARIO DE IMPORTADOR -->
|
||||
<div id="formImportadorContainer" class="form-container active">
|
||||
<h3 class="form-section-title">Registro de Importador</h3>
|
||||
<form action="/IMPORTADORES/registro/enviarSoliImportador" method="POST" enctype="multipart/form-data" id="formRegistro">
|
||||
<div class="row g-3">
|
||||
<div class="row g-4">
|
||||
<div class="col-md-6">
|
||||
<label for="company_name" class="form-label">Nombre de la empresa *</label>
|
||||
<input type="text" class="form-control" name="company_name" id="company_name" required>
|
||||
<label for="company_name" class="form-label">
|
||||
<i class="fas fa-building text-muted me-2"></i>Nombre de la empresa *
|
||||
</label>
|
||||
<input type="text" class="form-control" name="company_name" id="company_name" placeholder="Ingresa el nombre de tu empresa" required>
|
||||
</div>
|
||||
|
||||
<div class="col-md-6">
|
||||
<label for="rfc" class="form-label">RFC *</label>
|
||||
<input type="text" class="form-control" name="rfc" maxlength="13" id="rfc" required>
|
||||
<label for="rfc" class="form-label">
|
||||
<i class="fas fa-id-card text-muted me-2"></i>RFC *
|
||||
</label>
|
||||
<input type="text" class="form-control" name="rfc" maxlength="13" id="rfc" placeholder="RFC de la empresa" required>
|
||||
</div>
|
||||
|
||||
<div class="col-md-6">
|
||||
<label for="email" class="form-label">Correo electrónico *</label>
|
||||
<input type="email" class="form-control" name="email" id="email" required>
|
||||
<label for="email" class="form-label">
|
||||
<i class="fas fa-envelope text-muted me-2"></i>Correo electrónico *
|
||||
</label>
|
||||
<input type="email" class="form-control" name="email" id="email" placeholder="correo@empresa.com" required>
|
||||
</div>
|
||||
|
||||
<div class="col-md-6">
|
||||
<label for="phone" class="form-label">Teléfono *</label>
|
||||
<input type="text" class="form-control" name="phone" id="phone" required>
|
||||
<label for="phone" class="form-label">
|
||||
<i class="fas fa-phone text-muted me-2"></i>Teléfono *
|
||||
</label>
|
||||
<input type="text" class="form-control" name="phone" id="phone" placeholder="123 4567890" required>
|
||||
</div>
|
||||
|
||||
<div class="col-md-12">
|
||||
<label for="opinion_file_importador" class="form-label">Padrón de importadores o encargo conferido (PDF) *</label>
|
||||
<div class="col-12">
|
||||
<label for="opinion_file_importador" class="form-label">
|
||||
<i class="fas fa-file-pdf text-muted me-2"></i>Padrón de Importadores o Encargo Conferido (PDF) *
|
||||
</label>
|
||||
<input type="file" class="form-control" name="opinion_file" id="opinion_file_importador" accept=".pdf" required>
|
||||
<div class="form-text">Sube tu documento oficial que acredite tu actividad como importador.</div>
|
||||
</div>
|
||||
|
||||
<div class="col-md-12 text-center recaptcha">
|
||||
<div class="col-12 recaptcha">
|
||||
<div class="g-recaptcha" id="recaptcha-importador" data-sitekey="6LfkMB8rAAAAAH-BEC1cMYij1HEhw4sTaEOl-UCA"></div>
|
||||
</div>
|
||||
|
||||
<div class="col-md-12 text-end mt-4">
|
||||
<button type="submit" class="btn btn-primary px-4 py-2">Enviar solicitud</button>
|
||||
<div class="col-12 text-center">
|
||||
<button type="submit" class="btn-modern btn-primary-modern">
|
||||
<i class="fas fa-paper-plane me-2"></i>
|
||||
Enviar Solicitud
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<!-- Formulario de Agencia Aduanal -->
|
||||
<div id="formAgenciaContainer" class="form-container transition-fade">
|
||||
<h3 class="form-title">Registro de Agencia Aduanal</h3>
|
||||
<!-- FORMULARIO DE AGENCIA ADUANAL -->
|
||||
<div id="formAgenciaContainer" class="form-container">
|
||||
<h3 class="form-section-title">Registro de Agencia Aduanal</h3>
|
||||
<form action="/IMPORTADORES/registro/enviarSoliAgencia" method="POST" enctype="multipart/form-data" id="formRegistroAgencia">
|
||||
<div class="row g-3">
|
||||
<div class="col-md-6">
|
||||
<label for="nombre_agencia" class="form-label">Nombre de la agencia *</label>
|
||||
<input type="text" class="form-control" name="nombre_agencia" id="nombre_agencia" required>
|
||||
<div class="row g-4">
|
||||
<!-- Información de la Agencia -->
|
||||
<div class="col-12">
|
||||
<div class="section-subtitle">
|
||||
<i class="fas fa-handshake text-primary"></i>
|
||||
Información de la Agencia
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-md-6">
|
||||
<label for="rfc_agencia" class="form-label">RFC *</label>
|
||||
<input type="text" class="form-control" name="rfc_agencia" maxlength="13" id="rfc_agencia" required>
|
||||
<label for="nombre_agencia" class="form-label">
|
||||
<i class="fas fa-building text-muted me-2"></i>Nombre de la agencia *
|
||||
</label>
|
||||
<input type="text" class="form-control" name="nombre_agencia" id="nombre_agencia" placeholder="Nombre de la agencia aduanal" required>
|
||||
</div>
|
||||
|
||||
<div class="col-md-6">
|
||||
<label for="correo" class="form-label">Correo electrónico *</label>
|
||||
<input type="email" class="form-control" name="correo" id="correo" required>
|
||||
<label for="rfc_agencia" class="form-label">
|
||||
<i class="fas fa-id-card text-muted me-2"></i>RFC *
|
||||
</label>
|
||||
<input type="text" class="form-control" name="rfc_agencia" maxlength="13" id="rfc_agencia" placeholder="RFC de la agencia" required>
|
||||
</div>
|
||||
|
||||
<div class="col-md-6">
|
||||
<label for="telefono" class="form-label">Teléfono *</label>
|
||||
<input type="text" class="form-control" name="telefono" maxlength="11" id="telefono" required>
|
||||
<label for="correo" class="form-label">
|
||||
<i class="fas fa-envelope text-muted me-2"></i>Correo electrónico *
|
||||
</label>
|
||||
<input type="email" class="form-control" name="correo" id="correo" placeholder="contacto@agencia.com" required>
|
||||
</div>
|
||||
<div class="col-md-12">
|
||||
<label for="direccion" class="form-label">Dirección *</label>
|
||||
|
||||
<div class="col-md-6">
|
||||
<label for="telefono" class="form-label">
|
||||
<i class="fas fa-phone text-muted me-2"></i>Teléfono *
|
||||
</label>
|
||||
<input type="text" class="form-control" name="telefono" maxlength="11" id="telefono" placeholder="123 4567890" required>
|
||||
</div>
|
||||
|
||||
<div class="col-12">
|
||||
<label for="direccion" class="form-label">
|
||||
<i class="fas fa-map-marker-alt text-muted me-2"></i>Dirección completa *
|
||||
</label>
|
||||
<input type="text" class="form-control" name="direccion" id="direccion"
|
||||
placeholder="Ej: Calle, num.externo, num.interno, colonia, municipio, código postal, estado" required>
|
||||
placeholder="Calle, num.externo, num.interno, colonia, municipio, código postal, estado" required>
|
||||
</div>
|
||||
<div class="col-md-12">
|
||||
<label for="opinion_file_agencia" class="form-label">Padrón de importadores o encargo conferido (PDF) *</label>
|
||||
|
||||
<div class="col-12">
|
||||
<label for="opinion_file_agencia" class="form-label">
|
||||
<i class="fas fa-file-pdf text-muted me-2"></i>Padrón de Importadores o Encargo Conferido (PDF) *
|
||||
</label>
|
||||
<input type="file" class="form-control" name="opinion_file" id="opinion_file_agencia" accept=".pdf" required>
|
||||
</div>
|
||||
<hr>
|
||||
<p><strong>Administrador de la Agencia</strong></p>
|
||||
<div class="col-md-6">
|
||||
<label for="nombre_admin" class="form-label">Nombre *</label>
|
||||
<input type="text" class="form-control" name="nombre_admin" id="nombre_admin" required>
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<label for="correo_admin" class="form-label">Correo Administrador *</label>
|
||||
<input type="email" class="form-control" name="correo_admin" id="correo_admin" required>
|
||||
|
||||
<hr class="section-divider">
|
||||
|
||||
<!-- Información del Administrador -->
|
||||
<div class="col-12">
|
||||
<div class="section-subtitle">
|
||||
<i class="fas fa-user-tie text-primary"></i>
|
||||
Administrador de la Agencia
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-md-12 text-center recaptcha">
|
||||
<div class="col-md-6">
|
||||
<label for="nombre_admin" class="form-label">
|
||||
<i class="fas fa-user text-muted me-2"></i>Nombre completo *
|
||||
</label>
|
||||
<input type="text" class="form-control" name="nombre_admin" id="nombre_admin" placeholder="Nombre del administrador" required>
|
||||
</div>
|
||||
|
||||
<div class="col-md-6">
|
||||
<label for="correo_admin" class="form-label">
|
||||
<i class="fas fa-envelope text-muted me-2"></i>Correo del administrador *
|
||||
</label>
|
||||
<input type="email" class="form-control" name="correo_admin" id="correo_admin" placeholder="admin@agencia.com" required>
|
||||
</div>
|
||||
|
||||
<div class="col-12 recaptcha">
|
||||
<div class="g-recaptcha" id="recaptcha-agencia" data-sitekey="6LfkMB8rAAAAAH-BEC1cMYij1HEhw4sTaEOl-UCA"></div>
|
||||
</div>
|
||||
|
||||
<div class="col-md-12 text-end mt-4">
|
||||
<button type="submit" class="btn btn-primary px-4 py-2">Enviar solicitud</button>
|
||||
<div class="col-12 text-center">
|
||||
<button type="submit" class="btn-modern btn-primary-modern">
|
||||
<i class="fas fa-paper-plane me-2"></i>
|
||||
Enviar Solicitud
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="container mb-4">
|
||||
<div class="alert alert-warning" role="alert">
|
||||
Una vez que envíes tu solicitud, será revisada manualmente por la agencia aduanal.
|
||||
Recibirás una notificación por correo electrónico cuando tu registro sea aprobado, junto con las instrucciones para activar tu cuenta e iniciar sesión en la plataforma.
|
||||
<!-- INFORMACIÓN ADICIONAL -->
|
||||
<div class="info-alert">
|
||||
<div class="d-flex align-items-start">
|
||||
<i class="fas fa-info-circle alert-icon"></i>
|
||||
<div>
|
||||
<strong>Proceso de Aprobación</strong><br>
|
||||
Una vez enviada tu solicitud, será revisada manualmente por nuestro equipo.
|
||||
Recibirás una notificación por correo electrónico cuando tu registro sea aprobado,
|
||||
junto con las instrucciones para activar tu cuenta e iniciar sesión en la plataforma.
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- FOOTER -->
|
||||
<footer class="fixed-bottom text-center py-3">
|
||||
© <?= date('Y') ?> <?= htmlspecialchars($siglas) ?> · <?= htmlspecialchars($nombre) ?>
|
||||
<footer>
|
||||
<div class="container text-center">
|
||||
<div class="footer-text">
|
||||
© <?= date('Y') ?> <?= htmlspecialchars($siglas) ?> - <?= htmlspecialchars($nombre) ?><br>
|
||||
Optimizando el comercio internacional
|
||||
</div>
|
||||
</div>
|
||||
</footer>
|
||||
|
||||
<!-- Validación JS -->
|
||||
<!-- SCRIPTS -->
|
||||
<script>
|
||||
let formularioActual = 'importador';
|
||||
|
||||
function alternarFormulario() {
|
||||
function mostrarFormulario(tipo) {
|
||||
const formImportador = document.getElementById('formImportadorContainer');
|
||||
const formAgencia = document.getElementById('formAgenciaContainer');
|
||||
const btnToggle = document.getElementById('btnToggle');
|
||||
const btnImportador = document.getElementById('btnImportador');
|
||||
const btnAgencia = document.getElementById('btnAgencia');
|
||||
|
||||
if (formularioActual === 'importador') {
|
||||
// Cambiar a formulario de agencia
|
||||
formImportador.classList.remove('active');
|
||||
formAgencia.classList.add('active');
|
||||
btnToggle.textContent = '🔄 Registro de Importador';
|
||||
formularioActual = 'agencia';
|
||||
} else {
|
||||
// Cambiar a formulario de importador
|
||||
formAgencia.classList.remove('active');
|
||||
// Resetear clases
|
||||
formImportador.classList.remove('active');
|
||||
formAgencia.classList.remove('active');
|
||||
btnImportador.classList.remove('active');
|
||||
btnAgencia.classList.remove('active');
|
||||
|
||||
if (tipo === 'importador') {
|
||||
formImportador.classList.add('active');
|
||||
btnToggle.textContent = '🔄 Registro de Agencia Aduanal';
|
||||
formularioActual = 'importador';
|
||||
btnImportador.classList.add('active');
|
||||
formularioActual = 'importador';
|
||||
} else {
|
||||
formAgencia.classList.add('active');
|
||||
btnAgencia.classList.add('active');
|
||||
formularioActual = 'agencia';
|
||||
}
|
||||
|
||||
// Resetear reCAPTCHA si está disponible
|
||||
// Resetear reCAPTCHA
|
||||
if (typeof grecaptcha !== 'undefined') {
|
||||
try{
|
||||
grecaptcha.reset(0); // Reset primer reCAPTCHA
|
||||
grecaptcha.reset(1); // Reset primer reCAPTCHA
|
||||
grecaptcha.reset(0);
|
||||
grecaptcha.reset(1);
|
||||
} catch (e) {
|
||||
console.log('Error resetting reCAPTCHA:', e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Función para obtener la respuesta del reCAPTCHA activo
|
||||
function getActiveRecaptchaResponse() {
|
||||
if (typeof grecaptcha !== 'undefined') {
|
||||
try {
|
||||
@@ -248,14 +578,7 @@ $color2 = explode(',', $config['colores_primarios'] ?? '#003366,#0055A5')
|
||||
return '';
|
||||
}
|
||||
|
||||
// Inicializar al cargar la página
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
// Por defecto mostrar el formulario de importador
|
||||
const formImportador = document.getElementById('formImportadorContainer');
|
||||
formImportador.classList.add('active');
|
||||
});
|
||||
|
||||
// Registro de Importador
|
||||
// Validación para formulario de importador
|
||||
document.getElementById('formRegistro').addEventListener('submit', function(e) {
|
||||
e.preventDefault();
|
||||
|
||||
@@ -271,65 +594,75 @@ $color2 = explode(',', $config['colores_primarios'] ?? '#003366,#0055A5')
|
||||
const rfcRegex = /^[A-ZÑ&]{3,4}\d{6}[A-Z0-9]{3}$/;
|
||||
const phoneRegex = /^\d{3}\s\d{7}$/;
|
||||
|
||||
// Validación de nombre
|
||||
// Validaciones
|
||||
if (!nombre) {
|
||||
Swal.fire({ icon: 'warning', title: 'Campo requerido', text: 'El nombre es obligatorio.', confirmButtonColor: '#dc3545' });
|
||||
Swal.fire({
|
||||
icon: 'warning',
|
||||
title: 'Campo requerido',
|
||||
text: 'El nombre de la empresa es obligatorio.',
|
||||
confirmButtonColor: '#10b981'
|
||||
});
|
||||
document.getElementById('company_name').focus();
|
||||
return;
|
||||
}
|
||||
// Validación de correo
|
||||
if (!email) {
|
||||
Swal.fire({ icon: 'warning', title: 'Campo requerido', text: 'El correo es obligatorio.', confirmButtonColor: '#dc3545' });
|
||||
|
||||
if (!email || !emailRegex.test(email)) {
|
||||
Swal.fire({
|
||||
icon: 'error',
|
||||
title: 'Correo inválido',
|
||||
text: 'Ingresa una dirección de correo válida.',
|
||||
confirmButtonColor: '#10b981'
|
||||
});
|
||||
document.getElementById('email').focus();
|
||||
return;
|
||||
}
|
||||
if (!emailRegex.test(email)) {
|
||||
Swal.fire({ icon: 'error', title: 'Correo inválido', text: 'No es una dirección de correo válida.', confirmButtonColor: '#dc3545' });
|
||||
document.getElementById('email').focus();
|
||||
return;
|
||||
}
|
||||
// Validación de RFC
|
||||
if (!rfc) {
|
||||
Swal.fire({ icon: 'warning', title: 'Campo requerido', text: 'El RFC es obligatorio.', confirmButtonColor: '#dc3545' });
|
||||
|
||||
if (!rfc || !rfcRegex.test(rfc)) {
|
||||
Swal.fire({
|
||||
icon: 'error',
|
||||
title: 'RFC inválido',
|
||||
text: 'El RFC debe tener el formato correcto (ej. ABC123456XYZ).',
|
||||
confirmButtonColor: '#10b981'
|
||||
});
|
||||
document.getElementById('rfc').focus();
|
||||
return;
|
||||
}
|
||||
if (!rfcRegex.test(rfc)) {
|
||||
Swal.fire({ icon: 'error', title: 'RFC inválido', text: 'El RFC debe tener 12 o 13 caracteres con el formato correcto (ej. ABC123456XYZ).', confirmButtonColor: '#dc3545' });
|
||||
document.getElementById('rfc').focus();
|
||||
return;
|
||||
}
|
||||
// Validación de teléfono
|
||||
if (!phone) {
|
||||
Swal.fire({ icon: 'warning', title: 'Campo requerido', text: 'El teléfono es obligatorio.', confirmButtonColor: '#dc3545' });
|
||||
|
||||
if (!phone || !phoneRegex.test(phone)) {
|
||||
Swal.fire({
|
||||
icon: 'error',
|
||||
title: 'Teléfono inválido',
|
||||
text: 'Formato: 3 dígitos (LADA), espacio, y 7 dígitos. Ejemplo: 123 4567890',
|
||||
confirmButtonColor: '#10b981'
|
||||
});
|
||||
document.getElementById('phone').focus();
|
||||
return;
|
||||
}
|
||||
if (!phoneRegex.test(phone)) {
|
||||
Swal.fire({ icon: 'error', title: 'Teléfono inválido', text: 'Debe tener 10 dígitos en el formato: 3 dígitos (LADA), espacio, y 7 dígitos. Ejemplo: 123 4567890', confirmButtonColor: '#dc3545' });
|
||||
document.getElementById('phone').focus();
|
||||
|
||||
if (!archivo || archivo.type !== 'application/pdf') {
|
||||
Swal.fire({
|
||||
icon: 'error',
|
||||
title: 'Archivo requerido',
|
||||
text: 'Sube el padrón de importadores en formato PDF.',
|
||||
confirmButtonColor: '#10b981'
|
||||
});
|
||||
return;
|
||||
}
|
||||
// Validación de archivo PDF
|
||||
if (!archivo) {
|
||||
Swal.fire({ icon: 'warning', title: 'Campo requerido', text: 'El padrón de importadores es obligatorio.', confirmButtonColor: '#dc3545' });
|
||||
document.getElementById('opinion_file_importador').focus();
|
||||
|
||||
if (!recaptcha || recaptcha.length === 0) {
|
||||
Swal.fire({
|
||||
icon: 'warning',
|
||||
title: 'Verificación requerida',
|
||||
text: 'Por favor completa la verificación reCAPTCHA.',
|
||||
confirmButtonColor: '#10b981'
|
||||
});
|
||||
return;
|
||||
}
|
||||
if (!archivo || archivo.type !== 'application/pdf') {
|
||||
Swal.fire({ icon: 'error', title: 'Formato de documento inválido', text: 'Solo se permite subir archivos PDF.', confirmButtonColor: '#dc3545' });
|
||||
return;
|
||||
}
|
||||
// Validación de reCAPTCHA
|
||||
if (!recaptcha || recaptcha.length === 0) {
|
||||
Swal.fire({ icon: 'warning', title: 'Verificación requerida', text: 'Por favor completa el reCAPTCHA.', confirmButtonColor: '#dc3545' });
|
||||
return;
|
||||
}
|
||||
// Si todas las validaciones pasan, el formulario se envía.
|
||||
|
||||
this.submit();
|
||||
});
|
||||
|
||||
// Registro de Agencia
|
||||
// Validación para formulario de agencia
|
||||
document.getElementById('formRegistroAgencia').addEventListener('submit', function(e) {
|
||||
e.preventDefault();
|
||||
|
||||
@@ -343,88 +676,71 @@ $color2 = explode(',', $config['colores_primarios'] ?? '#003366,#0055A5')
|
||||
const correo_admin = document.getElementById('correo_admin').value.trim();
|
||||
const recaptcha = getActiveRecaptchaResponse();
|
||||
|
||||
// Expresiones regulares
|
||||
const correoRegex = /^[a-zA-Z0-9._+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$/;
|
||||
const rfcRegex = /^[A-ZÑ&]{3,4}\d{6}[A-Z0-9]{3}$/;
|
||||
const telefonoRegex = /^\d{3}\s\d{7}$/;
|
||||
|
||||
// Validación de nombre
|
||||
if (!nombre) {
|
||||
Swal.fire({ icon: 'warning', title: 'Campo requerido', text: 'El nombre es obligatorio.', confirmButtonColor: '#dc3545' });
|
||||
document.getElementById('nombre_agencia').focus();
|
||||
// Validaciones básicas
|
||||
if (!nombre || !correo || !rfc_agencia || !telefono || !direccion || !nombre_admin || !correo_admin) {
|
||||
Swal.fire({
|
||||
icon: 'warning',
|
||||
title: 'Campos incompletos',
|
||||
text: 'Todos los campos son obligatorios.',
|
||||
confirmButtonColor: '#10b981'
|
||||
});
|
||||
return;
|
||||
}
|
||||
// Validación de correo
|
||||
if (!correo) {
|
||||
Swal.fire({ icon: 'warning', title: 'Campo requerido', text: 'El correo es obligatorio.', confirmButtonColor: '#dc3545' });
|
||||
document.getElementById('correo').focus();
|
||||
return;
|
||||
}
|
||||
if (!correoRegex.test(correo)) {
|
||||
Swal.fire({ icon: 'error', title: 'Correo inválido', text: 'No es una dirección de correo válida.', confirmButtonColor: '#dc3545' });
|
||||
document.getElementById('correo').focus();
|
||||
return;
|
||||
}
|
||||
// Validación de RFC
|
||||
if (!rfc_agencia) {
|
||||
Swal.fire({ icon: 'warning', title: 'Campo requerido', text: 'El RFC es obligatorio.', confirmButtonColor: '#dc3545' });
|
||||
document.getElementById('rfc_agencia').focus();
|
||||
|
||||
if (!correoRegex.test(correo) || !correoRegex.test(correo_admin)) {
|
||||
Swal.fire({
|
||||
icon: 'error',
|
||||
title: 'Correos inválidos',
|
||||
text: 'Verifica que los correos tengan formato válido.',
|
||||
confirmButtonColor: '#10b981'
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
if (!rfcRegex.test(rfc_agencia)) {
|
||||
Swal.fire({ icon: 'error', title: 'RFC inválido', text: 'El RFC debe tener 12 o 13 caracteres con el formato correcto (ej. ABC123456XYZ).', confirmButtonColor: '#dc3545' });
|
||||
document.getElementById('rfc_agencia').focus();
|
||||
return;
|
||||
}
|
||||
// Validación de teléfono
|
||||
if (!telefono) {
|
||||
Swal.fire({ icon: 'warning', title: 'Campo requerido', text: 'El teléfono es obligatorio.', confirmButtonColor: '#dc3545' });
|
||||
document.getElementById('telefono').focus();
|
||||
Swal.fire({
|
||||
icon: 'error',
|
||||
title: 'RFC inválido',
|
||||
text: 'El RFC debe tener el formato correcto.',
|
||||
confirmButtonColor: '#10b981'
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
if (!telefonoRegex.test(telefono)) {
|
||||
Swal.fire({ icon: 'error', title: 'Teléfono inválido', text: 'Debe tener 10 dígitos en el formato: 3 dígitos (LADA), espacio, y 7 dígitos (ej. 123 4567890).', confirmButtonColor: '#dc3545' });
|
||||
document.getElementById('telefono').focus();
|
||||
Swal.fire({
|
||||
icon: 'error',
|
||||
title: 'Teléfono inválido',
|
||||
text: 'Formato: 3 dígitos (LADA), espacio, y 7 dígitos.',
|
||||
confirmButtonColor: '#10b981'
|
||||
});
|
||||
return;
|
||||
}
|
||||
// Validación de dirección
|
||||
if (!direccion) {
|
||||
Swal.fire({ icon: 'warning', title: 'Campo requerido', text: 'La dirección es obligatoria.', confirmButtonColor: '#dc3545' });
|
||||
document.getElementById('direccion').focus();
|
||||
|
||||
if (!archivo || archivo.type !== 'application/pdf') {
|
||||
Swal.fire({
|
||||
icon: 'error',
|
||||
title: 'Archivo requerido',
|
||||
text: 'Sube el documento en formato PDF.',
|
||||
confirmButtonColor: '#10b981'
|
||||
});
|
||||
return;
|
||||
}
|
||||
// Validación de archivo PDF
|
||||
if (!archivo) {
|
||||
Swal.fire({ icon: 'warning', title: 'Campo requerido', text: 'El padrón de importadores es obligatorio.', confirmButtonColor: '#dc3545' });
|
||||
document.getElementById('opinion_file_agencia').focus();
|
||||
|
||||
if (!recaptcha || recaptcha.length === 0) {
|
||||
Swal.fire({
|
||||
icon: 'warning',
|
||||
title: 'Verificación requerida',
|
||||
text: 'Por favor completa la verificación reCAPTCHA.',
|
||||
confirmButtonColor: '#10b981'
|
||||
});
|
||||
return;
|
||||
}
|
||||
if (!archivo || archivo.type !== 'application/pdf') {
|
||||
Swal.fire({ icon: 'error', title: 'Formato de documento inválido', text: 'Solo se permite subir archivos PDF.', confirmButtonColor: '#dc3545' });
|
||||
return; }
|
||||
// Validación de administrador
|
||||
if (!nombre_admin) {
|
||||
Swal.fire({ icon: 'warning', title: 'Campo requerido', text: 'El nombre del administrador es obligatorio.', confirmButtonColor: '#dc3545' });
|
||||
document.getElementById('nombre_admin').focus();
|
||||
return;
|
||||
}
|
||||
// Validación de correo de administrador
|
||||
if (!correo_admin) {
|
||||
Swal.fire({ icon: 'warning', title: 'Campo requerido', text: 'El correo de administrador es obligatorio.', confirmButtonColor: '#dc3545' });
|
||||
document.getElementById('correo_admin').focus();
|
||||
return;
|
||||
}
|
||||
if (!correoRegex.test(correo_admin)) {
|
||||
Swal.fire({ icon: 'error', title: 'Correo inválido', text: 'No es una dirección de correo válida.', confirmButtonColor: '#dc3545' });
|
||||
document.getElementById('correo_admin').focus();
|
||||
return;
|
||||
}
|
||||
// Validación de reCAPTCHA
|
||||
if (!recaptcha || recaptcha.length === 0) {
|
||||
Swal.fire({ icon: 'warning', title: 'Verificación requerida', text: 'Por favor completa el reCAPTCHA.', confirmButtonColor: '#dc3545' });
|
||||
return;
|
||||
}
|
||||
// Si todas las validaciones pasan, el formulario se envía.
|
||||
|
||||
this.submit();
|
||||
});
|
||||
</script>
|
||||
|
||||
@@ -121,6 +121,25 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Nueva card para Ventanilla Única -->
|
||||
<div class="col-md-6 mb-4">
|
||||
<div class="card h-100 card-hover">
|
||||
<div class="card-body text-center">
|
||||
<div class="security-icon mb-3">
|
||||
<i class="fas fa-university text-warning"></i>
|
||||
</div>
|
||||
<h5 class="card-title">Ventanilla Única</h5>
|
||||
<p class="card-text text-muted">
|
||||
Configurar ejecutable y certificados para transmisiones de Manifestación de Valor Electrónica
|
||||
</p>
|
||||
<a href="/IMPORTADORES/seguridad/ventanillaUnica" class="btn btn-outline-warning btn-animated">
|
||||
<i class="fas fa-cogs me-2"></i>Configurar VU
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
349
views/seguridad/ventanilla_unica.php
Normal file
349
views/seguridad/ventanilla_unica.php
Normal file
@@ -0,0 +1,349 @@
|
||||
<?php include __DIR__ . '/../partials/sidebar_configuracion.php'; ?>
|
||||
<!DOCTYPE html>
|
||||
<html lang="es">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>🔐 Configuración Ventanilla Única</title>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css" rel="stylesheet">
|
||||
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/js/bootstrap.bundle.min.js"></script>
|
||||
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0/css/all.min.css">
|
||||
<script src="https://cdn.jsdelivr.net/npm/sweetalert2@11"></script>
|
||||
<style>
|
||||
body { font-family: 'Segoe UI', sans-serif; background-color: #f4f6f9; }
|
||||
.sidebar { width: 220px; height: 100vh; background-color: #343a40; position: fixed; top: 0; left: 0; padding-top: 56px; z-index: 1040; }
|
||||
.sidebar .nav-link { font-weight: bold; color: white; transition: all 0.3s ease; }
|
||||
.sidebar .nav-link:hover, .sidebar .nav-link.active { background-color: #495057; color: #fff; }
|
||||
.content { margin-top: 56px; padding: 40px 20px; position: relative; z-index: 1; background-color: #f4f6f9; transition: margin-left 0.3s ease; }
|
||||
@media (min-width: 768px) { .content { margin-left: 250px; } }
|
||||
@media (max-width: 767.98px) {
|
||||
.content { margin-left: 0; }
|
||||
.sidebar .nav-link { font-weight: normal; color: #343a40; background-color: transparent; }
|
||||
.sidebar .nav-link:hover, .sidebar .nav-link.active { background-color: #e9ecef; color: #212529; }
|
||||
}
|
||||
.card { border-radius: 12px; box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1); }
|
||||
.btn-animated { transition: all 0.3s ease; }
|
||||
.btn-animated:hover { transform: translateY(-2px); box-shadow: 0 5px 15px rgba(0, 0, 0, 0.2); }
|
||||
.form-floating { position: relative; }
|
||||
.form-floating > .form-control:focus ~ label { color: #0d6efd; }
|
||||
.security-header { background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); }
|
||||
.status-indicator { position: absolute; right: 10px; top: 50%; transform: translateY(-50%); }
|
||||
.path-input { font-family: 'Courier New', monospace; font-size: 0.9em; }
|
||||
.config-section { border-left: 4px solid #0d6efd; padding-left: 20px; margin-bottom: 30px; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="content">
|
||||
<!-- Header -->
|
||||
<div class="security-header text-white p-4 rounded-3 mb-4">
|
||||
<div class="d-flex align-items-center justify-content-between">
|
||||
<div>
|
||||
<h4 class="mb-1"><i class="fas fa-university me-2"></i>Configuración Ventanilla Única</h4>
|
||||
<p class="mb-0 opacity-75">Configuración para transmisiones de Manifestación de Valor Electrónica</p>
|
||||
</div>
|
||||
<div class="text-end">
|
||||
<a href="/IMPORTADORES/seguridad/index" class="btn btn-light btn-sm">
|
||||
<i class="fas fa-arrow-left me-1"></i>Regresar a Seguridad
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Mensajes de éxito/error -->
|
||||
<?php if (isset($_SESSION['config_success'])): ?>
|
||||
<div class="alert alert-success alert-dismissible fade show" role="alert">
|
||||
<i class="fas fa-check-circle me-2"></i>
|
||||
<?= htmlspecialchars($_SESSION['config_success']) ?>
|
||||
<button type="button" class="btn-close" data-bs-dismiss="alert"></button>
|
||||
</div>
|
||||
<?php unset($_SESSION['config_success']); ?>
|
||||
<?php endif; ?>
|
||||
|
||||
<?php if (isset($_SESSION['config_error'])): ?>
|
||||
<div class="alert alert-danger alert-dismissible fade show" role="alert">
|
||||
<i class="fas fa-exclamation-triangle me-2"></i>
|
||||
<?= htmlspecialchars($_SESSION['config_error']) ?>
|
||||
<button type="button" class="btn-close" data-bs-dismiss="alert"></button>
|
||||
</div>
|
||||
<?php unset($_SESSION['config_error']); ?>
|
||||
<?php endif; ?>
|
||||
|
||||
<div class="row">
|
||||
<div class="col-12">
|
||||
<div class="card">
|
||||
<div class="card-header bg-primary text-white">
|
||||
<h5 class="mb-0"><i class="fas fa-cogs me-2"></i>Configuración de Ventanilla Única</h5>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<form action="/IMPORTADORES/seguridad/guardarConfiguracionVU" method="POST" id="configForm" enctype="multipart/form-data">
|
||||
|
||||
<!-- Sección 1: Certificados FIEL -->
|
||||
<div class="config-section">
|
||||
<h6 class="text-success mb-3">
|
||||
<i class="fas fa-certificate me-2"></i>Certificados FIEL
|
||||
</h6>
|
||||
<div class="row">
|
||||
<div class="col-md-6">
|
||||
<div class="mb-3">
|
||||
<label for="archivo_key" class="form-label">
|
||||
<i class="fas fa-key me-1"></i>Archivo KEY *
|
||||
</label>
|
||||
<input type="file"
|
||||
class="form-control"
|
||||
id="archivo_key"
|
||||
name="archivo_key"
|
||||
accept=".key"
|
||||
<?= empty($configuracion_vu['ruta_archivo_key']) ? 'required' : '' ?>>
|
||||
<?php if (!empty($configuracion_vu['ruta_archivo_key'])): ?>
|
||||
<div class="mt-2 p-2 bg-light rounded">
|
||||
<small class="text-success">
|
||||
<i class="fas fa-check me-1"></i>
|
||||
Archivo actual: <?= basename($configuracion_vu['ruta_archivo_key']) ?>
|
||||
</small>
|
||||
<input type="hidden" name="ruta_archivo_key_actual" value="<?= htmlspecialchars($configuracion_vu['ruta_archivo_key']) ?>">
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
<small class="text-muted d-block mt-1">
|
||||
Solo archivos .key permitidos
|
||||
</small>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<div class="mb-3">
|
||||
<label for="archivo_cer" class="form-label">
|
||||
<i class="fas fa-certificate me-1"></i>Archivo CER *
|
||||
</label>
|
||||
<input type="file"
|
||||
class="form-control"
|
||||
id="archivo_cer"
|
||||
name="archivo_cer"
|
||||
accept=".cer"
|
||||
<?= empty($configuracion_vu['ruta_archivo_cer']) ? 'required' : '' ?>>
|
||||
<?php if (!empty($configuracion_vu['ruta_archivo_cer'])): ?>
|
||||
<div class="mt-2 p-2 bg-light rounded">
|
||||
<small class="text-success">
|
||||
<i class="fas fa-check me-1"></i>
|
||||
Archivo actual: <?= basename($configuracion_vu['ruta_archivo_cer']) ?>
|
||||
</small>
|
||||
<input type="hidden" name="ruta_archivo_cer_actual" value="<?= htmlspecialchars($configuracion_vu['ruta_archivo_cer']) ?>">
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
<small class="text-muted d-block mt-1">
|
||||
Solo archivos .cer permitidos
|
||||
</small>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-12">
|
||||
<div class="form-floating mb-3">
|
||||
<input type="password"
|
||||
class="form-control"
|
||||
id="clave_fiel"
|
||||
name="clave_fiel"
|
||||
value="<?= htmlspecialchars($configuracion_vu['clave_fiel']) ?>"
|
||||
placeholder="Contraseña FIEL"
|
||||
required>
|
||||
<label for="clave_fiel">
|
||||
<i class="fas fa-lock me-1"></i>Clave de Acceso FIEL *
|
||||
</label>
|
||||
<button type="button"
|
||||
class="btn btn-outline-secondary position-absolute end-0 top-50 translate-middle-y me-3"
|
||||
style="z-index: 10;"
|
||||
onclick="togglePassword('clave_fiel')">
|
||||
<i class="fas fa-eye" id="clave_fiel_icon"></i>
|
||||
</button>
|
||||
</div>
|
||||
<small class="text-muted">
|
||||
<i class="fas fa-shield-alt me-1"></i>
|
||||
Esta contraseña se almacena de forma encriptada
|
||||
</small>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Sección 2: Acceso Ventanilla Única -->
|
||||
<div class="config-section">
|
||||
<h6 class="text-warning mb-3">
|
||||
<i class="fas fa-user-cog me-2"></i>Acceso a Ventanilla Única
|
||||
</h6>
|
||||
<div class="row">
|
||||
<div class="col-md-6">
|
||||
<div class="form-floating mb-3">
|
||||
<input type="text"
|
||||
class="form-control"
|
||||
id="rfc_usuario_vu"
|
||||
name="rfc_usuario_vu"
|
||||
value="<?= htmlspecialchars($configuracion_vu['rfc_usuario_vu']) ?>"
|
||||
placeholder="RFC12345678901"
|
||||
maxlength="13"
|
||||
style="text-transform: uppercase;"
|
||||
required>
|
||||
<label for="rfc_usuario_vu">
|
||||
<i class="fas fa-id-card me-1"></i>RFC Usuario VU *
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<div class="form-floating mb-3">
|
||||
<input type="password"
|
||||
class="form-control"
|
||||
id="clave_webservice"
|
||||
name="clave_webservice"
|
||||
value="<?= htmlspecialchars($configuracion_vu['clave_webservice']) ?>"
|
||||
placeholder="Contraseña Web Service">
|
||||
<label for="clave_webservice">
|
||||
<i class="fas fa-globe me-1"></i>Clave de Acceso Web Service (Opcional)
|
||||
</label>
|
||||
<button type="button"
|
||||
class="btn btn-outline-secondary position-absolute end-0 top-50 translate-middle-y me-3"
|
||||
style="z-index: 10;"
|
||||
onclick="togglePassword('clave_webservice')">
|
||||
<i class="fas fa-eye" id="clave_webservice_icon"></i>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<small class="text-muted">
|
||||
<i class="fas fa-info-circle me-1"></i>
|
||||
Las contraseñas se almacenan de forma encriptada por seguridad
|
||||
</small>
|
||||
</div>
|
||||
|
||||
<!-- Información adicional -->
|
||||
<?php if ($configuracion_vu['fecha_creacion'] || $configuracion_vu['fecha_actualizacion']): ?>
|
||||
<div class="alert alert-info">
|
||||
<i class="fas fa-info-circle me-2"></i>
|
||||
<strong>Información de configuración:</strong><br>
|
||||
<?php if ($configuracion_vu['fecha_creacion']): ?>
|
||||
Creada: <?= $configuracion_vu['fecha_creacion']->format('d/m/Y H:i') ?><br>
|
||||
<?php endif; ?>
|
||||
<?php if ($configuracion_vu['fecha_actualizacion']): ?>
|
||||
Última actualización: <?= $configuracion_vu['fecha_actualizacion']->format('d/m/Y H:i') ?>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
|
||||
<!-- Botones de acción -->
|
||||
<div class="d-flex justify-content-between align-items-center">
|
||||
<button type="button"
|
||||
class="btn btn-outline-info btn-animated"
|
||||
onclick="probarConfiguracion()">
|
||||
<i class="fas fa-vial me-2"></i>Probar Configuración
|
||||
</button>
|
||||
<div>
|
||||
<button type="button"
|
||||
class="btn btn-outline-secondary me-2"
|
||||
onclick="limpiarFormulario()">
|
||||
<i class="fas fa-eraser me-1"></i>Limpiar
|
||||
</button>
|
||||
<button type="submit"
|
||||
class="btn btn-primary btn-animated">
|
||||
<i class="fas fa-save me-2"></i>Guardar Configuración
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
// Función para mostrar/ocultar contraseñas
|
||||
function togglePassword(fieldId) {
|
||||
const field = document.getElementById(fieldId);
|
||||
const icon = document.getElementById(fieldId + '_icon');
|
||||
|
||||
if (field.type === 'password') {
|
||||
field.type = 'text';
|
||||
icon.classList.remove('fa-eye');
|
||||
icon.classList.add('fa-eye-slash');
|
||||
} else {
|
||||
field.type = 'password';
|
||||
icon.classList.remove('fa-eye-slash');
|
||||
icon.classList.add('fa-eye');
|
||||
}
|
||||
}
|
||||
|
||||
// Función para limpiar el formulario
|
||||
function limpiarFormulario() {
|
||||
if (confirm('¿Estás seguro de que deseas limpiar todos los campos?')) {
|
||||
document.getElementById('configForm').reset();
|
||||
}
|
||||
}
|
||||
|
||||
// Función para probar la configuración
|
||||
function probarConfiguracion() {
|
||||
const btn = event.target;
|
||||
const originalHtml = btn.innerHTML;
|
||||
|
||||
btn.innerHTML = '<i class="fas fa-spinner fa-spin me-2"></i>Probando...';
|
||||
btn.disabled = true;
|
||||
|
||||
fetch('/IMPORTADORES/seguridad/probarConexionVU', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
}
|
||||
})
|
||||
.then(response => response.json())
|
||||
.then(data => {
|
||||
if (data.success) {
|
||||
Swal.fire({
|
||||
icon: 'success',
|
||||
title: 'Configuración Válida',
|
||||
text: data.message,
|
||||
confirmButtonColor: '#28a745'
|
||||
});
|
||||
} else {
|
||||
Swal.fire({
|
||||
icon: 'error',
|
||||
title: 'Error en Configuración',
|
||||
text: data.message,
|
||||
confirmButtonColor: '#dc3545'
|
||||
});
|
||||
}
|
||||
})
|
||||
.catch(error => {
|
||||
Swal.fire({
|
||||
icon: 'error',
|
||||
title: 'Error de Conexión',
|
||||
text: 'No se pudo probar la configuración. Verifica tu conexión.',
|
||||
confirmButtonColor: '#dc3545'
|
||||
});
|
||||
})
|
||||
.finally(() => {
|
||||
btn.innerHTML = originalHtml;
|
||||
btn.disabled = false;
|
||||
});
|
||||
}
|
||||
|
||||
// Validación en tiempo real de rutas de archivos
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
const pathInputs = document.querySelectorAll('.path-input');
|
||||
|
||||
pathInputs.forEach(input => {
|
||||
input.addEventListener('blur', function() {
|
||||
const statusId = this.id.replace('ruta_', '') + '-status';
|
||||
const statusElement = document.getElementById(statusId);
|
||||
|
||||
if (this.value.trim()) {
|
||||
// Aquí podrías agregar validación adicional
|
||||
statusElement.innerHTML = '<i class="fas fa-check text-success"></i>';
|
||||
} else {
|
||||
statusElement.innerHTML = '';
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// Convertir RFC a mayúsculas automáticamente
|
||||
document.getElementById('rfc_usuario_vu').addEventListener('input', function() {
|
||||
this.value = this.value.toUpperCase();
|
||||
});
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
File diff suppressed because it is too large
Load Diff
@@ -96,7 +96,7 @@
|
||||
<body>
|
||||
|
||||
<div class="content">
|
||||
<h4 class="mb-4 animate__animated animate__fadeInDown title_glow">✏️ Editar Solicitud #<?= (int)$factura['id_solicitud'] ?></h4>
|
||||
<h4 class="mb-4 animate__animated animate__fadeInDown title_glow">✏️ Editar factura #<?= (int)$factura['id_solicitud'] ?></h4>
|
||||
<div class="card p-4 bg-white shadow-sm card-hover position-relative h-auto fade-in-up">
|
||||
<form id="solicitudForm" action="/IMPORTADORES/solicitud_importacion/actualizar" method="POST" enctype="multipart/form-data">
|
||||
<input type="hidden" name="id_solicitud" value="<?= (int)$factura['id_solicitud'] ?>">
|
||||
|
||||
790
views/templates_rapidos/crear.php
Normal file
790
views/templates_rapidos/crear.php
Normal file
@@ -0,0 +1,790 @@
|
||||
<?php include __DIR__ . '/../partials/sidebar_importador.php'; ?>
|
||||
|
||||
<!DOCTYPE html>
|
||||
<html lang="es">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>➕ Nuevo Template Rápido</title>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css" rel="stylesheet">
|
||||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/choices.js/public/assets/styles/choices.min.css"/>
|
||||
<script src="https://cdn.jsdelivr.net/npm/sweetalert2@11"></script>
|
||||
<script src="https://code.jquery.com/jquery-3.7.1.min.js"></script>
|
||||
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0/css/all.min.css">
|
||||
<style>
|
||||
body { font-family: 'Segoe UI', sans-serif; background-color: #f4f6f9; }
|
||||
.sidebar { width: 220px; height: 100vh; background-color: #343a40; position: fixed; top: 0; left: 0; padding-top: 56px; z-index: 1040; }
|
||||
.sidebar .nav-link { font-weight: bold; color: white; transition: all 0.3s ease; }
|
||||
.sidebar .nav-link:hover, .sidebar .nav-link.active { background-color: #495057; color: #fff; }
|
||||
.content { margin-top: 56px; padding: 40px 20px; position: relative; z-index: 1; background-color: #f4f6f9; transition: margin-left 0.3s ease; }
|
||||
@media (min-width: 768px) { .content { margin-left: 250px; } }
|
||||
@media (max-width: 767.98px) {
|
||||
.content { margin-left: 0; }
|
||||
.sidebar .nav-link { font-weight: normal; color: #343a40; background-color: transparent; }
|
||||
.sidebar .nav-link:hover, .sidebar .nav-link.active { background-color: #e9ecef; color: #212529; }
|
||||
}
|
||||
.card {
|
||||
border-radius: 12px;
|
||||
box-shadow: 0 4px 12px rgba(0,0,0,0.1);
|
||||
border: none;
|
||||
}
|
||||
.btn-animated {
|
||||
transition: all 0.3s ease;
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
}
|
||||
.btn-animated:hover {
|
||||
transform: translateY(-2px);
|
||||
box-shadow: 0 5px 15px rgba(0, 0, 0, 0.2);
|
||||
}
|
||||
.form-control:focus, .form-select:focus {
|
||||
border-color: #0d6efd;
|
||||
box-shadow: 0 0 0 0.2rem rgba(13, 110, 253, 0.25);
|
||||
}
|
||||
.emoji-picker {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(8, 1fr);
|
||||
gap: 8px;
|
||||
padding: 15px;
|
||||
max-height: 250px;
|
||||
overflow-y: auto;
|
||||
border: 1px solid #dee2e6;
|
||||
border-radius: 8px;
|
||||
background: white;
|
||||
box-shadow: 0 2px 10px rgba(0,0,0,0.1);
|
||||
}
|
||||
.emoji-item {
|
||||
padding: 10px;
|
||||
text-align: center;
|
||||
cursor: pointer;
|
||||
border-radius: 6px;
|
||||
transition: all 0.2s ease;
|
||||
font-size: 20px;
|
||||
border: 2px solid transparent;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
min-height: 40px;
|
||||
}
|
||||
.emoji-item:hover {
|
||||
background-color: #e3f2fd;
|
||||
border-color: #2196f3;
|
||||
transform: scale(1.1);
|
||||
}
|
||||
.emoji-item.selected {
|
||||
background-color: #1976d2;
|
||||
color: white;
|
||||
border-color: #0d47a1;
|
||||
}
|
||||
.preview-card {
|
||||
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||||
color: white;
|
||||
border-radius: 12px;
|
||||
padding: 20px;
|
||||
margin-top: 10px;
|
||||
box-shadow: 0 4px 15px rgba(0,0,0,0.2);
|
||||
}
|
||||
.template-preview-item {
|
||||
background: rgba(255,255,255,0.2);
|
||||
border: 1px solid rgba(255,255,255,0.3);
|
||||
color: white;
|
||||
padding: 8px 14px;
|
||||
border-radius: 25px;
|
||||
font-size: 11px;
|
||||
font-weight: 500;
|
||||
margin: 3px;
|
||||
display: inline-block;
|
||||
backdrop-filter: blur(10px);
|
||||
}
|
||||
.section-header {
|
||||
background: linear-gradient(135deg, #f8f9fa 0%, #e9ecef 100%);
|
||||
padding: 15px 20px;
|
||||
margin: -15px -15px 20px -15px;
|
||||
border-radius: 8px 8px 0 0;
|
||||
border-bottom: 1px solid #dee2e6;
|
||||
}
|
||||
.section-header h6 {
|
||||
margin: 0;
|
||||
color: #495057;
|
||||
font-weight: 600;
|
||||
}
|
||||
.icon-preview {
|
||||
font-size: 24px;
|
||||
margin-right: 8px;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
background: rgba(255,255,255,0.1);
|
||||
border-radius: 8px;
|
||||
border: 1px solid rgba(255,255,255,0.2);
|
||||
}
|
||||
.form-label {
|
||||
font-weight: 600;
|
||||
color: #495057;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
.choices__inner {
|
||||
min-height: 42px !important;
|
||||
border: 1px solid #ced4da !important;
|
||||
border-radius: 6px !important;
|
||||
}
|
||||
.choices__inner:focus {
|
||||
border-color: #0d6efd !important;
|
||||
box-shadow: 0 0 0 0.2rem rgba(13, 110, 253, 0.25) !important;
|
||||
}
|
||||
.loading-spinner {
|
||||
display: inline-block;
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
border: 3px solid rgba(255,255,255,.3);
|
||||
border-radius: 50%;
|
||||
border-top-color: #fff;
|
||||
animation: spin 1s ease-in-out infinite;
|
||||
}
|
||||
@keyframes spin {
|
||||
to { transform: rotate(360deg); }
|
||||
}
|
||||
.tips-section {
|
||||
background: linear-gradient(135deg, #e3f2fd 0%, #bbdefb 100%);
|
||||
border-radius: 8px;
|
||||
padding: 15px;
|
||||
margin-top: 15px;
|
||||
}
|
||||
.tip-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin-bottom: 8px;
|
||||
font-size: 13px;
|
||||
color: #1565c0;
|
||||
}
|
||||
.tip-item:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
.tip-item i {
|
||||
margin-right: 8px;
|
||||
width: 16px;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<div class="content">
|
||||
<div class="d-flex justify-content-between align-items-center mb-4">
|
||||
<h4 class="mb-0">➕ Nuevo Template Rápido</h4>
|
||||
<a href="/IMPORTADORES/templates_rapidos/lista" class="btn btn-secondary btn-animated">
|
||||
<i class="fas fa-arrow-left me-1"></i>Volver a Lista
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<!-- Formulario Principal -->
|
||||
<div class="col-lg-8">
|
||||
<div class="card">
|
||||
<div class="card-body p-4">
|
||||
<form id="templateForm" action="/IMPORTADORES/templates_rapidos/guardar" method="POST">
|
||||
|
||||
<!-- Información Básica -->
|
||||
<div class="section-header">
|
||||
<h6 class="mb-0"><i class="fas fa-info-circle me-2"></i>Información Básica</h6>
|
||||
</div>
|
||||
|
||||
<div class="row mb-4">
|
||||
<div class="col-md-6">
|
||||
<label for="nombre" class="form-label">Nombre del Template *</label>
|
||||
<input id="nombre" name="nombre" type="text" class="form-control" required
|
||||
placeholder="ej. Importación China" maxlength="100">
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<label for="icono" class="form-label">Icono</label>
|
||||
<div class="input-group">
|
||||
<span class="input-group-text" id="icon-display" style="font-size: 20px;">🏢</span>
|
||||
<input id="icono" name="icono" type="text" class="form-control" value="🏢" readonly>
|
||||
<button type="button" class="btn btn-outline-secondary dropdown-toggle"
|
||||
data-bs-toggle="dropdown" aria-expanded="false">
|
||||
<i class="fas fa-palette"></i>
|
||||
</button>
|
||||
<div class="dropdown-menu dropdown-menu-end p-0" style="width: 320px;">
|
||||
<div class="p-2">
|
||||
<h6 class="dropdown-header">🌍 Países</h6>
|
||||
<div class="emoji-picker" style="max-height: 120px;">
|
||||
<div class="emoji-item" data-emoji="🇨🇳">🇨🇳</div>
|
||||
<div class="emoji-item" data-emoji="🇺🇸">🇺🇸</div>
|
||||
<div class="emoji-item" data-emoji="🇪🇺">🇪🇺</div>
|
||||
<div class="emoji-item" data-emoji="🇩🇪">🇩🇪</div>
|
||||
<div class="emoji-item" data-emoji="🇯🇵">🇯🇵</div>
|
||||
<div class="emoji-item" data-emoji="🇰🇷">🇰🇷</div>
|
||||
<div class="emoji-item" data-emoji="🇮🇹">🇮🇹</div>
|
||||
<div class="emoji-item" data-emoji="🇫🇷">🇫🇷</div>
|
||||
<div class="emoji-item" data-emoji="🇨🇦">🇨🇦</div>
|
||||
<div class="emoji-item" data-emoji="🇧🇷">🇧🇷</div>
|
||||
<div class="emoji-item" data-emoji="🇮🇳">🇮🇳</div>
|
||||
<div class="emoji-item" data-emoji="🇹🇼">🇹🇼</div>
|
||||
</div>
|
||||
|
||||
<h6 class="dropdown-header mt-3">🏢 Empresas & Logística</h6>
|
||||
<div class="emoji-picker" style="max-height: 120px;">
|
||||
<div class="emoji-item" data-emoji="🏢">🏢</div>
|
||||
<div class="emoji-item" data-emoji="🏭">🏭</div>
|
||||
<div class="emoji-item" data-emoji="🏪">🏪</div>
|
||||
<div class="emoji-item" data-emoji="🏬">🏬</div>
|
||||
<div class="emoji-item" data-emoji="🚢">🚢</div>
|
||||
<div class="emoji-item" data-emoji="✈️">✈️</div>
|
||||
<div class="emoji-item" data-emoji="🚛">🚛</div>
|
||||
<div class="emoji-item" data-emoji="🚚">🚚</div>
|
||||
<div class="emoji-item" data-emoji="📦">📦</div>
|
||||
<div class="emoji-item" data-emoji="📋">📋</div>
|
||||
<div class="emoji-item" data-emoji="💼">💼</div>
|
||||
<div class="emoji-item" data-emoji="📊">📊</div>
|
||||
</div>
|
||||
|
||||
<h6 class="dropdown-header mt-3">⭐ Destacados</h6>
|
||||
<div class="emoji-picker" style="max-height: 80px;">
|
||||
<div class="emoji-item" data-emoji="⚡">⚡</div>
|
||||
<div class="emoji-item" data-emoji="🔥">🔥</div>
|
||||
<div class="emoji-item" data-emoji="⭐">⭐</div>
|
||||
<div class="emoji-item" data-emoji="🎯">🎯</div>
|
||||
<div class="emoji-item" data-emoji="🚀">🚀</div>
|
||||
<div class="emoji-item" data-emoji="💎">💎</div>
|
||||
<div class="emoji-item" data-emoji="🔧">🔧</div>
|
||||
<div class="emoji-item" data-emoji="⚙️">⚙️</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row mb-4">
|
||||
<div class="col-md-12">
|
||||
<label for="descripcion" class="form-label">Descripción</label>
|
||||
<textarea id="descripcion" name="descripcion" class="form-control" rows="2"
|
||||
placeholder="Descripción breve del template (opcional)" maxlength="255"></textarea>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Configuración Principal -->
|
||||
<div class="section-header">
|
||||
<h6 class="mb-0"><i class="fas fa-cog me-2"></i>Configuración Principal</h6>
|
||||
</div>
|
||||
|
||||
<div class="row mb-3">
|
||||
<div class="col-md-3">
|
||||
<label for="tipo_moneda" class="form-label">Moneda</label>
|
||||
<select id="tipo_moneda" name="tipo_moneda" class="form-select searchable">
|
||||
<option value="">-- Sin especificar --</option>
|
||||
<option value="MXN">Peso Mexicano (MXN)</option>
|
||||
<option value="USD">Dólar USD (USD)</option>
|
||||
<option value="EUR">Euro (EUR)</option>
|
||||
<option value="CNY">Yuan (CNY)</option>
|
||||
<option value="GBP">Libra GBP (GBP)</option>
|
||||
<option value="JPY">Yen (JPY)</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="col-md-3">
|
||||
<label for="incoterm" class="form-label">INCOTERM</label>
|
||||
<select id="incoterm" name="incoterm" class="form-select searchable">
|
||||
<option value="">-- Sin especificar --</option>
|
||||
<?php foreach($incoterms as $inc): ?>
|
||||
<option value="<?= htmlspecialchars($inc['INCOTERM']) ?>">
|
||||
<?= htmlspecialchars($inc['INCOTERM'].' - '.$inc['DESCESPANOL']) ?>
|
||||
</option>
|
||||
<?php endforeach; ?>
|
||||
</select>
|
||||
</div>
|
||||
<div class="col-md-3">
|
||||
<label for="vinculacion" class="form-label">Vinculación</label>
|
||||
<select id="vinculacion" name="vinculacion" class="form-select">
|
||||
<option value="">-- Sin especificar --</option>
|
||||
<option value="0">No existe</option>
|
||||
<option value="1">Existe, no afecta</option>
|
||||
<option value="2">Existe y afecta</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="col-md-3">
|
||||
<label for="tasa_preferencial" class="form-label">Tasa Preferencial</label>
|
||||
<select id="tasa_preferencial" name="tasa_preferencial" class="form-select">
|
||||
<option value="">-- Sin especificar --</option>
|
||||
<option value="General">General</option>
|
||||
<option value="TLC">TLC</option>
|
||||
<option value="PROSEC">PROSEC</option>
|
||||
<option value="ALADI">ALADI</option>
|
||||
<option value="COMERCIALIZADORA">COMERCIALIZADORA</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row mb-3">
|
||||
<div class="col-md-6">
|
||||
<label for="pais_proveedor" class="form-label">País Proveedor</label>
|
||||
<select id="pais_proveedor" name="pais_proveedor" class="form-select searchable">
|
||||
<option value="">-- Sin especificar --</option>
|
||||
<?php foreach($paises as $p): ?>
|
||||
<option value="<?= htmlspecialchars($p['id_pais']) ?>"><?= htmlspecialchars($p['nombre']) ?></option>
|
||||
<?php endforeach; ?>
|
||||
</select>
|
||||
<input type="hidden" id="pais_proveedor_texto" name="pais_proveedor_texto">
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<label for="unidad_comercial_id" class="form-label">Unidad de Medida</label>
|
||||
<select id="unidad_comercial_id" name="unidad_comercial_id" class="form-select searchable">
|
||||
<option value="">-- Sin especificar --</option>
|
||||
<?php foreach($unidades_medida as $um): ?>
|
||||
<option value="<?= htmlspecialchars($um['id']) ?>"><?= htmlspecialchars($um['descripcion']) ?></option>
|
||||
<?php endforeach; ?>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Configuración Avanzada -->
|
||||
<div class="section-header">
|
||||
<h6 class="mb-0"><i class="fas fa-tools me-2"></i>Configuración Avanzada</h6>
|
||||
</div>
|
||||
|
||||
<div class="row mb-3">
|
||||
<div class="col-md-6">
|
||||
<label for="anexo22_apendice" class="form-label">Aduana</label>
|
||||
<select id="anexo22_apendice" name="anexo22_apendice" class="form-select searchable">
|
||||
<option value="">-- Sin especificar --</option>
|
||||
<?php foreach($aduanas as $a): ?>
|
||||
<option value="<?= htmlspecialchars($a['aduana_seccion']) ?>">
|
||||
<?= htmlspecialchars($a['aduana_seccion'].' – '.$a['nombre']) ?>
|
||||
</option>
|
||||
<?php endforeach; ?>
|
||||
</select>
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<label for="patente" class="form-label">Patente Aduanal</label>
|
||||
<select id="patente" name="patente" class="form-select searchable">
|
||||
<option value="">-- Sin especificar --</option>
|
||||
<?php foreach($patentes as $pt): ?>
|
||||
<option value="<?= htmlspecialchars($pt['id_agente']) ?>">
|
||||
<?= htmlspecialchars($pt['patente'].' - '.$pt['agente_aduanal']) ?>
|
||||
</option>
|
||||
<?php endforeach; ?>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row mb-4">
|
||||
<div class="col-md-6">
|
||||
<label for="transportista_id" class="form-label">Transportista</label>
|
||||
<select id="transportista_id" name="transportista_id" class="form-select searchable">
|
||||
<option value="">-- Sin especificar --</option>
|
||||
<?php foreach($transportistas as $t): ?>
|
||||
<option value="<?= htmlspecialchars($t['id_transportista']) ?>">
|
||||
<?= htmlspecialchars($t['clave_identificador'] . ' - ' . $t['nombre']) ?>
|
||||
</option>
|
||||
<?php endforeach; ?>
|
||||
</select>
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<label for="chofer_id" class="form-label">Chofer</label>
|
||||
<select id="chofer_id" name="chofer_id" class="form-select searchable">
|
||||
<option value="">-- Sin especificar --</option>
|
||||
<?php foreach($choferes as $c): ?>
|
||||
<option value="<?= htmlspecialchars($c['id_chofer']) ?>">
|
||||
<?= htmlspecialchars($c['nombre']) ?>
|
||||
</option>
|
||||
<?php endforeach; ?>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Botones -->
|
||||
<div class="text-end">
|
||||
<button type="submit" class="btn btn-success btn-animated me-2">
|
||||
<i class="fas fa-save me-1"></i>Guardar Template
|
||||
</button>
|
||||
<a href="/IMPORTADORES/templates_rapidos/lista" class="btn btn-secondary btn-animated">
|
||||
<i class="fas fa-times me-1"></i>Cancelar
|
||||
</a>
|
||||
</div>
|
||||
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Vista Previa -->
|
||||
<div class="col-lg-4">
|
||||
<div class="card sticky-top" style="top: 20px;">
|
||||
<div class="card-header bg-primary text-white">
|
||||
<h6 class="mb-0"><i class="fas fa-eye me-2"></i>Vista Previa</h6>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<div id="template-preview">
|
||||
<div class="preview-card">
|
||||
<div class="d-flex align-items-center mb-2">
|
||||
<span id="preview-icono" class="fs-4 me-2">🏢</span>
|
||||
<span id="preview-nombre" class="fw-bold">Nuevo Template</span>
|
||||
</div>
|
||||
<div id="preview-descripcion" class="small mb-3 opacity-75">
|
||||
Descripción del template...
|
||||
</div>
|
||||
<div id="preview-config" class="d-flex flex-wrap">
|
||||
<!-- Configuración se mostrará aquí dinámicamente -->
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="tips-section">
|
||||
<h6 class="small text-primary mb-3 fw-bold">💡 Consejos para Templates Efectivos</h6>
|
||||
<div class="tip-item">
|
||||
<i class="fas fa-lightbulb text-warning"></i>
|
||||
<span>Usa nombres descriptivos que identifiquen fácilmente el template</span>
|
||||
</div>
|
||||
<div class="tip-item">
|
||||
<i class="fas fa-flag text-info"></i>
|
||||
<span>Elige iconos de países o símbolos representativos</span>
|
||||
</div>
|
||||
<div class="tip-item">
|
||||
<i class="fas fa-filter text-success"></i>
|
||||
<span>Solo llena campos que sean comunes en todas las importaciones</span>
|
||||
</div>
|
||||
<div class="tip-item">
|
||||
<i class="fas fa-clock text-danger"></i>
|
||||
<span>Deja vacíos los campos que cambien frecuentemente</span>
|
||||
</div>
|
||||
<div class="tip-item">
|
||||
<i class="fas fa-save text-primary"></i>
|
||||
<span>Los templates te ahorrarán tiempo en futuras importaciones</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Botón adicional para resetear -->
|
||||
<div class="d-grid mt-3">
|
||||
<button type="button" class="btn btn-outline-secondary btn-sm" onclick="resetForm()">
|
||||
<i class="fas fa-refresh me-1"></i>Limpiar Formulario
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script src="https://cdn.jsdelivr.net/npm/choices.js/public/assets/scripts/choices.min.js"></script>
|
||||
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/js/bootstrap.bundle.min.js"></script>
|
||||
<script>
|
||||
// Variables globales
|
||||
let choicesInstances = {};
|
||||
|
||||
// Inicializar cuando el DOM esté listo
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
initializeChoices();
|
||||
initializeEmojiPicker();
|
||||
initializePreview();
|
||||
initializeValidation();
|
||||
initializeDynamicUpdates();
|
||||
});
|
||||
|
||||
// Inicializar Choices.js para selects con búsqueda
|
||||
function initializeChoices() {
|
||||
document.querySelectorAll('.searchable').forEach(element => {
|
||||
choicesInstances[element.id] = new Choices(element, {
|
||||
searchEnabled: true,
|
||||
itemSelectText: '',
|
||||
shouldSort: false,
|
||||
searchPlaceholderValue: 'Buscar...',
|
||||
noResultsText: 'No se encontraron resultados',
|
||||
noChoicesText: 'No hay opciones disponibles'
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
// Inicializar selector de emojis
|
||||
function initializeEmojiPicker() {
|
||||
// Manejar clicks en emojis
|
||||
document.addEventListener('click', function(e) {
|
||||
if (e.target.classList.contains('emoji-item')) {
|
||||
const emoji = e.target.getAttribute('data-emoji') || e.target.textContent;
|
||||
selectEmoji(emoji);
|
||||
|
||||
// Cerrar dropdown
|
||||
const dropdown = bootstrap.Dropdown.getInstance(document.querySelector('[data-bs-toggle="dropdown"]'));
|
||||
if (dropdown) {
|
||||
dropdown.hide();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// Mostrar emoji seleccionado visualmente
|
||||
document.addEventListener('mouseenter', function(e) {
|
||||
if (e.target.classList.contains('emoji-item')) {
|
||||
// Quitar selección anterior
|
||||
document.querySelectorAll('.emoji-item.selected').forEach(item => {
|
||||
item.classList.remove('selected');
|
||||
});
|
||||
|
||||
// Agregar efecto hover mejorado
|
||||
e.target.style.transform = 'scale(1.2)';
|
||||
e.target.style.background = 'linear-gradient(135deg, #4CAF50, #45a049)';
|
||||
e.target.style.color = 'white';
|
||||
e.target.style.borderRadius = '8px';
|
||||
}
|
||||
});
|
||||
|
||||
document.addEventListener('mouseleave', function(e) {
|
||||
if (e.target.classList.contains('emoji-item')) {
|
||||
e.target.style.transform = '';
|
||||
e.target.style.background = '';
|
||||
e.target.style.color = '';
|
||||
e.target.style.borderRadius = '';
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Función para seleccionar emoji (mejorada)
|
||||
function selectEmoji(emoji) {
|
||||
const iconInput = document.getElementById('icono');
|
||||
const iconDisplay = document.getElementById('icon-display');
|
||||
|
||||
// Actualizar valor e interfaz
|
||||
iconInput.value = emoji;
|
||||
iconDisplay.textContent = emoji;
|
||||
|
||||
// Efecto visual en el display
|
||||
iconDisplay.style.transform = 'scale(1.3)';
|
||||
iconDisplay.style.transition = 'transform 0.3s ease';
|
||||
|
||||
setTimeout(() => {
|
||||
iconDisplay.style.transform = 'scale(1)';
|
||||
}, 300);
|
||||
|
||||
updatePreview();
|
||||
|
||||
// Mostrar feedback visual
|
||||
showToast('✅ Icono actualizado', 'success');
|
||||
}
|
||||
|
||||
// Inicializar vista previa
|
||||
function initializePreview() {
|
||||
updatePreview();
|
||||
|
||||
// Eventos para actualizar vista previa en tiempo real
|
||||
document.querySelectorAll('#templateForm input, #templateForm select, #templateForm textarea').forEach(element => {
|
||||
element.addEventListener('input', debounce(updatePreview, 300));
|
||||
element.addEventListener('change', updatePreview);
|
||||
});
|
||||
}
|
||||
|
||||
// Función para actualizar vista previa (mejorada)
|
||||
function updatePreview() {
|
||||
const nombre = document.getElementById('nombre').value.trim() || 'Nuevo Template';
|
||||
const descripcion = document.getElementById('descripcion').value.trim() || 'Descripción del template...';
|
||||
const icono = document.getElementById('icono').value;
|
||||
|
||||
// Actualizar elementos de vista previa con animación
|
||||
const previewNombre = document.getElementById('preview-nombre');
|
||||
const previewDescripcion = document.getElementById('preview-descripcion');
|
||||
const previewIcono = document.getElementById('preview-icono');
|
||||
|
||||
if (previewNombre.textContent !== nombre) {
|
||||
previewNombre.style.opacity = '0.5';
|
||||
setTimeout(() => {
|
||||
previewNombre.textContent = nombre;
|
||||
previewNombre.style.opacity = '1';
|
||||
}, 150);
|
||||
}
|
||||
|
||||
if (previewDescripcion.textContent !== descripcion) {
|
||||
previewDescripcion.style.opacity = '0.5';
|
||||
setTimeout(() => {
|
||||
previewDescripcion.textContent = descripcion;
|
||||
previewDescripcion.style.opacity = '1';
|
||||
}, 150);
|
||||
}
|
||||
|
||||
previewIcono.textContent = icono;
|
||||
|
||||
// Actualizar configuración con más campos
|
||||
const config = [];
|
||||
const fields = [
|
||||
{ id: 'tipo_moneda', label: '💰 Moneda' },
|
||||
{ id: 'incoterm', label: '📋 INCOTERM', getValue: (el) => el.value ? el.options[el.selectedIndex].text.split(' - ')[0] : '' },
|
||||
{ id: 'vinculacion', label: '🔗 Vinculación', map: {'0':'Sin Vinc.', '1':'No Afecta', '2':'Afecta'} },
|
||||
{ id: 'tasa_preferencial', label: '📊 Tasa' },
|
||||
{ id: 'pais_proveedor', label: '🌍 País', getValue: (el) => el.value ? el.options[el.selectedIndex].text : '' },
|
||||
{ id: 'anexo22_apendice', label: '🏢 Aduana', getValue: (el) => el.value ? el.options[el.selectedIndex].text.split(' –')[0] : '' },
|
||||
{ id: 'transportista_id', label: '🚛 Transportista', getValue: (el) => el.value ? el.options[el.selectedIndex].text.split(' - ')[0] : '' }
|
||||
];
|
||||
|
||||
fields.forEach(field => {
|
||||
const element = document.getElementById(field.id);
|
||||
let value = '';
|
||||
|
||||
if (field.getValue) {
|
||||
value = field.getValue(element);
|
||||
} else {
|
||||
value = element.value;
|
||||
}
|
||||
|
||||
if (value) {
|
||||
const displayValue = field.map && field.map[value] ? field.map[value] : value;
|
||||
config.push(`<span class="template-preview-item">${field.label}: ${displayValue}</span>`);
|
||||
}
|
||||
});
|
||||
|
||||
const configContainer = document.getElementById('preview-config');
|
||||
configContainer.style.opacity = '0.5';
|
||||
setTimeout(() => {
|
||||
configContainer.innerHTML = config.join('');
|
||||
configContainer.style.opacity = '1';
|
||||
}, 150);
|
||||
}
|
||||
|
||||
// Inicializar actualizaciones dinámicas
|
||||
function initializeDynamicUpdates() {
|
||||
// Actualizar texto del país cuando cambie
|
||||
const paisSelect = document.getElementById('pais_proveedor');
|
||||
if (paisSelect && choicesInstances[paisSelect.id]) {
|
||||
paisSelect.addEventListener('change', function() {
|
||||
const selectedOption = this.options[this.selectedIndex];
|
||||
document.getElementById('pais_proveedor_texto').value = selectedOption ? selectedOption.text : '';
|
||||
updatePreview();
|
||||
});
|
||||
}
|
||||
|
||||
// Filtrar choferes según transportista seleccionado
|
||||
const transportistaSelect = document.getElementById('transportista_id');
|
||||
const choferSelect = document.getElementById('chofer_id');
|
||||
|
||||
if (transportistaSelect && choferSelect) {
|
||||
transportistaSelect.addEventListener('change', function() {
|
||||
const transportistaId = this.value;
|
||||
const choferChoices = choicesInstances[choferSelect.id];
|
||||
|
||||
if (choferChoices) {
|
||||
// Aquí podrías hacer una llamada AJAX para filtrar choferes
|
||||
// Por ahora simplemente reseteamos la selección
|
||||
choferChoices.setChoiceByValue('');
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Inicializar validación del formulario
|
||||
function initializeValidation() {
|
||||
const form = document.getElementById('templateForm');
|
||||
|
||||
form.addEventListener('submit', function(e) {
|
||||
if (!validateForm()) {
|
||||
e.preventDefault();
|
||||
return false;
|
||||
}
|
||||
|
||||
// Mostrar loading mejorado
|
||||
Swal.fire({
|
||||
title: 'Guardando template...',
|
||||
html: '<div class="loading-spinner"></div><br>Por favor espera...',
|
||||
allowOutsideClick: false,
|
||||
allowEscapeKey: false,
|
||||
showConfirmButton: false,
|
||||
customClass: {
|
||||
popup: 'swal2-noanimation'
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// Validación en tiempo real del nombre
|
||||
const nombreInput = document.getElementById('nombre');
|
||||
nombreInput.addEventListener('input', function() {
|
||||
validateField(this);
|
||||
});
|
||||
}
|
||||
|
||||
// Función de validación del formulario
|
||||
function validateForm() {
|
||||
const nombre = document.getElementById('nombre').value.trim();
|
||||
|
||||
if (!nombre) {
|
||||
showErrorAlert('Campo requerido', 'El nombre del template es obligatorio');
|
||||
document.getElementById('nombre').focus();
|
||||
return false;
|
||||
}
|
||||
|
||||
if (nombre.length > 100) {
|
||||
showErrorAlert('Nombre muy largo', 'El nombre no puede exceder 100 caracteres');
|
||||
document.getElementById('nombre').focus();
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// Validar campo individual
|
||||
function validateField(field) {
|
||||
const value = field.value.trim();
|
||||
|
||||
if (field.id === 'nombre') {
|
||||
if (value.length > 100) {
|
||||
field.classList.add('is-invalid');
|
||||
field.classList.remove('is-valid');
|
||||
} else if (value.length > 0) {
|
||||
field.classList.add('is-valid');
|
||||
field.classList.remove('is-invalid');
|
||||
} else {
|
||||
field.classList.remove('is-valid', 'is-invalid');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Funciones de utilidad
|
||||
function debounce(func, wait) {
|
||||
let timeout;
|
||||
return function executedFunction(...args) {
|
||||
const later = () => {
|
||||
clearTimeout(timeout);
|
||||
func(...args);
|
||||
};
|
||||
clearTimeout(timeout);
|
||||
timeout = setTimeout(later, wait);
|
||||
};
|
||||
}
|
||||
|
||||
function showErrorAlert(title, text) {
|
||||
Swal.fire({
|
||||
icon: 'error',
|
||||
title: title,
|
||||
text: text,
|
||||
confirmButtonColor: '#dc3545',
|
||||
confirmButtonText: 'Entendido'
|
||||
});
|
||||
}
|
||||
|
||||
function showToast(message, type = 'info') {
|
||||
const Toast = Swal.mixin({
|
||||
toast: true,
|
||||
position: 'top-end',
|
||||
showConfirmButton: false,
|
||||
timer: 2000,
|
||||
timerProgressBar: true
|
||||
});
|
||||
|
||||
Toast.fire({
|
||||
icon: type,
|
||||
title: message
|
||||
});
|
||||
}
|
||||
|
||||
// Función para resetear el formulario
|
||||
function resetForm() {
|
||||
document.getElementById('templateForm').reset();
|
||||
|
||||
// Resetear Choices.js
|
||||
Object.values(choicesInstances).forEach(choice => {
|
||||
choice.setChoiceByValue('');
|
||||
});
|
||||
|
||||
// Resetear icono
|
||||
document.getElementById('icono').value = '🏢';
|
||||
document.getElementById('icon-display').textContent = '🏢';
|
||||
|
||||
updatePreview();
|
||||
showToast('Formulario reiniciado', 'info');
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
494
views/templates_rapidos/editar.php
Normal file
494
views/templates_rapidos/editar.php
Normal file
@@ -0,0 +1,494 @@
|
||||
<?php include __DIR__ . '/../partials/sidebar_importador.php'; ?>
|
||||
|
||||
<!DOCTYPE html>
|
||||
<html lang="es">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>✏️ Editar Template Rápido</title>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css" rel="stylesheet">
|
||||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/choices.js/public/assets/styles/choices.min.css"/>
|
||||
<script src="https://cdn.jsdelivr.net/npm/sweetalert2@11"></script>
|
||||
<script src="https://code.jquery.com/jquery-3.7.1.min.js"></script>
|
||||
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0/css/all.min.css">
|
||||
<style>
|
||||
body { font-family: 'Segoe UI', sans-serif; background-color: #f4f6f9; }
|
||||
.sidebar { width: 220px; height: 100vh; background-color: #343a40; position: fixed; top: 0; left: 0; padding-top: 56px; z-index: 1040; }
|
||||
.sidebar .nav-link { font-weight: bold; color: white; transition: all 0.3s ease; }
|
||||
.sidebar .nav-link:hover, .sidebar .nav-link.active { background-color: #495057; color: #fff; }
|
||||
.content { margin-top: 56px; padding: 40px 20px; position: relative; z-index: 1; background-color: #f4f6f9; transition: margin-left 0.3s ease; }
|
||||
@media (min-width: 768px) { .content { margin-left: 250px; } }
|
||||
@media (max-width: 767.98px) {
|
||||
.content { margin-left: 0; }
|
||||
.sidebar .nav-link { font-weight: normal; color: #343a40; background-color: transparent; }
|
||||
.sidebar .nav-link:hover, .sidebar .nav-link.active { background-color: #e9ecef; color: #212529; }
|
||||
}
|
||||
.card { border-radius: 12px; box-shadow: 0 4px 12px rgba(0,0,0,0.1); }
|
||||
.btn-animated { transition: all 0.3s ease; position: relative; overflow: hidden; }
|
||||
.btn-animated:hover { transform: translateY(-2px); box-shadow: 0 5px 15px rgba(0, 0, 0, 0.2); }
|
||||
.form-control:focus, .form-select:focus {
|
||||
border-color: #0d6efd;
|
||||
box-shadow: 0 0 0 0.2rem rgba(13, 110, 253, 0.25);
|
||||
}
|
||||
.emoji-picker {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(8, 1fr);
|
||||
gap: 5px;
|
||||
padding: 10px;
|
||||
max-height: 200px;
|
||||
overflow-y: auto;
|
||||
border: 1px solid #dee2e6;
|
||||
border-radius: 5px;
|
||||
background: white;
|
||||
}
|
||||
.emoji-item {
|
||||
padding: 8px;
|
||||
text-align: center;
|
||||
cursor: pointer;
|
||||
border-radius: 3px;
|
||||
transition: background-color 0.2s;
|
||||
}
|
||||
.emoji-item:hover {
|
||||
background-color: #f8f9fa;
|
||||
}
|
||||
.preview-card {
|
||||
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||||
color: white;
|
||||
border-radius: 8px;
|
||||
padding: 15px;
|
||||
margin-top: 10px;
|
||||
}
|
||||
.template-preview-item {
|
||||
background: rgba(255,255,255,0.2);
|
||||
border: 1px solid rgba(255,255,255,0.3);
|
||||
color: white;
|
||||
padding: 6px 12px;
|
||||
border-radius: 20px;
|
||||
font-size: 12px;
|
||||
margin: 2px;
|
||||
display: inline-block;
|
||||
}
|
||||
.choices.required .choices__inner { border: 1px solid #ced4da; }
|
||||
.choices.is-invalid .choices__inner { border: 1px solid #dc3545; background-color: #fff5f5; }
|
||||
.choices.is-valid .choices__inner { border: 1px solid #28a745; background-color: #f5fff5; }
|
||||
.section-header {
|
||||
background: linear-gradient(135deg, #f8f9fa 0%, #e9ecef 100%);
|
||||
padding: 15px;
|
||||
margin: -15px -15px 15px -15px;
|
||||
border-radius: 8px 8px 0 0;
|
||||
border-bottom: 1px solid #dee2e6;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<div class="content">
|
||||
<div class="d-flex justify-content-between align-items-center mb-4">
|
||||
<h4 class="mb-0">✏️ Editar Template: <?= htmlspecialchars($template['icono']) ?> <?= htmlspecialchars($template['nombre']) ?></h4>
|
||||
<div>
|
||||
<a href="/IMPORTADORES/templates_rapidos/lista" class="btn btn-secondary btn-animated me-2">
|
||||
<i class="fas fa-list me-1"></i>Volver a Lista
|
||||
</a>
|
||||
<a href="/IMPORTADORES/templates_rapidos/crear" class="btn btn-success btn-animated">
|
||||
<i class="fas fa-plus me-1"></i>Nuevo Template
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Info del template -->
|
||||
<div class="row mb-3">
|
||||
<div class="col-md-12">
|
||||
<div class="card bg-info text-white">
|
||||
<div class="card-body py-3">
|
||||
<div class="row align-items-center">
|
||||
<div class="col-md-8">
|
||||
<h6 class="mb-1"><i class="fas fa-info-circle me-2"></i>Información del Template</h6>
|
||||
<p class="mb-0 small">
|
||||
Creado el: <?= $template['fecha_creacion'] ? $template['fecha_creacion']->format('d/m/Y H:i') : 'N/A' ?> |
|
||||
Usado: <?= $template['veces_usado'] ?> veces |
|
||||
Última modificación: <?= $template['fecha_modificacion'] ? $template['fecha_modificacion']->format('d/m/Y H:i') : 'N/A' ?>
|
||||
</p>
|
||||
</div>
|
||||
<div class="col-md-4 text-end">
|
||||
<span class="h4"><?= htmlspecialchars($template['icono']) ?></span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<!-- Formulario Principal -->
|
||||
<div class="col-lg-8">
|
||||
<div class="card">
|
||||
<div class="card-body p-4">
|
||||
<form id="templateForm" action="/IMPORTADORES/templates_rapidos/actualizar" method="POST">
|
||||
<input type="hidden" name="id" value="<?= htmlspecialchars($template['id']) ?>">
|
||||
|
||||
<!-- Información Básica -->
|
||||
<div class="section-header">
|
||||
<h6 class="mb-0"><i class="fas fa-info-circle me-2"></i>Información Básica</h6>
|
||||
</div>
|
||||
|
||||
<div class="row mb-4">
|
||||
<div class="col-md-6">
|
||||
<label for="nombre" class="form-label">Nombre del Template *</label>
|
||||
<input id="nombre" name="nombre" type="text" class="form-control" required
|
||||
placeholder="ej. Importación China" maxlength="100"
|
||||
value="<?= htmlspecialchars($template['nombre']) ?>">
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<label for="icono" class="form-label">Icono</label>
|
||||
<div class="input-group">
|
||||
<input id="icono" name="icono" type="text" class="form-control" readonly
|
||||
value="<?= htmlspecialchars($template['icono']) ?>">
|
||||
<button type="button" class="btn btn-outline-secondary" data-bs-toggle="dropdown">
|
||||
<i class="fas fa-smile"></i>
|
||||
</button>
|
||||
<ul class="dropdown-menu dropdown-menu-end p-0" style="width: 300px;">
|
||||
<li>
|
||||
<div class="emoji-picker">
|
||||
<div class="emoji-item" onclick="selectEmoji('🇨🇳')">🇨🇳</div>
|
||||
<div class="emoji-item" onclick="selectEmoji('🇺🇸')">🇺🇸</div>
|
||||
<div class="emoji-item" onclick="selectEmoji('🇪🇺')">🇪🇺</div>
|
||||
<div class="emoji-item" onclick="selectEmoji('🇩🇪')">🇩🇪</div>
|
||||
<div class="emoji-item" onclick="selectEmoji('🇯🇵')">🇯🇵</div>
|
||||
<div class="emoji-item" onclick="selectEmoji('🇰🇷')">🇰🇷</div>
|
||||
<div class="emoji-item" onclick="selectEmoji('🇮🇹')">🇮🇹</div>
|
||||
<div class="emoji-item" onclick="selectEmoji('🇫🇷')">🇫🇷</div>
|
||||
<div class="emoji-item" onclick="selectEmoji('🏢')">🏢</div>
|
||||
<div class="emoji-item" onclick="selectEmoji('🏭')">🏭</div>
|
||||
<div class="emoji-item" onclick="selectEmoji('🚢')">🚢</div>
|
||||
<div class="emoji-item" onclick="selectEmoji('✈️')">✈️</div>
|
||||
<div class="emoji-item" onclick="selectEmoji('🚛')">🚛</div>
|
||||
<div class="emoji-item" onclick="selectEmoji('📦')">📦</div>
|
||||
<div class="emoji-item" onclick="selectEmoji('💼')">💼</div>
|
||||
<div class="emoji-item" onclick="selectEmoji('⚡')">⚡</div>
|
||||
<div class="emoji-item" onclick="selectEmoji('🔥')">🔥</div>
|
||||
<div class="emoji-item" onclick="selectEmoji('⭐')">⭐</div>
|
||||
<div class="emoji-item" onclick="selectEmoji('🎯')">🎯</div>
|
||||
<div class="emoji-item" onclick="selectEmoji('🚀')">🚀</div>
|
||||
</div>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row mb-4">
|
||||
<div class="col-md-12">
|
||||
<label for="descripcion" class="form-label">Descripción</label>
|
||||
<textarea id="descripcion" name="descripcion" class="form-control" rows="2"
|
||||
placeholder="Descripción breve del template (opcional)" maxlength="255"><?= htmlspecialchars($template['descripcion']) ?></textarea>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Configuración Principal -->
|
||||
<div class="section-header">
|
||||
<h6 class="mb-0"><i class="fas fa-cog me-2"></i>Configuración Principal</h6>
|
||||
</div>
|
||||
|
||||
<?php $config = $template['config']; ?>
|
||||
|
||||
<div class="row mb-3">
|
||||
<div class="col-md-3">
|
||||
<label for="tipo_moneda" class="form-label">Moneda</label>
|
||||
<select id="tipo_moneda" name="tipo_moneda" class="form-select searchable">
|
||||
<option value="">-- Sin especificar --</option>
|
||||
<option value="MXN" <?= ($config['tipo_moneda'] ?? '') === 'MXN' ? 'selected' : '' ?>>Peso Mexicano (MXN)</option>
|
||||
<option value="USD" <?= ($config['tipo_moneda'] ?? '') === 'USD' ? 'selected' : '' ?>>Dólar USD (USD)</option>
|
||||
<option value="EUR" <?= ($config['tipo_moneda'] ?? '') === 'EUR' ? 'selected' : '' ?>>Euro (EUR)</option>
|
||||
<option value="CNY" <?= ($config['tipo_moneda'] ?? '') === 'CNY' ? 'selected' : '' ?>>Yuan (CNY)</option>
|
||||
<option value="GBP" <?= ($config['tipo_moneda'] ?? '') === 'GBP' ? 'selected' : '' ?>>Libra GBP (GBP)</option>
|
||||
<option value="JPY" <?= ($config['tipo_moneda'] ?? '') === 'JPY' ? 'selected' : '' ?>>Yen (JPY)</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="col-md-3">
|
||||
<label for="incoterm" class="form-label">INCOTERM</label>
|
||||
<select id="incoterm" name="incoterm" class="form-select searchable">
|
||||
<option value="">-- Sin especificar --</option>
|
||||
<?php foreach($incoterms as $inc): ?>
|
||||
<option value="<?= htmlspecialchars($inc['INCOTERM']) ?>"
|
||||
<?= ($config['incoterm'] ?? '') === $inc['INCOTERM'] ? 'selected' : '' ?>>
|
||||
<?= htmlspecialchars($inc['INCOTERM'].' - '.$inc['DESCESPANOL']) ?>
|
||||
</option>
|
||||
<?php endforeach; ?>
|
||||
</select>
|
||||
</div>
|
||||
<div class="col-md-3">
|
||||
<label for="vinculacion" class="form-label">Vinculación</label>
|
||||
<select id="vinculacion" name="vinculacion" class="form-select">
|
||||
<option value="">-- Sin especificar --</option>
|
||||
<option value="0" <?= ($config['vinculacion'] ?? '') === '0' ? 'selected' : '' ?>>No existe</option>
|
||||
<option value="1" <?= ($config['vinculacion'] ?? '') === '1' ? 'selected' : '' ?>>Existe, no afecta</option>
|
||||
<option value="2" <?= ($config['vinculacion'] ?? '') === '2' ? 'selected' : '' ?>>Existe y afecta</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="col-md-3">
|
||||
<label for="tasa_preferencial" class="form-label">Tasa Preferencial</label>
|
||||
<select id="tasa_preferencial" name="tasa_preferencial" class="form-select">
|
||||
<option value="">-- Sin especificar --</option>
|
||||
<option value="General" <?= ($config['tasa_preferencial'] ?? '') === 'General' ? 'selected' : '' ?>>General</option>
|
||||
<option value="TLC" <?= ($config['tasa_preferencial'] ?? '') === 'TLC' ? 'selected' : '' ?>>TLC</option>
|
||||
<option value="PROSEC" <?= ($config['tasa_preferencial'] ?? '') === 'PROSEC' ? 'selected' : '' ?>>PROSEC</option>
|
||||
<option value="ALADI" <?= ($config['tasa_preferencial'] ?? '') === 'ALADI' ? 'selected' : '' ?>>ALADI</option>
|
||||
<option value="COMERCIALIZADORA" <?= ($config['tasa_preferencial'] ?? '') === 'COMERCIALIZADORA' ? 'selected' : '' ?>>COMERCIALIZADORA</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row mb-3">
|
||||
<div class="col-md-6">
|
||||
<label for="pais_proveedor" class="form-label">País Proveedor</label>
|
||||
<select id="pais_proveedor" name="pais_proveedor" class="form-select searchable">
|
||||
<option value="">-- Sin especificar --</option>
|
||||
<?php foreach($paises as $p): ?>
|
||||
<option value="<?= htmlspecialchars($p['id_pais']) ?>"
|
||||
<?= ($config['pais_proveedor'] ?? '') === $p['id_pais'] ? 'selected' : '' ?>>
|
||||
<?= htmlspecialchars($p['nombre']) ?>
|
||||
</option>
|
||||
<?php endforeach; ?>
|
||||
</select>
|
||||
<input type="hidden" id="pais_proveedor_texto" name="pais_proveedor_texto"
|
||||
value="<?= htmlspecialchars($config['pais_proveedor_texto'] ?? '') ?>">
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<label for="unidad_comercial_id" class="form-label">Unidad de Medida</label>
|
||||
<select id="unidad_comercial_id" name="unidad_comercial_id" class="form-select searchable">
|
||||
<option value="">-- Sin especificar --</option>
|
||||
<?php foreach($unidades_medida as $um): ?>
|
||||
<option value="<?= htmlspecialchars($um['id']) ?>"
|
||||
<?= ($config['unidad_comercial_id'] ?? '') === $um['id'] ? 'selected' : '' ?>>
|
||||
<?= htmlspecialchars($um['descripcion']) ?>
|
||||
</option>
|
||||
<?php endforeach; ?>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Configuración Avanzada -->
|
||||
<div class="section-header">
|
||||
<h6 class="mb-0"><i class="fas fa-tools me-2"></i>Configuración Avanzada</h6>
|
||||
</div>
|
||||
|
||||
<div class="row mb-3">
|
||||
<div class="col-md-6">
|
||||
<label for="anexo22_apendice" class="form-label">Aduana</label>
|
||||
<select id="anexo22_apendice" name="anexo22_apendice" class="form-select searchable">
|
||||
<option value="">-- Sin especificar --</option>
|
||||
<?php foreach($aduanas as $a): ?>
|
||||
<option value="<?= htmlspecialchars($a['aduana_seccion']) ?>"
|
||||
<?= ($config['anexo22_apendice'] ?? '') === $a['aduana_seccion'] ? 'selected' : '' ?>>
|
||||
<?= htmlspecialchars($a['aduana_seccion'].' – '.$a['nombre']) ?>
|
||||
</option>
|
||||
<?php endforeach; ?>
|
||||
</select>
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<label for="patente" class="form-label">Patente Aduanal</label>
|
||||
<select id="patente" name="patente" class="form-select searchable">
|
||||
<option value="">-- Sin especificar --</option>
|
||||
<?php foreach($patentes as $pt): ?>
|
||||
<option value="<?= htmlspecialchars($pt['id_agente']) ?>"
|
||||
<?= ($config['patente'] ?? '') === $pt['id_agente'] ? 'selected' : '' ?>>
|
||||
<?= htmlspecialchars($pt['patente'].' - '.$pt['agente_aduanal']) ?>
|
||||
</option>
|
||||
<?php endforeach; ?>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row mb-4">
|
||||
<div class="col-md-6">
|
||||
<label for="transportista_id" class="form-label">Transportista</label>
|
||||
<select id="transportista_id" name="transportista_id" class="form-select searchable">
|
||||
<option value="">-- Sin especificar --</option>
|
||||
<?php foreach($transportistas as $t): ?>
|
||||
<option value="<?= htmlspecialchars($t['id_transportista']) ?>"
|
||||
<?= ($config['transportista_id'] ?? '') === $t['id_transportista'] ? 'selected' : '' ?>>
|
||||
<?= htmlspecialchars($t['clave_identificador'] . ' - ' . $t['nombre']) ?>
|
||||
</option>
|
||||
<?php endforeach; ?>
|
||||
</select>
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<label for="chofer_id" class="form-label">Chofer</label>
|
||||
<select id="chofer_id" name="chofer_id" class="form-select searchable">
|
||||
<option value="">-- Sin especificar --</option>
|
||||
<?php foreach($choferes as $c): ?>
|
||||
<option value="<?= htmlspecialchars($c['id_chofer']) ?>"
|
||||
<?= ($config['chofer_id'] ?? '') === $c['id_chofer'] ? 'selected' : '' ?>>
|
||||
<?= htmlspecialchars($c['nombre']) ?>
|
||||
</option>
|
||||
<?php endforeach; ?>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Botones -->
|
||||
<div class="text-end">
|
||||
<button type="submit" class="btn btn-primary btn-animated me-2">
|
||||
<i class="fas fa-save me-1"></i>Actualizar Template
|
||||
</button>
|
||||
<a href="/IMPORTADORES/templates_rapidos/lista" class="btn btn-secondary btn-animated">
|
||||
<i class="fas fa-times me-1"></i>Cancelar
|
||||
</a>
|
||||
</div>
|
||||
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Vista Previa -->
|
||||
<div class="col-lg-4">
|
||||
<div class="card sticky-top" style="top: 20px;">
|
||||
<div class="card-header bg-warning text-dark">
|
||||
<h6 class="mb-0"><i class="fas fa-eye me-2"></i>Vista Previa</h6>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<div id="template-preview">
|
||||
<div class="preview-card">
|
||||
<div class="d-flex align-items-center mb-2">
|
||||
<span id="preview-icono" class="fs-4 me-2"><?= htmlspecialchars($template['icono']) ?></span>
|
||||
<span id="preview-nombre" class="fw-bold"><?= htmlspecialchars($template['nombre']) ?></span>
|
||||
</div>
|
||||
<div id="preview-descripcion" class="small mb-3 opacity-75">
|
||||
<?= htmlspecialchars($template['descripcion']) ?>
|
||||
</div>
|
||||
<div id="preview-config" class="d-flex flex-wrap">
|
||||
<!-- Configuración se mostrará aquí dinámicamente -->
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mt-3">
|
||||
<h6 class="small text-muted mb-2">📊 Estadísticas de Uso:</h6>
|
||||
<div class="small text-muted">
|
||||
<div><i class="fas fa-mouse-pointer text-primary me-1"></i>Usado: <?= $template['veces_usado'] ?> veces</div>
|
||||
<div><i class="fas fa-clock text-info me-1"></i>Última vez: <?= $template['ultima_vez_usado'] ? $template['ultima_vez_usado']->format('d/m/Y H:i') : 'Nunca' ?></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mt-3">
|
||||
<h6 class="small text-muted mb-2">💡 Consejos:</h6>
|
||||
<ul class="list-unstyled small text-muted">
|
||||
<li><i class="fas fa-check text-success me-1"></i> Usa nombres descriptivos</li>
|
||||
<li><i class="fas fa-check text-success me-1"></i> Elige iconos representativos</li>
|
||||
<li><i class="fas fa-check text-success me-1"></i> Solo llena campos comunes</li>
|
||||
<li><i class="fas fa-check text-success me-1"></i> Deja vacío lo que varía</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script src="https://cdn.jsdelivr.net/npm/choices.js/public/assets/scripts/choices.min.js"></script>
|
||||
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/js/bootstrap.bundle.min.js"></script>
|
||||
<script>
|
||||
// Inicializar Choices.js
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
document.querySelectorAll('.searchable').forEach(element => {
|
||||
new Choices(element, {
|
||||
searchEnabled: true,
|
||||
itemSelectText: '',
|
||||
shouldSort: false
|
||||
});
|
||||
});
|
||||
|
||||
// Inicializar vista previa
|
||||
updatePreview();
|
||||
|
||||
// Eventos para actualizar vista previa
|
||||
document.querySelectorAll('#templateForm input, #templateForm select, #templateForm textarea').forEach(element => {
|
||||
element.addEventListener('input', updatePreview);
|
||||
element.addEventListener('change', updatePreview);
|
||||
});
|
||||
});
|
||||
|
||||
// Función para seleccionar emoji
|
||||
function selectEmoji(emoji) {
|
||||
document.getElementById('icono').value = emoji;
|
||||
updatePreview();
|
||||
}
|
||||
|
||||
// Función para actualizar vista previa
|
||||
function updatePreview() {
|
||||
const nombre = document.getElementById('nombre').value || 'Template sin nombre';
|
||||
const descripcion = document.getElementById('descripcion').value || 'Sin descripción';
|
||||
const icono = document.getElementById('icono').value;
|
||||
|
||||
document.getElementById('preview-nombre').textContent = nombre;
|
||||
document.getElementById('preview-descripcion').textContent = descripcion;
|
||||
document.getElementById('preview-icono').textContent = icono;
|
||||
|
||||
// Actualizar configuración
|
||||
const config = [];
|
||||
const fields = [
|
||||
{ id: 'tipo_moneda', label: 'Moneda' },
|
||||
{ id: 'incoterm', label: 'INCOTERM' },
|
||||
{ id: 'vinculacion', label: 'Vinculación', map: {'0':'Sin Vinc.', '1':'No Afecta', '2':'Afecta'} },
|
||||
{ id: 'tasa_preferencial', label: 'Tasa' }
|
||||
];
|
||||
|
||||
fields.forEach(field => {
|
||||
const value = document.getElementById(field.id).value;
|
||||
if (value) {
|
||||
const displayValue = field.map && field.map[value] ? field.map[value] : value;
|
||||
config.push(`<span class="template-preview-item">${field.label}: ${displayValue}</span>`);
|
||||
}
|
||||
});
|
||||
|
||||
document.getElementById('preview-config').innerHTML = config.join('');
|
||||
}
|
||||
|
||||
// Actualizar texto del país cuando cambie
|
||||
document.getElementById('pais_proveedor').addEventListener('change', function() {
|
||||
const selectedOption = this.options[this.selectedIndex];
|
||||
document.getElementById('pais_proveedor_texto').value = selectedOption ? selectedOption.text : '';
|
||||
updatePreview();
|
||||
});
|
||||
|
||||
// Validación del formulario
|
||||
document.getElementById('templateForm').addEventListener('submit', function(e) {
|
||||
const nombre = document.getElementById('nombre').value.trim();
|
||||
|
||||
if (!nombre) {
|
||||
e.preventDefault();
|
||||
Swal.fire({
|
||||
icon: 'error',
|
||||
title: 'Campo requerido',
|
||||
text: 'El nombre del template es obligatorio',
|
||||
confirmButtonColor: '#3085d6'
|
||||
});
|
||||
document.getElementById('nombre').focus();
|
||||
return false;
|
||||
}
|
||||
|
||||
if (nombre.length > 100) {
|
||||
e.preventDefault();
|
||||
Swal.fire({
|
||||
icon: 'error',
|
||||
title: 'Nombre muy largo',
|
||||
text: 'El nombre no puede exceder 100 caracteres',
|
||||
confirmButtonColor: '#3085d6'
|
||||
});
|
||||
document.getElementById('nombre').focus();
|
||||
return false;
|
||||
}
|
||||
|
||||
// Mostrar loading
|
||||
Swal.fire({
|
||||
title: 'Actualizando template...',
|
||||
allowOutsideClick: false,
|
||||
didOpen: () => {
|
||||
Swal.showLoading();
|
||||
}
|
||||
});
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
948
views/templates_rapidos/lista.php
Normal file
948
views/templates_rapidos/lista.php
Normal file
@@ -0,0 +1,948 @@
|
||||
<?php include __DIR__ . '/../partials/sidebar_importador.php'; ?>
|
||||
|
||||
<!DOCTYPE html>
|
||||
<html lang="es">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>📋 Templates Rápidos</title>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css" rel="stylesheet">
|
||||
<link href="https://cdn.datatables.net/1.13.4/css/jquery.dataTables.min.css" rel="stylesheet">
|
||||
<script src="https://cdn.jsdelivr.net/npm/sweetalert2@11"></script>
|
||||
<script src="https://code.jquery.com/jquery-3.7.1.min.js"></script>
|
||||
<script src="https://cdn.datatables.net/1.13.4/js/jquery.dataTables.min.js"></script>
|
||||
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0/css/all.min.css">
|
||||
<style>
|
||||
body { font-family: 'Segoe UI', sans-serif; background-color: #f4f6f9; }
|
||||
.sidebar { width: 220px; height: 100vh; background-color: #343a40; position: fixed; top: 0; left: 0; padding-top: 56px; z-index: 1040; }
|
||||
.sidebar .nav-link { font-weight: bold; color: white; transition: all 0.3s ease; }
|
||||
.sidebar .nav-link:hover, .sidebar .nav-link.active { background-color: #495057; color: #fff; }
|
||||
.content { margin-top: 56px; padding: 40px 20px; position: relative; z-index: 1; background-color: #f4f6f9; transition: margin-left 0.3s ease; }
|
||||
@media (min-width: 768px) { .content { margin-left: 250px; } }
|
||||
@media (max-width: 767.98px) {
|
||||
.content { margin-left: 0; }
|
||||
.sidebar .nav-link { font-weight: normal; color: #343a40; background-color: transparent; }
|
||||
.sidebar .nav-link:hover, .sidebar .nav-link.active { background-color: #e9ecef; color: #212529; }
|
||||
}
|
||||
.card { border-radius: 12px; box-shadow: 0 4px 12px rgba(0,0,0,0.1); }
|
||||
.btn-animated { transition: all 0.3s ease; position: relative; overflow: hidden; }
|
||||
.btn-animated:hover { transform: translateY(-2px); box-shadow: 0 5px 15px rgba(0, 0, 0, 0.2); }
|
||||
.table-responsive { border-radius: 8px; overflow: hidden; }
|
||||
.dataTables_wrapper .dataTables_paginate .paginate_button { padding: 0.375rem 0.75rem; }
|
||||
.alert { border-radius: 8px; }
|
||||
.fade-in { animation: fadeIn 0.5s ease-in; }
|
||||
@keyframes fadeIn { from { opacity: 0; transform: translateY(20px); } to { opacity: 1; transform: translateY(0); } }
|
||||
|
||||
/* ✅ NUEVO: Estilos para filtros de templates */
|
||||
.template-filters {
|
||||
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||||
border-radius: 12px;
|
||||
padding: 20px;
|
||||
margin-bottom: 25px;
|
||||
color: white;
|
||||
}
|
||||
.filter-btn {
|
||||
background: rgba(255,255,255,0.1);
|
||||
border: 1px solid rgba(255,255,255,0.2);
|
||||
color: white;
|
||||
border-radius: 20px;
|
||||
padding: 8px 16px;
|
||||
margin: 0 5px 10px 0;
|
||||
transition: all 0.3s ease;
|
||||
font-size: 13px;
|
||||
cursor: pointer;
|
||||
}
|
||||
.filter-btn:hover, .filter-btn.active {
|
||||
background: rgba(255,255,255,0.2);
|
||||
border-color: rgba(255,255,255,0.4);
|
||||
transform: translateY(-2px);
|
||||
color: white;
|
||||
}
|
||||
.filter-btn.active {
|
||||
background: rgba(255,255,255,0.25);
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
/* ✅ NUEVO: Botón flotante de ayuda */
|
||||
.help-float-btn {
|
||||
position: fixed;
|
||||
bottom: 30px;
|
||||
right: 30px;
|
||||
width: 60px;
|
||||
height: 60px;
|
||||
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||||
border: none;
|
||||
border-radius: 50%;
|
||||
color: white;
|
||||
font-size: 24px;
|
||||
cursor: pointer;
|
||||
box-shadow: 0 8px 25px rgba(102, 126, 234, 0.4);
|
||||
transition: all 0.3s ease;
|
||||
z-index: 1060;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
.help-float-btn:hover {
|
||||
transform: translateY(-3px) scale(1.1);
|
||||
box-shadow: 0 12px 35px rgba(102, 126, 234, 0.6);
|
||||
color: white;
|
||||
}
|
||||
.help-float-btn:active {
|
||||
transform: translateY(-1px) scale(1.05);
|
||||
}
|
||||
|
||||
/* ✅ NUEVO: Mini-modal de ayuda */
|
||||
.shortcuts-modal {
|
||||
position: fixed;
|
||||
bottom: 100px;
|
||||
right: 30px;
|
||||
width: 320px;
|
||||
background: white;
|
||||
border-radius: 12px;
|
||||
box-shadow: 0 15px 40px rgba(0,0,0,0.2);
|
||||
z-index: 1070;
|
||||
display: none;
|
||||
animation: slideInUp 0.3s ease;
|
||||
}
|
||||
@keyframes slideInUp {
|
||||
from { opacity: 0; transform: translateY(20px); }
|
||||
to { opacity: 1; transform: translateY(0); }
|
||||
}
|
||||
.shortcuts-header {
|
||||
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||||
color: white;
|
||||
padding: 15px 20px;
|
||||
border-radius: 12px 12px 0 0;
|
||||
font-weight: bold;
|
||||
}
|
||||
.shortcuts-content {
|
||||
padding: 20px;
|
||||
max-height: 400px;
|
||||
overflow-y: auto;
|
||||
}
|
||||
.shortcut-item {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: 10px 0;
|
||||
border-bottom: 1px solid #eee;
|
||||
}
|
||||
.shortcut-item:last-child {
|
||||
border-bottom: none;
|
||||
}
|
||||
.shortcut-key {
|
||||
background: #f1f3f4;
|
||||
border: 1px solid #dadce0;
|
||||
border-radius: 4px;
|
||||
padding: 4px 8px;
|
||||
font-size: 12px;
|
||||
font-weight: bold;
|
||||
color: #5f6368;
|
||||
min-width: 50px;
|
||||
text-align: center;
|
||||
}
|
||||
.shortcut-desc {
|
||||
flex: 1;
|
||||
margin-right: 10px;
|
||||
font-size: 13px;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
/* ✅ NUEVO: Templates colapsados por defecto */
|
||||
.templates-section {
|
||||
margin-bottom: 25px;
|
||||
}
|
||||
.section-header {
|
||||
background: white;
|
||||
border: 1px solid #dee2e6;
|
||||
border-radius: 8px 8px 0 0;
|
||||
padding: 15px 20px;
|
||||
cursor: pointer;
|
||||
transition: all 0.2s ease;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
}
|
||||
.section-header:hover {
|
||||
background: #f8f9fa;
|
||||
}
|
||||
.section-content {
|
||||
border: 1px solid #dee2e6;
|
||||
border-top: none;
|
||||
border-radius: 0 0 8px 8px;
|
||||
display: none;
|
||||
}
|
||||
.section-content.show {
|
||||
display: block;
|
||||
}
|
||||
.toggle-icon {
|
||||
transition: transform 0.2s ease;
|
||||
}
|
||||
.section-header.active .toggle-icon {
|
||||
transform: rotate(180deg);
|
||||
}
|
||||
|
||||
/* Mejorar badges */
|
||||
.scope-badge {
|
||||
font-size: 11px;
|
||||
padding: 4px 8px;
|
||||
border-radius: 12px;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<div class="content">
|
||||
<div class="d-flex justify-content-between align-items-center mb-4">
|
||||
<h4 class="mb-0">📋 Templates Rápidos</h4>
|
||||
<div>
|
||||
<a href="/IMPORTADORES/templates_rapidos/crear" class="btn btn-success btn-animated">
|
||||
<i class="fas fa-plus me-1"></i>Nuevo Template
|
||||
</a>
|
||||
<a href="/IMPORTADORES/home" class="btn btn-secondary btn-animated ms-2">
|
||||
<i class="fas fa-arrow-left me-1"></i>Volver
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Alertas -->
|
||||
<?php if (isset($_GET['created'])): ?>
|
||||
<div class="alert alert-success alert-dismissible fade show fade-in" role="alert">
|
||||
<i class="fas fa-check-circle me-2"></i>
|
||||
<strong>¡Éxito!</strong> Template creado correctamente.
|
||||
<button type="button" class="btn-close" data-bs-dismiss="alert"></button>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
|
||||
<?php if (isset($_GET['updated'])): ?>
|
||||
<div class="alert alert-info alert-dismissible fade show fade-in" role="alert">
|
||||
<i class="fas fa-info-circle me-2"></i>
|
||||
<strong>¡Actualizado!</strong> Template modificado correctamente.
|
||||
<button type="button" class="btn-close" data-bs-dismiss="alert"></button>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
|
||||
<?php if (isset($_GET['deleted'])): ?>
|
||||
<div class="alert alert-warning alert-dismissible fade show fade-in" role="alert">
|
||||
<i class="fas fa-exclamation-circle me-2"></i>
|
||||
<strong>¡Eliminado!</strong> Template desactivado correctamente.
|
||||
<button type="button" class="btn-close" data-bs-dismiss="alert"></button>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
|
||||
<!-- ✅ NUEVO: Filtros mejorados -->
|
||||
<div class="template-filters">
|
||||
<div class="d-flex justify-content-between align-items-center mb-3">
|
||||
<h6 class="mb-0"><i class="fas fa-filter me-2"></i>Filtrar Templates</h6>
|
||||
<small id="template-count" class="opacity-75">Cargando...</small>
|
||||
</div>
|
||||
<div class="d-flex flex-wrap">
|
||||
<button class="filter-btn active" data-filter="all">
|
||||
<i class="fas fa-th-list me-1"></i>Todos
|
||||
</button>
|
||||
<button class="filter-btn" data-filter="personal">
|
||||
<i class="fas fa-user me-1"></i>Mis Templates
|
||||
</button>
|
||||
<button class="filter-btn" data-filter="agencia">
|
||||
<i class="fas fa-building me-1"></i>De mi Agencia
|
||||
</button>
|
||||
<button class="filter-btn" data-filter="mas-usados">
|
||||
<i class="fas fa-fire me-1"></i>Más Usados
|
||||
</button>
|
||||
<button class="filter-btn" data-filter="recientes">
|
||||
<i class="fas fa-clock me-1"></i>Recientes
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- ✅ MODIFICADO: Templates organizados por secciones colapsables -->
|
||||
<div class="templates-section">
|
||||
<div class="section-header" data-toggle="personal">
|
||||
<div>
|
||||
<h6 class="mb-0"><i class="fas fa-user text-primary me-2"></i>Mis Templates Personalizados</h6>
|
||||
<small class="text-muted">Templates que has creado</small>
|
||||
</div>
|
||||
<i class="fas fa-chevron-down toggle-icon"></i>
|
||||
</div>
|
||||
<div class="section-content show" id="personal-templates">
|
||||
<div class="card-body p-0">
|
||||
<div class="table-responsive">
|
||||
<table id="personalTemplatesTable" class="table table-hover mb-0" style="width:100%">
|
||||
<thead class="bg-light">
|
||||
<tr>
|
||||
<th width="60">ID</th>
|
||||
<th width="200">Nombre</th>
|
||||
<th>Descripción</th>
|
||||
<th width="80">Usos</th>
|
||||
<th width="100">Creado</th>
|
||||
<th width="120">Acciones</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody></tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="templates-section">
|
||||
<div class="section-header" data-toggle="agencia">
|
||||
<div>
|
||||
<h6 class="mb-0"><i class="fas fa-building text-info me-2"></i>Templates de Agencia</h6>
|
||||
<small class="text-muted">Compartidos en tu agencia</small>
|
||||
</div>
|
||||
<i class="fas fa-chevron-down toggle-icon"></i>
|
||||
</div>
|
||||
<div class="section-content" id="agencia-templates">
|
||||
<div class="card-body p-0">
|
||||
<div class="table-responsive">
|
||||
<table id="agenciaTemplatesTable" class="table table-hover mb-0" style="width:100%">
|
||||
<thead class="bg-light">
|
||||
<tr>
|
||||
<th width="60">ID</th>
|
||||
<th width="200">Nombre</th>
|
||||
<th>Descripción</th>
|
||||
<th width="80">Usos</th>
|
||||
<th width="100">Creado</th>
|
||||
<th width="120">Acciones</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody></tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Templates globales solo se muestran si se solicita explícitamente -->
|
||||
<div class="templates-section" style="display: none;" id="global-section">
|
||||
<div class="section-header" data-toggle="global">
|
||||
<div>
|
||||
<h6 class="mb-0"><i class="fas fa-globe text-secondary me-2"></i>Templates Globales</h6>
|
||||
<small class="text-muted">Predeterminados del sistema</small>
|
||||
</div>
|
||||
<i class="fas fa-chevron-down toggle-icon"></i>
|
||||
</div>
|
||||
<div class="section-content" id="global-templates">
|
||||
<div class="card-body p-0">
|
||||
<div class="table-responsive">
|
||||
<table id="globalTemplatesTable" class="table table-hover mb-0" style="width:100%">
|
||||
<thead class="bg-light">
|
||||
<tr>
|
||||
<th width="60">ID</th>
|
||||
<th width="200">Nombre</th>
|
||||
<th>Descripción</th>
|
||||
<th width="80">Usos</th>
|
||||
<th width="100">Creado</th>
|
||||
<th width="80">Ver</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody></tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- ✅ NUEVO: Botón flotante de ayuda -->
|
||||
<button class="help-float-btn" id="helpBtn" title="Atajos rápidos">
|
||||
<i class="fas fa-question"></i>
|
||||
</button>
|
||||
|
||||
<!-- ✅ NUEVO: Mini-modal de atajos -->
|
||||
<div class="shortcuts-modal" id="shortcutsModal">
|
||||
<div class="shortcuts-header">
|
||||
<div class="d-flex justify-content-between align-items-center">
|
||||
<span><i class="fas fa-keyboard me-2"></i>Atajos Rápidos</span>
|
||||
<button type="button" class="btn-close btn-close-white" id="closeShortcuts"></button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="shortcuts-content">
|
||||
<div class="mb-3">
|
||||
<strong class="text-primary">🎯 Templates</strong>
|
||||
</div>
|
||||
<div class="shortcut-item">
|
||||
<div class="shortcut-desc">Crear nuevo template</div>
|
||||
<div class="shortcut-key">Ctrl+N</div>
|
||||
</div>
|
||||
<div class="shortcut-item">
|
||||
<div class="shortcut-desc">Filtrar personal</div>
|
||||
<div class="shortcut-key">Alt+P</div>
|
||||
</div>
|
||||
<div class="shortcut-item">
|
||||
<div class="shortcut-desc">Filtrar agencia</div>
|
||||
<div class="shortcut-key">Alt+A</div>
|
||||
</div>
|
||||
<div class="shortcut-item">
|
||||
<div class="shortcut-desc">Más usados</div>
|
||||
<div class="shortcut-key">Alt+U</div>
|
||||
</div>
|
||||
|
||||
<div class="mt-4 mb-3">
|
||||
<strong class="text-primary">⚡ Navegación</strong>
|
||||
</div>
|
||||
<div class="shortcut-item">
|
||||
<div class="shortcut-desc">Buscar en tabla</div>
|
||||
<div class="shortcut-key">Ctrl+F</div>
|
||||
</div>
|
||||
<div class="shortcut-item">
|
||||
<div class="shortcut-desc">Actualizar datos</div>
|
||||
<div class="shortcut-key">F5</div>
|
||||
</div>
|
||||
<div class="shortcut-item">
|
||||
<div class="shortcut-desc">Expandir/Colapsar</div>
|
||||
<div class="shortcut-key">Space</div>
|
||||
</div>
|
||||
|
||||
<div class="mt-4 mb-3">
|
||||
<strong class="text-primary">📋 Pedimentos</strong>
|
||||
</div>
|
||||
<div class="shortcut-item">
|
||||
<div class="shortcut-desc">Nueva solicitud</div>
|
||||
<div class="shortcut-key">Ctrl+I</div>
|
||||
</div>
|
||||
<div class="shortcut-item">
|
||||
<div class="shortcut-desc">Aplicar template</div>
|
||||
<div class="shortcut-key">Enter</div>
|
||||
</div>
|
||||
<div class="shortcut-item">
|
||||
<div class="shortcut-desc">Guardar rápido</div>
|
||||
<div class="shortcut-key">Ctrl+S</div>
|
||||
</div>
|
||||
|
||||
<div class="mt-3 text-center">
|
||||
<small class="text-muted">
|
||||
<i class="fas fa-info-circle me-1"></i>
|
||||
Presiona <kbd>F1</kbd> para mostrar/ocultar esta ayuda
|
||||
</small>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/js/bootstrap.bundle.min.js"></script>
|
||||
<script>
|
||||
$(document).ready(function() {
|
||||
// ✅ CONFIGURAR DATATABLES POR SECCIÓN
|
||||
const personalTable = initDataTable('#personalTemplatesTable', 'personal');
|
||||
const agenciaTable = initDataTable('#agenciaTemplatesTable', 'agencia');
|
||||
const globalTable = initDataTable('#globalTemplatesTable', 'global');
|
||||
|
||||
// ✅ MANEJO DE SECCIONES COLAPSABLES
|
||||
$('.section-header').click(function() {
|
||||
const $this = $(this);
|
||||
const $content = $this.next('.section-content');
|
||||
const $icon = $this.find('.toggle-icon');
|
||||
|
||||
$content.slideToggle(300);
|
||||
$this.toggleClass('active');
|
||||
|
||||
// Si se está abriendo y la tabla no se ha cargado
|
||||
if ($content.is(':visible') && !$content.data('loaded')) {
|
||||
const section = $this.data('toggle');
|
||||
loadSectionData(section);
|
||||
$content.data('loaded', true);
|
||||
}
|
||||
});
|
||||
|
||||
// ✅ FILTROS MEJORADOS
|
||||
$('.filter-btn').click(function() {
|
||||
$('.filter-btn').removeClass('active');
|
||||
$(this).addClass('active');
|
||||
|
||||
const filter = $(this).data('filter');
|
||||
applyFilter(filter);
|
||||
});
|
||||
|
||||
// ✅ BOTÓN FLOTANTE DE AYUDA
|
||||
$('#helpBtn').click(function() {
|
||||
$('#shortcutsModal').toggle();
|
||||
});
|
||||
|
||||
$('#closeShortcuts').click(function() {
|
||||
$('#shortcutsModal').hide();
|
||||
});
|
||||
|
||||
// Cerrar modal al hacer clic fuera
|
||||
$(document).click(function(e) {
|
||||
if (!$(e.target).closest('#shortcutsModal, #helpBtn').length) {
|
||||
$('#shortcutsModal').hide();
|
||||
}
|
||||
});
|
||||
|
||||
// ✅ ATAJOS DE TECLADO
|
||||
$(document).keydown(function(e) {
|
||||
// Ctrl+N: Nuevo template
|
||||
if (e.ctrlKey && e.key === 'n') {
|
||||
e.preventDefault();
|
||||
window.location.href = '/IMPORTADORES/templates_rapidos/crear';
|
||||
}
|
||||
|
||||
// Ctrl+I: Nueva solicitud
|
||||
if (e.ctrlKey && e.key === 'i') {
|
||||
e.preventDefault();
|
||||
window.location.href = '/IMPORTADORES/solicitud_importacion/crear';
|
||||
}
|
||||
|
||||
// Alt+P: Filtrar personal
|
||||
if (e.altKey && e.key === 'p') {
|
||||
e.preventDefault();
|
||||
$('.filter-btn[data-filter="personal"]').click();
|
||||
}
|
||||
|
||||
// Alt+A: Filtrar agencia
|
||||
if (e.altKey && e.key === 'a') {
|
||||
e.preventDefault();
|
||||
$('.filter-btn[data-filter="agencia"]').click();
|
||||
}
|
||||
|
||||
// Alt+U: Más usados
|
||||
if (e.altKey && e.key === 'u') {
|
||||
e.preventDefault();
|
||||
$('.filter-btn[data-filter="mas-usados"]').click();
|
||||
}
|
||||
|
||||
// F1: Toggle ayuda
|
||||
if (e.key === 'F1') {
|
||||
e.preventDefault();
|
||||
$('#shortcutsModal').toggle();
|
||||
}
|
||||
|
||||
// F5: Actualizar
|
||||
if (e.key === 'F5') {
|
||||
e.preventDefault();
|
||||
location.reload();
|
||||
}
|
||||
|
||||
// Space: Toggle sección activa
|
||||
if (e.key === ' ' && !$(e.target).is('input, textarea, select')) {
|
||||
e.preventDefault();
|
||||
$('.section-header.active').click();
|
||||
}
|
||||
});
|
||||
|
||||
// Auto-refresh cada 30 segundos
|
||||
setInterval(() => {
|
||||
refreshActiveTables();
|
||||
}, 30000);
|
||||
|
||||
// Auto-dismiss alerts
|
||||
setTimeout(() => {
|
||||
$('.alert').fadeOut('slow');
|
||||
}, 5000);
|
||||
|
||||
// Cargar sección personal por defecto
|
||||
loadSectionData('personal');
|
||||
$('#personal-templates').data('loaded', true);
|
||||
});
|
||||
|
||||
// ✅ FUNCIÓN PARA INICIALIZAR DATATABLES
|
||||
function initDataTable(selector, scope) {
|
||||
return $(selector).DataTable({
|
||||
processing: true,
|
||||
serverSide: false,
|
||||
data: [],
|
||||
columns: [
|
||||
{ data: 0 }, // ID
|
||||
{ data: 1 }, // Nombre con icono
|
||||
{ data: 2 }, // Descripción
|
||||
{ data: 3 }, // Veces usado
|
||||
{ data: 4 }, // Fecha creación
|
||||
{ data: 5, orderable: false } // Acciones
|
||||
],
|
||||
order: [[3, 'desc']], // Ordenar por usos descendente
|
||||
language: {
|
||||
processing: "Procesando...",
|
||||
search: "Buscar:",
|
||||
lengthMenu: "Mostrar _MENU_ registros",
|
||||
info: "Mostrando de _START_ a _END_ de _TOTAL_ registros",
|
||||
infoEmpty: "Mostrando 0 a 0 de 0 registros",
|
||||
infoFiltered: "(filtrado de _MAX_ registros totales)",
|
||||
loadingRecords: "Cargando...",
|
||||
zeroRecords: "No se encontraron templates " + (scope === 'personal' ? 'personalizados' : scope === 'agencia' ? 'de agencia' : 'globales'),
|
||||
emptyTable: "No hay templates " + (scope === 'personal' ? 'personalizados' : scope === 'agencia' ? 'de agencia' : 'globales'),
|
||||
paginate: {
|
||||
first: "Primero",
|
||||
previous: "Anterior",
|
||||
next: "Siguiente",
|
||||
last: "Último"
|
||||
}
|
||||
},
|
||||
pageLength: 10,
|
||||
responsive: true,
|
||||
drawCallback: function() {
|
||||
$(selector + ' tbody tr').addClass('fade-in');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// ✅ FUNCIÓN PARA CARGAR DATOS POR SECCIÓN
|
||||
function loadSectionData(section) {
|
||||
$.ajax({
|
||||
url: '/IMPORTADORES/templates_rapidos/ajax_lista_por_seccion',
|
||||
method: 'GET',
|
||||
data: { seccion: section },
|
||||
dataType: 'json',
|
||||
success: function(response) {
|
||||
let table;
|
||||
switch(section) {
|
||||
case 'personal':
|
||||
table = $('#personalTemplatesTable').DataTable();
|
||||
break;
|
||||
case 'agencia':
|
||||
table = $('#agenciaTemplatesTable').DataTable();
|
||||
break;
|
||||
case 'global':
|
||||
table = $('#globalTemplatesTable').DataTable();
|
||||
break;
|
||||
}
|
||||
|
||||
if (table) {
|
||||
table.clear().rows.add(response.data || []).draw();
|
||||
}
|
||||
|
||||
updateTemplateCount();
|
||||
},
|
||||
error: function() {
|
||||
console.error('Error cargando templates de sección:', section);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// ✅ FUNCIÓN PARA APLICAR FILTROS
|
||||
function applyFilter(filter) {
|
||||
switch(filter) {
|
||||
case 'all':
|
||||
$('.templates-section').show();
|
||||
$('.section-content.show').each(function() {
|
||||
if (!$(this).data('loaded')) {
|
||||
const section = $(this).prev().data('toggle');
|
||||
loadSectionData(section);
|
||||
$(this).data('loaded', true);
|
||||
}
|
||||
});
|
||||
break;
|
||||
|
||||
case 'personal':
|
||||
$('.templates-section').hide();
|
||||
$('.templates-section').first().show();
|
||||
if (!$('#personal-templates').hasClass('show')) {
|
||||
$('.section-header').first().click();
|
||||
}
|
||||
break;
|
||||
|
||||
case 'agencia':
|
||||
$('.templates-section').hide();
|
||||
$('.templates-section').eq(1).show();
|
||||
if (!$('#agencia-templates').hasClass('show')) {
|
||||
$('.section-header').eq(1).click();
|
||||
}
|
||||
break;
|
||||
|
||||
case 'mas-usados':
|
||||
// Mostrar todos pero filtrar por más de 5 usos
|
||||
$('.templates-section').show();
|
||||
$('.section-content.show').each(function() {
|
||||
const table = $(this).find('table').DataTable();
|
||||
table.search('').columns().search('').draw();
|
||||
table.column(3).search('^([5-9]|[1-9][0-9]+)$', true, false).draw();
|
||||
});
|
||||
break;
|
||||
|
||||
case 'recientes':
|
||||
// Mostrar todos pero filtrar por últimos 7 días
|
||||
$('.templates-section').show();
|
||||
const weekAgo = new Date();
|
||||
weekAgo.setDate(weekAgo.getDate() - 7);
|
||||
$('.section-content.show').each(function() {
|
||||
const table = $(this).find('table').DataTable();
|
||||
table.search('').columns().search('').draw();
|
||||
// Filtrar por fecha en columna 4
|
||||
});
|
||||
break;
|
||||
}
|
||||
updateTemplateCount();
|
||||
}
|
||||
|
||||
// ✅ FUNCIÓN PARA ACTUALIZAR CONTADOR DE TEMPLATES
|
||||
function updateTemplateCount() {
|
||||
let total = 0;
|
||||
$('.section-content:visible').each(function() {
|
||||
const table = $(this).find('table').DataTable();
|
||||
if (table) {
|
||||
total += table.rows({search: 'applied'}).count();
|
||||
}
|
||||
});
|
||||
|
||||
$('#template-count').text(`${total} templates disponibles`);
|
||||
}
|
||||
|
||||
// ✅ FUNCIÓN PARA REFRESCAR TABLAS ACTIVAS
|
||||
function refreshActiveTables() {
|
||||
$('.section-content:visible').each(function() {
|
||||
if ($(this).data('loaded')) {
|
||||
const section = $(this).prev().data('toggle');
|
||||
loadSectionData(section);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// ✅ FUNCIONES DE ACCIONES
|
||||
function aplicarTemplate(id, nombre) {
|
||||
Swal.fire({
|
||||
title: '🚀 Aplicar Template',
|
||||
text: `¿Crear nueva solicitud con el template "${nombre}"?`,
|
||||
icon: 'question',
|
||||
showCancelButton: true,
|
||||
confirmButtonText: '<i class="fas fa-rocket me-1"></i>Aplicar',
|
||||
cancelButtonText: 'Cancelar',
|
||||
confirmButtonColor: '#28a745',
|
||||
cancelButtonColor: '#6c757d'
|
||||
}).then((result) => {
|
||||
if (result.isConfirmed) {
|
||||
window.location.href = `/IMPORTADORES/solicitud_importacion/crear?template=${id}`;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function editarTemplate(id) {
|
||||
window.location.href = `/IMPORTADORES/templates_rapidos/editar/${id}`;
|
||||
}
|
||||
|
||||
function duplicarTemplate(id, nombre) {
|
||||
Swal.fire({
|
||||
title: '📋 Duplicar Template',
|
||||
text: `¿Crear una copia de "${nombre}"?`,
|
||||
icon: 'question',
|
||||
showCancelButton: true,
|
||||
confirmButtonText: '<i class="fas fa-copy me-1"></i>Duplicar',
|
||||
cancelButtonText: 'Cancelar',
|
||||
confirmButtonColor: '#17a2b8',
|
||||
cancelButtonColor: '#6c757d'
|
||||
}).then((result) => {
|
||||
if (result.isConfirmed) {
|
||||
$.ajax({
|
||||
url: '/IMPORTADORES/templates_rapidos/duplicar',
|
||||
method: 'POST',
|
||||
data: { id: id },
|
||||
success: function(response) {
|
||||
if (response.success) {
|
||||
Swal.fire({
|
||||
title: '✅ ¡Duplicado!',
|
||||
text: 'Template duplicado exitosamente',
|
||||
icon: 'success',
|
||||
timer: 2000,
|
||||
showConfirmButton: false
|
||||
}).then(() => {
|
||||
location.reload();
|
||||
});
|
||||
} else {
|
||||
Swal.fire('Error', response.message || 'No se pudo duplicar', 'error');
|
||||
}
|
||||
},
|
||||
error: function() {
|
||||
Swal.fire('Error', 'Error de conexión', 'error');
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function eliminarTemplate(id, nombre) {
|
||||
Swal.fire({
|
||||
title: '⚠️ Eliminar Template',
|
||||
html: `¿Estás seguro de eliminar "<strong>${nombre}</strong>"?<br><small class="text-muted">Esta acción no se puede deshacer</small>`,
|
||||
icon: 'warning',
|
||||
showCancelButton: true,
|
||||
confirmButtonText: '<i class="fas fa-trash me-1"></i>Eliminar',
|
||||
cancelButtonText: 'Cancelar',
|
||||
confirmButtonColor: '#dc3545',
|
||||
cancelButtonColor: '#6c757d',
|
||||
focusCancel: true
|
||||
}).then((result) => {
|
||||
if (result.isConfirmed) {
|
||||
$.ajax({
|
||||
url: '/IMPORTADORES/templates_rapidos/eliminar',
|
||||
method: 'POST',
|
||||
data: { id: id },
|
||||
success: function(response) {
|
||||
if (response.success) {
|
||||
Swal.fire({
|
||||
title: '✅ ¡Eliminado!',
|
||||
text: 'Template eliminado exitosamente',
|
||||
icon: 'success',
|
||||
timer: 2000,
|
||||
showConfirmButton: false
|
||||
}).then(() => {
|
||||
location.reload();
|
||||
});
|
||||
} else {
|
||||
Swal.fire('Error', response.message || 'No se pudo eliminar', 'error');
|
||||
}
|
||||
},
|
||||
error: function() {
|
||||
Swal.fire('Error', 'Error de conexión', 'error');
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function verTemplate(id) {
|
||||
// Abrir modal con detalles del template
|
||||
$.ajax({
|
||||
url: `/IMPORTADORES/templates_rapidos/ver/${id}`,
|
||||
method: 'GET',
|
||||
success: function(response) {
|
||||
Swal.fire({
|
||||
title: '📄 Detalles del Template',
|
||||
html: response,
|
||||
width: '80%',
|
||||
showCloseButton: true,
|
||||
showConfirmButton: false,
|
||||
customClass: {
|
||||
container: 'template-preview-modal'
|
||||
}
|
||||
});
|
||||
},
|
||||
error: function() {
|
||||
Swal.fire('Error', 'No se pudo cargar el template', 'error');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// ✅ MANEJO DE EVENTOS GLOBALES
|
||||
$(document).on('click', '.btn-aplicar', function(e) {
|
||||
e.preventDefault();
|
||||
const id = $(this).data('id');
|
||||
const nombre = $(this).data('nombre');
|
||||
aplicarTemplate(id, nombre);
|
||||
});
|
||||
|
||||
$(document).on('click', '.btn-editar', function(e) {
|
||||
e.preventDefault();
|
||||
const id = $(this).data('id');
|
||||
editarTemplate(id);
|
||||
});
|
||||
|
||||
$(document).on('click', '.btn-duplicar', function(e) {
|
||||
e.preventDefault();
|
||||
const id = $(this).data('id');
|
||||
const nombre = $(this).data('nombre');
|
||||
duplicarTemplate(id, nombre);
|
||||
});
|
||||
|
||||
$(document).on('click', '.btn-eliminar', function(e) {
|
||||
e.preventDefault();
|
||||
const id = $(this).data('id');
|
||||
const nombre = $(this).data('nombre');
|
||||
eliminarTemplate(id, nombre);
|
||||
});
|
||||
|
||||
$(document).on('click', '.btn-ver', function(e) {
|
||||
e.preventDefault();
|
||||
const id = $(this).data('id');
|
||||
verTemplate(id);
|
||||
});
|
||||
|
||||
// ✅ TOOLTIPS MEJORADOS
|
||||
$(document).on('mouseenter', '[title]', function() {
|
||||
$(this).tooltip('show');
|
||||
});
|
||||
|
||||
// ✅ ANIMACIONES DE HOVER EN FILAS
|
||||
$(document).on('mouseenter', 'tbody tr', function() {
|
||||
$(this).addClass('table-active');
|
||||
}).on('mouseleave', 'tbody tr', function() {
|
||||
$(this).removeClass('table-active');
|
||||
});
|
||||
|
||||
// ✅ NOTIFICACIONES DE ACTUALIZACIÓN
|
||||
function showUpdateNotification() {
|
||||
const toast = $(`
|
||||
<div class="toast-notification">
|
||||
<div class="d-flex align-items-center">
|
||||
<i class="fas fa-sync-alt me-2 text-primary"></i>
|
||||
<span>Templates actualizados</span>
|
||||
<button type="button" class="btn-close btn-close-sm ms-auto"></button>
|
||||
</div>
|
||||
</div>
|
||||
`);
|
||||
|
||||
$('body').append(toast);
|
||||
toast.fadeIn();
|
||||
|
||||
setTimeout(() => {
|
||||
toast.fadeOut(() => toast.remove());
|
||||
}, 3000);
|
||||
}
|
||||
</script>
|
||||
|
||||
<style>
|
||||
/* ✅ ESTILOS ADICIONALES */
|
||||
.toast-notification {
|
||||
position: fixed;
|
||||
bottom: 100px;
|
||||
left: 30px;
|
||||
background: white;
|
||||
border: 1px solid #dee2e6;
|
||||
border-radius: 8px;
|
||||
padding: 15px;
|
||||
box-shadow: 0 4px 12px rgba(0,0,0,0.15);
|
||||
z-index: 1050;
|
||||
display: none;
|
||||
min-width: 250px;
|
||||
}
|
||||
|
||||
.template-preview-modal {
|
||||
font-family: inherit;
|
||||
}
|
||||
|
||||
.template-preview-modal .swal2-html-container {
|
||||
max-height: 70vh;
|
||||
overflow-y: auto;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
/* Mejorar responsive de tablas */
|
||||
@media (max-width: 768px) {
|
||||
.template-filters {
|
||||
padding: 15px;
|
||||
}
|
||||
|
||||
.filter-btn {
|
||||
font-size: 12px;
|
||||
padding: 6px 12px;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.help-float-btn {
|
||||
width: 50px;
|
||||
height: 50px;
|
||||
font-size: 20px;
|
||||
bottom: 20px;
|
||||
right: 20px;
|
||||
}
|
||||
|
||||
.shortcuts-modal {
|
||||
width: 280px;
|
||||
right: 20px;
|
||||
bottom: 80px;
|
||||
}
|
||||
}
|
||||
|
||||
/* Estados de carga */
|
||||
.loading-row {
|
||||
background: linear-gradient(90deg, #f0f0f0 25%, #e0e0e0 50%, #f0f0f0 75%);
|
||||
background-size: 200% 100%;
|
||||
animation: shimmer 1.5s infinite;
|
||||
}
|
||||
|
||||
@keyframes shimmer {
|
||||
0% { background-position: -200% 0; }
|
||||
100% { background-position: 200% 0; }
|
||||
}
|
||||
</style>
|
||||
</body>
|
||||
</html>
|
||||
35
winsaai_config_sqlserver.sql
Normal file
35
winsaai_config_sqlserver.sql
Normal file
@@ -0,0 +1,35 @@
|
||||
-- Tabla para configuración de WINSAAI vinculada a usuarios (SQL Server)
|
||||
CREATE TABLE winsaai_config (
|
||||
id INT IDENTITY(1,1) PRIMARY KEY,
|
||||
id_usuario INT NOT NULL, -- ID del usuario propietario de la configuración
|
||||
host NVARCHAR(255) NOT NULL, -- Dirección IP o DNS del servidor WINSAAI
|
||||
port INT NOT NULL DEFAULT 80, -- Puerto de conexión
|
||||
protocol NVARCHAR(10) NOT NULL DEFAULT 'https' CHECK (protocol IN ('http', 'https')), -- Protocolo de conexión
|
||||
usuario NVARCHAR(100) NOT NULL, -- Usuario de autenticación
|
||||
password NVARCHAR(500) NOT NULL, -- Contraseña encriptada
|
||||
sync_pedimentos BIT DEFAULT 1, -- Sincronizar pedimentos
|
||||
sync_coves BIT DEFAULT 1, -- Sincronizar COVES
|
||||
status NVARCHAR(20) NOT NULL DEFAULT 'inactivo' CHECK (status IN ('activo', 'inactivo', 'error')), -- Estado de la conexión
|
||||
last_sync DATETIME2 NULL, -- Última sincronización exitosa
|
||||
created_at DATETIME2 NOT NULL DEFAULT GETDATE(),
|
||||
updated_at DATETIME2 NOT NULL DEFAULT GETDATE(),
|
||||
|
||||
-- Constraints
|
||||
CONSTRAINT UQ_winsaai_config_usuario UNIQUE (id_usuario) -- Un usuario solo puede tener una configuración
|
||||
);
|
||||
|
||||
-- Indices para mejorar rendimiento
|
||||
CREATE INDEX IX_winsaai_config_status ON winsaai_config (status);
|
||||
CREATE INDEX IX_winsaai_config_usuario ON winsaai_config (id_usuario);
|
||||
|
||||
-- Trigger para actualizar updated_at automáticamente
|
||||
CREATE TRIGGER TR_winsaai_config_updated_at
|
||||
ON winsaai_config
|
||||
AFTER UPDATE
|
||||
AS
|
||||
BEGIN
|
||||
SET NOCOUNT ON;
|
||||
UPDATE winsaai_config
|
||||
SET updated_at = GETDATE()
|
||||
WHERE id IN (SELECT id FROM inserted);
|
||||
END;
|
||||
19
winsaai_config_updated.sql
Normal file
19
winsaai_config_updated.sql
Normal file
@@ -0,0 +1,19 @@
|
||||
-- Tabla para configuración de WINSAAI vinculada a usuarios
|
||||
CREATE TABLE winsaai_config (
|
||||
id INT AUTO_INCREMENT PRIMARY KEY,
|
||||
id_usuario INT NOT NULL COMMENT 'ID del usuario propietario de la configuración',
|
||||
host VARCHAR(255) NOT NULL COMMENT 'Dirección IP o DNS del servidor WINSAAI',
|
||||
port INT NOT NULL DEFAULT 80 COMMENT 'Puerto de conexión',
|
||||
protocol ENUM('http', 'https') NOT NULL DEFAULT 'https' COMMENT 'Protocolo de conexión',
|
||||
usuario VARCHAR(100) NOT NULL COMMENT 'Usuario de autenticación',
|
||||
password VARCHAR(255) NOT NULL COMMENT 'Contraseña encriptada',
|
||||
sync_pedimentos BOOLEAN DEFAULT TRUE COMMENT 'Sincronizar pedimentos',
|
||||
sync_coves BOOLEAN DEFAULT TRUE COMMENT 'Sincronizar COVES',
|
||||
status ENUM('activo', 'inactivo', 'error') DEFAULT 'inactivo' COMMENT 'Estado de la conexión',
|
||||
last_sync TIMESTAMP NULL COMMENT 'Última sincronización exitosa',
|
||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
||||
UNIQUE KEY unique_user_config (id_usuario) COMMENT 'Un usuario solo puede tener una configuración',
|
||||
INDEX idx_status (status),
|
||||
INDEX idx_usuario (id_usuario)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||||
Reference in New Issue
Block a user