Revisión 2.1
This commit is contained in:
@@ -15,7 +15,7 @@ function lista() {
|
|||||||
SELECT
|
SELECT
|
||||||
c.*,
|
c.*,
|
||||||
(c.nombre + ' ' + c.apellido) AS nombre_completo,
|
(c.nombre + ' ' + c.apellido) AS nombre_completo,
|
||||||
tr.nombre AS transportista
|
(tr.clave_identificador + ' - ' + tr.nombre) AS transportista
|
||||||
FROM dbo.choferes c
|
FROM dbo.choferes c
|
||||||
JOIN dbo.transportistas tr
|
JOIN dbo.transportistas tr
|
||||||
ON c.transportista_id = tr.id_transportista
|
ON c.transportista_id = tr.id_transportista
|
||||||
@@ -45,8 +45,10 @@ function crear() {
|
|||||||
|
|
||||||
// OJO: aquí usamos "activo" según tu esquema original
|
// OJO: aquí usamos "activo" según tu esquema original
|
||||||
$sql = "
|
$sql = "
|
||||||
SELECT id_transportista, nombre
|
SELECT t.id_transportista, t.clave_identificador, t.nombre, t.ciudad, t.domicilio,
|
||||||
FROM dbo.transportistas
|
c.nombre AS ciudad_nombre
|
||||||
|
FROM dbo.transportistas t
|
||||||
|
LEFT JOIN dbo.ciudades c ON t.ciudad = c.id_ciudad
|
||||||
WHERE id_usuario = ? AND activo = 1
|
WHERE id_usuario = ? AND activo = 1
|
||||||
ORDER BY nombre
|
ORDER BY nombre
|
||||||
";
|
";
|
||||||
@@ -70,44 +72,86 @@ function guardar() {
|
|||||||
$nombre = trim($_POST['nombre'] ?? '');
|
$nombre = trim($_POST['nombre'] ?? '');
|
||||||
$apellido = trim($_POST['apellido'] ?? '');
|
$apellido = trim($_POST['apellido'] ?? '');
|
||||||
$licencia = trim($_POST['numero_licencia'] ?? '');
|
$licencia = trim($_POST['numero_licencia'] ?? '');
|
||||||
|
$gafete = trim($_POST['numero_gafete'] ?? '');
|
||||||
$telefono = trim($_POST['telefono'] ?? '');
|
$telefono = trim($_POST['telefono'] ?? '');
|
||||||
$email = trim($_POST['email'] ?? '');
|
$email = trim($_POST['email'] ?? '');
|
||||||
$fecha_ingreso = $_POST['fecha_ingreso'] ?: null;
|
$fecha_ingreso = $_POST['fecha_ingreso'] ?: null;
|
||||||
|
|
||||||
if (!$transportista_id || $nombre === '' || $apellido === '' || $licencia === '') {
|
if (!$transportista_id || !is_numeric($transportista_id) || $nombre === '' ||
|
||||||
|
$apellido === '' || $licencia === '' || $gafete === '') {
|
||||||
die("❌ Todos los campos obligatorios deben llenarse.");
|
die("❌ Todos los campos obligatorios deben llenarse.");
|
||||||
}
|
}
|
||||||
|
|
||||||
// Manejo de foto
|
$conn = getConnection();
|
||||||
|
|
||||||
|
// ✅ CRÍTICO: Verificar que el transportista pertenece al usuario
|
||||||
|
$sqlVerify = "SELECT id_transportista FROM dbo.transportistas WHERE id_transportista = ? AND id_usuario = ?";
|
||||||
|
$stmtVerify = sqlsrv_query($conn, $sqlVerify, [(int)$transportista_id, $_SESSION['usuario_id']]);
|
||||||
|
if (!$stmtVerify || !sqlsrv_fetch($stmtVerify)) {
|
||||||
|
die("❌ Transportista no autorizado.");
|
||||||
|
}
|
||||||
|
|
||||||
|
// ✅ CRÍTICO: Verificar que el número de gafete no existe
|
||||||
|
$sqlCheckGafete = "SELECT id_chofer FROM dbo.choferes WHERE numero_gafete = ? AND status = 1";
|
||||||
|
$stmtCheck = sqlsrv_query($conn, $sqlCheckGafete, [$gafete]);
|
||||||
|
if ($stmtCheck && sqlsrv_fetch($stmtCheck)) {
|
||||||
|
die("❌ El número de gafete '{$gafete}' ya está en uso. Por favor, use otro número.");
|
||||||
|
}
|
||||||
|
|
||||||
|
// ✅ MEJORADO: Manejo de foto con validación
|
||||||
$fotoUrl = null;
|
$fotoUrl = null;
|
||||||
if (!empty($_FILES['foto']['tmp_name'])) {
|
if (!empty($_FILES['foto']['tmp_name']) && $_FILES['foto']['error'] === UPLOAD_ERR_OK) {
|
||||||
$ext = strtolower(pathinfo($_FILES['foto']['name'], PATHINFO_EXTENSION));
|
$allowedTypes = ['jpg', 'jpeg', 'png', 'gif'];
|
||||||
|
$ext = strtolower(pathinfo($_FILES['foto']['name'], PATHINFO_EXTENSION));
|
||||||
|
|
||||||
|
if (!in_array($ext, $allowedTypes)) {
|
||||||
|
die("❌ Tipo de archivo no permitido. Solo JPG, PNG, GIF.");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Validar tamaño (ej: máximo 5MB)
|
||||||
|
if ($_FILES['foto']['size'] > 5 * 1024 * 1024) {
|
||||||
|
die("❌ El archivo es demasiado grande. Máximo 5MB.");
|
||||||
|
}
|
||||||
|
|
||||||
$dest = __DIR__ . '/../../public/uploads/chofer_'.uniqid().".{$ext}";
|
$dest = __DIR__ . '/../../public/uploads/chofer_'.uniqid().".{$ext}";
|
||||||
if (!is_dir(dirname($dest))) mkdir(dirname($dest), 0755, true);
|
if (!is_dir(dirname($dest))) {
|
||||||
|
mkdir(dirname($dest), 0755, true);
|
||||||
|
}
|
||||||
if (move_uploaded_file($_FILES['foto']['tmp_name'], $dest)) {
|
if (move_uploaded_file($_FILES['foto']['tmp_name'], $dest)) {
|
||||||
$fotoUrl = "/IMPORTADORES/public/uploads/" . basename($dest);
|
$fotoUrl = "/IMPORTADORES/public/uploads/" . basename($dest);
|
||||||
|
} else {
|
||||||
|
error_log("Error al mover foto en guardar(): {$dest}");
|
||||||
|
die("❌ Error al subir la foto.");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
$conn = getConnection();
|
|
||||||
$sql = "
|
$sql = "
|
||||||
INSERT INTO dbo.choferes
|
INSERT INTO dbo.choferes
|
||||||
(transportista_id, nombre, apellido, numero_licencia, telefono, email, fecha_ingreso, foto_url, status)
|
(transportista_id, nombre, apellido, numero_licencia, numero_gafete, telefono, email, fecha_ingreso, foto_url, status)
|
||||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?, 1)
|
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, 1, GETDATE())
|
||||||
";
|
";
|
||||||
$params = [
|
$params = [
|
||||||
(int)$transportista_id,
|
(int)$transportista_id,
|
||||||
$nombre,
|
$nombre,
|
||||||
$apellido,
|
$apellido,
|
||||||
$licencia,
|
$licencia,
|
||||||
|
$gafete,
|
||||||
$telefono,
|
$telefono,
|
||||||
$email,
|
$email,
|
||||||
$fecha_ingreso,
|
$fecha_ingreso,
|
||||||
$fotoUrl
|
$fotoUrl
|
||||||
];
|
];
|
||||||
|
|
||||||
$stmt = sqlsrv_query($conn, $sql, $params);
|
$stmt = sqlsrv_query($conn, $sql, $params);
|
||||||
if ($stmt === false) {
|
if ($stmt === false) {
|
||||||
die("Error en guardar(): " . print_r(sqlsrv_errors(), true));
|
$errors = sqlsrv_errors();
|
||||||
|
// ✅ Manejo específico de error de duplicado
|
||||||
|
foreach ($errors as $error) {
|
||||||
|
if (strpos($error['message'], 'UQ_numero_gafete') !== false) {
|
||||||
|
die("❌ El número de gafete ya está en uso. Por favor, use otro número.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
die("❌ Error en guardar(): " . print_r($errors, true));
|
||||||
}
|
}
|
||||||
|
|
||||||
header('Location: /IMPORTADORES/choferes/lista?created=ok');
|
header('Location: /IMPORTADORES/choferes/lista?created=ok');
|
||||||
@@ -126,13 +170,15 @@ function editar() {
|
|||||||
$conn = getConnection();
|
$conn = getConnection();
|
||||||
|
|
||||||
$sql = "
|
$sql = "
|
||||||
SELECT c.*, tr.nombre AS transportista
|
SELECT
|
||||||
FROM dbo.choferes c
|
ch.*, tr.clave_identificador, tr.nombre AS transportista_nombre, tr.ciudad, tr.domicilio,
|
||||||
JOIN dbo.transportistas tr
|
ciu.nombre AS ciudad_nombre
|
||||||
ON c.transportista_id = tr.id_transportista
|
FROM dbo.choferes ch
|
||||||
WHERE c.id_chofer = ?
|
LEFT JOIN dbo.transportistas tr ON ch.transportista_id = tr.id_transportista
|
||||||
|
LEFT JOIN dbo.ciudades ciu ON tr.ciudad = ciu.id_ciudad
|
||||||
|
WHERE ch.id_chofer = ?
|
||||||
AND tr.id_usuario = ?
|
AND tr.id_usuario = ?
|
||||||
AND c.status = 1
|
AND ch.status = 1
|
||||||
";
|
";
|
||||||
$stmt = sqlsrv_query($conn, $sql, [(int)$id, $_SESSION['usuario_id']]);
|
$stmt = sqlsrv_query($conn, $sql, [(int)$id, $_SESSION['usuario_id']]);
|
||||||
if ($stmt === false) {
|
if ($stmt === false) {
|
||||||
@@ -150,10 +196,12 @@ function editar() {
|
|||||||
|
|
||||||
// Lista de transportistas
|
// Lista de transportistas
|
||||||
$sql2 = "
|
$sql2 = "
|
||||||
SELECT id_transportista, nombre
|
SELECT tr.id_transportista, tr.clave_identificador, tr.nombre, tr.domicilio,
|
||||||
FROM dbo.transportistas
|
ciu.nombre AS ciudad_nombre
|
||||||
WHERE id_usuario = ? AND activo = 1
|
FROM dbo.transportistas tr
|
||||||
ORDER BY nombre
|
LEFT JOIN dbo.ciudades ciu ON tr.ciudad = ciu.id_ciudad
|
||||||
|
WHERE tr.id_usuario = ? AND tr.activo = 1
|
||||||
|
ORDER BY tr.nombre
|
||||||
";
|
";
|
||||||
$stmt2 = sqlsrv_query($conn, $sql2, [$_SESSION['usuario_id']]);
|
$stmt2 = sqlsrv_query($conn, $sql2, [$_SESSION['usuario_id']]);
|
||||||
if ($stmt2 === false) {
|
if ($stmt2 === false) {
|
||||||
@@ -167,6 +215,42 @@ function editar() {
|
|||||||
include __DIR__ . '/../../views/choferes/editar.php';
|
include __DIR__ . '/../../views/choferes/editar.php';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Valida que el número de gafete sea único **/
|
||||||
|
function validarNumeroGafete() {
|
||||||
|
if (!($_SESSION['usuario_id'] ?? false)) {
|
||||||
|
http_response_code(403);
|
||||||
|
echo json_encode(['success' => false, 'message' => 'No autorizado']);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
$gafete = trim($_GET['numero_gafete'] ?? '');
|
||||||
|
$id_chofer = $_GET['id_chofer'] ?? null;
|
||||||
|
|
||||||
|
if ($gafete === '') {
|
||||||
|
echo json_encode(['success' => false, 'message' => 'Número de gafete vacío']);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
$conn = getConnection();
|
||||||
|
|
||||||
|
if ($id_chofer) {
|
||||||
|
// Edición: excluir el chofer actual
|
||||||
|
$sql = "SELECT id_chofer FROM dbo.choferes WHERE numero_gafete = ? AND id_chofer <> ? AND status = 1";
|
||||||
|
$stmt = sqlsrv_query($conn, $sql, [$gafete, $id_chofer]);
|
||||||
|
} else {
|
||||||
|
// Alta nueva
|
||||||
|
$sql = "SELECT id_chofer FROM dbo.choferes WHERE numero_gafete = ? AND status = 1";
|
||||||
|
$stmt = sqlsrv_query($conn, $sql, [$gafete]);
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($stmt && sqlsrv_fetch($stmt)) {
|
||||||
|
echo json_encode(['success' => true, 'existe' => true]);
|
||||||
|
} else {
|
||||||
|
echo json_encode(['success' => true, 'existe' => false]);
|
||||||
|
}
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
/** Procesa la actualización de un chofer **/
|
/** Procesa la actualización de un chofer **/
|
||||||
function actualizar() {
|
function actualizar() {
|
||||||
if (!($_SESSION['usuario_id'] ?? false)) {
|
if (!($_SESSION['usuario_id'] ?? false)) {
|
||||||
@@ -178,6 +262,7 @@ function actualizar() {
|
|||||||
$nombre = trim($_POST['nombre'] ?? '');
|
$nombre = trim($_POST['nombre'] ?? '');
|
||||||
$apellido = trim($_POST['apellido'] ?? '');
|
$apellido = trim($_POST['apellido'] ?? '');
|
||||||
$licencia = trim($_POST['numero_licencia']?? '');
|
$licencia = trim($_POST['numero_licencia']?? '');
|
||||||
|
$gafete = trim($_POST['numero_gafete']?? '');
|
||||||
$telefono = trim($_POST['telefono'] ?? '');
|
$telefono = trim($_POST['telefono'] ?? '');
|
||||||
$email = trim($_POST['email'] ?? '');
|
$email = trim($_POST['email'] ?? '');
|
||||||
$fecha_ingreso = $_POST['fecha_ingreso'] ?: null;
|
$fecha_ingreso = $_POST['fecha_ingreso'] ?: null;
|
||||||
@@ -187,15 +272,35 @@ function actualizar() {
|
|||||||
if (
|
if (
|
||||||
!$id || !is_numeric($id) ||
|
!$id || !is_numeric($id) ||
|
||||||
!$transportista_id || !is_numeric($transportista_id) ||
|
!$transportista_id || !is_numeric($transportista_id) ||
|
||||||
$nombre === '' || $apellido === '' || $licencia === ''
|
$nombre === '' || $apellido === '' || $licencia === '' || $gafete === ''
|
||||||
) {
|
) {
|
||||||
die("❌ Datos inválidos o incompletos.");
|
die("❌ Datos inválidos o incompletos.");
|
||||||
}
|
}
|
||||||
|
|
||||||
// Manejo de foto nueva (opcional)
|
$conn = getConnection();
|
||||||
|
|
||||||
|
// ✅ CRÍTICO: Verificar que el número de gafete no existe (excluyendo el chofer actual)
|
||||||
|
$sqlCheckGafete = "SELECT id_chofer FROM dbo.choferes WHERE numero_gafete = ? AND id_chofer <> ? AND status = 1";
|
||||||
|
$stmtCheck = sqlsrv_query($conn, $sqlCheckGafete, [$gafete, $id_chofer]);
|
||||||
|
if ($stmtCheck && sqlsrv_fetch($stmtCheck)) {
|
||||||
|
die("❌ El número de gafete '{$gafete}' ya está en uso. Por favor, use otro número.");
|
||||||
|
}
|
||||||
|
|
||||||
|
// ✅ MEJORADO: Manejo de foto nueva con validación
|
||||||
$fotoUrl = null;
|
$fotoUrl = null;
|
||||||
if (!empty($_FILES['foto']['tmp_name']) && $_FILES['foto']['error'] === UPLOAD_ERR_OK) {
|
if (!empty($_FILES['foto']['tmp_name']) && $_FILES['foto']['error'] === UPLOAD_ERR_OK) {
|
||||||
|
$allowedTypes = ['jpg', 'jpeg', 'png', 'gif'];
|
||||||
$ext = strtolower(pathinfo($_FILES['foto']['name'], PATHINFO_EXTENSION));
|
$ext = strtolower(pathinfo($_FILES['foto']['name'], PATHINFO_EXTENSION));
|
||||||
|
|
||||||
|
if (!in_array($ext, $allowedTypes)) {
|
||||||
|
die("❌ Tipo de archivo no permitido. Solo JPG, PNG, GIF.");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Validar tamaño (ej: máximo 5MB)
|
||||||
|
if ($_FILES['foto']['size'] > 5 * 1024 * 1024) {
|
||||||
|
die("❌ El archivo es demasiado grande. Máximo 5MB.");
|
||||||
|
}
|
||||||
|
|
||||||
$dest = __DIR__ . '/../../public/uploads/chofer_'.uniqid().".{$ext}";
|
$dest = __DIR__ . '/../../public/uploads/chofer_'.uniqid().".{$ext}";
|
||||||
if (!is_dir(dirname($dest))) {
|
if (!is_dir(dirname($dest))) {
|
||||||
mkdir(dirname($dest), 0755, true);
|
mkdir(dirname($dest), 0755, true);
|
||||||
@@ -204,11 +309,10 @@ function actualizar() {
|
|||||||
$fotoUrl = "/IMPORTADORES/public/uploads/" . basename($dest);
|
$fotoUrl = "/IMPORTADORES/public/uploads/" . basename($dest);
|
||||||
} else {
|
} else {
|
||||||
error_log("Error al mover foto en actualizar(): {$dest}");
|
error_log("Error al mover foto en actualizar(): {$dest}");
|
||||||
|
die("❌ Error al subir la foto.");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
$conn = getConnection();
|
|
||||||
|
|
||||||
if ($fotoUrl) {
|
if ($fotoUrl) {
|
||||||
$sql = "
|
$sql = "
|
||||||
UPDATE dbo.choferes SET
|
UPDATE dbo.choferes SET
|
||||||
@@ -216,6 +320,7 @@ function actualizar() {
|
|||||||
nombre = ?,
|
nombre = ?,
|
||||||
apellido = ?,
|
apellido = ?,
|
||||||
numero_licencia = ?,
|
numero_licencia = ?,
|
||||||
|
numero_gafete = ?,
|
||||||
telefono = ?,
|
telefono = ?,
|
||||||
email = ?,
|
email = ?,
|
||||||
fecha_ingreso = ?,
|
fecha_ingreso = ?,
|
||||||
@@ -229,6 +334,7 @@ function actualizar() {
|
|||||||
$nombre,
|
$nombre,
|
||||||
$apellido,
|
$apellido,
|
||||||
$licencia,
|
$licencia,
|
||||||
|
$gafete,
|
||||||
$telefono,
|
$telefono,
|
||||||
$email,
|
$email,
|
||||||
$fecha_ingreso,
|
$fecha_ingreso,
|
||||||
@@ -243,6 +349,7 @@ function actualizar() {
|
|||||||
nombre = ?,
|
nombre = ?,
|
||||||
apellido = ?,
|
apellido = ?,
|
||||||
numero_licencia = ?,
|
numero_licencia = ?,
|
||||||
|
numero_gafete = ?,
|
||||||
telefono = ?,
|
telefono = ?,
|
||||||
email = ?,
|
email = ?,
|
||||||
fecha_ingreso = ?,
|
fecha_ingreso = ?,
|
||||||
@@ -255,6 +362,7 @@ function actualizar() {
|
|||||||
$nombre,
|
$nombre,
|
||||||
$apellido,
|
$apellido,
|
||||||
$licencia,
|
$licencia,
|
||||||
|
$gafete,
|
||||||
$telefono,
|
$telefono,
|
||||||
$email,
|
$email,
|
||||||
$fecha_ingreso,
|
$fecha_ingreso,
|
||||||
@@ -265,7 +373,14 @@ function actualizar() {
|
|||||||
|
|
||||||
$stmt = sqlsrv_query($conn, $sql, $params);
|
$stmt = sqlsrv_query($conn, $sql, $params);
|
||||||
if ($stmt === false) {
|
if ($stmt === false) {
|
||||||
die("❌ Error en actualizar(): " . print_r(sqlsrv_errors(), true));
|
$errors = sqlsrv_errors();
|
||||||
|
// ✅ Manejo específico de error de duplicado
|
||||||
|
foreach ($errors as $error) {
|
||||||
|
if (strpos($error['message'], 'UQ_numero_gafete') !== false) {
|
||||||
|
die("❌ El número de gafete ya está en uso por otro chofer. Por favor, use otro número.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
die("❌ Error en actualizar(): " . print_r($errors, true));
|
||||||
}
|
}
|
||||||
|
|
||||||
header('Location: /IMPORTADORES/choferes/lista?updated=ok');
|
header('Location: /IMPORTADORES/choferes/lista?updated=ok');
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ function lista() {
|
|||||||
|
|
||||||
// Sólo mostrar transportes de los transportistas que le pertenecen al usuario
|
// Sólo mostrar transportes de los transportistas que le pertenecen al usuario
|
||||||
$sql = "
|
$sql = "
|
||||||
SELECT t.*, tr.nombre AS transportista
|
SELECT t.*, (tr.clave_identificador + ' - ' + tr.nombre) AS transportista
|
||||||
FROM dbo.transportes t
|
FROM dbo.transportes t
|
||||||
JOIN dbo.transportistas tr
|
JOIN dbo.transportistas tr
|
||||||
ON t.id_transportista = tr.id_transportista
|
ON t.id_transportista = tr.id_transportista
|
||||||
@@ -41,8 +41,10 @@ function crear() {
|
|||||||
|
|
||||||
// Traer transportistas propios para el select
|
// Traer transportistas propios para el select
|
||||||
$sql = "
|
$sql = "
|
||||||
SELECT id_transportista, clave_identificador, nombre
|
SELECT t.id_transportista, t.clave_identificador, t.nombre, t.ciudad, t.domicilio,
|
||||||
FROM dbo.transportistas
|
c.nombre AS ciudad_nombre
|
||||||
|
FROM dbo.transportistas t
|
||||||
|
LEFT JOIN dbo.ciudades c ON t.ciudad = c.id_ciudad
|
||||||
WHERE id_usuario = ? AND activo = 1
|
WHERE id_usuario = ? AND activo = 1
|
||||||
ORDER BY nombre
|
ORDER BY nombre
|
||||||
";
|
";
|
||||||
@@ -56,31 +58,62 @@ function crear() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/** Procesa la creación de un nuevo transporte **/
|
/** Procesa la creación de un nuevo transporte **/
|
||||||
function guardar() {
|
function guardar()
|
||||||
|
{
|
||||||
if (!($_SESSION['usuario_id'] ?? false)) {
|
if (!($_SESSION['usuario_id'] ?? false)) {
|
||||||
die("⚠️ No autorizado.");
|
die("⚠️ No autorizado.");
|
||||||
}
|
}
|
||||||
$vehiculo = trim($_POST['vehiculo'] ?? '');
|
|
||||||
$identFiscal= trim($_POST['identificador_fiscal'] ?? '');
|
$vehiculo = trim($_POST['vehiculo'] ?? '');
|
||||||
$idTrans = $_POST['id_transportista'] ?? null;
|
$identFiscal = trim($_POST['identificador_fiscal'] ?? '');
|
||||||
|
$idTrans = $_POST['id_transportista'] ?? null;
|
||||||
|
|
||||||
if ($vehiculo === '' || $identFiscal === '' || !$idTrans) {
|
if ($vehiculo === '' || $identFiscal === '' || !$idTrans) {
|
||||||
die("❌ Todos los campos son obligatorios.");
|
die("❌ Todos los campos son obligatorios.");
|
||||||
}
|
}
|
||||||
|
|
||||||
// Manejo de foto
|
// Validar que el transportista pertenece al usuario actual
|
||||||
|
$conn = getConnection();
|
||||||
|
$sqlCheck = "
|
||||||
|
SELECT 1 FROM dbo.transportistas
|
||||||
|
WHERE id_transportista = ? AND id_usuario = ? AND activo = 1
|
||||||
|
";
|
||||||
|
$stmtCheck = sqlsrv_query($conn, $sqlCheck, [$idTrans, $_SESSION['usuario_id']]);
|
||||||
|
if (!sqlsrv_fetch($stmtCheck)) {
|
||||||
|
die("❌ Transportista no válido o no autorizado.");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Manejo de foto mejorado
|
||||||
$fotoUrl = null;
|
$fotoUrl = null;
|
||||||
if (!empty($_FILES['foto']['tmp_name'])) {
|
if (isset($_FILES['foto']) && $_FILES['foto']['error'] === UPLOAD_ERR_OK) {
|
||||||
$ext = pathinfo($_FILES['foto']['name'], PATHINFO_EXTENSION);
|
$allowedExtensions = ['jpg', 'jpeg', 'png', 'gif', 'webp'];
|
||||||
$dest = __DIR__ . '/../../public/uploads/transporte_'.uniqid().".{$ext}";
|
$ext = strtolower(pathinfo($_FILES['foto']['name'], PATHINFO_EXTENSION));
|
||||||
|
|
||||||
|
if (!in_array($ext, $allowedExtensions)) {
|
||||||
|
die("❌ Formato de imagen no válido. Solo se permiten: " . implode(', ', $allowedExtensions));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Validar tamaño (2MB max)
|
||||||
|
if ($_FILES['foto']['size'] > 2 * 1024 * 1024) {
|
||||||
|
die("❌ La imagen no debe exceder 2 MB.");
|
||||||
|
}
|
||||||
|
|
||||||
|
$uploadDir = __DIR__ . '/../../public/uploads/';
|
||||||
|
if (!is_dir($uploadDir)) {
|
||||||
|
mkdir($uploadDir, 0755, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
$dest = $uploadDir . 'transporte_' . uniqid() . ".{$ext}";
|
||||||
|
|
||||||
if (move_uploaded_file($_FILES['foto']['tmp_name'], $dest)) {
|
if (move_uploaded_file($_FILES['foto']['tmp_name'], $dest)) {
|
||||||
// ruta relativa
|
|
||||||
$fotoUrl = "/IMPORTADORES/public/uploads/" . basename($dest);
|
$fotoUrl = "/IMPORTADORES/public/uploads/" . basename($dest);
|
||||||
|
} else {
|
||||||
|
error_log("Error al mover archivo en guardar(): {$dest}");
|
||||||
|
die("❌ Error al subir la imagen.");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
$conn = getConnection();
|
// Insertar el nuevo transporte
|
||||||
$sql = "
|
$sql = "
|
||||||
INSERT INTO dbo.transportes
|
INSERT INTO dbo.transportes
|
||||||
(vehiculo, identificador_fiscal, foto_url, status, id_transportista)
|
(vehiculo, identificador_fiscal, foto_url, status, id_transportista)
|
||||||
@@ -88,8 +121,17 @@ function guardar() {
|
|||||||
";
|
";
|
||||||
$params = [$vehiculo, $identFiscal, $fotoUrl, $idTrans];
|
$params = [$vehiculo, $identFiscal, $fotoUrl, $idTrans];
|
||||||
$stmt = sqlsrv_query($conn, $sql, $params);
|
$stmt = sqlsrv_query($conn, $sql, $params);
|
||||||
|
|
||||||
if ($stmt === false) {
|
if ($stmt === false) {
|
||||||
die("❌ Error al guardar: ".print_r(sqlsrv_errors(),true));
|
$errors = sqlsrv_errors();
|
||||||
|
error_log("Error SQL en guardar transporte: " . print_r($errors, true));
|
||||||
|
die("❌ Error al guardar: " . $errors[0]['message']);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Verificar que se insertó correctamente
|
||||||
|
$rowsAffected = sqlsrv_rows_affected($stmt);
|
||||||
|
if ($rowsAffected === 0) {
|
||||||
|
die("❌ No se pudo crear el registro.");
|
||||||
}
|
}
|
||||||
|
|
||||||
header('Location: /IMPORTADORES/transportes/lista?created=ok');
|
header('Location: /IMPORTADORES/transportes/lista?created=ok');
|
||||||
@@ -124,8 +166,10 @@ function editar() {
|
|||||||
|
|
||||||
// Mismo select de transportistas que en crear()
|
// Mismo select de transportistas que en crear()
|
||||||
$sql2 = "
|
$sql2 = "
|
||||||
SELECT id_transportista, nombre
|
SELECT t.id_transportista, t.clave_identificador, t.nombre, t.ciudad, t.domicilio,
|
||||||
FROM dbo.transportistas
|
c.nombre AS ciudad_nombre
|
||||||
|
FROM dbo.transportistas t
|
||||||
|
LEFT JOIN dbo.ciudades c ON t.ciudad = c.id_ciudad
|
||||||
WHERE id_usuario = ? AND activo = 1
|
WHERE id_usuario = ? AND activo = 1
|
||||||
ORDER BY nombre
|
ORDER BY nombre
|
||||||
";
|
";
|
||||||
@@ -145,24 +189,49 @@ function actualizar() {
|
|||||||
$vehiculo = trim($_POST['vehiculo'] ?? '');
|
$vehiculo = trim($_POST['vehiculo'] ?? '');
|
||||||
$identFiscal = trim($_POST['identificador_fiscal'] ?? '');
|
$identFiscal = trim($_POST['identificador_fiscal'] ?? '');
|
||||||
$idTrans = $_POST['id_transportista'] ?? null;
|
$idTrans = $_POST['id_transportista'] ?? null;
|
||||||
|
|
||||||
if (!$id || !is_numeric($id) || $vehiculo === '' || $identFiscal === '' || !$idTrans) {
|
if (!$id || !is_numeric($id) || $vehiculo === '' || $identFiscal === '' || !$idTrans) {
|
||||||
die("❌ Faltan datos.");
|
die("❌ Faltan datos.");
|
||||||
}
|
}
|
||||||
|
|
||||||
$conn = getConnection();
|
$conn = getConnection();
|
||||||
|
|
||||||
|
// Antes del UPDATE, validar que el transporte pertenece al usuario
|
||||||
|
$sqlCheck = "
|
||||||
|
SELECT 1 FROM dbo.transportes t
|
||||||
|
JOIN dbo.transportistas tr ON t.id_transportista = tr.id_transportista
|
||||||
|
wHERE t.id_transporte = ? AND tr.id_usuario = ?
|
||||||
|
";
|
||||||
|
$stmtCheck = sqlsrv_query($conn, $sqlCheck, [$id, $_SESSION['usuario_id']]);
|
||||||
|
if ($stmtCheck === false) {
|
||||||
|
$errors = sqlsrv_errors();
|
||||||
|
error_log("Error SQL al validar transporte: " . print_r($errors, true));
|
||||||
|
die("❌ Error en la validación SQL.");
|
||||||
|
}
|
||||||
|
if (!sqlsrv_fetch($stmtCheck)) {
|
||||||
|
die("❌ No autorizado para modificar este transporte.");
|
||||||
|
}
|
||||||
|
|
||||||
// —– Manejo de nueva foto —–
|
// —– Manejo de nueva foto —–
|
||||||
$fotoUrl = null;
|
$fotoUrl = null;
|
||||||
if (isset($_FILES['foto']) && $_FILES['foto']['error'] === UPLOAD_ERR_OK) {
|
if (isset($_FILES['foto']) && $_FILES['foto']['error'] === UPLOAD_ERR_OK) {
|
||||||
|
$allowedExtensions = ['jpg', 'jpeg', 'png', 'gif', 'webp'];
|
||||||
$ext = strtolower(pathinfo($_FILES['foto']['name'], PATHINFO_EXTENSION));
|
$ext = strtolower(pathinfo($_FILES['foto']['name'], PATHINFO_EXTENSION));
|
||||||
|
|
||||||
|
if (!in_array($ext, $allowedExtensions)) {
|
||||||
|
die("❌ Formato de imagen no válido.");
|
||||||
|
}
|
||||||
|
|
||||||
$dest = __DIR__ . '/../../public/uploads/transporte_'.uniqid().".{$ext}";
|
$dest = __DIR__ . '/../../public/uploads/transporte_'.uniqid().".{$ext}";
|
||||||
if (!is_dir(dirname($dest))) {
|
if (!is_dir(dirname($dest))) {
|
||||||
mkdir(dirname($dest), 0755, true);
|
mkdir(dirname($dest), 0755, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (move_uploaded_file($_FILES['foto']['tmp_name'], $dest)) {
|
if (move_uploaded_file($_FILES['foto']['tmp_name'], $dest)) {
|
||||||
$fotoUrl = "/IMPORTADORES/public/uploads/" . basename($dest);
|
$fotoUrl = "/IMPORTADORES/public/uploads/" . basename($dest);
|
||||||
} else {
|
} else {
|
||||||
error_log("Error al mover archivo en actualizar(): {$dest}");
|
error_log("Error al mover archivo en actualizar(): {$dest}");
|
||||||
|
die("❌ Error al subir la imagen.");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -174,7 +243,7 @@ function actualizar() {
|
|||||||
identificador_fiscal = ?,
|
identificador_fiscal = ?,
|
||||||
id_transportista = ?,
|
id_transportista = ?,
|
||||||
foto_url = ?
|
foto_url = ?
|
||||||
WHERE id_transporte = ?
|
WHERE id_transporte = ?
|
||||||
";
|
";
|
||||||
$params = [$vehiculo, $identFiscal, $idTrans, $fotoUrl, $id];
|
$params = [$vehiculo, $identFiscal, $idTrans, $fotoUrl, $id];
|
||||||
} else {
|
} else {
|
||||||
@@ -190,7 +259,15 @@ function actualizar() {
|
|||||||
|
|
||||||
$stmt = sqlsrv_query($conn, $sql, $params);
|
$stmt = sqlsrv_query($conn, $sql, $params);
|
||||||
if ($stmt === false) {
|
if ($stmt === false) {
|
||||||
die("❌ Error al actualizar: " . print_r(sqlsrv_errors(), true));
|
$errors = sqlsrv_errors();
|
||||||
|
error_log("Error SQL en actualizar transporte: " . print_r($errors, true));
|
||||||
|
die("❌ Error al actualizar: " . $errors[0]['message']);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Verificar si se afectó alguna fila
|
||||||
|
$rowsAffected = sqlsrv_rows_affected($stmt);
|
||||||
|
if ($rowsAffected === 0) {
|
||||||
|
die("❌ No se pudo actualizar el registro.");
|
||||||
}
|
}
|
||||||
|
|
||||||
header('Location: /IMPORTADORES/transportes/lista?updated=ok');
|
header('Location: /IMPORTADORES/transportes/lista?updated=ok');
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
require_once __DIR__ . '/../helpers/session.php';
|
require_once __DIR__ . '/../helpers/session.php';
|
||||||
require_once __DIR__ . '/../../config/database.php';
|
require_once __DIR__ . '/../../config/database.php';
|
||||||
require_once __DIR__ . '/../helpers/env.php';
|
require_once __DIR__ . '/../helpers/env.php';
|
||||||
|
ob_clean();
|
||||||
|
|
||||||
function guardar() {
|
function guardar() {
|
||||||
if (!($_SESSION['usuario_id'] ?? false)) {
|
if (!($_SESSION['usuario_id'] ?? false)) {
|
||||||
@@ -264,21 +265,13 @@ function ajax_lista() {
|
|||||||
$params = [$usr];
|
$params = [$usr];
|
||||||
|
|
||||||
if ($search !== '') {
|
if ($search !== '') {
|
||||||
// Ahora buscamos en campos de transportista Y en el nombre de la ciudad
|
|
||||||
$where .= " AND (
|
$where .= " AND (
|
||||||
t.clave_identificador LIKE ? OR
|
|
||||||
t.nombre LIKE ? OR
|
t.nombre LIKE ? OR
|
||||||
t.rfc LIKE ? OR
|
t.rfc LIKE ? OR
|
||||||
t.curp LIKE ? OR
|
c.nombre LIKE ?
|
||||||
t.telefono LIKE ? OR
|
|
||||||
t.caat LIKE ? OR
|
|
||||||
c.nombre LIKE ? OR
|
|
||||||
p.nombre LIKE ? OR
|
|
||||||
e.nombre LIKE ?
|
|
||||||
)";
|
)";
|
||||||
$like = "%{$search}%";
|
$like = "%{$search}%";
|
||||||
// Agregamos el parámetro para cada campo de búsqueda
|
$params = array_merge($params, array_fill(0, 3, $like));
|
||||||
$params = array_merge($params, array_fill(0, 9, $like));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 5) Total registros filtrados (CON JOIN)
|
// 5) Total registros filtrados (CON JOIN)
|
||||||
@@ -332,13 +325,23 @@ function ajax_lista() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 7) Devolver JSON
|
// 7) Devolver JSON
|
||||||
|
$response = [
|
||||||
|
"draw" => $draw,
|
||||||
|
"recordsTotal" => $recordsTotal,
|
||||||
|
"recordsFiltered" => $recordsFiltered,
|
||||||
|
"data" => $data
|
||||||
|
];
|
||||||
|
|
||||||
|
$json = json_encode($response, JSON_UNESCAPED_UNICODE);
|
||||||
|
if (json_last_error() !== JSON_ERROR_NONE) {
|
||||||
|
http_response_code(500);
|
||||||
|
echo json_encode(["error" => "JSON encoding error: " . json_last_error_msg()]);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
header('Content-Type: application/json; charset=UTF-8');
|
header('Content-Type: application/json; charset=UTF-8');
|
||||||
echo json_encode([
|
header('Cache-Control: no-cache, must-revalidate');
|
||||||
"draw" => $draw,
|
echo $json;
|
||||||
"recordsTotal" => $recordsTotal,
|
|
||||||
"recordsFiltered" => $recordsFiltered,
|
|
||||||
"data" => $data
|
|
||||||
]);
|
|
||||||
exit;
|
exit;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -44,7 +44,7 @@
|
|||||||
<option value="">-- Selecciona un transportista --</option>
|
<option value="">-- Selecciona un transportista --</option>
|
||||||
<?php foreach ($transportistas as $t): ?>
|
<?php foreach ($transportistas as $t): ?>
|
||||||
<option value="<?= $t['id_transportista'] ?>">
|
<option value="<?= $t['id_transportista'] ?>">
|
||||||
<?= htmlspecialchars($t['nombre']) ?>
|
<?= htmlspecialchars($t['clave_identificador'] . ' - ' . $t['nombre'] . ' - ' . $t['ciudad_nombre'] . ' - ' . $t['domicilio']) ?>
|
||||||
</option>
|
</option>
|
||||||
<?php endforeach; ?>
|
<?php endforeach; ?>
|
||||||
</select>
|
</select>
|
||||||
@@ -65,6 +65,11 @@
|
|||||||
<input name="numero_licencia" id="numero_licencia" type="text" maxlength="11" class="form-control" required>
|
<input name="numero_licencia" id="numero_licencia" type="text" maxlength="11" class="form-control" required>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div class="mb-3">
|
||||||
|
<label for="numero_gafete" class="form-label">Número de Gafete</label>
|
||||||
|
<input name="numero_gafete" id="numero_gafete" type="text" maxlength="24" class="form-control" required>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div class="mb-3">
|
<div class="mb-3">
|
||||||
<label for="telefono" class="form-label">Teléfono</label>
|
<label for="telefono" class="form-label">Teléfono</label>
|
||||||
<input name="telefono" id="telefono" type="tel" maxlength="11" class="form-control">
|
<input name="telefono" id="telefono" type="tel" maxlength="11" class="form-control">
|
||||||
@@ -101,6 +106,7 @@
|
|||||||
const nombre = document.getElementById('nombre').value.trim();
|
const nombre = document.getElementById('nombre').value.trim();
|
||||||
const apellido = document.getElementById('apellido').value.trim();
|
const apellido = document.getElementById('apellido').value.trim();
|
||||||
const numero_licencia = document.getElementById('numero_licencia').value.trim();
|
const numero_licencia = document.getElementById('numero_licencia').value.trim();
|
||||||
|
const numero_gafete = document.getElementById('numero_gafete').value.trim();
|
||||||
const telefono = document.getElementById('telefono').value.trim();
|
const telefono = document.getElementById('telefono').value.trim();
|
||||||
const email = document.getElementById('email').value.trim();
|
const email = document.getElementById('email').value.trim();
|
||||||
const fecha_ingreso = document.getElementById('fecha_ingreso').value;
|
const fecha_ingreso = document.getElementById('fecha_ingreso').value;
|
||||||
@@ -132,6 +138,11 @@
|
|||||||
document.getElementById('numero_licencia').focus();
|
document.getElementById('numero_licencia').focus();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
if (!numero_gafete) {
|
||||||
|
Swal.fire({ icon: 'error', title: 'Número de Gafete requerido', text: 'Por favor ingresa el número de gafete.', confirmButtonColor: '#dc3545' });
|
||||||
|
document.getElementById('numero_gafete').focus();
|
||||||
|
return;
|
||||||
|
}
|
||||||
if (!soloNumerosRegex.test(numero_licencia)) {
|
if (!soloNumerosRegex.test(numero_licencia)) {
|
||||||
Swal.fire({ icon: 'error', title: 'Número de Licencia inválido', text: 'El número de licencia solo puede contener números', confirmButtonColor: '#dc3545'
|
Swal.fire({ icon: 'error', title: 'Número de Licencia inválido', text: 'El número de licencia solo puede contener números', confirmButtonColor: '#dc3545'
|
||||||
});
|
});
|
||||||
@@ -175,9 +186,22 @@
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// VALIDACIÓN ASÍNCRONA DE DUPLICADO DE GAFETE
|
||||||
// SI TODAS LAS VALIDACIONES PASAN, ENVIAR EL FORMULARIO
|
fetch(`/IMPORTADORES/choferes/validarNumeroGafete?numero_gafete=${encodeURIComponent(numero_gafete)}`)
|
||||||
this.submit();
|
.then(response => response.json())
|
||||||
|
.then(data => {
|
||||||
|
if (data.success && data.existe) {
|
||||||
|
Swal.fire({ icon: 'error', title: 'Número de Gafete duplicado', text: 'El número de gafete ya está en uso, por favor ingresa uno diferente.', confirmButtonColor: '#dc3545' });
|
||||||
|
document.getElementById('numero_gafete').focus();
|
||||||
|
} else {
|
||||||
|
// Si no existe, enviamos el formulario
|
||||||
|
e.target.submit();
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch(error => {
|
||||||
|
console.error('Error validando el número de gafete:', error);
|
||||||
|
Swal.fire({ icon: 'error', title: 'Error de validación', text: 'No fue posible validar el número de gafete. Intenta de nuevo.', confirmButtonColor: '#dc3545' });
|
||||||
|
});
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|||||||
@@ -46,7 +46,7 @@
|
|||||||
<?php foreach ($transportistas as $t): ?>
|
<?php foreach ($transportistas as $t): ?>
|
||||||
<option value="<?= $t['id_transportista'] ?>"
|
<option value="<?= $t['id_transportista'] ?>"
|
||||||
<?= $chofer['transportista_id'] == $t['id_transportista'] ? 'selected' : '' ?>>
|
<?= $chofer['transportista_id'] == $t['id_transportista'] ? 'selected' : '' ?>>
|
||||||
<?= htmlspecialchars($t['nombre']) ?>
|
<?= htmlspecialchars($t['clave_identificador'] . ' - ' . $t['nombre'] . ' - ' . $t['ciudad_nombre'] . ' - ' . $t['domicilio']) ?>
|
||||||
</option>
|
</option>
|
||||||
<?php endforeach; ?>
|
<?php endforeach; ?>
|
||||||
</select>
|
</select>
|
||||||
@@ -76,6 +76,13 @@
|
|||||||
value="<?= htmlspecialchars($chofer['numero_licencia']) ?>" required>
|
value="<?= htmlspecialchars($chofer['numero_licencia']) ?>" required>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- Gafete -->
|
||||||
|
<div class="col-md-6 mb-3">
|
||||||
|
<label for="numero_gafete" class="form-label">Número de Gafete</label>
|
||||||
|
<input name="numero_gafete" id="numero_gafete" type="text" maxlength="24" class="form-control"
|
||||||
|
value="<?= htmlspecialchars($chofer['numero_gafete']) ?>" required>
|
||||||
|
</div>
|
||||||
|
|
||||||
<!-- Teléfono -->
|
<!-- Teléfono -->
|
||||||
<div class="col-md-6 mb-3">
|
<div class="col-md-6 mb-3">
|
||||||
<label for="telefono" class="form-label">Teléfono</label>
|
<label for="telefono" class="form-label">Teléfono</label>
|
||||||
@@ -138,6 +145,7 @@
|
|||||||
const nombre = document.getElementById('nombre').value.trim();
|
const nombre = document.getElementById('nombre').value.trim();
|
||||||
const apellido = document.getElementById('apellido').value.trim();
|
const apellido = document.getElementById('apellido').value.trim();
|
||||||
const numero_licencia = document.getElementById('numero_licencia').value.trim();
|
const numero_licencia = document.getElementById('numero_licencia').value.trim();
|
||||||
|
const numero_gafete = document.getElementById('numero_gafete').value.trim();
|
||||||
const telefono = document.getElementById('telefono').value.trim();
|
const telefono = document.getElementById('telefono').value.trim();
|
||||||
const email = document.getElementById('email').value.trim();
|
const email = document.getElementById('email').value.trim();
|
||||||
const fecha_ingreso = document.getElementById('fecha_ingreso').value;
|
const fecha_ingreso = document.getElementById('fecha_ingreso').value;
|
||||||
@@ -169,6 +177,11 @@
|
|||||||
document.getElementById('numero_licencia').focus();
|
document.getElementById('numero_licencia').focus();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
if (!numero_gafete) {
|
||||||
|
Swal.fire({ icon: 'error', title: 'Número de Gafete requerido', text: 'Por favor ingresa el número de gafete.', confirmButtonColor: '#dc3545' });
|
||||||
|
document.getElementById('numero_gafete').focus();
|
||||||
|
return;
|
||||||
|
}
|
||||||
if (!soloNumerosRegex.test(numero_licencia)) {
|
if (!soloNumerosRegex.test(numero_licencia)) {
|
||||||
Swal.fire({ icon: 'error', title: 'Número de Licencia inválido', text: 'El número de licencia solo puede contener números', confirmButtonColor: '#dc3545' });
|
Swal.fire({ icon: 'error', title: 'Número de Licencia inválido', text: 'El número de licencia solo puede contener números', confirmButtonColor: '#dc3545' });
|
||||||
document.getElementById('Clave').focus();
|
document.getElementById('Clave').focus();
|
||||||
@@ -211,9 +224,24 @@
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// VALIDACIÓN ASÍNCRONA DE DUPLICADO DE GAFETE
|
||||||
|
const id_chofer = document.querySelector('input[name="id_chofer"]').value;
|
||||||
|
|
||||||
// SI TODAS LAS VALIDACIONES PASAN, ENVIAR EL FORMULARIO
|
fetch(`/IMPORTADORES/choferes/validarNumeroGafete?numero_gafete=${encodeURIComponent(numero_gafete)}&id_chofer={id_chofer}`)
|
||||||
this.submit();
|
.then(response => response.json())
|
||||||
|
.then(data => {
|
||||||
|
if (data.success && data.existe) {
|
||||||
|
Swal.fire({ icon: 'error', title: 'Número de Gafete duplicado', text: 'El número de gafete ya está en uso, por favor ingresa uno diferente.', confirmButtonColor: '#dc3545' });
|
||||||
|
document.getElementById('numero_gafete').focus();
|
||||||
|
} else {
|
||||||
|
// Si no existe, enviamos el formulario
|
||||||
|
e.target.submit();
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch(error => {
|
||||||
|
console.error('Error validando el número de gafete:', error);
|
||||||
|
Swal.fire({ icon: 'error', title: 'Error de validación', text: 'No fue posible validar el número de gafete. Intenta de nuevo.', confirmButtonColor: '#dc3545' });
|
||||||
|
});
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|||||||
@@ -47,6 +47,7 @@
|
|||||||
<th>#</th>
|
<th>#</th>
|
||||||
<th>Nombre Completo</th>
|
<th>Nombre Completo</th>
|
||||||
<th>Licencia</th>
|
<th>Licencia</th>
|
||||||
|
<th>Gafete</th>
|
||||||
<th>Teléfono</th>
|
<th>Teléfono</th>
|
||||||
<th>Email</th>
|
<th>Email</th>
|
||||||
<th>Ingreso</th>
|
<th>Ingreso</th>
|
||||||
@@ -60,6 +61,7 @@
|
|||||||
<td><?= $c['id_chofer'] ?></td>
|
<td><?= $c['id_chofer'] ?></td>
|
||||||
<td><?= htmlspecialchars($c['nombre_completo']) ?></td>
|
<td><?= htmlspecialchars($c['nombre_completo']) ?></td>
|
||||||
<td><?= htmlspecialchars($c['numero_licencia']) ?></td>
|
<td><?= htmlspecialchars($c['numero_licencia']) ?></td>
|
||||||
|
<td><?= htmlspecialchars($c['numero_gafete']) ?></td>
|
||||||
<td><?= htmlspecialchars($c['telefono']) ?></td>
|
<td><?= htmlspecialchars($c['telefono']) ?></td>
|
||||||
<td><?= htmlspecialchars($c['email']) ?></td>
|
<td><?= htmlspecialchars($c['email']) ?></td>
|
||||||
<td>
|
<td>
|
||||||
|
|||||||
@@ -59,7 +59,7 @@
|
|||||||
<button type="submit" class="btn btn-success w-100">
|
<button type="submit" class="btn btn-success w-100">
|
||||||
<i class="fas fa-plus"></i> Registrar Estado
|
<i class="fas fa-plus"></i> Registrar Estado
|
||||||
</button>
|
</button>
|
||||||
<a href="/IMPORTADORES/agentes/lista" class="btn btn-secondary ms-2">Cancelar</a>
|
<a href="/IMPORTADORES/locaciones/lista" class="btn btn-secondary ms-2">Cancelar</a>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
|
|
||||||
@@ -69,7 +69,7 @@
|
|||||||
<!-- Nueva Ciudad -->
|
<!-- Nueva Ciudad -->
|
||||||
<div class="col-md">
|
<div class="col-md">
|
||||||
<div class="card p-4 bg-white shadow-sm">
|
<div class="card p-4 bg-white shadow-sm">
|
||||||
<form id="ciudadForm" action="/IMPORTADORES/agentes/guadarCiudad" method="POST" enctype="multipart/form-data">
|
<form id="ciudadForm" action="/IMPORTADORES/locaciones/guadarCiudad" method="POST" enctype="multipart/form-data">
|
||||||
<h4 class="mb-4 text-dark">➕ Nueva Ciudad</h4>
|
<h4 class="mb-4 text-dark">➕ Nueva Ciudad</h4>
|
||||||
<!-- País -->
|
<!-- País -->
|
||||||
<div class="col-md-12">
|
<div class="col-md-12">
|
||||||
@@ -102,7 +102,7 @@
|
|||||||
<button type="submit" class="btn btn-success w-100">
|
<button type="submit" class="btn btn-success w-100">
|
||||||
<i class="fas fa-plus"></i> Registrar Ciudad
|
<i class="fas fa-plus"></i> Registrar Ciudad
|
||||||
</button>
|
</button>
|
||||||
<a href="/IMPORTADORES/agentes/lista" class="btn btn-secondary ms-2">Cancelar</a>
|
<a href="/IMPORTADORES/locaciones/lista" class="btn btn-secondary ms-2">Cancelar</a>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
@@ -117,7 +117,7 @@
|
|||||||
selectElement.innerHTML = '<option>Cargando...</option>';
|
selectElement.innerHTML = '<option>Cargando...</option>';
|
||||||
selectElement.disabled = true;
|
selectElement.disabled = true;
|
||||||
|
|
||||||
fetch(`/IMPORTADORES/agentes/estados?pais=${paisId}`)
|
fetch(`/IMPORTADORES/locaciones/estados?pais=${paisId}`)
|
||||||
.then(response => response.json())
|
.then(response => response.json())
|
||||||
.then(estados => {
|
.then(estados => {
|
||||||
selectElement.innerHTML = '<option value="">Selecciona estado</option>';
|
selectElement.innerHTML = '<option value="">Selecciona estado</option>';
|
||||||
@@ -193,7 +193,7 @@
|
|||||||
entidad: entidadValue
|
entidad: entidadValue
|
||||||
});
|
});
|
||||||
|
|
||||||
fetch('/IMPORTADORES/agentes/guardarEstado', {
|
fetch('/IMPORTADORES/locaciones/guardarEstado', {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
body: formData
|
body: formData
|
||||||
})
|
})
|
||||||
@@ -241,7 +241,7 @@
|
|||||||
submitBtn.innerHTML = '<i class="fas fa-spinner fa-spin"></i> Guardando...';
|
submitBtn.innerHTML = '<i class="fas fa-spinner fa-spin"></i> Guardando...';
|
||||||
submitBtn.disabled = true;
|
submitBtn.disabled = true;
|
||||||
|
|
||||||
fetch('/IMPORTADORES/agentes/guardarCiudad', {
|
fetch('/IMPORTADORES/locaciones/guardarCiudad', {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
body: formData
|
body: formData
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -47,7 +47,7 @@
|
|||||||
/* Asegurar que las pestañas sean completamente visibles */
|
/* Asegurar que las pestañas sean completamente visibles */
|
||||||
.nav-tabs .nav-item:first-child .nav-link { margin-left: 0; }
|
.nav-tabs .nav-item:first-child .nav-link { margin-left: 0; }
|
||||||
.nav-tabs .nav-item:last-child .nav-link { margin-right: 0; }
|
.nav-tabs .nav-item:last-child .nav-link { margin-right: 0; }
|
||||||
.tab-content { padding: 5px; }
|
.tab-content { padding: 10px; }
|
||||||
</style>
|
</style>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
@@ -119,7 +119,7 @@
|
|||||||
<div class="col-md-10">
|
<div class="col-md-10">
|
||||||
<input name="razon_social" id="razon_social" type="text" class="form-control" required>
|
<input name="razon_social" id="razon_social" type="text" class="form-control" required>
|
||||||
</div>
|
</div>
|
||||||
</div><hr><br>
|
</div><hr>
|
||||||
|
|
||||||
<div class="col mb-3">
|
<div class="col mb-3">
|
||||||
<P>Capturar para el llenado de la Manifestación de Valor</P>
|
<P>Capturar para el llenado de la Manifestación de Valor</P>
|
||||||
@@ -251,7 +251,7 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div style="display: flex; justify-content: right;">
|
<div style="display: flex; justify-content: right;">
|
||||||
<button type="submit" class="btn btn-success">Guardar</button>
|
<button type="submit" class="btn btn-primary">Registrar</button>
|
||||||
<a href="/IMPORTADORES/patente/lista" class="btn btn-secondary ms-2">Cancelar</a>
|
<a href="/IMPORTADORES/patente/lista" class="btn btn-secondary ms-2">Cancelar</a>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
|
|||||||
@@ -37,7 +37,7 @@
|
|||||||
<body>
|
<body>
|
||||||
|
|
||||||
<div class="content">
|
<div class="content">
|
||||||
<h4 class="mb-4">📦 Panel del Importador</h4>
|
<h4 class="mb-4">📦 Panel de Patentes</h4>
|
||||||
|
|
||||||
<div class="row g-4">
|
<div class="row g-4">
|
||||||
<div class="col-md-4">
|
<div class="col-md-4">
|
||||||
@@ -45,8 +45,8 @@
|
|||||||
<h5 class="text-primary">Ver patentes</h5>
|
<h5 class="text-primary">Ver patentes</h5>
|
||||||
<p>Revisa y gestiona las patentes.</p>
|
<p>Revisa y gestiona las patentes.</p>
|
||||||
<a href="/IMPORTADORES/patente/lista"
|
<a href="/IMPORTADORES/patente/lista"
|
||||||
class="btn btn-primary btn-sm mt-2 <?= $_SERVER['REQUEST_URI'] === '/IMPORTADORES/transportes/lista' ? 'active' : '' ?>">
|
class="btn btn-primary btn-sm mt-2 <?= $_SERVER['REQUEST_URI'] === '/IMPORTADORES/transportes/lista' ? 'active' : '' ?>">
|
||||||
Ver transportes
|
Ver patentes
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -56,8 +56,8 @@
|
|||||||
<h5 class="text-success">Nueva patente</h5>
|
<h5 class="text-success">Nueva patente</h5>
|
||||||
<p>Agrega nuevas patentes:</p>
|
<p>Agrega nuevas patentes:</p>
|
||||||
<a href="/IMPORTADORES/patente/alta"
|
<a href="/IMPORTADORES/patente/alta"
|
||||||
class="btn btn-success btn-sm mt-2 <?= $_SERVER['REQUEST_URI'] === '/IMPORTADORES/transportistas/lista' ? 'active' : '' ?>">
|
class="btn btn-success btn-sm mt-2 <?= $_SERVER['REQUEST_URI'] === '/IMPORTADORES/transportistas/lista' ? 'active' : '' ?>">
|
||||||
Ver transportistas
|
Registrar nueva patente
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -44,7 +44,7 @@
|
|||||||
/* Asegurar que las pestañas sean completamente visibles */
|
/* Asegurar que las pestañas sean completamente visibles */
|
||||||
.nav-tabs .nav-item:first-child .nav-link { margin-left: 0; }
|
.nav-tabs .nav-item:first-child .nav-link { margin-left: 0; }
|
||||||
.nav-tabs .nav-item:last-child .nav-link { margin-right: 0; }
|
.nav-tabs .nav-item:last-child .nav-link { margin-right: 0; }
|
||||||
.tab-content { padding: 5px; }
|
.tab-content { padding: 10px; }
|
||||||
</style>
|
</style>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
@@ -124,11 +124,11 @@
|
|||||||
<input name="razon_social" id="razon_social" type="text" class="form-control"
|
<input name="razon_social" id="razon_social" type="text" class="form-control"
|
||||||
value="<?= htmlspecialchars($agente['razon_social'] ?? '') ?>" required>
|
value="<?= htmlspecialchars($agente['razon_social'] ?? '') ?>" required>
|
||||||
</div>
|
</div>
|
||||||
</div><hr><br>
|
</div><hr>
|
||||||
|
|
||||||
<div class="col mb-3">
|
<div class="col mb-3">
|
||||||
<P>Capturar para el llenado de la Manifestación de Valor</P>
|
<P>Capturar para el llenado de la Manifestación de Valor</P>
|
||||||
<p>Datos del Agente Aduanal:</p>
|
<p style="font-weight: bold;">Datos del Agente Aduanal:</p>
|
||||||
<div class="row mb-3">
|
<div class="row mb-3">
|
||||||
<label for="mf_nombre" class="col-md-2 col-form-label">Nombre(s)</label>
|
<label for="mf_nombre" class="col-md-2 col-form-label">Nombre(s)</label>
|
||||||
<div class="col-md-9">
|
<div class="col-md-9">
|
||||||
@@ -274,7 +274,7 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div style="display: flex; justify-content: right;">
|
<div style="display: flex; justify-content: right;">
|
||||||
<button type="submit" class="btn btn-success">Guardar</button>
|
<button type="submit" class="btn btn-success">💾 Guardar</button>
|
||||||
<a href="/IMPORTADORES/patente/lista" class="btn btn-secondary ms-2">Cancelar</a>
|
<a href="/IMPORTADORES/patente/lista" class="btn btn-secondary ms-2">Cancelar</a>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
|
|||||||
@@ -135,7 +135,7 @@
|
|||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-md-4 mb-3">
|
<div class="col-md-4 mb-3">
|
||||||
<label for="foto_solicitud" class="form-label">Foto de la solicitud</label>
|
<label for="foto_solicitud" class="form-label">Foto de la Carga (PIPA)</label>
|
||||||
<input id="foto_solicitud" name="foto_solicitud" type="file" class="form-control" accept="image/*">
|
<input id="foto_solicitud" name="foto_solicitud" type="file" class="form-control" accept="image/*">
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -217,135 +217,134 @@
|
|||||||
<script src="https://cdn.jsdelivr.net/npm/select2@4.1.0/dist/js/select2.min.js"></script>
|
<script src="https://cdn.jsdelivr.net/npm/select2@4.1.0/dist/js/select2.min.js"></script>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
// 1) Inicializar Choices para todos los selects excepto proveedor_id
|
document.addEventListener('DOMContentLoaded', () => {
|
||||||
document.querySelectorAll('.searchable:not(#proveedor_id)').forEach(el => {
|
// Primero cargamos proveedores, luego inicializamos Choices en todos los selects
|
||||||
new Choices(el, { searchEnabled: true, itemSelectText: '', shouldSort: false });
|
cargarProveedores().then(() => {
|
||||||
|
inicializarChoicesGlobal();
|
||||||
|
});
|
||||||
|
|
||||||
|
// Inicializamos los eventos adicionales
|
||||||
|
inicializarEventos();
|
||||||
});
|
});
|
||||||
|
|
||||||
// 2) Cargar proveedores dinámicamente
|
// Función para cargar proveedores (retorna promesa)
|
||||||
const proveedorEl = document.getElementById('proveedor_id');
|
function cargarProveedores() {
|
||||||
let proveedorChoices = null;
|
const proveedorEl = document.getElementById('proveedor_id');
|
||||||
|
|
||||||
fetch('/IMPORTADORES/solicitud_importacion/ajax_proveedores')
|
return fetch('/IMPORTADORES/solicitud_importacion/ajax_proveedores')
|
||||||
.then(res => res.ok ? res.json() : Promise.reject(res.status))
|
.then(res => {
|
||||||
.then(json => {
|
if (!res.ok) throw new Error(`HTTP ${res.status}`);
|
||||||
proveedorEl.innerHTML = '<option value="">-- Selecciona Proveedor --</option>';
|
return res.json();
|
||||||
json.results.forEach(item => {
|
})
|
||||||
const opt = document.createElement('option');
|
.then(json => {
|
||||||
opt.value = item.id;
|
proveedorEl.innerHTML = '<option value="">-- Selecciona Proveedor --</option>';
|
||||||
opt.text = item.text;
|
json.results.forEach(item => {
|
||||||
proveedorEl.add(opt);
|
const opt = document.createElement('option');
|
||||||
|
opt.value = item.id;
|
||||||
|
opt.textContent = item.text;
|
||||||
|
proveedorEl.appendChild(opt);
|
||||||
|
});
|
||||||
|
})
|
||||||
|
.catch(err => {
|
||||||
|
console.error('❌ Error cargando proveedores:', err);
|
||||||
|
proveedorEl.innerHTML = '<option value="">Error cargando proveedores</option>';
|
||||||
});
|
});
|
||||||
// destruir instancia previa si existe
|
}
|
||||||
if (proveedorChoices) proveedorChoices.destroy();
|
|
||||||
proveedorChoices = new Choices(proveedorEl, {
|
// Inicializamos Choices globalmente (todos los .searchable)
|
||||||
|
function inicializarChoicesGlobal() {
|
||||||
|
document.querySelectorAll('.searchable').forEach(el => {
|
||||||
|
if (el._choices) el._choices.destroy(); // destruye instancia previa si existe
|
||||||
|
el._choices = new Choices(el, {
|
||||||
searchEnabled: true,
|
searchEnabled: true,
|
||||||
itemSelectText: '',
|
itemSelectText: '',
|
||||||
shouldSort: false
|
shouldSort: false,
|
||||||
|
searchFields: ['label'] // 🔐 Solo busca en el texto visible
|
||||||
});
|
});
|
||||||
// seleccionar valor actual
|
});
|
||||||
const current = "<?= htmlspecialchars($factura['proveedor_id'], ENT_QUOTES) ?>";
|
}
|
||||||
if (current) {
|
|
||||||
proveedorChoices.setChoiceByValue(current);
|
// Eventos principales del formulario
|
||||||
|
function inicializarEventos() {
|
||||||
|
// Agregar partidas dinámicamente
|
||||||
|
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);
|
||||||
|
// Inicializamos Choices en los nuevos selects
|
||||||
|
row.querySelectorAll('.searchable').forEach(el => {
|
||||||
|
el._choices = new Choices(el, {
|
||||||
|
searchEnabled: true,
|
||||||
|
itemSelectText: '',
|
||||||
|
shouldSort: false
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// Eliminar partidas
|
||||||
|
document.querySelector('#tabla-partidas tbody').addEventListener('click', e => {
|
||||||
|
if (e.target.matches('.remove-row')) {
|
||||||
|
const row = e.target.closest('tr');
|
||||||
|
row.querySelectorAll('.searchable').forEach(el => {
|
||||||
|
if (el._choices) el._choices.destroy();
|
||||||
|
});
|
||||||
|
row.remove();
|
||||||
}
|
}
|
||||||
})
|
|
||||||
.catch(err => {
|
|
||||||
console.error('Error cargando proveedores:', err);
|
|
||||||
proveedorEl.innerHTML = '<option value="">No fue posible cargar proveedores</option>';
|
|
||||||
});
|
});
|
||||||
|
|
||||||
// 3) Agregar partida dinámica
|
// Control overflow tabla cuando se abre el dropdown
|
||||||
document.getElementById('add-partida').addEventListener('click', () => {
|
document.addEventListener('click', function(e) {
|
||||||
const tbody = document.querySelector('#tabla-partidas tbody');
|
const tableContainer = document.querySelector('.table-responsive');
|
||||||
const idx = tbody.querySelectorAll('tr').length;
|
if (!tableContainer) return;
|
||||||
const row = document.createElement('tr');
|
if (e.target.closest('.choices__inner')) {
|
||||||
row.innerHTML = `
|
tableContainer.style.overflow = 'visible';
|
||||||
<td><input name="partidas[\${idx}][descripcion]" class="form-control"></td>
|
} else {
|
||||||
<td><input name="partidas[\${idx}][cantidad_comercial]" type="number" step="0.0001" class="form-control"></td>
|
tableContainer.style.overflow = 'auto';
|
||||||
<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 });
|
|
||||||
});
|
});
|
||||||
});
|
|
||||||
|
|
||||||
// Manejador para controlar el overflow al abrir dropdowns
|
// Validación suma de partidas al enviar
|
||||||
document.addEventListener('click', function(e) {
|
$('#solicitudForm').submit(function(e){
|
||||||
const tableContainer = document.querySelector('.table-responsive');
|
const total = parseFloat($('#valor_factura').val()) || 0;
|
||||||
if (!tableContainer) return;
|
let sum = 0;
|
||||||
|
$('.valor-partida').each(function(){ sum += parseFloat($(this).val()) || 0; });
|
||||||
if (e.target.closest('.choices__inner')) {
|
if(Math.abs(sum - total) > 0.001){
|
||||||
tableContainer.style.overflow = 'visible';
|
e.preventDefault();
|
||||||
} else {
|
Swal.fire({
|
||||||
tableContainer.style.overflow = 'auto';
|
icon:'error',
|
||||||
}
|
title:'Error de validación',
|
||||||
});
|
text:`La suma de partidas (${sum.toFixed(2)}) no coincide con Valor Factura (${total.toFixed(2)}).`
|
||||||
|
});
|
||||||
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>
|
|
||||||
// proveedores script (no modificado)
|
|
||||||
document.querySelectorAll('.searchable').forEach(el => {
|
|
||||||
if (el.id !== 'proveedor_id') {
|
|
||||||
new Choices(el, { searchEnabled: true, itemSelectText: '', shouldSort: false });
|
|
||||||
}
|
|
||||||
});
|
|
||||||
const proveedorEl = document.getElementById('proveedor_id');
|
|
||||||
fetch('/IMPORTADORES/solicitud_importacion/ajax_proveedores')
|
|
||||||
.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;
|
|
||||||
proveedorEl.add(opt);
|
|
||||||
});
|
|
||||||
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>';
|
|
||||||
});
|
});
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
</body>
|
</body>
|
||||||
|
|||||||
@@ -145,7 +145,7 @@
|
|||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-md-4 mb-3">
|
<div class="col-md-4 mb-3">
|
||||||
<label for="foto_solicitud" class="form-label">Foto de la solicitud</label>
|
<label for="foto_solicitud" class="form-label">Foto de la Carga (PIPA)</label>
|
||||||
<input id="foto_solicitud" name="foto_solicitud" type="file" class="form-control" accept="image/*">
|
<input id="foto_solicitud" name="foto_solicitud" type="file" class="form-control" accept="image/*">
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -243,7 +243,6 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Choices.js & jQuery -->
|
|
||||||
<!-- Choices.js JS -->
|
<!-- Choices.js JS -->
|
||||||
<script src="https://cdn.jsdelivr.net/npm/choices.js/public/assets/scripts/choices.min.js"></script>
|
<script src="https://cdn.jsdelivr.net/npm/choices.js/public/assets/scripts/choices.min.js"></script>
|
||||||
<script src="https://code.jquery.com/jquery-3.6.0.min.js"></script>
|
<script src="https://code.jquery.com/jquery-3.6.0.min.js"></script>
|
||||||
|
|||||||
@@ -29,6 +29,13 @@
|
|||||||
.sidebar .nav-link.active { background-color: #e9ecef; color: #212529; }
|
.sidebar .nav-link.active { background-color: #e9ecef; color: #212529; }
|
||||||
}
|
}
|
||||||
.card { border-radius: 12px; }
|
.card { border-radius: 12px; }
|
||||||
|
/* Forzar z-index más alto para modales anidados */
|
||||||
|
.modal { z-index: 9999 !important; }
|
||||||
|
.modal-backdrop { z-index: 9998 !important; }
|
||||||
|
/* Asegurar que el contenido del modal esté por encima */
|
||||||
|
.modal-dialog { z-index: 10000 !important; position: relative; }
|
||||||
|
/* Opcional: Mejorar la apariencia del overlay */
|
||||||
|
.modal-backdrop.show { opacity: 0.5; }
|
||||||
</style>
|
</style>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
@@ -39,12 +46,20 @@
|
|||||||
<div class="row g-3">
|
<div class="row g-3">
|
||||||
<div class="col-md-6">
|
<div class="col-md-6">
|
||||||
<label class="form-label">Contenedor *</label>
|
<label class="form-label">Contenedor *</label>
|
||||||
<input name="vehiculo" id="vehiculo" class="form-control" required>
|
<div class="d-flex align-items-center">
|
||||||
|
<input name="vehiculo" id="vehiculo" class="form-control" maxlength="20" required>
|
||||||
|
<button type="button" class="btn btn-outline-secondary ms-2" data-bs-toggle="modal" data-bs-target="#infoContenedor" style="border: none;">ℹ️</button>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="col-md-6">
|
<div class="col-md-6">
|
||||||
<label class="form-label">Identificador fiscal *</label>
|
<label class="form-label">Identificación fiscal *</label>
|
||||||
<input name="identificador_fiscal" id="identFiscal" class="form-control" required>
|
<div class="d-flex align-items-center">
|
||||||
|
<input name="identificador_fiscal" id="ident_fiscal" class="form-control" maxlength="20" required>
|
||||||
|
<button type="button" class="btn btn-outline-secondary ms-2" data-bs-toggle="modal" data-bs-target="#infoIdentificacion" style="border: none;">ℹ️</button>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="col-md-6">
|
<div class="col-md-6">
|
||||||
<label class="form-label">Foto (opcional)</label>
|
<label class="form-label">Foto (opcional)</label>
|
||||||
<input type="file" name="foto" id="foto" class="form-control" accept="image/*">
|
<input type="file" name="foto" id="foto" class="form-control" accept="image/*">
|
||||||
@@ -55,7 +70,7 @@
|
|||||||
<option value="">Selecciona...</option>
|
<option value="">Selecciona...</option>
|
||||||
<?php foreach($transportistas as $tr): ?>
|
<?php foreach($transportistas as $tr): ?>
|
||||||
<option value="<?= $tr['id_transportista'] ?>">
|
<option value="<?= $tr['id_transportista'] ?>">
|
||||||
<?= htmlspecialchars(($tr['clave_identificador'] . ' - ' . $tr['nombre'])) ?>
|
<?= htmlspecialchars(($tr['clave_identificador'] . ' - ' . $tr['nombre'] . ' - ' . $tr['ciudad_nombre'] . ' - ' . $tr['domicilio'])) ?>
|
||||||
</option>
|
</option>
|
||||||
<?php endforeach; ?>
|
<?php endforeach; ?>
|
||||||
</select>
|
</select>
|
||||||
@@ -68,38 +83,70 @@
|
|||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- Modales movidos FUERA del formulario -->
|
||||||
|
<!-- Modal de ayuda - Contenedor -->
|
||||||
|
<div class="modal fade" id="infoContenedor" tabindex="-1" aria-labelledby="infoContenedorLabel" aria-hidden="true">
|
||||||
|
<div class="modal-dialog modal-dialog-centered">
|
||||||
|
<div class="modal-content">
|
||||||
|
<div class="modal-header">
|
||||||
|
<h5 class="modal-title" id="infoContenedorLabel">Info - Contenedor</h5>
|
||||||
|
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Cerrar"></button>
|
||||||
|
</div>
|
||||||
|
<div class="modal-body">
|
||||||
|
<strong>• Número económico del vehículo.</strong>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Modal de ayuda - Identificación Fiscal -->
|
||||||
|
<div class="modal fade" id="infoIdentificacion" tabindex="-1" aria-labelledby="infoIdentificacionLabel" aria-hidden="true">
|
||||||
|
<div class="modal-dialog modal-dialog-centered">
|
||||||
|
<div class="modal-content">
|
||||||
|
<div class="modal-header">
|
||||||
|
<h5 class="modal-title" id="infoIdentificacionLabel">Info - Identificación Fiscal</h5>
|
||||||
|
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Cerrar"></button>
|
||||||
|
</div>
|
||||||
|
<div class="modal-body">
|
||||||
|
• Si el medio de transporte es <strong>vehículo terrestre</strong>, se anotarán las <strong>placas de circulación</strong> del mismo.<br><br>
|
||||||
|
• Si el medio de transporte es <strong>ferrocarril</strong>, se anotará el <strong>número de furgón o plataforma</strong>.<br><br>
|
||||||
|
• Si el medio de transporte es <strong>marítimo</strong>, se anotará el <strong>nombre de la embarcación</strong>.
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
document.getElementById('formTransCrear').addEventListener('submit', function(e) {
|
document.getElementById('formTransCrear').addEventListener('submit', function(e) {
|
||||||
|
e.preventDefault(); // Prevenir envío por defecto
|
||||||
|
|
||||||
// Campos que validamos:
|
// Campos que validamos:
|
||||||
const veh = document.getElementById('vehiculo').value.trim();
|
const vehiculo = document.getElementById('vehiculo').value.trim();
|
||||||
const fisc = document.getElementById('identFiscal').value.trim();
|
const ident_fiscal = document.getElementById('ident_fiscal').value.trim();
|
||||||
const trans = document.getElementById('transportista').value;
|
const transportista = document.getElementById('transportista').value;
|
||||||
const fotoF = document.getElementById('foto').files[0];
|
const fotoF = document.getElementById('foto').files[0];
|
||||||
|
|
||||||
// Validaciones de campos obligatorios
|
// Validaciones de campos obligatorios
|
||||||
if (!veh) {
|
if (!vehiculo) {
|
||||||
e.preventDefault();
|
Swal.fire({ icon:'error', title:'Contenedor requerido', text:'Por favor ingresa el número económico del vehículo.', confirmButtonColor: '#dc3545'});
|
||||||
Swal.fire({ icon:'error', title:'Vehículo requerido', text:'Por favor ingresa el nombre del vehículo.', confirmButtonColor: '#dc3545'});
|
document.getElementById('vehiculo').focus();
|
||||||
document.querySelector('input[name="veh"]').focus();
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (!fisc) {
|
if (!ident_fiscal) {
|
||||||
e.preventDefault();
|
Swal.fire({ icon:'error', title:'Identificación fiscal requerida', text:'Por favor ingresa la identificación fiscal.', confirmButtonColor: '#dc3545' });
|
||||||
Swal.fire({ icon:'error', title:'Identificador fiscal requerido', text:'Por favor ingresa el identificador fiscal.', confirmButtonColor: '#dc3545' });
|
document.getElementById('ident_fiscal').focus();
|
||||||
document.querySelector('input[name="fisc"]').focus();
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (!trans) {
|
if (!transportista) {
|
||||||
e.preventDefault();
|
|
||||||
Swal.fire({ icon:'error', title:'Transportista no seleccionado', text:'Debes elegir un transportista.', confirmButtonColor: '#dc3545' });
|
Swal.fire({ icon:'error', title:'Transportista no seleccionado', text:'Debes elegir un transportista.', confirmButtonColor: '#dc3545' });
|
||||||
document.querySelector('input[name="trans"]').focus();
|
document.getElementById('transportista').focus();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (fotoF && fotoF.size > 2 * 1024 * 1024) { // 2 MB
|
if (fotoF && fotoF.size > 2 * 1024 * 1024) { // 2 MB
|
||||||
e.preventDefault();
|
|
||||||
return Swal.fire({ icon:'error', title:'Foto demasiado grande', text:'La imagen no debe exceder 2 MB.' });
|
return Swal.fire({ icon:'error', title:'Foto demasiado grande', text:'La imagen no debe exceder 2 MB.' });
|
||||||
}
|
}
|
||||||
// Si todas las validaciones pasan, el formulario se envía.
|
// Si todas las validaciones pasan, el formulario se envía.
|
||||||
|
this.submit();
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|||||||
@@ -41,13 +41,19 @@
|
|||||||
<div class="row g-3">
|
<div class="row g-3">
|
||||||
<div class="col-md-6">
|
<div class="col-md-6">
|
||||||
<label class="form-label">Contenedor *</label>
|
<label class="form-label">Contenedor *</label>
|
||||||
<input name="vehiculo" id="vehEdit" class="form-control"
|
<div class="d-flex align-items-center">
|
||||||
value="<?= htmlspecialchars($t['vehiculo']) ?>" required>
|
<input name="vehiculo" id="vehiculo" class="form-control" maxlength="20"
|
||||||
|
value="<?= htmlspecialchars($t['vehiculo']) ?>" required>
|
||||||
|
<button type="button" class="btn btn-outline-secondary ms-2" data-bs-toggle="modal" data-bs-target="#infoContenedor" style="border: none;">ℹ️</button>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-md-6">
|
<div class="col-md-6">
|
||||||
<label class="form-label">Identificador fiscal *</label>
|
<label class="form-label">Identificación fiscal *</label>
|
||||||
<input name="identificador_fiscal" id="fiscEdit" class="form-control"
|
<div class="d-flex align-items-center">
|
||||||
value="<?= htmlspecialchars($t['identificador_fiscal']) ?>" required>
|
<input name="identificador_fiscal" id="identificador_fiscal" class="form-control" maxlength="20"
|
||||||
|
value="<?= htmlspecialchars($t['identificador_fiscal']) ?>" required>
|
||||||
|
<button type="button" class="btn btn-outline-secondary ms-2" data-bs-toggle="modal" data-bs-target="#infoIdentificacion" style="border: none;">ℹ️</button>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-md-6">
|
<div class="col-md-6">
|
||||||
<label class="form-label">Foto actual</label><br>
|
<label class="form-label">Foto actual</label><br>
|
||||||
@@ -59,15 +65,15 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="col-md-6">
|
<div class="col-md-6">
|
||||||
<label class="form-label">Reemplazar foto (opcional)</label>
|
<label class="form-label">Reemplazar foto (opcional)</label>
|
||||||
<input type="file" name="foto" id="fotoEdit" class="form-control" accept="image/*">
|
<input type="file" name="foto" id="foto" class="form-control" accept="image/*">
|
||||||
</div>
|
</div>
|
||||||
<div class="col-md-6">
|
<div class="col-md-6">
|
||||||
<label class="form-label">Transportista *</label>
|
<label class="form-label">Transportista *</label>
|
||||||
<select name="id_transportista" id="trEdit" class="form-select" required>
|
<select name="id_transportista" id="transportista" class="form-select" required>
|
||||||
<?php foreach($transportistas as $tr): ?>
|
<?php foreach($transportistas as $tr): ?>
|
||||||
<option value="<?= $tr['id_transportista'] ?>"
|
<option value="<?= $tr['id_transportista'] ?>"
|
||||||
<?= $tr['id_transportista']==$t['id_transportista']?'selected':'' ?>>
|
<?= $tr['id_transportista']==$t['id_transportista']?'selected':'' ?>>
|
||||||
<?= htmlspecialchars($tr['nombre']) ?>
|
<?= htmlspecialchars(($tr['clave_identificador'] . ' - ' . $tr['nombre'] . ' - ' . $tr['ciudad_nombre'] . ' - ' . $tr['domicilio'])) ?>
|
||||||
</option>
|
</option>
|
||||||
<?php endforeach; ?>
|
<?php endforeach; ?>
|
||||||
</select>
|
</select>
|
||||||
@@ -80,38 +86,70 @@
|
|||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- Modales movidos FUERA del formulario -->
|
||||||
|
<!-- Modal de ayuda - Contenedor -->
|
||||||
|
<div class="modal fade" id="infoContenedor" tabindex="-1" aria-labelledby="infoContenedorLabel" aria-hidden="true">
|
||||||
|
<div class="modal-dialog modal-dialog-centered">
|
||||||
|
<div class="modal-content">
|
||||||
|
<div class="modal-header">
|
||||||
|
<h5 class="modal-title" id="infoContenedorLabel">Info - Contenedor</h5>
|
||||||
|
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Cerrar"></button>
|
||||||
|
</div>
|
||||||
|
<div class="modal-body">
|
||||||
|
<strong>• Número económico del vehículo.</strong>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Modal de ayuda - Identificación Fiscal -->
|
||||||
|
<div class="modal fade" id="infoIdentificacion" tabindex="-1" aria-labelledby="infoIdentificacionLabel" aria-hidden="true">
|
||||||
|
<div class="modal-dialog modal-dialog-centered">
|
||||||
|
<div class="modal-content">
|
||||||
|
<div class="modal-header">
|
||||||
|
<h5 class="modal-title" id="infoIdentificacionLabel">Info - Identificación Fiscal</h5>
|
||||||
|
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Cerrar"></button>
|
||||||
|
</div>
|
||||||
|
<div class="modal-body">
|
||||||
|
• Si el medio de transporte es <strong>vehículo terrestre</strong>, se anotarán las <strong>placas de circulación</strong> del mismo.<br><br>
|
||||||
|
• Si el medio de transporte es <strong>ferrocarril</strong>, se anotará el <strong>número de furgón o plataforma</strong>.<br><br>
|
||||||
|
• Si el medio de transporte es <strong>marítimo</strong>, se anotará el <strong>nombre de la embarcación</strong>.
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
document.getElementById('formTransEdit').addEventListener('submit', function(e) {
|
document.getElementById('formTransEdit').addEventListener('submit', function(e) {
|
||||||
|
e.preventDefault(); // Prevenir envío por defecto
|
||||||
|
|
||||||
// Campos que validamos:
|
// Campos que validamos:
|
||||||
const veh = document.getElementById('vehEdit').value.trim();
|
const veh = document.getElementById('vehiculo').value.trim();
|
||||||
const fisc = document.getElementById('fiscEdit').value.trim();
|
const fisc = document.getElementById('identificador_fiscal').value.trim();
|
||||||
const trans = document.getElementById('trEdit').value;
|
const trans = document.getElementById('transportista').value;
|
||||||
const fotoF = document.getElementById('fotoEdit').files[0];
|
const fotoF = document.getElementById('foto').files[0];
|
||||||
|
|
||||||
// Validaciones de campos obligatorios
|
// Validaciones de campos obligatorios
|
||||||
if (!veh) {
|
if (!veh) {
|
||||||
e.preventDefault();
|
Swal.fire({ icon:'error', title:'Contenedor requerido', text:'Por favor ingresa el número económico del vehículo.', confirmButtonColor: '#dc3545'});
|
||||||
Swal.fire({ icon:'error', title:'Vehículo requerido', text:'Por favor ingresa el nombre del vehículo.', confirmButtonColor: '#dc3545'});
|
document.getElementById('vehiculo').focus();
|
||||||
document.querySelector('input[name="veh"]').focus();
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (!fisc) {
|
if (!fisc) {
|
||||||
e.preventDefault();
|
Swal.fire({ icon:'error', title:'Identificación fiscal requerida', text:'Por favor ingresa la identificación fiscal.', confirmButtonColor: '#dc3545' });
|
||||||
Swal.fire({ icon:'error', title:'Identificador fiscal requerido', text:'Por favor ingresa el identificador fiscal.', confirmButtonColor: '#dc3545' });
|
document.getElementById('identificador_fiscal').focus();
|
||||||
document.querySelector('input[name="fisc"]').focus();
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (!trans) {
|
if (!trans) {
|
||||||
e.preventDefault();
|
|
||||||
Swal.fire({ icon:'error', title:'Transportista no seleccionado', text:'Debes elegir un transportista.', confirmButtonColor: '#dc3545' });
|
Swal.fire({ icon:'error', title:'Transportista no seleccionado', text:'Debes elegir un transportista.', confirmButtonColor: '#dc3545' });
|
||||||
document.querySelector('input[name="trans"]').focus();
|
document.getElementById('transportista').focus();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (fotoF && fotoF.size > 2 * 1024 * 1024) { // 2 MB
|
if (fotoF && fotoF.size > 2 * 1024 * 1024) { // 2 MB
|
||||||
e.preventDefault();
|
|
||||||
return Swal.fire({ icon:'error', title:'Foto demasiado grande', text:'La imagen no debe exceder 2 MB.' });
|
return Swal.fire({ icon:'error', title:'Foto demasiado grande', text:'La imagen no debe exceder 2 MB.' });
|
||||||
}
|
}
|
||||||
// Si todas las validaciones pasan, el formulario se envía.
|
// Si todas las validaciones pasan, el formulario se envía.
|
||||||
|
this.submit();
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|||||||
@@ -57,7 +57,7 @@
|
|||||||
<label class="form-label">Archivo CSV *</label>
|
<label class="form-label">Archivo CSV *</label>
|
||||||
<input type="file" name="csv" accept=".csv" class="form-control" required>
|
<input type="file" name="csv" accept=".csv" class="form-control" required>
|
||||||
</div>
|
</div>
|
||||||
<p>Descarga la plantilla y llena las columnas: <code>contenedor, identificador_fiscal, id_transportista</code>.</p>
|
<p>Descarga la plantilla y llena las columnas: <code>contenedor, identificación_fiscal, id_transportista</code>.</p>
|
||||||
<a href="/IMPORTADORES/public/downloads/transportes_masivo_template.csv" class="btn btn-outline-secondary mb-3">
|
<a href="/IMPORTADORES/public/downloads/transportes_masivo_template.csv" class="btn btn-outline-secondary mb-3">
|
||||||
📥 Descargar plantilla
|
📥 Descargar plantilla
|
||||||
</a><br>
|
</a><br>
|
||||||
|
|||||||
@@ -48,7 +48,7 @@
|
|||||||
|
|
||||||
<div class="col-md-4">
|
<div class="col-md-4">
|
||||||
<label for="curp" class="form-label">CURP *</label>
|
<label for="curp" class="form-label">CURP *</label>
|
||||||
<input name="curp" id="curp" class="form-control" maxlength="18" required>
|
<input name="curp" id="curp" class="form-control" maxlength="18">
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="col-md-4">
|
<div class="col-md-4">
|
||||||
@@ -57,7 +57,7 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="col-md-4">
|
<div class="col-md-4">
|
||||||
<label for="caat" class="form-label">Código CAAT *</label>
|
<label for="caat" class="form-label">Código caat *</label>
|
||||||
<input name="caat" id="caat" class="form-control" maxlength="20" required>
|
<input name="caat" id="caat" class="form-control" maxlength="20" required>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -89,7 +89,7 @@
|
|||||||
|
|
||||||
<div class="col-md-8">
|
<div class="col-md-8">
|
||||||
<label for="domicilio" class="form-label">Domicilio *</label>
|
<label for="domicilio" class="form-label">Domicilio *</label>
|
||||||
<input name="domicilio" id="domicilio" class="form-control" required>
|
<input name="domicilio" id="domicilio" class="form-control" maxlength="100" required>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -123,7 +123,6 @@
|
|||||||
const curpRegex = /^[A-Z]{4}[0-9]{6}[HM](AS|BC|BS|CC|CL|CM|CS|CH|DF|DG|GT|GR|HG|JC|MC|MN|MS|NT|NL|OC|PL|QT|QR|SP|SL|SR|TC|TS|TL|VZ|YN|ZS)[A-Z]{3}[A-Z0-9]{2}$/;
|
const curpRegex = /^[A-Z]{4}[0-9]{6}[HM](AS|BC|BS|CC|CL|CM|CS|CH|DF|DG|GT|GR|HG|JC|MC|MN|MS|NT|NL|OC|PL|QT|QR|SP|SL|SR|TC|TS|TL|VZ|YN|ZS)[A-Z]{3}[A-Z0-9]{2}$/;
|
||||||
const rfcRegex = /^[A-ZÑ&]{3,4}\d{6}[A-Z0-9]{3}$/;
|
const rfcRegex = /^[A-ZÑ&]{3,4}\d{6}[A-Z0-9]{3}$/;
|
||||||
const telefonoRegex = /^\d{3}\s\d{7}$/;
|
const telefonoRegex = /^\d{3}\s\d{7}$/;
|
||||||
const soloNumerosRegex = /^[0-9]+$/;
|
|
||||||
|
|
||||||
// Validaciones de campos obligatorios
|
// Validaciones de campos obligatorios
|
||||||
if (!clave) {
|
if (!clave) {
|
||||||
@@ -151,11 +150,6 @@
|
|||||||
document.getElementById('rfc').focus();
|
document.getElementById('rfc').focus();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (!curp) {
|
|
||||||
Swal.fire({ icon: 'error', title: 'Campo requerido', text: 'El CURP es obligatorio.', confirmButtonColor: '#dc3545' });
|
|
||||||
document.getElementById('curp').focus();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
// Validación de CURP (solo si se ingresó)
|
// Validación de CURP (solo si se ingresó)
|
||||||
if (curp && !curpRegex.test(curp)) {
|
if (curp && !curpRegex.test(curp)) {
|
||||||
Swal.fire({ icon: 'error', title: 'CURP inválido', text: 'El CURP no tiene el formato correcto.', confirmButtonColor: '#dc3545'
|
Swal.fire({ icon: 'error', title: 'CURP inválido', text: 'El CURP no tiene el formato correcto.', confirmButtonColor: '#dc3545'
|
||||||
@@ -179,12 +173,7 @@
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (!caat) {
|
if (!caat) {
|
||||||
Swal.fire({ icon: 'error', title: 'Campo requerido', text: 'El código CAAT es obligatorio.', confirmButtonColor: '#dc3545' });
|
Swal.fire({ icon: 'error', title: 'Campo requerido', text: 'El código caat es obligatorio.', confirmButtonColor: '#dc3545' });
|
||||||
document.getElementById('caat').focus();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (!soloNumerosRegex.test(caat)) {
|
|
||||||
Swal.fire({ icon: 'error', title: 'Código CAAT inválido', text: 'El código caat solo puede contener números.', confirmButtonColor: '#dc3545' });
|
|
||||||
document.getElementById('caat').focus();
|
document.getElementById('caat').focus();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -63,7 +63,7 @@
|
|||||||
id="telefono" class="form-control" maxlength="11" required>
|
id="telefono" class="form-control" maxlength="11" required>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-md-4">
|
<div class="col-md-4">
|
||||||
<label class="form-label">Código CAAT</label>
|
<label class="form-label">Código caat</label>
|
||||||
<input name="caat" value="<?= htmlspecialchars($t['caat']) ?>"
|
<input name="caat" value="<?= htmlspecialchars($t['caat']) ?>"
|
||||||
id="caat" class="form-control" maxlength="20" required>
|
id="caat" class="form-control" maxlength="20" required>
|
||||||
</div>
|
</div>
|
||||||
@@ -140,7 +140,6 @@
|
|||||||
const curpRegex = /^[A-Z]{4}[0-9]{6}[HM](AS|BC|BS|CC|CL|CM|CS|CH|DF|DG|GT|GR|HG|JC|MC|MN|MS|NT|NL|OC|PL|QT|QR|SP|SL|SR|TC|TS|TL|VZ|YN|ZS)[A-Z]{3}[A-Z0-9]{2}$/;
|
const curpRegex = /^[A-Z]{4}[0-9]{6}[HM](AS|BC|BS|CC|CL|CM|CS|CH|DF|DG|GT|GR|HG|JC|MC|MN|MS|NT|NL|OC|PL|QT|QR|SP|SL|SR|TC|TS|TL|VZ|YN|ZS)[A-Z]{3}[A-Z0-9]{2}$/;
|
||||||
const rfcRegex = /^[A-ZÑ&]{3,4}\d{6}[A-Z0-9]{3}$/;
|
const rfcRegex = /^[A-ZÑ&]{3,4}\d{6}[A-Z0-9]{3}$/;
|
||||||
const telefonoRegex = /^\d{3}\s\d{7}$/;
|
const telefonoRegex = /^\d{3}\s\d{7}$/;
|
||||||
const soloNumerosRegex = /^[0-9]+$/;
|
|
||||||
|
|
||||||
// Validaciones de campos obligatorios
|
// Validaciones de campos obligatorios
|
||||||
if (!clave) {
|
if (!clave) {
|
||||||
@@ -168,11 +167,6 @@
|
|||||||
document.getElementById('rfc').focus();
|
document.getElementById('rfc').focus();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (!curp) {
|
|
||||||
Swal.fire({ icon: 'error', title: 'Campo requerido', text: 'El CURP es obligatorio.', confirmButtonColor: '#dc3545' });
|
|
||||||
document.getElementById('curp').focus();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
// Validación de CURP (solo si se ingresó)
|
// Validación de CURP (solo si se ingresó)
|
||||||
if (curp && !curpRegex.test(curp)) {
|
if (curp && !curpRegex.test(curp)) {
|
||||||
Swal.fire({ icon: 'error', title: 'CURP inválido', text: 'El CURP no tiene el formato correcto.', confirmButtonColor: '#dc3545'
|
Swal.fire({ icon: 'error', title: 'CURP inválido', text: 'El CURP no tiene el formato correcto.', confirmButtonColor: '#dc3545'
|
||||||
@@ -196,12 +190,7 @@
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (!caat) {
|
if (!caat) {
|
||||||
Swal.fire({ icon: 'error', title: 'Campo requerido', text: 'El código CAAT es obligatorio.', confirmButtonColor: '#dc3545' });
|
Swal.fire({ icon: 'error', title: 'Campo requerido', text: 'El código caat es obligatorio.', confirmButtonColor: '#dc3545' });
|
||||||
document.getElementById('caat').focus();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (!soloNumerosRegex.test(caat)) {
|
|
||||||
Swal.fire({ icon: 'error', title: 'Código CAAT inválido', text: 'El código caat solo puede contener números', confirmButtonColor: '#dc3545' });
|
|
||||||
document.getElementById('caat').focus();
|
document.getElementById('caat').focus();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -67,29 +67,30 @@
|
|||||||
$('#transportistas-table').DataTable({
|
$('#transportistas-table').DataTable({
|
||||||
serverSide: true,
|
serverSide: true,
|
||||||
processing: true,
|
processing: true,
|
||||||
|
searchDelay: 500, // espera 500 ms antes de lanzar la búsqueda
|
||||||
|
deferRender: true, // renderiza las filas sólo cuando tiene los datos
|
||||||
ajax: {
|
ajax: {
|
||||||
url: '/IMPORTADORES/transportistas/ajax_lista',
|
url: '/IMPORTADORES/transportistas/ajax_lista',
|
||||||
type: 'GET'
|
type: 'GET'
|
||||||
},
|
},
|
||||||
columns: [
|
columns: [
|
||||||
{ data: 0 },
|
{ data: 0 }, // ID
|
||||||
{ data: 1 },
|
{ data: 1 }, // Código
|
||||||
{ data: 2 },
|
{ data: 2 }, // Nombre
|
||||||
{ data: 3 },
|
{ data: 3 }, // RFC
|
||||||
{ data: 4 },
|
{ data: 4 }, // Ciudad
|
||||||
{ data: 5 },
|
{ data: 5 }, // Fecha
|
||||||
{
|
{
|
||||||
data: null,
|
data: null,
|
||||||
orderable: false,
|
orderable: false,
|
||||||
searchable: false,
|
searchable: false,
|
||||||
render: function(row) {
|
render: function(data, type, row) {
|
||||||
const id = row[0];
|
const id = row[0];
|
||||||
return `
|
return `
|
||||||
<a href="/IMPORTADORES/transportistas/editar?id=${id}" class="btn btn-sm btn-primary">✏️</a>
|
<a href="/IMPORTADORES/transportistas/editar?id=${id}" class="btn btn-sm btn-primary">✏️</a>
|
||||||
<button class="btn btn-sm btn-danger" onclick="confirmDelete(${id})">🗑️</button>
|
<button class="btn btn-sm btn-danger" onclick="confirmDelete(${id})">🗑️</button>
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
language: {
|
language: {
|
||||||
|
|||||||
Reference in New Issue
Block a user