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']; ?>](https://barcode.tec-it.com/barcode.ashx?data=<?php echo $product['id']; ?>&code=Code128)
+
+
![Barcode for product SKU <?php echo htmlspecialchars($product['sku']); ?>](https://barcode.tec-it.com/barcode.ashx?data=<?php echo urlencode($product['sku']); ?>&code=Code128)
+
+
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);
|
|
-
-
+
+
+
+ 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