Autosave: 20260518-171536

This commit is contained in:
Flatlogic Bot 2026-05-18 17:15:37 +00:00
parent 4048403247
commit 515395635b
7 changed files with 289 additions and 13 deletions

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 315 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 349 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 619 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 374 KiB

View File

@ -195,6 +195,40 @@ $stmtTopDetalleCE = $db->query("
");
$topProductosDetalleCE = $stmtTopDetalleCE->fetchAll(PDO::FETCH_ASSOC);
// 14. Rendimiento de Productos en Contraentrega (Para Gráfica de Barras Apiladas)
$stmtProdRendimientoCE = $db->query("
SELECT
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,
COUNT(*) as total
FROM pedidos p
WHERE $date_condition
AND (estado IN ('RUTA_CONTRAENTREGA', 'ENTREGA EXITOSA') OR (estado = 'RETORNADO' AND agencia = 'CONTRAENTREGA'))
GROUP BY producto
ORDER BY total DESC
LIMIT 10
");
$prodRendimientoCE = $stmtProdRendimientoCE->fetchAll(PDO::FETCH_ASSOC);
// 15. Rendimiento de Productos en Provincia
$stmtProdRendimientoProv = $db->query("
SELECT
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,
COUNT(*) as total
FROM pedidos p
WHERE $date_condition
AND NOT (estado IN ('RUTA_CONTRAENTREGA', 'ENTREGA EXITOSA') OR (estado = 'RETORNADO' AND agencia = 'CONTRAENTREGA'))
GROUP BY producto
ORDER BY total DESC
LIMIT 10
");
$prodRendimientoProv = $stmtProdRendimientoProv->fetchAll(PDO::FETCH_ASSOC);
$canalesResumen = [
'Provincia' => ['pedidos' => 0, 'monto' => 0, 'estados' => []],
'Contraentrega' => ['pedidos' => 0, 'monto' => 0, 'estados' => []]
@ -384,7 +418,7 @@ include 'layout_header.php';
<span class="badge bg-primary text-white">Distribución Interna</span>
</div>
<div class="card-body d-flex flex-column align-items-center justify-content-center" style="min-height: 300px;">
<div style="width: 100%; max-width: 280px;">
<div style="width: 100%; max-width: 450px;">
<canvas id="provinciaPieChart"></canvas>
</div>
<div class="mt-3 text-center">
@ -403,7 +437,7 @@ include 'layout_header.php';
<span class="badge bg-warning text-dark">Distribución Interna</span>
</div>
<div class="card-body d-flex flex-column align-items-center justify-content-center" style="min-height: 300px;">
<div style="width: 100%; max-width: 280px;">
<div style="width: 100%; max-width: 450px;">
<canvas id="contraentregaPieChart"></canvas>
</div>
<div class="mt-3 text-center">
@ -438,7 +472,7 @@ include 'layout_header.php';
<h6 class="border-bottom pb-2 mb-3">Distribución de Estados</h6>
<div class="list-group list-group-flush">
<?php
$estadosProv = ['ROTULADO 📦', 'EN TRANSITO 🚛', 'EN DESTINO 🏬', 'COMPLETADO ✅', 'RETORNADO'];
$estadosProv = ['ROTULADO 📦', 'EN TRANSITO 🚛', 'EN DESTINO 🏬', 'COMPLETADO ✅', 'GESTIONES'];
foreach ($estadosProv as $est):
$count = $canalesResumen['Provincia']['estados'][$est] ?? 0;
$pct = $canalesResumen['Provincia']['pedidos'] > 0 ? ($count / array_sum($canalesResumen['Provincia']['estados'])) * 100 : 0;
@ -507,7 +541,7 @@ include 'layout_header.php';
<div class="row">
<!-- Gráfico de Ventas -->
<div class="col-md-8 mb-4">
<div class="col-md-7 mb-4">
<div class="card shadow">
<div class="card-header bg-white">
<h5 class="mb-0">Tendencia de Ventas (<?php echo $label_period; ?>)</h5>
@ -518,7 +552,7 @@ include 'layout_header.php';
</div>
</div>
<!-- Gráfico de Estados -->
<div class="col-md-4 mb-4">
<div class="col-md-5 mb-4">
<div class="card shadow">
<div class="card-header bg-white">
<h5 class="mb-0">Distribución de Estados</h5>
@ -640,6 +674,44 @@ include 'layout_header.php';
</div>
</div>
<div class="row">
<!-- Rendimiento Detallado por Producto en Provincia -->
<div class="col-md-6 mb-4">
<div class="card shadow border-left-primary h-100">
<div class="card-header bg-white d-flex justify-content-between align-items-center">
<h5 class="mb-0 text-primary"><i class="fas fa-chart-bar me-2"></i> Rendimiento Producto (Provincia)</h5>
<span class="badge bg-primary text-white">Top 10</span>
</div>
<div class="card-body">
<div style="height: 350px;">
<canvas id="rendimientoProdProvChart"></canvas>
</div>
<div class="mt-3 small text-muted text-center">
<i class="fas fa-info-circle me-1"></i> Completados vs Gestiones en Provincia.
</div>
</div>
</div>
</div>
<!-- Rendimiento Detallado por Producto en Contraentrega -->
<div class="col-md-6 mb-4">
<div class="card shadow border-left-warning h-100">
<div class="card-header bg-white d-flex justify-content-between align-items-center">
<h5 class="mb-0 text-warning"><i class="fas fa-chart-bar me-2"></i> Rendimiento Producto (Contraentrega)</h5>
<span class="badge bg-warning text-dark">Top 10</span>
</div>
<div class="card-body">
<div style="height: 350px;">
<canvas id="rendimientoProdCEChart"></canvas>
</div>
<div class="mt-3 small text-muted text-center">
<i class="fas fa-info-circle me-1"></i> Éxito vs Retorno en Contraentrega.
</div>
</div>
</div>
</div>
</div>
<div class="row">
<!-- Ventas por Asesor -->
<div class="col-md-12 mb-4">
@ -656,22 +728,32 @@ include 'layout_header.php';
</div>
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
<script src="https://cdn.jsdelivr.net/npm/chartjs-plugin-datalabels@2.0.0"></script>
<script>
// Registrar el plugin de etiquetas de datos
Chart.register(ChartDataLabels);
// Gráfico de Provincia (Doughnut)
const ctxProvincia = document.getElementById('provinciaPieChart').getContext('2d');
new Chart(ctxProvincia, {
type: 'doughnut',
data: {
labels: ['ROTULADO 📦', 'EN TRANSITO 🚛', 'EN DESTINO 🏬', 'COMPLETADO ✅', 'RETORNADO'],
labels: [
'ROTULADO 📦 (<?php echo $canalesResumen['Provincia']['estados']['ROTULADO 📦'] ?? 0; ?>)',
'EN TRANSITO 🚛 (<?php echo $canalesResumen['Provincia']['estados']['EN TRANSITO 🚛'] ?? 0; ?>)',
'EN DESTINO 🏬 (<?php echo $canalesResumen['Provincia']['estados']['EN DESTINO 🏬'] ?? 0; ?>)',
'COMPLETADO ✅ (<?php echo $canalesResumen['Provincia']['estados']['COMPLETADO '] ?? 0; ?>)',
'GESTIONES (<?php echo $canalesResumen['Provincia']['estados']['GESTIONES'] ?? 0; ?>)'
],
datasets: [{
data: [
<?php echo $canalesResumen['Provincia']['estados']['ROTULADO 📦'] ?? 0; ?>,
<?php echo $canalesResumen['Provincia']['estados']['EN TRANSITO 🚛'] ?? 0; ?>,
<?php echo $canalesResumen['Provincia']['estados']['EN DESTINO 🏬'] ?? 0; ?>,
<?php echo $canalesResumen['Provincia']['estados']['COMPLETADO ✅'] ?? 0; ?>,
<?php echo $canalesResumen['Provincia']['estados']['RETORNADO'] ?? 0; ?>
<?php echo $canalesResumen['Provincia']['estados']['GESTIONES'] ?? 0; ?>
],
backgroundColor: ['#ffc107', '#0dcaf0', '#6610f2', '#198754', '#dc3545'],
backgroundColor: ['#ffc107', '#0dcaf0', '#6610f2', '#198754', '#6c757d'],
hoverOffset: 10,
borderWidth: 0,
cutout: '70%'
@ -681,7 +763,27 @@ include 'layout_header.php';
responsive: true,
maintainAspectRatio: false,
plugins: {
legend: { display: false }
legend: {
display: true,
position: 'right',
labels: {
boxWidth: 15,
padding: 20,
color: '#000',
font: { size: 14, weight: 'bold' }
}
},
datalabels: {
color: '#fff',
font: { weight: 'bold', size: 16 },
formatter: (value, ctx) => {
let sum = 0;
let dataArr = ctx.chart.data.datasets[0].data;
dataArr.map(data => { sum += data; });
let percentage = (value * 100 / sum).toFixed(0) + "%";
return value > 0 ? percentage : '';
}
}
}
}
});
@ -691,7 +793,11 @@ include 'layout_header.php';
new Chart(ctxContraentrega, {
type: 'doughnut',
data: {
labels: ['RUTA_CONTRAENTREGA', 'ENTREGA EXITOSA', 'RETORNADO'],
labels: [
'RUTA CE (<?php echo $canalesResumen['Contraentrega']['estados']['RUTA_CONTRAENTREGA'] ?? 0; ?>)',
'EXITOSA ✅ (<?php echo $canalesResumen['Contraentrega']['estados']['ENTREGA EXITOSA'] ?? 0; ?>)',
'RETORNADO (<?php echo $canalesResumen['Contraentrega']['estados']['RETORNADO'] ?? 0; ?>)'
],
datasets: [{
data: [
<?php echo $canalesResumen['Contraentrega']['estados']['RUTA_CONTRAENTREGA'] ?? 0; ?>,
@ -708,7 +814,27 @@ include 'layout_header.php';
responsive: true,
maintainAspectRatio: false,
plugins: {
legend: { display: false }
legend: {
display: true,
position: 'right',
labels: {
boxWidth: 15,
padding: 20,
color: '#000',
font: { size: 14, weight: 'bold' }
}
},
datalabels: {
color: '#fff',
font: { weight: 'bold', size: 16 },
formatter: (value, ctx) => {
let sum = 0;
let dataArr = ctx.chart.data.datasets[0].data;
dataArr.map(data => { sum += data; });
let percentage = (value * 100 / sum).toFixed(0) + "%";
return value > 0 ? percentage : '';
}
}
}
}
});
@ -759,11 +885,43 @@ include 'layout_header.php';
new Chart(ctxEstados, {
type: 'doughnut',
data: {
labels: <?php echo json_encode(array_column($estadosData, 'estado')); ?>,
labels: <?php
$labelsConValores = array_map(function($item) {
return $item['estado'] . ' (' . $item['total'] . ')';
}, $estadosData);
echo json_encode($labelsConValores);
?>,
datasets: [{
data: <?php echo json_encode(array_column($estadosData, 'total')); ?>,
backgroundColor: ['#ffc107', '#0dcaf0', '#198754', '#dc3545', '#6c757d', '#0d6efd', '#20c997', '#6610f2']
backgroundColor: ['#ffc107', '#0dcaf0', '#198754', '#dc3545', '#6c757d', '#0d6efd', '#20c997', '#6610f2'],
borderWidth: 0
}]
},
options: {
responsive: true,
plugins: {
legend: {
display: true,
position: 'right',
labels: {
boxWidth: 15,
padding: 15,
color: '#000',
font: { size: 12, weight: 'bold' }
}
},
datalabels: {
color: '#fff',
font: { weight: 'bold', size: 14 },
formatter: (value, ctx) => {
let sum = 0;
let dataArr = ctx.chart.data.datasets[0].data;
dataArr.map(data => { sum += data; });
let percentage = (value * 100 / sum).toFixed(0) + "%";
return value > 0 ? percentage : '';
}
}
}
}
});
@ -803,6 +961,124 @@ include 'layout_header.php';
}
});
// Gráfico de Rendimiento por Producto en Contraentrega (Barras Apiladas - Vertical)
const ctxRendimientoCE = document.getElementById('rendimientoProdCEChart').getContext('2d');
new Chart(ctxRendimientoCE, {
type: 'bar',
data: {
labels: <?php echo json_encode(array_map(function($p) {
return strlen($p['producto']) > 15 ? substr($p['producto'], 0, 15) . '...' : $p['producto'];
}, $prodRendimientoCE)); ?>,
datasets: [
{
label: 'Exitosa ✅',
data: <?php echo json_encode(array_column($prodRendimientoCE, 'exitosas')); ?>,
backgroundColor: '#198754',
},
{
label: 'Retornado ❌',
data: <?php echo json_encode(array_column($prodRendimientoCE, 'retornados')); ?>,
backgroundColor: '#dc3545',
},
{
label: 'En Ruta 🚛',
data: <?php echo json_encode(array_column($prodRendimientoCE, 'en_ruta')); ?>,
backgroundColor: '#0d6efd',
}
]
},
options: {
indexAxis: 'x',
responsive: true,
maintainAspectRatio: false,
scales: {
x: {
stacked: true,
ticks: {
maxRotation: 45,
minRotation: 45,
color: '#000',
font: { size: 10, weight: 'bold' }
}
},
y: {
stacked: true,
title: { display: true, text: 'Cantidad' }
}
},
plugins: {
legend: {
position: 'top',
labels: { boxWidth: 12, font: { size: 11 } }
},
datalabels: {
color: '#fff',
font: { weight: 'bold', size: 10 },
formatter: (value) => value > 0 ? value : ''
}
}
}
});
// Gráfico de Rendimiento por Producto en Provincia (Barras Apiladas - Vertical)
const ctxRendimientoProv = document.getElementById('rendimientoProdProvChart').getContext('2d');
new Chart(ctxRendimientoProv, {
type: 'bar',
data: {
labels: <?php echo json_encode(array_map(function($p) {
return strlen($p['producto']) > 15 ? substr($p['producto'], 0, 15) . '...' : $p['producto'];
}, $prodRendimientoProv)); ?>,
datasets: [
{
label: 'Completado ✅',
data: <?php echo json_encode(array_column($prodRendimientoProv, 'completados')); ?>,
backgroundColor: '#198754',
},
{
label: 'Gestiones ⚠️',
data: <?php echo json_encode(array_column($prodRendimientoProv, 'gestiones')); ?>,
backgroundColor: '#6c757d',
},
{
label: 'En Proceso 🚛',
data: <?php echo json_encode(array_column($prodRendimientoProv, 'en_proceso')); ?>,
backgroundColor: '#0dcaf0',
}
]
},
options: {
indexAxis: 'x',
responsive: true,
maintainAspectRatio: false,
scales: {
x: {
stacked: true,
ticks: {
maxRotation: 45,
minRotation: 45,
color: '#000',
font: { size: 10, weight: 'bold' }
}
},
y: {
stacked: true,
title: { display: true, text: 'Cantidad' }
}
},
plugins: {
legend: {
position: 'top',
labels: { boxWidth: 12, font: { size: 11 } }
},
datalabels: {
color: '#fff',
font: { weight: 'bold', size: 10 },
formatter: (value) => value > 0 ? value : ''
}
}
}
});
// Gráfico de Asesores
const ctxAsesores = document.getElementById('asesoresChart').getContext('2d');
new Chart(ctxAsesores, {