Filtro de choferes pertenecientes a transportista en solicitudes de importación

This commit is contained in:
2025-07-04 17:00:08 -06:00
parent 8434121efc
commit a3e2f8e45e
4 changed files with 620 additions and 241 deletions

View File

@@ -126,7 +126,7 @@ function crear()
}
$id_importador = $_SESSION['usuario_id'];
$conn = getConnection();
$conn = getConnection();
// ✅ NUEVO: Obtener la agencia actual del usuario
$stmt = sqlsrv_query($conn, "SELECT id_agencia_en_uso FROM dbo.usuarios_sistema WHERE id_usuario = ?", [$id_importador]);
@@ -140,11 +140,12 @@ function crear()
while ($r = sqlsrv_fetch_array($stmtP, SQLSRV_FETCH_ASSOC)) { $patentes[] = $r; }
$transportistas = [];
$stmtT = sqlsrv_query($conn, "SELECT id_transportista, clave_identificador, 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; }
// ✅ MODIFICADO: Obtener choferes con su transportista_id para el filtro
$choferes = [];
$stmtC = sqlsrv_query($conn, "SELECT c.id_chofer, c.nombre+' '+c.apellido AS nombre FROM dbo.choferes c JOIN dbo.transportistas t ON c.transportista_id=t.id_transportista WHERE t.id_usuario=? AND c.status=1 ORDER BY c.nombre",[$id_importador]);
$stmtC = sqlsrv_query($conn, "SELECT c.id_chofer, c.nombre+' '+c.apellido AS nombre, c.transportista_id FROM dbo.choferes c JOIN dbo.transportistas t ON c.transportista_id=t.id_transportista WHERE t.id_usuario=? AND c.status=1 ORDER BY c.nombre",[$id_importador]);
while ($r = sqlsrv_fetch_array($stmtC, SQLSRV_FETCH_ASSOC)) { $choferes[] = $r; }
$paises = [];
@@ -161,13 +162,74 @@ function crear()
$unidades_medida = [];
$stmtU = sqlsrv_query($conn, "SELECT id, descripcion FROM dbo.unidades_medida_apendice7 ORDER BY id");
while ($r = sqlsrv_fetch_array($stmtU, SQLSRV_FETCH_ASSOC)) {
$unidades_medida[] = $r;
}
while ($r = sqlsrv_fetch_array($stmtU, SQLSRV_FETCH_ASSOC)) { $unidades_medida[] = $r; }
include __DIR__ . '/../../views/solicitud_importacion/crear.php';
}
// ✅ NUEVO: Endpoint AJAX para obtener choferes por transportista
function obtenerChoferesPorTransportista()
{
// Configurar headers para JSON desde el inicio
header('Content-Type: application/json');
try {
if (!($_SESSION['usuario_id'] ?? false)) {
http_response_code(401);
echo json_encode(['error' => 'No autorizado']);
exit;
}
$id_importador = $_SESSION['usuario_id'];
$transportista_id = $_GET['transportista_id'] ?? null;
if (!$transportista_id || !is_numeric($transportista_id)) {
http_response_code(400);
echo json_encode(['error' => 'ID de transportista requerido']);
exit;
}
$conn = getConnection();
// Validar que el transportista pertenezca al usuario
$stmtValidate = sqlsrv_query($conn,
"SELECT id_transportista FROM dbo.transportistas WHERE id_transportista = ? AND id_usuario = ? AND activo = 1",
[intval($transportista_id), $id_importador]);
if (!$stmtValidate || !sqlsrv_fetch_array($stmtValidate, SQLSRV_FETCH_ASSOC)) {
http_response_code(403);
echo json_encode(['error' => 'Transportista no válido']);
exit;
}
// Obtener choferes del transportista
$choferes = [];
$stmtC = sqlsrv_query($conn,
"SELECT c.id_chofer, c.nombre+' '+c.apellido AS nombre
FROM dbo.choferes c
WHERE c.transportista_id = ? AND c.status = 1
ORDER BY c.nombre",
[intval($transportista_id)]);
if ($stmtC) {
while ($r = sqlsrv_fetch_array($stmtC, SQLSRV_FETCH_ASSOC)) {
$choferes[] = [
'id_chofer' => $r['id_chofer'],
'nombre' => $r['nombre']
];
}
}
echo json_encode($choferes);
exit;
} catch (Exception $e) {
http_response_code(500);
echo json_encode(['error' => 'Error interno del servidor']);
exit;
}
}
/** Procesa la creación de una nueva factura y sus partidas **/
function guardar()
{
@@ -250,13 +312,15 @@ function guardar()
$patente_id ? (int)$patente_id : null
];
$sql = "INSERT INTO dbo.solicitud_importacion_factura
(id_importador, id_agencia, aduana, anexo22_apendice, numero_factura, fecha_factura,
numero_pedimento, incoterm, pais_proveedor, tipo_moneda,
valor_factura, vinculacion, transportista_id, chofer_id,
foto_solicitud_url, status, proveedor_clave, patente_id)
OUTPUT INSERTED.id_solicitud
VALUES(?, ?, ?, ?, ?, ?, NULL, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)";
$sql = "
INSERT INTO dbo.solicitud_importacion_factura
(id_importador, id_agencia, aduana, anexo22_apendice, numero_factura,
fecha_factura, numero_pedimento, incoterm, pais_proveedor, tipo_moneda,
valor_factura, vinculacion, transportista_id, chofer_id,
foto_solicitud_url, status, proveedor_clave, patente_id)
OUTPUT INSERTED.id_solicitud
VALUES(?, ?, ?, ?, ?, ?, NULL, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
";
$stmt = sqlsrv_query($conn, $sql, $params, ['Scrollable' => SQLSRV_CURSOR_KEYSET]);
if ($stmt === false) {
@@ -281,9 +345,12 @@ function guardar()
COALESCE(p.nuevas_solicitudes, 0) as nuevas_solicitudes,
ce.correo as correo_extra
FROM usuarios_sistema u
LEFT JOIN preferencias_notificaciones_usuario p ON u.id_usuario = p.id_usuario
LEFT JOIN correo_extra ce ON u.id_usuario = ce.id_usuario
WHERE u.id_usuario = ?";
LEFT JOIN preferencias_notificaciones_usuario p
ON u.id_usuario = p.id_usuario
LEFT JOIN correo_extra ce
ON u.id_usuario = ce.id_usuario
WHERE u.id_usuario = ?
";
$stmtNotif = sqlsrv_prepare($conn, $sqlNotif, [$id_importador]);
@@ -306,11 +373,11 @@ function guardar()
// Preparar datos para la notificación
$datosNotificacion = [
'id_solicitud' => $id_solicitud,
'id_solicitud' => $id_solicitud,
'numero_factura' => $num_factura,
'fecha_factura' => $fecha,
'valor_factura' => $valor_factura,
'tipo_moneda' => $tipo_moneda
'fecha_factura' => $fecha,
'valor_factura' => $valor_factura,
'tipo_moneda' => $tipo_moneda
];
// Enviar al correo principal
@@ -356,15 +423,13 @@ function guardar()
error_log("Partida $i: " . print_r($p, true));
$params = [
$id_solicitud,
trim($p['descripcion'] ?? ''),
trim($p['descripcion'] ?? ''),
floatval($p['cantidad_comercial'] ?? 0),
floatval($p['cantidad_tarifa'] ?? 0),
floatval($p['valor_factura'] ?? 0),
floatval($p['peso_bruto'] ?? 0),
intval($p['unidad_comercial_id'] ?? 0) ?: null,
trim($p['tasa_preferencial'] ?? '')
floatval($p['cantidad_tarifa'] ?? 0),
floatval($p['valor_factura'] ?? 0),
floatval($p['peso_bruto'] ?? 0),
intval($p['unidad_comercial_id'] ?? 0) ?: null,
trim($p['tasa_preferencial'] ?? '')
];
if ($params[1] !== '' && $params[2] > 0) {
@@ -396,13 +461,13 @@ function enviarNotificacionNuevaSolicitud($email, $nombreUsuario, $datosSolicitu
try {
// Configuración SMTP
$mail->isSMTP();
$mail->Host = 'secure.emailsrvr.com';
$mail->SMTPAuth = true;
$mail->Username = 'noreply@aduanasoft.com.mx';
$mail->Password = $_ENV['SMTP_PASS'] ?? '';
$mail->Host = 'secure.emailsrvr.com';
$mail->SMTPAuth = true;
$mail->Username = 'noreply@aduanasoft.com.mx';
$mail->Password = $_ENV['SMTP_PASS'] ?? '';
$mail->SMTPSecure = PHPMailer::ENCRYPTION_STARTTLS;
$mail->Port = 587;
$mail->CharSet = 'UTF-8';
$mail->Port = 587;
$mail->CharSet = 'UTF-8';
// Configuración del mensaje
$mail->setFrom('noreply@aduanasoft.com.mx', 'SIIH | AduanaSoft');
@@ -414,11 +479,11 @@ function enviarNotificacionNuevaSolicitud($email, $nombreUsuario, $datosSolicitu
$mail->Subject = $subjectPrefix . '📦 Nueva Solicitud de Importación Registrada';
// Formatear datos para el email
$idSolicitud = $datosSolicitud['id_solicitud'];
$idSolicitud = $datosSolicitud['id_solicitud'];
$numeroFactura = htmlspecialchars($datosSolicitud['numero_factura']);
$fechaFactura = htmlspecialchars($datosSolicitud['fecha_factura']);
$valorFactura = number_format($datosSolicitud['valor_factura'], 2);
$tipoMoneda = htmlspecialchars($datosSolicitud['tipo_moneda']);
$fechaFactura = htmlspecialchars($datosSolicitud['fecha_factura']);
$valorFactura = number_format($datosSolicitud['valor_factura'], 2);
$tipoMoneda = htmlspecialchars($datosSolicitud['tipo_moneda']);
$tipoNotificacion = $esCorreoExtra ?
'<div style="background: #fff3cd; padding: 10px; border-radius: 5px; margin-bottom: 15px; border-left: 4px solid #ffc107;">
@@ -498,7 +563,7 @@ function editar()
}
$id_importador = $_SESSION['usuario_id'];
$conn = getConnection();
$conn = getConnection();
// ✅ NUEVO: Obtener la agencia actual del usuario
$stmtAg = sqlsrv_query($conn, "SELECT id_agencia_en_uso FROM dbo.usuarios_sistema WHERE id_usuario = ?", [$id_importador]);
@@ -528,36 +593,30 @@ function editar()
while ($r = sqlsrv_fetch_array($stmtP, SQLSRV_FETCH_ASSOC)) { $patentes[] = $r; }
// Transportistas
$transportistas = []; $stmtT = sqlsrv_query($conn,"SELECT id_transportista, 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;
}
$transportistas = [];
$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
$choferes = []; $stmtC = sqlsrv_query($conn,"SELECT c.id_chofer, c.nombre+' '+c.apellido AS nombre FROM dbo.choferes c JOIN dbo.transportistas t ON c.transportista_id=t.id_transportista
$choferes = [];
$stmtC = sqlsrv_query($conn, "SELECT c.id_chofer, c.nombre+' '+c.apellido AS nombre, c.transportista_id FROM dbo.choferes c JOIN dbo.transportistas t ON c.transportista_id=t.id_transportista
WHERE t.id_usuario=? AND c.status=1 ORDER BY c.nombre",[$id_importador]);
while($r=sqlsrv_fetch_array($stmtC,SQLSRV_FETCH_ASSOC)) {
$choferes[] = $r;
}
while($r=sqlsrv_fetch_array($stmtC,SQLSRV_FETCH_ASSOC)) { $choferes[] = $r; }
// Paises
$paises = []; $stmtP = sqlsrv_query($conn,"SELECT id_pais, nombre FROM dbo.paises ORDER BY nombre");
while($r=sqlsrv_fetch_array($stmtP,SQLSRV_FETCH_ASSOC)) {
$paises[] = $r;
}
$paises = [];
$stmtP = sqlsrv_query($conn, "SELECT id_pais, nombre FROM dbo.paises ORDER BY nombre");
while($r=sqlsrv_fetch_array($stmtP,SQLSRV_FETCH_ASSOC)) { $paises[] = $r;}
// Aduanas
$aduanas = []; $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;
}
$aduanas = [];
$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
$incoterms = []; $stmtI = sqlsrv_query($conn,"SELECT INCOTERM,DESCESPANOL FROM dbo.gIncoterms ORDER BY INCOTERM");
while($r = sqlsrv_fetch_array($stmtI,SQLSRV_FETCH_ASSOC)) {
$incoterms[] = $r;
}
$incoterms = [];
$stmtI = sqlsrv_query($conn, "SELECT INCOTERM, DESCESPANOL FROM dbo.gIncoterms ORDER BY INCOTERM");
while($r = sqlsrv_fetch_array($stmtI,SQLSRV_FETCH_ASSOC)) { $incoterms[] = $r; }
// Unidades de Medida
$unidades_medida = []; $stmtU = sqlsrv_query($conn, "SELECT id, descripcion FROM dbo.unidades_medida_apendice7 ORDER BY id");
while ($r = sqlsrv_fetch_array($stmtU, SQLSRV_FETCH_ASSOC)) {
$unidades_medida[] = $r;
}
$unidades_medida = [];
$stmtU = sqlsrv_query($conn, "SELECT id, descripcion FROM dbo.unidades_medida_apendice7 ORDER BY id");
while ($r = sqlsrv_fetch_array($stmtU, SQLSRV_FETCH_ASSOC)) { $unidades_medida[] = $r; }
// Partidas existentes
$partidas = [];
@@ -634,6 +693,7 @@ function actualizar()
$chofer_id = (int)($_POST['chofer_id'] ?? 0);
$status = isset($_POST['status']) ?? 1;
$patente_id = $_POST['patente'] ?? null;
$proveedor_clave = $_POST['proveedor_clave'] ?? null;
// ✅ NUEVO: Validar que la patente pertenezca a la agencia del usuario (si se seleccionó una)
if ($patente_id) {
@@ -653,27 +713,6 @@ function actualizar()
}
// 6) UPDATE de la cabecera
$sqlU = "
UPDATE dbo.solicitud_importacion_factura
SET aduana = ?,
anexo22_apendice = ?,
numero_factura = ?,
fecha_factura = ?,
incoterm = ?,
pais_proveedor = ?,
tipo_moneda = ?,
valor_factura = ?,
vinculacion = ?,
transportista_id = ?,
chofer_id = ?,
foto_solicitud_url = ?,
status = ?,
patente_id = ?,
updated_at = GETDATE()
WHERE id_solicitud = ?
AND id_importador = ?
AND id_agencia = ?
";
$paramsU = [
$aduana_seccion,
$aduana_seccion,
@@ -684,26 +723,47 @@ function actualizar()
$tipo_moneda,
$valor_factura,
$vinculacion,
$transportista_id,
$chofer_id,
(int)$transportista_id,
(int)$chofer_id,
$fotoUrl,
$status,
$proveedor_clave,
$patente_id ? (int)$patente_id : null,
$id_solicitud,
$_SESSION['usuario_id'],
$id_agencia
];
$sqlU = "
UPDATE dbo.solicitud_importacion_factura
SET aduana = ?,
anexo22_apendice = ?,
numero_factura = ?,
fecha_factura = ?,
incoterm = ?,
pais_proveedor = ?,
tipo_moneda = ?,
valor_factura = ?,
vinculacion = ?,
transportista_id = ?,
chofer_id = ?,
foto_solicitud_url = ?,
status = ?,
proveedor_clave = ?,
patente_id = ?,
updated_at = GETDATE()
WHERE id_solicitud = ?
AND id_importador = ?
AND id_agencia = ?
";
$stmtU = sqlsrv_query($conn, $sqlU, $paramsU);
if ($stmtU === false) {
die("❌ Error ejecutando UPDATE: " . print_r(sqlsrv_errors(), true));
}
// 7) Borrar partidas anteriores
$del = sqlsrv_query(
$conn,
"DELETE FROM dbo.solicitud_importacion_partidas WHERE id_solicitud = ?",
[ $id_solicitud ]
);
$del = sqlsrv_query($conn, "DELETE FROM dbo.solicitud_importacion_partidas WHERE id_solicitud = ?", [ $id_solicitud ]);
if ($del === false) {
die("❌ Error borrando partidas previas: " . print_r(sqlsrv_errors(), true));
}
@@ -717,13 +777,13 @@ function actualizar()
VALUES (?, ?, ?, ?, ?, ?, ?, ?)
";
foreach ($_POST['partidas'] as $i => $p) {
$desc = trim($p['descripcion'] ?? '');
$desc = trim($p['descripcion'] ?? '');
$cantCom = floatval($p['cantidad_comercial'] ?? 0);
$cantTar = floatval($p['cantidad_tarifa'] ?? 0);
$valPart = floatval($p['valor_factura'] ?? 0);
$peso = floatval($p['peso_bruto'] ?? 0);
$umId = intval($p['unidad_comercial_id'] ?? 0) ?: null;
$tasaPref = trim($p['tasa_preferencial'] ?? '');
$cantTar = floatval($p['cantidad_tarifa'] ?? 0);
$valPart = floatval($p['valor_factura'] ?? 0);
$peso = floatval($p['peso_bruto'] ?? 0);
$umId = intval($p['unidad_comercial_id'] ?? 0) ?: null;
$tasaPref = trim($p['tasa_preferencial'] ?? '');
// Sólo inserta si descripción y cantidad comercial válidos
if ($desc !== '' && $cantCom > 0) {

View File

@@ -35,8 +35,8 @@
.card-hover:hover { transform: translateY(-8px) scale(1.02); box-shadow: 0 1rem 2rem rgba(0, 0, 0, 0.15) !important; }
.btn-pulse { animation: pulse 2s infinite; }
@keyframes pulse {
0% { transform: scale(1); }
50% { transform: scale(1.05); }
0% { transform: scale(1); }
50% { transform: scale(1.05); }
100% { transform: scale(1); }
}
.fade-in-up { animation: fadeInUp 0.8s ease-out; }
@@ -63,10 +63,16 @@
<body>
<?php if (!isset($_SESSION['id_agencia_en_uso']) || !$_SESSION['id_agencia_en_uso']): ?>
<div class="alert alert-warning mt-4">
<h5>⚠️ No tienes una agencia activa vinculada.</h5>
<p>Para poder gestionar tus solicitudes de importación, primero debes <strong>vincularte a una agencia</strong>.</p>
<a href="/IMPORTADORES/vinculaciones/nuevaVinculacion" class="btn btn-primary mt-auto w-auto btn-animated">Ir a Vinculaciones</a>
<div class="container mt-4">
<div class="row">
<div class="col-12">
<div class="alert alert-warning mt-4">
<h5>⚠️ No tienes una agencia activa vinculada.</h5>
<p>Para poder gestionar tus solicitudes de importación, primero debes <strong>vincularte a una agencia</strong>.</p>
<a href="/IMPORTADORES/vinculaciones/nuevaVinculacion" class="btn btn-primary btn-animated">Ir a Vinculaciones</a>
</div>
</div>
</div>
</div>
<?php return; ?>
<?php endif; ?>
@@ -399,6 +405,113 @@
}
});
}
document.addEventListener('DOMContentLoaded', function() {
const transportistaSelect = document.getElementById('transportista_id');
const choferSelect = document.getElementById('chofer_id');
// Inicializar Choices.js si está disponible
let choferChoices = null;
if (typeof Choices !== 'undefined') {
choferChoices = new Choices(choferSelect, {
searchEnabled: true,
placeholderValue: '-- Selecciona Transportista primero --',
noResultsText: 'No se encontraron resultados',
itemSelectText: '',
searchPlaceholderValue: 'Buscar chofer...'
});
}
transportistaSelect.addEventListener('change', function() {
const transportistaId = this.value;
// Limpiar opciones del chofer
if (choferChoices) {
choferChoices.clearStore();
choferChoices.setChoices([{
value: '',
label: transportistaId ? '-- Cargando choferes... --' : '-- Selecciona Transportista primero --',
disabled: true
}], 'value', 'label', true);
} else {
choferSelect.innerHTML = '<option value="">' +
(transportistaId ? '-- Cargando choferes... --' : '-- Selecciona Transportista primero --') +
'</option>';
}
if (!transportistaId) {
return;
}
// Hacer petición AJAX para obtener choferes
fetch(`/IMPORTADORES/solicitud_importacion/obtenerChoferesPorTransportista?transportista_id=${transportistaId}`)
.then(response => {
console.log('Response status:', response.status);
console.log('Response headers:', response.headers.get('content-type'));
if (!response.ok) {
return response.text().then(text => {
console.error('Error response:', text);
throw new Error(`Error ${response.status}: ${text}`);
});
}
// Verificar que la respuesta sea JSON
const contentType = response.headers.get('content-type');
if (!contentType || !contentType.includes('application/json')) {
return response.text().then(text => {
console.error('Non-JSON response:', text);
throw new Error('La respuesta no es JSON válido');
});
}
return response.json();
})
.then(choferes => {
const opciones = [{
value: '',
label: '-- Selecciona Chofer --',
disabled: false
}];
choferes.forEach(chofer => {
opciones.push({
value: chofer.id_chofer,
label: chofer.nombre,
disabled: false
});
});
if (choferChoices) {
choferChoices.clearStore();
choferChoices.setChoices(opciones, 'value', 'label', true);
} else {
choferSelect.innerHTML = '';
opciones.forEach(opcion => {
const option = document.createElement('option');
option.value = opcion.value;
option.textContent = opcion.label;
if (opcion.disabled) option.disabled = true;
choferSelect.appendChild(option);
});
}
})
.catch(error => {
console.error('Error al cargar choferes:', error);
const errorMessage = '-- Error al cargar choferes --';
if (choferChoices) {
choferChoices.clearStore();
choferChoices.setChoices([{
value: '',
label: errorMessage,
disabled: true
}], 'value', 'label', true);
} else {
choferSelect.innerHTML = `<option value="">${errorMessage}</option>`;
}
});
});
});
</script>
</body>

View File

@@ -165,7 +165,7 @@
</div>
</div>
<!-- Transporte y Foto -->
<!-- Transportista, Ghofer y Foto -->
<div class="row">
<div class="col-md-4 mb-3">
<label for="transportista_id" class="form-label">Transportista</label>
@@ -173,7 +173,7 @@
<option value="">-- Selecciona --</option>
<?php foreach($transportistas as $t): ?>
<option value="<?= htmlspecialchars($t['id_transportista']) ?>" <?= $factura['transportista_id']==$t['id_transportista']?'selected':'' ?>>
<?= htmlspecialchars($t['nombre']) ?>
<?= htmlspecialchars($t['clave_identificador'] . ' - ' . $t['nombre']) ?>
</option>
<?php endforeach; ?>
</select>
@@ -183,7 +183,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']) ?>" <?= $factura['chofer_id']==$c['id_chofer']?'selected':'' ?>>
<option value="<?= htmlspecialchars($c['id_chofer']) ?>"
data-transportista="<?= htmlspecialchars($c['transportista_id']) ?>"
<?= $factura['chofer_id'] == $c['id_chofer']?'selected':'' ?>>
<?= htmlspecialchars($c['nombre']) ?>
</option>
<?php endforeach; ?>
@@ -295,12 +297,95 @@
<script src="https://cdn.jsdelivr.net/npm/select2@4.1.0/dist/js/select2.min.js"></script>
<script>
// ✅ 1. INICIALIZACIÓN PRINCIPAL
document.addEventListener('DOMContentLoaded', function() {
// Primero cargar proveedores, después inicializar otros selects
cargarProveedores().then(() => {
// Inicializar Choices.js en todos los selects searchable DESPUÉS de cargar proveedores
document.querySelectorAll('.searchable').forEach(el => {
// ✅ 1. INICIALIZACIÓN PRINCIPAL
document.addEventListener('DOMContentLoaded', function() {
// Primero cargar proveedores, después inicializar otros selects
cargarProveedores().then(() => {
// Inicializar Choices.js en todos los selects searchable DESPUÉS de cargar proveedores
document.querySelectorAll('.searchable').forEach(el => {
new Choices(el, {
searchEnabled: true,
itemSelectText: '',
shouldSort: false
});
});
});
});
// ✅ 2. FUNCIÓN PARA CARGAR PROVEEDORES (CON PROMESA)
function cargarProveedores() {
const proveedorEl = document.getElementById('proveedor_id');
// Obtenemos la cadena con la ID del proveedor guardado
const proveedorActual = '<?= htmlspecialchars($proveedor_clave_actual ?? '') ?>';
return fetch('/IMPORTADORES/solicitud_importacion/ajax_proveedores')
.then(res => {
if (!res.ok) throw new Error(`HTTP ${res.status}`);
return res.json();
})
.then(json => {
// Limpiar el select
proveedorEl.innerHTML = '<option value="">-- Selecciona Proveedor --</option>';
// Agregar todas las opciones
if (json.results && json.results.length > 0) {
json.results.forEach(item => {
const opt = document.createElement('option');
opt.value = item.id;
opt.textContent = item.text;
// Comparar forzando a cadena para que coincida con proveedorActual
if (String(item.id) === proveedorActual && proveedorActual !== '') {
opt.selected = true;
}
proveedorEl.appendChild(opt);
});
}
console.log('✅ Proveedores cargados. Proveedor actual:', proveedorActual);
})
.catch(err => {
console.error('❌ Error cargando proveedores:', err);
proveedorEl.innerHTML = '<option value="">Error cargando proveedores</option>';
});
}
// ✅ 3. AGREGAR PARTIDAS
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);
// Inicializar Choices.js en los nuevos selects
row.querySelectorAll('.searchable').forEach(el => {
new Choices(el, {
searchEnabled: true,
itemSelectText: '',
@@ -308,135 +393,250 @@
});
});
});
});
// ✅ 2. FUNCIÓN PARA CARGAR PROVEEDORES (CON PROMESA)
function cargarProveedores() {
const proveedorEl = document.getElementById('proveedor_id');
// Obtenemos la cadena con la ID del proveedor guardado
const proveedorActual = '<?= htmlspecialchars($proveedor_clave_actual ?? '') ?>';
return fetch('/IMPORTADORES/solicitud_importacion/ajax_proveedores')
.then(res => {
if (!res.ok) throw new Error(`HTTP ${res.status}`);
return res.json();
})
.then(json => {
// Limpiar el select
proveedorEl.innerHTML = '<option value="">-- Selecciona Proveedor --</option>';
// Agregar todas las opciones
if (json.results && json.results.length > 0) {
json.results.forEach(item => {
const opt = document.createElement('option');
opt.value = item.id;
opt.textContent = item.text;
// Comparar forzando a cadena para que coincida con proveedorActual
if (String(item.id) === proveedorActual && proveedorActual !== '') {
opt.selected = true;
}
proveedorEl.appendChild(opt);
});
}
console.log('✅ Proveedores cargados. Proveedor actual:', proveedorActual);
})
.catch(err => {
console.error('❌ Error cargando proveedores:', err);
proveedorEl.innerHTML = '<option value="">Error cargando proveedores</option>';
});
}
// ✅ 3. AGREGAR PARTIDAS
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);
// Inicializar Choices.js en los nuevos selects
row.querySelectorAll('.searchable').forEach(el => {
new Choices(el, {
searchEnabled: true,
itemSelectText: '',
shouldSort: false
});
});
});
// ✅ 4. REMOVER PARTIDAS
document.querySelector('#tabla-partidas tbody').addEventListener('click', e => {
if (e.target.matches('.remove-row')) {
// Destruir instancia de Choices.js antes de remover la fila
const row = e.target.closest('tr');
row.querySelectorAll('.searchable').forEach(el => {
if (el.choicesInstance) {
el.choicesInstance.destroy();
}
});
row.remove();
}
});
// ✅ 5. CONTROLAR OVERFLOW DE LA TABLA
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';
}
});
// ✅ 6. VALIDACIÓN DEL FORMULARIO
$(document).ready(function() {
$('#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)}).`
// ✅ 4. REMOVER PARTIDAS
document.querySelector('#tabla-partidas tbody').addEventListener('click', e => {
if (e.target.matches('.remove-row')) {
// Destruir instancia de Choices.js antes de remover la fila
const row = e.target.closest('tr');
row.querySelectorAll('.searchable').forEach(el => {
if (el.choicesInstance) {
el.choicesInstance.destroy();
}
});
row.remove();
}
});
});
// ✅ 5. CONTROLAR OVERFLOW DE LA TABLA
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';
}
});
// ✅ 6. VALIDACIÓN DEL FORMULARIO
$(document).ready(function() {
$('#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)}).`
});
}
});
});
document.addEventListener('DOMContentLoaded', function() {
const transportistaSelect = document.getElementById('transportista_id');
const choferSelect = document.getElementById('chofer_id');
// Guardar todas las opciones de choferes originales (para fallback)
const allChoferes = Array.from(choferSelect.querySelectorAll('option')).slice(1); // Excluir la primera opción vacía
// Obtener valores actuales (para modo edición)
const transportistaActual = transportistaSelect.value;
const choferActual = choferSelect.value;
// Crear un mapa de choferes por transportista desde las opciones cargadas
const choferesPorTransportista = {};
allChoferes.forEach(option => {
const transportistaId = option.getAttribute('data-transportista');
if (transportistaId) {
if (!choferesPorTransportista[transportistaId]) {
choferesPorTransportista[transportistaId] = [];
}
choferesPorTransportista[transportistaId].push({
id_chofer: option.value,
nombre: option.textContent.trim()
});
}
});
// Debug: mostrar el cache inicial
console.log('Cache de choferes por transportista:', choferesPorTransportista);
// Inicializar Choices.js si está disponible
let choferChoices = null;
if (typeof Choices !== 'undefined') {
choferChoices = new Choices(choferSelect, {
searchEnabled: true,
placeholderValue: '-- Selecciona Transportista primero --',
noResultsText: 'No se encontraron resultados',
itemSelectText: '',
searchPlaceholderValue: 'Buscar chofer...'
});
}
// Función para filtrar choferes por transportista
function filtrarChoferes(transportistaId) {
// Limpiar opciones excepto la primera
if (choferChoices) {
choferChoices.clearStore();
} else {
choferSelect.innerHTML = '<option value="">-- Selecciona Chofer --</option>';
}
if (transportistaId) {
// Intentar usar datos locales primero (más rápido)
if (choferesPorTransportista[transportistaId] && choferesPorTransportista[transportistaId].length > 0) {
console.log('Usando datos locales para transportista:', transportistaId);
cargarChoferes(choferesPorTransportista[transportistaId]);
} else {
// Si no hay datos locales, hacer petición AJAX
console.log('Haciendo petición AJAX para transportista:', transportistaId);
mostrarCargando();
fetch(`/IMPORTADORES/solicitud_importacion/obtenerChoferesPorTransportista?transportista_id=${transportistaId}`)
.then(response => {
console.log('Response status:', response.status);
if (!response.ok) {
return response.json().then(data => {
throw new Error(data.error || `Error ${response.status}`);
});
}
return response.json();
})
.then(choferes => {
console.log('Choferes recibidos:', choferes);
// Guardar en cache local para futuras consultas
choferesPorTransportista[transportistaId] = choferes;
cargarChoferes(choferes);
})
.catch(error => {
console.error('Error al cargar choferes:', error);
mostrarError();
});
}
} else {
// Si no hay transportista seleccionado, mostrar mensaje
mostrarSeleccionarTransportista();
}
}
// Función para mostrar estado de carga
function mostrarCargando() {
if (choferChoices) {
choferChoices.clearStore();
choferChoices.setChoices([{
value: '',
label: '-- Cargando choferes... --',
disabled: true
}], 'value', 'label', true);
} else {
choferSelect.innerHTML = '<option value="">-- Cargando choferes... --</option>';
}
}
// Función para mostrar mensaje de seleccionar transportista
function mostrarSeleccionarTransportista() {
if (choferChoices) {
choferChoices.clearStore();
choferChoices.setChoices([{
value: '',
label: '-- Selecciona Transportista primero --',
disabled: true
}], 'value', 'label', true);
} else {
choferSelect.innerHTML = '<option value="">-- Selecciona Transportista primero --</option>';
}
}
// Función para mostrar error
function mostrarError() {
const errorMessage = '-- Error al cargar choferes --';
if (choferChoices) {
choferChoices.clearStore();
choferChoices.setChoices([{
value: '',
label: errorMessage,
disabled: true
}], 'value', 'label', true);
} else {
choferSelect.innerHTML = `<option value="">${errorMessage}</option>`;
}
}
// Función para cargar choferes en el select
function cargarChoferes(choferes) {
const opciones = [{
value: '',
label: '-- Selecciona Chofer --',
disabled: false
}];
choferes.forEach(chofer => {
opciones.push({
value: chofer.id_chofer,
label: chofer.nombre,
disabled: false
});
});
if (choferChoices) {
choferChoices.clearStore();
choferChoices.setChoices(opciones, 'value', 'label', true);
// Restaurar selección actual si es válida
if (choferActual && choferes.some(c => c.id_chofer == choferActual)) {
choferChoices.setChoiceByValue(choferActual);
}
} else {
choferSelect.innerHTML = '';
opciones.forEach(opcion => {
const option = document.createElement('option');
option.value = opcion.value;
option.textContent = opcion.label;
if (opcion.disabled) option.disabled = true;
choferSelect.appendChild(option);
});
// Restaurar selección actual si es válida
if (choferActual && choferes.some(c => c.id_chofer == choferActual)) {
choferSelect.value = choferActual;
}
}
}
// Inicializar el filtro al cargar la página (para modo edición)
if (transportistaActual) {
filtrarChoferes(transportistaActual);
} else {
mostrarSeleccionarTransportista();
}
// Event listener para cambio de transportista
transportistaSelect.addEventListener('change', function() {
const transportistaId = this.value;
filtrarChoferes(transportistaId);
});
// Event listener para validar que se seleccione un chofer válido
choferSelect.addEventListener('change', function() {
const choferSeleccionado = this.value;
if (choferSeleccionado && !transportistaSelect.value) {
alert('Por favor, selecciona un transportista primero.');
this.value = '';
if (choferChoices) {
choferChoices.setChoiceByValue('');
}
}
});
});
</script>
</body>

View File

@@ -70,10 +70,16 @@
<body>
<?php if (!isset($_SESSION['id_agencia_en_uso']) || !$_SESSION['id_agencia_en_uso']): ?>
<div class="alert alert-warning mt-4">
<h5>⚠️ No tienes una agencia activa vinculada.</h5>
<p>Para poder gestionar tus solicitudes de importación, primero debes <strong>vincularte a una agencia</strong>.</p>
<a href="/IMPORTADORES/vinculaciones/nuevaVinculacion" class="btn btn-primary">Ir a Vinculaciones</a>
<div class="container mt-4">
<div class="row">
<div class="col-12">
<div class="alert alert-warning mt-4">
<h5>⚠️ No tienes una agencia activa vinculada.</h5>
<p>Para poder gestionar tus solicitudes de importación, primero debes <strong>vincularte a una agencia</strong>.</p>
<a href="/IMPORTADORES/vinculaciones/nuevaVinculacion" class="btn btn-primary btn-animated">Ir a Vinculaciones</a>
</div>
</div>
</div>
</div>
<?php return; ?>
<?php endif; ?>