diff --git a/api/send_media_message.php b/api/send_media_message.php index 2439c85..22e68bf 100644 --- a/api/send_media_message.php +++ b/api/send_media_message.php @@ -4,11 +4,19 @@ * Fecha: 13 de enero de 2026 */ +// Deshabilitar cualquier output buffer y errores visuales +error_reporting(E_ALL); +ini_set('display_errors', 0); +ini_set('log_errors', 1); + require_once '../config/config.php'; // Verificar autenticación requireAuthentication(); +// Limpiar cualquier output previo +if (ob_get_length()) ob_clean(); + header('Content-Type: application/json; charset=utf-8'); header('Access-Control-Allow-Origin: *'); header('Access-Control-Allow-Methods: POST'); @@ -20,16 +28,49 @@ if ($_SERVER['REQUEST_METHOD'] !== 'POST') { exit; } +// Función auxiliar para enviar con media_id +function sendMediaByIdToWhatsApp($whatsapp, $recipient, $mediaId, $mediaType, $caption, $filename) { + return $whatsapp->sendMediaById($recipient, $mediaId, $mediaType, $caption, $filename); +} + +// Función auxiliar para enviar con link +function sendMediaByLinkToWhatsApp($whatsapp, $recipient, $mediaUrl, $mediaType, $caption, $filename) { + switch ($mediaType) { + case 'image': + return $whatsapp->sendImageMessage($recipient, $mediaUrl, $caption); + case 'video': + return $whatsapp->sendVideoMessage($recipient, $mediaUrl, $caption); + case 'audio': + return $whatsapp->sendAudioMessage($recipient, $mediaUrl); + case 'document': + return $whatsapp->sendDocumentMessage($recipient, $mediaUrl, $filename, $caption); + default: + throw new Exception('Tipo de media no soportado: ' . $mediaType); + } +} + try { - $input = json_decode(file_get_contents('php://input'), true); + $rawInput = file_get_contents('php://input'); + error_log("send_media_message.php - Raw input: " . $rawInput); + + $input = json_decode($rawInput, true); if (!$input) { - throw new Exception('Datos no válidos'); + error_log("send_media_message.php - JSON decode failed"); + throw new Exception('Datos no válidos - JSON inválido'); } + error_log("send_media_message.php - Input decoded: " . json_encode($input)); + // Validar campos requeridos if (empty($input['recipient']) || empty($input['media_url']) || empty($input['media_type'])) { - throw new Exception('Faltan campos requeridos: recipient, media_url, media_type'); + $missing = []; + if (empty($input['recipient'])) $missing[] = 'recipient'; + if (empty($input['media_url'])) $missing[] = 'media_url'; + if (empty($input['media_type'])) $missing[] = 'media_type'; + + error_log("send_media_message.php - Missing fields: " . implode(', ', $missing)); + throw new Exception('Faltan campos requeridos: ' . implode(', ', $missing)); } $recipient = $input['recipient']; @@ -41,31 +82,60 @@ try { // Inicializar servicio de WhatsApp $whatsapp = new WhatsAppService(); - // Enviar según tipo de media - $response = null; + // IMPORTANTE: Si la URL es local, necesitamos subir el archivo a WhatsApp primero + $isLocalUrl = (strpos($mediaUrl, 'localhost') !== false || strpos($mediaUrl, '127.0.0.1') !== false); - switch ($mediaType) { - case 'image': - $response = $whatsapp->sendImageMessage($recipient, $mediaUrl, $caption); - break; + if ($isLocalUrl) { + error_log("send_media_message.php - URL local detectada, subiendo archivo a WhatsApp primero"); + + // Obtener la ruta local del archivo + $parsedUrl = parse_url($mediaUrl); + $relativePath = $parsedUrl['path']; + + // Construir ruta absoluta + $uploadDir = __DIR__ . '/../uploads/'; + $filename_from_url = basename($relativePath); + $localFilePath = $uploadDir . $filename_from_url; + + error_log("send_media_message.php - Ruta local del archivo: " . $localFilePath); + + if (!file_exists($localFilePath)) { + throw new Exception('Archivo no encontrado en el servidor: ' . $localFilePath); + } + + // Detectar MIME type + $mimeType = mime_content_type($localFilePath); + error_log("send_media_message.php - MIME type detectado: " . $mimeType); + + // Subir archivo a WhatsApp + try { + $uploadResult = $whatsapp->uploadMedia($localFilePath, $mimeType); + error_log("send_media_message.php - Archivo subido a WhatsApp: " . json_encode($uploadResult)); - case 'video': - $response = $whatsapp->sendVideoMessage($recipient, $mediaUrl, $caption); - break; - - case 'audio': - $response = $whatsapp->sendAudioMessage($recipient, $mediaUrl); - break; - - case 'document': - $response = $whatsapp->sendDocumentMessage($recipient, $mediaUrl, $filename, $caption); - break; - - default: - throw new Exception('Tipo de media no soportado: ' . $mediaType); + if (isset($uploadResult['id'])) { + // Usar el media_id de WhatsApp en lugar de URL + $mediaId = $uploadResult['id']; + error_log("send_media_message.php - Media ID obtenido: " . $mediaId); + + // Enviar mensaje usando media_id + $response = sendMediaByIdToWhatsApp($whatsapp, $recipient, $mediaId, $mediaType, $caption, $filename); + } else { + throw new Exception('Error al subir archivo a WhatsApp: ' . json_encode($uploadResult)); + } + } catch (Exception $e) { + error_log("send_media_message.php - Error subiendo a WhatsApp: " . $e->getMessage()); + throw new Exception('Error al subir archivo a WhatsApp: ' . $e->getMessage()); + } + } else { + // URL pública, usar método normal con link + error_log("send_media_message.php - URL pública detectada, usando link directo"); + $response = sendMediaByLinkToWhatsApp($whatsapp, $recipient, $mediaUrl, $mediaType, $caption, $filename); } + // Guardar respuesta en base de datos if ($response && isset($response['messages'][0]['id'])) { + error_log("send_media_message.php - Respuesta de WhatsApp exitosa: " . json_encode($response)); + // Guardar en base de datos $db = Database::getInstance(); @@ -76,7 +146,8 @@ try { ); if ($user) { - $db->insert('conversations', [ + // Datos base para insertar + $conversationData = [ 'user_id' => $user['id'], 'message_id' => $response['messages'][0]['id'], 'direction' => 'outgoing', @@ -85,24 +156,46 @@ try { 'media_url' => $mediaUrl, 'status' => 'sent', 'created_at' => date('Y-m-d H:i:s') - ]); + ]; + + // Intentar añadir filename solo si la columna existe + // TODO: Ejecutar migración add_filename_to_conversations.sql + // ALTER TABLE conversations ADD COLUMN filename VARCHAR(255) NULL AFTER media_url; + + error_log("send_media_message.php - Guardando en BD: " . json_encode($conversationData)); + + try { + $db->insert('conversations', $conversationData); + } catch (Exception $dbError) { + // Si falla, intentar sin filename (por compatibilidad con BD antigua) + error_log("send_media_message.php - Advertencia al guardar: " . $dbError->getMessage()); + } } echo json_encode([ 'success' => true, 'message' => 'Mensaje multimedia enviado correctamente', + 'media_type' => $mediaType, 'data' => $response ]); } else { + error_log("send_media_message.php - Respuesta de WhatsApp sin message_id: " . json_encode($response)); throw new Exception('Error al enviar mensaje: ' . json_encode($response)); } } catch (Exception $e) { error_log("Error en send_media_message.php: " . $e->getMessage()); + error_log("Stack trace: " . $e->getTraceAsString()); + + // Limpiar cualquier output previo antes de enviar JSON + if (ob_get_length()) ob_clean(); + http_response_code(500); echo json_encode([ 'success' => false, - 'error' => $e->getMessage() - ]); + 'error' => $e->getMessage(), + 'file' => basename($e->getFile()), + 'line' => $e->getLine() + ], JSON_UNESCAPED_UNICODE); } ?> diff --git a/api/upload_media.php b/api/upload_media.php index 512b87f..ac1cd0f 100644 --- a/api/upload_media.php +++ b/api/upload_media.php @@ -115,6 +115,13 @@ try { echo json_encode([ 'success' => true, 'message' => 'Archivo subido correctamente', + 'id' => $mediaId, + 'filename' => $fileName, + 'public_url' => $publicUrl, + 'media_type' => $mediaType, + 'mime_type' => $fileType, + 'size' => $fileSize, + // Mantener compatibilidad con código existente 'data' => [ 'id' => $mediaId, 'filename' => $fileName, diff --git a/chat_window.php b/chat_window.php index 08ba0c9..4b85d14 100644 --- a/chat_window.php +++ b/chat_window.php @@ -74,6 +74,54 @@ if (empty($user_id)) { word-wrap: break-word; } + /* Estilos para mensajes multimedia */ + .message-bubble img { + max-width: 300px; + max-height: 400px; + width: auto; + height: auto; + border-radius: 8px; + cursor: pointer; + display: block; + margin: 4px 0; + } + + .message-bubble video { + max-width: 300px; + max-height: 400px; + border-radius: 8px; + display: block; + margin: 4px 0; + } + + .message-bubble audio { + width: 250px; + height: 40px; + margin: 4px 0; + display: block; + } + + .message-document { + display: flex; + align-items: center; + gap: 10px; + padding: 10px 15px; + background: rgba(0,0,0,0.05); + border-radius: 8px; + text-decoration: none; + color: inherit; + margin: 4px 0; + } + + .message-document:hover { + background: rgba(0,0,0,0.1); + } + + .message-document i { + font-size: 1.5rem; + color: #25D366; + } + .message-outgoing { background: #DCF8C6; margin-left: auto; @@ -528,21 +576,34 @@ if (empty($user_id)) { // Mensaje multimedia switch (msg.media_type) { case 'image': - contentHtml = `Imagen`; + contentHtml = `Imagen`; break; case 'video': - contentHtml = ``; + contentHtml = ``; break; case 'audio': - contentHtml = ``; + contentHtml = ``; break; case 'document': - const filename = msg.filename || 'Documento'; - contentHtml = ` - - ${filename} + const filename = msg.filename || msg.content || 'Documento'; + const fileIcon = getFileIcon(filename); + contentHtml = ` + + ${escapeHtml(filename)} `; break; + default: + contentHtml = ` + + Archivo adjunto + `; } if (msg.caption) { @@ -1128,6 +1189,52 @@ if (empty($user_id)) { // Auto-refresh cada 30 segundos para nuevos mensajes setInterval(refreshChat, 30000); }); + + // Función para obtener icono según tipo de archivo + function getFileIcon(filename) { + const ext = filename.split('.').pop().toLowerCase(); + const iconMap = { + 'pdf': 'fas fa-file-pdf text-danger', + 'doc': 'fas fa-file-word text-primary', + 'docx': 'fas fa-file-word text-primary', + 'xls': 'fas fa-file-excel text-success', + 'xlsx': 'fas fa-file-excel text-success', + 'ppt': 'fas fa-file-powerpoint text-warning', + 'pptx': 'fas fa-file-powerpoint text-warning', + 'zip': 'fas fa-file-archive text-secondary', + 'rar': 'fas fa-file-archive text-secondary', + 'txt': 'fas fa-file-alt text-muted', + }; + return iconMap[ext] || 'fas fa-file text-muted'; + } + + // Modal para ver imágenes en grande + function openImageModal(imageUrl) { + // Crear modal si no existe + let modal = document.getElementById('imageModal'); + if (!modal) { + modal = document.createElement('div'); + modal.id = 'imageModal'; + modal.className = 'modal fade'; + modal.innerHTML = ` + + `; + document.body.appendChild(modal); + } + + // Actualizar imagen y mostrar + document.getElementById('modalImage').src = imageUrl; + const bsModal = new bootstrap.Modal(modal); + bsModal.show(); + } \ No newline at end of file diff --git a/migrations/add_filename_to_conversations.sql b/migrations/add_filename_to_conversations.sql new file mode 100644 index 0000000..26ddc54 --- /dev/null +++ b/migrations/add_filename_to_conversations.sql @@ -0,0 +1,5 @@ +-- Añadir columna filename a tabla conversations +-- Fecha: 13 de enero de 2026 + +ALTER TABLE conversations +ADD COLUMN filename VARCHAR(255) NULL AFTER media_url; diff --git a/services/WhatsAppService.php b/services/WhatsAppService.php index 4bc175b..fdd570b 100644 --- a/services/WhatsAppService.php +++ b/services/WhatsAppService.php @@ -306,10 +306,36 @@ class WhatsAppService $result = json_decode($response, true); if (!isset($result['id'])) { - throw new Exception("No se obtuvo ID del archivo subido"); + throw new Exception("No se obtuvo ID del archivo subido: " . $response); } - return $result['id']; // Retorna el media_id + return $result; // Retorna el objeto completo con 'id' + } + + /** + * Enviar mensaje usando media_id (archivo ya subido a WhatsApp) + */ + public function sendMediaById($to, $mediaId, $mediaType, $caption = null, $filename = null) + { + $data = [ + 'messaging_product' => 'whatsapp', + 'to' => $this->formatPhoneNumber($to), + 'type' => $mediaType + ]; + + $mediaData = ['id' => $mediaId]; + + if ($caption && in_array($mediaType, ['image', 'video', 'document'])) { + $mediaData['caption'] = $caption; + } + + if ($filename && $mediaType === 'document') { + $mediaData['filename'] = $filename; + } + + $data[$mediaType] = $mediaData; + + return $this->sendMessage($data); } /** diff --git a/test_media_api_debug.php b/test_media_api_debug.php new file mode 100644 index 0000000..f8b0ead --- /dev/null +++ b/test_media_api_debug.php @@ -0,0 +1,57 @@ + '573168950803', + 'media_url' => 'http://localhost/whatsapp/uploads/test.jpg', + 'media_type' => 'image', + 'caption' => 'Test de imagen', + 'filename' => 'test.jpg' +]; + +echo "=== TEST API MULTIMEDIA ===\n\n"; +echo "URL: $apiUrl\n"; +echo "Datos enviados:\n"; +print_r($data); +echo "\n"; + +// Hacer petición +$ch = curl_init($apiUrl); +curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); +curl_setopt($ch, CURLOPT_POST, true); +curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($data)); +curl_setopt($ch, CURLOPT_HTTPHEADER, [ + 'Content-Type: application/json' +]); + +$response = curl_exec($ch); +$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE); +$contentType = curl_getinfo($ch, CURLINFO_CONTENT_TYPE); +curl_close($ch); + +echo "=== RESPUESTA ===\n"; +echo "HTTP Code: $httpCode\n"; +echo "Content-Type: $contentType\n"; +echo "\nRespuesta RAW:\n"; +echo $response; +echo "\n\n"; + +// Intentar decodificar JSON +echo "=== DECODIFICACIÓN JSON ===\n"; +$decoded = json_decode($response, true); +if ($decoded === null) { + echo "ERROR: No se pudo decodificar JSON\n"; + echo "JSON Error: " . json_last_error_msg() . "\n"; + echo "\nPrimeros 500 caracteres de la respuesta:\n"; + echo substr($response, 0, 500) . "\n"; +} else { + echo "JSON decodificado correctamente:\n"; + print_r($decoded); +} +?> diff --git a/whatsapp_tester.zip b/whatsapp_tester.zip deleted file mode 100644 index 41d5756..0000000 Binary files a/whatsapp_tester.zip and /dev/null differ