From 5dd48ba5c2465f11c69df6dddd3e939d2b2c5307 Mon Sep 17 00:00:00 2001 From: Lizandro Guarnizo <77708265+lizandrogd@users.noreply.github.com> Date: Sat, 21 Feb 2026 11:24:15 -0500 Subject: [PATCH] up --- api/force_download_media.php | 127 ++++++++++++++++++++++++ api/get_conversations.php | 32 +++++- api/send_media_message_debug.log | 9 ++ conversations.php | 165 ++++++++++++++++++++++++------- services/WhatsAppService.php | 104 +++++++++++++++---- 5 files changed, 378 insertions(+), 59 deletions(-) create mode 100644 api/force_download_media.php diff --git a/api/force_download_media.php b/api/force_download_media.php new file mode 100644 index 0000000..2d4f886 --- /dev/null +++ b/api/force_download_media.php @@ -0,0 +1,127 @@ + false, 'error' => 'Method not allowed']); + exit; +} + +try { + $input = json_decode(file_get_contents('php://input'), true) ?: []; + $messageId = isset($input['message_id']) ? intval($input['message_id']) : 0; + + if (!$messageId) { + echo json_encode(['success' => false, 'error' => 'message_id requerido']); + exit; + } + + $db = Database::getInstance(); + + // Obtener info del mensaje + $msg = $db->fetchOne( + "SELECT id, media_url, whatsapp_media_id, message_type, local_file, conversation_id + FROM conversations + WHERE id = ?", + [$messageId] + ); + + if (!$msg) { + echo json_encode(['success' => false, 'error' => 'Mensaje no encontrado']); + exit; + } + + // Si ya tiene archivo local, retornar éxito directo + if (!empty($msg['local_file']) && file_exists($_SERVER['DOCUMENT_ROOT'] . '/' . $msg['local_file'])) { + echo json_encode(['success' => true, 'status' => 'already_downloaded', 'local_file' => $msg['local_file']]); + exit; + } + + // Determinar el media_id a descargar + $mediaId = !empty($msg['whatsapp_media_id']) ? $msg['whatsapp_media_id'] : $msg['media_url']; + if (empty($mediaId)) { + echo json_encode(['success' => false, 'error' => 'Sin media_id disponible']); + exit; + } + + // Buscar o crear entrada en media_queue + $existing = $db->fetchOne( + "SELECT id, status, attempts FROM media_queue WHERE conversation_id = ? OR media_id = ? LIMIT 1", + [$msg['id'], $mediaId] + ); + + if ($existing) { + // Resetear a pending para que sea procesado + $db->query( + "UPDATE media_queue SET status = 'pending', attempts = 0, result = NULL, updated_at = NOW() WHERE id = ?", + [$existing['id']] + ); + $queueId = $existing['id']; + } else { + // Crear nueva entrada en la cola + $convInfo = $db->fetchOne("SELECT user_id FROM conversations WHERE id = ?", [$msg['id']]); + $userId = $convInfo['user_id'] ?? 0; + + // Determinar subdirectorio por mes + $subdir = date('Y/m'); + + $db->query( + "INSERT INTO media_queue (media_id, subdir, status, attempts, conversation_id, media_url, message_type, created_at) + VALUES (?, ?, 'pending', 0, ?, ?, ?, NOW())", + [$mediaId, $subdir, $msg['id'], $mediaId, $msg['message_type'] ?? 'image'] + ); + $queueId = $db->lastInsertId(); + } + + // Ejecutar descarga inmediata usando MediaService + $result = ['success' => false, 'status' => 'queued']; + + try { + require_once '../classes/MediaService.php'; + $mediaService = new MediaService(); + + $subdir = date('Y/m'); + $localPath = $mediaService->fetchAndStoreFromGraph($mediaId, $subdir); + + if ($localPath) { + // Actualizar el mensaje con el archivo local + $db->query( + "UPDATE conversations SET local_file = ?, updated_at = NOW() WHERE id = ?", + [$localPath, $messageId] + ); + // Marcar como completado en la cola + $db->query( + "UPDATE media_queue SET status = 'completed', result = ?, updated_at = NOW() WHERE id = ?", + ['OK: ' . $localPath, $queueId] + ); + + $result = ['success' => true, 'status' => 'downloaded', 'local_file' => $localPath]; + } else { + // Falló la descarga pero quedó en cola para el worker + $db->query( + "UPDATE media_queue SET status = 'failed', attempts = attempts + 1, updated_at = NOW() WHERE id = ?", + [$queueId] + ); + $result = ['success' => false, 'status' => 'failed', 'message' => 'No se pudo descargar ahora. Quedó en cola para reintento automático.']; + } + } catch (Exception $e) { + error_log('[force_download_media] Error al descargar: ' . $e->getMessage()); + $result = ['success' => false, 'status' => 'failed', 'message' => 'Error: ' . $e->getMessage()]; + } + + echo json_encode($result); + +} catch (Exception $e) { + error_log('[force_download_media] Exception: ' . $e->getMessage()); + http_response_code(500); + echo json_encode(['success' => false, 'error' => $e->getMessage()]); +} diff --git a/api/get_conversations.php b/api/get_conversations.php index e3e0944..fd8ece6 100644 --- a/api/get_conversations.php +++ b/api/get_conversations.php @@ -159,8 +159,35 @@ try { $conversations = []; } + // Si hay búsqueda, obtener el snippet del mensaje que coincidió (no solo el último) + $matchedContentMap = []; + if (!empty($search)) { + $userIds = array_column($conversations, 'user_id'); + if (!empty($userIds)) { + $placeholders = implode(',', array_fill(0, count($userIds), '?')); + $searchParam = '%' . $search . '%'; + $params2 = array_merge([$searchParam], $userIds); + $matchedRows = $db->fetchAll( + "SELECT user_id, content FROM conversations + WHERE content LIKE ? AND user_id IN ($placeholders) + ORDER BY created_at DESC", + $params2 + ); + // Guardar el primer mensaje coincidente por usuario + foreach ($matchedRows as $row) { + if (!isset($matchedContentMap[$row['user_id']])) { + $matchedContentMap[$row['user_id']] = mb_substr($row['content'], 0, 80); + } + } + } + } + // Formatear datos para el frontend - $conversations = array_map(function($conv) { + $conversations = array_map(function($conv) use ($matchedContentMap, $search) { + $matched = null; + if (!empty($search) && isset($matchedContentMap[$conv['user_id']])) { + $matched = $matchedContentMap[$conv['user_id']]; + } return [ 'user_id' => intval($conv['user_id']), 'name' => $conv['name'] ?? $conv['phone_number'], @@ -178,7 +205,8 @@ try { '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 + 'in_service_at' => $conv['in_service_at'] ?? null, + 'matched_content' => $matched // snippet del mensaje que coincidió (solo en búsqueda) ]; }, $conversations); diff --git a/api/send_media_message_debug.log b/api/send_media_message_debug.log index f779560..a2e5d82 100644 --- a/api/send_media_message_debug.log +++ b/api/send_media_message_debug.log @@ -326,3 +326,12 @@ [2026-02-21 08:41:55] Send media by id response: {"messaging_product":"whatsapp","contacts":[{"input":"573022548060","wa_id":"573022548060"}],"messages":[{"id":"wamid.HBgMNTczMDIyNTQ4MDYwFQIAERgSODUxOUM2MzM5RUU4NDE0RjA4AA=="}]} [2026-02-21 08:41:55] WhatsApp response (successful): {"messaging_product":"whatsapp","contacts":[{"input":"573022548060","wa_id":"573022548060"}],"messages":[{"id":"wamid.HBgMNTczMDIyNTQ4MDYwFQIAERgSODUxOUM2MzM5RUU4NDE0RjA4AA=="}]} [2026-02-21 08:41:55] Saving to DB - content: 3 (2).png, media_url: 2095167437923441, local_file: uploads/media_6999b620bce0e2.99054574.png, local_thumb: uploads/media_6999b620bce0e2.99054574.png +[2026-02-21 11:19:23] Raw input: {"recipient":"573022548060","media_url":"https://bot.u-s.app/uploads/media_6999db0b6b3f01.27857143.png","media_type":"image","caption":null,"filename":"image.png","is_voice":false} +[2026-02-21 11:19:23] Input decoded: {"recipient":"573022548060","media_url":"https:\/\/bot.u-s.app\/uploads\/media_6999db0b6b3f01.27857143.png","media_type":"image","caption":null,"filename":"image.png","is_voice":false} +[2026-02-21 11:19:23] is_voice: false +[2026-02-21 11:19:23] Local file: /var/www/html/api/../uploads/media_6999db0b6b3f01.27857143.png exists=yes size=2515546 +[2026-02-21 11:19:25] Upload result: {"id":"25968821909466059"} +[2026-02-21 11:19:25] Media ID obtained: 25968821909466059 +[2026-02-21 11:19:26] Send media by id response: {"messaging_product":"whatsapp","contacts":[{"input":"573022548060","wa_id":"573022548060"}],"messages":[{"id":"wamid.HBgMNTczMDIyNTQ4MDYwFQIAERgSNEI4QkZFQjk2QjEyNkQ4RkMwAA=="}]} +[2026-02-21 11:19:26] WhatsApp response (successful): {"messaging_product":"whatsapp","contacts":[{"input":"573022548060","wa_id":"573022548060"}],"messages":[{"id":"wamid.HBgMNTczMDIyNTQ4MDYwFQIAERgSNEI4QkZFQjk2QjEyNkQ4RkMwAA=="}]} +[2026-02-21 11:19:26] Saving to DB - content: image.png, media_url: 25968821909466059, local_file: uploads/media_6999db0b6b3f01.27857143.png, local_thumb: uploads/media_6999db0b6b3f01.27857143.png diff --git a/conversations.php b/conversations.php index 894b8bb..f236d6b 100644 --- a/conversations.php +++ b/conversations.php @@ -88,8 +88,21 @@ if (!isset($_SESSION['user_id'])) { flex: 1; overflow-y: auto; padding: 6px; + scroll-behavior: smooth; } + /* Scrollbar personalizado estilo WhatsApp (fino y discreto) */ + .conversation-list::-webkit-scrollbar { width: 4px; } + .conversation-list::-webkit-scrollbar-track { background: transparent; } + .conversation-list::-webkit-scrollbar-thumb { background: rgba(0,0,0,0.15); border-radius: 4px; } + .conversation-list::-webkit-scrollbar-thumb:hover { background: rgba(0,0,0,0.28); } + + /* Buscador mejorado */ + .search-wrapper { position: relative; } + .search-spinner { position: absolute; right: 10px; top: 50%; transform: translateY(-50%); display: none; color: #999; font-size: 13px; } + .search-clear { position: absolute; right: 10px; top: 50%; transform: translateY(-50%); display: none; background: none; border: none; padding: 0; color: #999; cursor: pointer; font-size: 14px; line-height: 1; } + .search-clear:hover { color: #333; } + .conversation-item { padding: 12px 14px; border-bottom: 1px solid #f1f5f8; @@ -847,11 +860,13 @@ if (!isset($_SESSION['user_id'])) {