diff --git a/api/get_user_messages.php b/api/get_user_messages.php index 43d8ecb..83f41b2 100644 --- a/api/get_user_messages.php +++ b/api/get_user_messages.php @@ -31,11 +31,15 @@ try { id, user_id, COALESCE(content, message_text) as content, + message_id, + c.media_url, + u.phone_number as user_phone, direction, message_type, status, created_at - FROM conversations + FROM conversations c + LEFT JOIN users u ON c.user_id = u.id WHERE user_id = :user_id UNION ALL SELECT @@ -76,6 +80,9 @@ try { 'id' => intval($msg['id']), 'user_id' => intval($msg['user_id']), 'content' => $msg['content'] ?? '', + 'message_id' => $msg['message_id'] ?? null, + 'media_url' => $msg['media_url'] ?? null, + 'user_phone' => $msg['user_phone'] ?? null, 'direction' => $msg['direction'] ?? 'incoming', 'message_type' => $msg['message_type'] ?? 'text', 'status' => $msg['status'] ?? 'sent', diff --git a/api/send_message.php b/api/send_message.php index 5464b6d..c66b1c7 100644 --- a/api/send_message.php +++ b/api/send_message.php @@ -155,10 +155,16 @@ try { error_log("=== DEBUG WHATSAPP TEXT MESSAGE ==="); error_log("Recipient: " . $recipient); error_log("Message: " . $input['message']); - error_log("Method: sendTextMessage()"); + error_log("Method: sendTextMessage()/sendTextReply()"); error_log("===================================="); - - $response = $whatsappService->sendTextMessage($recipient, $input['message']); + + // Si viene reply_to, usar reply con contexto + if (!empty($input['reply_to'])) { + $replyTo = $input['reply_to']; + $response = $whatsappService->sendTextReply($recipient, $replyTo, $input['message']); + } else { + $response = $whatsappService->sendTextMessage($recipient, $input['message']); + } break; case 'template': diff --git a/api/send_reaction.php b/api/send_reaction.php new file mode 100644 index 0000000..8e6c51e --- /dev/null +++ b/api/send_reaction.php @@ -0,0 +1,32 @@ + false, 'error' => 'Parámetros faltantes: to, message_id, emoji']); + exit; +} + +try { + $wa = new WhatsAppService(); + $result = $wa->sendReaction($to, $message_id, $emoji, $dry); + echo json_encode(['success' => true, 'dry_run' => $dry, 'result' => $result]); +} catch (Throwable $t) { + http_response_code(500); + echo json_encode(['success' => false, 'error' => $t->getMessage()]); +} diff --git a/api/send_reply.php b/api/send_reply.php new file mode 100644 index 0000000..c614795 --- /dev/null +++ b/api/send_reply.php @@ -0,0 +1,33 @@ + false, 'error' => 'Parámetros faltantes: to, message_id, text']); + exit; +} + +try { + $wa = new WhatsAppService(); + $result = $wa->sendTextReply($to, $message_id, $text, $preview, $dry); + echo json_encode(['success' => true, 'dry_run' => $dry, 'result' => $result]); +} catch (Throwable $t) { + http_response_code(500); + echo json_encode(['success' => false, 'error' => $t->getMessage()]); +} diff --git a/api/webhook.php b/api/webhook.php index c639a6d..2108c8c 100644 --- a/api/webhook.php +++ b/api/webhook.php @@ -128,7 +128,13 @@ class WhatsAppWebhook { $messageType = 'text'; $mediaUrl = null; - if (isset($message['text'])) { + if (isset($message['reaction'])) { + // Reacción (emoji) a un mensaje anterior + $messageType = 'reaction'; + $emoji = $message['reaction']['emoji'] ?? ''; + $reactionTo = $message['reaction']['message_id'] ?? ''; + $messageText = json_encode(['emoji' => $emoji, 'message_id' => $reactionTo]); + } elseif (isset($message['text'])) { $messageText = $message['text']['body']; $messageType = 'text'; } elseif (isset($message['image'])) { diff --git a/assets/js/app.js b/assets/js/app.js index 09c5114..40265e9 100644 --- a/assets/js/app.js +++ b/assets/js/app.js @@ -1028,6 +1028,38 @@ class WhatsAppBotManager { this.showTab('messages'); } + replyToMessage(messageId, userPhone) { + // Poner en el input del chat y almacenar reply_to en data attribute + document.getElementById('message-input').focus(); + document.getElementById('message-input').dataset.replyTo = messageId; + this.showSuccess('Preparado para responder al mensaje ' + messageId); + } + + async reactToMessage(messageId, userPhone) { + const emoji = prompt('Escribe el emoji de reacción (ej: ❤️, 👍):'); + if (!emoji) return; + + try { + const resp = await fetch('api/send_reaction.php', { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ to: userPhone, message_id: messageId, emoji: emoji }) + }); + const data = await resp.json(); + if (data.success) { + this.showSuccess('Reacción enviada'); + // Refrescar mensajes + await this.loadMessages(this.currentUserId, false); + } else { + this.showError('Error enviando reacción: ' + (data.error || 'desconocido')); + } + } catch (err) { + console.error('reactToMessage error', err); + this.showError('Error comunicándose con el servidor'); + } + } + + editUser(userId) { // Implementar modal de edición de usuario console.log('Editar usuario:', userId); diff --git a/config/config_enhanced.php b/config/config_enhanced.php index a4b8812..c7f8e7f 100644 --- a/config/config_enhanced.php +++ b/config/config_enhanced.php @@ -44,50 +44,50 @@ function env($key, $default = null) { } // Configuración de la base de datos -define('DB_HOST', env('DB_HOST', 'localhost')); -define('DB_PORT', env('DB_PORT', '3306')); -define('DB_NAME', env('DB_NAME', 'whatsapp_bot')); -define('DB_USER', env('DB_USER', 'whatsapp_user')); -define('DB_PASS', env('DB_PASS', '')); // Se generará automáticamente en la instalación -define('DB_CHARSET', env('DB_CHARSET', 'utf8mb4')); +if (!defined('DB_HOST')) define('DB_HOST', env('DB_HOST', 'localhost')); +if (!defined('DB_PORT')) define('DB_PORT', env('DB_PORT', '3306')); +if (!defined('DB_NAME')) define('DB_NAME', env('DB_NAME', 'whatsapp_bot')); +if (!defined('DB_USER')) define('DB_USER', env('DB_USER', 'whatsapp_user')); +if (!defined('DB_PASS')) define('DB_PASS', env('DB_PASS', '')); // Se generará automáticamente en la instalación +if (!defined('DB_CHARSET')) define('DB_CHARSET', env('DB_CHARSET', 'utf8mb4')); // WhatsApp Business API -define('WHATSAPP_TOKEN', env('WHATSAPP_TOKEN', 'TU_TOKEN_DE_WHATSAPP_AQUI')); -define('WHATSAPP_PHONE_NUMBER_ID', env('WHATSAPP_PHONE_NUMBER_ID', 'TU_PHONE_ID_AQUI')); -define('WHATSAPP_API_URL', env('WHATSAPP_API_URL', 'https://graph.facebook.com/v22.0/')); -define('WEBHOOK_VERIFY_TOKEN', env('WEBHOOK_VERIFY_TOKEN', 'mi_token_secreto_123')); +if (!defined('WHATSAPP_TOKEN')) define('WHATSAPP_TOKEN', env('WHATSAPP_TOKEN', 'TU_TOKEN_DE_WHATSAPP_AQUI')); +if (!defined('WHATSAPP_PHONE_NUMBER_ID')) define('WHATSAPP_PHONE_NUMBER_ID', env('WHATSAPP_PHONE_NUMBER_ID', 'TU_PHONE_ID_AQUI')); +if (!defined('WHATSAPP_API_URL')) define('WHATSAPP_API_URL', env('WHATSAPP_API_URL', 'https://graph.facebook.com/v22.0/')); +if (!defined('WEBHOOK_VERIFY_TOKEN')) define('WEBHOOK_VERIFY_TOKEN', env('WEBHOOK_VERIFY_TOKEN', 'mi_token_secreto_123')); // Auto-approve templates: Si está activado, las plantillas creadas localmente se marcarán como 'approved' automáticamente -define('AUTO_APPROVE_TEMPLATES', filter_var(env('AUTO_APPROVE_TEMPLATES', 'false'), FILTER_VALIDATE_BOOLEAN)); +if (!defined('AUTO_APPROVE_TEMPLATES')) define('AUTO_APPROVE_TEMPLATES', filter_var(env('AUTO_APPROVE_TEMPLATES', 'false'), FILTER_VALIDATE_BOOLEAN)); // Configuración general -define('APP_NAME', env('APP_NAME', 'WhatsApp Bot System')); -define('APP_VERSION', env('APP_VERSION', '1.0.0')); -define('APP_URL', env('APP_URL', 'https://tudominio.com')); // Se auto-detectará -define('TIMEZONE', env('TIMEZONE', 'America/Bogota')); +if (!defined('APP_NAME')) define('APP_NAME', env('APP_NAME', 'WhatsApp Bot System')); +if (!defined('APP_VERSION')) define('APP_VERSION', env('APP_VERSION', '1.0.0')); +if (!defined('APP_URL')) define('APP_URL', env('APP_URL', 'https://tudominio.com')); +if (!defined('TIMEZONE')) define('TIMEZONE', env('TIMEZONE', 'America/Bogota')); // Información del desarrollador -define('DEVELOPER_NAME', 'U-Site.app'); -define('DEVELOPER_URL', 'https://u-site.app'); -define('DEVELOPER_EMAIL', 'support@u-site.app'); -define('DEVELOPER_SUPPORT', 'https://u-site.app/support'); +if (!defined('DEVELOPER_NAME')) define('DEVELOPER_NAME', 'U-Site.app'); +if (!defined('DEVELOPER_URL')) define('DEVELOPER_URL', 'https://u-site.app'); +if (!defined('DEVELOPER_EMAIL')) define('DEVELOPER_EMAIL', 'support@u-site.app'); +if (!defined('DEVELOPER_SUPPORT')) define('DEVELOPER_SUPPORT', 'https://u-site.app/support'); // Configuración de seguridad -define('INSTALLATION_LOCK', '.installation_completed'); // Archivo de bloqueo -define('ADMIN_USERNAME', env('ADMIN_USERNAME', 'admin')); // Usuario administrador por defecto -define('ADMIN_PASSWORD', env('ADMIN_PASSWORD', '')); // Se configurará en instalación -define('SESSION_TIMEOUT', (int)env('SESSION_TIMEOUT', 1800)); // 30 minutos -define('MAX_LOGIN_ATTEMPTS', (int)env('MAX_LOGIN_ATTEMPTS', 3)); // Intentos máximos de login -define('LOGIN_LOCKOUT_TIME', (int)env('LOGIN_LOCKOUT_TIME', 900)); // 15 minutos de bloqueo tras fallos +if (!defined('INSTALLATION_LOCK')) define('INSTALLATION_LOCK', '.installation_completed'); +if (!defined('ADMIN_USERNAME')) define('ADMIN_USERNAME', env('ADMIN_USERNAME', 'admin')); +if (!defined('ADMIN_PASSWORD')) define('ADMIN_PASSWORD', env('ADMIN_PASSWORD', '')); +if (!defined('SESSION_TIMEOUT')) define('SESSION_TIMEOUT', (int)env('SESSION_TIMEOUT', 1800)); +if (!defined('MAX_LOGIN_ATTEMPTS')) define('MAX_LOGIN_ATTEMPTS', (int)env('MAX_LOGIN_ATTEMPTS', 3)); +if (!defined('LOGIN_LOCKOUT_TIME')) define('LOGIN_LOCKOUT_TIME', (int)env('LOGIN_LOCKOUT_TIME', 900)); // Configuración de logs -define('ENABLE_LOGGING', filter_var(env('ENABLE_LOGGING', 'true'), FILTER_VALIDATE_BOOLEAN)); -define('LOG_LEVEL', env('LOG_LEVEL', 'INFO')); // DEBUG, INFO, WARNING, ERROR -define('LOG_FILE', env('LOG_FILE', 'logs/system.log')); +if (!defined('ENABLE_LOGGING')) define('ENABLE_LOGGING', filter_var(env('ENABLE_LOGGING', 'true'), FILTER_VALIDATE_BOOLEAN)); +if (!defined('LOG_LEVEL')) define('LOG_LEVEL', env('LOG_LEVEL', 'INFO')); // DEBUG, INFO, WARNING, ERROR +if (!defined('LOG_FILE')) define('LOG_FILE', env('LOG_FILE', 'logs/system.log')); // Configuración de archivos -define('UPLOAD_MAX_SIZE', (int)env('UPLOAD_MAX_SIZE', 10485760)); // 10MB -define('ALLOWED_FILE_TYPES', ['jpg', 'jpeg', 'png', 'gif', 'pdf', 'doc', 'docx', 'mp4', 'mp3', 'wav']); +if (!defined('UPLOAD_MAX_SIZE')) define('UPLOAD_MAX_SIZE', (int)env('UPLOAD_MAX_SIZE', 10485760)); // 10MB +if (!defined('ALLOWED_FILE_TYPES')) define('ALLOWED_FILE_TYPES', ['jpg', 'jpeg', 'png', 'gif', 'pdf', 'doc', 'docx', 'mp4', 'mp3', 'wav']); // Configurar zona horaria date_default_timezone_set(TIMEZONE); diff --git a/conversations.php b/conversations.php index 7112de0..2c6f25c 100644 --- a/conversations.php +++ b/conversations.php @@ -748,6 +748,10 @@