Compare commits
1 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d9d2abcbfc |
36
agregar_ciudad.php
Normal file
@ -0,0 +1,36 @@
|
||||
<?php
|
||||
session_start();
|
||||
if (!isset($_SESSION["user_id"])) {
|
||||
header("Location: auth/login.php");
|
||||
exit();
|
||||
}
|
||||
require_once 'includes/header.php';
|
||||
?>
|
||||
|
||||
<div class="container-fluid">
|
||||
<div class="d-sm-flex justify-content-between align-items-center mb-4">
|
||||
<h3 class="text-dark mb-0">Agregar Nueva Ciudad</h3>
|
||||
</div>
|
||||
|
||||
<div class="card shadow">
|
||||
<div class="card-header py-3">
|
||||
<p class="text-primary m-0 fw-bold">Datos de la Ciudad</p>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<form action="handle_agregar_ciudad.php" method="post">
|
||||
<div class="row">
|
||||
<div class="col-md-12">
|
||||
<div class="mb-3">
|
||||
<label for="nombre" class="form-label">Nombre de la Ciudad</label>
|
||||
<input type="text" class="form-control" id="nombre" name="nombre" required>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<button type="submit" class="btn btn-primary">Agregar Ciudad</button>
|
||||
<a href="ciudades.php" class="btn btn-secondary">Cancelar</a>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<?php require_once 'includes/footer.php'; ?>
|
||||
54
agregar_colaborador.php
Normal file
@ -0,0 +1,54 @@
|
||||
<?php
|
||||
session_start();
|
||||
if (!isset($_SESSION["user_id"])) {
|
||||
header("Location: auth/login.php");
|
||||
exit();
|
||||
}
|
||||
|
||||
if (!isset($_SESSION['user_rol']) || $_SESSION['user_rol'] !== 'Administrador General') {
|
||||
$_SESSION['error_message'] = 'No tienes permiso para acceder a esta página.';
|
||||
header('Location: colaboradores.php');
|
||||
exit();
|
||||
}
|
||||
require_once 'includes/header.php';
|
||||
?>
|
||||
|
||||
<div class="container-fluid">
|
||||
<div class="d-sm-flex justify-content-between align-items-center mb-4">
|
||||
<h3 class="text-dark mb-0">Agregar Nuevo Colaborador</h3>
|
||||
</div>
|
||||
|
||||
<div class="card shadow">
|
||||
<div class="card-header py-3">
|
||||
<p class="text-primary m-0 fw-bold">Datos del Colaborador</p>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<form action="handle_agregar_colaborador.php" method="post">
|
||||
<div class="mb-3">
|
||||
<label for="nombre" class="form-label">Nombre Completo</label>
|
||||
<input type="text" class="form-control" id="nombre" name="nombre" required>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label for="email" class="form-label">Correo Electrónico</label>
|
||||
<input type="email" class="form-control" id="email" name="email" required>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label for="password" class="form-label">Contraseña</label>
|
||||
<input type="password" class="form-control" id="password" name="password" required>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label for="rol" class="form-label">Rol</label>
|
||||
<select class="form-control" id="rol" name="rol" required>
|
||||
<option value="" disabled selected>Selecciona un rol</option>
|
||||
<option value="Administrador General">Administrador General</option>
|
||||
<option value="Encargado de Stock">Encargado de Stock</option>
|
||||
</select>
|
||||
</div>
|
||||
<button type="submit" class="btn btn-primary">Agregar Colaborador</button>
|
||||
<a href="colaboradores.php" class="btn btn-secondary">Cancelar</a>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<?php require_once 'includes/footer.php'; ?>
|
||||
77
agregar_producto.php
Normal file
@ -0,0 +1,77 @@
|
||||
<?php
|
||||
require_once 'includes/header.php';
|
||||
|
||||
// Fetch cities for the stock inputs
|
||||
$ciudades = [];
|
||||
try {
|
||||
$pdo = db();
|
||||
$stmt = $pdo->query("SELECT id, nombre FROM ciudades ORDER BY nombre");
|
||||
$ciudades = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
||||
} catch (PDOException $e) {
|
||||
die("Error: No se pudieron cargar las ciudades. Por favor, contacte al administrador.");
|
||||
}
|
||||
|
||||
?>
|
||||
|
||||
<div class="container-fluid">
|
||||
<h3 class="text-dark mb-4">Agregar Nuevo Producto</h3>
|
||||
|
||||
<div class="card shadow mb-4">
|
||||
<div class="card-header py-3">
|
||||
<p class="text-primary m-0 fw-bold">Detalles del Producto</p>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<form action="handle_agregar_producto.php" method="post">
|
||||
<div class="mb-3">
|
||||
<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="descripcion"><strong>Descripción</strong></label>
|
||||
<textarea class="form-control" id="descripcion" name="descripcion" rows="4"></textarea>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-md-6">
|
||||
<div class="mb-3">
|
||||
<label class="form-label" for="costo"><strong>Costo</strong></label>
|
||||
<input class="form-control" type="number" id="costo" name="costo" step="0.01" required>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<div class="mb-3">
|
||||
<label class="form-label" for="precio_venta"><strong>Precio de Venta</strong></label>
|
||||
<input class="form-control" type="number" id="precio_venta" name="precio_venta" step="0.01" required>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<hr>
|
||||
|
||||
<div class="mb-3">
|
||||
<h5 class="text-dark mb-3">Inventario por Ciudad</h5>
|
||||
<div class="row">
|
||||
<?php if (empty($ciudades)): ?>
|
||||
<div class="col">
|
||||
<p class="text-center">No hay ciudades registradas. <a href="#">Agrega una ciudad</a> para continuar.</p>
|
||||
</div>
|
||||
<?php else: ?>
|
||||
<?php foreach ($ciudades as $ciudad): ?>
|
||||
<div class="col-md-4 mb-3">
|
||||
<label class="form-label" for="stock_ciudad_<?php echo $ciudad['id']; ?>"><strong><?php echo htmlspecialchars($ciudad['nombre']); ?></strong></label>
|
||||
<input class="form-control" type="number" id="stock_ciudad_<?php echo $ciudad['id']; ?>" name="stock_ciudad[<?php echo $ciudad['id']; ?>]" value="0" min="0">
|
||||
</div>
|
||||
<?php endforeach; ?>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mb-3 mt-4">
|
||||
<button class="btn btn-primary btn-sm" type="submit" <?php if (empty($ciudades)) echo 'disabled'; ?>>Guardar Producto</button>
|
||||
<a class="btn btn-secondary btn-sm" href="productos.php">Cancelar</a>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<?php require_once 'includes/footer.php'; ?>
|
||||
321
assets/css/style.css
Normal file
@ -0,0 +1,321 @@
|
||||
:root {
|
||||
--color-principal: #1E88E5;
|
||||
--color-secundario: #43A047;
|
||||
--color-fondo: #F9FAFB;
|
||||
--color-texto: #333;
|
||||
--color-texto-claro: #FFF;
|
||||
--color-borde: #E0E0E0;
|
||||
--font-principal: 'Inter', 'Roboto', sans-serif;
|
||||
}
|
||||
|
||||
*,
|
||||
*::before,
|
||||
*::after {
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
body {
|
||||
margin: 0;
|
||||
font-family: var(--font-principal);
|
||||
background-color: #f0f2f5; /* Un gris un poco más visible */
|
||||
color: #1a1a1a; /* Un negro más intenso */
|
||||
font-size: 16px;
|
||||
line-height: 1.6;
|
||||
}
|
||||
|
||||
a {
|
||||
color: var(--color-principal);
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
a:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
.content-wrapper {
|
||||
padding: 2rem;
|
||||
background-color: #ffffff; /* Fondo blanco explícito */
|
||||
border-radius: 8px;
|
||||
}
|
||||
|
||||
/* --- Formularios --- */
|
||||
.form-container {
|
||||
max-width: 600px;
|
||||
margin: 0 auto;
|
||||
padding: 2rem;
|
||||
background-color: #fff;
|
||||
border: 1px solid var(--color-borde);
|
||||
border-radius: 8px;
|
||||
}
|
||||
|
||||
.form-group {
|
||||
margin-bottom: 1.5rem;
|
||||
}
|
||||
|
||||
.form-group label {
|
||||
display: block;
|
||||
font-weight: 500;
|
||||
margin-bottom: 0.5rem;
|
||||
}
|
||||
|
||||
.form-control {
|
||||
display: block;
|
||||
width: 100%;
|
||||
padding: 0.75rem;
|
||||
font-size: 1rem;
|
||||
border: 1px solid #ccc;
|
||||
border-radius: 4px;
|
||||
transition: border-color 0.2s;
|
||||
}
|
||||
|
||||
.form-control:focus {
|
||||
outline: none;
|
||||
border-color: var(--color-principal);
|
||||
}
|
||||
|
||||
/* --- Botones --- */
|
||||
.btn {
|
||||
display: inline-block;
|
||||
padding: 0.75rem 1.5rem;
|
||||
border: none;
|
||||
border-radius: 4px;
|
||||
font-size: 1rem;
|
||||
font-weight: 500;
|
||||
cursor: pointer;
|
||||
text-align: center;
|
||||
transition: background-color 0.2s;
|
||||
}
|
||||
|
||||
.btn-principal {
|
||||
background-color: var(--color-principal);
|
||||
color: var(--color-texto-claro);
|
||||
}
|
||||
|
||||
.btn-principal:hover {
|
||||
background-color: #1a73c8;
|
||||
}
|
||||
|
||||
.btn-secundario {
|
||||
background-color: var(--color-secundario);
|
||||
color: var(--color-texto-claro);
|
||||
}
|
||||
|
||||
.btn-secundario:hover {
|
||||
background-color: #388e3c;
|
||||
}
|
||||
|
||||
/* --- Tablas --- */
|
||||
.table {
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
margin-top: 2rem;
|
||||
}
|
||||
|
||||
.table th,
|
||||
.table td {
|
||||
padding: 0.75rem;
|
||||
text-align: left;
|
||||
border-bottom: 1px solid var(--color-borde);
|
||||
}
|
||||
|
||||
.table thead th {
|
||||
background-color: #f5f5f5;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
/* --- Alertas --- */
|
||||
.alert {
|
||||
padding: 1rem;
|
||||
margin-bottom: 1rem;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
.alert-error {
|
||||
background-color: #f8d7da;
|
||||
color: #721c24;
|
||||
border: 1px solid #f5c6cb;
|
||||
}
|
||||
|
||||
.alert-success {
|
||||
background-color: #d4edda;
|
||||
color: #155724;
|
||||
border: 1px solid #c3e6cb;
|
||||
}
|
||||
|
||||
/* --- Login Page --- */
|
||||
.login-container {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
width: 100%;
|
||||
min-height: 100vh;
|
||||
padding: 2rem;
|
||||
}
|
||||
|
||||
/* Fix para botones de acciones en tablas */
|
||||
.btn-warning, .btn-warning:hover {
|
||||
color: #fff !important;
|
||||
background-color: #ffc107 !important;
|
||||
border-color: #ffc107 !important;
|
||||
}
|
||||
|
||||
.btn-danger, .btn-danger:hover {
|
||||
color: #fff !important;
|
||||
background-color: #dc3545 !important;
|
||||
border-color: #dc3545 !important;
|
||||
}
|
||||
|
||||
.btn-warning i, .btn-danger i {
|
||||
color: #fff !important;
|
||||
}
|
||||
|
||||
/* --- Estilos del Panel de Control (Dashboard) --- */
|
||||
.welcome-title {
|
||||
font-size: 2.5rem;
|
||||
font-weight: 700;
|
||||
color: #333;
|
||||
margin-bottom: 0.5rem;
|
||||
}
|
||||
|
||||
.welcome-subtitle {
|
||||
font-size: 1.2rem;
|
||||
color: #666;
|
||||
margin-bottom: 2rem;
|
||||
}
|
||||
|
||||
.info-card {
|
||||
background-color: #fff;
|
||||
border: 1px solid var(--color-borde);
|
||||
border-radius: 12px;
|
||||
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.05);
|
||||
transition: transform 0.2s ease, box-shadow 0.2s ease;
|
||||
}
|
||||
|
||||
.info-card:hover {
|
||||
transform: translateY(-5px);
|
||||
box-shadow: 0 8px 20px rgba(0, 0, 0, 0.08);
|
||||
}
|
||||
|
||||
.info-card .card-body {
|
||||
padding: 1.5rem;
|
||||
}
|
||||
|
||||
.info-card .card-title {
|
||||
font-size: 1rem;
|
||||
font-weight: 600;
|
||||
color: #555;
|
||||
text-transform: uppercase;
|
||||
margin-bottom: 0.5rem;
|
||||
}
|
||||
|
||||
.info-card .card-text {
|
||||
font-size: 2rem;
|
||||
font-weight: 700;
|
||||
color: var(--color-principal);
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.icon-circle {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 50px;
|
||||
height: 50px;
|
||||
border-radius: 50%;
|
||||
background-color: #e3f2fd; /* Un azul muy claro */
|
||||
}
|
||||
|
||||
.icon-circle svg {
|
||||
color: var(--color-principal);
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
}
|
||||
|
||||
/* Estilo para la tarjeta que es un enlace */
|
||||
.card-link {
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.card-link .card-title {
|
||||
color: #333;
|
||||
font-size: 1.1rem;
|
||||
font-weight: 600;
|
||||
text-transform: none;
|
||||
}
|
||||
|
||||
.card-link .card-text {
|
||||
font-size: 1rem;
|
||||
font-weight: 400;
|
||||
color: #666;
|
||||
}
|
||||
|
||||
.card-link:hover .card-title {
|
||||
color: var(--color-principal);
|
||||
}
|
||||
|
||||
/* --- Estilos del Menú Lateral (Sidebar) --- */
|
||||
.sidebar {
|
||||
min-width: 250px;
|
||||
max-width: 250px;
|
||||
background-color: #00754A;
|
||||
color: #fff;
|
||||
min-height: 100vh;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.sidebar-heading {
|
||||
padding: 1.25rem;
|
||||
font-size: 1.5rem;
|
||||
font-weight: 700;
|
||||
text-align: center;
|
||||
border-bottom: 1px solid rgba(255, 255, 255, 0.1);
|
||||
}
|
||||
|
||||
.sidebar .nav-link {
|
||||
color: #fff;
|
||||
padding: 0.8rem 1.25rem; /* Un poco más de padding vertical */
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin: 0.25rem 0.75rem; /* Margen para separar los elementos */
|
||||
border-radius: 8px; /* Bordes redondeados */
|
||||
transition: background-color 0.3s ease, color 0.3s ease;
|
||||
}
|
||||
|
||||
.sidebar .nav-link i {
|
||||
margin-right: 0.85rem;
|
||||
width: 20px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.sidebar .nav-item.active .nav-link,
|
||||
.sidebar .nav-link:hover {
|
||||
background-color: rgba(255, 255, 255, 0.1); /* Un fondo más sutil al pasar el ratón o si está activo */
|
||||
color: #fff;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
/* Separar el último elemento (Cerrar Sesión) */
|
||||
.sidebar .nav-item.mt-auto {
|
||||
margin-top: auto;
|
||||
border-top: 1px solid rgba(255, 255, 255, 0.1);
|
||||
}
|
||||
|
||||
/* Contenido Principal */
|
||||
.main-content {
|
||||
flex-grow: 1;
|
||||
padding: 2rem;
|
||||
background-color: var(--color-fondo);
|
||||
}
|
||||
|
||||
/* Forzar color de texto en el cuerpo de las tablas */
|
||||
.table tbody td {
|
||||
color: #000 !important;
|
||||
}
|
||||
|
||||
/* Forzar color de texto en el encabezado de las tablas */
|
||||
.table thead th {
|
||||
color: #000 !important;
|
||||
}
|
||||
|
||||
|
||||
|
After Width: | Height: | Size: 232 KiB |
|
After Width: | Height: | Size: 729 KiB |
|
After Width: | Height: | Size: 48 KiB |
|
After Width: | Height: | Size: 215 KiB |
BIN
assets/pasted-20251009-153405-5efa9042.png
Normal file
|
After Width: | Height: | Size: 45 KiB |
BIN
assets/pasted-20251009-153721-c3ee8a0d.png
Normal file
|
After Width: | Height: | Size: 63 KiB |
BIN
assets/pasted-20251009-153854-67f41483.png
Normal file
|
After Width: | Height: | Size: 32 KiB |
BIN
assets/pasted-20251009-154502-eaaa16da.png
Normal file
|
After Width: | Height: | Size: 36 KiB |
BIN
assets/pasted-20251009-155241-929a256b.png
Normal file
|
After Width: | Height: | Size: 4.5 KiB |
BIN
assets/pasted-20251009-155443-716779fc.png
Normal file
|
After Width: | Height: | Size: 5.4 KiB |
BIN
assets/pasted-20251009-155643-bc35fc15.png
Normal file
|
After Width: | Height: | Size: 5.5 KiB |
BIN
assets/pasted-20251009-161759-d7c8e086.png
Normal file
|
After Width: | Height: | Size: 16 KiB |
BIN
assets/pasted-20251009-170151-443ce4d4.png
Normal file
|
After Width: | Height: | Size: 16 KiB |
BIN
assets/pasted-20251009-172318-8cc07b61.png
Normal file
|
After Width: | Height: | Size: 42 KiB |
BIN
assets/pasted-20251009-172723-72289218.png
Normal file
|
After Width: | Height: | Size: 21 KiB |
BIN
assets/pasted-20251009-173459-c300009e.png
Normal file
|
After Width: | Height: | Size: 42 KiB |
BIN
assets/pasted-20251009-174008-c55bef89.png
Normal file
|
After Width: | Height: | Size: 38 KiB |
BIN
assets/pasted-20251009-174154-f411e099.png
Normal file
|
After Width: | Height: | Size: 30 KiB |
BIN
assets/pasted-20251009-174347-3038db6f.png
Normal file
|
After Width: | Height: | Size: 6.9 KiB |
BIN
assets/pasted-20251010-015046-225fb527.png
Normal file
|
After Width: | Height: | Size: 93 KiB |
BIN
assets/pasted-20251010-020843-4b62e505.png
Normal file
|
After Width: | Height: | Size: 298 KiB |
BIN
assets/pasted-20251011-070910-74d1faaa.png
Normal file
|
After Width: | Height: | Size: 57 KiB |
BIN
assets/pasted-20251011-071347-abbe92fd.png
Normal file
|
After Width: | Height: | Size: 57 KiB |
BIN
assets/pasted-20251011-071720-cc2fe22e.png
Normal file
|
After Width: | Height: | Size: 84 KiB |
BIN
assets/pasted-20251013-182206-7476421e.png
Normal file
|
After Width: | Height: | Size: 86 KiB |
BIN
assets/pasted-20251024-185029-36185b3b.png
Normal file
|
After Width: | Height: | Size: 90 KiB |
BIN
assets/pasted-20251025-044828-5d93e938.png
Normal file
|
After Width: | Height: | Size: 1.2 KiB |
BIN
assets/pasted-20251117-044410-77bacded.png
Normal file
|
After Width: | Height: | Size: 43 KiB |
BIN
assets/pasted-20251125-180131-92003672.png
Normal file
|
After Width: | Height: | Size: 125 KiB |
49
auth/handle_login.php
Normal file
@ -0,0 +1,49 @@
|
||||
<?php
|
||||
session_start();
|
||||
require_once __DIR__ . '/../db/config.php';
|
||||
|
||||
if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
|
||||
header('Location: /auth/login.php');
|
||||
exit();
|
||||
}
|
||||
|
||||
$email = $_POST['email'] ?? '';
|
||||
$password = $_POST['password'] ?? '';
|
||||
|
||||
if (empty($email) || empty($password)) {
|
||||
$_SESSION['login_error'] = 'Por favor, ingresa tu correo y contraseña.';
|
||||
header('Location: /auth/login.php');
|
||||
exit();
|
||||
}
|
||||
|
||||
try {
|
||||
$pdo = db();
|
||||
$stmt = $pdo->prepare("SELECT * FROM usuarios WHERE email = :email");
|
||||
$stmt->execute(['email' => $email]);
|
||||
$user = $stmt->fetch();
|
||||
|
||||
if ($user && password_verify($password, $user['password'])) {
|
||||
// Login exitoso
|
||||
session_regenerate_id(true);
|
||||
$_SESSION['user_id'] = $user['id'];
|
||||
$_SESSION['user_nombre'] = $user['nombre'];
|
||||
$_SESSION['user_rol'] = $user['rol'];
|
||||
|
||||
// Redirigir al dashboard
|
||||
header('Location: /index.php');
|
||||
exit();
|
||||
} else {
|
||||
// Error de credenciales
|
||||
$_SESSION['login_error'] = 'El correo o la contraseña son incorrectos.';
|
||||
header('Location: /auth/login.php');
|
||||
exit();
|
||||
}
|
||||
|
||||
} catch (PDOException $e) {
|
||||
// Error de base de datos
|
||||
// En un entorno de producción, esto debería ser registrado en un log.
|
||||
$_SESSION['login_error'] = 'Ocurrió un error en el servidor. Inténtalo de nuevo más tarde.';
|
||||
// die($e->getMessage()); // Para depuración
|
||||
header('Location: /auth/login.php');
|
||||
exit();
|
||||
}
|
||||
46
auth/login.php
Normal file
@ -0,0 +1,46 @@
|
||||
<?php
|
||||
$page_title = 'Iniciar Sesión';
|
||||
// We don't want the full header here, just the HTML head part.
|
||||
// So we create a simpler header for login/public pages.
|
||||
?>
|
||||
<!DOCTYPE html>
|
||||
<html lang="es">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title><?= htmlspecialchars($page_title) ?></title>
|
||||
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
||||
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;700&display=swap" rel="stylesheet">
|
||||
<link rel="stylesheet" href="/assets/css/style.css?v=<?php echo time(); ?>">
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<div class="login-container">
|
||||
<div class="form-container">
|
||||
<h1>Iniciar Sesión</h1>
|
||||
|
||||
<?php
|
||||
session_start();
|
||||
if (isset($_SESSION['login_error'])) {
|
||||
echo '<div class="alert alert-error">' . $_SESSION['login_error'] . '</div>';
|
||||
unset($_SESSION['login_error']);
|
||||
}
|
||||
?>
|
||||
|
||||
<form action="/auth/handle_login.php" method="POST">
|
||||
<div class="form-group">
|
||||
<label for="email">Correo Electrónico</label>
|
||||
<input type="email" id="email" name="email" class="form-control" required>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="password">Contraseña</label>
|
||||
<input type="password" id="password" name="password" class="form-control" required>
|
||||
</div>
|
||||
<button type="submit" class="btn btn-principal" style="width:100%;">Ingresar</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
22
auth/logout.php
Normal file
@ -0,0 +1,22 @@
|
||||
<?php
|
||||
session_start();
|
||||
|
||||
// Destruir todas las variables de sesión.
|
||||
$_SESSION = [];
|
||||
|
||||
// Si se desea destruir la sesión completamente, borre también la cookie de sesión.
|
||||
// Nota: ¡Esto destruirá la sesión, y no solo los datos de la sesión!
|
||||
if (ini_get("session.use_cookies")) {
|
||||
$params = session_get_cookie_params();
|
||||
setcookie(session_name(), '', time() - 42000,
|
||||
$params["path"], $params["domain"],
|
||||
$params["secure"], $params["httponly"]
|
||||
);
|
||||
}
|
||||
|
||||
// Finalmente, destruir la sesión.
|
||||
session_destroy();
|
||||
|
||||
// Redirigir a la página de login
|
||||
header("Location: /auth/login.php");
|
||||
exit();
|
||||
89
ciudades.php
Normal file
@ -0,0 +1,89 @@
|
||||
<?php
|
||||
session_start();
|
||||
if (!isset($_SESSION["user_id"])) {
|
||||
header("Location: auth/login.php");
|
||||
exit();
|
||||
}
|
||||
require_once 'includes/header.php';
|
||||
|
||||
$pdo = db();
|
||||
|
||||
// Fetch all cities ordered by the 'orden' column
|
||||
$stmt = $pdo->prepare("SELECT * FROM ciudades ORDER BY orden ASC");
|
||||
$stmt->execute();
|
||||
$ciudades = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
||||
?>
|
||||
|
||||
<div class="container-fluid">
|
||||
|
||||
<!-- Page Heading -->
|
||||
<div class="d-sm-flex justify-content-between align-items-center mb-4">
|
||||
<h3 class="text-dark mb-0">Gestión de Ciudades</h3>
|
||||
<a class="btn btn-primary btn-sm" role="button" href="agregar_ciudad.php">
|
||||
<i class="fas fa-plus fa-sm text-white-50"></i> Agregar Ciudad
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<!-- Success/Error Messages -->
|
||||
<?php if (isset($_SESSION['success_message'])): ?>
|
||||
<div class="alert alert-success alert-dismissible fade show" role="alert">
|
||||
<?php echo $_SESSION['success_message']; ?>
|
||||
<button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>
|
||||
</div>
|
||||
<?php unset($_SESSION['success_message']); ?>
|
||||
<?php endif; ?>
|
||||
<?php if (isset($_SESSION['error_message'])): ?>
|
||||
<div class="alert alert-danger alert-dismissible fade show" role="alert">
|
||||
<?php echo $_SESSION['error_message']; ?>
|
||||
<button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>
|
||||
</div>
|
||||
<?php unset($_SESSION['error_message']); ?>
|
||||
<?php endif; ?>
|
||||
|
||||
<div class="card shadow">
|
||||
<div class="card-header py-3 d-flex justify-content-between align-items-center">
|
||||
<p class="text-primary m-0 fw-bold">Listado y Orden de Ciudades</p>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<form action="handle_orden_ciudades.php" method="post">
|
||||
<div class="table-responsive">
|
||||
<table class="table table-bordered" id="dataTable">
|
||||
<thead>
|
||||
<tr>
|
||||
<th style="width: 100px;">Orden</th>
|
||||
<th>Nombre</th>
|
||||
<th>Acciones</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<?php if (count($ciudades) > 0): ?>
|
||||
<?php foreach ($ciudades as $ciudad): ?>
|
||||
<tr>
|
||||
<td>
|
||||
<input type="number" name="orden[<?php echo $ciudad['id']; ?>]" value="<?php echo htmlspecialchars($ciudad['orden']); ?>" class="form-control form-control-sm text-center" style="width: 80px;">
|
||||
</td>
|
||||
<td><?php echo htmlspecialchars($ciudad['nombre']); ?></td>
|
||||
<td>
|
||||
<a href="editar_ciudad.php?id=<?php echo $ciudad['id']; ?>" class="btn btn-warning btn-sm"><i class="fas fa-edit"></i> Editar</a>
|
||||
<a href="eliminar_ciudad.php?id=<?php echo $ciudad['id']; ?>" class="btn btn-danger btn-sm" onclick="return confirm('¿Estás seguro de que quieres eliminar esta ciudad?');"><i class="fas fa-trash"></i> Eliminar</a>
|
||||
</td>
|
||||
</tr>
|
||||
<?php endforeach; ?>
|
||||
<?php else: ?>
|
||||
<tr>
|
||||
<td colspan="3" class="text-center">No hay ciudades registradas.</td>
|
||||
</tr>
|
||||
<?php endif; ?>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<div class="text-end mt-3">
|
||||
<button type="submit" class="btn btn-success"><i class="fas fa-save"></i> Guardar Orden</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<?php require_once 'includes/footer.php'; ?>
|
||||
|
||||
160
colaboradores.php
Normal file
@ -0,0 +1,160 @@
|
||||
<?php
|
||||
session_start();
|
||||
if (!isset($_SESSION["user_id"])) {
|
||||
header("Location: auth/login.php");
|
||||
exit();
|
||||
}
|
||||
require_once 'includes/header.php';
|
||||
|
||||
// Search term
|
||||
$search_term = isset($_GET['search']) ? trim($_GET['search']) : '';
|
||||
|
||||
// Pagination settings
|
||||
$items_per_page = 10;
|
||||
$page = isset($_GET['page']) && is_numeric($_GET['page']) ? (int)$_GET['page'] : 1;
|
||||
$offset = ($page - 1) * $items_per_page;
|
||||
|
||||
$pdo = db();
|
||||
|
||||
// Base query
|
||||
$sql_count = "SELECT COUNT(*) FROM usuarios";
|
||||
$sql = "SELECT id, nombre, email, rol FROM usuarios";
|
||||
|
||||
$params = [];
|
||||
|
||||
// Apply search filter
|
||||
if (!empty($search_term)) {
|
||||
$sql_count .= " WHERE nombre LIKE :search OR email LIKE :search";
|
||||
$sql .= " WHERE nombre LIKE :search OR email LIKE :search";
|
||||
$params[':search'] = '%' . $search_term . '%';
|
||||
}
|
||||
|
||||
// Get total number of items (filtered or not)
|
||||
$total_stmt = $pdo->prepare($sql_count);
|
||||
$total_stmt->execute($params);
|
||||
$total_items = $total_stmt->fetchColumn();
|
||||
$total_pages = ceil($total_items / $items_per_page);
|
||||
|
||||
// Add ordering and pagination to the main query
|
||||
$sql .= " ORDER BY id DESC LIMIT :limit OFFSET :offset";
|
||||
|
||||
$stmt = $pdo->prepare($sql);
|
||||
|
||||
// Bind search param if it exists
|
||||
if (!empty($search_term)) {
|
||||
$stmt->bindValue(':search', $params[':search'], PDO::PARAM_STR);
|
||||
}
|
||||
|
||||
$stmt->bindValue(':limit', $items_per_page, PDO::PARAM_INT);
|
||||
$stmt->bindValue(':offset', $offset, PDO::PARAM_INT);
|
||||
$stmt->execute();
|
||||
$usuarios = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
||||
?>
|
||||
|
||||
<div class="container-fluid">
|
||||
|
||||
<!-- Page Heading -->
|
||||
<div class="d-sm-flex justify-content-between align-items-center mb-4">
|
||||
<h3 class="text-dark mb-0">Gestión de Colaboradores</h3>
|
||||
<?php if (isset($_SESSION['user_rol']) && $_SESSION['user_rol'] === 'Administrador General'): ?>
|
||||
<a class="btn btn-primary btn-sm" role="button" href="agregar_colaborador.php">
|
||||
<i class="fas fa-plus fa-sm text-white-50"></i> Agregar Colaborador
|
||||
</a>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
|
||||
<!-- Success/Error Messages -->
|
||||
<?php if (isset($_SESSION['success_message'])): ?>
|
||||
<div class="alert alert-success alert-dismissible fade show" role="alert">
|
||||
<?php echo $_SESSION['success_message']; ?>
|
||||
<button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>
|
||||
</div>
|
||||
<?php unset($_SESSION['success_message']); ?>
|
||||
<?php endif; ?>
|
||||
<?php if (isset($_SESSION['error_message'])): ?>
|
||||
<div class="alert alert-danger alert-dismissible fade show" role="alert">
|
||||
<?php echo $_SESSION['error_message']; ?>
|
||||
<button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>
|
||||
</div>
|
||||
<?php unset($_SESSION['error_message']); ?>
|
||||
<?php endif; ?>
|
||||
|
||||
<!-- Search Form -->
|
||||
<div class="card shadow mb-4">
|
||||
<div class="card-body">
|
||||
<form action="colaboradores.php" method="get" class="form-inline">
|
||||
<div class="input-group">
|
||||
<input type="text" name="search" class="form-control" placeholder="Buscar por nombre o email..." value="<?php echo isset($_GET['search']) ? htmlspecialchars($_GET['search']) : ''; ?>">
|
||||
<button class="btn btn-primary" type="submit">Buscar</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card shadow">
|
||||
<div class="card-header py-3">
|
||||
<p class="text-primary m-0 fw-bold">Listado de Colaboradores</p>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<div class="table-responsive table mt-2" id="dataTable" role="grid" aria-describedby="dataTable_info">
|
||||
<table class="table my-0" id="dataTable">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>ID</th>
|
||||
<th>Nombre</th>
|
||||
<th>Email</th>
|
||||
<th>Rol</th>
|
||||
<th>Acciones</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<?php if (count($usuarios) > 0): ?>
|
||||
<?php foreach ($usuarios as $usuario): ?>
|
||||
<tr>
|
||||
<td><?php echo htmlspecialchars($usuario['id']); ?></td>
|
||||
<td><?php echo htmlspecialchars($usuario['nombre']); ?></td>
|
||||
<td><?php echo htmlspecialchars($usuario['email']); ?></td>
|
||||
<td><?php echo htmlspecialchars($usuario['rol']); ?></td>
|
||||
<td>
|
||||
<?php if (isset($_SESSION['user_rol']) && $_SESSION['user_rol'] === 'Administrador General'): ?>
|
||||
<a href="editar_colaborador.php?id=<?php echo $usuario['id']; ?>" class="btn btn-warning btn-sm"><i class="fas fa-edit"></i> Editar</a>
|
||||
<a href="eliminar_colaborador.php?id=<?php echo $usuario['id']; ?>" class="btn btn-danger btn-sm" onclick="return confirm('¿Estás seguro de que quieres eliminar este colaborador?');"><i class="fas fa-trash"></i> Eliminar</a>
|
||||
<?php else: ?>
|
||||
<span class="text-muted">No tienes permisos</span>
|
||||
<?php endif; ?>
|
||||
</td>
|
||||
</tr>
|
||||
<?php endforeach; ?>
|
||||
<?php else: ?>
|
||||
<tr>
|
||||
<td colspan="5" class="text-center">No hay colaboradores que coincidan con la búsqueda.</td>
|
||||
</tr>
|
||||
<?php endif; ?>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<!-- Pagination Controls -->
|
||||
<div class="d-flex justify-content-center">
|
||||
<nav>
|
||||
<ul class="pagination">
|
||||
<?php if ($page > 1): ?>
|
||||
<li class="page-item"><a class="page-link" href="?page=<?php echo $page - 1; ?><?php echo isset($_GET['search']) ? '&search=' . htmlspecialchars($_GET['search']) : ''; ?>">Anterior</a></li>
|
||||
<?php endif; ?>
|
||||
|
||||
<?php for ($i = 1; $i <= $total_pages; $i++): ?>
|
||||
<li class="page-item <?php echo ($i == $page) ? 'active' : ''; ?>">
|
||||
<a class="page-link" href="?page=<?php echo $i; ?><?php echo isset($_GET['search']) ? '&search=' . htmlspecialchars($_GET['search']) : ''; ?>"><?php echo $i; ?></a>
|
||||
</li>
|
||||
<?php endfor; ?>
|
||||
|
||||
<?php if ($page < $total_pages): ?>
|
||||
<li class="page-item"><a class="page-link" href="?page=<?php echo $page + 1; ?><?php echo isset($_GET['search']) ? '&search=' . htmlspecialchars($_GET['search']) : ''; ?>">Siguiente</a></li>
|
||||
<?php endif; ?>
|
||||
</ul>
|
||||
</nav>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<?php require_once 'includes/footer.php'; ?>
|
||||
105
confirmacion_stock.php
Normal file
@ -0,0 +1,105 @@
|
||||
<?php
|
||||
require_once 'includes/header.php';
|
||||
require_once 'db/config.php';
|
||||
|
||||
$pdo = db();
|
||||
|
||||
// Fetch users
|
||||
$stmt_usuarios = $pdo->query('SELECT id, nombre FROM usuarios ORDER BY nombre');
|
||||
$usuarios = $stmt_usuarios->fetchAll(PDO::FETCH_ASSOC);
|
||||
|
||||
// Fetch cities
|
||||
$stmt_ciudades = $pdo->query('SELECT id, nombre FROM ciudades ORDER BY nombre');
|
||||
$ciudades = $stmt_ciudades->fetchAll(PDO::FETCH_ASSOC);
|
||||
|
||||
// Fetch confirmations
|
||||
$sql = "
|
||||
SELECT
|
||||
sc.fecha_hora,
|
||||
sc.fecha_actualizacion,
|
||||
u.nombre AS usuario_nombre,
|
||||
GROUP_CONCAT(c.nombre SEPARATOR ', ') AS ciudades
|
||||
FROM stock_confirmaciones sc
|
||||
JOIN usuarios u ON sc.usuario_id = u.id
|
||||
LEFT JOIN stock_confirmacion_ciudades scc ON sc.id = scc.confirmacion_id
|
||||
LEFT JOIN ciudades c ON scc.ciudad_id = c.id
|
||||
GROUP BY sc.id
|
||||
ORDER BY sc.fecha_actualizacion DESC, sc.fecha_hora DESC
|
||||
";
|
||||
$stmt_confirmaciones = $pdo->prepare($sql);
|
||||
$stmt_confirmaciones->execute();
|
||||
$confirmaciones = $stmt_confirmaciones->fetchAll(PDO::FETCH_ASSOC);
|
||||
|
||||
?>
|
||||
|
||||
<div class="container mt-4">
|
||||
<h1>Reporte de Entregas</h1>
|
||||
<p>Seleccione su usuario, la fecha de entrega, las ciudades y presione confirmar para registrar la entrega.</p>
|
||||
|
||||
<div class="card mb-4">
|
||||
<div class="card-body">
|
||||
<h5 class="card-title">Registrar Entrega</h5>
|
||||
<form action="handle_confirmacion_stock.php" method="POST">
|
||||
<div class="form-group">
|
||||
<label for="usuario_id">Usuario</label>
|
||||
<select class="form-control" id="usuario_id" name="usuario_id" required>
|
||||
<option value="">Seleccione un usuario</option>
|
||||
<?php foreach ($usuarios as $usuario) : ?>
|
||||
<option value="<?php echo htmlspecialchars($usuario['id']); ?>"><?php echo htmlspecialchars($usuario['nombre']); ?></option>
|
||||
<?php endforeach; ?>
|
||||
</select>
|
||||
</div>
|
||||
<div class="form-group mt-3">
|
||||
<label for="fecha_actualizacion">Fecha de Entrega</label>
|
||||
<input type="date" class="form-control" id="fecha_actualizacion" name="fecha_actualizacion" value="<?php echo date('Y-m-d'); ?>" required>
|
||||
</div>
|
||||
<div class="form-group mt-3">
|
||||
<label for="ciudad_id">Ciudad</label>
|
||||
<select class="form-control" id="ciudad_id" name="ciudad_id" required>
|
||||
<option value="">Seleccione 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 mt-3">Confirmar</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<div class="card-body">
|
||||
<h5 class="card-title">Historial de Entregas</h5>
|
||||
<table class="table table-striped">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Fecha de Entrega</th>
|
||||
<th>Fecha y Hora de Registro</th>
|
||||
<th>Usuario</th>
|
||||
<th>Ciudades</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<?php if (empty($confirmaciones)) : ?>
|
||||
<tr>
|
||||
<td colspan="4" class="text-center">No hay entregas registradas.</td>
|
||||
</tr>
|
||||
<?php else : ?>
|
||||
<?php foreach ($confirmaciones as $confirmacion) : ?>
|
||||
<tr>
|
||||
<td><?php echo htmlspecialchars($confirmacion['fecha_actualizacion'] ? date('d/m/Y', strtotime($confirmacion['fecha_actualizacion'])) : 'No especificada'); ?></td>
|
||||
<td><?php echo htmlspecialchars(date('d/m/Y H:i:s', strtotime($confirmacion['fecha_hora']))); ?></td>
|
||||
<td><?php echo htmlspecialchars($confirmacion['usuario_nombre']); ?></td>
|
||||
<td><?php echo htmlspecialchars($confirmacion['ciudades'] ?? 'N/A'); ?></td>
|
||||
</tr>
|
||||
<?php endforeach; ?>
|
||||
<?php endif; ?>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<?php
|
||||
require_once 'includes/footer.php';
|
||||
?>
|
||||
@ -5,6 +5,11 @@ define('DB_NAME', 'app_30953');
|
||||
define('DB_USER', 'app_30953');
|
||||
define('DB_PASS', 'e45f2778-db1f-450c-99c6-29efb4601472');
|
||||
|
||||
// Iniciar la sesión si no está activa
|
||||
if (session_status() == PHP_SESSION_NONE) {
|
||||
session_start();
|
||||
}
|
||||
|
||||
function db() {
|
||||
static $pdo;
|
||||
if (!$pdo) {
|
||||
@ -12,6 +17,31 @@ function db() {
|
||||
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
|
||||
PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
|
||||
]);
|
||||
$pdo->exec("SET time_zone = '-05:00'");
|
||||
}
|
||||
return $pdo;
|
||||
}
|
||||
|
||||
/**
|
||||
* Comprueba si el usuario ha iniciado sesión.
|
||||
* @return bool
|
||||
*/
|
||||
function is_logged_in() {
|
||||
return isset($_SESSION['user_id']);
|
||||
}
|
||||
|
||||
/**
|
||||
* Devuelve el ID del usuario que ha iniciado sesión.
|
||||
* @return int|null
|
||||
*/
|
||||
function get_current_user_id() {
|
||||
return $_SESSION['user_id'] ?? null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Devuelve el nombre de usuario del usuario que ha iniciado sesión.
|
||||
* @return string|null
|
||||
*/
|
||||
function get_current_user_name() {
|
||||
return $_SESSION['user_name'] ?? null;
|
||||
}
|
||||
424
db/setup.php
Normal file
@ -0,0 +1,424 @@
|
||||
<?php
|
||||
require_once __DIR__ . '/config.php';
|
||||
|
||||
try {
|
||||
$pdo = db();
|
||||
echo "Conexión a la base de datos exitosa.\n";
|
||||
|
||||
$sql_statements = [
|
||||
"CREATE TABLE IF NOT EXISTS `usuarios` (
|
||||
`id` INT AUTO_INCREMENT PRIMARY KEY,
|
||||
`nombre` VARCHAR(255) NOT NULL,
|
||||
`email` VARCHAR(255) NOT NULL UNIQUE,
|
||||
`password` VARCHAR(255) NOT NULL,
|
||||
`rol` ENUM('Administrador General', 'Encargado de Stock') NOT NULL,
|
||||
`created_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;",
|
||||
|
||||
"CREATE TABLE IF NOT EXISTS `productos` (
|
||||
`id` INT AUTO_INCREMENT PRIMARY KEY,
|
||||
`sku` VARCHAR(100) NOT NULL UNIQUE,
|
||||
`nombre` VARCHAR(255) NOT NULL,
|
||||
`categoria` VARCHAR(100),
|
||||
`unidad` VARCHAR(50),
|
||||
`precio_venta` DECIMAL(10, 2) NOT NULL DEFAULT 0.00,
|
||||
`costo` DECIMAL(10, 2) NOT NULL DEFAULT 0.00,
|
||||
`codigo_barras` VARCHAR(255),
|
||||
`activo` BOOLEAN NOT NULL DEFAULT TRUE,
|
||||
`descripcion` TEXT,
|
||||
`created_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;",
|
||||
|
||||
"CREATE TABLE IF NOT EXISTS `ciudades` (
|
||||
`id` INT AUTO_INCREMENT PRIMARY KEY,
|
||||
`nombre` VARCHAR(100) NOT NULL UNIQUE,
|
||||
`codigo` VARCHAR(20)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;",
|
||||
|
||||
"CREATE TABLE IF NOT EXISTS `stock_por_ciudad` (
|
||||
`id` INT AUTO_INCREMENT PRIMARY KEY,
|
||||
`producto_id` INT NOT NULL,
|
||||
`ciudad_id` INT NOT NULL,
|
||||
`stock_actual` INT NOT NULL DEFAULT 0,
|
||||
`stock_minimo` INT NOT NULL DEFAULT 0,
|
||||
`stock_reservado` INT NOT NULL DEFAULT 0,
|
||||
FOREIGN KEY (`producto_id`) REFERENCES `productos`(`id`) ON DELETE CASCADE,
|
||||
FOREIGN KEY (`ciudad_id`) REFERENCES `ciudades`(`id`) ON DELETE CASCADE,
|
||||
UNIQUE KEY `producto_ciudad` (`producto_id`, `ciudad_id`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;",
|
||||
|
||||
"CREATE TABLE IF NOT EXISTS `movimientos` (
|
||||
`id` INT AUTO_INCREMENT PRIMARY KEY,
|
||||
`tipo` ENUM('Entrada', 'Salida', 'Transferencia', 'Reverso de Salida', 'Reverso de Entrada') NOT NULL,
|
||||
`producto_id` INT NOT NULL,
|
||||
`ciudad_origen_id` INT,
|
||||
`ciudad_destino_id` INT,
|
||||
`cantidad` INT NOT NULL,
|
||||
`fecha` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
`usuario_id` INT NOT NULL,
|
||||
`documento_referencia` VARCHAR(255),
|
||||
`observacion` TEXT,
|
||||
FOREIGN KEY (`producto_id`) REFERENCES `productos`(`id`),
|
||||
FOREIGN KEY (`ciudad_origen_id`) REFERENCES `ciudades`(`id`),
|
||||
FOREIGN KEY (`ciudad_destino_id`) REFERENCES `ciudades`(`id`),
|
||||
FOREIGN KEY (`usuario_id`) REFERENCES `usuarios`(`id`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;",
|
||||
|
||||
"CREATE TABLE IF NOT EXISTS `ventas` (
|
||||
`id` INT AUTO_INCREMENT PRIMARY KEY,
|
||||
`nro_doc` VARCHAR(50) UNIQUE,
|
||||
`fecha` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
`cliente` VARCHAR(255),
|
||||
`ciudad_id` INT NOT NULL,
|
||||
`vendedor_id` INT NOT NULL,
|
||||
`estado` ENUM('Borrador', 'Confirmado', 'Anulado') NOT NULL DEFAULT 'Borrador',
|
||||
`total` DECIMAL(10, 2) NOT NULL DEFAULT 0.00,
|
||||
FOREIGN KEY (`ciudad_id`) REFERENCES `ciudades`(`id`),
|
||||
FOREIGN KEY (`vendedor_id`) REFERENCES `usuarios`(`id`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;",
|
||||
|
||||
"CREATE TABLE IF NOT EXISTS `ventas_items` (
|
||||
`id` INT AUTO_INCREMENT PRIMARY KEY,
|
||||
`venta_id` INT NOT NULL,
|
||||
`producto_id` INT NOT NULL,
|
||||
`cantidad` INT NOT NULL,
|
||||
`precio` DECIMAL(10, 2) NOT NULL,
|
||||
FOREIGN KEY (`venta_id`) REFERENCES `ventas`(`id`) ON DELETE CASCADE,
|
||||
FOREIGN KEY (`producto_id`) REFERENCES `productos`(`id`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;",
|
||||
|
||||
"CREATE TABLE IF NOT EXISTS `compras` (
|
||||
`id` INT AUTO_INCREMENT PRIMARY KEY,
|
||||
`nro_doc` VARCHAR(50) UNIQUE,
|
||||
`fecha` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
`proveedor` VARCHAR(255),
|
||||
`ciudad_id` INT NOT NULL,
|
||||
`usuario_id` INT NOT NULL,
|
||||
`estado` ENUM('Borrador', 'Confirmado', 'Anulado') NOT NULL DEFAULT 'Borrador',
|
||||
`total` DECIMAL(10, 2) NOT NULL DEFAULT 0.00,
|
||||
FOREIGN KEY (`ciudad_id`) REFERENCES `ciudades`(`id`),
|
||||
FOREIGN KEY (`usuario_id`) REFERENCES `usuarios`(`id`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;",
|
||||
|
||||
"CREATE TABLE IF NOT EXISTS `compras_items` (
|
||||
`id` INT AUTO_INCREMENT PRIMARY KEY,
|
||||
`compra_id` INT NOT NULL,
|
||||
`producto_id` INT NOT NULL,
|
||||
`cantidad` INT NOT NULL,
|
||||
`costo` DECIMAL(10, 2) NOT NULL,
|
||||
FOREIGN KEY (`compra_id`) REFERENCES `compras`(`id`) ON DELETE CASCADE,
|
||||
FOREIGN KEY (`producto_id`) REFERENCES `productos`(`id`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;",
|
||||
|
||||
"CREATE TABLE IF NOT EXISTS `bitacora` (
|
||||
`id` INT AUTO_INCREMENT PRIMARY KEY,
|
||||
`fecha_hora` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
`usuario_ejecutor` INT NOT NULL,
|
||||
`tipo_operacion` VARCHAR(100) NOT NULL,
|
||||
`entidad_origen` VARCHAR(100),
|
||||
`id_referencia_origen` INT,
|
||||
`ciudad_origen` INT,
|
||||
`ciudad_destino` INT,
|
||||
`before_after` JSON,
|
||||
`observacion` TEXT,
|
||||
FOREIGN KEY (`usuario_ejecutor`) REFERENCES `usuarios`(`id`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;",
|
||||
|
||||
"CREATE TABLE IF NOT EXISTS `stock_confirmaciones` (
|
||||
`id` INT AUTO_INCREMENT PRIMARY KEY,
|
||||
`usuario_id` INT NOT NULL,
|
||||
`fecha_hora` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
FOREIGN KEY (`usuario_id`) REFERENCES `usuarios`(`id`) ON DELETE CASCADE
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;",
|
||||
|
||||
"CREATE TABLE IF NOT EXISTS `stock_confirmacion_ciudades` (
|
||||
`confirmacion_id` INT NOT NULL,
|
||||
`ciudad_id` INT NOT NULL,
|
||||
PRIMARY KEY (`confirmacion_id`, `ciudad_id`),
|
||||
FOREIGN KEY (`confirmacion_id`) REFERENCES `stock_confirmaciones`(`id`) ON DELETE CASCADE,
|
||||
FOREIGN KEY (`ciudad_id`) REFERENCES `ciudades`(`id`) ON DELETE CASCADE
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;",
|
||||
|
||||
"CREATE TABLE IF NOT EXISTS `liquidaciones` (
|
||||
`id` INT AUTO_INCREMENT PRIMARY KEY,
|
||||
`fecha` DATE NOT NULL,
|
||||
`monto` DECIMAL(10, 2) NOT NULL,
|
||||
`ciudad_id` INT,
|
||||
FOREIGN KEY (`ciudad_id`) REFERENCES `ciudades`(`id`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;",
|
||||
|
||||
"CREATE TABLE IF NOT EXISTS `inversiones_operativas` (
|
||||
`id` INT AUTO_INCREMENT PRIMARY KEY,
|
||||
`mercaderia` VARCHAR(255) NOT NULL,
|
||||
`fecha` DATE NOT NULL,
|
||||
`cantidad` INT NOT NULL,
|
||||
`precio` DECIMAL(10, 2) NOT NULL,
|
||||
`created_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;",
|
||||
|
||||
"CREATE TABLE IF NOT EXISTS `liquidaciones_ciudades_estados` (
|
||||
`id` INT AUTO_INCREMENT PRIMARY KEY,
|
||||
`liquidacion_id` INT NOT NULL,
|
||||
`ciudad_id` INT NOT NULL,
|
||||
`fecha` DATE NOT NULL,
|
||||
`estado` ENUM('Pendiente', 'Pagado') NOT NULL DEFAULT 'Pendiente',
|
||||
FOREIGN KEY (`liquidacion_id`) REFERENCES `liquidaciones`(`id`) ON DELETE CASCADE,
|
||||
FOREIGN KEY (`ciudad_id`) REFERENCES `ciudades`(`id`) ON DELETE CASCADE,
|
||||
UNIQUE KEY `liquidacion_ciudad_fecha` (`liquidacion_id`, `ciudad_id`, `fecha`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;",
|
||||
|
||||
"CREATE TABLE IF NOT EXISTS `flujo_de_caja` (
|
||||
`id` INT AUTO_INCREMENT PRIMARY KEY,
|
||||
`fecha` DATE NOT NULL,
|
||||
`bcp_yape` DECIMAL(10, 2) DEFAULT 0.00,
|
||||
`banco_nacion` DECIMAL(10, 2) DEFAULT 0.00,
|
||||
`interbank` DECIMAL(10, 2) DEFAULT 0.00,
|
||||
`bbva` DECIMAL(10, 2) DEFAULT 0.00,
|
||||
`otros_ingresos` DECIMAL(10, 2) DEFAULT 0.00,
|
||||
`created_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;",
|
||||
|
||||
"CREATE TABLE IF NOT EXISTS `kanban_columns` (
|
||||
`id` INT AUTO_INCREMENT PRIMARY KEY,
|
||||
`nombre` VARCHAR(255) NOT NULL,
|
||||
`orden` INT NOT NULL DEFAULT 0
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;",
|
||||
|
||||
"CREATE TABLE IF NOT EXISTS `info_productos` (
|
||||
`id` INT AUTO_INCREMENT PRIMARY KEY,
|
||||
`producto_id` INT NOT NULL,
|
||||
`imagen_url` VARCHAR(255) NOT NULL,
|
||||
`texto_informativo` TEXT,
|
||||
`created_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
`column_id` INT,
|
||||
`orden` INT NOT NULL DEFAULT 0,
|
||||
FOREIGN KEY (`producto_id`) REFERENCES `productos`(`id`) ON DELETE CASCADE,
|
||||
FOREIGN KEY (`column_id`) REFERENCES `kanban_columns`(`id`) ON DELETE SET NULL
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;"
|
||||
];
|
||||
|
||||
foreach ($sql_statements as $sql) {
|
||||
$pdo->exec($sql);
|
||||
echo "Ejecutado: " . substr($sql, 0, 50) . "...\n";
|
||||
}
|
||||
echo "Todas las tablas fueron creadas exitosamente.\n";
|
||||
|
||||
// Add column_id to info_productos if it doesn't exist
|
||||
$stmt = $pdo->prepare("SELECT COUNT(*) FROM information_schema.columns WHERE table_schema = DATABASE() AND table_name = 'info_productos' AND column_name = 'column_id'");
|
||||
$stmt->execute();
|
||||
if ($stmt->fetchColumn() == 0) {
|
||||
$pdo->exec("ALTER TABLE `info_productos` ADD `column_id` INT NULL DEFAULT NULL AFTER `texto_informativo`;");
|
||||
$pdo->exec("ALTER TABLE `info_productos` ADD FOREIGN KEY (`column_id`) REFERENCES `kanban_columns`(`id`) ON DELETE SET NULL;");
|
||||
echo "Columna 'column_id' añadida a la tabla 'info_productos'.\n";
|
||||
} else {
|
||||
echo "La columna 'column_id' ya existe en la tabla 'info_productos'.\n";
|
||||
}
|
||||
|
||||
// Add orden to info_productos if it doesn't exist
|
||||
$stmt = $pdo->prepare("SELECT COUNT(*) FROM information_schema.columns WHERE table_schema = DATABASE() AND table_name = 'info_productos' AND column_name = 'orden'");
|
||||
$stmt->execute();
|
||||
if ($stmt->fetchColumn() == 0) {
|
||||
$pdo->exec("ALTER TABLE `info_productos` ADD `orden` INT NOT NULL DEFAULT 0;");
|
||||
echo "Columna 'orden' añadida a la tabla 'info_productos'.\n";
|
||||
} else {
|
||||
echo "La columna 'orden' ya existe en la tabla 'info_productos'.\n";
|
||||
}
|
||||
|
||||
// Add fecha_actualizacion to stock_confirmaciones if it doesn't exist
|
||||
$stmt = $pdo->prepare("SELECT COUNT(*) FROM information_schema.columns WHERE table_schema = DATABASE() AND table_name = 'stock_confirmaciones' AND column_name = 'fecha_actualizacion'");
|
||||
$stmt->execute();
|
||||
if ($stmt->fetchColumn() == 0) {
|
||||
$pdo->exec("ALTER TABLE `stock_confirmaciones` ADD `fecha_actualizacion` DATE DEFAULT NULL AFTER `fecha_hora`");
|
||||
echo "Columna 'fecha_actualizacion' añadida a la tabla 'stock_confirmaciones'.\n";
|
||||
} else {
|
||||
echo "La columna 'fecha_actualizacion' ya existe en la tabla 'stock_confirmaciones'.\n";
|
||||
}
|
||||
|
||||
// Add precio_liquidacion column to movimientos table if it doesn't exist
|
||||
$stmt = $pdo->prepare("SELECT COUNT(*) FROM information_schema.columns WHERE table_schema = DATABASE() AND table_name = 'movimientos' AND column_name = 'precio_liquidacion'");
|
||||
$stmt->execute();
|
||||
if ($stmt->fetchColumn() == 0) {
|
||||
$pdo->exec("ALTER TABLE `movimientos` ADD `precio_liquidacion` DECIMAL(10, 2) DEFAULT NULL;");
|
||||
echo "Columna 'precio_liquidacion' añadida a la tabla 'movimientos'.\n";
|
||||
} else {
|
||||
echo "La columna 'precio_liquidacion' ya existe en la tabla 'movimientos'.\n";
|
||||
}
|
||||
|
||||
// Add cantidad_pedidos column to movimientos table if it doesn't exist
|
||||
$stmt = $pdo->prepare("SELECT COUNT(*) FROM information_schema.columns WHERE table_schema = DATABASE() AND table_name = 'movimientos' AND column_name = 'cantidad_pedidos'");
|
||||
$stmt->execute();
|
||||
if ($stmt->fetchColumn() == 0) {
|
||||
$pdo->exec("ALTER TABLE `movimientos` ADD `cantidad_pedidos` INT DEFAULT NULL;");
|
||||
echo "Columna 'cantidad_pedidos' añadida a la tabla 'movimientos'.\n";
|
||||
} else {
|
||||
echo "La columna 'cantidad_pedidos' ya existe en la tabla 'movimientos'.\n";
|
||||
}
|
||||
|
||||
// Insertar ciudades iniciales
|
||||
$ciudades = [
|
||||
'Lima', 'Arequipa', 'Piura', 'Cusco', 'Trujillo',
|
||||
'Chiclayo', 'Iquitos', 'Tacna', 'Puno', 'Huancayo'
|
||||
];
|
||||
$stmt = $pdo->prepare("INSERT IGNORE INTO `ciudades` (`nombre`) VALUES (:nombre)");
|
||||
echo "Insertando ciudades...\n";
|
||||
foreach ($ciudades as $ciudad) {
|
||||
$stmt->execute(['nombre' => $ciudad]);
|
||||
}
|
||||
echo "Ciudades insertadas.\n";
|
||||
|
||||
// Añadir columna de orden si no existe
|
||||
$stmt = $pdo->prepare("SELECT COUNT(*) FROM information_schema.columns WHERE table_schema = DATABASE() AND table_name = 'ciudades' AND column_name = 'orden'");
|
||||
$stmt->execute();
|
||||
if ($stmt->fetchColumn() == 0) {
|
||||
$pdo->exec("ALTER TABLE `ciudades` ADD `orden` INT NOT NULL DEFAULT 0;");
|
||||
echo "Columna 'orden' añadida a la tabla 'ciudades'.\n";
|
||||
|
||||
// Asignar un orden inicial a las ciudades existentes
|
||||
$pdo->exec("SET @i = 0; UPDATE `ciudades` SET `orden` = (@i:=@i+1) ORDER BY `nombre`;");
|
||||
echo "Orden inicial asignado a las ciudades existentes.\n";
|
||||
} else {
|
||||
echo "La columna 'orden' ya existe en la tabla 'ciudades'.\n";
|
||||
}
|
||||
|
||||
// Añadir columna de orden a productos si no existe
|
||||
$stmt = $pdo->prepare("SELECT COUNT(*) FROM information_schema.columns WHERE table_schema = DATABASE() AND table_name = 'productos' AND column_name = 'orden'");
|
||||
$stmt->execute();
|
||||
if ($stmt->fetchColumn() == 0) {
|
||||
$pdo->exec("ALTER TABLE `productos` ADD `orden` INT NOT NULL DEFAULT 0;");
|
||||
echo "Columna 'orden' añadida a la tabla 'productos'.\n";
|
||||
|
||||
// Asignar un orden inicial a los productos existentes
|
||||
$pdo->exec("SET @i = 0; UPDATE `productos` SET `orden` = (@i:=@i+1) ORDER BY `nombre`;");
|
||||
echo "Orden inicial asignado a los productos existentes.\n";
|
||||
} else {
|
||||
echo "La columna 'orden' ya existe en la tabla 'productos'.\n";
|
||||
}
|
||||
|
||||
// --- Migration for liquidaciones_ciudades_estados ---
|
||||
echo "Verificando estructura de 'liquidaciones_ciudades_estados'...\n";
|
||||
|
||||
// Ensure 'fecha' column exists
|
||||
$stmt_col = $pdo->prepare("SELECT COUNT(*) FROM information_schema.columns WHERE table_schema = DATABASE() AND table_name = 'liquidaciones_ciudades_estados' AND column_name = 'fecha'");
|
||||
$stmt_col->execute();
|
||||
if ($stmt_col->fetchColumn() == 0) {
|
||||
// This block might run on a partially migrated DB
|
||||
echo "Añadiendo columna 'fecha'...\n";
|
||||
$pdo->exec("ALTER TABLE `liquidaciones_ciudades_estados` ADD `fecha` DATE NOT NULL AFTER `ciudad_id`;");
|
||||
|
||||
// Drop old key if it exists
|
||||
$stmt_key = $pdo->prepare("SHOW KEYS FROM `liquidaciones_ciudades_estados` WHERE Key_name = 'liquidacion_ciudad'");
|
||||
$stmt_key->execute();
|
||||
if ($stmt_key->fetch()) {
|
||||
$pdo->exec("ALTER TABLE `liquidaciones_ciudades_estados` DROP KEY `liquidacion_ciudad`;");
|
||||
}
|
||||
|
||||
// Add new unique key
|
||||
$pdo->exec("ALTER TABLE `liquidaciones_ciudades_estados` ADD UNIQUE KEY `liquidacion_ciudad_fecha` (`liquidacion_id`, `ciudad_id`, `fecha`);");
|
||||
}
|
||||
|
||||
// Ensure foreign key to 'liquidaciones' exists
|
||||
$stmt_fk = $pdo->prepare("SELECT COUNT(*) FROM information_schema.key_column_usage WHERE table_schema = DATABASE() AND table_name = 'liquidaciones_ciudades_estados' AND column_name = 'liquidacion_id' AND referenced_table_name = 'liquidaciones';");
|
||||
$stmt_fk->execute();
|
||||
if ($stmt_fk->fetchColumn() == 0) {
|
||||
echo "Añadiendo clave foránea a 'liquidaciones'...\n";
|
||||
// Truncate before adding FK to prevent integrity errors from old data
|
||||
$pdo->exec("TRUNCATE TABLE `liquidaciones_ciudades_estados`;");
|
||||
$pdo->exec("ALTER TABLE `liquidaciones_ciudades_estados` ADD FOREIGN KEY (`liquidacion_id`) REFERENCES `liquidaciones`(`id`) ON DELETE CASCADE;");
|
||||
echo "Clave foránea añadida.\n";
|
||||
}
|
||||
|
||||
echo "Verificación de 'liquidaciones_ciudades_estados' completada.\n";
|
||||
|
||||
// Find and drop the problematic foreign key if it exists
|
||||
$stmt = $pdo->prepare("SELECT CONSTRAINT_NAME FROM INFORMATION_SCHEMA.KEY_COLUMN_USAGE WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = 'liquidaciones_ciudades_estados' AND COLUMN_NAME = 'liquidacion_id' AND REFERENCED_TABLE_NAME = 'liquidaciones';");
|
||||
$stmt->execute();
|
||||
$constraint_name = $stmt->fetchColumn();
|
||||
|
||||
if ($constraint_name) {
|
||||
$pdo->exec("ALTER TABLE `liquidaciones_ciudades_estados` DROP FOREIGN KEY `{$constraint_name}`;");
|
||||
echo "Clave foránea '{$constraint_name}' eliminada de la tabla 'liquidaciones_ciudades_estados'.\n";
|
||||
} else {
|
||||
echo "La clave foránea de liquidacion_id en liquidaciones_ciudades_estados a liquidaciones no existe o ya fue eliminada.\n";
|
||||
}
|
||||
|
||||
// Crear usuario administrador por defecto
|
||||
$admin_email = 'admin@example.com';
|
||||
$admin_pass = 'admin123';
|
||||
$hashed_pass = password_hash($admin_pass, PASSWORD_DEFAULT);
|
||||
|
||||
$stmt = $pdo->prepare("SELECT id FROM `usuarios` WHERE `email` = :email");
|
||||
$stmt->execute(['email' => $admin_email]);
|
||||
if ($stmt->fetchColumn()) {
|
||||
echo "El usuario administrador ya existe.\n";
|
||||
} else {
|
||||
$stmt = $pdo->prepare("INSERT INTO `usuarios` (`nombre`, `email`, `password`, `rol`) VALUES (:nombre, :email, :password, :rol)");
|
||||
$stmt->execute([
|
||||
'nombre' => 'Administrador',
|
||||
'email' => $admin_email,
|
||||
'password' => $hashed_pass,
|
||||
'rol' => 'Administrador General'
|
||||
]);
|
||||
echo "Usuario administrador creado exitosamente.\n";
|
||||
echo "Email: " . $admin_email . "\n";
|
||||
echo "Contraseña: " . $admin_pass . "\n";
|
||||
}
|
||||
|
||||
|
||||
// Rename 'otros' to 'otros_ingresos' in 'flujo_de_caja' if necessary
|
||||
$stmt_check_otros = $pdo->prepare("SELECT COUNT(*) FROM information_schema.columns WHERE table_schema = DATABASE() AND table_name = 'flujo_de_caja' AND column_name = 'otros'");
|
||||
$stmt_check_otros->execute();
|
||||
$otros_exists = $stmt_check_otros->fetchColumn() > 0;
|
||||
|
||||
$stmt_check_otros_ingresos = $pdo->prepare("SELECT COUNT(*) FROM information_schema.columns WHERE table_schema = DATABASE() AND table_name = 'flujo_de_caja' AND column_name = 'otros_ingresos'");
|
||||
$stmt_check_otros_ingresos->execute();
|
||||
$otros_ingresos_exists = $stmt_check_otros_ingresos->fetchColumn() > 0;
|
||||
|
||||
if ($otros_exists && !$otros_ingresos_exists) {
|
||||
$pdo->exec("ALTER TABLE `flujo_de_caja` CHANGE `otros` `otros_ingresos` DECIMAL(10, 2) DEFAULT 0.00;");
|
||||
echo "Columna 'otros' renombrada a 'otros_ingresos' en la tabla 'flujo_de_caja'.\n";
|
||||
} else {
|
||||
echo "La columna 'otros_ingresos' ya existe o la columna 'otros' no fue encontrada en 'flujo_de_caja'.\n";
|
||||
}
|
||||
|
||||
|
||||
// Add new columns to flujo_de_caja if they don't exist
|
||||
$new_columns = [
|
||||
'fl_1' => 'DECIMAL(10, 2) DEFAULT 0.00',
|
||||
'tu1' => 'DECIMAL(10, 2) DEFAULT 0.00',
|
||||
'tu2' => 'DECIMAL(10, 2) DEFAULT 0.00',
|
||||
'tu3' => 'DECIMAL(10, 2) DEFAULT 0.00',
|
||||
'fl_2' => 'DECIMAL(10, 2) DEFAULT 0.00',
|
||||
'fl_3' => 'DECIMAL(10, 2) DEFAULT 0.00',
|
||||
'rc_envio' => 'DECIMAL(10, 2) NOT NULL DEFAULT 0.00'
|
||||
];
|
||||
|
||||
foreach ($new_columns as $column_name => $column_definition) {
|
||||
$stmt = $pdo->prepare("SELECT COUNT(*) FROM information_schema.columns WHERE table_schema = DATABASE() AND table_name = 'flujo_de_caja' AND column_name = :column_name");
|
||||
$stmt->execute(['column_name' => $column_name]);
|
||||
if ($stmt->fetchColumn() == 0) {
|
||||
$pdo->exec("ALTER TABLE `flujo_de_caja` ADD `{$column_name}` {$column_definition}");
|
||||
echo "Columna '{$column_name}' añadida a la tabla 'flujo_de_caja'.\n";
|
||||
} else {
|
||||
echo "La columna '{$column_name}' ya existe en la tabla 'flujo_de_caja'.\n";
|
||||
}
|
||||
}
|
||||
|
||||
// --- Migration for inversiones_operativas ---
|
||||
echo "Verificando estructura de 'inversiones_operativas'...";
|
||||
$stmt_check_desc = $pdo->prepare("SELECT COUNT(*) FROM information_schema.columns WHERE table_schema = DATABASE() AND table_name = 'inversiones_operativas' AND column_name = 'descripcion'");
|
||||
$stmt_check_desc->execute();
|
||||
if ($stmt_check_desc->fetchColumn() > 0) {
|
||||
echo "Migrando tabla 'inversiones_operativas'. Cambiando columnas...";
|
||||
$pdo->exec("ALTER TABLE `inversiones_operativas` DROP COLUMN `descripcion`;");
|
||||
$pdo->exec("ALTER TABLE `inversiones_operativas` DROP COLUMN `monto`;");
|
||||
$pdo->exec("ALTER TABLE `inversiones_operativas` ADD `mercaderia` VARCHAR(255) NOT NULL AFTER `id`;");
|
||||
$pdo->exec("ALTER TABLE `inversiones_operativas` ADD `cantidad` INT NOT NULL AFTER `fecha`;");
|
||||
$pdo->exec("ALTER TABLE `inversiones_operativas` ADD `precio` DECIMAL(10, 2) NOT NULL AFTER `cantidad`;");
|
||||
echo "Tabla 'inversiones_operativas' actualizada a la nueva estructura.";
|
||||
} else {
|
||||
echo "La tabla 'inversiones_operativas' ya tiene la nueva estructura.";
|
||||
}
|
||||
|
||||
} catch (PDOException $e) {
|
||||
die("Error en la base de datos: " . $e->getMessage());
|
||||
}
|
||||
70
editar_ciudad.php
Normal file
@ -0,0 +1,70 @@
|
||||
<?php
|
||||
session_start();
|
||||
if (!isset($_SESSION["user_id"])) {
|
||||
header("Location: auth/login.php");
|
||||
exit();
|
||||
}
|
||||
require_once 'includes/header.php';
|
||||
require_once 'db/config.php';
|
||||
|
||||
$ciudad_id = isset($_GET['id']) ? (int)$_GET['id'] : 0;
|
||||
|
||||
if ($ciudad_id === 0) {
|
||||
$_SESSION['error_message'] = "ID de ciudad no válido.";
|
||||
header("Location: ciudades.php");
|
||||
exit();
|
||||
}
|
||||
|
||||
try {
|
||||
$pdo = db();
|
||||
$stmt = $pdo->prepare("SELECT * FROM ciudades WHERE id = :id");
|
||||
$stmt->bindParam(':id', $ciudad_id, PDO::PARAM_INT);
|
||||
$stmt->execute();
|
||||
$ciudad = $stmt->fetch(PDO::FETCH_ASSOC);
|
||||
|
||||
if (!$ciudad) {
|
||||
$_SESSION['error_message'] = "Ciudad no encontrada.";
|
||||
header("Location: ciudades.php");
|
||||
exit();
|
||||
}
|
||||
} catch (PDOException $e) {
|
||||
$_SESSION['error_message'] = "Error de base de datos: " . $e->getMessage();
|
||||
header("Location: ciudades.php");
|
||||
exit();
|
||||
}
|
||||
?>
|
||||
|
||||
<div class="container-fluid">
|
||||
<div class="d-sm-flex justify-content-between align-items-center mb-4">
|
||||
<h3 class="text-dark mb-0">Editar Ciudad</h3>
|
||||
</div>
|
||||
|
||||
<div class="card shadow">
|
||||
<div class="card-header py-3">
|
||||
<p class="text-primary m-0 fw-bold">Datos de la Ciudad</p>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<form action="handle_editar_ciudad.php" method="post">
|
||||
<input type="hidden" name="id" value="<?php echo htmlspecialchars($ciudad['id']); ?>">
|
||||
<div class="row">
|
||||
<div class="col-md-8">
|
||||
<div class="mb-3">
|
||||
<label for="nombre" class="form-label">Nombre de la Ciudad</label>
|
||||
<input type="text" class="form-control" id="nombre" name="nombre" value="<?php echo htmlspecialchars($ciudad['nombre']); ?>" required>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-4">
|
||||
<div class="mb-3">
|
||||
<label for="orden" class="form-label">Orden</label>
|
||||
<input type="number" class="form-control" id="orden" name="orden" value="<?php echo htmlspecialchars($ciudad['orden']); ?>" required>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<button type="submit" class="btn btn-primary">Actualizar Ciudad</button>
|
||||
<a href="ciudades.php" class="btn btn-secondary">Cancelar</a>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<?php require_once 'includes/footer.php'; ?>
|
||||
94
editar_colaborador.php
Normal file
@ -0,0 +1,94 @@
|
||||
<?php
|
||||
session_start();
|
||||
if (!isset($_SESSION["user_id"])) {
|
||||
header("Location: auth/login.php");
|
||||
exit();
|
||||
}
|
||||
|
||||
if (!isset($_SESSION['user_rol']) || $_SESSION['user_rol'] !== 'Administrador General') {
|
||||
$_SESSION['error_message'] = 'No tienes permiso para acceder a esta página.';
|
||||
header('Location: colaboradores.php');
|
||||
exit();
|
||||
}
|
||||
|
||||
require_once 'db/config.php';
|
||||
require_once 'includes/header.php';
|
||||
|
||||
// Check for user ID
|
||||
if (!isset($_GET['id']) || !is_numeric($_GET['id'])) {
|
||||
$_SESSION['error_message'] = "ID de colaborador no válido.";
|
||||
header("Location: colaboradores.php");
|
||||
exit();
|
||||
}
|
||||
|
||||
$user_id = $_GET['id'];
|
||||
|
||||
try {
|
||||
$pdo = db();
|
||||
$stmt = $pdo->prepare("SELECT id, nombre, email, rol FROM usuarios WHERE id = :id");
|
||||
$stmt->execute([':id' => $user_id]);
|
||||
$usuario = $stmt->fetch(PDO::FETCH_ASSOC);
|
||||
|
||||
if (!$usuario) {
|
||||
$_SESSION['error_message'] = "Colaborador no encontrado.";
|
||||
header("Location: colaboradores.php");
|
||||
exit();
|
||||
}
|
||||
} catch (PDOException $e) {
|
||||
error_log("Error al buscar colaborador: " . $e->getMessage());
|
||||
$_SESSION['error_message'] = "Error al conectar con la base de datos.";
|
||||
header("Location: colaboradores.php");
|
||||
exit();
|
||||
}
|
||||
|
||||
$roles = ['Administrador General', 'Encargado de Stock'];
|
||||
|
||||
?>
|
||||
|
||||
<div class="container-fluid">
|
||||
<div class="d-sm-flex justify-content-between align-items-center mb-4">
|
||||
<h3 class="text-dark mb-0">Editar Colaborador</h3>
|
||||
</div>
|
||||
|
||||
<div class="card shadow">
|
||||
<div class="card-header py-3">
|
||||
<p class="text-primary m-0 fw-bold">Datos del Colaborador</p>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<form action="handle_editar_colaborador.php" method="post">
|
||||
<input type="hidden" name="id" value="<?php echo htmlspecialchars($usuario['id']); ?>">
|
||||
|
||||
<div class="mb-3">
|
||||
<label for="nombre" class="form-label">Nombre Completo</label>
|
||||
<input type="text" class="form-control" id="nombre" name="nombre" value="<?php echo htmlspecialchars($usuario['nombre']); ?>" required>
|
||||
</div>
|
||||
|
||||
<div class="mb-3">
|
||||
<label for="email" class="form-label">Correo Electrónico</label>
|
||||
<input type="email" class="form-control" id="email" name="email" value="<?php echo htmlspecialchars($usuario['email']); ?>" required>
|
||||
</div>
|
||||
|
||||
<div class="mb-3">
|
||||
<label for="password" class="form-label">Nueva Contraseña (opcional)</label>
|
||||
<input type="password" class="form-control" id="password" name="password">
|
||||
<small class="form-text text-muted">Deja este campo en blanco para no cambiar la contraseña.</small>
|
||||
</div>
|
||||
|
||||
<div class="mb-3">
|
||||
<label for="rol" class="form-label">Rol</label>
|
||||
<select class="form-control" id="rol" name="rol" required>
|
||||
<option value="" disabled>Selecciona un rol</option>
|
||||
<?php foreach ($roles as $rol): ?>
|
||||
<option value="<?php echo $rol; ?>" <?php echo ($usuario['rol'] == $rol) ? 'selected' : ''; ?>><?php echo $rol; ?></option>
|
||||
<?php endforeach; ?>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<button type="submit" class="btn btn-primary">Actualizar Colaborador</button>
|
||||
<a href="colaboradores.php" class="btn btn-secondary">Cancelar</a>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<?php require_once 'includes/footer.php'; ?>
|
||||
68
editar_flujo_de_caja.php
Normal file
@ -0,0 +1,68 @@
|
||||
<?php
|
||||
require_once 'includes/header.php';
|
||||
require_once 'db/config.php';
|
||||
|
||||
if (!isset($_GET['id'])) {
|
||||
header('Location: flujo_de_caja.php');
|
||||
exit;
|
||||
}
|
||||
|
||||
$id = $_GET['id'];
|
||||
$pdo = db();
|
||||
|
||||
$stmt = $pdo->prepare('SELECT * FROM flujo_de_caja WHERE id = ?');
|
||||
$stmt->execute([$id]);
|
||||
$movimiento = $stmt->fetch(PDO::FETCH_ASSOC);
|
||||
|
||||
if (!$movimiento) {
|
||||
header('Location: flujo_de_caja.php');
|
||||
exit;
|
||||
}
|
||||
?>
|
||||
|
||||
<div class="container-fluid">
|
||||
<h1 class="h3 mb-4 text-gray-800">Editar Movimiento de Caja</h1>
|
||||
|
||||
<div class="card shadow mb-4">
|
||||
<div class="card-header py-3">
|
||||
<h6 class="m-0 font-weight-bold text-primary">Editor de Movimiento</h6>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<form action="handle_editar_flujo_de_caja.php" method="POST">
|
||||
<input type="hidden" name="id" value="<?php echo htmlspecialchars($movimiento['id']); ?>">
|
||||
<div class="row">
|
||||
<div class="col-md-3 form-group">
|
||||
<label for="fecha">Fecha</label>
|
||||
<input type="date" name="fecha" class="form-control" value="<?php echo htmlspecialchars($movimiento['fecha']); ?>" required>
|
||||
</div>
|
||||
<div class="col-md-3 form-group">
|
||||
<label for="bcp_yape">BCP/YAPE</label>
|
||||
<input type="number" step="0.01" name="bcp_yape" class="form-control" value="<?php echo htmlspecialchars($movimiento['bcp_yape']); ?>">
|
||||
</div>
|
||||
<div class="col-md-3 form-group">
|
||||
<label for="banco_nacion">Banco Nación</label>
|
||||
<input type="number" step="0.01" name="banco_nacion" class="form-control" value="<?php echo htmlspecialchars($movimiento['banco_nacion']); ?>">
|
||||
</div>
|
||||
<div class="col-md-3 form-group">
|
||||
<label for="interbank">Interbank</label>
|
||||
<input type="number" step="0.01" name="interbank" class="form-control" value="<?php echo htmlspecialchars($movimiento['interbank']); ?>">
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-md-3 form-group">
|
||||
<label for="bbva">BBVA</label>
|
||||
<input type="number" step="0.01" name="bbva" class="form-control" value="<?php echo htmlspecialchars($movimiento['bbva']); ?>">
|
||||
</div>
|
||||
<div class="col-md-3 form-group">
|
||||
<label for="otros">Otros</label>
|
||||
<input type="number" step="0.01" name="otros" class="form-control" value="<?php echo htmlspecialchars($movimiento['otros']); ?>">
|
||||
</div>
|
||||
</div>
|
||||
<button type="submit" class="btn btn-primary">Actualizar Movimiento</button>
|
||||
<a href="flujo_de_caja.php" class="btn btn-secondary">Cancelar</a>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<?php require_once 'includes/footer.php'; ?>
|
||||
73
editar_inversion.php
Normal file
@ -0,0 +1,73 @@
|
||||
<?php
|
||||
require_once 'includes/header.php';
|
||||
require_once 'db/config.php';
|
||||
|
||||
// Verificar permisos
|
||||
if (!isset($_SESSION['user_id'])) {
|
||||
header('Location: /auth/login.php');
|
||||
exit;
|
||||
}
|
||||
|
||||
$id = $_GET['id'] ?? null;
|
||||
if (!$id) {
|
||||
header('Location: inversiones_operativas.php?error=missing_id');
|
||||
exit;
|
||||
}
|
||||
|
||||
// Obtener los datos de la inversión
|
||||
try {
|
||||
$pdo = db();
|
||||
$stmt = $pdo->prepare("SELECT * FROM inversiones WHERE id = ?");
|
||||
$stmt->execute([$id]);
|
||||
$inversion = $stmt->fetch(PDO::FETCH_ASSOC);
|
||||
|
||||
if (!$inversion) {
|
||||
header('Location: inversiones_operativas.php?error=not_found');
|
||||
exit;
|
||||
}
|
||||
} catch (PDOException $e) {
|
||||
die("Error al obtener los datos: " . $e->getMessage());
|
||||
}
|
||||
|
||||
?>
|
||||
|
||||
<div class="container mt-5">
|
||||
<h2>Editar Inversión</h2>
|
||||
<hr>
|
||||
<div class="card">
|
||||
<div class="card-body">
|
||||
<form action="handle_editar_inversion.php" method="POST">
|
||||
<input type="hidden" name="id" value="<?= htmlspecialchars($inversion['id']) ?>">
|
||||
|
||||
<div class="mb-3">
|
||||
<label for="fecha" class="form-label">Fecha</label>
|
||||
<input type="date" class="form-control" id="fecha" name="fecha" value="<?= htmlspecialchars($inversion['fecha']) ?>" required>
|
||||
</div>
|
||||
|
||||
<div class="mb-3">
|
||||
<label for="descripcion" class="form-label">Descripción</label>
|
||||
<input type="text" class="form-control" id="descripcion" name="descripcion" value="<?= htmlspecialchars($inversion['descripcion']) ?>" required>
|
||||
</div>
|
||||
|
||||
<div class="mb-3">
|
||||
<label for="monto" class="form-label">Monto (S/)</label>
|
||||
<input type="number" step="0.01" class="form-control" id="monto" name="monto" value="<?= htmlspecialchars($inversion['monto']) ?>" required>
|
||||
</div>
|
||||
|
||||
<div class="mb-3">
|
||||
<label for="tipo" class="form-label">Tipo</label>
|
||||
<select class="form-select" id="tipo" name="tipo" required>
|
||||
<option value="operativa" <?= $inversion['tipo'] === 'operativa' ? 'selected' : '' ?>>Inversión Operativa</option>
|
||||
<option value="operacional" <?= $inversion['tipo'] === 'operacional' ? 'selected' : '' ?>>Inversión Operacional</option>
|
||||
<option value="ads" <?= $inversion['tipo'] === 'ads' ? 'selected' : '' ?>>Inversión en Ads</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<button type="submit" class="btn btn-primary">Guardar Cambios</button>
|
||||
<a href="inversiones_operativas.php" class="btn btn-secondary">Cancelar</a>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<?php require_once 'includes/footer.php'; ?>
|
||||
96
editar_liquidacion.php
Normal file
@ -0,0 +1,96 @@
|
||||
<?php
|
||||
require_once 'includes/header.php';
|
||||
|
||||
// Verificar si se proporcionó un ID
|
||||
if (!isset($_GET['id']) || empty($_GET['id'])) {
|
||||
header('Location: liquidaciones.php?error=invalid_id');
|
||||
exit();
|
||||
}
|
||||
|
||||
$movimiento_id = $_GET['id'];
|
||||
$pdo = db();
|
||||
|
||||
// Obtener los datos de la liquidación actual
|
||||
$stmt = $pdo->prepare("SELECT * FROM movimientos WHERE id = ? AND tipo = 'Salida'");
|
||||
$stmt->execute([$movimiento_id]);
|
||||
$liquidacion = $stmt->fetch(PDO::FETCH_ASSOC);
|
||||
|
||||
if (!$liquidacion) {
|
||||
header('Location: liquidaciones.php?error=not_found');
|
||||
exit();
|
||||
}
|
||||
|
||||
// Obtener productos y ciudades para los menús desplegables
|
||||
$productos = $pdo->query("SELECT id, nombre FROM productos ORDER BY nombre")->fetchAll(PDO::FETCH_ASSOC);
|
||||
$ciudades = $pdo->query("SELECT id, nombre FROM ciudades ORDER BY nombre")->fetchAll(PDO::FETCH_ASSOC);
|
||||
|
||||
?>
|
||||
|
||||
<h1 class="mb-4">Editar Liquidación</h1>
|
||||
|
||||
<?php
|
||||
if (isset($_GET['error'])) {
|
||||
$error_msg = 'Ocurrió un error.';
|
||||
if ($_GET['error'] === 'missing_fields') {
|
||||
$error_msg = 'Por favor, complete todos los campos requeridos.';
|
||||
}
|
||||
echo '<div class="alert alert-danger">' . $error_msg . '</div>';
|
||||
}
|
||||
?>
|
||||
|
||||
<div class="card">
|
||||
<div class="card-body">
|
||||
<form action="handle_editar_liquidacion.php" method="POST">
|
||||
<input type="hidden" name="movimiento_id" value="<?php echo htmlspecialchars($liquidacion['id']); ?>">
|
||||
|
||||
<div class="mb-3">
|
||||
<label for="producto_id" class="form-label">Producto</label>
|
||||
<select class="form-control" id="producto_id" name="producto_id" required>
|
||||
<option value="">Seleccione un producto</option>
|
||||
<?php foreach ($productos as $producto): ?>
|
||||
<option value="<?php echo $producto['id']; ?>" <?php echo ($producto['id'] == $liquidacion['producto_id']) ? 'selected' : ''; ?>>
|
||||
<?php echo htmlspecialchars($producto['nombre']); ?>
|
||||
</option>
|
||||
<?php endforeach; ?>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div class="mb-3">
|
||||
<label for="ciudad_id" class="form-label">Ciudad</label>
|
||||
<select class="form-control" id="ciudad_id" name="ciudad_id" required>
|
||||
<option value="">Seleccione una ciudad</option>
|
||||
<?php foreach ($ciudades as $ciudad): ?>
|
||||
<option value="<?php echo $ciudad['id']; ?>" <?php echo ($ciudad['id'] == $liquidacion['ciudad_origen_id']) ? 'selected' : ''; ?>>
|
||||
<?php echo htmlspecialchars($ciudad['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" value="<?php echo htmlspecialchars($liquidacion['cantidad']); ?>" required>
|
||||
</div>
|
||||
|
||||
<div class="mb-3">
|
||||
<label for="cantidad_pedidos" class="form-label">Cantidad de Pedidos</label>
|
||||
<input type="number" class="form-control" id="cantidad_pedidos" name="cantidad_pedidos" value="<?php echo htmlspecialchars($liquidacion['cantidad_pedidos']); ?>" required>
|
||||
</div>
|
||||
|
||||
<div class="mb-3">
|
||||
<label for="precio_liquidacion" class="form-label">Precio Liquidación</label>
|
||||
<input type="number" step="0.01" class="form-control" id="precio_liquidacion" name="precio_liquidacion" value="<?php echo htmlspecialchars($liquidacion['precio_liquidacion']); ?>" required>
|
||||
</div>
|
||||
|
||||
<div class="mb-3">
|
||||
<label for="fecha" class="form-label">Fecha</label>
|
||||
<input type="datetime-local" class="form-control" id="fecha" name="fecha" value="<?php echo date('Y-m-d\TH:i', strtotime($liquidacion['fecha'])); ?>" required>
|
||||
</div>
|
||||
|
||||
<button type="submit" class="btn btn-primary">Guardar Cambios</button>
|
||||
<a href="liquidaciones.php" class="btn btn-secondary">Cancelar</a>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<?php require_once 'includes/footer.php'; ?>
|
||||
65
editar_producto.php
Normal file
@ -0,0 +1,65 @@
|
||||
<?php
|
||||
require_once 'includes/header.php';
|
||||
|
||||
$product_id = isset($_GET['id']) ? (int)$_GET['id'] : 0;
|
||||
|
||||
if ($product_id === 0) {
|
||||
$_SESSION['error_message'] = "ID de producto no válido.";
|
||||
header("Location: productos.php");
|
||||
exit();
|
||||
}
|
||||
|
||||
$pdo = db();
|
||||
$stmt = $pdo->prepare("SELECT * FROM productos WHERE id = ?");
|
||||
$stmt->execute([$product_id]);
|
||||
$product = $stmt->fetch(PDO::FETCH_ASSOC);
|
||||
|
||||
if (!$product) {
|
||||
$_SESSION['error_message'] = "Producto no encontrado.";
|
||||
header("Location: productos.php");
|
||||
exit();
|
||||
}
|
||||
?>
|
||||
|
||||
<div class="container-fluid">
|
||||
<div class="d-sm-flex justify-content-between align-items-center mb-4">
|
||||
<h3 class="text-dark mb-0">Editar Producto</h3>
|
||||
</div>
|
||||
|
||||
<div class="card shadow mb-4">
|
||||
<div class="card-header py-3">
|
||||
<h6 class="m-0 font-weight-bold text-primary">Detalles del Producto</h6>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<form action="handle_editar_producto.php" method="post">
|
||||
<input type="hidden" name="id" value="<?php echo htmlspecialchars($product['id']); ?>">
|
||||
|
||||
<div class="mb-3">
|
||||
<label for="nombre" class="form-label">Nombre del Producto</label>
|
||||
<input type="text" class="form-control" id="nombre" name="nombre" value="<?php echo htmlspecialchars($product['nombre']); ?>" required>
|
||||
</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>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<div class="col-md-6 mb-3">
|
||||
<label for="costo" class="form-label">Precio de Compra</label>
|
||||
<input type="number" class="form-control" id="costo" name="costo" step="0.01" value="<?php echo htmlspecialchars($product['costo']); ?>" required>
|
||||
</div>
|
||||
<div class="col-md-6 mb-3">
|
||||
<label for="precio_venta" class="form-label">Precio de Venta</label>
|
||||
<input type="number" class="form-control" id="precio_venta" name="precio_venta" step="0.01" value="<?php echo htmlspecialchars($product['precio_venta']); ?>" required>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<button type="submit" class="btn btn-primary">Guardar Cambios</button>
|
||||
<a href="productos.php" class="btn btn-secondary">Cancelar</a>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<?php require_once 'includes/footer.php'; ?>
|
||||
50
eliminar_ciudad.php
Normal file
@ -0,0 +1,50 @@
|
||||
<?php
|
||||
session_start();
|
||||
if (!isset($_SESSION["user_id"])) {
|
||||
header("Location: auth/login.php");
|
||||
exit();
|
||||
}
|
||||
|
||||
require_once 'db/config.php';
|
||||
|
||||
$id = isset($_GET['id']) ? (int)$_GET['id'] : 0;
|
||||
|
||||
if ($id === 0) {
|
||||
$_SESSION['error_message'] = "ID de ciudad no válido.";
|
||||
header("Location: ciudades.php");
|
||||
exit();
|
||||
}
|
||||
|
||||
try {
|
||||
$pdo = db();
|
||||
|
||||
// Check if the city is being used in stock_por_ciudad
|
||||
$check_sql = "SELECT COUNT(*) FROM stock_por_ciudad WHERE ciudad_id = :id";
|
||||
$check_stmt = $pdo->prepare($check_sql);
|
||||
$check_stmt->bindParam(':id', $id, PDO::PARAM_INT);
|
||||
$check_stmt->execute();
|
||||
$usage_count = $check_stmt->fetchColumn();
|
||||
|
||||
if ($usage_count > 0) {
|
||||
$_SESSION['error_message'] = "No se puede eliminar la ciudad porque tiene stock asociado. Por favor, reasigne o elimine el stock primero.";
|
||||
header("Location: ciudades.php");
|
||||
exit();
|
||||
}
|
||||
|
||||
// If not in use, proceed with deletion
|
||||
$sql = "DELETE FROM ciudades WHERE id = :id";
|
||||
$stmt = $pdo->prepare($sql);
|
||||
$stmt->bindParam(':id', $id, PDO::PARAM_INT);
|
||||
|
||||
if ($stmt->execute()) {
|
||||
$_SESSION['success_message'] = "Ciudad eliminada exitosamente.";
|
||||
} else {
|
||||
$_SESSION['error_message'] = "Error al eliminar la ciudad.";
|
||||
}
|
||||
} catch (PDOException $e) {
|
||||
$_SESSION['error_message'] = "Error de base de datos: " . $e->getMessage();
|
||||
}
|
||||
|
||||
header("Location: ciudades.php");
|
||||
exit();
|
||||
?>
|
||||
70
eliminar_colaborador.php
Normal file
@ -0,0 +1,70 @@
|
||||
<?php
|
||||
session_start();
|
||||
if (!isset($_SESSION["user_id"])) {
|
||||
header("Location: auth/login.php");
|
||||
exit();
|
||||
}
|
||||
|
||||
if (!isset($_SESSION['user_rol']) || $_SESSION['user_rol'] !== 'Administrador General') {
|
||||
$_SESSION['error_message'] = 'No tienes permiso para realizar esta acción.';
|
||||
header('Location: colaboradores.php');
|
||||
exit();
|
||||
}
|
||||
|
||||
require_once 'db/config.php';
|
||||
|
||||
// Check for user ID
|
||||
if (!isset($_GET['id']) || !is_numeric($_GET['id'])) {
|
||||
$_SESSION['error_message'] = "ID de colaborador no válido.";
|
||||
header("Location: colaboradores.php");
|
||||
exit();
|
||||
}
|
||||
|
||||
$id_to_delete = (int)$_GET['id'];
|
||||
$current_user_id = (int)$_SESSION['user_id'];
|
||||
|
||||
// Security checks
|
||||
if ($id_to_delete === $current_user_id) {
|
||||
$_SESSION['error_message'] = "No puedes eliminar tu propia cuenta.";
|
||||
header("Location: colaboradores.php");
|
||||
exit();
|
||||
}
|
||||
|
||||
if ($id_to_delete === 1) {
|
||||
$_SESSION['error_message'] = "No se puede eliminar al administrador principal del sistema.";
|
||||
header("Location: colaboradores.php");
|
||||
exit();
|
||||
}
|
||||
|
||||
try {
|
||||
$pdo = db();
|
||||
|
||||
// Check if the user exists before trying to delete
|
||||
$stmt = $pdo->prepare("SELECT id FROM usuarios WHERE id = :id");
|
||||
$stmt->execute([':id' => $id_to_delete]);
|
||||
if (!$stmt->fetch()) {
|
||||
$_SESSION['error_message'] = "El colaborador que intentas eliminar no existe.";
|
||||
header("Location: colaboradores.php");
|
||||
exit();
|
||||
}
|
||||
|
||||
// Proceed with deletion
|
||||
$stmt = $pdo->prepare("DELETE FROM usuarios WHERE id = :id");
|
||||
$stmt->execute([':id' => $id_to_delete]);
|
||||
|
||||
$_SESSION['success_message'] = "Colaborador eliminado exitosamente.";
|
||||
header("Location: colaboradores.php");
|
||||
exit();
|
||||
|
||||
} catch (PDOException $e) {
|
||||
error_log("Error al eliminar colaborador: " . $e->getMessage());
|
||||
// Foreign key constraint check
|
||||
if ($e->getCode() == '23000') {
|
||||
$_SESSION['error_message'] = "No se puede eliminar este colaborador porque tiene registros asociados (movimientos, ventas, etc.).";
|
||||
} else {
|
||||
$_SESSION['error_message'] = "Error al conectar con la base de datos.";
|
||||
}
|
||||
header("Location: colaboradores.php");
|
||||
exit();
|
||||
}
|
||||
?>
|
||||
34
eliminar_info_producto.php
Normal file
@ -0,0 +1,34 @@
|
||||
<?php
|
||||
require_once 'db/config.php';
|
||||
|
||||
if (!isset($_GET['id']) || empty($_GET['id'])) {
|
||||
header('Location: info_producto.php');
|
||||
exit;
|
||||
}
|
||||
|
||||
$info_id = $_GET['id'];
|
||||
$pdo = db();
|
||||
|
||||
// First, get the image URL to delete the file
|
||||
$stmt_select = $pdo->prepare('SELECT imagen_url FROM info_productos WHERE id = :id');
|
||||
$stmt_select->execute([':id' => $info_id]);
|
||||
$info = $stmt_select->fetch(PDO::FETCH_ASSOC);
|
||||
|
||||
if ($info) {
|
||||
// Delete the database record
|
||||
$stmt_delete = $pdo->prepare('DELETE FROM info_productos WHERE id = :id');
|
||||
if ($stmt_delete->execute([':id' => $info_id])) {
|
||||
// If DB deletion is successful, delete the image file
|
||||
if (file_exists($info['imagen_url'])) {
|
||||
unlink($info['imagen_url']);
|
||||
}
|
||||
$_SESSION['success_message'] = 'La tarjeta de información ha sido eliminada.';
|
||||
} else {
|
||||
$_SESSION['error_message'] = 'No se pudo eliminar la tarjeta de la base de datos.';
|
||||
}
|
||||
} else {
|
||||
$_SESSION['error_message'] = 'No se encontró la tarjeta especificada.';
|
||||
}
|
||||
|
||||
header('Location: info_producto.php');
|
||||
exit;
|
||||
29
eliminar_inversion.php
Normal file
@ -0,0 +1,29 @@
|
||||
<?php
|
||||
require_once 'db/config.php';
|
||||
require_once 'includes/header.php'; // Para la sesión y seguridad
|
||||
|
||||
// Verificar permisos
|
||||
if (!isset($_SESSION['user_id'])) {
|
||||
header('Location: /auth/login.php');
|
||||
exit;
|
||||
}
|
||||
|
||||
$id = $_GET['id'] ?? null;
|
||||
|
||||
if (!$id) {
|
||||
header('Location: inversiones_operativas.php?error=missing_id');
|
||||
exit;
|
||||
}
|
||||
|
||||
try {
|
||||
$pdo = db();
|
||||
$stmt = $pdo->prepare("DELETE FROM inversiones WHERE id = ?");
|
||||
$stmt->execute([$id]);
|
||||
|
||||
header('Location: inversiones_operativas.php?success=deleted');
|
||||
exit;
|
||||
} catch (PDOException $e) {
|
||||
header('Location: inversiones_operativas.php?error=db_error');
|
||||
exit;
|
||||
}
|
||||
?>
|
||||
62
eliminar_producto.php
Normal file
@ -0,0 +1,62 @@
|
||||
<?php
|
||||
session_start();
|
||||
|
||||
if (!isset($_SESSION["user_id"])) {
|
||||
header("Location: auth/login.php");
|
||||
exit();
|
||||
}
|
||||
|
||||
require_once 'db/config.php';
|
||||
|
||||
$product_id = isset($_GET['id']) ? (int)$_GET['id'] : 0;
|
||||
|
||||
if ($product_id > 0) {
|
||||
$pdo = db();
|
||||
try {
|
||||
// 1. Verificar si hay movimientos asociados
|
||||
$sql_check = "SELECT COUNT(*) FROM movimientos WHERE producto_id = ?";
|
||||
$stmt_check = $pdo->prepare($sql_check);
|
||||
$stmt_check->execute([$product_id]);
|
||||
$movement_count = $stmt_check->fetchColumn();
|
||||
|
||||
if ($movement_count > 0) {
|
||||
// Si hay movimientos, no permitir eliminar y mostrar un error amigable
|
||||
$_SESSION['error_message'] = "No se puede eliminar: este producto ya tiene un historial de entradas y salidas.";
|
||||
} else {
|
||||
// Si no hay movimientos, proceder con la eliminación
|
||||
$pdo->beginTransaction();
|
||||
|
||||
// Primero, eliminar el stock asociado (si existe)
|
||||
$sql_stock = "DELETE FROM stock_por_ciudad WHERE producto_id = ?";
|
||||
$stmt_stock = $pdo->prepare($sql_stock);
|
||||
$stmt_stock->execute([$product_id]);
|
||||
|
||||
// Luego, eliminar el producto
|
||||
$sql_product = "DELETE FROM productos WHERE id = ?";
|
||||
$stmt_product = $pdo->prepare($sql_product);
|
||||
$stmt_product->execute([$product_id]);
|
||||
|
||||
if ($stmt_product->rowCount() > 0) {
|
||||
$pdo->commit();
|
||||
$_SESSION['success_message'] = "Producto eliminado correctamente.";
|
||||
} else {
|
||||
$pdo->rollBack();
|
||||
$_SESSION['error_message'] = "El producto no pudo ser encontrado o ya fue eliminado.";
|
||||
}
|
||||
}
|
||||
} catch (PDOException $e) {
|
||||
if ($pdo->inTransaction()) {
|
||||
$pdo->rollBack();
|
||||
}
|
||||
// Ocultamos el mensaje técnico y mostramos uno más amigable
|
||||
$_SESSION['error_message'] = "Ocurrió un error al intentar eliminar el producto. Por favor, inténtalo de nuevo.";
|
||||
// Opcional: podrías loggear el error real para depuración
|
||||
// error_log("Error al eliminar producto: " . $e->getMessage());
|
||||
}
|
||||
} else {
|
||||
$_SESSION['error_message'] = "ID de producto no válido.";
|
||||
}
|
||||
|
||||
header("Location: productos.php");
|
||||
exit();
|
||||
?>
|
||||
345
flujo_de_caja.php
Normal file
@ -0,0 +1,345 @@
|
||||
<?php
|
||||
require_once 'includes/header.php';
|
||||
|
||||
if (isset($_SESSION['user_rol']) && $_SESSION['user_rol'] === 'Encargado de Stock') {
|
||||
header('Location: /index.php?error=unauthorized');
|
||||
exit;
|
||||
}
|
||||
|
||||
require_once 'db/config.php';
|
||||
|
||||
$db = db();
|
||||
|
||||
// Lógica para obtener el mes y año seleccionados
|
||||
$selected_month = isset($_GET['month']) ? (int)$_GET['month'] : date('m');
|
||||
$selected_year = isset($_GET['year']) ? (int)$_GET['year'] : date('Y');
|
||||
|
||||
$days_in_month = cal_days_in_month(CAL_GREGORIAN, $selected_month, $selected_year);
|
||||
$start_date = "$selected_year-$selected_month-01";
|
||||
$end_date = "$selected_year-$selected_month-$days_in_month";
|
||||
|
||||
// --- Obtener Recaudos Contraentrega ---
|
||||
$stmt_recaudos = $db->prepare(
|
||||
"SELECT DATE(fecha) as fecha_recaudo, SUM(precio_liquidacion) as total_recaudado
|
||||
FROM movimientos
|
||||
WHERE tipo = 'salida' AND fecha BETWEEN ? AND ?
|
||||
GROUP BY DATE(fecha)"
|
||||
);
|
||||
$stmt_recaudos->execute([$start_date, $end_date]);
|
||||
$recaudos_por_fecha = $stmt_recaudos->fetchAll(PDO::FETCH_KEY_PAIR);
|
||||
|
||||
// --- Obtener datos del flujo de caja para el mes ---
|
||||
$stmt_flujo = $db->prepare("SELECT * FROM flujo_de_caja WHERE YEAR(fecha) = ? AND MONTH(fecha) = ? ORDER BY fecha ASC");
|
||||
$stmt_flujo->execute([$selected_year, $selected_month]);
|
||||
$flujo_data = $stmt_flujo->fetchAll(PDO::FETCH_ASSOC);
|
||||
|
||||
$data_por_dia = [];
|
||||
foreach ($flujo_data as $row) {
|
||||
$data_por_dia[$row['fecha']] = $row;
|
||||
}
|
||||
|
||||
$dias_del_mes = [];
|
||||
|
||||
for ($day = 1; $day <= $days_in_month; $day++) {
|
||||
$fecha_str = sprintf("%s-%s-%02d", $selected_year, str_pad($selected_month, 2, '0', STR_PAD_LEFT), $day);
|
||||
|
||||
$default_row = [
|
||||
'bcp_yape' => 0, 'banco_nacion' => 0, 'interbank' => 0, 'bbva' => 0, 'otros_ingresos' => 0, 'rc_envio' => 0,
|
||||
'tu_1' => 0, 'tu_2' => 0, 'tu_3' => 0, 'fl_1' => 0, 'fl_2' => 0, 'fl_3' => 0
|
||||
];
|
||||
$dia_data = $data_por_dia[$fecha_str] ?? $default_row;
|
||||
|
||||
// RC ENVIO es la suma de los otros ingresos
|
||||
$rc_envio_dia =
|
||||
(float)($dia_data['bcp_yape'] ?? 0) + (float)($dia_data['banco_nacion'] ?? 0) +
|
||||
(float)($dia_data['interbank'] ?? 0) + (float)($dia_data['bbva'] ?? 0) +
|
||||
(float)($dia_data['otros_ingresos'] ?? 0);
|
||||
|
||||
$dia_data['rc_envio'] = $rc_envio_dia;
|
||||
|
||||
$recaudo_contraentrega = $recaudos_por_fecha[$fecha_str] ?? 0;
|
||||
|
||||
$total_ingresos_dia = $rc_envio_dia + (float)$recaudo_contraentrega;
|
||||
|
||||
$total_egresos_dia =
|
||||
(float)($dia_data['tu_1'] ?? 0) + (float)($dia_data['tu_2'] ?? 0) +
|
||||
(float)($dia_data['tu_3'] ?? 0) + (float)($dia_data['fl_1'] ?? 0) +
|
||||
(float)($dia_data['fl_2'] ?? 0) + (float)($dia_data['fl_3'] ?? 0);
|
||||
|
||||
// El "Recaudo final" es la diferencia del día
|
||||
$recaudo_final_dia = $total_ingresos_dia - $total_egresos_dia;
|
||||
|
||||
$dias_del_mes[] = [
|
||||
'fecha' => $fecha_str,
|
||||
'data' => $dia_data,
|
||||
'recaudo_contraentrega' => $recaudo_contraentrega,
|
||||
'total_ingresos' => $total_ingresos_dia,
|
||||
'total_egresos' => $total_egresos_dia,
|
||||
'recaudo_final' => $recaudo_final_dia
|
||||
];
|
||||
}
|
||||
|
||||
|
||||
?>
|
||||
|
||||
<style>
|
||||
.table-responsive { max-height: 80vh; overflow-y: auto; }
|
||||
.table th, .table td { white-space: nowrap; padding: 0.5rem; vertical-align: middle; }
|
||||
.editable { cursor: pointer; background-color: #f9f9f9; }
|
||||
.editable:hover { background-color: #e9ecef; }
|
||||
.ingreso { background-color: #d4edda; }
|
||||
.egreso { background-color: #f8d7da; }
|
||||
.total-ingreso, .total-egreso, .saldo { font-weight: bold; }
|
||||
|
||||
/* Estilos para el encabezado anclado */
|
||||
thead.table-dark th {
|
||||
position: sticky;
|
||||
top: 0;
|
||||
z-index: 1;
|
||||
background-color: #343a40 !important; /* Fondo oscuro */
|
||||
color: #fff !important; /* Texto blanco */
|
||||
}
|
||||
|
||||
tfoot { position: sticky; bottom: 0; background-color: #fff; z-index: 10; }
|
||||
</style>
|
||||
|
||||
<div class="container-fluid mt-4">
|
||||
<h2>Flujo de Caja</h2>
|
||||
|
||||
<form method="get" class="row g-3 align-items-center mb-3">
|
||||
<div class="col-auto"><label for="month" class="col-form-label">Mes</label></div>
|
||||
<div class="col-auto">
|
||||
<select name="month" id="month" class="form-select">
|
||||
<?php for ($i = 1; $i <= 12; $i++): ?>
|
||||
<option value="<?= $i ?>" <?= $i == $selected_month ? 'selected' : '' ?>><?= ucfirst(strftime('%B', mktime(0, 0, 0, $i, 1))) ?></option>
|
||||
<?php endfor; ?>
|
||||
</select>
|
||||
</div>
|
||||
<div class="col-auto"><label for="year" class="col-form-label">Año</label></div>
|
||||
<div class="col-auto">
|
||||
<select name="year" id="year" class="form-select">
|
||||
<?php for ($y = date('Y'); $y >= 2020; $y--): ?>
|
||||
<option value="<?= $y ?>" <?= $y == $selected_year ? 'selected' : '' ?>><?= $y ?></option>
|
||||
<?php endfor; ?>
|
||||
</select>
|
||||
</div>
|
||||
<div class="col-auto"><button type="submit" class="btn btn-primary">Ver</button></div>
|
||||
</form>
|
||||
|
||||
<div class="table-responsive">
|
||||
<table class="table table-bordered table-striped table-hover">
|
||||
<thead class="table-dark">
|
||||
<tr>
|
||||
<th rowspan="2" style="text-align:center; vertical-align: middle;">Fecha</th>
|
||||
<th colspan="5">Ingresos</th>
|
||||
<th colspan="6">Inversion publicitaria</th>
|
||||
<th rowspan="2" class="ingreso" style="text-align:center; vertical-align: middle;">RC ENVIO</th>
|
||||
<th rowspan="2" class="ingreso" style="text-align:center; vertical-align: middle;">RC CONTRAENT</th>
|
||||
<th rowspan="2" style="text-align:center; vertical-align: middle;">Total Ingresos</th>
|
||||
<th rowspan="2" style="text-align:center; vertical-align: middle;">Total Inversion Publicitaria</th>
|
||||
<th rowspan="2" style="text-align:center; vertical-align: middle;">Recaudo final</th>
|
||||
</tr>
|
||||
<tr>
|
||||
<!-- Ingresos -->
|
||||
<th class="ingreso">BCP/YAPE</th>
|
||||
<th class="ingreso">B. NACION</th>
|
||||
<th class="ingreso">INTERBANK</th>
|
||||
<th class="ingreso">BBVA</th>
|
||||
<th class="ingreso">Otros Ingresos</th>
|
||||
<!-- Egresos -->
|
||||
<th class="egreso">TU 1</th>
|
||||
<th class="egreso">TU 2</th>
|
||||
<th class="egreso">TU 3</th>
|
||||
<th class="egreso">FL1</th>
|
||||
<th class="egreso">FL2</th>
|
||||
<th class="egreso">FL3</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<?php foreach ($dias_del_mes as $dia): ?>
|
||||
<tr data-fecha="<?= $dia['fecha'] ?>">
|
||||
<td><?= date("d/m/Y", strtotime($dia['fecha'])) ?></td>
|
||||
<!-- Ingresos -->
|
||||
<td class="editable ingreso" data-field="bcp_yape"><?= number_format($dia['data']['bcp_yape'] ?? 0, 2) ?></td>
|
||||
<td class="editable ingreso" data-field="banco_nacion"><?= number_format($dia['data']['banco_nacion'] ?? 0, 2) ?></td>
|
||||
<td class="editable ingreso" data-field="interbank"><?= number_format($dia['data']['interbank'] ?? 0, 2) ?></td>
|
||||
<td class="editable ingreso" data-field="bbva"><?= number_format($dia['data']['bbva'] ?? 0, 2) ?></td>
|
||||
<td class="editable ingreso" data-field="otros_ingresos"><?= number_format($dia['data']['otros_ingresos'] ?? 0, 2) ?></td>
|
||||
<!-- Egresos -->
|
||||
<td class="editable egreso" data-field="tu_1"><?= number_format($dia['data']['tu_1'] ?? 0, 2) ?></td>
|
||||
<td class="editable egreso" data-field="tu_2"><?= number_format($dia['data']['tu_2'] ?? 0, 2) ?></td>
|
||||
<td class="editable egreso" data-field="tu_3"><?= number_format($dia['data']['tu_3'] ?? 0, 2) ?></td>
|
||||
<td class="editable egreso" data-field="fl_1"><?= number_format($dia['data']['fl_1'] ?? 0, 2) ?></td>
|
||||
<td class="editable egreso" data-field="fl_2"><?= number_format($dia['data']['fl_2'] ?? 0, 2) ?></td>
|
||||
<td class="editable egreso" data-field="fl_3"><?= number_format($dia['data']['fl_3'] ?? 0, 2) ?></td>
|
||||
<!-- RC (ya no es editable) -->
|
||||
<td class="ingreso" data-field="rc_envio"><?= number_format($dia['data']['rc_envio'] ?? 0, 2) ?></td>
|
||||
<td class="ingreso" data-field="rc_contraent"><?= number_format($dia['recaudo_contraentrega'], 2) ?></td>
|
||||
<!-- Totales -->
|
||||
<td class="total-ingreso" data-field="total_ingresos"><?= number_format($dia['total_ingresos'], 2) ?></td>
|
||||
<td class="total-egreso" data-field="total_egresos"><?= number_format($dia['total_egresos'], 2) ?></td>
|
||||
<td class="saldo" data-field="recaudo_final"><?= number_format($dia['recaudo_final'], 2) ?></td>
|
||||
</tr>
|
||||
<?php endforeach; ?>
|
||||
</tbody>
|
||||
<tfoot>
|
||||
<tr class="table-dark" style="font-weight: bold;">
|
||||
<td>TOTALES</td>
|
||||
<td data-total="bcp_yape">0.00</td>
|
||||
<td data-total="banco_nacion">0.00</td>
|
||||
<td data-total="interbank">0.00</td>
|
||||
<td data-total="bbva">0.00</td>
|
||||
<td data-total="otros_ingresos">0.00</td>
|
||||
<td data-total="tu_1">0.00</td>
|
||||
<td data-total="tu_2">0.00</td>
|
||||
<td data-total="tu_3">0.00</td>
|
||||
<td data-total="fl_1">0.00</td>
|
||||
<td data-total="fl_2">0.00</td>
|
||||
<td data-total="fl_3">0.00</td>
|
||||
<td data-total="rc_envio">0.00</td>
|
||||
<td data-total="rc_contraent">0.00</td>
|
||||
<td data-total="total_ingresos">0.00</td>
|
||||
<td data-total="total_egresos">0.00</td>
|
||||
<td data-total="recaudo_final">0.00</td>
|
||||
</tr>
|
||||
</tfoot>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
document.addEventListener('DOMContentLoaded', function () {
|
||||
const table = document.querySelector('.table');
|
||||
|
||||
table.addEventListener('dblclick', function (e) {
|
||||
if (e.target.classList.contains('editable')) {
|
||||
makeCellEditable(e.target);
|
||||
}
|
||||
});
|
||||
|
||||
function makeCellEditable(cell) {
|
||||
const originalValue = parseFloat(cell.textContent.trim().replace(/,/g, '')) || 0;
|
||||
if (cell.querySelector('input')) return;
|
||||
|
||||
const input = document.createElement('input');
|
||||
input.type = 'number';
|
||||
input.step = '0.01';
|
||||
input.value = originalValue.toFixed(2);
|
||||
input.className = 'form-control';
|
||||
cell.innerHTML = '';
|
||||
cell.appendChild(input);
|
||||
input.focus();
|
||||
input.select();
|
||||
|
||||
const onFinish = () => {
|
||||
const newValue = parseFloat(input.value) || 0;
|
||||
cell.textContent = newValue.toFixed(2);
|
||||
saveData(cell, newValue, originalValue);
|
||||
};
|
||||
|
||||
input.addEventListener('blur', onFinish, { once: true });
|
||||
input.addEventListener('keydown', e => {
|
||||
if (e.key === 'Enter') input.blur();
|
||||
if (e.key === 'Escape') {
|
||||
cell.textContent = originalValue.toFixed(2);
|
||||
input.removeEventListener('blur', onFinish);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function saveData(cell, value, originalValue) {
|
||||
const row = cell.closest('tr');
|
||||
updateUI(row); // Optimistic UI update
|
||||
|
||||
const formData = new FormData();
|
||||
formData.append('fecha', row.dataset.fecha);
|
||||
formData.append('field', cell.dataset.field);
|
||||
formData.append('value', value);
|
||||
|
||||
fetch('handle_editar_flujo_de_caja.php', { method: 'POST', body: formData })
|
||||
.then(res => res.json())
|
||||
.then(data => {
|
||||
if (!data.success) {
|
||||
console.error('Error al guardar:', data.message);
|
||||
cell.textContent = originalValue.toFixed(2);
|
||||
updateUI(row); // Revert on failure
|
||||
}
|
||||
})
|
||||
.catch(error => {
|
||||
console.error('Error de red:', error);
|
||||
cell.textContent = originalValue.toFixed(2);
|
||||
updateUI(row); // Revert on failure
|
||||
});
|
||||
}
|
||||
|
||||
function getCellValue(row, field) {
|
||||
const cell = row.querySelector(`[data-field="${field}"]`);
|
||||
return cell ? parseFloat(cell.textContent.replace(/,/g, '')) : 0;
|
||||
}
|
||||
|
||||
function setCellValue(row, field, value) {
|
||||
const cell = row.querySelector(`[data-field="${field}"]`);
|
||||
if (cell) cell.textContent = value.toFixed(2);
|
||||
}
|
||||
|
||||
function recalculateRow(row) {
|
||||
// Calcular rc_envio a partir de sus componentes
|
||||
const rc_envio_calc =
|
||||
getCellValue(row, 'bcp_yape') + getCellValue(row, 'banco_nacion') +
|
||||
getCellValue(row, 'interbank') + getCellValue(row, 'bbva') +
|
||||
getCellValue(row, 'otros_ingresos');
|
||||
|
||||
setCellValue(row, 'rc_envio', rc_envio_calc);
|
||||
|
||||
const ingresos = rc_envio_calc + getCellValue(row, 'rc_contraent');
|
||||
|
||||
const egresos =
|
||||
getCellValue(row, 'tu_1') + getCellValue(row, 'tu_2') +
|
||||
getCellValue(row, 'tu_3') + getCellValue(row, 'fl_1') +
|
||||
getCellValue(row, 'fl_2') + getCellValue(row, 'fl_3');
|
||||
|
||||
const recaudoFinal = ingresos - egresos;
|
||||
|
||||
setCellValue(row, 'total_ingresos', ingresos);
|
||||
setCellValue(row, 'total_egresos', egresos);
|
||||
setCellValue(row, 'recaudo_final', recaudoFinal);
|
||||
}
|
||||
|
||||
function updateUI(row) {
|
||||
recalculateRow(row);
|
||||
updateFooterTotals();
|
||||
}
|
||||
|
||||
function updateFooterTotals() {
|
||||
const footer = document.querySelector('tfoot tr');
|
||||
const totals = {};
|
||||
|
||||
footer.querySelectorAll('[data-total]').forEach(cell => {
|
||||
totals[cell.dataset.total] = 0;
|
||||
});
|
||||
|
||||
document.querySelectorAll('tbody tr').forEach(row => {
|
||||
for (const key in totals) {
|
||||
totals[key] += getCellValue(row, key);
|
||||
}
|
||||
});
|
||||
|
||||
// Recalcular totales derivados que no son una suma directa de columnas
|
||||
totals['rc_envio'] = totals['bcp_yape'] + totals['banco_nacion'] + totals['interbank'] + totals['bbva'] + totals['otros_ingresos'];
|
||||
totals['total_ingresos'] = totals['rc_envio'] + totals['rc_contraent'];
|
||||
totals['total_egresos'] = totals['tu_1'] + totals['tu_2'] + totals['tu_3'] + totals['fl_1'] + totals['fl_2'] + totals['fl_3'];
|
||||
totals['recaudo_final'] = totals['total_ingresos'] - totals['total_egresos'];
|
||||
|
||||
for (const key in totals) {
|
||||
const cell = footer.querySelector(`[data-total="${key}"]`);
|
||||
if(cell) cell.textContent = totals[key].toFixed(2);
|
||||
}
|
||||
}
|
||||
|
||||
// Initial calculation on load
|
||||
document.querySelectorAll('tbody tr').forEach(row => recalculateRow(row));
|
||||
updateFooterTotals();
|
||||
});
|
||||
</script>
|
||||
|
||||
<?php require_once 'includes/footer.php'; ?>
|
||||
52
handle_agregar_ciudad.php
Normal file
@ -0,0 +1,52 @@
|
||||
<?php
|
||||
session_start();
|
||||
if (!isset($_SESSION["user_id"])) {
|
||||
header("Location: auth/login.php");
|
||||
exit();
|
||||
}
|
||||
|
||||
require_once 'db/config.php';
|
||||
|
||||
if ($_SERVER["REQUEST_METHOD"] == "POST") {
|
||||
$nombre = trim($_POST['nombre']);
|
||||
|
||||
if (empty($nombre)) {
|
||||
$_SESSION['error_message'] = "El nombre de la ciudad no puede estar vacío.";
|
||||
header("Location: agregar_ciudad.php");
|
||||
exit();
|
||||
}
|
||||
|
||||
try {
|
||||
$pdo = db();
|
||||
|
||||
// Obtener el máximo valor de orden actual
|
||||
$stmt_max = $pdo->query("SELECT MAX(orden) AS max_orden FROM ciudades");
|
||||
$max_orden = $stmt_max->fetchColumn();
|
||||
$nuevo_orden = ($max_orden !== null) ? $max_orden + 1 : 0;
|
||||
|
||||
$sql = "INSERT INTO ciudades (nombre, orden) VALUES (:nombre, :orden)";
|
||||
$stmt = $pdo->prepare($sql);
|
||||
$stmt->bindParam(':nombre', $nombre, PDO::PARAM_STR);
|
||||
$stmt->bindParam(':orden', $nuevo_orden, PDO::PARAM_INT);
|
||||
|
||||
if ($stmt->execute()) {
|
||||
$_SESSION['success_message'] = "Ciudad agregada exitosamente.";
|
||||
} else {
|
||||
$_SESSION['error_message'] = "Error al agregar la ciudad.";
|
||||
}
|
||||
} catch (PDOException $e) {
|
||||
// Check for duplicate entry
|
||||
if ($e->errorInfo[1] == 1062) {
|
||||
$_SESSION['error_message'] = "Error: Ya existe una ciudad con ese nombre.";
|
||||
} else {
|
||||
$_SESSION['error_message'] = "Error de base de datos: " . $e->getMessage();
|
||||
}
|
||||
}
|
||||
|
||||
header("Location: ciudades.php");
|
||||
exit();
|
||||
} else {
|
||||
header("Location: agregar_ciudad.php");
|
||||
exit();
|
||||
}
|
||||
?>
|
||||
83
handle_agregar_colaborador.php
Normal file
@ -0,0 +1,83 @@
|
||||
<?php
|
||||
session_start();
|
||||
if (!isset($_SESSION["user_id"])) {
|
||||
header("Location: auth/login.php");
|
||||
exit();
|
||||
}
|
||||
|
||||
if (!isset($_SESSION['user_rol']) || $_SESSION['user_rol'] !== 'Administrador General') {
|
||||
$_SESSION['error_message'] = 'No tienes permiso para realizar esta acción.';
|
||||
header('Location: colaboradores.php');
|
||||
exit();
|
||||
}
|
||||
|
||||
require_once 'db/config.php';
|
||||
|
||||
if ($_SERVER["REQUEST_METHOD"] == "POST") {
|
||||
$nombre = trim($_POST['nombre']);
|
||||
$email = trim($_POST['email']);
|
||||
$password = $_POST['password'];
|
||||
$rol = $_POST['rol'];
|
||||
|
||||
// Validations
|
||||
if (empty($nombre) || empty($email) || empty($password) || empty($rol)) {
|
||||
$_SESSION['error_message'] = "Todos los campos son obligatorios.";
|
||||
header("Location: agregar_colaborador.php");
|
||||
exit();
|
||||
}
|
||||
|
||||
if (!filter_var($email, FILTER_VALIDATE_EMAIL)) {
|
||||
$_SESSION['error_message'] = "El formato del correo electrónico no es válido.";
|
||||
header("Location: agregar_colaborador.php");
|
||||
exit();
|
||||
}
|
||||
|
||||
if (!in_array($rol, ['Administrador General', 'Encargado de Stock'])) {
|
||||
$_SESSION['error_message'] = "El rol seleccionado no es válido.";
|
||||
header("Location: agregar_colaborador.php");
|
||||
exit();
|
||||
}
|
||||
|
||||
try {
|
||||
$pdo = db();
|
||||
|
||||
// Check if email already exists
|
||||
$stmt = $pdo->prepare("SELECT id FROM usuarios WHERE email = :email");
|
||||
$stmt->execute([':email' => $email]);
|
||||
if ($stmt->fetch()) {
|
||||
$_SESSION['error_message'] = "El correo electrónico ya está registrado.";
|
||||
header("Location: agregar_colaborador.php");
|
||||
exit();
|
||||
}
|
||||
|
||||
// Hash the password
|
||||
$hashed_password = password_hash($password, PASSWORD_DEFAULT);
|
||||
|
||||
// Insert new user
|
||||
$sql = "INSERT INTO usuarios (nombre, email, password, rol) VALUES (:nombre, :email, :password, :rol)";
|
||||
$stmt = $pdo->prepare($sql);
|
||||
|
||||
$stmt->execute([
|
||||
':nombre' => $nombre,
|
||||
':email' => $email,
|
||||
':password' => $hashed_password,
|
||||
':rol' => $rol
|
||||
]);
|
||||
|
||||
$_SESSION['success_message'] = "Colaborador agregado exitosamente.";
|
||||
header("Location: colaboradores.php");
|
||||
exit();
|
||||
|
||||
} catch (PDOException $e) {
|
||||
error_log("Error al agregar colaborador: " . $e->getMessage());
|
||||
$_SESSION['error_message'] = "Error al conectar con la base de datos. Por favor, inténtelo de nuevo.";
|
||||
header("Location: agregar_colaborador.php");
|
||||
exit();
|
||||
}
|
||||
|
||||
} else {
|
||||
// Redirect if not a POST request
|
||||
header("Location: agregar_colaborador.php");
|
||||
exit();
|
||||
}
|
||||
?>
|
||||
47
handle_agregar_info_producto.php
Normal file
@ -0,0 +1,47 @@
|
||||
<?php
|
||||
require_once 'db/config.php';
|
||||
|
||||
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
||||
$producto_id = $_POST['producto_id'] ?? null;
|
||||
$texto_informativo = $_POST['texto_informativo'] ?? null;
|
||||
$column_id = $_POST['column_id'] ?? null;
|
||||
$imagen = $_FILES['imagen'] ?? null;
|
||||
|
||||
if (!$producto_id || !$texto_informativo || !$column_id || !$imagen || $imagen['error'] !== UPLOAD_ERR_OK) {
|
||||
$_SESSION['error_message'] = 'Por favor, completa todos los campos, incluyendo la columna Kanban, y sube una imagen válida.';
|
||||
header('Location: info_producto.php');
|
||||
exit;
|
||||
}
|
||||
|
||||
$upload_dir = 'assets/images/info_productos/';
|
||||
if (!is_dir($upload_dir)) {
|
||||
mkdir($upload_dir, 0777, true);
|
||||
}
|
||||
|
||||
$imagen_nombre = uniqid('prod_', true) . '_' . basename($imagen['name']);
|
||||
$imagen_path = $upload_dir . $imagen_nombre;
|
||||
|
||||
if (move_uploaded_file($imagen['tmp_name'], $imagen_path)) {
|
||||
try {
|
||||
$pdo = db();
|
||||
$sql = 'INSERT INTO info_productos (producto_id, imagen_url, texto_informativo, column_id) VALUES (:producto_id, :imagen_url, :texto_informativo, :column_id)';
|
||||
$stmt = $pdo->prepare($sql);
|
||||
$stmt->execute([
|
||||
':producto_id' => $producto_id,
|
||||
':imagen_url' => $imagen_path,
|
||||
':texto_informativo' => $texto_informativo,
|
||||
':column_id' => $column_id
|
||||
]);
|
||||
$_SESSION['success_message'] = 'Tarjeta de información creada exitosamente.';
|
||||
} catch (PDOException $e) {
|
||||
// If DB insert fails, delete the uploaded image
|
||||
unlink($imagen_path);
|
||||
$_SESSION['error_message'] = 'Error al guardar en la base de datos: ' . $e->getMessage();
|
||||
}
|
||||
} else {
|
||||
$_SESSION['error_message'] = 'Error al subir la imagen.';
|
||||
}
|
||||
}
|
||||
|
||||
header('Location: info_producto.php');
|
||||
exit;
|
||||
84
handle_agregar_liquidacion.php
Normal file
@ -0,0 +1,84 @@
|
||||
<?php
|
||||
session_start();
|
||||
require_once 'db/config.php';
|
||||
|
||||
// Verificar que el usuario sea administrador
|
||||
if (!isset($_SESSION['user_rol']) || ($_SESSION['user_rol'] !== 'admin' && $_SESSION['user_rol'] !== 'administrador')) {
|
||||
// Si no es admin, redirigir o mostrar un error
|
||||
header('Location: /liquidaciones.php?error=auth');
|
||||
exit;
|
||||
}
|
||||
|
||||
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
||||
$producto_id = $_POST['producto_id'] ?? null;
|
||||
$ciudad_id = $_POST['ciudad_id'] ?? null;
|
||||
$cantidad = $_POST['cantidad'] ?? null;
|
||||
$cantidad_pedidos = $_POST['cantidad_pedidos'] ?? null;
|
||||
$precio_liquidacion = $_POST['precio_liquidacion'] ?? null;
|
||||
|
||||
// Validación simple
|
||||
if (empty($producto_id) || empty($ciudad_id) || empty($cantidad) || $cantidad <= 0 || empty($cantidad_pedidos) || empty($precio_liquidacion)) {
|
||||
header('Location: /liquidaciones.php?error=missing_fields');
|
||||
exit;
|
||||
}
|
||||
|
||||
$pdo = db();
|
||||
|
||||
try {
|
||||
$pdo->beginTransaction();
|
||||
|
||||
// 1. Verificar stock actual
|
||||
$stmt = $pdo->prepare("SELECT stock_actual FROM stock_por_ciudad WHERE producto_id = :producto_id AND ciudad_id = :ciudad_id FOR UPDATE");
|
||||
$stmt->execute(['producto_id' => $producto_id, 'ciudad_id' => $ciudad_id]);
|
||||
$stock_disponible = $stmt->fetchColumn();
|
||||
|
||||
if ($stock_disponible === false || $stock_disponible < $cantidad) {
|
||||
header('Location: /liquidaciones.php?error=no_stock');
|
||||
$pdo->rollBack();
|
||||
exit();
|
||||
}
|
||||
|
||||
// 2. Actualizar el stock en la ciudad
|
||||
$stmt_stock = $pdo->prepare("UPDATE stock_por_ciudad SET stock_actual = stock_actual - :cantidad WHERE producto_id = :producto_id AND ciudad_id = :ciudad_id");
|
||||
$stmt_stock->execute([':cantidad' => $cantidad, ':producto_id' => $producto_id, ':ciudad_id' => $ciudad_id]);
|
||||
|
||||
// 3. Insertar el movimiento de salida
|
||||
$stmt = $pdo->prepare("
|
||||
INSERT INTO movimientos
|
||||
(tipo, producto_id, cantidad, ciudad_origen_id, cantidad_pedidos, precio_liquidacion, fecha, usuario_id)
|
||||
VALUES ('Salida', :producto_id, :cantidad, :ciudad_id, :cantidad_pedidos, :precio_liquidacion, NOW(), :usuario_id)
|
||||
");
|
||||
|
||||
$user_id = get_current_user_id();
|
||||
|
||||
$stmt->execute([
|
||||
':producto_id' => $producto_id,
|
||||
':cantidad' => $cantidad,
|
||||
':ciudad_id' => $ciudad_id,
|
||||
':cantidad_pedidos' => $cantidad_pedidos,
|
||||
':precio_liquidacion' => $precio_liquidacion,
|
||||
':usuario_id' => $user_id
|
||||
]);
|
||||
|
||||
// Confirmar transacción
|
||||
$pdo->commit();
|
||||
|
||||
header('Location: /liquidaciones.php?success=added');
|
||||
exit;
|
||||
|
||||
} catch (PDOException $e) {
|
||||
// Revertir transacción en caso de error
|
||||
if ($pdo->inTransaction()) {
|
||||
$pdo->rollBack();
|
||||
}
|
||||
// Manejo de errores
|
||||
error_log($e->getMessage());
|
||||
header('Location: /liquidaciones.php?error=db_error');
|
||||
exit;
|
||||
}
|
||||
} else {
|
||||
// Si no es POST, redirigir
|
||||
header('Location: /liquidaciones.php');
|
||||
exit;
|
||||
}
|
||||
?>
|
||||
72
handle_agregar_producto.php
Normal file
@ -0,0 +1,72 @@
|
||||
<?php
|
||||
session_start();
|
||||
|
||||
if (!isset($_SESSION["user_id"])) {
|
||||
header("Location: auth/login.php");
|
||||
exit();
|
||||
}
|
||||
|
||||
require_once 'db/config.php';
|
||||
|
||||
if ($_SERVER["REQUEST_METHOD"] == "POST") {
|
||||
// Sanitize and retrieve product data
|
||||
$nombre = filter_input(INPUT_POST, 'nombre', 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);
|
||||
|
||||
// Retrieve stock data array
|
||||
$stocks_por_ciudad = isset($_POST['stock_ciudad']) ? $_POST['stock_ciudad'] : [];
|
||||
|
||||
// Basic validation
|
||||
if ($nombre && $costo !== false && $precio_venta !== false) {
|
||||
$pdo = db();
|
||||
try {
|
||||
$pdo->beginTransaction();
|
||||
|
||||
// 1. Insert product
|
||||
$sku = 'SKU-' . strtoupper(substr(uniqid(), -8));
|
||||
$sql_producto = "INSERT INTO productos (sku, nombre, descripcion, costo, precio_venta, created_at) VALUES (?, ?, ?, ?, ?, NOW())";
|
||||
$stmt_producto = $pdo->prepare($sql_producto);
|
||||
$stmt_producto->execute([$sku, $nombre, $descripcion, $costo, $precio_venta]);
|
||||
|
||||
// Get the ID of the new product
|
||||
$producto_id = $pdo->lastInsertId();
|
||||
|
||||
// 2. Insert stock for each city
|
||||
$sql_stock = "INSERT INTO stock_por_ciudad (producto_id, ciudad_id, stock_actual) VALUES (?, ?, ?)";
|
||||
$stmt_stock = $pdo->prepare($sql_stock);
|
||||
|
||||
foreach ($stocks_por_ciudad as $ciudad_id => $cantidad) {
|
||||
$ciudad_id_int = filter_var($ciudad_id, FILTER_VALIDATE_INT);
|
||||
$cantidad_int = filter_var($cantidad, FILTER_VALIDATE_INT);
|
||||
|
||||
// Only insert if stock is greater than 0 and IDs are valid
|
||||
if ($ciudad_id_int && $cantidad_int > 0) {
|
||||
$stmt_stock->execute([$producto_id, $ciudad_id_int, $cantidad_int]);
|
||||
}
|
||||
}
|
||||
|
||||
// If all is well, commit the transaction
|
||||
$pdo->commit();
|
||||
|
||||
$_SESSION['success_message'] = "Producto y stock por ciudad agregados exitosamente.";
|
||||
|
||||
} catch (PDOException $e) {
|
||||
// If something goes wrong, roll back the transaction
|
||||
if ($pdo->inTransaction()) {
|
||||
$pdo->rollBack();
|
||||
}
|
||||
// Log error properly in a real application
|
||||
$_SESSION['error_message'] = "Error al agregar el producto. Detalles: " . $e->getMessage();
|
||||
}
|
||||
} else {
|
||||
$_SESSION['error_message'] = "Datos de producto inválidos. Por favor, revisa el formulario.";
|
||||
}
|
||||
} else {
|
||||
$_SESSION['error_message'] = "Método no permitido.";
|
||||
}
|
||||
|
||||
// Redirect back to the products page
|
||||
header("Location: productos.php");
|
||||
exit();
|
||||
47
handle_confirmacion_stock.php
Normal file
@ -0,0 +1,47 @@
|
||||
<?php
|
||||
session_start();
|
||||
require_once 'db/config.php';
|
||||
|
||||
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
||||
date_default_timezone_set('America/Lima');
|
||||
$usuario_id = filter_input(INPUT_POST, 'usuario_id', FILTER_VALIDATE_INT);
|
||||
$fecha_actualizacion = $_POST['fecha_actualizacion'] ?? null;
|
||||
$ciudad_id = filter_input(INPUT_POST, 'ciudad_id', FILTER_VALIDATE_INT);
|
||||
|
||||
if ($usuario_id && $fecha_actualizacion && $ciudad_id) {
|
||||
$pdo = db();
|
||||
try {
|
||||
$pdo->beginTransaction();
|
||||
|
||||
$fecha_hora_actual = date('Y-m-d H:i:s');
|
||||
$stmt = $pdo->prepare('INSERT INTO stock_confirmaciones (usuario_id, fecha_hora, fecha_actualizacion) VALUES (:usuario_id, :fecha_hora, :fecha_actualizacion)');
|
||||
$stmt->execute([
|
||||
'usuario_id' => $usuario_id,
|
||||
'fecha_hora' => $fecha_hora_actual,
|
||||
'fecha_actualizacion' => $fecha_actualizacion
|
||||
]);
|
||||
|
||||
$confirmacion_id = $pdo->lastInsertId();
|
||||
|
||||
$stmt_ciudad = $pdo->prepare('INSERT INTO stock_confirmacion_ciudades (confirmacion_id, ciudad_id) VALUES (:confirmacion_id, :ciudad_id)');
|
||||
$stmt_ciudad->execute([
|
||||
'confirmacion_id' => $confirmacion_id,
|
||||
'ciudad_id' => $ciudad_id
|
||||
]);
|
||||
|
||||
$pdo->commit();
|
||||
|
||||
$_SESSION['flash_message'] = ['type' => 'success', 'message' => 'Entrega registrada exitosamente.'];
|
||||
} catch (PDOException $e) {
|
||||
$pdo->rollBack();
|
||||
error_log('Error en handle_confirmacion_stock.php: ' . $e->getMessage());
|
||||
$_SESSION['flash_message'] = ['type' => 'danger', 'message' => 'Error al registrar la entrega.'];
|
||||
}
|
||||
} else {
|
||||
$_SESSION['flash_message'] = ['type' => 'danger', 'message' => 'Por favor, complete todos los campos.'];
|
||||
}
|
||||
}
|
||||
|
||||
header('Location: confirmacion_stock.php');
|
||||
exit;
|
||||
?>
|
||||
18
handle_create_column.php
Normal file
@ -0,0 +1,18 @@
|
||||
<?php
|
||||
require_once 'db/config.php';
|
||||
|
||||
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
||||
$column_name = trim($_POST['column_name']);
|
||||
if (!empty($column_name)) {
|
||||
$pdo = db();
|
||||
// Find the current max order value
|
||||
$stmt_max = $pdo->query('SELECT MAX(orden) as max_orden FROM kanban_columns');
|
||||
$max_orden = $stmt_max->fetchColumn() ?? 0;
|
||||
|
||||
$stmt = $pdo->prepare('INSERT INTO kanban_columns (nombre, orden) VALUES (:nombre, :orden)');
|
||||
$stmt->execute([':nombre' => $column_name, ':orden' => $max_orden + 1]);
|
||||
}
|
||||
}
|
||||
|
||||
header('Location: kanban.php');
|
||||
exit;
|
||||
22
handle_delete_column.php
Normal file
@ -0,0 +1,22 @@
|
||||
<?php
|
||||
require_once 'db/config.php';
|
||||
|
||||
if (isset($_GET['id'])) {
|
||||
$column_id = $_GET['id'];
|
||||
$pdo = db();
|
||||
|
||||
// Set column_id to NULL for all cards in this column
|
||||
$stmt_update = $pdo->prepare('UPDATE info_productos SET column_id = NULL WHERE column_id = :column_id');
|
||||
$stmt_update->execute([':column_id' => $column_id]);
|
||||
|
||||
// Delete the column
|
||||
$stmt_delete = $pdo->prepare('DELETE FROM kanban_columns WHERE id = :id');
|
||||
$stmt_delete->execute([':id' => $column_id]);
|
||||
|
||||
$_SESSION['success_message'] = 'Columna eliminada. Las tarjetas han sido desasignadas.';
|
||||
} else {
|
||||
$_SESSION['error_message'] = 'ID de columna no especificado.';
|
||||
}
|
||||
|
||||
header('Location: kanban.php');
|
||||
exit;
|
||||
48
handle_editar_ciudad.php
Normal file
@ -0,0 +1,48 @@
|
||||
<?php
|
||||
session_start();
|
||||
if (!isset($_SESSION["user_id"])) {
|
||||
header("Location: auth/login.php");
|
||||
exit();
|
||||
}
|
||||
|
||||
require_once 'db/config.php';
|
||||
|
||||
if ($_SERVER["REQUEST_METHOD"] == "POST") {
|
||||
$id = isset($_POST['id']) ? (int)$_POST['id'] : 0;
|
||||
$nombre = trim($_POST['nombre']);
|
||||
$orden = filter_input(INPUT_POST, 'orden', FILTER_VALIDATE_INT);
|
||||
|
||||
if ($id === 0 || empty($nombre) || $orden === false || $orden < 0) {
|
||||
$_SESSION['error_message'] = "Datos inválidos para actualizar la ciudad.";
|
||||
header("Location: ciudades.php");
|
||||
exit();
|
||||
}
|
||||
|
||||
try {
|
||||
$pdo = db();
|
||||
$sql = "UPDATE ciudades SET nombre = :nombre, orden = :orden WHERE id = :id";
|
||||
$stmt = $pdo->prepare($sql);
|
||||
$stmt->bindParam(':nombre', $nombre, PDO::PARAM_STR);
|
||||
$stmt->bindParam(':orden', $orden, PDO::PARAM_INT);
|
||||
$stmt->bindParam(':id', $id, PDO::PARAM_INT);
|
||||
|
||||
if ($stmt->execute()) {
|
||||
$_SESSION['success_message'] = "Ciudad actualizada exitosamente.";
|
||||
} else {
|
||||
$_SESSION['error_message'] = "Error al actualizar la ciudad.";
|
||||
}
|
||||
} catch (PDOException $e) {
|
||||
if ($e->errorInfo[1] == 1062) {
|
||||
$_SESSION['error_message'] = "Error: Ya existe una ciudad con ese nombre.";
|
||||
} else {
|
||||
$_SESSION['error_message'] = "Error de base de datos: " . $e->getMessage();
|
||||
}
|
||||
}
|
||||
|
||||
header("Location: ciudades.php");
|
||||
exit();
|
||||
} else {
|
||||
header("Location: ciudades.php");
|
||||
exit();
|
||||
}
|
||||
?>
|
||||
94
handle_editar_colaborador.php
Normal file
@ -0,0 +1,94 @@
|
||||
<?php
|
||||
session_start();
|
||||
if (!isset($_SESSION["user_id"])) {
|
||||
header("Location: auth/login.php");
|
||||
exit();
|
||||
}
|
||||
|
||||
if (!isset($_SESSION['user_rol']) || $_SESSION['user_rol'] !== 'Administrador General') {
|
||||
$_SESSION['error_message'] = 'No tienes permiso para realizar esta acción.';
|
||||
header('Location: colaboradores.php');
|
||||
exit();
|
||||
}
|
||||
|
||||
require_once 'db/config.php';
|
||||
|
||||
if ($_SERVER["REQUEST_METHOD"] == "POST") {
|
||||
$id = $_POST['id'];
|
||||
$nombre = trim($_POST['nombre']);
|
||||
$email = trim($_POST['email']);
|
||||
$password = $_POST['password'];
|
||||
$rol = $_POST['rol'];
|
||||
|
||||
// Validations
|
||||
if (empty($id) || empty($nombre) || empty($email) || empty($rol)) {
|
||||
$_SESSION['error_message'] = "Todos los campos, excepto la contraseña, son obligatorios.";
|
||||
header("Location: editar_colaborador.php?id=" . $id);
|
||||
exit();
|
||||
}
|
||||
|
||||
if (!filter_var($email, FILTER_VALIDATE_EMAIL)) {
|
||||
$_SESSION['error_message'] = "El formato del correo electrónico no es válido.";
|
||||
header("Location: editar_colaborador.php?id=" . $id);
|
||||
exit();
|
||||
}
|
||||
|
||||
if (!in_array($rol, ['Administrador General', 'Encargado de Stock'])) {
|
||||
$_SESSION['error_message'] = "El rol seleccionado no es válido.";
|
||||
header("Location: editar_colaborador.php?id=" . $id);
|
||||
exit();
|
||||
}
|
||||
|
||||
try {
|
||||
$pdo = db();
|
||||
|
||||
// Check if email already exists for another user
|
||||
$stmt = $pdo->prepare("SELECT id FROM usuarios WHERE email = :email AND id != :id");
|
||||
$stmt->execute([':email' => $email, ':id' => $id]);
|
||||
if ($stmt->fetch()) {
|
||||
$_SESSION['error_message'] = "El correo electrónico ya está registrado por otro usuario.";
|
||||
header("Location: editar_colaborador.php?id=" . $id);
|
||||
exit();
|
||||
}
|
||||
|
||||
// Build the query
|
||||
$sql_parts = [
|
||||
"nombre = :nombre",
|
||||
"email = :email",
|
||||
"rol = :rol"
|
||||
];
|
||||
$params = [
|
||||
':nombre' => $nombre,
|
||||
':email' => $email,
|
||||
':rol' => $rol,
|
||||
':id' => $id
|
||||
];
|
||||
|
||||
// If password is provided, add it to the query
|
||||
if (!empty($password)) {
|
||||
$hashed_password = password_hash($password, PASSWORD_DEFAULT);
|
||||
$sql_parts[] = "password = :password";
|
||||
$params[':password'] = $hashed_password;
|
||||
}
|
||||
|
||||
$sql = "UPDATE usuarios SET " . implode(", ", $sql_parts) . " WHERE id = :id";
|
||||
|
||||
$stmt = $pdo->prepare($sql);
|
||||
$stmt->execute($params);
|
||||
|
||||
$_SESSION['success_message'] = "Colaborador actualizado exitosamente.";
|
||||
header("Location: colaboradores.php");
|
||||
exit();
|
||||
|
||||
} catch (PDOException $e) {
|
||||
error_log("Error al editar colaborador: " . $e->getMessage());
|
||||
$_SESSION['error_message'] = "Error al conectar con la base de datos. Por favor, inténtelo de nuevo.";
|
||||
header("Location: editar_colaborador.php?id=" . $id);
|
||||
exit();
|
||||
}
|
||||
|
||||
} else {
|
||||
header("Location: colaboradores.php");
|
||||
exit();
|
||||
}
|
||||
?>
|
||||
73
handle_editar_flujo_de_caja.php
Normal file
@ -0,0 +1,73 @@
|
||||
<?php
|
||||
require_once 'db/config.php';
|
||||
|
||||
// Habilitar el reporte de errores para depuración
|
||||
ini_set('display_errors', 1);
|
||||
ini_set('display_startup_errors', 1);
|
||||
error_reporting(E_ALL);
|
||||
|
||||
header('Content-Type: application/json');
|
||||
|
||||
$response = ['success' => false, 'message' => 'Error desconocido.'];
|
||||
|
||||
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
||||
$fecha = $_POST['fecha'] ?? null;
|
||||
$field = $_POST['field'] ?? null;
|
||||
$value = $_POST['value'] ?? null;
|
||||
|
||||
if (!$fecha || !$field || $value === null) {
|
||||
$response['message'] = 'Datos incompletos: fecha, campo o valor no proporcionados.';
|
||||
echo json_encode($response);
|
||||
exit;
|
||||
}
|
||||
|
||||
// Lista blanca de campos editables para seguridad
|
||||
$allowed_fields = [
|
||||
'bcp_yape', 'banco_nacion', 'interbank', 'bbva', 'otros_ingresos',
|
||||
'tu_1', 'tu_2', 'tu_3',
|
||||
'fl_1', 'fl_2', 'fl_3'
|
||||
];
|
||||
|
||||
if (!in_array($field, $allowed_fields)) {
|
||||
$response['message'] = "El campo '{$field}' no es editable.";
|
||||
echo json_encode($response);
|
||||
exit;
|
||||
}
|
||||
|
||||
try {
|
||||
$db = db();
|
||||
|
||||
// Verificar si ya existe una fila para esa fecha
|
||||
$stmt_check = $db->prepare("SELECT id FROM flujo_de_caja WHERE fecha = ?");
|
||||
$stmt_check->execute([$fecha]);
|
||||
$existing_id = $stmt_check->fetchColumn();
|
||||
|
||||
if ($existing_id) {
|
||||
// Si existe, actualizar el campo específico
|
||||
$sql = "UPDATE flujo_de_caja SET {$field} = ? WHERE id = ?";
|
||||
$stmt = $db->prepare($sql);
|
||||
$stmt->execute([$value, $existing_id]);
|
||||
} else {
|
||||
// Si no existe, crear una nueva fila con el valor
|
||||
$sql = "INSERT INTO flujo_de_caja (fecha, {$field}) VALUES (?, ?)";
|
||||
$stmt = $db->prepare($sql);
|
||||
$stmt->execute([$fecha, $value]);
|
||||
}
|
||||
|
||||
if ($stmt->rowCount() > 0 || $existing_id) {
|
||||
$response['success'] = true;
|
||||
$response['message'] = 'Dato guardado correctamente.';
|
||||
} else {
|
||||
$response['message'] = 'No se realizaron cambios.';
|
||||
}
|
||||
|
||||
} catch (PDOException $e) {
|
||||
// Capturar errores de la base de datos
|
||||
$response['message'] = "Error de base de datos: " . $e->getMessage();
|
||||
}
|
||||
} else {
|
||||
$response['message'] = 'Método de solicitud no válido.';
|
||||
}
|
||||
|
||||
echo json_encode($response);
|
||||
?>
|
||||
38
handle_editar_inversion.php
Normal file
@ -0,0 +1,38 @@
|
||||
<?php
|
||||
require_once 'db/config.php';
|
||||
require_once 'includes/header.php'; // Para la sesión y seguridad
|
||||
|
||||
// Verificar permisos
|
||||
if (!isset($_SESSION['user_id'])) {
|
||||
header('Location: /auth/login.php');
|
||||
exit;
|
||||
}
|
||||
|
||||
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
||||
$id = $_POST['id'] ?? null;
|
||||
$fecha = $_POST['fecha'] ?? null;
|
||||
$descripcion = trim($_POST['descripcion'] ?? '');
|
||||
$monto = $_POST['monto'] ?? null;
|
||||
$tipo = $_POST['tipo'] ?? null;
|
||||
|
||||
if (!$id || !$fecha || empty($descripcion) || !is_numeric($monto) || empty($tipo)) {
|
||||
header('Location: inversiones_operativas.php?error=invalid_data');
|
||||
exit;
|
||||
}
|
||||
|
||||
try {
|
||||
$pdo = db();
|
||||
$stmt = $pdo->prepare("UPDATE inversiones SET fecha = ?, descripcion = ?, monto = ?, tipo = ? WHERE id = ?");
|
||||
$stmt->execute([$fecha, $descripcion, $monto, $tipo, $id]);
|
||||
|
||||
header('Location: inversiones_operativas.php?success=updated#section-' . $tipo);
|
||||
exit;
|
||||
} catch (PDOException $e) {
|
||||
header('Location: inversiones_operativas.php?error=db_error');
|
||||
exit;
|
||||
}
|
||||
} else {
|
||||
header('Location: inversiones_operativas.php');
|
||||
exit;
|
||||
}
|
||||
?>
|
||||
95
handle_editar_liquidacion.php
Normal file
@ -0,0 +1,95 @@
|
||||
<?php
|
||||
require_once 'includes/header.php';
|
||||
|
||||
if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
|
||||
header('Location: liquidaciones.php');
|
||||
exit();
|
||||
}
|
||||
|
||||
// Validar que el usuario sea administrador
|
||||
if (!isset($_SESSION['user_rol']) || ($_SESSION['user_rol'] !== 'admin' && $_SESSION['user_rol'] !== 'administrador')) {
|
||||
header('Location: login.php?error=unauthorized');
|
||||
exit();
|
||||
}
|
||||
|
||||
// Validar campos
|
||||
$movimiento_id = $_POST['movimiento_id'] ?? null;
|
||||
$producto_id = $_POST['producto_id'] ?? null;
|
||||
$ciudad_id = $_POST['ciudad_id'] ?? null;
|
||||
$cantidad = $_POST['cantidad'] ?? null;
|
||||
$cantidad_pedidos = $_POST['cantidad_pedidos'] ?? null;
|
||||
$precio_liquidacion = $_POST['precio_liquidacion'] ?? null;
|
||||
$fecha = $_POST['fecha'] ?? null;
|
||||
|
||||
if (empty($movimiento_id) || empty($producto_id) || empty($ciudad_id) || empty($cantidad) || !isset($cantidad_pedidos) || !isset($precio_liquidacion) || empty($fecha)) {
|
||||
header('Location: editar_liquidacion.php?id=' . $movimiento_id . '&error=missing_fields');
|
||||
exit();
|
||||
}
|
||||
|
||||
$pdo = db();
|
||||
|
||||
// Iniciar transacción
|
||||
$pdo->beginTransaction();
|
||||
|
||||
try {
|
||||
// 1. Obtener el movimiento original para revertir el stock
|
||||
$stmt = $pdo->prepare("SELECT producto_id, ciudad_origen_id, cantidad FROM movimientos WHERE id = ?");
|
||||
$stmt->execute([$movimiento_id]);
|
||||
$movimiento_original = $stmt->fetch(PDO::FETCH_ASSOC);
|
||||
|
||||
if ($movimiento_original) {
|
||||
// Revertir el stock del producto original en la ciudad de origen
|
||||
$stmt_revert = $pdo->prepare("UPDATE stock_por_ciudad SET stock_actual = stock_actual + ? WHERE producto_id = ? AND ciudad_id = ?");
|
||||
$stmt_revert->execute([$movimiento_original['cantidad'], $movimiento_original['producto_id'], $movimiento_original['ciudad_origen_id']]);
|
||||
}
|
||||
|
||||
// 2. Verificar si hay suficiente stock para el nuevo producto/cantidad en la nueva ciudad
|
||||
$stmt_stock = $pdo->prepare("SELECT stock_actual FROM stock_por_ciudad WHERE producto_id = ? AND ciudad_id = ?");
|
||||
$stmt_stock->execute([$producto_id, $ciudad_id]);
|
||||
$stock_actual = $stmt_stock->fetchColumn();
|
||||
|
||||
if ($stock_actual === false || $stock_actual < $cantidad) {
|
||||
// Si no hay suficiente stock, revertir la transacción y redirigir con error
|
||||
$pdo->rollBack();
|
||||
header('Location: editar_liquidacion.php?id=' . $movimiento_id . '&error=no_stock');
|
||||
exit();
|
||||
}
|
||||
|
||||
// 3. Actualizar el movimiento
|
||||
$sql = "UPDATE movimientos
|
||||
SET producto_id = ?,
|
||||
ciudad_origen_id = ?,
|
||||
cantidad = ?,
|
||||
cantidad_pedidos = ?,
|
||||
precio_liquidacion = ?,
|
||||
fecha = ?
|
||||
WHERE id = ?";
|
||||
$stmt_update = $pdo->prepare($sql);
|
||||
$stmt_update->execute([
|
||||
$producto_id,
|
||||
$ciudad_id,
|
||||
$cantidad,
|
||||
$cantidad_pedidos,
|
||||
$precio_liquidacion,
|
||||
$fecha,
|
||||
$movimiento_id
|
||||
]);
|
||||
|
||||
// 4. Actualizar el stock para el nuevo producto/ciudad
|
||||
$stmt_update_stock = $pdo->prepare("UPDATE stock_por_ciudad SET stock_actual = stock_actual - ? WHERE producto_id = ? AND ciudad_id = ?");
|
||||
$stmt_update_stock->execute([$cantidad, $producto_id, $ciudad_id]);
|
||||
|
||||
// Confirmar la transacción
|
||||
$pdo->commit();
|
||||
|
||||
header('Location: liquidaciones.php?success=updated');
|
||||
exit();
|
||||
|
||||
} catch (Exception $e) {
|
||||
// Si hay un error, revertir la transacción
|
||||
$pdo->rollBack();
|
||||
// Log del error (opcional)
|
||||
// error_log($e->getMessage());
|
||||
header('Location: editar_liquidacion.php?id=' . $movimiento_id . '&error=db_error');
|
||||
exit();
|
||||
}
|
||||
40
handle_editar_producto.php
Normal file
@ -0,0 +1,40 @@
|
||||
<?php
|
||||
session_start();
|
||||
if (!isset($_SESSION["user_id"])) {
|
||||
header("Location: auth/login.php");
|
||||
exit();
|
||||
}
|
||||
|
||||
require_once 'db/config.php';
|
||||
|
||||
if ($_SERVER["REQUEST_METHOD"] == "POST") {
|
||||
$product_id = isset($_POST['id']) ? (int)$_POST['id'] : 0;
|
||||
$nombre = trim($_POST['nombre']);
|
||||
$descripcion = trim($_POST['descripcion']);
|
||||
$costo = filter_input(INPUT_POST, 'costo', FILTER_VALIDATE_FLOAT);
|
||||
$precio_venta = filter_input(INPUT_POST, 'precio_venta', FILTER_VALIDATE_FLOAT);
|
||||
|
||||
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 = ?";
|
||||
$stmt = $pdo->prepare($sql);
|
||||
|
||||
if ($stmt->execute([$nombre, $descripcion, $costo, $precio_venta, $product_id])) {
|
||||
$_SESSION['success_message'] = "Producto actualizado correctamente.";
|
||||
} else {
|
||||
$_SESSION['error_message'] = "Error al actualizar el producto.";
|
||||
}
|
||||
} catch (PDOException $e) {
|
||||
$_SESSION['error_message'] = "Error de base de datos: " . $e->getMessage();
|
||||
}
|
||||
} else {
|
||||
$_SESSION['error_message'] = "Por favor, complete todos los campos requeridos correctamente.";
|
||||
}
|
||||
} else {
|
||||
$_SESSION['error_message'] = "Método de solicitud no válido.";
|
||||
}
|
||||
|
||||
header("Location: productos.php");
|
||||
exit();
|
||||
?>
|
||||
76
handle_entrada.php
Normal file
@ -0,0 +1,76 @@
|
||||
<?php
|
||||
date_default_timezone_set('America/Lima');
|
||||
require_once 'db/config.php';
|
||||
|
||||
if (!is_logged_in()) {
|
||||
$_SESSION['error'] = "Debe iniciar sesión para realizar esta acción.";
|
||||
header("Location: auth/login.php");
|
||||
exit();
|
||||
}
|
||||
|
||||
if ($_SERVER["REQUEST_METHOD"] == "POST") {
|
||||
$producto_id = $_POST['producto_id'] ?? null;
|
||||
$ciudad_id = $_POST['ciudad_id'] ?? null;
|
||||
$cantidad = $_POST['cantidad'] ?? null;
|
||||
$observacion = $_POST['observacion'] ?? '';
|
||||
$usuario_id = get_current_user_id();
|
||||
|
||||
if (empty($producto_id) || empty($ciudad_id) || empty($cantidad) || !is_numeric($cantidad) || $cantidad <= 0) {
|
||||
$_SESSION['error'] = "Por favor, complete todos los campos correctamente.";
|
||||
header("Location: registrar_entrada.php");
|
||||
exit();
|
||||
}
|
||||
|
||||
$pdo = db();
|
||||
|
||||
try {
|
||||
$pdo->beginTransaction();
|
||||
|
||||
// 1. Verificar si ya existe stock para este producto en esta ciudad
|
||||
$stmt = $pdo->prepare("SELECT id FROM stock_por_ciudad WHERE producto_id = :producto_id AND ciudad_id = :ciudad_id FOR UPDATE");
|
||||
$stmt->execute(['producto_id' => $producto_id, 'ciudad_id' => $ciudad_id]);
|
||||
$stock_id = $stmt->fetchColumn();
|
||||
|
||||
// 2. Actualizar o insertar el stock
|
||||
if ($stock_id) {
|
||||
// Si existe, actualizar
|
||||
$stmt = $pdo->prepare("UPDATE stock_por_ciudad SET stock_actual = stock_actual + :cantidad WHERE id = :stock_id");
|
||||
$stmt->execute(['cantidad' => $cantidad, 'stock_id' => $stock_id]);
|
||||
} else {
|
||||
// Si no existe, insertar
|
||||
$stmt = $pdo->prepare("INSERT INTO stock_por_ciudad (producto_id, ciudad_id, stock_actual) VALUES (:producto_id, :ciudad_id, :stock_actual)");
|
||||
$stmt->execute(['producto_id' => $producto_id, 'ciudad_id' => $ciudad_id, 'stock_actual' => $cantidad]);
|
||||
}
|
||||
|
||||
// 3. Registrar el movimiento de entrada
|
||||
$fecha = $_POST['fecha'];
|
||||
$stmt = $pdo->prepare(
|
||||
"INSERT INTO movimientos (tipo, producto_id, ciudad_destino_id, cantidad, usuario_id, observacion, fecha, fecha_registro)
|
||||
VALUES ('Entrada', :producto_id, :ciudad_destino_id, :cantidad, :usuario_id, :observacion, :fecha, NOW())"
|
||||
);
|
||||
$stmt->execute([
|
||||
'producto_id' => $producto_id,
|
||||
'ciudad_destino_id' => $ciudad_id,
|
||||
'cantidad' => $cantidad,
|
||||
'usuario_id' => $usuario_id,
|
||||
'observacion' => $observacion,
|
||||
'fecha' => $fecha
|
||||
]);
|
||||
|
||||
$pdo->commit();
|
||||
|
||||
$_SESSION['success'] = "Entrada de stock registrada correctamente.";
|
||||
header("Location: productos.php");
|
||||
exit();
|
||||
|
||||
} catch (Exception $e) {
|
||||
$pdo->rollBack();
|
||||
$_SESSION['error'] = "Error al registrar la entrada: " . $e->getMessage();
|
||||
header("Location: registrar_entrada.php");
|
||||
exit();
|
||||
}
|
||||
} else {
|
||||
header("Location: registrar_entrada.php");
|
||||
exit();
|
||||
}
|
||||
?>
|
||||
24
handle_flujo_de_caja.php
Normal file
@ -0,0 +1,24 @@
|
||||
<?php
|
||||
require_once 'db/config.php';
|
||||
|
||||
if ($_SERVER['REQUEST_METHOD'] == 'POST') {
|
||||
$fecha = $_POST['fecha'];
|
||||
$bcp_yape = $_POST['bcp_yape'] ?? 0;
|
||||
$banco_nacion = $_POST['banco_nacion'] ?? 0;
|
||||
$interbank = $_POST['interbank'] ?? 0;
|
||||
$bbva = $_POST['bbva'] ?? 0;
|
||||
$otros = $_POST['otros'] ?? 0;
|
||||
|
||||
try {
|
||||
$pdo = db();
|
||||
$stmt = $pdo->prepare('INSERT INTO flujo_de_caja (fecha, bcp_yape, banco_nacion, interbank, bbva, otros) VALUES (?, ?, ?, ?, ?, ?)');
|
||||
$stmt->execute([$fecha, $bcp_yape, $banco_nacion, $interbank, $bbva, $otros]);
|
||||
header('Location: flujo_de_caja.php?status=success');
|
||||
} catch (PDOException $e) {
|
||||
// Log error or handle it appropriately
|
||||
header('Location: flujo_de_caja.php?status=error');
|
||||
}
|
||||
} else {
|
||||
header('Location: flujo_de_caja.php');
|
||||
}
|
||||
?>
|
||||
32
handle_move_card.php
Normal file
@ -0,0 +1,32 @@
|
||||
<?php
|
||||
require_once 'db/config.php';
|
||||
|
||||
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
||||
$card_id = $_POST['card_id'] ?? null;
|
||||
$column_id = $_POST['column_id'] ?? null;
|
||||
|
||||
if ($card_id && $column_id) {
|
||||
$pdo = db();
|
||||
|
||||
// Update the card's column
|
||||
$stmt = $pdo->prepare('UPDATE info_productos SET column_id = :column_id WHERE id = :card_id');
|
||||
$stmt->execute([':column_id' => $column_id, ':card_id' => $card_id]);
|
||||
|
||||
// Optional: Handle card order within the new column
|
||||
// For simplicity, we are not reordering, but you could add that logic here.
|
||||
// For example, set the moved card to be the last in the new column.
|
||||
$stmt_max_order = $pdo->prepare('SELECT MAX(orden) FROM info_productos WHERE column_id = :column_id');
|
||||
$stmt_max_order->execute([':column_id' => $column_id]);
|
||||
$max_order = $stmt_max_order->fetchColumn() ?? 0;
|
||||
|
||||
$stmt_order = $pdo->prepare('UPDATE info_productos SET orden = :orden WHERE id = :card_id');
|
||||
$stmt_order->execute([':orden' => $max_order + 1, ':card_id' => $card_id]);
|
||||
|
||||
$_SESSION['success_message'] = 'Tarjeta movida exitosamente.';
|
||||
} else {
|
||||
$_SESSION['error_message'] = 'Faltan datos para mover la tarjeta.';
|
||||
}
|
||||
}
|
||||
|
||||
header('Location: kanban.php');
|
||||
exit;
|
||||
42
handle_orden_ciudades.php
Normal file
@ -0,0 +1,42 @@
|
||||
<?php
|
||||
session_start();
|
||||
require_once 'db/config.php';
|
||||
|
||||
// Proteger contra acceso no autorizado
|
||||
if (!isset($_SESSION['user_rol']) || $_SESSION['user_rol'] !== 'Administrador General') {
|
||||
$_SESSION['error_message'] = 'No tienes permiso para realizar esta acción.';
|
||||
header('Location: ciudades.php');
|
||||
exit;
|
||||
}
|
||||
|
||||
if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['orden'])) {
|
||||
$orden_array = $_POST['orden'];
|
||||
$pdo = db();
|
||||
|
||||
try {
|
||||
$pdo->beginTransaction();
|
||||
|
||||
$stmt = $pdo->prepare('UPDATE ciudades SET orden = :orden WHERE id = :id');
|
||||
|
||||
foreach ($orden_array as $id => $orden) {
|
||||
$stmt->execute([
|
||||
'orden' => (int)$orden,
|
||||
'id' => (int)$id
|
||||
]);
|
||||
}
|
||||
|
||||
$pdo->commit();
|
||||
$_SESSION['success_message'] = 'El orden de las ciudades ha sido actualizado correctamente.';
|
||||
|
||||
} catch (Exception $e) {
|
||||
$pdo->rollBack();
|
||||
$_SESSION['error_message'] = 'Error al actualizar el orden: ' . $e->getMessage();
|
||||
}
|
||||
|
||||
} else {
|
||||
$_SESSION['error_message'] = 'Petición no válida.';
|
||||
}
|
||||
|
||||
header('Location: ciudades.php');
|
||||
exit;
|
||||
?>
|
||||
68
handle_orden_productos.php
Normal file
@ -0,0 +1,68 @@
|
||||
<?php
|
||||
session_start();
|
||||
if (!isset($_SESSION['user_id'])) {
|
||||
header('Location: auth/login.php');
|
||||
exit;
|
||||
}
|
||||
|
||||
require_once 'db/config.php';
|
||||
|
||||
if (!isset($_GET['id']) || !isset($_GET['dir'])) {
|
||||
header('Location: resumen_stock.php');
|
||||
exit;
|
||||
}
|
||||
|
||||
$producto_id = (int)$_GET['id'];
|
||||
$direction = $_GET['dir'];
|
||||
|
||||
$pdo = db();
|
||||
$pdo->beginTransaction();
|
||||
|
||||
try {
|
||||
// Obtener el orden actual del producto a mover
|
||||
$stmt = $pdo->prepare("SELECT orden FROM productos WHERE id = :id");
|
||||
$stmt->execute(['id' => $producto_id]);
|
||||
$current_orden = $stmt->fetchColumn();
|
||||
|
||||
if ($current_orden === false) {
|
||||
throw new Exception("Producto no encontrado.");
|
||||
}
|
||||
|
||||
$producto_a_intercambiar = null;
|
||||
|
||||
if ($direction === 'up') {
|
||||
// Mover hacia arriba (menor valor de 'orden')
|
||||
$stmt = $pdo->prepare("SELECT id, orden FROM productos WHERE orden < :orden ORDER BY orden DESC LIMIT 1");
|
||||
$stmt->execute(['orden' => $current_orden]);
|
||||
$producto_a_intercambiar = $stmt->fetch(PDO::FETCH_ASSOC);
|
||||
} elseif ($direction === 'down') {
|
||||
// Mover hacia abajo (mayor valor de 'orden')
|
||||
$stmt = $pdo->prepare("SELECT id, orden FROM productos WHERE orden > :orden ORDER BY orden ASC LIMIT 1");
|
||||
$stmt->execute(['orden' => $current_orden]);
|
||||
$producto_a_intercambiar = $stmt->fetch(PDO::FETCH_ASSOC);
|
||||
}
|
||||
|
||||
if ($producto_a_intercambiar) {
|
||||
// Intercambiar los valores de 'orden'
|
||||
$nuevo_orden_actual = $producto_a_intercambiar['orden'];
|
||||
$nuevo_orden_otro = $current_orden;
|
||||
|
||||
$stmt = $pdo->prepare("UPDATE productos SET orden = :orden WHERE id = :id");
|
||||
|
||||
// Actualizar el producto que se mueve
|
||||
$stmt->execute(['orden' => $nuevo_orden_actual, 'id' => $producto_id]);
|
||||
|
||||
// Actualizar el otro producto
|
||||
$stmt->execute(['orden' => $nuevo_orden_otro, 'id' => $producto_a_intercambiar['id']]);
|
||||
}
|
||||
|
||||
$pdo->commit();
|
||||
|
||||
} catch (Exception $e) {
|
||||
$pdo->rollBack();
|
||||
// Opcional: registrar el error o mostrar un mensaje
|
||||
// error_log($e->getMessage());
|
||||
}
|
||||
|
||||
header('Location: resumen_stock.php');
|
||||
exit;
|
||||
43
handle_pago_ciudad.php
Normal file
@ -0,0 +1,43 @@
|
||||
<?php
|
||||
require_once __DIR__ . '/db/config.php';
|
||||
|
||||
header('Content-Type: application/json');
|
||||
|
||||
$response = ['success' => false, 'message' => 'Petición inválida.'];
|
||||
|
||||
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
||||
$data = json_decode(file_get_contents('php://input'), true);
|
||||
$liquidacion_id = $data['liquidacion_id'] ?? null;
|
||||
$ciudad_id = $data['ciudad_id'] ?? null;
|
||||
$fecha = $data['fecha'] ?? null;
|
||||
$estado = $data['estado'] ?? null;
|
||||
|
||||
if ($liquidacion_id && $ciudad_id && $fecha && $estado) {
|
||||
if (!in_array($estado, ['Pendiente', 'Pagado'])) {
|
||||
$response['message'] = 'Estado no válido.';
|
||||
} else {
|
||||
try {
|
||||
$pdo = db();
|
||||
$sql = "INSERT INTO liquidaciones_ciudades_estados (liquidacion_id, ciudad_id, fecha, estado)
|
||||
VALUES (:liquidacion_id, :ciudad_id, :fecha, :estado)
|
||||
ON DUPLICATE KEY UPDATE estado = VALUES(estado)";
|
||||
$stmt = $pdo->prepare($sql);
|
||||
$stmt->execute([
|
||||
':liquidacion_id' => $liquidacion_id,
|
||||
':ciudad_id' => $ciudad_id,
|
||||
':fecha' => $fecha,
|
||||
':estado' => $estado
|
||||
]);
|
||||
|
||||
$response['success'] = true;
|
||||
$response['message'] = 'Estado actualizado correctamente.';
|
||||
} catch (PDOException $e) {
|
||||
$response['message'] = 'Error al actualizar la base de datos: ' . $e->getMessage();
|
||||
}
|
||||
}
|
||||
} else {
|
||||
$response['message'] = 'Faltan datos para realizar la operación.';
|
||||
}
|
||||
}
|
||||
|
||||
echo json_encode($response);
|
||||
19
handle_rename_column.php
Normal file
@ -0,0 +1,19 @@
|
||||
<?php
|
||||
require_once 'db/config.php';
|
||||
|
||||
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
||||
$column_id = $_POST['column_id'] ?? null;
|
||||
$column_name = trim($_POST['column_name']);
|
||||
|
||||
if ($column_id && !empty($column_name)) {
|
||||
$pdo = db();
|
||||
$stmt = $pdo->prepare('UPDATE kanban_columns SET nombre = :nombre WHERE id = :id');
|
||||
$stmt->execute([':nombre' => $column_name, ':id' => $column_id]);
|
||||
$_SESSION['success_message'] = 'Columna renombrada exitosamente.';
|
||||
} else {
|
||||
$_SESSION['error_message'] = 'Faltan datos para renombrar la columna.';
|
||||
}
|
||||
}
|
||||
|
||||
header('Location: kanban.php');
|
||||
exit;
|
||||
81
handle_salida.php
Normal file
@ -0,0 +1,81 @@
|
||||
<?php
|
||||
date_default_timezone_set('America/Lima');
|
||||
require_once 'db/config.php';
|
||||
|
||||
// Asegurarse de que el usuario esté logueado
|
||||
if (!isset($_SESSION['user_id'])) {
|
||||
$_SESSION['error'] = "Debe iniciar sesión para realizar esta acción.";
|
||||
header("Location: auth/login.php");
|
||||
exit();
|
||||
}
|
||||
|
||||
if ($_SERVER["REQUEST_METHOD"] == "POST") {
|
||||
$producto_id = $_POST['producto_id'];
|
||||
$ciudad_id = $_POST['ciudad_id'];
|
||||
$cantidad = $_POST['cantidad'];
|
||||
$observacion = $_POST['descripcion']; // El campo del formulario se llama 'descripcion'
|
||||
$usuario_id = $_SESSION['user_id'];
|
||||
$fecha = $_POST['fecha'];
|
||||
|
||||
if (empty($producto_id) || empty($ciudad_id) || empty($cantidad) || $cantidad <= 0) {
|
||||
$_SESSION['error'] = "Por favor, complete todos los campos obligatorios.";
|
||||
header("Location: registrar_salida.php");
|
||||
exit();
|
||||
}
|
||||
|
||||
$pdo = db();
|
||||
|
||||
try {
|
||||
$pdo->beginTransaction();
|
||||
|
||||
// 1. Verificar stock actual
|
||||
$stmt = $pdo->prepare("SELECT stock_actual FROM stock_por_ciudad WHERE producto_id = :producto_id AND ciudad_id = :ciudad_id FOR UPDATE");
|
||||
$stmt->execute(['producto_id' => $producto_id, 'ciudad_id' => $ciudad_id]);
|
||||
$stock_disponible = $stmt->fetchColumn();
|
||||
|
||||
if ($stock_disponible === false || $stock_disponible < $cantidad) {
|
||||
$_SESSION['error'] = "No hay stock suficiente para realizar esta operación. Stock actual: " . ($stock_disponible ?: 0);
|
||||
header("Location: registrar_salida.php");
|
||||
$pdo->rollBack();
|
||||
exit();
|
||||
}
|
||||
|
||||
// 2. Actualizar el stock en la ciudad
|
||||
$stmt = $pdo->prepare("UPDATE stock_por_ciudad SET stock_actual = stock_actual - :cantidad WHERE producto_id = :producto_id AND ciudad_id = :ciudad_id");
|
||||
$stmt->execute([
|
||||
'cantidad' => $cantidad,
|
||||
'producto_id' => $producto_id,
|
||||
'ciudad_id' => $ciudad_id
|
||||
]);
|
||||
|
||||
// 3. Registrar el movimiento de salida
|
||||
$stmt = $pdo->prepare(
|
||||
"INSERT INTO movimientos (tipo, producto_id, ciudad_origen_id, cantidad, usuario_id, observacion, fecha, fecha_registro)
|
||||
VALUES ('Salida', :producto_id, :ciudad_origen_id, :cantidad, :usuario_id, :observacion, :fecha, NOW())"
|
||||
);
|
||||
$stmt->execute([
|
||||
'producto_id' => $producto_id,
|
||||
'ciudad_origen_id' => $ciudad_id,
|
||||
'cantidad' => $cantidad,
|
||||
'usuario_id' => $usuario_id,
|
||||
'observacion' => $observacion,
|
||||
'fecha' => $fecha
|
||||
]);
|
||||
|
||||
$pdo->commit();
|
||||
|
||||
$_SESSION['success'] = "Salida de producto registrada correctamente.";
|
||||
header("Location: productos.php");
|
||||
exit();
|
||||
|
||||
} catch (Exception $e) {
|
||||
$pdo->rollBack();
|
||||
$_SESSION['error'] = "Error al registrar la salida: " . $e->getMessage();
|
||||
header("Location: registrar_salida.php");
|
||||
exit();
|
||||
}
|
||||
} else {
|
||||
header("Location: registrar_salida.php");
|
||||
exit();
|
||||
}
|
||||
?>
|
||||
186
historial.php
Normal file
@ -0,0 +1,186 @@
|
||||
<?php
|
||||
date_default_timezone_set('America/Lima');
|
||||
require_once 'includes/header.php';
|
||||
require_once 'db/config.php'; // Conexión a la base de datos
|
||||
|
||||
// Obtener todas las ciudades para el filtro
|
||||
try {
|
||||
$stmt_ciudades = db()->query("SELECT id, nombre FROM ciudades ORDER BY nombre");
|
||||
$ciudades = $stmt_ciudades->fetchAll(PDO::FETCH_ASSOC);
|
||||
} catch (PDOException $e) {
|
||||
die("Error al obtener las ciudades: " . $e->getMessage());
|
||||
}
|
||||
|
||||
// Obtener todos los productos para el filtro
|
||||
try {
|
||||
$stmt_productos = db()->query("SELECT id, nombre FROM productos ORDER BY nombre");
|
||||
$productos = $stmt_productos->fetchAll(PDO::FETCH_ASSOC);
|
||||
} catch (PDOException $e) {
|
||||
die("Error al obtener los productos: " . $e->getMessage());
|
||||
}
|
||||
|
||||
// Filtrar por ciudad y/o producto si se han seleccionado
|
||||
$ciudad_seleccionada_id = isset($_GET['ciudad_id']) ? (int)$_GET['ciudad_id'] : 0;
|
||||
$producto_seleccionado_id = isset($_GET['producto_id']) ? (int)$_GET['producto_id'] : 0;
|
||||
|
||||
// Consulta para obtener el historial de movimientos
|
||||
try {
|
||||
$sql = "
|
||||
SELECT
|
||||
m.id,
|
||||
p.nombre AS producto_nombre,
|
||||
m.tipo AS tipo_movimiento,
|
||||
m.cantidad,
|
||||
COALESCE(c_origen.nombre, c_destino.nombre) AS ciudad_nombre,
|
||||
u.nombre AS usuario_nombre,
|
||||
m.fecha AS fecha_movimiento,
|
||||
m.fecha_registro,
|
||||
m.observacion
|
||||
FROM
|
||||
movimientos m
|
||||
JOIN
|
||||
productos p ON m.producto_id = p.id
|
||||
LEFT JOIN
|
||||
ciudades c_origen ON m.ciudad_origen_id = c_origen.id
|
||||
LEFT JOIN
|
||||
ciudades c_destino ON m.ciudad_destino_id = c_destino.id
|
||||
JOIN
|
||||
usuarios u ON m.usuario_id = u.id
|
||||
";
|
||||
|
||||
$conditions = [];
|
||||
$params = [];
|
||||
|
||||
if ($ciudad_seleccionada_id > 0) {
|
||||
$conditions[] = "(c_origen.id = :ciudad_id OR c_destino.id = :ciudad_id)";
|
||||
$params[':ciudad_id'] = $ciudad_seleccionada_id;
|
||||
}
|
||||
|
||||
if ($producto_seleccionado_id > 0) {
|
||||
$conditions[] = "m.producto_id = :producto_id";
|
||||
$params[':producto_id'] = $producto_seleccionado_id;
|
||||
}
|
||||
|
||||
if (!empty($conditions)) {
|
||||
$sql .= " WHERE " . implode(" AND ", $conditions);
|
||||
}
|
||||
|
||||
$sql .= " ORDER BY m.fecha_registro DESC";
|
||||
|
||||
$stmt = db()->prepare($sql);
|
||||
$stmt->execute($params);
|
||||
$movimientos = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
||||
} catch (PDOException $e) {
|
||||
// En caso de un error de base de datos, muestra un mensaje en lugar de una página en blanco
|
||||
die("Error al obtener el historial de movimientos: " . $e->getMessage());
|
||||
}
|
||||
?>
|
||||
|
||||
<div class="container mt-5">
|
||||
<h2>Historial de Movimientos de Stock</h2>
|
||||
<hr>
|
||||
|
||||
<!-- Filtros -->
|
||||
<form action="historial.php" method="get" class="mb-4">
|
||||
<div class="form-row align-items-end">
|
||||
<div class="col-md-4">
|
||||
<label for="ciudad_id">Filtrar por Ciudad:</label>
|
||||
<select name="ciudad_id" id="ciudad_id" class="form-control">
|
||||
<option value="0">Todas las ciudades</option>
|
||||
<?php foreach ($ciudades as $ciudad): ?>
|
||||
<option value="<?php echo $ciudad['id']; ?>" <?php echo ($ciudad_seleccionada_id == $ciudad['id']) ? 'selected' : ''; ?>>
|
||||
<?php echo htmlspecialchars($ciudad['nombre']); ?>
|
||||
</option>
|
||||
<?php endforeach; ?>
|
||||
</select>
|
||||
</div>
|
||||
<div class="col-md-4">
|
||||
<label for="producto_id">Filtrar por Producto:</label>
|
||||
<select name="producto_id" id="producto_id" class="form-control">
|
||||
<option value="0">Todos los productos</option>
|
||||
<?php foreach ($productos as $producto): ?>
|
||||
<option value="<?php echo $producto['id']; ?>" <?php echo ($producto_seleccionado_id == $producto['id']) ? 'selected' : ''; ?>>
|
||||
<?php echo htmlspecialchars($producto['nombre']); ?>
|
||||
</option>
|
||||
<?php endforeach; ?>
|
||||
</select>
|
||||
</div>
|
||||
<div class="col-md-2">
|
||||
<button type="submit" class="btn btn-primary mt-4">Filtrar</button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
<div class="table-responsive">
|
||||
<table class="table table-striped table-bordered table-hover">
|
||||
<thead class="thead-dark">
|
||||
<tr>
|
||||
<th>Producto</th>
|
||||
<th>Tipo</th>
|
||||
<th>Cantidad</th>
|
||||
<th>Ciudad</th>
|
||||
<th>Usuario</th>
|
||||
<th>Fecha Movimiento</th>
|
||||
<th>Fecha Registro</th>
|
||||
<th>Observación</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<?php if (!empty($movimientos)): ?>
|
||||
<?php foreach ($movimientos as $movimiento): ?>
|
||||
<tr>
|
||||
<td><?php echo htmlspecialchars($movimiento['producto_nombre']); ?></td>
|
||||
<td>
|
||||
<?php
|
||||
$tipo = $movimiento['tipo_movimiento'];
|
||||
$clase_color = '';
|
||||
if ($tipo == 'Entrada') {
|
||||
$clase_color = 'bg-success';
|
||||
} elseif ($tipo == 'Salida') {
|
||||
$clase_color = 'bg-danger';
|
||||
} else {
|
||||
$clase_color = 'bg-secondary';
|
||||
}
|
||||
echo '<span class="badge ' . $clase_color . '">' . htmlspecialchars($tipo) . '</span>';
|
||||
?>
|
||||
</td>
|
||||
<td><?php echo htmlspecialchars($movimiento['cantidad']); ?></td>
|
||||
<td><?php echo htmlspecialchars($movimiento['ciudad_nombre']); ?></td>
|
||||
<td><?php echo htmlspecialchars($movimiento['usuario_nombre']); ?></td>
|
||||
<td>
|
||||
<?php
|
||||
try {
|
||||
$fecha = new DateTime($movimiento['fecha_movimiento']);
|
||||
echo htmlspecialchars($fecha->format('d/m/Y'));
|
||||
} catch (Exception $e) {
|
||||
echo htmlspecialchars($movimiento['fecha_movimiento']);
|
||||
}
|
||||
?>
|
||||
</td>
|
||||
<td>
|
||||
<?php
|
||||
try {
|
||||
$fecha = new DateTime($movimiento['fecha_registro'], new DateTimeZone('UTC'));
|
||||
$fecha->setTimezone(new DateTimeZone('America/Lima'));
|
||||
echo htmlspecialchars($fecha->format('d/m/Y H:i'));
|
||||
} catch (Exception $e) {
|
||||
echo htmlspecialchars($movimiento['fecha_registro']);
|
||||
}
|
||||
?>
|
||||
</td>
|
||||
<td><?php echo htmlspecialchars($movimiento['observacion']); ?></td>
|
||||
</tr>
|
||||
<?php endforeach; ?>
|
||||
<?php else: ?>
|
||||
<tr>
|
||||
<td colspan="8" class="text-center">No hay movimientos registrados para los filtros seleccionados.</td>
|
||||
</tr>
|
||||
<?php endif; ?>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<?php
|
||||
require_once 'includes/footer.php';
|
||||
?>
|
||||
10
includes/footer.php
Normal file
@ -0,0 +1,10 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Scripts JS de Bootstrap y personalizados -->
|
||||
<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>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
118
includes/header.php
Normal file
@ -0,0 +1,118 @@
|
||||
<?php
|
||||
date_default_timezone_set('America/Lima');
|
||||
require_once __DIR__ . '/../db/config.php';
|
||||
|
||||
// Validar sesión activa
|
||||
if (!is_logged_in()) {
|
||||
header('Location: /auth/login.php');
|
||||
exit;
|
||||
}
|
||||
?>
|
||||
<!DOCTYPE html>
|
||||
<html lang="es">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Sistema de Inventario</title>
|
||||
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/css/bootstrap.min.css" rel="stylesheet">
|
||||
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/5.15.3/css/all.min.css">
|
||||
<link rel="stylesheet" href="/assets/css/style.css?v=<?php echo time(); ?>">
|
||||
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<div class="d-flex">
|
||||
<!-- Menú Lateral (Sidebar) -->
|
||||
<div class="sidebar">
|
||||
<h3 class="sidebar-heading">Panel de control</h3>
|
||||
<ul class="nav flex-column">
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="/">
|
||||
<i class="fas fa-tachometer-alt"></i> Dashboard
|
||||
</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="/productos.php">
|
||||
<i class="fas fa-box"></i> Productos
|
||||
</a>
|
||||
</li>
|
||||
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="/ciudades.php">
|
||||
<i class="fas fa-city"></i> Ciudades
|
||||
</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="/colaboradores.php">
|
||||
<i class="fas fa-users"></i> Colaboradores
|
||||
</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="/registrar_entrada.php">
|
||||
<i class="fas fa-plus-circle"></i> Registrar Entrada
|
||||
</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="/registrar_salida.php">
|
||||
<i class="fas fa-minus-circle"></i> Registrar Salida
|
||||
</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="/historial.php">
|
||||
<i class="fas fa-history"></i> Historial
|
||||
</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="/resumen_stock.php">
|
||||
<i class="fas fa-clipboard-list"></i> Resumen de Stock
|
||||
</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="/liquidaciones.php">
|
||||
<i class="fas fa-dollar-sign"></i> Liquidaciones
|
||||
</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="/liquidaciones_por_fecha.php">
|
||||
<i class="fas fa-calendar-alt"></i> Liquidaciones por Fecha
|
||||
</a>
|
||||
</li>
|
||||
<?php if ($_SESSION['user_rol'] !== 'Encargado de Stock') : ?>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="/reporte_de_pagos.php">
|
||||
<i class="fas fa-chart-bar"></i> Validaciones De Pagos
|
||||
</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="/flujo_de_caja.php">
|
||||
<i class="fas fa-wallet"></i> Flujo de Caja
|
||||
</a>
|
||||
</li>
|
||||
<?php endif; ?>
|
||||
<?php if (isset($_SESSION['user_rol']) && $_SESSION['user_rol'] === 'Administrador General') : ?>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="/inversiones_operativas.php">
|
||||
<i class="fas fa-chart-line"></i> Gestión de Inversiones
|
||||
</a>
|
||||
</li>
|
||||
<?php endif; ?>
|
||||
<?php if ($_SESSION['user_rol'] !== 'Encargado de Stock') : ?>
|
||||
|
||||
|
||||
<?php endif; ?>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="/confirmacion_stock.php">
|
||||
<i class="fas fa-truck"></i> Reporte de Entregas
|
||||
</a>
|
||||
</li>
|
||||
<li class="nav-item mt-auto">
|
||||
<a class="nav-link" href="/auth/logout.php">
|
||||
<i class="fas fa-sign-out-alt"></i> Cerrar Sesión
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<!-- Contenido Principal -->
|
||||
<div class="main-content">
|
||||
<div class="container-fluid">
|
||||
400
index.php
@ -1,150 +1,258 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
@ini_set('display_errors', '1');
|
||||
@error_reporting(E_ALL);
|
||||
@date_default_timezone_set('UTC');
|
||||
$page_title = 'Panel de Control';
|
||||
require_once __DIR__ . '/includes/header.php';
|
||||
require_once __DIR__ . '/db/config.php';
|
||||
|
||||
// Proteger la página: si el usuario no ha iniciado sesión, redirigir a login
|
||||
if (!is_logged_in()) {
|
||||
header('Location: /auth/login.php');
|
||||
exit();
|
||||
}
|
||||
|
||||
// Obtener el número total de productos
|
||||
$pdo = db();
|
||||
$stmt = $pdo->query("SELECT COUNT(*) FROM productos");
|
||||
$total_products = $stmt->fetchColumn();
|
||||
|
||||
// Obtener stock por ciudad y producto, ordenando las ciudades por stock total
|
||||
$stock_stmt = $pdo->query("
|
||||
SELECT
|
||||
c.nombre AS ciudad,
|
||||
p.nombre AS producto,
|
||||
s.stock_actual
|
||||
FROM
|
||||
stock_por_ciudad s
|
||||
JOIN
|
||||
ciudades c ON s.ciudad_id = c.id
|
||||
JOIN
|
||||
productos p ON s.producto_id = p.id
|
||||
JOIN
|
||||
(SELECT ciudad_id, SUM(stock_actual) as total_stock FROM stock_por_ciudad GROUP BY ciudad_id) as stock_totales
|
||||
ON c.id = stock_totales.ciudad_id
|
||||
WHERE
|
||||
s.stock_actual > 0
|
||||
ORDER BY
|
||||
stock_totales.total_stock DESC, p.nombre;
|
||||
");
|
||||
$stock_data = $stock_stmt->fetchAll(PDO::FETCH_ASSOC);
|
||||
|
||||
// Agrupar por ciudad
|
||||
$stock_por_ciudad = [];
|
||||
foreach ($stock_data as $row) {
|
||||
$stock_por_ciudad[$row['ciudad']][] = [
|
||||
'producto' => $row['producto'],
|
||||
'stock' => $row['stock_actual']
|
||||
];
|
||||
}
|
||||
|
||||
$phpVersion = PHP_VERSION;
|
||||
$now = date('Y-m-d H:i:s');
|
||||
?>
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<title>New Style</title>
|
||||
<?php
|
||||
// Read project preview data from environment
|
||||
$projectDescription = $_SERVER['PROJECT_DESCRIPTION'] ?? '';
|
||||
$projectImageUrl = $_SERVER['PROJECT_IMAGE_URL'] ?? '';
|
||||
?>
|
||||
<?php if ($projectDescription): ?>
|
||||
<!-- Meta description -->
|
||||
<meta name="description" content='<?= htmlspecialchars($projectDescription) ?>' />
|
||||
<!-- Open Graph meta tags -->
|
||||
<meta property="og:description" content="<?= htmlspecialchars($projectDescription) ?>" />
|
||||
<!-- Twitter meta tags -->
|
||||
<meta property="twitter:description" content="<?= htmlspecialchars($projectDescription) ?>" />
|
||||
<?php endif; ?>
|
||||
<?php if ($projectImageUrl): ?>
|
||||
<!-- Open Graph image -->
|
||||
<meta property="og:image" content="<?= htmlspecialchars($projectImageUrl) ?>" />
|
||||
<!-- Twitter image -->
|
||||
<meta property="twitter:image" content="<?= htmlspecialchars($projectImageUrl) ?>" />
|
||||
<?php endif; ?>
|
||||
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
||||
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;700&display=swap" rel="stylesheet">
|
||||
<style>
|
||||
:root {
|
||||
--bg-color-start: #6a11cb;
|
||||
--bg-color-end: #2575fc;
|
||||
--text-color: #ffffff;
|
||||
--card-bg-color: rgba(255, 255, 255, 0.01);
|
||||
--card-border-color: rgba(255, 255, 255, 0.1);
|
||||
}
|
||||
body {
|
||||
margin: 0;
|
||||
font-family: 'Inter', sans-serif;
|
||||
background: linear-gradient(45deg, var(--bg-color-start), var(--bg-color-end));
|
||||
color: var(--text-color);
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
min-height: 100vh;
|
||||
text-align: center;
|
||||
overflow: hidden;
|
||||
position: relative;
|
||||
}
|
||||
body::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background-image: url('data:image/svg+xml,<svg xmlns="http://www.w3.org/2000/svg" width="100" height="100" viewBox="0 0 100 100"><path d="M-10 10L110 10M10 -10L10 110" stroke-width="1" stroke="rgba(255,255,255,0.05)"/></svg>');
|
||||
animation: bg-pan 20s linear infinite;
|
||||
z-index: -1;
|
||||
}
|
||||
@keyframes bg-pan {
|
||||
0% { background-position: 0% 0%; }
|
||||
100% { background-position: 100% 100%; }
|
||||
}
|
||||
main {
|
||||
padding: 2rem;
|
||||
}
|
||||
.card {
|
||||
background: var(--card-bg-color);
|
||||
border: 1px solid var(--card-border-color);
|
||||
border-radius: 16px;
|
||||
padding: 2rem;
|
||||
backdrop-filter: blur(20px);
|
||||
-webkit-backdrop-filter: blur(20px);
|
||||
box-shadow: 0 8px 32px 0 rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
.loader {
|
||||
margin: 1.25rem auto 1.25rem;
|
||||
width: 48px;
|
||||
height: 48px;
|
||||
border: 3px solid rgba(255, 255, 255, 0.25);
|
||||
border-top-color: #fff;
|
||||
border-radius: 50%;
|
||||
animation: spin 1s linear infinite;
|
||||
}
|
||||
@keyframes spin {
|
||||
from { transform: rotate(0deg); }
|
||||
to { transform: rotate(360deg); }
|
||||
}
|
||||
.hint {
|
||||
opacity: 0.9;
|
||||
}
|
||||
.sr-only {
|
||||
position: absolute;
|
||||
width: 1px; height: 1px;
|
||||
padding: 0; margin: -1px;
|
||||
overflow: hidden;
|
||||
clip: rect(0, 0, 0, 0);
|
||||
white-space: nowrap; border: 0;
|
||||
}
|
||||
h1 {
|
||||
font-size: 3rem;
|
||||
font-weight: 700;
|
||||
margin: 0 0 1rem;
|
||||
letter-spacing: -1px;
|
||||
}
|
||||
p {
|
||||
margin: 0.5rem 0;
|
||||
font-size: 1.1rem;
|
||||
}
|
||||
code {
|
||||
background: rgba(0,0,0,0.2);
|
||||
padding: 2px 6px;
|
||||
border-radius: 4px;
|
||||
font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
|
||||
}
|
||||
footer {
|
||||
position: absolute;
|
||||
bottom: 1rem;
|
||||
font-size: 0.8rem;
|
||||
opacity: 0.7;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<main>
|
||||
<div class="card">
|
||||
<h1>Analyzing your requirements and generating your website…</h1>
|
||||
<div class="loader" role="status" aria-live="polite" aria-label="Applying initial changes">
|
||||
<span class="sr-only">Loading…</span>
|
||||
</div>
|
||||
<p class="hint"><?= ($_SERVER['HTTP_HOST'] ?? '') === 'appwizzy.com' ? 'AppWizzy' : 'Flatlogic' ?> AI is collecting your requirements and applying the first changes.</p>
|
||||
<p class="hint">This page will update automatically as the plan is implemented.</p>
|
||||
<p>Runtime: PHP <code><?= htmlspecialchars($phpVersion) ?></code> — UTC <code><?= htmlspecialchars($now) ?></code></p>
|
||||
|
||||
<div class="container-fluid">
|
||||
<!-- Mensaje de Bienvenida -->
|
||||
<div class="row mb-4">
|
||||
<div class="col-12 text-center">
|
||||
<h1 class="welcome-title">Bienvenido a tu Panel de Control</h1>
|
||||
<p class="welcome-subtitle">Aquí tienes un resumen de tu aplicación.</p>
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
<footer>
|
||||
Page updated: <?= htmlspecialchars($now) ?> (UTC)
|
||||
</footer>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
<div class="row justify-content-center">
|
||||
<!-- Tarjeta de Total de Productos -->
|
||||
<div class="col-md-6 col-lg-4 mb-4">
|
||||
<div class="card info-card h-100">
|
||||
<div class="card-body">
|
||||
<div class="d-flex align-items-center">
|
||||
<div class="icon-circle">
|
||||
<!-- Icono de Caja (Productos) -->
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-box"><path d="M21 16V8a2 2 0 0 0-1-1.73l-7-4a2 2 0 0 0-2 0l-7 4A2 2 0 0 0 3 8v8a2 2 0 0 0 1 1.73l7 4a2 2 0 0 0 2 0l7-4A2 2 0 0 0 21 16z"></path><polyline points="3.27 6.96 12 12.01 20.73 6.96"></polyline><line x1="12" y1="22.08" x2="12" y2="12"></line></svg>
|
||||
</div>
|
||||
<div class="ms-3">
|
||||
<h5 class="card-title">Total de Productos</h5>
|
||||
<p class="card-text"><?php echo $total_products; ?></p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Tarjeta de Acceso a Productos -->
|
||||
<div class="col-md-6 col-lg-4 mb-4">
|
||||
<a href="productos.php" class="card-link">
|
||||
<div class="card info-card h-100">
|
||||
<div class="card-body">
|
||||
<div class="d-flex align-items-center">
|
||||
<div class="icon-circle">
|
||||
<!-- Icono de Tareas (Administrar) -->
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-settings"><circle cx="12" cy="12" r="3"></circle><path d="M19.4 15a1.65 1.65 0 0 0 .33 1.82l.06.06a2 2 0 0 1 0 2.83 2 2 0 0 1-2.83 0l-.06-.06a1.65 1.65 0 0 0-1.82-.33 1.65 1.65 0 0 0-1 1.51V21a2 2 0 0 1-2 2 2 2 0 0 1-2-2v-.09A1.65 1.65 0 0 0 9 19.4a1.65 1.65 0 0 0-1.82.33l-.06.06a2 2 0 0 1-2.83 0 2 2 0 0 1 0-2.83l.06-.06a1.65 1.65 0 0 0 .33-1.82 1.65 1.65 0 0 0-1.51-1H3a2 2 0 0 1-2-2 2 2 0 0 1 2-2h.09A1.65 1.65 0 0 0 4.6 9a1.65 1.65 0 0 0-.33-1.82l-.06-.06a2 2 0 0 1 0-2.83 2 2 0 0 1 2.83 0l.06.06a1.65 1.65 0 0 0 1.82.33H9a1.65 1.65 0 0 0 1-1.51V3a2 2 0 0 1 2-2 2 2 0 0 1 2 2v.09a1.65 1.65 0 0 0 1 1.51 1.65 1.65 0 0 0 1.82-.33l.06-.06a2 2 0 0 1 2.83 0 2 2 0 0 1 0 2.83l-.06.06a1.65 1.65 0 0 0-.33 1.82V9a1.65 1.65 0 0 0 1.51 1H21a2 2 0 0 1 2 2 2 2 0 0 1-2 2h-.09a1.65 1.65 0 0 0-1.51 1z"></path></svg>
|
||||
</div>
|
||||
<div class="ms-3">
|
||||
<h5 class="card-title">Administrar Productos</h5>
|
||||
<p class="card-text">Ir a la lista de productos</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<!-- Tarjeta de Gráfico de Stock -->
|
||||
<div class="col-md-12 col-lg-4 mb-4">
|
||||
<div class="card info-card h-100">
|
||||
<div class="card-body">
|
||||
<h5 class="card-title text-center">Distribución de Stock</h5>
|
||||
<div style="height: 300px;">
|
||||
<canvas id="stockChart"></canvas>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row justify-content-center">
|
||||
<!-- Tarjeta de Stock por Ciudad y Producto -->
|
||||
<div class="col-md-12 col-lg-8 mb-4">
|
||||
<div class="card info-card h-100">
|
||||
<div class="card-body">
|
||||
<h5 class="card-title">Stock por Ciudad y Producto</h5>
|
||||
<div class="accordion" id="stockAccordion">
|
||||
<?php if (empty($stock_por_ciudad)): ?>
|
||||
<p class="text-center mt-3">No hay datos de stock disponibles.</p>
|
||||
<?php else: ?>
|
||||
<?php
|
||||
$i = 0;
|
||||
foreach ($stock_por_ciudad as $ciudad => $productos):
|
||||
$ciudad_id = 'ciudad_' . $i++;
|
||||
?>
|
||||
<div class="accordion-item">
|
||||
<h2 class="accordion-header" id="heading-<?php echo $ciudad_id; ?>">
|
||||
<button class="accordion-button collapsed" type="button" data-bs-toggle="collapse" data-bs-target="#collapse-<?php echo $ciudad_id; ?>" aria-expanded="false" aria-controls="collapse-<?php echo $ciudad_id; ?>">
|
||||
<?php echo htmlspecialchars($ciudad); ?>
|
||||
</button>
|
||||
</h2>
|
||||
<div id="collapse-<?php echo $ciudad_id; ?>" class="accordion-collapse collapse" aria-labelledby="heading-<?php echo $ciudad_id; ?>" data-bs-parent="#stockAccordion">
|
||||
<div class="accordion-body">
|
||||
<div class="table-responsive">
|
||||
<table class="table table-sm table-hover">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Producto</th>
|
||||
<th>Stock</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<?php foreach ($productos as $producto): ?>
|
||||
<tr>
|
||||
<td><?php echo htmlspecialchars($producto['producto']); ?></td>
|
||||
<td><?php echo htmlspecialchars($producto['stock']); ?></td>
|
||||
</tr>
|
||||
<?php endforeach; ?>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<?php endforeach; ?>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<?php
|
||||
// Preparar datos para el gráfico
|
||||
$chart_labels = [];
|
||||
$chart_data = [];
|
||||
$chart_bg_colors = []; // Array para los colores de fondo
|
||||
$chart_border_colors = []; // Array para los colores de borde
|
||||
|
||||
// Paleta de colores (puedes añadir más si tienes muchas ciudades)
|
||||
$color_palette_bg = [
|
||||
'rgba(54, 162, 235, 0.8)', // Azul
|
||||
'rgba(255, 206, 86, 0.8)', // Amarillo
|
||||
'rgba(75, 192, 192, 0.8)', // Turquesa
|
||||
'rgba(153, 102, 255, 0.8)',// Morado
|
||||
'rgba(255, 99, 132, 0.8)', // Rojo
|
||||
'rgba(255, 159, 64, 0.8)' // Naranja
|
||||
];
|
||||
$color_palette_border = [
|
||||
'rgba(54, 162, 235, 1)',
|
||||
'rgba(255, 206, 86, 1)',
|
||||
'rgba(75, 192, 192, 1)',
|
||||
'rgba(153, 102, 255, 1)',
|
||||
'rgba(255, 99, 132, 1)',
|
||||
'rgba(255, 159, 64, 1)'
|
||||
];
|
||||
|
||||
// Asignar verde a Lima
|
||||
$city_color_map = [
|
||||
'Lima' => [
|
||||
'bg' => 'rgba(40, 167, 69, 0.8)', // Verde fondo
|
||||
'border' => 'rgba(40, 167, 69, 1)' // Verde borde
|
||||
]
|
||||
];
|
||||
|
||||
if (!empty($stock_por_ciudad)) {
|
||||
$color_index = 0;
|
||||
foreach ($stock_por_ciudad as $ciudad => $productos) {
|
||||
$chart_labels[] = $ciudad;
|
||||
|
||||
// Asignar color
|
||||
if (isset($city_color_map[$ciudad])) {
|
||||
$chart_bg_colors[] = $city_color_map[$ciudad]['bg'];
|
||||
$chart_border_colors[] = $city_color_map[$ciudad]['border'];
|
||||
} else {
|
||||
// Asignar un color de la paleta
|
||||
$chart_bg_colors[] = $color_palette_bg[$color_index % count($color_palette_bg)];
|
||||
$chart_border_colors[] = $color_palette_border[$color_index % count($color_palette_border)];
|
||||
$color_index++;
|
||||
}
|
||||
|
||||
$total_stock = 0;
|
||||
foreach ($productos as $producto) {
|
||||
$total_stock += $producto['stock'];
|
||||
}
|
||||
$chart_data[] = $total_stock;
|
||||
}
|
||||
}
|
||||
?>
|
||||
|
||||
<script>
|
||||
document.addEventListener('DOMContentLoaded', function () {
|
||||
const ctx = document.getElementById('stockChart');
|
||||
if (ctx && <?php echo json_encode(!empty($chart_data)); ?>) {
|
||||
new Chart(ctx, {
|
||||
type: 'pie',
|
||||
data: {
|
||||
labels: <?php echo json_encode($chart_labels); ?>,
|
||||
datasets: [{
|
||||
label: 'Stock Total',
|
||||
data: <?php echo json_encode($chart_data); ?>,
|
||||
backgroundColor: <?php echo json_encode($chart_bg_colors); ?>,
|
||||
borderColor: <?php echo json_encode($chart_border_colors); ?>,
|
||||
borderWidth: 1
|
||||
}]
|
||||
},
|
||||
options: {
|
||||
responsive: true,
|
||||
plugins: {
|
||||
legend: {
|
||||
position: 'bottom',
|
||||
},
|
||||
title: {
|
||||
display: false,
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
} else if (ctx) {
|
||||
ctx.getContext('2d').fillText("No hay datos de stock para mostrar.", ctx.width / 2 - 50, ctx.height / 2);
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
<?php
|
||||
require_once __DIR__ . '/includes/footer.php';
|
||||
?>
|
||||
|
||||
100
info_producto.php
Normal file
@ -0,0 +1,100 @@
|
||||
<?php
|
||||
require_once 'includes/header.php';
|
||||
require_once 'db/config.php';
|
||||
|
||||
$pdo = db();
|
||||
|
||||
// Fetch products for the dropdown
|
||||
$stmt_productos = $pdo->query('SELECT id, nombre FROM productos ORDER BY nombre ASC');
|
||||
$productos = $stmt_productos->fetchAll(PDO::FETCH_ASSOC);
|
||||
|
||||
// Fetch Kanban columns for the dropdown
|
||||
$stmt_columns = $pdo->query('SELECT id, nombre FROM kanban_columns ORDER BY orden ASC');
|
||||
$columns = $stmt_columns->fetchAll(PDO::FETCH_ASSOC);
|
||||
|
||||
// If no columns exist, create default ones to ensure the dropdown is never empty
|
||||
if (empty($columns)) {
|
||||
$default_columns = ['Para empezar', 'En proceso', 'Terminado'];
|
||||
$stmt_insert = $pdo->prepare('INSERT INTO kanban_columns (nombre, orden) VALUES (?, ?)');
|
||||
foreach ($default_columns as $index => $name) {
|
||||
$stmt_insert->execute([$name, $index + 1]);
|
||||
}
|
||||
// Re-fetch columns so they appear on this page load
|
||||
$stmt_columns = $pdo->query('SELECT id, nombre FROM kanban_columns ORDER BY orden ASC');
|
||||
$columns = $stmt_columns->fetchAll(PDO::FETCH_ASSOC);
|
||||
}
|
||||
|
||||
// Fetch existing info cards
|
||||
$stmt_info = $pdo->query('SELECT info_productos.id, productos.nombre as producto_nombre, info_productos.imagen_url, info_productos.texto_informativo, kanban_columns.nombre as columna_nombre FROM info_productos JOIN productos ON info_productos.producto_id = productos.id LEFT JOIN kanban_columns ON info_productos.column_id = kanban_columns.id ORDER BY info_productos.created_at DESC');
|
||||
$info_cards = $stmt_info->fetchAll(PDO::FETCH_ASSOC);
|
||||
?>
|
||||
|
||||
<div class="container mt-4">
|
||||
<h2>Info de Productos</h2>
|
||||
<p>Crea y gestiona tarjetas con información y fotos de tus productos.</p>
|
||||
|
||||
<div class="card mb-4">
|
||||
<div class="card-header">Agregar Nueva Tarjeta de Información</div>
|
||||
<div class="card-body">
|
||||
<form action="handle_agregar_info_producto.php" method="POST" enctype="multipart/form-data">
|
||||
<div class="form-group">
|
||||
<label for="producto_id">Producto</label>
|
||||
<select class="form-control" 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>
|
||||
<div class="form-group mt-3">
|
||||
<label for="column_id">Columna Kanban</label>
|
||||
<select class="form-control" id="column_id" name="column_id" required>
|
||||
<option value="">Selecciona una columna</option>
|
||||
<?php foreach ($columns as $column): ?>
|
||||
<option value="<?php echo htmlspecialchars($column['id']); ?>">
|
||||
<?php echo htmlspecialchars($column['nombre']); ?>
|
||||
</option>
|
||||
<?php endforeach; ?>
|
||||
</select>
|
||||
</div>
|
||||
<div class="form-group mt-3">
|
||||
<label for="imagen">Imagen del Producto</label>
|
||||
<input type="file" class="form-control-file" id="imagen" name="imagen" accept="image/*" required>
|
||||
</div>
|
||||
<div class="form-group mt-3">
|
||||
<label for="texto_informativo">Texto Informativo</label>
|
||||
<textarea class="form-control" id="texto_informativo" name="texto_informativo" rows="3" required></textarea>
|
||||
</div>
|
||||
<button type="submit" class="btn btn-primary mt-3">Guardar Tarjeta</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<?php if (empty($info_cards)): ?>
|
||||
<div class="col">
|
||||
<p class="text-center">Aún no has creado ninguna tarjeta de información.</p>
|
||||
</div>
|
||||
<?php else: ?>
|
||||
<?php foreach ($info_cards as $card): ?>
|
||||
<div class="col-md-4 mb-4">
|
||||
<div class="card h-100">
|
||||
<img src="<?php echo htmlspecialchars($card['imagen_url']); ?>" class="card-img-top" alt="Imagen de <?php echo htmlspecialchars($card['producto_nombre']); ?>">
|
||||
<div class="card-body">
|
||||
<h5 class="card-title"><?php echo htmlspecialchars($card['producto_nombre']); ?></h5>
|
||||
<p class="card-text"><small class="text-muted">Columna: <?php echo htmlspecialchars($card['columna_nombre'] ?? 'Sin asignar'); ?></small></p>
|
||||
<p class="card-text"><?php echo nl2br(htmlspecialchars($card['texto_informativo'])); ?></p>
|
||||
</div>
|
||||
<div class="card-footer">
|
||||
<a href="eliminar_info_producto.php?id=<?php echo $card['id']; ?>" class="btn btn-danger btn-sm" onclick="return confirm('¿Estás seguro de que quieres eliminar esta tarjeta?');">Eliminar</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<?php endforeach; ?>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<?php require_once 'includes/footer.php'; ?>
|
||||
223
inversiones_operativas.php
Normal file
@ -0,0 +1,223 @@
|
||||
<?php
|
||||
require_once 'includes/header.php';
|
||||
require_once 'db/config.php';
|
||||
|
||||
// Redirigir si el usuario no tiene el rol de Administrador General
|
||||
if (!isset($_SESSION['user_rol']) || $_SESSION['user_rol'] !== 'Administrador General') {
|
||||
header('Location: /index.php?error=unauthorized');
|
||||
exit;
|
||||
}
|
||||
|
||||
$pdo = db();
|
||||
|
||||
// --- Manejar Formularios (Agregar Inversión) ---
|
||||
if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['add_investment'])) {
|
||||
$fecha = $_POST['fecha'] ?? date('Y-m-d');
|
||||
$descripcion = trim($_POST['descripcion'] ?? '');
|
||||
$monto = $_POST['monto'] ?? 0;
|
||||
$tipo = $_POST['tipo'] ?? '';
|
||||
|
||||
if (!empty($descripcion) && is_numeric($monto) && $monto > 0 && !empty($tipo)) {
|
||||
try {
|
||||
$stmt = $pdo->prepare("INSERT INTO inversiones (fecha, descripcion, monto, tipo) VALUES (?, ?, ?, ?)");
|
||||
$stmt->execute([$fecha, $descripcion, $monto, $tipo]);
|
||||
|
||||
// Redirigir para evitar reenvío de formulario
|
||||
header("Location: " . $_SERVER['PHP_SELF'] . "?success=true#section-" . $tipo);
|
||||
exit;
|
||||
} catch (PDOException $e) {
|
||||
$error_message = "Error al guardar en la base de datos: " . $e->getMessage();
|
||||
}
|
||||
} else {
|
||||
$error_message = "Por favor, complete todos los campos correctamente.";
|
||||
}
|
||||
}
|
||||
|
||||
// --- Filtrado por Mes ---
|
||||
$selected_month = $_GET['mes'] ?? null;
|
||||
$selected_year = $_GET['anio'] ?? null;
|
||||
$filter_active = $selected_month && $selected_year;
|
||||
|
||||
// --- Obtener todos los datos de inversiones (con filtro si aplica) ---
|
||||
try {
|
||||
$sql = "SELECT * FROM inversiones";
|
||||
$params = [];
|
||||
if ($filter_active) {
|
||||
$sql .= " WHERE MONTH(fecha) = ? AND YEAR(fecha) = ?";
|
||||
$params = [$selected_month, $selected_year];
|
||||
}
|
||||
$sql .= " ORDER BY fecha DESC, id DESC";
|
||||
|
||||
$stmt = $pdo->prepare($sql);
|
||||
$stmt->execute($params);
|
||||
$all_inversiones = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
||||
|
||||
} catch (PDOException $e) {
|
||||
die("Error al obtener los datos: " . $e->getMessage());
|
||||
}
|
||||
|
||||
// --- Obtener totales por mes (esto no se filtra) ---
|
||||
try {
|
||||
$stmt_totals = $pdo->query("
|
||||
SELECT
|
||||
DATE_FORMAT(fecha, '%Y') as anio,
|
||||
DATE_FORMAT(fecha, '%m') as mes_num,
|
||||
DATE_FORMAT(fecha, '%M %Y') as mes_nombre,
|
||||
SUM(monto) as total_monto
|
||||
FROM
|
||||
inversiones
|
||||
GROUP BY
|
||||
anio, mes_num, mes_nombre
|
||||
ORDER BY
|
||||
anio DESC, mes_num DESC
|
||||
");
|
||||
$monthly_totals = $stmt_totals->fetchAll(PDO::FETCH_ASSOC);
|
||||
} catch (PDOException $e) {
|
||||
die("Error al obtener los totales mensuales: " . $e->getMessage());
|
||||
}
|
||||
|
||||
|
||||
// Separar por tipo
|
||||
$inversiones_operativas = array_filter($all_inversiones, fn($inv) => $inv['tipo'] === 'operativa');
|
||||
$inversiones_operacionales = array_filter($all_inversiones, fn($inv) => $inv['tipo'] === 'operacional');
|
||||
$inversiones_ads = array_filter($all_inversiones, fn($inv) => $inv['tipo'] === 'ads');
|
||||
|
||||
// Función para renderizar una sección de inversión
|
||||
function render_investment_section($title, $type, $data, $description_label) {
|
||||
$total = array_sum(array_column($data, 'monto'));
|
||||
?>
|
||||
<div class="container mt-5" id="section-<?= htmlspecialchars($type) ?>">
|
||||
<h2><?= htmlspecialchars($title) ?></h2>
|
||||
<div class="card">
|
||||
<div class="card-body">
|
||||
<!-- Formulario para agregar -->
|
||||
<form method="POST" action="<?= $_SERVER['PHP_SELF'] ?>#section-<?= htmlspecialchars($type) ?>" class="mb-4">
|
||||
<input type="hidden" name="tipo" value="<?= htmlspecialchars($type) ?>">
|
||||
<div class="row align-items-end g-3">
|
||||
<div class="col-md-3">
|
||||
<label for="fecha_<?= htmlspecialchars($type) ?>" class="form-label">Fecha</label>
|
||||
<input type="date" class="form-control" id="fecha_<?= htmlspecialchars($type) ?>" name="fecha" value="<?= date('Y-m-d') ?>" required>
|
||||
</div>
|
||||
<div class="col-md-5">
|
||||
<label for="desc_<?= htmlspecialchars($type) ?>" class="form-label"><?= htmlspecialchars($description_label) ?></label>
|
||||
<input type="text" class="form-control" id="desc_<?= htmlspecialchars($type) ?>" name="descripcion" required>
|
||||
</div>
|
||||
<div class="col-md-2">
|
||||
<label for="monto_<?= htmlspecialchars($type) ?>" class="form-label">Monto (S/)</label>
|
||||
<input type="number" step="0.01" class="form-control" id="monto_<?= htmlspecialchars($type) ?>" name="monto" required>
|
||||
</div>
|
||||
<div class="col-md-2">
|
||||
<button type="submit" name="add_investment" class="btn btn-primary w-100">Agregar</button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
<!-- Tabla de datos -->
|
||||
<div class="table-responsive">
|
||||
<table class="table table-bordered table-striped table-hover">
|
||||
<thead class="table-dark">
|
||||
<tr>
|
||||
<th style="width: 15%;">Fecha</th>
|
||||
<th>Descripción</th>
|
||||
<th style="width: 15%;">Monto (S/)</th>
|
||||
<th style="width: 15%;">Acciones</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<?php if (empty($data)): ?>
|
||||
<tr>
|
||||
<td colspan="4" class="text-center">No hay registros.</td>
|
||||
</tr>
|
||||
<?php else: ?>
|
||||
<?php foreach ($data as $item): ?>
|
||||
<tr>
|
||||
<td><?= htmlspecialchars(date("d/m/Y", strtotime($item['fecha']))) ?></td>
|
||||
<td><?= htmlspecialchars($item['descripcion']) ?></td>
|
||||
<td><?= number_format($item['monto'], 2) ?></td>
|
||||
<td>
|
||||
<a href="editar_inversion.php?id=<?= $item['id'] ?>" class="btn btn-warning btn-sm">Editar</a>
|
||||
<a href="eliminar_inversion.php?id=<?= $item['id'] ?>" class="btn btn-danger btn-sm" onclick="return confirm('¿Estás seguro de que quieres eliminar este registro?');">Eliminar</a>
|
||||
</td>
|
||||
</tr>
|
||||
<?php endforeach; ?>
|
||||
<?php endif; ?>
|
||||
</tbody>
|
||||
<tfoot>
|
||||
<tr class="table-dark fw-bold">
|
||||
<td colspan="2" class="text-end">TOTAL:</td>
|
||||
<td><?= number_format($total, 2) ?></td>
|
||||
<td></td>
|
||||
</tr>
|
||||
</tfoot>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<?php
|
||||
}
|
||||
?>
|
||||
|
||||
<div class="container-fluid mt-4">
|
||||
<h1>Gestión de Inversiones</h1>
|
||||
<hr>
|
||||
<?php if (isset($error_message)): ?>
|
||||
<div class="alert alert-danger"><?= htmlspecialchars($error_message) ?></div>
|
||||
<?php endif; ?>
|
||||
<?php if (isset($_GET['success'])): ?>
|
||||
<div class="alert alert-success">Registro guardado exitosamente.</div>
|
||||
<?php endif; ?>
|
||||
|
||||
<!-- Resumen de Totales por Mes -->
|
||||
<div class="container mt-4 mb-5">
|
||||
<h2>Resumen de Inversiones por Mes</h2>
|
||||
<div class="card">
|
||||
<div class="card-body">
|
||||
<div class="table-responsive">
|
||||
<table class="table table-bordered table-hover">
|
||||
<thead class="table-dark">
|
||||
<tr>
|
||||
<th>Mes</th>
|
||||
<th>Monto Total (S/)</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<?php if (empty($monthly_totals)): ?>
|
||||
<tr>
|
||||
<td colspan="2" class="text-center">No hay datos para mostrar.</td>
|
||||
</tr>
|
||||
<?php else: ?>
|
||||
<?php foreach ($monthly_totals as $total): ?>
|
||||
<tr style="cursor: pointer;" onclick="window.location='inversiones_operativas.php?mes=<?= $total['mes_num'] ?>&anio=<?= $total['anio'] ?>'">
|
||||
<td><?= htmlspecialchars(ucfirst($total['mes_nombre'])) ?></td>
|
||||
<td><?= number_format($total['total_monto'], 2) ?></td>
|
||||
</tr>
|
||||
<?php endforeach; ?>
|
||||
<?php endif; ?>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<?php if ($filter_active): ?>
|
||||
<div class="container mb-4">
|
||||
<div class="d-flex justify-content-between align-items-center">
|
||||
<h3 class="mb-0">Mostrando Inversiones de: <strong><?= htmlspecialchars(ucfirst(strftime('%B %Y', mktime(0, 0, 0, $selected_month, 1, $selected_year)))) ?></strong></h3>
|
||||
<a href="inversiones_operativas.php" class="btn btn-info">Mostrar Todos los Meses</a>
|
||||
</div>
|
||||
<hr>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
|
||||
</div>
|
||||
|
||||
<?php
|
||||
// Renderizar las tres secciones
|
||||
render_investment_section("Inversion Mercaderia", "operativa", $inversiones_operativas, "Descripción de la Inversión");
|
||||
render_investment_section("Inversiones en Ads", "ads", $inversiones_ads, "Descripción del Gasto de Publicidad");
|
||||
render_investment_section("Inversiones Operacionales", "operacional", $inversiones_operacionales, "Descripción del Gasto");
|
||||
|
||||
require_once 'includes/footer.php';
|
||||
?>
|
||||
244
liquidaciones.php
Normal file
@ -0,0 +1,244 @@
|
||||
<?php
|
||||
require_once 'includes/header.php';
|
||||
|
||||
// Lógica para manejar el formulario de actualización
|
||||
if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['precios'])) {
|
||||
$pdo = db();
|
||||
$success = false;
|
||||
|
||||
// Actualizar precios de liquidación
|
||||
if (isset($_POST['precios'])) {
|
||||
$stmt_precio = $pdo->prepare("UPDATE movimientos SET precio_liquidacion = :precio WHERE id = :id AND tipo = 'Salida'");
|
||||
foreach ($_POST['precios'] as $movimiento_id => $precio) {
|
||||
if (!empty($precio)) {
|
||||
$stmt_precio->execute([':precio' => $precio, ':id' => $movimiento_id]);
|
||||
$success = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Actualizar cantidad de pedidos
|
||||
if (isset($_POST['pedidos'])) {
|
||||
$stmt_pedidos = $pdo->prepare("UPDATE movimientos SET cantidad_pedidos = :pedidos WHERE id = :id AND tipo = 'Salida'");
|
||||
foreach ($_POST['pedidos'] as $movimiento_id => $pedidos) {
|
||||
if (!empty($pedidos)) {
|
||||
$stmt_pedidos->execute([':pedidos' => $pedidos, ':id' => $movimiento_id]);
|
||||
$success = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ($success) {
|
||||
echo '<div class="alert alert-success">Liquidaciones actualizadas correctamente.</div>';
|
||||
}
|
||||
}
|
||||
|
||||
// Obtener todos los movimientos de salida
|
||||
$pdo = db();
|
||||
$stmt = $pdo->query("
|
||||
SELECT
|
||||
m.id,
|
||||
m.fecha,
|
||||
p.nombre as producto,
|
||||
m.cantidad,
|
||||
c.nombre as ciudad,
|
||||
m.precio_liquidacion,
|
||||
m.cantidad_pedidos
|
||||
FROM movimientos m
|
||||
JOIN productos p ON m.producto_id = p.id
|
||||
JOIN ciudades c ON m.ciudad_origen_id = c.id
|
||||
WHERE m.tipo = 'Salida'
|
||||
ORDER BY DATE(m.fecha) DESC, c.nombre, p.nombre
|
||||
");
|
||||
|
||||
$salidas = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
||||
|
||||
// Agrupar salidas por fecha
|
||||
$salidas_por_fecha = [];
|
||||
foreach ($salidas as $salida) {
|
||||
$fecha = date('Y-m-d', strtotime($salida['fecha']));
|
||||
$salidas_por_fecha[$fecha][] = $salida;
|
||||
}
|
||||
|
||||
// Obtener productos y ciudades para el formulario
|
||||
$productos = $pdo->query("SELECT id, nombre FROM productos ORDER BY nombre")->fetchAll();
|
||||
$ciudades = $pdo->query("SELECT id, nombre FROM ciudades ORDER BY nombre")->fetchAll();
|
||||
|
||||
$user_rol = $_SESSION['user_rol'] ?? '';
|
||||
|
||||
?>
|
||||
|
||||
<h1 class="mb-4">Liquidaciones de Salidas</h1>
|
||||
|
||||
<?php
|
||||
if (isset($_GET['success']) && $_GET['success'] === 'added') {
|
||||
echo '<div class="alert alert-success">Liquidación agregada correctamente.</div>';
|
||||
}
|
||||
if (isset($_GET['error'])) {
|
||||
$error_msg = 'Ocurrió un error.';
|
||||
if ($_GET['error'] === 'missing_fields') {
|
||||
$error_msg = 'Por favor, complete todos los campos.';
|
||||
} elseif ($_GET['error'] === 'db_error') {
|
||||
$error_msg = 'Error al guardar en la base de datos.';
|
||||
} elseif ($_GET['error'] === 'no_stock') {
|
||||
$error_msg = 'No hay stock suficiente para realizar esta operación.';
|
||||
}
|
||||
echo '<div class="alert alert-danger">'.$error_msg.'</div>';
|
||||
}
|
||||
?>
|
||||
|
||||
<p>Aquí puedes ver todas las salidas de productos y asignar un precio de liquidación y la cantidad de pedidos a cada una.</p>
|
||||
|
||||
<?php if ($user_rol === 'admin' || $user_rol === 'administrador'): ?>
|
||||
<!-- Botón para abrir el modal -->
|
||||
<button type="button" class="btn btn-success mb-3" data-bs-toggle="modal" data-bs-target="#agregarLiquidacionModal">
|
||||
<i class="fas fa-plus"></i> Agregar Liquidación
|
||||
</button>
|
||||
|
||||
<!-- Modal para agregar liquidación -->
|
||||
<div class="modal fade" id="agregarLiquidacionModal" tabindex="-1" aria-labelledby="agregarLiquidacionModalLabel" aria-hidden="true">
|
||||
<div class="modal-dialog">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h5 class="modal-title" id="agregarLiquidacionModalLabel">Agregar Nueva Liquidación</h5>
|
||||
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<form action="handle_agregar_liquidacion.php" method="POST">
|
||||
<div class="mb-3">
|
||||
<label for="producto_id" class="form-label">Producto</label>
|
||||
<select class="form-control" id="producto_id" name="producto_id" required>
|
||||
<option value="">Seleccione un producto</option>
|
||||
<?php foreach ($productos as $producto): ?>
|
||||
<option value="<?php echo $producto['id']; ?>"><?php echo htmlspecialchars($producto['nombre']); ?></option>
|
||||
<?php endforeach; ?>
|
||||
</select>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label for="ciudad_id" class="form-label">Ciudad</label>
|
||||
<select class="form-control" id="ciudad_id" name="ciudad_id" required>
|
||||
<option value="">Seleccione una ciudad</option>
|
||||
<?php foreach ($ciudades as $ciudad): ?>
|
||||
<option value="<?php echo $ciudad['id']; ?>"><?php echo htmlspecialchars($ciudad['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" required>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label for="cantidad_pedidos" class="form-label">Cantidad de Pedidos</label>
|
||||
<input type="number" class="form-control" id="cantidad_pedidos" name="cantidad_pedidos" required>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label for="precio_liquidacion" class="form-label">Precio Liquidación</label>
|
||||
<input type="number" step="0.01" class="form-control" id="precio_liquidacion" name="precio_liquidacion" required>
|
||||
</div>
|
||||
<button type="submit" class="btn btn-primary">Guardar Liquidación</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
|
||||
|
||||
<form method="POST" action="liquidaciones.php">
|
||||
<?php if (empty($salidas_por_fecha)): ?>
|
||||
<div class="alert alert-info">No hay salidas registradas para liquidar.</div>
|
||||
<?php else: ?>
|
||||
<div class="accordion" id="accordionLiquidaciones">
|
||||
<?php foreach ($salidas_por_fecha as $fecha => $salidas_del_dia): ?>
|
||||
<div class="accordion-item">
|
||||
<h2 class="accordion-header" id="heading-<?php echo $fecha; ?>">
|
||||
<button class="accordion-button collapsed" type="button" data-bs-toggle="collapse" data-bs-target="#collapse-<?php echo $fecha; ?>" aria-expanded="false" aria-controls="collapse-<?php echo $fecha; ?>">
|
||||
<?php echo date('d/m/Y', strtotime($fecha)); ?> (<?php echo count($salidas_del_dia); ?> salidas)
|
||||
</button>
|
||||
</h2>
|
||||
<div id="collapse-<?php echo $fecha; ?>" class="accordion-collapse collapse" aria-labelledby="heading-<?php echo $fecha; ?>" data-bs-parent="#accordionLiquidaciones">
|
||||
<div class="accordion-body">
|
||||
<table class="table table-bordered table-striped">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Producto</th>
|
||||
<th>Ciudad</th>
|
||||
<th>Cantidad de Pedidos</th>
|
||||
<th>Cantidad</th>
|
||||
<th>Precio Liquidación</th>
|
||||
<th>Acciones</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<?php
|
||||
$total_pedidos_dia = 0;
|
||||
$total_precio_dia = 0;
|
||||
$totales_por_ciudad = [];
|
||||
|
||||
foreach ($salidas_del_dia as $salida):
|
||||
$total_pedidos_dia += (int)$salida['cantidad_pedidos'];
|
||||
$total_precio_dia += (float)$salida['precio_liquidacion'];
|
||||
|
||||
if (!isset($totales_por_ciudad[$salida['ciudad']])) {
|
||||
$totales_por_ciudad[$salida['ciudad']] = ['pedidos' => 0, 'precio' => 0];
|
||||
}
|
||||
$totales_por_ciudad[$salida['ciudad']]['pedidos'] += (int)$salida['cantidad_pedidos'];
|
||||
$totales_por_ciudad[$salida['ciudad']]['precio'] += (float)$salida['precio_liquidacion'];
|
||||
?>
|
||||
<tr>
|
||||
<td><?php echo htmlspecialchars($salida['producto']); ?></td>
|
||||
<td><?php echo htmlspecialchars($salida['ciudad']); ?></td>
|
||||
<td>
|
||||
<input type="number" step="1" name="pedidos[<?php echo $salida['id']; ?>]" class="form-control" value="<?php echo htmlspecialchars($salida['cantidad_pedidos']); ?>">
|
||||
</td>
|
||||
<td><?php echo $salida['cantidad']; ?></td>
|
||||
<td>
|
||||
<input type="number" step="0.01" name="precios[<?php echo $salida['id']; ?>]" class="form-control" value="<?php echo htmlspecialchars($salida['precio_liquidacion']); ?>">
|
||||
</td>
|
||||
<td>
|
||||
<a href="editar_liquidacion.php?id=<?php echo $salida['id']; ?>" class="btn btn-sm btn-warning">
|
||||
<i class="fas fa-edit"></i>
|
||||
</a>
|
||||
</td>
|
||||
</tr>
|
||||
<?php endforeach; ?>
|
||||
</tbody>
|
||||
<tfoot>
|
||||
<tr>
|
||||
<td colspan="2" class="text-end"><strong>Total del día:</strong></td>
|
||||
<td><strong><?php echo $total_pedidos_dia; ?></strong></td>
|
||||
<td></td>
|
||||
<td><strong><?php echo number_format($total_precio_dia, 2); ?></strong></td>
|
||||
</tr>
|
||||
</tfoot>
|
||||
</table>
|
||||
|
||||
<h5 class="mt-4">Resumen de Liquidación por Ciudad</h5>
|
||||
<table class="table table-sm table-bordered">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Ciudad</th>
|
||||
<th>Total Pedidos</th>
|
||||
<th>Monto Total Liquidado</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<?php foreach ($totales_por_ciudad as $ciudad => $totales): ?>
|
||||
<tr>
|
||||
<td><?php echo htmlspecialchars($ciudad); ?></td>
|
||||
<td><?php echo $totales['pedidos']; ?></td>
|
||||
<td><?php echo number_format($totales['precio'], 2); ?></td>
|
||||
</tr>
|
||||
<?php endforeach; ?>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<?php endforeach; ?>
|
||||
</div>
|
||||
<button type="submit" class="btn btn-primary mt-3">Guardar Cambios</button>
|
||||
<?php endif; ?>
|
||||
</form>
|
||||
|
||||
<?php require_once 'includes/footer.php'; ?>
|
||||
108
liquidaciones_por_fecha.php
Normal file
@ -0,0 +1,108 @@
|
||||
<?php
|
||||
require_once 'includes/header.php';
|
||||
|
||||
$fecha_inicio = isset($_GET['fecha_inicio']) ? $_GET['fecha_inicio'] : '';
|
||||
$fecha_fin = isset($_GET['fecha_fin']) ? $_GET['fecha_fin'] : '';
|
||||
|
||||
$resultados = [];
|
||||
|
||||
if ($fecha_inicio && $fecha_fin) {
|
||||
$pdo = db();
|
||||
$stmt = $pdo->prepare("
|
||||
SELECT
|
||||
p.nombre AS producto,
|
||||
c.nombre AS ciudad,
|
||||
SUM(m.cantidad_pedidos) AS cantidad_pedidos,
|
||||
SUM(m.cantidad) AS cantidad,
|
||||
SUM(m.precio_liquidacion) AS precio_liquidaciones
|
||||
FROM
|
||||
movimientos m
|
||||
JOIN
|
||||
productos p ON m.producto_id = p.id
|
||||
JOIN
|
||||
ciudades c ON m.ciudad_origen_id = c.id
|
||||
WHERE
|
||||
m.tipo = 'Salida' AND DATE(m.fecha) BETWEEN :fecha_inicio AND :fecha_fin
|
||||
GROUP BY
|
||||
p.nombre, c.nombre
|
||||
ORDER BY
|
||||
precio_liquidaciones DESC
|
||||
");
|
||||
$stmt->execute([
|
||||
':fecha_inicio' => $fecha_inicio,
|
||||
':fecha_fin' => $fecha_fin
|
||||
]);
|
||||
$resultados = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
||||
}
|
||||
?>
|
||||
|
||||
<h1 class="mb-4">Liquidaciones por Fecha</h1>
|
||||
|
||||
<div class="card mb-4">
|
||||
<div class="card-body">
|
||||
<h5 class="card-title">Filtrar por Fecha</h5>
|
||||
<form method="GET" action="liquidaciones_por_fecha.php" class="row g-3">
|
||||
<div class="col-md-5">
|
||||
<label for="fecha_inicio" class="form-label">Fecha de Inicio</label>
|
||||
<input type="date" class="form-control" id="fecha_inicio" name="fecha_inicio" value="<?php echo htmlspecialchars($fecha_inicio); ?>" required>
|
||||
</div>
|
||||
<div class="col-md-5">
|
||||
<label for="fecha_fin" class="form-label">Fecha de Fin</label>
|
||||
<input type="date" class="form-control" id="fecha_fin" name="fecha_fin" value="<?php echo htmlspecialchars($fecha_fin); ?>" required>
|
||||
</div>
|
||||
<div class="col-md-2 d-flex align-items-end">
|
||||
<button type="submit" class="btn btn-primary w-100">Calcular</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<?php if ($fecha_inicio && $fecha_fin): ?>
|
||||
<h2 class="mb-3">Resultados del <?php echo htmlspecialchars(date('d/m/Y', strtotime($fecha_inicio))); ?> al <?php echo htmlspecialchars(date('d/m/Y', strtotime($fecha_fin))); ?></h2>
|
||||
<?php if (empty($resultados)): ?>
|
||||
<div class="alert alert-info">No se encontraron liquidaciones para el rango de fechas seleccionado.</div>
|
||||
<?php else: ?>
|
||||
<div class="table-responsive">
|
||||
<table class="table table-bordered table-striped">
|
||||
<thead class="table-dark">
|
||||
<tr>
|
||||
<th>Producto</th>
|
||||
<th>Ciudad</th>
|
||||
<th>Cantidad de Pedidos</th>
|
||||
<th>Cantidad</th>
|
||||
<th>Precio Liquidaciones</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<?php
|
||||
$total_pedidos = 0;
|
||||
$total_cantidad = 0;
|
||||
$total_precio = 0;
|
||||
foreach ($resultados as $fila):
|
||||
$total_pedidos += $fila['cantidad_pedidos'];
|
||||
$total_cantidad += $fila['cantidad'];
|
||||
$total_precio += $fila['precio_liquidaciones'];
|
||||
?>
|
||||
<tr>
|
||||
<td><?php echo htmlspecialchars($fila['producto']); ?></td>
|
||||
<td><?php echo htmlspecialchars($fila['ciudad']); ?></td>
|
||||
<td><?php echo htmlspecialchars($fila['cantidad_pedidos']); ?></td>
|
||||
<td><?php echo htmlspecialchars($fila['cantidad']); ?></td>
|
||||
<td><?php echo htmlspecialchars(number_format($fila['precio_liquidaciones'], 2)); ?></td>
|
||||
</tr>
|
||||
<?php endforeach; ?>
|
||||
</tbody>
|
||||
<tfoot>
|
||||
<tr class="table-secondary">
|
||||
<th colspan="2" class="text-end">Totales:</th>
|
||||
<th><?php echo $total_pedidos; ?></th>
|
||||
<th><?php echo $total_cantidad; ?></th>
|
||||
<th><?php echo number_format($total_precio, 2); ?></th>
|
||||
</tr>
|
||||
</tfoot>
|
||||
</table>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
<?php endif; ?>
|
||||
|
||||
<?php require_once 'includes/footer.php'; ?>
|
||||
166
productos.php
Normal file
@ -0,0 +1,166 @@
|
||||
<?php
|
||||
session_start();
|
||||
if (!isset($_SESSION["user_id"])) {
|
||||
header("Location: auth/login.php");
|
||||
exit();
|
||||
}
|
||||
require_once 'includes/header.php';
|
||||
|
||||
// Search term
|
||||
$search_term = isset($_GET['search']) ? trim($_GET['search']) : '';
|
||||
|
||||
// Pagination settings
|
||||
$products_per_page = 10;
|
||||
$page = isset($_GET['page']) && is_numeric($_GET['page']) ? (int)$_GET['page'] : 1;
|
||||
$offset = ($page - 1) * $products_per_page;
|
||||
|
||||
$pdo = db();
|
||||
|
||||
// Base query
|
||||
$sql_count = "SELECT COUNT(*) FROM productos";
|
||||
$sql = "SELECT
|
||||
p.*,
|
||||
GROUP_CONCAT(c.nombre, ': ', spc.stock_actual SEPARATOR '<br>') as stock_por_ciudad
|
||||
FROM
|
||||
productos p
|
||||
LEFT JOIN
|
||||
stock_por_ciudad spc ON p.id = spc.producto_id
|
||||
LEFT JOIN
|
||||
ciudades c ON spc.ciudad_id = c.id";
|
||||
|
||||
$params = [];
|
||||
|
||||
// Apply search filter
|
||||
if (!empty($search_term)) {
|
||||
$sql_count .= " WHERE nombre LIKE :search";
|
||||
$sql .= " WHERE p.nombre LIKE :search";
|
||||
$params[':search'] = '%' . $search_term . '%';
|
||||
}
|
||||
|
||||
// Get total number of products (filtered or not)
|
||||
$total_stmt = $pdo->prepare($sql_count);
|
||||
$total_stmt->execute($params);
|
||||
$total_products = $total_stmt->fetchColumn();
|
||||
$total_pages = ceil($total_products / $products_per_page);
|
||||
|
||||
// Add grouping, ordering and pagination to the main query
|
||||
$sql .= " GROUP BY p.id
|
||||
ORDER BY p.id DESC
|
||||
LIMIT :limit OFFSET :offset";
|
||||
|
||||
$stmt = $pdo->prepare($sql);
|
||||
|
||||
// Bind search param if it exists
|
||||
if (!empty($search_term)) {
|
||||
$stmt->bindValue(':search', $params[':search'], PDO::PARAM_STR);
|
||||
}
|
||||
|
||||
$stmt->bindValue(':limit', $products_per_page, PDO::PARAM_INT);
|
||||
$stmt->bindValue(':offset', $offset, PDO::PARAM_INT);
|
||||
$stmt->execute();
|
||||
$products = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
||||
?>
|
||||
|
||||
<div class="container-fluid">
|
||||
|
||||
<!-- Page Heading -->
|
||||
<div class="d-sm-flex justify-content-between align-items-center mb-4">
|
||||
<h3 class="text-dark mb-0">Gestión de Productos</h3>
|
||||
<a class="btn btn-primary btn-sm" role="button" href="agregar_producto.php">
|
||||
<i class="fas fa-plus fa-sm text-white-50"></i> Agregar Producto
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<!-- Success/Error Messages -->
|
||||
<?php if (isset($_SESSION['success_message'])): ?>
|
||||
<div class="alert alert-success alert-dismissible fade show" role="alert">
|
||||
<?php echo $_SESSION['success_message']; ?>
|
||||
<button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>
|
||||
</div>
|
||||
<?php unset($_SESSION['success_message']); ?>
|
||||
<?php endif; ?>
|
||||
<?php if (isset($_SESSION['error_message'])): ?>
|
||||
<div class="alert alert-danger alert-dismissible fade show" role="alert">
|
||||
<?php echo $_SESSION['error_message']; ?>
|
||||
<button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>
|
||||
</div>
|
||||
<?php unset($_SESSION['error_message']); ?>
|
||||
<?php endif; ?>
|
||||
|
||||
<!-- Search Form -->
|
||||
<div class="card shadow mb-4">
|
||||
<div class="card-body">
|
||||
<form action="productos.php" method="get" class="form-inline">
|
||||
<div class="input-group">
|
||||
<input type="text" name="search" class="form-control" placeholder="Buscar por nombre..." value="<?php echo isset($_GET['search']) ? htmlspecialchars($_GET['search']) : ''; ?>">
|
||||
<button class="btn btn-primary" type="submit">Buscar</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card shadow">
|
||||
<div class="card-header py-3">
|
||||
<p class="text-primary m-0 fw-bold">Listado de Productos</p>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<div class="table-responsive table mt-2" id="dataTable" role="grid" aria-describedby="dataTable_info">
|
||||
<table class="table my-0" id="dataTable">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>ID</th>
|
||||
<th>Nombre</th>
|
||||
<th>Descripción</th>
|
||||
<th>Precio</th>
|
||||
<th>Stock por Ciudad</th>
|
||||
<th>Acciones</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<?php if (count($products) > 0): ?>
|
||||
<?php foreach ($products as $product): ?>
|
||||
<tr>
|
||||
<td><?php echo htmlspecialchars($product['id']); ?></td>
|
||||
<td><?php echo htmlspecialchars($product['nombre']); ?></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="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>
|
||||
</tr>
|
||||
<?php endforeach; ?>
|
||||
<?php else: ?>
|
||||
<tr>
|
||||
<td colspan="6" class="text-center">No hay productos que coincidan con la búsqueda.</td>
|
||||
</tr>
|
||||
<?php endif; ?>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<!-- Pagination Controls -->
|
||||
<div class="d-flex justify-content-center">
|
||||
<nav>
|
||||
<ul class="pagination">
|
||||
<?php if ($page > 1): ?>
|
||||
<li class="page-item"><a class="page-link" href="?page=<?php echo $page - 1; ?><?php echo isset($_GET['search']) ? '&search=' . htmlspecialchars($_GET['search']) : ''; ?>">Anterior</a></li>
|
||||
<?php endif; ?>
|
||||
|
||||
<?php for ($i = 1; $i <= $total_pages; $i++): ?>
|
||||
<li class="page-item <?php echo ($i == $page) ? 'active' : ''; ?>">
|
||||
<a class="page-link" href="?page=<?php echo $i; ?><?php echo isset($_GET['search']) ? '&search=' . htmlspecialchars($_GET['search']) : ''; ?>"><?php echo $i; ?></a>
|
||||
</li>
|
||||
<?php endfor; ?>
|
||||
|
||||
<?php if ($page < $total_pages): ?>
|
||||
<li class="page-item"><a class="page-link" href="?page=<?php echo $page + 1; ?><?php echo isset($_GET['search']) ? '&search=' . htmlspecialchars($_GET['search']) : ''; ?>">Siguiente</a></li>
|
||||
<?php endif; ?>
|
||||
</ul>
|
||||
</nav>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<?php require_once 'includes/footer.php'; ?>
|
||||
86
registrar_entrada.php
Normal file
@ -0,0 +1,86 @@
|
||||
<?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 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-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>
|
||||
|
||||
<?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>
|
||||
|
||||
<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-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 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>
|
||||
|
||||
<button type="submit" class="btn btn-success">Registrar Entrada</button>
|
||||
<a href="productos.php" class="btn btn-secondary">Cancelar</a>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<?php
|
||||
require_once 'includes/footer.php';
|
||||
?>
|
||||
112
registrar_salida.php
Normal file
@ -0,0 +1,112 @@
|
||||
<?php
|
||||
// registrar_salida.php
|
||||
require_once 'includes/header.php';
|
||||
|
||||
// Restringir acceso solo a roles autorizados
|
||||
$allowed_roles = ['Administrador General', 'Encargado de Stock'];
|
||||
if (!isset($_SESSION['user_rol']) || !in_array($_SESSION['user_rol'], $allowed_roles)) {
|
||||
$_SESSION['error'] = 'No tienes permiso para acceder a esta página.';
|
||||
header('Location: index.php');
|
||||
exit;
|
||||
}
|
||||
|
||||
|
||||
// Fetch products and cities for the dropdowns
|
||||
try {
|
||||
$pdo = db();
|
||||
|
||||
// Fetch products
|
||||
$stmt_productos = $pdo->query("SELECT id, nombre FROM productos 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) {
|
||||
// Handle DB error
|
||||
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-5">
|
||||
<h1>Registrar Salida de Producto</h1>
|
||||
<p>Selecciona el producto, la ciudad y la cantidad que deseas dar de baja del 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_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>
|
||||
|
||||
<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-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>
|
||||
|
||||
<button type="submit" class="btn btn-primary">Registrar Salida</button>
|
||||
<a href="productos.php" class="btn btn-secondary">Cancelar</a>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<?php
|
||||
require_once 'includes/footer.php';
|
||||
?>
|
||||
255
reporte_de_pagos.php
Normal file
@ -0,0 +1,255 @@
|
||||
<?php
|
||||
require_once 'includes/header.php';
|
||||
|
||||
// Restringir acceso solo a administradores
|
||||
if (isset($_SESSION['user_rol']) && $_SESSION['user_rol'] === 'Encargado de Stock') {
|
||||
header('Location: /index.php?error=unauthorized');
|
||||
exit;
|
||||
}
|
||||
require_once 'db/config.php';
|
||||
|
||||
// Get current month and year, or from query string
|
||||
$month = isset($_GET['month']) ? (int)$_GET['month'] : date('m');
|
||||
$year = isset($_GET['year']) ? (int)$_GET['year'] : date('Y');
|
||||
|
||||
// Handle month overflow/underflow
|
||||
if ($month > 12) {
|
||||
$month = 1;
|
||||
$year++;
|
||||
} elseif ($month < 1) {
|
||||
$month = 12;
|
||||
$year--;
|
||||
}
|
||||
|
||||
$liquidacion_id = (int)($year . str_pad($month, 2, '0', STR_PAD_LEFT));
|
||||
|
||||
// Get city and sales data
|
||||
$db = db();
|
||||
$cities_stmt = $db->query("SELECT id, nombre FROM ciudades ORDER BY orden ASC");
|
||||
$cities = $cities_stmt->fetchAll(PDO::FETCH_ASSOC);
|
||||
|
||||
// Get payment statuses for each city and day
|
||||
$estados_stmt = $db->prepare("SELECT ciudad_id, fecha, estado FROM liquidaciones_ciudades_estados WHERE liquidacion_id = :liquidacion_id");
|
||||
$estados_stmt->execute(['liquidacion_id' => $liquidacion_id]);
|
||||
$estados_raw = $estados_stmt->fetchAll(PDO::FETCH_ASSOC);
|
||||
$estados = [];
|
||||
foreach ($estados_raw as $row) {
|
||||
$estados[$row['ciudad_id']][$row['fecha']] = $row['estado'];
|
||||
}
|
||||
|
||||
$sales_stmt = $db->prepare("
|
||||
SELECT
|
||||
DAY(m.fecha) as dia,
|
||||
m.ciudad_origen_id,
|
||||
SUM(m.precio_liquidacion) as monto_total
|
||||
FROM movimientos m
|
||||
WHERE m.tipo = 'Salida'
|
||||
AND MONTH(m.fecha) = :month
|
||||
AND YEAR(m.fecha) = :year
|
||||
GROUP BY DAY(m.fecha), m.ciudad_origen_id
|
||||
");
|
||||
$sales_stmt->execute(['month' => $month, 'year' => $year]);
|
||||
$sales_data = $sales_stmt->fetchAll(PDO::FETCH_ASSOC);
|
||||
|
||||
// Organize sales data in a structured way: [day][city_id] => amount
|
||||
$sales_by_day_city = [];
|
||||
foreach ($sales_data as $sale) {
|
||||
$sales_by_day_city[$sale['dia']][$sale['ciudad_origen_id']] = $sale['monto_total'];
|
||||
}
|
||||
|
||||
$days_in_month = cal_days_in_month(CAL_GREGORIAN, $month, $year);
|
||||
$month_name = DateTime::createFromFormat('!m', $month)->format('F');
|
||||
|
||||
?>
|
||||
|
||||
<div class="container-fluid">
|
||||
<div class="d-sm-flex align-items-center justify-content-between mb-4">
|
||||
<h1 class="h3 mb-0 text-gray-800">Validaciones De Pagos</h1>
|
||||
</div>
|
||||
|
||||
<div class="card shadow mb-4">
|
||||
<div class="card-header py-3">
|
||||
<h6 class="m-0 font-weight-bold text-primary">Resumen de Ventas por Ciudad</h6>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<div class="d-flex justify-content-center align-items-center mb-3">
|
||||
<a href="?month=<?php echo $month - 1; ?>&year=<?php echo $year; ?>" class="btn btn-secondary btn-sm mr-2">< Mes Anterior</a>
|
||||
<h4 class="mb-0"><?php echo ucfirst($month_name) . ' ' . $year; ?></h4>
|
||||
<a href="?month=<?php echo $month + 1; ?>&year=<?php echo $year; ?>" class="btn btn-secondary btn-sm ml-2">Mes Siguiente ></a>
|
||||
</div>
|
||||
|
||||
<style>
|
||||
#dataTable td,
|
||||
#dataTable th {
|
||||
padding-top: 0.2rem;
|
||||
padding-bottom: 0.2rem;
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
#dataTable .form-control-sm {
|
||||
padding-top: 0.1rem;
|
||||
padding-bottom: 0.1rem;
|
||||
height: auto;
|
||||
}
|
||||
.table-responsive {
|
||||
max-height: 70vh; /* Ajusta la altura máxima según necesites */
|
||||
overflow-y: auto;
|
||||
}
|
||||
#dataTable thead th {
|
||||
position: sticky;
|
||||
background-color: #343a40 !important;
|
||||
color: #fff !important;
|
||||
z-index: 1;
|
||||
}
|
||||
#dataTable thead tr:first-child th {
|
||||
top: 0;
|
||||
z-index: 2; /* Prioridad para la fila superior */
|
||||
}
|
||||
#dataTable thead tr:nth-child(2) th {
|
||||
top: 38px; /* Ajusta si es necesario a la altura de la primera fila */
|
||||
}
|
||||
</style>
|
||||
|
||||
<div class="table-responsive">
|
||||
<table class="table table-bordered table-hover" id="dataTable" width="100%" cellspacing="0">
|
||||
<thead class="thead-dark">
|
||||
<tr>
|
||||
<th rowspan="2" class="align-middle text-center">Día</th>
|
||||
<?php foreach ($cities as $city) : ?>
|
||||
<th colspan="2" class="text-center"><?php echo htmlspecialchars($city['nombre']); ?></th>
|
||||
<?php endforeach; ?>
|
||||
<th rowspan="2" class="text-center table-primary align-middle">Total Diario</th>
|
||||
</tr>
|
||||
<tr>
|
||||
<?php foreach ($cities as $city) : ?>
|
||||
<th class="text-center">Recaudo</th>
|
||||
<th class="text-center">Estado</th>
|
||||
<?php endforeach; ?>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<?php
|
||||
$grand_total = 0;
|
||||
$city_totals = array_fill_keys(array_column($cities, 'id'), 0);
|
||||
|
||||
for ($day = 1; $day <= $days_in_month; $day++) :
|
||||
$daily_total = 0;
|
||||
$current_date = $year . '-' . str_pad($month, 2, '0', STR_PAD_LEFT) . '-' . str_pad($day, 2, '0', STR_PAD_LEFT);
|
||||
?>
|
||||
<tr>
|
||||
<td class="text-center align-middle"><?php echo htmlspecialchars(date('d-m-Y', strtotime($current_date))); ?></td>
|
||||
<?php foreach ($cities as $city) :
|
||||
$amount = $sales_by_day_city[$day][$city['id']] ?? 0;
|
||||
$daily_total += $amount;
|
||||
$city_totals[$city['id']] += $amount;
|
||||
?>
|
||||
<td class="text-right align-middle">
|
||||
<?php echo $amount > 0 ? number_format($amount, 2) : '-'; ?>
|
||||
</td>
|
||||
<?php
|
||||
$estado_actual = $estados[$city['id']][$current_date] ?? 'Pendiente';
|
||||
?>
|
||||
<td>
|
||||
<select class="form-control form-control-sm estado-pago"
|
||||
data-ciudad-id="<?php echo $city['id']; ?>"
|
||||
data-liquidacion-id="<?php echo $liquidacion_id; ?>"
|
||||
data-fecha="<?php echo $current_date; ?>">
|
||||
<option value="Pendiente" <?php echo $estado_actual === 'Pendiente' ? 'selected' : ''; ?>>Pendiente</option>
|
||||
<option value="Pagado" <?php echo $estado_actual === 'Pagado' ? 'selected' : ''; ?>>Pagado</option>
|
||||
</select>
|
||||
</td>
|
||||
<?php endforeach; ?>
|
||||
<td class="text-right font-weight-bold table-primary align-middle">
|
||||
<?php echo number_format($daily_total, 2); ?>
|
||||
</td>
|
||||
</tr>
|
||||
<?php
|
||||
$grand_total += $daily_total;
|
||||
endfor;
|
||||
?>
|
||||
</tbody>
|
||||
<tfoot class="thead-dark">
|
||||
<tr>
|
||||
<th>Total por Ciudad</th>
|
||||
<?php foreach ($cities as $city) : ?>
|
||||
<th class="text-right">
|
||||
<?php echo number_format($city_totals[$city['id']], 2); ?>
|
||||
</th>
|
||||
<th></th>
|
||||
<?php endforeach; ?>
|
||||
<th class="text-right table-success font-weight-bolder">
|
||||
<?php echo number_format($grand_total, 2); ?>
|
||||
</th>
|
||||
</tr>
|
||||
</tfoot>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
const selects = document.querySelectorAll('.estado-pago');
|
||||
|
||||
function setSelectStyle(select, estado) {
|
||||
const td = select.parentElement;
|
||||
if (estado === 'Pagado') {
|
||||
td.classList.add('bg-success');
|
||||
select.classList.add('bg-success', 'text-white');
|
||||
select.classList.remove('bg-light');
|
||||
} else {
|
||||
td.classList.remove('bg-success');
|
||||
select.classList.remove('bg-success', 'text-white');
|
||||
select.classList.add('bg-light');
|
||||
}
|
||||
}
|
||||
|
||||
selects.forEach(select => {
|
||||
// Set initial style based on the loaded value
|
||||
setSelectStyle(select, select.value);
|
||||
|
||||
select.addEventListener('change', function() {
|
||||
const ciudadId = this.dataset.ciudadId;
|
||||
const liquidacionId = this.dataset.liquidacionId;
|
||||
const fecha = this.dataset.fecha;
|
||||
const estado = this.value;
|
||||
const originalState = estado === 'Pagado' ? 'Pendiente' : 'Pagado';
|
||||
|
||||
// Optimistically update UI
|
||||
setSelectStyle(this, estado);
|
||||
|
||||
fetch('handle_pago_ciudad.php', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
body: JSON.stringify({
|
||||
liquidacion_id: liquidacionId,
|
||||
ciudad_id: ciudadId,
|
||||
fecha: fecha,
|
||||
estado: estado
|
||||
})
|
||||
})
|
||||
.then(response => response.json())
|
||||
.then(data => {
|
||||
if (!data.success) {
|
||||
alert('Error al actualizar el estado: ' + data.message);
|
||||
// Revert UI on error
|
||||
this.value = originalState;
|
||||
setSelectStyle(this, originalState);
|
||||
}
|
||||
})
|
||||
.catch(error => {
|
||||
console.error('Error en la petición:', error);
|
||||
alert('Ocurrió un error de red. Inténtalo de nuevo.');
|
||||
// Revert UI on error
|
||||
this.value = originalState;
|
||||
setSelectStyle(this, originalState);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
</script>
|
||||
|
||||
<?php require_once 'includes/footer.php'; ?>
|
||||
120
resumen_stock.php
Normal file
@ -0,0 +1,120 @@
|
||||
<?php
|
||||
session_start();
|
||||
if (!isset($_SESSION['user_id'])) {
|
||||
header('Location: auth/login.php');
|
||||
exit;
|
||||
}
|
||||
|
||||
require_once 'db/config.php';
|
||||
require_once 'includes/header.php';
|
||||
|
||||
$pdo = db();
|
||||
|
||||
// 1. Obtener todas las ciudades, ordenadas por stock total descendente
|
||||
$query_ciudades = '
|
||||
SELECT
|
||||
c.id,
|
||||
c.nombre,
|
||||
SUM(COALESCE(spc.stock_actual, 0)) as total_stock
|
||||
FROM
|
||||
ciudades c
|
||||
LEFT JOIN
|
||||
stock_por_ciudad spc ON c.id = spc.ciudad_id
|
||||
GROUP BY
|
||||
c.id, c.nombre
|
||||
ORDER BY
|
||||
total_stock DESC, c.orden ASC
|
||||
';
|
||||
$stmt_ciudades = $pdo->query($query_ciudades);
|
||||
$ciudades = $stmt_ciudades->fetchAll(PDO::FETCH_ASSOC);
|
||||
|
||||
// 2. Obtener todos los productos ordenados por la nueva columna 'orden'
|
||||
$stmt_productos = $pdo->query('SELECT id, nombre FROM productos ORDER BY orden ASC');
|
||||
$productos = $stmt_productos->fetchAll(PDO::FETCH_ASSOC);
|
||||
|
||||
// 3. Obtener todo el stock y organizarlo en un array para acceso rápido
|
||||
$stmt_stock = $pdo->query('SELECT producto_id, ciudad_id, stock_actual FROM stock_por_ciudad');
|
||||
$stock_data = [];
|
||||
while ($row = $stmt_stock->fetch(PDO::FETCH_ASSOC)) {
|
||||
$stock_data[$row['producto_id']][$row['ciudad_id']] = $row['stock_actual'];
|
||||
}
|
||||
|
||||
?>
|
||||
|
||||
<div class="container-fluid px-4">
|
||||
<h1 class="mt-4">Resumen de Stock</h1>
|
||||
<ol class="breadcrumb mb-4">
|
||||
<li class="breadcrumb-item"><a href="index.php">Dashboard</a></li>
|
||||
<li class="breadcrumb-item active">Resumen de Stock</li>
|
||||
</ol>
|
||||
|
||||
<div class="card mb-4">
|
||||
<div class="card-header">
|
||||
<i class="fas fa-table me-1"></i>
|
||||
Inventario por Producto y Ciudad
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<style>
|
||||
.table-compact th,
|
||||
.table-compact td {
|
||||
padding: 0.25rem 0.5rem;
|
||||
vertical-align: middle;
|
||||
}
|
||||
.bg-success-soft {
|
||||
background-color: #d1e7dd !important; /* Verde suave */
|
||||
}
|
||||
.bg-danger-soft {
|
||||
background-color: #f8d7da !important; /* Rojo suave */
|
||||
}
|
||||
</style>
|
||||
<div class="table-responsive">
|
||||
<table class="table table-bordered table-striped table-hover table-compact">
|
||||
<thead class="table-dark">
|
||||
<tr>
|
||||
<th style="width: 250px;">Producto</th>
|
||||
<th class="text-center" style="width: 100px;">Orden</th>
|
||||
<?php foreach ($ciudades as $ciudad): ?>
|
||||
<th class="text-center"><?php echo htmlspecialchars($ciudad['nombre']); ?></th>
|
||||
<?php endforeach; ?>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<?php if (empty($productos)): ?>
|
||||
<tr>
|
||||
<td colspan="<?php echo count($ciudades) + 2; ?>" class="text-center">No hay productos para mostrar.</td>
|
||||
</tr>
|
||||
<?php else: ?>
|
||||
<?php foreach ($productos as $producto): ?>
|
||||
<tr>
|
||||
<td><?php echo htmlspecialchars($producto['nombre']); ?></td>
|
||||
<td class="text-center">
|
||||
<a href="handle_orden_productos.php?id=<?php echo $producto['id']; ?>&dir=up" class="btn btn-sm btn-outline-secondary py-0 px-1"><i class="fas fa-arrow-up"></i></a>
|
||||
<a href="handle_orden_productos.php?id=<?php echo $producto['id']; ?>&dir=down" class="btn btn-sm btn-outline-secondary py-0 px-1"><i class="fas fa-arrow-down"></i></a>
|
||||
</td>
|
||||
<?php foreach ($ciudades as $ciudad): ?>
|
||||
<?php
|
||||
$stock = isset($stock_data[$producto['id']][$ciudad['id']]) ? $stock_data[$producto['id']][$ciudad['id']] : 0;
|
||||
$cell_class = '';
|
||||
if ($stock >= 10) {
|
||||
$cell_class = 'bg-success-soft';
|
||||
} elseif ($stock > 0 && $stock < 10) {
|
||||
$cell_class = 'bg-danger-soft';
|
||||
}
|
||||
?>
|
||||
<td class="text-center <?php echo $cell_class; ?>">
|
||||
<?php echo $stock; ?>
|
||||
</td>
|
||||
<?php endforeach; ?>
|
||||
</tr>
|
||||
<?php endforeach; ?>
|
||||
<?php endif; ?>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<?php
|
||||
require_once 'includes/footer.php';
|
||||
?>
|
||||
1
test_page.php
Normal file
@ -0,0 +1 @@
|
||||
<?php echo 'Esta es una página de prueba.'; ?>
|
||||