Autosave: 20260203-070225

This commit is contained in:
Flatlogic Bot 2026-02-03 07:02:26 +00:00
parent a059de67e8
commit 9a3241c3ec
9 changed files with 613 additions and 513 deletions

65
agregar_producto.php Normal file
View File

@ -0,0 +1,65 @@
<?php
$pageTitle = "Agregar Nuevo Producto";
require_once 'layout_header.php';
require_once 'db/config.php';
$message = '';
$error = '';
if ($_SERVER["REQUEST_METHOD"] == "POST") {
$nombre_producto = filter_input(INPUT_POST, 'nombre_producto', FILTER_SANITIZE_STRING);
if ($nombre_producto) {
try {
$pdo = new PDO("mysql:host=" . DB_HOST . ";dbname=" . DB_NAME . ";charset=utf8", DB_USER, DB_PASS);
$pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
// Insertar el nuevo producto
$stmt = $pdo->prepare("INSERT INTO products (nombre) VALUES (:nombre)");
$stmt->execute(['nombre' => $nombre_producto]);
$message = "¡Producto '" . htmlspecialchars($nombre_producto) . "' agregado correctamente!";
} catch (PDOException $e) {
$error = "Error al agregar el producto: " . $e->getMessage();
}
} else {
$error = "Por favor, ingrese el nombre del producto.";
}
}
?>
<div class="container mt-4">
<div class="row">
<div class="col-lg-6 mx-auto">
<?php if ($message): ?>
<div class="alert alert-success" role="alert">
<?php echo $message; ?>
</div>
<?php endif; ?>
<?php if ($error): ?>
<div class="alert alert-danger" role="alert">
<?php echo htmlspecialchars($error); ?>
</div>
<?php endif; ?>
<div class="card">
<div class="card-header">
<i class="fa fa-plus"></i> Agregar Nuevo Producto al Catálogo
</div>
<div class="card-body">
<form action="agregar_producto.php" method="post">
<div class="mb-3">
<label for="nombre_producto" class="form-label">Nombre del Producto</label>
<input type="text" class="form-control" id="nombre_producto" name="nombre_producto" required>
</div>
<button type="submit" class="btn btn-primary w-100"> <i class="fa fa-plus-circle"></i> Guardar Producto</button>
</form>
</div>
</div>
</div>
</div>
</div>
<?php require_once 'layout_footer.php'; ?>

View File

