up
This commit is contained in:
@@ -0,0 +1,19 @@
|
|||||||
|
<?php
|
||||||
|
require_once '../config/config.php';
|
||||||
|
requireAuthentication();
|
||||||
|
header('Content-Type: application/json; charset=utf-8');
|
||||||
|
|
||||||
|
try {
|
||||||
|
$db = Database::getInstance();
|
||||||
|
$limit = isset($_GET['limit']) ? intval($_GET['limit']) : 20;
|
||||||
|
|
||||||
|
$rows = $db->fetchAll(
|
||||||
|
"SELECT id, user_id, type, message, data, is_read, created_at FROM notifications WHERE is_read = 0 ORDER BY created_at DESC LIMIT ?",
|
||||||
|
[$limit]
|
||||||
|
);
|
||||||
|
|
||||||
|
echo json_encode(['success' => true, 'data' => $rows]);
|
||||||
|
} catch (Exception $e) {
|
||||||
|
http_response_code(500);
|
||||||
|
echo json_encode(['success' => false, 'error' => $e->getMessage()]);
|
||||||
|
}
|
||||||
@@ -0,0 +1,22 @@
|
|||||||
|
<?php
|
||||||
|
require_once '../config/config.php';
|
||||||
|
requireAuthentication();
|
||||||
|
header('Content-Type: application/json; charset=utf-8');
|
||||||
|
|
||||||
|
try {
|
||||||
|
$input = json_decode(file_get_contents('php://input'), true) ?: $_POST;
|
||||||
|
$id = isset($input['id']) ? intval($input['id']) : 0;
|
||||||
|
if (!$id) {
|
||||||
|
http_response_code(400);
|
||||||
|
echo json_encode(['success' => false, 'error' => 'id required']);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
$db = Database::getInstance();
|
||||||
|
$db->update('notifications', ['is_read' => 1], 'id = :id', ['id' => $id]);
|
||||||
|
|
||||||
|
echo json_encode(['success' => true]);
|
||||||
|
} catch (Exception $e) {
|
||||||
|
http_response_code(500);
|
||||||
|
echo json_encode(['success' => false, 'error' => $e->getMessage()]);
|
||||||
|
}
|
||||||
@@ -185,6 +185,20 @@ class WhatsAppWebhook {
|
|||||||
|
|
||||||
$this->saveMessage($saveData);
|
$this->saveMessage($saveData);
|
||||||
|
|
||||||
|
// Crear notificación para UI (nuevo mensaje entrante)
|
||||||
|
try {
|
||||||
|
$this->db->insert('notifications', [
|
||||||
|
'user_id' => $user['id'],
|
||||||
|
'type' => 'incoming_message',
|
||||||
|
'message' => substr($messageText, 0, 250),
|
||||||
|
'data' => json_encode(['message_id' => $messageId]),
|
||||||
|
'is_read' => 0,
|
||||||
|
'created_at' => date('Y-m-d H:i:s')
|
||||||
|
]);
|
||||||
|
} catch (Exception $e) {
|
||||||
|
error_log('Failed to create notification: ' . $e->getMessage());
|
||||||
|
}
|
||||||
|
|
||||||
// Procesar con bot (protección contra excepciones externas)
|
// Procesar con bot (protección contra excepciones externas)
|
||||||
try {
|
try {
|
||||||
$this->botService->processMessage($user, $messageText, $messageType);
|
$this->botService->processMessage($user, $messageText, $messageType);
|
||||||
|
|||||||
@@ -539,6 +539,100 @@
|
|||||||
this.loadConversations();
|
this.loadConversations();
|
||||||
this.setupEventListeners();
|
this.setupEventListeners();
|
||||||
this.setupAutoRefresh();
|
this.setupAutoRefresh();
|
||||||
|
this.setupNotificationPolling();
|
||||||
|
}
|
||||||
|
|
||||||
|
setupNotificationPolling() {
|
||||||
|
// Poll every 7 seconds for unread notifications
|
||||||
|
this.loadNotifications();
|
||||||
|
setInterval(() => this.loadNotifications(), 7000);
|
||||||
|
}
|
||||||
|
|
||||||
|
async loadNotifications() {
|
||||||
|
try {
|
||||||
|
const resp = await fetch('api/get_notifications.php');
|
||||||
|
const json = await resp.json();
|
||||||
|
if (json && json.success && Array.isArray(json.data)) {
|
||||||
|
json.data.forEach(n => this.showNotificationToast(n));
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
console.error('Error loading notifications', e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async showNotificationToast(notification) {
|
||||||
|
// Create toast element
|
||||||
|
const containerId = 'notification-toasts';
|
||||||
|
let container = document.getElementById(containerId);
|
||||||
|
if (!container) {
|
||||||
|
container = document.createElement('div');
|
||||||
|
container.id = containerId;
|
||||||
|
container.style.position = 'fixed';
|
||||||
|
container.style.top = '12px';
|
||||||
|
container.style.right = '12px';
|
||||||
|
container.style.zIndex = 9999;
|
||||||
|
document.body.appendChild(container);
|
||||||
|
}
|
||||||
|
|
||||||
|
const toast = document.createElement('div');
|
||||||
|
toast.className = 'notification-toast';
|
||||||
|
toast.style.background = '#fff';
|
||||||
|
toast.style.padding = '10px 14px';
|
||||||
|
toast.style.border = '1px solid #ddd';
|
||||||
|
toast.style.boxShadow = '0 2px 6px rgba(0,0,0,0.12)';
|
||||||
|
toast.style.marginTop = '8px';
|
||||||
|
toast.style.borderRadius = '6px';
|
||||||
|
toast.style.minWidth = '220px';
|
||||||
|
|
||||||
|
const msg = document.createElement('div');
|
||||||
|
msg.textContent = notification.message || 'Nuevo evento';
|
||||||
|
toast.appendChild(msg);
|
||||||
|
|
||||||
|
const actions = document.createElement('div');
|
||||||
|
actions.style.marginTop = '8px';
|
||||||
|
actions.style.display = 'flex';
|
||||||
|
actions.style.gap = '8px';
|
||||||
|
|
||||||
|
const openBtn = document.createElement('button');
|
||||||
|
openBtn.className = 'btn btn-sm btn-primary';
|
||||||
|
openBtn.textContent = 'Abrir';
|
||||||
|
openBtn.onclick = async () => {
|
||||||
|
// Mark notification read, then open conversation
|
||||||
|
await fetch('api/mark_notification_read.php', {method:'POST', headers:{'Content-Type':'application/json'}, body: JSON.stringify({id: notification.id})});
|
||||||
|
// Parse data to get user_id
|
||||||
|
let data = {};
|
||||||
|
try { data = notification.data ? JSON.parse(notification.data) : {}; } catch(e) {}
|
||||||
|
const userId = data.user_id || notification.user_id;
|
||||||
|
if (userId) {
|
||||||
|
// find conversation item and open it
|
||||||
|
const conv = this.conversations.find(c => c.user_id == userId);
|
||||||
|
if (conv) {
|
||||||
|
this.openConversation(conv.user_id, conv.name, conv.phone_number);
|
||||||
|
} else {
|
||||||
|
// fallback: open by fetching user info
|
||||||
|
this.openConversation(userId, 'Usuario', '');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// remove toast
|
||||||
|
toast.remove();
|
||||||
|
};
|
||||||
|
|
||||||
|
const dismiss = document.createElement('button');
|
||||||
|
dismiss.className = 'btn btn-sm btn-secondary';
|
||||||
|
dismiss.textContent = 'Descartar';
|
||||||
|
dismiss.onclick = async () => {
|
||||||
|
await fetch('api/mark_notification_read.php', {method:'POST', headers:{'Content-Type':'application/json'}, body: JSON.stringify({id: notification.id})});
|
||||||
|
toast.remove();
|
||||||
|
};
|
||||||
|
|
||||||
|
actions.appendChild(openBtn);
|
||||||
|
actions.appendChild(dismiss);
|
||||||
|
toast.appendChild(actions);
|
||||||
|
|
||||||
|
container.appendChild(toast);
|
||||||
|
|
||||||
|
// Auto remove after 18s
|
||||||
|
setTimeout(() => { if (toast.parentNode) toast.remove(); }, 18000);
|
||||||
}
|
}
|
||||||
|
|
||||||
setupEventListeners() {
|
setupEventListeners() {
|
||||||
|
|||||||
@@ -0,0 +1,25 @@
|
|||||||
|
<?php
|
||||||
|
require_once __DIR__ . '/../config/config.php';
|
||||||
|
$db = Database::getInstance();
|
||||||
|
$col = $db->fetch("SHOW TABLES LIKE 'notifications'");
|
||||||
|
if ($col) {
|
||||||
|
echo "Table notifications already exists\n";
|
||||||
|
exit(0);
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
$db->query("CREATE TABLE notifications (
|
||||||
|
id INT AUTO_INCREMENT PRIMARY KEY,
|
||||||
|
user_id INT NULL,
|
||||||
|
type VARCHAR(64) NOT NULL,
|
||||||
|
message TEXT,
|
||||||
|
data JSON NULL,
|
||||||
|
is_read TINYINT(1) DEFAULT 0,
|
||||||
|
created_at DATETIME DEFAULT NOW(),
|
||||||
|
INDEX idx_user_id (user_id),
|
||||||
|
INDEX idx_is_read (is_read)
|
||||||
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;");
|
||||||
|
echo "Created notifications table\n";
|
||||||
|
} catch (Exception $e) {
|
||||||
|
echo "Failed to create notifications table: " . $e->getMessage() . "\n";
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
@@ -0,0 +1,35 @@
|
|||||||
|
<?php
|
||||||
|
require_once __DIR__ . '/../config/config.php';
|
||||||
|
require_once __DIR__ . '/../api/webhook.php';
|
||||||
|
|
||||||
|
// Simular payload de mensaje entrante para que webhook cree notificación
|
||||||
|
$payload = [
|
||||||
|
'object' => 'whatsapp_business_account',
|
||||||
|
'entry' => [
|
||||||
|
[
|
||||||
|
'changes' => [
|
||||||
|
[
|
||||||
|
'field' => 'messages',
|
||||||
|
'value' => [
|
||||||
|
'messages' => [
|
||||||
|
[
|
||||||
|
'from' => '573019999900',
|
||||||
|
'id' => 'NTFTEST1',
|
||||||
|
'timestamp' => time(),
|
||||||
|
'type' => 'text',
|
||||||
|
'text' => ['body' => 'Notificacion test']
|
||||||
|
]
|
||||||
|
]
|
||||||
|
]
|
||||||
|
]
|
||||||
|
]
|
||||||
|
]
|
||||||
|
]
|
||||||
|
];
|
||||||
|
|
||||||
|
$w = new WhatsAppWebhook();
|
||||||
|
$w->processPayload($payload);
|
||||||
|
|
||||||
|
$db = Database::getInstance();
|
||||||
|
$rows = $db->fetchAll('SELECT * FROM notifications ORDER BY id DESC LIMIT 5');
|
||||||
|
foreach ($rows as $r) echo json_encode($r) . "\n";
|
||||||
Reference in New Issue
Block a user