diff --git a/assets/css/style.css b/assets/css/style.css index ba06515..d62ae85 100644 --- a/assets/css/style.css +++ b/assets/css/style.css @@ -104,6 +104,17 @@ body { body.sidebar-active .content { margin-left: 260px; } + + .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 */ + } } @@ -293,4 +304,26 @@ h1, .h1 { .sidebar .submenu .nav-link.active { color: #fff; /* White color for active sub-item */ font-weight: bold; +} + +@media (max-width: 767px) { + .content { + padding: 15px; + } + + .card-header, .card-body { + padding: 1rem; + } + + h1, .h1 { + font-size: 1.75rem; + } + + .table { + font-size: 0.9rem; + } + + .table td, .table th { + padding: 0.5rem; + } } \ No newline at end of file diff --git a/assets/uploads/vouchers/698de35a83aff-CAPTURA 3.png b/assets/uploads/vouchers/698de35a83aff-CAPTURA 3.png new file mode 100644 index 0000000..b785aa4 Binary files /dev/null and b/assets/uploads/vouchers/698de35a83aff-CAPTURA 3.png differ diff --git a/assets/uploads/vouchers/698de4145d43c-CAPTURA 4.png b/assets/uploads/vouchers/698de4145d43c-CAPTURA 4.png new file mode 100644 index 0000000..0b22f07 Binary files /dev/null and b/assets/uploads/vouchers/698de4145d43c-CAPTURA 4.png differ diff --git a/assets/uploads/vouchers/698de60f82358-WhatsApp Image 2026-02-12 at 9.34.18 AM.jpeg b/assets/uploads/vouchers/698de60f82358-WhatsApp Image 2026-02-12 at 9.34.18 AM.jpeg new file mode 100644 index 0000000..88ae65a Binary files /dev/null and b/assets/uploads/vouchers/698de60f82358-WhatsApp Image 2026-02-12 at 9.34.18 AM.jpeg differ diff --git a/assets/uploads/vouchers/698ded43bcdce-ENTREGADO.png b/assets/uploads/vouchers/698ded43bcdce-ENTREGADO.png new file mode 100644 index 0000000..ae215bb Binary files /dev/null and b/assets/uploads/vouchers/698ded43bcdce-ENTREGADO.png differ diff --git a/db/migrations/062_create_unidades_inventario_table.sql b/db/migrations/062_create_unidades_inventario_table.sql new file mode 100644 index 0000000..2e66aea --- /dev/null +++ b/db/migrations/062_create_unidades_inventario_table.sql @@ -0,0 +1,12 @@ +CREATE TABLE IF NOT EXISTS `unidades_inventario` ( + `id` INT AUTO_INCREMENT PRIMARY KEY, + `codigo_unico` VARCHAR(255) NOT NULL UNIQUE, + `producto_id` INT NOT NULL, + `estado` VARCHAR(50) NOT NULL DEFAULT 'Generado', -- Puede ser 'Generado', 'En Almacén', 'Vendido' + `fecha_creacion` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, + `fecha_ingreso` DATETIME DEFAULT NULL, + `fecha_salida` DATETIME DEFAULT NULL, + `pedido_id` INT DEFAULT NULL, + FOREIGN KEY (`producto_id`) REFERENCES `products`(`id`) ON DELETE CASCADE, + FOREIGN KEY (`pedido_id`) REFERENCES `pedidos`(`id`) ON DELETE SET NULL +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; diff --git a/generar_etiquetas.php b/generar_etiquetas.php new file mode 100644 index 0000000..260c84d --- /dev/null +++ b/generar_etiquetas.php @@ -0,0 +1,97 @@ +query("SELECT id, nombre FROM products ORDER BY nombre ASC"); + $products = $stmt->fetchAll(PDO::FETCH_ASSOC); +} catch (PDOException $e) { + $_SESSION['error_message'] = "Error al cargar los productos: " . $e->getMessage(); +} + +$generated_codes = []; +// Handle form submission +if ($_SERVER['REQUEST_METHOD'] === 'POST') { + $producto_id = $_POST['producto_id'] ?? null; + $cantidad = isset($_POST['cantidad']) ? (int)$_POST['cantidad'] : 0; + + if ($producto_id && $cantidad > 0 && $cantidad <= 1000) { + $db = db(); + try { + $db->beginTransaction(); + + // Prepare statement for insertion + $stmt = $db->prepare("INSERT INTO unidades_inventario (codigo_unico, producto_id) VALUES (?, ?)"); + + for ($i = 0; $i < $cantidad; $i++) { + // Generate a more robust unique code + $unique_code = 'FL-' . $producto_id . '-' . strtoupper(uniqid()); + + $stmt->execute([$unique_code, $producto_id]); + $generated_codes[] = $unique_code; + } + + $db->commit(); + $_SESSION['success_message'] = 'Se generaron ' . count($generated_codes) . ' códigos exitosamente.'; + + } catch (PDOException $e) { + $db->rollBack(); + $_SESSION['error_message'] = 'Error al generar los códigos: ' . $e->getMessage(); + } + } else { + $_SESSION['error_message'] = 'Por favor, seleccione un producto y especifique una cantidad válida (entre 1 y 1000).'; + } +} +?> + +
+
+

Generar Nuevas Etiquetas de Inventario

+
+
+
+
+
+ + +
+
+ + +
+
+ +
+
+
+
+
+ + +
+
+

Códigos Generados

+
+
+

Guarda estos códigos para imprimirlos y pegarlos en tus paquetes.

+ + +
+
+ + + \ No newline at end of file diff --git a/layout_header.php b/layout_header.php index 18a15aa..77766d7 100644 --- a/layout_header.php +++ b/layout_header.php @@ -97,6 +97,12 @@ $navItems = [ 'text' => 'Inventario General', 'roles' => ['Administrador', 'admin', 'Control Logistico'] ], + 'generar_etiquetas' => [ + 'url' => 'generar_etiquetas.php', + 'icon' => 'fa-barcode', + 'text' => 'Etiquetas', + 'roles' => ['Administrador', 'admin', 'Control Logistico'] + ], 'registro_entrada' => [ 'url' => 'registro_entrada.php', 'icon' => 'fa-arrow-circle-down', diff --git a/registrar_salida_unidad_api.php b/registrar_salida_unidad_api.php new file mode 100644 index 0000000..720b50b --- /dev/null +++ b/registrar_salida_unidad_api.php @@ -0,0 +1,88 @@ + false, 'message' => 'Acceso no autorizado.']); + exit(); +} + +$response = ['success' => false, 'message' => 'Petición 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(); + + $stmt_unidad = $pdo->prepare(" + SELECT u.*, p.nombre as producto_nombre + FROM unidades_inventario u + JOIN products p ON u.producto_id = p.id + WHERE u.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("La unidad no está en el almacén. Estado actual: " . $unidad['estado']); + } + + $update_unidad_stmt = $pdo->prepare("UPDATE unidades_inventario SET estado = 'Vendido', fecha_salida = :fecha_salida WHERE id = :id"); + $update_unidad_stmt->execute(['fecha_salida' => $movement_date, 'id' => $unidad['id']]); + + $product_id = $unidad['producto_id']; + $quantity = 1; + + $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 && $existing_stock['quantity'] > 0) { + $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 { + if ($existing_stock) { + $update_stock_stmt = $pdo->prepare("UPDATE stock_sedes SET quantity = 0 WHERE id = :id"); + $update_stock_stmt->execute(['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(); + $response = ['success' => true, 'message' => 'Salida de "' . $unidad['producto_nombre'] . '" registrada.']; + + } catch (Exception $e) { + $pdo->rollBack(); + $response = ['success' => false, 'message' => $e->getMessage()]; + } + } else { + $response = ['success' => false, 'message' => 'Faltan datos: código de unidad o sede.']; + } +} + +echo json_encode($response); +?> \ No newline at end of file diff --git a/registro_entrada.php b/registro_entrada.php index 111a28a..d5c79c4 100644 --- a/registro_entrada.php +++ b/registro_entrada.php @@ -1,5 +1,5 @@ beginTransaction(); - // 1. Actualizar o insertar en stock_sedes - $stmt = $pdo->prepare("SELECT * FROM stock_sedes WHERE product_id = :product_id AND sede_id = :sede_id"); - $stmt->execute(['product_id' => $product_id, 'sede_id' => $sede_id]); - $existing_stock = $stmt->fetch(); + // 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_stmt = $pdo->prepare("UPDATE stock_sedes SET quantity = :quantity WHERE id = :id"); - $update_stmt->execute(['quantity' => $new_quantity, 'id' => $existing_stock['id']]); + $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_stmt = $pdo->prepare("INSERT INTO stock_sedes (product_id, sede_id, quantity) VALUES (:product_id, :sede_id, :quantity)"); - $insert_stmt->execute(['product_id' => $product_id, 'sede_id' => $sede_id, 'quantity' => $quantity]); + $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]); } - // 2. Insertar en el historial de movimientos + // 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)" @@ -45,29 +68,25 @@ if ($_SERVER["REQUEST_METHOD"] == "POST") { ]); $pdo->commit(); - $message = "¡Inventario actualizado y movimiento registrado correctamente!"; + $message = "Unidad '$codigo_unico' registrada en el inventario correctamente."; - } catch (PDOException $e) { + } catch (Exception $e) { $pdo->rollBack(); - $error = "Error al actualizar el inventario: " . $e->getMessage(); + $error = "Error: " . $e->getMessage(); } } else { - $error = "Por favor, complete todos los campos del formulario, incluyendo la fecha."; + $error = "Por favor, escanee un código y seleccione una sede."; } } -// Obtener productos y sedes para los dropdowns -$products = []; +// Obtener sedes para el dropdown $sedes = []; try { $pdo = db(); - $products_stmt = $pdo->query("SELECT id, nombre FROM products ORDER BY nombre ASC"); - $products = $products_stmt->fetchAll(PDO::FETCH_ASSOC); - $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 datos: " . $e->getMessage(); + $error = "Error al cargar las sedes: " . $e->getMessage(); } ?> @@ -76,44 +95,28 @@ try {
- + - +
- Registro de Entrada de Producto + Registro de Entrada por Unidad
-
+
- - -
-
- - - -
-
- - + +
+ + +
+
- +
@@ -138,7 +141,7 @@ try {