token = $config['telegram_bot_token']; $this->apiUrl = "https://api.telegram.org/bot{$this->token}"; } private function request($method, $data = [], $httpMethod = 'POST') { $url = "{$this->apiUrl}/{$method}"; $ch = curl_init(); curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); // --- MEJORAS --- // 1. Forzar el uso de IPv4. curl_setopt($ch, CURLOPT_IPRESOLVE, CURL_IPRESOLVE_V4); // 2. Añadir timeouts de seguridad. curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, 5); // 5 segundos para conectar curl_setopt($ch, CURLOPT_TIMEOUT, 10); // 10 segundos para la transferencia total // 3. (ÚLTIMO INTENTO) Forzar versión de TLS. curl_setopt($ch, CURLOPT_SSLVERSION, CURL_SSLVERSION_TLSv1_2); 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); } public function sendMessage($chatId, $text, $parseMode = 'HTML', $replyMarkup = null) { $data = [ 'chat_id' => $chatId, 'text' => $text, 'parse_mode' => $parseMode ]; if ($replyMarkup) { $data['reply_markup'] = $replyMarkup; } return $this->request('sendMessage', $data); } public function getMe() { return $this->request('getMe', [], 'GET'); } public function getUpdates($offset = 0) { 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' => [ [ ['text' => 'Ver Turnos', 'callback_data' => 'ver_turnos'], ['text' => 'Semana Actual', 'callback_data' => 'semana_actual'] ], [ ['text' => '📄 Horarios PDF', 'callback_data' => 'mi_pdf'] ], [ ['text' => '🔍 Buscar por Nombre', 'callback_data' => 'buscar_nombre'] ] ] ]; return $this->sendMessage($chatId, $text, 'HTML', json_encode($keyboard)); } public function answerCallback($callbackId, $text, $showAlert = false) { return $this->request('answerCallbackQuery', [ 'callback_query_id' => $callbackId, 'text' => $text, 'show_alert' => $showAlert ]); } public function deleteMessage($chatId, $messageId) { return $this->request('deleteMessage', [ 'chat_id' => $chatId, 'message_id' => $messageId ]); } public function editMessage($chatId, $messageId, $text, $keyboard = null) { $data = [ 'chat_id' => $chatId, 'message_id' => $messageId, 'text' => $text, 'parse_mode' => 'HTML' ]; if ($keyboard) { $data['reply_markup'] = $keyboard; } return $this->request('editMessageText', $data); } public function getTablaTurnos($semanas = 4) { $asignacion = new Asignacion(); $ayudantes = $asignacion->getAyudantesPorOrden(); if (empty($ayudantes)) { return "No hay ayudantes configurados."; } $hoy = new DateTime(); $diaSemana = (int)$hoy->format('w'); $domingo = clone $hoy; $domingo->modify('-' . $diaSemana . ' days'); $tabla = "TABLA DE TURNOS\n\n"; $tabla .= ""; $tabla .= str_pad("Semana", 10) . " | " . str_pad("Ayudante", 12) . " | Periodo\n"; $tabla .= str_repeat("-", 45) . "\n"; for ($i = 0; $i < $semanas; $i++) { $domingoSemana = clone $domingo; $domingoSemana->modify("+{$i} weeks"); $viernesSemana = clone $domingoSemana; $viernesSemana->modify('+5 days'); $posicion = ($i % count($ayudantes)); $ayudante = $ayudantes[$posicion]; $semanaNum = $i + 1; $periodo = $domingoSemana->format('d/m') . '-' . $viernesSemana->format('d/m'); $tabla .= str_pad("Sem $semanaNum", 10) . " | " . str_pad(substr($ayudante['nombre'], 0, 10), 12) . " | $periodo\n"; } $tabla .= ""; $tabla .= "\n\nCiclo: " . implode(' -> ', array_column($ayudantes, 'nombre')); return $tabla; } public function getSemanaActual() { $asignacion = new Asignacion(); $hoy = new DateTime(); $diaSemana = (int)$hoy->format('w'); $domingo = clone $hoy; $domingo->modify('-' . $diaSemana . ' days'); $asignacionActual = $asignacion->getAsignacionPorSemana($domingo->format('Y-m-d')); if ($asignacionActual) { return "SEMANA ACTUAL\n\n" . "Asignado: {$asignacionActual['nombre']}\n" . "Periodo: " . date('d/m/Y', strtotime($asignacionActual['semana_inicio'])) . " - " . date('d/m/Y', strtotime($asignacionActual['semana_fin'])) . "\n" . "Dias: Domingo a Viernes"; } else { return "No hay asignacion para esta semana."; } } public function getTurnosAyudante($nombre) { $config = require __DIR__ . '/../config/config.php'; try { $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] ); } catch (Exception $e) { return "Error de conexion."; } $stmt = $pdo->prepare("SELECT * FROM users WHERE nombre LIKE ? AND (rol = 'ayudante' OR rol = 'coordinador') AND activo = 1 LIMIT 1"); $stmt->execute(["%$nombre%"]); $user = $stmt->fetch(); if (!$user) { $ayudantes = (new Asignacion())->getAyudantesPorOrden(); $nombres = implode(', ', array_map(fn($a) => $a['nombre'], $ayudantes)); return "No encontre '$nombre'.\n\nAyudantes: $nombres"; } $stmt = $pdo->prepare(" SELECT semana_inicio, semana_fin FROM asignaciones_turnos WHERE user_id = ? AND semana_inicio >= CURDATE() ORDER BY semana_inicio LIMIT 4 "); $stmt->execute([$user['id']]); $turnos = $stmt->fetchAll(); if (empty($turnos)) { return "{$user['nombre']} no tiene turnos proximos."; } $result = "TURNOS DE {$user['nombre']}\n\n"; foreach ($turnos as $turno) { $result .= date('d/m/Y', strtotime($turno['semana_inicio'])) . " - " . date('d/m/Y', strtotime($turno['semana_fin'])) . "\n"; } return $result; } public function getListaAyudantesParaBusqueda() { $ayudantes = (new Asignacion())->getAyudantesPorOrden(); return array_map(fn($a) => $a['nombre'], $ayudantes); } public function sendPDF($chatId, $userId) { require_once __DIR__ . '/../src/PDFGenerator.php'; require_once __DIR__ . '/../src/User.php'; require_once __DIR__ . '/../src/DiasHorarios.php'; require_once __DIR__ . '/../src/Asignacion.php'; require_once __DIR__ . '/../src/Database.php'; $userModel = new User(); $userData = $userModel->getById($userId); if (!$userData) { $this->sendMessage($chatId, "Usuario no encontrado."); return; } $horariosModel = new DiasHorarios(); $asignacionModel = new Asignacion(); $db = \Database::getInstance()->getConnection(); $horarios = $horariosModel->getActivos(); $ayudantes = $asignacionModel->getAyudantesPorOrden(); $semanasFuturas = []; $hoy = new DateTime(); $diaSemana = (int)$hoy->format('w'); $domingoEstaSemana = clone $hoy; $domingoEstaSemana->modify('-' . $diaSemana . ' days'); for ($i = 0; $i <= 4; $i++) { $semanaDomingo = clone $domingoEstaSemana; $semanaDomingo->modify("+{$i} weeks"); $semanaInicio = $semanaDomingo->format('Y-m-d'); $asignacionesSemana = $asignacionModel->getTodasAsignacionesPorSemana($semanaInicio); $semanasFuturas[] = [ 'inicio' => $semanaInicio, 'fin' => date('Y-m-d', strtotime('+5 days', strtotime($semanaInicio))), 'asignaciones' => $asignacionesSemana, ]; } $diasNombres = [ 'domingo' => 'Domingo', 'lunes' => 'Lunes', 'martes' => 'Martes', 'miercoles' => 'Miercoles', 'jueves' => 'Jueves', 'viernes' => 'Viernes', 'sabado' => 'Sabado' ]; $diasOrden = ['domingo', 'lunes', 'martes', 'miercoles', 'jueves', 'viernes', 'sabado']; $html = PDFGenerator::getStyles(); $html .= PDFGenerator::getHeader('Horarios y Turnos - ' . $userData['nombre']); $html .= '

