252 lines
10 KiB
PHP
252 lines
10 KiB
PHP
<?php
|
|
require_once 'includes/header.php';
|
|
|
|
// Fetch products and cities for the dropdowns
|
|
try {
|
|
$pdo = db();
|
|
|
|
// Fetch products
|
|
$stmt_productos = $pdo->query("SELECT id, nombre, sku, codigo_barras FROM productos WHERE activo = 1 ORDER BY nombre ASC");
|
|
$productos = $stmt_productos->fetchAll(PDO::FETCH_ASSOC);
|
|
|
|
// Fetch cities
|
|
$stmt_ciudades = $pdo->query("SELECT id, nombre FROM ciudades ORDER BY nombre ASC");
|
|
$ciudades = $stmt_ciudades->fetchAll(PDO::FETCH_ASSOC);
|
|
|
|
} catch (PDOException $e) {
|
|
echo '<div class="alert alert-danger" role="alert">Error al conectar con la base de datos: ' . htmlspecialchars($e->getMessage()) . '</div>';
|
|
$productos = [];
|
|
$ciudades = [];
|
|
}
|
|
|
|
?>
|
|
|
|
<div class="container mt-4">
|
|
<div class="row justify-content-center">
|
|
<div class="col-md-8 col-lg-6">
|
|
<div class="card shadow-sm">
|
|
<div class="card-body p-4">
|
|
<h2 class="h3 mb-3 text-center">Registrar Entrada</h2>
|
|
<p class="text-muted text-center mb-4">Añade nuevas unidades al inventario.</p>
|
|
|
|
<?php if (isset($_SESSION['error'])): ?>
|
|
<div class="alert alert-danger" role="alert">
|
|
<?php echo htmlspecialchars($_SESSION['error']); unset($_SESSION['error']); ?>
|
|
</div>
|
|
<?php endif; ?>
|
|
<?php if (isset($_SESSION['success'])): ?>
|
|
<div class="alert alert-success" role="alert">
|
|
<?php echo htmlspecialchars($_SESSION['success']); unset($_SESSION['success']); ?>
|
|
</div>
|
|
<?php endif; ?>
|
|
|
|
<form action="handle_entrada.php" method="POST">
|
|
<div class="mb-4">
|
|
<label for="scan_barcode" class="form-label"><strong>Escanear Código</strong></label>
|
|
<div class="d-grid gap-2">
|
|
<input type="text" class="form-control" id="scan_barcode" placeholder="Haz clic y escanea..." autofocus>
|
|
<button class="btn btn-outline-success" type="button" id="btn-scan-camera">
|
|
<i class="fas fa-camera"></i> Escanear con Cámara
|
|
</button>
|
|
</div>
|
|
<div id="reader" style="width: 100%; display:none; margin-top: 10px;"></div>
|
|
</div>
|
|
|
|
<div class="mb-4">
|
|
<label for="producto_id" class="form-label">Producto</label>
|
|
<select class="form-select" id="producto_id" name="producto_id" required>
|
|
<option value="">Selecciona un producto</option>
|
|
<?php foreach ($productos as $producto): ?>
|
|
<option value="<?php echo htmlspecialchars($producto['id']); ?>" data-barcode="<?php echo htmlspecialchars($producto['codigo_barras'] ?? ''); ?>" data-sku="<?php echo htmlspecialchars($producto['sku']); ?>">
|
|
<?php echo htmlspecialchars($producto['nombre']) . ' (SKU: ' . htmlspecialchars($producto['sku']) . ')'; ?>
|
|
</option>
|
|
<?php endforeach; ?>
|
|
</select>
|
|
</div>
|
|
|
|
<div class="mb-4">
|
|
<label for="ciudad_id" class="form-label">Ciudad de Destino</label>
|
|
<select class="form-select" id="ciudad_id" name="ciudad_id" required>
|
|
<option value="">Selecciona una ciudad</option>
|
|
<?php foreach ($ciudades as $ciudad): ?>
|
|
<option value="<?php echo htmlspecialchars($ciudad['id']); ?>">
|
|
<?php echo htmlspecialchars($ciudad['nombre']); ?>
|
|
</option>
|
|
<?php endforeach; ?>
|
|
</select>
|
|
</div>
|
|
|
|
<div class="mb-4">
|
|
<label for="cantidad" class="form-label">Cantidad</label>
|
|
<input type="number" class="form-control" id="cantidad" name="cantidad" min="1" required>
|
|
</div>
|
|
|
|
<div class="mb-4">
|
|
<label for="fecha" class="form-label">Fecha de Entrada</label>
|
|
<input type="date" class="form-control" id="fecha" name="fecha" required>
|
|
</div>
|
|
|
|
<div class="mb-4">
|
|
<label for="observacion" class="form-label">Observación (Opcional)</label>
|
|
<textarea class="form-control" id="observacion" name="observacion" rows="3" placeholder="Ej: Compra, ajuste..."></textarea>
|
|
</div>
|
|
|
|
<div class="d-grid gap-2">
|
|
<button type="submit" class="btn btn-success btn-lg">Registrar Entrada</button>
|
|
<a href="productos.php" class="btn btn-outline-secondary">Cancelar</a>
|
|
</div>
|
|
</form>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Include html5-qrcode library -->
|
|
<script src="https://unpkg.com/html5-qrcode" type="text/javascript"></script>
|
|
|
|
<script>
|
|
document.addEventListener('DOMContentLoaded', function() {
|
|
const barcodeInput = document.getElementById('scan_barcode');
|
|
const productSelect = document.getElementById('producto_id');
|
|
const quantityInput = document.getElementById('cantidad');
|
|
const btnScan = document.getElementById('btn-scan-camera');
|
|
const readerDiv = document.getElementById('reader');
|
|
let html5QrCode = null;
|
|
let isScanning = false;
|
|
let audioCtx = null;
|
|
|
|
function initAudio() {
|
|
if (!audioCtx) {
|
|
audioCtx = new (window.AudioContext || window.webkitAudioContext)();
|
|
}
|
|
if (audioCtx.state === 'suspended') {
|
|
audioCtx.resume();
|
|
}
|
|
}
|
|
|
|
// Function to search and select product
|
|
function searchProduct(code) {
|
|
if (code) {
|
|
let found = false;
|
|
for (let i = 0; i < productSelect.options.length; i++) {
|
|
const option = productSelect.options[i];
|
|
const barcode = option.getAttribute('data-barcode');
|
|
const sku = option.getAttribute('data-sku');
|
|
|
|
if (barcode === code || sku === code) {
|
|
productSelect.selectedIndex = i;
|
|
found = true;
|
|
barcodeInput.value = ''; // Clear input
|
|
quantityInput.focus(); // Move to quantity
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (!found) {
|
|
alert('Producto no encontrado con el código: ' + code);
|
|
barcodeInput.value = '';
|
|
}
|
|
}
|
|
}
|
|
|
|
// Manual input listener
|
|
barcodeInput.addEventListener('keypress', function(e) {
|
|
if (e.key === 'Enter') {
|
|
e.preventDefault(); // Prevent form submission
|
|
searchProduct(barcodeInput.value.trim());
|
|
}
|
|
});
|
|
|
|
// Camera scan listener
|
|
btnScan.addEventListener('click', function() {
|
|
initAudio();
|
|
if (isScanning) {
|
|
stopScanning();
|
|
} else {
|
|
startScanning();
|
|
}
|
|
});
|
|
|
|
function startScanning() {
|
|
readerDiv.style.display = 'block';
|
|
html5QrCode = new Html5Qrcode("reader");
|
|
|
|
const config = { fps: 10, qrbox: { width: 250, height: 250 } };
|
|
|
|
// Prefer back camera
|
|
html5QrCode.start({ facingMode: "environment" }, config, onScanSuccess, onScanFailure)
|
|
.then(() => {
|
|
isScanning = true;
|
|
btnScan.innerHTML = '<i class="fas fa-stop"></i> Detener Cámara';
|
|
btnScan.classList.remove('btn-outline-success');
|
|
btnScan.classList.add('btn-danger');
|
|
})
|
|
.catch(err => {
|
|
console.error("Error starting scanner", err);
|
|
alert("No se pudo iniciar la cámara. Por favor, verifica los permisos y que estés usando HTTPS.");
|
|
readerDiv.style.display = 'none';
|
|
});
|
|
}
|
|
|
|
function stopScanning() {
|
|
if (html5QrCode) {
|
|
html5QrCode.stop().then(() => {
|
|
readerDiv.style.display = 'none';
|
|
isScanning = false;
|
|
btnScan.innerHTML = '<i class="fas fa-camera"></i> Escanear con Cámara';
|
|
btnScan.classList.remove('btn-danger');
|
|
btnScan.classList.add('btn-outline-success');
|
|
html5QrCode.clear();
|
|
}).catch(err => {
|
|
console.error("Failed to stop scanner", err);
|
|
});
|
|
}
|
|
}
|
|
|
|
// Professional scanner sound using Web Audio API
|
|
function playScanSound() {
|
|
if (!audioCtx) initAudio();
|
|
const oscillator = audioCtx.createOscillator();
|
|
const gainNode = audioCtx.createGain();
|
|
|
|
oscillator.connect(gainNode);
|
|
gainNode.connect(audioCtx.destination);
|
|
|
|
oscillator.type = 'sine';
|
|
oscillator.frequency.setValueAtTime(1200, audioCtx.currentTime); // 1200Hz beep
|
|
|
|
// Smooth envelope
|
|
gainNode.gain.setValueAtTime(0, audioCtx.currentTime);
|
|
gainNode.gain.linearRampToValueAtTime(0.1, audioCtx.currentTime + 0.01);
|
|
gainNode.gain.exponentialRampToValueAtTime(0.001, audioCtx.currentTime + 0.15);
|
|
|
|
oscillator.start();
|
|
oscillator.stop(audioCtx.currentTime + 0.15);
|
|
}
|
|
|
|
function onScanSuccess(decodedText, decodedResult) {
|
|
// Handle the scanned code
|
|
console.log(`Code matched = ${decodedText}`, decodedResult);
|
|
|
|
// Play sound
|
|
playScanSound();
|
|
|
|
// Stop scanning after successful read
|
|
stopScanning();
|
|
|
|
// Fill input and search
|
|
barcodeInput.value = decodedText;
|
|
searchProduct(decodedText);
|
|
}
|
|
|
|
function onScanFailure(error) {
|
|
// handle scan failure, usually better to ignore and keep scanning.
|
|
// console.warn(`Code scan error = ${error}`);
|
|
}
|
|
});
|
|
</script>
|
|
|
|
<?php
|
|
require_once 'includes/footer.php';
|
|
?>
|