From 98f0c5802777ab22cea07c462bcb01995b2ca4dc Mon Sep 17 00:00:00 2001 From: Flatlogic Bot Date: Thu, 12 Feb 2026 05:13:55 +0000 Subject: [PATCH] Autosave: 20260212-051355 --- db/config.php | 3 +- imprimir_etiquetas.php | 8 +- productos.php | 6 +- registrar_salida_api.php | 92 +++++++++++++++ registro_salida.php | 241 +++++++++++++++++++++++---------------- 5 files changed, 245 insertions(+), 105 deletions(-) create mode 100644 registrar_salida_api.php diff --git a/db/config.php b/db/config.php index 545c0d7..10ce8c2 100644 --- a/db/config.php +++ b/db/config.php @@ -14,8 +14,7 @@ if (!function_exists('db')) { PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION, PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC, ]); - $pdo->exec("SET time_zone = '-05:00'"); } return $pdo; } -} +} \ No newline at end of file diff --git a/imprimir_etiquetas.php b/imprimir_etiquetas.php index 57f0bc8..4719eb8 100644 --- a/imprimir_etiquetas.php +++ b/imprimir_etiquetas.php @@ -15,7 +15,7 @@ if (empty($product_ids)) { // Sanitize input $placeholders = implode(',', array_fill(0, count($product_ids), '?')); -$stmt = db()->prepare("SELECT id, nombre FROM products WHERE id IN ($placeholders)"); +$stmt = db()->prepare("SELECT id, nombre, sku FROM products WHERE id IN ($placeholders)"); foreach ($product_ids as $k => $id) { $stmt->bindValue(($k + 1), $id, PDO::PARAM_INT); } @@ -77,7 +77,11 @@ $products = $stmt->fetchAll(PDO::FETCH_ASSOC);
- Barcode for product ID <?php echo $product['id']; ?> + + Barcode for product SKU <?php echo htmlspecialchars($product['sku']); ?> + +

Sin SKU

+
diff --git a/productos.php b/productos.php index a7eaaba..3a0cfa4 100644 --- a/productos.php +++ b/productos.php @@ -69,8 +69,10 @@ $products = $stmt->fetchAll(PDO::FETCH_ASSOC); - - Barcode for product ID <?php echo $product['id']; ?> + + Barcode for product SKU <?php echo htmlspecialchars($product['sku']); ?> + + Sin SKU diff --git a/registrar_salida_api.php b/registrar_salida_api.php new file mode 100644 index 0000000..2cf1271 --- /dev/null +++ b/registrar_salida_api.php @@ -0,0 +1,92 @@ +beginTransaction(); + + $stmt = $pdo->prepare("SELECT * FROM stock_sedes WHERE product_id = :product_id AND sede_id = :sede_id FOR UPDATE"); + $stmt->execute(['product_id' => $product_id, 'sede_id' => $sede_id]); + $existing_stock = $stmt->fetch(); + + if ($existing_stock) { + $new_quantity = $existing_stock['quantity'] - $quantity; + if ($new_quantity < 0) { + $error = "No hay stock para registrar la salida. Stock actual: " . $existing_stock['quantity']; + $pdo->rollBack(); + } else { + $update_stmt = $pdo->prepare("UPDATE stock_sedes SET quantity = :quantity WHERE id = :id"); + $update_stmt->execute(['quantity' => $new_quantity, 'id' => $existing_stock['id']]); + + $history_stmt = $pdo->prepare( + "INSERT INTO stock_movements (product_id, sede_id, quantity, type, movement_date) + VALUES (:product_id, :sede_id, :quantity, 'salida', :movement_date)" + ); + $history_stmt->execute([ + 'product_id' => $product_id, + 'sede_id' => $sede_id, + 'quantity' => $quantity, + 'movement_date' => $movement_date + ]); + + $pdo->commit(); + + $stmt_prod_name = $pdo->prepare("SELECT nombre FROM products WHERE id = :id"); + $stmt_prod_name->execute(['id' => $product_id]); + $product_name = $stmt_prod_name->fetchColumn(); + + $message = "Salida de 1 unidad de '{$product_name}' registrada. Stock restante: {$new_quantity}."; + } + } else { + $error = "No hay stock registrado para este producto en la sede seleccionada."; + $pdo->rollBack(); + } + + } catch (PDOException $e) { + if (isset($pdo) && $pdo->inTransaction()) { + $pdo->rollBack(); + } + // No exponer detalles del error de la DB al cliente + $error = "Error al actualizar el inventario. Verifique la conexión o los datos."; + // Loguear el error real para depuración + error_log("PDOException en registrar_salida_api.php: " . $e->getMessage()); + } + } else { + $error = "Faltan datos para registrar la salida (producto, sede o fecha)."; + } +} else { + $error = "Método de solicitud no válido."; +} + +if ($error) { + $response['success'] = false; + $response['message'] = $error; +} else { + $response['success'] = true; + $response['message'] = $message; +} + +echo json_encode($response); +exit; +?> diff --git a/registro_salida.php b/registro_salida.php index 83ff44c..e34dc7d 100644 --- a/registro_salida.php +++ b/registro_salida.php @@ -3,75 +3,7 @@ $pageTitle = "Registro de Salida de Producto"; require_once 'layout_header.php'; require_once 'db/config.php'; -$message = ''; -$error = ''; - -// Esta sección ahora solo se usará para las respuestas AJAX del escáner -if ($_SERVER["REQUEST_METHOD"] == "POST" && !empty($_SERVER['HTTP_X_REQUESTED_WITH'])) { - $product_id = filter_input(INPUT_POST, 'product_id', FILTER_VALIDATE_INT); - $sede_id = filter_input(INPUT_POST, 'sede_id', FILTER_VALIDATE_INT); - $movement_date = filter_input(INPUT_POST, 'movement_date'); - $quantity = 1; // Siempre es 1 al escanear - - if ($product_id && $sede_id && $movement_date) { - try { - $pdo = db(); - $pdo->beginTransaction(); - - $stmt = $pdo->prepare("SELECT * FROM stock_sedes WHERE product_id = :product_id AND sede_id = :sede_id FOR UPDATE"); - $stmt->execute(['product_id' => $product_id, 'sede_id' => $sede_id]); - $existing_stock = $stmt->fetch(); - - if ($existing_stock) { - $new_quantity = $existing_stock['quantity'] - $quantity; - if ($new_quantity < 0) { - $error = "No hay stock para registrar la salida. Stock actual: " . $existing_stock['quantity']; - $pdo->rollBack(); - } else { - $update_stmt = $pdo->prepare("UPDATE stock_sedes SET quantity = :quantity WHERE id = :id"); - $update_stmt->execute(['quantity' => $new_quantity, 'id' => $existing_stock['id']]); - - $history_stmt = $pdo->prepare( - "INSERT INTO stock_movements (product_id, sede_id, quantity, type, movement_date) - VALUES (:product_id, :sede_id, :quantity, 'salida', :movement_date)" - ); - $history_stmt->execute([ - 'product_id' => $product_id, - 'sede_id' => $sede_id, - 'quantity' => $quantity, - 'movement_date' => $movement_date - ]); - - $pdo->commit(); - $stmt_prod_name = $pdo->prepare("SELECT nombre FROM products WHERE id = :id"); - $stmt_prod_name->execute(['id' => $product_id]); - $product_name = $stmt_prod_name->fetchColumn(); - $message = "Salida de 1 unidad de '{$product_name}' registrada. Stock restante: {$new_quantity}."; - } - } else { - $error = "No hay stock registrado para este producto en la sede seleccionada."; - $pdo->rollBack(); - } - - } catch (PDOException $e) { - if ($pdo && $pdo->inTransaction()) { - $pdo->rollBack(); - } - $error = "Error al actualizar el inventario: " . $e->getMessage(); - } - } else { - $error = "Faltan datos para registrar la salida (producto, sede o fecha)."; - } - - // Devolvemos JSON y terminamos la ejecución - header('Content-Type: application/json'); - if ($error) { - echo json_encode(['success' => false, 'message' => $error]); - } else { - echo json_encode(['success' => true, 'message' => $message]); - } - exit; -} +$error_page_load = ''; // Obtener sedes para el dropdown $sedes = []; @@ -119,7 +51,12 @@ try {
- +
+ + +
@@ -179,11 +116,9 @@ document.addEventListener('DOMContentLoaded', (event) => { } } } - // El usuario debe interactuar con la página para iniciar el audio document.body.addEventListener('click', initAudioContext, { once: true }); document.body.addEventListener('keydown', initAudioContext, { once: true }); - // --- Funciones de ayuda --- function showNotification(message, isSuccess) { const container = document.getElementById('notification-container'); @@ -232,7 +167,7 @@ document.addEventListener('DOMContentLoaded', (event) => { } processing = true; - this.disabled = true; // Bloquear input mientras se procesa + this.disabled = true; const sede_id = sedeSelect.value; const movement_date = dateInput.value; @@ -247,7 +182,6 @@ document.addEventListener('DOMContentLoaded', (event) => { return; } - // Añadir fila a la tabla const newRow = tableBody.insertRow(0); const cellProduct = newRow.insertCell(0); const cellSku = newRow.insertCell(1); @@ -263,32 +197,15 @@ document.addEventListener('DOMContentLoaded', (event) => { if (data.success && data.product) { cellProduct.textContent = data.product.nombre; cellSku.textContent = data.product.sku; - - const formData = new FormData(); - formData.append('product_id', data.product.id); - formData.append('sede_id', sede_id); - formData.append('movement_date', movement_date); - - return fetch('registro_salida.php', { - method: 'POST', - headers: { 'X-Requested-With': 'XMLHttpRequest' }, - body: formData - }); + cellStatus.innerHTML = ` + + + `; + playBeep(true); } else { throw new Error(data.message || 'Producto no encontrado.'); } }) - .then(response => response.json()) - .then(result => { - showNotification(result.message, result.success); - if (result.success) { - playBeep(true); - cellStatus.innerHTML = 'Registrado'; - } else { - playBeep(false); - cellStatus.innerHTML = `Error`; - } - }) .catch(error => { console.error('Error en el proceso de escaneo:', error); showNotification(error.message, false); @@ -297,7 +214,6 @@ document.addEventListener('DOMContentLoaded', (event) => { cellStatus.innerHTML = `Fallo`; }) .finally(() => { - // Limpiar y re-enfocar el input para el siguiente escaneo this.value = ''; this.disabled = false; processing = false; @@ -305,15 +221,142 @@ document.addEventListener('DOMContentLoaded', (event) => { }); }); + // --- Lógica para Aceptar o Cancelar la salida --- + tableBody.addEventListener('click', function(event) { + const button = event.target; + + // --- Botón Cancelar --- + if (button.classList.contains('cancelar-salida-btn')) { + button.closest('tr').remove(); + return; + } + + // --- Botón Aceptar --- + if (button.classList.contains('aceptar-salida-btn')) { + const productId = button.dataset.productId; + const sedeId = sedeSelect.value; + const movementDate = dateInput.value; + + if (!productId || !sedeId || !movementDate) { + showNotification('Error interno: Faltan datos para registrar la salida.', false); + return; + } + + // Deshabilitar ambos botones para evitar doble clic + button.parentElement.querySelectorAll('button').forEach(btn => btn.disabled = true); + button.innerHTML = ' Registrando...'; + + const formData = new FormData(); + formData.append('product_id', productId); + formData.append('sede_id', sedeId); + formData.append('movement_date', movementDate); + + fetch('registrar_salida_api.php', { + method: 'POST', + headers: { + 'X-Requested-With': 'XMLHttpRequest' + }, + body: formData + }) + .then(response => response.json()) + .then(data => { + if (data.success) { + showNotification(data.message, true); + playBeep(true); + button.parentElement.innerHTML = 'Salida Registrada'; + } else { + throw new Error(data.message || 'Error al registrar la salida.'); + } + }) + .catch(error => { + console.error('Error en el registro:', error); + showNotification(error.message, false); + playBeep(false); + // Reactivar botones en caso de error + const originalButtons = ` + + + `; + button.parentElement.innerHTML = originalButtons; + }); + } + }); + // Asegurarse de que el campo de texto siempre tenga el foco barcodeInput.focus(); document.body.addEventListener('click', (e) => { - // Si el clic no es en un input o select, re-enfocar el campo de escaneo - if (!['INPUT', 'SELECT', 'BUTTON'].includes(e.target.tagName)) { + if (!['INPUT', 'SELECT', 'BUTTON', 'A'].includes(e.target.tagName) && !e.target.closest('button')) { barcodeInput.focus(); } }); }); + + + + + + \ No newline at end of file