Mis Turnos

'; $html .= ''; foreach ($semanasFuturas as $index => $semana) { $esMiTurno = !empty($semana['asignaciones']) && in_array($userId, array_column($semana['asignaciones'], 'id')); $html .= ''; $html .= ''; $html .= ''; $html .= ''; $html .= ''; } $html .= '
SemanaPeriodoEstado
' . date('d/m/Y', strtotime($semana['inicio'])); if ($index === 0) { $html .= ' Actual'; } if ($esMiTurno) { $html .= ' Tu turno'; } $html .= '' . date('d/m/Y', strtotime($semana['inicio'])) . ' - ' . date('d/m/Y', strtotime($semana['fin'])) . '' . ($esMiTurno ? 'Asignado' : 'Sin asignar') . '
'; $html .= '

Horarios por Semana

'; $html .= ''; foreach ($diasOrden as $dia) { $html .= ''; } $html .= ''; foreach ($semanasFuturas as $index => $semana) { $esMiTurno = !empty($semana['asignaciones']) && in_array($userId, array_column($semana['asignaciones'], 'id')); $html .= ''; $html .= ''; foreach ($diasOrden as $dia) { $horarioDia = null; foreach ($horarios as $h) { if ($h['dia_semana'] === $dia) { $horarioDia = $h; break; } } $esActivo = $horarioDia && $horarioDia['activo']; if ($esMiTurno && $esActivo) { $html .= ''; } elseif (!$esActivo) { $html .= ''; } else { $html .= ''; } } $html .= ''; } $html .= '
Semana' . $diasNombres[$dia] . '
' . date('d/m', strtotime($semana['inicio'])); if ($index === 0) { $html .= ' Actual'; } $html .= ''; $html .= date('H:i', strtotime($horarioDia['hora_apertura'])) . '
' . date('H:i', strtotime($horarioDia['hora_cierre'])); $html .= '
Cerrado' . date('H:i', strtotime($horarioDia['hora_apertura'])) . '
' . date('H:i', strtotime($horarioDia['hora_cierre'])) . '
'; $html .= PDFGenerator::getFooter(); $pdfGenerator = new PDFGenerator(); $pdfContent = $pdfGenerator->generateFromHtml($html); $pdfFile = tempnam(sys_get_temp_dir(), 'turnos'); file_put_contents($pdfFile, $pdfContent); $this->sendDocument($chatId, $pdfFile, 'mis-turnos-' . date('Y-m-d') . '.pdf'); unlink($pdfFile); } public function sendDocument($chatId, $documentPath, $filename) { $url = "{$this->apiUrl}/sendDocument"; $postFields = [ 'chat_id' => $chatId, 'document' => new CURLFile($documentPath, 'application/pdf', $filename) ]; $ch = curl_init($url); curl_setopt($ch, CURLOPT_POST, true); curl_setopt($ch, CURLOPT_POSTFIELDS, $postFields); curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); curl_exec($ch); curl_close($ch); } public function sendPDFGeneral($chatId) { require_once __DIR__ . '/../src/PDFGenerator.php'; require_once __DIR__ . '/../src/User.php'; require_once __DIR__ . '/../src/DiasHorarios.php'; require_once __DIR__ . '/../src/Asignacion.php'; require_once __DIR__ . '/../src/Database.php'; $userModel = new User(); $horariosModel = new DiasHorarios(); $asignacionModel = new Asignacion(); $db = \Database::getInstance()->getConnection(); $horarios = $horariosModel->getActivos(); $ayudantes = $asignacionModel->getAyudantesPorOrden(); $semanasFuturas = []; $hoy = new DateTime(); $diaSemana = (int)$hoy->format('w'); $domingoEstaSemana = clone $hoy; $domingoEstaSemana->modify('-' . $diaSemana . ' days'); for ($i = 0; $i <= 4; $i++) { $semanaDomingo = clone $domingoEstaSemana; $semanaDomingo->modify("+{$i} weeks"); $semanaInicio = $semanaDomingo->format('Y-m-d'); $asignacionesSemana = $asignacionModel->getTodasAsignacionesPorSemana($semanaInicio); $semanasFuturas[] = [ 'inicio' => $semanaInicio, 'fin' => date('Y-m-d', strtotime('+5 days', strtotime($semanaInicio))), 'asignaciones' => $asignacionesSemana, ]; } $diasNombres = [ 'domingo' => 'Domingo', 'lunes' => 'Lunes', 'martes' => 'Martes', 'miercoles' => 'Miercoles', 'jueves' => 'Jueves', 'viernes' => 'Viernes', 'sabado' => 'Sabado' ]; $diasOrden = ['domingo', 'lunes', 'martes', 'miercoles', 'jueves', 'viernes', 'sabado']; $html = PDFGenerator::getStyles(); $html .= PDFGenerator::getHeader('Horarios y Turnos - Todos los Ayudantes'); $html .= '

