Autosave: 20260519-183100
This commit is contained in:
parent
2454573a89
commit
4bc4e0b695
@ -118,15 +118,36 @@ $stmtEstados = $db->query("SELECT estado, COUNT(*) as total FROM pedidos p WHERE
|
||||
$estadosData = $stmtEstados->fetchAll(PDO::FETCH_ASSOC);
|
||||
|
||||
// 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");
|
||||
$stmtTopProd = $db->query("SELECT
|
||||
CASE WHEN producto LIKE '%,%' THEN CONCAT('COMBO ', cantidad, ': ', producto) ELSE producto END as producto,
|
||||
SUM(cantidad) as ventas,
|
||||
SUM(monto_total) as monto
|
||||
FROM pedidos p
|
||||
WHERE $date_condition AND estado NOT IN ('RETORNADO', 'GESTIONES')
|
||||
GROUP BY producto
|
||||
ORDER BY monto DESC");
|
||||
$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");
|
||||
$stmtTopProdCE = $db->query("SELECT
|
||||
CASE WHEN producto LIKE '%,%' THEN CONCAT('COMBO ', cantidad, ': ', producto) ELSE producto END as producto,
|
||||
SUM(cantidad) as ventas,
|
||||
SUM(monto_total) as monto
|
||||
FROM pedidos p
|
||||
WHERE $date_condition AND estado IN ('RUTA_CONTRAENTREGA', 'ENTREGA EXITOSA')
|
||||
GROUP BY producto
|
||||
ORDER BY monto DESC");
|
||||
$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");
|
||||
$stmtTopProdProv = $db->query("SELECT
|
||||
CASE WHEN producto LIKE '%,%' THEN CONCAT('COMBO ', cantidad, ': ', producto) ELSE producto END as producto,
|
||||
SUM(cantidad) as ventas,
|
||||
SUM(monto_total) as monto
|
||||
FROM pedidos p
|
||||
WHERE $date_condition AND estado NOT IN ('RUTA_CONTRAENTREGA', 'ENTREGA EXITOSA', 'RETORNADO', 'GESTIONES')
|
||||
GROUP BY producto
|
||||
ORDER BY monto DESC");
|
||||
$topProductosProv = $stmtTopProdProv->fetchAll(PDO::FETCH_ASSOC);
|
||||
|
||||
// 8. Ventas por Asesor (Ajustado al periodo)
|
||||
@ -188,41 +209,53 @@ $detalleCanalRaw = $stmtDetalleCanal->fetchAll(PDO::FETCH_ASSOC);
|
||||
|
||||
// 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
|
||||
SELECT
|
||||
CASE WHEN p.producto LIKE '%,%' THEN CONCAT('COMBO ', p.cantidad, ': ', p.producto) ELSE p.producto END as 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 ('RETORNADO', 'GESTIONES')
|
||||
GROUP BY p.producto
|
||||
ORDER BY total_cantidad DESC
|
||||
GROUP BY producto
|
||||
ORDER BY monto_total DESC
|
||||
");
|
||||
$topProductosDetalle = $stmtTopDetalle->fetchAll(PDO::FETCH_ASSOC);
|
||||
|
||||
// 13b. Top Productos Detallado Contraentrega
|
||||
$stmtTopDetalleCE = $db->query("
|
||||
SELECT p.producto, SUM(p.cantidad) as total_cantidad, SUM(p.monto_total) as monto_total, AVG(pr.costo) as costo_promedio
|
||||
SELECT
|
||||
CASE WHEN p.producto LIKE '%,%' THEN CONCAT('COMBO ', p.cantidad, ': ', p.producto) ELSE p.producto END as 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 IN ('RUTA_CONTRAENTREGA', 'ENTREGA EXITOSA')
|
||||
GROUP BY p.producto
|
||||
ORDER BY total_cantidad DESC
|
||||
GROUP BY producto
|
||||
ORDER BY monto_total DESC
|
||||
");
|
||||
$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
|
||||
SELECT
|
||||
CASE WHEN p.producto LIKE '%,%' THEN CONCAT('COMBO ', p.cantidad, ': ', p.producto) ELSE p.producto END as 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
|
||||
GROUP BY producto
|
||||
ORDER BY monto_total DESC
|
||||
");
|
||||
$topProductosDetalleProv = $stmtTopDetalleProv->fetchAll(PDO::FETCH_ASSOC);
|
||||
|
||||
// 14. Rendimiento de Productos en Contraentrega (Para Gráfica de Barras Apiladas)
|
||||
$stmtProdRendimientoCE = $db->query("
|
||||
SELECT
|
||||
producto,
|
||||
CASE WHEN producto LIKE '%,%' THEN CONCAT('COMBO ', cantidad, ': ', producto) ELSE producto END as producto,
|
||||
COUNT(CASE WHEN estado = 'ENTREGA EXITOSA' THEN 1 END) as exitosas,
|
||||
COUNT(CASE WHEN estado = 'RETORNADO' THEN 1 END) as retornados,
|
||||
COUNT(CASE WHEN estado = 'RUTA_CONTRAENTREGA' THEN 1 END) as en_ruta,
|
||||
@ -238,7 +271,7 @@ $prodRendimientoCE = $stmtProdRendimientoCE->fetchAll(PDO::FETCH_ASSOC);
|
||||
// 15. Rendimiento de Productos en Provincia
|
||||
$stmtProdRendimientoProv = $db->query("
|
||||
SELECT
|
||||
producto,
|
||||
CASE WHEN producto LIKE '%,%' THEN CONCAT('COMBO ', cantidad, ': ', producto) ELSE producto END as producto,
|
||||
COUNT(CASE WHEN estado = 'COMPLETADO ✅' THEN 1 END) as completados,
|
||||
COUNT(CASE WHEN estado = 'GESTIONES' THEN 1 END) as gestiones,
|
||||
COUNT(CASE WHEN estado IN ('ROTULADO 📦', 'EN TRANSITO 🚛', 'EN DESTINO 🏬') THEN 1 END) as en_proceso,
|
||||
@ -253,13 +286,16 @@ $prodRendimientoProv = $stmtProdRendimientoProv->fetchAll(PDO::FETCH_ASSOC);
|
||||
|
||||
// 16. Top Upsells (Combinaciones de productos distintos)
|
||||
$stmtTopUpsell = $db->query("
|
||||
SELECT producto, COUNT(*) as total_pedidos, SUM(monto_total) as recaudo
|
||||
SELECT
|
||||
CONCAT('COMBO ', cantidad, ': ', producto) as producto,
|
||||
COUNT(*) as total_pedidos,
|
||||
SUM(monto_total) as recaudo
|
||||
FROM pedidos p
|
||||
WHERE $date_condition
|
||||
AND producto LIKE '%,%'
|
||||
AND estado NOT IN ('RETORNADO', 'GESTIONES')
|
||||
GROUP BY producto
|
||||
ORDER BY total_pedidos DESC
|
||||
GROUP BY producto, cantidad
|
||||
ORDER BY recaudo DESC
|
||||
");
|
||||
$topUpsells = $stmtTopUpsell->fetchAll(PDO::FETCH_ASSOC);
|
||||
|
||||
@ -650,67 +686,6 @@ include 'layout_header.php';
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- SECCIÓN TOP UPSELL -->
|
||||
<div class="row">
|
||||
<div class="col-12 mb-4">
|
||||
<div class="card shadow h-100 border-left-success">
|
||||
<div class="card-header bg-white d-flex justify-content-between align-items-center">
|
||||
<h5 class="mb-0 text-success"><i class="fas fa-plus-circle me-2"></i> Top Upsell: Combinaciones de Productos Distintos</h5>
|
||||
<span class="badge bg-success text-white">Estrategia de Venta Cruzada</span>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<?php if (empty($topUpsells)): ?>
|
||||
<div class="text-center py-5">
|
||||
<i class="fas fa-shopping-basket fa-3x text-gray-300 mb-3"></i>
|
||||
<p class="text-muted">No se encontraron pedidos con combinaciones de productos distintos en este periodo.</p>
|
||||
</div>
|
||||
<?php else: ?>
|
||||
<div class="row">
|
||||
<div class="col-lg-7 mb-4">
|
||||
<div style="position: relative; height: 400px;">
|
||||
<canvas id="upsellChart"></canvas>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-lg-5">
|
||||
<div class="table-responsive" style="max-height: 400px; overflow-y: auto;">
|
||||
<table class="table table-hover table-sm align-middle">
|
||||
<thead class="table-light sticky-top">
|
||||
<tr>
|
||||
<th>Combinación de Productos</th>
|
||||
<th class="text-center">Pedidos</th>
|
||||
<th class="text-end">Recaudo</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<?php foreach ($topUpsells as $upsell): ?>
|
||||
<tr>
|
||||
<td class="small fw-bold text-dark">
|
||||
<?php
|
||||
$parts = explode(', ', $upsell['producto']);
|
||||
foreach($parts as $index => $part) {
|
||||
echo '<span class="badge bg-light text-dark border mb-1">' . htmlspecialchars($part) . '</span>' . ($index < count($parts)-1 ? ' <i class="fas fa-plus text-muted small mx-1"></i> ' : '');
|
||||
}
|
||||
?>
|
||||
</td>
|
||||
<td class="text-center">
|
||||
<span class="badge rounded-pill bg-success px-3"><?php echo $upsell['total_pedidos']; ?></span>
|
||||
</td>
|
||||
<td class="text-end fw-bold text-success">
|
||||
S/ <?php echo number_format($upsell['recaudo'], 2); ?>
|
||||
</td>
|
||||
</tr>
|
||||
<?php endforeach; ?>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<!-- Top Productos Contraentrega -->
|
||||
<div class="col-md-6 mb-4">
|
||||
@ -797,6 +772,73 @@ include 'layout_header.php';
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- SECCIÓN TOP UPSELL -->
|
||||
<div class="row">
|
||||
<div class="col-12 mb-4">
|
||||
<div class="card shadow h-100 border-left-success">
|
||||
<div class="card-header bg-white d-flex justify-content-between align-items-center">
|
||||
<h5 class="mb-0 text-success"><i class="fas fa-plus-circle me-2"></i> Top Upsell: Combinaciones de Productos Distintos</h5>
|
||||
<span class="badge bg-success text-white">Estrategia de Venta Cruzada</span>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<?php if (empty($topUpsells)): ?>
|
||||
<div class="text-center py-5">
|
||||
<i class="fas fa-shopping-basket fa-3x text-gray-300 mb-3"></i>
|
||||
<p class="text-muted">No se encontraron pedidos con combinaciones de productos distintos en este periodo.</p>
|
||||
</div>
|
||||
<?php else: ?>
|
||||
<div class="row">
|
||||
<div class="col-lg-7 mb-4">
|
||||
<div style="position: relative; height: 400px;">
|
||||
<canvas id="upsellChart"></canvas>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-lg-5">
|
||||
<div class="table-responsive" style="max-height: 400px; overflow-y: auto;">
|
||||
<table class="table table-hover table-sm align-middle">
|
||||
<thead class="table-light sticky-top">
|
||||
<tr>
|
||||
<th>Combinación de Productos</th>
|
||||
<th class="text-center">Pedidos</th>
|
||||
<th class="text-end">Recaudo</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<?php foreach ($topUpsells as $upsell): ?>
|
||||
<tr>
|
||||
<td class="small fw-bold text-dark">
|
||||
<?php
|
||||
$displayName = $upsell['producto'];
|
||||
if (strpos($displayName, 'COMBO ') === 0 && strpos($displayName, ': ') !== false) {
|
||||
$parts_combo = explode(': ', $displayName, 2);
|
||||
echo '<span class="badge bg-primary text-white mb-1">' . htmlspecialchars($parts_combo[0]) . '</span> ';
|
||||
$displayName = $parts_combo[1];
|
||||
}
|
||||
$parts = explode(', ', $displayName);
|
||||
foreach($parts as $index => $part) {
|
||||
echo '<span class="badge bg-light text-dark border mb-1">' . htmlspecialchars($part) . '</span>' . ($index < count($parts)-1 ? ' <i class="fas fa-plus text-muted small mx-1"></i> ' : '');
|
||||
}
|
||||
?>
|
||||
</td>
|
||||
<td class="text-center">
|
||||
<span class="badge rounded-pill bg-success px-3"><?php echo $upsell['total_pedidos']; ?></span>
|
||||
</td>
|
||||
<td class="text-end fw-bold text-success">
|
||||
S/ <?php echo number_format($upsell['recaudo'], 2); ?>
|
||||
</td>
|
||||
</tr>
|
||||
<?php endforeach; ?>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<!-- Rendimiento Detallado por Producto en Provincia -->
|
||||
<div class="col-md-6 mb-4">
|
||||
@ -1055,8 +1097,8 @@ include 'layout_header.php';
|
||||
data: {
|
||||
labels: <?php echo json_encode(array_column($topProductos, 'producto')); ?>,
|
||||
datasets: [{
|
||||
label: 'Unidades',
|
||||
data: <?php echo json_encode(array_column($topProductos, 'ventas')); ?>,
|
||||
label: 'Monto Total (S/)',
|
||||
data: <?php echo json_encode(array_column($topProductos, 'monto')); ?>,
|
||||
backgroundColor: '#0d6efd',
|
||||
borderRadius: 5,
|
||||
barPercentage: 0.7
|
||||
@ -1073,7 +1115,8 @@ include 'layout_header.php';
|
||||
align: 'top',
|
||||
color: '#000',
|
||||
offset: 5,
|
||||
font: { size: 13, weight: 'bold' }
|
||||
font: { size: 13, weight: 'bold' },
|
||||
formatter: (value) => 'S/ ' + new Intl.NumberFormat('es-PE').format(value)
|
||||
}
|
||||
},
|
||||
scales: {
|
||||
@ -1083,7 +1126,7 @@ include 'layout_header.php';
|
||||
drawBorder: false,
|
||||
color: 'rgba(0,0,0,0.05)'
|
||||
},
|
||||
title: { display: true, text: 'Unidades Vendidas', font: { weight: 'bold' } }
|
||||
title: { display: true, text: 'Monto Total (Soles)', font: { weight: 'bold' } }
|
||||
},
|
||||
x: {
|
||||
grid: { display: false },
|
||||
@ -1104,8 +1147,8 @@ include 'layout_header.php';
|
||||
data: {
|
||||
labels: <?php echo json_encode(array_column($topProductosCE, 'producto')); ?>,
|
||||
datasets: [{
|
||||
label: 'Unidades',
|
||||
data: <?php echo json_encode(array_column($topProductosCE, 'ventas')); ?>,
|
||||
label: 'Monto Total (S/)',
|
||||
data: <?php echo json_encode(array_column($topProductosCE, 'monto')); ?>,
|
||||
backgroundColor: '#ffc107',
|
||||
borderRadius: 5,
|
||||
barPercentage: 0.7
|
||||
@ -1121,13 +1164,15 @@ include 'layout_header.php';
|
||||
anchor: 'end',
|
||||
align: 'top',
|
||||
color: '#000',
|
||||
font: { size: 11, weight: 'bold' }
|
||||
font: { size: 11, weight: 'bold' },
|
||||
formatter: (value) => 'S/ ' + new Intl.NumberFormat('es-PE').format(value)
|
||||
}
|
||||
},
|
||||
scales: {
|
||||
y: {
|
||||
beginAtZero: true,
|
||||
grid: { color: 'rgba(0,0,0,0.05)' }
|
||||
grid: { color: 'rgba(0,0,0,0.05)' },
|
||||
title: { display: true, text: 'Monto Total (Soles)' }
|
||||
},
|
||||
x: {
|
||||
grid: { display: false },
|
||||
@ -1148,8 +1193,8 @@ include 'layout_header.php';
|
||||
data: {
|
||||
labels: <?php echo json_encode(array_column($topProductosProv, 'producto')); ?>,
|
||||
datasets: [{
|
||||
label: 'Unidades',
|
||||
data: <?php echo json_encode(array_column($topProductosProv, 'ventas')); ?>,
|
||||
label: 'Monto Total (S/)',
|
||||
data: <?php echo json_encode(array_column($topProductosProv, 'monto')); ?>,
|
||||
backgroundColor: '#0dcaf0',
|
||||
borderRadius: 5,
|
||||
barPercentage: 0.7
|
||||
@ -1165,13 +1210,15 @@ include 'layout_header.php';
|
||||
anchor: 'end',
|
||||
align: 'top',
|
||||
color: '#000',
|
||||
font: { size: 11, weight: 'bold' }
|
||||
font: { size: 11, weight: 'bold' },
|
||||
formatter: (value) => 'S/ ' + new Intl.NumberFormat('es-PE').format(value)
|
||||
}
|
||||
},
|
||||
scales: {
|
||||
y: {
|
||||
beginAtZero: true,
|
||||
grid: { color: 'rgba(0,0,0,0.05)' }
|
||||
grid: { color: 'rgba(0,0,0,0.05)' },
|
||||
title: { display: true, text: 'Monto Total (Soles)' }
|
||||
},
|
||||
x: {
|
||||
grid: { display: false },
|
||||
@ -1348,8 +1395,8 @@ include 'layout_header.php';
|
||||
data: {
|
||||
labels: <?php echo json_encode(array_column($topUpsells, 'producto')); ?>,
|
||||
datasets: [{
|
||||
label: 'Número de Pedidos',
|
||||
data: <?php echo json_encode(array_column($topUpsells, 'total_pedidos')); ?>,
|
||||
label: 'Recaudo Total (S/)',
|
||||
data: <?php echo json_encode(array_column($topUpsells, 'recaudo')); ?>,
|
||||
backgroundColor: '#198754',
|
||||
borderRadius: 5,
|
||||
}]
|
||||
@ -1364,11 +1411,12 @@ include 'layout_header.php';
|
||||
anchor: 'end',
|
||||
align: 'right',
|
||||
color: '#000',
|
||||
font: { weight: 'bold' }
|
||||
font: { weight: 'bold' },
|
||||
formatter: (value) => 'S/ ' + new Intl.NumberFormat('es-PE').format(value)
|
||||
}
|
||||
},
|
||||
scales: {
|
||||
x: { beginAtZero: true, title: { display: true, text: 'Cantidad de Pedidos' } },
|
||||
x: { beginAtZero: true, title: { display: true, text: 'Recaudo (Soles)' } },
|
||||
y: { ticks: { font: { size: 10 } } }
|
||||
}
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user