$hasta) [$desde, $hasta] = [$hasta, $desde]; // ── Sesiones de atención en el rango ───────────────────────────────── // Para cada "attend" buscamos: // a) El primer mensaje outgoing del mismo user_id DESPUÉS del attend // y dentro de 4 horas (descartamos bots post-sesión). // b) El primer "finish" del mismo user_id DESPUÉS del attend. $sesiones = $db->fetchAll(" SELECT oa.id AS sesion_id, oa.user_id, oa.operator_id, COALESCE(au.full_name, au.username, CONCAT('Asesor #', oa.operator_id)) AS operador, oa.created_at AS attend_at, -- Primera respuesta outgoing del asesor ( SELECT MIN(c.created_at) FROM conversations c WHERE c.user_id = oa.user_id AND c.direction = 'outgoing' AND c.created_at > oa.created_at AND c.created_at < oa.created_at + INTERVAL 4 HOUR ) AS primera_respuesta_at, -- Fin de la sesión ( SELECT MIN(oa2.created_at) FROM operator_activity oa2 WHERE oa2.user_id = oa.user_id AND oa2.action = 'finish' AND oa2.created_at > oa.created_at AND oa2.created_at < oa.created_at + INTERVAL 8 HOUR ) AS finish_at FROM operator_activity oa LEFT JOIN admin_users au ON au.id = oa.operator_id WHERE oa.action = 'attend' AND DATE(oa.created_at) BETWEEN ? AND ? ORDER BY oa.created_at ASC ", [$desde, $hasta]); // ── Calcular métricas por sesión ───────────────────────────────────── $totalSesiones = 0; $conRespuesta = 0; $sumaMinRespuesta = 0; $sumaMinDuracion = 0; $conDuracion = 0; $sinRespuesta = 0; // Por asesor $asesores = []; // Por día (para tendencia) $porDia = []; // Histograma: cubos < 1, 1-3, 3-5, 5-10, 10-20, >20 minutos $hist = ['< 1 min' => 0, '1-3 min' => 0, '3-5 min' => 0, '5-10 min' => 0, '10-20 min' => 0, '> 20 min' => 0, 'Sin resp.' => 0]; foreach ($sesiones as $s) { $totalSesiones++; $attendDt = new DateTime($s['attend_at']); $fechaDia = $attendDt->format('Y-m-d'); if (!isset($porDia[$fechaDia])) { $porDia[$fechaDia] = ['fecha' => $fechaDia, 'sesiones' => 0, 'suma_resp' => 0, 'con_resp' => 0]; } $porDia[$fechaDia]['sesiones']++; $opId = $s['operator_id'] ?? 0; if (!isset($asesores[$opId])) { $asesores[$opId] = [ 'operador' => $s['operador'], 'sesiones' => 0, 'con_resp' => 0, 'suma_resp' => 0, 'suma_dur' => 0, 'con_dur' => 0, ]; } $asesores[$opId]['sesiones']++; // Tiempo de primera respuesta if (!empty($s['primera_respuesta_at'])) { $respDt = new DateTime($s['primera_respuesta_at']); $minResp = round(($respDt->getTimestamp() - $attendDt->getTimestamp()) / 60, 1); if ($minResp < 0) $minResp = 0; $conRespuesta++; $sumaMinRespuesta += $minResp; $asesores[$opId]['con_resp']++; $asesores[$opId]['suma_resp'] += $minResp; $porDia[$fechaDia]['suma_resp'] += $minResp; $porDia[$fechaDia]['con_resp']++; // Histograma if ($minResp < 1) $hist['< 1 min']++; elseif ($minResp < 3) $hist['1-3 min']++; elseif ($minResp < 5) $hist['3-5 min']++; elseif ($minResp < 10) $hist['5-10 min']++; elseif ($minResp < 20) $hist['10-20 min']++; else $hist['> 20 min']++; } else { $sinRespuesta++; $hist['Sin resp.']++; } // Duración de la sesión if (!empty($s['finish_at'])) { $finDt = new DateTime($s['finish_at']); $minDur = round(($finDt->getTimestamp() - $attendDt->getTimestamp()) / 60, 1); if ($minDur >= 0) { $conDuracion++; $sumaMinDuracion += $minDur; $asesores[$opId]['suma_dur'] += $minDur; $asesores[$opId]['con_dur']++; } } } $promedioRespuesta = $conRespuesta > 0 ? round($sumaMinRespuesta / $conRespuesta, 1) : null; $promedioDuracion = $conDuracion > 0 ? round($sumaMinDuracion / $conDuracion, 1) : null; $pctRapidas = $totalSesiones > 0 ? round(($hist['< 1 min'] + $hist['1-3 min']) / $totalSesiones * 100) : 0; // Formatear asesores $listaAsesores = []; foreach ($asesores as $a) { $listaAsesores[] = [ 'operador' => $a['operador'], 'sesiones' => $a['sesiones'], 'prom_respuesta' => $a['con_resp'] > 0 ? round($a['suma_resp'] / $a['con_resp'], 1) : null, 'prom_duracion' => $a['con_dur'] > 0 ? round($a['suma_dur'] / $a['con_dur'], 1) : null, ]; } usort($listaAsesores, fn($a, $b) => ($a['prom_respuesta'] ?? 9999) <=> ($b['prom_respuesta'] ?? 9999)); // Rellenar días sin actividad en el rango $cur = new DateTime($desde); $fin = new DateTime($hasta); while ($cur <= $fin) { $f = $cur->format('Y-m-d'); if (!isset($porDia[$f])) $porDia[$f] = ['fecha' => $f, 'sesiones' => 0, 'suma_resp' => 0, 'con_resp' => 0]; $cur->modify('+1 day'); } ksort($porDia); $serieDia = array_map(fn($d) => [ 'fecha' => $d['fecha'], 'sesiones' => $d['sesiones'], 'prom_respuesta' => $d['con_resp'] > 0 ? round($d['suma_resp'] / $d['con_resp'], 1) : null, ], array_values($porDia)); echo json_encode([ 'ok' => true, 'desde' => $desde, 'hasta' => $hasta, 'totales' => [ 'sesiones' => $totalSesiones, 'con_respuesta' => $conRespuesta, 'sin_respuesta' => $sinRespuesta, 'prom_respuesta' => $promedioRespuesta, 'prom_duracion' => $promedioDuracion, 'pct_rapidas' => $pctRapidas, ], 'asesores' => $listaAsesores, 'histograma' => array_map(fn($k, $v) => ['rango' => $k, 'sesiones' => $v], array_keys($hist), $hist), 'serie_dia' => $serieDia, ], JSON_UNESCAPED_UNICODE); } catch (Exception $e) { http_response_code(500); echo json_encode(['ok' => false, 'error' => $e->getMessage()]); }