Autosave: 20260727-195621
This commit is contained in:
parent
de25048671
commit
543b57f55d
@ -16,6 +16,28 @@ $departamentosContraentrega = array_keys($provinciasPorDepartamentoContraentrega
|
||||
$pageTitle = 'Pedidos Asignados | Bandeja de la asesora';
|
||||
$pageDescription = 'Bandeja para que cada asesora gestione sus pedidos asignados con la misma interfaz, estados, historial y acciones.';
|
||||
|
||||
$kpiPeriodConfig = cc_test_resolve_kpi_period((string) ($_GET['kpi_period'] ?? 'today'), (string) ($_GET['kpi_date'] ?? ''));
|
||||
$kpiPeriodKey = (string) ($kpiPeriodConfig['key'] ?? 'today');
|
||||
$kpiPeriodLabel = (string) ($kpiPeriodConfig['label'] ?? 'Hoy');
|
||||
$kpiPeriodCustomDate = $kpiPeriodConfig['custom_date'] instanceof DateTimeImmutable ? $kpiPeriodConfig['custom_date'] : null;
|
||||
$kpiPeriodCustomDateInput = $kpiPeriodCustomDate instanceof DateTimeImmutable ? $kpiPeriodCustomDate->format('Y-m-d') : '';
|
||||
$kpiPeriodTitleLabel = match ($kpiPeriodKey) {
|
||||
'today' => 'diario',
|
||||
'yesterday' => 'ayer',
|
||||
'7', '15', '30' => 'los ' . mb_strtolower($kpiPeriodLabel),
|
||||
default => mb_strtolower($kpiPeriodLabel),
|
||||
};
|
||||
$kpiPeriodStart = $kpiPeriodConfig['start'] instanceof DateTimeImmutable ? $kpiPeriodConfig['start'] : new DateTimeImmutable('today');
|
||||
$kpiPeriodEnd = $kpiPeriodConfig['end'] instanceof DateTimeImmutable ? $kpiPeriodConfig['end'] : $kpiPeriodStart;
|
||||
$kpiPeriodButtons = [
|
||||
'today' => 'Hoy',
|
||||
'yesterday' => 'Ayer',
|
||||
'7' => '7 Días',
|
||||
'15' => '15 Días',
|
||||
'30' => '30 Días',
|
||||
'month' => 'Este Mes',
|
||||
];
|
||||
|
||||
if (!cc_test_current_user_can_access_module($pdo)) {
|
||||
http_response_code(403);
|
||||
require_once 'layout_header.php';
|
||||
@ -676,28 +698,39 @@ try {
|
||||
}));
|
||||
}
|
||||
|
||||
$performanceDashboard = cc_test_build_performance_dashboard($performanceOrders, $assessors, 7);
|
||||
$performanceDashboard = cc_test_build_performance_dashboard_period($performanceOrders, $assessors, $kpiPeriodStart, $kpiPeriodEnd);
|
||||
$performanceSubtitle = $isTuaniRecuperablesStore
|
||||
? 'Los indicadores de esta bandeja se calculan sobre todos los pedidos visibles para cualquier asesora en el periodo seleccionado.'
|
||||
: ($isAdmin
|
||||
? 'Confirmación = pedidos asignados en el periodo confirmados en el mismo periodo. Recuperación = pedidos asignados antes del periodo confirmados dentro del periodo. Rendimiento = confirmados totales (nuevos + recuperados) sobre asignados del periodo.'
|
||||
: 'Tus KPIs del periodo seleccionado se calculan sobre tus pedidos asignados en ese rango. Confirmación = asignados del periodo confirmados en el mismo periodo. Recuperación = asignados antes del periodo confirmados dentro del periodo. Rendimiento = confirmados totales (nuevos + recuperados) sobre asignados del periodo.');
|
||||
|
||||
if ($isTuaniRecuperablesStore) {
|
||||
$performanceTitle = 'KPI compartido de recuperación';
|
||||
$performanceSubtitle = 'Los indicadores de esta bandeja se calculan sobre todos los pedidos visibles para cualquier asesora.';
|
||||
$performanceTitle = $kpiPeriodKey === 'today'
|
||||
? 'KPI compartido de recuperación'
|
||||
: 'KPI compartido de recuperación · ' . $kpiPeriodLabel;
|
||||
$performanceChart1Title = 'Distribución de estados de la bandeja';
|
||||
$performanceChart1Note = 'La dona separa los pedidos visibles por estado en esta bandeja compartida.';
|
||||
$performanceChart2Title = 'Evolución de los últimos 7 días';
|
||||
$performanceChart2Note = 'La línea muestra cómo se mueve la bandeja compartida en los últimos 7 días.';
|
||||
$performanceChart1Note = 'La dona separa los pedidos del periodo seleccionado por estado: confirmados nuevos, recuperados, POR LLAMAR, DEVOLVER LLAMADA, OBSERVADO, SE ENVIO NUMERO DE CUENTA, repetidos y cancelados.';
|
||||
$performanceChart2Title = $kpiPeriodKey === 'today' ? 'Evolución de hoy' : 'Evolución de ' . $kpiPeriodTitleLabel;
|
||||
$performanceChart2Note = 'La línea muestra cómo se mueve la bandeja compartida en el periodo seleccionado.';
|
||||
$performanceFootnote = 'Los indicadores se calculan sobre todos los pedidos visibles de esta bandeja compartida.';
|
||||
} else {
|
||||
$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. 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.';
|
||||
if ($isAdmin) {
|
||||
$performanceTitle = $selectedAssessorFilterLabel !== ''
|
||||
? ($kpiPeriodKey === 'today' ? 'KPI diario · ' . $selectedAssessorFilterLabel : 'KPI de ' . $kpiPeriodTitleLabel . ' · ' . $selectedAssessorFilterLabel)
|
||||
: ($kpiPeriodKey === 'today' ? 'KPI diario por asesora' : 'KPI de ' . $kpiPeriodTitleLabel . ' por asesora');
|
||||
} else {
|
||||
$performanceTitle = $kpiPeriodKey === 'today' ? 'Mi KPI diario' : 'Mi KPI de ' . $kpiPeriodTitleLabel;
|
||||
}
|
||||
$performanceChart1Title = $isAdmin ? 'Distribución de estados' : 'Mi distribución de estados';
|
||||
$performanceChart1Note = '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.';
|
||||
$performanceChart2Title = $isAdmin ? 'Evolución de los últimos 7 días' : 'Tu evolución de los últimos 7 días';
|
||||
$performanceChart2Note = 'La línea de confirmados separa pedidos asignados hoy y pedidos asignados antes de hoy.';
|
||||
$performanceFootnote = '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.';
|
||||
$performanceChart1Note = 'La dona separa los pedidos del periodo seleccionado por estado: confirmados nuevos, recuperados, POR LLAMAR, DEVOLVER LLAMADA, OBSERVADO, SE ENVIO NUMERO DE CUENTA, repetidos y cancelados.';
|
||||
$performanceChart2Title = $kpiPeriodKey === 'today'
|
||||
? ($isAdmin ? 'Evolución de hoy' : 'Tu evolución de hoy')
|
||||
: ($isAdmin ? 'Evolución de ' . $kpiPeriodTitleLabel : 'Tu evolución de ' . $kpiPeriodTitleLabel);
|
||||
$performanceChart2Note = 'La línea de confirmados separa pedidos asignados y recuperados dentro del periodo seleccionado.';
|
||||
$performanceFootnote = 'Seguimiento abierto = pedidos asignados en el periodo en POR LLAMAR, DEVOLVER LLAMADA, OBSERVADO y SE ENVIO NUMERO DE CUENTA que todavía no pasan a Promo Final.';
|
||||
}
|
||||
|
||||
$performanceDashboardHtml = cc_test_render_performance_dashboard($performanceDashboard, [
|
||||
'title' => $performanceTitle,
|
||||
'subtitle' => $performanceSubtitle,
|
||||
@ -706,6 +739,8 @@ try {
|
||||
'chart2_title' => $performanceChart2Title,
|
||||
'chart2_note' => $performanceChart2Note,
|
||||
'footnote' => $performanceFootnote,
|
||||
'performance_label' => $kpiPeriodKey === 'today' ? 'Rendimiento del día' : 'Rendimiento del periodo',
|
||||
'status_detail_heading' => $kpiPeriodKey === 'today' ? 'Detalle del KPI de hoy (%)' : 'Detalle del KPI del periodo (%)',
|
||||
]);
|
||||
|
||||
$visibleOrders = array_values(array_filter($orders, static function (array $order) use ($view, $selectedAssessorFilter, $todayStart): bool {
|
||||
@ -828,7 +863,11 @@ try {
|
||||
$callCenterBaseParams = [
|
||||
'view' => $view,
|
||||
'store' => $storeKey,
|
||||
'kpi_period' => $kpiPeriodKey,
|
||||
];
|
||||
if ($kpiPeriodCustomDateInput !== '') {
|
||||
$callCenterBaseParams['kpi_date'] = $kpiPeriodCustomDateInput;
|
||||
}
|
||||
$callCenterParams = $callCenterBaseParams;
|
||||
if ($selectedAssessorFilter !== '') {
|
||||
$callCenterParams['assessor'] = $selectedAssessorFilter;
|
||||
@ -1083,6 +1122,41 @@ require_once 'layout_header.php';
|
||||
</section>
|
||||
<?php else: ?>
|
||||
|
||||
|
||||
<section class="mb-4">
|
||||
<div class="card border-0 shadow-sm">
|
||||
<div class="card-body py-3">
|
||||
<div class="d-flex flex-column flex-lg-row justify-content-between align-items-lg-center gap-3">
|
||||
<div>
|
||||
<div class="small text-uppercase text-muted fw-semibold mb-1">Filtro KPI</div>
|
||||
<h2 class="h5 fw-bold mb-1">Panel KPI · <?php echo htmlspecialchars($kpiPeriodLabel); ?></h2>
|
||||
<div class="text-muted small">Filtra el panel para ver el avance del periodo seleccionado.</div>
|
||||
</div>
|
||||
<div class="d-flex flex-wrap gap-3 align-items-center justify-content-lg-end">
|
||||
<div class="d-flex flex-wrap gap-2">
|
||||
<?php foreach ($kpiPeriodButtons as $periodKey => $periodLabel): ?>
|
||||
<a href="<?php echo htmlspecialchars(cc_test_build_url('call_center_pro.php', array_merge($callCenterParams, ['kpi_period' => $periodKey, 'kpi_date' => null]))); ?>" class="btn btn-sm <?php echo $kpiPeriodKey === $periodKey ? 'btn-primary' : 'btn-outline-primary'; ?>"><?php echo htmlspecialchars($periodLabel); ?></a>
|
||||
<?php endforeach; ?>
|
||||
</div>
|
||||
<form method="get" class="d-flex flex-wrap align-items-center gap-2 mb-0">
|
||||
<input type="hidden" name="view" value="<?php echo htmlspecialchars($view); ?>">
|
||||
<input type="hidden" name="store" value="<?php echo htmlspecialchars($storeKey); ?>">
|
||||
<?php if ($selectedAssessorFilter !== ''): ?>
|
||||
<input type="hidden" name="assessor" value="<?php echo htmlspecialchars($selectedAssessorFilter); ?>">
|
||||
<?php endif; ?>
|
||||
<input type="hidden" name="kpi_period" value="custom">
|
||||
<label for="kpiDateFilter" class="small text-muted fw-semibold mb-0 text-nowrap">Fecha exacta</label>
|
||||
<div class="input-group input-group-sm" style="min-width: 12rem;">
|
||||
<span class="input-group-text bg-white"><i class="bi bi-calendar3"></i></span>
|
||||
<input type="date" id="kpiDateFilter" name="kpi_date" class="form-control" value="<?php echo htmlspecialchars($kpiPeriodCustomDateInput); ?>" onchange="this.form.submit()" aria-label="Seleccionar fecha exacta del KPI">
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<?php echo $performanceDashboardHtml ?? ''; ?>
|
||||
|
||||
<section class="row g-3 mb-4">
|
||||
@ -1108,6 +1182,10 @@ require_once 'layout_header.php';
|
||||
<form method="get" class="row g-3 align-items-end">
|
||||
<input type="hidden" name="view" value="<?php echo htmlspecialchars($view); ?>">
|
||||
<input type="hidden" name="store" value="<?php echo htmlspecialchars($storeKey); ?>">
|
||||
<input type="hidden" name="kpi_period" value="<?php echo htmlspecialchars($kpiPeriodKey); ?>">
|
||||
<?php if ($kpiPeriodCustomDateInput !== ''): ?>
|
||||
<input type="hidden" name="kpi_date" value="<?php echo htmlspecialchars($kpiPeriodCustomDateInput); ?>">
|
||||
<?php endif; ?>
|
||||
|
||||
<div class="col-12 col-lg-4">
|
||||
<label for="assessorFilter" class="form-label small text-uppercase text-muted fw-semibold mb-1">Filtrar por asesora</label>
|
||||
|
||||
@ -277,15 +277,23 @@ try {
|
||||
$is_admin = in_array($user_role, ['Administrador', 'admin'], true);
|
||||
|
||||
$search_query = trim($_GET['q'] ?? '');
|
||||
$selected_delivery_date = trim((string)($_GET['fecha_entrega'] ?? ''));
|
||||
if ($selected_delivery_date !== '' && !preg_match('/^\d{4}-\d{2}-\d{2}$/', $selected_delivery_date)) {
|
||||
$selected_delivery_date = '';
|
||||
}
|
||||
if ($selected_delivery_date !== '') {
|
||||
[$deliveryYear, $deliveryMonth, $deliveryDay] = array_map('intval', explode('-', $selected_delivery_date));
|
||||
if (!checkdate($deliveryMonth, $deliveryDay, $deliveryYear)) {
|
||||
$selected_delivery_date = '';
|
||||
}
|
||||
}
|
||||
|
||||
if (!$is_admin) {
|
||||
http_response_code(403);
|
||||
exit('Acceso denegado.');
|
||||
}
|
||||
|
||||
// Exportar solo los pedidos con fecha de entrega "mañana"
|
||||
$date_condition = "DATE(p.fecha_entrega) = DATE_ADD(CURDATE(), INTERVAL 1 DAY)";
|
||||
|
||||
// Exportar pedidos con fecha de entrega seleccionada; si no hay filtro, por defecto mañana
|
||||
$sql = "SELECT p.* FROM pedidos p WHERE p.estado IN ('RUTA_CONTRAENTREGA', 'NO CONTESTO, VOLVER A LLAMAR', 'NO CONTESTO, DEVOLVER LLAMADA', 'CANCELADO', 'REPROGRAMADO', 'ENTREGA EXITOSA', 'RETORNADO')";
|
||||
$params = [];
|
||||
|
||||
@ -301,8 +309,13 @@ $is_admin = in_array($user_role, ['Administrador', 'admin'], true);
|
||||
$params[] = "%$search_query%";
|
||||
}
|
||||
|
||||
// Siempre: pedidos cuya fecha de entrega sea mañana
|
||||
$sql .= " AND $date_condition";
|
||||
if ($selected_delivery_date !== '') {
|
||||
$sql .= " AND DATE(p.fecha_entrega) = ?";
|
||||
$params[] = $selected_delivery_date;
|
||||
} else {
|
||||
// Siempre: pedidos cuya fecha de entrega sea mañana
|
||||
$sql .= " AND DATE(p.fecha_entrega) = DATE_ADD(CURDATE(), INTERVAL 1 DAY)";
|
||||
}
|
||||
|
||||
$sql .= " ORDER BY p.created_at DESC";
|
||||
$stmt = $pdo->prepare($sql);
|
||||
@ -440,7 +453,11 @@ $is_admin = in_array($user_role, ['Administrador', 'admin'], true);
|
||||
}
|
||||
}
|
||||
unset($row);
|
||||
$filename = 'ruta_contraentrega_' . date('Y-m-d_H-i') . '.xlsx';
|
||||
if ($selected_delivery_date !== '') {
|
||||
$filename = 'ruta_contraentrega_' . $selected_delivery_date . '.xlsx';
|
||||
} else {
|
||||
$filename = 'ruta_contraentrega_' . date('Y-m-d_H-i') . '.xlsx';
|
||||
}
|
||||
SimpleXLSXGen::fromArray($rows, 'Ruta Contraentrega')->downloadAs($filename);
|
||||
exit;
|
||||
} catch (Throwable $e) {
|
||||
|
||||
@ -1426,6 +1426,83 @@ function cc_test_dashboard_action_datetime(array $order): ?DateTimeImmutable
|
||||
return null;
|
||||
}
|
||||
|
||||
function cc_test_resolve_kpi_period(string $period, ?string $customDate = null): array
|
||||
{
|
||||
$period = mb_strtolower(trim($period));
|
||||
$today = new DateTimeImmutable('today');
|
||||
$todayEnd = $today->setTime(23, 59, 59);
|
||||
|
||||
$start = $today;
|
||||
$end = $todayEnd;
|
||||
$label = 'Hoy';
|
||||
$trendDays = 1;
|
||||
$customDateValue = null;
|
||||
|
||||
$customDate = trim((string) $customDate);
|
||||
if ($customDate !== '' && preg_match('/^(\d{4})-(\d{2})-(\d{2})$/', $customDate, $matches) === 1 && checkdate((int) $matches[2], (int) $matches[3], (int) $matches[1])) {
|
||||
$customDateValue = new DateTimeImmutable($customDate);
|
||||
}
|
||||
|
||||
if (in_array($period, ['custom', 'date', 'specific'], true)) {
|
||||
if ($customDateValue instanceof DateTimeImmutable) {
|
||||
$start = $customDateValue->setTime(0, 0, 0);
|
||||
$end = $customDateValue->setTime(23, 59, 59);
|
||||
$label = $customDateValue->format('d/m/Y');
|
||||
$trendDays = 1;
|
||||
$period = 'custom';
|
||||
} else {
|
||||
$period = 'today';
|
||||
}
|
||||
}
|
||||
|
||||
switch ($period) {
|
||||
case 'yesterday':
|
||||
$start = $today->sub(new DateInterval('P1D'));
|
||||
$end = $start->setTime(23, 59, 59);
|
||||
$label = 'Ayer';
|
||||
break;
|
||||
case '7':
|
||||
$start = $today->sub(new DateInterval('P6D'));
|
||||
$end = $todayEnd;
|
||||
$label = 'Últimos 7 días';
|
||||
$trendDays = 7;
|
||||
break;
|
||||
case '15':
|
||||
$start = $today->sub(new DateInterval('P14D'));
|
||||
$end = $todayEnd;
|
||||
$label = 'Últimos 15 días';
|
||||
$trendDays = 15;
|
||||
break;
|
||||
case '30':
|
||||
$start = $today->sub(new DateInterval('P29D'));
|
||||
$end = $todayEnd;
|
||||
$label = 'Últimos 30 días';
|
||||
$trendDays = 30;
|
||||
break;
|
||||
case 'month':
|
||||
$start = $today->modify('first day of this month');
|
||||
$end = $todayEnd;
|
||||
$label = 'Este mes';
|
||||
$trendDays = max(1, $start->diff($end)->days + 1);
|
||||
break;
|
||||
case 'custom':
|
||||
break;
|
||||
case 'today':
|
||||
default:
|
||||
$period = 'today';
|
||||
break;
|
||||
}
|
||||
|
||||
return [
|
||||
'key' => $period,
|
||||
'label' => $label,
|
||||
'start' => $start,
|
||||
'end' => $end,
|
||||
'trend_days' => $trendDays,
|
||||
'custom_date' => $customDateValue,
|
||||
];
|
||||
}
|
||||
|
||||
function cc_test_is_recovered_today(array $order, ?DateTimeImmutable $todayStart = null): bool
|
||||
{
|
||||
$estado = cc_test_normalize_state((string) ($order['estado'] ?? ''));
|
||||
@ -1832,6 +1909,367 @@ function cc_test_build_performance_dashboard(array $orders, array $assessors, in
|
||||
];
|
||||
}
|
||||
|
||||
function cc_test_build_performance_dashboard_period(array $orders, array $assessors, DateTimeImmutable $scopeStart, DateTimeImmutable $scopeEnd): array
|
||||
{
|
||||
if ($scopeStart > $scopeEnd) {
|
||||
[$scopeStart, $scopeEnd] = [$scopeEnd, $scopeStart];
|
||||
}
|
||||
|
||||
$trendDays = max(1, $scopeStart->diff($scopeEnd)->days + 1);
|
||||
|
||||
$trend = [];
|
||||
$trendLabels = [];
|
||||
for ($i = 0; $i < $trendDays; $i++) {
|
||||
$day = $scopeStart->add(new DateInterval('P' . $i . 'D'));
|
||||
$dayKey = $day->format('Y-m-d');
|
||||
$trend[$dayKey] = [
|
||||
'label' => $day->format('d/m'),
|
||||
'assigned_today' => 0,
|
||||
'confirmed_new' => 0,
|
||||
'recovered' => 0,
|
||||
'canceled' => 0,
|
||||
];
|
||||
$trendLabels[] = $day->format('d/m');
|
||||
}
|
||||
|
||||
$orderedAssessorKeys = cc_test_ordered_assessor_keys($assessors);
|
||||
|
||||
$bucketStats = [];
|
||||
foreach ($orderedAssessorKeys as $assessorKey) {
|
||||
$bucketStats[$assessorKey] = [
|
||||
'label' => cc_test_dashboard_bucket_label($assessorKey, $assessors),
|
||||
'assigned_today' => 0,
|
||||
'confirmed_new' => 0,
|
||||
'recovered' => 0,
|
||||
'canceled' => 0,
|
||||
'followup_open' => 0,
|
||||
'repeated' => 0,
|
||||
];
|
||||
}
|
||||
|
||||
$summary = [
|
||||
'scope_total' => 0,
|
||||
'assigned_today' => 0,
|
||||
'confirmed_today_new' => 0,
|
||||
'confirmed_today_recovered' => 0,
|
||||
'confirmed_today_total' => 0,
|
||||
'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) {
|
||||
$bucketKey = cc_test_dashboard_bucket_key($order, $assessors, true);
|
||||
if ($bucketKey === '') {
|
||||
continue;
|
||||
}
|
||||
if ($bucketKey === 'SIN_ASIGNAR') {
|
||||
$hasUnassignedBucket = true;
|
||||
}
|
||||
if (!isset($bucketStats[$bucketKey])) {
|
||||
$bucketStats[$bucketKey] = [
|
||||
'label' => cc_test_dashboard_bucket_label($bucketKey, $assessors),
|
||||
'assigned_today' => 0,
|
||||
'confirmed_new' => 0,
|
||||
'recovered' => 0,
|
||||
'canceled' => 0,
|
||||
'followup_open' => 0,
|
||||
'repeated' => 0,
|
||||
];
|
||||
}
|
||||
|
||||
$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);
|
||||
$actionKey = $actionDate ? $actionDate->format('Y-m-d') : null;
|
||||
$estado = cc_test_normalize_state((string) ($order['estado'] ?? ''));
|
||||
$followupInfo = cc_test_followup_day_info($order);
|
||||
$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';
|
||||
$isFollowupState = in_array($estado, cc_test_followup_tracking_states(), true);
|
||||
$isFollowupOpen = $isFollowupState && !$isPromoFinal;
|
||||
$assignedInScope = $assignedDate instanceof DateTimeImmutable && $assignedDate >= $scopeStart && $assignedDate <= $scopeEnd;
|
||||
$actionInScope = $actionDate instanceof DateTimeImmutable && $actionDate >= $scopeStart && $actionDate <= $scopeEnd;
|
||||
|
||||
if (!$assignedInScope && !$actionInScope) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$summary['scope_total']++;
|
||||
|
||||
if ($assignedInScope) {
|
||||
$bucketStats[$bucketKey]['assigned_today']++;
|
||||
$summary['assigned_today']++;
|
||||
|
||||
if (isset($trend[$assignedKey])) {
|
||||
$trend[$assignedKey]['assigned_today']++;
|
||||
}
|
||||
|
||||
if ($isFollowupOpen) {
|
||||
$bucketStats[$bucketKey]['followup_open']++;
|
||||
$summary['followup_open']++;
|
||||
|
||||
if (isset($followupStateCounts[$estado])) {
|
||||
$followupStateCounts[$estado]++;
|
||||
}
|
||||
|
||||
if ($estado === 'OBSERVADO') {
|
||||
$summary['observados_today']++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ($isConfirmed && $actionInScope && $assignedDate instanceof DateTimeImmutable) {
|
||||
$isNewConfirmation = $assignedKey === $actionKey;
|
||||
$isRecoveredConfirmation = $assignedDate < $actionDate;
|
||||
|
||||
if ($isNewConfirmation) {
|
||||
$bucketStats[$bucketKey]['confirmed_new']++;
|
||||
$summary['confirmed_today_new']++;
|
||||
} elseif ($isRecoveredConfirmation) {
|
||||
$bucketStats[$bucketKey]['recovered']++;
|
||||
$summary['confirmed_today_recovered']++;
|
||||
}
|
||||
|
||||
if (($isNewConfirmation || $isRecoveredConfirmation) && isset($trend[$actionKey])) {
|
||||
$trend[$actionKey][$isNewConfirmation ? 'confirmed_new' : 'recovered']++;
|
||||
}
|
||||
}
|
||||
|
||||
if ($isCanceled && $assignedInScope && $actionInScope && $assignedKey === $actionKey) {
|
||||
$bucketStats[$bucketKey]['canceled']++;
|
||||
$summary['canceled_today']++;
|
||||
|
||||
if (isset($trend[$actionKey])) {
|
||||
$trend[$actionKey]['canceled']++;
|
||||
}
|
||||
}
|
||||
|
||||
if ($isRepeated && $assignedInScope && $actionInScope && $assignedKey === $actionKey) {
|
||||
$bucketStats[$bucketKey]['repeated']++;
|
||||
$summary['repeated_today']++;
|
||||
}
|
||||
}
|
||||
|
||||
$summary['confirmed_today_total'] = $summary['confirmed_today_new'] + $summary['confirmed_today_recovered'];
|
||||
$summary['confirm_rate_today'] = $summary['assigned_today'] > 0
|
||||
? round(($summary['confirmed_today_new'] / $summary['assigned_today']) * 100, 1)
|
||||
: 0.0;
|
||||
$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 = [];
|
||||
$advisorConfirmedNew = [];
|
||||
$advisorRecovered = [];
|
||||
$advisorCanceled = [];
|
||||
$advisorFollowup = [];
|
||||
|
||||
$finalBucketKeys = $orderedAssessorKeys;
|
||||
foreach (array_keys($bucketStats) as $bucketKey) {
|
||||
if (!in_array($bucketKey, $finalBucketKeys, true) && $bucketKey !== 'SIN_ASIGNAR') {
|
||||
$finalBucketKeys[] = $bucketKey;
|
||||
}
|
||||
}
|
||||
if ($hasUnassignedBucket) {
|
||||
$finalBucketKeys[] = 'SIN_ASIGNAR';
|
||||
}
|
||||
|
||||
foreach ($finalBucketKeys as $bucketKey) {
|
||||
if (!isset($bucketStats[$bucketKey])) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$bucket = $bucketStats[$bucketKey];
|
||||
$advisorLabels[] = $bucket['label'];
|
||||
$advisorAssigned[] = (int) $bucket['assigned_today'];
|
||||
$advisorConfirmedNew[] = (int) $bucket['confirmed_new'];
|
||||
$advisorRecovered[] = (int) $bucket['recovered'];
|
||||
$advisorCanceled[] = (int) $bucket['canceled'];
|
||||
$advisorFollowup[] = (int) $bucket['followup_open'];
|
||||
}
|
||||
|
||||
if (empty($advisorLabels)) {
|
||||
$advisorLabels = ['Sin datos'];
|
||||
$advisorAssigned = [0];
|
||||
$advisorConfirmedNew = [0];
|
||||
$advisorRecovered = [0];
|
||||
$advisorCanceled = [0];
|
||||
$advisorFollowup = [0];
|
||||
}
|
||||
|
||||
$trendAssigned = [];
|
||||
$trendConfirmedNew = [];
|
||||
$trendRecovered = [];
|
||||
$trendCanceled = [];
|
||||
foreach (array_keys($trend) as $dayKey) {
|
||||
$trendAssigned[] = (int) $trend[$dayKey]['assigned_today'];
|
||||
$trendConfirmedNew[] = (int) $trend[$dayKey]['confirmed_new'];
|
||||
$trendRecovered[] = (int) $trend[$dayKey]['recovered'];
|
||||
$trendCanceled[] = (int) $trend[$dayKey]['canceled'];
|
||||
}
|
||||
|
||||
$advisorCount = 0;
|
||||
foreach ($advisorLabels as $advisorLabel) {
|
||||
if ($advisorLabel !== 'Sin datos' && $advisorLabel !== 'Sin asignar') {
|
||||
$advisorCount++;
|
||||
}
|
||||
}
|
||||
|
||||
return [
|
||||
'summary' => $summary,
|
||||
'status_chart' => [
|
||||
'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) ($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(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(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)',
|
||||
],
|
||||
'borderWidth' => 2,
|
||||
'hoverOffset' => 6,
|
||||
],
|
||||
],
|
||||
],
|
||||
'advisor_count' => $advisorCount,
|
||||
'advisor_chart' => [
|
||||
'labels' => $advisorLabels,
|
||||
'datasets' => [
|
||||
[
|
||||
'label' => 'Asignados en el periodo',
|
||||
'data' => $advisorAssigned,
|
||||
'backgroundColor' => 'rgba(13, 110, 253, 0.75)',
|
||||
'borderColor' => 'rgba(13, 110, 253, 1)',
|
||||
'borderWidth' => 1,
|
||||
'borderRadius' => 6,
|
||||
'maxBarThickness' => 22,
|
||||
],
|
||||
[
|
||||
'label' => 'Confirmados nuevos',
|
||||
'data' => $advisorConfirmedNew,
|
||||
'backgroundColor' => 'rgba(25, 135, 84, 0.78)',
|
||||
'borderColor' => 'rgba(25, 135, 84, 1)',
|
||||
'borderWidth' => 1,
|
||||
'borderRadius' => 6,
|
||||
'maxBarThickness' => 22,
|
||||
],
|
||||
[
|
||||
'label' => 'Recuperados de otros días',
|
||||
'data' => $advisorRecovered,
|
||||
'backgroundColor' => 'rgba(255, 193, 7, 0.82)',
|
||||
'borderColor' => 'rgba(245, 158, 11, 1)',
|
||||
'borderWidth' => 1,
|
||||
'borderRadius' => 6,
|
||||
'maxBarThickness' => 22,
|
||||
],
|
||||
[
|
||||
'label' => 'Cancelados',
|
||||
'data' => $advisorCanceled,
|
||||
'backgroundColor' => 'rgba(220, 53, 69, 0.75)',
|
||||
'borderColor' => 'rgba(220, 53, 69, 1)',
|
||||
'borderWidth' => 1,
|
||||
'borderRadius' => 6,
|
||||
'maxBarThickness' => 22,
|
||||
],
|
||||
],
|
||||
],
|
||||
'trend_chart' => [
|
||||
'labels' => $trendLabels,
|
||||
'datasets' => [
|
||||
[
|
||||
'label' => 'Asignados',
|
||||
'data' => $trendAssigned,
|
||||
'borderColor' => 'rgba(13, 110, 253, 1)',
|
||||
'backgroundColor' => 'rgba(13, 110, 253, 0.12)',
|
||||
'borderWidth' => 2,
|
||||
'tension' => 0.35,
|
||||
'fill' => false,
|
||||
'pointRadius' => 2,
|
||||
'pointHoverRadius' => 4,
|
||||
],
|
||||
[
|
||||
'label' => 'Confirmados nuevos',
|
||||
'data' => $trendConfirmedNew,
|
||||
'borderColor' => 'rgba(25, 135, 84, 1)',
|
||||
'backgroundColor' => 'rgba(25, 135, 84, 0.12)',
|
||||
'borderWidth' => 2,
|
||||
'tension' => 0.35,
|
||||
'fill' => false,
|
||||
'pointRadius' => 2,
|
||||
'pointHoverRadius' => 4,
|
||||
],
|
||||
[
|
||||
'label' => 'Recuperados',
|
||||
'data' => $trendRecovered,
|
||||
'borderColor' => 'rgba(245, 158, 11, 1)',
|
||||
'backgroundColor' => 'rgba(245, 158, 11, 0.12)',
|
||||
'borderWidth' => 2,
|
||||
'tension' => 0.35,
|
||||
'fill' => false,
|
||||
'pointRadius' => 2,
|
||||
'pointHoverRadius' => 4,
|
||||
],
|
||||
[
|
||||
'label' => 'Cancelados',
|
||||
'data' => $trendCanceled,
|
||||
'borderColor' => 'rgba(220, 53, 69, 1)',
|
||||
'backgroundColor' => 'rgba(220, 53, 69, 0.12)',
|
||||
'borderWidth' => 2,
|
||||
'tension' => 0.35,
|
||||
'fill' => false,
|
||||
'pointRadius' => 2,
|
||||
'pointHoverRadius' => 4,
|
||||
],
|
||||
],
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
function cc_test_render_performance_dashboard(array $dashboard, array $options = []): string
|
||||
{
|
||||
$title = trim((string) ($options['title'] ?? 'KPI diario por asesora'));
|
||||
@ -1841,6 +2279,8 @@ function cc_test_render_performance_dashboard(array $dashboard, array $options =
|
||||
$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 asignados hoy en POR LLAMAR, DEVOLVER LLAMADA, OBSERVADO y SE ENVIO NUMERO DE CUENTA que todavía no pasan a Promo Final.'));
|
||||
$performanceLabel = trim((string) ($options['performance_label'] ?? 'Rendimiento del día'));
|
||||
$statusDetailHeading = trim((string) ($options['status_detail_heading'] ?? 'Detalle del KPI de hoy (%)'));
|
||||
$sectionId = preg_replace('/[^A-Za-z0-9_-]+/', '', (string) ($options['section_id'] ?? 'ccPerformanceDashboard'));
|
||||
if ($sectionId === '') {
|
||||
$sectionId = 'ccPerformanceDashboard';
|
||||
@ -1966,12 +2406,12 @@ function cc_test_render_performance_dashboard(array $dashboard, array $options =
|
||||
<div class="cc-kpi-status-legend">
|
||||
<div class="cc-kpi-pill cc-kpi-pill-total cc-kpi-performance-box mb-0" title="<?php echo htmlspecialchars($performanceTodayMetaTitle); ?>" style="flex-direction:column; align-items:flex-start; gap:.1rem;">
|
||||
<div class="cc-kpi-performance-head">
|
||||
<span class="cc-kpi-label">Rendimiento del día</span>
|
||||
<span class="cc-kpi-label"><?php echo htmlspecialchars($performanceLabel); ?></span>
|
||||
<span class="cc-kpi-value"><?php echo htmlspecialchars($performanceTodayValue); ?></span>
|
||||
</div>
|
||||
<div class="cc-kpi-meta"><?php echo htmlspecialchars($performanceTodayMetaTitle); ?></div>
|
||||
</div>
|
||||
<div class="small text-uppercase text-muted fw-semibold mb-0 cc-kpi-status-heading">Detalle del KPI de hoy (%)</div>
|
||||
<div class="small text-uppercase text-muted fw-semibold mb-0 cc-kpi-status-heading"><?php echo htmlspecialchars($statusDetailHeading); ?></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>
|
||||
|
||||
16
index.php
16
index.php
@ -1,6 +1,16 @@
|
||||
<?php
|
||||
session_start();
|
||||
$isLoggedIn = !empty($_SESSION['user_id']);
|
||||
$userRole = (string) ($_SESSION['user_role'] ?? '');
|
||||
$homeUrl = 'pedidos.php';
|
||||
$homeLabel = 'Ir a Pedidos';
|
||||
if (in_array($userRole, ['Administrador', 'admin'], true)) {
|
||||
$homeUrl = 'dashboard.php';
|
||||
$homeLabel = 'Ir al dashboard';
|
||||
} elseif (strcasecmp(trim($userRole), 'Marketing') === 0) {
|
||||
$homeUrl = 'marketing_produccion.php';
|
||||
$homeLabel = 'Ir a Producción de Video';
|
||||
}
|
||||
$callCenterParams = [];
|
||||
foreach ([
|
||||
'callcenter_assessor_filter' => 'assessor',
|
||||
@ -108,15 +118,15 @@ $pageDescription = 'Entrada rápida al sistema CRM y panel de administración.';
|
||||
<h1>Panel CRM y Administración</h1>
|
||||
<p>
|
||||
<?php if ($isLoggedIn): ?>
|
||||
Ya tienes una sesión activa. Entra directo al dashboard para continuar con la operación.
|
||||
Ya tienes una sesión activa. Entra directo al módulo que corresponde a tu rol para continuar con la operación.
|
||||
<?php else: ?>
|
||||
Accede al sistema para gestionar pedidos, inventario, llamadas y operaciones desde un solo lugar.
|
||||
<?php endif; ?>
|
||||
</p>
|
||||
|
||||
<div class="actions">
|
||||
<a class="btn btn-primary" href="<?php echo $isLoggedIn ? 'dashboard.php' : 'login.php'; ?>">
|
||||
<?php echo $isLoggedIn ? 'Ir al dashboard' : 'Iniciar sesión'; ?>
|
||||
<a class="btn btn-primary" href="<?php echo $isLoggedIn ? htmlspecialchars($homeUrl) : 'login.php'; ?>">
|
||||
<?php echo $isLoggedIn ? htmlspecialchars($homeLabel) : 'Iniciar sesión'; ?>
|
||||
</a>
|
||||
<a class="btn btn-secondary" href="<?php echo htmlspecialchars($callCenterUrl); ?>">Abrir Base de Datos Pedidos</a>
|
||||
</div>
|
||||
|
||||
@ -14,6 +14,12 @@ require_once __DIR__ . '/db/config.php';
|
||||
require_once __DIR__ . '/includes/callcenter_test_helpers.php';
|
||||
|
||||
$userRole = $_SESSION['user_role'] ?? '';
|
||||
$currentPage = basename($_SERVER['PHP_SELF']);
|
||||
$isMarketingUser = strcasecmp(trim((string) $userRole), 'Marketing') === 0;
|
||||
if ($isMarketingUser && $currentPage !== 'marketing_produccion.php') {
|
||||
header('Location: marketing_produccion.php');
|
||||
exit();
|
||||
}
|
||||
$currentUserId = (int) ($_SESSION['user_id'] ?? 0);
|
||||
$currentUserUsername = $_SESSION['username'] ?? '';
|
||||
$currentUserNombreAsesor = null;
|
||||
@ -346,13 +352,13 @@ $navItems = [
|
||||
'marketing_group' => [
|
||||
'icon' => 'fa-bullhorn',
|
||||
'text' => 'Marketing',
|
||||
'roles' => ['Administrador', 'admin'],
|
||||
'roles' => ['Administrador', 'admin', 'Marketing', 'marketing'],
|
||||
'submenu' => [
|
||||
'marketing_produccion' => [
|
||||
'url' => 'marketing_produccion.php',
|
||||
'icon' => 'fa-video',
|
||||
'text' => 'Producción de Video',
|
||||
'roles' => ['Administrador', 'admin']
|
||||
'roles' => ['Administrador', 'admin', 'Marketing', 'marketing']
|
||||
],
|
||||
'calculo_costos_v3' => [
|
||||
'url' => 'calculo_costos_v3.php',
|
||||
@ -447,7 +453,7 @@ $navItems = [
|
||||
</button>
|
||||
<div class="sidebar">
|
||||
<?php
|
||||
$homeUrl = in_array($userRole, ['Administrador', 'admin']) ? 'dashboard_principal.php' : 'pedidos.php';
|
||||
$homeUrl = in_array($userRole, ['Administrador', 'admin'], true) ? 'dashboard_principal.php' : ($isMarketingUser ? 'marketing_produccion.php' : 'pedidos.php');
|
||||
?>
|
||||
<a href="<?php echo $homeUrl; ?>" class="navbar-brand"><h3>FLOOWER ERP</h3></a>
|
||||
<div class="menu-wrapper">
|
||||
|
||||
10
login.php
10
login.php
@ -2,8 +2,11 @@
|
||||
session_start();
|
||||
|
||||
if (isset($_SESSION['user_id'])) {
|
||||
if (in_array($_SESSION['user_role'], ['Administrador', 'admin'])) {
|
||||
$currentRole = (string) ($_SESSION['user_role'] ?? '');
|
||||
if (in_array($currentRole, ['Administrador', 'admin'], true)) {
|
||||
header('Location: dashboard_principal.php');
|
||||
} elseif (strcasecmp(trim($currentRole), 'Marketing') === 0) {
|
||||
header('Location: marketing_produccion.php');
|
||||
} else {
|
||||
header('Location: pedidos.php');
|
||||
}
|
||||
@ -59,8 +62,11 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
||||
// Silently fail if session recording fails, don't block login
|
||||
}
|
||||
|
||||
if (in_array($user['role'], ['Administrador', 'admin'])) {
|
||||
$userRole = (string) ($user['role'] ?? '');
|
||||
if (in_array($userRole, ['Administrador', 'admin'], true)) {
|
||||
header('Location: dashboard_principal.php');
|
||||
} elseif (strcasecmp(trim($userRole), 'Marketing') === 0) {
|
||||
header('Location: marketing_produccion.php');
|
||||
} else {
|
||||
header('Location: pedidos.php');
|
||||
}
|
||||
|
||||
@ -15,7 +15,7 @@ if (isset($_SESSION['message'])) {
|
||||
}
|
||||
|
||||
$defaultRole = trim((string) ($_GET['role'] ?? ''));
|
||||
$allowedDefaultRoles = ['Administrador', 'Asesor', 'Control Logistico', 'Soporte Logistico', 'Verificador de Pagos', 'Logistica'];
|
||||
$allowedDefaultRoles = ['Administrador', 'Asesor', 'Control Logistico', 'Soporte Logistico', 'Verificador de Pagos', 'Logistica', 'Marketing'];
|
||||
if ($defaultRole !== '' && !in_array($defaultRole, $allowedDefaultRoles, true)) {
|
||||
$defaultRole = '';
|
||||
}
|
||||
@ -162,6 +162,7 @@ include 'layout_header.php';
|
||||
<option value="Soporte Logistico"<?php echo $defaultRole === 'Soporte Logistico' ? ' selected' : ''; ?>>Soporte Logístico</option>
|
||||
<option value="Verificador de Pagos"<?php echo $defaultRole === 'Verificador de Pagos' ? ' selected' : ''; ?>>Verificador de Pagos</option>
|
||||
<option value="Logistica"<?php echo $defaultRole === 'Logistica' ? ' selected' : ''; ?>>Logistica</option>
|
||||
<option value="Marketing"<?php echo $defaultRole === 'Marketing' ? ' selected' : ''; ?>>Marketing</option>
|
||||
</select>
|
||||
</div>
|
||||
<button type="submit" name="create_user" class="btn btn-primary w-100">Crear Usuario</button>
|
||||
@ -247,6 +248,7 @@ include 'layout_header.php';
|
||||
<option value="Soporte Logistico">Soporte Logístico</option>
|
||||
<option value="Verificador de Pagos">Verificador de Pagos</option>
|
||||
<option value="Logistica">Logistica</option>
|
||||
<option value="Marketing">Marketing</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@ -107,6 +107,16 @@ $years = $years_stmt->fetchAll(PDO::FETCH_COLUMN);
|
||||
$selected_month = $_GET['mes'] ?? '';
|
||||
$selected_year = $_GET['año'] ?? '';
|
||||
$search_query = $_GET['q'] ?? '';
|
||||
$selected_delivery_date = trim((string)($_GET['fecha_entrega'] ?? ''));
|
||||
if ($selected_delivery_date !== '' && !preg_match('/^\d{4}-\d{2}-\d{2}$/', $selected_delivery_date)) {
|
||||
$selected_delivery_date = '';
|
||||
}
|
||||
if ($selected_delivery_date !== '') {
|
||||
[$deliveryYear, $deliveryMonth, $deliveryDay] = array_map('intval', explode('-', $selected_delivery_date));
|
||||
if (!checkdate($deliveryMonth, $deliveryDay, $deliveryYear)) {
|
||||
$selected_delivery_date = '';
|
||||
}
|
||||
}
|
||||
|
||||
$period = $_GET['period'] ?? '';
|
||||
$date_condition = '';
|
||||
@ -152,6 +162,11 @@ if (!empty($search_query)) {
|
||||
$params[] = "%$search_query%";
|
||||
}
|
||||
|
||||
if (!empty($selected_delivery_date)) {
|
||||
$sql .= " AND DATE(p.fecha_entrega) = ?";
|
||||
$params[] = $selected_delivery_date;
|
||||
}
|
||||
|
||||
if (!empty($date_condition)) {
|
||||
$sql .= " AND $date_condition";
|
||||
} else {
|
||||
@ -322,12 +337,16 @@ include 'layout_header.php';
|
||||
<?php endforeach; ?>
|
||||
</select>
|
||||
</div>
|
||||
<div class="col-auto">
|
||||
<label for="fecha_entrega" class="form-label">Fecha de entrega</label>
|
||||
<input type="date" name="fecha_entrega" id="fecha_entrega" class="form-control" value="<?php echo htmlspecialchars($selected_delivery_date, ENT_QUOTES, 'UTF-8'); ?>">
|
||||
</div>
|
||||
<?php
|
||||
$qParam = !empty($search_query) ? '&q=' . urlencode((string)$search_query) : '';
|
||||
?>
|
||||
<div class="col-12">
|
||||
<div class="d-flex flex-wrap gap-2 align-items-center">
|
||||
<span class="text-muted fw-semibold me-2">Fecha:</span>
|
||||
<span class="text-muted fw-semibold me-2">Fecha de registro:</span>
|
||||
<a href="ruta_contraentrega.php?period=today<?php echo htmlspecialchars($qParam, ENT_QUOTES, 'UTF-8'); ?>" class="btn btn-outline-primary btn-sm <?php echo $period == 'today' ? 'active' : ''; ?>">Hoy</a>
|
||||
<a href="ruta_contraentrega.php?period=yesterday<?php echo htmlspecialchars($qParam, ENT_QUOTES, 'UTF-8'); ?>" class="btn btn-outline-primary btn-sm <?php echo $period == 'yesterday' ? 'active' : ''; ?>">Ayer</a>
|
||||
<a href="ruta_contraentrega.php?period=7<?php echo htmlspecialchars($qParam, ENT_QUOTES, 'UTF-8'); ?>" class="btn btn-outline-primary btn-sm <?php echo $period == '7' ? 'active' : ''; ?>">7 Días</a>
|
||||
@ -347,13 +366,15 @@ include 'layout_header.php';
|
||||
<?php
|
||||
$excelParams = array_filter([
|
||||
'q' => $search_query,
|
||||
'fecha_entrega' => $selected_delivery_date,
|
||||
], static function ($value) {
|
||||
return $value !== '' && $value !== null;
|
||||
});
|
||||
$excelUrl = 'download_ruta_contraentrega.php' . (!empty($excelParams) ? '?' . http_build_query($excelParams) : '');
|
||||
$excelLabel = !empty($selected_delivery_date) ? date('d/m/Y', strtotime($selected_delivery_date)) : 'Mañana';
|
||||
?>
|
||||
<a href="<?php echo htmlspecialchars($excelUrl); ?>" class="btn btn-success">
|
||||
<i class="fas fa-file-excel me-1"></i> Descargar Excel (Mañana)
|
||||
<i class="fas fa-file-excel me-1"></i> Descargar Excel (<?php echo htmlspecialchars($excelLabel, ENT_QUOTES, 'UTF-8'); ?>)
|
||||
</a>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
@ -640,6 +661,29 @@ include 'layout_header.php';
|
||||
|
||||
const errorBox = document.getElementById('ccVerifErrorBox');
|
||||
const successBox = document.getElementById('ccVerifSuccessBox');
|
||||
let activeVerificationButton = null;
|
||||
|
||||
function getVerificationButtonState(tracking, imagePath, seguimiento) {
|
||||
const hasVerification = !!(tracking && imagePath);
|
||||
const isMatch = hasVerification && !!seguimiento && seguimiento === tracking;
|
||||
|
||||
return {
|
||||
className: isMatch ? 'cc-status-match' : (hasVerification ? 'cc-status-pending' : 'cc-status-none'),
|
||||
icon: isMatch ? '✅' : '📷',
|
||||
};
|
||||
}
|
||||
|
||||
function syncVerificationButton(buttonEl, tracking, imagePath, seguimiento) {
|
||||
if (!buttonEl) return;
|
||||
|
||||
const state = getVerificationButtonState(tracking, imagePath, seguimiento);
|
||||
buttonEl.setAttribute('data-verificacion-tracking', tracking || '');
|
||||
buttonEl.setAttribute('data-verificacion-imagen', imagePath || '');
|
||||
buttonEl.setAttribute('data-seguimiento', seguimiento || '');
|
||||
buttonEl.classList.remove('cc-status-none', 'cc-status-pending', 'cc-status-match');
|
||||
buttonEl.classList.add(state.className);
|
||||
buttonEl.textContent = state.icon;
|
||||
}
|
||||
|
||||
function showBox(boxEl, msg) {
|
||||
boxEl.textContent = msg;
|
||||
@ -668,6 +712,7 @@ include 'layout_header.php';
|
||||
const btn = event.relatedTarget;
|
||||
if (!btn) return;
|
||||
|
||||
activeVerificationButton = btn;
|
||||
hideBoxes();
|
||||
|
||||
const pedidoId = btn.getAttribute('data-pedido-id') || '';
|
||||
@ -755,13 +800,21 @@ include 'layout_header.php';
|
||||
return;
|
||||
}
|
||||
|
||||
const savedTracking = (data.tracking_verificacion || tracking || '').trim();
|
||||
const savedImage = (data.imagen || '').trim();
|
||||
const seguimientoActual = activeVerificationButton ? (activeVerificationButton.getAttribute('data-seguimiento') || '').trim() : '';
|
||||
const targetButton = activeVerificationButton || document.querySelector('.cc-verificacion-btn[data-pedido-id="' + pedidoId + '"]');
|
||||
|
||||
showBox(successBox, 'Verificación guardada ✅');
|
||||
trackingSavedEl.textContent = savedTracking || 'N/A';
|
||||
seguimientoActualEl.textContent = seguimientoActual || 'N/A';
|
||||
copyBtn.disabled = !(savedTracking && savedImage);
|
||||
syncVerificationButton(targetButton, savedTracking, savedImage, seguimientoActual);
|
||||
|
||||
await new Promise((resolve) => setTimeout(resolve, 250));
|
||||
|
||||
const modalInstance = bootstrap.Modal.getInstance(modalEl) || new bootstrap.Modal(modalEl);
|
||||
modalInstance.hide();
|
||||
|
||||
// Reload to update icon/status
|
||||
window.location.href = window.location.pathname + window.location.search;
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
showBox(errorBox, 'Error de conexión al guardar.');
|
||||
|
||||
@ -155,13 +155,19 @@ try {
|
||||
|
||||
$stmt->execute([$relativePath, $tracking_verificacion, $user_id, $pedido_id]);
|
||||
|
||||
$response = [
|
||||
'success' => true,
|
||||
'pedido_id' => $pedido_id,
|
||||
'imagen' => $relativePath,
|
||||
'tracking_verificacion' => $tracking_verificacion,
|
||||
];
|
||||
|
||||
if ($stmt->rowCount() <= 0) {
|
||||
// Still treat as success because UPDATE might not change values
|
||||
echo json_encode(['success' => true, 'info' => 'La verificación se guardó, pero no hubo cambios.']);
|
||||
exit;
|
||||
$response['info'] = 'La verificación se guardó, pero no hubo cambios.';
|
||||
}
|
||||
|
||||
echo json_encode(['success' => true, 'imagen' => $relativePath, 'tracking_verificacion' => $tracking_verificacion]);
|
||||
echo json_encode($response);
|
||||
} catch (Throwable $e) {
|
||||
error_log('save_contraentrega_verificacion.php: ' . $e->getMessage());
|
||||
http_response_code(500);
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user