Autosave: 20260722-224452
This commit is contained in:
parent
0897d311c3
commit
88aaaa4cae
@ -528,6 +528,64 @@ h1, .h1 {
|
||||
height: 320px !important;
|
||||
}
|
||||
|
||||
.cc-kpi-status-legend {
|
||||
border: 1px solid rgba(15, 23, 42, .08);
|
||||
border-radius: 1rem;
|
||||
padding: 1rem;
|
||||
background: linear-gradient(180deg, rgba(248, 250, 252, .94), rgba(255, 255, 255, .98));
|
||||
box-shadow: inset 0 1px 0 rgba(255, 255, 255, .6);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: .65rem;
|
||||
}
|
||||
|
||||
.cc-kpi-status-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: .75rem;
|
||||
padding: .75rem .85rem;
|
||||
border-radius: .95rem;
|
||||
border: 1px solid rgba(15, 23, 42, .06);
|
||||
background: #fff;
|
||||
box-shadow: 0 .55rem 1rem rgba(15, 23, 42, .04);
|
||||
}
|
||||
|
||||
.cc-kpi-status-dot {
|
||||
width: .9rem;
|
||||
height: .9rem;
|
||||
border-radius: 999px;
|
||||
flex: 0 0 .9rem;
|
||||
border: 2px solid rgba(255, 255, 255, .9);
|
||||
box-shadow: 0 0 0 1px rgba(15, 23, 42, .06);
|
||||
}
|
||||
|
||||
.cc-kpi-status-text {
|
||||
min-width: 0;
|
||||
flex: 1 1 auto;
|
||||
}
|
||||
|
||||
.cc-kpi-status-label {
|
||||
font-size: .94rem;
|
||||
font-weight: 700;
|
||||
line-height: 1.1;
|
||||
color: #111827;
|
||||
}
|
||||
|
||||
.cc-kpi-status-count {
|
||||
font-size: .78rem;
|
||||
color: #6c757d;
|
||||
line-height: 1.1;
|
||||
margin-top: .18rem;
|
||||
}
|
||||
|
||||
.cc-kpi-status-percent {
|
||||
font-size: 1.05rem;
|
||||
font-weight: 800;
|
||||
line-height: 1;
|
||||
color: #111827;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.cc-kpi-footnote {
|
||||
font-size: .85rem;
|
||||
color: #6c757d;
|
||||
@ -543,6 +601,18 @@ h1, .h1 {
|
||||
min-height: 280px;
|
||||
height: 280px !important;
|
||||
}
|
||||
|
||||
.cc-kpi-status-legend {
|
||||
padding: .9rem;
|
||||
}
|
||||
|
||||
.cc-kpi-status-item {
|
||||
padding: .7rem .75rem;
|
||||
}
|
||||
|
||||
.cc-kpi-status-percent {
|
||||
font-size: 1rem;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 575.98px) {
|
||||
|
||||
@ -479,15 +479,15 @@ try {
|
||||
? ($selectedAssessorFilterLabel !== '' ? 'KPI diario · ' . $selectedAssessorFilterLabel : 'KPI diario por asesora')
|
||||
: 'Mi KPI diario';
|
||||
$performanceSubtitle = $isAdmin
|
||||
? 'Confirmación = pedidos asignados hoy confirmados hoy. Recuperación = pedidos de días anteriores confirmados hoy.'
|
||||
: 'Tus KPIs de hoy se calculan sobre tus pedidos asignados. Confirmación y recuperación se separan para ver tu productividad real.';
|
||||
? 'Confirmación = pedidos asignados hoy confirmados hoy. Recuperación = pedidos asignados antes de hoy confirmados hoy.'
|
||||
: 'Tus KPIs de hoy se calculan sobre tus pedidos asignados hoy. Confirmación = asignados hoy confirmados hoy. Recuperación = asignados antes de hoy confirmados hoy.';
|
||||
$performanceDashboardHtml = cc_test_render_performance_dashboard($performanceDashboard, [
|
||||
'title' => $performanceTitle,
|
||||
'subtitle' => $performanceSubtitle,
|
||||
'chart1_title' => $isAdmin ? 'Rendimiento por asesora hoy' : 'Mi rendimiento de hoy',
|
||||
'chart1_note' => 'Asignados, confirmados nuevos, recuperados y cancelados de hoy.',
|
||||
'chart1_title' => $isAdmin ? 'Distribución de estados' : 'Mi distribución de estados',
|
||||
'chart1_note' => 'Verde = confirmados de pedidos asignados hoy, amarillo = confirmados de pedidos asignados antes de hoy, azul = seguimiento abierto, gris = repetidos, rojo = cancelados. La lista lateral muestra el % de cada estado.',
|
||||
'chart2_title' => $isAdmin ? 'Evolución de los últimos 7 días' : 'Tu evolución de los últimos 7 días',
|
||||
'chart2_note' => 'La línea de confirmados separa pedidos nuevos y recuperados.',
|
||||
'chart2_note' => 'La línea de confirmados separa pedidos asignados hoy y pedidos asignados antes de hoy.',
|
||||
'footnote' => 'Seguimiento abierto = pedidos en SE ENVIO NUMERO DE CUENTA que todavía no pasan a Promo Final.',
|
||||
]);
|
||||
|
||||
|
||||
@ -678,11 +678,11 @@ try {
|
||||
$performanceDashboard = cc_test_build_performance_dashboard($orders, $assessors, 7);
|
||||
$performanceDashboardHtml = cc_test_render_performance_dashboard($performanceDashboard, [
|
||||
'title' => 'KPI diario por asesora',
|
||||
'subtitle' => 'Confirmación = pedidos asignados hoy confirmados hoy. Recuperación = pedidos de días anteriores confirmados hoy.',
|
||||
'chart1_title' => 'Rendimiento por asesora hoy',
|
||||
'chart1_note' => 'Asignados, confirmados nuevos, recuperados y cancelados de hoy.',
|
||||
'subtitle' => 'Confirmación = pedidos asignados hoy confirmados hoy. Recuperación = pedidos asignados antes de hoy confirmados hoy.',
|
||||
'chart1_title' => 'Distribución de estados',
|
||||
'chart1_note' => 'Verde = confirmados de pedidos asignados hoy, amarillo = confirmados de pedidos asignados antes de hoy, azul = seguimiento abierto, gris = repetidos, rojo = cancelados. La lista lateral muestra el % de cada estado.',
|
||||
'chart2_title' => 'Evolución de los últimos 7 días',
|
||||
'chart2_note' => 'La línea de confirmados separa pedidos nuevos y recuperados.',
|
||||
'chart2_note' => 'La línea de confirmados separa pedidos asignados hoy y pedidos asignados antes de hoy.',
|
||||
'footnote' => 'Seguimiento abierto = pedidos en SE ENVIO NUMERO DE CUENTA que todavía no pasan a Promo Final.',
|
||||
]);
|
||||
|
||||
|
||||
@ -725,6 +725,7 @@ function cc_test_build_performance_dashboard(array $orders, array $assessors, in
|
||||
'recovered' => 0,
|
||||
'canceled' => 0,
|
||||
'followup_open' => 0,
|
||||
'repeated' => 0,
|
||||
];
|
||||
}
|
||||
|
||||
@ -735,6 +736,7 @@ function cc_test_build_performance_dashboard(array $orders, array $assessors, in
|
||||
'confirmed_today_recovered' => 0,
|
||||
'confirmed_today_total' => 0,
|
||||
'canceled_today' => 0,
|
||||
'repeated_today' => 0,
|
||||
'followup_open' => 0,
|
||||
'confirm_rate_today' => 0.0,
|
||||
'recovery_rate_today' => 0.0,
|
||||
@ -760,6 +762,7 @@ function cc_test_build_performance_dashboard(array $orders, array $assessors, in
|
||||
'recovered' => 0,
|
||||
'canceled' => 0,
|
||||
'followup_open' => 0,
|
||||
'repeated' => 0,
|
||||
];
|
||||
}
|
||||
|
||||
@ -774,6 +777,7 @@ function cc_test_build_performance_dashboard(array $orders, array $assessors, in
|
||||
$isPromoFinal = !empty($followupInfo['is_promo_final']) && !empty($order['promo_final_evidence_path']);
|
||||
$isConfirmed = in_array($estado, cc_test_confirmed_states(), true);
|
||||
$isCanceled = $estado === 'CANCELADO';
|
||||
$isRepeated = $estado === 'REPETIDO';
|
||||
$isFollowupOpen = $estado === 'SE ENVIO NUMERO DE CUENTA' && !$isPromoFinal;
|
||||
|
||||
if ($assignedKey === $todayKey) {
|
||||
@ -790,8 +794,10 @@ function cc_test_build_performance_dashboard(array $orders, array $assessors, in
|
||||
$summary['followup_open']++;
|
||||
}
|
||||
|
||||
$confirmedReferenceKey = $assignedKey ?? $originKey;
|
||||
|
||||
if ($isConfirmed && $actionKey === $todayKey) {
|
||||
if ($originKey !== null && $originKey < $todayKey) {
|
||||
if ($confirmedReferenceKey !== null && $confirmedReferenceKey < $todayKey) {
|
||||
$bucketStats[$bucketKey]['recovered']++;
|
||||
$summary['confirmed_today_recovered']++;
|
||||
} else {
|
||||
@ -800,7 +806,7 @@ function cc_test_build_performance_dashboard(array $orders, array $assessors, in
|
||||
}
|
||||
|
||||
if (isset($trend[$actionKey])) {
|
||||
if ($originKey !== null && $originKey < $actionKey) {
|
||||
if ($confirmedReferenceKey !== null && $confirmedReferenceKey < $actionKey) {
|
||||
$trend[$actionKey]['recovered']++;
|
||||
} else {
|
||||
$trend[$actionKey]['confirmed_new']++;
|
||||
@ -816,6 +822,11 @@ function cc_test_build_performance_dashboard(array $orders, array $assessors, in
|
||||
$trend[$actionKey]['canceled']++;
|
||||
}
|
||||
}
|
||||
|
||||
if ($isRepeated && $actionKey === $todayKey) {
|
||||
$bucketStats[$bucketKey]['repeated']++;
|
||||
$summary['repeated_today']++;
|
||||
}
|
||||
}
|
||||
|
||||
$summary['confirmed_today_total'] = $summary['confirmed_today_new'] + $summary['confirmed_today_recovered'];
|
||||
@ -886,6 +897,37 @@ function cc_test_build_performance_dashboard(array $orders, array $assessors, in
|
||||
|
||||
return [
|
||||
'summary' => $summary,
|
||||
'status_chart' => [
|
||||
'labels' => ['Confirmados nuevos', 'Recuperados', 'Seguimiento abierto', 'Repetidos', 'Cancelados'],
|
||||
'datasets' => [
|
||||
[
|
||||
'label' => 'Estados',
|
||||
'data' => [
|
||||
(int) ($summary['confirmed_today_new'] ?? 0),
|
||||
(int) ($summary['confirmed_today_recovered'] ?? 0),
|
||||
(int) ($summary['followup_open'] ?? 0),
|
||||
(int) ($summary['repeated_today'] ?? 0),
|
||||
(int) ($summary['canceled_today'] ?? 0),
|
||||
],
|
||||
'backgroundColor' => [
|
||||
'rgba(25, 135, 84, 0.9)',
|
||||
'rgba(255, 193, 7, 0.92)',
|
||||
'rgba(13, 110, 253, 0.88)',
|
||||
'rgba(108, 117, 125, 0.84)',
|
||||
'rgba(220, 53, 69, 0.9)',
|
||||
],
|
||||
'borderColor' => [
|
||||
'rgba(25, 135, 84, 1)',
|
||||
'rgba(245, 158, 11, 1)',
|
||||
'rgba(13, 110, 253, 1)',
|
||||
'rgba(108, 117, 125, 1)',
|
||||
'rgba(220, 53, 69, 1)',
|
||||
],
|
||||
'borderWidth' => 2,
|
||||
'hoverOffset' => 6,
|
||||
],
|
||||
],
|
||||
],
|
||||
'advisor_count' => $advisorCount,
|
||||
'advisor_chart' => [
|
||||
'labels' => $advisorLabels,
|
||||
@ -983,11 +1025,11 @@ function cc_test_build_performance_dashboard(array $orders, array $assessors, in
|
||||
function cc_test_render_performance_dashboard(array $dashboard, array $options = []): string
|
||||
{
|
||||
$title = trim((string) ($options['title'] ?? 'KPI diario por asesora'));
|
||||
$subtitle = trim((string) ($options['subtitle'] ?? 'Confirmación = pedidos de hoy cerrados hoy. Recuperación = pedidos de otros días cerrados hoy.'));
|
||||
$chart1Title = trim((string) ($options['chart1_title'] ?? 'Rendimiento por asesora hoy'));
|
||||
$chart1Note = trim((string) ($options['chart1_note'] ?? 'Asignados, confirmados nuevos, recuperados y cancelados de hoy.'));
|
||||
$subtitle = trim((string) ($options['subtitle'] ?? 'Confirmación = pedidos asignados hoy confirmados hoy. Recuperación = pedidos asignados antes de hoy confirmados hoy.'));
|
||||
$chart1Title = trim((string) ($options['chart1_title'] ?? 'Distribución de estados'));
|
||||
$chart1Note = trim((string) ($options['chart1_note'] ?? 'Verde = confirmados de pedidos asignados hoy, amarillo = confirmados de pedidos asignados antes de hoy, azul = seguimiento abierto, gris = repetidos, rojo = cancelados. La lista lateral muestra el % de cada estado.'));
|
||||
$chart2Title = trim((string) ($options['chart2_title'] ?? 'Evolución de los últimos 7 días'));
|
||||
$chart2Note = trim((string) ($options['chart2_note'] ?? 'La línea de confirmados separa pedidos nuevos y recuperados.'));
|
||||
$chart2Note = trim((string) ($options['chart2_note'] ?? 'La línea de confirmados separa pedidos asignados hoy y pedidos asignados antes de hoy.'));
|
||||
$footnote = trim((string) ($options['footnote'] ?? 'Seguimiento abierto = pedidos en SE ENVIO NUMERO DE CUENTA que todavía no pasan a Promo Final.'));
|
||||
$sectionId = preg_replace('/[^A-Za-z0-9_-]+/', '', (string) ($options['section_id'] ?? 'ccPerformanceDashboard'));
|
||||
if ($sectionId === '') {
|
||||
@ -995,9 +1037,38 @@ function cc_test_render_performance_dashboard(array $dashboard, array $options =
|
||||
}
|
||||
|
||||
$summary = (array) ($dashboard['summary'] ?? []);
|
||||
$advisorChart = (array) ($dashboard['advisor_chart'] ?? []);
|
||||
$trendChart = (array) ($dashboard['trend_chart'] ?? []);
|
||||
$advisorCount = (int) ($dashboard['advisor_count'] ?? 0);
|
||||
$statusTotal = (int) ($summary['confirmed_today_new'] ?? 0)
|
||||
+ (int) ($summary['confirmed_today_recovered'] ?? 0)
|
||||
+ (int) ($summary['followup_open'] ?? 0)
|
||||
+ (int) ($summary['repeated_today'] ?? 0)
|
||||
+ (int) ($summary['canceled_today'] ?? 0);
|
||||
|
||||
$statusLegendItems = [];
|
||||
$statusLegendRows = [
|
||||
['label' => 'Confirmados nuevos', 'value' => (int) ($summary['confirmed_today_new'] ?? 0), 'color' => 'rgba(25, 135, 84, 0.9)'],
|
||||
['label' => 'Recuperados', 'value' => (int) ($summary['confirmed_today_recovered'] ?? 0), 'color' => 'rgba(255, 193, 7, 0.92)'],
|
||||
['label' => 'Seguimiento abierto', 'value' => (int) ($summary['followup_open'] ?? 0), 'color' => 'rgba(13, 110, 253, 0.88)'],
|
||||
['label' => 'Repetidos', 'value' => (int) ($summary['repeated_today'] ?? 0), 'color' => 'rgba(108, 117, 125, 0.84)'],
|
||||
['label' => 'Cancelados', 'value' => (int) ($summary['canceled_today'] ?? 0), 'color' => 'rgba(220, 53, 69, 0.9)'],
|
||||
];
|
||||
if ($statusTotal > 0) {
|
||||
foreach ($statusLegendRows as $row) {
|
||||
$statusLegendItems[] = [
|
||||
'label' => $row['label'],
|
||||
'value' => (int) $row['value'],
|
||||
'percentage' => round(((float) $row['value'] / $statusTotal) * 100, 1),
|
||||
'color' => $row['color'],
|
||||
];
|
||||
}
|
||||
} else {
|
||||
$statusLegendItems[] = [
|
||||
'label' => 'Sin estados',
|
||||
'value' => 0,
|
||||
'percentage' => 0.0,
|
||||
'color' => 'rgba(108, 117, 125, 0.65)',
|
||||
];
|
||||
}
|
||||
|
||||
$badges = [
|
||||
['label' => 'Asignados hoy', 'value' => (int) ($summary['assigned_today'] ?? 0), 'class' => 'cc-kpi-pill-primary'],
|
||||
@ -1016,6 +1087,9 @@ function cc_test_render_performance_dashboard(array $dashboard, array $options =
|
||||
if (isset($summary['confirmed_today_recovered'])) {
|
||||
$extraLineParts[] = (int) $summary['confirmed_today_recovered'] . ' recuperados';
|
||||
}
|
||||
if (isset($summary['repeated_today'])) {
|
||||
$extraLineParts[] = (int) $summary['repeated_today'] . ' repetidos';
|
||||
}
|
||||
if (isset($summary['canceled_today'])) {
|
||||
$extraLineParts[] = (int) $summary['canceled_today'] . ' cancelados';
|
||||
}
|
||||
@ -1057,10 +1131,29 @@ function cc_test_render_performance_dashboard(array $dashboard, array $options =
|
||||
<h3 class="h6 fw-bold mb-1"><?php echo htmlspecialchars($chart1Title); ?></h3>
|
||||
<div class="text-muted small"><?php echo htmlspecialchars($chart1Note); ?></div>
|
||||
</div>
|
||||
<span class="badge text-bg-light border align-self-start"><?php echo (int) $advisorCount; ?> <?php echo $advisorCount === 1 ? 'asesora' : 'asesoras'; ?></span>
|
||||
<span class="badge text-bg-light border align-self-start"><?php echo (int) $statusTotal; ?> <?php echo $statusTotal === 1 ? 'estado' : 'estados'; ?></span>
|
||||
</div>
|
||||
<div class="cc-kpi-chart-wrap mt-3">
|
||||
<canvas id="<?php echo htmlspecialchars($sectionId . 'AdvisorChart'); ?>"></canvas>
|
||||
<div class="row g-3 mt-3 align-items-stretch">
|
||||
<div class="col-12 col-xl-7">
|
||||
<div class="cc-kpi-chart-wrap">
|
||||
<canvas id="<?php echo htmlspecialchars($sectionId . 'StatusChart'); ?>"></canvas>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-12 col-xl-5">
|
||||
<div class="cc-kpi-status-legend h-100">
|
||||
<div class="small text-uppercase text-muted fw-semibold mb-2">Detalle por estado (%)</div>
|
||||
<?php foreach ($statusLegendItems as $item): ?>
|
||||
<div class="cc-kpi-status-item">
|
||||
<span class="cc-kpi-status-dot" style="background: <?php echo htmlspecialchars((string) $item['color']); ?>;"></span>
|
||||
<div class="cc-kpi-status-text">
|
||||
<div class="cc-kpi-status-label"><?php echo htmlspecialchars((string) $item['label']); ?></div>
|
||||
<div class="cc-kpi-status-count"><?php echo (int) $item['value']; ?> pedidos</div>
|
||||
</div>
|
||||
<div class="cc-kpi-status-percent"><?php echo htmlspecialchars(number_format((float) $item['percentage'], 1)); ?>%</div>
|
||||
</div>
|
||||
<?php endforeach; ?>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@ -1092,51 +1185,104 @@ function cc_test_render_performance_dashboard(array $dashboard, array $options =
|
||||
<script>
|
||||
(function () {
|
||||
const dashboard = <?php echo json_encode($dashboard, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES); ?>;
|
||||
const advisorCanvas = document.getElementById(<?php echo json_encode($sectionId . 'AdvisorChart', JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES); ?>);
|
||||
const statusCanvas = document.getElementById(<?php echo json_encode($sectionId . 'StatusChart', JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES); ?>);
|
||||
const trendCanvas = document.getElementById(<?php echo json_encode($sectionId . 'TrendChart', JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES); ?>);
|
||||
|
||||
if (!window.Chart) {
|
||||
return;
|
||||
}
|
||||
|
||||
const advisorData = dashboard.advisor_chart || { labels: [], datasets: [] };
|
||||
const statusData = dashboard.status_chart || { labels: [], datasets: [] };
|
||||
const trendData = dashboard.trend_chart || { labels: [], datasets: [] };
|
||||
const statusLabels = Array.isArray(statusData.labels) ? statusData.labels : [];
|
||||
const statusDataset = Array.isArray(statusData.datasets) && statusData.datasets.length > 0 && statusData.datasets[0]
|
||||
? statusData.datasets[0]
|
||||
: { label: 'Estados', data: [] };
|
||||
const statusValues = Array.isArray(statusDataset.data)
|
||||
? statusDataset.data.map(function (value) {
|
||||
return Number(value) || 0;
|
||||
})
|
||||
: [];
|
||||
const statusTotal = statusValues.reduce(function (sum, value) {
|
||||
return sum + value;
|
||||
}, 0);
|
||||
const statusHasData = statusTotal > 0;
|
||||
const statusBackgroundColors = Array.isArray(statusDataset.backgroundColor) && statusDataset.backgroundColor.length === statusValues.length
|
||||
? statusDataset.backgroundColor
|
||||
: [
|
||||
'rgba(25, 135, 84, 0.9)',
|
||||
'rgba(255, 193, 7, 0.92)',
|
||||
'rgba(13, 110, 253, 0.88)',
|
||||
'rgba(108, 117, 125, 0.84)',
|
||||
'rgba(220, 53, 69, 0.9)'
|
||||
];
|
||||
const statusBorderColors = Array.isArray(statusDataset.borderColor) && statusDataset.borderColor.length === statusValues.length
|
||||
? statusDataset.borderColor
|
||||
: [
|
||||
'rgba(25, 135, 84, 1)',
|
||||
'rgba(245, 158, 11, 1)',
|
||||
'rgba(13, 110, 253, 1)',
|
||||
'rgba(108, 117, 125, 1)',
|
||||
'rgba(220, 53, 69, 1)'
|
||||
];
|
||||
const statusChartData = statusHasData
|
||||
? {
|
||||
labels: statusLabels,
|
||||
datasets: [{
|
||||
label: statusDataset.label || 'Estados',
|
||||
data: statusValues,
|
||||
backgroundColor: statusBackgroundColors,
|
||||
borderColor: statusBorderColors,
|
||||
borderWidth: 2,
|
||||
hoverOffset: 6
|
||||
}]
|
||||
}
|
||||
: {
|
||||
labels: ['Sin estados'],
|
||||
datasets: [{
|
||||
label: 'Sin datos',
|
||||
data: [1],
|
||||
backgroundColor: ['rgba(108, 117, 125, 0.22)'],
|
||||
borderColor: ['rgba(108, 117, 125, 0.55)'],
|
||||
borderWidth: 2,
|
||||
hoverOffset: 0
|
||||
}]
|
||||
};
|
||||
|
||||
if (advisorCanvas) {
|
||||
new Chart(advisorCanvas, {
|
||||
type: 'bar',
|
||||
data: advisorData,
|
||||
if (statusCanvas) {
|
||||
new Chart(statusCanvas, {
|
||||
type: 'doughnut',
|
||||
data: statusChartData,
|
||||
options: {
|
||||
responsive: true,
|
||||
maintainAspectRatio: false,
|
||||
indexAxis: 'y',
|
||||
cutout: '58%',
|
||||
radius: '92%',
|
||||
plugins: {
|
||||
legend: {
|
||||
position: 'bottom'
|
||||
display: false
|
||||
},
|
||||
tooltip: {
|
||||
mode: 'index',
|
||||
intersect: false
|
||||
}
|
||||
},
|
||||
scales: {
|
||||
x: {
|
||||
beginAtZero: true,
|
||||
ticks: {
|
||||
precision: 0,
|
||||
stepSize: 1
|
||||
}
|
||||
},
|
||||
y: {
|
||||
ticks: {
|
||||
autoSkip: false
|
||||
callbacks: {
|
||||
label: function(context) {
|
||||
if (!statusHasData) {
|
||||
return 'Sin estados';
|
||||
}
|
||||
const label = context.label || '';
|
||||
const value = typeof context.raw === 'number' ? context.raw : Number(context.raw || 0);
|
||||
const data = Array.isArray(context.dataset.data) ? context.dataset.data : [];
|
||||
const total = data.reduce(function (sum, item) {
|
||||
return sum + (Number(item) || 0);
|
||||
}, 0);
|
||||
const percentage = total > 0 ? ((value / total) * 100).toFixed(1) : '0.0';
|
||||
return label + ': ' + value + ' (' + percentage + '%)';
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
if (trendCanvas) {
|
||||
new Chart(trendCanvas, {
|
||||
type: 'line',
|
||||
|
||||
@ -13,6 +13,8 @@ ensureTipoPaqueteEnumDefinition($pdo);
|
||||
|
||||
$user_id = $_SESSION['user_id'];
|
||||
$user_role = $_SESSION['user_role'] ?? 'Asesor';
|
||||
$can_edit_estado_contraentrega = !in_array($user_role, ['Logistica', 'Logística'], true);
|
||||
$can_edit_paquete_contraentrega = !in_array($user_role, ['Control Logistico', 'Control Logístico'], true);
|
||||
|
||||
$pedido = [
|
||||
'id' => '',
|
||||
@ -318,7 +320,7 @@ include 'layout_header.php';
|
||||
<div class="row">
|
||||
<div class="col-md-4 mb-3">
|
||||
<label for="tipo_paquete" class="form-label">Paquete</label>
|
||||
<select class="form-select" id="tipo_paquete" name="tipo_paquete">
|
||||
<select class="form-select" id="tipo_paquete" name="tipo_paquete" <?php echo (!empty($pedido['id']) && !$can_edit_paquete_contraentrega) ? 'disabled' : ''; ?>>
|
||||
<option value="">Seleccionar</option>
|
||||
<?php
|
||||
$current_paquete = $pedido['tipo_paquete'] ?? '';
|
||||
@ -332,7 +334,7 @@ include 'layout_header.php';
|
||||
</div>
|
||||
<div class="col-md-4 mb-3">
|
||||
<label for="estado" class="form-label">Estado del Pedido</label>
|
||||
<select class="form-select" id="estado" name="estado" required>
|
||||
<select class="form-select" id="estado" name="estado" required <?php echo (!empty($pedido['id']) && !$can_edit_estado_contraentrega) ? 'disabled' : ''; ?>>
|
||||
<?php
|
||||
$current_status = $pedido['estado'] ?? 'RUTA_CONTRAENTREGA';
|
||||
$statuses = ['RUTA_CONTRAENTREGA', 'PENDIENTE', 'NO CONTESTO, VOLVER A LLAMAR', 'CANCELADO', 'REPROGRAMADO', 'ENTREGA EXITOSA'];
|
||||
|
||||
@ -86,6 +86,11 @@ $user_id = $_SESSION['user_id'];
|
||||
$user_role = $_SESSION['user_role'] ?? 'Asesor';
|
||||
$is_admin = in_array($user_role, ['Administrador', 'admin'], true);
|
||||
|
||||
$can_edit_status = !in_array($user_role, ['Logistica', 'Logística'], true);
|
||||
$can_edit_paquete = !in_array($user_role, ['Control Logistico', 'Control Logístico'], true);
|
||||
$estado_cell_title = $can_edit_status ? 'Haz clic para editar el estado' : 'Tu rol no puede editar el estado en esta sección';
|
||||
$paquete_cell_title = $can_edit_paquete ? 'Haz clic para editar el paquete' : 'Tu rol no puede editar el paquete en esta sección';
|
||||
|
||||
// Fetch years for the filter
|
||||
$years_query = "SELECT DISTINCT YEAR(created_at) as year FROM pedidos";
|
||||
if ($user_role === 'Asesor') {
|
||||
@ -244,6 +249,10 @@ include 'layout_header.php';
|
||||
white-space: normal;
|
||||
font-size: 0.82rem;
|
||||
}
|
||||
#pedidos-table .cc-cell-readonly {
|
||||
cursor: not-allowed !important;
|
||||
opacity: 0.9;
|
||||
}
|
||||
|
||||
|
||||
/* Contraentrega: icono para ver/subir verificación (foto + tracking) */
|
||||
@ -414,12 +423,12 @@ include 'layout_header.php';
|
||||
</div>
|
||||
</td>
|
||||
<td class="editable" data-id="<?php echo $pedido['id']; ?>" data-field="seguimiento"><?php echo htmlspecialchars($pedido['seguimiento'] ?? ''); ?></td>
|
||||
<td class="editable-paquete" data-id="<?php echo $pedido['id']; ?>" data-value="<?php echo htmlspecialchars($pedido['tipo_paquete'] ?? ''); ?>" style="cursor: pointer;">
|
||||
<td class="editable-paquete<?php echo $can_edit_paquete ? '' : ' cc-cell-readonly'; ?>" data-id="<?php echo $pedido['id']; ?>" data-value="<?php echo htmlspecialchars($pedido['tipo_paquete'] ?? ''); ?>" style="cursor: <?php echo $can_edit_paquete ? 'pointer' : 'not-allowed'; ?>;" title="<?php echo htmlspecialchars($paquete_cell_title, ENT_QUOTES, 'UTF-8'); ?>" aria-disabled="<?php echo $can_edit_paquete ? 'false' : 'true'; ?>">
|
||||
<span class="badge" style="<?php echo getPaqueteStyle($pedido['tipo_paquete'] ?? ''); ?>">
|
||||
<?php echo htmlspecialchars(!empty($pedido['tipo_paquete']) ? $pedido['tipo_paquete'] : 'Seleccionar'); ?>
|
||||
</span>
|
||||
</td>
|
||||
<td class="editable-estado" data-id="<?php echo $pedido['id']; ?>" data-value="<?php echo htmlspecialchars($pedido['estado'] ?? ''); ?>" style="cursor: pointer;">
|
||||
<td class="editable-estado<?php echo $can_edit_status ? '' : ' cc-cell-readonly'; ?>" data-id="<?php echo $pedido['id']; ?>" data-value="<?php echo htmlspecialchars($pedido['estado'] ?? ''); ?>" style="cursor: <?php echo $can_edit_status ? 'pointer' : 'not-allowed'; ?>;" title="<?php echo htmlspecialchars($estado_cell_title, ENT_QUOTES, 'UTF-8'); ?>" aria-disabled="<?php echo $can_edit_status ? 'false' : 'true'; ?>">
|
||||
<span class="badge" style="<?php echo getStatusStyle($pedido['estado'] ?? ''); ?>">
|
||||
<?php echo htmlspecialchars(!empty($pedido['estado']) ? $pedido['estado'] : 'Seleccionar'); ?>
|
||||
</span>
|
||||
@ -931,6 +940,9 @@ function getStatusStyleJS(status) {
|
||||
return `background-color: ${bgColor} !important; ${style}`;
|
||||
}
|
||||
|
||||
const canEditPaquete = <?php echo $can_edit_paquete ? 'true' : 'false'; ?>;
|
||||
const canEditEstado = <?php echo $can_edit_status ? 'true' : 'false'; ?>;
|
||||
|
||||
$(document).ready(function() {
|
||||
$('#pedidos-table').DataTable({
|
||||
"language": {
|
||||
@ -952,6 +964,7 @@ document.addEventListener('DOMContentLoaded', function() {
|
||||
if (e.target && (e.target.classList.contains('editable-paquete') || e.target.closest('.editable-paquete'))) {
|
||||
const cell = e.target.classList.contains('editable-paquete') ? e.target : e.target.closest('.editable-paquete');
|
||||
|
||||
if (!canEditPaquete) return;
|
||||
if (cell.querySelector('select')) return; // Already editing
|
||||
|
||||
const currentVal = cell.dataset.value;
|
||||
@ -1024,6 +1037,7 @@ document.addEventListener('DOMContentLoaded', function() {
|
||||
if (e.target && (e.target.classList.contains('editable-estado') || e.target.closest('.editable-estado'))) {
|
||||
const cell = e.target.classList.contains('editable-estado') ? e.target : e.target.closest('.editable-estado');
|
||||
|
||||
if (!canEditEstado) return;
|
||||
if (cell.querySelector('select')) return; // Already editing
|
||||
|
||||
const currentVal = cell.dataset.value;
|
||||
|
||||
@ -197,6 +197,15 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
||||
if ($id) {
|
||||
// UPDATE: Explicit and safe query
|
||||
$user_role = $_SESSION['user_role'] ?? '';
|
||||
$can_edit_estado_contraentrega = !in_array($user_role, ['Logistica', 'Logística'], true);
|
||||
$can_edit_paquete_contraentrega = !in_array($user_role, ['Control Logistico', 'Control Logístico'], true);
|
||||
|
||||
$stmt_current = $pdo->prepare('SELECT estado, tipo_paquete FROM pedidos WHERE id = ?');
|
||||
$stmt_current->execute([$id]);
|
||||
$current_pedido = $stmt_current->fetch(PDO::FETCH_ASSOC);
|
||||
if (!$current_pedido) {
|
||||
die('Pedido no encontrado.');
|
||||
}
|
||||
|
||||
$sql_parts = [
|
||||
"dni_cliente = :dni_cliente",
|
||||
@ -224,7 +233,8 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
||||
"estado = :estado",
|
||||
"tipo_paquete = :tipo_paquete",
|
||||
];
|
||||
$params['estado'] = $_POST['estado'] ?? 'RUTA_CONTRAENTREGA';
|
||||
$params['estado'] = $can_edit_estado_contraentrega ? ($_POST['estado'] ?? 'RUTA_CONTRAENTREGA') : ($current_pedido['estado'] ?? 'RUTA_CONTRAENTREGA');
|
||||
$params['tipo_paquete'] = $can_edit_paquete_contraentrega ? $tipo_paquete : ($current_pedido['tipo_paquete'] ?? null);
|
||||
|
||||
// Conditionally add asesor_id
|
||||
if ($user_role === 'Administrador' && !empty($_POST['asesor_id'])) {
|
||||
|
||||
@ -9,6 +9,12 @@ if (!isset($_SESSION['user_id'])) {
|
||||
exit;
|
||||
}
|
||||
|
||||
$user_role = $_SESSION['user_role'] ?? '';
|
||||
if (in_array($user_role, ['Logistica', 'Logística'], true)) {
|
||||
echo json_encode(['success' => false, 'message' => 'Tu rol no puede editar el estado en ruta contraentrega.']);
|
||||
exit;
|
||||
}
|
||||
|
||||
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
||||
$pedido_id = $_POST['pedido_id'] ?? $_POST['id'] ?? null;
|
||||
$estado = $_POST['estado'] ?? null;
|
||||
|
||||
@ -50,6 +50,12 @@ if (!isset($_SESSION['user_id'])) {
|
||||
exit;
|
||||
}
|
||||
|
||||
$user_role = $_SESSION['user_role'] ?? '';
|
||||
if (in_array($user_role, ['Control Logistico', 'Control Logístico'], true)) {
|
||||
echo json_encode(['success' => false, 'message' => 'Tu rol no puede editar el paquete en ruta contraentrega.']);
|
||||
exit;
|
||||
}
|
||||
|
||||
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
||||
$pedido_id_raw = $_POST['pedido_id'] ?? null;
|
||||
$pedido_id = (is_numeric($pedido_id_raw) && (int)$pedido_id_raw > 0) ? (int)$pedido_id_raw : null;
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user