Turnos de Ayudantes

'; $html .= ''; foreach ($ayudantes as $ayudante) { $stmt = $db->prepare("SELECT semana_inicio, semana_fin FROM asignaciones_turnos WHERE user_id = ? AND semana_inicio >= CURDATE() ORDER BY semana_inicio LIMIT 4"); $stmt->execute([$ayudante['id']]); $turnos = $stmt->fetchAll(); $html .= ''; $html .= ''; for ($i = 0; $i < 4; $i++) { if (isset($turnos[$i])) { $html .= ''; } else { $html .= ''; } } $html .= ''; } $html .= '
AyudanteFecha 1Fecha 2Fecha 3Fecha 4
' . htmlspecialchars($ayudante['nombre']) . '' . date('d/m/Y', strtotime($turnos[$i]['semana_inicio'])) . ' - ' . date('d/m/Y', strtotime($turnos[$i]['semana_fin'])) . '-
'; $html .= '

Horarios por Semana

'; $html .= ''; foreach ($diasOrden as $dia) { $html .= ''; } $html .= ''; foreach ($semanasFuturas as $index => $semana) { $html .= ''; $html .= ''; foreach ($diasOrden as $dia) { $horarioDia = null; foreach ($horarios as $h) { if ($h['dia_semana'] === $dia) { $horarioDia = $h; break; } } $esActivo = $horarioDia && $horarioDia['activo']; if ($esActivo) { $html .= ''; } else { $html .= ''; } } $html .= ''; } $html .= '
Semana' . $diasNombres[$dia] . '
' . date('d/m', strtotime($semana['inicio'])); if ($index === 0) { $html .= ' Actual'; } $html .= '' . date('H:i', strtotime($horarioDia['hora_apertura'])) . '
' . date('H:i', strtotime($horarioDia['hora_cierre'])) . '
Cerrado
'; $html .= '

Horarios de Apertura

'; $html .= ''; foreach ($horarios as $h) { $html .= ''; $html .= ''; $html .= ''; $html .= ''; $html .= ''; $html .= ''; } $html .= '
DiaHora AperturaHora CierreEstado
' . ucfirst($h['dia_semana']) . '' . date('H:i', strtotime($h['hora_apertura'])) . '' . date('H:i', strtotime($h['hora_cierre'])) . '' . ($h['activo'] ? 'Abierto' : 'Cerrado') . '
'; $html .= PDFGenerator::getFooter(); $pdfGenerator = new PDFGenerator(); $pdfContent = $pdfGenerator->generateFromHtml($html); $pdfFile = tempnam(sys_get_temp_dir(), 'turnos'); file_put_contents($pdfFile, $pdfContent); $this->sendDocument($chatId, $pdfFile, 'turnos-contenedor-' . date('Y-m-d') . '.pdf'); unlink($pdfFile); } }