@ -1,212 +1,98 @@
<?php
session_start();
if (!isset($_SESSION['user_id'])) {
header("Location: login.php");
header('Location: login.php');
exit;
}
require_once 'db/config.php';
$pageTitle = "Cobertura";
require_once 'layout_header.php';
require_once 'db/config.php';
// Fetch all items from the new 'cobertura' table
$db = db();
$stmt = $db->query("SELECT id, titulo, texto FROM cobertura ORDER BY id ASC");
$coberturas = $stmt->fetchAll(PDO::FETCH_ASSOC);
try {
$pdo = db();
$stmt = $pdo->query("SELECT * FROM cobertura ORDER BY id DESC");
$coberturas = $stmt->fetchAll(PDO::FETCH_ASSOC);
} catch (PDOException $e) {
echo "<div class='alert alert-danger'>Error al conectar con la base de datos: " . $e->getMessage() . "</div>";
die();
}
$cobertura_banner = 'assets/uploads/cobertura_banner.jpg';
?>
<style>
@keyframes saved {
0% { background-color: #ffffff; }
30% { background-color: #d4edda; }
100% { background-color: #ffffff; }
}
.saved-animation {
animation: saved 1.5s ease-in-out;
}
[contenteditable="true"]:focus {
outline: 2px solid #007bff;
background-color: #f8f9fa;
}
.action-controls .btn {
padding: 0.2rem 0.5rem;
font-size: 0.8rem;
}
</style>
<div class="container-fluid mt-4">
<h1>Gestión de Cobertura</h1>
<div class="container-fluid">
<h1 class="mt-4">Textos de Cobertura</h1>
<?php if (isset($_SESSION['message'])): ?>
<div class="alert alert-success">
<?php echo $_SESSION['message']; unset($_SESSION['message']); ?>
<div class="card">
<div class="card-header">
Banner de la Página de Cobertura
</div>
<?php endif; ?>
<?php if (isset($_SESSION['error'])): ?>
<div class="alert alert-danger">
<?php echo $_SESSION['error']; unset($_SESSION['error']); ?>
</div>
<?php endif; ?>
<div class="card mt-4">
<div class="card-body">
<?php
$banner_path = 'assets/uploads/cobertura_banner.jpg';
if (file_exists($banner_path)) {
echo '<img src="' . $banner_path . '?t=' . time() . '" alt="Banner de Cobertura" class="img-fluid rounded">';
} else {
echo '<p>No hay imagen de cabecera todavía.</p>';
}
?>
<?php if (file_exists($cobertura_banner)): ?>
<p><strong>Banner Actual:</strong></p>
<img src="<?php echo $cobertura_banner; ?>?v=<?php echo time(); ?>" alt="Banner Cobertura" class="img-fluid mb-3" style="max-width: 400px;">
<?php else: ?>
<p class="text-muted">No hay un banner de cobertura actualmente.</p>
<?php endif; ?>
<form action="save_cobertura_banner.php" method="post" enctype="multipart/form-data" class="mt-3">
<div class="form-group">
<label for="cobertura_banner_input">Cambiar/Subir Banner (se recomienda formato JPG, 1200x400px)</label>
<input type="file" name="cobertura_banner" id="cobertura_banner_input" class="form-control" accept=".jpg,.jpeg">
</div>
<button type="submit" class="btn btn-primary mt-2">Guardar Banner</button>
</form>
</div>
</div>
<div class="card mt-4">
<div class="card-header">
Coberturas Guardadas
<div class="card-header d-flex justify-content-between align-items-center">
Zonas de Cobertura
<a href="add_cobertura.php" class="btn btn-success">Agregar Nueva Zona</a>
</div>
<div class="card-body">
<div class="table-responsive">
<table class="table table-bordered">
<thead>
<table class="table table-bordered table-striped">
<thead class="thead-dark">
<tr>
<th>Productos</th>
<th>Cobertura</th>
<th><!-- Columna para botón de guardar en nuevas filas --></th>
<th style="width: 150px;">Imagen</th>
<th>Título</th>
<th>Descripción</th>
<th style="width: 100px;">Acciones</th>
</tr>
</thead>
<tbody>
<?php foreach ($coberturas as $item): ?>
<tr data-id="<?php echo $item['id']; ?>">
<td contenteditable="true" class="editable-cell" data-field="titulo" style="font-weight: bold;"><?php echo htmlspecialchars($item['titulo']); ?></td>
<td contenteditable="true" class="editable-cell" data-field="texto" style="font-weight: bold;"><?php echo htmlspecialchars($item['texto']); ?></td>
<td><!-- Celda vacía para alinear con la cabecera --></td>
<?php if (empty($coberturas)): ?>
<tr>
<td colspan="4" class="text-center">No hay zonas de cobertura definidas.</td>
</tr>
<?php endforeach; ?>
<?php else: ?>
<?php foreach ($coberturas as $row): ?>
<tr>
<td>
<?php
$image_path = 'assets/uploads/cobertura_images/' . $row['imagen'];
if (file_exists($image_path) && !empty($row['imagen'])):
?>
<img src="<?php echo $image_path; ?>?v=<?php echo time();?>" alt="Imagen de cobertura" class="img-fluid">
<?php else: ?>
<span class="text-muted">Sin imagen</span>
<?php endif; ?>
</td>
<td><?php echo htmlspecialchars($row['titulo']); ?></td>
<td><?php echo nl2br(htmlspecialchars($row['descripcion'])); ?></td>
<td>
<a href="delete_cobertura.php?id=<?php echo $row['id']; ?>" class="btn btn-danger btn-sm" onclick="return confirm('¿Estás seguro de que quieres eliminar esta zona de cobertura?');">
<i class="fas fa-trash"></i> Eliminar
</a>
</td>
</tr>
<?php endforeach; ?>
<?php endif; ?>
</tbody>
</table>
</div>
</div>
<?php if ($_SESSION['user_role'] !== 'Asesor'): ?>
<div class="card-footer">
<button class="btn btn-primary" id="addRowBtn">Agregar Fila</button>
</div>
<?php endif; ?>
</div>
</div>
<script>
document.addEventListener('DOMContentLoaded', function() {
const tableBody = document.querySelector('.table-bordered tbody');
// --- INLINE EDITING ---
tableBody.addEventListener('blur', function(e) {
if (e.target.classList.contains('editable-cell')) {
const cell = e.target;
const id = cell.closest('tr').dataset.id;
if (!id) return; // No guardar celdas de filas nuevas que aún no se han creado en DB
const field = cell.dataset.field;
const value = cell.innerText;
const formData = new FormData();
formData.append('id', id);
formData.append('field', field);
formData.append('value', value);
fetch('save_cobertura.php', {
method: 'POST',
body: formData
})
.then(response => response.json())
.then(data => {
if (data.success) {
if (data.deleted) {
// Si la fila fue eliminada en el servidor, la quitamos de la vista
cell.closest('tr').remove();
} else {
cell.classList.add('saved-animation');
setTimeout(() => cell.classList.remove('saved-animation'), 1500);
}
} else {
alert('Error al guardar: ' + (data.error || 'Error desconocido'));
}
})
.catch(error => {
console.error('Error:', error);
alert('Ocurrió un error de red.');
});
}
}, true); // Use capturing to get the blur event
// --- ADD NEW ROW ---
document.getElementById('addRowBtn').addEventListener('click', function() {
const newRow = document.createElement('tr');
newRow.innerHTML = `
<td contenteditable="true" class="new-cobertura-titulo" placeholder="Nuevo Producto"></td>
<td contenteditable="true" class="new-cobertura-texto" placeholder="Nueva Cobertura"></td>
<td>
<button class="btn btn-success btn-sm save-new-row">Guardar</button>
</td>
`;
tableBody.appendChild(newRow);
newRow.querySelector('.new-cobertura-titulo').focus();
});
// --- SAVE NEW ROW ---
tableBody.addEventListener('click', function(e) {
if (e.target.classList.contains('save-new-row')) {
const saveButton = e.target;
const newRow = saveButton.closest('tr');
const titulo = newRow.querySelector('.new-cobertura-titulo').innerText.trim();
const texto = newRow.querySelector('.new-cobertura-texto').innerText.trim();
if (!titulo && !texto) {
// Si ambos están vacíos, simplemente quita la fila nueva sin guardar
newRow.remove();
return;
}
if (!titulo) {
alert('Por favor, introduce un título.');
return;
}
saveButton.disabled = true;
saveButton.innerText = 'Guardando...';
const formData = new FormData();
formData.append('titulo', titulo);
formData.append('texto', texto);
fetch('add_cobertura.php', {
method: 'POST',
body: formData
})
.then(response => response.json())
.then(data => {
if (data.success) {
location.reload();
} else {
alert('Error al guardar: ' + (data.error || 'Error desconocido'));
saveButton.disabled = false;
saveButton.innerText = 'Guardar';
}
})
.catch(error => {
console.error('Error:', error);
alert('Ocurrió un error de red.');
saveButton.disabled = false;
saveButton.innerText = 'Guardar';
});
}
});
});
</script>
<?php
require_once 'layout_footer.php';
?>
<?php require_once 'layout_footer.php'; ?>

View File

@ -1,208 +1,52 @@
<?php
session_start();
if (!isset($_SESSION['user_id'])) {
header("Location: login.php");
header('Location: login.php');
exit;
}
require_once 'db/config.php';
$pageTitle = "Cobertura Xpress";
require_once 'layout_header.php';
require_once 'db/config.php';
$pdo = db();
$cobertura_xpress_banner = 'assets/uploads/cobertura_xpress_banner.jpg';
// Fetch all items from the 'cobertura_xpress' table
$db = db();
$stmt = $db->query("SELECT id, titulo, texto FROM cobertura_xpress ORDER BY id ASC");
$coberturas = $stmt->fetchAll(PDO::FETCH_ASSOC);
?>
<style>
@keyframes saved {
0% { background-color: #ffffff; }
30% { background-color: #d4edda; }
100% { background-color: #ffffff; }
}
.saved-animation {
animation: saved 1.5s ease-in-out;
}
[contenteditable="true"]:focus {
outline: 2px solid #007bff;
background-color: #f8f9fa;
}
.action-controls .btn {
padding: 0.2rem 0.5rem;
font-size: 0.8rem;
}
</style>
<div class="container-fluid mt-4">
<h1>Gestión de Cobertura Xpress</h1>
<div class="container-fluid">
<h1 class="mt-4">Textos de Cobertura Xpress</h1>
<?php if (isset($_SESSION['message'])): ?>
<div class="alert alert-success">
<?php echo $_SESSION['message']; unset($_SESSION['message']); ?>
<div class="card">
<div class="card-header">
Banner de la Página de Cobertura Xpress
</div>
<?php endif; ?>
<?php if (isset($_SESSION['error'])): ?>
<div class="alert alert-danger">
<?php echo $_SESSION['error']; unset($_SESSION['error']); ?>
</div>
<?php endif; ?>
<div class="card mt-4">
<div class="card-body">
<?php
$banner_path = 'assets/uploads/cobertura_xpress_banner.jpg';
if (file_exists($banner_path)) {
echo '<img src="' . $banner_path . '?t=' . time() . '" alt="Banner de Cobertura Xpress" class="img-fluid rounded">';
} else {
echo '<p>No hay imagen de cabecera todavía.</p>';
}
?>
<?php if (file_exists($cobertura_xpress_banner)): ?>
<p><strong>Banner Actual:</strong></p>
<img src="<?php echo $cobertura_xpress_banner; ?>?v=<?php echo time(); ?>" alt="Banner Cobertura Xpress" class="img-fluid mb-3" style="max-width: 400px;">
<?php else: ?>
<p class="text-muted">No hay un banner de Cobertura Xpress actualmente.</p>
<?php endif; ?>
<form action="save_cobertura_xpress_banner.php" method="post" enctype="multipart/form-data" class="mt-3">
<div class="form-group">
<label for="cobertura_xpress_banner_input">Cambiar/Subir Banner (se recomienda formato JPG, 1200x400px)</label>
<input type="file" name="cobertura_xpress_banner" id="cobertura_xpress_banner_input" class="form-control" accept=".jpg,.jpeg">
</div>
<button type="submit" class="btn btn-primary mt-2">Guardar Banner</button>
</form>
</div>
</div>
<div class="card mt-4">
<div class="card-header">
Coberturas Xpress Guardadas
<div class="card-header d-flex justify-content-between align-items-center">
Zonas de Cobertura Xpress
<a href="add_cobertura_xpress.php" class="btn btn-success">Agregar Nueva Zona Xpress</a>
</div>
<div class="card-body">
<div class="table-responsive">
<table class="table table-bordered">
<thead>
<tr>
<th>Ciudad</th>
<th>Cobertura</th>
<th><!-- Columna para botón de guardar en nuevas filas --></th>
</tr>
</thead>
<tbody>
<?php foreach ($coberturas as $item): ?>
<tr data-id="<?php echo $item['id']; ?>">
<td contenteditable="true" class="editable-cell" data-field="titulo" style="font-weight: bold;"><?php echo htmlspecialchars($item['titulo']); ?></td>
<td contenteditable="true" class="editable-cell" data-field="texto" style="font-weight: bold;"><?php echo htmlspecialchars($item['texto']); ?></td>
<td><!-- Celda vacía para alinear con la cabecera --></td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
</div>
<p class="text-muted">La configuración para las zonas de cobertura Xpress estará disponible aquí.</p>
<!-- Aquí iría la tabla y lógica para mostrar las zonas de Cobertura Xpress -->
</div>
<?php if ($_SESSION['user_role'] !== 'Asesor'): ?>
<div class="card-footer">
<button class="btn btn-primary" id="addRowBtn">Agregar Fila</button>
</div>
<?php endif; ?>
</div>
</div>
<script>
document.addEventListener('DOMContentLoaded', function() {
const tableBody = document.querySelector('.table-bordered tbody');
// --- INLINE EDITING ---
tableBody.addEventListener('blur', function(e) {
if (e.target.classList.contains('editable-cell')) {
const cell = e.target;
const id = cell.closest('tr').dataset.id;
if (!id) return; // No guardar celdas de filas nuevas que aún no se han creado en DB
const field = cell.dataset.field;
const value = cell.innerText;
const formData = new FormData();
formData.append('id', id);
formData.append('field', field);
formData.append('value', value);
fetch('save_cobertura_xpress.php', {
method: 'POST',
body: formData
})
.then(response => response.json())
.then(data => {
if (data.success) {
if (data.deleted) {
// Si la fila fue eliminada en el servidor, la quitamos de la vista
cell.closest('tr').remove();
} else {
cell.classList.add('saved-animation');
setTimeout(() => cell.classList.remove('saved-animation'), 1500);
}
} else {
alert('Error al guardar: ' + (data.error || 'Error desconocido'));
}
})
.catch(error => {
console.error('Error:', error);
alert('Ocurrió un error de red.');
});
}
}, true); // Use capturing to get the blur event
// --- ADD NEW ROW ---
document.getElementById('addRowBtn').addEventListener('click', function() {
const newRow = document.createElement('tr');
newRow.innerHTML = `
<td contenteditable="true" class="new-cobertura-titulo" placeholder="Nueva Ciudad"></td>
<td contenteditable="true" class="new-cobertura-texto" placeholder="Nueva Cobertura"></td>
<td>
<button class="btn btn-success btn-sm save-new-row">Guardar</button>
</td>
`;
tableBody.appendChild(newRow);
newRow.querySelector('.new-cobertura-titulo').focus();
});
// --- SAVE NEW ROW ---
tableBody.addEventListener('click', function(e) {
if (e.target.classList.contains('save-new-row')) {
const saveButton = e.target;
const newRow = saveButton.closest('tr');
const titulo = newRow.querySelector('.new-cobertura-titulo').innerText.trim();
const texto = newRow.querySelector('.new-cobertura-texto').innerText.trim();
if (!titulo && !texto) {
// Si ambos están vacíos, simplemente quita la fila nueva sin guardar
newRow.remove();
return;
}
if (!titulo) {
alert('Por favor, introduce un título.');
return;
}
saveButton.disabled = true;
saveButton.innerText = 'Guardando...';
const formData = new FormData();
formData.append('titulo', titulo);
formData.append('texto', texto);
fetch('add_cobertura_xpress.php', {
method: 'POST',
body: formData
})
.then(response => response.json())
.then(data => {
if (data.success) {
location.reload();
} else {
alert('Error al guardar: ' + (data.error || 'Error desconocido'));
saveButton.disabled = false;
saveButton.innerText = 'Guardar';
}
})
.catch(error => {
console.error('Error:', error);
alert('Ocurrió un error de red.');
saveButton.disabled = false;
saveButton.innerText = 'Guardar';
});
}
});
});
</script>
<?php
require_once 'layout_footer.php';
?>
<?php require_once 'layout_footer.php'; ?>

View File

@ -0,0 +1,10 @@
CREATE TABLE IF NOT EXISTS stock_sedes (
id INT AUTO_INCREMENT PRIMARY KEY,
product_id INT NOT NULL,
sede_id INT NOT NULL,
quantity INT NOT NULL DEFAULT 0,
last_updated TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
FOREIGN KEY (product_id) REFERENCES products(id) ON DELETE CASCADE,
FOREIGN KEY (sede_id) REFERENCES sedes(id) ON DELETE CASCADE,
UNIQUE KEY `product_sede` (`product_id`, `sede_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

View File

@ -74,43 +74,49 @@ $navItems = [
'text' => 'Agregar Pedido',
'roles' => ['Administrador', 'admin', 'Asesor', 'Control Logistico']
],
'productos' => [
'url' => 'productos.php',
'icon' => 'fa-box',
'text' => 'Gestionar Productos',
'roles' => ['Administrador', 'admin']
],
'panel_inventario' => [
'icon' => 'fa-warehouse',
'text' => 'Panel de Inventario',
'roles' => ['Administrador', 'admin', 'Control Logistico'],
'submenu' => [
'dashboard' => [
'url' => 'panel_inventario.php?seccion=dashboard',
'icon' => 'fa-tachometer-alt',
'text' => 'Dashboard',
'roles' => ['Administrador', 'admin', 'Control Logistico']
],
'entrada' => [
'url' => 'panel_inventario.php?seccion=entrada',
'icon' => 'fa-arrow-circle-down',
'text' => 'Registro de Entrada',
'roles' => ['Administrador', 'admin', 'Control Logistico']
],
'salida' => [
'url' => 'panel_inventario.php?seccion=salida',
'icon' => 'fa-arrow-circle-up',
'text' => 'Registro de Salida',
'roles' => ['Administrador', 'admin', 'Control Logistico']
],
'registro_producto' => [
'url' => 'panel_inventario.php?seccion=registro_producto',
'icon' => 'fa-plus-circle',
'text' => 'Registro de Producto',
'roles' => ['Administrador', 'admin', 'Control Logistico']
'inventario_group' => [
'icon' => 'fa-warehouse',
'text' => 'Inventario',
'roles' => ['Administrador', 'admin', 'Control Logistico'],
'submenu' => [
'panel_inventario' => [
'url' => 'panel_inventario.php',
'icon' => 'fa-warehouse',
'text' => 'Inventario General',
'roles' => ['Administrador', 'admin', 'Control Logistico']
],
'registro_entrada' => [
'url' => 'registro_entrada.php',
'icon' => 'fa-arrow-circle-down',
'text' => 'Registro de Entrada',
'roles' => ['Administrador', 'admin', 'Control Logistico']
],
'registro_salida' => [
'url' => 'registro_salida.php',
'icon' => 'fa-arrow-circle-up',
'text' => 'Registro de Salida',
'roles' => ['Administrador', 'admin', 'Control Logistico']
],
'sede' => [
'url' => 'sedes.php',
'icon' => 'fa-building',
'text' => 'Sede',
'roles' => ['Administrador', 'admin', 'Control Logistico']
],
'resumen_stock' => [
'url' => '#',
'icon' => 'fa-chart-bar',
'text' => 'Resumen de stock',
'roles' => ['Administrador', 'admin', 'Control Logistico']
],
'agregar_producto' => [
'url' => 'agregar_producto.php',
'icon' => 'fa-plus',
'text' => 'Agregar Producto',
'roles' => ['Administrador', 'admin', 'Control Logistico']
]
]
]
],
],
'finanzas_group' => [
'icon' => 'fa-dollar-sign',
'text' => 'Finanzas',
@ -142,12 +148,6 @@ $navItems = [
],
]
],
'info_producto' => [
'url' => 'info_producto.php',
'icon' => 'fa-info-circle',
'text' => 'Info Producto',
'roles' => ['Administrador', 'admin', 'Control Logistico']
],
'pedidos_duplicados' => [
'url' => 'pedidos_duplicados.php',
'icon' => 'fa-copy',
@ -165,12 +165,6 @@ $navItems = [
'icon' => 'fa-users',
'text' => 'Gestionar Usuarios',
'roles' => ['Administrador', 'admin']
],
'configuracion' => [
'url' => 'configuracion.php',
'icon' => 'fa-cog',
'text' => 'Configuración',
'roles' => ['Administrador', 'admin']
]
];

View File

@ -6,116 +6,62 @@ if (!isset($_SESSION['user_id'])) {
}
require_once 'layout_header.php';
require_once 'db/config.php';
$pdo = db();
try {
$pdo = db();
$stmt = $pdo->query("SELECT * FROM products ORDER BY order_position ASC");
$products = $stmt->fetchAll(PDO::FETCH_ASSOC);
} catch (PDOException $e) {
echo "<div class='alert alert-danger'>Error al conectar con la base de datos: " . $e->getMessage() . "</div>";
// Consider logging the error and showing a more user-friendly message
// For now, we stop execution if the database connection fails.
die();
}
?>
<div class="container-fluid mt-4">
<h1>Panel de Inventario</h1>
<div class="d-flex justify-content-between align-items-center mb-3">
<h1 class="m-0">Inventario General</h1>
<a href="edit_product.php" class="btn btn-primary">Añadir Producto</a>
</div>
<ul class="nav nav-tabs" id="inventoryTabs" role="tablist">
<li class="nav-item" role="presentation">
<a class="nav-link active" id="general-tab" data-bs-toggle="tab" href="#general" role="tab" aria-controls="general" aria-selected="true">Inventario General</a>
</li>
<li class="nav-item" role="presentation">
<a class="nav-link" id="cobertura-tab" data-bs-toggle="tab" href="#cobertura" role="tab" aria-controls="cobertura" aria-selected="false">Cobertura</a>
</li>
<li class="nav-item" role="presentation">
<a class="nav-link" id="cobertura-xpress-tab" data-bs-toggle="tab" href="#cobertura-xpress" role="tab" aria-controls="cobertura-xpress" aria-selected="false">Cobertura Xpress</a>
</li>
</ul>
<div class="tab-content" id="inventoryTabsContent">
<div class="tab-pane fade show active" id="inventario" role="tabpanel" aria-labelledby="inventario-tab">
<?php
// Obtener productos
$stmt = $pdo->query("SELECT * FROM products ORDER BY order_position ASC");
$products = $stmt->fetchAll(PDO::FETCH_ASSOC);
?>
<div class="d-flex justify-content-between align-items-center mb-3">
<h2 class="m-0">Inventario General</h2>
<a href="edit_product.php" class="btn btn-primary">Añadir Producto</a>
</div>
<table class="table table-striped">
<thead>
<tr>
<th>ID</th>
<th>Nombre</th>
<th>SKU</th>
<th>Precio</th>
<th>Acciones</th>
</tr>
</thead>
<tbody>
<?php foreach ($products as $product): ?>
<div class="card">
<div class="card-body">
<div class="table-responsive">
<table class="table table-striped table-hover">
<thead class="thead-dark">
<tr>
<td><?php echo htmlspecialchars($product['id']); ?></td>
<td><?php echo htmlspecialchars($product['nombre']); ?></td>
<td><?php echo htmlspecialchars($product['sku']); ?></td>
<td>S/ <?php echo htmlspecialchars(number_format($product['precio'], 2)); ?></td>
<td>
<a href="edit_product.php?id=<?php echo $product['id']; ?>" class="btn btn-sm btn-warning">Editar</a>
<a href="delete_product.php?id=<?php echo $product['id']; ?>" class="btn btn-sm btn-danger" onclick="return confirm('¿Estás seguro de que quieres eliminar este producto?');">Eliminar</a>
</td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
</div>
<div id="cobertura" class="tab-pane fade">
<?php
// Restaurando la pestaña Cobertura
$cobertura_banner = 'assets/uploads/cobertura_banner.jpg';
if (file_exists($cobertura_banner)) {
echo "<img src='{$cobertura_banner}?v=" . time() . "' alt='Banner Cobertura' class='img-fluid mb-3'>";
}
?>
<form action="save_cobertura_banner.php" method="post" enctype="multipart/form-data" class="mb-3">
<div class="form-group">
<label for="cobertura_banner_input">Cambiar Banner de Cobertura (JPG)</label>
<input type="file" name="cobertura_banner" id="cobertura_banner_input" class="form-control" accept=".jpg">
</div>
<button type="submit" class="btn btn-primary">Subir Banner</button>
</form>
<a href="add_cobertura.php" class="btn btn-success mb-3">Agregar Nueva Cobertura</a>
<table class="table table-bordered">
<thead>
<tr>
<th>Imagen</th>
<th>Título</th>
<th>Descripción</th>
<th>Acciones</th>
<th>ID</th>
<th>Nombre</th>
<th>SKU</th>
<th>Precio</th>
<th class="text-center">Acciones</th>
</tr>
</thead>
<tbody>
<?php
$stmt = $pdo->query("SELECT * FROM cobertura ORDER BY id DESC");
while ($row = $stmt->fetch(PDO::FETCH_ASSOC)) {
echo "<tr>";
$image_path = 'assets/uploads/cobertura_images/' . $row['imagen'];
if (file_exists($image_path)) {
echo "<td><img src='{$image_path}?v=".time()."' alt='Imagen de cobertura' style='width: 100px;'></td>";
} else {
echo "<td><span class='text-danger'>Imagen no encontrada</span></td>";
}
echo "<td>" . htmlspecialchars($row['titulo']) . "</td>";
echo "<td>" . htmlspecialchars($row['descripcion']) . "</td>";
echo "<td>";
echo "<a href='delete_cobertura.php?id=" . $row['id'] . "' class='btn btn-danger btn-sm' onclick='return confirm("¿Estás seguro de que quieres eliminar esta cobertura?");'>Eliminar</a>";
echo "</td>";
echo "</tr>";
}
?>
<?php if (empty($products)): ?>
<tr>
<td colspan="5" class="text-center">No hay productos en el inventario. <a href="edit_product.php">Agrega el primero</a>.</td>
</tr>
<?php else: ?>
<?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['sku']); ?></td>
<td>S/ <?php echo htmlspecialchars(number_format($product['precio'], 2)); ?></td>
<td class="text-center">
<a href="edit_product.php?id=<?php echo $product['id']; ?>" class="btn btn-sm btn-warning">Editar</a>
<a href="delete_product.php?id=<?php echo $product['id']; ?>" class="btn btn-sm btn-danger" onclick="return confirm('¿Estás seguro de que quieres eliminar este producto?');">Eliminar</a>
</td>
</tr>
<?php endforeach; ?>
<?php endif; ?>
</tbody>
</table>
</div>
<div class="tab-pane fade" id="cobertura-xpress" role="tabpanel" aria-labelledby="cobertura-xpress-tab">
<p class="mt-3">Aquí irá la configuración de cobertura xpress.</p>
</div>
</div>
</div>
<?php
require_once 'layout_footer.php';
?>
<?php require_once 'layout_footer.php'; ?>

121
registro_entrada.php Normal file
View File

@ -0,0 +1,121 @@
<?php
$pageTitle = "Agregar Producto al Inventario";
require_once 'layout_header.php';
require_once 'db/config.php';
$message = '';
$error = '';
// Lógica para manejar el envío del formulario
if ($_SERVER["REQUEST_METHOD"] == "POST") {
$product_id = filter_input(INPUT_POST, 'product_id', FILTER_VALIDATE_INT);
$sede_id = filter_input(INPUT_POST, 'sede_id', FILTER_VALIDATE_INT);
$quantity = filter_input(INPUT_POST, 'quantity', FILTER_VALIDATE_INT);
if ($product_id && $sede_id && $quantity) {
try {
$pdo = new PDO("mysql:host=" . DB_HOST . ";dbname=" . DB_NAME . ";charset=utf8", DB_USER, DB_PASS);
$pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
// Verificar si ya existe un registro para este producto en esta sede
$stmt = $pdo->prepare("SELECT * FROM stock_sedes WHERE product_id = :product_id AND sede_id = :sede_id");
$stmt->execute(['product_id' => $product_id, 'sede_id' => $sede_id]);
$existing_stock = $stmt->fetch();
if ($existing_stock) {
// Si existe, actualizar la cantidad
$new_quantity = $existing_stock['quantity'] + $quantity;
$update_stmt = $pdo->prepare("UPDATE stock_sedes SET quantity = :quantity WHERE id = :id");
$update_stmt->execute(['quantity' => $new_quantity, 'id' => $existing_stock['id']]);
} else {
// Si no existe, insertar un nuevo registro
$insert_stmt = $pdo->prepare("INSERT INTO stock_sedes (product_id, sede_id, quantity) VALUES (:product_id, :sede_id, :quantity)");
$insert_stmt->execute(['product_id' => $product_id, 'sede_id' => $sede_id, 'quantity' => $quantity]);
}
$message = "¡Inventario actualizado correctamente!";
} catch (PDOException $e) {
$error = "Error al actualizar el inventario: " . $e->getMessage();
}
} else {
$error = "Por favor, complete todos los campos del formulario.";
}
}
// Obtener productos y sedes de la base de datos para los dropdowns
$products = [];
$sedes = [];
try {
$pdo = new PDO("mysql:host=" . DB_HOST . ";dbname=" . DB_NAME . ";charset=utf8", DB_USER, DB_PASS);
$pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
$products_stmt = $pdo->query("SELECT id, nombre FROM products ORDER BY nombre ASC");
$products = $products_stmt->fetchAll(PDO::FETCH_ASSOC);
$sedes_stmt = $pdo->query("SELECT id, nombre FROM sedes ORDER BY nombre ASC");
$sedes = $sedes_stmt->fetchAll(PDO::FETCH_ASSOC);
} catch (PDOException $e) {
$error = "Error al cargar datos: " . $e->getMessage();
}
?>
<div class="container mt-4">
<div class="row">
<div class="col-lg-6 mx-auto">
<?php if ($message): ?>
<div class="alert alert-success" role="alert">
<?php echo htmlspecialchars($message); ?>
</div>
<?php endif; ?>
<?php if ($error): ?>
<div class="alert alert-danger" role="alert">
<?php echo htmlspecialchars($error); ?>
</div>
<?php endif; ?>
<div class="card">
<div class="card-header">
<i class="fa fa-plus"></i> Registro de Entrada de Producto
</div>
<div class="card-body">
<form action="registro_entrada.php" method="post">
<div class="mb-3">
<label for="producto" class="form-label">Producto</label>
<select class="form-select" id="producto" name="product_id" required>
<option value="">Seleccione un producto</option>
<?php foreach ($products as $product): ?>
<option value="<?php echo htmlspecialchars($product['id']); ?>">
<?php echo htmlspecialchars($product['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="quantity" min="1" required>
</div>
<div class="mb-3">
<label for="sede" class="form-label">Sede de Destino</label>
<select class="form-select" id="sede" name="sede_id" required>
<option value="">Seleccione una sede</option>
<?php foreach ($sedes as $sede): ?>
<option value="<?php echo htmlspecialchars($sede['id']); ?>">
<?php echo htmlspecialchars($sede['nombre']); ?>
</option>
<?php endforeach; ?>
</select>
</div>
<button type="submit" class="btn btn-primary w-100"> <i class="fa fa-plus-circle"></i> Agregar al Inventario</button>
</form>
</div>
</div>
</div>
</div>
</div>
<?php require_once 'layout_footer.php'; ?>

123
registro_salida.php Normal file
View File

@ -0,0 +1,123 @@
<?php
$pageTitle = "Registro de Salida de Producto";
require_once 'layout_header.php';
require_once 'db/config.php';
$message = '';
$error = '';
// Lógica para manejar el envío del formulario
if ($_SERVER["REQUEST_METHOD"] == "POST") {
$product_id = filter_input(INPUT_POST, 'product_id', FILTER_VALIDATE_INT);
$sede_id = filter_input(INPUT_POST, 'sede_id', FILTER_VALIDATE_INT);
$quantity = filter_input(INPUT_POST, 'quantity', FILTER_VALIDATE_INT);
if ($product_id && $sede_id && $quantity) {
try {
$pdo = new PDO("mysql:host=" . DB_HOST . ";dbname=" . DB_NAME . ";charset=utf8", DB_USER, DB_PASS);
$pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
// Verificar si ya existe un registro para este producto en esta sede
$stmt = $pdo->prepare("SELECT * FROM stock_sedes WHERE product_id = :product_id AND sede_id = :sede_id");
$stmt->execute(['product_id' => $product_id, 'sede_id' => $sede_id]);
$existing_stock = $stmt->fetch();
if ($existing_stock) {
// Si existe, actualizar la cantidad
$new_quantity = $existing_stock['quantity'] - $quantity;
if ($new_quantity < 0) {
$error = "No hay suficiente stock para registrar la salida.";
} else {
$update_stmt = $pdo->prepare("UPDATE stock_sedes SET quantity = :quantity WHERE id = :id");
$update_stmt->execute(['quantity' => $new_quantity, 'id' => $existing_stock['id']]);
$message = "¡Inventario actualizado correctamente!";
}
} else {
// Si no existe, no se puede dar salida
$error = "No hay stock registrado para este producto en la sede seleccionada.";
}
} catch (PDOException $e) {
$error = "Error al actualizar el inventario: " . $e->getMessage();
}
} else {
$error = "Por favor, complete todos los campos del formulario.";
}
}
// Obtener productos y sedes de la base de datos para los dropdowns
$products = [];
$sedes = [];
try {
$pdo = new PDO("mysql:host=" . DB_HOST . ";dbname=" . DB_NAME . ";charset=utf8", DB_USER, DB_PASS);
$pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
$products_stmt = $pdo->query("SELECT id, nombre FROM products ORDER BY nombre ASC");
$products = $products_stmt->fetchAll(PDO::FETCH_ASSOC);
$sedes_stmt = $pdo->query("SELECT id, nombre FROM sedes ORDER BY nombre ASC");
$sedes = $sedes_stmt->fetchAll(PDO::FETCH_ASSOC);
} catch (PDOException $e) {
$error = "Error al cargar datos: " . $e->getMessage();
}
?>
<div class="container mt-4">
<div class="row">
<div class="col-lg-6 mx-auto">
<?php if ($message): ?>
<div class="alert alert-success" role="alert">
<?php echo htmlspecialchars($message); ?>
</div>
<?php endif; ?>
<?php if ($error): ?>
<div class="alert alert-danger" role="alert">
<?php echo htmlspecialchars($error); ?>
</div>
<?php endif; ?>
<div class="card">
<div class="card-header">
<i class="fa fa-minus"></i> Registro de Salida de Producto
</div>
<div class="card-body">
<form action="registro_salida.php" method="post">
<div class="mb-3">
<label for="producto" class="form-label">Producto</label>
<select class="form-select" id="producto" name="product_id" required>
<option value="">Seleccione un producto</option>
<?php foreach ($products as $product): ?>
<option value="<?php echo htmlspecialchars($product['id']); ?>">
<?php echo htmlspecialchars($product['nombre']); ?>
</option>
<?php endforeach; ?>
</select>
</div>
<div class="mb-3">
<label for="cantidad" class="form-label">Cantidad a Retirar</label>
<input type="number" class="form-control" id="cantidad" name="quantity" min="1" required>
</div>
<div class="mb-3">
<label for="sede" class="form-label">Sede de Origen</label>
<select class="form-select" id="sede" name="sede_id" required>
<option value="">Seleccione una sede</option>
<?php foreach ($sedes as $sede): ?>
<option value="<?php echo htmlspecialchars($sede['id']); ?>">
<?php echo htmlspecialchars($sede['nombre']); ?>
</option>
<?php endforeach; ?>
</select>
</div>
<button type="submit" class="btn btn-primary w-100"> <i class="fa fa-minus-circle"></i> Registrar Salida</button>
</form>
</div>
</div>
</div>
</div>
</div>
<?php require_once 'layout_footer.php'; ?>

111
sedes.php Normal file
View File

@ -0,0 +1,111 @@
<?php
require_once 'layout_header.php';
require_once 'db/config.php';
$message = '';
$error = '';
// Handle form submission for adding a new sede
if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['nombre_sede'])) {
$nombre_sede = trim($_POST['nombre_sede']);
if (!empty($nombre_sede)) {
try {
$pdo = db();
// Check if sede already exists
$stmt = $pdo->prepare("SELECT COUNT(*) FROM sedes WHERE nombre = ?");
$stmt->execute([$nombre_sede]);
if ($stmt->fetchColumn() > 0) {
$error = "La sede '" . htmlspecialchars($nombre_sede) . "' ya existe.";
} else {
// Insert new sede
$stmt = $pdo->prepare("INSERT INTO sedes (nombre) VALUES (?)");
if ($stmt->execute([$nombre_sede])) {
$message = "Sede '" . htmlspecialchars($nombre_sede) . "' agregada correctamente.";
} else {
$error = "Error al agregar la sede.";
}
}
} catch (PDOException $e) {
// Do not show detailed SQL errors to the user
error_log("Database Error: " . $e->getMessage());
$error = "Error de base de datos al intentar agregar la sede.";
}
} else {
$error = "El nombre de la sede no puede estar vacío.";
}
}
// Fetch all existing sedes
$sedes = [];
try {
$pdo = db();
$stmt = $pdo->query("SELECT id, nombre FROM sedes ORDER BY nombre ASC");
$sedes = $stmt->fetchAll(PDO::FETCH_ASSOC);
} catch (PDOException $e) {
error_log("Database Error: " . $e->getMessage());
$error .= " Error al cargar la lista de sedes.";
}
?>
<div class="container mt-4">
<div class="row">
<div class="col-lg-8 offset-lg-2">
<h2 class="mb-4 text-center">Gestionar Sedes</h2>
<p class="text-center">Aquí puedes agregar nuevas sedes para la gestión de inventario.</p>
<?php if ($message): ?>
<div class="alert alert-success"><?php echo $message; ?></div>
<?php endif; ?>
<?php if ($error): ?>
<div class="alert alert-danger"><?php echo $error; ?></div>
<?php endif; ?>
<!-- Add Sede Form -->
<div class="card mb-4 shadow-sm">
<div class="card-header">
<h5 class="mb-0">Agregar Nueva Sede</h5>
</div>
<div class="card-body">
<form action="sedes.php" method="post">
<div class="form-group">
<label for="nombre_sede">Nombre de la Sede</label>
<input type="text" class="form-control" id="nombre_sede" name="nombre_sede" placeholder="Ej: Almacén Principal" required>
</div>
<button type="submit" class="btn btn-primary mt-3">
<i class="fas fa-plus-circle"></i> Agregar Sede
</button>
</form>
</div>
</div>
<!-- List of Sedes -->
<div class="card shadow-sm">
<div class="card-header">
<h5 class="mb-0">Sedes Existentes</h5>
</div>
<div class="card-body">
<?php if (count($sedes) > 0): ?>
<ul class="list-group">
<?php foreach ($sedes as $sede): ?>
<li class="list-group-item d-flex justify-content-between align-items-center">
<?php echo htmlspecialchars($sede['nombre']); ?>
<!-- Optional: Add delete/edit buttons here in the future -->
</li>
<?php endforeach; ?>
</ul>
<?php else: ?>
<p class="text-muted">No hay sedes registradas. ¡Agrega la primera!</p>
<?php endif; ?>
</div>
</div>
</div>
</div>
</div>
<?php
require_once 'layout_footer.php';
?>