Autosave: 20260212-031011

This commit is contained in:
Flatlogic Bot 2026-02-12 03:10:11 +00:00
parent 3cc9a53922
commit 1c6ec71bf9
12 changed files with 620 additions and 128 deletions

View File

@ -26,6 +26,10 @@ try {
<label class="form-label" for="nombre"><strong>Nombre del Producto</strong></label>
<input class="form-control" type="text" id="nombre" name="nombre" required>
</div>
<div class="mb-3">
<label class="form-label" for="codigo_barras"><strong>Código de Barras</strong></label>
<input class="form-control" type="text" id="codigo_barras" name="codigo_barras" placeholder="Escanea o escribe el código">
</div>
<div class="mb-3">
<label class="form-label" for="descripcion"><strong>Descripción</strong></label>
<textarea class="form-control" id="descripcion" name="descripcion" rows="4"></textarea>

View File

@ -306,6 +306,7 @@ a:hover {
flex-grow: 1;
padding: 2rem;
background-color: var(--color-fondo);
width: 100%; /* Ensure it takes full width */
}
/* Forzar color de texto en el cuerpo de las tablas */
@ -318,4 +319,39 @@ a:hover {
color: #000 !important;
}
/* --- Responsive / Mobile --- */
@media (max-width: 768px) {
.d-flex {
flex-direction: column;
}
.sidebar {
min-width: 100%;
max-width: 100%;
min-height: auto;
display: none; /* Hidden by default on mobile */
}
.sidebar.show {
display: flex;
}
.main-content {
padding: 1rem;
}
.mobile-nav {
display: flex !important;
justify-content: space-between;
align-items: center;
padding: 1rem;
background-color: #00754A;
color: white;
}
}
.mobile-nav {
display: none; /* Hidden on desktop */
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

View File

@ -39,6 +39,11 @@ if (!$product) {
<input type="text" class="form-control" id="nombre" name="nombre" value="<?php echo htmlspecialchars($product['nombre']); ?>" required>
</div>
<div class="mb-3">
<label for="codigo_barras" class="form-label">Código de Barras</label>
<input type="text" class="form-control" id="codigo_barras" name="codigo_barras" value="<?php echo htmlspecialchars($product['codigo_barras'] ?? ''); ?>">
</div>
<div class="mb-3">
<label for="descripcion" class="form-label">Descripción</label>
<textarea class="form-control" id="descripcion" name="descripcion" rows="3"><?php echo htmlspecialchars($product['descripcion']); ?></textarea>

View File

@ -11,6 +11,7 @@ require_once 'db/config.php';
if ($_SERVER["REQUEST_METHOD"] == "POST") {
// Sanitize and retrieve product data
$nombre = filter_input(INPUT_POST, 'nombre', FILTER_SANITIZE_STRING);
$codigo_barras = filter_input(INPUT_POST, 'codigo_barras', FILTER_SANITIZE_STRING);
$descripcion = filter_input(INPUT_POST, 'descripcion', FILTER_SANITIZE_STRING);
$costo = filter_input(INPUT_POST, 'costo', FILTER_VALIDATE_FLOAT);
$precio_venta = filter_input(INPUT_POST, 'precio_venta', FILTER_VALIDATE_FLOAT);
@ -26,9 +27,9 @@ if ($_SERVER["REQUEST_METHOD"] == "POST") {
// 1. Insert product
$sku = 'SKU-' . strtoupper(substr(uniqid(), -8));
$sql_producto = "INSERT INTO productos (sku, nombre, descripcion, costo, precio_venta, created_at) VALUES (?, ?, ?, ?, ?, NOW())";
$sql_producto = "INSERT INTO productos (sku, nombre, codigo_barras, descripcion, costo, precio_venta, created_at) VALUES (?, ?, ?, ?, ?, ?, NOW())";
$stmt_producto = $pdo->prepare($sql_producto);
$stmt_producto->execute([$sku, $nombre, $descripcion, $costo, $precio_venta]);
$stmt_producto->execute([$sku, $nombre, $codigo_barras, $descripcion, $costo, $precio_venta]);
// Get the ID of the new product
$producto_id = $pdo->lastInsertId();

View File

@ -10,6 +10,7 @@ require_once 'db/config.php';
if ($_SERVER["REQUEST_METHOD"] == "POST") {
$product_id = isset($_POST['id']) ? (int)$_POST['id'] : 0;
$nombre = trim($_POST['nombre']);
$codigo_barras = trim($_POST['codigo_barras']);
$descripcion = trim($_POST['descripcion']);
$costo = filter_input(INPUT_POST, 'costo', FILTER_VALIDATE_FLOAT);
$precio_venta = filter_input(INPUT_POST, 'precio_venta', FILTER_VALIDATE_FLOAT);
@ -17,10 +18,10 @@ if ($_SERVER["REQUEST_METHOD"] == "POST") {
if ($product_id > 0 && !empty($nombre) && $costo !== false && $precio_venta !== false) {
try {
$pdo = db();
$sql = "UPDATE productos SET nombre = ?, descripcion = ?, costo = ?, precio_venta = ? WHERE id = ?";
$sql = "UPDATE productos SET nombre = ?, codigo_barras = ?, descripcion = ?, costo = ?, precio_venta = ? WHERE id = ?";
$stmt = $pdo->prepare($sql);
if ($stmt->execute([$nombre, $descripcion, $costo, $precio_venta, $product_id])) {
if ($stmt->execute([$nombre, $codigo_barras, $descripcion, $costo, $precio_venta, $product_id])) {
$_SESSION['success_message'] = "Producto actualizado correctamente.";
} else {
$_SESSION['error_message'] = "Error al actualizar el producto.";

82
imprimir_codigo.php Normal file
View File

@ -0,0 +1,82 @@
<?php
require_once 'db/config.php';
$product_id = isset($_GET['id']) ? (int)$_GET['id'] : 0;
if ($product_id === 0) {
die("ID de producto no válido.");
}
$pdo = db();
$stmt = $pdo->prepare("SELECT * FROM productos WHERE id = ?");
$stmt->execute([$product_id]);
$product = $stmt->fetch(PDO::FETCH_ASSOC);
if (!$product) {
die("Producto no encontrado.");
}
$barcodeValue = $product['codigo_barras'] ? $product['codigo_barras'] : $product['sku'];
?>
<!DOCTYPE html>
<html lang="es">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Imprimir Código de Barras - <?php echo htmlspecialchars($product['nombre']); ?></title>
<script src="https://cdn.jsdelivr.net/npm/jsbarcode@3.11.5/dist/JsBarcode.all.min.js"></script>
<style>
body {
font-family: Arial, sans-serif;
text-align: center;
padding: 20px;
}
.barcode-container {
border: 1px solid #ccc;
padding: 10px;
display: inline-block;
margin: 10px;
}
.product-name {
font-size: 14px;
font-weight: bold;
margin-bottom: 5px;
}
@media print {
.no-print {
display: none;
}
body {
padding: 0;
margin: 0;
}
.barcode-container {
border: none;
}
}
</style>
</head>
<body>
<div class="no-print">
<button onclick="window.print()">Imprimir</button>
<button onclick="window.close()">Cerrar</button>
<hr>
</div>
<div class="barcode-container">
<div class="product-name"><?php echo htmlspecialchars($product['nombre']); ?></div>
<svg id="barcode"></svg>
</div>
<script>
JsBarcode("#barcode", "<?php echo htmlspecialchars($barcodeValue); ?>", {
format: "CODE128",
lineColor: "#000",
width: 2,
height: 40,
displayValue: true
});
</script>
</body>
</html>

View File

@ -6,5 +6,25 @@
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/js/bootstrap.bundle.min.js"></script>
<script>
// Mobile Sidebar Toggle
document.addEventListener('DOMContentLoaded', function() {
const toggleBtn = document.getElementById('sidebarToggle');
const sidebar = document.querySelector('.sidebar');
if (toggleBtn && sidebar) {
toggleBtn.addEventListener('click', function() {
if (sidebar.style.display === 'flex') {
sidebar.style.display = ''; // Reset to CSS rule (none on mobile)
sidebar.classList.remove('show');
} else {
sidebar.style.display = 'flex';
sidebar.classList.add('show');
}
});
}
});
</script>
</body>
</html>

View File

@ -29,8 +29,16 @@ if (!is_logged_in()) {
<body>
<div class="d-flex">
<!-- Mobile Navigation Bar -->
<div class="mobile-nav w-100">
<h4 class="m-0">Inventario</h4>
<button class="btn btn-outline-light" id="sidebarToggle">
<i class="fas fa-bars"></i> Menú
</button>
</div>
<!-- Menú Lateral (Sidebar) -->
<div class="sidebar">
<div class="sidebar" id="sidebar">
<h3 class="sidebar-heading">Panel de control</h3>
<ul class="nav flex-column">
<li class="nav-item">

View File

@ -110,6 +110,7 @@ $products = $stmt->fetchAll(PDO::FETCH_ASSOC);
<tr>
<th>ID</th>
<th>Nombre</th>
<th>Código de Barras</th>
<th>Descripción</th>
<th>Precio</th>
<th>Stock por Ciudad</th>
@ -122,10 +123,12 @@ $products = $stmt->fetchAll(PDO::FETCH_ASSOC);
<tr>
<td><?php echo htmlspecialchars($product['id']); ?></td>
<td><?php echo htmlspecialchars($product['nombre']); ?></td>
<td><?php echo htmlspecialchars($product['codigo_barras'] ?? 'N/A'); ?></td>
<td><?php echo htmlspecialchars($product['descripcion']); ?></td>
<td>$<?php echo htmlspecialchars(number_format($product['precio_venta'], 2)); ?></td>
<td><?php echo $product['stock_por_ciudad'] ? $product['stock_por_ciudad'] : 'Sin stock'; ?></td>
<td>
<a href="imprimir_codigo.php?id=<?php echo $product['id']; ?>" target="_blank" class="btn btn-info btn-sm"><i class="fas fa-print"></i> Código</a>
<a href="editar_producto.php?id=<?php echo $product['id']; ?>" class="btn btn-warning btn-sm"><i class="fas fa-edit"></i> Editar</a>
<a href="eliminar_producto.php?id=<?php echo $product['id']; ?>" class="btn btn-danger btn-sm" onclick="return confirm('¿Estás seguro de que quieres eliminar este producto?');"><i class="fas fa-trash"></i> Eliminar</a>
</td>

View File

@ -6,7 +6,7 @@ try {
$pdo = db();
// Fetch products
$stmt_productos = $pdo->query("SELECT id, nombre, sku FROM productos WHERE activo = 1 ORDER BY nombre ASC");
$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
@ -21,66 +21,232 @@ try {
?>
<div class="container mt-5">
<h2>Registrar Entrada de Stock</h2>
<p>Utiliza este formulario para añadir nuevas unidades de un producto a una ciudad específica.</p>
<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; ?>
<?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" class="mt-4">
<div class="mb-3">
<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']); ?>">
<?php echo htmlspecialchars($producto['nombre']) . ' (SKU: ' . htmlspecialchars($producto['sku']) . ')'; ?>
</option>
<?php endforeach; ?>
</select>
</div>
<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-3">
<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="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-3">
<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="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-3">
<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-3">
<label for="observacion" class="form-label">Observación (Opcional)</label>
<textarea class="form-control" id="observacion" name="observacion" rows="3" placeholder="Ej: Compra a proveedor, ajuste de inventario, etc."></textarea>
</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>
<button type="submit" class="btn btn-success">Registrar Entrada</button>
<a href="productos.php" class="btn btn-secondary">Cancelar</a>
</form>
<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';
?>
?>

View File

@ -16,7 +16,7 @@ try {
$pdo = db();
// Fetch products
$stmt_productos = $pdo->query("SELECT id, nombre FROM productos ORDER BY nombre ASC");
$stmt_productos = $pdo->query("SELECT id, nombre, sku, codigo_barras FROM productos ORDER BY nombre ASC");
$productos = $stmt_productos->fetchAll(PDO::FETCH_ASSOC);
// Fetch cities
@ -32,81 +32,247 @@ try {
?>
<div class="container mt-5">
<h1>Registrar Salida de Producto</h1>
<p>Selecciona el producto, la ciudad y la cantidad que deseas dar de baja del inventario.</p>
<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">
<h1 class="h3 mb-3 text-center">Registrar Salida</h1>
<p class="text-muted text-center mb-4">Escanea o selecciona el producto para dar de baja.</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;
?>
<?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_salida.php" method="POST" class="mt-4">
<div class="mb-3">
<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']); ?>">
<?php echo htmlspecialchars($producto['nombre']); ?>
</option>
<?php endforeach; ?>
</select>
</div>
<form action="handle_salida.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-primary" 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-3">
<label for="ciudad_id" class="form-label">Ciudad</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="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']); ?>
</option>
<?php endforeach; ?>
</select>
</div>
<div class="mb-3">
<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-3">
<label for="fecha" class="form-label">Fecha de Salida</label>
<input type="date" class="form-control" id="fecha" name="fecha" required>
</div>
<div class="mb-3">
<label for="descripcion" class="form-label">Descripción (Opcional)</label>
<textarea class="form-control" id="descripcion" name="descripcion" rows="3" placeholder="Ej: Venta a cliente, traslado a otra tienda, etc."></textarea>
</div>
<div class="mb-4">
<label for="ciudad_id" class="form-label">Ciudad</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>
<button type="submit" class="btn btn-primary">Registrar Salida</button>
<a href="productos.php" class="btn btn-secondary">Cancelar</a>
</form>
<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 Salida</label>
<input type="date" class="form-control" id="fecha" name="fecha" required>
</div>
<div class="mb-4">
<label for="descripcion" class="form-label">Descripción (Opcional)</label>
<textarea class="form-control" id="descripcion" name="descripcion" rows="3" placeholder="Ej: Venta, traslado..."></textarea>
</div>
<div class="d-grid gap-2">
<button type="submit" class="btn btn-primary btn-lg">Registrar Salida</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-primary');
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-primary');
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';
?>