Autosave: 20260212-193734

This commit is contained in:
Flatlogic Bot 2026-02-12 19:37:34 +00:00
parent 304de737e6
commit 54f691879e
41 changed files with 361 additions and 249 deletions

View File

@ -86,6 +86,7 @@ body {
@media (max-width: 992px) {
.sidebar {
transform: translateX(-100%);
z-index: 1020; /* Ensure sidebar is on top */
}
.sidebar.active {
@ -95,6 +96,7 @@ body {
.content {
margin-left: 0;
width: 100%;
transition: filter 0.3s ease; /* Smooth transition for the filter */
}
.sidebar-toggle {
@ -102,18 +104,21 @@ body {
}
body.sidebar-active .content {
margin-left: 260px;
/* Don't push content, but apply a visual effect */
filter: blur(3px) brightness(0.6);
pointer-events: none; /* Prevent interaction with content when sidebar is open */
}
.sidebar .nav {
display: flex;
overflow-x: auto;
white-space: nowrap;
-webkit-overflow-scrolling: touch; /* Smooth scrolling on iOS */
}
.sidebar .nav-item {
flex-basis: auto; /* Allow items to take their natural width */
/* The body itself gets an overlay to darken it */
body.sidebar-active::before {
content: '';
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: rgba(0,0,0,0.4);
z-index: 1010; /* Below sidebar, above content */
}
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 42 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 33 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 38 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 91 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 99 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 80 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 52 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 75 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 56 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 54 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 55 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 93 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 56 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 76 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 36 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 315 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 134 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 131 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 148 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 248 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 144 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 165 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 93 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 247 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 180 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 214 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 164 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 39 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 56 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 56 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 527 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 107 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 147 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 99 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 55 KiB

View File

@ -117,11 +117,22 @@ if (!empty($generated_codes)) {
<div class="card-header d-flex justify-content-between align-items-center">
<h3 class="card-title mb-0">Códigos Generados</h3>
<form action="imprimir_etiquetas.php" method="POST" target="_blank" class="m-0">
<?php
// Get product name to pass to the export script
$product_name = '';
foreach ($products as $p) {
if ($p['id'] == $producto_id) {
$product_name = $p['nombre'];
break;
}
}
?>
<input type="hidden" name="product_name" value="<?php echo htmlspecialchars($product_name); ?>">
<?php foreach ($generated_codes as $code): ?>
<input type="hidden" name="codes[]" value="<?php echo htmlspecialchars($code); ?>">
<?php endforeach; ?>
<button type="submit" class="btn btn-success">
<i class="fas fa-print"></i> Imprimir Etiquetas
<i class="fas fa-file-excel"></i> Exportar a Excel
</button>
</form>
</div>

View File

@ -1,99 +1,38 @@
<?php
// Receive data from the form
$codes = $_POST['codes'] ?? [];
$product_name = $_POST['product_name'] ?? 'Producto Desconocido';
if (empty($codes)) {
// For direct access, you can add a fallback or a message.
// For now, we'll just show a blank page if no codes are provided.
die("No se proporcionaron códigos para exportar.");
}
// 1. Set HTTP headers to trigger file download
$filename = "etiquetas_" . str_replace(' ', '_', $product_name) . "_" . date("Y-m-d") . ".csv";
header('Content-Type: text/csv; charset=utf-8');
header('Content-Disposition: attachment; filename="' . $filename . '"');
?>
<!DOCTYPE html>
<html lang="es">
<head>
<meta charset="UTF-8">
<title>Imprimir Etiquetas</title>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/css/bootstrap.min.css" rel="stylesheet">
<style>
@media print {
body {
margin: 0;
padding: 0;
}
.no-print {
display: none !important;
}
.label {
page-break-inside: avoid;
}
}
body {
font-family: sans-serif;
background-color: #f8f9fa;
}
.label-grid {
display: grid;
grid-template-columns: 1fr 1fr; /* Two columns */
gap: 0.5rem;
padding: 0.5rem;
}
.label {
border: 1px dashed #ccc;
padding: 0.5rem;
text-align: center;
background-color: white;
}
.barcode-svg svg {
max-width: 100%;
height: 50px;
display: block;
margin: 0 auto;
}
.code-text {
font-size: 0.7rem;
word-break: break-all;
margin-top: 0.25rem;
}
.print-button {
position: fixed;
top: 20px;
right: 20px;
z-index: 1000;
}
</style>
</head>
<body>
// 2. Create a file pointer connected to the output stream
$output = fopen('php://output', 'w');
<div class="container-fluid">
<div class="my-3 text-center no-print">
<h1 class="h3">Vista Previa de Impresión</h1>
<p>Ajusta el tamaño y la escala en el diálogo de impresión de tu navegador si es necesario.</p>
<button onclick="window.print();" class="btn btn-primary">
<i class="fas fa-print"></i> Imprimir Ahora
</button>
</div>
// 3. Add a UTF-8 BOM to ensure Excel opens it correctly
fprintf($output, chr(0xEF).chr(0xBB).chr(0xBF));
<?php if (!empty($codes)): ?>
<div class="label-grid">
<?php foreach ($codes as $code): ?>
<div class="label">
<div class="barcode-svg">
<img src="https://barcode.tec-it.com/barcode.ashx?data=<?php echo urlencode($code); ?>&code=Code128" alt="Barcode for <?php echo htmlspecialchars($code); ?>" style="max-height: 50px;">
</div>
<div class="code-text">
<code><?php echo htmlspecialchars($code); ?></code>
</div>
</div>
<?php endforeach; ?>
</div>
<?php else: ?>
<div class="alert alert-warning text-center no-print">
No se han proporcionado códigos. Por favor, <a href="generar_etiquetas.php">vuelve al generador</a> para crear nuevas etiquetas.
</div>
<?php endif; ?>
</div>
// 4. Write the header row
$header = ['Nombre del producto', 'Código único', 'Código de barra (usar fuente)'];
fputcsv($output, $header, ';');
</body>
</html>
// 5. Write the data rows
foreach ($codes as $code) {
$row = [
$product_name,
$code,
$code // The same code, to be formatted as a barcode in Excel
];
fputcsv($output, $row, ';');
}
// 6. Close the file pointer
fclose($output);
exit;

View File

@ -31,13 +31,36 @@
const sidebarToggle = document.querySelector('.sidebar-toggle');
const sidebar = document.querySelector('.sidebar');
const body = document.querySelector('body');
const content = document.querySelector('.content');
function closeSidebar() {
if (sidebar.classList.contains('active')) {
sidebar.classList.remove('active');
body.classList.remove('sidebar-active');
}
}
if (sidebarToggle && sidebar) {
sidebarToggle.addEventListener('click', function() {
sidebarToggle.addEventListener('click', function(e) {
e.stopPropagation(); // Prevent click from bubbling to body
sidebar.classList.toggle('active');
body.classList.toggle('sidebar-active');
});
}
// Close sidebar if user clicks on the content area
if (content) {
content.addEventListener('click', function() {
closeSidebar();
});
}
// Close sidebar on body click if the click is outside the sidebar
body.addEventListener('click', function(e) {
if (body.classList.contains('sidebar-active') && !sidebar.contains(e.target)) {
closeSidebar();
}
});
});
</script>
</body>

83
registrar_entrada_api.php Normal file
View File

@ -0,0 +1,83 @@
<?php
require_once 'db/config.php';
header('Content-Type: application/json');
$response = ['success' => false, 'message' => 'Solicitud inválida.'];
if ($_SERVER['REQUEST_METHOD'] == 'POST') {
$codigo_unico = filter_input(INPUT_POST, 'codigo_unico', FILTER_SANITIZE_STRING);
$sede_id = filter_input(INPUT_POST, 'sede_id', FILTER_VALIDATE_INT);
$movement_date = date('Y-m-d H:i:s');
if ($codigo_unico && $sede_id) {
try {
$pdo = db();
$pdo->beginTransaction();
// 1. Buscar la unidad de inventario
$stmt_unidad = $pdo->prepare("SELECT * FROM unidades_inventario WHERE codigo_unico = :codigo_unico");
$stmt_unidad->execute(['codigo_unico' => $codigo_unico]);
$unidad = $stmt_unidad->fetch(PDO::FETCH_ASSOC);
if (!$unidad) {
throw new Exception("El código de unidad '$codigo_unico' no existe.");
}
if ($unidad['estado'] === 'En Almacén') {
throw new Exception("Esta unidad ya se encuentra en el almacén.");
}
if ($unidad['estado'] === 'Vendido') {
throw new Exception("Esta unidad ya fue vendida y no puede ser ingresada nuevamente.");
}
// 2. Actualizar el estado de la unidad
$update_unidad_stmt = $pdo->prepare("UPDATE unidades_inventario SET estado = 'En Almacén', fecha_ingreso = :fecha_ingreso WHERE id = :id");
$update_unidad_stmt->execute(['fecha_ingreso' => $movement_date, 'id' => $unidad['id']]);
$product_id = $unidad['producto_id'];
$quantity = 1;
// 3. Actualizar o insertar en stock_sedes
$stmt_stock = $pdo->prepare("SELECT * FROM stock_sedes WHERE product_id = :product_id AND sede_id = :sede_id");
$stmt_stock->execute(['product_id' => $product_id, 'sede_id' => $sede_id]);
$existing_stock = $stmt_stock->fetch();
if ($existing_stock) {
$new_quantity = $existing_stock['quantity'] + $quantity;
$update_stock_stmt = $pdo->prepare("UPDATE stock_sedes SET quantity = :quantity WHERE id = :id");
$update_stock_stmt->execute(['quantity' => $new_quantity, 'id' => $existing_stock['id']]);
} else {
$insert_stock_stmt = $pdo->prepare("INSERT INTO stock_sedes (product_id, sede_id, quantity) VALUES (:product_id, :sede_id, :quantity)");
$insert_stock_stmt->execute(['product_id' => $product_id, 'sede_id' => $sede_id, 'quantity' => $quantity]);
}
// 4. Insertar en el historial de movimientos
$history_stmt = $pdo->prepare(
"INSERT INTO stock_movements (product_id, sede_id, quantity, type, movement_date)
VALUES (:product_id, :sede_id, :quantity, 'entrada', :movement_date)"
);
$history_stmt->execute([
'product_id' => $product_id,
'sede_id' => $sede_id,
'quantity' => $quantity,
'movement_date' => $movement_date
]);
$pdo->commit();
$response = ['success' => true, 'message' => "Unidad '$codigo_unico' registrada correctamente."];
} catch (Exception $e) {
if ($pdo->inTransaction()) {
$pdo->rollBack();
}
$response = ['success' => false, 'message' => $e->getMessage()];
}
} else {
$response['message'] = 'Por favor, proporcione un código de unidad y una sede.';
}
}
echo json_encode($response);
?>

View File

@ -3,81 +3,7 @@ $pageTitle = "Registro de Entrada por Unidad";
require_once 'layout_header.php';
require_once 'db/config.php';
$message = '';
$error = '';
// Lógica para manejar el envío del formulario
if ($_SERVER["REQUEST_METHOD"] == "POST") {
$codigo_unico = filter_input(INPUT_POST, 'codigo_unico', FILTER_SANITIZE_STRING);
$sede_id = filter_input(INPUT_POST, 'sede_id', FILTER_VALIDATE_INT);
$movement_date = date('Y-m-d H:i:s');
if ($codigo_unico && $sede_id) {
try {
$pdo = db();
$pdo->beginTransaction();
// 1. Buscar la unidad de inventario
$stmt_unidad = $pdo->prepare("SELECT * FROM unidades_inventario WHERE codigo_unico = :codigo_unico");
$stmt_unidad->execute(['codigo_unico' => $codigo_unico]);
$unidad = $stmt_unidad->fetch(PDO::FETCH_ASSOC);
if (!$unidad) {
throw new Exception("El código de unidad '$codigo_unico' no existe.");
}
if ($unidad['estado'] === 'En Almacén') {
throw new Exception("Esta unidad ya se encuentra en el almacén.");
}
if ($unidad['estado'] === 'Vendido') {
throw new Exception("Esta unidad ya fue vendida y no puede ser ingresada nuevamente.");
}
// 2. Actualizar el estado de la unidad
$update_unidad_stmt = $pdo->prepare("UPDATE unidades_inventario SET estado = 'En Almacén', fecha_ingreso = :fecha_ingreso WHERE id = :id");
$update_unidad_stmt->execute(['fecha_ingreso' => $movement_date, 'id' => $unidad['id']]);
$product_id = $unidad['producto_id'];
$quantity = 1; // Siempre es 1 para el inventario serializado
// 3. Actualizar o insertar en stock_sedes (reutilizando lógica anterior)
$stmt_stock = $pdo->prepare("SELECT * FROM stock_sedes WHERE product_id = :product_id AND sede_id = :sede_id");
$stmt_stock->execute(['product_id' => $product_id, 'sede_id' => $sede_id]);
$existing_stock = $stmt_stock->fetch();
if ($existing_stock) {
$new_quantity = $existing_stock['quantity'] + $quantity;
$update_stock_stmt = $pdo->prepare("UPDATE stock_sedes SET quantity = :quantity WHERE id = :id");
$update_stock_stmt->execute(['quantity' => $new_quantity, 'id' => $existing_stock['id']]);
} else {
$insert_stock_stmt = $pdo->prepare("INSERT INTO stock_sedes (product_id, sede_id, quantity) VALUES (:product_id, :sede_id, :quantity)");
$insert_stock_stmt->execute(['product_id' => $product_id, 'sede_id' => $sede_id, 'quantity' => $quantity]);
}
// 4. Insertar en el historial de movimientos
$history_stmt = $pdo->prepare(
"INSERT INTO stock_movements (product_id, sede_id, quantity, type, movement_date)
VALUES (:product_id, :sede_id, :quantity, 'entrada', :movement_date)"
);
$history_stmt->execute([
'product_id' => $product_id,
'sede_id' => $sede_id,
'quantity' => $quantity,
'movement_date' => $movement_date
]);
$pdo->commit();
$message = "Unidad '$codigo_unico' registrada en el inventario correctamente.";
} catch (Exception $e) {
$pdo->rollBack();
$error = "Error: " . $e->getMessage();
}
} else {
$error = "Por favor, escanee un código y seleccione una sede.";
}
}
$error_page_load = '';
// Obtener sedes para el dropdown
$sedes = [];
@ -86,7 +12,7 @@ try {
$sedes_stmt = $pdo->query("SELECT id, nombre FROM sedes ORDER BY nombre ASC");
$sedes = $sedes_stmt->fetchAll(PDO::FETCH_ASSOC);
} catch (PDOException $e) {
$error = "Error al cargar las sedes: " . $e->getMessage();
$error_page_load = "Error al cargar las sedes: " . $e->getMessage();
}
?>
@ -94,11 +20,13 @@ try {
<div class="row">
<div class="col-lg-6 mx-auto">
<?php if ($message): ?>
<div class="alert alert-success" role="alert" id="form-message"><?php echo htmlspecialchars($message); ?></div>
<?php endif; ?>
<?php if ($error): ?>
<div class="alert alert-danger" role="alert" id="form-error"><?php echo htmlspecialchars($error); ?></div>
<!-- Contenedor para notificaciones (toasts) -->
<div id="notification-container" class="position-fixed top-0 end-0 p-3" style="z-index: 1100"></div>
<?php if (!empty($error_page_load)): ?>
<div class="alert alert-danger" role="alert">
<?php echo htmlspecialchars($error_page_load); ?>
</div>
<?php endif; ?>
<div class="card">
@ -106,17 +34,7 @@ try {
<i class="fa fa-barcode"></i> Registro de Entrada por Unidad
</div>
<div class="card-body">
<form action="registro_entrada.php" method="post" id="entrada-form">
<div class="mb-3">
<label for="codigo_unico" class="form-label">Código de Unidad</label>
<div class="input-group">
<input type="text" class="form-control" id="codigo_unico" name="codigo_unico" required autofocus>
<button type="button" class="btn btn-info" data-bs-toggle="modal" data-bs-target="#scannerModal">
<i class="fa fa-camera"></i>
</button>
</div>
</div>
<form id="scan-form" onsubmit="return false;">
<div class="mb-3">
<label for="sede" class="form-label">Sede de Destino</label>
<select class="form-select" id="sede" name="sede_id" required>
@ -128,7 +46,23 @@ try {
<?php endforeach; ?>
</select>
</div>
<button type="submit" class="btn btn-primary w-100"> <i class="fa fa-plus-circle"></i> Registrar Entrada</button>
<hr>
<div class="mb-3">
<label for="barcode-input" class="form-label">Esperando código de barras...</label>
<div class="input-group">
<input type="text" class="form-control form-control-lg" id="barcode-input" placeholder="Escanee el producto aquí" autofocus>
<button class="btn btn-outline-secondary" type="button" data-bs-toggle="modal" data-bs-target="#camera-modal">
<i class="fa fa-camera"></i>
</button>
</div>
</div>
<div id="scanned-product-info" class="mt-3" style="display: none;">
<p><strong>Producto escaneado:</strong> <span id="scanned-code"></span></p>
<div class="d-grid gap-2">
<button type="button" id="accept-btn" class="btn btn-success"> <i class="fa fa-check-circle"></i> Aceptar y Registrar Entrada</button>
<button type="button" id="cancel-btn" class="btn btn-danger"> <i class="fa fa-times-circle"></i> Cancelar</button>
</div>
</div>
</form>
</div>
</div>
@ -136,77 +70,194 @@ try {
</div>
</div>
<!-- Modal para el Escáner -->
<div class="modal fade" id="scannerModal" tabindex="-1" aria-labelledby="scannerModalLabel" aria-hidden="true">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title" id="scannerModalLabel">Escanear Código de Unidad</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<div class="modal-body">
<div id="reader" style="width: 100%;"></div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Cerrar</button>
</div>
<!-- Modal para la cámara -->
<div class="modal fade" id="camera-modal" tabindex="-1" aria-labelledby="camera-modal-label" aria-hidden="true">
<div class="modal-dialog modal-lg">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title" id="camera-modal-label">Escanear Código de Barras</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<div class="modal-body">
<div id="reader" width="100%"></div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Cerrar</button>
</div>
</div>
</div>
</div>
</div>
<script src="https://unpkg.com/html5-qrcode" type="text/javascript"></script>
<script src="https://unpkg.com/html5-qrcode@2.3.8/html5-qrcode.min.js"></script>
<script>
document.addEventListener('DOMContentLoaded', (event) => {
const codigoUnicoInput = document.getElementById('codigo_unico');
const form = document.getElementById('entrada-form');
if(codigoUnicoInput) {
codigoUnicoInput.focus();
}
if (form) {
codigoUnicoInput.addEventListener('keypress', function (e) {
if (e.key === 'Enter') {
e.preventDefault();
form.submit();
}
});
}
if (typeof bootstrap === 'undefined' || typeof Html5Qrcode === 'undefined') {
console.error('Bootstrap o Html5Qrcode no están cargados.');
// --- CONFIGURACIÓN INICIAL ---
if (typeof bootstrap === 'undefined') {
console.error('Bootstrap no está cargado.');
return;
}
const scannerModalElement = document.getElementById('scannerModal');
if (!scannerModalElement) return;
const barcodeInput = document.getElementById('barcode-input');
const sedeSelect = document.getElementById('sede');
const scannedProductInfo = document.getElementById('scanned-product-info');
const scannedCodeSpan = document.getElementById('scanned-code');
const acceptBtn = document.getElementById('accept-btn');
const cancelBtn = document.getElementById('cancel-btn');
const scannerModal = new bootstrap.Modal(scannerModalElement);
const html5QrCode = new Html5Qrcode("reader");
let processing = false;
let lastScannedCode = null;
const qrCodeSuccessCallback = (decodedText, decodedResult) => {
html5QrCode.stop().then(ignore => {}).catch(err => console.log("Failed to stop scanner"));
scannerModal.hide();
if(codigoUnicoInput) {
codigoUnicoInput.value = decodedText;
codigoUnicoInput.focus();
// --- LÓGICA DE SONIDO (WEB AUDIO API) ---
let audioCtx;
function wakeUpAudio() {
if (!audioCtx) {
try {
audioCtx = new (window.AudioContext || window.webkitAudioContext)();
} catch (e) { console.error("Web Audio API no es soportada.", e); return; }
}
};
if (audioCtx.state === 'suspended') {
audioCtx.resume();
}
}
document.body.addEventListener('click', wakeUpAudio, { once: true });
document.body.addEventListener('touchstart', wakeUpAudio, { once: true });
function playBeep() {
if (!audioCtx || audioCtx.state !== 'running') {
wakeUpAudio();
if (!audioCtx || audioCtx.state !== 'running') return;
}
const oscillator = audioCtx.createOscillator();
const gainNode = audioCtx.createGain();
oscillator.connect(gainNode);
gainNode.connect(audioCtx.destination);
gainNode.gain.value = 0.1;
oscillator.frequency.value = 880;
oscillator.type = 'sine';
oscillator.start();
setTimeout(() => oscillator.stop(), 150);
}
const config = { fps: 10, qrbox: { width: 250, height: 250 } };
// --- INICIALIZACIÓN DE CÁMARA ---
const cameraModal = document.getElementById('camera-modal');
let html5QrCode;
scannerModalElement.addEventListener('shown.bs.modal', function () {
html5QrCode.start({ facingMode: "environment" }, config, qrCodeSuccessCallback)
.catch(err => {
alert("Error al iniciar la cámara. Asegúrese de dar permisos.");
});
function onScanSuccess(decodedText, decodedResult) {
const modal = bootstrap.Modal.getInstance(cameraModal);
if(modal) modal.hide();
barcodeInput.value = decodedText;
const changeEvent = new Event('change');
barcodeInput.dispatchEvent(changeEvent);
}
cameraModal.addEventListener('shown.bs.modal', function () {
wakeUpAudio();
html5QrCode = new Html5Qrcode("reader");
const config = { fps: 10, qrbox: { width: 250, height: 250 } };
html5QrCode.start({ facingMode: "environment" }, config, onScanSuccess, (e)=>{})
.catch(err => alert("Error al iniciar la cámara. Asegúrate de dar permisos."));
});
scannerModalElement.addEventListener('hidden.bs.modal', function () {
html5QrCode.stop().catch(err => {});
cameraModal.addEventListener('hidden.bs.modal', function () {
if (html5QrCode && html5QrCode.isScanning) {
html5QrCode.stop().catch(err => {});
}
});
// --- FUNCIONES DE AYUDA (Notificaciones) ---
function showNotification(message, isSuccess) {
const container = document.getElementById('notification-container');
if (!container) return;
const toastId = 'toast-' + Date.now();
const toastHTML = `
<div id="${toastId}" class="toast" role="alert" aria-live="assertive" aria-atomic="true">
<div class="toast-header ${isSuccess ? 'bg-success text-white' : 'bg-danger text-white'}">
<strong class="me-auto">${isSuccess ? 'Éxito' : 'Error'}</strong>
<button type="button" class="btn-close btn-close-white" data-bs-dismiss="toast" aria-label="Close"></button>
</div>
<div class="toast-body">${message}</div>
</div>`;
container.insertAdjacentHTML('beforeend', toastHTML);
const toast = new bootstrap.Toast(document.getElementById(toastId), { delay: 5000 });
toast.show();
document.getElementById(toastId).addEventListener('hidden.bs.toast', e => e.target.remove());
}
function resetScanner() {
lastScannedCode = null;
barcodeInput.value = '';
barcodeInput.disabled = false;
scannedProductInfo.style.display = 'none';
processing = false;
barcodeInput.focus();
}
// --- LÓGICA PRINCIPAL DEL ESCÁNER ---
barcodeInput.addEventListener('change', function() {
const barcodeValue = this.value.trim();
if (barcodeValue === '') return;
playBeep();
lastScannedCode = barcodeValue;
scannedCodeSpan.textContent = barcodeValue;
scannedProductInfo.style.display = 'block';
this.disabled = true;
acceptBtn.focus(); // Mover foco al botón de aceptar
});
// --- LÓGICA DE BOTONES ACEPTAR/CANCELAR ---
cancelBtn.addEventListener('click', function() {
resetScanner();
});
acceptBtn.addEventListener('click', function() {
if (processing || !lastScannedCode) return;
processing = true;
const sedeId = sedeSelect.value;
if (!sedeId) {
showNotification("Por favor, seleccione una sede de destino.", false);
processing = false;
return;
}
const formData = new FormData();
formData.append('codigo_unico', lastScannedCode);
formData.append('sede_id', sedeId);
fetch('registrar_entrada_api.php', {
method: 'POST',
body: formData
})
.then(response => response.json())
.then(data => {
if (data.success) {
showNotification(data.message, true);
} else {
throw new Error(data.message || 'Error desconocido al registrar la entrada.');
}
})
.catch(error => {
showNotification(error.message, false);
})
.finally(() => {
resetScanner();
});
});
// Mantener el foco en el input principal cuando no se interactúa con otros elementos
barcodeInput.focus();
document.body.addEventListener('click', (e) => {
// No re-enfocar si se hace clic en inputs, botones, selects, o dentro de un modal
if (!e.target.closest('input, button, select, .modal')) {
barcodeInput.focus();
}
});
});
</script>
<?php require_once 'layout_footer.php'; ?>
<?php require_once 'layout_footer.php'; ?>