Sinónimos - Solicitudes de importación
This commit is contained in:
@@ -129,7 +129,7 @@ function lista()
|
|||||||
ON pf.pais_comprador_vendedor = pc.nombre
|
ON pf.pais_comprador_vendedor = pc.nombre
|
||||||
WHERE pf.id_importador = ?
|
WHERE pf.id_importador = ?
|
||||||
AND pf.status = 1
|
AND pf.status = 1
|
||||||
ORDER BY pf.fecha_alta DESC
|
ORDER BY pf.frecuencia_uso DESC
|
||||||
";
|
";
|
||||||
$stmt = sqlsrv_query($conn, $sql, [$_SESSION['usuario_id']]);
|
$stmt = sqlsrv_query($conn, $sql, [$_SESSION['usuario_id']]);
|
||||||
|
|
||||||
|
|||||||
@@ -239,6 +239,150 @@ function obtenerChoferesPorTransportista()
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function buscar_productos()
|
||||||
|
{
|
||||||
|
header('Content-Type: application/json');
|
||||||
|
|
||||||
|
// 1. Validar usuario autenticado
|
||||||
|
if (!($_SESSION['usuario_id'] ?? false)) {
|
||||||
|
http_response_code(401);
|
||||||
|
echo json_encode(['error' => 'No autorizado']);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
$id_importador = $_SESSION['usuario_id'];
|
||||||
|
$query = trim($_GET['q'] ?? '');
|
||||||
|
|
||||||
|
// 2. Validar longitud mínima
|
||||||
|
if (strlen($query) < 2) {
|
||||||
|
echo json_encode([]);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
$conn = getConnection(); // Obtener conexión
|
||||||
|
|
||||||
|
// 3. Búsqueda mejorada con ponderación
|
||||||
|
$searchTerm = "%$query%";
|
||||||
|
$sql = "SELECT TOP 10
|
||||||
|
id_producto_frecuente,
|
||||||
|
sinonimo,
|
||||||
|
descripcion,
|
||||||
|
preferencia,
|
||||||
|
fraccion,
|
||||||
|
nico,
|
||||||
|
numero_parte,
|
||||||
|
CAST(umc_id AS VARCHAR) AS umc_id,
|
||||||
|
-- Campos para cálculo de relevancia
|
||||||
|
CASE
|
||||||
|
WHEN sinonimo LIKE ? THEN 100
|
||||||
|
WHEN descripcion LIKE ? THEN 50
|
||||||
|
ELSE 0
|
||||||
|
END AS relevancia
|
||||||
|
FROM dbo.productos_frecuentes
|
||||||
|
WHERE id_importador = ?
|
||||||
|
AND status = 1
|
||||||
|
AND (sinonimo LIKE ? OR descripcion LIKE ? OR numero_parte LIKE ?)
|
||||||
|
ORDER BY relevancia DESC, frecuencia_uso DESC, sinonimo";
|
||||||
|
|
||||||
|
$params = [
|
||||||
|
"$query%", // Para búsqueda al inicio del sinonimo
|
||||||
|
"$query%", // Para búsqueda al inicio de descripción
|
||||||
|
$id_importador,
|
||||||
|
$searchTerm,
|
||||||
|
$searchTerm,
|
||||||
|
$searchTerm
|
||||||
|
];
|
||||||
|
|
||||||
|
$stmt = sqlsrv_query($conn, $sql, $params);
|
||||||
|
|
||||||
|
if ($stmt === false) {
|
||||||
|
error_log("Error en búsqueda de productos: " . print_r(sqlsrv_errors(), true));
|
||||||
|
echo json_encode([]);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
$productos = [];
|
||||||
|
while ($row = sqlsrv_fetch_array($stmt, SQLSRV_FETCH_ASSOC)) {
|
||||||
|
$productos[] = [
|
||||||
|
'id' => $row['id_producto_frecuente'],
|
||||||
|
'sinonimo' => $row['sinonimo'],
|
||||||
|
'descripcion' => $row['descripcion'] ?? '',
|
||||||
|
'preferencia' => $row['preferencia'] ?? '',
|
||||||
|
'fraccion' => $row['fraccion'] ?? '',
|
||||||
|
'nico' => $row['nico'] ?? '',
|
||||||
|
'numero_parte' => $row['numero_parte'] ?? '',
|
||||||
|
'umc_id' => $row['umc_id'] ?? null
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
sqlsrv_free_stmt($stmt);
|
||||||
|
echo json_encode($productos);
|
||||||
|
|
||||||
|
} catch (Exception $e) {
|
||||||
|
error_log("Excepción en buscar_productos: " . $e->getMessage());
|
||||||
|
http_response_code(500);
|
||||||
|
echo json_encode(['error' => 'Error interno']);
|
||||||
|
}
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
function incrementar_frecuencia()
|
||||||
|
{
|
||||||
|
header('Content-Type: application/json');
|
||||||
|
|
||||||
|
// 1. Validar usuario
|
||||||
|
if (!($_SESSION['usuario_id'] ?? false)) {
|
||||||
|
http_response_code(401);
|
||||||
|
echo json_encode(['success' => false, 'error' => 'No autorizado']);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
$id_importador = $_SESSION['usuario_id'];
|
||||||
|
$producto_id = (int)($_POST['producto_id'] ?? 0);
|
||||||
|
|
||||||
|
if ($producto_id <= 0) {
|
||||||
|
echo json_encode(['success' => false, 'error' => 'ID inválido']);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
$conn = getConnection();
|
||||||
|
|
||||||
|
// 2. Verificar que el producto pertenece al usuario
|
||||||
|
$sqlValidate = "SELECT 1 FROM dbo.productos_frecuentes
|
||||||
|
WHERE id_producto_frecuente = ? AND id_importador = ?";
|
||||||
|
$stmtValidate = sqlsrv_query($conn, $sqlValidate, [$producto_id, $id_importador]);
|
||||||
|
|
||||||
|
if (!$stmtValidate || !sqlsrv_fetch_array($stmtValidate)) {
|
||||||
|
http_response_code(403);
|
||||||
|
echo json_encode(['success' => false, 'error' => 'Producto no válido']);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3. Actualizar frecuencia
|
||||||
|
$sqlUpdate = "UPDATE dbo.productos_frecuentes
|
||||||
|
SET frecuencia_uso = frecuencia_uso + 1
|
||||||
|
WHERE id_producto_frecuente = ?";
|
||||||
|
|
||||||
|
$stmtUpdate = sqlsrv_query($conn, $sqlUpdate, [$producto_id]);
|
||||||
|
|
||||||
|
if ($stmtUpdate === false) {
|
||||||
|
error_log("Error actualizando frecuencia: " . print_r(sqlsrv_errors(), true));
|
||||||
|
echo json_encode(['success' => false, 'error' => 'Error en actualización']);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
echo json_encode(['success' => true]);
|
||||||
|
|
||||||
|
} catch (Exception $e) {
|
||||||
|
error_log("Excepción en incrementar_frecuencia: " . $e->getMessage());
|
||||||
|
http_response_code(500);
|
||||||
|
echo json_encode(['success' => false, 'error' => 'Error interno']);
|
||||||
|
}
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
/** Procesa la creación de una nueva factura y sus partidas **/
|
/** Procesa la creación de una nueva factura y sus partidas **/
|
||||||
function guardar()
|
function guardar()
|
||||||
{
|
{
|
||||||
@@ -759,20 +903,17 @@ function actualizar()
|
|||||||
die("❌ Error ejecutando UPDATE: " . print_r(sqlsrv_errors(), true));
|
die("❌ Error ejecutando UPDATE: " . print_r(sqlsrv_errors(), true));
|
||||||
}
|
}
|
||||||
|
|
||||||
// 7) Borrar partidas anteriores
|
// 7) Manejo de partidas - Versión mejorada
|
||||||
$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));
|
|
||||||
}
|
|
||||||
|
|
||||||
// 8) Reinsertar partidas desde el formulario
|
|
||||||
if (!empty($_POST['partidas']) && is_array($_POST['partidas'])) {
|
if (!empty($_POST['partidas']) && is_array($_POST['partidas'])) {
|
||||||
$sqlP = "INSERT INTO dbo.solicitud_importacion_partidas
|
// Obtener partidas existentes de la base de datos
|
||||||
(id_solicitud, descripcion, cantidad_comercial, cantidad_tarifa,
|
$partidasExistentes = [];
|
||||||
valor_factura, peso_bruto, unidad_comercial_id, tasa_preferencial)
|
$stmtPartidas = sqlsrv_query($conn, "SELECT id_partida FROM dbo.solicitud_importacion_partidas WHERE id_solicitud = ?", [$id_solicitud]);
|
||||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?)
|
|
||||||
";
|
while ($row = sqlsrv_fetch_array($stmtPartidas, SQLSRV_FETCH_ASSOC)) {
|
||||||
|
$partidasExistentes[] = $row['id_partida'];
|
||||||
|
}
|
||||||
|
|
||||||
|
// Procesar cada partida del formulario
|
||||||
foreach ($_POST['partidas'] as $i => $p) {
|
foreach ($_POST['partidas'] as $i => $p) {
|
||||||
$desc = trim($p['descripcion'] ?? '');
|
$desc = trim($p['descripcion'] ?? '');
|
||||||
$cantCom = floatval($p['cantidad_comercial'] ?? 0);
|
$cantCom = floatval($p['cantidad_comercial'] ?? 0);
|
||||||
@@ -782,28 +923,57 @@ function actualizar()
|
|||||||
$umId = intval($p['unidad_comercial_id'] ?? 0) ?: null;
|
$umId = intval($p['unidad_comercial_id'] ?? 0) ?: null;
|
||||||
$tasaPref = trim($p['tasa_preferencial'] ?? '');
|
$tasaPref = trim($p['tasa_preferencial'] ?? '');
|
||||||
|
|
||||||
// Sólo inserta si descripción y cantidad comercial válidos
|
// Solo procesar si tiene descripción y cantidad válida
|
||||||
if ($desc !== '' && $cantCom > 0) {
|
if ($desc !== '' && $cantCom > 0) {
|
||||||
$paramsP = [
|
// Verificar si es una partida existente (tiene id_partida numérico > 0)
|
||||||
$id_solicitud,
|
if (!empty($p['id_partida']) && intval($p['id_partida']) > 0) {
|
||||||
$desc,
|
// ACTUALIZAR partida existente
|
||||||
$cantCom,
|
$sql = "UPDATE dbo.solicitud_importacion_partidas SET
|
||||||
$cantTar,
|
descripcion = ?, cantidad_comercial = ?, cantidad_tarifa = ?,
|
||||||
$valPart,
|
valor_factura = ?, peso_bruto = ?, unidad_comercial_id = ?, tasa_preferencial = ?
|
||||||
$peso,
|
WHERE id_partida = ?
|
||||||
$umId,
|
AND id_solicitud = ?
|
||||||
$tasaPref
|
";
|
||||||
];
|
$params = [ $desc, $cantCom, $cantTar, $valPart, $peso, $umId, $tasaPref, intval($p['id_partida']), $id_solicitud ];
|
||||||
$stmtP = sqlsrv_query($conn, $sqlP, $paramsP);
|
|
||||||
|
|
||||||
if ($stmtP === false) {
|
// Eliminar de la lista de existentes
|
||||||
die("❌ Error insertando partida #$i: " . print_r(sqlsrv_errors(), true));
|
if (($key = array_search($p['id_partida'], $partidasExistentes)) !== false) {
|
||||||
|
unset($partidasExistentes[$key]);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// INSERTAR nueva partida (asegurarse que no tenga id_partida o sea 0)
|
||||||
|
$sql = "INSERT INTO dbo.solicitud_importacion_partidas
|
||||||
|
(id_solicitud, descripcion, cantidad_comercial, cantidad_tarifa,
|
||||||
|
valor_factura, peso_bruto, unidad_comercial_id, tasa_preferencial)
|
||||||
|
VALUES (?, ?, ?, ?, ?, ?, ?, ?)
|
||||||
|
";
|
||||||
|
$params = [ $id_solicitud, $desc, $cantCom, $cantTar, $valPart, $peso, $umId, $tasaPref ];
|
||||||
|
|
||||||
|
error_log("Insertando nueva partida: " . print_r($params, true));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$stmt = sqlsrv_query($conn, $sql, $params);
|
||||||
|
if ($stmt === false) {
|
||||||
|
error_log("Error en consulta SQL: " . print_r(sqlsrv_errors(), true));
|
||||||
|
die("❌ Error procesando partida #$i: " . print_r(sqlsrv_errors(), true));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Eliminar partidas que ya no están en el formulario
|
||||||
|
if (!empty($partidasExistentes)) {
|
||||||
|
$ids = implode(',', $partidasExistentes);
|
||||||
|
$sql = "DELETE FROM dbo.solicitud_importacion_partidas
|
||||||
|
WHERE id_partida IN ($ids)
|
||||||
|
AND id_solicitud = ?
|
||||||
|
";
|
||||||
|
$stmt = sqlsrv_query($conn, $sql, [$id_solicitud]);
|
||||||
|
if ($stmt === false) {
|
||||||
|
die("❌ Error eliminando partidas obsoletas: " . print_r(sqlsrv_errors(), true));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 9) Redirigir
|
// 8) Redirigir
|
||||||
header('Location: /IMPORTADORES/solicitud_importacion/lista?updated=ok');
|
header('Location: /IMPORTADORES/solicitud_importacion/lista?updated=ok');
|
||||||
exit;
|
exit;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -23,10 +23,8 @@ CREATE TABLE dbo.productos_frecuentes (
|
|||||||
id_importador INT NOT NULL, -- FK a usuarios_sistema(id_usuario)
|
id_importador INT NOT NULL, -- FK a usuarios_sistema(id_usuario)
|
||||||
fecha_alta DATETIME2 NOT NULL DEFAULT GETDATE(),
|
fecha_alta DATETIME2 NOT NULL DEFAULT GETDATE(),
|
||||||
status INT NOT NULL DEFAULT 1,
|
status INT NOT NULL DEFAULT 1,
|
||||||
|
|
||||||
frecuencia_uso INT NOT NULL DEFAULT 1, -- cuántas veces se ha utilizado
|
frecuencia_uso INT NOT NULL DEFAULT 1, -- cuántas veces se ha utilizado
|
||||||
|
|
||||||
|
|
||||||
CONSTRAINT FK_prodFreq_UMC
|
CONSTRAINT FK_prodFreq_UMC
|
||||||
FOREIGN KEY (umc_id)
|
FOREIGN KEY (umc_id)
|
||||||
REFERENCES dbo.unidades_medida_apendice7(id),
|
REFERENCES dbo.unidades_medida_apendice7(id),
|
||||||
|
|||||||
@@ -79,6 +79,19 @@
|
|||||||
.form-group-animated:nth-child(9) { animation-delay: 0.9s; }
|
.form-group-animated:nth-child(9) { animation-delay: 0.9s; }
|
||||||
.form-group-animated:nth-child(10) { animation-delay: 1.0s; }
|
.form-group-animated:nth-child(10) { animation-delay: 1.0s; }
|
||||||
@keyframes slideUp { to { opacity: 1; transform: translateY(0); } }
|
@keyframes slideUp { to { opacity: 1; transform: translateY(0); } }
|
||||||
|
/* Estilos para las sugerencias de autocompletado */
|
||||||
|
.autocomplete-suggestions { position: absolute; width: calc(100% - 2px); /* Ajustar al ancho del input */ background: white; border: 1px solid #ced4da; border-top: none;
|
||||||
|
border-radius: 0 0 4px 4px; box-shadow: 0 4px 6px rgba(0,0,0,0.1); z-index: 1000; max-height: 200px; overflow-y: auto; display: none; /* Inicialmente oculto */ }
|
||||||
|
.autocomplete-item { padding: 8px 12px; cursor: pointer; transition: background-color 0.2s; }
|
||||||
|
.autocomplete-item:hover, .autocomplete-item.active { background-color: #f8f9fa; }
|
||||||
|
.autocomplete-item strong { display: block; margin-bottom: 2px; }
|
||||||
|
.autocomplete-item .text-muted { font-size: 0.85em; color: #6c757d; }
|
||||||
|
.cursor-pointer { cursor: pointer; }
|
||||||
|
/* Estilos para validación */
|
||||||
|
.choices.required .choices__inner { border: 1px solid #ced4da; }
|
||||||
|
.choices.is-invalid .choices__inner { border: 1px solid #dc3545; background-color: #fff5f5; }
|
||||||
|
.choices.is-valid .choices__inner { border: 1px solid #28a745; background-color: #f5fff5; }
|
||||||
|
.choices__input { opacity: 1 !important; }
|
||||||
</style>
|
</style>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
@@ -117,14 +130,14 @@
|
|||||||
|
|
||||||
<div class="col-md-3 mb-3">
|
<div class="col-md-3 mb-3">
|
||||||
<label for="proveedor_id" class="form-label">Proveedor</label>
|
<label for="proveedor_id" class="form-label">Proveedor</label>
|
||||||
<select id="proveedor_id" name="proveedor_id" class="form-select searchable" required>
|
<select id="proveedor_id" name="proveedor_id" class="form-select searchable" data-required="true" required>
|
||||||
<option value="">Cargando proveedores…</option>
|
<option value="">Cargando proveedores…</option>
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="col-md-3 mb-3">
|
<div class="col-md-3 mb-3">
|
||||||
<label for="anexo22_apendice" class="form-label">Anexo 22 – Apéndice</label>
|
<label for="anexo22_apendice" class="form-label">Anexo 22 – Apéndice</label>
|
||||||
<select id="anexo22_apendice" name="anexo22_apendice" class="form-select searchable" required>
|
<select id="anexo22_apendice" name="anexo22_apendice" class="form-select searchable" data-required="true" required>
|
||||||
<option value="">-- Selecciona Aduana --</option>
|
<option value="">-- Selecciona Aduana --</option>
|
||||||
<?php foreach($aduanas as $a): ?>
|
<?php foreach($aduanas as $a): ?>
|
||||||
<option value="<?= htmlspecialchars($a['aduana_seccion']) ?>">
|
<option value="<?= htmlspecialchars($a['aduana_seccion']) ?>">
|
||||||
@@ -139,7 +152,7 @@
|
|||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col-md-3 mb-3">
|
<div class="col-md-3 mb-3">
|
||||||
<label for="patente" class="form-label">Patente Aduanal</label>
|
<label for="patente" class="form-label">Patente Aduanal</label>
|
||||||
<select id="patente" name="patente" class="form-select searchable" required>
|
<select id="patente" name="patente" class="form-select searchable" data-required="true" required>
|
||||||
<option value="">-- Selecciona Patente --</option>
|
<option value="">-- Selecciona Patente --</option>
|
||||||
<?php foreach($patentes as $pt): ?>
|
<?php foreach($patentes as $pt): ?>
|
||||||
<option value="<?= htmlspecialchars($pt['id_agente']) ?>">
|
<option value="<?= htmlspecialchars($pt['id_agente']) ?>">
|
||||||
@@ -151,7 +164,7 @@
|
|||||||
|
|
||||||
<div class="col-md-3 mb-3">
|
<div class="col-md-3 mb-3">
|
||||||
<label for="incoterm" class="form-label">INCOTERM</label>
|
<label for="incoterm" class="form-label">INCOTERM</label>
|
||||||
<select id="incoterm" name="incoterm" class="form-select searchable" required>
|
<select id="incoterm" name="incoterm" class="form-select searchable" data-required="true" required>
|
||||||
<option value="">-- Selecciona Incoterm --</option>
|
<option value="">-- Selecciona Incoterm --</option>
|
||||||
<?php foreach($incoterms as $inc): ?>
|
<?php foreach($incoterms as $inc): ?>
|
||||||
<option value="<?= htmlspecialchars($inc['INCOTERM']) ?>">
|
<option value="<?= htmlspecialchars($inc['INCOTERM']) ?>">
|
||||||
@@ -163,7 +176,7 @@
|
|||||||
|
|
||||||
<div class="col-md-3 mb-3">
|
<div class="col-md-3 mb-3">
|
||||||
<label for="pais_proveedor" class="form-label">País (Proveedor)</label>
|
<label for="pais_proveedor" class="form-label">País (Proveedor)</label>
|
||||||
<select id="pais_proveedor" name="pais_proveedor" class="form-select searchable" required>
|
<select id="pais_proveedor" name="pais_proveedor" class="form-select searchable" data-required="true" required>
|
||||||
<option value="">-- Selecciona País --</option>
|
<option value="">-- Selecciona País --</option>
|
||||||
<?php foreach($paises as $p): ?>
|
<?php foreach($paises as $p): ?>
|
||||||
<option value="<?= htmlspecialchars($p['id_pais']) ?>"><?= htmlspecialchars($p['nombre']) ?></option>
|
<option value="<?= htmlspecialchars($p['id_pais']) ?>"><?= htmlspecialchars($p['nombre']) ?></option>
|
||||||
@@ -173,7 +186,7 @@
|
|||||||
|
|
||||||
<div class="col-md-3 mb-3">
|
<div class="col-md-3 mb-3">
|
||||||
<label for="tipo_moneda" class="form-label">Moneda</label>
|
<label for="tipo_moneda" class="form-label">Moneda</label>
|
||||||
<select id="tipo_moneda" name="tipo_moneda" class="form-select searchable" required>
|
<select id="tipo_moneda" name="tipo_moneda" class="form-select searchable" data-required="true" required>
|
||||||
<?php foreach(['MXN'=>'Peso Mexicano','USD'=>'Dólar USD','EUR'=>'Euro','CNY'=>'Yuan','GBP'=>'Libra GBP','JPY'=>'Yen'] as $code=>$label): ?>
|
<?php foreach(['MXN'=>'Peso Mexicano','USD'=>'Dólar USD','EUR'=>'Euro','CNY'=>'Yuan','GBP'=>'Libra GBP','JPY'=>'Yen'] as $code=>$label): ?>
|
||||||
<option value="<?= $code ?>"><?= "$label ($code)" ?></option>
|
<option value="<?= $code ?>"><?= "$label ($code)" ?></option>
|
||||||
<?php endforeach; ?>
|
<?php endforeach; ?>
|
||||||
@@ -190,7 +203,7 @@
|
|||||||
|
|
||||||
<div class="col-md-4 mb-3">
|
<div class="col-md-4 mb-3">
|
||||||
<label for="vinculacion" class="form-label">Vinculación</label>
|
<label for="vinculacion" class="form-label">Vinculación</label>
|
||||||
<select id="vinculacion" name="vinculacion" class="form-select searchable">
|
<select id="vinculacion" name="vinculacion" class="form-select searchable" data-required="true" required>
|
||||||
<option value="0">No existe</option>
|
<option value="0">No existe</option>
|
||||||
<option value="1">Existe, no afecta</option>
|
<option value="1">Existe, no afecta</option>
|
||||||
<option value="2">Existe y afecta</option>
|
<option value="2">Existe y afecta</option>
|
||||||
@@ -202,7 +215,7 @@
|
|||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col-md-4 mb-3">
|
<div class="col-md-4 mb-3">
|
||||||
<label for="transportista_id" class="form-label">Transportista</label>
|
<label for="transportista_id" class="form-label">Transportista</label>
|
||||||
<select id="transportista_id" name="transportista_id" class="form-select searchable" required>
|
<select id="transportista_id" name="transportista_id" class="form-select searchable" data-required="true" required>
|
||||||
<option value="">-- Selecciona --</option>
|
<option value="">-- Selecciona --</option>
|
||||||
<?php foreach($transportistas as $t): ?>
|
<?php foreach($transportistas as $t): ?>
|
||||||
<option value="<?= htmlspecialchars($t['id_transportista']) ?>">
|
<option value="<?= htmlspecialchars($t['id_transportista']) ?>">
|
||||||
@@ -214,7 +227,7 @@
|
|||||||
|
|
||||||
<div class="col-md-4 mb-3">
|
<div class="col-md-4 mb-3">
|
||||||
<label for="chofer_id" class="form-label">Chofer</label>
|
<label for="chofer_id" class="form-label">Chofer</label>
|
||||||
<select id="chofer_id" name="chofer_id" class="form-select searchable" required>
|
<select id="chofer_id" name="chofer_id" class="form-select searchable" data-required="true" required>
|
||||||
<option value="">-- Selecciona Chofer --</option>
|
<option value="">-- Selecciona Chofer --</option>
|
||||||
<?php foreach($choferes as $c): ?>
|
<?php foreach($choferes as $c): ?>
|
||||||
<option value="<?= htmlspecialchars($c['id_chofer']) ?>">
|
<option value="<?= htmlspecialchars($c['id_chofer']) ?>">
|
||||||
@@ -251,12 +264,16 @@
|
|||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
<tr>
|
<tr>
|
||||||
<td><input name="partidas[0][descripcion]" class="form-control w-auto" required></td>
|
<td>
|
||||||
|
<div class="position-relative">
|
||||||
|
<input name="partidas[0][descripcion]" class="form-control w-auto descripcion-input" required autocomplete="off">
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
<td><input name="partidas[0][cantidad_comercial]" type="number" step="0.0001" class="form-control w-auto" min="0" required></td>
|
<td><input name="partidas[0][cantidad_comercial]" type="number" step="0.0001" class="form-control w-auto" min="0" required></td>
|
||||||
<td><input name="partidas[0][cantidad_tarifa]" type="number" step="0.0001" class="form-control w-auto" min="0" required></td>
|
<td><input name="partidas[0][cantidad_tarifa]" type="number" step="0.0001" class="form-control w-auto" min="0" required></td>
|
||||||
<td>
|
<td>
|
||||||
<div class="mb-3">
|
<div class="mb-3">
|
||||||
<select name="partidas[0][unidad_comercial_id]" class="form-select searchable" required>
|
<select name="partidas[0][unidad_comercial_id]" class="form-select searchable" data-required="true" required>
|
||||||
<option value="">-- Unidad --</option>
|
<option value="">-- Unidad --</option>
|
||||||
<?php foreach($unidades_medida as $um): ?>
|
<?php foreach($unidades_medida as $um): ?>
|
||||||
<option value="<?= htmlspecialchars($um['id']) ?>"><?= htmlspecialchars($um['descripcion']) ?></option>
|
<option value="<?= htmlspecialchars($um['id']) ?>"><?= htmlspecialchars($um['descripcion']) ?></option>
|
||||||
@@ -268,7 +285,7 @@
|
|||||||
<td><input name="partidas[0][peso_bruto]" type="number" step="0.0001" class="form-control w-auto" min="0" required></td>
|
<td><input name="partidas[0][peso_bruto]" type="number" step="0.0001" class="form-control w-auto" min="0" required></td>
|
||||||
<td>
|
<td>
|
||||||
<div class="mb-3">
|
<div class="mb-3">
|
||||||
<select name="partidas[0][tasa_preferencial]" class="form-select searchable" required>
|
<select name="partidas[0][tasa_preferencial]" class="form-select searchable" data-required="true" required>
|
||||||
<option value="">-- Selecciona --</option>
|
<option value="">-- Selecciona --</option>
|
||||||
<option>General</option>
|
<option>General</option>
|
||||||
<option>TLC</option>
|
<option>TLC</option>
|
||||||
@@ -306,16 +323,36 @@
|
|||||||
|
|
||||||
<!-- 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://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/js/bootstrap.bundle.min.js"></script>
|
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/js/bootstrap.bundle.min.js"></script>
|
||||||
<script src="https://cdn.jsdelivr.net/npm/select2@4.1.0/dist/js/select2.min.js"></script>
|
<script src="https://cdn.jsdelivr.net/npm/select2@4.1.0/dist/js/select2.min.js"></script>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
|
function safeInitializeChoices(element) {
|
||||||
|
if (!element._choices) {
|
||||||
|
const isRequired = element.hasAttribute('required');
|
||||||
|
if (isRequired) {
|
||||||
|
element.removeAttribute('required');
|
||||||
|
element.setAttribute('data-required', 'true');
|
||||||
|
}
|
||||||
|
|
||||||
|
element._choices = new Choices(element, {
|
||||||
|
searchEnabled: true,
|
||||||
|
itemSelectText: '',
|
||||||
|
shouldSort: false,
|
||||||
|
silent: true
|
||||||
|
});
|
||||||
|
|
||||||
|
if (isRequired) {
|
||||||
|
const container = element.closest('.choices');
|
||||||
|
if (container) { container.classList.add('required'); }
|
||||||
|
element.addEventListener('change', validateChoice.bind(null, element));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
document.addEventListener('DOMContentLoaded', () => {
|
document.addEventListener('DOMContentLoaded', () => {
|
||||||
// Primero cargamos proveedores, luego inicializamos Choices en todos los selects
|
// Primero cargamos proveedores, luego inicializamos Choices en todos los selects
|
||||||
cargarProveedores().then(() => {
|
cargarProveedores().then(() => { document.querySelectorAll('.searchable').forEach(safeInitializeChoices); });
|
||||||
inicializarChoicesGlobal();
|
|
||||||
});
|
|
||||||
|
|
||||||
// Inicializamos los eventos adicionales
|
// Inicializamos los eventos adicionales
|
||||||
inicializarEventos();
|
inicializarEventos();
|
||||||
@@ -348,30 +385,92 @@
|
|||||||
// Inicializamos Choices globalmente (todos los .searchable)
|
// Inicializamos Choices globalmente (todos los .searchable)
|
||||||
function inicializarChoicesGlobal() {
|
function inicializarChoicesGlobal() {
|
||||||
document.querySelectorAll('.searchable').forEach(el => {
|
document.querySelectorAll('.searchable').forEach(el => {
|
||||||
if (el._choices) el._choices.destroy(); // destruye instancia previa si existe
|
if (!el._choices) {
|
||||||
el._choices = new Choices(el, {
|
// Eliminar el atributo 'required' del select original
|
||||||
searchEnabled: true,
|
el.removeAttribute('required');
|
||||||
itemSelectText: '',
|
|
||||||
shouldSort: false,
|
// Configuración de Choices sin espacios en classNames
|
||||||
searchFields: ['label'] // 🔐 Solo busca en el texto visible
|
el._choices = new Choices(el, {
|
||||||
});
|
searchEnabled: true,
|
||||||
|
itemSelectText: '',
|
||||||
|
shouldSort: false,
|
||||||
|
searchFields: ['label'],
|
||||||
|
silent: true
|
||||||
|
});
|
||||||
|
|
||||||
|
// Agregar clase 'required' al contenedor externo manualmente
|
||||||
|
if (el.hasAttribute('data-required')) {
|
||||||
|
const container = el.closest('.choices');
|
||||||
|
if (container) {
|
||||||
|
container.classList.add('required');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Agregar validación personalizada
|
||||||
|
el.addEventListener('change', function() {
|
||||||
|
validateChoice(this);
|
||||||
|
});
|
||||||
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Función para validar los selects de Choices
|
||||||
|
function validateChoice(selectElement) {
|
||||||
|
const choicesInstance = selectElement._choices;
|
||||||
|
if (choicesInstance) {
|
||||||
|
const container = selectElement.closest('.choices');
|
||||||
|
const hasValue = choicesInstance.getValue(true).length > 0;
|
||||||
|
const isRequired = selectElement.hasAttribute('data-required');
|
||||||
|
if (isRequired) {
|
||||||
|
if (hasValue) {
|
||||||
|
container.classList.remove('is-invalid');
|
||||||
|
container.classList.add('is-valid');
|
||||||
|
} else {
|
||||||
|
container.classList.remove('is-valid');
|
||||||
|
container.classList.add('is-invalid');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Validar todos los selects antes de enviar el formulario
|
||||||
|
document.getElementById('solicitudForm').addEventListener('submit', function(e) {
|
||||||
|
let isValid = true;
|
||||||
|
|
||||||
|
document.querySelectorAll('.searchable[data-required]').forEach(el => {
|
||||||
|
validateChoice(el);
|
||||||
|
const choicesInstance = el._choices;
|
||||||
|
if (choicesInstance && choicesInstance.getValue(true).length === 0) {
|
||||||
|
isValid = false;
|
||||||
|
// Desplazarse al primer error
|
||||||
|
if (isValid === false) { el.closest('.choices').scrollIntoView({ behavior: 'smooth', block: 'center' }); }
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!isValid) {
|
||||||
|
e.preventDefault();
|
||||||
|
Swal.fire({ icon: 'error', title: 'Campos requeridos', text: 'Por favor complete todos los campos obligatorios marcados en rojo', confirmButtonColor: '#3085d6' });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
// Eventos principales del formulario
|
// Eventos principales del formulario
|
||||||
function inicializarEventos() {
|
function inicializarEventos() {
|
||||||
// Agregar partidas dinámicamente
|
// Agregar partidas dinámicamente
|
||||||
document.getElementById('add-partida').addEventListener('click', () => {
|
document.getElementById('add-partida').addEventListener('click', () => {
|
||||||
const tbody = document.querySelector('#tabla-partidas tbody');
|
const tbody = document.querySelector('#tabla-partidas tbody');
|
||||||
const idx = tbody.querySelectorAll('tr').length;
|
const idx = tbody.querySelectorAll('tr').length;
|
||||||
const row = document.createElement('tr');
|
const row = document.createElement('tr');
|
||||||
|
|
||||||
row.innerHTML = `
|
row.innerHTML = `
|
||||||
<td><input name="partidas[${idx}][descripcion]" class="form-control w-auto"></td>
|
<td>
|
||||||
|
<div class="position-relative">
|
||||||
|
<input name="partidas[0][descripcion]" class="form-control w-auto descripcion-input" required autocomplete="off">
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
<td><input name="partidas[${idx}][cantidad_comercial]" type="number" step="0.0001" class="form-control w-auto"></td>
|
<td><input name="partidas[${idx}][cantidad_comercial]" type="number" step="0.0001" class="form-control w-auto"></td>
|
||||||
<td><input name="partidas[${idx}][cantidad_tarifa]" type="number" step="0.0001" class="form-control w-auto"></td>
|
<td><input name="partidas[${idx}][cantidad_tarifa]" type="number" step="0.0001" class="form-control w-auto"></td>
|
||||||
<td>
|
<td>
|
||||||
<select name="partidas[${idx}][unidad_comercial_id]" class="form-select searchable">
|
<select name="partidas[${idx}][unidad_comercial_id]" class="form-select searchable" data-required="true" required>
|
||||||
<option value="">-- Unidad --</option>
|
<option value="">-- Unidad --</option>
|
||||||
<?php foreach($unidades_medida as $um): ?>
|
<?php foreach($unidades_medida as $um): ?>
|
||||||
<option value="<?= htmlspecialchars($um['id']) ?>"><?= htmlspecialchars($um['descripcion']) ?></option>
|
<option value="<?= htmlspecialchars($um['id']) ?>"><?= htmlspecialchars($um['descripcion']) ?></option>
|
||||||
@@ -381,7 +480,7 @@
|
|||||||
<td><input name="partidas[${idx}][valor_factura]" type="number" step="0.01" class="form-control valor-partida w-auto"></td>
|
<td><input name="partidas[${idx}][valor_factura]" type="number" step="0.01" class="form-control valor-partida w-auto"></td>
|
||||||
<td><input name="partidas[${idx}][peso_bruto]" type="number" step="0.0001" class="form-control w-auto"></td>
|
<td><input name="partidas[${idx}][peso_bruto]" type="number" step="0.0001" class="form-control w-auto"></td>
|
||||||
<td>
|
<td>
|
||||||
<select name="partidas[${idx}][tasa_preferencial]" class="form-select searchable">
|
<select name="partidas[${idx}][tasa_preferencial]" class="form-select searchable" data-required="true" required>
|
||||||
<option value="">-- Selecciona --</option>
|
<option value="">-- Selecciona --</option>
|
||||||
<option>General</option>
|
<option>General</option>
|
||||||
<option>TLC</option>
|
<option>TLC</option>
|
||||||
@@ -397,13 +496,25 @@
|
|||||||
|
|
||||||
tbody.appendChild(row);
|
tbody.appendChild(row);
|
||||||
// Inicializamos Choices en los nuevos selects
|
// Inicializamos Choices en los nuevos selects
|
||||||
row.querySelectorAll('.searchable').forEach(el => {
|
setTimeout(() => {
|
||||||
el._choices = new Choices(el, {
|
row.querySelectorAll('.searchable').forEach(el => {
|
||||||
searchEnabled: true,
|
if (!el._choices) {
|
||||||
itemSelectText: '',
|
el._choices = new Choices(el, {
|
||||||
shouldSort: false
|
searchEnabled: true,
|
||||||
|
itemSelectText: '',
|
||||||
|
shouldSort: false,
|
||||||
|
silent: true
|
||||||
|
});
|
||||||
|
// Agregar clase required si corresponde
|
||||||
|
if (el.hasAttribute('data-required')) {
|
||||||
|
const container = el.closest('.choices');
|
||||||
|
if (container) { container.classList.add('required'); }
|
||||||
|
}
|
||||||
|
|
||||||
|
el.addEventListener('change', function() { validateChoice(this); });
|
||||||
|
}
|
||||||
});
|
});
|
||||||
});
|
}, 50);
|
||||||
});
|
});
|
||||||
|
|
||||||
// Eliminar partidas
|
// Eliminar partidas
|
||||||
@@ -443,6 +554,7 @@
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
document.addEventListener('DOMContentLoaded', function() {
|
document.addEventListener('DOMContentLoaded', function() {
|
||||||
const transportistaSelect = document.getElementById('transportista_id');
|
const transportistaSelect = document.getElementById('transportista_id');
|
||||||
const choferSelect = document.getElementById('chofer_id');
|
const choferSelect = document.getElementById('chofer_id');
|
||||||
@@ -550,6 +662,147 @@
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
$(document).ready(function() {
|
||||||
|
let timeoutId;
|
||||||
|
let currentRequest = null;
|
||||||
|
|
||||||
|
// Función para configurar autocompletado en un input
|
||||||
|
function setupAutocomplete(input) {
|
||||||
|
const $input = $(input);
|
||||||
|
|
||||||
|
// Crear contenedor de sugerencias si no existe
|
||||||
|
if ($input.siblings('.autocomplete-suggestions').length === 0) {
|
||||||
|
$input.after('<div class="autocomplete-suggestions position-absolute w-100 bg-white border border-top-0 shadow-sm" style="max-height: 200px; overflow-y: auto; z-index: 1000; display: none;"></div>');
|
||||||
|
}
|
||||||
|
|
||||||
|
const $suggestions = $input.siblings('.autocomplete-suggestions');
|
||||||
|
const $row = $input.closest('tr');
|
||||||
|
|
||||||
|
$input.off('input').on('input', function() {
|
||||||
|
const query = $(this).val().trim();
|
||||||
|
|
||||||
|
clearTimeout(timeoutId);
|
||||||
|
if (currentRequest) currentRequest.abort();
|
||||||
|
|
||||||
|
if (query.length < 2) {
|
||||||
|
$suggestions.hide().empty();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
timeoutId = setTimeout(() => {
|
||||||
|
currentRequest = $.ajax({
|
||||||
|
url: '/IMPORTADORES/solicitud_importacion/buscar_productos',
|
||||||
|
method: 'GET',
|
||||||
|
data: { q: query },
|
||||||
|
dataType: 'json',
|
||||||
|
success: function(productos) {
|
||||||
|
$suggestions.empty();
|
||||||
|
|
||||||
|
if (productos && productos.length > 0) {
|
||||||
|
productos.forEach(function(producto) {
|
||||||
|
const $item = $(`
|
||||||
|
<div class="autocomplete-item px-3 py-2 cursor-pointer border-bottom">
|
||||||
|
<strong>${producto.sinonimo}</strong>
|
||||||
|
<div class="row mb-3 small text-muted">
|
||||||
|
<span>${producto.fraccion || 'Sin fracción'}</span>
|
||||||
|
<span>${producto.descripcion?.substring(0, 50) || ''}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
`).data('producto', producto);
|
||||||
|
|
||||||
|
$item.on('click', function() { selectProduct($(this).data('producto'), $row); });
|
||||||
|
$suggestions.append($item);
|
||||||
|
});
|
||||||
|
$suggestions.show();
|
||||||
|
}
|
||||||
|
else { $suggestions.hide(); }
|
||||||
|
},
|
||||||
|
error: function(xhr, status, error) {
|
||||||
|
if (status !== 'abort') { console.error("Error en búsqueda:", error); }
|
||||||
|
$suggestions.hide();
|
||||||
|
},
|
||||||
|
complete: function() { currentRequest = null; }
|
||||||
|
});
|
||||||
|
}, 300);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Manejar teclado
|
||||||
|
$input.on('keydown', function(e) {
|
||||||
|
const $items = $suggestions.find('.autocomplete-item');
|
||||||
|
const $active = $items.filter('.active');
|
||||||
|
switch(e.key) {
|
||||||
|
case 'ArrowDown':
|
||||||
|
e.preventDefault();
|
||||||
|
const $next = $active.length ? $active.next() : $items.first();
|
||||||
|
$items.removeClass('active');
|
||||||
|
$next.addClass('active');
|
||||||
|
break;
|
||||||
|
case 'ArrowUp':
|
||||||
|
e.preventDefault();
|
||||||
|
const $prev = $active.length ? $active.prev() : $items.last();
|
||||||
|
$items.removeClass('active');
|
||||||
|
$prev.addClass('active');
|
||||||
|
break;
|
||||||
|
case 'Enter':
|
||||||
|
if ($active.length) {
|
||||||
|
e.preventDefault();
|
||||||
|
selectProduct($active.data('producto'), $row);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case 'Tab':
|
||||||
|
if ($suggestions.is(':visible')) {
|
||||||
|
e.preventDefault();
|
||||||
|
const $first = $items.first();
|
||||||
|
if ($first.length) selectProduct($first.data('producto'), $row);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case 'Escape':
|
||||||
|
$suggestions.hide();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Ocultar al hacer clic fuera
|
||||||
|
$(document).on('click', function(e) { if (!$input.is(e.target) && !$suggestions.is(e.target) && !$suggestions.has(e.target).length) { $suggestions.hide(); } });
|
||||||
|
}
|
||||||
|
|
||||||
|
// Función para seleccionar un producto
|
||||||
|
function selectProduct(producto, $row) {
|
||||||
|
// Campos básicos
|
||||||
|
$row.find('input[name*="[descripcion]"]').val(producto.sinonimo);
|
||||||
|
// Tasa preferencial
|
||||||
|
if (producto.preferencia) {
|
||||||
|
const $select = $row.find('select[name*="[tasa_preferencial]"]');
|
||||||
|
if ($select[0] && $select[0]._choices) { $select[0]._choices.setChoiceByValue(producto.preferencia); }
|
||||||
|
else { $select.val(producto.preferencia).trigger('change'); }
|
||||||
|
}
|
||||||
|
// Unidad de medida
|
||||||
|
if (producto.umc_id) {
|
||||||
|
const $select = $row.find('select[name*="[unidad_comercial_id]"]');
|
||||||
|
setTimeout(() => {
|
||||||
|
if ($select[0] && $select[0]._choices) { $select[0]._choices.setChoiceByValue(producto.umc_id.toString()); }
|
||||||
|
else { $select.val(producto.umc_id).trigger('change'); }
|
||||||
|
}, 50);
|
||||||
|
}
|
||||||
|
// Número de parte
|
||||||
|
if (producto.numero_parte) { $row.find('input[name*="[oma_factura]"]').val(producto.numero_parte); }
|
||||||
|
// Incrementar frecuencia
|
||||||
|
$.ajax({
|
||||||
|
url: '/IMPORTADORES/solicitud_importacion/incrementar_frecuencia',
|
||||||
|
method: 'POST',
|
||||||
|
data: { producto_id: producto.id }
|
||||||
|
});
|
||||||
|
// Ocultar sugerencias
|
||||||
|
$row.find('.autocomplete-suggestions').hide();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Inicializar autocompletado en inputs existentes
|
||||||
|
$('.descripcion-input').each(function() { setupAutocomplete(this); });
|
||||||
|
|
||||||
|
// Configurar autocompletado para nuevas filas
|
||||||
|
$('#add-partida').on('click', function() { setTimeout(function() { $('.descripcion-input').last().each(function() { setupAutocomplete(this); }); }, 100); });
|
||||||
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
</body>
|
</body>
|
||||||
|
|||||||
@@ -80,6 +80,19 @@
|
|||||||
.form-group-animated:nth-child(9) { animation-delay: 0.9s; }
|
.form-group-animated:nth-child(9) { animation-delay: 0.9s; }
|
||||||
.form-group-animated:nth-child(10) { animation-delay: 1.0s; }
|
.form-group-animated:nth-child(10) { animation-delay: 1.0s; }
|
||||||
@keyframes slideUp { to { opacity: 1; transform: translateY(0); } }
|
@keyframes slideUp { to { opacity: 1; transform: translateY(0); } }
|
||||||
|
/* Estilos para las sugerencias de autocompletado */
|
||||||
|
.autocomplete-suggestions { position: absolute; width: calc(100% - 2px); /* Ajustar al ancho del input */ background: white; border: 1px solid #ced4da; border-top: none;
|
||||||
|
border-radius: 0 0 4px 4px; box-shadow: 0 4px 6px rgba(0,0,0,0.1); z-index: 1000; max-height: 200px; overflow-y: auto; display: none; /* Inicialmente oculto */ }
|
||||||
|
.autocomplete-item { padding: 8px 12px; cursor: pointer; transition: background-color 0.2s; }
|
||||||
|
.autocomplete-item:hover, .autocomplete-item.active { background-color: #f8f9fa; }
|
||||||
|
.autocomplete-item strong { display: block; margin-bottom: 2px; }
|
||||||
|
.autocomplete-item .text-muted { font-size: 0.85em; color: #6c757d; }
|
||||||
|
.cursor-pointer { cursor: pointer; }
|
||||||
|
/* Estilos para validación */
|
||||||
|
.choices.required .choices__inner { border: 1px solid #ced4da; }
|
||||||
|
.choices.is-invalid .choices__inner { border: 1px solid #dc3545; background-color: #fff5f5; }
|
||||||
|
.choices.is-valid .choices__inner { border: 1px solid #28a745; background-color: #f5fff5; }
|
||||||
|
.choices__input { opacity: 1 !important; }
|
||||||
</style>
|
</style>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
@@ -106,14 +119,14 @@
|
|||||||
|
|
||||||
<div class="col-md-3 mb-3">
|
<div class="col-md-3 mb-3">
|
||||||
<label for="proveedor_id" class="form-label">Proveedor</label>
|
<label for="proveedor_id" class="form-label">Proveedor</label>
|
||||||
<select id="proveedor_id" name="proveedor_clave" class="form-select searchable">
|
<select id="proveedor_id" name="proveedor_clave" class="form-select searchable" data-required="true" required>
|
||||||
<option value="">Cargando proveedores...</option>
|
<option value="">Cargando proveedores...</option>
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="col-md-3 mb-3">
|
<div class="col-md-3 mb-3">
|
||||||
<label for="anexo22_apendice" class="form-label">Anexo 22 – Apéndice</label>
|
<label for="anexo22_apendice" class="form-label">Anexo 22 – Apéndice</label>
|
||||||
<select id="anexo22_apendice" name="anexo22_apendice" class="form-select searchable" required>
|
<select id="anexo22_apendice" name="anexo22_apendice" class="form-select searchable" data-required="true" required>
|
||||||
<option value="">-- Selecciona Aduana --</option>
|
<option value="">-- Selecciona Aduana --</option>
|
||||||
<?php foreach($aduanas as $a): ?>
|
<?php foreach($aduanas as $a): ?>
|
||||||
<option value="<?= htmlspecialchars($a['aduana_seccion']) ?>" <?= $factura['anexo22_apendice']==$a['aduana_seccion']?'selected':'' ?>>
|
<option value="<?= htmlspecialchars($a['aduana_seccion']) ?>" <?= $factura['anexo22_apendice']==$a['aduana_seccion']?'selected':'' ?>>
|
||||||
@@ -128,7 +141,7 @@
|
|||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col-md-3 mb-3">
|
<div class="col-md-3 mb-3">
|
||||||
<label for="patente" class="form-label">Patente Aduanal</label>
|
<label for="patente" class="form-label">Patente Aduanal</label>
|
||||||
<select id="patente" name="patente" class="form-select searchable" required>
|
<select id="patente" name="patente" class="form-select searchable" data-required="true" required>
|
||||||
<option value="">-- Selecciona Patente --</option>
|
<option value="">-- Selecciona Patente --</option>
|
||||||
<?php foreach($patentes as $pt): ?>
|
<?php foreach($patentes as $pt): ?>
|
||||||
<option value="<?= htmlspecialchars($pt['id_agente']) ?>"
|
<option value="<?= htmlspecialchars($pt['id_agente']) ?>"
|
||||||
@@ -141,7 +154,7 @@
|
|||||||
|
|
||||||
<div class="col-md-3 mb-3">
|
<div class="col-md-3 mb-3">
|
||||||
<label for="incoterm" class="form-label">INCOTERM</label>
|
<label for="incoterm" class="form-label">INCOTERM</label>
|
||||||
<select id="incoterm" name="incoterm" class="form-select searchable" required>
|
<select id="incoterm" name="incoterm" class="form-select searchable" data-required="true" required>
|
||||||
<option value="">-- Selecciona Incoterm --</option>
|
<option value="">-- Selecciona Incoterm --</option>
|
||||||
<?php foreach($incoterms as $inc): ?>
|
<?php foreach($incoterms as $inc): ?>
|
||||||
<option value="<?= htmlspecialchars($inc['INCOTERM']) ?>" <?= $factura['incoterm']==$inc['INCOTERM']?'selected':'' ?>>
|
<option value="<?= htmlspecialchars($inc['INCOTERM']) ?>" <?= $factura['incoterm']==$inc['INCOTERM']?'selected':'' ?>>
|
||||||
@@ -153,7 +166,7 @@
|
|||||||
|
|
||||||
<div class="col-md-3 mb-3">
|
<div class="col-md-3 mb-3">
|
||||||
<label for="pais_proveedor" class="form-label">País (Proveedor)</label>
|
<label for="pais_proveedor" class="form-label">País (Proveedor)</label>
|
||||||
<select id="pais_proveedor" name="pais_proveedor" class="form-select searchable">
|
<select id="pais_proveedor" name="pais_proveedor" class="form-select searchable" data-required="true" required>
|
||||||
<option value="">-- Selecciona País --</option>
|
<option value="">-- Selecciona País --</option>
|
||||||
<?php foreach($paises as $p): ?>
|
<?php foreach($paises as $p): ?>
|
||||||
<option value="<?= htmlspecialchars($p['id_pais']) ?>" <?= $factura['pais_proveedor']==$p['id_pais']?'selected':'' ?>>
|
<option value="<?= htmlspecialchars($p['id_pais']) ?>" <?= $factura['pais_proveedor']==$p['id_pais']?'selected':'' ?>>
|
||||||
@@ -165,7 +178,7 @@
|
|||||||
|
|
||||||
<div class="col-md-3 mb-3">
|
<div class="col-md-3 mb-3">
|
||||||
<label for="tipo_moneda" class="form-label">Moneda</label>
|
<label for="tipo_moneda" class="form-label">Moneda</label>
|
||||||
<select id="tipo_moneda" name="tipo_moneda" class="form-select searchable">
|
<select id="tipo_moneda" name="tipo_moneda" class="form-select searchable" data-required="true" required>
|
||||||
<?php foreach(['MXN'=>'Peso Mexicano','USD'=>'Dólar USD','EUR'=>'Euro','CNY'=>'Yuan','GBP'=>'Libra GBP','JPY'=>'Yen'] as $code=>$label): ?>
|
<?php foreach(['MXN'=>'Peso Mexicano','USD'=>'Dólar USD','EUR'=>'Euro','CNY'=>'Yuan','GBP'=>'Libra GBP','JPY'=>'Yen'] as $code=>$label): ?>
|
||||||
<option value="<?= $code ?>" <?= $factura['tipo_moneda']==$code?'selected':'' ?>>
|
<option value="<?= $code ?>" <?= $factura['tipo_moneda']==$code?'selected':'' ?>>
|
||||||
<?= "$label ($code)" ?>
|
<?= "$label ($code)" ?>
|
||||||
@@ -185,7 +198,7 @@
|
|||||||
|
|
||||||
<div class="col-md-4 mb-3">
|
<div class="col-md-4 mb-3">
|
||||||
<label for="vinculacion" class="form-label">Vinculación</label>
|
<label for="vinculacion" class="form-label">Vinculación</label>
|
||||||
<select id="vinculacion" name="vinculacion" class="form-select searchable">
|
<select id="vinculacion" name="vinculacion" class="form-select searchable" data-required="true" required>
|
||||||
<option value="0" <?= $factura['vinculacion']==0?'selected':'' ?>>No existe</option>
|
<option value="0" <?= $factura['vinculacion']==0?'selected':'' ?>>No existe</option>
|
||||||
<option value="1" <?= $factura['vinculacion']==1?'selected':'' ?>>Existe, no afecta</option>
|
<option value="1" <?= $factura['vinculacion']==1?'selected':'' ?>>Existe, no afecta</option>
|
||||||
<option value="2" <?= $factura['vinculacion']==2?'selected':'' ?>>Existe y afecta</option>
|
<option value="2" <?= $factura['vinculacion']==2?'selected':'' ?>>Existe y afecta</option>
|
||||||
@@ -197,7 +210,7 @@
|
|||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col-md-4 mb-3">
|
<div class="col-md-4 mb-3">
|
||||||
<label for="transportista_id" class="form-label">Transportista</label>
|
<label for="transportista_id" class="form-label">Transportista</label>
|
||||||
<select id="transportista_id" name="transportista_id" class="form-select searchable" required>
|
<select id="transportista_id" name="transportista_id" class="form-select searchable" data-required="true" required>
|
||||||
<option value="">-- Selecciona --</option>
|
<option value="">-- Selecciona --</option>
|
||||||
<?php foreach($transportistas as $t): ?>
|
<?php foreach($transportistas as $t): ?>
|
||||||
<option value="<?= htmlspecialchars($t['id_transportista']) ?>" <?= $factura['transportista_id']==$t['id_transportista']?'selected':'' ?>>
|
<option value="<?= htmlspecialchars($t['id_transportista']) ?>" <?= $factura['transportista_id']==$t['id_transportista']?'selected':'' ?>>
|
||||||
@@ -209,7 +222,7 @@
|
|||||||
|
|
||||||
<div class="col-md-4 mb-3">
|
<div class="col-md-4 mb-3">
|
||||||
<label for="chofer_id" class="form-label">Chofer</label>
|
<label for="chofer_id" class="form-label">Chofer</label>
|
||||||
<select id="chofer_id" name="chofer_id" class="form-select searchable" required>
|
<select id="chofer_id" name="chofer_id" class="form-select searchable" data-required="true" required>
|
||||||
<option value="">-- Selecciona Chofer --</option>
|
<option value="">-- Selecciona Chofer --</option>
|
||||||
<?php foreach($choferes as $c): ?>
|
<?php foreach($choferes as $c): ?>
|
||||||
<option value="<?= htmlspecialchars($c['id_chofer']) ?>"
|
<option value="<?= htmlspecialchars($c['id_chofer']) ?>"
|
||||||
@@ -249,7 +262,14 @@
|
|||||||
<tbody>
|
<tbody>
|
||||||
<?php if (!empty($partidas)): foreach($partidas as $i=>$p): ?>
|
<?php if (!empty($partidas)): foreach($partidas as $i=>$p): ?>
|
||||||
<tr>
|
<tr>
|
||||||
<td><input name="partidas[<?= $i ?>][descripcion]" class="form-control w-auto" value="<?= htmlspecialchars($p['descripcion']) ?>"></td>
|
<td>
|
||||||
|
<div class="position-relative">
|
||||||
|
<input name="partidas[<?= $i ?>][descripcion]" class="form-control w-auto descripcion-input"
|
||||||
|
value="<?= htmlspecialchars($p['descripcion']) ?>" autocomplete="off">
|
||||||
|
<div class="autocomplete-suggestions position-absolute w-100 bg-white border border-top-0 shadow-sm"
|
||||||
|
style="max-height: 200px; overflow-y: auto; z-index: 1000; display: none;"></div>
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
<td><input name="partidas[<?= $i ?>][cantidad_comercial]" type="number" step="0.0001" min="0" class="form-control w-auto" value="<?= htmlspecialchars($p['cantidad_comercial']) ?>"></td>
|
<td><input name="partidas[<?= $i ?>][cantidad_comercial]" type="number" step="0.0001" min="0" class="form-control w-auto" value="<?= htmlspecialchars($p['cantidad_comercial']) ?>"></td>
|
||||||
<td><input name="partidas[<?= $i ?>][cantidad_tarifa]" type="number" step="0.0001" min="0" class="form-control w-auto" value="<?= htmlspecialchars($p['cantidad_tarifa']) ?>"></td>
|
<td><input name="partidas[<?= $i ?>][cantidad_tarifa]" type="number" step="0.0001" min="0" class="form-control w-auto" value="<?= htmlspecialchars($p['cantidad_tarifa']) ?>"></td>
|
||||||
<td>
|
<td>
|
||||||
@@ -278,11 +298,18 @@
|
|||||||
</tr>
|
</tr>
|
||||||
<?php endforeach; else: ?>
|
<?php endforeach; else: ?>
|
||||||
<tr>
|
<tr>
|
||||||
<td><input name="partidas[0][descripcion]" class="form-control w-auto"></td>
|
<td>
|
||||||
|
<div class="position-relative">
|
||||||
|
<input name="partidas[0][descripcion]" class="form-control w-auto descripcion-input"
|
||||||
|
value="<?= htmlspecialchars($p['descripcion']) ?>" autocomplete="off">
|
||||||
|
<div class="autocomplete-suggestions position-absolute w-100 bg-white border border-top-0 shadow-sm"
|
||||||
|
style="max-height: 200px; overflow-y: auto; z-index: 1000; display: none;"></div>
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
<td><input name="partidas[0][cantidad_comercial]" type="number" step="0.0001" class="form-control w-auto" min="0"></td>
|
<td><input name="partidas[0][cantidad_comercial]" type="number" step="0.0001" class="form-control w-auto" min="0"></td>
|
||||||
<td><input name="partidas[0][cantidad_tarifa]" type="number" step="0.0001" class="form-control w-auto" min="0"></td>
|
<td><input name="partidas[0][cantidad_tarifa]" type="number" step="0.0001" class="form-control w-auto" min="0"></td>
|
||||||
<td>
|
<td>
|
||||||
<select name="partidas[0][unidad_comercial_id]" class="form-select searchable">
|
<select name="partidas[0][unidad_comercial_id]" class="form-select searchable" data-required="true" required>
|
||||||
<option value="">-- Unidad --</option>
|
<option value="">-- Unidad --</option>
|
||||||
<?php foreach($unidades_medida as $um): ?>
|
<?php foreach($unidades_medida as $um): ?>
|
||||||
<option value="<?= htmlspecialchars($um['id']) ?>"><?= htmlspecialchars($um['descripcion']) ?></option>
|
<option value="<?= htmlspecialchars($um['id']) ?>"><?= htmlspecialchars($um['descripcion']) ?></option>
|
||||||
@@ -292,10 +319,13 @@
|
|||||||
<td><input name="partidas[0][valor_factura]" type="number" step="0.01" class="form-control valor-partida w-auto" min="0"></td>
|
<td><input name="partidas[0][valor_factura]" type="number" step="0.01" class="form-control valor-partida w-auto" min="0"></td>
|
||||||
<td><input name="partidas[0][peso_bruto]" type="number" step="0.0001" class="form-control w-auto" min="0"></td>
|
<td><input name="partidas[0][peso_bruto]" type="number" step="0.0001" class="form-control w-auto" min="0"></td>
|
||||||
<td>
|
<td>
|
||||||
<select name="partidas[0][tasa_preferencial]" class="form-select searchable">
|
<select name="partidas[0][tasa_preferencial]" class="form-select searchable" data-required="true" required>
|
||||||
<option value="">-- Selecciona --</option>
|
<option value="">-- Selecciona --</option>
|
||||||
<option>General</option><option>TLC</option><option>PROSEC</option>
|
<option>General</option>
|
||||||
<option>ALADI</option><option>COMERCIALIZADORA</option>
|
<option>TLC</option>
|
||||||
|
<option>PROSEC</option>
|
||||||
|
<option>ALADI</option>
|
||||||
|
<option>COMERCIALIZADORA</option>
|
||||||
</select>
|
</select>
|
||||||
</td>
|
</td>
|
||||||
<td class="hide"><input name="partidas[0][precio_unitario]" type="number" class="form-control w-auto"></td>
|
<td class="hide"><input name="partidas[0][precio_unitario]" type="number" class="form-control w-auto"></td>
|
||||||
@@ -316,6 +346,7 @@
|
|||||||
<label for="status" class="hide form-check-label">Activo</label>
|
<label for="status" class="hide form-check-label">Activo</label>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- Botones finales -->
|
||||||
<div class="text-end mt-4 form-group-animated">
|
<div class="text-end mt-4 form-group-animated">
|
||||||
<button type="submit" class="btn btn-success mt-auto w-auto btn-animated">Actualizar</button>
|
<button type="submit" class="btn btn-success mt-auto w-auto btn-animated">Actualizar</button>
|
||||||
<a href="/IMPORTADORES/solicitud_importacion/lista" class="btn btn-secondary ms-2 mt-auto w-auto btn-animated">Cancelar</a>
|
<a href="/IMPORTADORES/solicitud_importacion/lista" class="btn btn-secondary ms-2 mt-auto w-auto btn-animated">Cancelar</a>
|
||||||
@@ -330,6 +361,29 @@
|
|||||||
<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>
|
||||||
|
function safeInitializeChoices(element) {
|
||||||
|
if (!element._choices) {
|
||||||
|
const isRequired = element.hasAttribute('required');
|
||||||
|
if (isRequired) {
|
||||||
|
element.removeAttribute('required');
|
||||||
|
element.setAttribute('data-required', 'true');
|
||||||
|
}
|
||||||
|
|
||||||
|
element._choices = new Choices(element, {
|
||||||
|
searchEnabled: true,
|
||||||
|
itemSelectText: '',
|
||||||
|
shouldSort: false,
|
||||||
|
silent: true
|
||||||
|
});
|
||||||
|
|
||||||
|
if (isRequired) {
|
||||||
|
const container = element.closest('.choices');
|
||||||
|
if (container) { container.classList.add('required'); }
|
||||||
|
element.addEventListener('change', validateChoice.bind(null, element));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// ✅ 1. INICIALIZACIÓN PRINCIPAL
|
// ✅ 1. INICIALIZACIÓN PRINCIPAL
|
||||||
document.addEventListener('DOMContentLoaded', function() {
|
document.addEventListener('DOMContentLoaded', function() {
|
||||||
const proveedorActual = '<?= htmlspecialchars($proveedor_clave_actual ?? '') ?>';
|
const proveedorActual = '<?= htmlspecialchars($proveedor_clave_actual ?? '') ?>';
|
||||||
@@ -386,16 +440,23 @@
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// ✅ 3. AGREGAR PARTIDAS
|
// ✅ 3. AGREGAR PARTIDAS - VERSIÓN CORREGIDA
|
||||||
document.getElementById('add-partida').addEventListener('click', () => {
|
document.getElementById('add-partida').addEventListener('click', () => {
|
||||||
const tbody = document.querySelector('#tabla-partidas tbody');
|
const tbody = document.querySelector('#tabla-partidas tbody');
|
||||||
const idx = tbody.querySelectorAll('tr').length;
|
const rows = tbody.querySelectorAll('tr');
|
||||||
|
const idx = rows.length; // Esto asegura índices únicos y secuenciales
|
||||||
const row = document.createElement('tr');
|
const row = document.createElement('tr');
|
||||||
|
|
||||||
row.innerHTML = `
|
row.innerHTML = `
|
||||||
<td><input name="partidas[${idx}][descripcion]" class="form-control w-auto"></td>
|
<td>
|
||||||
<td><input name="partidas[${idx}][cantidad_comercial]" type="number" step="0.0001" class="form-control w-auto"></td>
|
<div class="position-relative">
|
||||||
<td><input name="partidas[${idx}][cantidad_tarifa]" type="number" step="0.0001" class="form-control w-auto"></td>
|
<input name="partidas[${idx}][descripcion]" class="form-control w-auto descripcion-input" required autocomplete="off">
|
||||||
|
<div class="autocomplete-suggestions position-absolute w-100 bg-white border border-top-0 shadow-sm"
|
||||||
|
style="max-height: 200px; overflow-y: auto; z-index: 1000; display: none;"></div>
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
<td><input name="partidas[${idx}][cantidad_comercial]" type="number" step="0.0001" class="form-control w-auto" min="0" required></td>
|
||||||
|
<td><input name="partidas[${idx}][cantidad_tarifa]" type="number" step="0.0001" class="form-control w-auto" min="0"></td>
|
||||||
<td>
|
<td>
|
||||||
<select name="partidas[${idx}][unidad_comercial_id]" class="form-select searchable">
|
<select name="partidas[${idx}][unidad_comercial_id]" class="form-select searchable">
|
||||||
<option value="">-- Unidad --</option>
|
<option value="">-- Unidad --</option>
|
||||||
@@ -404,8 +465,8 @@
|
|||||||
<?php endforeach; ?>
|
<?php endforeach; ?>
|
||||||
</select>
|
</select>
|
||||||
</td>
|
</td>
|
||||||
<td><input name="partidas[${idx}][valor_factura]" type="number" step="0.01" class="form-control valor-partida w-auto"></td>
|
<td><input name="partidas[${idx}][valor_factura]" type="number" step="0.01" class="form-control valor-partida w-auto" min="0"></td>
|
||||||
<td><input name="partidas[${idx}][peso_bruto]" type="number" step="0.0001" class="form-control w-auto"></td>
|
<td><input name="partidas[${idx}][peso_bruto]" type="number" step="0.0001" class="form-control w-auto" min="0"></td>
|
||||||
<td>
|
<td>
|
||||||
<select name="partidas[${idx}][tasa_preferencial]" class="form-select searchable">
|
<select name="partidas[${idx}][tasa_preferencial]" class="form-select searchable">
|
||||||
<option value="">-- Selecciona --</option>
|
<option value="">-- Selecciona --</option>
|
||||||
@@ -423,14 +484,22 @@
|
|||||||
|
|
||||||
tbody.appendChild(row);
|
tbody.appendChild(row);
|
||||||
|
|
||||||
// Inicializar Choices.js en los nuevos selects
|
// Inicializamos Choices en los nuevos selects
|
||||||
row.querySelectorAll('.searchable').forEach(el => {
|
setTimeout(() => {
|
||||||
new Choices(el, {
|
row.querySelectorAll('.searchable').forEach(el => {
|
||||||
searchEnabled: true,
|
if (!el._choices) {
|
||||||
itemSelectText: '',
|
el._choices = new Choices(el, {
|
||||||
shouldSort: false
|
searchEnabled: true,
|
||||||
|
itemSelectText: '',
|
||||||
|
shouldSort: false,
|
||||||
|
silent: true
|
||||||
|
});
|
||||||
|
}
|
||||||
});
|
});
|
||||||
});
|
|
||||||
|
// Configurar autocompletado para el nuevo campo de descripción
|
||||||
|
setupAutocomplete(row.querySelector('.descripcion-input'));
|
||||||
|
}, 50);
|
||||||
});
|
});
|
||||||
|
|
||||||
// ✅ 4. REMOVER PARTIDAS
|
// ✅ 4. REMOVER PARTIDAS
|
||||||
@@ -463,18 +532,12 @@
|
|||||||
$(document).ready(function() {
|
$(document).ready(function() {
|
||||||
$('#solicitudForm').submit(function(e){
|
$('#solicitudForm').submit(function(e){
|
||||||
const total = parseFloat($('#valor_factura').val()) || 0;
|
const total = parseFloat($('#valor_factura').val()) || 0;
|
||||||
let sum = 0;
|
let sum = 0;
|
||||||
$('.valor-partida').each(function(){
|
$('.valor-partida').each(function(){ sum += parseFloat($(this).val()) || 0; });
|
||||||
sum += parseFloat($(this).val()) || 0;
|
|
||||||
});
|
|
||||||
|
|
||||||
if(Math.abs(sum - total) > 0.001){
|
if(Math.abs(sum - total) > 0.001){
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
Swal.fire({
|
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)}).` });
|
||||||
icon: 'error',
|
|
||||||
title: 'Error de validación',
|
|
||||||
text: `La suma de partidas (${sum.toFixed(2)}) no coincide con Valor Factura (${total.toFixed(2)}).`
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@@ -523,11 +586,8 @@
|
|||||||
// Función para filtrar choferes por transportista
|
// Función para filtrar choferes por transportista
|
||||||
function filtrarChoferes(transportistaId) {
|
function filtrarChoferes(transportistaId) {
|
||||||
// Limpiar opciones excepto la primera
|
// Limpiar opciones excepto la primera
|
||||||
if (choferChoices) {
|
if (choferChoices) { choferChoices.clearStore(); }
|
||||||
choferChoices.clearStore();
|
else { choferSelect.innerHTML = '<option value="">-- Selecciona Chofer --</option>'; }
|
||||||
} else {
|
|
||||||
choferSelect.innerHTML = '<option value="">-- Selecciona Chofer --</option>';
|
|
||||||
}
|
|
||||||
|
|
||||||
if (transportistaId) {
|
if (transportistaId) {
|
||||||
// Intentar usar datos locales primero (más rápido)
|
// Intentar usar datos locales primero (más rápido)
|
||||||
@@ -542,12 +602,7 @@
|
|||||||
fetch(`/IMPORTADORES/solicitud_importacion/obtenerChoferesPorTransportista?transportista_id=${transportistaId}`)
|
fetch(`/IMPORTADORES/solicitud_importacion/obtenerChoferesPorTransportista?transportista_id=${transportistaId}`)
|
||||||
.then(response => {
|
.then(response => {
|
||||||
console.log('Response status:', response.status);
|
console.log('Response status:', response.status);
|
||||||
|
if (!response.ok) { return response.json().then(data => { throw new Error(data.error || `Error ${response.status}`); }); }
|
||||||
if (!response.ok) {
|
|
||||||
return response.json().then(data => {
|
|
||||||
throw new Error(data.error || `Error ${response.status}`);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
return response.json();
|
return response.json();
|
||||||
})
|
})
|
||||||
.then(choferes => {
|
.then(choferes => {
|
||||||
@@ -561,7 +616,8 @@
|
|||||||
mostrarError();
|
mostrarError();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
} else {
|
}
|
||||||
|
else {
|
||||||
// Si no hay transportista seleccionado, mostrar mensaje
|
// Si no hay transportista seleccionado, mostrar mensaje
|
||||||
mostrarSeleccionarTransportista();
|
mostrarSeleccionarTransportista();
|
||||||
}
|
}
|
||||||
@@ -576,9 +632,8 @@
|
|||||||
label: '-- Cargando choferes... --',
|
label: '-- Cargando choferes... --',
|
||||||
disabled: true
|
disabled: true
|
||||||
}], 'value', 'label', true);
|
}], 'value', 'label', true);
|
||||||
} else {
|
|
||||||
choferSelect.innerHTML = '<option value="">-- Cargando choferes... --</option>';
|
|
||||||
}
|
}
|
||||||
|
else { choferSelect.innerHTML = '<option value="">-- Cargando choferes... --</option>'; }
|
||||||
}
|
}
|
||||||
|
|
||||||
// Función para mostrar mensaje de seleccionar transportista
|
// Función para mostrar mensaje de seleccionar transportista
|
||||||
@@ -590,9 +645,8 @@
|
|||||||
label: '-- Selecciona Transportista primero --',
|
label: '-- Selecciona Transportista primero --',
|
||||||
disabled: true
|
disabled: true
|
||||||
}], 'value', 'label', true);
|
}], 'value', 'label', true);
|
||||||
} else {
|
|
||||||
choferSelect.innerHTML = '<option value="">-- Selecciona Transportista primero --</option>';
|
|
||||||
}
|
}
|
||||||
|
else { choferSelect.innerHTML = '<option value="">-- Selecciona Transportista primero --</option>'; }
|
||||||
}
|
}
|
||||||
|
|
||||||
// Función para mostrar error
|
// Función para mostrar error
|
||||||
@@ -605,9 +659,8 @@
|
|||||||
label: errorMessage,
|
label: errorMessage,
|
||||||
disabled: true
|
disabled: true
|
||||||
}], 'value', 'label', true);
|
}], 'value', 'label', true);
|
||||||
} else {
|
|
||||||
choferSelect.innerHTML = `<option value="">${errorMessage}</option>`;
|
|
||||||
}
|
}
|
||||||
|
else { choferSelect.innerHTML = `<option value="">${errorMessage}</option>`; }
|
||||||
}
|
}
|
||||||
|
|
||||||
// Función para cargar choferes en el select
|
// Función para cargar choferes en el select
|
||||||
@@ -645,18 +698,13 @@
|
|||||||
});
|
});
|
||||||
|
|
||||||
// Restaurar selección actual si es válida
|
// Restaurar selección actual si es válida
|
||||||
if (choferActual && choferes.some(c => c.id_chofer == choferActual)) {
|
if (choferActual && choferes.some(c => c.id_chofer == choferActual)) { choferSelect.value = choferActual; }
|
||||||
choferSelect.value = choferActual;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Inicializar el filtro al cargar la página (para modo edición)
|
// Inicializar el filtro al cargar la página (para modo edición)
|
||||||
if (transportistaActual) {
|
if (transportistaActual) { filtrarChoferes(transportistaActual); }
|
||||||
filtrarChoferes(transportistaActual);
|
else { mostrarSeleccionarTransportista(); }
|
||||||
} else {
|
|
||||||
mostrarSeleccionarTransportista();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Event listener para cambio de transportista
|
// Event listener para cambio de transportista
|
||||||
transportistaSelect.addEventListener('change', function() {
|
transportistaSelect.addEventListener('change', function() {
|
||||||
@@ -670,12 +718,145 @@
|
|||||||
if (choferSeleccionado && !transportistaSelect.value) {
|
if (choferSeleccionado && !transportistaSelect.value) {
|
||||||
alert('Por favor, selecciona un transportista primero.');
|
alert('Por favor, selecciona un transportista primero.');
|
||||||
this.value = '';
|
this.value = '';
|
||||||
if (choferChoices) {
|
if (choferChoices) { choferChoices.setChoiceByValue(''); }
|
||||||
choferChoices.setChoiceByValue('');
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
$(document).ready(function() {
|
||||||
|
let timeoutId;
|
||||||
|
let currentRequest = null;
|
||||||
|
|
||||||
|
// Función para configurar autocompletado
|
||||||
|
function setupAutocomplete(input) {
|
||||||
|
const $input = $(input);
|
||||||
|
|
||||||
|
// Crear contenedor de sugerencias si no existe
|
||||||
|
if ($input.siblings('.autocomplete-suggestions').length === 0) { $input.after('<div class="autocomplete-suggestions position-absolute w-100 bg-white border border-top-0 shadow-sm" style="max-height: 200px; overflow-y: auto; z-index: 1000; display: none;"></div>'); }
|
||||||
|
|
||||||
|
const $suggestions = $input.siblings('.autocomplete-suggestions');
|
||||||
|
const $row = $input.closest('tr');
|
||||||
|
|
||||||
|
$input.on('input', function() {
|
||||||
|
const query = $(this).val().trim();
|
||||||
|
|
||||||
|
clearTimeout(timeoutId);
|
||||||
|
if (currentRequest) currentRequest.abort();
|
||||||
|
if (query.length < 2) {
|
||||||
|
$suggestions.hide().empty();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
timeoutId = setTimeout(() => {
|
||||||
|
currentRequest = $.ajax({
|
||||||
|
url: '/IMPORTADORES/solicitud_importacion/buscar_productos',
|
||||||
|
method: 'GET',
|
||||||
|
data: { q: query },
|
||||||
|
dataType: 'json',
|
||||||
|
success: function(productos) {
|
||||||
|
$suggestions.empty();
|
||||||
|
|
||||||
|
if (productos && productos.length > 0) {
|
||||||
|
productos.forEach(function(producto) {
|
||||||
|
const $item = $(`
|
||||||
|
<div class="autocomplete-item px-3 py-2 cursor-pointer border-bottom">
|
||||||
|
<strong>${producto.sinonimo}</strong>
|
||||||
|
<div class="row mb-3 small text-muted">
|
||||||
|
<span>${producto.fraccion || 'Sin fracción'}</span>
|
||||||
|
<span>${producto.descripcion?.substring(0, 50) || ''}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
`).data('producto', producto);
|
||||||
|
$item.on('click', function() { selectProduct($(this).data('producto'), $row); });
|
||||||
|
$suggestions.append($item);
|
||||||
|
});
|
||||||
|
$suggestions.show();
|
||||||
|
}
|
||||||
|
else { $suggestions.hide(); }
|
||||||
|
},
|
||||||
|
error: function(xhr, status, error) {
|
||||||
|
if (status !== 'abort') { console.error("Error en búsqueda:", error); }
|
||||||
|
$suggestions.hide();
|
||||||
|
},
|
||||||
|
complete: function() { currentRequest = null; }
|
||||||
|
});
|
||||||
|
}, 300);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Manejar teclado
|
||||||
|
$input.on('keydown', function(e) {
|
||||||
|
const $items = $suggestions.find('.autocomplete-item');
|
||||||
|
const $active = $items.filter('.active');
|
||||||
|
switch(e.key) {
|
||||||
|
case 'ArrowDown':
|
||||||
|
e.preventDefault();
|
||||||
|
const $next = $active.length ? $active.next() : $items.first();
|
||||||
|
$items.removeClass('active');
|
||||||
|
$next.addClass('active');
|
||||||
|
break;
|
||||||
|
case 'ArrowUp':
|
||||||
|
e.preventDefault();
|
||||||
|
const $prev = $active.length ? $active.prev() : $items.last();
|
||||||
|
$items.removeClass('active');
|
||||||
|
$prev.addClass('active');
|
||||||
|
break;
|
||||||
|
case 'Enter':
|
||||||
|
if ($active.length) {
|
||||||
|
e.preventDefault();
|
||||||
|
selectProduct($active.data('producto'), $row);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case 'Tab':
|
||||||
|
if ($suggestions.is(':visible')) {
|
||||||
|
e.preventDefault();
|
||||||
|
const $first = $items.first();
|
||||||
|
if ($first.length) selectProduct($first.data('producto'), $row);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case 'Escape':
|
||||||
|
$suggestions.hide();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Ocultar al hacer clic fuera
|
||||||
|
$(document).on('click', function(e) { if (!$input.is(e.target) && !$suggestions.is(e.target) && !$suggestions.has(e.target).length) { $suggestions.hide(); } });
|
||||||
|
}
|
||||||
|
|
||||||
|
// Función para seleccionar un producto
|
||||||
|
function selectProduct(producto, $row) {
|
||||||
|
// Campos básicos
|
||||||
|
$row.find('input[name*="[descripcion]"]').val(producto.sinonimo);
|
||||||
|
// Tasa preferencial
|
||||||
|
if (producto.preferencia) {
|
||||||
|
const $select = $row.find('select[name*="[tasa_preferencial]"]');
|
||||||
|
if ($select[0] && $select[0]._choices) { $select[0]._choices.setChoiceByValue(producto.preferencia); }
|
||||||
|
else { $select.val(producto.preferencia).trigger('change'); }
|
||||||
|
}
|
||||||
|
// Unidad de medida
|
||||||
|
if (producto.umc_id) {
|
||||||
|
const $select = $row.find('select[name*="[unidad_comercial_id]"]');
|
||||||
|
setTimeout(() => {
|
||||||
|
if ($select[0] && $select[0]._choices) { $select[0]._choices.setChoiceByValue(producto.umc_id.toString()); }
|
||||||
|
else { $select.val(producto.umc_id).trigger('change'); }
|
||||||
|
}, 50);
|
||||||
|
}
|
||||||
|
// Número de parte
|
||||||
|
if (producto.numero_parte) { $row.find('input[name*="[oma_factura]"]').val(producto.numero_parte); }
|
||||||
|
// Incrementar frecuencia
|
||||||
|
$.ajax({
|
||||||
|
url: '/IMPORTADORES/solicitud_importacion/incrementar_frecuencia',
|
||||||
|
method: 'POST',
|
||||||
|
data: { producto_id: producto.id }
|
||||||
|
});
|
||||||
|
// Ocultar sugerencias
|
||||||
|
$row.find('.autocomplete-suggestions').hide();
|
||||||
|
}
|
||||||
|
// Inicializar autocompletado en inputs existentes
|
||||||
|
$('.descripcion-input').each(function() { setupAutocomplete(this); });
|
||||||
|
// Configurar autocompletado para nuevas filas
|
||||||
|
$('#add-partida').on('click', function() { setTimeout(function() { $('.descripcion-input').last().each(function() { setupAutocomplete(this); }); }, 100); });
|
||||||
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
</body>
|
</body>
|
||||||
|
|||||||
Reference in New Issue
Block a user