Autosave: 20260519-165135
BIN
assets/uploads/marketing_images/6a0c8d55a3395.png
Normal file
|
After Width: | Height: | Size: 527 KiB |
BIN
assets/uploads/vouchers/6a0c6f56d57b3-9719.png
Normal file
|
After Width: | Height: | Size: 273 KiB |
BIN
assets/uploads/vouchers/6a0c6fd00bc00-883.png
Normal file
|
After Width: | Height: | Size: 400 KiB |
BIN
assets/uploads/vouchers/6a0c70b6716f9-7778.png
Normal file
|
After Width: | Height: | Size: 384 KiB |
BIN
assets/uploads/vouchers/6a0c71aaf37a5-716.png
Normal file
|
After Width: | Height: | Size: 422 KiB |
BIN
assets/uploads/vouchers/6a0c722f2d696-999.png
Normal file
|
After Width: | Height: | Size: 358 KiB |
BIN
assets/uploads/vouchers/6a0c7329305be-6448.png
Normal file
|
After Width: | Height: | Size: 321 KiB |
BIN
assets/uploads/vouchers/6a0c794d3be79-490.png
Normal file
|
After Width: | Height: | Size: 303 KiB |
|
After Width: | Height: | Size: 241 KiB |
BIN
assets/uploads/vouchers/6a0c80cc97b69-606.png
Normal file
|
After Width: | Height: | Size: 315 KiB |
BIN
assets/uploads/vouchers/6a0c84687bd94-Screenshot_350.png
Normal file
|
After Width: | Height: | Size: 253 KiB |
BIN
assets/uploads/vouchers/6a0c878f24c9b-192.png
Normal file
|
After Width: | Height: | Size: 360 KiB |
|
After Width: | Height: | Size: 199 KiB |
@ -49,6 +49,18 @@ if ($period === 'custom' && !empty($start_date) && !empty($end_date)) {
|
||||
$date_condition = "MONTH(p.created_at) = MONTH(CURDATE()) AND YEAR(p.created_at) = YEAR(CURDATE())";
|
||||
$label_period = "Este Mes";
|
||||
break;
|
||||
case 'last_month':
|
||||
$date_condition = "MONTH(p.created_at) = MONTH(DATE_SUB(CURDATE(), INTERVAL 1 MONTH)) AND YEAR(p.created_at) = YEAR(DATE_SUB(CURDATE(), INTERVAL 1 MONTH))";
|
||||
$label_period = "Mes Anterior";
|
||||
break;
|
||||
case '3_months':
|
||||
$date_condition = "DATE(p.created_at) >= DATE_SUB(CURDATE(), INTERVAL 3 MONTH)";
|
||||
$label_period = "Últimos 3 Meses";
|
||||
break;
|
||||
case '6_months':
|
||||
$date_condition = "DATE(p.created_at) >= DATE_SUB(CURDATE(), INTERVAL 6 MONTH)";
|
||||
$label_period = "Últimos 6 Meses";
|
||||
break;
|
||||
case 'year':
|
||||
$date_condition = "DATE(p.created_at) >= DATE_SUB(CURDATE(), INTERVAL 1 YEAR)";
|
||||
$label_period = "Último Año";
|
||||
@ -105,14 +117,18 @@ if ($period === 'today' || $period === 'yesterday') {
|
||||
$stmtEstados = $db->query("SELECT estado, COUNT(*) as total FROM pedidos p WHERE $date_condition GROUP BY estado");
|
||||
$estadosData = $stmtEstados->fetchAll(PDO::FETCH_ASSOC);
|
||||
|
||||
// 7. Top Productos (Ajustado al periodo)
|
||||
$stmtTopProd = $db->query("SELECT producto, SUM(cantidad) as ventas FROM pedidos p WHERE $date_condition AND estado != 'RETORNADO' GROUP BY producto ORDER BY ventas DESC LIMIT 5");
|
||||
// 7. Top Productos (Ajustado al periodo) - TOTAL
|
||||
$stmtTopProd = $db->query("SELECT producto, SUM(cantidad) as ventas FROM pedidos p WHERE $date_condition AND estado NOT IN ('RETORNADO', 'GESTIONES') GROUP BY producto ORDER BY ventas DESC LIMIT 10");
|
||||
$topProductos = $stmtTopProd->fetchAll(PDO::FETCH_ASSOC);
|
||||
|
||||
// 7b. Top Productos Contraentrega (Ajustado al periodo)
|
||||
$stmtTopProdCE = $db->query("SELECT producto, SUM(cantidad) as ventas FROM pedidos p WHERE $date_condition AND estado IN ('RUTA_CONTRAENTREGA', 'ENTREGA EXITOSA') GROUP BY producto ORDER BY ventas DESC LIMIT 5");
|
||||
$stmtTopProdCE = $db->query("SELECT producto, SUM(cantidad) as ventas FROM pedidos p WHERE $date_condition AND estado IN ('RUTA_CONTRAENTREGA', 'ENTREGA EXITOSA') GROUP BY producto ORDER BY ventas DESC LIMIT 10");
|
||||
$topProductosCE = $stmtTopProdCE->fetchAll(PDO::FETCH_ASSOC);
|
||||
|
||||
// 7c. Top Productos Provincia (Ajustado al periodo)
|
||||
$stmtTopProdProv = $db->query("SELECT producto, SUM(cantidad) as ventas FROM pedidos p WHERE $date_condition AND estado NOT IN ('RUTA_CONTRAENTREGA', 'ENTREGA EXITOSA', 'RETORNADO', 'GESTIONES') GROUP BY producto ORDER BY ventas DESC LIMIT 10");
|
||||
$topProductosProv = $stmtTopProdProv->fetchAll(PDO::FETCH_ASSOC);
|
||||
|
||||
// 8. Ventas por Asesor (Ajustado al periodo)
|
||||
$stmtAsesores = $db->query("SELECT u.nombre_asesor,
|
||||
COUNT(p.id) as total_pedidos,
|
||||
@ -159,8 +175,7 @@ $utilidadTotal = $stmtUtilidad->fetchColumn() ?: 0;
|
||||
// 12. Datos Detallados por Canal (Provincia vs Contraentrega)
|
||||
$stmtDetalleCanal = $db->query("SELECT
|
||||
CASE
|
||||
WHEN estado IN ('RUTA_CONTRAENTREGA', 'ENTREGA EXITOSA') THEN 'Contraentrega'
|
||||
WHEN estado = 'RETORNADO' AND agencia = 'CONTRAENTREGA' THEN 'Contraentrega'
|
||||
WHEN estado IN ('RUTA_CONTRAENTREGA', 'ENTREGA EXITOSA', 'RETORNADO') THEN 'Contraentrega'
|
||||
ELSE 'Provincia'
|
||||
END as canal,
|
||||
estado,
|
||||
@ -171,15 +186,15 @@ $stmtDetalleCanal = $db->query("SELECT
|
||||
GROUP BY canal, estado");
|
||||
$detalleCanalRaw = $stmtDetalleCanal->fetchAll(PDO::FETCH_ASSOC);
|
||||
|
||||
// 13. Top Productos Detallado (Para la tabla)
|
||||
// 13. Top Productos Detallado (Para la tabla) - TOTAL
|
||||
$stmtTopDetalle = $db->query("
|
||||
SELECT p.producto, SUM(p.cantidad) as total_cantidad, SUM(p.monto_total) as monto_total, AVG(pr.costo) as costo_promedio
|
||||
FROM pedidos p
|
||||
LEFT JOIN products pr ON p.producto = pr.nombre
|
||||
WHERE $date_condition AND p.estado != 'RETORNADO'
|
||||
WHERE $date_condition AND p.estado NOT IN ('RETORNADO', 'GESTIONES')
|
||||
GROUP BY p.producto
|
||||
ORDER BY total_cantidad DESC
|
||||
LIMIT 5
|
||||
LIMIT 10
|
||||
");
|
||||
$topProductosDetalle = $stmtTopDetalle->fetchAll(PDO::FETCH_ASSOC);
|
||||
|
||||
@ -191,10 +206,22 @@ $stmtTopDetalleCE = $db->query("
|
||||
WHERE $date_condition AND p.estado IN ('RUTA_CONTRAENTREGA', 'ENTREGA EXITOSA')
|
||||
GROUP BY p.producto
|
||||
ORDER BY total_cantidad DESC
|
||||
LIMIT 5
|
||||
LIMIT 10
|
||||
");
|
||||
$topProductosDetalleCE = $stmtTopDetalleCE->fetchAll(PDO::FETCH_ASSOC);
|
||||
|
||||
// 13c. Top Productos Detallado Provincia
|
||||
$stmtTopDetalleProv = $db->query("
|
||||
SELECT p.producto, SUM(p.cantidad) as total_cantidad, SUM(p.monto_total) as monto_total, AVG(pr.costo) as costo_promedio
|
||||
FROM pedidos p
|
||||
LEFT JOIN products pr ON p.producto = pr.nombre
|
||||
WHERE $date_condition AND p.estado NOT IN ('RUTA_CONTRAENTREGA', 'ENTREGA EXITOSA', 'RETORNADO', 'GESTIONES')
|
||||
GROUP BY p.producto
|
||||
ORDER BY total_cantidad DESC
|
||||
LIMIT 10
|
||||
");
|
||||
$topProductosDetalleProv = $stmtTopDetalleProv->fetchAll(PDO::FETCH_ASSOC);
|
||||
|
||||
// 14. Rendimiento de Productos en Contraentrega (Para Gráfica de Barras Apiladas)
|
||||
$stmtProdRendimientoCE = $db->query("
|
||||
SELECT
|
||||
@ -205,7 +232,7 @@ $stmtProdRendimientoCE = $db->query("
|
||||
COUNT(*) as total
|
||||
FROM pedidos p
|
||||
WHERE $date_condition
|
||||
AND (estado IN ('RUTA_CONTRAENTREGA', 'ENTREGA EXITOSA') OR (estado = 'RETORNADO' AND agencia = 'CONTRAENTREGA'))
|
||||
AND estado IN ('RUTA_CONTRAENTREGA', 'ENTREGA EXITOSA', 'RETORNADO')
|
||||
GROUP BY producto
|
||||
ORDER BY total DESC
|
||||
LIMIT 10
|
||||
@ -222,7 +249,7 @@ $stmtProdRendimientoProv = $db->query("
|
||||
COUNT(*) as total
|
||||
FROM pedidos p
|
||||
WHERE $date_condition
|
||||
AND NOT (estado IN ('RUTA_CONTRAENTREGA', 'ENTREGA EXITOSA') OR (estado = 'RETORNADO' AND agencia = 'CONTRAENTREGA'))
|
||||
AND estado NOT IN ('RUTA_CONTRAENTREGA', 'ENTREGA EXITOSA', 'RETORNADO')
|
||||
GROUP BY producto
|
||||
ORDER BY total DESC
|
||||
LIMIT 10
|
||||
@ -271,6 +298,9 @@ include 'layout_header.php';
|
||||
<a href="?period=15" class="btn btn-outline-primary btn-sm <?php echo $period == '15' ? 'active' : ''; ?>">15 Días</a>
|
||||
<a href="?period=30" class="btn btn-outline-primary btn-sm <?php echo $period == '30' ? 'active' : ''; ?>">30 Días</a>
|
||||
<a href="?period=month" class="btn btn-outline-primary btn-sm <?php echo $period == 'month' ? 'active' : ''; ?>">Este Mes</a>
|
||||
<a href="?period=last_month" class="btn btn-outline-primary btn-sm <?php echo $period == 'last_month' ? 'active' : ''; ?>">Mes Anterior</a>
|
||||
<a href="?period=3_months" class="btn btn-outline-primary btn-sm <?php echo $period == '3_months' ? 'active' : ''; ?>">3 Meses</a>
|
||||
<a href="?period=6_months" class="btn btn-outline-primary btn-sm <?php echo $period == '6_months' ? 'active' : ''; ?>">6 Meses</a>
|
||||
<a href="?period=year" class="btn btn-outline-primary btn-sm <?php echo $period == 'year' ? 'active' : ''; ?>">1 Año</a>
|
||||
</div>
|
||||
</form>
|
||||
@ -565,53 +595,86 @@ include 'layout_header.php';
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<!-- Top Productos -->
|
||||
<div class="col-md-6 mb-4">
|
||||
<div class="card shadow">
|
||||
<!-- Top Productos Total -->
|
||||
<div class="col-12 mb-4">
|
||||
<div class="card shadow h-100">
|
||||
<div class="card-header bg-white d-flex justify-content-between align-items-center">
|
||||
<h5 class="mb-0">Top 5 Productos más Vendidos</h5>
|
||||
<span class="badge bg-primary">Por Unidades</span>
|
||||
<h5 class="mb-0 text-primary"><i class="fas fa-trophy me-2"></i> Top 10 Productos Más Vendidos (Total)</h5>
|
||||
<span class="badge bg-primary">Rendimiento Global</span>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<canvas id="productosChart" class="mb-4"></canvas>
|
||||
<div class="row">
|
||||
<div class="col-12 mb-4">
|
||||
<canvas id="productosChart" style="height: 550px;"></canvas>
|
||||
</div>
|
||||
<div class="col-12">
|
||||
<div class="table-responsive">
|
||||
<table class="table table-hover table-sm align-middle">
|
||||
<thead class="table-light">
|
||||
<tr>
|
||||
<th>Producto</th>
|
||||
<th class="text-center">Cant.</th>
|
||||
<th class="text-end">Monto Total</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<?php foreach ($topProductosDetalle as $prod): ?>
|
||||
<tr>
|
||||
<td class="fw-bold text-dark small" style="font-size: 0.85rem;"><?php echo htmlspecialchars(strlen($prod['producto']) > 35 ? substr($prod['producto'], 0, 35) . '...' : $prod['producto']); ?></td>
|
||||
<td class="text-center">
|
||||
<span class="badge bg-light text-primary border border-primary px-3">
|
||||
<?php echo number_format($prod['total_cantidad']); ?>
|
||||
</span>
|
||||
</td>
|
||||
<td class="text-end fw-bold text-success" style="font-size: 0.85rem;">
|
||||
S/ <?php echo number_format($prod['monto_total'], 2); ?>
|
||||
</td>
|
||||
</tr>
|
||||
<?php endforeach; ?>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<!-- Top Productos Contraentrega -->
|
||||
<div class="col-md-6 mb-4">
|
||||
<div class="card shadow h-100 border-left-warning">
|
||||
<div class="card-header bg-white d-flex justify-content-between align-items-center">
|
||||
<h5 class="mb-0 text-warning">Top 10 Contraentrega</h5>
|
||||
<span class="badge bg-warning text-dark">Motorizado</span>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<canvas id="productosCEChart" class="mb-4" style="height: 450px;"></canvas>
|
||||
|
||||
<div class="table-responsive">
|
||||
<table class="table table-hover align-middle">
|
||||
<table class="table table-hover table-sm align-middle">
|
||||
<thead class="table-light">
|
||||
<tr>
|
||||
<th>Producto</th>
|
||||
<th class="text-center">Cant.</th>
|
||||
<th class="text-end">Monto Total</th>
|
||||
<th class="text-end">Rent. %</th>
|
||||
<th class="text-end">Monto</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<?php foreach ($topProductosDetalle as $prod):
|
||||
$costoTotal = $prod['costo_promedio'] * $prod['total_cantidad'];
|
||||
$utilidad = $prod['monto_total'] - $costoTotal;
|
||||
$rentabilidad = ($prod['monto_total'] > 0) ? ($utilidad / $prod['monto_total']) * 100 : 0;
|
||||
$rentClass = $rentabilidad > 30 ? 'text-success' : ($rentabilidad > 15 ? 'text-warning' : 'text-danger');
|
||||
?>
|
||||
<?php foreach ($topProductosDetalleCE as $prod): ?>
|
||||
<tr>
|
||||
<td class="fw-bold text-dark small"><?php echo htmlspecialchars($prod['producto']); ?></td>
|
||||
<td class="fw-bold text-dark small" style="font-size: 0.75rem;"><?php echo htmlspecialchars(strlen($prod['producto']) > 25 ? substr($prod['producto'], 0, 25) . '...' : $prod['producto']); ?></td>
|
||||
<td class="text-center">
|
||||
<span class="badge bg-light text-primary border border-primary px-2">
|
||||
<span class="badge bg-light text-warning border border-warning">
|
||||
<?php echo number_format($prod['total_cantidad']); ?>
|
||||
</span>
|
||||
</td>
|
||||
<td class="text-end fw-bold text-success small">
|
||||
<td class="text-end fw-bold text-success small" style="font-size: 0.75rem;">
|
||||
S/ <?php echo number_format($prod['monto_total'], 2); ?>
|
||||
</td>
|
||||
<td class="text-end fw-bold <?php echo $rentClass; ?> small">
|
||||
<?php echo number_format($rentabilidad, 1); ?>%
|
||||
</td>
|
||||
</tr>
|
||||
<?php endforeach; ?>
|
||||
<?php if (empty($topProductosDetalle)): ?>
|
||||
<tr>
|
||||
<td colspan="4" class="text-center text-muted py-3">No hay datos en este periodo</td>
|
||||
</tr>
|
||||
<?php endif; ?>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
@ -619,53 +682,39 @@ include 'layout_header.php';
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Top Productos Contraentrega -->
|
||||
<!-- Top Productos Provincia -->
|
||||
<div class="col-md-6 mb-4">
|
||||
<div class="card shadow border-left-warning">
|
||||
<div class="card shadow h-100 border-left-info">
|
||||
<div class="card-header bg-white d-flex justify-content-between align-items-center">
|
||||
<h5 class="mb-0 text-warning"><i class="fas fa-motorcycle me-2"></i> Top 5 Contraentrega</h5>
|
||||
<span class="badge bg-warning text-dark">Solo Contraentrega</span>
|
||||
<h5 class="mb-0 text-info">Top 10 Provincia</h5>
|
||||
<span class="badge bg-info text-white">Agencias</span>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<canvas id="productosCEChart" class="mb-4"></canvas>
|
||||
<canvas id="productosProvChart" class="mb-4" style="height: 450px;"></canvas>
|
||||
|
||||
<div class="table-responsive">
|
||||
<table class="table table-hover align-middle">
|
||||
<table class="table table-hover table-sm align-middle">
|
||||
<thead class="table-light">
|
||||
<tr>
|
||||
<th>Producto</th>
|
||||
<th class="text-center">Cant.</th>
|
||||
<th class="text-end">Monto Total</th>
|
||||
<th class="text-end">Rent. %</th>
|
||||
<th class="text-end">Monto</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<?php foreach ($topProductosDetalleCE as $prod):
|
||||
$costoTotal = $prod['costo_promedio'] * $prod['total_cantidad'];
|
||||
$utilidad = $prod['monto_total'] - $costoTotal;
|
||||
$rentabilidad = ($prod['monto_total'] > 0) ? ($utilidad / $prod['monto_total']) * 100 : 0;
|
||||
$rentClass = $rentabilidad > 30 ? 'text-success' : ($rentabilidad > 15 ? 'text-warning' : 'text-danger');
|
||||
?>
|
||||
<?php foreach ($topProductosDetalleProv as $prod): ?>
|
||||
<tr>
|
||||
<td class="fw-bold text-dark small"><?php echo htmlspecialchars($prod['producto']); ?></td>
|
||||
<td class="fw-bold text-dark small" style="font-size: 0.75rem;"><?php echo htmlspecialchars(strlen($prod['producto']) > 25 ? substr($prod['producto'], 0, 25) . '...' : $prod['producto']); ?></td>
|
||||
<td class="text-center">
|
||||
<span class="badge bg-light text-warning border border-warning px-2">
|
||||
<span class="badge bg-light text-info border border-info">
|
||||
<?php echo number_format($prod['total_cantidad']); ?>
|
||||
</span>
|
||||
</td>
|
||||
<td class="text-end fw-bold text-success small">
|
||||
<td class="text-end fw-bold text-success small" style="font-size: 0.75rem;">
|
||||
S/ <?php echo number_format($prod['monto_total'], 2); ?>
|
||||
</td>
|
||||
<td class="text-end fw-bold <?php echo $rentClass; ?> small">
|
||||
<?php echo number_format($rentabilidad, 1); ?>%
|
||||
</td>
|
||||
</tr>
|
||||
<?php endforeach; ?>
|
||||
<?php if (empty($topProductosDetalleCE)): ?>
|
||||
<tr>
|
||||
<td colspan="4" class="text-center text-muted py-3">No hay datos en este periodo</td>
|
||||
</tr>
|
||||
<?php endif; ?>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
@ -930,16 +979,47 @@ include 'layout_header.php';
|
||||
new Chart(ctxProductos, {
|
||||
type: 'bar',
|
||||
data: {
|
||||
labels: <?php echo json_encode(array_map(function($p) { return strlen($p['producto']) > 20 ? substr($p['producto'], 0, 20) . '...' : $p['producto']; }, $topProductos)); ?>,
|
||||
labels: <?php echo json_encode(array_map(function($p) { return strlen($p['producto']) > 30 ? substr($p['producto'], 0, 30) . '...' : $p['producto']; }, $topProductos)); ?>,
|
||||
datasets: [{
|
||||
label: 'Unidades Vendidas',
|
||||
label: 'Unidades',
|
||||
data: <?php echo json_encode(array_column($topProductos, 'ventas')); ?>,
|
||||
backgroundColor: '#0d6efd'
|
||||
backgroundColor: '#0d6efd',
|
||||
borderRadius: 5,
|
||||
barPercentage: 0.7
|
||||
}]
|
||||
},
|
||||
options: {
|
||||
indexAxis: 'y',
|
||||
responsive: true
|
||||
indexAxis: 'x',
|
||||
responsive: true,
|
||||
maintainAspectRatio: false,
|
||||
plugins: {
|
||||
legend: { display: false },
|
||||
datalabels: {
|
||||
anchor: 'end',
|
||||
align: 'top',
|
||||
color: '#000',
|
||||
offset: 5,
|
||||
font: { size: 13, weight: 'bold' }
|
||||
}
|
||||
},
|
||||
scales: {
|
||||
y: {
|
||||
beginAtZero: true,
|
||||
grid: {
|
||||
drawBorder: false,
|
||||
color: 'rgba(0,0,0,0.05)'
|
||||
},
|
||||
title: { display: true, text: 'Unidades Vendidas', font: { weight: 'bold' } }
|
||||
},
|
||||
x: {
|
||||
grid: { display: false },
|
||||
ticks: {
|
||||
maxRotation: 45,
|
||||
minRotation: 45,
|
||||
font: { size: 11, weight: 'bold' }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
@ -948,16 +1028,86 @@ include 'layout_header.php';
|
||||
new Chart(ctxProductosCE, {
|
||||
type: 'bar',
|
||||
data: {
|
||||
labels: <?php echo json_encode(array_map(function($p) { return strlen($p['producto']) > 20 ? substr($p['producto'], 0, 20) . '...' : $p['producto']; }, $topProductosCE)); ?>,
|
||||
labels: <?php echo json_encode(array_map(function($p) { return strlen($p['producto']) > 15 ? substr($p['producto'], 0, 15) . '...' : $p['producto']; }, $topProductosCE)); ?>,
|
||||
datasets: [{
|
||||
label: 'Unidades Vendidas (CE)',
|
||||
label: 'Unidades',
|
||||
data: <?php echo json_encode(array_column($topProductosCE, 'ventas')); ?>,
|
||||
backgroundColor: '#ffc107'
|
||||
backgroundColor: '#ffc107',
|
||||
borderRadius: 5,
|
||||
barPercentage: 0.7
|
||||
}]
|
||||
},
|
||||
options: {
|
||||
indexAxis: 'y',
|
||||
responsive: true
|
||||
indexAxis: 'x',
|
||||
responsive: true,
|
||||
maintainAspectRatio: false,
|
||||
plugins: {
|
||||
legend: { display: false },
|
||||
datalabels: {
|
||||
anchor: 'end',
|
||||
align: 'top',
|
||||
color: '#000',
|
||||
font: { size: 11, weight: 'bold' }
|
||||
}
|
||||
},
|
||||
scales: {
|
||||
y: {
|
||||
beginAtZero: true,
|
||||
grid: { color: 'rgba(0,0,0,0.05)' }
|
||||
},
|
||||
x: {
|
||||
grid: { display: false },
|
||||
ticks: {
|
||||
maxRotation: 45,
|
||||
minRotation: 45,
|
||||
font: { size: 10, weight: 'bold' }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// Gráfico de Productos Provincia
|
||||
const ctxProductosProv = document.getElementById('productosProvChart').getContext('2d');
|
||||
new Chart(ctxProductosProv, {
|
||||
type: 'bar',
|
||||
data: {
|
||||
labels: <?php echo json_encode(array_map(function($p) { return strlen($p['producto']) > 15 ? substr($p['producto'], 0, 15) . '...' : $p['producto']; }, $topProductosProv)); ?>,
|
||||
datasets: [{
|
||||
label: 'Unidades',
|
||||
data: <?php echo json_encode(array_column($topProductosProv, 'ventas')); ?>,
|
||||
backgroundColor: '#0dcaf0',
|
||||
borderRadius: 5,
|
||||
barPercentage: 0.7
|
||||
}]
|
||||
},
|
||||
options: {
|
||||
indexAxis: 'x',
|
||||
responsive: true,
|
||||
maintainAspectRatio: false,
|
||||
plugins: {
|
||||
legend: { display: false },
|
||||
datalabels: {
|
||||
anchor: 'end',
|
||||
align: 'top',
|
||||
color: '#000',
|
||||
font: { size: 11, weight: 'bold' }
|
||||
}
|
||||
},
|
||||
scales: {
|
||||
y: {
|
||||
beginAtZero: true,
|
||||
grid: { color: 'rgba(0,0,0,0.05)' }
|
||||
},
|
||||
x: {
|
||||
grid: { display: false },
|
||||
ticks: {
|
||||
maxRotation: 45,
|
||||
minRotation: 45,
|
||||
font: { size: 10, weight: 'bold' }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
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;
|
||||
@ -13,7 +13,7 @@ $end_date = "$year-$month-$days_in_month";
|
||||
$db_columns = [
|
||||
'bcp_yape', 'b_nacion', 'interbank', 'bbva', 'otros_ingresos',
|
||||
'tu1', 'tu2', 'tu3', 'fl1', 'fl2', 'fl3',
|
||||
'rc_contraent', 'sc', 'c_pedidos'
|
||||
'rc_contraent', 'sc', 'cp_t', 'cp_f'
|
||||
];
|
||||
|
||||
// Columns to display in the table body in order
|
||||
@ -53,7 +53,8 @@ for ($day = 1; $day <= $days_in_month; $day++) {
|
||||
// Calculate totals
|
||||
$totals = array_fill_keys($display_columns, 0);
|
||||
$totals['sc'] = 0;
|
||||
$totals['c_pedidos'] = 0;
|
||||
$totals['cp_t'] = 0;
|
||||
$totals['cp_f'] = 0;
|
||||
$totals['total_ingresos'] = 0;
|
||||
$totals['total_inversion'] = 0;
|
||||
$totals['recaudo_final'] = 0;
|
||||
@ -69,7 +70,8 @@ foreach ($all_days_data as $date => &$day_data) {
|
||||
$totals[$col] += (float)($day_data[$col] ?? 0);
|
||||
}
|
||||
$totals['sc'] += (float)($day_data['sc'] ?? 0);
|
||||
$totals['c_pedidos'] += (int)($day_data['c_pedidos'] ?? 0);
|
||||
$totals['cp_t'] += (int)($day_data['cp_t'] ?? 0);
|
||||
$totals['cp_f'] += (int)($day_data['cp_f'] ?? 0);
|
||||
$totals['total_ingresos'] += $ingresos_dia;
|
||||
$totals['total_inversion'] += $inversion_dia;
|
||||
$totals['recaudo_final'] += ($ingresos_dia - $inversion_dia);
|
||||
@ -130,7 +132,7 @@ unset($day_data);
|
||||
<thead>
|
||||
<tr>
|
||||
<th rowspan="2" style="vertical-align: middle; text-align: center;">Fecha</th>
|
||||
<th rowspan="2" style="vertical-align: middle; text-align: center;">C.pedidos</th>
|
||||
<th colspan="2" style="text-align: center;">C.pedidos</th>
|
||||
<th colspan="5" style="text-align: center;">Ingresos</th>
|
||||
<th colspan="6" style="text-align: center;">Inversion Publicitaria</th>
|
||||
<th rowspan="2" style="vertical-align: middle; text-align: center;">RC ENVIO</th>
|
||||
@ -141,6 +143,8 @@ unset($day_data);
|
||||
<th rowspan="2" style="vertical-align: middle; text-align: center;">Sc</th>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>CP. T</th>
|
||||
<th>CP. F</th>
|
||||
<th>BCP/YAPE</th>
|
||||
<th>B. NACION</th>
|
||||
<th>INTERBANK</th>
|
||||
@ -158,7 +162,8 @@ unset($day_data);
|
||||
<?php foreach ($all_days_data as $date => $day_data): ?>
|
||||
<tr data-date="<?php echo $date; ?>">
|
||||
<td><?php echo $date; ?></td>
|
||||
<td contenteditable="true" data-column="c_pedidos"><?php echo htmlspecialchars(number_format((int)($day_data['c_pedidos'] ?? 0), 0, '', '')); ?></td>
|
||||
<td contenteditable="true" data-column="cp_t"><?php echo htmlspecialchars(number_format((int)($day_data['cp_t'] ?? 0), 0, '', '')); ?></td>
|
||||
<td contenteditable="true" data-column="cp_f"><?php echo htmlspecialchars(number_format((int)($day_data['cp_f'] ?? 0), 0, '', '')); ?></td>
|
||||
<?php foreach ($display_columns as $col): ?>
|
||||
<?php if ($col === 'rc_envio'): ?>
|
||||
<td data-column="rc_envio"><?php echo htmlspecialchars(number_format((float)($day_data[$col] ?? 0), 2, '.', '')); ?></td>
|
||||
@ -176,7 +181,8 @@ unset($day_data);
|
||||
<tfoot>
|
||||
<tr>
|
||||
<th>TOTAL</th>
|
||||
<th data-total-column="c_pedidos"><?php echo number_format($totals['c_pedidos'], 0); ?></th>
|
||||
<th data-total-column="cp_t"><?php echo number_format($totals['cp_t'], 0); ?></th>
|
||||
<th data-total-column="cp_f"><?php echo number_format($totals['cp_f'], 0); ?></th>
|
||||
<?php foreach ($display_columns as $col): ?>
|
||||
<th data-total-column="<?php echo $col; ?>"><?php echo number_format($totals[$col], 2); ?></th>
|
||||
<?php endforeach; ?>
|
||||
@ -218,7 +224,8 @@ document.addEventListener('DOMContentLoaded', function() {
|
||||
function updateGrandTotals() {
|
||||
const grandTotals = {
|
||||
<?php foreach ($display_columns as $col) echo "'$col': 0,"; ?>
|
||||
'c_pedidos': 0,
|
||||
'cp_t': 0,
|
||||
'cp_f': 0,
|
||||
'sc': 0,
|
||||
'total-ingresos': 0,
|
||||
'total-inversion': 0,
|
||||
@ -229,7 +236,8 @@ document.addEventListener('DOMContentLoaded', function() {
|
||||
<?php foreach ($display_columns as $col): ?>
|
||||
grandTotals['<?php echo $col; ?>'] += parseFloat(row.querySelector(`[data-column="<?php echo $col; ?>"]`).textContent.replace(/,/g, '')) || 0;
|
||||
<?php endforeach; ?>
|
||||
grandTotals['c_pedidos'] += parseInt(row.querySelector(`[data-column="c_pedidos"]`).textContent.replace(/,/g, '')) || 0;
|
||||
grandTotals['cp_t'] += parseInt(row.querySelector(`[data-column="cp_t"]`).textContent.replace(/,/g, '')) || 0;
|
||||
grandTotals['cp_f'] += parseInt(row.querySelector(`[data-column="cp_f"]`).textContent.replace(/,/g, '')) || 0;
|
||||
grandTotals['sc'] += parseFloat(row.querySelector(`[data-column="sc"]`).textContent.replace(/,/g, '')) || 0;
|
||||
grandTotals['total-ingresos'] += parseFloat(row.querySelector('.total-ingresos').textContent.replace(/,/g, '')) || 0;
|
||||
grandTotals['total-inversion'] += parseFloat(row.querySelector('.total-inversion').textContent.replace(/,/g, '')) || 0;
|
||||
@ -239,7 +247,7 @@ document.addEventListener('DOMContentLoaded', function() {
|
||||
for (const key in grandTotals) {
|
||||
const th = table.querySelector(`tfoot [data-total-column="${key}"]`);
|
||||
if (th) {
|
||||
if (key === 'c_pedidos') {
|
||||
if (key === 'cp_t' || key === 'cp_f') {
|
||||
th.textContent = grandTotals[key];
|
||||
} else {
|
||||
th.textContent = grandTotals[key].toFixed(2);
|
||||
@ -269,7 +277,7 @@ document.addEventListener('DOMContentLoaded', function() {
|
||||
const column = cell.dataset.column;
|
||||
let value;
|
||||
|
||||
if (column === 'c_pedidos') {
|
||||
if (column === 'cp_t' || column === 'cp_f') {
|
||||
value = parseInt(cell.textContent.replace(/,/g, ''));
|
||||
} else {
|
||||
value = parseFloat(cell.textContent.replace(/,/g, ''));
|
||||
@ -279,7 +287,7 @@ document.addEventListener('DOMContentLoaded', function() {
|
||||
value = 0;
|
||||
}
|
||||
|
||||
if (column === 'c_pedidos') {
|
||||
if (column === 'cp_t' || column === 'cp_f') {
|
||||
cell.textContent = value;
|
||||
} else {
|
||||
cell.textContent = value.toFixed(2);
|
||||
|
||||
@ -10,7 +10,7 @@ if ($data) {
|
||||
|
||||
// Column name validation to prevent SQL injection
|
||||
$allowed_columns = [
|
||||
'c_pedidos', 'bcp_yape', 'b_nacion', 'interbank', 'bbva', 'otros_ingresos',
|
||||
'c_pedidos', 'cp_t', 'cp_f', 'bcp_yape', 'b_nacion', 'interbank', 'bbva', 'otros_ingresos',
|
||||
'tu1', 'tu2', 'tu3', 'fl1', 'fl2', 'fl3',
|
||||
'rc_contraent', 'sc', 'total_inversion_publicitaria'
|
||||
];
|
||||
|
||||