Compare commits
122 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
4b4a7e2b8b | ||
|
|
c234c7d5ad | ||
|
|
a630d16063 | ||
|
|
10a0221760 | ||
|
|
2c651b72fb | ||
|
|
bd9aa664a9 | ||
|
|
891893f221 | ||
|
|
623d4cd358 | ||
|
|
00437625c8 | ||
|
|
2c3b594aa0 | ||
|
|
4671e9246d | ||
|
|
e71ff2d7a4 | ||
|
|
f587c9e57c | ||
|
|
7b765bd9d0 | ||
|
|
6800ed2d90 | ||
|
|
37cd8f837c | ||
|
|
709ff2f0d0 | ||
|
|
491b21a0f8 | ||
|
|
b959c55838 | ||
|
|
7df520a3f9 | ||
|
|
5707981abe | ||
|
|
8dc1fb0062 | ||
|
|
8073e5e1b3 | ||
|
|
603ef2ccb9 | ||
|
|
621d2a48d9 | ||
|
|
cd09784a88 | ||
|
|
1a7ea8d1c8 | ||
|
|
f06e0c415c | ||
|
|
0eaaa1c343 | ||
|
|
370e45f837 | ||
|
|
b4284d6ec3 | ||
|
|
29814f9aa8 | ||
|
|
4bc4e0b695 | ||
|
|
2454573a89 | ||
|
|
8d1b45365f | ||
|
|
26b4617b8a | ||
|
|
a8d2f9cb0a | ||
|
|
bbd26aa303 | ||
|
|
faacccd59f | ||
|
|
ff10c06eaf | ||
|
|
b928365afa | ||
|
|
515395635b | ||
|
|
4048403247 | ||
|
|
5336ec4122 | ||
|
|
ca33865e0a | ||
|
|
40389434b1 | ||
|
|
16e55cb151 | ||
|
|
a897557f9b | ||
|
|
5c9514cbb3 | ||
|
|
667d0238fa | ||
|
|
77cd3da5bc | ||
|
|
4f3867406b | ||
|
|
5a30279f6c | ||
|
|
5b04629711 | ||
|
|
62877072fb | ||
|
|
b7f5f0e7d2 | ||
|
|
1e1e9e674a | ||
|
|
faa1a9f2b3 | ||
|
|
388bd415b3 | ||
|
|
f09833681a | ||
|
|
09acd5dd5b | ||
|
|
fc63421a68 | ||
|
|
c214262958 | ||
|
|
870bef9157 | ||
|
|
f8fadfdc8a | ||
|
|
7eb4c17bef | ||
|
|
5de02535d2 | ||
|
|
0a1f6ad1d2 | ||
|
|
bb70c82e6c | ||
|
|
67e276f3a4 | ||
|
|
a88941468e | ||
|
|
58d6986d95 | ||
|
|
88c5d32ce4 | ||
|
|
75e09a3c47 | ||
|
|
becf639f3b | ||
|
|
fdce191a3f | ||
|
|
b9b815f6ba | ||
|
|
8914f6e2df | ||
|
|
9e7939eebb | ||
|
|
24ba5cd24a | ||
|
|
61ccd5cda6 | ||
|
|
47257c628c | ||
|
|
11c42a7000 | ||
|
|
1bf602b5b0 | ||
|
|
520d1acb1b | ||
|
|
05af420873 | ||
|
|
05c83857b0 | ||
|
|
54f691879e | ||
|
|
304de737e6 | ||
|
|
914c98e457 | ||
|
|
f59550e0b4 | ||
|
|
d37c7e72bd | ||
|
|
7ba3e3fac5 | ||
|
|
60510752fa | ||
|
|
98f0c58027 | ||
|
|
b390ef6318 | ||
|
|
f88375f643 | ||
|
|
697dbbf92b | ||
|
|
184bb26fca | ||
|
|
4cf44f13b0 | ||
|
|
8430b2c637 | ||
|
|
7ccbc77b67 | ||
|
|
149d498ccf | ||
|
|
d0a0fa9577 | ||
|
|
eb777eb9a8 | ||
|
|
306385f2ae | ||
|
|
7917ea5f98 | ||
|
|
2e2d50688f | ||
|
|
5ebd11482d | ||
|
|
762a54dd3e | ||
|
|
e2dbd8b86f | ||
|
|
9a3241c3ec | ||
|
|
a059de67e8 | ||
|
|
db4413f46e | ||
|
|
96d2546dba | ||
|
|
2caed5f3df | ||
|
|
ea1b1360ff | ||
|
|
eeb9b89ef6 | ||
|
|
ef8b2b7b59 | ||
|
|
05535adcfa | ||
|
|
6995544f62 | ||
|
|
bdc6e40a34 |
2
.gitignore
vendored
@ -1,3 +1,5 @@
|
|||||||
node_modules/
|
node_modules/
|
||||||
*/node_modules/
|
*/node_modules/
|
||||||
*/build/
|
*/build/
|
||||||
|
|
||||||
|
assets/uploads/
|
||||||
|
|||||||
19
.htaccess
@ -1,18 +1,3 @@
|
|||||||
DirectoryIndex index.php index.html
|
|
||||||
Options -Indexes
|
|
||||||
Options -MultiViews
|
|
||||||
|
|
||||||
RewriteEngine On
|
DirectoryIndex index.php
|
||||||
|
php_value auto_prepend_file "db/config.php"
|
||||||
# 0) Serve existing files/directories as-is
|
|
||||||
RewriteCond %{REQUEST_FILENAME} -f [OR]
|
|
||||||
RewriteCond %{REQUEST_FILENAME} -d
|
|
||||||
RewriteRule ^ - [L]
|
|
||||||
|
|
||||||
# 1) Internal map: /page or /page/ -> /page.php (if such PHP file exists)
|
|
||||||
RewriteCond %{REQUEST_FILENAME}.php -f
|
|
||||||
RewriteRule ^(.+?)/?$ $1.php [L]
|
|
||||||
|
|
||||||
# 2) Optional: strip trailing slash for non-directories (keeps .php links working)
|
|
||||||
RewriteCond %{REQUEST_FILENAME} !-d
|
|
||||||
RewriteRule ^(.+)/$ $1 [R=301,L]
|
|
||||||
|
|||||||
0
.perm_test_apache
Normal file
0
.perm_test_exec
Normal file
31
add_cobertura.php
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
<?php
|
||||||
|
session_start();
|
||||||
|
header('Content-Type: application/json');
|
||||||
|
|
||||||
|
if (!isset($_SESSION['user_id'])) {
|
||||||
|
echo json_encode(['success' => false, 'error' => 'No autorizado']);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
require_once 'db/config.php';
|
||||||
|
|
||||||
|
$titulo = isset($_POST['titulo']) ? trim($_POST['titulo']) : '';
|
||||||
|
$texto = isset($_POST['texto']) ? trim($_POST['texto']) : '';
|
||||||
|
|
||||||
|
if (empty($titulo)) {
|
||||||
|
echo json_encode(['success' => false, 'error' => 'El título no puede estar vacío.']);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
$db = db();
|
||||||
|
$stmt = $db->prepare("INSERT INTO cobertura (titulo, texto) VALUES (?, ?)");
|
||||||
|
|
||||||
|
if ($stmt->execute([$titulo, $texto])) {
|
||||||
|
echo json_encode(['success' => true, 'id' => $db->lastInsertId()]);
|
||||||
|
} else {
|
||||||
|
echo json_encode(['success' => false, 'error' => 'No se pudo guardar en la base de datos.']);
|
||||||
|
}
|
||||||
|
} catch (PDOException $e) {
|
||||||
|
echo json_encode(['success' => false, 'error' => 'Error de base de datos: ' . $e->getMessage()]);
|
||||||
|
}
|
||||||
32
add_cobertura_xpress.php
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
<?php
|
||||||
|
session_start();
|
||||||
|
header('Content-Type: application/json');
|
||||||
|
|
||||||
|
if (!isset($_SESSION['user_id'])) {
|
||||||
|
echo json_encode(['success' => false, 'error' => 'No autorizado']);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
require_once 'db/config.php';
|
||||||
|
|
||||||
|
$titulo = isset($_POST['titulo']) ? trim($_POST['titulo']) : '';
|
||||||
|
$texto = isset($_POST['texto']) ? trim($_POST['texto']) : '';
|
||||||
|
|
||||||
|
if (empty($titulo) && empty($texto)) {
|
||||||
|
echo json_encode(['success' => false, 'error' => 'Los campos no pueden estar vacíos.']);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
$db = db();
|
||||||
|
$stmt = $db->prepare("INSERT INTO cobertura_xpress (titulo, texto) VALUES (?, ?)");
|
||||||
|
|
||||||
|
if ($stmt->execute([$titulo, $texto])) {
|
||||||
|
echo json_encode(['success' => true]);
|
||||||
|
} else {
|
||||||
|
echo json_encode(['success' => false, 'error' => 'No se pudo guardar en la base de datos.']);
|
||||||
|
}
|
||||||
|
} catch (PDOException $e) {
|
||||||
|
echo json_encode(['success' => false, 'error' => 'Error de base de datos: ' . $e->getMessage()]);
|
||||||
|
}
|
||||||
|
?>
|
||||||
65
agregar_producto.php
Normal 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'; ?>
|
||||||
414
assets/css/style.css
Normal file
@ -0,0 +1,414 @@
|
|||||||
|
/* General Body Styles */
|
||||||
|
body {
|
||||||
|
background-color: #f4f7f6;
|
||||||
|
font-family: 'Segoe UI', 'Roboto', 'Helvetica Neue', Arial, sans-serif;
|
||||||
|
display: flex;
|
||||||
|
color: #333;
|
||||||
|
transition: margin-left 0.3s;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Sidebar Styles */
|
||||||
|
.sidebar {
|
||||||
|
width: 260px;
|
||||||
|
height: 100vh;
|
||||||
|
position: fixed;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
background: linear-gradient(145deg, #2c3e50, #34495e);
|
||||||
|
color: #ecf0f1;
|
||||||
|
box-shadow: 2px 0 15px rgba(0,0,0,0.1);
|
||||||
|
transition: transform 0.3s ease;
|
||||||
|
z-index: 1000;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sidebar .navbar-brand {
|
||||||
|
padding: 1rem 1.5rem;
|
||||||
|
color: #fff;
|
||||||
|
font-weight: bold;
|
||||||
|
text-align: center;
|
||||||
|
font-size: 1.5rem;
|
||||||
|
flex-shrink: 0; /* Prevent brand from shrinking */
|
||||||
|
}
|
||||||
|
|
||||||
|
.menu-wrapper {
|
||||||
|
flex-grow: 1; /* Takes up all available space */
|
||||||
|
overflow-y: auto; /* Allows scrolling for menu items */
|
||||||
|
}
|
||||||
|
|
||||||
|
.sidebar .nav-link {
|
||||||
|
padding: 12px 25px;
|
||||||
|
text-decoration: none;
|
||||||
|
font-size: 1rem;
|
||||||
|
color: #bdc3c7;
|
||||||
|
display: block;
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sidebar .nav-link i {
|
||||||
|
margin-right: 15px;
|
||||||
|
width: 20px; /* Align icons */
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sidebar .nav-link:hover, .sidebar .nav-link.active {
|
||||||
|
background-color: #4a627a;
|
||||||
|
color: #fff;
|
||||||
|
border-left: 4px solid #3498db;
|
||||||
|
padding-left: 21px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.logout-wrapper {
|
||||||
|
flex-shrink: 0; /* Prevents the logout area from shrinking */
|
||||||
|
border-top: 1px solid #4a627a; /* Separator line */
|
||||||
|
}
|
||||||
|
|
||||||
|
.logout-wrapper .nav-link {
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Content Area Styles */
|
||||||
|
.content {
|
||||||
|
margin-left: 260px;
|
||||||
|
padding: 30px;
|
||||||
|
width: calc(100% - 260px);
|
||||||
|
overflow-y: auto;
|
||||||
|
transition: margin-left 0.3s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Hamburger Menu Button */
|
||||||
|
.sidebar-toggle {
|
||||||
|
display: none;
|
||||||
|
position: fixed;
|
||||||
|
top: 15px;
|
||||||
|
left: 15px;
|
||||||
|
z-index: 1001;
|
||||||
|
background: #34495e;
|
||||||
|
color: #fff;
|
||||||
|
border: none;
|
||||||
|
padding: 10px 15px;
|
||||||
|
border-radius: 5px;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Responsive Styles */
|
||||||
|
@media (max-width: 992px) {
|
||||||
|
.sidebar {
|
||||||
|
transform: translateX(-100%);
|
||||||
|
z-index: 1020; /* Ensure sidebar is on top */
|
||||||
|
}
|
||||||
|
|
||||||
|
.sidebar.active {
|
||||||
|
transform: translateX(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
.content {
|
||||||
|
margin-left: 0;
|
||||||
|
width: 100%;
|
||||||
|
transition: filter 0.3s ease; /* Smooth transition for the filter */
|
||||||
|
}
|
||||||
|
|
||||||
|
.sidebar-toggle {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
body.sidebar-active .content {
|
||||||
|
/* Don't push content, but apply a visual effect */
|
||||||
|
filter: blur(3px) brightness(0.6);
|
||||||
|
/* pointer-events: none; REMOVED to allow interaction even if sidebar is active */
|
||||||
|
}
|
||||||
|
|
||||||
|
/* The body itself gets an overlay to darken it */
|
||||||
|
body.sidebar-active::before {
|
||||||
|
content: '';
|
||||||
|
position: fixed;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
background: rgba(0,0,0,0.4);
|
||||||
|
z-index: 1010; /* Below sidebar, above content */
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
h1, .h1 {
|
||||||
|
color: #333 !important; /* Color oscuro para los títulos */
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/* Card Styles */
|
||||||
|
.card {
|
||||||
|
border: none;
|
||||||
|
border-radius: 10px;
|
||||||
|
box-shadow: 0 4px 12px rgba(0,0,0,0.08);
|
||||||
|
margin-bottom: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.card-header {
|
||||||
|
background-color: #fff;
|
||||||
|
border-bottom: 1px solid #e0e0e0;
|
||||||
|
padding: 1rem 1.5rem;
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
|
||||||
|
.card-title {
|
||||||
|
margin: 0;
|
||||||
|
font-size: 1.2rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Button Styles */
|
||||||
|
.btn-primary {
|
||||||
|
background-color: #3498db;
|
||||||
|
border-color: #3498db;
|
||||||
|
transition: background-color 0.3s;
|
||||||
|
}
|
||||||
|
.btn-primary:hover {
|
||||||
|
background-color: #2980b9;
|
||||||
|
border-color: #2980b9;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-info {
|
||||||
|
background-color: #1abc9c;
|
||||||
|
border-color: #1abc9c;
|
||||||
|
}
|
||||||
|
.btn-info:hover {
|
||||||
|
background-color: #16a085;
|
||||||
|
border-color: #16a085;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Table Styles */
|
||||||
|
.table-responsive {
|
||||||
|
border-radius: 8px;
|
||||||
|
overflow: hidden; /* This is important to make border-radius work on tables */
|
||||||
|
border: 1px solid #dee2e6;
|
||||||
|
}
|
||||||
|
.table {
|
||||||
|
margin-bottom: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.table thead th {
|
||||||
|
background-color: #f8f9fa;
|
||||||
|
border-bottom-width: 1px;
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
|
||||||
|
.table-striped tbody tr:nth-of-type(odd) {
|
||||||
|
background-color: rgba(0,0,0,0.02);
|
||||||
|
}
|
||||||
|
|
||||||
|
.badge {
|
||||||
|
font-size: 0.85rem;
|
||||||
|
padding: 0.5em 0.9em;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Form Styles */
|
||||||
|
.form-label {
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-control, .form-select {
|
||||||
|
border-radius: 5px;
|
||||||
|
}
|
||||||
|
.form-control:focus, .form-select:focus {
|
||||||
|
border-color: #3498db;
|
||||||
|
box-shadow: 0 0 0 0.2rem rgba(52, 152, 219, 0.25);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Payment Verification Status Styles */
|
||||||
|
.badge.estado-pago-pendiente {
|
||||||
|
background-color: #fcf8e3 !important;
|
||||||
|
color: #8a6d3b !important;
|
||||||
|
padding: 8px 12px;
|
||||||
|
border-radius: 4px;
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
|
||||||
|
.badge.estado-pago-verificado {
|
||||||
|
background-color: #2E8B57 !important;
|
||||||
|
color: #fff !important;
|
||||||
|
padding: 8px 12px;
|
||||||
|
border-radius: 4px;
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
|
||||||
|
.td-pago-verificado {
|
||||||
|
background-color: #d4edda !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Style for the select element inside the badge */
|
||||||
|
.form-select-pago {
|
||||||
|
background-color: transparent;
|
||||||
|
border: none;
|
||||||
|
color: inherit; /* Inherit text color from parent badge */
|
||||||
|
font-weight: inherit; /* Inherit font weight */
|
||||||
|
padding: 0;
|
||||||
|
-webkit-appearance: none;
|
||||||
|
-moz-appearance: none;
|
||||||
|
appearance: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-select-pago:focus {
|
||||||
|
box-shadow: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Make the select options have a standard background */
|
||||||
|
.form-select-pago option {
|
||||||
|
background-color: #fff;
|
||||||
|
color: #333;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Excel-like table for specific pages */
|
||||||
|
.excel-container {
|
||||||
|
overflow: auto !important;
|
||||||
|
max-height: 75vh;
|
||||||
|
border: 1px solid #dee2e6;
|
||||||
|
border-radius: 8px;
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
.excel-container .table {
|
||||||
|
min-width: 1200px; /* Ancho más moderado para que se vea más compacto */
|
||||||
|
margin-bottom: 0;
|
||||||
|
border-collapse: separate;
|
||||||
|
border-spacing: 0;
|
||||||
|
table-layout: fixed; /* Ayuda a controlar mejor los anchos de columna */
|
||||||
|
}
|
||||||
|
.excel-container th {
|
||||||
|
position: sticky;
|
||||||
|
top: 0;
|
||||||
|
z-index: 20;
|
||||||
|
background-color: #f8f9fa !important;
|
||||||
|
white-space: nowrap;
|
||||||
|
box-shadow: inset 0 -1px 0 #dee2e6;
|
||||||
|
font-size: 0.85rem;
|
||||||
|
padding: 8px 10px !important;
|
||||||
|
}
|
||||||
|
/* Fijar primera columna (ID) */
|
||||||
|
.excel-container th:first-child,
|
||||||
|
.excel-container td:first-child {
|
||||||
|
position: sticky;
|
||||||
|
left: 0;
|
||||||
|
z-index: 15;
|
||||||
|
background-color: #f8f9fa !important;
|
||||||
|
box-shadow: inset -1px 0 0 #dee2e6;
|
||||||
|
width: 35px; /* Ancho fijo para el ID */
|
||||||
|
}
|
||||||
|
.excel-container th:first-child {
|
||||||
|
z-index: 30; /* Esquina superior izquierda */
|
||||||
|
}
|
||||||
|
.excel-container td {
|
||||||
|
white-space: normal; /* Permitir que el texto baje a la siguiente línea */
|
||||||
|
word-wrap: break-word;
|
||||||
|
padding: 6px 10px !important; /* Padding más reducido */
|
||||||
|
vertical-align: middle;
|
||||||
|
background-color: #fff;
|
||||||
|
font-size: 0.85rem; /* Fuente más pequeña como en Pedidos Rotulados */
|
||||||
|
line-height: 1.2;
|
||||||
|
}
|
||||||
|
.excel-container tr:nth-of-type(odd) td {
|
||||||
|
background-color: #f9f9f9;
|
||||||
|
}
|
||||||
|
.excel-container tr:nth-of-type(odd) td:first-child {
|
||||||
|
background-color: #f1f1f1 !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Barras de desplazamiento siempre visibles y más gruesas */
|
||||||
|
.excel-container::-webkit-scrollbar {
|
||||||
|
width: 14px;
|
||||||
|
height: 14px;
|
||||||
|
}
|
||||||
|
.excel-container::-webkit-scrollbar-track {
|
||||||
|
background: #f1f1f1;
|
||||||
|
border-radius: 10px;
|
||||||
|
}
|
||||||
|
.excel-container::-webkit-scrollbar-thumb {
|
||||||
|
background: #888;
|
||||||
|
border-radius: 10px;
|
||||||
|
border: 3px solid #f1f1f1;
|
||||||
|
}
|
||||||
|
.excel-container::-webkit-scrollbar-thumb:hover {
|
||||||
|
background: #555;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Custom Search Table Styles */
|
||||||
|
.custom-search-table {
|
||||||
|
border-collapse: separate;
|
||||||
|
border-spacing: 0;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.custom-search-table thead th {
|
||||||
|
background-color: #e9ecef; /* A light grey for the header */
|
||||||
|
color: #495057;
|
||||||
|
font-weight: 600;
|
||||||
|
text-align: left;
|
||||||
|
padding: 12px 15px;
|
||||||
|
border-bottom: 2px solid #dee2e6;
|
||||||
|
}
|
||||||
|
|
||||||
|
.custom-search-table tbody td {
|
||||||
|
padding: 12px 15px;
|
||||||
|
border-bottom: 1px solid #ecf0f1; /* Light border for rows */
|
||||||
|
color: #555;
|
||||||
|
}
|
||||||
|
|
||||||
|
.custom-search-table tbody tr:last-child td {
|
||||||
|
border-bottom: none; /* No border for the last row */
|
||||||
|
}
|
||||||
|
|
||||||
|
.custom-search-table tbody tr:hover {
|
||||||
|
background-color: #f8f9fa; /* Subtle hover effect */
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Submenu Styles */
|
||||||
|
.sidebar .submenu {
|
||||||
|
display: none;
|
||||||
|
list-style: none;
|
||||||
|
padding-left: 20px;
|
||||||
|
background-color: #2c3e50;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Show submenu when it has .show class or its parent .has-submenu is active */
|
||||||
|
.sidebar .submenu.show,
|
||||||
|
.sidebar .nav-item.has-submenu.active > .submenu {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Keep the submenu open when the parent has the 'open' class (for JS toggle) */
|
||||||
|
.sidebar .nav-item.open > .submenu {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sidebar .submenu .nav-link {
|
||||||
|
padding-left: 35px;
|
||||||
|
color: #bdc3c7; /* Match main link color */
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Style for the active link within the submenu */
|
||||||
|
.sidebar .submenu .nav-link.active {
|
||||||
|
color: #fff; /* White color for active sub-item */
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 767px) {
|
||||||
|
.content {
|
||||||
|
padding: 15px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.card-header, .card-body {
|
||||||
|
padding: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
h1, .h1 {
|
||||||
|
font-size: 1.75rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.table {
|
||||||
|
font-size: 0.9rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.table td, .table th {
|
||||||
|
padding: 0.5rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
BIN
assets/pasted-20251010-033700-792445f1.png
Normal file
|
After Width: | Height: | Size: 18 KiB |
BIN
assets/pasted-20251010-034342-90eb2f5d.png
Normal file
|
After Width: | Height: | Size: 29 KiB |
BIN
assets/pasted-20251010-035926-06fd2395.png
Normal file
|
After Width: | Height: | Size: 20 KiB |
BIN
assets/pasted-20251010-044333-d85c9092.png
Normal file
|
After Width: | Height: | Size: 223 KiB |
BIN
assets/pasted-20251010-051639-f756b641.png
Normal file
|
After Width: | Height: | Size: 25 KiB |
BIN
assets/pasted-20251010-052923-97e40bb1.png
Normal file
|
After Width: | Height: | Size: 16 KiB |
BIN
assets/pasted-20251011-074024-2f48f8e2.png
Normal file
|
After Width: | Height: | Size: 198 KiB |
BIN
assets/pasted-20251011-074403-a972324b.png
Normal file
|
After Width: | Height: | Size: 240 KiB |
BIN
assets/pasted-20251011-080404-2f9f6306.png
Normal file
|
After Width: | Height: | Size: 40 KiB |
BIN
assets/pasted-20251011-080606-631527e5.png
Normal file
|
After Width: | Height: | Size: 207 KiB |
BIN
assets/pasted-20251011-082322-054551fc.png
Normal file
|
After Width: | Height: | Size: 174 KiB |
BIN
assets/pasted-20251011-083432-2dbef491.png
Normal file
|
After Width: | Height: | Size: 20 KiB |
BIN
assets/pasted-20251011-083603-fdcba762.png
Normal file
|
After Width: | Height: | Size: 197 KiB |
BIN
assets/pasted-20251011-084323-e105e08e.png
Normal file
|
After Width: | Height: | Size: 155 KiB |
BIN
assets/pasted-20251011-084711-7c548b08.png
Normal file
|
After Width: | Height: | Size: 148 KiB |
BIN
assets/pasted-20251011-084937-a4d5791b.png
Normal file
|
After Width: | Height: | Size: 142 KiB |
BIN
assets/pasted-20251011-090922-a55e9ed9.png
Normal file
|
After Width: | Height: | Size: 81 KiB |
BIN
assets/pasted-20251011-091450-3262dcbb.png
Normal file
|
After Width: | Height: | Size: 215 KiB |
BIN
assets/pasted-20251011-230750-83a34b96.png
Normal file
|
After Width: | Height: | Size: 31 KiB |
BIN
assets/pasted-20251011-233144-83d6a087.png
Normal file
|
After Width: | Height: | Size: 35 KiB |
BIN
assets/pasted-20251012-001409-e155c6db.png
Normal file
|
After Width: | Height: | Size: 223 KiB |
BIN
assets/pasted-20251012-001702-276849dd.png
Normal file
|
After Width: | Height: | Size: 252 KiB |
BIN
assets/pasted-20251012-003051-c816f4a8.png
Normal file
|
After Width: | Height: | Size: 256 KiB |
BIN
assets/pasted-20251014-032659-e8b2ec02.png
Normal file
|
After Width: | Height: | Size: 185 KiB |
BIN
assets/pasted-20251014-033433-efeb9d84.png
Normal file
|
After Width: | Height: | Size: 226 KiB |
BIN
assets/pasted-20251014-033858-32592865.png
Normal file
|
After Width: | Height: | Size: 214 KiB |
BIN
assets/pasted-20251014-034251-1819ce99.png
Normal file
|
After Width: | Height: | Size: 217 KiB |
BIN
assets/pasted-20251014-035121-821ba44c.png
Normal file
|
After Width: | Height: | Size: 220 KiB |
BIN
assets/pasted-20251014-040055-e3fb0a1b.png
Normal file
|
After Width: | Height: | Size: 220 KiB |
BIN
assets/pasted-20251014-041518-a6285d67.png
Normal file
|
After Width: | Height: | Size: 21 KiB |
BIN
assets/pasted-20251014-041715-2c79d156.png
Normal file
|
After Width: | Height: | Size: 21 KiB |
BIN
assets/pasted-20251014-041936-cbcd84e4.png
Normal file
|
After Width: | Height: | Size: 19 KiB |
BIN
assets/pasted-20251014-054435-b305c0ef.png
Normal file
|
After Width: | Height: | Size: 219 KiB |
BIN
assets/pasted-20260430-180021-d1e90ce2.png
Normal file
|
After Width: | Height: | Size: 5.8 KiB |
BIN
assets/pasted-20260430-180545-b6671ba6.png
Normal file
|
After Width: | Height: | Size: 42 KiB |
BIN
assets/pasted-20260430-182045-c7e1b687.png
Normal file
|
After Width: | Height: | Size: 7.6 KiB |
BIN
assets/pasted-20260430-184730-18d601a4.png
Normal file
|
After Width: | Height: | Size: 60 KiB |
BIN
assets/vm-shot-2026-04-30T18-06-01-963Z.jpg
Normal file
|
After Width: | Height: | Size: 20 KiB |
225
buscador_general.php
Normal file
@ -0,0 +1,225 @@
|
|||||||
|
<?php
|
||||||
|
require_once 'layout_header.php';
|
||||||
|
require_once 'db/config.php';
|
||||||
|
|
||||||
|
// Function from pedidos.php to style the status
|
||||||
|
function getStatusStyle($status) {
|
||||||
|
$style = 'color: white;'; // Default text color
|
||||||
|
$bgColor = '#0dcaf0'; // Default info blue
|
||||||
|
|
||||||
|
switch (strtoupper(trim($status))) {
|
||||||
|
case 'ROTULADO':
|
||||||
|
$bgColor = '#ffc107'; // yellow
|
||||||
|
$style = 'color: black;';
|
||||||
|
break;
|
||||||
|
case 'EN TRANSITO':
|
||||||
|
$bgColor = '#90EE90'; // light green
|
||||||
|
$style = 'color: black;';
|
||||||
|
break;
|
||||||
|
case 'EN DESTINO':
|
||||||
|
$bgColor = '#800080'; // purple
|
||||||
|
break;
|
||||||
|
case 'COMPLETADO':
|
||||||
|
case 'COMPLETADO ✅':
|
||||||
|
$bgColor = '#198754'; // dark green
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
return "background-color: {$bgColor} !important; {$style}";
|
||||||
|
}
|
||||||
|
|
||||||
|
$search_term = '';
|
||||||
|
$fecha_creacion = '';
|
||||||
|
$asesor_id = '';
|
||||||
|
$pedidos = [];
|
||||||
|
$is_search_performed = false;
|
||||||
|
$error_message = null;
|
||||||
|
$user_role = $_SESSION['user_role'] ?? 'Asesor';
|
||||||
|
$asesores = [];
|
||||||
|
|
||||||
|
try {
|
||||||
|
$pdo = db();
|
||||||
|
|
||||||
|
// Get asesores for the dropdown if user is superadmin
|
||||||
|
if ($user_role === 'superadmin') {
|
||||||
|
$stmt_asesores = $pdo->query("SELECT id, nombre_asesor FROM users WHERE role = 'Asesor' ORDER BY nombre_asesor");
|
||||||
|
$asesores = $stmt_asesores->fetchAll(PDO::FETCH_ASSOC);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isset($_GET['q']) || isset($_GET['fecha_creacion']) || isset($_GET['asesor_id'])) {
|
||||||
|
$is_search_performed = true;
|
||||||
|
$search_term = trim($_GET['q'] ?? '');
|
||||||
|
|
||||||
|
// Only process these filters if the user is a superadmin
|
||||||
|
if ($user_role === 'superadmin') {
|
||||||
|
$fecha_creacion = trim($_GET['fecha_creacion'] ?? '');
|
||||||
|
$asesor_id = trim($_GET['asesor_id'] ?? '');
|
||||||
|
} else {
|
||||||
|
$fecha_creacion = '';
|
||||||
|
$asesor_id = '';
|
||||||
|
}
|
||||||
|
|
||||||
|
$sql_conditions = [];
|
||||||
|
$params = [];
|
||||||
|
|
||||||
|
if ($search_term !== '') {
|
||||||
|
$sql_conditions[] = "(p.nombre_completo LIKE :term OR p.celular LIKE :term OR p.dni_cliente LIKE :term OR p.id = :id_term)";
|
||||||
|
$params['term'] = '%' . $search_term . '%';
|
||||||
|
$params['id_term'] = is_numeric($search_term) ? $search_term : 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($fecha_creacion !== '' && $user_role === 'superadmin') {
|
||||||
|
$sql_conditions[] = "DATE(p.created_at) = :fecha_creacion";
|
||||||
|
$params['fecha_creacion'] = $fecha_creacion;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($asesor_id !== '' && $user_role === 'superadmin') {
|
||||||
|
$sql_conditions[] = "p.asesor_id = :asesor_id";
|
||||||
|
$params['asesor_id'] = $asesor_id;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!empty($sql_conditions)) {
|
||||||
|
$sql = "
|
||||||
|
SELECT p.*, u.nombre_asesor as asesor_nombre
|
||||||
|
FROM pedidos p
|
||||||
|
LEFT JOIN users u ON p.asesor_id = u.id
|
||||||
|
WHERE " . implode(' AND ', $sql_conditions) . "
|
||||||
|
ORDER BY p.created_at DESC
|
||||||
|
";
|
||||||
|
|
||||||
|
$stmt = $pdo->prepare($sql);
|
||||||
|
|
||||||
|
if (!$stmt->execute($params)) {
|
||||||
|
$error_info = $stmt->errorInfo();
|
||||||
|
throw new PDOException("Error en la consulta SQL: " . $error_info[2]);
|
||||||
|
}
|
||||||
|
|
||||||
|
$pedidos = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
||||||
|
} elseif (isset($_GET['q'])) { // Handle case where only empty q is passed
|
||||||
|
$pedidos = [];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (PDOException $e) {
|
||||||
|
$error_message = "Error de base de datos: " . $e->getMessage();
|
||||||
|
} catch (Exception $e) {
|
||||||
|
$error_message = "Ha ocurrido un error inesperado.";
|
||||||
|
}
|
||||||
|
|
||||||
|
?>
|
||||||
|
|
||||||
|
<div class="container-fluid mt-4">
|
||||||
|
<h1>Buscador General</h1>
|
||||||
|
<p>Busca pedidos por nombre, teléfono, DNI o ID del pedido.</p>
|
||||||
|
|
||||||
|
<div class="card">
|
||||||
|
<div class="card-body">
|
||||||
|
<form action="buscador_general.php" method="GET" class="mb-4">
|
||||||
|
<div class="row g-3 align-items-end">
|
||||||
|
<div class="col">
|
||||||
|
<label for="q" class="form-label">Búsqueda</label>
|
||||||
|
<input type="text" class="form-control" id="q" name="q" placeholder="Introduce tu búsqueda..." value="<?php echo htmlspecialchars($search_term); ?>">
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<?php if ($user_role === 'superadmin'): ?>
|
||||||
|
<div class="col">
|
||||||
|
<label for="fecha_creacion" class="form-label">Fecha de Creación</label>
|
||||||
|
<input type="date" class="form-control" id="fecha_creacion" name="fecha_creacion" value="<?php echo htmlspecialchars($fecha_creacion); ?>">
|
||||||
|
</div>
|
||||||
|
<div class="col">
|
||||||
|
<label for="asesor_id" class="form-label">Asesor</label>
|
||||||
|
<select class="form-select" id="asesor_id" name="asesor_id">
|
||||||
|
<option value="">Todos los asesores</option>
|
||||||
|
<?php foreach ($asesores as $asesor): ?>
|
||||||
|
<option value="<?php echo $asesor['id']; ?>" <?php echo ($asesor_id == $asesor['id']) ? 'selected' : ''; ?>>
|
||||||
|
<?php echo htmlspecialchars($asesor['nombre_asesor']); ?>
|
||||||
|
</option>
|
||||||
|
<?php endforeach; ?>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
<?php endif; ?>
|
||||||
|
|
||||||
|
<div class="col-auto">
|
||||||
|
<button class="btn btn-primary" type="submit">Buscar</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
|
||||||
|
<?php if ($error_message): ?>
|
||||||
|
<div class="alert alert-danger">
|
||||||
|
<?php echo htmlspecialchars($error_message); ?>
|
||||||
|
</div>
|
||||||
|
<?php elseif ($is_search_performed): ?>
|
||||||
|
<hr>
|
||||||
|
<h3>Resultados de la Búsqueda</h3>
|
||||||
|
<?php if (count($pedidos) > 0): ?>
|
||||||
|
<div class="table-responsive">
|
||||||
|
<table class="table table-striped table-hover">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>ID</th>
|
||||||
|
<th>Cliente</th>
|
||||||
|
<th>DNI</th>
|
||||||
|
<th>Celular</th>
|
||||||
|
<th>Producto</th>
|
||||||
|
<th>Sede de Envío</th>
|
||||||
|
<?php if ($user_role !== 'Logistica'): ?>
|
||||||
|
<th>Monto Total</th>
|
||||||
|
<th>Monto Debe</th>
|
||||||
|
<?php endif; ?>
|
||||||
|
<th>Nº De Orden</th>
|
||||||
|
<th>Codigo De Orden</th>
|
||||||
|
<th>CLAVE</th>
|
||||||
|
<th>Estado</th>
|
||||||
|
<?php if ($user_role !== 'Asesor'): ?><th>Asesor</th><?php endif; ?>
|
||||||
|
<th>Fecha Creación</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<?php foreach ($pedidos as $pedido): ?>
|
||||||
|
<tr>
|
||||||
|
<td><?php echo htmlspecialchars($pedido['id']); ?></td>
|
||||||
|
<td><?php echo htmlspecialchars($pedido['nombre_completo']); ?></td>
|
||||||
|
<td><?php echo htmlspecialchars($pedido['dni_cliente'] ?? 'N/A'); ?></td>
|
||||||
|
<td><?php echo htmlspecialchars($pedido['celular']); ?></td>
|
||||||
|
<td><?php echo htmlspecialchars($pedido['producto']); ?></td>
|
||||||
|
<td><?php echo htmlspecialchars($pedido['sede_envio'] ?? 'N/A'); ?></td>
|
||||||
|
<?php if ($user_role !== 'Logistica'): ?>
|
||||||
|
<td><?php echo htmlspecialchars($pedido['monto_total']); ?></td>
|
||||||
|
<td><?php echo htmlspecialchars($pedido['monto_debe']); ?></td>
|
||||||
|
<?php endif; ?>
|
||||||
|
<td><?php echo htmlspecialchars($pedido['codigo_rastreo'] ?? 'N/A'); ?></td>
|
||||||
|
<td><?php echo htmlspecialchars($pedido['codigo_tracking'] ?? 'N/A'); ?></td>
|
||||||
|
<td>
|
||||||
|
<?php
|
||||||
|
$show_clave = true;
|
||||||
|
if ($user_role === 'Asesor') {
|
||||||
|
$estado_actual = strtoupper(trim($pedido['estado']));
|
||||||
|
if ($estado_actual !== 'COMPLETADO' && $estado_actual !== 'COMPLETADO ✅') {
|
||||||
|
$show_clave = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if ($show_clave) {
|
||||||
|
echo htmlspecialchars($pedido['clave'] ?? 'N/A');
|
||||||
|
} else {
|
||||||
|
echo '<i class="fas fa-eye-slash text-muted" title="Suba el número de operación y seleccione el banco para ver la clave"></i> <span class="text-muted" style="font-size: 0.8rem;">Oculto</span>';
|
||||||
|
}
|
||||||
|
?>
|
||||||
|
</td>
|
||||||
|
<td><span class="badge" style="<?php echo getStatusStyle($pedido['estado']); ?>"><?php echo htmlspecialchars($pedido['estado']); ?></span></td>
|
||||||
|
<?php if ($user_role !== 'Asesor'): ?><td><?php echo htmlspecialchars($pedido['asesor_nombre'] ?? 'N/A'); ?></td><?php endif; ?>
|
||||||
|
<td><?php echo htmlspecialchars($pedido['created_at']); ?></td>
|
||||||
|
</tr>
|
||||||
|
<?php endforeach; ?>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
<?php else: ?>
|
||||||
|
<div class="alert alert-info">
|
||||||
|
No se encontraron pedidos que coincidan con los criterios de búsqueda.
|
||||||
|
</div>
|
||||||
|
<?php endif; ?>
|
||||||
|
<?php endif; ?>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<?php require_once 'layout_footer.php'; ?>
|
||||||
145
buscador_inventario.php
Normal file
@ -0,0 +1,145 @@
|
|||||||
|
<?php
|
||||||
|
session_start();
|
||||||
|
if (!isset($_SESSION['user_id'])) {
|
||||||
|
header('Location: login.php');
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
require_once 'db/config.php';
|
||||||
|
require_once 'layout_header.php';
|
||||||
|
|
||||||
|
$codigo_unico_buscado = isset($_GET['codigo_unico']) ? trim($_GET['codigo_unico']) : '';
|
||||||
|
$unidad_info = null;
|
||||||
|
$error_message = '';
|
||||||
|
|
||||||
|
if (!empty($codigo_unico_buscado)) {
|
||||||
|
try {
|
||||||
|
$db = db();
|
||||||
|
$stmt = $db->prepare(
|
||||||
|
"\n SELECT
|
||||||
|
ui.codigo_unico,
|
||||||
|
ui.estado,
|
||||||
|
ui.fecha_creacion,
|
||||||
|
ui.fecha_ingreso,
|
||||||
|
ui.fecha_salida,
|
||||||
|
ui.pedido_id,
|
||||||
|
p.nombre AS producto_nombre,
|
||||||
|
p.sku AS producto_sku
|
||||||
|
FROM unidades_inventario ui
|
||||||
|
JOIN products p ON ui.producto_id = p.id
|
||||||
|
WHERE ui.codigo_unico = :codigo_unico
|
||||||
|
");
|
||||||
|
$stmt->bindParam(':codigo_unico', $codigo_unico_buscado, PDO::PARAM_STR);
|
||||||
|
$stmt->execute();
|
||||||
|
$unidad_info = $stmt->fetch(PDO::FETCH_ASSOC);
|
||||||
|
|
||||||
|
if (!$unidad_info) {
|
||||||
|
$error_message = "No se encontró ninguna unidad de inventario con el código \"" . htmlspecialchars($codigo_unico_buscado) . "\".";
|
||||||
|
}
|
||||||
|
} catch (PDOException $e) {
|
||||||
|
$error_message = "Error en la base de datos: " . $e->getMessage();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
?>
|
||||||
|
|
||||||
|
<div class="container mt-4">
|
||||||
|
<h1 class="mb-4">Buscador de Unidades de Inventario</h1>
|
||||||
|
<p>Ingresa o escanea el código de barras de una unidad individual para ver su historial y estado actual.</p>
|
||||||
|
|
||||||
|
<div class="card shadow-sm mb-4">
|
||||||
|
<div class="card-body">
|
||||||
|
<form action="buscador_inventario.php" method="GET" class="mb-3">
|
||||||
|
<div class="input-group">
|
||||||
|
<input type="text" class="form-control form-control-lg" id="codigo_unico" name="codigo_unico" placeholder="Ej: AFS-0035" value="<?php echo htmlspecialchars($codigo_unico_buscado); ?>" required autofocus>
|
||||||
|
<button class="btn btn-primary" type="submit">
|
||||||
|
<i class="fas fa-search"></i> Buscar
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<?php if ($error_message): ?>
|
||||||
|
<div class="alert alert-warning">
|
||||||
|
<?php echo $error_message; ?>
|
||||||
|
</div>
|
||||||
|
<?php endif; ?>
|
||||||
|
|
||||||
|
<?php if ($unidad_info): ?>
|
||||||
|
<div class="card shadow-sm">
|
||||||
|
<div class="card-header">
|
||||||
|
<h5 class="mb-0">Resultados para: <?php echo htmlspecialchars($unidad_info['codigo_unico']); ?></h5>
|
||||||
|
</div>
|
||||||
|
<div class="card-body">
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-md-6">
|
||||||
|
<h4>Información del Producto</h4>
|
||||||
|
<table class="table table-bordered">
|
||||||
|
<tr>
|
||||||
|
<th style="width: 30%;">Nombre</th>
|
||||||
|
<td><?php echo htmlspecialchars($unidad_info['producto_nombre']); ?></td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<th>SKU</th>
|
||||||
|
<td><?php echo htmlspecialchars($unidad_info['producto_sku']); ?></td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
<div class="col-md-6">
|
||||||
|
<h4>Trazabilidad de la Unidad</h4>
|
||||||
|
<table class="table table-bordered">
|
||||||
|
<tr>
|
||||||
|
<th style="width: 30%;">Estado Actual</th>
|
||||||
|
<td>
|
||||||
|
<?php
|
||||||
|
$estado = htmlspecialchars($unidad_info['estado']);
|
||||||
|
$badge_class = 'secondary';
|
||||||
|
if ($estado == 'En Almacén') $badge_class = 'success';
|
||||||
|
if ($estado == 'Vendido') $badge_class = 'danger';
|
||||||
|
if ($estado == 'Generado') $badge_class = 'info';
|
||||||
|
echo "<span class=\"badge bg-$badge_class\">$estado</span>";
|
||||||
|
?>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<th>Pedido ID</th>
|
||||||
|
<td>
|
||||||
|
<?php if ($unidad_info['pedido_id']): ?>
|
||||||
|
<a href="info_producto.php?id=<?php echo $unidad_info['pedido_id']; // Asumiendo que esta es la página de detalle de pedido ?>">
|
||||||
|
<?php echo $unidad_info['pedido_id']; ?>
|
||||||
|
</a>
|
||||||
|
<?php else: ?>
|
||||||
|
N/A
|
||||||
|
<?php endif; ?>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-md-12">
|
||||||
|
<h4>Historial de Fechas</h4>
|
||||||
|
<table class="table table-sm table-bordered">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>Creación (Etiqueta)</th>
|
||||||
|
<th>Ingreso a Almacén</th>
|
||||||
|
<th>Salida (Venta)</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<tr>
|
||||||
|
<td><?php echo $unidad_info['fecha_creacion'] ? date('d/m/Y H:i:s', strtotime($unidad_info['fecha_creacion'])) : 'N/A'; ?></td>
|
||||||
|
<td><?php echo $unidad_info['fecha_ingreso'] ? date('d/m/Y H:i:s', strtotime($unidad_info['fecha_ingreso'])) : 'N/A'; ?></td>
|
||||||
|
<td><?php echo $unidad_info['fecha_salida'] ? date('d/m/Y H:i:s', strtotime($unidad_info['fecha_salida'])) : 'N/A'; ?></td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<?php endif; ?>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<?php require_once 'layout_footer.php'; ?>
|
||||||
476
calculo_costos.php
Normal file
@ -0,0 +1,476 @@
|
|||||||
|
<?php
|
||||||
|
$pageTitle = "Cálculo de Costos";
|
||||||
|
include 'db/config.php';
|
||||||
|
include 'layout_header.php';
|
||||||
|
|
||||||
|
$db = db();
|
||||||
|
|
||||||
|
// Obtener productos para el select del modal
|
||||||
|
$stmt_products = $db->query("SELECT id, nombre FROM products ORDER BY nombre ASC");
|
||||||
|
$productos_select = $stmt_products->fetchAll(PDO::FETCH_ASSOC);
|
||||||
|
|
||||||
|
// Obtener videos y sus costos asociados
|
||||||
|
$stmt = $db->query("SELECT mv.id, mv.orden, mv.foto_producto, p.nombre as nombre_producto,
|
||||||
|
mc.costo_producto, mc.costo_fijo_film, mc.comision_asesora,
|
||||||
|
mc.delivery, mc.costo_publicitario, mc.inversion_total,
|
||||||
|
mc.promo_1, mc.promo_2, mc.promo_3
|
||||||
|
FROM marketing_videos mv
|
||||||
|
LEFT JOIN products p ON mv.producto_id = p.id
|
||||||
|
LEFT JOIN marketing_costos mc ON mv.id = mc.video_id
|
||||||
|
ORDER BY mv.orden ASC, mv.fecha_creacion DESC");
|
||||||
|
$costos = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
||||||
|
?>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.table-excel {
|
||||||
|
font-size: 0.85rem;
|
||||||
|
}
|
||||||
|
.table-excel th {
|
||||||
|
background-color: #f8f9fa;
|
||||||
|
border-bottom: 2px solid #dee2e6;
|
||||||
|
white-space: nowrap;
|
||||||
|
text-transform: uppercase;
|
||||||
|
font-weight: 600;
|
||||||
|
color: #495057;
|
||||||
|
}
|
||||||
|
.table-excel td {
|
||||||
|
vertical-align: middle;
|
||||||
|
border-bottom: 1px solid #eee;
|
||||||
|
}
|
||||||
|
.img-preview {
|
||||||
|
width: 50px;
|
||||||
|
height: 50px;
|
||||||
|
object-fit: cover;
|
||||||
|
border-radius: 4px;
|
||||||
|
}
|
||||||
|
.editable {
|
||||||
|
background-color: #ffffff;
|
||||||
|
transition: all 0.2s;
|
||||||
|
position: relative;
|
||||||
|
min-width: 80px;
|
||||||
|
pointer-events: auto !important;
|
||||||
|
}
|
||||||
|
.editable:hover {
|
||||||
|
background-color: #f0f7ff !important;
|
||||||
|
cursor: pointer;
|
||||||
|
box-shadow: inset 0 0 0 2px #0d6efd;
|
||||||
|
z-index: 1;
|
||||||
|
}
|
||||||
|
.edit-icon {
|
||||||
|
position: absolute;
|
||||||
|
right: 4px;
|
||||||
|
top: 4px;
|
||||||
|
font-size: 0.7rem;
|
||||||
|
color: #0d6efd;
|
||||||
|
background: rgba(255,255,255,0.9);
|
||||||
|
padding: 2px 4px;
|
||||||
|
border-radius: 3px;
|
||||||
|
border: 1px solid #0d6efd;
|
||||||
|
display: none;
|
||||||
|
z-index: 10;
|
||||||
|
}
|
||||||
|
.editable:hover .edit-icon {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
.recaudo-cell {
|
||||||
|
background-color: #fdfdfd;
|
||||||
|
cursor: default;
|
||||||
|
color: #6c757d;
|
||||||
|
}
|
||||||
|
.inline-edit-input {
|
||||||
|
width: 100%;
|
||||||
|
padding: 4px 8px;
|
||||||
|
font-size: 0.85rem;
|
||||||
|
border: 2px solid #0d6efd;
|
||||||
|
border-radius: 4px;
|
||||||
|
text-align: center;
|
||||||
|
box-shadow: 0 2px 5px rgba(0,0,0,0.1);
|
||||||
|
pointer-events: auto !important;
|
||||||
|
}
|
||||||
|
.bg-total {
|
||||||
|
background-color: #f1f3f5;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
.btn-delete-row {
|
||||||
|
padding: 0.2rem 0.4rem;
|
||||||
|
font-size: 0.75rem;
|
||||||
|
opacity: 0.3;
|
||||||
|
transition: opacity 0.2s;
|
||||||
|
pointer-events: auto !important;
|
||||||
|
}
|
||||||
|
tr:hover .btn-delete-row {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
/* Fix for modal selection and backdrop issues */
|
||||||
|
.modal {
|
||||||
|
z-index: 2000 !important;
|
||||||
|
}
|
||||||
|
.modal-backdrop {
|
||||||
|
z-index: 1900 !important;
|
||||||
|
}
|
||||||
|
.modal-content {
|
||||||
|
box-shadow: 0 5px 15px rgba(0,0,0,0.5);
|
||||||
|
pointer-events: auto !important;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
||||||
|
<div class="d-flex justify-content-between align-items-center mb-4">
|
||||||
|
<div>
|
||||||
|
<h2 class="mb-0">Cálculo de Costos</h2>
|
||||||
|
<p class="text-muted small">Gestión de costos por producto de marketing. <span class="badge bg-info text-dark">Haz clic en las celdas blancas para editar</span></p>
|
||||||
|
</div>
|
||||||
|
<div class="d-flex gap-2">
|
||||||
|
<button type="button" class="btn btn-outline-secondary btn-sm" onclick="alert('JavaScript está funcionando correctamente en este navegador.')">
|
||||||
|
<i class="fas fa-bug"></i> Probar JS
|
||||||
|
</button>
|
||||||
|
<button type="button" class="btn btn-primary shadow-sm" onclick="abrirModalNuevo()">
|
||||||
|
<i class="fas fa-plus me-2"></i> Nuevo Producto
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<?php if (isset($_GET['success'])): ?>
|
||||||
|
<div class="alert alert-success alert-dismissible fade show" role="alert">
|
||||||
|
¡Operación realizada con éxito!
|
||||||
|
<button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>
|
||||||
|
</div>
|
||||||
|
<?php endif; ?>
|
||||||
|
|
||||||
|
<div class="card shadow-sm border-0">
|
||||||
|
<div class="card-body p-0">
|
||||||
|
<div class="table-responsive">
|
||||||
|
<table class="table table-hover table-excel mb-0" id="costosTable">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th class="text-center">Orden</th>
|
||||||
|
<th>Producto</th>
|
||||||
|
<th class="text-center">Imagen</th>
|
||||||
|
<th class="text-center">Costo Producto</th>
|
||||||
|
<th class="text-center">Costo Fijo Film</th>
|
||||||
|
<th class="text-center">Comisión Asesora</th>
|
||||||
|
<th class="text-center">Delivery</th>
|
||||||
|
<th class="text-center">Costo Publicitario</th>
|
||||||
|
<th class="text-center bg-total">Inversión Total</th>
|
||||||
|
<th class="text-center">Promo 1</th>
|
||||||
|
<th class="text-center recaudo-cell">Recaudo 1 (Auto)</th>
|
||||||
|
<th class="text-center">Promo 2</th>
|
||||||
|
<th class="text-center recaudo-cell">Recaudo 2 (Auto)</th>
|
||||||
|
<th class="text-center">Promo 3</th>
|
||||||
|
<th class="text-center recaudo-cell">Recaudo 3 (Auto)</th>
|
||||||
|
<th class="text-center">Acción</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<?php if (empty($costos)): ?>
|
||||||
|
<tr>
|
||||||
|
<td colspan="16" class="text-center py-4 text-muted">No hay videos registrados en producción.</td>
|
||||||
|
</tr>
|
||||||
|
<?php else: ?>
|
||||||
|
<?php foreach ($costos as $c): ?>
|
||||||
|
<?php
|
||||||
|
$inversion_total = ($c['costo_producto'] ?? 0) +
|
||||||
|
($c['costo_fijo_film'] ?? 0) +
|
||||||
|
($c['comision_asesora'] ?? 0) +
|
||||||
|
($c['delivery'] ?? 0) +
|
||||||
|
($c['costo_publicitario'] ?? 0);
|
||||||
|
|
||||||
|
$costos_operativos = ($c['costo_fijo_film'] ?? 0) +
|
||||||
|
($c['comision_asesora'] ?? 0) +
|
||||||
|
($c['delivery'] ?? 0) +
|
||||||
|
($c['costo_publicitario'] ?? 0);
|
||||||
|
|
||||||
|
function calculateRecaudo($promo, $costos) {
|
||||||
|
if (!$promo || $promo == '-') return null;
|
||||||
|
preg_match('/[\d.]+/', $promo, $matches);
|
||||||
|
$val = isset($matches[0]) ? floatval($matches[0]) : 0;
|
||||||
|
return $val > 0 ? $val - $costos : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
$recaudo1 = calculateRecaudo($c['promo_1'], $costos_operativos);
|
||||||
|
$recaudo2 = calculateRecaudo($c['promo_2'], $costos_operativos);
|
||||||
|
$recaudo3 = calculateRecaudo($c['promo_3'], $costos_operativos);
|
||||||
|
?>
|
||||||
|
<tr data-row-id="<?php echo $c['id']; ?>">
|
||||||
|
<td class="text-center fw-bold text-primary"><?php echo $c['orden']; ?></td>
|
||||||
|
<td class="fw-bold"><?php echo htmlspecialchars($c['nombre_producto'] ?: 'General'); ?></td>
|
||||||
|
<td class="text-center">
|
||||||
|
<?php if ($c['foto_producto']): ?>
|
||||||
|
<img src="<?php echo $c['foto_producto']; ?>" class="img-preview" alt="Ref">
|
||||||
|
<?php else: ?>
|
||||||
|
<span class="text-muted small">Sin foto</span>
|
||||||
|
<?php endif; ?>
|
||||||
|
</td>
|
||||||
|
<td class="text-center editable" data-id="<?php echo $c['id']; ?>" data-field="costo_producto" onclick="startEdit(this)">
|
||||||
|
S/ <?php echo number_format($c['costo_producto'] ?? 0, 2); ?>
|
||||||
|
<span class="edit-icon"><i class="fas fa-pencil-alt"></i></span>
|
||||||
|
</td>
|
||||||
|
<td class="text-center editable" data-id="<?php echo $c['id']; ?>" data-field="costo_fijo_film" onclick="startEdit(this)">
|
||||||
|
S/ <?php echo number_format($c['costo_fijo_film'] ?? 0, 2); ?>
|
||||||
|
<span class="edit-icon"><i class="fas fa-pencil-alt"></i></span>
|
||||||
|
</td>
|
||||||
|
<td class="text-center editable" data-id="<?php echo $c['id']; ?>" data-field="comision_asesora" onclick="startEdit(this)">
|
||||||
|
S/ <?php echo number_format($c['comision_asesora'] ?? 0, 2); ?>
|
||||||
|
<span class="edit-icon"><i class="fas fa-pencil-alt"></i></span>
|
||||||
|
</td>
|
||||||
|
<td class="text-center editable" data-id="<?php echo $c['id']; ?>" data-field="delivery" onclick="startEdit(this)">
|
||||||
|
S/ <?php echo number_format($c['delivery'] ?? 0, 2); ?>
|
||||||
|
<span class="edit-icon"><i class="fas fa-pencil-alt"></i></span>
|
||||||
|
</td>
|
||||||
|
<td class="text-center editable" data-id="<?php echo $c['id']; ?>" data-field="costo_publicitario" onclick="startEdit(this)">
|
||||||
|
S/ <?php echo number_format($c['costo_publicitario'] ?? 0, 2); ?>
|
||||||
|
<span class="edit-icon"><i class="fas fa-pencil-alt"></i></span>
|
||||||
|
</td>
|
||||||
|
<td class="text-center bg-total inversion-total" id="total-<?php echo $c['id']; ?>">
|
||||||
|
S/ <?php echo number_format($inversion_total, 2); ?>
|
||||||
|
</td>
|
||||||
|
<td class="text-center editable" data-id="<?php echo $c['id']; ?>" data-field="promo_1" onclick="startEdit(this)">
|
||||||
|
<?php echo htmlspecialchars($c['promo_1'] ?: '-'); ?>
|
||||||
|
<span class="edit-icon"><i class="fas fa-pencil-alt"></i></span>
|
||||||
|
</td>
|
||||||
|
<td class="text-center recaudo-cell recaudo-1 <?php echo $recaudo1 !== null ? ($recaudo1 >= 0 ? 'text-success fw-bold' : 'text-danger fw-bold') : 'text-muted'; ?>" data-id="<?php echo $c['id']; ?>" title="Calculado automáticamente (Promo - Costos)">
|
||||||
|
<?php echo $recaudo1 !== null ? 'S/ ' . number_format($recaudo1, 2) : '-'; ?>
|
||||||
|
</td>
|
||||||
|
<td class="text-center editable" data-id="<?php echo $c['id']; ?>" data-field="promo_2" onclick="startEdit(this)">
|
||||||
|
<?php echo htmlspecialchars($c['promo_2'] ?: '-'); ?>
|
||||||
|
<span class="edit-icon"><i class="fas fa-pencil-alt"></i></span>
|
||||||
|
</td>
|
||||||
|
<td class="text-center recaudo-cell recaudo-2 <?php echo $recaudo2 !== null ? ($recaudo2 >= 0 ? 'text-success fw-bold' : 'text-danger fw-bold') : 'text-muted'; ?>" data-id="<?php echo $c['id']; ?>" title="Calculado automáticamente (Promo - Costos)">
|
||||||
|
<?php echo $recaudo2 !== null ? 'S/ ' . number_format($recaudo2, 2) : '-'; ?>
|
||||||
|
</td>
|
||||||
|
<td class="text-center editable" data-id="<?php echo $c['id']; ?>" data-field="promo_3" onclick="startEdit(this)">
|
||||||
|
<?php echo htmlspecialchars($c['promo_3'] ?: '-'); ?>
|
||||||
|
<span class="edit-icon"><i class="fas fa-pencil-alt"></i></span>
|
||||||
|
</td>
|
||||||
|
<td class="text-center recaudo-cell recaudo-3 <?php echo $recaudo3 !== null ? ($recaudo3 >= 0 ? 'text-success fw-bold' : 'text-danger fw-bold') : 'text-muted'; ?>" data-id="<?php echo $c['id']; ?>" title="Calculado automáticamente (Promo - Costos)">
|
||||||
|
<?php echo $recaudo3 !== null ? 'S/ ' . number_format($recaudo3, 2) : '-'; ?>
|
||||||
|
</td>
|
||||||
|
<td class="text-center">
|
||||||
|
<button class="btn btn-outline-danger btn-delete-row" onclick="eliminarProducto(<?php echo $c['id']; ?>)" title="Eliminar">
|
||||||
|
<i class="fas fa-trash-alt"></i>
|
||||||
|
</button>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<?php endforeach; ?>
|
||||||
|
<?php endif; ?>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Modal Nuevo Video -->
|
||||||
|
<div class="modal fade" id="nuevoVideoModal" tabindex="-1" aria-labelledby="nuevoVideoModalLabel" aria-hidden="true" data-bs-backdrop="static" data-bs-keyboard="false">
|
||||||
|
<div class="modal-dialog modal-lg">
|
||||||
|
<div class="modal-content">
|
||||||
|
<form action="save_marketing_video.php" method="POST" enctype="multipart/form-data">
|
||||||
|
<input type="hidden" name="redirect" value="calculo_costos.php?success=created">
|
||||||
|
<div class="modal-header">
|
||||||
|
<h5 class="modal-title" id="nuevoVideoModalLabel">Nuevo Producto para Marketing</h5>
|
||||||
|
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
||||||
|
</div>
|
||||||
|
<div class="modal-body">
|
||||||
|
<div class="row g-3">
|
||||||
|
<div class="col-md-2">
|
||||||
|
<label class="form-label">Orden</label>
|
||||||
|
<input type="number" name="orden" class="form-control" value="1">
|
||||||
|
</div>
|
||||||
|
<div class="col-md-5">
|
||||||
|
<label class="form-label">Fecha Entrega</label>
|
||||||
|
<input type="date" name="fecha_entrega" class="form-control" value="<?php echo date('Y-m-d'); ?>">
|
||||||
|
</div>
|
||||||
|
<div class="col-md-5">
|
||||||
|
<label class="form-label">Producto</label>
|
||||||
|
<select name="producto_id" class="form-select" required>
|
||||||
|
<option value="">Seleccionar producto...</option>
|
||||||
|
<?php foreach ($productos_select as $p): ?>
|
||||||
|
<option value="<?php echo $p['id']; ?>"><?php echo htmlspecialchars($p['nombre']); ?></option>
|
||||||
|
<?php endforeach; ?>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
<div class="col-md-6">
|
||||||
|
<label class="form-label">Imagen Referencia</label>
|
||||||
|
<input type="file" name="foto_producto" class="form-control" accept="image/*">
|
||||||
|
</div>
|
||||||
|
<div class="col-md-6">
|
||||||
|
<label class="form-label">Material</label>
|
||||||
|
<input type="text" name="material" class="form-control" placeholder="Ej: Acero, Plástico, etc.">
|
||||||
|
</div>
|
||||||
|
<div class="col-12">
|
||||||
|
<label class="form-label">Instrucciones Adicionales</label>
|
||||||
|
<textarea name="instrucciones" class="form-control" rows="3"></textarea>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="modal-footer">
|
||||||
|
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Cancelar</button>
|
||||||
|
<button type="submit" class="btn btn-primary">Agregar Producto</button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
// Funciones globales para asegurar que funcionen incluso si jQuery tarda en cargar
|
||||||
|
function abrirModalNuevo() {
|
||||||
|
try {
|
||||||
|
const modalEl = document.getElementById('nuevoVideoModal');
|
||||||
|
const modal = bootstrap.Modal.getOrCreateInstance(modalEl);
|
||||||
|
modal.show();
|
||||||
|
} catch (e) {
|
||||||
|
console.error("Error al abrir modal:", e);
|
||||||
|
// Fallback manual si bootstrap falla
|
||||||
|
$('#nuevoVideoModal').modal('show');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function startEdit(element) {
|
||||||
|
const cell = $(element);
|
||||||
|
if (cell.find('input').length > 0) return;
|
||||||
|
|
||||||
|
// Evitar selección de texto
|
||||||
|
if (window.getSelection) {
|
||||||
|
window.getSelection().removeAllRanges();
|
||||||
|
}
|
||||||
|
|
||||||
|
const id = cell.data('id');
|
||||||
|
const field = cell.data('field');
|
||||||
|
|
||||||
|
// Obtener el valor limpio (sin S/ ni comas)
|
||||||
|
let currentValue = cell.text().trim().replace('S/ ', '').replace(/,/g, '');
|
||||||
|
if (currentValue === '-') currentValue = '';
|
||||||
|
|
||||||
|
const isPromo = field.startsWith('promo_');
|
||||||
|
const input = $('<input>', {
|
||||||
|
type: isPromo ? 'text' : 'number',
|
||||||
|
class: 'form-control form-control-sm inline-edit-input',
|
||||||
|
value: currentValue
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!isPromo) {
|
||||||
|
input.attr('step', '0.01');
|
||||||
|
}
|
||||||
|
|
||||||
|
const originalContent = cell.html();
|
||||||
|
cell.empty().append(input);
|
||||||
|
input.focus().select();
|
||||||
|
|
||||||
|
let isSaving = false;
|
||||||
|
|
||||||
|
const saveChange = () => {
|
||||||
|
if (isSaving) return;
|
||||||
|
const newValue = input.val();
|
||||||
|
|
||||||
|
if (newValue === currentValue) {
|
||||||
|
cell.html(originalContent);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
isSaving = true;
|
||||||
|
input.prop('disabled', true);
|
||||||
|
|
||||||
|
$.ajax({
|
||||||
|
url: 'update_marketing_costos_field.php',
|
||||||
|
method: 'POST',
|
||||||
|
contentType: 'application/json',
|
||||||
|
data: JSON.stringify({ id, field, value: newValue }),
|
||||||
|
success: function(data) {
|
||||||
|
if (data.success) {
|
||||||
|
if (isPromo) {
|
||||||
|
cell.html((newValue || '-') + '<span class="edit-icon"><i class="fas fa-pencil-alt"></i></span>');
|
||||||
|
} else {
|
||||||
|
const formatted = parseFloat(newValue || 0).toLocaleString('es-PE', {
|
||||||
|
minimumFractionDigits: 2,
|
||||||
|
maximumFractionDigits: 2
|
||||||
|
});
|
||||||
|
cell.html('S/ ' + formatted + '<span class="edit-icon"><i class="fas fa-pencil-alt"></i></span>');
|
||||||
|
}
|
||||||
|
updateTotal(id);
|
||||||
|
} else {
|
||||||
|
alert('Error: ' + data.error);
|
||||||
|
cell.html(originalContent);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
error: function() {
|
||||||
|
alert('Error de conexión al guardar.');
|
||||||
|
cell.html(originalContent);
|
||||||
|
},
|
||||||
|
complete: function() {
|
||||||
|
isSaving = false;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
input.on('blur', saveChange);
|
||||||
|
input.on('keydown', function(e) {
|
||||||
|
if (e.key === 'Enter') {
|
||||||
|
input.blur();
|
||||||
|
} else if (e.key === 'Escape') {
|
||||||
|
input.off('blur');
|
||||||
|
cell.html(originalContent);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function updateTotal(id) {
|
||||||
|
const row = $(`tr[data-row-id="${id}"]`);
|
||||||
|
if (!row.length) return;
|
||||||
|
|
||||||
|
const costFields = ['costo_fijo_film', 'comision_asesora', 'delivery', 'costo_publicitario'];
|
||||||
|
const allFields = ['costo_producto', ...costFields];
|
||||||
|
|
||||||
|
let totalInversion = 0;
|
||||||
|
let totalCostosOperativos = 0;
|
||||||
|
|
||||||
|
allFields.forEach(field => {
|
||||||
|
const cell = row.find(`[data-field="${field}"]`);
|
||||||
|
if (cell.length) {
|
||||||
|
const val = parseFloat(cell.text().replace('S/ ', '').replace(/,/g, '') || 0);
|
||||||
|
totalInversion += val;
|
||||||
|
if (costFields.includes(field)) {
|
||||||
|
totalCostosOperativos += val;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
$(`#total-${id}`).text('S/ ' + totalInversion.toLocaleString('es-PE', {minimumFractionDigits: 2, maximumFractionDigits: 2}));
|
||||||
|
|
||||||
|
// Update Recaudos
|
||||||
|
[1, 2, 3].forEach(num => {
|
||||||
|
const promoCell = row.find(`[data-field="promo_${num}"]`);
|
||||||
|
const recaudoCell = row.find(`.recaudo-${num}`);
|
||||||
|
|
||||||
|
if (promoCell.length && recaudoCell.length) {
|
||||||
|
const promoText = promoCell.text().trim();
|
||||||
|
const promoValMatch = promoText.match(/[\d.]+/);
|
||||||
|
const promoVal = promoValMatch ? parseFloat(promoValMatch[0]) : 0;
|
||||||
|
|
||||||
|
if (promoVal > 0) {
|
||||||
|
const recaudo = promoVal - totalCostosOperativos;
|
||||||
|
recaudoCell.text('S/ ' + recaudo.toLocaleString('es-PE', {minimumFractionDigits: 2, maximumFractionDigits: 2}));
|
||||||
|
recaudoCell.removeClass('text-muted').addClass('fw-bold');
|
||||||
|
if (recaudo >= 0) {
|
||||||
|
recaudoCell.addClass('text-success').removeClass('text-danger');
|
||||||
|
} else {
|
||||||
|
recaudoCell.addClass('text-danger').removeClass('text-success');
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
recaudoCell.text('-').addClass('text-muted').removeClass('fw-bold text-success text-danger');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function eliminarProducto(id) {
|
||||||
|
if (confirm('¿Estás seguro de que deseas eliminar este producto de la lista de costos?')) {
|
||||||
|
window.location.href = 'delete_marketing_video.php?id=' + id + '&redirect=calculo_costos.php';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$(document).ready(function() {
|
||||||
|
// Mover el modal al body para evitar problemas de z-index con el backdrop
|
||||||
|
$('#nuevoVideoModal').appendTo('body');
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<?php include 'layout_footer.php'; ?>
|
||||||
371
calculo_costos_v2.php
Normal file
@ -0,0 +1,371 @@
|
|||||||
|
<?php
|
||||||
|
header("Location: calculo_costos_v3.php");
|
||||||
|
exit();
|
||||||
|
$pageTitle = "Cálculo de Costos V2";
|
||||||
|
include 'db/config.php';
|
||||||
|
include 'layout_header.php';
|
||||||
|
|
||||||
|
$db = db();
|
||||||
|
|
||||||
|
// Obtener productos para el select del modal
|
||||||
|
$stmt_products = $db->query("SELECT id, nombre FROM products ORDER BY nombre ASC");
|
||||||
|
$productos_select = $stmt_products->fetchAll(PDO::FETCH_ASSOC);
|
||||||
|
|
||||||
|
// Obtener videos y sus costos asociados
|
||||||
|
$stmt = $db->query("SELECT mv.id, mv.orden, mv.foto_producto, p.nombre as nombre_producto,
|
||||||
|
mc.costo_producto, mc.costo_fijo_film, mc.comision_asesora,
|
||||||
|
mc.delivery, mc.costo_publicitario, mc.inversion_total,
|
||||||
|
mc.promo_1, mc.promo_2, mc.promo_3
|
||||||
|
FROM marketing_videos mv
|
||||||
|
LEFT JOIN products p ON mv.producto_id = p.id
|
||||||
|
LEFT JOIN marketing_costos mc ON mv.id = mc.video_id
|
||||||
|
ORDER BY mv.orden ASC, mv.fecha_creacion DESC");
|
||||||
|
$costos = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
||||||
|
?>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.table-v2 {
|
||||||
|
font-size: 0.85rem;
|
||||||
|
border-collapse: separate;
|
||||||
|
border-spacing: 0 5px;
|
||||||
|
}
|
||||||
|
.table-v2 th {
|
||||||
|
background-color: #f1f3f5;
|
||||||
|
border-bottom: 2px solid #dee2e6;
|
||||||
|
white-space: nowrap;
|
||||||
|
padding: 12px 8px;
|
||||||
|
position: sticky;
|
||||||
|
top: 0;
|
||||||
|
z-index: 10;
|
||||||
|
}
|
||||||
|
.table-v2 td {
|
||||||
|
vertical-align: middle;
|
||||||
|
padding: 8px 4px;
|
||||||
|
background-color: #fff;
|
||||||
|
}
|
||||||
|
.input-cell {
|
||||||
|
width: 100%;
|
||||||
|
min-width: 85px;
|
||||||
|
padding: 8px;
|
||||||
|
border: 2px solid #e9ecef;
|
||||||
|
border-radius: 6px;
|
||||||
|
text-align: center;
|
||||||
|
font-size: 0.9rem;
|
||||||
|
font-weight: 500;
|
||||||
|
transition: all 0.2s;
|
||||||
|
cursor: text !important;
|
||||||
|
pointer-events: auto !important;
|
||||||
|
}
|
||||||
|
.input-cell:focus {
|
||||||
|
border-color: #0d6efd;
|
||||||
|
box-shadow: 0 0 0 0.25rem rgba(13, 110, 253, 0.15);
|
||||||
|
outline: 0;
|
||||||
|
background-color: #fff;
|
||||||
|
}
|
||||||
|
.input-cell:hover {
|
||||||
|
border-color: #adb5bd;
|
||||||
|
}
|
||||||
|
.input-cell.saving {
|
||||||
|
background-color: #fff3cd !important;
|
||||||
|
border-color: #ffc107 !important;
|
||||||
|
}
|
||||||
|
.input-cell.saved {
|
||||||
|
background-color: #d1e7dd !important;
|
||||||
|
border-color: #198754 !important;
|
||||||
|
}
|
||||||
|
.recaudo-v2 {
|
||||||
|
background-color: #f8f9fa;
|
||||||
|
font-weight: bold;
|
||||||
|
text-align: center;
|
||||||
|
padding: 10px;
|
||||||
|
border-radius: 6px;
|
||||||
|
min-width: 100px;
|
||||||
|
border: 1px solid #dee2e6;
|
||||||
|
}
|
||||||
|
.img-v2 {
|
||||||
|
width: 45px;
|
||||||
|
height: 45px;
|
||||||
|
object-fit: cover;
|
||||||
|
border-radius: 6px;
|
||||||
|
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
|
||||||
|
}
|
||||||
|
.btn-action {
|
||||||
|
width: 32px;
|
||||||
|
height: 32px;
|
||||||
|
padding: 0;
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
border-radius: 6px;
|
||||||
|
}
|
||||||
|
.editable-label {
|
||||||
|
font-size: 0.7rem;
|
||||||
|
color: #6c757d;
|
||||||
|
display: block;
|
||||||
|
margin-bottom: 2px;
|
||||||
|
text-transform: uppercase;
|
||||||
|
letter-spacing: 0.5px;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
||||||
|
<div class="d-flex justify-content-between align-items-center mb-4 mt-2">
|
||||||
|
<div>
|
||||||
|
<p class="text-muted mb-0">Edición directa: Escribe en los cuadros y los cambios se guardarán al salir del cuadro.</p>
|
||||||
|
</div>
|
||||||
|
<div class="d-flex gap-2">
|
||||||
|
<button type="button" class="btn btn-outline-secondary" onclick="location.reload()">
|
||||||
|
<i class="fas fa-sync-alt"></i> Actualizar Todo
|
||||||
|
</button>
|
||||||
|
<button type="button" class="btn btn-primary shadow-sm" data-bs-toggle="modal" data-bs-target="#modalNuevoV2">
|
||||||
|
<i class="fas fa-plus me-2"></i> Nuevo Producto
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<?php if (isset($_GET['success'])): ?>
|
||||||
|
<div class="alert alert-success alert-dismissible fade show shadow-sm" role="alert">
|
||||||
|
<i class="fas fa-check-circle me-2"></i> ¡Operación realizada con éxito!
|
||||||
|
<button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>
|
||||||
|
</div>
|
||||||
|
<?php endif; ?>
|
||||||
|
|
||||||
|
<div class="card shadow-sm border-0">
|
||||||
|
<div class="card-body p-0">
|
||||||
|
<div class="table-responsive" style="max-height: 70vh;">
|
||||||
|
<table class="table table-v2 mb-0">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th class="text-center">Orden</th>
|
||||||
|
<th>Producto</th>
|
||||||
|
<th class="text-center">Imagen</th>
|
||||||
|
<th class="text-center">Costo Prod.</th>
|
||||||
|
<th class="text-center">Film</th>
|
||||||
|
<th class="text-center">Asesora</th>
|
||||||
|
<th class="text-center">Delivery</th>
|
||||||
|
<th class="text-center">Publicidad</th>
|
||||||
|
<th class="text-center bg-light">Inversión Total</th>
|
||||||
|
<th class="text-center">Promo 1</th>
|
||||||
|
<th class="text-center">Recaudo 1</th>
|
||||||
|
<th class="text-center">Promo 2</th>
|
||||||
|
<th class="text-center">Recaudo 2</th>
|
||||||
|
<th class="text-center">Promo 3</th>
|
||||||
|
<th class="text-center">Recaudo 3</th>
|
||||||
|
<th class="text-center">Acción</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<?php foreach ($costos as $c): ?>
|
||||||
|
<?php
|
||||||
|
$inversion_total = ($c['costo_producto'] ?? 0) +
|
||||||
|
($c['costo_fijo_film'] ?? 0) +
|
||||||
|
($c['comision_asesora'] ?? 0) +
|
||||||
|
($c['delivery'] ?? 0) +
|
||||||
|
($c['costo_publicitario'] ?? 0);
|
||||||
|
|
||||||
|
function getVal($promo) {
|
||||||
|
if (empty($promo)) return 0;
|
||||||
|
preg_match('/[\d.]+/', $promo, $matches);
|
||||||
|
return isset($matches[0]) ? floatval($matches[0]) : 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
$p1 = getVal($c['promo_1']);
|
||||||
|
$p2 = getVal($c['promo_2']);
|
||||||
|
$p3 = getVal($c['promo_3']);
|
||||||
|
|
||||||
|
$r1 = $p1 > 0 ? $p1 - $inversion_total : null;
|
||||||
|
$r2 = $p2 > 0 ? $p2 - $inversion_total : null;
|
||||||
|
$r3 = $p3 > 0 ? $p3 - $inversion_total : null;
|
||||||
|
?>
|
||||||
|
<tr id="row-<?php echo $c['id']; ?>" data-id="<?php echo $c['id']; ?>">
|
||||||
|
<td class="text-center fw-bold text-primary"><?php echo $c['orden']; ?></td>
|
||||||
|
<td style="max-width: 140px;">
|
||||||
|
<div class="text-truncate fw-bold" title="<?php echo htmlspecialchars($c['nombre_producto']); ?>">
|
||||||
|
<?php echo htmlspecialchars($c['nombre_producto'] ?: 'General'); ?>
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
<td class="text-center">
|
||||||
|
<?php if ($c['foto_producto']): ?>
|
||||||
|
<img src="<?php echo $c['foto_producto']; ?>" class="img-v2">
|
||||||
|
<?php else: ?>
|
||||||
|
<div class="bg-light rounded d-flex align-items-center justify-content-center" style="width:45px; height:45px;">
|
||||||
|
<i class="fas fa-image text-muted"></i>
|
||||||
|
</div>
|
||||||
|
<?php endif; ?>
|
||||||
|
</td>
|
||||||
|
<td><input type="number" step="0.01" class="input-cell" data-field="costo_producto" value="<?php echo $c['costo_producto']; ?>" onchange="updateRow(<?php echo $c['id']; ?>, 'costo_producto', this.value)"></td>
|
||||||
|
<td><input type="number" step="0.01" class="input-cell" data-field="costo_fijo_film" value="<?php echo $c['costo_fijo_film']; ?>" onchange="updateRow(<?php echo $c['id']; ?>, 'costo_fijo_film', this.value)"></td>
|
||||||
|
<td><input type="number" step="0.01" class="input-cell" data-field="comision_asesora" value="<?php echo $c['comision_asesora']; ?>" onchange="updateRow(<?php echo $c['id']; ?>, 'comision_asesora', this.value)"></td>
|
||||||
|
<td><input type="number" step="0.01" class="input-cell" data-field="delivery" value="<?php echo $c['delivery']; ?>" onchange="updateRow(<?php echo $c['id']; ?>, 'delivery', this.value)"></td>
|
||||||
|
<td><input type="number" step="0.01" class="input-cell" data-field="costo_publicitario" value="<?php echo $c['costo_publicitario']; ?>" onchange="updateRow(<?php echo $c['id']; ?>, 'costo_publicitario', this.value)"></td>
|
||||||
|
<td class="text-center">
|
||||||
|
<div class="recaudo-v2 bg-light fw-bold text-dark" id="total-<?php echo $c['id']; ?>">
|
||||||
|
S/ <?php echo number_format($inversion_total, 2); ?>
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
|
||||||
|
<td><input type="text" class="input-cell" data-field="promo_1" value="<?php echo htmlspecialchars($c['promo_1']); ?>" onchange="updateRow(<?php echo $c['id']; ?>, 'promo_1', this.value)"></td>
|
||||||
|
<td><div class="recaudo-v2 recaudo-1 <?php echo $r1 >= 0 ? 'text-success' : 'text-danger'; ?>" id="r1-<?php echo $c['id']; ?>"><?php echo $r1 !== null ? 'S/ '.number_format($r1, 2) : '-'; ?></div></td>
|
||||||
|
|
||||||
|
<td><input type="text" class="input-cell" data-field="promo_2" value="<?php echo htmlspecialchars($c['promo_2']); ?>" onchange="updateRow(<?php echo $c['id']; ?>, 'promo_2', this.value)"></td>
|
||||||
|
<td><div class="recaudo-v2 recaudo-2 <?php echo $r2 >= 0 ? 'text-success' : 'text-danger'; ?>" id="r2-<?php echo $c['id']; ?>"><?php echo $r2 !== null ? 'S/ '.number_format($r2, 2) : '-'; ?></div></td>
|
||||||
|
|
||||||
|
<td><input type="text" class="input-cell" data-field="promo_3" value="<?php echo htmlspecialchars($c['promo_3']); ?>" onchange="updateRow(<?php echo $c['id']; ?>, 'promo_3', this.value)"></td>
|
||||||
|
<td><div class="recaudo-v2 recaudo-3 <?php echo $r3 >= 0 ? 'text-success' : 'text-danger'; ?>" id="r3-<?php echo $c['id']; ?>"><?php echo $r3 !== null ? 'S/ '.number_format($r3, 2) : '-'; ?></div></td>
|
||||||
|
|
||||||
|
<td class="text-center">
|
||||||
|
<button class="btn btn-danger btn-action" onclick="eliminarRow(<?php echo $c['id']; ?>)" title="Eliminar">
|
||||||
|
<i class="fas fa-trash-alt"></i>
|
||||||
|
</button>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<?php endforeach; ?>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Modal Nuevo -->
|
||||||
|
<div class="modal fade" id="modalNuevoV2" tabindex="-1" aria-hidden="true">
|
||||||
|
<div class="modal-dialog modal-dialog-centered">
|
||||||
|
<div class="modal-content border-0 shadow">
|
||||||
|
<form action="save_marketing_video.php" method="POST" enctype="multipart/form-data">
|
||||||
|
<input type="hidden" name="redirect" value="calculo_costos_v2.php?success=1">
|
||||||
|
<div class="modal-header bg-primary text-white">
|
||||||
|
<h5 class="modal-title"><i class="fas fa-plus-circle me-2"></i> Nuevo Producto para Costos</h5>
|
||||||
|
<button type="button" class="btn-close btn-close-white" data-bs-dismiss="modal" aria-label="Close"></button>
|
||||||
|
</div>
|
||||||
|
<div class="modal-body p-4">
|
||||||
|
<div class="mb-3">
|
||||||
|
<label class="form-label fw-bold">Seleccionar Producto</label>
|
||||||
|
<select name="producto_id" id="select_producto_nuevo" class="form-select form-select-lg" required onchange="fetchProductCost(this.value)">
|
||||||
|
<option value="">Seleccionar...</option>
|
||||||
|
<?php foreach ($productos_select as $p): ?>
|
||||||
|
<option value="<?php echo $p['id']; ?>"><?php echo htmlspecialchars($p['nombre']); ?></option>
|
||||||
|
<?php endforeach; ?>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-md-6 mb-3">
|
||||||
|
<label class="form-label fw-bold">Costo Base (S/)</label>
|
||||||
|
<input type="number" name="costo_producto" id="costo_producto_nuevo" class="form-control" step="0.01" placeholder="0.00">
|
||||||
|
</div>
|
||||||
|
<div class="col-md-6 mb-3">
|
||||||
|
<label class="form-label fw-bold">Orden de Lista</label>
|
||||||
|
<input type="number" name="orden" class="form-control" value="1">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="mb-0">
|
||||||
|
<label class="form-label fw-bold">Imagen del Producto</label>
|
||||||
|
<input type="file" name="foto_producto" class="form-control">
|
||||||
|
<small class="text-muted">Opcional. Si no se sube, se usará la del producto.</small>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="modal-footer bg-light">
|
||||||
|
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Cancelar</button>
|
||||||
|
<button type="submit" class="btn btn-primary px-4">Crear Registro</button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
console.log("Script de Cálculo de Costos V2 cargado");
|
||||||
|
|
||||||
|
function fetchProductCost(productId) {
|
||||||
|
if (!productId) return;
|
||||||
|
|
||||||
|
fetch(`get_product_details.php?id=${productId}`)
|
||||||
|
.then(response => response.json())
|
||||||
|
.then(data => {
|
||||||
|
if (data.success && data.product.costo) {
|
||||||
|
document.getElementById('costo_producto_nuevo').value = data.product.costo;
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch(error => console.error('Error fetching product cost:', error));
|
||||||
|
}
|
||||||
|
|
||||||
|
function updateRow(id, field, value) {
|
||||||
|
const row = document.getElementById(`row-${id}`);
|
||||||
|
const input = row.querySelector(`[data-field="${field}"]`);
|
||||||
|
|
||||||
|
if (!input) return;
|
||||||
|
|
||||||
|
input.classList.add('saving');
|
||||||
|
console.log(`Actualizando ${field} para ID ${id} con valor ${value}`);
|
||||||
|
|
||||||
|
fetch('update_marketing_costos_field.php', {
|
||||||
|
method: 'POST',
|
||||||
|
headers: { 'Content-Type': 'application/json' },
|
||||||
|
body: JSON.stringify({ id, field, value })
|
||||||
|
})
|
||||||
|
.then(response => response.json())
|
||||||
|
.then(data => {
|
||||||
|
input.classList.remove('saving');
|
||||||
|
if (data.success) {
|
||||||
|
input.classList.add('saved');
|
||||||
|
setTimeout(() => input.classList.remove('saved'), 1500);
|
||||||
|
|
||||||
|
// Recalcular todo en la fila
|
||||||
|
recalculateRow(id);
|
||||||
|
} else {
|
||||||
|
alert('Error al guardar: ' + (data.error || 'Error desconocido'));
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch(error => {
|
||||||
|
input.classList.remove('saving');
|
||||||
|
console.error('Error:', error);
|
||||||
|
alert('Error de conexión al servidor');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function recalculateRow(id) {
|
||||||
|
const row = document.getElementById(`row-${id}`);
|
||||||
|
if (!row) return;
|
||||||
|
|
||||||
|
const fields = ['costo_producto', 'costo_fijo_film', 'comision_asesora', 'delivery', 'costo_publicitario'];
|
||||||
|
|
||||||
|
let total = 0;
|
||||||
|
fields.forEach(f => {
|
||||||
|
const el = row.querySelector(`[data-field="${f}"]`);
|
||||||
|
if (el) {
|
||||||
|
total += parseFloat(el.value) || 0;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const totalEl = document.getElementById(`total-${id}`);
|
||||||
|
if (totalEl) {
|
||||||
|
totalEl.innerText = 'S/ ' + total.toFixed(2);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Recalcular recaudos
|
||||||
|
for (let i = 1; i <= 3; i++) {
|
||||||
|
const promoEl = row.querySelector(`[data-field="promo_${i}"]`);
|
||||||
|
const recaudoEl = document.getElementById(`r${i}-${id}`);
|
||||||
|
|
||||||
|
if (promoEl && recaudoEl) {
|
||||||
|
const promoVal = promoEl.value;
|
||||||
|
const match = promoVal.match(/[\d.]+/);
|
||||||
|
const pVal = match ? parseFloat(match[0]) : 0;
|
||||||
|
|
||||||
|
if (pVal > 0) {
|
||||||
|
const recaudo = pVal - total;
|
||||||
|
recaudoEl.innerText = 'S/ ' + recaudo.toFixed(2);
|
||||||
|
recaudoEl.className = 'recaudo-v2 ' + (recaudo >= 0 ? 'text-success' : 'text-danger');
|
||||||
|
} else {
|
||||||
|
recaudoEl.innerText = '-';
|
||||||
|
recaudoEl.className = 'recaudo-v2 text-muted';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function eliminarRow(id) {
|
||||||
|
if (confirm('¿Estás seguro de que deseas eliminar este registro de costos?')) {
|
||||||
|
window.location.href = 'delete_marketing_video.php?id=' + id + '&redirect=calculo_costos_v2.php';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<?php include 'layout_footer.php'; ?>
|
||||||
500
calculo_costos_v3.php
Normal file
@ -0,0 +1,500 @@
|
|||||||
|
<?php
|
||||||
|
if (session_status() === PHP_SESSION_NONE) {
|
||||||
|
session_start();
|
||||||
|
}
|
||||||
|
if (!isset($_SESSION['user_role']) || !in_array($_SESSION['user_role'], ['Administrador', 'admin'])) {
|
||||||
|
header('Location: dashboard.php');
|
||||||
|
exit();
|
||||||
|
}
|
||||||
|
$pageTitle = "Cálculo de Costos V3";
|
||||||
|
include 'db/config.php';
|
||||||
|
include 'layout_header.php';
|
||||||
|
|
||||||
|
$db = db();
|
||||||
|
|
||||||
|
// Obtener productos para el select del modal
|
||||||
|
$stmt_products = $db->query("SELECT id, nombre FROM products ORDER BY nombre ASC");
|
||||||
|
$productos_select = $stmt_products->fetchAll(PDO::FETCH_ASSOC);
|
||||||
|
|
||||||
|
// Obtener videos y sus costos asociados de las tablas V3
|
||||||
|
$stmt = $db->query("SELECT mv.id, mv.orden, mv.foto_producto, p.nombre as nombre_producto,
|
||||||
|
mc.costo_producto, mc.costo_fijo_film, mc.comision_asesora,
|
||||||
|
mc.delivery, mc.costo_publicitario, mc.inversion_total,
|
||||||
|
mc.promo_1, mc.promo_2, mc.promo_3,
|
||||||
|
mc.comision_asesora_provincia, mc.delivery_provincia
|
||||||
|
FROM marketing_videos_v3 mv
|
||||||
|
LEFT JOIN products p ON mv.producto_id = p.id
|
||||||
|
LEFT JOIN marketing_costos_v3 mc ON mv.id = mc.video_id
|
||||||
|
ORDER BY mv.orden ASC, mv.id DESC");
|
||||||
|
$costos = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
||||||
|
?>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.table-v3 {
|
||||||
|
font-size: 0.8rem;
|
||||||
|
border-collapse: separate;
|
||||||
|
border-spacing: 0 5px;
|
||||||
|
}
|
||||||
|
.table-v3 th {
|
||||||
|
background-color: #f8f9fa;
|
||||||
|
border-bottom: 2px solid #dee2e6;
|
||||||
|
white-space: nowrap;
|
||||||
|
padding: 10px 5px;
|
||||||
|
position: sticky;
|
||||||
|
top: 0;
|
||||||
|
z-index: 10;
|
||||||
|
}
|
||||||
|
.table-v3 td {
|
||||||
|
vertical-align: middle;
|
||||||
|
padding: 6px 3px;
|
||||||
|
background-color: #fff;
|
||||||
|
}
|
||||||
|
.input-cell {
|
||||||
|
width: 100%;
|
||||||
|
min-width: 70px;
|
||||||
|
padding: 6px;
|
||||||
|
border: 2px solid #e9ecef;
|
||||||
|
border-radius: 6px;
|
||||||
|
text-align: center;
|
||||||
|
font-size: 0.85rem;
|
||||||
|
font-weight: 500;
|
||||||
|
transition: all 0.2s;
|
||||||
|
}
|
||||||
|
.input-cell:focus {
|
||||||
|
border-color: #0d6efd;
|
||||||
|
box-shadow: 0 0 0 0.25rem rgba(13, 110, 253, 0.15);
|
||||||
|
outline: 0;
|
||||||
|
}
|
||||||
|
.input-cell.saving { background-color: #fff3cd !important; border-color: #ffc107 !important; }
|
||||||
|
.input-cell.saved { background-color: #d1e7dd !important; border-color: #198754 !important; }
|
||||||
|
|
||||||
|
.recaudo-v3 {
|
||||||
|
background-color: #f8f9fa;
|
||||||
|
font-weight: bold;
|
||||||
|
text-align: center;
|
||||||
|
padding: 6px 4px;
|
||||||
|
border-radius: 6px;
|
||||||
|
min-width: 85px;
|
||||||
|
border: 1px solid #dee2e6;
|
||||||
|
font-size: 0.8rem;
|
||||||
|
}
|
||||||
|
.recaudo-label {
|
||||||
|
font-size: 0.65rem;
|
||||||
|
display: block;
|
||||||
|
color: #6c757d;
|
||||||
|
margin-bottom: 2px;
|
||||||
|
}
|
||||||
|
.img-v3 {
|
||||||
|
width: 120px;
|
||||||
|
height: 120px;
|
||||||
|
object-fit: cover;
|
||||||
|
border-radius: 12px;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: all 0.4s cubic-bezier(0.165, 0.84, 0.44, 1);
|
||||||
|
box-shadow: 0 4px 12px rgba(0,0,0,0.12);
|
||||||
|
}
|
||||||
|
.img-v3:hover {
|
||||||
|
transform: scale(2.2);
|
||||||
|
z-index: 100;
|
||||||
|
position: relative;
|
||||||
|
box-shadow: 0 15px 30px rgba(0,0,0,0.2);
|
||||||
|
}
|
||||||
|
.img-placeholder {
|
||||||
|
width: 120px;
|
||||||
|
height: 120px;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: background-color 0.2s;
|
||||||
|
border-radius: 12px;
|
||||||
|
}
|
||||||
|
.img-placeholder:hover {
|
||||||
|
background-color: #e2e6ea !important;
|
||||||
|
}
|
||||||
|
.btn-action {
|
||||||
|
width: 28px;
|
||||||
|
height: 28px;
|
||||||
|
padding: 0;
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
border-radius: 6px;
|
||||||
|
}
|
||||||
|
.bg-provincia {
|
||||||
|
background-color: #fff4e6 !important;
|
||||||
|
}
|
||||||
|
.header-provincia {
|
||||||
|
background-color: #ffe8cc !important;
|
||||||
|
color: #d9480f !important;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
||||||
|
<div class="row align-items-center mb-4 mt-2">
|
||||||
|
<div class="col-md-6">
|
||||||
|
<p class="text-muted mb-0">Total de registros encontrados: <span class="fw-bold text-dark"><?php echo count($costos); ?></span></p>
|
||||||
|
</div>
|
||||||
|
<div class="col-md-6 text-end">
|
||||||
|
<button type="button" class="btn btn-outline-secondary me-2" onclick="location.reload()">
|
||||||
|
<i class="fas fa-sync-alt"></i> Actualizar
|
||||||
|
</button>
|
||||||
|
<button type="button" id="btnAbrirModalV3" class="btn btn-success btn-lg shadow-sm px-4">
|
||||||
|
<i class="fas fa-plus-circle me-2"></i> CREAR NUEVO PRODUCTO
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<?php if (isset($_GET['success'])): ?>
|
||||||
|
<div class="alert alert-success alert-dismissible fade show shadow-sm" role="alert">
|
||||||
|
<i class="fas fa-check-circle me-2"></i> ¡Operación realizada con éxito!
|
||||||
|
<button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>
|
||||||
|
</div>
|
||||||
|
<?php endif; ?>
|
||||||
|
|
||||||
|
<?php if (isset($_GET['error'])): ?>
|
||||||
|
<div class="alert alert-danger alert-dismissible fade show shadow-sm" role="alert">
|
||||||
|
<i class="fas fa-exclamation-triangle me-2"></i> <?php echo htmlspecialchars($_GET['error']); ?>
|
||||||
|
<button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>
|
||||||
|
</div>
|
||||||
|
<?php endif; ?>
|
||||||
|
|
||||||
|
<div class="card shadow-sm border-0">
|
||||||
|
<div class="card-body p-0">
|
||||||
|
<div class="table-responsive" style="max-height: 70vh;">
|
||||||
|
<table class="table table-v3 mb-0">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th class="text-center">Orden</th>
|
||||||
|
<th>Producto</th>
|
||||||
|
<th class="text-center">Imagen</th>
|
||||||
|
<th class="text-center">Costo Prod.</th>
|
||||||
|
<th class="text-center">Film</th>
|
||||||
|
<th class="text-center">Asesora (L)</th>
|
||||||
|
<th class="text-center header-provincia">Asesora (P)</th>
|
||||||
|
<th class="text-center">Delivery (L)</th>
|
||||||
|
<th class="text-center header-provincia">Delivery (P)</th>
|
||||||
|
<th class="text-center">Publicidad</th>
|
||||||
|
<th class="text-center">Promo 1</th>
|
||||||
|
<th class="text-center">Recaudo 1</th>
|
||||||
|
<th class="text-center">Promo 2</th>
|
||||||
|
<th class="text-center">Recaudo 2</th>
|
||||||
|
<th class="text-center">Promo 3</th>
|
||||||
|
<th class="text-center">Recaudo 3</th>
|
||||||
|
<th class="text-center">Acción</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<?php if (empty($costos)): ?>
|
||||||
|
<tr>
|
||||||
|
<td colspan="17" class="text-center py-5 text-muted">
|
||||||
|
<i class="fas fa-info-circle me-2"></i> No hay productos en esta lista. Haz clic en "Nuevo Producto" para empezar.
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<?php endif; ?>
|
||||||
|
<?php
|
||||||
|
if (!function_exists('getValV3')) {
|
||||||
|
function getValV3($promo) {
|
||||||
|
if (empty($promo)) return 0;
|
||||||
|
preg_match('/[\d.]+/', $promo, $matches);
|
||||||
|
return isset($matches[0]) ? floatval($matches[0]) : 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
?>
|
||||||
|
<?php foreach ($costos as $c): ?>
|
||||||
|
<?php
|
||||||
|
$costo_prod = ($c['costo_producto'] ?? 0);
|
||||||
|
$costo_film = ($c['costo_fijo_film'] ?? 0);
|
||||||
|
$costo_asesora = ($c['comision_asesora'] ?? 0);
|
||||||
|
$costo_asesora_p = ($c['comision_asesora_provincia'] ?? 0);
|
||||||
|
$costo_delivery = ($c['delivery'] ?? 0);
|
||||||
|
$costo_delivery_p = ($c['delivery_provincia'] ?? 0);
|
||||||
|
$costo_publicidad = ($c['costo_publicitario'] ?? 0);
|
||||||
|
|
||||||
|
$p1 = getValV3($c['promo_1']);
|
||||||
|
$p2 = getValV3($c['promo_2']);
|
||||||
|
$p3 = getValV3($c['promo_3']);
|
||||||
|
|
||||||
|
// Cálculos Local
|
||||||
|
$r1_l = $p1 > 0 ? $p1 - ($costo_prod + $costo_film + $costo_asesora + $costo_delivery + $costo_publicidad) : null;
|
||||||
|
$r2_l = $p2 > 0 ? $p2 - (($costo_prod + $costo_film) * 2 + $costo_asesora + $costo_delivery + $costo_publicidad) : null;
|
||||||
|
$r3_l = $p3 > 0 ? $p3 - (($costo_prod + $costo_film) * 3 + $costo_asesora + $costo_delivery + $costo_publicidad) : null;
|
||||||
|
|
||||||
|
// Cálculos Provincia
|
||||||
|
$r1_p = $p1 > 0 ? $p1 - ($costo_prod + $costo_film + $costo_asesora_p + $costo_delivery_p + $costo_publicidad) : null;
|
||||||
|
$r2_p = $p2 > 0 ? $p2 - (($costo_prod + $costo_film) * 2 + $costo_asesora_p + $costo_delivery_p + $costo_publicidad) : null;
|
||||||
|
$r3_p = $p3 > 0 ? $p3 - (($costo_prod + $costo_film) * 3 + $costo_asesora_p + $costo_delivery_p + $costo_publicidad) : null;
|
||||||
|
?>
|
||||||
|
<tr id="row-<?php echo $c['id']; ?>" data-id="<?php echo $c['id']; ?>">
|
||||||
|
<td class="text-center fw-bold text-primary"><?php echo $c['orden']; ?></td>
|
||||||
|
<td style="max-width: 120px;">
|
||||||
|
<div class="text-truncate fw-bold" title="<?php echo htmlspecialchars($c['nombre_producto']); ?>">
|
||||||
|
<?php echo htmlspecialchars($c['nombre_producto'] ?: 'General'); ?>
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
<td class="text-center">
|
||||||
|
<div class="position-relative d-inline-block">
|
||||||
|
<?php if ($c['foto_producto']): ?>
|
||||||
|
<img src="<?php echo $c['foto_producto']; ?>" class="img-v3" onclick="triggerImageUpload(<?php echo $c['id']; ?>)">
|
||||||
|
<?php else: ?>
|
||||||
|
<div class="bg-light rounded d-flex align-items-center justify-content-center img-placeholder" onclick="triggerImageUpload(<?php echo $c['id']; ?>)">
|
||||||
|
<i class="fas fa-camera text-muted"></i>
|
||||||
|
</div>
|
||||||
|
<?php endif; ?>
|
||||||
|
<input type="file" id="file-input-<?php echo $c['id']; ?>" class="d-none" accept="image/*" onchange="uploadImage(<?php echo $c['id']; ?>)">
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
<td><input type="number" step="0.01" class="input-cell cost-input" data-field="costo_producto" value="<?php echo $c['costo_producto']; ?>"></td>
|
||||||
|
<td><input type="number" step="0.01" class="input-cell cost-input" data-field="costo_fijo_film" value="<?php echo $c['costo_fijo_film']; ?>"></td>
|
||||||
|
<td><input type="number" step="0.01" class="input-cell cost-input" data-field="comision_asesora" value="<?php echo $c['comision_asesora']; ?>"></td>
|
||||||
|
<td><input type="number" step="0.01" class="input-cell cost-input bg-provincia" data-field="comision_asesora_provincia" value="<?php echo $c['comision_asesora_provincia']; ?>"></td>
|
||||||
|
<td><input type="number" step="0.01" class="input-cell cost-input" data-field="delivery" value="<?php echo $c['delivery']; ?>"></td>
|
||||||
|
<td><input type="number" step="0.01" class="input-cell cost-input bg-provincia" data-field="delivery_provincia" value="<?php echo $c['delivery_provincia']; ?>"></td>
|
||||||
|
<td><input type="number" step="0.01" class="input-cell cost-input" data-field="costo_publicitario" value="<?php echo $c['costo_publicitario']; ?>"></td>
|
||||||
|
|
||||||
|
<td><input type="text" class="input-cell promo-input" data-field="promo_1" value="<?php echo htmlspecialchars($c['promo_1']); ?>"></td>
|
||||||
|
<td>
|
||||||
|
<div class="recaudo-v3 mb-1 <?php echo $r1_l >= 0 ? 'text-success' : 'text-danger'; ?>" id="r1l-<?php echo $c['id']; ?>">
|
||||||
|
<span class="recaudo-label">LOCAL</span>
|
||||||
|
<?php echo $r1_l !== null ? 'S/ '.number_format($r1_l, 2) : '-'; ?>
|
||||||
|
</div>
|
||||||
|
<div class="recaudo-v3 bg-provincia <?php echo $r1_p >= 0 ? 'text-success' : 'text-danger'; ?>" id="r1p-<?php echo $c['id']; ?>">
|
||||||
|
<span class="recaudo-label">PROVINCIA</span>
|
||||||
|
<?php echo $r1_p !== null ? 'S/ '.number_format($r1_p, 2) : '-'; ?>
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
|
||||||
|
<td><input type="text" class="input-cell promo-input" data-field="promo_2" value="<?php echo htmlspecialchars($c['promo_2']); ?>"></td>
|
||||||
|
<td>
|
||||||
|
<div class="recaudo-v3 mb-1 <?php echo $r2_l >= 0 ? 'text-success' : 'text-danger'; ?>" id="r2l-<?php echo $c['id']; ?>">
|
||||||
|
<span class="recaudo-label">LOCAL</span>
|
||||||
|
<?php echo $r2_l !== null ? 'S/ '.number_format($r2_l, 2) : '-'; ?>
|
||||||
|
</div>
|
||||||
|
<div class="recaudo-v3 bg-provincia <?php echo $r2_p >= 0 ? 'text-success' : 'text-danger'; ?>" id="r2p-<?php echo $c['id']; ?>">
|
||||||
|
<span class="recaudo-label">PROVINCIA</span>
|
||||||
|
<?php echo $r2_p !== null ? 'S/ '.number_format($r2_p, 2) : '-'; ?>
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
|
||||||
|
<td><input type="text" class="input-cell promo-input" data-field="promo_3" value="<?php echo htmlspecialchars($c['promo_3']); ?>"></td>
|
||||||
|
<td>
|
||||||
|
<div class="recaudo-v3 mb-1 <?php echo $r3_l >= 0 ? 'text-success' : 'text-danger'; ?>" id="r3l-<?php echo $c['id']; ?>">
|
||||||
|
<span class="recaudo-label">LOCAL</span>
|
||||||
|
<?php echo $r3_l !== null ? 'S/ '.number_format($r3_l, 2) : '-'; ?>
|
||||||
|
</div>
|
||||||
|
<div class="recaudo-v3 bg-provincia <?php echo $r3_p >= 0 ? 'text-success' : 'text-danger'; ?>" id="r3p-<?php echo $c['id']; ?>">
|
||||||
|
<span class="recaudo-label">PROVINCIA</span>
|
||||||
|
<?php echo $r3_p !== null ? 'S/ '.number_format($r3_p, 2) : '-'; ?>
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
|
||||||
|
<td class="text-center">
|
||||||
|
<button class="btn btn-danger btn-action" onclick="eliminarRow(<?php echo $c['id']; ?>)" title="Eliminar">
|
||||||
|
<i class="fas fa-trash-alt"></i>
|
||||||
|
</button>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<?php endforeach; ?>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Modal Nuevo -->
|
||||||
|
<div class="modal fade" id="modalNuevoV3" tabindex="-1" aria-hidden="true">
|
||||||
|
<div class="modal-dialog modal-dialog-centered">
|
||||||
|
<div class="modal-content border-0 shadow">
|
||||||
|
<form action="save_marketing_video_v3.php" method="POST" enctype="multipart/form-data">
|
||||||
|
<input type="hidden" name="redirect" value="calculo_costos_v3.php?success=1">
|
||||||
|
<div class="modal-header bg-primary text-white">
|
||||||
|
<h5 class="modal-title"><i class="fas fa-plus-circle me-2"></i> Nuevo Producto (V3)</h5>
|
||||||
|
<button type="button" class="btn-close btn-close-white" data-bs-dismiss="modal" aria-label="Close"></button>
|
||||||
|
</div>
|
||||||
|
<div class="modal-body p-4">
|
||||||
|
<div class="mb-3">
|
||||||
|
<label class="form-label fw-bold">Seleccionar Producto</label>
|
||||||
|
<select name="producto_id" class="form-select form-select-lg" onchange="fetchProductCost(this.value)">
|
||||||
|
<option value="">General / Sin producto específico</option>
|
||||||
|
<?php foreach ($productos_select as $p): ?>
|
||||||
|
<option value="<?php echo $p['id']; ?>"><?php echo htmlspecialchars($p['nombre']); ?></option>
|
||||||
|
<?php endforeach; ?>
|
||||||
|
</select>
|
||||||
|
<small class="text-muted">Si no seleccionas uno, se marcará como "General".</small>
|
||||||
|
</div>
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-md-6 mb-3">
|
||||||
|
<label class="form-label fw-bold">Costo Base (S/)</label>
|
||||||
|
<input type="number" name="costo_producto" id="costo_producto_nuevo_v3" class="form-control" step="0.01" placeholder="0.00">
|
||||||
|
</div>
|
||||||
|
<div class="col-md-6 mb-3">
|
||||||
|
<label class="form-label fw-bold">Orden de Lista</label>
|
||||||
|
<input type="number" name="orden" class="form-control" value="1">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="mb-0">
|
||||||
|
<label class="form-label fw-bold">Imagen del Producto</label>
|
||||||
|
<input type="file" name="foto_producto" class="form-control">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="modal-footer bg-light">
|
||||||
|
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Cancelar</button>
|
||||||
|
<button type="submit" class="btn btn-primary px-4">Crear Registro</button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
function fetchProductCost(productId) {
|
||||||
|
if (!productId) return;
|
||||||
|
fetch(`get_product_details.php?id=${productId}`)
|
||||||
|
.then(response => response.json())
|
||||||
|
.then(data => {
|
||||||
|
if (data.success && data.product.costo) {
|
||||||
|
document.getElementById('costo_producto_nuevo_v3').value = data.product.costo;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
document.querySelectorAll('.input-cell').forEach(input => {
|
||||||
|
// Actualización instantánea visual
|
||||||
|
input.addEventListener('input', function() {
|
||||||
|
const row = this.closest('tr');
|
||||||
|
recalculateRowVisual(row);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Guardado real al salir del cuadro
|
||||||
|
input.addEventListener('change', function() {
|
||||||
|
const row = this.closest('tr');
|
||||||
|
const id = row.dataset.id;
|
||||||
|
const field = this.dataset.field;
|
||||||
|
const value = this.value;
|
||||||
|
|
||||||
|
this.classList.add('saving');
|
||||||
|
|
||||||
|
fetch('update_marketing_costos_field_v3.php', {
|
||||||
|
method: 'POST',
|
||||||
|
headers: { 'Content-Type': 'application/json' },
|
||||||
|
body: JSON.stringify({ id, field, value })
|
||||||
|
})
|
||||||
|
.then(response => response.json())
|
||||||
|
.then(data => {
|
||||||
|
this.classList.remove('saving');
|
||||||
|
if (data.success) {
|
||||||
|
this.classList.add('saved');
|
||||||
|
setTimeout(() => this.classList.remove('saved'), 1000);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
function recalculateRowVisual(row) {
|
||||||
|
const id = row.dataset.id;
|
||||||
|
const costProd = parseFloat(row.querySelector('[data-field="costo_producto"]').value) || 0;
|
||||||
|
const costFilm = parseFloat(row.querySelector('[data-field="costo_fijo_film"]').value) || 0;
|
||||||
|
const costAsesora = parseFloat(row.querySelector('[data-field="comision_asesora"]').value) || 0;
|
||||||
|
const costAsesoraP = parseFloat(row.querySelector('[data-field="comision_asesora_provincia"]').value) || 0;
|
||||||
|
const costDelivery = parseFloat(row.querySelector('[data-field="delivery"]').value) || 0;
|
||||||
|
const costDeliveryP = parseFloat(row.querySelector('[data-field="delivery_provincia"]').value) || 0;
|
||||||
|
const costPublicidad = parseFloat(row.querySelector('[data-field="costo_publicitario"]').value) || 0;
|
||||||
|
|
||||||
|
for (let i = 1; i <= 3; i++) {
|
||||||
|
const promoVal = row.querySelector(`[data-field="promo_${i}"]`).value;
|
||||||
|
const match = promoVal.match(/[\d.]+/);
|
||||||
|
const pVal = match ? parseFloat(match[0]) : 0;
|
||||||
|
|
||||||
|
const recaudoElL = row.querySelector(`#r${i}l-${id}`);
|
||||||
|
const recaudoElP = row.querySelector(`#r${i}p-${id}`);
|
||||||
|
|
||||||
|
if (pVal > 0) {
|
||||||
|
// Local
|
||||||
|
const recaudoL = pVal - ((costProd + costFilm) * i + costAsesora + costDelivery + costPublicidad);
|
||||||
|
recaudoElL.innerHTML = `<span class="recaudo-label">LOCAL</span>S/ ${recaudoL.toFixed(2)}`;
|
||||||
|
recaudoElL.className = 'recaudo-v3 mb-1 ' + (recaudoL >= 0 ? 'text-success' : 'text-danger');
|
||||||
|
|
||||||
|
// Provincia
|
||||||
|
const recaudoP = pVal - ((costProd + costFilm) * i + costAsesoraP + costDeliveryP + costPublicidad);
|
||||||
|
recaudoElP.innerHTML = `<span class="recaudo-label">PROVINCIA</span>S/ ${recaudoP.toFixed(2)}`;
|
||||||
|
recaudoElP.className = 'recaudo-v3 bg-provincia ' + (recaudoP >= 0 ? 'text-success' : 'text-danger');
|
||||||
|
} else {
|
||||||
|
recaudoElL.innerHTML = `<span class="recaudo-label">LOCAL</span>-`;
|
||||||
|
recaudoElP.innerHTML = `<span class="recaudo-label">PROVINCIA</span>-`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function eliminarRow(id) {
|
||||||
|
if (confirm("¿Eliminar este registro de la V3?")) {
|
||||||
|
window.location.href = "delete_marketing_video_v3.php?id=" + id;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function triggerImageUpload(id) {
|
||||||
|
document.getElementById(`file-input-${id}`).click();
|
||||||
|
}
|
||||||
|
|
||||||
|
function uploadImage(id) {
|
||||||
|
const fileInput = document.getElementById(`file-input-${id}`);
|
||||||
|
if (!fileInput.files || !fileInput.files[0]) return;
|
||||||
|
|
||||||
|
const formData = new FormData();
|
||||||
|
formData.append('id', id);
|
||||||
|
formData.append('foto_producto', fileInput.files[0]);
|
||||||
|
|
||||||
|
const cell = fileInput.closest('td');
|
||||||
|
const originalContent = cell.innerHTML;
|
||||||
|
cell.innerHTML = '<div class="spinner-border spinner-border-sm text-primary" role="status"></div>';
|
||||||
|
|
||||||
|
fetch('update_marketing_video_image_v3.php', {
|
||||||
|
method: 'POST',
|
||||||
|
body: formData
|
||||||
|
})
|
||||||
|
.then(response => response.json())
|
||||||
|
.then(data => {
|
||||||
|
if (data.success) {
|
||||||
|
location.reload(); // Recargamos para ver la nueva imagen
|
||||||
|
} else {
|
||||||
|
alert('Error al subir imagen: ' + data.error);
|
||||||
|
cell.innerHTML = originalContent;
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch(err => {
|
||||||
|
console.error(err);
|
||||||
|
alert('Error técnico al subir imagen');
|
||||||
|
cell.innerHTML = originalContent;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Script de apertura forzada y diagnóstico
|
||||||
|
document.addEventListener('DOMContentLoaded', function() {
|
||||||
|
const btn = document.getElementById('btnAbrirModalV3');
|
||||||
|
const modalEl = document.getElementById('modalNuevoV3');
|
||||||
|
|
||||||
|
if (btn && modalEl) {
|
||||||
|
btn.addEventListener('click', function() {
|
||||||
|
console.log('Intento de apertura manual del modal...');
|
||||||
|
try {
|
||||||
|
// Intento 1: Usando la API de Bootstrap 5
|
||||||
|
if (typeof bootstrap !== 'undefined') {
|
||||||
|
const modalInstance = new bootstrap.Modal(modalEl);
|
||||||
|
modalInstance.show();
|
||||||
|
console.log('Modal abierto con bootstrap.Modal');
|
||||||
|
} else {
|
||||||
|
// Intento 2: Si bootstrap no está definido, intentar vía jQuery si existe
|
||||||
|
if (typeof $ !== 'undefined' && typeof $.fn.modal !== 'undefined') {
|
||||||
|
$(modalEl).modal('show');
|
||||||
|
console.log('Modal abierto con jQuery');
|
||||||
|
} else {
|
||||||
|
alert('Error: No se encontró la librería de Bootstrap. Por favor, recarga la página.');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
console.error('Error al abrir modal:', err);
|
||||||
|
alert('Hubo un problema técnico al abrir la ventana. Error: ' + err.message);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
console.error('No se encontró el botón o el modal en el DOM');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<?php include 'layout_footer.php'; ?>
|
||||||
1349
call_center_pro.php
Normal file
40
check_duplicate_operation.php
Normal file
@ -0,0 +1,40 @@
|
|||||||
|
<?php
|
||||||
|
header('Content-Type: application/json');
|
||||||
|
session_start();
|
||||||
|
|
||||||
|
if (!isset($_SESSION['user_id'])) {
|
||||||
|
echo json_encode(['error' => 'No autorizado']);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
require_once 'db/config.php';
|
||||||
|
$pdo = db();
|
||||||
|
|
||||||
|
$numero_operacion = trim($_GET['numero_operacion'] ?? '');
|
||||||
|
$pedido_id = $_GET['pedido_id'] ?? null;
|
||||||
|
|
||||||
|
if (empty($numero_operacion)) {
|
||||||
|
echo json_encode(['duplicate' => false]);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
$sql = "SELECT id FROM pedidos WHERE numero_operacion = :numero_operacion";
|
||||||
|
$params = [':numero_operacion' => $numero_operacion];
|
||||||
|
|
||||||
|
if ($pedido_id) {
|
||||||
|
$sql .= " AND id != :pedido_id";
|
||||||
|
$params[':pedido_id'] = $pedido_id;
|
||||||
|
}
|
||||||
|
|
||||||
|
$stmt = $pdo->prepare($sql);
|
||||||
|
$stmt->execute($params);
|
||||||
|
$result = $stmt->fetch();
|
||||||
|
|
||||||
|
if ($result) {
|
||||||
|
echo json_encode([
|
||||||
|
'duplicate' => true,
|
||||||
|
'pedido_id' => $result['id']
|
||||||
|
]);
|
||||||
|
} else {
|
||||||
|
echo json_encode(['duplicate' => false]);
|
||||||
|
}
|
||||||
87
cobertura.php
Normal file
@ -0,0 +1,87 @@
|
|||||||
|
<?php
|
||||||
|
session_start();
|
||||||
|
if (!isset($_SESSION['user_id'])) {
|
||||||
|
header('Location: login.php');
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
require_once 'layout_header.php';
|
||||||
|
require_once 'db/config.php';
|
||||||
|
|
||||||
|
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';
|
||||||
|
?>
|
||||||
|
|
||||||
|
<div class="container-fluid mt-4">
|
||||||
|
<h1>Gestión de Cobertura</h1>
|
||||||
|
|
||||||
|
<div class="card">
|
||||||
|
<div class="card-header">
|
||||||
|
Banner de la Página de Cobertura
|
||||||
|
</div>
|
||||||
|
<div class="card-body">
|
||||||
|
<?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">
|
||||||
|
<?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 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 table-striped">
|
||||||
|
<thead class="thead-dark">
|
||||||
|
<tr>
|
||||||
|
<th>Título</th>
|
||||||
|
<th>Descripción</th>
|
||||||
|
<th style="width: 100px;">Acciones</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<?php if (empty($coberturas)): ?>
|
||||||
|
<tr>
|
||||||
|
<td colspan="3" class="text-center">No hay zonas de cobertura definidas.</td>
|
||||||
|
</tr>
|
||||||
|
<?php else: ?>
|
||||||
|
<?php foreach ($coberturas as $row): ?>
|
||||||
|
<tr>
|
||||||
|
<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>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<?php require_once 'layout_footer.php'; ?>
|
||||||
52
cobertura_xpress.php
Normal file
@ -0,0 +1,52 @@
|
|||||||
|
<?php
|
||||||
|
session_start();
|
||||||
|
if (!isset($_SESSION['user_id'])) {
|
||||||
|
header('Location: login.php');
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
require_once 'layout_header.php';
|
||||||
|
require_once 'db/config.php';
|
||||||
|
|
||||||
|
$pdo = db();
|
||||||
|
$cobertura_xpress_banner = 'assets/uploads/cobertura_xpress_banner.jpg';
|
||||||
|
|
||||||
|
?>
|
||||||
|
|
||||||
|
<div class="container-fluid mt-4">
|
||||||
|
<h1>Gestión de Cobertura Xpress</h1>
|
||||||
|
|
||||||
|
<div class="card">
|
||||||
|
<div class="card-header">
|
||||||
|
Banner de la Página de Cobertura Xpress
|
||||||
|
</div>
|
||||||
|
<div class="card-body">
|
||||||
|
<?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">
|
||||||
|
<?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 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">
|
||||||
|
<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>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<?php require_once 'layout_footer.php'; ?>
|
||||||
497
completados.php
Normal file
@ -0,0 +1,497 @@
|
|||||||
|
<?php
|
||||||
|
session_start();
|
||||||
|
if (!isset($_SESSION['user_id'])) {
|
||||||
|
header('Location: login.php');
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
require_once 'db/config.php';
|
||||||
|
|
||||||
|
function getStatusStyle($status) {
|
||||||
|
$style = 'color: white;'; // Default text color
|
||||||
|
$bgColor = '#0dcaf0'; // Default info blue
|
||||||
|
|
||||||
|
switch (strtoupper(trim($status))) {
|
||||||
|
case 'ROTULADO':
|
||||||
|
$bgColor = '#ffc107'; // yellow
|
||||||
|
$style = 'color: black;';
|
||||||
|
break;
|
||||||
|
case 'EN TRANSITO':
|
||||||
|
$bgColor = '#90EE90'; // light green
|
||||||
|
$style = 'color: black;';
|
||||||
|
break;
|
||||||
|
case 'EN DESTINO':
|
||||||
|
$bgColor = '#800080'; // purple
|
||||||
|
break;
|
||||||
|
case 'COMPLETADO':
|
||||||
|
case 'COMPLETADO ✅':
|
||||||
|
$bgColor = '#198754'; // dark green
|
||||||
|
break;
|
||||||
|
case 'GESTION':
|
||||||
|
$bgColor = '#6c757d'; // secondary grey
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
return "background-color: {$bgColor} !important; {$style}";
|
||||||
|
}
|
||||||
|
|
||||||
|
$pdo = db();
|
||||||
|
|
||||||
|
$user_id = $_SESSION['user_id'];
|
||||||
|
$user_role = $_SESSION['user_role'] ?? 'Asesor';
|
||||||
|
|
||||||
|
// Fetch years for the filter
|
||||||
|
$years_query = "SELECT DISTINCT YEAR(created_at) as year FROM pedidos WHERE estado = 'COMPLETADO ✅'";
|
||||||
|
if ($user_role === 'Asesor') {
|
||||||
|
$years_query .= " AND asesor_id = ?";
|
||||||
|
$years_stmt = $pdo->prepare($years_query);
|
||||||
|
$years_stmt->execute([$user_id]);
|
||||||
|
} else {
|
||||||
|
$years_stmt = $pdo->query($years_query);
|
||||||
|
}
|
||||||
|
$years = $years_stmt->fetchAll(PDO::FETCH_COLUMN);
|
||||||
|
|
||||||
|
|
||||||
|
// Filter logic
|
||||||
|
$selected_month = $_GET['mes'] ?? '';
|
||||||
|
$selected_year = $_GET['año'] ?? '';
|
||||||
|
$search_query = $_GET['q'] ?? '';
|
||||||
|
|
||||||
|
$sql = "SELECT p.*, u.nombre_asesor as asesor_nombre FROM pedidos p LEFT JOIN users u ON p.asesor_id = u.id WHERE p.estado = 'COMPLETADO ✅'";
|
||||||
|
$params = [];
|
||||||
|
|
||||||
|
if ($user_role === 'Asesor') {
|
||||||
|
$sql .= " AND p.asesor_id = ?";
|
||||||
|
$params[] = $user_id;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!empty($search_query)) {
|
||||||
|
$sql .= " AND (p.nombre_completo LIKE ? OR p.dni_cliente LIKE ? OR p.celular LIKE ?)";
|
||||||
|
$params[] = "%$search_query%";
|
||||||
|
$params[] = "%$search_query%";
|
||||||
|
$params[] = "%$search_query%";
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!empty($selected_month)) {
|
||||||
|
$sql .= " AND MONTH(p.created_at) = ?";
|
||||||
|
$params[] = $selected_month;
|
||||||
|
}
|
||||||
|
if (!empty($selected_year)) {
|
||||||
|
$sql .= " AND YEAR(p.created_at) = ?";
|
||||||
|
$params[] = $selected_year;
|
||||||
|
}
|
||||||
|
|
||||||
|
$sql .= " ORDER BY p.fecha_completado DESC";
|
||||||
|
$stmt = $pdo->prepare($sql);
|
||||||
|
$stmt->execute($params);
|
||||||
|
$pedidos = $stmt->fetchAll();
|
||||||
|
|
||||||
|
$months = [
|
||||||
|
1 => 'Enero', 2 => 'Febrero', 3 => 'Marzo', 4 => 'Abril', 5 => 'Mayo', 6 => 'Junio',
|
||||||
|
7 => 'Julio', 8 => 'Agosto', 9 => 'Septiembre', 10 => 'Octubre', 11 => 'Noviembre', 12 => 'Diciembre'
|
||||||
|
];
|
||||||
|
|
||||||
|
?>
|
||||||
|
<?php
|
||||||
|
$pageTitle = "Pedidos Completados";
|
||||||
|
include 'layout_header.php';
|
||||||
|
?>
|
||||||
|
|
||||||
|
<div class="card mb-4">
|
||||||
|
<div class="card-body">
|
||||||
|
<form method="GET" action="completados.php" class="row g-3 align-items-center">
|
||||||
|
<div class="col-auto">
|
||||||
|
<label for="q" class="form-label">Buscar</label>
|
||||||
|
<input type="text" name="q" id="q" class="form-control" value="<?php echo htmlspecialchars($_GET['q'] ?? ''); ?>" placeholder="Nombre, DNI o Celular">
|
||||||
|
</div>
|
||||||
|
<div class="col-auto">
|
||||||
|
<label for="mes" class="form-label">Mes</label>
|
||||||
|
<select name="mes" id="mes" class="form-select">
|
||||||
|
<option value="">Todos</option>
|
||||||
|
<?php foreach ($months as $num => $name): ?>
|
||||||
|
<option value="<?php echo $num; ?>" <?php echo $selected_month == $num ? 'selected' : ''; ?>><?php echo $name; ?></option>
|
||||||
|
<?php endforeach; ?>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
<div class="col-auto">
|
||||||
|
<label for="año" class="form-label">Año</label>
|
||||||
|
<select name="año" id="año" class="form-select">
|
||||||
|
<option value="">Todos</option>
|
||||||
|
<?php foreach ($years as $year): ?>
|
||||||
|
<option value="<?php echo $year; ?>" <?php echo $selected_year == $year ? 'selected' : ''; ?>><?php echo $year; ?></option>
|
||||||
|
<?php endforeach; ?>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
<div class="col-auto mt-4">
|
||||||
|
<button type="submit" class="btn btn-info">Filtrar</button>
|
||||||
|
<a href="completados.php" class="btn btn-secondary">Limpiar</a>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="card">
|
||||||
|
<div class="card-body">
|
||||||
|
<div class="table-responsive">
|
||||||
|
<table class="table table-striped">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>ID</th>
|
||||||
|
<th>Cliente</th>
|
||||||
|
<th>Celular</th>
|
||||||
|
<th>Agencia</th>
|
||||||
|
<th>Producto</th>
|
||||||
|
<th>Monto Total</th>
|
||||||
|
<th>Monto Debe</th>
|
||||||
|
<th>Nro. Operación</th>
|
||||||
|
<th>Clave</th>
|
||||||
|
<th>Banco</th>
|
||||||
|
<th>Recojo Cliente (Día y Hora)</th>
|
||||||
|
<th>Estado</th>
|
||||||
|
<th>Asesor</th>
|
||||||
|
<th>Fecha Creación</th>
|
||||||
|
<th>Fecha Completado</th>
|
||||||
|
<th>Voucher Restante</th>
|
||||||
|
<th>Verificación de Pago</th>
|
||||||
|
<th>OBSERVACION</th>
|
||||||
|
<th>Acciones</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<?php if (empty($pedidos)): ?>
|
||||||
|
<tr>
|
||||||
|
<td colspan="18" class="text-center">No hay pedidos completados que coincidan con el filtro.</td>
|
||||||
|
</tr>
|
||||||
|
<?php else: ?>
|
||||||
|
<?php foreach ($pedidos as $pedido): ?>
|
||||||
|
<tr>
|
||||||
|
<td><?php echo htmlspecialchars($pedido['id']); ?></td>
|
||||||
|
<td><?php echo htmlspecialchars($pedido['nombre_completo']); ?></td>
|
||||||
|
<td><?php echo htmlspecialchars($pedido['celular']); ?></td>
|
||||||
|
<td><?php echo htmlspecialchars($pedido['agencia'] ?? 'SHALOM'); ?></td>
|
||||||
|
<td><?php echo htmlspecialchars($pedido['producto']); ?></td>
|
||||||
|
<td><?php echo htmlspecialchars($pedido['monto_total']); ?></td>
|
||||||
|
<td><?php echo htmlspecialchars($pedido['monto_debe']); ?></td>
|
||||||
|
<td class="editable" data-id="<?php echo $pedido['id']; ?>" data-field="numero_operacion">
|
||||||
|
<?php echo htmlspecialchars($pedido['numero_operacion'] ?? 'N/A'); ?>
|
||||||
|
</td>
|
||||||
|
<?php
|
||||||
|
$canSeeClave = ($user_role !== 'Asesor' || (!empty($pedido['numero_operacion']) && !empty($pedido['banco'])));
|
||||||
|
$isEditableClave = ($user_role !== 'Asesor') ? 'editable' : '';
|
||||||
|
?>
|
||||||
|
<td class="<?php echo $isEditableClave; ?> clave-cell" data-id="<?php echo $pedido['id']; ?>" data-field="clave" id="clave-<?php echo $pedido['id']; ?>">
|
||||||
|
<?php echo $canSeeClave ? htmlspecialchars($pedido['clave'] ?? 'N/A') : '<i class="fas fa-eye-slash text-muted" title="Suba el número de operación y seleccione el banco para ver la clave"></i> <span class="text-muted" style="font-size: 0.8rem;">Oculto</span>'; ?>
|
||||||
|
</td>
|
||||||
|
<td class="editable-select" data-id="<?php echo $pedido['id']; ?>" data-field="banco">
|
||||||
|
<?php echo !empty($pedido['banco']) ? htmlspecialchars($pedido['banco']) : 'N/A'; ?>
|
||||||
|
</td>
|
||||||
|
<td <?php if (in_array($user_role, ['Administrador', 'personal', 'Verificador de Pagos', 'Control Logistico', 'Logistica']) || strpos($user_role, 'Asesor') !== false) { echo 'class="editable-recojo" data-id="'.$pedido['id'].'" title="Doble clic para editar"'; } ?>>
|
||||||
|
<?php echo !empty($pedido['fecha_recojo']) ? htmlspecialchars($pedido['fecha_recojo']) : 'N/A'; ?>
|
||||||
|
</td>
|
||||||
|
<td><span class="badge" style="<?php echo getStatusStyle($pedido['estado']); ?>"><?php echo ($pedido['estado'] == 'Gestion') ? 'GESTIONES ⚙️' : htmlspecialchars($pedido['estado']); ?></span></td>
|
||||||
|
<td><?php echo htmlspecialchars($pedido['asesor_nombre'] ?? 'N/A'); ?></td>
|
||||||
|
<td><?php echo htmlspecialchars($pedido['created_at']); ?></td>
|
||||||
|
<td><?php
|
||||||
|
if (!empty($pedido['fecha_completado'])) {
|
||||||
|
try {
|
||||||
|
// Create DateTime object directly, assuming server timezone is correct
|
||||||
|
$date = new DateTime($pedido['fecha_completado']);
|
||||||
|
// Format to d/m/Y H:i:s
|
||||||
|
echo htmlspecialchars($date->format('d/m/Y H:i:s'));
|
||||||
|
} catch (Exception $e) {
|
||||||
|
// Fallback for invalid date formats, which is the likely issue
|
||||||
|
echo htmlspecialchars($pedido['fecha_completado']);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
echo 'N/A';
|
||||||
|
}
|
||||||
|
?></td>
|
||||||
|
<td>
|
||||||
|
<?php if (!empty($pedido['voucher_restante_path'])): ?>
|
||||||
|
<a href="<?php echo htmlspecialchars($pedido['voucher_restante_path']); ?>" target="_blank">Ver</a>
|
||||||
|
<?php else: ?>
|
||||||
|
N/A
|
||||||
|
<?php endif; ?>
|
||||||
|
</td>
|
||||||
|
<td class="<?php echo (isset($pedido['estado_pago']) && $pedido['estado_pago'] == 'Verificado') ? 'td-pago-verificado' : ''; ?>">
|
||||||
|
<?php
|
||||||
|
$estado_pago_class = '';
|
||||||
|
if (isset($pedido['estado_pago'])) {
|
||||||
|
if ($pedido['estado_pago'] == 'Pendiente a verificación') {
|
||||||
|
$estado_pago_class = 'estado-pago-pendiente';
|
||||||
|
} elseif ($pedido['estado_pago'] == 'Verificado') {
|
||||||
|
$estado_pago_class = 'estado-pago-verificado';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
?>
|
||||||
|
<span class="badge <?php echo $estado_pago_class; ?>">
|
||||||
|
<?php if (in_array($user_role, ['Administrador', 'Verificador de Pagos'])): ?>
|
||||||
|
<select class="form-select-pago" onchange="updateEstadoPago(<?php echo $pedido['id']; ?>, this)">
|
||||||
|
<option value="Pendiente a verificación" <?php echo ($pedido['estado_pago'] == 'Pendiente a verificación') ? 'selected' : ''; ?>>Pendiente a verificación</option>
|
||||||
|
<option value="Verificado" <?php echo ($pedido['estado_pago'] == 'Verificado') ? 'selected' : ''; ?>>Verificado</option>
|
||||||
|
</select>
|
||||||
|
<?php else: ?>
|
||||||
|
<?php echo htmlspecialchars($pedido['estado_pago']); ?>
|
||||||
|
<?php endif; ?>
|
||||||
|
</span>
|
||||||
|
</td>
|
||||||
|
<td class="editable" data-id="<?php echo $pedido['id']; ?>" data-field="observacion">
|
||||||
|
<?php echo htmlspecialchars($pedido['observacion'] ?? 'N/A'); ?>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<a href="pedido_form.php?id=<?php echo $pedido['id']; ?>" class="btn btn-sm btn-warning">Editar</a>
|
||||||
|
<?php if ($user_role === 'Administrador'): ?>
|
||||||
|
<a href="delete_pedido.php?id=<?php echo $pedido['id']; ?>" class="btn btn-sm btn-danger" onclick="return confirm('¿Estás seguro de que quieres eliminar este pedido?');">Eliminar</a>
|
||||||
|
<?php endif; ?>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<?php endforeach; ?>
|
||||||
|
<?php endif; ?>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
function updatePedidoField(pedidoId, field, value) {
|
||||||
|
fetch('update_pedido_field.php', {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
},
|
||||||
|
body: JSON.stringify({ id: pedidoId, field: field, value: value })
|
||||||
|
})
|
||||||
|
.then(response => response.json())
|
||||||
|
.then(data => {
|
||||||
|
if (data.success) {
|
||||||
|
console.log(`${field} actualizado con éxito.`);
|
||||||
|
|
||||||
|
// Si el usuario es Asesor, verificar si ahora puede ver la clave
|
||||||
|
const userRole = "<?php echo $user_role; ?>";
|
||||||
|
if (userRole === 'Asesor') {
|
||||||
|
const pedido = data.pedido;
|
||||||
|
const claveCell = document.getElementById(`clave-${pedidoId}`);
|
||||||
|
if (pedido.numero_operacion && pedido.banco) {
|
||||||
|
claveCell.innerHTML = pedido.clave || 'N/A';
|
||||||
|
} else {
|
||||||
|
claveCell.innerHTML = '<i class="fas fa-eye-slash text-muted" title="Suba el número de operación y seleccione el banco para ver la clave"></i> <span class="text-muted" style="font-size: 0.8rem;">Oculto</span>';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if (data.error) {
|
||||||
|
alert('Error: ' + data.error);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch(error => {
|
||||||
|
console.error('Error:', error);
|
||||||
|
alert('Hubo un error de conexión.');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function updateEstadoPago(pedidoId, selectElement) {
|
||||||
|
const estado = selectElement.value;
|
||||||
|
const badge = selectElement.closest('.badge');
|
||||||
|
const td = selectElement.closest('td');
|
||||||
|
|
||||||
|
// Guardar el estado original en caso de error
|
||||||
|
const originalOption = Array.from(selectElement.options).find(opt => opt.defaultSelected);
|
||||||
|
const originalState = originalOption ? originalOption.value : selectElement.value;
|
||||||
|
|
||||||
|
console.log(`Actualizando pedido ${pedidoId} a estado: ${estado}`);
|
||||||
|
|
||||||
|
const formData = new URLSearchParams();
|
||||||
|
formData.append('pedido_id', pedidoId);
|
||||||
|
formData.append('estado_pago', estado);
|
||||||
|
|
||||||
|
fetch('update_pago.php', {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/x-www-form-urlencoded',
|
||||||
|
},
|
||||||
|
body: formData
|
||||||
|
})
|
||||||
|
.then(response => {
|
||||||
|
if (!response.ok) {
|
||||||
|
throw new Error('Respuesta de red no OK');
|
||||||
|
}
|
||||||
|
return response.json();
|
||||||
|
})
|
||||||
|
.then(data => {
|
||||||
|
if (data.success) {
|
||||||
|
console.log('Estado de pago actualizado con éxito en el servidor.');
|
||||||
|
|
||||||
|
// Limpiar clases previas
|
||||||
|
badge.classList.remove('estado-pago-pendiente', 'estado-pago-verificado');
|
||||||
|
td.classList.remove('td-pago-verificado');
|
||||||
|
|
||||||
|
// Aplicar nuevas clases según el estado
|
||||||
|
if (estado === 'Pendiente a verificación') {
|
||||||
|
badge.classList.add('estado-pago-pendiente');
|
||||||
|
} else if (estado === 'Verificado') {
|
||||||
|
badge.classList.add('estado-pago-verificado');
|
||||||
|
td.classList.add('td-pago-verificado');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Actualizar el estado 'defaultSelected' para futuras reversiones
|
||||||
|
Array.from(selectElement.options).forEach(opt => {
|
||||||
|
opt.defaultSelected = (opt.value === estado);
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
console.error('Error del servidor:', data.message);
|
||||||
|
alert('Error: ' + (data.message || 'No se pudo actualizar el estado.'));
|
||||||
|
selectElement.value = originalState;
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch(error => {
|
||||||
|
console.error('Error de conexión:', error);
|
||||||
|
alert('Hubo un error de conexión al intentar actualizar el estado.');
|
||||||
|
selectElement.value = originalState;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
document.addEventListener('DOMContentLoaded', function() {
|
||||||
|
const userRole = "<?php echo $user_role; ?>";
|
||||||
|
const authorizedRoles = ['Administrador', 'personal', 'Verificador de Pagos', 'Control Logistico', 'Logistica'];
|
||||||
|
|
||||||
|
if (authorizedRoles.includes(userRole) || userRole.includes('Asesor')) {
|
||||||
|
const table = document.querySelector('.table');
|
||||||
|
table.addEventListener('dblclick', function(e) {
|
||||||
|
const cell = e.target.closest('.editable-recojo, .editable, .editable-select');
|
||||||
|
if (!cell) return;
|
||||||
|
|
||||||
|
// Evitar doble edición si ya hay un input o select
|
||||||
|
if (cell.querySelector('input, select')) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const originalText = cell.textContent.trim() === 'N/A' || cell.textContent.trim() === 'Seleccionar' ? '' : cell.textContent.trim();
|
||||||
|
const pedidoId = cell.dataset.id;
|
||||||
|
const field = cell.dataset.field;
|
||||||
|
|
||||||
|
if (cell.classList.contains('editable-select')) {
|
||||||
|
const bancos = ['YAPE', 'PLIN', 'BCP', 'INTERBANK', 'BANCO DE LA NACION', 'BBVA'];
|
||||||
|
let options = '<option value="">Seleccionar</option>';
|
||||||
|
bancos.forEach(b => {
|
||||||
|
options += `<option value="${b}" ${originalText === b ? 'selected' : ''}>${b}</option>`;
|
||||||
|
});
|
||||||
|
cell.innerHTML = `<select class="form-select form-select-sm">${options}</select>`;
|
||||||
|
const select = cell.querySelector('select');
|
||||||
|
select.focus();
|
||||||
|
|
||||||
|
const saveSelectChanges = function() {
|
||||||
|
const newValue = select.value;
|
||||||
|
cell.innerHTML = newValue === '' ? 'N/A' : newValue;
|
||||||
|
if (newValue === originalText) return;
|
||||||
|
updatePedidoField(pedidoId, field, newValue);
|
||||||
|
};
|
||||||
|
|
||||||
|
select.addEventListener('blur', saveSelectChanges);
|
||||||
|
select.addEventListener('change', function() {
|
||||||
|
select.blur();
|
||||||
|
});
|
||||||
|
|
||||||
|
select.addEventListener('keydown', function(e) {
|
||||||
|
if (e.key === 'Escape') {
|
||||||
|
select.removeEventListener('blur', saveSelectChanges);
|
||||||
|
cell.innerHTML = originalText === '' ? 'N/A' : originalText;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
cell.innerHTML = `<input type="text" class="form-control form-control-sm" value="${originalText}">`;
|
||||||
|
const input = cell.querySelector('input');
|
||||||
|
|
||||||
|
input.focus();
|
||||||
|
|
||||||
|
const saveChanges = function() {
|
||||||
|
const newValue = input.value.trim();
|
||||||
|
|
||||||
|
// Reemplazar el input con el texto
|
||||||
|
cell.innerHTML = newValue === '' ? 'N/A' : newValue;
|
||||||
|
|
||||||
|
// Si el valor no ha cambiado, no hacer nada
|
||||||
|
if (newValue === originalText) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (cell.classList.contains('editable-recojo')) {
|
||||||
|
const formData = new URLSearchParams();
|
||||||
|
formData.append('id', pedidoId);
|
||||||
|
formData.append('fecha_recojo', newValue);
|
||||||
|
|
||||||
|
fetch('update_recojo.php', {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/x-www-form-urlencoded',
|
||||||
|
},
|
||||||
|
body: formData
|
||||||
|
})
|
||||||
|
.then(response => response.json())
|
||||||
|
.then(data => {
|
||||||
|
if (!data.success) {
|
||||||
|
console.error('Error al guardar:', data.message);
|
||||||
|
cell.innerHTML = originalText === '' ? 'N/A' : originalText; // Revertir en caso de error
|
||||||
|
alert('No se pudo guardar el cambio. Por favor, inténtalo de nuevo.');
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch(error => {
|
||||||
|
console.error('Error de red:', error);
|
||||||
|
cell.innerHTML = originalText === '' ? 'N/A' : originalText; // Revertir en caso de error
|
||||||
|
alert('Error de conexión. No se pudo guardar el cambio.');
|
||||||
|
});
|
||||||
|
} // Cierre de if (cell.classList.contains('editable-recojo'))
|
||||||
|
|
||||||
|
if (field === 'clave' || field === 'numero_operacion' || field === 'observacion') {
|
||||||
|
if (field === 'numero_operacion' && newValue !== '' && newValue.length < 6) {
|
||||||
|
alert('El número de operación debe tener al menos 6 dígitos.');
|
||||||
|
cell.innerHTML = originalText === '' ? 'N/A' : originalText;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
fetch('update_pedido_field.php', {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
},
|
||||||
|
body: JSON.stringify({ id: pedidoId, field: field, value: newValue })
|
||||||
|
})
|
||||||
|
.then(response => response.json())
|
||||||
|
.then(data => {
|
||||||
|
if (data.success) {
|
||||||
|
// Si es numero_operacion y es Asesor, verificar visibilidad de clave
|
||||||
|
if (field === 'numero_operacion' && userRole === 'Asesor') {
|
||||||
|
const pedido = data.pedido;
|
||||||
|
const claveCell = document.getElementById(`clave-${pedidoId}`);
|
||||||
|
if (pedido.numero_operacion && pedido.banco) {
|
||||||
|
claveCell.innerHTML = pedido.clave || 'N/A';
|
||||||
|
} else {
|
||||||
|
claveCell.innerHTML = '<i class="fas fa-eye-slash text-muted" title="Suba el número de operación y seleccione el banco para ver la clave"></i> <span class="text-muted" style="font-size: 0.8rem;">Oculto</span>';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if (data.error) {
|
||||||
|
console.error('Error al guardar:', data.error);
|
||||||
|
cell.innerHTML = originalText === '' ? 'N/A' : originalText;
|
||||||
|
alert('Error: ' + data.error);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch(error => {
|
||||||
|
console.error('Error de red:', error);
|
||||||
|
cell.innerHTML = originalText === '' ? 'N/A' : originalText;
|
||||||
|
alert('Error de conexión.');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
input.addEventListener('blur', saveChanges);
|
||||||
|
input.addEventListener('keydown', function(e) {
|
||||||
|
if (e.key === 'Enter') {
|
||||||
|
input.blur(); // Dispara el evento blur para guardar
|
||||||
|
} else if (e.key === 'Escape') {
|
||||||
|
input.removeEventListener('blur', saveChanges); // Evitar que se guarde
|
||||||
|
cell.innerHTML = originalText === '' ? 'N/A' : originalText;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<?php include 'layout_footer.php'; ?>
|
||||||
6
composer.json
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
{
|
||||||
|
"require": {
|
||||||
|
"shuchkin/simplexlsxgen": "^1.5",
|
||||||
|
"google/apiclient": "^2.15"
|
||||||
|
}
|
||||||
|
}
|
||||||
1117
composer.lock
generated
Normal file
407
configuracion.php
Normal file
@ -0,0 +1,407 @@
|
|||||||
|
<?php
|
||||||
|
$pageTitle = "Configuración General";
|
||||||
|
require_once 'layout_header.php';
|
||||||
|
require_once 'db/config.php';
|
||||||
|
|
||||||
|
// Asegurarse de que el usuario sea administrador
|
||||||
|
if ($_SESSION['user_role'] !== 'Administrador' && $_SESSION['user_role'] !== 'admin') {
|
||||||
|
echo "<div class='alert alert-danger'>Acceso denegado.</div>";
|
||||||
|
require_once 'layout_footer.php';
|
||||||
|
exit();
|
||||||
|
}
|
||||||
|
|
||||||
|
$conn = db();
|
||||||
|
|
||||||
|
// --- Procesamiento de Formularios ---
|
||||||
|
|
||||||
|
// Guardar visibilidad de columnas del Kanban
|
||||||
|
if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['save_visibility'])) {
|
||||||
|
$visible_columns = $_POST['visible_columns'] ?? [];
|
||||||
|
$json_visible_columns = json_encode($visible_columns);
|
||||||
|
|
||||||
|
$stmt = $conn->prepare("INSERT INTO configuracion (clave, valor) VALUES ('kanban_visible_columns', :valor) ON DUPLICATE KEY UPDATE valor = :valor");
|
||||||
|
$stmt->bindParam(':valor', $json_visible_columns);
|
||||||
|
if ($stmt->execute()) {
|
||||||
|
$_SESSION['success_message'] = 'Visibilidad de columnas actualizada.';
|
||||||
|
} else {
|
||||||
|
$_SESSION['error_message'] = 'Error al actualizar la visibilidad.';
|
||||||
|
}
|
||||||
|
header("Location: configuracion.php");
|
||||||
|
exit();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sincronizar sedes de Shalom
|
||||||
|
if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['sync_shalom'])) {
|
||||||
|
$apiKey = 'sk_mlq1j3na_4a676ewvaop';
|
||||||
|
$url = "https://shalom-api.lat/api/listar";
|
||||||
|
|
||||||
|
$ch = curl_init();
|
||||||
|
curl_setopt($ch, CURLOPT_URL, $url);
|
||||||
|
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
|
||||||
|
curl_setopt($ch, CURLOPT_HTTPHEADER, [
|
||||||
|
'Accept: application/json',
|
||||||
|
"x-api-key: {$apiKey}"
|
||||||
|
]);
|
||||||
|
|
||||||
|
$response = curl_exec($ch);
|
||||||
|
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
|
||||||
|
curl_close($ch);
|
||||||
|
|
||||||
|
if ($httpCode === 200) {
|
||||||
|
$data = json_decode($response, true);
|
||||||
|
if ($data && isset($data['success']) && $data['success'] && isset($data['data'])) {
|
||||||
|
$sedes = $data['data'];
|
||||||
|
$count = 0;
|
||||||
|
foreach ($sedes as $sede) {
|
||||||
|
$id = $sede['ter_id'];
|
||||||
|
// Usamos el campo 'nombre' que es más completo (Departamento / Provincia / Distrito / Lugar)
|
||||||
|
$nombre = $sede['nombre'];
|
||||||
|
$stmt = $conn->prepare("INSERT INTO sedes_shalom (id_terminal, nombre_sede, activo, permite_origen, permite_destino)
|
||||||
|
VALUES (?, ?, 1, 1, 1)
|
||||||
|
ON DUPLICATE KEY UPDATE nombre_sede = VALUES(nombre_sede)");
|
||||||
|
$stmt->execute([$id, $nombre]);
|
||||||
|
$count++;
|
||||||
|
}
|
||||||
|
$_SESSION['success_message'] = "Sincronización completada. Se procesaron $count sedes.";
|
||||||
|
} else {
|
||||||
|
$_SESSION['error_message'] = "Error al procesar los datos de la API.";
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
$_SESSION['error_message'] = "Error al conectar con la API de Shalom (Código: $httpCode).";
|
||||||
|
}
|
||||||
|
header("Location: configuracion.php");
|
||||||
|
exit();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// --- Carga de Datos para la Vista ---
|
||||||
|
|
||||||
|
// Cargar configuraciones generales
|
||||||
|
$stmt_config = $conn->query("SELECT clave, valor FROM configuracion");
|
||||||
|
$configs = $stmt_config->fetchAll(PDO::FETCH_KEY_PAIR);
|
||||||
|
|
||||||
|
$banner_text = $configs['banner_text'] ?? '';
|
||||||
|
$whatsapp_template_notificacion = $configs['whatsapp_template_notificacion'] ?? ($configs['whatsapp_template_contraentrega'] ?? '');
|
||||||
|
|
||||||
|
// Datos para la gestión de columnas
|
||||||
|
$stmt_manage_columns = $conn->query("SELECT * FROM kanban_columns ORDER BY orden ASC");
|
||||||
|
$management_columns = $stmt_manage_columns->fetchAll(PDO::FETCH_ASSOC);
|
||||||
|
|
||||||
|
// Datos para la visibilidad de columnas
|
||||||
|
$stmt_visible = $conn->prepare("SELECT valor FROM configuracion WHERE clave = 'kanban_visible_columns'");
|
||||||
|
$stmt_visible->execute();
|
||||||
|
$config_row = $stmt_visible->fetch(PDO::FETCH_ASSOC);
|
||||||
|
$visible_columns = $config_row ? json_decode($config_row['valor'], true) : [];
|
||||||
|
$stmt_all_db_columns = $conn->query("SELECT nombre FROM kanban_columns ORDER BY orden ASC");
|
||||||
|
$available_columns_for_visibility = $stmt_all_db_columns->fetchAll(PDO::FETCH_COLUMN);
|
||||||
|
|
||||||
|
// --- Datos para la GESTIÓN DE CONTENIDO DEL KANBAN ---
|
||||||
|
$products = $conn->query("SELECT id, nombre FROM products ORDER BY nombre ASC")->fetchAll(PDO::FETCH_ASSOC);
|
||||||
|
$kanban_columns_for_cards = $conn->query("SELECT id, nombre FROM kanban_columns ORDER BY orden, id")->fetchAll(PDO::FETCH_ASSOC);
|
||||||
|
$info_cards = $conn->query("
|
||||||
|
SELECT ip.*, p.nombre as producto_nombre, kc.nombre as column_nombre
|
||||||
|
FROM info_productos ip
|
||||||
|
LEFT JOIN products p ON ip.producto_id = p.id
|
||||||
|
LEFT JOIN kanban_columns kc ON ip.column_id = kc.id
|
||||||
|
ORDER BY ip.orden, ip.id DESC
|
||||||
|
")->fetchAll(PDO::FETCH_ASSOC);
|
||||||
|
|
||||||
|
|
||||||
|
// Mostrar mensajes de éxito o error
|
||||||
|
if (isset($_SESSION['success_message'])) {
|
||||||
|
echo "<div class='alert alert-success'>".$_SESSION['success_message']."</div>";
|
||||||
|
unset($_SESSION['success_message']);
|
||||||
|
}
|
||||||
|
if (isset($_SESSION['error_message'])) {
|
||||||
|
echo "<div class='alert alert-danger'>".$_SESSION['error_message']."</div>";
|
||||||
|
unset($_SESSION['error_message']);
|
||||||
|
}
|
||||||
|
?>
|
||||||
|
|
||||||
|
<div class="container mt-5">
|
||||||
|
|
||||||
|
<!-- SECCIÓN PARA SINCRONIZAR SEDES DE SHALOM -->
|
||||||
|
<div class="card mb-5">
|
||||||
|
<div class="card-header">
|
||||||
|
<h3>Sincronización de Sedes Shalom</h3>
|
||||||
|
</div>
|
||||||
|
<div class="card-body">
|
||||||
|
<p>Conéctate directamente con la API de Shalom para actualizar el listado de agencias disponibles en el sistema.</p>
|
||||||
|
<form action="configuracion.php" method="post">
|
||||||
|
<input type="hidden" name="sync_shalom" value="1">
|
||||||
|
<button type="submit" class="btn btn-info">
|
||||||
|
<i class="fas fa-sync"></i> Sincronizar Sedes Ahora
|
||||||
|
</button>
|
||||||
|
</form>
|
||||||
|
<?php
|
||||||
|
// Mostrar resultado de la sincronización si existe
|
||||||
|
if (isset($_SESSION['sync_result'])) {
|
||||||
|
echo "<div class='alert alert-info mt-3'>" . $_SESSION['sync_result'] . "</div>";
|
||||||
|
unset($_SESSION['sync_result']);
|
||||||
|
}
|
||||||
|
?>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- SECCIÓN PARA GESTIONAR CONTENIDO DEL KANBAN (TARJETAS) -->
|
||||||
|
<div class="card mb-5">
|
||||||
|
<div class="card-header">
|
||||||
|
<h3>Gestionar Contenido del Kanban</h3>
|
||||||
|
</div>
|
||||||
|
<div class="card-body">
|
||||||
|
<p>Aquí puedes crear, editar y eliminar las tarjetas de información que aparecen en el tablero Kanban.</p>
|
||||||
|
|
||||||
|
<!-- Formulario para añadir/editar tarjeta -->
|
||||||
|
<div class="card mb-4">
|
||||||
|
<div class="card-header">
|
||||||
|
<h5 id="form-title">Añadir Nueva Tarjeta</h5>
|
||||||
|
</div>
|
||||||
|
<div class="card-body">
|
||||||
|
<form action="save_info_producto.php" method="post" enctype="multipart/form-data">
|
||||||
|
<input type="hidden" name="id" id="info-id">
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="producto_id">Producto Asociado (Opcional)</label>
|
||||||
|
<select name="producto_id" id="producto_id" class="form-control">
|
||||||
|
<option value="">Selecciona un producto</option>
|
||||||
|
<?php foreach ($products as $product): ?>
|
||||||
|
<option value="<?php echo $product['id']; ?>"><?php echo htmlspecialchars($product['nombre']); ?></option>
|
||||||
|
<?php endforeach; ?>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
<div class="form-group mt-3">
|
||||||
|
<label for="texto_informativo">Texto de la Tarjeta</label>
|
||||||
|
<textarea name="texto_informativo" id="texto_informativo" class="form-control" rows="3" required></textarea>
|
||||||
|
</div>
|
||||||
|
<div class="form-group mt-3">
|
||||||
|
<label for="imagen">Imagen de la Tarjeta</label>
|
||||||
|
<div id="image-preview" class="mt-2 mb-2"></div>
|
||||||
|
<input type="file" name="imagen" id="imagen" class="form-control-file">
|
||||||
|
<input type="hidden" name="current_imagen" id="current_imagen">
|
||||||
|
<small class="form-text text-muted">Sube una imagen para añadir o reemplazar la actual.</small>
|
||||||
|
</div>
|
||||||
|
<div class="form-group mt-3">
|
||||||
|
<label for="column_id">Columna del Kanban</label>
|
||||||
|
<select name="column_id" id="column_id" class="form-control" required>
|
||||||
|
<option value="">Selecciona una columna</option>
|
||||||
|
<?php foreach ($kanban_columns_for_cards as $column): ?>
|
||||||
|
<option value="<?php echo $column['id']; ?>"><?php echo htmlspecialchars($column['nombre']); ?></option>
|
||||||
|
<?php endforeach; ?>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
<button type="submit" class="btn btn-primary mt-3">Guardar Tarjeta</button>
|
||||||
|
<button type="button" class="btn btn-secondary mt-3" onclick="resetForm()">Limpiar</button>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Lista de tarjetas existentes -->
|
||||||
|
<div class="card">
|
||||||
|
<div class="card-header">
|
||||||
|
<h5>Tarjetas Existentes</h5>
|
||||||
|
</div>
|
||||||
|
<div class="card-body">
|
||||||
|
<div class="table-responsive">
|
||||||
|
<table class="table table-bordered table-striped">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>Producto</th>
|
||||||
|
<th>Imagen</th>
|
||||||
|
<th>Texto</th>
|
||||||
|
<th>Columna</th>
|
||||||
|
<th style="width: 120px;">Acciones</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<?php if (empty($info_cards)): ?>
|
||||||
|
<tr><td colspan="5" class="text-center">No hay tarjetas creadas.</td></tr>
|
||||||
|
<?php else: ?>
|
||||||
|
<?php foreach ($info_cards as $card): ?>
|
||||||
|
<tr>
|
||||||
|
<td><?php echo htmlspecialchars($card['producto_nombre'] ?: 'N/A'); ?></td>
|
||||||
|
<td>
|
||||||
|
<?php if (!empty($card['imagen_url'])): ?>
|
||||||
|
<img src="<?php echo htmlspecialchars($card['imagen_url']); ?>?t=<?php echo time(); ?>" alt="Imagen" style="width: 100px; height: auto; border-radius: 5px;">
|
||||||
|
<?php endif; ?>
|
||||||
|
</td>
|
||||||
|
<td><?php echo nl2br(htmlspecialchars($card['texto_informativo'])); ?></td>
|
||||||
|
<td><?php echo htmlspecialchars($card['column_nombre'] ?: 'Sin asignar'); ?></td>
|
||||||
|
<td>
|
||||||
|
<button class="btn btn-sm btn-info mb-1 w-100" onclick='editCard(<?php echo json_encode($card); ?>)'>Editar</button>
|
||||||
|
<a href="delete_info_producto.php?id=<?php echo $card['id']; ?>" class="btn btn-sm btn-danger w-100" onclick="return confirm('¿Seguro?')">Eliminar</a>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<?php endforeach; ?>
|
||||||
|
<?php endif; ?>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Sección para Configuración de Banners y Textos -->
|
||||||
|
<div class="card">
|
||||||
|
<div class="card-body">
|
||||||
|
<h5 class="card-title">Texto del Banner Principal</h5>
|
||||||
|
<p class="card-text">Edita el texto que aparece en el banner de la página principal.</p>
|
||||||
|
<form action="save_banner_text.php" method="post">
|
||||||
|
<div class="form-group">
|
||||||
|
<textarea class="form-control" id="banner_text" name="banner_text" rows="3"><?php echo htmlspecialchars($banner_text); ?></textarea>
|
||||||
|
</div>
|
||||||
|
<button type="submit" class="btn btn-primary mt-2">Guardar Texto</button>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Plantilla de Notificación de WhatsApp -->
|
||||||
|
<div class="card mt-4 border-primary">
|
||||||
|
<div class="card-header bg-primary text-white">
|
||||||
|
<h5 class="card-title mb-0"><i class="fab fa-whatsapp"></i> PLANTILLA DE NOTIFICACIÓN</h5>
|
||||||
|
</div>
|
||||||
|
<div class="card-body">
|
||||||
|
<p class="card-text text-muted">
|
||||||
|
Este es el mensaje que se enviará a los clientes para notificarles sobre su pedido (disponible en <strong>Pedidos en Tránsito</strong> y <strong>Ruta Contraentrega</strong>).
|
||||||
|
Puedes personalizarlo usando las siguientes etiquetas:
|
||||||
|
</p>
|
||||||
|
<div class="alert alert-light border">
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-md-6">
|
||||||
|
<ul class="mb-0">
|
||||||
|
<li><code>{NOMBRE_CLIENTE}</code>: Nombre del cliente.</li>
|
||||||
|
<li><code>{PRODUCTO}</code>: Nombre del producto.</li>
|
||||||
|
<li><code>{SEDE_ENVIO}</code>: Agencia de destino.</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
<div class="col-md-6">
|
||||||
|
<ul class="mb-0">
|
||||||
|
<li><code>{MONTO_TOTAL}</code>: Precio total.</li>
|
||||||
|
<li><code>{ADELANTO}</code>: Pago realizado.</li>
|
||||||
|
<li><code>{SALDO_PENDIENTE}</code>: Lo que falta pagar.</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<form action="save_whatsapp_template.php" method="post">
|
||||||
|
<input type="hidden" name="template_name" value="whatsapp_template_notificacion">
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="whatsapp_template_notificacion" class="form-label font-weight-bold">Contenido del Mensaje:</label>
|
||||||
|
<textarea class="form-control" id="whatsapp_template_notificacion" name="template_content" rows="10" placeholder="Escribe aquí tu mensaje..."><?php echo htmlspecialchars($whatsapp_template_notificacion); ?></textarea>
|
||||||
|
</div>
|
||||||
|
<div class="mt-3">
|
||||||
|
<button type="submit" class="btn btn-primary">
|
||||||
|
<i class="fas fa-save"></i> Guardar Plantilla
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Sección para Gestionar Columnas (Añadir, Editar, Eliminar) -->
|
||||||
|
<div class="card mb-5">
|
||||||
|
<div class="card-header">
|
||||||
|
<h3>Gestionar Columnas del Kanban</h3>
|
||||||
|
</div>
|
||||||
|
<div class="card-body">
|
||||||
|
<!-- Formulario para añadir nueva columna -->
|
||||||
|
<div class="card mb-4">
|
||||||
|
<div class="card-header">
|
||||||
|
<h6>Añadir Nueva Columna</h6>
|
||||||
|
</div>
|
||||||
|
<div class="card-body">
|
||||||
|
<form action="add_column.php" method="post">
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="nombre">Nombre de la Columna</label>
|
||||||
|
<input type="text" name="nombre" id="nombre" class="form-control" required>
|
||||||
|
</div>
|
||||||
|
<button type="submit" class="btn btn-success mt-3">Añadir Columna</button>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Tabla de columnas existentes -->
|
||||||
|
<h6>Columnas Actuales</h6>
|
||||||
|
<div class="table-responsive">
|
||||||
|
<table class="table table-bordered table-striped">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>Orden</th>
|
||||||
|
<th>Nombre</th>
|
||||||
|
<th>Acciones</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<?php if (empty($management_columns)): ?>
|
||||||
|
<tr><td colspan="3" class="text-center">No hay columnas definidas.</td></tr>
|
||||||
|
<?php else: ?>
|
||||||
|
<?php foreach ($management_columns as $col): ?>
|
||||||
|
<tr>
|
||||||
|
<td><?php echo htmlspecialchars($col['orden']); ?></td>
|
||||||
|
<td><?php echo htmlspecialchars($col['nombre']); ?></td>
|
||||||
|
<td>
|
||||||
|
<a href="edit_column.php?id=<?php echo $col['id']; ?>" class="btn btn-sm btn-info">Editar</a>
|
||||||
|
<a href="delete_column.php?id=<?php echo $col['id']; ?>" class="btn btn-sm btn-danger" onclick="return confirm('¿Estás seguro de que quieres eliminar esta columna?');">Eliminar</a>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<?php endforeach; ?>
|
||||||
|
<?php endif; ?>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Sección para Configurar Visibilidad de Columnas -->
|
||||||
|
<div class="card">
|
||||||
|
<div class="card-header">
|
||||||
|
<h3>Configurar Visibilidad de Columnas en Kanban</h3>
|
||||||
|
</div>
|
||||||
|
<div class="card-body">
|
||||||
|
<p>Selecciona las columnas que quieres que sean visibles en el tablero Kanban principal.</p>
|
||||||
|
<form action="configuracion.php" method="post">
|
||||||
|
<input type="hidden" name="save_visibility" value="1">
|
||||||
|
<div class="form-group">
|
||||||
|
<?php foreach ($available_columns_for_visibility as $column_name): ?>
|
||||||
|
<div class="form-check">
|
||||||
|
<input class="form-check-input" type="checkbox" name="visible_columns[]" value="<?php echo htmlspecialchars($column_name); ?>" id="check_<?php echo htmlspecialchars($column_name); ?>"
|
||||||
|
<?php echo (in_array($column_name, $visible_columns)) ? 'checked' : ''; ?>>
|
||||||
|
<label class="form-check-label" for="check_<?php echo htmlspecialchars($column_name); ?>">
|
||||||
|
<?php echo htmlspecialchars($column_name); ?>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
<?php endforeach; ?>
|
||||||
|
</div>
|
||||||
|
<button type="submit" class="btn btn-primary mt-3">Guardar Visibilidad</button>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
function editCard(card) {
|
||||||
|
document.getElementById('form-title').innerText = 'Editar Tarjeta';
|
||||||
|
document.getElementById('info-id').value = card.id;
|
||||||
|
document.getElementById('producto_id').value = card.producto_id;
|
||||||
|
document.getElementById('texto_informativo').value = card.texto_informativo;
|
||||||
|
document.getElementById('column_id').value = card.column_id;
|
||||||
|
document.getElementById('current_imagen').value = card.imagen_url;
|
||||||
|
|
||||||
|
const imagePreview = document.getElementById('image-preview');
|
||||||
|
imagePreview.innerHTML = '';
|
||||||
|
if (card.imagen_url) {
|
||||||
|
imagePreview.innerHTML = `<p class="mb-1">Imagen actual:</p><img src="${card.imagen_url}?t=${new Date().getTime()}" style="width: 100px; height: auto; border-radius: 5px;"/>`;
|
||||||
|
}
|
||||||
|
|
||||||
|
const formCard = document.querySelector('#form-title').closest('.card');
|
||||||
|
formCard.scrollIntoView({ behavior: 'smooth', block: 'start' });
|
||||||
|
}
|
||||||
|
|
||||||
|
function resetForm() {
|
||||||
|
document.getElementById('form-title').innerText = 'Añadir Nueva Tarjeta';
|
||||||
|
document.getElementById('info-id').value = '';
|
||||||
|
document.querySelector('form[action="save_info_producto.php"]').reset();
|
||||||
|
document.getElementById('image-preview').innerHTML = '';
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<?php require_once 'layout_footer.php'; ?>
|
||||||
89
configuracion_rentabilidad.php
Normal file
@ -0,0 +1,89 @@
|
|||||||
|
<?php
|
||||||
|
session_start();
|
||||||
|
require_once 'db/config.php';
|
||||||
|
|
||||||
|
// Verificar si el usuario es Administrador
|
||||||
|
if (!isset($_SESSION['user_id']) || ($_SESSION['user_role'] !== 'admin' && $_SESSION['user_role'] !== 'Administrador')) {
|
||||||
|
header("Location: login.php");
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
$db = db();
|
||||||
|
$message = '';
|
||||||
|
|
||||||
|
// Crear tabla de configuración si no existe
|
||||||
|
$db->exec("CREATE TABLE IF NOT EXISTS configuracion_costos (
|
||||||
|
id INT AUTO_INCREMENT PRIMARY KEY,
|
||||||
|
clave VARCHAR(50) UNIQUE,
|
||||||
|
valor DECIMAL(10,2)
|
||||||
|
)");
|
||||||
|
|
||||||
|
// Valores por defecto
|
||||||
|
$defaults = [
|
||||||
|
'envio_contraentrega' => 10.00,
|
||||||
|
'envio_agencia' => 12.00,
|
||||||
|
'comision_venta' => 5.00,
|
||||||
|
'publicidad_diaria' => 50.00
|
||||||
|
];
|
||||||
|
|
||||||
|
foreach ($defaults as $clave => $valor) {
|
||||||
|
$stmt = $db->prepare("INSERT IGNORE INTO configuracion_costos (clave, valor) VALUES (?, ?)");
|
||||||
|
$stmt->execute([$clave, $valor]);
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
||||||
|
foreach ($_POST['costos'] as $clave => $valor) {
|
||||||
|
$stmt = $db->prepare("UPDATE configuracion_costos SET valor = ? WHERE clave = ?");
|
||||||
|
$stmt->execute([$valor, $clave]);
|
||||||
|
}
|
||||||
|
$message = '<div class="alert alert-success">Configuración actualizada correctamente.</div>';
|
||||||
|
}
|
||||||
|
|
||||||
|
$stmt = $db->query("SELECT clave, valor FROM configuracion_costos");
|
||||||
|
$config = $stmt->fetchAll(PDO::FETCH_KEY_PAIR);
|
||||||
|
|
||||||
|
$pageTitle = 'Configuración de Rentabilidad';
|
||||||
|
include 'layout_header.php';
|
||||||
|
?>
|
||||||
|
|
||||||
|
<div class="container-fluid mt-4">
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-md-8 offset-md-2">
|
||||||
|
<div class="card shadow">
|
||||||
|
<div class="card-header bg-primary text-white">
|
||||||
|
<h4 class="mb-0"><i class="fas fa-calculator me-2"></i>Configuración de Costos y Rentabilidad</h4>
|
||||||
|
</div>
|
||||||
|
<div class="card-body">
|
||||||
|
<?php echo $message; ?>
|
||||||
|
<form method="POST">
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-md-6 mb-3">
|
||||||
|
<label class="form-label">Costo Envío Contraentrega Estándar (S/)</label>
|
||||||
|
<input type="number" step="0.01" name="costos[envio_contraentrega]" class="form-control" value="<?php echo $config['envio_contraentrega']; ?>">
|
||||||
|
</div>
|
||||||
|
<div class="col-md-6 mb-3">
|
||||||
|
<label class="form-label">Costo Envío Agencia Estándar (S/)</label>
|
||||||
|
<input type="number" step="0.01" name="costos[envio_agencia]" class="form-control" value="<?php echo $config['envio_agencia']; ?>">
|
||||||
|
</div>
|
||||||
|
<div class="col-md-6 mb-3">
|
||||||
|
<label class="form-label">Comisión por Venta Estándar (S/)</label>
|
||||||
|
<input type="number" step="0.01" name="costos[comision_venta]" class="form-control" value="<?php echo $config['comision_venta']; ?>">
|
||||||
|
</div>
|
||||||
|
<div class="col-md-6 mb-3">
|
||||||
|
<label class="form-label">Presupuesto Publicidad Diario Estimado (S/)</label>
|
||||||
|
<input type="number" step="0.01" name="costos[publicidad_diaria]" class="form-control" value="<?php echo $config['publicidad_diaria']; ?>">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<hr>
|
||||||
|
<div class="alert alert-info">
|
||||||
|
<i class="fas fa-info-circle me-2"></i> Estos valores se utilizarán para calcular la rentabilidad automática en los reportes, sin necesidad de llenar datos en cada pedido.
|
||||||
|
</div>
|
||||||
|
<button type="submit" class="btn btn-primary w-100">Guardar Configuración</button>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<?php include 'layout_footer.php'; ?>
|
||||||
685
dashboard.php
Normal file
@ -0,0 +1,685 @@
|
|||||||
|
<?php
|
||||||
|
session_start();
|
||||||
|
if (!isset($_SESSION['user_id'])) {
|
||||||
|
header('Location: login.php');
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
require_once 'db/config.php';
|
||||||
|
|
||||||
|
$user_role = $_SESSION['user_role'];
|
||||||
|
$user_id = $_SESSION['user_id'];
|
||||||
|
$user_name = $_SESSION['user_name'] ?? 'Usuario';
|
||||||
|
|
||||||
|
$pdo = db();
|
||||||
|
$page_title = "Dashboard";
|
||||||
|
|
||||||
|
// Lógica para manejar el filtro de fecha
|
||||||
|
$selected_month = isset($_GET['month']) ? (int)$_GET['month'] : date('m');
|
||||||
|
$selected_year = isset($_GET['year']) ? (int)$_GET['year'] : date('Y');
|
||||||
|
|
||||||
|
$meses = [
|
||||||
|
1 => 'Enero', 2 => 'Febrero', 3 => 'Marzo', 4 => 'Abril', 5 => 'Mayo', 6 => 'Junio',
|
||||||
|
7 => 'Julio', 8 => 'Agosto', 9 => 'Septiembre', 10 => 'Octubre', 11 => 'Noviembre', 12 => 'Diciembre'
|
||||||
|
];
|
||||||
|
|
||||||
|
$current_year = date('Y');
|
||||||
|
$years = range($current_year, $current_year - 5); // Últimos 5 años
|
||||||
|
|
||||||
|
include 'layout_header.php';
|
||||||
|
|
||||||
|
// Contar pedidos del día (esto no cambia con el filtro mensual)
|
||||||
|
$stmt_pedidos_hoy = $pdo->prepare("SELECT COUNT(*) FROM pedidos WHERE DATE(created_at) = CURDATE()");
|
||||||
|
$stmt_pedidos_hoy->execute();
|
||||||
|
$pedidos_hoy = $stmt_pedidos_hoy->fetchColumn();
|
||||||
|
|
||||||
|
// Contar pedidos del mes seleccionado
|
||||||
|
$stmt_pedidos_mes = $pdo->prepare("SELECT COUNT(*) FROM pedidos WHERE MONTH(created_at) = ? AND YEAR(created_at) = ?");
|
||||||
|
$stmt_pedidos_mes->execute([$selected_month, $selected_year]);
|
||||||
|
$pedidos_mes = $stmt_pedidos_mes->fetchColumn();
|
||||||
|
|
||||||
|
// Contar pedidos enviados (esto es un total general, no cambia con el filtro)
|
||||||
|
$stmt_pedidos_enviados = $pdo->prepare("SELECT COUNT(*) FROM pedidos WHERE estado = 'EN TRANSITO 🚛'");
|
||||||
|
$stmt_pedidos_enviados->execute();
|
||||||
|
$pedidos_enviados = $stmt_pedidos_enviados->fetchColumn();
|
||||||
|
|
||||||
|
// Contadores específicos para el rol de Asesor
|
||||||
|
$pedidos_hoy_asesor = 0;
|
||||||
|
$pedidos_mes_asesor = 0;
|
||||||
|
if ($user_role === 'Asesor') {
|
||||||
|
// Contar pedidos del día para el asesor actual
|
||||||
|
$stmt_hoy_asesor = $pdo->prepare("SELECT COUNT(*) FROM pedidos WHERE DATE(created_at) = CURDATE() AND asesor_id = ?");
|
||||||
|
$stmt_hoy_asesor->execute([$user_id]);
|
||||||
|
$pedidos_hoy_asesor = $stmt_hoy_asesor->fetchColumn();
|
||||||
|
|
||||||
|
// Contar pedidos del mes seleccionado para el asesor actual
|
||||||
|
$stmt_mes_asesor = $pdo->prepare("SELECT COUNT(*) FROM pedidos WHERE MONTH(created_at) = ? AND YEAR(created_at) = ? AND asesor_id = ?");
|
||||||
|
$stmt_mes_asesor->execute([$selected_month, $selected_year, $user_id]);
|
||||||
|
$pedidos_mes_asesor = $stmt_mes_asesor->fetchColumn();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Define los estados de los pedidos
|
||||||
|
$estados = ['RUTA_CONTRAENTREGA', 'RETORNADO', 'ENTREGA EXITOSA', 'ROTULADO 📦', 'EN TRANSITO 🚛', 'EN DESTINO 🏬', 'COMPLETADO ✅'];
|
||||||
|
$asesor_data = [];
|
||||||
|
$chart_data = [];
|
||||||
|
|
||||||
|
if ($user_role === 'Administrador' || $user_role === 'Control Logistico' || $user_role === 'Logistica' || $user_role === 'Asesor') {
|
||||||
|
// 1. Obtener todos los asesores
|
||||||
|
$stmt_asesores = $pdo->query("SELECT id, username, nombre_asesor FROM users WHERE role = 'Asesor'");
|
||||||
|
$asesores = $stmt_asesores->fetchAll(PDO::FETCH_ASSOC);
|
||||||
|
|
||||||
|
// 2. Preparar la estructura de datos
|
||||||
|
foreach ($asesores as $asesor) {
|
||||||
|
$asesor_data[$asesor['id']] = [
|
||||||
|
'nombre' => !empty($asesor['nombre_asesor']) ? $asesor['nombre_asesor'] : $asesor['username'],
|
||||||
|
'pedidos' => array_fill_keys($estados, 0),
|
||||||
|
'total' => 0
|
||||||
|
];
|
||||||
|
}
|
||||||
|
// Add a special entry for unassigned orders
|
||||||
|
$asesor_data['sin_asignar'] = [
|
||||||
|
'nombre' => 'Sin Asignar',
|
||||||
|
'pedidos' => array_fill_keys($estados, 0),
|
||||||
|
'total' => 0
|
||||||
|
];
|
||||||
|
|
||||||
|
// 3. Construir y ejecutar una consulta única para todos los pedidos del mes seleccionado
|
||||||
|
$placeholders = rtrim(str_repeat('?,', count($estados)), ',');
|
||||||
|
$sql = "
|
||||||
|
SELECT asesor_id, estado, COUNT(*) as total_pedidos
|
||||||
|
FROM pedidos
|
||||||
|
WHERE estado IN ($placeholders) AND MONTH(created_at) = ? AND YEAR(created_at) = ?
|
||||||
|
GROUP BY asesor_id, estado
|
||||||
|
";
|
||||||
|
$stmt_pedidos = $pdo->prepare($sql);
|
||||||
|
$params = array_merge($estados, [$selected_month, $selected_year]);
|
||||||
|
$stmt_pedidos->execute($params);
|
||||||
|
|
||||||
|
// 4. Llenar la estructura con los datos de la consulta
|
||||||
|
while ($row = $stmt_pedidos->fetch(PDO::FETCH_ASSOC)) {
|
||||||
|
$asesor_id = $row['asesor_id'];
|
||||||
|
$estado = $row['estado'];
|
||||||
|
$total_pedidos = (int)$row['total_pedidos'];
|
||||||
|
|
||||||
|
// Si el asesor_id es nulo o no corresponde a un asesor conocido, se agrupa en "Sin Asignar".
|
||||||
|
if ($asesor_id === null || !isset($asesor_data[$asesor_id])) {
|
||||||
|
$asesor_data['sin_asignar']['pedidos'][$estado] += $total_pedidos;
|
||||||
|
} else {
|
||||||
|
// De lo contrario, se asigna al asesor correspondiente.
|
||||||
|
$asesor_data[$asesor_id]['pedidos'][$estado] = $total_pedidos;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 5. Calcular totales y preparar datos para el gráfico
|
||||||
|
foreach ($asesor_data as $id => &$data) {
|
||||||
|
$data['total'] = array_sum($data['pedidos']);
|
||||||
|
}
|
||||||
|
unset($data);
|
||||||
|
|
||||||
|
// Encontrar a la asesora del mes
|
||||||
|
$top_advisor_name = 'N/A';
|
||||||
|
$top_advisor_total = 0;
|
||||||
|
if (!empty($asesor_data)) {
|
||||||
|
// Clonar el array para no afectar el orden del gráfico
|
||||||
|
$temp_asesor_data = $asesor_data;
|
||||||
|
// Ordenar de forma descendente para encontrar a la mejor
|
||||||
|
uasort($temp_asesor_data, function ($a, $b) {
|
||||||
|
return $b['total'] <=> $a['total'];
|
||||||
|
});
|
||||||
|
$top_advisor = reset($temp_asesor_data); // Obtener la primera
|
||||||
|
if ($top_advisor && $top_advisor['total'] > 0) {
|
||||||
|
$top_advisor_name = $top_advisor['nombre'];
|
||||||
|
$top_advisor_total = $top_advisor['total'];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Encontrar la mejor efectividad en completados
|
||||||
|
$best_completion_advisor = ['nombre' => 'N/A', 'efectividad' => 0];
|
||||||
|
if (!empty($asesores)) {
|
||||||
|
$completion_effectiveness = [];
|
||||||
|
|
||||||
|
foreach ($asesores as $asesor) {
|
||||||
|
// Contar el total de pedidos asignados al asesor en el mes
|
||||||
|
$stmt_total = $pdo->prepare("
|
||||||
|
SELECT COUNT(*)
|
||||||
|
FROM pedidos
|
||||||
|
WHERE asesor_id = ? AND MONTH(created_at) = ? AND YEAR(created_at) = ?
|
||||||
|
");
|
||||||
|
$stmt_total->execute([$asesor['id'], $selected_month, $selected_year]);
|
||||||
|
$total_asignados = $stmt_total->fetchColumn();
|
||||||
|
|
||||||
|
if ($total_asignados > 0) {
|
||||||
|
// Contar pedidos completados para el asesor en el mes
|
||||||
|
$stmt_completados = $pdo->prepare("
|
||||||
|
SELECT COUNT(*)
|
||||||
|
FROM pedidos
|
||||||
|
WHERE asesor_id = ? AND estado = 'COMPLETADO ✅' AND MONTH(created_at) = ? AND YEAR(created_at) = ?
|
||||||
|
");
|
||||||
|
$stmt_completados->execute([$asesor['id'], $selected_month, $selected_year]);
|
||||||
|
$total_completados = $stmt_completados->fetchColumn();
|
||||||
|
|
||||||
|
$efectividad = ($total_completados / $total_asignados) * 100;
|
||||||
|
|
||||||
|
$nombre_asesor = !empty($asesor['nombre_asesor']) ? $asesor['nombre_asesor'] : $asesor['username'];
|
||||||
|
$completion_effectiveness[] = ['nombre' => $nombre_asesor, 'efectividad' => $efectividad];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!empty($completion_effectiveness)) {
|
||||||
|
// Ordenar por efectividad descendente
|
||||||
|
usort($completion_effectiveness, function ($a, $b) {
|
||||||
|
return $b['efectividad'] <=> $a['efectividad'];
|
||||||
|
});
|
||||||
|
$best_completion_advisor = $completion_effectiveness[0];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ordenar asesores por total de pedidos (descendente) para la tabla y el gráfico
|
||||||
|
uasort($asesor_data, function ($a, $b) {
|
||||||
|
return $b['total'] <=> $a['total'];
|
||||||
|
});
|
||||||
|
|
||||||
|
// Preparar datos para Chart.js
|
||||||
|
foreach ($asesor_data as $data) {
|
||||||
|
$chart_data['labels'][] = $data['nombre'];
|
||||||
|
$chart_data['data'][] = $data['total'];
|
||||||
|
}
|
||||||
|
|
||||||
|
// Invertir el orden para el gráfico (de menor a mayor)
|
||||||
|
if (isset($chart_data['labels'])) {
|
||||||
|
$chart_data['labels'] = array_reverse($chart_data['labels']);
|
||||||
|
$chart_data['data'] = array_reverse($chart_data['data']);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TABLA DE PEDIDOS POR DÍA Y ASESORA (TIPO EXCEL)
|
||||||
|
$pedidos_mensual_asesora = [];
|
||||||
|
$asesoras_mensual = [];
|
||||||
|
if ($user_role === 'Administrador' || $user_role === 'Control Logistico' || $user_role === 'Logistica' || $user_role === 'Asesor') {
|
||||||
|
// 1. Obtener todas las asesoras
|
||||||
|
$stmt_asesoras = $pdo->query("SELECT id, nombre_asesor FROM users WHERE role = 'Asesor' AND nombre_asesor IS NOT NULL AND nombre_asesor != ''");
|
||||||
|
$asesoras_mensual = $stmt_asesoras->fetchAll(PDO::FETCH_ASSOC);
|
||||||
|
$asesora_ids = array_column($asesoras_mensual, 'id');
|
||||||
|
|
||||||
|
// 2. Obtener y procesar los datos de pedidos del mes
|
||||||
|
if (!empty($asesora_ids)) {
|
||||||
|
$placeholders = implode(',', array_fill(0, count($asesora_ids), '?'));
|
||||||
|
$stmt_pedidos_mensual = $pdo->prepare("
|
||||||
|
SELECT DAY(created_at) as dia, asesor_id, COUNT(id) as total_pedidos
|
||||||
|
FROM pedidos
|
||||||
|
WHERE MONTH(created_at) = ? AND YEAR(created_at) = ? AND asesor_id IN (" . $placeholders . ")
|
||||||
|
GROUP BY dia, asesor_id
|
||||||
|
");
|
||||||
|
$params = array_merge([$selected_month, $selected_year], $asesora_ids);
|
||||||
|
$stmt_pedidos_mensual->execute($params);
|
||||||
|
$pedidos_data = $stmt_pedidos_mensual->fetchAll(PDO::FETCH_ASSOC);
|
||||||
|
|
||||||
|
foreach ($pedidos_data as $row) {
|
||||||
|
$pedidos_mensual_asesora[$row['dia']][$row['asesor_id']] = $row['total_pedidos'];
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3. Calcular totales por asesora para el mes
|
||||||
|
$totales_por_asesora_para_orden = array_fill_keys($asesora_ids, 0);
|
||||||
|
foreach ($pedidos_mensual_asesora as $dia_data) {
|
||||||
|
foreach ($dia_data as $asesor_id => $cantidad) {
|
||||||
|
if (isset($totales_por_asesora_para_orden[$asesor_id])) {
|
||||||
|
$totales_por_asesora_para_orden[$asesor_id] += $cantidad;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 4. Ordenar el array de asesoras basado en el total de pedidos (de menor a mayor)
|
||||||
|
usort($asesoras_mensual, function ($a, $b) use ($totales_por_asesora_para_orden) {
|
||||||
|
$total_a = $totales_por_asesora_para_orden[$a['id']] ?? 0;
|
||||||
|
$total_b = $totales_por_asesora_para_orden[$b['id']] ?? 0;
|
||||||
|
return $total_a <=> $total_b;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
?>
|
||||||
|
|
||||||
|
<div class="container-fluid">
|
||||||
|
<h1 class="h3 mb-4 text-gray-800">Dashboard</h1>
|
||||||
|
<p class="mb-4">Bienvenido, <?php echo htmlspecialchars($user_name); ?>!</p>
|
||||||
|
|
||||||
|
<!-- Filtro de Fecha -->
|
||||||
|
<div class="card shadow mb-4">
|
||||||
|
<div class="card-body">
|
||||||
|
<form method="get" action="dashboard.php" class="form-inline">
|
||||||
|
<div class="form-group mr-3">
|
||||||
|
<label for="month" class="mr-2">Mes:</label>
|
||||||
|
<select name="month" id="month" class="form-control">
|
||||||
|
<?php foreach ($meses as $num => $nombre):
|
||||||
|
echo "<option value=\"" . $num . "\" " . (($num == $selected_month) ? 'selected' : '') . ">" . $nombre . "</option>";
|
||||||
|
endforeach; ?>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
<div class="form-group mr-3">
|
||||||
|
<label for="year" class="mr-2">Año:</label>
|
||||||
|
<select name="year" id="year" class="form-control">
|
||||||
|
<?php foreach ($years as $year):
|
||||||
|
echo "<option value=\"" . $year . "\" " . (($year == $selected_year) ? 'selected' : '') . ">" . $year . "</option>";
|
||||||
|
endforeach; ?>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
<button type="submit" class="btn btn-primary">Filtrar</button>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Fila de tarjetas de resumen para Asesor -->
|
||||||
|
<?php if ($user_role === 'Asesor'): ?>
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-xl-3 col-md-6 mb-4">
|
||||||
|
<div class="card border-left-info shadow h-100 py-2">
|
||||||
|
<div class="card-body">
|
||||||
|
<div class="row no-gutters align-items-center">
|
||||||
|
<div class="col mr-2">
|
||||||
|
<div class="text-xs font-weight-bold text-info text-uppercase mb-1">
|
||||||
|
Mis Pedidos del Día
|
||||||
|
</div>
|
||||||
|
<div class="h5 mb-0 font-weight-bold text-gray-800">
|
||||||
|
<?php echo $pedidos_hoy_asesor; ?>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="col-auto">
|
||||||
|
<i class="fas fa-box-open fa-2x text-gray-300"></i>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="col-xl-3 col-md-6 mb-4">
|
||||||
|
<div class="card border-left-primary shadow h-100 py-2">
|
||||||
|
<div class="card-body">
|
||||||
|
<div class="row no-gutters align-items-center">
|
||||||
|
<div class="col mr-2">
|
||||||
|
<div class="text-xs font-weight-bold text-primary text-uppercase mb-1">
|
||||||
|
Mis Pedidos de <?php echo $meses[$selected_month]; ?>
|
||||||
|
</div>
|
||||||
|
<div class="h5 mb-0 font-weight-bold text-gray-800">
|
||||||
|
<?php echo $pedidos_mes_asesor; ?>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="col-auto">
|
||||||
|
<i class="fas fa-calendar-alt fa-2x text-gray-300"></i>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Tarjeta para la Asesora del Mes -->
|
||||||
|
<div class="col-xl-3 col-md-6 mb-4">
|
||||||
|
<div class="card border-left-warning shadow h-100 py-2">
|
||||||
|
<div class="card-body">
|
||||||
|
<div class="row no-gutters align-items-center">
|
||||||
|
<div class="col mr-2">
|
||||||
|
<div class="text-xs font-weight-bold text-warning text-uppercase mb-1">
|
||||||
|
Asesora del Mes (<?php echo $meses[$selected_month]; ?>) - En Curso<br>Bonificacion S/250
|
||||||
|
</div>
|
||||||
|
<div class="h5 mb-0 font-weight-bold text-gray-800">
|
||||||
|
<?php echo htmlspecialchars($top_advisor_name); ?>
|
||||||
|
</div>
|
||||||
|
<div class="text-xs font-weight-bold text-gray-600 mt-1">
|
||||||
|
<?php echo $top_advisor_total; ?> pedidos
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="col-auto">
|
||||||
|
<i class="fas fa-trophy fa-2x text-gray-300"></i>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Tarjeta para Mejor Efectividad en Completados -->
|
||||||
|
<div class="col-xl-3 col-md-6 mb-4">
|
||||||
|
<div class="card border-left-danger shadow h-100 py-2">
|
||||||
|
<div class="card-body">
|
||||||
|
<div class="row no-gutters align-items-center">
|
||||||
|
<div class="col mr-2">
|
||||||
|
<div class="text-xs font-weight-bold text-danger text-uppercase mb-1">
|
||||||
|
Mejor Efectividad en Completados (<?php echo $meses[$selected_month]; ?>) - En Curso
|
||||||
|
</div>
|
||||||
|
<div class="h5 mb-0 font-weight-bold text-gray-800">
|
||||||
|
<?php echo htmlspecialchars($best_completion_advisor['nombre']); ?>
|
||||||
|
</div>
|
||||||
|
<div class="text-xs font-weight-bold text-gray-600 mt-1">
|
||||||
|
<?php echo round($best_completion_advisor['efectividad'], 2); ?>% de efectividad
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="col-auto">
|
||||||
|
<i class="fas fa-check-circle fa-2x text-gray-300"></i>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<?php endif; ?>
|
||||||
|
|
||||||
|
<!-- Card para Pedidos del Día -->
|
||||||
|
<?php if ($user_role !== 'Asesor'): ?>
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-xl-3 col-md-6 mb-4">
|
||||||
|
<div class="card border-left-info shadow h-100 py-2">
|
||||||
|
<div class="card-body">
|
||||||
|
<div class="row no-gutters align-items-center">
|
||||||
|
<div class="col mr-2">
|
||||||
|
<div class="text-xs font-weight-bold text-info text-uppercase mb-1">
|
||||||
|
Pedidos del Día Cargados
|
||||||
|
</div>
|
||||||
|
<div class="h5 mb-0 font-weight-bold text-gray-800">
|
||||||
|
<?php echo $pedidos_hoy; ?>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="col-auto">
|
||||||
|
<i class="fas fa-box-open fa-2x text-gray-300"></i>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="col-xl-3 col-md-6 mb-4">
|
||||||
|
<div class="card border-left-primary shadow h-100 py-2">
|
||||||
|
<div class="card-body">
|
||||||
|
<div class="row no-gutters align-items-center">
|
||||||
|
<div class="col mr-2">
|
||||||
|
<div class="text-xs font-weight-bold text-primary text-uppercase mb-1">
|
||||||
|
Pedidos de <?php echo $meses[$selected_month]; ?>
|
||||||
|
</div>
|
||||||
|
<div class="h5 mb-0 font-weight-bold text-gray-800">
|
||||||
|
<?php echo $pedidos_mes; ?>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="col-auto">
|
||||||
|
<i class="fas fa-calendar-alt fa-2x text-gray-300"></i>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="col-xl-3 col-md-6 mb-4">
|
||||||
|
<div class="card border-left-success shadow h-100 py-2">
|
||||||
|
<div class="card-body">
|
||||||
|
<div class="row no-gutters align-items-center">
|
||||||
|
<div class="col mr-2">
|
||||||
|
<div class="text-xs font-weight-bold text-success text-uppercase mb-1">
|
||||||
|
Pedidos en Tránsito (Total)
|
||||||
|
</div>
|
||||||
|
<div class="h5 mb-0 font-weight-bold text-gray-800">
|
||||||
|
<?php echo $pedidos_enviados; ?>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="col-auto">
|
||||||
|
<i class="fas fa-truck fa-2x text-gray-300"></i>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Tarjeta para la Asesora del Mes -->
|
||||||
|
<div class="col-xl-3 col-md-6 mb-4">
|
||||||
|
<div class="card border-left-warning shadow h-100 py-2">
|
||||||
|
<div class="card-body">
|
||||||
|
<div class="row no-gutters align-items-center">
|
||||||
|
<div class="col mr-2">
|
||||||
|
<div class="text-xs font-weight-bold text-warning text-uppercase mb-1">
|
||||||
|
Asesora del Mes (<?php echo $meses[$selected_month]; ?>) - En Curso<br>Bonificacion S/250
|
||||||
|
</div>
|
||||||
|
<div class="h5 mb-0 font-weight-bold text-gray-800">
|
||||||
|
<?php echo htmlspecialchars($top_advisor_name); ?>
|
||||||
|
</div>
|
||||||
|
<div class="text-xs font-weight-bold text-gray-600 mt-1">
|
||||||
|
<?php echo $top_advisor_total; ?> pedidos
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="col-auto">
|
||||||
|
<i class="fas fa-trophy fa-2x text-gray-300"></i>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Tarjeta para Mejor Efectividad en Completados -->
|
||||||
|
<div class="col-xl-3 col-md-6 mb-4">
|
||||||
|
<div class="card border-left-danger shadow h-100 py-2">
|
||||||
|
<div class="card-body">
|
||||||
|
<div class="row no-gutters align-items-center">
|
||||||
|
<div class="col mr-2">
|
||||||
|
<div class="text-xs font-weight-bold text-danger text-uppercase mb-1">
|
||||||
|
Mejor Efectividad en Completados (<?php echo $meses[$selected_month]; ?>) - En Curso
|
||||||
|
</div>
|
||||||
|
<div class="h5 mb-0 font-weight-bold text-gray-800">
|
||||||
|
<?php echo htmlspecialchars($best_completion_advisor['nombre']); ?>
|
||||||
|
</div>
|
||||||
|
<div class="text-xs font-weight-bold text-gray-600 mt-1">
|
||||||
|
<?php echo round($best_completion_advisor['efectividad'], 2); ?>% de efectividad
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="col-auto">
|
||||||
|
<i class="fas fa-check-circle fa-2x text-gray-300"></i>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<?php endif; ?>
|
||||||
|
|
||||||
|
<?php if (($user_role === 'Administrador' || $user_role === 'Control Logistico' || $user_role === 'Logistica' || $user_role === 'Asesor')): ?>
|
||||||
|
<!-- Reporte Mensual de Pedidos por Asesora -->
|
||||||
|
<div class="card shadow mb-4">
|
||||||
|
<div class="card-header py-3">
|
||||||
|
<h6 class="m-0 font-weight-bold text-primary">Reporte Mensual de Pedidos por Asesora (<?php echo $meses[$selected_month] . ' ' . $selected_year; ?>)</h6>
|
||||||
|
</div>
|
||||||
|
<div class="card-body">
|
||||||
|
<?php if (empty($asesoras_mensual)): ?>
|
||||||
|
<div class="alert alert-warning">No hay asesoras configuradas para mostrar en este reporte.</div>
|
||||||
|
<?php else: ?>
|
||||||
|
<div class="table-responsive">
|
||||||
|
<table class="table table-bordered table-striped" width="100%" cellspacing="0" style="font-size: 0.8rem;">
|
||||||
|
<thead class="thead-dark">
|
||||||
|
<tr class="text-center">
|
||||||
|
<th style="width: 80px; vertical-align: middle;">Día</th>
|
||||||
|
<?php foreach ($asesoras_mensual as $asesora): ?>
|
||||||
|
<th><?php echo htmlspecialchars($asesora['nombre_asesor']); ?></th>
|
||||||
|
<?php endforeach; ?>
|
||||||
|
<th style="vertical-align: middle;">Total Día</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<?php
|
||||||
|
$dias_en_mes = cal_days_in_month(CAL_GREGORIAN, $selected_month, $selected_year);
|
||||||
|
$totales_por_asesora = array_fill_keys(array_column($asesoras_mensual, 'id'), 0);
|
||||||
|
$gran_total_mes = 0;
|
||||||
|
|
||||||
|
for ($dia = 1; $dia <= $dias_en_mes; $dia++):
|
||||||
|
$total_dia = 0;
|
||||||
|
?>
|
||||||
|
<tr class="text-center">
|
||||||
|
<td class="font-weight-bold"><?php echo str_pad($dia, 2, '0', STR_PAD_LEFT) . '/' . str_pad($selected_month, 2, '0', STR_PAD_LEFT); ?></td>
|
||||||
|
<?php foreach ($asesoras_mensual as $asesora):
|
||||||
|
$asesora_id = $asesora['id'];
|
||||||
|
$cantidad = $pedidos_mensual_asesora[$dia][$asesora_id] ?? 0;
|
||||||
|
$total_dia += $cantidad;
|
||||||
|
$totales_por_asesora[$asesora_id] += $cantidad;
|
||||||
|
echo '<td>' . ($cantidad > 0 ? $cantidad : '<span class="text-muted">-</span>') . '</td>';
|
||||||
|
endforeach; ?>
|
||||||
|
<td class="font-weight-bold"><?php echo $total_dia; ?></td>
|
||||||
|
</tr>
|
||||||
|
<?php
|
||||||
|
$gran_total_mes += $total_dia;
|
||||||
|
endfor;
|
||||||
|
?>
|
||||||
|
</tbody>
|
||||||
|
<tfoot class="thead-dark">
|
||||||
|
<tr class="text-center">
|
||||||
|
<th>Total Mes</th>
|
||||||
|
<?php foreach ($asesoras_mensual as $asesora): ?>
|
||||||
|
<th class="text-center"><?php echo $totales_por_asesora[$asesora['id']]; ?></th>
|
||||||
|
<?php endforeach; ?>
|
||||||
|
<th class="text-center"><?php echo $gran_total_mes; ?></th>
|
||||||
|
</tr>
|
||||||
|
</tfoot>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
<?php endif; ?>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<?php endif; ?>
|
||||||
|
|
||||||
|
<?php if ($user_role === 'Administrador' || $user_role === 'Control Logistico' || $user_role === 'Logistica' || $user_role === 'Asesor'): ?>
|
||||||
|
<!-- Gráfico de Rendimiento de Asesores -->
|
||||||
|
<div class="card shadow mb-4">
|
||||||
|
<div class="card-header py-3">
|
||||||
|
<h6 class="m-0 font-weight-bold text-primary">Rendimiento de Asesoras (<?php echo $meses[
|
||||||
|
$selected_month] . ' ' . $selected_year; ?>)</h6>
|
||||||
|
</div>
|
||||||
|
<div class="card-body">
|
||||||
|
<div class="chart-container" style="position: relative; height:40vh; width:100%">
|
||||||
|
<canvas id="adminChart"></canvas>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Tabla de Resumen por Asesora -->
|
||||||
|
<div class="card shadow mb-4">
|
||||||
|
<div class="card-header py-3">
|
||||||
|
<h6 class="m-0 font-weight-bold text-primary">Resumen Detallado por Asesora (<?php echo $meses[$selected_month] . ' ' . $selected_year; ?>)</h6>
|
||||||
|
</div>
|
||||||
|
<div class="card-body">
|
||||||
|
<div class="table-responsive">
|
||||||
|
<table class="table table-bordered" id="dataTable" width="100%" cellspacing="0">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>Asesora</th>
|
||||||
|
<?php
|
||||||
|
$estado_display_map = ['RUTA_CONTRAENTREGA' => 'Ruta Contraentrega', 'RETORNADO' => 'Retornado', 'ENTREGA EXITOSA' => 'Entrega Exitosa'];
|
||||||
|
foreach ($estados as $estado):
|
||||||
|
$display_name = $estado_display_map[$estado] ?? $estado;
|
||||||
|
echo "<th>" . htmlspecialchars($display_name) . "</th>";
|
||||||
|
endforeach; ?>
|
||||||
|
<th>Total</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<?php if (empty($asesor_data)):
|
||||||
|
echo "<tr><td colspan=\"" . (count($estados) + 2) . "\" class=\"text-center\">No hay datos para el período seleccionado.</td></tr>";
|
||||||
|
else:
|
||||||
|
// Initialize totals
|
||||||
|
$totals = array_fill_keys($estados, 0);
|
||||||
|
$grand_total = 0;
|
||||||
|
|
||||||
|
// Definir los grupos de estados para los cálculos de porcentaje
|
||||||
|
$estados_efectividad = ['RUTA_CONTRAENTREGA', 'RETORNADO', 'ENTREGA EXITOSA'];
|
||||||
|
|
||||||
|
foreach ($asesor_data as $data):
|
||||||
|
echo "<tr>";
|
||||||
|
echo "<td>" . htmlspecialchars($data['nombre']) . "</td>";
|
||||||
|
|
||||||
|
// Calcular los totales para cada grupo
|
||||||
|
$total_efectividad = 0;
|
||||||
|
$total_general_grupo = 0;
|
||||||
|
foreach ($estados_efectividad as $e) {
|
||||||
|
$total_efectividad += $data['pedidos'][$e];
|
||||||
|
}
|
||||||
|
foreach ($estados as $estado) {
|
||||||
|
if (!in_array($estado, $estados_efectividad)) {
|
||||||
|
$total_general_grupo += $data['pedidos'][$estado];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach ($estados as $estado):
|
||||||
|
$cantidad = $data['pedidos'][$estado];
|
||||||
|
$totals[$estado] += $cantidad;
|
||||||
|
echo "<td>";
|
||||||
|
echo $cantidad;
|
||||||
|
|
||||||
|
// Calcular porcentaje basado en el grupo
|
||||||
|
if (in_array($estado, $estados_efectividad)) {
|
||||||
|
if ($total_efectividad > 0) {
|
||||||
|
$porcentaje = round(($cantidad / $total_efectividad) * 100, 1);
|
||||||
|
echo " <span class=\"text-muted\">({" . $porcentaje . "}%)</span>";
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if ($total_general_grupo > 0) {
|
||||||
|
$porcentaje = round(($cantidad / $total_general_grupo) * 100, 1);
|
||||||
|
echo " <span class=\"text-muted\">({" . $porcentaje . "}%)</span>";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
echo "</td>";
|
||||||
|
endforeach;
|
||||||
|
echo "<td><strong>" . $data['total'] . "</strong></td>";
|
||||||
|
$grand_total += $data['total'];
|
||||||
|
echo "</tr>";
|
||||||
|
endforeach;
|
||||||
|
endif;
|
||||||
|
?>
|
||||||
|
</tbody>
|
||||||
|
<?php if (!empty($asesor_data)):
|
||||||
|
?>
|
||||||
|
<tfoot>
|
||||||
|
<tr style="background-color: #f8f9fc; font-weight: bold;">
|
||||||
|
<td>Total General</td>
|
||||||
|
<?php foreach ($totals as $total_estado):
|
||||||
|
echo "<td>" . $total_estado . "</td>";
|
||||||
|
endforeach;
|
||||||
|
echo "<td>" . $grand_total . "</td>";
|
||||||
|
?>
|
||||||
|
</tr>
|
||||||
|
</tfoot>
|
||||||
|
<?php endif; ?>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<?php endif; ?>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Scripts de Chart.js -->
|
||||||
|
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
|
||||||
|
<script>
|
||||||
|
document.addEventListener("DOMContentLoaded", function() {
|
||||||
|
<?php if (($user_role === 'Administrador' || $user_role === 'Control Logistico' || $user_role === 'Logistica' || $user_role === 'Asesor') && !empty($chart_data['labels'])):
|
||||||
|
?>
|
||||||
|
const adminCtx = document.getElementById('adminChart').getContext('2d');
|
||||||
|
new Chart(adminCtx, {
|
||||||
|
type: 'bar',
|
||||||
|
data: {
|
||||||
|
labels: <?php echo json_encode($chart_data['labels']); ?>,
|
||||||
|
datasets: [{
|
||||||
|
label: 'Total de Pedidos',
|
||||||
|
data: <?php echo json_encode($chart_data['data']); ?>,
|
||||||
|
backgroundColor: 'rgba(78, 115, 223, 0.7)',
|
||||||
|
borderColor: 'rgba(78, 115, 223, 1)',
|
||||||
|
borderWidth: 1
|
||||||
|
}]
|
||||||
|
},
|
||||||
|
options: {
|
||||||
|
responsive: true,
|
||||||
|
maintainAspectRatio: false,
|
||||||
|
scales: {
|
||||||
|
y: {
|
||||||
|
beginAtZero: true,
|
||||||
|
ticks: { stepSize: 1 }
|
||||||
|
}
|
||||||
|
},
|
||||||
|
plugins: {
|
||||||
|
legend: { display: false }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
<?php endif; ?>
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<?php include 'layout_footer.php'; ?>
|
||||||
1480
dashboard_principal.php
Normal file
@ -1,17 +1,34 @@
|
|||||||
<?php
|
<?php
|
||||||
// Generated by setup_mariadb_project.sh — edit as needed.
|
date_default_timezone_set('America/Lima');
|
||||||
define('DB_HOST', '127.0.0.1');
|
|
||||||
define('DB_NAME', 'app_30953');
|
|
||||||
define('DB_USER', 'app_30953');
|
|
||||||
define('DB_PASS', 'e45f2778-db1f-450c-99c6-29efb4601472');
|
|
||||||
|
|
||||||
function db() {
|
if (!function_exists('required_env')) {
|
||||||
|
function required_env($key) {
|
||||||
|
$value = getenv($key);
|
||||||
|
if ($value === false || $value === '') {
|
||||||
|
throw new RuntimeException('Missing required environment variable: ' . $key);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
define('DB_HOST', required_env('DB_HOST'));
|
||||||
|
define('DB_NAME', required_env('DB_NAME'));
|
||||||
|
define('DB_USER', required_env('DB_USER'));
|
||||||
|
define('DB_PASS', required_env('DB_PASS'));
|
||||||
|
|
||||||
|
if (!function_exists('db')) {
|
||||||
|
function db() {
|
||||||
static $pdo;
|
static $pdo;
|
||||||
|
|
||||||
if (!$pdo) {
|
if (!$pdo) {
|
||||||
$pdo = new PDO('mysql:host='.DB_HOST.';dbname='.DB_NAME.';charset=utf8mb4', DB_USER, DB_PASS, [
|
$pdo = new PDO('mysql:host=' . DB_HOST . ';dbname=' . DB_NAME . ';charset=utf8mb4', DB_USER, DB_PASS, [
|
||||||
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
|
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
|
||||||
PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
|
PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
|
||||||
]);
|
]);
|
||||||
|
$pdo->exec("SET time_zone = '-05:00'");
|
||||||
}
|
}
|
||||||
|
|
||||||
return $pdo;
|
return $pdo;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
17
db/config.php.recovery_20260321_1525
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
<?php
|
||||||
|
// Generated by setup_mariadb_project.sh — edit as needed.
|
||||||
|
define('DB_HOST', '127.0.0.1');
|
||||||
|
define('DB_NAME', 'app_34849');
|
||||||
|
define('DB_USER', 'app_34849');
|
||||||
|
define('DB_PASS', '37a77d1f-97d4-4cf8-8b1e-cfcb83d3b778');
|
||||||
|
|
||||||
|
function db() {
|
||||||
|
static $pdo;
|
||||||
|
if (!$pdo) {
|
||||||
|
$pdo = new PDO('mysql:host='.DB_HOST.';dbname='.DB_NAME.';charset=utf8mb4', DB_USER, DB_PASS, [
|
||||||
|
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
|
||||||
|
PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
return $pdo;
|
||||||
|
}
|
||||||
5
db/migrations/000_create_migrations_table.sql
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
CREATE TABLE IF NOT EXISTS `migrations` (
|
||||||
|
`id` INT AUTO_INCREMENT PRIMARY_KEY,
|
||||||
|
`migration` VARCHAR(255) NOT NULL,
|
||||||
|
`created_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
||||||
|
);
|
||||||
7
db/migrations/001_create_users_table.sql
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
CREATE TABLE IF NOT EXISTS `users` (
|
||||||
|
`id` int(11) NOT NULL AUTO_INCREMENT,
|
||||||
|
`username` varchar(50) NOT NULL,
|
||||||
|
`password` varchar(255) NOT NULL,
|
||||||
|
`role` enum('Administrador','Asesor','Supervisor','Contabilidad') NOT NULL,
|
||||||
|
PRIMARY KEY (`id`)
|
||||||
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
|
||||||
20
db/migrations/002_create_pedidos_table.sql
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
CREATE TABLE IF NOT EXISTS `pedidos` (
|
||||||
|
`id` INT AUTO_INCREMENT PRIMARY KEY,
|
||||||
|
`dni_cliente` VARCHAR(20) NOT NULL,
|
||||||
|
`nombre_completo` VARCHAR(255) NOT NULL,
|
||||||
|
`celular` VARCHAR(20) NOT NULL,
|
||||||
|
`sede_envio` VARCHAR(100) NOT NULL,
|
||||||
|
`codigo_rastreo` VARCHAR(100),
|
||||||
|
`codigo_tracking` VARCHAR(100),
|
||||||
|
`producto` TEXT NOT NULL,
|
||||||
|
`cantidad` INT NOT NULL,
|
||||||
|
`monto_total` DECIMAL(10, 2) NOT NULL,
|
||||||
|
`monto_adelantado` DECIMAL(10, 2) DEFAULT 0.00,
|
||||||
|
`monto_debe` DECIMAL(10, 2) NOT NULL,
|
||||||
|
`estado` ENUM('pendiente_validar', 'contactado', 'pago_en_proceso', 'pagado_validado', 'rechazado', 'cancelado') NOT NULL DEFAULT 'pendiente_validar',
|
||||||
|
`asesor_id` INT,
|
||||||
|
`notas` TEXT,
|
||||||
|
`created_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
`updated_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
||||||
|
FOREIGN KEY (`asesor_id`) REFERENCES `users`(`id`) ON DELETE SET NULL
|
||||||
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
|
||||||
3
db/migrations/003_add_voucher_columns_to_pedidos.sql
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
ALTER TABLE `pedidos`
|
||||||
|
ADD COLUMN `voucher_adelanto_path` VARCHAR(255) NULL,
|
||||||
|
ADD COLUMN `voucher_restante_path` VARCHAR(255) NULL;
|
||||||
1
db/migrations/004_add_numero_operacion_to_pedidos.sql
Normal file
@ -0,0 +1 @@
|
|||||||
|
ALTER TABLE pedidos ADD COLUMN numero_operacion VARCHAR(255) NULL;
|
||||||
1
db/migrations/005_update_estado_column_in_pedidos.sql
Normal file
@ -0,0 +1 @@
|
|||||||
|
ALTER TABLE pedidos MODIFY COLUMN estado ENUM('ROTULADO 📦', 'EN TRANSITO 🚛', 'EN DESTINO 🏬', 'COMPLETADO ✅') CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT 'ROTULADO 📦';
|
||||||
1
db/migrations/006_add_role_to_users.sql
Normal file
@ -0,0 +1 @@
|
|||||||
|
ALTER TABLE users ADD COLUMN role VARCHAR(50) NOT NULL DEFAULT 'asesora';
|
||||||
2
db/migrations/007_add_asesor_id_to_pedidos.sql
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
ALTER TABLE pedidos ADD COLUMN asesor_id INT NULL;
|
||||||
|
ALTER TABLE pedidos ADD CONSTRAINT fk_asesor_id FOREIGN KEY (asesor_id) REFERENCES users(id) ON DELETE SET NULL;
|
||||||
1
db/migrations/010_add_dni_cliente_if_not_exists.sql
Normal file
@ -0,0 +1 @@
|
|||||||
|
ALTER TABLE `pedidos` ADD COLUMN `dni_cliente` VARCHAR(20) NULL DEFAULT NULL AFTER `nombre_completo`;
|
||||||
1
db/migrations/011_add_fecha_recojo_to_pedidos.sql
Normal file
@ -0,0 +1 @@
|
|||||||
|
ALTER TABLE pedidos ADD COLUMN fecha_recojo DATETIME NULL;
|
||||||
1
db/migrations/012_change_fecha_recojo_to_varchar.sql
Normal file
@ -0,0 +1 @@
|
|||||||
|
ALTER TABLE pedidos MODIFY fecha_recojo VARCHAR(255) NULL;
|
||||||
3
db/migrations/013_standardize_roles.sql
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
-- Actualiza los roles de 'user' a 'Asesor' y de 'admin' a 'Administrador'
|
||||||
|
UPDATE users SET role = 'Asesor' WHERE role = 'user';
|
||||||
|
UPDATE users SET role = 'Administrador' WHERE role = 'admin';
|
||||||
1
db/migrations/014_add_estado_pago_to_pedidos.sql
Normal file
@ -0,0 +1 @@
|
|||||||
|
ALTER TABLE pedidos ADD COLUMN estado_pago VARCHAR(50) NOT NULL DEFAULT 'Pendiente a verificación';
|
||||||
3
db/migrations/015_add_cp_t_cp_f_to_flujo_caja.sql
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
-- Add cp_t and cp_f columns to flujo_caja table
|
||||||
|
ALTER TABLE flujo_caja ADD COLUMN cp_t INT DEFAULT 0;
|
||||||
|
ALTER TABLE flujo_caja ADD COLUMN cp_f INT DEFAULT 0;
|
||||||
2
db/migrations/015_add_nota_adicional_to_pedidos.sql
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
-- Migration: Add nota_adicional column to pedidos table
|
||||||
|
ALTER TABLE pedidos ADD COLUMN nota_adicional TEXT NULL AFTER clave;
|
||||||
7
db/migrations/015_create_products_table.sql
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
CREATE TABLE IF NOT EXISTS products (
|
||||||
|
id INT AUTO_INCREMENT PRIMARY KEY,
|
||||||
|
name VARCHAR(255) NOT NULL,
|
||||||
|
description TEXT,
|
||||||
|
price DECIMAL(10, 2) NOT NULL,
|
||||||
|
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
||||||
|
);
|
||||||
5
db/migrations/016_add_profit_columns_to_products.sql
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
|
||||||
|
ALTER TABLE products
|
||||||
|
ADD COLUMN cost DECIMAL(10, 2) NOT NULL DEFAULT 0.00,
|
||||||
|
ADD COLUMN ads_cost DECIMAL(10, 2) NOT NULL DEFAULT 0.00,
|
||||||
|
ADD COLUMN commission DECIMAL(10, 2) NOT NULL DEFAULT 0.00;
|
||||||
@ -0,0 +1,4 @@
|
|||||||
|
ALTER TABLE products
|
||||||
|
ADD COLUMN ingreso_rentabilidad DECIMAL(10, 2) DEFAULT 0.00 NOT NULL AFTER price;
|
||||||
|
|
||||||
|
UPDATE products SET ingreso_rentabilidad = price;
|
||||||
1
db/migrations/018_add_unidades_vendidas_to_products.sql
Normal file
@ -0,0 +1 @@
|
|||||||
|
ALTER TABLE products ADD COLUMN unidades_vendidas INT DEFAULT 0;
|
||||||
6
db/migrations/019_create_rentabilidad_notas_table.sql
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
CREATE TABLE IF NOT EXISTS `rentabilidad_notas` (
|
||||||
|
`id` INT AUTO_INCREMENT PRIMARY KEY,
|
||||||
|
`titulo` VARCHAR(255) NOT NULL DEFAULT 'Nueva Nota',
|
||||||
|
`contenido` TEXT,
|
||||||
|
`fecha_creacion` TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
||||||
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
|
||||||
5
db/migrations/020_create_rentabilidad_reportes_table.sql
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
CREATE TABLE IF NOT EXISTS `rentabilidad_reportes` (
|
||||||
|
`id` INT AUTO_INCREMENT PRIMARY KEY,
|
||||||
|
`titulo` VARCHAR(255) NOT NULL DEFAULT 'Nuevo Reporte',
|
||||||
|
`created_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
||||||
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
|
||||||
3
db/migrations/021_add_promo_2_3_to_marketing_costos.sql
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
-- Add promo_2 and promo_3 to marketing_costos
|
||||||
|
ALTER TABLE marketing_costos ADD COLUMN promo_2 VARCHAR(255) DEFAULT NULL AFTER promo_1;
|
||||||
|
ALTER TABLE marketing_costos ADD COLUMN promo_3 VARCHAR(255) DEFAULT NULL AFTER promo_2;
|
||||||
12
db/migrations/021_create_rentabilidad_productos_table.sql
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
CREATE TABLE IF NOT EXISTS `rentabilidad_productos` (
|
||||||
|
`id` INT AUTO_INCREMENT PRIMARY KEY,
|
||||||
|
`reporte_id` INT NOT NULL,
|
||||||
|
`product_id` INT NOT NULL,
|
||||||
|
`recaudo` DECIMAL(10, 2) DEFAULT 0.00,
|
||||||
|
`unidades_vendidas` INT DEFAULT 0,
|
||||||
|
`costo_ads` DECIMAL(10, 2) DEFAULT 0.00,
|
||||||
|
`comision` DECIMAL(10, 2) DEFAULT 0.00,
|
||||||
|
FOREIGN KEY (`reporte_id`) REFERENCES `rentabilidad_reportes`(`id`) ON DELETE CASCADE,
|
||||||
|
FOREIGN KEY (`product_id`) REFERENCES `products`(`id`) ON DELETE CASCADE,
|
||||||
|
UNIQUE KEY `reporte_product` (`reporte_id`, `product_id`)
|
||||||
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
|
||||||
12
db/migrations/022_add_duplicados_to_estado.sql
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
-- Add 'Duplicados' to the estado ENUM in the pedidos table
|
||||||
|
ALTER TABLE pedidos MODIFY COLUMN estado ENUM(
|
||||||
|
'ROTULADO 📦',
|
||||||
|
'EN TRANSITO 🚛',
|
||||||
|
'EN DESTINO 🏬',
|
||||||
|
'COMPLETADO ✅',
|
||||||
|
'Gestion',
|
||||||
|
'RUTA_CONTRAENTREGA',
|
||||||
|
'ENTREGA EXITOSA',
|
||||||
|
'RETORNADO',
|
||||||
|
'Duplicados'
|
||||||
|
) DEFAULT 'ROTULADO 📦';
|
||||||
1
db/migrations/022_drop_rentabilidad_notas_table.sql
Normal file
@ -0,0 +1 @@
|
|||||||
|
DROP TABLE IF EXISTS `rentabilidad_notas`;
|
||||||
@ -0,0 +1,3 @@
|
|||||||
|
ALTER TABLE `products`
|
||||||
|
DROP COLUMN IF EXISTS `ingreso_rentabilidad`,
|
||||||
|
DROP COLUMN IF EXISTS `unidades_vendidas`;
|
||||||
1
db/migrations/024_add_dates_to_reportes.sql
Normal file
@ -0,0 +1 @@
|
|||||||
|
ALTER TABLE rentabilidad_reportes ADD COLUMN fecha_inicio DATE, ADD COLUMN fecha_fin DATE;
|
||||||
@ -0,0 +1,4 @@
|
|||||||
|
-- Add provincia columns to marketing_costos_v3
|
||||||
|
ALTER TABLE marketing_costos_v3
|
||||||
|
ADD COLUMN comision_asesora_provincia DECIMAL(10,2) DEFAULT 0.00 AFTER comision_asesora,
|
||||||
|
ADD COLUMN delivery_provincia DECIMAL(10,2) DEFAULT 0.00 AFTER delivery;
|
||||||
1
db/migrations/025_remove_titulo_from_reportes.sql
Normal file
@ -0,0 +1 @@
|
|||||||
|
ALTER TABLE rentabilidad_reportes DROP COLUMN titulo;
|
||||||
1
db/migrations/026_add_costo_producto_to_rentabilidad.sql
Normal file
@ -0,0 +1 @@
|
|||||||
|
ALTER TABLE rentabilidad_productos ADD COLUMN costo_producto DECIMAL(10, 2) DEFAULT 0.00;
|
||||||
1
db/migrations/027_widen_user_role_column.sql
Normal file
@ -0,0 +1 @@
|
|||||||
|
ALTER TABLE users MODIFY COLUMN role VARCHAR(50) NOT NULL;
|
||||||
1
db/migrations/028_add_clave_to_pedidos.sql
Normal file
@ -0,0 +1 @@
|
|||||||
|
ALTER TABLE `pedidos` ADD `clave` VARCHAR(255) NULL DEFAULT NULL AFTER `codigo_tracking`;
|
||||||
1
db/migrations/029_add_fecha_completado_to_pedidos.sql
Normal file
@ -0,0 +1 @@
|
|||||||
|
ALTER TABLE pedidos ADD COLUMN fecha_completado DATETIME DEFAULT NULL;
|
||||||
1
db/migrations/030_add_recaudo_ct_to_rentabilidad.sql
Normal file
@ -0,0 +1 @@
|
|||||||
|
ALTER TABLE rentabilidad_productos ADD COLUMN recaudo_ct DECIMAL(10, 2) DEFAULT 0.00;
|
||||||
@ -0,0 +1 @@
|
|||||||
|
ALTER TABLE rentabilidad_productos ADD COLUMN unidades_vendidas_ct INT DEFAULT 0;
|
||||||
1
db/migrations/032_add_nombre_asesor_to_users.sql
Normal file
@ -0,0 +1 @@
|
|||||||
|
ALTER TABLE users ADD COLUMN nombre_asesor VARCHAR(255) NULL;
|
||||||