This commit is contained in:
lizandrogd
2026-01-21 16:32:40 -05:00
parent 1c578b14b0
commit ac179cec98
5 changed files with 182 additions and 3 deletions
+41
View File
@@ -0,0 +1,41 @@
<?php
require_once '../config/config.php';
header('Content-Type: application/json; charset=utf-8');
requireAuthentication();
try {
$input = json_decode(file_get_contents('php://input'), true) ?: $_POST;
$userId = isset($input['user_id']) ? intval($input['user_id']) : 0;
if (!$userId) {
http_response_code(400);
echo json_encode(['success' => false, 'error' => 'user_id required']);
exit;
}
$db = Database::getInstance();
$user = $db->fetch('SELECT phone_number FROM users WHERE id = :id', ['id' => $userId]);
if (!$user) {
http_response_code(404);
echo json_encode(['success' => false, 'error' => 'User not found']);
exit;
}
$operator = $_SESSION['admin_user'] ?? null;
$operatorId = $operator['id'] ?? null;
try {
require_once __DIR__ . '/../services/BotService.php';
$bot = new BotService();
$bot->attendConversation($user['phone_number'], $operatorId);
echo json_encode(['success' => true]);
} catch (Exception $e) {
// Fallback: apply updates directly and log activity
$now = date('Y-m-d H:i:s');
$db->update('users', ['in_service' => 1, 'in_service_by' => $operatorId, 'in_service_at' => $now, 'advisor_requested' => 0], 'id = :id', ['id' => $userId]);
$db->insert('operator_activity', ['user_id' => $userId, 'operator_id' => $operatorId, 'action' => 'attend', 'details' => 'Marked in_service via fallback', 'created_at' => $now]);
echo json_encode(['success' => true, 'warning' => 'BotService failed, fallback applied']);
}
} catch (Exception $e) {
http_response_code(500);
echo json_encode(['success' => false, 'error' => $e->getMessage()]);
}
+9 -1
View File
@@ -23,6 +23,10 @@ try {
u.avatar_url,
u.bot_enabled,
u.on_hold,
u.advisor_requested,
u.in_service,
u.in_service_by,
u.in_service_at,
lm.message_id AS last_message_id,
lm.content AS last_message,
lm.direction AS direction,
@@ -60,7 +64,11 @@ try {
'message_type' => $conv['message_type'] ?? 'text',
'unread_count' => intval($conv['unread_count'] ?? 0),
'bot_enabled' => isset($conv['bot_enabled']) ? (bool)$conv['bot_enabled'] : true,
'on_hold' => isset($conv['on_hold']) ? (bool)$conv['on_hold'] : false
'on_hold' => isset($conv['on_hold']) ? (bool)$conv['on_hold'] : false,
'advisor_requested' => isset($conv['advisor_requested']) ? (bool)$conv['advisor_requested'] : false,
'in_service' => isset($conv['in_service']) ? (bool)$conv['in_service'] : false,
'in_service_by' => isset($conv['in_service_by']) ? intval($conv['in_service_by']) : null,
'in_service_at' => $conv['in_service_at'] ?? null
];
}, $conversations);
+40 -1
View File
@@ -475,7 +475,10 @@
<span id="hold-indicator" style="display:none; margin-left:8px; color:#b85; font-weight:600">EN ESPERA</span>
</h6>
<small id="chat-phone">+1234567890</small>
<div style="font-size:12px; margin-top:4px;"><button id="release-hold-btn" class="btn btn-sm btn-outline-primary" style="display:none">Liberar espera</button></div>
<div style="font-size:12px; margin-top:4px;">
<button id="attend-btn" class="btn btn-sm btn-outline-success" style="display:none; margin-right:6px;">Atender</button>
<button id="release-hold-btn" class="btn btn-sm btn-outline-primary" style="display:none">Liberar espera</button>
</div>
</div>
<div style="margin-left: auto; display:flex; gap:8px; align-items:center;">
<div id="bot-toggle-container" style="display:flex;align-items:center;gap:6px;margin-right:8px;">
@@ -886,6 +889,42 @@
alert('Error liberando espera');
}
};
// Attender (marcar 'en servicio' sin liberar)
const attendBtn = document.getElementById('attend-btn');
if (attendBtn) {
attendBtn.style.display = (conv.advisor_requested && !conv.in_service) ? 'inline-block' : 'none';
attendBtn.onclick = async () => {
try {
const resp = await fetch('api/attend.php', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ user_id: userId })
});
if (resp.status === 401) {
alert('Sesión expirada. Por favor inicie sesión de nuevo.');
return;
}
const json = await resp.json();
if (json && json.success) {
conv.in_service = true;
conv.advisor_requested = 0;
holdIndicator.textContent = 'EN SERVICIO';
holdIndicator.style.color = '#28a745';
holdIndicator.style.display = 'inline';
attendBtn.style.display = 'none';
// keep release available
releaseBtn.style.display = 'inline-block';
await this.loadConversations();
} else {
alert('Error al marcar como atendida');
}
} catch (e) {
console.error('Error attending', e);
alert('Error al atender');
}
};
}
}
}
};
+50
View File
@@ -0,0 +1,50 @@
<?php
require_once __DIR__ . '/../config/config.php';
$db = Database::getInstance();
try {
$col = $db->fetch("SHOW COLUMNS FROM users LIKE 'in_service'");
if ($col) {
echo "Column in_service already exists\n";
} else {
$db->query("ALTER TABLE users ADD COLUMN in_service TINYINT(1) DEFAULT 0 AFTER advisor_requested");
echo "Added in_service column\n";
}
$col = $db->fetch("SHOW COLUMNS FROM users LIKE 'in_service_by'");
if ($col) {
echo "Column in_service_by already exists\n";
} else {
$db->query("ALTER TABLE users ADD COLUMN in_service_by INT NULL AFTER in_service");
echo "Added in_service_by column\n";
}
$col = $db->fetch("SHOW COLUMNS FROM users LIKE 'in_service_at'");
if ($col) {
echo "Column in_service_at already exists\n";
} else {
$db->query("ALTER TABLE users ADD COLUMN in_service_at DATETIME NULL AFTER in_service_by");
echo "Added in_service_at column\n";
}
// Crear tabla operator_activity si no existe
$check = $db->fetch("SHOW TABLES LIKE 'operator_activity'");
if ($check) {
echo "Table operator_activity already exists\n";
} else {
$sql = "CREATE TABLE operator_activity (
id INT AUTO_INCREMENT PRIMARY KEY,
user_id INT NULL,
operator_id INT NULL,
action VARCHAR(50) NOT NULL,
details TEXT NULL,
created_at DATETIME NOT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4";
$db->query($sql);
echo "Created table operator_activity\n";
}
echo "Done.\n";
} catch (Exception $e) {
echo "Migration failed: " . $e->getMessage() . "\n";
}
+42 -1
View File
@@ -138,7 +138,7 @@ class BotService {
} else {
// Opcional: informar que el asesor ya respondió y no podemos enviar automáticas
try {
$this->whatsappService->sendTextMessage($phoneNumber, "🔕 Un asesor ya ha intervenido en la conversación, por favor espera su respuesta o utiliza *menu* para ver opciones.");
//$this->whatsappService->sendTextMessage($phoneNumber, "🔕 Un asesor ya ha intervenido en la conversación, por favor espera su respuesta o utiliza *menu* para ver opciones.");
} catch (Exception $e) {
// ignore
}
@@ -504,6 +504,47 @@ class BotService {
}
}
/**
* Marcar la conversación como "en servicio" por un operador (Atender)
*/
public function attendConversation($phoneNumber, $operatorId = null) {
try {
$now = date('Y-m-d H:i:s');
$update = ['in_service' => 1, 'in_service_at' => $now, 'advisor_requested' => 0];
if ($operatorId) $update['in_service_by'] = $operatorId;
$this->db->update('users', $update, 'phone_number = :phone', ['phone' => $phoneNumber]);
// Notificar al usuario
try {
$this->whatsappService->sendTextMessage($phoneNumber, "🔔 Un asesor está atendiendo tu conversación. Por favor, espera su respuesta.");
} catch (Exception $e) {
// ignore send errors
}
// Registrar actividad del operador si hay información disponible
$user = $this->db->fetch('SELECT id FROM users WHERE phone_number = :phone', ['phone' => $phoneNumber]);
$userId = $user['id'] ?? null;
$activity = [
'user_id' => $userId,
'operator_id' => $operatorId,
'action' => 'attend',
'details' => 'Operator started attending the conversation',
'created_at' => $now
];
try {
$this->db->insert('operator_activity', $activity);
} catch (Exception $e) {
if (function_exists('writeLog')) writeLog('WARN', 'Failed to insert operator_activity: ' . $e->getMessage());
error_log('Failed to insert operator_activity: ' . $e->getMessage());
}
if (function_exists('writeLog')) writeLog('INFO', "Operator {$operatorId} attending conversation for {$phoneNumber}");
} catch (Exception $e) {
error_log('attendConversation failed: ' . $e->getMessage());
throw $e;
}
}
/**
* Actualizar estado del menú del usuario
*/