diff --git a/add_hello_world.php b/add_hello_world.php new file mode 100644 index 0000000..b3b7a56 --- /dev/null +++ b/add_hello_world.php @@ -0,0 +1,20 @@ +query( + "INSERT INTO message_templates (name, template_name, language_code, status) VALUES (?, ?, ?, ?)", + ['Hello World Template', 'hello_world', 'en_US', 'approved'] +); + +echo "✅ Plantilla agregada exitosamente\n"; + +// Verificar +$stmt = $db->query("SELECT template_name, language_code, status FROM message_templates"); +$templates = $stmt->fetchAll(); + +foreach ($templates as $template) { + echo "• {$template['template_name']} ({$template['language_code']}) - {$template['status']}\n"; +} +?> \ No newline at end of file diff --git a/add_working_template.php b/add_working_template.php new file mode 100644 index 0000000..7f16605 --- /dev/null +++ b/add_working_template.php @@ -0,0 +1,42 @@ +query( + "INSERT INTO message_templates (template_name, language_code, status, body_text, created_at, updated_at) VALUES (?, ?, ?, ?, NOW(), NOW())", + ['hello_world', 'en_US', 'approved', 'Hello World! This is a test message from WhatsApp Business API.'] + ); + + echo "✅ Agregada plantilla 'hello_world' (en_US) - FUNCIONA CORRECTAMENTE\n\n"; + + // Mostrar plantillas finales + echo "📋 Plantillas en la base de datos:\n"; + $stmt = $db->query("SELECT id, template_name, language_code, status, body_text FROM message_templates ORDER BY template_name"); + $templates = $stmt->fetchAll(); + + foreach ($templates as $template) { + echo "• ID: {$template['id']}\n"; + echo " Nombre: {$template['template_name']}\n"; + echo " Idioma: {$template['language_code']}\n"; + echo " Estado: {$template['status']}\n"; + echo " Texto: {$template['body_text']}\n"; + echo " -----\n"; + } + + echo "\n🎉 Ahora puedes enviar mensajes usando la plantilla 'hello_world'\n"; + +} catch (Exception $e) { + echo "❌ Error: " . $e->getMessage() . "\n"; +} +?> \ No newline at end of file diff --git a/api/get_conversation_detail.php b/api/get_conversation_detail.php new file mode 100644 index 0000000..b2ce04f --- /dev/null +++ b/api/get_conversation_detail.php @@ -0,0 +1,96 @@ + 'user_id es requerido']); + exit; + } + + $db = Database::getInstance(); + + // Obtener información del usuario + $user = $db->fetch( + "SELECT id, phone_number, name FROM users WHERE id = ?", + [$user_id] + ); + + if (!$user) { + http_response_code(404); + echo json_encode(['error' => 'Usuario no encontrado']); + exit; + } + + // Obtener todos los mensajes de la conversación + $messages = $db->fetchAll( + "SELECT + id, + message_id, + direction, + message_type, + content, + status, + created_at + FROM conversations + WHERE user_id = ? + ORDER BY created_at ASC", + [$user_id] + ); + + // Formatear mensajes + $messages = array_map(function($msg) { + return [ + 'id' => intval($msg['id']), + 'message_id' => $msg['message_id'] ?? '', + 'direction' => $msg['direction'], + 'message_type' => $msg['message_type'], + 'content' => $msg['content'] ?? '', + 'status' => $msg['status'] ?? 'sent', + 'created_at' => $msg['created_at'], + 'time' => date('H:i', strtotime($msg['created_at'])), + 'date' => date('d/m/Y', strtotime($msg['created_at'])) + ]; + }, $messages); + + // Marcar mensajes como leídos + $db->query( + "UPDATE conversations SET status = 'read' WHERE user_id = ? AND direction = 'incoming' AND status != 'read'", + [$user_id] + ); + + echo json_encode([ + 'success' => true, + 'user' => [ + 'id' => intval($user['id']), + 'phone_number' => $user['phone_number'], + 'name' => $user['name'] ?? $user['phone_number'] + ], + 'messages' => $messages, + 'total_messages' => count($messages) + ]); + +} catch (Exception $e) { + error_log("Error in get_conversation_detail.php: " . $e->getMessage()); + http_response_code(500); + echo json_encode(['error' => 'Error interno del servidor: ' . $e->getMessage()]); +} +?> \ No newline at end of file diff --git a/api/get_conversation_list.php b/api/get_conversation_list.php new file mode 100644 index 0000000..32c9201 --- /dev/null +++ b/api/get_conversation_list.php @@ -0,0 +1,95 @@ +fetchAll( + "SELECT + u.id as user_id, + u.phone_number, + COALESCE(u.name, u.phone_number) as name, + c.content as last_message, + c.direction as last_direction, + c.message_type as last_message_type, + c.created_at as last_message_time, + c.status as last_message_status, + COUNT(*) as total_messages, + SUM(CASE WHEN c.direction = 'incoming' AND c.status = 'received' THEN 1 ELSE 0 END) as unread_count + FROM users u + LEFT JOIN conversations c ON u.id = c.user_id + WHERE c.id IN ( + SELECT MAX(id) + FROM conversations + GROUP BY user_id + ) + GROUP BY u.id, u.phone_number, u.name, c.content, c.direction, c.message_type, c.created_at, c.status + ORDER BY c.created_at DESC + LIMIT 50" + ); + + // Si no hay datos, retornar array vacío + if (empty($conversations)) { + $conversations = []; + } + + // Formatear datos para el frontend + $conversations = array_map(function($conv) { + return [ + 'user_id' => intval($conv['user_id']), + 'phone_number' => $conv['phone_number'], + 'name' => $conv['name'], + 'last_message' => $conv['last_message'] ?? '', + 'last_direction' => $conv['last_direction'] ?? 'incoming', + 'last_message_type' => $conv['last_message_type'] ?? 'text', + 'last_message_time' => $conv['last_message_time'], + 'last_message_status' => $conv['last_message_status'] ?? 'sent', + 'total_messages' => intval($conv['total_messages']), + 'unread_count' => intval($conv['unread_count']), + 'time_ago' => timeAgo($conv['last_message_time']) + ]; + }, $conversations); + + echo json_encode([ + 'success' => true, + 'conversations' => $conversations, + 'total' => count($conversations) + ]); + +} catch (Exception $e) { + error_log("Error in get_conversation_list.php: " . $e->getMessage()); + http_response_code(500); + echo json_encode(['error' => 'Error interno del servidor: ' . $e->getMessage()]); +} + +/** + * Calcular tiempo transcurrido + */ +function timeAgo($datetime) { + $time = time() - strtotime($datetime); + + if ($time < 60) return 'Ahora'; + if ($time < 3600) return floor($time/60) . 'm'; + if ($time < 86400) return floor($time/3600) . 'h'; + if ($time < 2592000) return floor($time/86400) . 'd'; + if ($time < 31536000) return floor($time/2592000) . ' mes'; + return floor($time/31536000) . ' año'; +} +?> \ No newline at end of file diff --git a/api/send_message.php b/api/send_message.php index 0239aed..ccf1385 100644 --- a/api/send_message.php +++ b/api/send_message.php @@ -18,21 +18,38 @@ if (!$debugMode) { header('Content-Type: application/json; charset=utf-8'); header('Access-Control-Allow-Origin: *'); -header('Access-Control-Allow-Methods: POST'); +header('Access-Control-Allow-Methods: POST, OPTIONS'); header('Access-Control-Allow-Headers: Content-Type'); +// Manejar preflight OPTIONS request +if ($_SERVER['REQUEST_METHOD'] === 'OPTIONS') { + http_response_code(200); + exit; +} + if ($_SERVER['REQUEST_METHOD'] !== 'POST') { http_response_code(405); - echo json_encode(['error' => 'Método no permitido']); + echo json_encode(['error' => 'Método no permitido. Use POST.', 'method_received' => $_SERVER['REQUEST_METHOD']]); exit; } try { - $input = json_decode(file_get_contents('php://input'), true); + $rawInput = file_get_contents('php://input'); + $input = json_decode($rawInput, true); + + // Debug logging + if ($debugMode) { + error_log("=== SEND MESSAGE DEBUG ==="); + error_log("Method: " . $_SERVER['REQUEST_METHOD']); + error_log("Content-Type: " . ($_SERVER['CONTENT_TYPE'] ?? 'not set')); + error_log("Raw input: " . $rawInput); + error_log("Parsed JSON: " . json_encode($input)); + error_log("========================"); + } if (!$input) { http_response_code(400); - echo json_encode(['error' => 'JSON inválido']); + echo json_encode(['error' => 'JSON inválido o vacío', 'raw_input' => $rawInput]); exit; } @@ -241,6 +258,49 @@ try { error_log("Response: " . json_encode($response)); error_log("======================================"); + // Guardar mensaje en la base de datos + try { + // Buscar o crear usuario por teléfono + $stmt = $db->query( + "SELECT id FROM users WHERE phone_number = ? LIMIT 1", + [$recipient] + ); + $user = $stmt->fetch(); + + $userId = null; + if ($user) { + $userId = $user['id']; + } else { + // Crear nuevo usuario + $stmt = $db->query( + "INSERT INTO users (phone_number, name, created_at) VALUES (?, ?, NOW())", + [$recipient, $recipient, date('Y-m-d H:i:s')] + ); + $userId = $db->lastInsertId(); + } + + if ($userId) { + // Preparar datos del mensaje + $messageContent = ''; + $messageType = $type; + + if ($type === 'text') { + $messageContent = $input['message']; + } elseif ($type === 'template') { + $templateName = $input['template'] ?? $input['template_name'] ?? ''; + $messageContent = "Plantilla: {$templateName}"; + } + + // Guardar en la tabla conversations + $stmt = $db->query( + "INSERT INTO conversations (user_id, content, direction, message_type, status, message_id, created_at) VALUES (?, ?, 'outgoing', ?, 'sent', ?, NOW())", + [$userId, $messageContent, $messageType, $response['messages'][0]['id'] ?? null] + ); + } + } catch (Exception $e) { + error_log("Error guardando mensaje en base de datos: " . $e->getMessage()); + } + echo json_encode([ 'success' => true, 'message' => 'Mensaje enviado correctamente', diff --git a/assets/css/styles.css b/assets/css/styles.css index 4f78c33..ee725a3 100644 --- a/assets/css/styles.css +++ b/assets/css/styles.css @@ -782,4 +782,304 @@ body { box-shadow: none; border: 1px solid #ddd; } +} + +/* ================================= + CHAT STYLES + ================================= */ + +.chat-container { + background: linear-gradient(135deg, #e5ddd5 0%, #d4c5b9 100%); +} + +.conversation-row:hover { + background-color: #f8f9fa !important; + cursor: pointer; +} + +.message-bubble { + position: relative; + word-wrap: break-word; + border: none; +} + +.message-bubble.outgoing { + margin-left: auto; +} + +.message-bubble.incoming { + margin-right: auto; +} + +.message-time { + text-align: right; + margin-top: 4px; + font-size: 0.7rem; +} + +.avatar-placeholder { + font-weight: bold; + font-size: 16px; + flex-shrink: 0; +} + +/* Modal de chat personalizado */ +#chatModal .modal-content { + border: none; + border-radius: 12px; + box-shadow: 0 10px 30px rgba(0, 0, 0, 0.2); +} + +#chatModal .modal-header { + border-radius: 12px 12px 0 0; + border-bottom: 1px solid rgba(255,255,255,0.2); +} + +#chatModal .modal-body { + padding: 0; +} + +.message-bubble.bg-success { + background: linear-gradient(135deg, #25d366, #20b358) !important; +} + +.message-bubble.bg-white { + background: #ffffff !important; + border: 1px solid #e9ecef; +} + +/* Mejorar responsividad de conversaciones */ +@media (max-width: 768px) { + .conversation-row .btn-group .btn { + padding: 0.25rem 0.5rem; + font-size: 0.75rem; + } + + .message-bubble { + max-width: 85% !important; + } + + #chatModal .modal-dialog { + max-width: 95%; + margin: 1rem auto; + } +} + +/* Animaciones para mensajes */ +.message-bubble { + animation: messageAppear 0.3s ease-out; +} + +@keyframes messageAppear { + from { + opacity: 0; + transform: translateY(10px); + } + to { + opacity: 1; + transform: translateY(0); + } +} + +/* Estilos mejorados para lista de conversaciones */ +.conversation-card { + border: none; + box-shadow: 0 8px 25px rgba(0, 0, 0, 0.1); + border-radius: 20px; + overflow: hidden; +} + +.conversation-card .card-header { + border: none; + background: linear-gradient(135deg, #25d366 0%, #128c7e 100%); + padding: 1.5rem; +} + +.conversation-list { + max-height: 600px; + overflow-y: auto; +} + +.conversation-item { + display: flex; + align-items: center; + padding: 15px 20px; + border-bottom: 1px solid #f0f0f0; + cursor: pointer; + transition: all 0.2s ease; + position: relative; + background: white; +} + +.conversation-item:hover { + background: #f8f9fa; + transform: translateX(5px); +} + +.conversation-item:last-child { + border-bottom: none; +} + +.conversation-avatar { + width: 55px; + height: 55px; + background: linear-gradient(135deg, #25d366, #128c7e); + border-radius: 50%; + display: flex; + align-items: center; + justify-content: center; + color: white; + font-weight: bold; + font-size: 1.4rem; + margin-right: 15px; + box-shadow: 0 4px 12px rgba(37, 211, 102, 0.3); + position: relative; +} + +.conversation-avatar.online::after { + content: ''; + position: absolute; + bottom: 2px; + right: 2px; + width: 14px; + height: 14px; + background: #4ade80; + border: 2px solid white; + border-radius: 50%; + box-shadow: 0 2px 4px rgba(0, 0, 0, 0.2); +} + +.conversation-details { + flex: 1; + min-width: 0; +} + +.conversation-header { + display: flex; + justify-content: space-between; + align-items: center; + margin-bottom: 5px; +} + +.conversation-name { + font-weight: 600; + font-size: 1.1rem; + color: #1f2937; + display: flex; + align-items: center; + gap: 8px; +} + +.conversation-time { + font-size: 0.8rem; + color: #9ca3af; + white-space: nowrap; +} + +.conversation-preview { + display: flex; + align-items: center; + gap: 8px; +} + +.conversation-last-message { + color: #6b7280; + font-size: 0.9rem; + display: -webkit-box; + -webkit-line-clamp: 2; + -webkit-box-orient: vertical; + overflow: hidden; + line-height: 1.3; +} + +.conversation-meta { + display: flex; + flex-direction: column; + align-items: flex-end; + gap: 5px; +} + +.conversation-unread { + background: #25d366; + color: white; + border-radius: 50%; + padding: 4px 8px; + font-size: 0.75rem; + font-weight: bold; + min-width: 20px; + height: 20px; + display: flex; + align-items: center; + justify-content: center; +} + +.conversation-status-icon { + font-size: 0.8rem; +} + +.conversation-status-icon.outgoing { + color: #25d366; +} + +.conversation-status-icon.incoming { + color: #3b82f6; +} + +.conversation-actions { + display: flex; + gap: 8px; + opacity: 0; + transition: opacity 0.2s ease; +} + +.conversation-item:hover .conversation-actions { + opacity: 1; +} + +.conversation-actions .btn { + padding: 6px 10px; + font-size: 0.8rem; + border-radius: 8px; + border: 1px solid #e5e7eb; + background: white; + color: #6b7280; + transition: all 0.2s ease; +} + +.conversation-actions .btn:hover { + background: #25d366; + color: white; + border-color: #25d366; + transform: translateY(-1px); +} + +.conversation-empty { + text-align: center; + padding: 60px 20px; + color: #9ca3af; +} + +.conversation-empty .empty-icon { + font-size: 4rem; + color: #d1d5db; + margin-bottom: 20px; +} + +.conversation-search { + border-radius: 25px; + border: none; + background: rgba(255, 255, 255, 0.2); + color: white; + padding: 8px 20px; +} + +.conversation-search::placeholder { + color: rgba(255, 255, 255, 0.7); +} + +.conversation-search:focus { + background: rgba(255, 255, 255, 0.3); + color: white; + box-shadow: none; + border: 1px solid rgba(255, 255, 255, 0.5); } \ No newline at end of file diff --git a/assets/js/app_simple.js b/assets/js/app_simple.js index 07a073c..e2db864 100644 --- a/assets/js/app_simple.js +++ b/assets/js/app_simple.js @@ -409,11 +409,11 @@ class SimpleWhatsAppManager { this.log('Cargando conversaciones'); try { - const response = await this.apiCall('get_conversations.php'); - console.log('Respuesta get_conversations:', response); // Debug + const response = await this.apiCall('get_conversation_list.php'); + console.log('Respuesta get_conversation_list:', response); // Debug - if (response && response.success && Array.isArray(response.data)) { - this.updateConversationsList(response.data); + if (response && response.success && Array.isArray(response.conversations)) { + this.updateConversationsList(response.conversations); } else if (response && Array.isArray(response)) { // Retrocompatibilidad por si la API devuelve directamente el array this.updateConversationsList(response); @@ -428,46 +428,201 @@ class SimpleWhatsAppManager { } updateConversationsList(conversations) { - const container = document.getElementById('conversations-table'); + const container = document.getElementById('conversations-container'); if (!container) return; if (conversations.length === 0) { - container.innerHTML = '
Los chats aparecerán aquí cuando recibas o envíes mensajes
+Intenta con otros términos de búsqueda
+| Usuario | -Último Mensaje | -Tipo | -Estado | -Fecha | -Acciones | -
|---|
"; +echo "Token: " . (strlen($config['token']) > 0 ? substr($config['token'], 0, 20) . "... (length: " . strlen($config['token']) . ")" : "EMPTY") . "\n"; +echo "Phone Number ID: " . ($config['phone_number_id'] ?: "EMPTY") . "\n"; +echo "API URL: " . ($config['api_url'] ?: "EMPTY") . "\n"; +echo ""; + +// Construir URL como lo haría WhatsAppService +$apiUrl = rtrim($config['api_url'], '/') . '/' . $config['phone_number_id'] . '/messages'; +echo "
" . $apiUrl . ""; + +// Comparar con tu curl que funciona +$expectedUrl = "https://graph.facebook.com/v22.0/938064202726485/messages"; +echo "
" . $expectedUrl . ""; + +echo "
" . ($apiUrl === $expectedUrl ? "✅ SÍ" : "❌ NO") . ""; + +// Verificar token +$expectedTokenStart = "EAAcGjPRqunIBQVT6b86O0wWWGLGJ1PANmolIRXRBmEcpqa3P85VlUz0Rrv2c5deXky3gOKZCBw7ZCpdicly4c0G8qFMOH1DfOcSzmV49FlPId3AGPlHEXpqlsPFIE8DF18trW2vglQrFiCHXZCEBSUBQfp1jEASD1NrKKKVHiGVZARrwoxP0J931u0KIr2ZAZCug4fFpnyRZBls9LEgJuomXd4ZCnJtBZA4GvP2zaplmZA52mJiG5H4YiKKWby5GnQq9BgAVaq6sLZBnYI26aR0FgZDZD"; + +echo "
" . ($tokenMatch ? "✅ SÍ (primeros 50 caracteres)" : "❌ NO") . ""; + +if (!$tokenMatch) { + echo "
"; + echo "Configurado: " . substr($config['token'], 0, 50) . "...\n"; + echo "Esperado: " . substr($expectedTokenStart, 0, 50) . "...\n"; + echo ""; +} + +// Test directo de configuración +echo "
";
+foreach(['whatsapp_token', 'whatsapp_phone_number_id', 'whatsapp_api_url'] as $key) {
+ $value = getConfigFromDB($key, 'NO_ENCONTRADO');
+ echo "$key: " . ($value === 'NO_ENCONTRADO' ? "❌ NO_ENCONTRADO" : "✅ " . substr($value, 0, 30) . "...") . "\n";
+}
+echo "";
+?>
\ No newline at end of file