diff --git a/assets/uploads/vouchers/69821b803f44e-WhatsApp Image 2026-02-03 at 10.27.45 AM.jpeg b/assets/uploads/vouchers/69821b803f44e-WhatsApp Image 2026-02-03 at 10.27.45 AM.jpeg new file mode 100644 index 0000000..73ad3da Binary files /dev/null and b/assets/uploads/vouchers/69821b803f44e-WhatsApp Image 2026-02-03 at 10.27.45 AM.jpeg differ diff --git a/db/migrations/051_create_stock_movements_table.sql b/db/migrations/051_create_stock_movements_table.sql new file mode 100644 index 0000000..652a22b --- /dev/null +++ b/db/migrations/051_create_stock_movements_table.sql @@ -0,0 +1,11 @@ +CREATE TABLE IF NOT EXISTS `stock_movements` ( + `id` INT AUTO_INCREMENT PRIMARY KEY, + `product_id` INT NOT NULL, + `sede_id` INT NOT NULL, + `quantity` INT NOT NULL, + `type` ENUM('entrada', 'salida') NOT NULL, + `movement_date` DATE NOT NULL, + `created_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + FOREIGN KEY (`product_id`) REFERENCES `products`(`id`) ON DELETE CASCADE, + FOREIGN KEY (`sede_id`) REFERENCES `sedes`(`id`) ON DELETE CASCADE +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; diff --git a/db/migrations/052_add_sc_to_flujo_caja.sql b/db/migrations/052_add_sc_to_flujo_caja.sql new file mode 100644 index 0000000..1955e49 --- /dev/null +++ b/db/migrations/052_add_sc_to_flujo_caja.sql @@ -0,0 +1 @@ +ALTER TABLE flujo_caja ADD COLUMN sc DECIMAL(10, 2) DEFAULT 0.00; \ No newline at end of file diff --git a/finanzas.php b/finanzas.php index 3a49e28..2cc1a08 100644 --- a/finanzas.php +++ b/finanzas.php @@ -10,9 +10,10 @@ ini_set('error_log', 'php-error.log'); session_start(); require_once 'db/config.php'; -// Check if user is logged in and is an admin -if (!isset($_SESSION['user_id']) || !isset($_SESSION['user_role']) || $_SESSION['user_role'] !== 'Administrador') { - header('Location: pedidos.php'); +// Check if user is logged in and has an admin role +$allowed_roles = ['Administrador', 'admin']; +if (!isset($_SESSION['user_id']) || !isset($_SESSION['user_role']) || !in_array($_SESSION['user_role'], $allowed_roles)) { + header('Location: dashboard.php?error=access_denied'); exit; } diff --git a/flujo_de_caja.php b/flujo_de_caja.php index 3cf335d..f5dae5c 100644 --- a/flujo_de_caja.php +++ b/flujo_de_caja.php @@ -12,7 +12,7 @@ $end_date = "$year-$month-$days_in_month"; $columns = [ 'bcp_yape', 'b_nacion', 'interbank', 'bbva', 'otros_ingresos', 'tu1', 'tu2', 'tu3', 'fl1', 'fl2', 'fl3', - 'rc_envio', 'rc_contraent' + 'rc_envio', 'rc_contraent', 'sc' ]; // Fetch data from DB @@ -112,6 +112,7 @@ foreach ($all_days_data as $day_data) { Inversion Publicitaria RC ENVIO RC CONTRAENT + Sc Total Ingresos Total Inversion Publicitaria Recaudo final diff --git a/panel_inventario.php b/panel_inventario.php index d1b0bc0..c01ef95 100644 --- a/panel_inventario.php +++ b/panel_inventario.php @@ -4,6 +4,14 @@ if (!isset($_SESSION['user_id'])) { header('Location: login.php'); exit; } + +// Role check for inventory panel +$userRole = $_SESSION['user_role'] ?? ''; +$allowedRoles = ['Administrador', 'admin', 'Control Logistico']; +if (!in_array($userRole, $allowedRoles)) { + header('Location: dashboard.php?error=access_denied'); + exit(); +} require_once 'layout_header.php'; require_once 'db/config.php'; @@ -25,7 +33,6 @@ try { $stock_stmt = $pdo->query("SELECT product_id, sede_id, quantity FROM stock_sedes"); $stock_data = $stock_stmt->fetchAll(PDO::FETCH_ASSOC); - // Procesar datos para la tabla $inventario = []; foreach ($products as $product) { $inventario[$product['id']] = [ @@ -42,33 +49,56 @@ try { } } - // 3. Datos para el gráfico circular (Distribución por Sede) + // 3. Datos para gráficos $stock_por_sede_stmt = $pdo->query(" SELECT s.nombre, SUM(ss.quantity) as total_stock - FROM sedes s - JOIN stock_sedes ss ON s.id = ss.sede_id - GROUP BY s.nombre - HAVING total_stock > 0 - ORDER BY s.nombre + FROM sedes s JOIN stock_sedes ss ON s.id = ss.sede_id + GROUP BY s.nombre HAVING total_stock > 0 ORDER BY s.nombre "); $stock_por_sede = $stock_por_sede_stmt->fetchAll(PDO::FETCH_ASSOC); - $sede_chart_labels = json_encode(array_column($stock_por_sede, 'nombre')); - $sede_chart_data = json_encode(array_column($stock_por_sede, 'total_stock')); + + $sede_chart_labels_array = array_column($stock_por_sede, 'nombre'); + $sede_chart_data_array = array_column($stock_por_sede, 'total_stock'); + + $colores_sedes = []; + $colores_default = ['#FF6384', '#FFCE56', '#4BC0C0', '#9966FF', '#FF9F40']; + $color_map = [ + 'ALIDRV' => '#28a745', // Verde + 'Almacen Principal' => '#36A2EB' // Azul + ]; + $default_color_idx = 0; + foreach ($sede_chart_labels_array as $label) { + if (isset($color_map[$label])) { + $colores_sedes[] = $color_map[$label]; + } else { + $colores_sedes[] = $colores_default[$default_color_idx % count($colores_default)]; + $default_color_idx++; + } + } + + $sede_chart_labels = json_encode($sede_chart_labels_array); + $sede_chart_data = json_encode($sede_chart_data_array); + $sede_chart_colors = json_encode($colores_sedes); - // 4. Datos para el gráfico de barras (Stock por Producto) $stock_por_producto_stmt = $pdo->query(" SELECT p.nombre, SUM(ss.quantity) as total_stock - FROM products p - JOIN stock_sedes ss ON p.id = ss.product_id - GROUP BY p.nombre - HAVING total_stock > 0 - ORDER BY total_stock DESC - LIMIT 15 + FROM products p JOIN stock_sedes ss ON p.id = ss.product_id + GROUP BY p.nombre HAVING total_stock > 0 ORDER BY total_stock DESC LIMIT 15 "); $stock_por_producto = $stock_por_producto_stmt->fetchAll(PDO::FETCH_ASSOC); $producto_chart_labels = json_encode(array_column($stock_por_producto, 'nombre')); $producto_chart_data = json_encode(array_column($stock_por_producto, 'total_stock')); + // 4. Datos para el historial de movimientos (últimos 50) + $movements_stmt = $pdo->query(" + SELECT sm.movement_date, p.nombre as product_name, s.nombre as sede_name, sm.quantity, sm.type + FROM stock_movements sm + JOIN products p ON sm.product_id = p.id + JOIN sedes s ON sm.sede_id = s.id + ORDER BY sm.created_at DESC + LIMIT 50 + "); + $movements = $movements_stmt->fetchAll(PDO::FETCH_ASSOC); } catch (PDOException $e) { echo "
Error al conectar o consultar la base de datos: " . $e->getMessage() . "
"; @@ -78,7 +108,13 @@ try { ?>
-

Dashboard de Inventario

+
+

Dashboard de Inventario

+
+ Registrar Entrada + Registrar Salida +
+
@@ -112,9 +148,7 @@ try {
-
-
Distribución por Sede
-
+
Distribución por Sede
@@ -122,22 +156,15 @@ try {
-
-
Top 15 Productos con Más Stock
-
-
- -
+
Top 15 Productos con Más Stock
+
- -
-
-
Inventario por Sede
-
+
+
Inventario por Sede
@@ -152,9 +179,7 @@ try { - - - + @@ -171,90 +196,78 @@ try { + + +
+
Historial de Movimientos Recientes
+
+
+
No hay productos para mostrar.
No hay productos.
+ + + + + + + + + + + + + + + + + + + + + + + + +
FechaProductoSedeCantidadTipo
No hay movimientos registrados.
+ + Entrada + + Salida + +
+
+
+
- - - \ No newline at end of file + diff --git a/pedidos_en_transito.php b/pedidos_en_transito.php index a6fbc10..b876c47 100644 --- a/pedidos_en_transito.php +++ b/pedidos_en_transito.php @@ -176,16 +176,7 @@ include 'layout_header.php'; - - 🔎'; - } - ?> - + diff --git a/registro_entrada.php b/registro_entrada.php index 767eb0e..8a5b73c 100644 --- a/registro_entrada.php +++ b/registro_entrada.php @@ -11,56 +11,64 @@ if ($_SERVER["REQUEST_METHOD"] == "POST") { $product_id = filter_input(INPUT_POST, 'product_id', FILTER_VALIDATE_INT); $sede_id = filter_input(INPUT_POST, 'sede_id', FILTER_VALIDATE_INT); $quantity = filter_input(INPUT_POST, 'quantity', FILTER_VALIDATE_INT); + $movement_date = filter_input(INPUT_POST, 'movement_date'); - if ($product_id && $sede_id && $quantity) { + if ($product_id && $sede_id && $quantity && $movement_date) { try { - $pdo = new PDO("mysql:host=" . DB_HOST . ";dbname=" . DB_NAME . ";charset=utf8", DB_USER, DB_PASS); - $pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION); + $pdo = db(); + $pdo->beginTransaction(); - // Verificar si ya existe un registro para este producto en esta sede + // 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(); if ($existing_stock) { - // Si existe, actualizar la cantidad $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']]); } else { - // Si no existe, insertar un nuevo registro $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]); } - $message = "¡Inventario actualizado correctamente!"; + // 2. 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 = "¡Inventario actualizado y movimiento registrado correctamente!"; } catch (PDOException $e) { + $pdo->rollBack(); $error = "Error al actualizar el inventario: " . $e->getMessage(); } } else { - $error = "Por favor, complete todos los campos del formulario."; + $error = "Por favor, complete todos los campos del formulario, incluyendo la fecha."; } } -// Obtener productos y sedes de la base de datos para los dropdowns +// Obtener productos y sedes para los dropdowns $products = []; $sedes = []; - try { - $pdo = new PDO("mysql:host=" . DB_HOST . ";dbname=" . DB_NAME . ";charset=utf8", DB_USER, DB_PASS); - $pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION); - + $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(); } - ?>
@@ -84,6 +92,10 @@ try {
+
+ + +
+