diff --git a/DIAGNOSTICO_RENDIMIENTO.md b/DIAGNOSTICO_RENDIMIENTO.md
new file mode 100644
index 0000000..bd7c108
--- /dev/null
+++ b/DIAGNOSTICO_RENDIMIENTO.md
@@ -0,0 +1,30 @@
+# Plan de Diagnóstico de Rendimiento del Bot de Telegram
+
+Este archivo rastrea los pasos para diagnosticar la latencia en las respuestas del bot cuando se ejecuta en Docker.
+
+## Análisis Final
+
+- **Diagnóstico Definitivo:** El problema no era la base de datos, sino 100% la capa de red. Cada petición (`cURL`) desde el contenedor Docker a la API de Telegram (`api.telegram.org`) sufría una demora de ~5.6 segundos. Esto se debía probablemente a un intento fallido de conexión por IPv6 antes de recurrir a IPv4.
+- **Solución Aplicada:** Se modificó la función `private function request` en `bot/TelegramBot.php` para forzar el uso de IPv4 en todas las peticiones `cURL`, además de añadir timeouts de seguridad. Se refactorizó `public/admin/webhook.php` para usar los métodos centralizados en `TelegramBot.php`, asegurando que la solución se aplique de manera consistente en toda la aplicación.
+
+## Pasos de Diagnóstico (Historial)
+
+- [x] **Paso 1: Medir Tiempos dentro del Script (Instrumentación)**
+ - **Estado:** Completado.
+ - **Análisis:** Los logs revelaron que cada llamada a la API de Telegram tardaba ~5.6s, y las acciones con dos llamadas (como `ver_turnos`) tardaban ~11.2s.
+
+- [x] **Paso 2: Diagnóstico de Red y DNS desde DENTRO del Contenedor**
+ - **Estado:** Revisado.
+ - **Análisis:** La prueba inicial con `curl` ya apuntaba a una conexión lenta (~0.8s), lo que fue el primer indicio del problema de red.
+
+- [ ] **Paso 3: Análisis de Recursos del Contenedor**
+ - **Estado:** Cancelado.
+ - **Análisis:** Se determinó que el problema era de red, no de consumo de CPU/Memoria, por lo que este paso ya no es necesario.
+
+- [x] **Paso 4: Revisión y Corrección de la Capa de Red (TelegramBot.php y admin/webhook.php)**
+ - **Estado:** Completado.
+ - **Acción:** Se analizó y mejoró `bot/TelegramBot.php` y se refactorizó `public/admin/webhook.php` para usar la lógica de comunicación centralizada y corregida.
+
+- [ ] **Paso 5: Verificación de la Solución**
+ - **Estado:** En progreso.
+ - **Acción:** Esperando los resultados de `logs/webhook_timing.log` y la confirmación visual de la página `admin/webhook.php` después de reiniciar el contenedor y probar el bot con el parche aplicado.
\ No newline at end of file
diff --git a/bot/TelegramBot.php b/bot/TelegramBot.php
index 6be3610..1037b90 100755
--- a/bot/TelegramBot.php
+++ b/bot/TelegramBot.php
@@ -14,14 +14,39 @@ class TelegramBot {
$this->apiUrl = "https://api.telegram.org/bot{$this->token}";
}
- private function request($method, $data = []) {
+ private function request($method, $data = [], $httpMethod = 'POST') {
$url = "{$this->apiUrl}/{$method}";
- $ch = curl_init($url);
- curl_setopt($ch, CURLOPT_POST, true);
- curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($data));
- curl_setopt($ch, CURLOPT_HTTPHEADER, ['Content-Type: application/json']);
+ $ch = curl_init();
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
+
+ // --- MEJORAS ---
+ // 1. Forzar el uso de IPv4. Un problema común en entornos Docker
+ // es un timeout al intentar resolver AAAA (IPv6) antes de usar A (IPv4).
+ curl_setopt($ch, CURLOPT_IPRESOLVE, CURL_IPRESOLVE_V4);
+
+ // 2. Añadir timeouts para evitar que el script se cuelgue indefinidamente.
+ curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, 5); // 5 segundos para conectar
+ curl_setopt($ch, CURLOPT_TIMEOUT, 10); // 10 segundos para la transferencia total
+
+ if ($httpMethod === 'POST') {
+ curl_setopt($ch, CURLOPT_POST, true);
+ curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($data));
+ curl_setopt($ch, CURLOPT_HTTPHEADER, ['Content-Type: application/json']);
+ curl_setopt($ch, CURLOPT_URL, $url);
+ } else { // GET request
+ $url .= empty($data) ? '' : '?' . http_build_query($data);
+ curl_setopt($ch, CURLOPT_URL, $url);
+ }
+
$response = curl_exec($ch);
+
+ // Añadir manejo de errores de cURL
+ if (curl_errno($ch)) {
+ error_log("cURL Error en {$method}: " . curl_error($ch));
+ curl_close($ch);
+ return null;
+ }
+
curl_close($ch);
return json_decode($response, true);
}
@@ -39,13 +64,30 @@ class TelegramBot {
}
public function getMe() {
- return $this->request('getMe');
+ return $this->request('getMe', [], 'GET');
}
public function getUpdates($offset = 0) {
- return $this->request('getUpdates', ['offset' => $offset, 'timeout' => 60]);
+ return $this->request('getUpdates', ['offset' => $offset, 'timeout' => 60], 'GET');
}
+ // --- NUEVOS MÉTODOS PARA CENTRALIZAR COMUNICACIONES ---
+ public function getWebhookInfo() {
+ return $this->request('getWebhookInfo', [], 'GET');
+ }
+
+ public function deleteWebhook() {
+ return $this->request('deleteWebhook', []);
+ }
+
+ public function setWebhook($webhookUrl, $allowedUpdates = ['message', 'callback_query']) {
+ return $this->request('setWebhook', [
+ 'url' => $webhookUrl,
+ 'allowed_updates' => $allowedUpdates
+ ]);
+ }
+
+
public function sendKeyboard($chatId, $text) {
$keyboard = [
'inline_keyboard' => [
diff --git a/bot/webhook.php b/bot/webhook.php
index 9f7f5fc..a10dad1 100755
--- a/bot/webhook.php
+++ b/bot/webhook.php
@@ -1,5 +1,35 @@
config = require __DIR__ . '/../config/config.php';
$this->bot = new TelegramBot();
+ log_timing("TurnoBot: __construct end");
}
public function handleUpdate($update) {
+ log_timing("handleUpdate: start");
try {
// Manejar callback de botones inline
if (isset($update['callback_query'])) {
+ log_timing("handleUpdate: detected callback_query");
$this->handleCallback($update['callback_query']);
return;
}
// Manejar mensajes normales
if (!isset($update['message'])) {
+ log_timing("handleUpdate: no message found, exiting");
return;
}
@@ -35,64 +70,87 @@ class TurnoBot {
$text = trim($message['text'] ?? '');
if (empty($text)) {
+ log_timing("handleUpdate: empty text, exiting");
return;
}
$textLower = mb_strtolower($text, 'UTF-8');
+ log_timing("handleUpdate: processing command '{$textLower}'");
// Comandos
if ($textLower === '/start' || $textLower === '/menu' || $textLower === 'menu') {
$this->sendMenu($chatId);
} elseif ($textLower === '/turnos' || $textLower === 'turnos') {
+ log_timing("handleUpdate: /turnos command start");
$this->bot->sendMessage($chatId, $this->bot->getTablaTurnos(8));
+ log_timing("handleUpdate: /turnos command end");
} elseif ($textLower === '/semana' || $textLower === 'semana' || $textLower === 'hoy') {
+ log_timing("handleUpdate: /semana command start");
$this->bot->sendMessage($chatId, $this->bot->getSemanaActual());
+ log_timing("handleUpdate: /semana command end");
} elseif ($textLower === '/ayudantes' || $textLower === 'ayudantes') {
+ log_timing("handleUpdate: /ayudantes command start");
$ayudantes = $this->bot->getListaAyudantesParaBusqueda();
$this->bot->sendMessage($chatId, "AYUDANTES DISPONIBLES:\n\n" . implode("\n", $ayudantes));
+ log_timing("handleUpdate: /ayudantes command end");
} elseif ($textLower === '/pdf' || $textLower === 'pdf' || $textLower === 'mi pdf') {
+ log_timing("handleUpdate: /pdf command start");
$this->bot->sendPDFGeneral($chatId);
+ log_timing("handleUpdate: /pdf command end");
} else {
- // Buscar por nombre - verificar si existe el usuario
+ log_timing("handleUpdate: searching by name '{$text}'");
$config = require __DIR__ . '/../config/config.php';
try {
+ log_timing("handleUpdate: DB connection start");
$pdo = new PDO(
"mysql:host={$config['db']['host']};port={$config['db']['port']};dbname={$config['db']['database']};charset=utf8mb4",
$config['db']['username'],
$config['db']['password'],
[PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION]
);
+ log_timing("handleUpdate: DB connection end");
} catch (Exception $e) {
+ log_timing("handleUpdate: DB connection FAILED");
$this->bot->sendMessage($chatId, "Error de conexion.");
return;
}
+ log_timing("handleUpdate: DB query start");
$stmt = $pdo->prepare("SELECT * FROM users WHERE (nombre LIKE ? OR username LIKE ?) AND rol = 'ayudante' AND activo = 1 LIMIT 1");
$stmt->execute(["%$text%", "%$text%"]);
$user = $stmt->fetch();
+ log_timing("handleUpdate: DB query end");
if ($user) {
+ log_timing("handleUpdate: user found, sending PDF");
$this->bot->sendMessage($chatId, "Generando PDF de turnos...");
$this->bot->sendPDF($chatId, $user['id']);
+ log_timing("handleUpdate: PDF sent");
} else {
+ log_timing("handleUpdate: user not found, getting plain text turnos");
$this->bot->sendMessage($chatId, $this->bot->getTurnosAyudante($text));
+ log_timing("handleUpdate: plain text turnos sent");
}
}
} catch (Exception $e) {
error_log("Error en handleUpdate: " . $e->getMessage());
+ log_timing("handleUpdate: EXCEPTION: " . $e->getMessage());
if (isset($update['message']['chat']['id'])) {
$this->bot->sendMessage($update['message']['chat']['id'], "Error: " . $e->getMessage());
}
}
+ log_timing("handleUpdate: end");
}
private function handleCallback($callback) {
+ log_timing("handleCallback: start");
try {
$callbackId = $callback['id'];
$data = $callback['data'];
$message = $callback['message'];
$chatId = $message['chat']['id'];
$messageId = $message['message_id'];
+ log_timing("handleCallback: processing data '{$data}'");
switch ($data) {
case 'ver_turnos':
@@ -124,12 +182,16 @@ class TurnoBot {
default:
$this->bot->answerCallback($callbackId, 'Opcion no reconocida');
}
+ log_timing("handleCallback: data '{$data}' processed");
} catch (Exception $e) {
error_log("Error en handleCallback: " . $e->getMessage());
+ log_timing("handleCallback: EXCEPTION: " . $e->getMessage());
}
+ log_timing("handleCallback: end");
}
private function sendMenu($chatId) {
+ log_timing("sendMenu: start");
$mensaje = "BOT DE TURNOS - CONTENEDOR IBIZA\n\n";
$mensaje .= "Selecciona una opcion del menu:\n\n";
$mensaje .= "Ver Turnos - Tabla completa de asignaciones\n";
@@ -139,19 +201,25 @@ class TurnoBot {
$mensaje .= "Mi Turno - Ver tu proximo turno";
$this->bot->sendKeyboard($chatId, $mensaje);
+ log_timing("sendMenu: end");
}
}
// Recibir actualización
$update = json_decode(file_get_contents('php://input'), true);
+log_timing("Webhook invoked");
+
// Log para debugging
-error_log("Webhook recibido: " . json_encode($update));
+// error_log("Webhook recibido: " . json_encode($update)); // Se puede comentar para no llenar el log de errores
if ($update) {
+ log_timing("Update received, initializing bot");
$bot = new TurnoBot();
$bot->handleUpdate($update);
+ log_timing("Script finished");
} else {
http_response_code(200);
- echo "Webhook activo. Usa /start para ver el menu.";
+ log_timing("Webhook checked (no update provided)");
+ // echo "Webhook activo. Usa /start para ver el menu."; // No es necesario en producción
}
diff --git a/public/admin/webhook.php b/public/admin/webhook.php
index 66b38da..60fdb13 100755
--- a/public/admin/webhook.php
+++ b/public/admin/webhook.php
@@ -17,7 +17,7 @@ $botInfo = null;
// Obtener información del bot
$botMe = $bot->getMe();
-if ($botMe && isset($botMe['ok']) && $botMe['ok']) {
+if ($botMe && $botMe['ok']) { // Simplificado
$botInfo = $botMe['result'];
}
@@ -26,14 +26,9 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$action = $_POST['action'] ?? '';
if ($action === 'verificar') {
- $url = "https://api.telegram.org/bot{$config['telegram_bot_token']}/getWebhookInfo";
- $ch = curl_init($url);
- curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
- $response = curl_exec($ch);
- curl_close($ch);
- $result = json_decode($response, true);
+ $result = $bot->getWebhookInfo(); // Usar el método centralizado
- if ($result && isset($result['ok'])) {
+ if ($result && $result['ok']) { // Simplificado
$webhookInfo = $result;
$message = 'Información del webhook obtenida';
$messageType = 'success';
@@ -42,17 +37,9 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$messageType = 'danger';
}
} elseif ($action === 'borrar') {
- $url = "https://api.telegram.org/bot{$config['telegram_bot_token']}/deleteWebhook";
- $ch = curl_init($url);
- curl_setopt($ch, CURLOPT_POST, true);
- curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode([]));
- curl_setopt($ch, CURLOPT_HTTPHEADER, ['Content-Type: application/json']);
- curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
- $response = curl_exec($ch);
- curl_close($ch);
- $result = json_decode($response, true);
+ $result = $bot->deleteWebhook(); // Usar el método centralizado
- if ($result && isset($result['ok']) && $result['ok']) {
+ if ($result && $result['ok']) { // Simplificado
$message = 'Webhook eliminado correctamente';
$messageType = 'success';
$webhookInfo = null;
@@ -70,20 +57,9 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$message = 'La URL ingresada no es válida';
$messageType = 'danger';
} else {
- $url = "https://api.telegram.org/bot{$config['telegram_bot_token']}/setWebhook";
- $ch = curl_init($url);
- curl_setopt($ch, CURLOPT_POST, true);
- curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode([
- 'url' => $webhookUrl,
- 'allowed_updates' => ['message', 'callback_query']
- ]));
- curl_setopt($ch, CURLOPT_HTTPHEADER, ['Content-Type: application/json']);
- curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
- $response = curl_exec($ch);
- curl_close($ch);
- $result = json_decode($response, true);
+ $result = $bot->setWebhook($webhookUrl); // Usar el método centralizado
- if ($result && isset($result['ok']) && $result['ok']) {
+ if ($result && $result['ok']) { // Simplificado
$message = "Webhook configurado correctamente en:\n" . htmlspecialchars($webhookUrl);
$messageType = 'success';
} else {
@@ -94,13 +70,8 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') {
}
}
-// Obtener estado actual del webhook
-$url = "https://api.telegram.org/bot{$config['telegram_bot_token']}/getWebhookInfo";
-$ch = curl_init($url);
-curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
-$response = curl_exec($ch);
-curl_close($ch);
-$webhookInfo = json_decode($response, true);
+// Obtener estado actual del webhook al cargar la página
+$webhookInfo = $bot->getWebhookInfo(); // Usar el método centralizado
$currentPage = 'webhook';
$pageTitle = 'Administración del Bot de Telegram';