diff --git a/api/get_webhook_log.php b/api/get_webhook_log.php new file mode 100644 index 0000000..a0c693c --- /dev/null +++ b/api/get_webhook_log.php @@ -0,0 +1,80 @@ + 'Método no permitido. Use GET.']); + exit; +} + +try { + $db = Database::getInstance(); + + $id = intval($_GET['id'] ?? 0); + if (!$id) { + http_response_code(400); + echo json_encode(['error' => 'ID de webhook requerido']); + exit; + } + + $row = $db->fetch( + 'SELECT id, request_body, response_body, status_code, ip_address, created_at FROM webhook_logs WHERE id = ? LIMIT 1', + [$id] + ); + + if (!$row) { + http_response_code(404); + echo json_encode(['error' => 'Registro no encontrado']); + exit; + } + + // Intentar parsear JSON bonito + $pretty = null; + $decoded = json_decode($row['request_body'], true); + if ($decoded !== null) { + $pretty = json_encode($decoded, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE); + } + + echo json_encode([ + 'success' => true, + 'data' => [ + 'id' => $row['id'], + 'request_body' => $row['request_body'], + 'request_body_pretty' => $pretty, + 'response_body' => $row['response_body'], + 'status_code' => $row['status_code'], + 'ip_address' => $row['ip_address'], + 'created_at' => $row['created_at'] + ] + ]); + +} catch (Exception $e) { + error_log("Error in get_webhook_log.php: " . $e->getMessage()); + http_response_code(500); + echo json_encode(['error' => 'Error interno del servidor']); +} diff --git a/api/get_webhook_logs.php b/api/get_webhook_logs.php new file mode 100644 index 0000000..2389012 --- /dev/null +++ b/api/get_webhook_logs.php @@ -0,0 +1,101 @@ + 'Método no permitido. Use GET.']); + exit; +} + +try { + $db = Database::getInstance(); + + $limit = max(1, min(200, intval($_GET['limit'] ?? 50))); + $page = max(1, intval($_GET['page'] ?? 1)); + $offset = ($page - 1) * $limit; + $sinceId = isset($_GET['since_id']) ? intval($_GET['since_id']) : null; + $search = trim($_GET['search'] ?? ''); + $filter = trim($_GET['filter'] ?? ''); // e.g., HAS_MESSAGES + + $where = []; + $params = []; + + if ($sinceId) { + $where[] = 'id > ?'; + $params[] = $sinceId; + } + + if ($search !== '') { + $where[] = '(request_body LIKE ?)'; + $params[] = "%{$search}%"; + } + + if ($filter === 'HAS_MESSAGES') { + $where[] = 'request_body LIKE ?'; + $params[] = '%"messages"%'; + } + + $whereClause = empty($where) ? '' : 'WHERE ' . implode(' AND ', $where); + + if ($sinceId) { + // Return newer logs only in ascending order for incremental updates + $rows = $db->fetchAll( + "SELECT id, SUBSTRING(request_body, 1, 1000) as snippet, status_code, ip_address, created_at FROM webhook_logs {$whereClause} ORDER BY id ASC LIMIT ?", + array_merge($params, [$limit]) + ); + echo json_encode(['success' => true, 'data' => $rows]); + exit; + } + + $rows = $db->fetchAll( + "SELECT id, SUBSTRING(request_body, 1, 500) as snippet, status_code, ip_address, created_at FROM webhook_logs {$whereClause} ORDER BY created_at DESC LIMIT ? OFFSET ?", + array_merge($params, [$limit, $offset]) + ); + + $total = $db->fetch( + "SELECT COUNT(*) as cnt FROM webhook_logs {$whereClause}", + $params + ); + $totalCount = intval($total['cnt'] ?? 0); + + echo json_encode([ + 'success' => true, + 'data' => $rows, + 'pagination' => [ + 'page' => $page, + 'limit' => $limit, + 'total' => $totalCount, + 'pages' => $totalCount > 0 ? ceil($totalCount / $limit) : 0 + ] + ]); + +} catch (Exception $e) { + error_log("Error in get_webhook_logs.php: " . $e->getMessage()); + http_response_code(500); + echo json_encode(['error' => 'Error interno del servidor']); +} diff --git a/api/webhook.php b/api/webhook.php index 4b8ac40..f0e29bf 100644 --- a/api/webhook.php +++ b/api/webhook.php @@ -4,7 +4,7 @@ * Fecha: 13 de noviembre de 2025 */ -require_once '../config/config.php'; +require_once __DIR__ . '/../config/config_enhanced.php'; // Headers para API header('Content-Type: application/json; charset=utf-8'); @@ -146,8 +146,12 @@ class WhatsAppWebhook { 'status' => 'received' ]); - // Procesar con bot - $this->botService->processMessage($user, $messageText, $messageType); + // Procesar con bot (protección contra excepciones externas) + try { + $this->botService->processMessage($user, $messageText, $messageType); + } catch (Exception $e) { + error_log("Bot processing failed: " . $e->getMessage()); + } } // Procesar estados de mensajes (entregado, leído, etc.) @@ -207,15 +211,50 @@ class WhatsAppWebhook { ]); } } + + /** + * Procesar un payload (array) recibido manualmente (replay) + * Útil para reproducir entradas desde la UI o scripts. + */ + public function processPayload(array $data) { + error_log("processPayload START"); + // Registrar como recibido (reproducción) + $this->logWebhook(json_encode($data), json_encode(['status' => 'replayed']), 200); + + if (empty($data) || !isset($data['entry'])) { + error_log("processPayload: empty or missing entry"); + return false; + } + + foreach ($data['entry'] as $entry) { + if (isset($entry['changes'])) { + foreach ($entry['changes'] as $change) { + error_log("processPayload: processing change field=" . ($change['field'] ?? '')); + if (($change['field'] ?? '') === 'messages' && isset($change['value'])) { + try { + $this->processMessages($change['value']); + } catch (Exception $e) { + error_log("processPayload: processMessages failed: " . $e->getMessage()); + } + } + } + } + } + + error_log("processPayload END"); + return true; + } } -// Procesar la solicitud -try { - $webhook = new WhatsAppWebhook(); - $webhook->handleRequest(); -} catch (Exception $e) { - error_log("Fatal error in webhook: " . $e->getMessage()); - http_response_code(500); - echo json_encode(['error' => 'Error fatal del servidor']); +// Procesar la solicitud (solo si no estamos en CLI) +if (php_sapi_name() !== 'cli') { + try { + $webhook = new WhatsAppWebhook(); + $webhook->handleRequest(); + } catch (Exception $e) { + error_log("Fatal error in webhook: " . $e->getMessage()); + http_response_code(500); + echo json_encode(['error' => 'Error fatal del servidor']); + } } ?> \ No newline at end of file diff --git a/api/webhook_replay.php b/api/webhook_replay.php new file mode 100644 index 0000000..c47460b --- /dev/null +++ b/api/webhook_replay.php @@ -0,0 +1,61 @@ + 'Método no permitido. Use POST.']); + exit; +} + +try { + error_log('webhook_replay START'); + $raw = file_get_contents('php://input'); + error_log('webhook_replay raw length: ' . strlen($raw)); + $data = json_decode($raw, true); + if (!$data) { + error_log('webhook_replay: invalid json'); + http_response_code(400); + echo json_encode(['error' => 'JSON inválido o vacío']); + exit; + } + + $webhook = new WhatsAppWebhook(); + $ok = $webhook->processPayload($data); + + if ($ok) { + echo json_encode(['success' => true, 'message' => 'Payload reproducido correctamente']); + } else { + http_response_code(400); + echo json_encode(['error' => 'El payload no contenía entradas válidas']); + } + +} catch (Exception $e) { + error_log('Error in webhook_replay.php: ' . $e->getMessage()); + http_response_code(500); + echo json_encode(['error' => 'Error interno del servidor']); +} diff --git a/chat_window.php b/chat_window.php index 54305d3..6d028eb 100644 --- a/chat_window.php +++ b/chat_window.php @@ -650,17 +650,39 @@ if (empty($user_id)) { // Cargar plantillas async function loadTemplates() { try { - const response = await whatsappManager.apiCall('check_templates.php'); - const templateSelect = document.getElementById('templateSelect'); - - if (response && response.templates) { - templateSelect.innerHTML = ''; - response.templates.forEach(template => { - templateSelect.innerHTML += ``; - }); + const response = await whatsappManager.apiCall('check_templates.php'); + const templateSelect = document.getElementById('templateSelect'); + + if (response && response.templates) { + templateSelect.innerHTML = ''; + + response.templates.forEach(template => { + // Esperamos que la API devuelva "language_code" (fallback a en_US) + const lang = template.language_code || template.language || 'en_US'; + const display = template.display_name || template.name; + const option = document.createElement('option'); + option.value = template.name; + option.textContent = `${display} (${lang})`; + option.dataset.language = lang; + templateSelect.appendChild(option); + }); + + // Valor por defecto global para la plantilla seleccionada + window.language = 'en_US'; + + // Actualizar el language global cuando cambie la plantilla seleccionada + templateSelect.addEventListener('change', function() { + const sel = this.selectedOptions[0]; + window.language = sel && sel.dataset && sel.dataset.language ? sel.dataset.language : 'en_US'; + }); + + // Si hay una plantilla seleccionada por defecto, establecer language acorde + if (templateSelect.selectedOptions.length && templateSelect.selectedOptions[0].dataset.language) { + window.language = templateSelect.selectedOptions[0].dataset.language; } + } } catch (error) { - console.error('Error cargando plantillas:', error); + console.error('Error cargando plantillas:', error); } } @@ -690,7 +712,7 @@ if (empty($user_id)) { return; } - await sendTemplateMessage(template, message); + await sendTemplateMessage(template,language, message); } else { await sendTextMessage(message); } @@ -739,7 +761,7 @@ if (empty($user_id)) { } // Enviar mensaje de plantilla - async function sendTemplateMessage(template, parameters) { + async function sendTemplateMessage(template,language, parameters) { try { showTyping(); diff --git a/database/schema.sql b/database/schema.sql index aa26cdd..a81f6b3 100644 --- a/database/schema.sql +++ b/database/schema.sql @@ -26,7 +26,7 @@ CREATE TABLE conversations ( message_type ENUM('text', 'image', 'audio', 'video', 'document', 'template') DEFAULT 'text', content TEXT, media_url VARCHAR(500), - status ENUM('sent', 'delivered', 'read', 'failed') DEFAULT 'sent', + status ENUM('sent', 'delivered', 'read', 'failed', 'received') DEFAULT 'sent', created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE, INDEX idx_user (user_id), diff --git a/index.php b/index.php index df3fb5b..75878ec 100644 --- a/index.php +++ b/index.php @@ -196,9 +196,9 @@ try { - + @@ -581,10 +581,9 @@ try {
- + +
@@ -689,7 +688,7 @@ try { // Si el mensaje es un string simple apiMessage = data.api.message; } - + html += `
Conectividad API
@@ -774,7 +773,7 @@ try {
- +
📋 Opciones del Menú