up
This commit is contained in:
@@ -135,7 +135,9 @@ try {
|
||||
'in_service' => !empty($user['in_service']) ? true : false,
|
||||
'in_service_by' => isset($user['in_service_by']) ? intval($user['in_service_by']) : null,
|
||||
'in_service_at' => $user['in_service_at'] ?? null,
|
||||
'in_service_by_name' => $inServiceByName
|
||||
'in_service_by_name' => $inServiceByName,
|
||||
'advisor_requested' => !empty($user['advisor_requested']) ? true : false,
|
||||
'on_hold' => !empty($user['on_hold']) ? true : false
|
||||
],
|
||||
'conversations' => $conversations,
|
||||
'total_conversations' => count($conversations)
|
||||
|
||||
@@ -31,6 +31,7 @@ try {
|
||||
c.message_type as last_message_type,
|
||||
c.created_at as last_message_time,
|
||||
c.status as last_message_status,
|
||||
u.advisor_requested as advisor_requested,
|
||||
COUNT(*) as total_conversations,
|
||||
SUM(CASE WHEN c.direction = 'incoming' AND c.status = 'received' THEN 1 ELSE 0 END) as unread_count
|
||||
FROM users u
|
||||
@@ -40,7 +41,7 @@ try {
|
||||
FROM conversations
|
||||
GROUP BY user_id
|
||||
)
|
||||
GROUP BY u.id, u.phone_number, u.name, c.content, c.direction, c.message_type, c.created_at, c.status
|
||||
GROUP BY u.id, u.phone_number, u.name, c.content, c.direction, c.message_type, c.created_at, c.status, u.advisor_requested
|
||||
ORDER BY c.created_at DESC
|
||||
LIMIT 50"
|
||||
);
|
||||
@@ -61,6 +62,7 @@ try {
|
||||
'last_message_type' => $conv['last_message_type'] ?? 'text',
|
||||
'last_message_time' => $conv['last_message_time'],
|
||||
'last_message_status' => $conv['last_message_status'] ?? 'sent',
|
||||
'advisor_requested' => !empty($conv['advisor_requested']) ? true : false,
|
||||
'total_conversations' => intval($conv['total_conversations']),
|
||||
'unread_count' => intval($conv['unread_count']),
|
||||
'time_ago' => timeAgo($conv['last_message_time'])
|
||||
|
||||
@@ -1164,6 +1164,15 @@ body {
|
||||
box-shadow: 0 6px 18px rgba(11, 122, 87, 0.25);
|
||||
}
|
||||
|
||||
/* Conversation requiring advisor attention */
|
||||
.conversation-item.attention {
|
||||
border-left: 4px solid #dc3545;
|
||||
background: rgba(220, 53, 69, 0.03);
|
||||
}
|
||||
.conversation-item.attention .conversation-avatar {
|
||||
box-shadow: 0 6px 18px rgba(220, 53, 69, 0.12);
|
||||
}
|
||||
|
||||
/* Notification bell indicator when there are unseen notifications */
|
||||
#notification-bell.has-notifications {
|
||||
position: relative;
|
||||
|
||||
@@ -515,6 +515,10 @@ class SimpleWhatsAppManager {
|
||||
// Add class to highlight unread conversation (darker green)
|
||||
const unreadClass = (conv.unread_count && conv.unread_count > 0) ? ' unread' : '';
|
||||
|
||||
// Attention badge for advisor requests
|
||||
const attentionBadge = conv.advisor_requested ? `<div class="conversation-attention" title="Requiere atención"><i class="fas fa-exclamation"></i></div>` : '';
|
||||
const attentionClass = conv.advisor_requested ? ' attention' : '';
|
||||
|
||||
// Status icon
|
||||
const statusIcon = conv.last_direction === 'outgoing'
|
||||
? '<i class="fas fa-check-double conversation-status-icon outgoing"></i>'
|
||||
@@ -528,7 +532,7 @@ class SimpleWhatsAppManager {
|
||||
: '';
|
||||
|
||||
html += `
|
||||
<div class="conversation-item${unreadClass}" onclick="openChatWindow(${conv.user_id})">
|
||||
<div class="conversation-item${unreadClass}${attentionClass}" onclick="openChatWindow(${conv.user_id})">
|
||||
<div class="conversation-avatar ${isOnline ? 'online' : ''}">
|
||||
${userInitial}
|
||||
</div>
|
||||
@@ -549,6 +553,7 @@ class SimpleWhatsAppManager {
|
||||
</div>
|
||||
<div class="conversation-meta">
|
||||
${unreadBadge}
|
||||
${attentionBadge}
|
||||
<div class="conversation-actions">
|
||||
<button class="btn btn-sm" onclick="event.stopPropagation(); openChatWindow(${conv.user_id})" title="Abrir chat">
|
||||
<i class="fas fa-comments"></i>
|
||||
@@ -635,7 +640,7 @@ class SimpleWhatsAppManager {
|
||||
const unreadClass = (conv.unread_count && conv.unread_count > 0) ? ' unread' : '';
|
||||
|
||||
html += `
|
||||
<div class="conversation-item${unreadClass}" onclick="openChatWindow(${conv.user_id})">
|
||||
<div class="conversation-item${unreadClass}${attentionClass}" onclick="openChatWindow(${conv.user_id})">
|
||||
<div class="conversation-avatar ${isOnline ? 'online' : ''}">
|
||||
${userInitial}
|
||||
</div>
|
||||
@@ -656,6 +661,7 @@ class SimpleWhatsAppManager {
|
||||
</div>
|
||||
<div class="conversation-meta">
|
||||
${unreadBadge}
|
||||
${attentionBadge}
|
||||
<div class="conversation-actions">
|
||||
<button class="btn btn-sm" onclick="event.stopPropagation(); openChatWindow(${conv.user_id})" title="Abrir chat">
|
||||
<i class="fas fa-comments"></i>
|
||||
|
||||
+62
-4
@@ -200,6 +200,35 @@ if (empty($user_id)) {
|
||||
font-size: 0.65rem;
|
||||
opacity: 0.85;
|
||||
}
|
||||
|
||||
/* Mensajes que requieren atención (asesor) */
|
||||
.message-alert {
|
||||
background: #fff7f7 !important;
|
||||
border-left: 4px solid #dc3545 !important;
|
||||
color: #6b0000;
|
||||
}
|
||||
|
||||
.header-advisor-badge {
|
||||
display: inline-block;
|
||||
background: #dc3545;
|
||||
color: white;
|
||||
padding: 2px 6px;
|
||||
border-radius: 12px;
|
||||
font-size: 0.75rem;
|
||||
margin-left: 8px;
|
||||
}
|
||||
|
||||
.conversation-attention {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 28px;
|
||||
height: 28px;
|
||||
border-radius: 50%;
|
||||
background: #dc3545;
|
||||
color: white;
|
||||
font-size: 0.9rem;
|
||||
}
|
||||
|
||||
/* Estilos para multimedia */
|
||||
.attach-btn {
|
||||
@@ -358,11 +387,11 @@ if (empty($user_id)) {
|
||||
<i class="fas fa-user"></i>
|
||||
</div>
|
||||
<div>
|
||||
<h5 class="mb-0" id="userName">Cargando...</h5>
|
||||
<h5 class="mb-0" id="userName">Cargando... <span id="advisorBadge" style="display:none;" class="header-advisor-badge">¡Atención!</span></h5>
|
||||
<small class="status-indicator" id="userStatus">
|
||||
<i class="fas fa-circle text-success"></i> En línea
|
||||
</small>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="d-flex align-items-center">
|
||||
<button id="attendBtn" class="btn btn-outline-light btn-sm me-2" onclick="toggleAttend()" title="Atender/Finalizar">
|
||||
@@ -696,6 +725,16 @@ if (empty($user_id)) {
|
||||
document.getElementById('userAvatar').innerHTML =
|
||||
`<span>${(currentUser.name || 'U').charAt(0).toUpperCase()}</span>`;
|
||||
|
||||
// Mostrar badge de atención si existe
|
||||
try {
|
||||
const advisorBadge = document.getElementById('advisorBadge');
|
||||
if (advisorBadge) {
|
||||
advisorBadge.style.display = currentUser.advisor_requested ? 'inline-block' : 'none';
|
||||
}
|
||||
} catch (e) {
|
||||
// ignore
|
||||
}
|
||||
|
||||
// Estado de atención
|
||||
renderAttendControls();
|
||||
|
||||
@@ -735,7 +774,7 @@ if (empty($user_id)) {
|
||||
let html = '';
|
||||
conversations.forEach(msg => {
|
||||
const isOutgoing = msg.direction === 'outgoing';
|
||||
const messageClass = isOutgoing ? 'message-outgoing' : 'message-incoming';
|
||||
let messageClass = isOutgoing ? 'message-outgoing' : 'message-incoming';
|
||||
|
||||
// Debug: ver todos los datos del mensaje (prioriza media_url_external y valida URLs)
|
||||
const has_media_external = isValidMediaUrl(msg.media_url_external);
|
||||
@@ -923,7 +962,26 @@ if (empty($user_id)) {
|
||||
}
|
||||
} else {
|
||||
// Mensaje de texto normal
|
||||
contentHtml = escapeHtml(msg.content) || '<em>Sin contenido</em>';
|
||||
// Detectar mensajes 'system' embebidos en content (JSON)
|
||||
let alertInfo = null;
|
||||
try {
|
||||
const parsed = JSON.parse(msg.content || '');
|
||||
if (parsed && parsed.system) {
|
||||
alertInfo = parsed;
|
||||
}
|
||||
} catch (e) {
|
||||
// No es JSON, seguir
|
||||
}
|
||||
|
||||
if (alertInfo) {
|
||||
contentHtml = `<div class="fw-bold">${escapeHtml(alertInfo.text || '[Atención]')}</div>`;
|
||||
// Añadir clase especial a la burbuja
|
||||
// marca el mensaje como requiring attention mediante messageClass
|
||||
// we'll append an additional class later
|
||||
messageClass += ' message-alert';
|
||||
} else {
|
||||
contentHtml = escapeHtml(msg.content) || '<em>Sin contenido</em>';
|
||||
}
|
||||
}
|
||||
|
||||
const messageTime = escapeHtml(msg.time || '');
|
||||
|
||||
+4
-2
@@ -733,17 +733,19 @@
|
||||
const avatar = conv.avatar_url ? `<img src="${conv.avatar_url}" alt="avatar" class="conversation-avatar-img">` : `<div class="conversation-avatar">${this.getInitials(conv.name)}</div>`;
|
||||
|
||||
const unreadBadge = conv.unread_count && conv.unread_count > 0 ? `<span class="badge bg-danger unread-badge">${conv.unread_count}</span>` : '';
|
||||
const attentionBadge = conv.advisor_requested ? `<span class="badge bg-danger ms-1">¡Atención!</span>` : '';
|
||||
|
||||
// different color if has unread and not active
|
||||
const unreadClass = (conv.unread_count && conv.unread_count > 0 && !isActive) ? 'unread' : '';
|
||||
const attentionClass = conv.advisor_requested ? ' attention' : '';
|
||||
|
||||
return `
|
||||
<div class="conversation-item ${isActive} ${unreadClass}" data-user-id="${conv.user_id}" onclick="chat.openConversation(${conv.user_id}, '${conv.name}', '${conv.phone_number}')">
|
||||
<div class="conversation-item ${isActive} ${unreadClass}${attentionClass}" data-user-id="${conv.user_id}" onclick="chat.openConversation(${conv.user_id}, '${conv.name}', '${conv.phone_number}')">
|
||||
<div class="conversation-avatar-wrapper">
|
||||
${avatar}
|
||||
</div>
|
||||
<div class="conversation-info">
|
||||
<div class="conversation-name">${conv.name} ${unreadBadge}</div>
|
||||
<div class="conversation-name">${conv.name} ${unreadBadge} ${attentionBadge}</div>
|
||||
<div class="conversation-preview">
|
||||
${conv.direction === 'outgoing' ? '✓ ' : ''}${preview}
|
||||
</div>
|
||||
|
||||
+40
-1
@@ -69,7 +69,30 @@ class BotService {
|
||||
} catch (Exception $e) {
|
||||
// ignore send errors
|
||||
}
|
||||
error_log("[BotService] processMessage - sent 'enviados' ack to user {$user['id']}");
|
||||
|
||||
// Marcar para atención humana y crear un system message para que el asesor lo vea
|
||||
try {
|
||||
$this->db->update('users', ['advisor_requested' => 1], 'phone_number = :phone', ['phone' => $phoneNumber]);
|
||||
|
||||
$sys = [
|
||||
'system' => 'attention',
|
||||
'type' => 'user_sent_documents',
|
||||
'text' => 'El usuario indicó que ha enviado documentos o información; requiere atención.'
|
||||
];
|
||||
|
||||
$this->db->insert('conversations', [
|
||||
'user_id' => $user['id'],
|
||||
'direction' => 'incoming',
|
||||
'message_type' => 'text',
|
||||
'content' => json_encode($sys),
|
||||
'status' => 'received',
|
||||
'created_at' => date('Y-m-d H:i:s')
|
||||
]);
|
||||
} catch (Exception $e) {
|
||||
error_log('[BotService] failed to mark advisor_requested on envio: ' . $e->getMessage());
|
||||
}
|
||||
|
||||
error_log("[BotService] processMessage - sent 'enviados' ack to user {$user['id']} and marked advisor_requested");
|
||||
return;
|
||||
}
|
||||
} catch (Throwable $t) {
|
||||
@@ -508,6 +531,22 @@ class BotService {
|
||||
// No ponemos on_hold para evitar bloqueo automático; usamos advisor_requested
|
||||
$this->db->update('users', ['advisor_requested' => 1, 'bot_paused_until' => $pausedUntil], 'phone_number = :phone', ['phone' => $phoneNumber]);
|
||||
$this->whatsappService->sendTextMessage($phoneNumber, "🔔 Te hemos transferido con un asesor, te responderemos en breve.");
|
||||
|
||||
// Crear un mensaje 'system' en la conversación para que el equipo lo detecte fácilmente
|
||||
try {
|
||||
$user = $this->db->fetch('SELECT id FROM users WHERE phone_number = :phone', ['phone' => $phoneNumber]);
|
||||
$this->db->insert('conversations', [
|
||||
'user_id' => $user['id'] ?? null,
|
||||
'direction' => 'incoming',
|
||||
'message_type' => 'text',
|
||||
'content' => json_encode(['system' => 'advisor_request', 'text' => 'Usuario solicitó asesor']),
|
||||
'status' => 'received',
|
||||
'created_at' => date('Y-m-d H:i:s')
|
||||
]);
|
||||
} catch (Exception $e) {
|
||||
error_log('[BotService] requestAdvisor - failed to insert system message: ' . $e->getMessage());
|
||||
}
|
||||
|
||||
if (function_exists('writeLog')) writeLog('INFO', "Advisor solicited for $phoneNumber until $pausedUntil");
|
||||
} catch (Exception $e) {
|
||||
error_log('requestAdvisor failed: ' . $e->getMessage());
|
||||
|
||||
Reference in New Issue
Block a user