This commit is contained in:
2025-06-02 13:32:49 -06:00
parent c574183cee
commit 46a92a4415
19 changed files with 728 additions and 379 deletions

View File

@@ -13,7 +13,8 @@ use PHPMailer\PHPMailer\Exception;
loadEnv();
function dashboard() {
function dashboard()
{
if (!isset($_SESSION['usuario_id']) || $_SESSION['tipo_usuario'] !== 'agente_aduanal') {
header("Location: /IMPORTADORES/login");
exit;
@@ -28,7 +29,8 @@ function dashboard() {
include __DIR__ . '/../../views/agentes/dashboard.php';
}
function importadores_activos() {
function importadores_activos()
{
if (!($_SESSION['usuario_id'] ?? false) || $_SESSION['tipo_usuario'] !== 'agente_aduanal') {
header('Location: /IMPORTADORES/login');
exit;
@@ -175,8 +177,8 @@ function aprobar_solicitud()
exit;
}
function activos() {
function activos()
{
if (!($_SESSION['usuario_id'] ?? false)) {
die("⚠️ No autorizado.");
}
@@ -203,9 +205,8 @@ function activos() {
include __DIR__ . '/../../views/agentes/importadores_activos.php';
}
function toggle_estado() {
function toggle_estado()
{
if (!($_SESSION['usuario_id'] ?? false) || $_SESSION['tipo_usuario'] !== 'agente_aduanal') {
die("⚠️ No autorizado.");
}
@@ -241,3 +242,32 @@ function toggle_estado() {
header("Location: /IMPORTADORES/agentes/activos");
exit;
}
function bitacora()
{
$conn = getConnection();
if (!($_SESSION['usuario_id'] ?? false)) {
header('Location: /IMPORTADORES/login');
exit;
}
$sql = "
SELECT u.id_usuario, u.nombre, b.email, b.ip, b.fecha, b.exito, b.detalle
FROM dbo.bitacora_login b
JOIN dbo.usuarios_sistema u ON u.id_usuario = b.id_usuario
ORDER BY b.fecha DESC
";
$stmt = sqlsrv_query($conn, $sql);
if ($stmt === false) {
die("Error en bitacora(): " . print_r(sqlsrv_errors(), true));
}
$bitacoras = [];
while ($row = sqlsrv_fetch_array($stmt, SQLSRV_FETCH_ASSOC)) {
$row['nombre'] = decrypt($row['nombre']); // Desencripta aquí
$bitacoras[] = $row;
}
include __DIR__ . '/../../views/agentes/bitacora.php';
}

View File

@@ -62,7 +62,7 @@ function guardar()
// Captura los datos del formulario
$campos = [
'clave', 'tipo_identificador', 'curp', 'calle', 'num_exterior', 'num_interior',
'ciudad', 'colonia', 'pais', 'codigo_postal', 'municipio', 'estado', 'fax', 'observaciones'
'ciudad', 'colonia', 'pais', 'codigo_postal', 'municipio', 'estado', 'telefono', 'fax', 'observaciones'
];
$sql_parts = [];
@@ -75,23 +75,95 @@ function guardar()
}
}
// Si no hay datos que actualizar
if (empty($sql_parts)) {
header("Location: /IMPORTADORES/configuracion");
exit;
}
// Iniciar transacción para asegurar consistencia
sqlsrv_begin_transaction($conn);
try {
// 1. Actualiza la tabla informacion_general
$sql = "UPDATE informacion_general SET " . implode(", ", $sql_parts) . " WHERE id_usuario = ?";
$params[] = $id_usuario;
$stmt = sqlsrv_query($conn, $sql, $params);
if ($stmt === false) {
die(print_r(sqlsrv_errors(), true));
throw new Exception(print_r(sqlsrv_errors(), true));
}
// 2. Actualiza el teléfono en solicitudes_importadores usando company_name encriptado
if (isset($_POST['telefono']) && $_POST['telefono'] !== '') {
$telefono = $_POST['telefono'];
// Primero obtenemos el nombre de la empresa de informacion_general
$sql_get_name = "SELECT nombre FROM informacion_general WHERE id_usuario = ?";
$params_get_name = [$id_usuario];
$stmt_get_name = sqlsrv_query($conn, $sql_get_name, $params_get_name);
if ($stmt_get_name === false) {
throw new Exception("Error al obtener nombre de empresa: " . print_r(sqlsrv_errors(), true));
}
$empresa_data = sqlsrv_fetch_array($stmt_get_name, SQLSRV_FETCH_ASSOC);
if ($empresa_data && !empty($empresa_data['nombre'])) {
$nombre_empresa = $empresa_data['nombre'];
// Encriptar el nombre de la empresa para comparar
$nombre_encriptado = encrypt($nombre_empresa);
// Actualizar solicitudes_importadores usando el company_name encriptado
$sql_solicitud = "UPDATE solicitudes_importadores SET phone = ? WHERE company_name = ?";
$params_solicitud = [$telefono, $nombre_encriptado];
$stmt_solicitud = sqlsrv_query($conn, $sql_solicitud, $params_solicitud);
if ($stmt_solicitud === false) {
throw new Exception("Error al actualizar teléfono en solicitudes: " . print_r(sqlsrv_errors(), true));
}
// Verificar si se actualizó algún registro
$rows_affected = sqlsrv_rows_affected($stmt_solicitud);
if ($rows_affected === false || $rows_affected == 0) {
// Log para debugging
error_log("No se encontró registro en solicitudes_importadores para actualizar teléfono. Empresa: " . $nombre_empresa);
// Opcional: Buscar por RFC como fallback
$sql_rfc = "SELECT rfc FROM informacion_general WHERE id_usuario = ?";
$stmt_rfc = sqlsrv_query($conn, $sql_rfc, [$id_usuario]);
if ($stmt_rfc) {
$rfc_data = sqlsrv_fetch_array($stmt_rfc, SQLSRV_FETCH_ASSOC);
if ($rfc_data && !empty($rfc_data['rfc'])) {
$sql_solicitud_rfc = "UPDATE solicitudes_importadores SET phone = ? WHERE rfc = ?";
$params_solicitud_rfc = [$telefono, $rfc_data['rfc']];
$stmt_solicitud_rfc = sqlsrv_query($conn, $sql_solicitud_rfc, $params_solicitud_rfc);
if ($stmt_solicitud_rfc === false) {
error_log("Error al actualizar por RFC: " . print_r(sqlsrv_errors(), true));
}
}
}
}
} else {
error_log("No se pudo obtener el nombre de la empresa para el usuario: " . $id_usuario);
}
}
// Confirmar transacción
sqlsrv_commit($conn);
header("Location: /IMPORTADORES/configuracion/index");
exit;
} catch (Exception $e) {
// Revertir transacción en caso de error
sqlsrv_rollback($conn);
die("Error al actualizar datos: " . $e->getMessage());
}
}
function automatizaciones()

View File

@@ -24,6 +24,17 @@ function index()
function opciones()
{
$conn = getConnection();
$id_usuario = $_SESSION['usuario_id'] ?? null;
// Asegúrate de que el usuario está autenticado
if (!$id_usuario) {
die("Usuario no autenticado.");
}
// Obtener correos
$correos = obtenerCorreos($conn, $id_usuario);
include __DIR__ . '/../../views/seguridad/agregar.php';
}
@@ -145,6 +156,35 @@ function correoExtra()
}
}
function modificarCorreoExtra()
{
if (!isset($_SESSION['usuario_id']) || ($_SESSION['pendiente_confirmacion'] ?? false)) {
echo "Error: no se ha iniciado sesión o sesión incompleta.";
exit;
}
$id_usuario = $_SESSION['usuario_id'];
$conn = getConnection();
$correo = $_POST['email-extra'] ?? '';
if (!filter_var($correo, FILTER_VALIDATE_EMAIL)) {
echo "Correo inválido.";
return;
}
$query = "UPDATE correo_extra SET correo = ? WHERE id_usuario = ?";
$stmt = sqlsrv_prepare($conn, $query, [$correo, $id_usuario]);
if ($stmt && sqlsrv_execute($stmt)) {
$_SESSION['config_success'] = 'Correo adicional actualizado correctamente.';
header('Location: /IMPORTADORES/seguridad/index');
exit;
} else {
echo "Error al actualizar el correo extra.";
}
}
function correoRespaldo()
{
// CORRIGIDO: Cambiar id_usuario por usuario_id y verificar confirmación
@@ -184,6 +224,35 @@ function correoRespaldo()
}
}
function modificarCorreoRspaldo()
{
if (!isset($_SESSION['usuario_id']) || ($_SESSION['pendiente_confirmacion'] ?? false)) {
echo "Error: no se ha iniciado sesión o sesión incompleta.";
exit;
}
$id_usuario = $_SESSION['usuario_id'];
$conn = getConnection();
$correo = $_POST['email-respaldo'] ?? '';
if (!filter_var($correo, FILTER_VALIDATE_EMAIL)) {
echo "Correo inválido.";
return;
}
$query = "UPDATE correo_respaldo SET correo = ? WHERE id_usuario = ?";
$stmt = sqlsrv_prepare($conn, $query, [$correo, $id_usuario]);
if ($stmt && sqlsrv_execute($stmt)) {
$_SESSION['config_success'] = 'Correo de respaldo actualizado correctamente.';
header('Location: /IMPORTADORES/seguridad/index');
exit;
} else {
echo "Error al actualizar el correo de respaldo.";
}
}
function obtenerCorreos($conn, $id_usuario)
{
$correos = [

View File

@@ -77,12 +77,15 @@ function lista() {
f.*,
tr.nombre AS transportista,
(c.nombre + ' ' + c.apellido) AS chofer,
p.nombre AS nombre_pais_proveedor,
f.foto_solicitud_url
FROM dbo.solicitud_importacion_factura f
JOIN dbo.transportistas tr
ON f.transportista_id = tr.id_transportista
LEFT JOIN dbo.choferes c
ON f.chofer_id = c.id_chofer
LEFT JOIN dbo.paises p
ON f.pais_proveedor = p.id_pais
WHERE f.id_importador = ?
AND f.status >= 1
ORDER BY f.created_at DESC
@@ -104,7 +107,8 @@ function lista() {
}
/** Formulario de nueva factura **/
function crear() {
function crear()
{
if (!($_SESSION['usuario_id'] ?? false)) {
header('Location: /IMPORTADORES/login');
exit;
@@ -114,7 +118,7 @@ function crear() {
// Carga de datos para selects
$transportistas = [];
$stmtT = sqlsrv_query($conn, "SELECT id_transportista,nombre FROM dbo.transportistas WHERE id_usuario=? AND activo=1 ORDER BY nombre",[$id_importador]);
$stmtT = sqlsrv_query($conn, "SELECT id_transportista, clave_identificador, nombre FROM dbo.transportistas WHERE id_usuario=? AND activo=1 ORDER BY nombre",[$id_importador]);
while ($r = sqlsrv_fetch_array($stmtT, SQLSRV_FETCH_ASSOC)) { $transportistas[] = $r; }
$choferes = [];
@@ -126,7 +130,7 @@ function crear() {
while ($r = sqlsrv_fetch_array($stmtP, SQLSRV_FETCH_ASSOC)) { $paises[] = $r; }
$aduanas = [];
$stmtA = sqlsrv_query($conn, "SELECT aduana_seccion,nombre FROM dbo.aduanas ORDER BY aduana_seccion");
$stmtA = sqlsrv_query($conn, "SELECT DISTINCT RIGHT(REPLICATE('0', 3) + CAST(aduana_seccion AS VARCHAR), 3) AS aduana_seccion, nombre FROM dbo.aduanas ORDER BY aduana_seccion");
while ($r = sqlsrv_fetch_array($stmtA, SQLSRV_FETCH_ASSOC)) { $aduanas[] = $r; }
$incoterms = [];
@@ -143,7 +147,8 @@ function crear() {
}
/** Procesa la creación de una nueva factura y sus partidas **/
function guardar() {
function guardar()
{
if (!($_SESSION['usuario_id'] ?? false)) {
die("⚠️ No autorizado.");
}

View File

@@ -2,9 +2,7 @@
session_start();
require_once __DIR__ . '/../../config/database.php';
/**
* Listado de transportes (sólo activos) para el importador logueado
*/
/** Listado de transportes (sólo activos) para el importador logueado **/
function lista() {
if (!($_SESSION['usuario_id'] ?? false)) {
header('Location: /IMPORTADORES/login');
@@ -31,9 +29,7 @@ function lista() {
include __DIR__ . '/../../views/transportes/lista.php';
}
/**
* Formulario de alta de transporte
*/
/** Formulario de alta de transporte **/
function crear() {
if (!($_SESSION['usuario_id'] ?? false)) {
header('Location: /IMPORTADORES/login');
@@ -44,7 +40,7 @@ function crear() {
// Traer transportistas propios para el select
$sql = "
SELECT id_transportista, nombre
SELECT id_transportista, clave_identificador, nombre
FROM dbo.transportistas
WHERE id_usuario = ? AND activo = 1
ORDER BY nombre
@@ -58,9 +54,7 @@ function crear() {
include __DIR__ . '/../../views/transportes/crear.php';
}
/**
* Procesa la creación de un nuevo transporte
*/
/** Procesa la creación de un nuevo transporte **/
function guardar() {
if (!($_SESSION['usuario_id'] ?? false)) {
@@ -101,9 +95,7 @@ function guardar() {
exit;
}
/**
* Formulario de edición
*/
/** Formulario de edición **/
function editar() {
if (!($_SESSION['usuario_id'] ?? false)) {
@@ -143,9 +135,7 @@ function editar() {
include __DIR__ . '/../../views/transportes/editar.php';
}
/**
* Procesa la actualización
*/
/** Procesa la actualización **/
function actualizar() {
if (!($_SESSION['usuario_id'] ?? false)) {
die("⚠️ No autorizado.");
@@ -206,11 +196,7 @@ function actualizar() {
exit;
}
/**
* “Soft-delete” (status = 0)
*/
/** “Soft-delete” (status = 0) **/
function eliminar() {
if (!($_SESSION['usuario_id'] ?? false)) {
@@ -232,9 +218,7 @@ function eliminar() {
}
/**
* Formulario de importación masiva
*/
/** Formulario de importación masiva **/
function masivo() {
if (!($_SESSION['usuario_id'] ?? false)) {
header('Location: /IMPORTADORES/login');
@@ -243,9 +227,7 @@ function masivo() {
include __DIR__ . '/../../views/transportes/importar_masivo.php';
}
/**
* Procesa la importación masiva desde CSV
*/
/** Procesa la importación masiva desde CSV **/
function importarGuardar() {
if (!($_SESSION['usuario_id'] ?? false)) {
die("⚠️ No autorizado.");

View File

@@ -460,7 +460,6 @@ function actualizar() {
exit;
}
function eliminar() {
session_start();
if (!($_SESSION['usuario_id'] ?? false)) {

132
views/agentes/bitacora.php Normal file
View File

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

View File

@@ -1,3 +1,7 @@
<?php
require_once __DIR__ . '/../../app/helpers/crypto.php';
include __DIR__ . '/../partials/sidebar_agente.php';
?>
<!DOCTYPE html>
<html lang="es">
@@ -47,10 +51,6 @@
</head>
<body>
<?php
require_once __DIR__ . '/../../app/helpers/crypto.php';
include __DIR__ . '/../partials/sidebar_agente.php';
?>
<!-- 📄 CONTENIDO -->
<div class="content">
<h4 class="mb-4">Panel principal del agente</h4>
@@ -60,7 +60,7 @@ include __DIR__ . '/../partials/sidebar_agente.php';
<div class="card shadow-sm p-3">
<h5 class="text-primary">Importadores activos</h5>
<p>Consulta los que ya fueron autorizados.</p>
<a href="/IMPORTADORES/importadores/activos" class="btn btn-primary btn-sm mt-2">Ver importadores</a>
<a href="/IMPORTADORES/AGENTES/activos" class="btn btn-primary btn-sm mt-2">Ver importadores</a>
</div>
</div>
@@ -76,7 +76,7 @@ include __DIR__ . '/../partials/sidebar_agente.php';
<div class="card shadow-sm p-3">
<h5 class="text-warning">Bitácora del sistema</h5>
<p>Revisa los accesos y acciones recientes.</p>
<a href="/IMPORTADORES/bitacoras/login" class="btn btn-warning btn-sm mt-2">Ver bitácora</a>
<a href="/IMPORTADORES/agentes/bitacora" class="btn btn-warning btn-sm mt-2">Ver bitácora</a>
</div>
</div>
</div>

View File

@@ -2,6 +2,9 @@
require_once __DIR__ . '/../../app/helpers/crypto.php';
include __DIR__ . '/../partials/sidebar_agente.php';
?>
<html>
<head>
<title>Dashboard | Agente Aduanal</title>
<meta name="viewport" content="width=device-width, initial-scale=1">
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css" rel="stylesheet">
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/js/bootstrap.bundle.min.js"></script>
@@ -44,6 +47,7 @@ include __DIR__ . '/../partials/sidebar_agente.php';
}
</style>
</head>
<body>
<div class="content px-4 pt-5 mt-4">
<h4>✅ Importadores Activos</h4>
@@ -87,3 +91,6 @@ include __DIR__ . '/../partials/sidebar_agente.php';
});
</script>
<?php endif; ?>
</body>
</html>

View File

@@ -2,6 +2,9 @@
require_once __DIR__ . '/../../app/helpers/crypto.php';
include __DIR__ . '/../partials/sidebar_agente.php';
?>
<html>
<head>
<title>Dashboard | Agente Aduanal</title>
<meta name="viewport" content="width=device-width, initial-scale=1">
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css" rel="stylesheet">
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/js/bootstrap.bundle.min.js"></script>
@@ -43,6 +46,8 @@ include __DIR__ . '/../partials/sidebar_agente.php';
}
</style>
</head>
<body>
<div class="content px-4 pt-5 mt-4">
<h4>📥 Solicitudes Pendientes</h4>
@@ -83,3 +88,6 @@ include __DIR__ . '/../partials/sidebar_agente.php';
</tbody>
</table>
</div>
</body>
</html>

View File

@@ -84,17 +84,22 @@ include __DIR__ . '/../partials/sidebar_configuracion.php';
<h4 class="mb-4"> Información General</h4>
<div class="card shadow-sm p-4 mb-4 bg-white">
<form>
<div class="row mb-2">
<div class="row mb-3">
<label class="col-md-2 col-form-label">Clave:</label>
<div class="col-md-3">
<input type="text" class="form-control" value="<?= htmlspecialchars($datos['clave'] ?? '') ?>" disabled>
</div>
<div class="col-md-1"></div>
<div class="col-md-3"></div>
<label class="col-md-2 col-form-label">Tipo de identificador:</label>
<div class="col-md-4">
<input type="text" class="form-control" value="<?= htmlspecialchars($datos['tipo_identificador'] ?? '') ?>" disabled>
<div class="col-md-2">
<select name="tipo_identificador" class="form-control" disabled>
<option value="">-- Selecciona una opción --</option>
<option value="1" <?= ($datos['tipo_identificador'] ?? '') == '1' ? 'selected' : '' ?>>1 - RFC</option>
<option value="2" <?= ($datos['tipo_identificador'] ?? '') == '2' ? 'selected' : '' ?>>2 - CURP</option>
<option value="3" <?= ($datos['tipo_identificador'] ?? '') == '3' ? 'selected' : '' ?>>3 - SIN TAX ID</option>
</select>
</div>
</div>

View File

@@ -88,20 +88,23 @@ include __DIR__ . '/../partials/sidebar_configuracion.php';
<h4 class="mb-4">✏️ Editar Información</h4>
<div class="card shadow-sm p-4 mb-4 bg-white">
<form id="form-edicion" method="POST" action="/IMPORTADORES/configuracion/guardar">
<div class="row mb-2">
<div class="row mb-3">
<label class="col-md-2 col-form-label">Clave:</label>
<div class="col-md-3">
<input type="text" name="clave" maxlength="8"
class="form-control" value="<?= htmlspecialchars($datos['clave'] ?? '') ?>" disabled>
</div>
<div class="col-md-1"></div>
<div class="col-md-3"></div>
<label class="col-md-2 col-form-label">Tipo de identificador:</label>
<div class="col-md-4">
<input type="number" name="tipo_identificador"
class="form-control" value="<?= htmlspecialchars($datos['tipo_identificador'] ?? '') ?>"
onfocus="this.value=''" onblur="if(this.value=='') this.value='<?= htmlspecialchars($datos['tipo_identificador'] ?? '') ?>'">
<div class="col-md-2">
<select name="tipo_identificador" class="form-control" required>
<option value="">-- Selecciona una opción --</option>
<option value="1" <?= ($datos['tipo_identificador'] ?? '') == '1' ? 'selected' : '' ?>>1 - RFC</option>
<option value="2" <?= ($datos['tipo_identificador'] ?? '') == '2' ? 'selected' : '' ?>>2 - CURP</option>
<option value="3" <?= ($datos['tipo_identificador'] ?? '') == '3' ? 'selected' : '' ?>>3 - SIN TAX ID</option>
</select>
</div>
</div>
@@ -123,87 +126,78 @@ include __DIR__ . '/../partials/sidebar_configuracion.php';
<label class="col-md-1 col-form-label">CURP:</label>
<div class="col-md-5">
<input type="text" name="curp" maxlength="18"
class="form-control" value="<?= htmlspecialchars($datos['curp'] ?? '') ?>"
onfocus="this.value=''" onblur="if(this.value=='') this.value='<?= htmlspecialchars($datos['curp'] ?? '') ?>'">
class="form-control" value="<?= htmlspecialchars($datos['curp'] ?? '') ?>">
</div>
</div>
<div class="row mb-3">
<label class="col-md-2 col-form-label">Calle:</label>
<div class="col-md-10">
<input type="text" name="calle" class="form-control" value="<?= htmlspecialchars($datos['calle'] ?? '') ?>"
onfocus="this.value=''" onblur="if(this.value=='') this.value='<?= htmlspecialchars($datos['calle'] ?? '') ?>'">
<input type="text" name="calle" class="form-control" value="<?= htmlspecialchars($datos['calle'] ?? '') ?>">
</div>
</div>
<div class="row mb-3">
<label class="col-md-2 col-form-label">Núm. Exterior:</label>
<div class="col-md-2">
<input type="number" name="num_exterior" class="form-control" value="<?= htmlspecialchars($datos['num_exterior'] ?? '') ?>"
onfocus="this.value=''" onblur="if(this.value=='') this.value='<?= htmlspecialchars($datos['num_exterior'] ?? '') ?>'">
<input type="text" name="num_exterior" class="form-control" value="<?= htmlspecialchars($datos['num_exterior'] ?? '') ?>">
</div>
<div class="col-md-4"></div>
<label class="col-md-2 col-form-label">Núm. Interior:</label>
<div class="col-md-2">
<input type="text" name="num_interior" class="form-control" value="<?= htmlspecialchars($datos['num_interior'] ?? '') ?>"
onfocus="this.value=''" onblur="if(this.value=='') this.value='<?= htmlspecialchars($datos['num_interior'] ?? '') ?>'">
<input type="text" name="num_interior" class="form-control" value="<?= htmlspecialchars($datos['num_interior'] ?? '') ?>">
</div>
</div>
<div class="row mb-3">
<label class="col-md-2 col-form-label">Ciudad / Localidad:</label>
<div class="col-md-3">
<input type="text" name="ciudad" class="form-control" value="<?= htmlspecialchars($datos['ciudad'] ?? '') ?>"
onfocus="this.value=''" onblur="if(this.value=='') this.value='<?= htmlspecialchars($datos['ciudad'] ?? '') ?>'">
<input type="text" name="ciudad" class="form-control" value="<?= htmlspecialchars($datos['ciudad'] ?? '') ?>">
</div>
<div class="col-md-1"></div>
<label class="col-md-1 col-form-label">Colonia:</label>
<div class="col-md-5">
<input type="text" name="colonia" class="form-control" value="<?= htmlspecialchars($datos['colonia'] ?? '') ?>"
onfocus="this.value=''" onblur="if(this.value=='') this.value='<?= htmlspecialchars($datos['colonia'] ?? '') ?>'">
<input type="text" name="colonia" class="form-control" value="<?= htmlspecialchars($datos['colonia'] ?? '') ?>">
</div>
</div>
<div class="row mb-3">
<label class="col-md-2 col-form-label">País:</label>
<div class="col-md-3">
<input type="text" name="pais" class="form-control" value="<?= htmlspecialchars($datos['pais'] ?? '') ?>"
onfocus="this.value=''" onblur="if(this.value=='') this.value='<?= htmlspecialchars($datos['pais'] ?? '') ?>'">
<input type="text" name="pais" class="form-control" value="<?= htmlspecialchars($datos['pais'] ?? '') ?>">
</div>
<div class="col-md-3"></div>
<label class="col-md-2 col-form-label">Código Postal:</label>
<div class="col-md-2">
<input type="number" name="codigo_postal" class="form-control" value="<?= htmlspecialchars($datos['codigo_postal'] ?? '') ?>"
onfocus="this.value=''" onblur="if(this.value=='') this.value='<?= htmlspecialchars($datos['codigo_postal'] ?? '') ?>'">
<input type="number" name="codigo_postal" class="form-control" value="<?= htmlspecialchars($datos['codigo_postal'] ?? '') ?>">
</div>
</div>
<div class="row mb-3">
<label class="col-md-2 col-form-label">Municipio:</label>
<div class="col-md-4">
<input type="text" name="municipio" class="form-control" value="<?= htmlspecialchars($datos['municipio'] ?? '') ?>"
onfocus="this.value=''" onblur="if(this.value=='') this.value='<?= htmlspecialchars($datos['municipio'] ?? '') ?>'">
<input type="text" name="municipio" class="form-control" value="<?= htmlspecialchars($datos['municipio'] ?? '') ?>">
</div>
<div class="col-md-1"></div>
<label class="col-md-1 col-form-label">Estado:</label>
<div class="col-md-4">
<input type="text" name="estado" class="form-control" value="<?= htmlspecialchars($datos['estado'] ?? '') ?>"
onfocus="this.value=''" onblur="if(this.value=='') this.value='<?= htmlspecialchars($datos['estado'] ?? '') ?>'">
<input type="text" name="estado" class="form-control" value="<?= htmlspecialchars($datos['estado'] ?? '') ?>">
</div>
</div>
<div class="row mb-3">
<label class="col-md-2 col-form-label">Teléfono:</label>
<div class="col-md-5">
<input type="text" class="form-control" value="<?= htmlspecialchars($datos['telefono'] ?? '') ?>" disabled>
<input type="text" name="telefono" maxlength="11"
class="form-control" value="<?= htmlspecialchars($datos['telefono'] ?? '') ?>">
</div>
<div class="col-md-1"></div>
@@ -211,8 +205,7 @@ include __DIR__ . '/../partials/sidebar_configuracion.php';
<label class="col-md-1 col-form-label">Fax:</label>
<div class="col-md-3">
<input type="text" name="fax" maxlength="12"
class="form-control" value="<?= htmlspecialchars($datos['fax'] ?? '') ?>"
onfocus="this.value=''" onblur="if(this.value=='') this.value='<?= htmlspecialchars($datos['fax'] ?? '') ?>'">
class="form-control" value="<?= htmlspecialchars($datos['fax'] ?? '') ?>">
</div>
</div>
@@ -227,9 +220,7 @@ include __DIR__ . '/../partials/sidebar_configuracion.php';
<label class="col-md-2 col-form-label">Observaciones:</label>
<div class="col-md-10">
<?php $valor_obs = htmlspecialchars($datos['observaciones'] ?? ''); ?>
<textarea name="observaciones" class="form-control" rows="3"
onfocus="if(this.value === '<?= $valor_obs ?>') this.value=''"
onblur="if(this.value === '') this.value='<?= $valor_obs ?>'"><?= $valor_obs ?></textarea>
<textarea name="observaciones" class="form-control" rows="3"><?= $valor_obs ?></textarea>
</div>
</div>
<div style="display: flex; justify-content: right;">
@@ -245,6 +236,7 @@ include __DIR__ . '/../partials/sidebar_configuracion.php';
event.preventDefault(); // Evita que el formulario se envíe
const curp = document.querySelector('[name="curp"]').value.trim();
const telefono = document.querySelector('input[name="telefono"]').value.trim();
const fax = document.querySelector('[name="fax"]').value.trim();
let errores = [];
@@ -254,9 +246,14 @@ include __DIR__ . '/../partials/sidebar_configuracion.php';
errores.push('La CURP no tiene el formato correcto.');
}
// Validación Teléfono - Ajustada para permitir diferentes formatos
if (telefono !== '' && !/^\d{10,11}$/.test(telefono.replace(/\s/g, ''))) {
errores.push('El número de teléfono debe tener 10 u 11 dígitos.');
}
// Validación Fax
if (fax !== '' && !/^\d{3} \d{3} \d{4}$/.test(fax)) {
errores.push('El número de fax debe tener el formato: 123 456 7890.');
if (fax !== '' && !/^\d{3}\s?\d{3}\s?\d{4}$/.test(fax)) {
errores.push('El número de fax debe tener el formato: 123 456 7890 o 1234567890.');
}
// Si hay errores, mostrar alerta y no enviar

View File

@@ -8,9 +8,6 @@ if (!isset($_SESSION['usuario_id']) || $_SESSION['tipo_usuario'] !== 'agente_adu
$nombreAgente = $_SESSION['usuario_nombre'];
?>
<!-- 🔷 NAVBAR -->
<nav class="navbar navbar-dark bg-dark">
<div class="container-fluid">
@@ -28,6 +25,6 @@ $nombreAgente = $_SESSION['usuario_nombre'];
<a href="/IMPORTADORES/AGENTES/dashboard" class="nav-link active">📊 Dashboard</a>
<a href="/IMPORTADORES/AGENTES/activos" class="nav-link">✅ Importadores Activos</a>
<a href="/IMPORTADORES/AGENTES/solicitudes_pendientes" class="nav-link">📥 Solicitudes de Registro</a>
<a href="/IMPORTADORES/bitacoras/login" class="nav-link">🕓 Bitácora</a>
<a href="/IMPORTADORES/AGENTES/bitacora" class="nav-link">🕓 Bitácora</a>
</nav>
</div>

View File

@@ -108,15 +108,24 @@ $dos_factores_estado = obtenerEstadoDosFactores();
<div class="card shadow-sm p-3">
<h4 class="mb-4 text-dark">Recibe Notificaciones</h4>
<p>Agrega un correo adicional para recibir notificaciones.</p>
<form action="/IMPORTADORES/seguridad/correoExtra" method="POST">
<?php if (!empty($correos['correo_extra'])): ?>
<p><?= htmlspecialchars($correos['correo_extra']) ?></p>
<form method="POST" action="/IMPORTADORES/seguridad/modificarCorreoExtra">
<div class="mb-3">
<label class="form-label">Correo electrónico</label>
<input type="email" name="email-extra" class="form-control" required value="">
<input type="email" name="email-extra" class="form-control" value="<?= htmlspecialchars($correos['correo_extra']) ?>" required>
</div>
<div class="d-grid">
<button type="submit" class="btn btn-primary">Registrar</button>
<div class="mb-3">
<button type="submit" class="btn btn-success">Actualizar</button>
</div>
</form>
<?php else: ?>
<form method="POST" action="/IMPORTADORES/seguridad/correoExtra">
<div class="mb-3">
<input type="email" name="email-extra" class="form-control" placeholder="Correo adicional" required>
</div>
<button type="submit" class="btn btn-primary">Registrar</button>
</form>
<?php endif; ?>
</div>
</div>
@@ -125,15 +134,22 @@ $dos_factores_estado = obtenerEstadoDosFactores();
<div class="card shadow-sm p-3">
<h4 class="mb-4 text-dark">Correo de Respaldo</h4>
<p>Agrega un correo de respaldo para reuperación de tu cuenta.</p>
<form action="/IMPORTADORES/seguridad/correoRespaldo" method="POST">
<?php if (!empty($correos['correo_respaldo'])): ?>
<p><?= htmlspecialchars($correos['correo_respaldo']) ?></p>
<form method="POST" action="/IMPORTADORES/seguridad/modificarCorreoRspaldo">
<div class="mb-3">
<label class="form-label">Correo electrónico respaldo</label>
<input type="email" name="email-respaldo" class="form-control" required value="">
</div>
<div class="d-grid">
<button type="submit" class="btn btn-success">Registrar</button>
<input type="email" name="email-respaldo" class="form-control" value="<?= htmlspecialchars($correos['correo_respaldo']) ?>" required>
</div>
<button type="submit" class="btn btn-success">Actualizar</button>
</form>
<?php else: ?>
<form method="POST" action="/IMPORTADORES/seguridad/correoRespaldo">
<div class="mb-3">
<input type="email" name="email-respaldo" class="form-control" placeholder="Correo de respaldo" required>
</div>
<button type="submit" class="btn btn-primary">Registrar</button>
</form>
<?php endif; ?>
</div>
</div>

View File

@@ -99,7 +99,7 @@
</div>
<div class="col-md-4 mb-3">
<label for="pais_proveedor" class="form-label">País (Proveedor)</label>
<select id="pais_proveedor" name="pais_proveedor" class="form-select searchable">
<select id="pais_proveedor" name="pais_proveedor" class="form-select searchable" required>
<option value="">-- Selecciona País --</option>
<?php foreach($paises as $p): ?>
<option value="<?= htmlspecialchars($p['id_pais']) ?>"><?= htmlspecialchars($p['nombre']) ?></option>
@@ -108,7 +108,7 @@
</div>
<div class="col-md-4 mb-3">
<label for="tipo_moneda" class="form-label">Moneda</label>
<select id="tipo_moneda" name="tipo_moneda" class="form-select searchable">
<select id="tipo_moneda" name="tipo_moneda" class="form-select searchable" required>
<?php foreach(['MXN'=>'Peso Mexicano','USD'=>'Dólar USD','EUR'=>'Euro','CNY'=>'Yuan','GBP'=>'Libra GBP','JPY'=>'Yen'] as $code=>$label): ?>
<option value="<?= $code ?>"><?= "$label ($code)" ?></option>
<?php endforeach; ?>
@@ -139,7 +139,9 @@
<select id="transportista_id" name="transportista_id" class="form-select searchable" required>
<option value="">-- Selecciona --</option>
<?php foreach($transportistas as $t): ?>
<option value="<?= htmlspecialchars($t['id_transportista']) ?>"><?= htmlspecialchars($t['nombre']) ?></option>
<option value="<?= htmlspecialchars($t['id_transportista']) ?>">
<?= htmlspecialchars($t['clave_identificador'] . ' - ' . $t['nombre']) ?>
</option>
<?php endforeach; ?>
</select>
</div>
@@ -148,7 +150,9 @@
<select id="chofer_id" name="chofer_id" class="form-select searchable" required>
<option value="">-- Selecciona Chofer --</option>
<?php foreach($choferes as $c): ?>
<option value="<?= htmlspecialchars($c['id_chofer']) ?>"><?= htmlspecialchars($c['nombre']) ?></option>
<option value="<?= htmlspecialchars($c['id_chofer']) ?>">
<?= htmlspecialchars($c['nombre']) ?>
</option>
<?php endforeach; ?>
</select>
</div>
@@ -179,12 +183,12 @@
</thead>
<tbody>
<tr>
<td><input name="partidas[0][descripcion]" class="form-control w-100"></td>
<td><input name="partidas[0][cantidad_comercial]" type="number" step="0.0001" class="form-control w-100"></td>
<td><input name="partidas[0][cantidad_tarifa]" type="number" step="0.0001" class="form-control w-100"></td>
<td><input name="partidas[0][descripcion]" class="form-control w-100" required></td>
<td><input name="partidas[0][cantidad_comercial]" type="number" step="0.0001" class="form-control w-100" min="0" required></td>
<td><input name="partidas[0][cantidad_tarifa]" type="number" step="0.0001" class="form-control w-100" min="0" required></td>
<td>
<div class="mb-3">
<select name="partidas[0][unidad_comercial_id]" class="form-select searchable">
<select name="partidas[0][unidad_comercial_id]" class="form-select searchable" required>
<option value="">-- Unidad --</option>
<?php foreach($unidades_medida as $um): ?>
<option value="<?= htmlspecialchars($um['id']) ?>"><?= htmlspecialchars($um['descripcion']) ?></option>
@@ -192,11 +196,11 @@
</select>
</div>
</td>
<td><input name="partidas[0][valor_factura]" type="number" step="0.01" class="form-control valor-partida w-100"></td>
<td><input name="partidas[0][peso_bruto]" type="number" step="0.0001" class="form-control w-100"></td>
<td><input name="partidas[0][valor_factura]" type="number" step="0.01" class="form-control valor-partida w-100" min="0" required></td>
<td><input name="partidas[0][peso_bruto]" type="number" step="0.0001" class="form-control w-100" min="0" required></td>
<td>
<div class="mb-3">
<select name="partidas[0][tasa_preferencial]" class="form-select searchable">
<select name="partidas[0][tasa_preferencial]" class="form-select searchable" required>
<option value="">-- Selecciona --</option>
<option>General</option>
<option>TLC</option>

View File

@@ -201,8 +201,8 @@
<?php endforeach; else: ?>
<tr>
<td><input name="partidas[0][descripcion]" class="form-control"></td>
<td><input name="partidas[0][cantidad_comercial]" type="number" step="0.0001" class="form-control"></td>
<td><input name="partidas[0][cantidad_tarifa]" type="number" step="0.0001" class="form-control"></td>
<td><input name="partidas[0][cantidad_comercial]" type="number" step="0.0001" class="form-control" min="0"></td>
<td><input name="partidas[0][cantidad_tarifa]" type="number" step="0.0001" class="form-control" min="0"></td>
<td>
<select name="partidas[0][unidad_comercial_id]" class="form-select searchable">
<option value="">-- Unidad --</option>
@@ -211,8 +211,8 @@
<?php endforeach; ?>
</select>
</td>
<td><input name="partidas[0][valor_factura]" type="number" step="0.01" class="form-control valor-partida"></td>
<td><input name="partidas[0][peso_bruto]" type="number" step="0.0001" class="form-control"></td>
<td><input name="partidas[0][valor_factura]" type="number" step="0.01" class="form-control valor-partida" min="0"></td>
<td><input name="partidas[0][peso_bruto]" type="number" step="0.0001" class="form-control" min="0"></td>
<td>
<select name="partidas[0][tasa_preferencial]" class="form-select searchable">
<option value="">-- Selecciona --</option>
@@ -249,98 +249,119 @@
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/js/bootstrap.bundle.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/select2@4.1.0/dist/js/select2.min.js"></script>
<
<script>
// 1) Inicializar Choices para todos los selects excepto proveedor_id
document.querySelectorAll('.searchable:not(#proveedor_id)').forEach(el => {
// add-partida & validation script
document.getElementById('add-partida').addEventListener('click', () => {
const tbody = document.querySelector('#tabla-partidas tbody');
const idx = tbody.querySelectorAll('tr').length;
const row = document.createElement('tr');
row.innerHTML = `
<td><input name="partidas[${idx}][descripcion]" class="form-control"></td>
<td><input name="partidas[${idx}][cantidad_comercial]" type="number" step="0.0001" class="form-control"></td>
<td><input name="partidas[${idx}][cantidad_tarifa]" type="number" step="0.0001" class="form-control"></td>
<td>
<select name="partidas[${idx}][unidad_comercial_id]" class="form-select searchable">
<option value="">-- Unidad --</option>
<?php foreach($unidades_medida as $um): ?>
<option value="<?= htmlspecialchars($um['id']) ?>"><?= htmlspecialchars($um['descripcion']) ?></option>
<?php endforeach; ?>
</select>
</td>
<td><input name="partidas[${idx}][valor_factura]" type="number" step="0.01" class="form-control valor-partida"></td>
<td><input name="partidas[${idx}][peso_bruto]" type="number" step="0.0001" class="form-control"></td>
<td>
<select name="partidas[${idx}][tasa_preferencial]" class="form-select searchable">
<option value="">-- Selecciona --</option>
<option>General</option><option>TLC</option><option>PROSEC</option><option>ALADI</option><option>COMERCIALIZADORA</option>
</select>
</td>
<td class="hide"><input name="partidas[${idx}][precio_unitario]" type="number" class="form-control"></td>
<td class="hide"><input name="partidas[${idx}][oma_factura]" class="form-control"></td>
<td><button type="button" class="btn btn-danger btn-sm remove-row">✖️</button></td>
`;
tbody.appendChild(row);
// Re-inicializar Choices.js en los nuevos selects
row.querySelectorAll('.searchable').forEach(el => {
new Choices(el, { searchEnabled: true, itemSelectText: '', shouldSort: false });
});
});
// 2) Cargar proveedores dinámicamente
// Manejador para controlar el overflow al abrir dropdowns
document.addEventListener('click', function(e) {
const tableContainer = document.querySelector('.table-responsive');
if (!tableContainer) return;
if (e.target.closest('.choices__inner')) {
tableContainer.style.overflow = 'visible';
} else {
tableContainer.style.overflow = 'auto';
}
});
document.querySelector('#tabla-partidas tbody').addEventListener('click', e => {
if (e.target.matches('.remove-row')) e.target.closest('tr').remove();
});
// validate suma partidas == valor_factura
$('#solicitudForm').submit(function(e){
const total = parseFloat($('#valor_factura').val())||0;
let sum = 0;
$('.valor-partida').each(function(){ sum += parseFloat($(this).val())||0; });
if(Math.abs(sum - total) > 0.001){
e.preventDefault();
Swal.fire({
icon:'error',
title:'Error de validación',
text:`La suma de partidas (${sum.toFixed(2)}) no coincide con Valor Factura (${total.toFixed(2)}).`
});
}
});
</script>
<script>
// ✅ DIFERENCIA PRINCIPAL: Inicializar Choices.js en partidas existentes
document.addEventListener('DOMContentLoaded', function() {
// Inicializar Choices.js en selects existentes de partidas (para edición)
document.querySelectorAll('#tabla-partidas .searchable').forEach(el => {
if (el.id !== 'proveedor_id') {
new Choices(el, { searchEnabled: true, itemSelectText: '', shouldSort: false });
}
});
// Inicializar otros selects searchable (excepto proveedor_id que se maneja aparte)
document.querySelectorAll('.searchable').forEach(el => {
if (el.id !== 'proveedor_id' && !el.closest('#tabla-partidas')) {
new Choices(el, { searchEnabled: true, itemSelectText: '', shouldSort: false });
}
});
});
// proveedores script - ✅ DIFERENCIA: Pre-seleccionar proveedor existente
const proveedorEl = document.getElementById('proveedor_id');
let proveedorChoices = null;
const proveedorActual = '<?= htmlspecialchars($factura['proveedor_clave'] ?? '') ?>'; // ← Proveedor actual
fetch('/IMPORTADORES/solicitud_importacion/ajax_proveedores')
.then(res => res.ok ? res.json() : Promise.reject(res.status))
.then(res => { if (!res.ok) throw new Error(`HTTP ${res.status}`); return res.json(); })
.then(json => {
proveedorEl.innerHTML = '<option value="">-- Selecciona Proveedor --</option>';
json.results.forEach(item => {
const opt = document.createElement('option');
opt.value = item.id;
opt.text = item.text;
// ✅ Pre-seleccionar si coincide con el proveedor actual
if (item.id === proveedorActual) {
opt.selected = true;
}
proveedorEl.add(opt);
});
// destruir instancia previa si existe
if (proveedorChoices) proveedorChoices.destroy();
proveedorChoices = new Choices(proveedorEl, {
searchEnabled: true,
itemSelectText: '',
shouldSort: false
});
// seleccionar valor actual
const current = '<?= htmlspecialchars($factura['proveedor_id'], ENT_QUOTES) ?>';
if (current) {
proveedorChoices.setChoiceByValue(current);
}
if (proveedorEl._choice) proveedorEl._choice.destroy();
new Choices(proveedorEl, { searchEnabled: true, itemSelectText: '', shouldSort: false });
})
.catch(err => {
console.error('Error cargando proveedores:', err);
proveedorEl.innerHTML = '<option value="">No fue posible cargar proveedores</option>';
});
// 3) Agregar partida dinámica
document.getElementById('add-partida').addEventListener('click', () => {
const tbody = document.querySelector('#tabla-partidas tbody');
const idx = tbody.querySelectorAll('tr').length;
const row = document.createElement('tr');
row.innerHTML = `
<td><input name="partidas[\${idx}][descripcion]" class="form-control"></td>
<td><input name="partidas[\${idx}][cantidad_comercial]" type="number" step="0.0001" class="form-control"></td>
<td><input name="partidas[\${idx}][cantidad_tarifa]" type="number" step="0.0001" class="form-control"></td>
<td><select name="partidas[\${idx}][unidad_comercial_id]" class="form-select searchable"><option value="">-- Unidad --</option>
<?php foreach($unidades_medida as $um): ?>
<option value="<?= htmlspecialchars($um['id']) ?>"><?= htmlspecialchars($um['descripcion']) ?></option>
<?php endforeach; ?>
</select></td>
<td><input name="partidas[\${idx}][valor_factura]" type="number" step="0.01" class="form-control valor-partida"></td>
<td><input name="partidas[\${idx}][peso_bruto]" type="number" step="0.0001" class="form-control"></td>
<td><select name="partidas[\${idx}][tasa_preferencial]" class="form-select searchable"><option value="">-- Selecciona --</option>
<option>General</option><option>TLC</option><option>PROSEC</option><option>ALADI</option><option>COMERCIALIZADORA</option>
</select></td>
<td class="hide"><input name="partidas[\${idx}][precio_unitario]" type="number" class="form-control"></td>
<td class="hide"><input name="partidas[\${idx}][oma_factura]" class="form-control"></td>
<td><button type="button" class="btn btn-danger btn-sm remove-row">✖️</button></td>
`;
tbody.appendChild(row);
// re-init Choices para nuevos selects
row.querySelectorAll('.searchable').forEach(el => {
new Choices(el, { searchEnabled: true, itemSelectText: '', shouldSort: false });
});
});
// 4) Remover partida
document.querySelector('#tabla-partidas tbody').addEventListener('click', e => {
if (e.target.matches('.remove-row')) {
e.target.closest('tr').remove();
}
});
// 5) Validar suma de partidas
$('#solicitudForm').submit(function(e) {
const total = parseFloat($('.valor-total').val()) || 0;
let sum = 0;
$('.valor-partida').each(function() {
sum += parseFloat($(this).val()) || 0;
});
if (Math.abs(sum - total) > 0.01) {
e.preventDefault();
Swal.fire({
icon: 'error',
title: 'Error de validación',
text: `La suma de partidas (${sum.toFixed(2)}) no coincide con Valor Factura (${total.toFixed(2)})`
});
}
});
</script>
</body>
</html>

View File

@@ -1,3 +1,5 @@
<?php include __DIR__ . '/../partials/sidebar_importador.php'; ?>
<!DOCTYPE html>
<html lang="es">
<head>
@@ -82,7 +84,7 @@
</style>
</head>
<body>
<?php include __DIR__ . '/../partials/sidebar_importador.php'; ?>
<div class="content">
<h4>📄 Solicitudes de Importación</h4>
<a href="/IMPORTADORES/solicitud_importacion/crear" class="btn btn-success mb-3"> Nueva Solicitud</a>
@@ -114,7 +116,7 @@
<td><?= htmlspecialchars($f['fecha_factura']) ?></td>
<td><?= htmlspecialchars($f['numero_pedimento']) ?></td>
<td><?= htmlspecialchars($f['incoterm']) ?></td>
<td><?= htmlspecialchars($f['pais_proveedor']) ?></td>
<td><?= htmlspecialchars($f['nombre_pais_proveedor']) ?></td>
<td><?= htmlspecialchars($f['tipo_moneda']) ?></td>
<td><?= number_format($f['valor_factura'],2) ?></td>
<td>

View File

@@ -1,3 +1,5 @@
<?php include __DIR__ . '/../partials/sidebar_importador.php'; ?>
<!DOCTYPE html>
<html lang="es">
<head>
@@ -77,17 +79,15 @@
}
</style>
</head>
<body>
<?php include __DIR__ . '/../partials/sidebar_importador.php'; ?>
<div class="content">
<br> <br>
<h4> Nuevo Transporte</h4>
<form id="formTransCrear" action="/IMPORTADORES/transportes/guardar" method="POST" enctype="multipart/form-data" class="card p-4 shadow-sm">
<div class="row g-3">
<div class="col-md-6">
<label class="form-label">Vehículo *</label>
<label class="form-label">Contenedor *</label>
<input name="vehiculo" id="vehiculo" class="form-control" required>
</div>
<div class="col-md-6">
@@ -104,7 +104,7 @@
<option value="">Selecciona...</option>
<?php foreach($transportistas as $tr): ?>
<option value="<?= $tr['id_transportista'] ?>">
<?= htmlspecialchars($tr['nombre']) ?>
<?= htmlspecialchars(($tr['clave_identificador'] . ' - ' . $tr['nombre'])) ?>
</option>
<?php endforeach; ?>
</select>
@@ -143,6 +143,7 @@
// Si todas las validaciones pasan, el formulario se envía.
});
</script>
</body>
</html>

View File

@@ -112,7 +112,7 @@ if (!($_SESSION['usuario_id'] ?? false)) {
<label class="form-label">Archivo CSV *</label>
<input type="file" name="csv" accept=".csv" class="form-control" required>
</div>
<p>Descarga la plantilla y llena las columnas: <code>vehiculo, identificador_fiscal, id_transportista</code>.</p>
<p>Descarga la plantilla y llena las columnas: <code>contenedor, identificador_fiscal, id_transportista</code>.</p>
<a href="/IMPORTADORES/public/downloads/transportes_masivo_template.csv" class="btn btn-outline-secondary mb-3">
📥 Descargar plantilla
</a><br>
@@ -120,8 +120,7 @@ if (!($_SESSION['usuario_id'] ?? false)) {
</form>
</div>
</div>
</body>
</html>
<script>
document.addEventListener('DOMContentLoaded', function() {
const Toast = Swal.mixin({
@@ -149,3 +148,6 @@ if (!($_SESSION['usuario_id'] ?? false)) {
});
});
</script>
</body>
</html>