Autosave: 20260722-233438

This commit is contained in:
Flatlogic Bot 2026-07-22 23:33:16 +00:00
parent 88aaaa4cae
commit 82475a4d30
4 changed files with 205 additions and 56 deletions

View File

@ -512,6 +512,18 @@ h1, .h1 {
background: linear-gradient(135deg, rgba(108, 117, 125, .12), rgba(108, 117, 125, .04));
}
.cc-kpi-pill-total {
background: linear-gradient(135deg, rgba(20, 184, 166, .18), rgba(13, 110, 253, .06));
border-color: rgba(20, 184, 166, .18);
}
.cc-kpi-meta {
font-size: .73rem;
line-height: 1.15;
color: #64748b;
font-weight: 600;
}
.cc-kpi-chart-card {
border-radius: 1.2rem;
overflow: hidden;

View File

@ -251,6 +251,7 @@ $allowedViews = [
'pendientes_hoy' => 'Bandeja principal',
'nuevos_hoy' => 'Pedidos asignados hoy',
'confirmados' => 'Confirmados',
'recuperados' => 'Recuperados',
'seguimiento' => 'Seguimiento',
'promo_final' => 'Promo Final',
'observados' => 'Observados',
@ -290,6 +291,7 @@ $stats = [
'pendientes_hoy' => 0,
'nuevos_hoy' => 0,
'confirmados' => 0,
'recuperados' => 0,
'seguimiento' => 0,
'promo_final' => 0,
'observados' => 0,
@ -462,6 +464,9 @@ try {
if (in_array($order['estado'], cc_test_confirmed_states(), true)) {
$stats['confirmados']++;
}
if (cc_test_is_recovered_today($order, $todayStart)) {
$stats['recuperados']++;
}
if ($order['estado'] === 'SE ENVIO NUMERO DE CUENTA' && !$order['es_promo_final']) {
$stats['seguimiento']++;
}
@ -474,26 +479,33 @@ try {
}
unset($order);
$performanceDashboard = cc_test_build_performance_dashboard($orders, $assessors, 7);
$performanceOrders = $orders;
if ($selectedAssessorFilter !== '') {
$performanceOrders = array_values(array_filter($orders, static function (array $order) use ($selectedAssessorFilter): bool {
return ($order['matches_assessor_filter'] ?? false) === true;
}));
}
$performanceDashboard = cc_test_build_performance_dashboard($performanceOrders, $assessors, 7);
$performanceTitle = $isAdmin
? ($selectedAssessorFilterLabel !== '' ? 'KPI diario · ' . $selectedAssessorFilterLabel : 'KPI diario por asesora')
: 'Mi KPI diario';
$performanceSubtitle = $isAdmin
? '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.';
? 'Confirmación = pedidos asignados hoy confirmados hoy. Recuperación = pedidos asignados antes de hoy confirmados hoy. Rendimiento del día = confirmados totales (nuevos + recuperados) sobre asignados 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. Rendimiento del día = confirmados totales (nuevos + recuperados) sobre asignados hoy.';
$performanceDashboardHtml = cc_test_render_performance_dashboard($performanceDashboard, [
'title' => $performanceTitle,
'subtitle' => $performanceSubtitle,
'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.',
'chart1_note' => 'La dona separa los pedidos de hoy por estado: confirmados nuevos, recuperados, POR LLAMAR, DEVOLVER LLAMADA, OBSERVADO, SE ENVIO NUMERO DE CUENTA, repetidos y cancelados.',
'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 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.',
'footnote' => 'Seguimiento abierto = pedidos asignados hoy en POR LLAMAR, DEVOLVER LLAMADA, OBSERVADO y SE ENVIO NUMERO DE CUENTA que todavía no pasan a Promo Final.',
]);
$panelSignature = cc_test_panel_orders_signature($orders);
$visibleOrders = array_values(array_filter($orders, static function (array $order) use ($view, $selectedAssessorFilter): bool {
$visibleOrders = array_values(array_filter($orders, static function (array $order) use ($view, $selectedAssessorFilter, $todayStart): bool {
if ($selectedAssessorFilter !== '' && !($order['matches_assessor_filter'] ?? false)) {
return false;
}
@ -502,6 +514,7 @@ try {
'pendientes_hoy' => (bool) ($order['es_pendiente_hoy'] ?? false),
'nuevos_hoy' => (bool) ($order['es_nuevo_hoy'] ?? false),
'confirmados' => in_array(($order['estado'] ?? ''), cc_test_confirmed_states(), true),
'recuperados' => cc_test_is_recovered_today($order, $todayStart),
'seguimiento' => ($order['estado'] ?? '') === 'SE ENVIO NUMERO DE CUENTA' && !($order['es_promo_final'] ?? false),
'promo_final' => (bool) ($order['es_promo_final'] ?? false),
'observados' => ($order['estado'] ?? '') === 'OBSERVADO' && !($order['es_promo_final'] ?? false),
@ -528,6 +541,20 @@ try {
}
}
if ($view === 'recuperados') {
$aRecovered = cc_test_dashboard_action_datetime($a);
$bRecovered = cc_test_dashboard_action_datetime($b);
if ($aRecovered && $bRecovered && $aRecovered != $bRecovered) {
return $bRecovered <=> $aRecovered;
}
if ($aRecovered && !$bRecovered) {
return -1;
}
if (!$aRecovered && $bRecovered) {
return 1;
}
}
if ($view === 'promo_final') {
$aDays = (int) ($a['dias_seguimiento'] ?? 0);
$bDays = (int) ($b['dias_seguimiento'] ?? 0);
@ -841,6 +868,16 @@ require_once 'layout_header.php';
</article>
</a>
</div>
<div class="col-md-6 col-xl-2">
<a href="<?php echo htmlspecialchars(cc_test_build_url('call_center_pro.php', array_merge($callCenterParams, ['view' => 'recuperados']))); ?>" class="text-decoration-none">
<article class="card border-0 shadow-sm h-100 <?php echo $view === 'recuperados' ? 'bg-warning-subtle border border-warning' : 'bg-white'; ?>">
<div class="card-body">
<div class="small text-uppercase text-muted mb-2">Recuperados</div>
<div class="display-6 fw-bold mb-0"><?php echo (int) $stats['recuperados']; ?></div>
</div>
</article>
</a>
</div>
<div class="col-md-6 col-xl-2">
<a href="<?php echo htmlspecialchars(cc_test_build_url('call_center_pro.php', array_merge($callCenterParams, ['view' => 'seguimiento']))); ?>" class="text-decoration-none">
<article class="card border-0 shadow-sm h-100 <?php echo $view === 'seguimiento' ? 'bg-primary-subtle border border-primary' : 'bg-white'; ?>">

View File

@ -164,6 +164,7 @@ $allowedViews = [
'pendientes_hoy' => 'Bandeja principal',
'nuevos_hoy' => 'Nuevos de hoy',
'confirmados' => 'Confirmados',
'recuperados' => 'Recuperados',
'seguimiento' => 'Seguimiento',
'promo_final' => 'Promo Final',
'observados' => 'Observados',
@ -206,6 +207,7 @@ $stats = [
'pendientes_hoy' => 0,
'nuevos_hoy' => 0,
'confirmados' => 0,
'recuperados' => 0,
'seguimiento' => 0,
'promo_final' => 0,
'observados' => 0,
@ -663,6 +665,9 @@ try {
if (in_array($order['estado'], cc_test_confirmed_states(), true)) {
$stats['confirmados']++;
}
if (cc_test_is_recovered_today($order, $todayStart)) {
$stats['recuperados']++;
}
if ($order['estado'] === 'SE ENVIO NUMERO DE CUENTA' && !$order['es_promo_final']) {
$stats['seguimiento']++;
}
@ -678,12 +683,12 @@ 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 asignados antes de hoy confirmados hoy.',
'subtitle' => 'Confirmación = pedidos asignados hoy confirmados hoy. Recuperación = pedidos asignados antes de hoy confirmados hoy. Rendimiento del día = confirmados totales (nuevos + recuperados) sobre asignados 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.',
'chart1_note' => 'La dona separa los pedidos de hoy por estado: confirmados nuevos, recuperados, POR LLAMAR, DEVOLVER LLAMADA, OBSERVADO, SE ENVIO NUMERO DE CUENTA, repetidos y cancelados.',
'chart2_title' => 'Evolución de los últimos 7 días',
'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.',
'footnote' => 'Seguimiento abierto = pedidos asignados hoy en POR LLAMAR, DEVOLVER LLAMADA, OBSERVADO y SE ENVIO NUMERO DE CUENTA que todavía no pasan a Promo Final.',
]);
$orderedAssessorKeys = array_values(array_filter(cc_test_allowed_module_user_keys(), static function (string $key) use ($assessors): bool {
@ -753,11 +758,12 @@ try {
];
}
$visibleOrders = array_values(array_filter($orders, static function (array $order) use ($view): bool {
$visibleOrders = array_values(array_filter($orders, static function (array $order) use ($view, $todayStart): bool {
return match ($view) {
'pendientes_hoy' => (bool) ($order['es_pendiente_hoy'] ?? false),
'nuevos_hoy' => (bool) ($order['es_nuevo_hoy'] ?? false),
'confirmados' => in_array(($order['estado'] ?? ''), cc_test_confirmed_states(), true),
'recuperados' => cc_test_is_recovered_today($order, $todayStart),
'seguimiento' => ($order['estado'] ?? '') === 'SE ENVIO NUMERO DE CUENTA' && !($order['es_promo_final'] ?? false),
'promo_final' => (bool) ($order['es_promo_final'] ?? false),
'observados' => ($order['estado'] ?? '') === 'OBSERVADO' && !($order['es_promo_final'] ?? false),
@ -781,6 +787,20 @@ try {
}
}
if ($view === 'recuperados') {
$aRecovered = cc_test_dashboard_action_datetime($a);
$bRecovered = cc_test_dashboard_action_datetime($b);
if ($aRecovered && $bRecovered && $aRecovered != $bRecovered) {
return $bRecovered <=> $aRecovered;
}
if ($aRecovered && !$bRecovered) {
return -1;
}
if (!$aRecovered && $bRecovered) {
return 1;
}
}
if ($view === 'nuevos_hoy') {
$aNew = cc_test_parse_datetime($a['first_seen_at'] ?? null);
$bNew = cc_test_parse_datetime($b['first_seen_at'] ?? null);
@ -1182,6 +1202,16 @@ require_once 'layout_header.php';
</article>
</a>
</div>
<div class="col-md-6 col-xl-2">
<a href="?view=recuperados&store=<?php echo htmlspecialchars($storeKey); ?>" class="text-decoration-none">
<article class="card border-0 shadow-sm h-100 <?php echo $view === 'recuperados' ? 'bg-warning-subtle border border-warning' : 'bg-white'; ?>">
<div class="card-body">
<div class="small text-uppercase text-muted mb-2">Recuperados</div>
<div class="display-6 fw-bold mb-0"><?php echo (int) $stats['recuperados']; ?></div>
</div>
</article>
</a>
</div>
<div class="col-md-6 col-xl-2">
<a href="?view=seguimiento&store=<?php echo htmlspecialchars($storeKey); ?>" class="text-decoration-none">
<article class="card border-0 shadow-sm h-100 <?php echo $view === 'seguimiento' ? 'bg-primary-subtle border border-primary' : 'bg-white'; ?>">

View File

@ -660,6 +660,25 @@ function cc_test_dashboard_action_datetime(array $order): ?DateTimeImmutable
return null;
}
function cc_test_is_recovered_today(array $order, ?DateTimeImmutable $todayStart = null): bool
{
$estado = cc_test_normalize_state((string) ($order['estado'] ?? ''));
if (!in_array($estado, cc_test_confirmed_states(), true)) {
return false;
}
$todayStart = $todayStart ?? new DateTimeImmutable('today');
$assignedDate = cc_test_followup_parse_datetime($order['assigned_at'] ?? null);
$actionDate = cc_test_dashboard_action_datetime($order);
if (!$assignedDate || !$actionDate) {
return false;
}
return $assignedDate->format('Y-m-d') < $todayStart->format('Y-m-d')
&& $actionDate->format('Y-m-d') === $todayStart->format('Y-m-d');
}
function cc_test_dashboard_bucket_key(array $order, array $assessors, bool $includeUnassigned = true): string
{
$assessorKey = trim((string) ($order['assessor_key'] ?? ''));
@ -738,11 +757,19 @@ function cc_test_build_performance_dashboard(array $orders, array $assessors, in
'canceled_today' => 0,
'repeated_today' => 0,
'followup_open' => 0,
'observados_today' => 0,
'confirm_rate_today' => 0.0,
'recovery_rate_today' => 0.0,
'performance_today_rate' => 0.0,
];
$hasUnassignedBucket = false;
$followupStateCounts = [
'POR LLAMAR' => 0,
'DEVOLVER LLAMADA' => 0,
'OBSERVADO' => 0,
'SE ENVIO NUMERO DE CUENTA' => 0,
];
foreach ($orders as $order) {
$summary['scope_total']++;
@ -766,8 +793,6 @@ function cc_test_build_performance_dashboard(array $orders, array $assessors, in
];
}
$originDate = cc_test_dashboard_origin_datetime($order);
$originKey = $originDate ? $originDate->format('Y-m-d') : null;
$assignedDate = cc_test_followup_parse_datetime($order['assigned_at'] ?? null);
$assignedKey = $assignedDate ? $assignedDate->format('Y-m-d') : null;
$actionDate = cc_test_dashboard_action_datetime($order);
@ -778,34 +803,46 @@ function cc_test_build_performance_dashboard(array $orders, array $assessors, in
$isConfirmed = in_array($estado, cc_test_confirmed_states(), true);
$isCanceled = $estado === 'CANCELADO';
$isRepeated = $estado === 'REPETIDO';
$isFollowupOpen = $estado === 'SE ENVIO NUMERO DE CUENTA' && !$isPromoFinal;
$isFollowupState = in_array($estado, cc_test_followup_tracking_states(), true);
$isFollowupOpen = $isFollowupState && !$isPromoFinal;
if ($assignedKey === $todayKey) {
$assignmentKey = $assignedKey;
$isAssignedToday = $assignmentKey === $todayKey;
if ($isAssignedToday) {
$bucketStats[$bucketKey]['assigned_today']++;
$summary['assigned_today']++;
if (isset($trend[$assignedKey])) {
$trend[$assignedKey]['assigned_today']++;
if (isset($trend[$assignmentKey])) {
$trend[$assignmentKey]['assigned_today']++;
}
if ($isFollowupOpen) {
$bucketStats[$bucketKey]['followup_open']++;
$summary['followup_open']++;
if (isset($followupStateCounts[$estado])) {
$followupStateCounts[$estado]++;
}
if ($estado === 'OBSERVADO') {
$summary['observados_today']++;
}
}
}
if ($isFollowupOpen) {
$bucketStats[$bucketKey]['followup_open']++;
$summary['followup_open']++;
}
$confirmedReferenceKey = $assignedKey ?? $originKey;
$confirmedReferenceKey = $assignmentKey;
if ($isConfirmed && $actionKey === $todayKey) {
if ($confirmedReferenceKey !== null && $confirmedReferenceKey < $todayKey) {
$bucketStats[$bucketKey]['recovered']++;
$summary['confirmed_today_recovered']++;
} else {
} elseif ($isAssignedToday) {
$bucketStats[$bucketKey]['confirmed_new']++;
$summary['confirmed_today_new']++;
}
if (isset($trend[$actionKey])) {
if ($isAssignedToday && isset($trend[$actionKey])) {
if ($confirmedReferenceKey !== null && $confirmedReferenceKey < $actionKey) {
$trend[$actionKey]['recovered']++;
} else {
@ -814,7 +851,7 @@ function cc_test_build_performance_dashboard(array $orders, array $assessors, in
}
}
if ($isCanceled && $actionKey === $todayKey) {
if ($isAssignedToday && $isCanceled && $actionKey === $todayKey) {
$bucketStats[$bucketKey]['canceled']++;
$summary['canceled_today']++;
@ -823,7 +860,7 @@ function cc_test_build_performance_dashboard(array $orders, array $assessors, in
}
}
if ($isRepeated && $actionKey === $todayKey) {
if ($isAssignedToday && $isRepeated && $actionKey === $todayKey) {
$bucketStats[$bucketKey]['repeated']++;
$summary['repeated_today']++;
}
@ -836,6 +873,9 @@ function cc_test_build_performance_dashboard(array $orders, array $assessors, in
$summary['recovery_rate_today'] = $summary['confirmed_today_total'] > 0
? round(($summary['confirmed_today_recovered'] / $summary['confirmed_today_total']) * 100, 1)
: 0.0;
$summary['performance_today_rate'] = $summary['assigned_today'] > 0
? round(($summary['confirmed_today_total'] / $summary['assigned_today']) * 100, 1)
: 0.0;
$advisorLabels = [];
$advisorAssigned = [];
@ -898,28 +938,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'],
'labels' => ['Confirmados nuevos', 'Recuperados', 'Por llamar', 'Devolver llamada', 'Observados', 'Se envió número de cuenta', 'Repetidos', 'Cancelados'],
'datasets' => [
[
'label' => 'Estados',
'data' => [
(int) ($summary['confirmed_today_new'] ?? 0),
(int) ($summary['confirmed_today_recovered'] ?? 0),
(int) ($summary['followup_open'] ?? 0),
(int) ($followupStateCounts['POR LLAMAR'] ?? 0),
(int) ($followupStateCounts['DEVOLVER LLAMADA'] ?? 0),
(int) ($followupStateCounts['OBSERVADO'] ?? 0),
(int) ($followupStateCounts['SE ENVIO NUMERO DE CUENTA'] ?? 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(20, 184, 166, 0.84)',
'rgba(13, 110, 253, 0.88)',
'rgba(13, 202, 240, 0.86)',
'rgba(255, 193, 7, 0.82)',
'rgba(253, 126, 20, 0.84)',
'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, 148, 136, 1)',
'rgba(13, 110, 253, 1)',
'rgba(13, 202, 240, 1)',
'rgba(245, 158, 11, 1)',
'rgba(253, 126, 20, 1)',
'rgba(108, 117, 125, 1)',
'rgba(220, 53, 69, 1)',
],
@ -1025,12 +1074,12 @@ 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 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.'));
$subtitle = trim((string) ($options['subtitle'] ?? 'Confirmación = pedidos asignados hoy confirmados hoy. Recuperación = pedidos asignados antes de hoy confirmados hoy. Rendimiento del día = confirmados totales (nuevos + recuperados) sobre asignados hoy.'));
$chart1Title = trim((string) ($options['chart1_title'] ?? 'Distribución de estados de hoy'));
$chart1Note = trim((string) ($options['chart1_note'] ?? 'La dona separa los pedidos de hoy por estado: confirmados nuevos, recuperados, POR LLAMAR, DEVOLVER LLAMADA, OBSERVADO, SE ENVIO NUMERO DE CUENTA, repetidos y cancelados.'));
$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 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.'));
$footnote = trim((string) ($options['footnote'] ?? 'Seguimiento abierto = pedidos asignados hoy en POR LLAMAR, DEVOLVER LLAMADA, OBSERVADO y 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 === '') {
$sectionId = 'ccPerformanceDashboard';
@ -1038,27 +1087,38 @@ function cc_test_render_performance_dashboard(array $dashboard, array $options =
$summary = (array) ($dashboard['summary'] ?? []);
$trendChart = (array) ($dashboard['trend_chart'] ?? []);
$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);
$statusChart = (array) ($dashboard['status_chart'] ?? []);
$statusChartLabels = array_values((array) ($statusChart['labels'] ?? []));
$statusChartDataset = (array) (($statusChart['datasets'][0] ?? []) ?: []);
$statusChartValues = array_map(static function ($value) {
return (int) $value;
}, array_values((array) ($statusChartDataset['data'] ?? [])));
$statusChartColors = is_array($statusChartDataset['backgroundColor'] ?? null)
? array_values($statusChartDataset['backgroundColor'])
: [];
$statusTotal = array_sum($statusChartValues);
$confirmedTodayNew = (int) ($summary['confirmed_today_new'] ?? 0);
$confirmedTodayRecovered = (int) ($summary['confirmed_today_recovered'] ?? 0);
$confirmedTodayTotal = (int) ($summary['confirmed_today_total'] ?? 0);
$performanceTodayRate = (float) ($summary['performance_today_rate'] ?? 0);
$performanceTodayMeta = sprintf(
'%d confirmado%s = %d nuevo%s + %d recuperado%s',
$confirmedTodayTotal,
$confirmedTodayTotal === 1 ? '' : 's',
$confirmedTodayNew,
$confirmedTodayNew === 1 ? '' : 's',
$confirmedTodayRecovered,
$confirmedTodayRecovered === 1 ? '' : 's'
);
$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) {
foreach ($statusChartValues as $index => $value) {
$statusLegendItems[] = [
'label' => $row['label'],
'value' => (int) $row['value'],
'percentage' => round(((float) $row['value'] / $statusTotal) * 100, 1),
'color' => $row['color'],
'label' => (string) ($statusChartLabels[$index] ?? ('Estado ' . ($index + 1))),
'value' => (int) $value,
'percentage' => round(((float) $value / $statusTotal) * 100, 1),
'color' => (string) ($statusChartColors[$index] ?? 'rgba(108, 117, 125, 0.65)'),
];
}
} else {
@ -1072,8 +1132,9 @@ function cc_test_render_performance_dashboard(array $dashboard, array $options =
$badges = [
['label' => 'Asignados hoy', 'value' => (int) ($summary['assigned_today'] ?? 0), 'class' => 'cc-kpi-pill-primary'],
['label' => 'Confirmados hoy', 'value' => (int) ($summary['confirmed_today_total'] ?? 0), 'class' => 'cc-kpi-pill-success'],
['label' => 'Recuperados', 'value' => (int) ($summary['confirmed_today_recovered'] ?? 0), 'class' => 'cc-kpi-pill-warning'],
['label' => 'Confirmados hoy', 'value' => $confirmedTodayTotal, 'class' => 'cc-kpi-pill-success'],
['label' => 'Recuperados', 'value' => $confirmedTodayRecovered, 'class' => 'cc-kpi-pill-warning'],
['label' => 'Rendimiento del día', 'value' => number_format($performanceTodayRate, 1) . '%', 'class' => 'cc-kpi-pill-total', 'meta' => $performanceTodayMeta],
['label' => 'Seguimiento abierto', 'value' => (int) ($summary['followup_open'] ?? 0), 'class' => 'cc-kpi-pill-info'],
['label' => 'Cancelados hoy', 'value' => (int) ($summary['canceled_today'] ?? 0), 'class' => 'cc-kpi-pill-danger'],
['label' => '% confirmación', 'value' => number_format((float) ($summary['confirm_rate_today'] ?? 0), 1) . '%', 'class' => 'cc-kpi-pill-dark'],
@ -1114,6 +1175,9 @@ function cc_test_render_performance_dashboard(array $dashboard, array $options =
<div class="cc-kpi-pill <?php echo htmlspecialchars($badge['class']); ?>">
<span class="cc-kpi-label"><?php echo htmlspecialchars((string) $badge['label']); ?></span>
<span class="cc-kpi-value"><?php echo htmlspecialchars((string) $badge['value']); ?></span>
<?php if (!empty($badge['meta'])): ?>
<span class="cc-kpi-meta"><?php echo htmlspecialchars((string) $badge['meta']); ?></span>
<?php endif; ?>
</div>
<?php endforeach; ?>
</div>
@ -1141,7 +1205,7 @@ function cc_test_render_performance_dashboard(array $dashboard, array $options =
</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>
<div class="small text-uppercase text-muted fw-semibold mb-2">Detalle del KPI de hoy (%)</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>
@ -1211,8 +1275,11 @@ function cc_test_render_performance_dashboard(array $dashboard, array $options =
? statusDataset.backgroundColor
: [
'rgba(25, 135, 84, 0.9)',
'rgba(255, 193, 7, 0.92)',
'rgba(20, 184, 166, 0.84)',
'rgba(13, 110, 253, 0.88)',
'rgba(13, 202, 240, 0.86)',
'rgba(255, 193, 7, 0.82)',
'rgba(253, 126, 20, 0.84)',
'rgba(108, 117, 125, 0.84)',
'rgba(220, 53, 69, 0.9)'
];
@ -1220,8 +1287,11 @@ function cc_test_render_performance_dashboard(array $dashboard, array $options =
? statusDataset.borderColor
: [
'rgba(25, 135, 84, 1)',
'rgba(245, 158, 11, 1)',
'rgba(13, 148, 136, 1)',
'rgba(13, 110, 253, 1)',
'rgba(13, 202, 240, 1)',
'rgba(245, 158, 11, 1)',
'rgba(253, 126, 20, 1)',
'rgba(108, 117, 125, 1)',
'rgba(220, 53, 69, 1)'
];