up
This commit is contained in:
@@ -0,0 +1,52 @@
|
||||
<?php
|
||||
/**
|
||||
* API - Cambiar contraseña de usuario administrador
|
||||
* Fecha: 3 de febrero de 2026
|
||||
*/
|
||||
|
||||
require_once '../config/config.php';
|
||||
|
||||
// Verificar autenticación
|
||||
requireAuthentication();
|
||||
|
||||
header('Content-Type: application/json; charset=utf-8');
|
||||
header('Cache-Control: no-cache, no-store, must-revalidate');
|
||||
|
||||
try {
|
||||
$db = Database::getInstance();
|
||||
|
||||
// Leer datos del request
|
||||
$input = json_decode(file_get_contents('php://input'), true);
|
||||
$userId = $input['user_id'] ?? null;
|
||||
$newPassword = $input['new_password'] ?? '';
|
||||
|
||||
if (!$userId) {
|
||||
throw new Exception('ID de usuario requerido');
|
||||
}
|
||||
|
||||
if (strlen($newPassword) < 6) {
|
||||
throw new Exception('La contraseña debe tener al menos 6 caracteres');
|
||||
}
|
||||
|
||||
// Encriptar contraseña
|
||||
$hashedPassword = password_hash($newPassword, PASSWORD_DEFAULT);
|
||||
|
||||
// Actualizar contraseña
|
||||
$db->execute(
|
||||
"UPDATE admin_users SET password_hash = ? WHERE id = ?",
|
||||
[$hashedPassword, $userId]
|
||||
);
|
||||
|
||||
echo json_encode([
|
||||
'success' => true,
|
||||
'message' => 'Contraseña actualizada correctamente'
|
||||
]);
|
||||
|
||||
} catch (Exception $e) {
|
||||
error_log('Error changing admin password: ' . $e->getMessage());
|
||||
http_response_code(500);
|
||||
echo json_encode([
|
||||
'success' => false,
|
||||
'error' => $e->getMessage()
|
||||
]);
|
||||
}
|
||||
@@ -0,0 +1,65 @@
|
||||
<?php
|
||||
/**
|
||||
* API - Crear nuevo usuario administrador
|
||||
* Fecha: 3 de febrero de 2026
|
||||
*/
|
||||
|
||||
require_once '../config/config.php';
|
||||
|
||||
// Verificar autenticación
|
||||
requireAuthentication();
|
||||
|
||||
header('Content-Type: application/json; charset=utf-8');
|
||||
header('Cache-Control: no-cache, no-store, must-revalidate');
|
||||
|
||||
try {
|
||||
$db = Database::getInstance();
|
||||
|
||||
// Leer datos del request
|
||||
$input = json_decode(file_get_contents('php://input'), true);
|
||||
$username = trim($input['username'] ?? '');
|
||||
$fullName = trim($input['full_name'] ?? '');
|
||||
$email = trim($input['email'] ?? '');
|
||||
$password = $input['password'] ?? '';
|
||||
|
||||
if (empty($username)) {
|
||||
throw new Exception('El nombre de usuario es requerido');
|
||||
}
|
||||
|
||||
if (strlen($password) < 6) {
|
||||
throw new Exception('La contraseña debe tener al menos 6 caracteres');
|
||||
}
|
||||
|
||||
// Verificar si el username ya existe
|
||||
$existing = $db->fetch(
|
||||
"SELECT id FROM admin_users WHERE username = ?",
|
||||
[$username]
|
||||
);
|
||||
|
||||
if ($existing) {
|
||||
throw new Exception('El nombre de usuario ya está en uso');
|
||||
}
|
||||
|
||||
// Encriptar contraseña
|
||||
$hashedPassword = password_hash($password, PASSWORD_DEFAULT);
|
||||
|
||||
// Insertar usuario
|
||||
$db->execute(
|
||||
"INSERT INTO admin_users (username, password_hash, full_name, email, is_active, created_at)
|
||||
VALUES (?, ?, ?, ?, 1, NOW())",
|
||||
[$username, $hashedPassword, $fullName, $email]
|
||||
);
|
||||
|
||||
echo json_encode([
|
||||
'success' => true,
|
||||
'message' => 'Usuario creado correctamente'
|
||||
]);
|
||||
|
||||
} catch (Exception $e) {
|
||||
error_log('Error creating admin user: ' . $e->getMessage());
|
||||
http_response_code(500);
|
||||
echo json_encode([
|
||||
'success' => false,
|
||||
'error' => $e->getMessage()
|
||||
]);
|
||||
}
|
||||
@@ -0,0 +1,193 @@
|
||||
<?php
|
||||
/**
|
||||
* API - Eliminar mensaje
|
||||
* Permite eliminar un mensaje de la conversación
|
||||
* Fecha: 28 de enero de 2026
|
||||
*/
|
||||
|
||||
// Habilitar display de errores para debug
|
||||
ini_set('display_errors', 1);
|
||||
error_reporting(E_ALL);
|
||||
|
||||
require_once '../config/config.php';
|
||||
|
||||
// Log para debug
|
||||
error_log("delete_message.php: Iniciando, session_id=" . session_id() . ", admin_logged_in=" . (isset($_SESSION['admin_logged_in']) ? $_SESSION['admin_logged_in'] : 'no definido'));
|
||||
|
||||
requireAuthentication();
|
||||
|
||||
header('Content-Type: application/json; charset=utf-8');
|
||||
header('Access-Control-Allow-Origin: *');
|
||||
header('Access-Control-Allow-Methods: POST, DELETE, 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' && $_SERVER['REQUEST_METHOD'] !== 'DELETE') {
|
||||
http_response_code(405);
|
||||
echo json_encode(['success' => false, 'error' => 'Método no permitido. Use POST o DELETE.']);
|
||||
exit;
|
||||
}
|
||||
|
||||
try {
|
||||
$input = json_decode(file_get_contents('php://input'), true);
|
||||
|
||||
// También soportar query params para DELETE
|
||||
if (!$input && $_SERVER['REQUEST_METHOD'] === 'DELETE') {
|
||||
$input = [
|
||||
'message_id' => $_GET['message_id'] ?? $_GET['id'] ?? null,
|
||||
'delete_for_everyone' => $_GET['delete_for_everyone'] ?? false
|
||||
];
|
||||
}
|
||||
|
||||
if (!$input) {
|
||||
http_response_code(400);
|
||||
echo json_encode(['success' => false, 'error' => 'JSON inválido']);
|
||||
exit;
|
||||
}
|
||||
|
||||
$messageId = $input['message_id'] ?? $input['id'] ?? null;
|
||||
$deleteForEveryone = $input['delete_for_everyone'] ?? false;
|
||||
$softDelete = $input['soft_delete'] ?? true; // Por defecto, soft delete
|
||||
|
||||
if (!$messageId) {
|
||||
http_response_code(400);
|
||||
echo json_encode(['success' => false, 'error' => 'message_id es requerido']);
|
||||
exit;
|
||||
}
|
||||
|
||||
$db = Database::getInstance();
|
||||
|
||||
// Buscar el mensaje
|
||||
$sql = "SELECT id, user_id, direction, message_type, content, message_id as wa_message_id,
|
||||
local_file, local_thumb, media_url
|
||||
FROM conversations
|
||||
WHERE id = :mid1 OR message_id = :mid2
|
||||
LIMIT 1";
|
||||
$message = $db->fetch($sql, ['mid1' => $messageId, 'mid2' => $messageId]);
|
||||
|
||||
if (!$message) {
|
||||
http_response_code(404);
|
||||
echo json_encode(['success' => false, 'error' => 'Mensaje no encontrado']);
|
||||
exit;
|
||||
}
|
||||
|
||||
// Solo permitir eliminar mensajes salientes (outgoing) a menos que sea admin
|
||||
$isAdmin = isset($_SESSION['admin_logged_in']) && $_SESSION['admin_logged_in'];
|
||||
if ($message['direction'] !== 'outgoing' && !$isAdmin) {
|
||||
http_response_code(403);
|
||||
echo json_encode(['success' => false, 'error' => 'Solo puedes eliminar tus propios mensajes']);
|
||||
exit;
|
||||
}
|
||||
|
||||
$deletedMessageId = $message['id'];
|
||||
$userId = $message['user_id'];
|
||||
$waMessageId = $message['wa_message_id'];
|
||||
|
||||
if ($softDelete) {
|
||||
// Soft delete: marcar como eliminado pero mantener registro
|
||||
// Verificar si existen las columnas deleted_at y original_content
|
||||
$hasDeletedAt = false;
|
||||
$hasOriginalContent = false;
|
||||
try {
|
||||
$columns = $db->fetchAll("SHOW COLUMNS FROM conversations WHERE Field IN ('deleted_at', 'original_content')");
|
||||
foreach ($columns as $col) {
|
||||
if ($col['Field'] === 'deleted_at') $hasDeletedAt = true;
|
||||
if ($col['Field'] === 'original_content') $hasOriginalContent = true;
|
||||
}
|
||||
} catch (Exception $e) {
|
||||
// Ignorar
|
||||
}
|
||||
|
||||
// Construir la query según las columnas disponibles
|
||||
// IMPORTANTE: Guardar original_content ANTES de modificar content
|
||||
if ($hasDeletedAt && $hasOriginalContent) {
|
||||
// Primero guardamos el contenido original
|
||||
$db->execute("UPDATE conversations SET original_content = content WHERE id = :id AND original_content IS NULL", ['id' => $deletedMessageId]);
|
||||
// Luego marcamos como eliminado
|
||||
$updateSql = "UPDATE conversations SET content = '[Mensaje eliminado]', deleted_at = NOW() WHERE id = :id";
|
||||
} elseif ($hasDeletedAt) {
|
||||
$updateSql = "UPDATE conversations SET content = '[Mensaje eliminado]', deleted_at = NOW() WHERE id = :id";
|
||||
} else {
|
||||
$updateSql = "UPDATE conversations SET content = '[Mensaje eliminado]' WHERE id = :id";
|
||||
}
|
||||
|
||||
$db->execute($updateSql, ['id' => $deletedMessageId]);
|
||||
|
||||
error_log("Message soft-deleted: ID={$deletedMessageId}, User={$userId}");
|
||||
} else {
|
||||
// Hard delete: eliminar completamente
|
||||
|
||||
// Primero, eliminar archivos multimedia asociados si existen
|
||||
if (!empty($message['local_file'])) {
|
||||
$filePath = dirname(__DIR__) . '/' . $message['local_file'];
|
||||
if (file_exists($filePath)) {
|
||||
@unlink($filePath);
|
||||
error_log("Deleted media file: {$filePath}");
|
||||
}
|
||||
}
|
||||
|
||||
if (!empty($message['local_thumb'])) {
|
||||
$thumbPath = dirname(__DIR__) . '/' . $message['local_thumb'];
|
||||
if (file_exists($thumbPath)) {
|
||||
@unlink($thumbPath);
|
||||
error_log("Deleted thumbnail: {$thumbPath}");
|
||||
}
|
||||
}
|
||||
|
||||
// Eliminar el mensaje de la base de datos
|
||||
$deleteSql = "DELETE FROM conversations WHERE id = :id";
|
||||
$db->execute($deleteSql, ['id' => $deletedMessageId]);
|
||||
|
||||
error_log("Message hard-deleted: ID={$deletedMessageId}, User={$userId}");
|
||||
}
|
||||
|
||||
// Intentar notificar por SSE si está disponible
|
||||
try {
|
||||
$ssePayload = [
|
||||
'type' => 'message_deleted',
|
||||
'user_id' => $userId,
|
||||
'message_id' => $deletedMessageId,
|
||||
'wa_message_id' => $waMessageId,
|
||||
'soft_delete' => $softDelete,
|
||||
'deleted_at' => date('Y-m-d H:i:s')
|
||||
];
|
||||
|
||||
// Llamar al endpoint push_event interno
|
||||
$pushUrl = 'http://127.0.0.1' . dirname($_SERVER['SCRIPT_NAME']) . '/push_event.php';
|
||||
$ch = curl_init($pushUrl);
|
||||
curl_setopt($ch, CURLOPT_POST, true);
|
||||
curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode([
|
||||
'event_type' => 'message_deleted',
|
||||
'data' => $ssePayload,
|
||||
'target_user_id' => 'all'
|
||||
]));
|
||||
curl_setopt($ch, CURLOPT_HTTPHEADER, ['Content-Type: application/json']);
|
||||
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
|
||||
curl_setopt($ch, CURLOPT_TIMEOUT, 2);
|
||||
curl_exec($ch);
|
||||
curl_close($ch);
|
||||
} catch (Exception $sseErr) {
|
||||
error_log("delete_message: SSE notification failed: " . $sseErr->getMessage());
|
||||
}
|
||||
|
||||
echo json_encode([
|
||||
'success' => true,
|
||||
'message' => $softDelete ? 'Mensaje eliminado' : 'Mensaje eliminado permanentemente',
|
||||
'data' => [
|
||||
'id' => $deletedMessageId,
|
||||
'message_id' => $waMessageId,
|
||||
'soft_delete' => $softDelete,
|
||||
'deleted_at' => date('Y-m-d H:i:s')
|
||||
]
|
||||
]);
|
||||
|
||||
} catch (Exception $e) {
|
||||
error_log('delete_message.php error: ' . $e->getMessage() . ' | Trace: ' . $e->getTraceAsString());
|
||||
http_response_code(500);
|
||||
echo json_encode(['success' => false, 'error' => 'Error interno del servidor', 'debug' => $e->getMessage()]);
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
<?php
|
||||
/**
|
||||
* API - Eliminar mensaje programado
|
||||
*/
|
||||
|
||||
require_once '../config/config.php';
|
||||
|
||||
header('Content-Type: application/json; charset=utf-8');
|
||||
header('Access-Control-Allow-Origin: *');
|
||||
header('Access-Control-Allow-Methods: DELETE, POST');
|
||||
header('Access-Control-Allow-Headers: Content-Type');
|
||||
|
||||
requireAuthentication();
|
||||
|
||||
try {
|
||||
$input = json_decode(file_get_contents('php://input'), true);
|
||||
|
||||
if (!$input) {
|
||||
// Intentar obtener de query params
|
||||
$id = isset($_GET['id']) ? intval($_GET['id']) : null;
|
||||
} else {
|
||||
$id = isset($input['id']) ? intval($input['id']) : null;
|
||||
}
|
||||
|
||||
if (!$id) {
|
||||
throw new Exception('ID de mensaje programado requerido');
|
||||
}
|
||||
|
||||
$db = Database::getInstance();
|
||||
|
||||
// Verificar que existe
|
||||
$existing = $db->fetch("SELECT * FROM scheduled_messages WHERE id = ?", [$id]);
|
||||
|
||||
if (!$existing) {
|
||||
throw new Exception('Mensaje programado no encontrado');
|
||||
}
|
||||
|
||||
// Eliminar
|
||||
$db->execute("DELETE FROM scheduled_messages WHERE id = ?", [$id]);
|
||||
|
||||
writeLog('INFO', "Mensaje programado eliminado: ID={$id}");
|
||||
|
||||
echo json_encode([
|
||||
'success' => true,
|
||||
'message' => 'Mensaje programado eliminado exitosamente'
|
||||
]);
|
||||
|
||||
} catch (Exception $e) {
|
||||
error_log("Error in delete_scheduled_message.php: " . $e->getMessage());
|
||||
http_response_code(500);
|
||||
echo json_encode([
|
||||
'success' => false,
|
||||
'error' => $e->getMessage()
|
||||
]);
|
||||
}
|
||||
@@ -0,0 +1,160 @@
|
||||
<?php
|
||||
/**
|
||||
* API - Editar mensaje
|
||||
* Permite editar el contenido de un mensaje enviado (solo mensajes propios/outgoing)
|
||||
* Fecha: 28 de enero de 2026
|
||||
*/
|
||||
|
||||
// Habilitar display de errores para debug
|
||||
ini_set('display_errors', 1);
|
||||
error_reporting(E_ALL);
|
||||
|
||||
require_once '../config/config.php';
|
||||
|
||||
// Log para debug
|
||||
error_log("edit_message.php: Iniciando, session_id=" . session_id() . ", admin_logged_in=" . (isset($_SESSION['admin_logged_in']) ? $_SESSION['admin_logged_in'] : 'no definido'));
|
||||
|
||||
requireAuthentication();
|
||||
|
||||
header('Content-Type: application/json; charset=utf-8');
|
||||
header('Access-Control-Allow-Origin: *');
|
||||
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(['success' => false, 'error' => 'Método no permitido. Use POST.']);
|
||||
exit;
|
||||
}
|
||||
|
||||
try {
|
||||
$input = json_decode(file_get_contents('php://input'), true);
|
||||
|
||||
if (!$input) {
|
||||
http_response_code(400);
|
||||
echo json_encode(['success' => false, 'error' => 'JSON inválido']);
|
||||
exit;
|
||||
}
|
||||
|
||||
$messageId = $input['message_id'] ?? $input['id'] ?? null;
|
||||
$newContent = $input['content'] ?? $input['new_content'] ?? null;
|
||||
|
||||
if (!$messageId) {
|
||||
http_response_code(400);
|
||||
echo json_encode(['success' => false, 'error' => 'message_id es requerido']);
|
||||
exit;
|
||||
}
|
||||
|
||||
if ($newContent === null || trim($newContent) === '') {
|
||||
http_response_code(400);
|
||||
echo json_encode(['success' => false, 'error' => 'El contenido no puede estar vacío']);
|
||||
exit;
|
||||
}
|
||||
|
||||
$db = Database::getInstance();
|
||||
|
||||
// Buscar el mensaje
|
||||
$sql = "SELECT id, user_id, direction, message_type, content, message_id as wa_message_id
|
||||
FROM conversations
|
||||
WHERE id = :mid1 OR message_id = :mid2
|
||||
LIMIT 1";
|
||||
$message = $db->fetch($sql, ['mid1' => $messageId, 'mid2' => $messageId]);
|
||||
|
||||
if (!$message) {
|
||||
http_response_code(404);
|
||||
echo json_encode(['success' => false, 'error' => 'Mensaje no encontrado']);
|
||||
exit;
|
||||
}
|
||||
|
||||
// Solo permitir editar mensajes salientes (outgoing)
|
||||
if ($message['direction'] !== 'outgoing') {
|
||||
http_response_code(403);
|
||||
echo json_encode(['success' => false, 'error' => 'Solo puedes editar tus propios mensajes']);
|
||||
exit;
|
||||
}
|
||||
|
||||
// Solo permitir editar mensajes de texto
|
||||
if ($message['message_type'] !== 'text' && $message['message_type'] !== null) {
|
||||
http_response_code(400);
|
||||
echo json_encode(['success' => false, 'error' => 'Solo se pueden editar mensajes de texto']);
|
||||
exit;
|
||||
}
|
||||
|
||||
// Guardar contenido original para historial (opcional)
|
||||
$originalContent = $message['content'];
|
||||
|
||||
// Verificar si existe la columna edited_at
|
||||
$hasEditedAt = false;
|
||||
try {
|
||||
$columns = $db->fetchAll("SHOW COLUMNS FROM conversations WHERE Field = 'edited_at'");
|
||||
$hasEditedAt = !empty($columns);
|
||||
} catch (Exception $e) {
|
||||
// Ignorar
|
||||
}
|
||||
|
||||
// Actualizar el mensaje en la base de datos
|
||||
if ($hasEditedAt) {
|
||||
$updateSql = "UPDATE conversations SET content = :content, edited_at = NOW() WHERE id = :id";
|
||||
} else {
|
||||
$updateSql = "UPDATE conversations SET content = :content WHERE id = :id";
|
||||
}
|
||||
|
||||
$db->execute($updateSql, [
|
||||
'content' => trim($newContent),
|
||||
'id' => $message['id']
|
||||
]);
|
||||
|
||||
// Registrar la edición en el log
|
||||
error_log("Message edited: ID={$message['id']}, User={$message['user_id']}, Original='{$originalContent}', New='{$newContent}'");
|
||||
|
||||
// Intentar notificar por SSE si está disponible
|
||||
try {
|
||||
$ssePayload = [
|
||||
'type' => 'message_edited',
|
||||
'user_id' => $message['user_id'],
|
||||
'message_id' => $message['id'],
|
||||
'wa_message_id' => $message['wa_message_id'],
|
||||
'new_content' => trim($newContent),
|
||||
'edited_at' => date('Y-m-d H:i:s')
|
||||
];
|
||||
|
||||
// Llamar al endpoint push_event interno
|
||||
$pushUrl = 'http://127.0.0.1' . dirname($_SERVER['SCRIPT_NAME']) . '/push_event.php';
|
||||
$ch = curl_init($pushUrl);
|
||||
curl_setopt($ch, CURLOPT_POST, true);
|
||||
curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode([
|
||||
'event_type' => 'message_edited',
|
||||
'data' => $ssePayload,
|
||||
'target_user_id' => 'all'
|
||||
]));
|
||||
curl_setopt($ch, CURLOPT_HTTPHEADER, ['Content-Type: application/json']);
|
||||
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
|
||||
curl_setopt($ch, CURLOPT_TIMEOUT, 2);
|
||||
curl_exec($ch);
|
||||
curl_close($ch);
|
||||
} catch (Exception $sseErr) {
|
||||
error_log("edit_message: SSE notification failed: " . $sseErr->getMessage());
|
||||
}
|
||||
|
||||
echo json_encode([
|
||||
'success' => true,
|
||||
'message' => 'Mensaje editado correctamente',
|
||||
'data' => [
|
||||
'id' => $message['id'],
|
||||
'message_id' => $message['wa_message_id'],
|
||||
'new_content' => trim($newContent),
|
||||
'edited_at' => date('Y-m-d H:i:s')
|
||||
]
|
||||
]);
|
||||
|
||||
} catch (Exception $e) {
|
||||
error_log('edit_message.php error: ' . $e->getMessage() . ' | Trace: ' . $e->getTraceAsString());
|
||||
http_response_code(500);
|
||||
echo json_encode(['success' => false, 'error' => 'Error interno del servidor', 'debug' => $e->getMessage()]);
|
||||
}
|
||||
@@ -25,6 +25,9 @@ try {
|
||||
$limit = max(1, min(200, $limit)); // máximo 200 por página
|
||||
$page = isset($_GET['page']) ? max(1, intval($_GET['page'])) : 1;
|
||||
$offset = ($page - 1) * $limit;
|
||||
|
||||
// Parámetro de búsqueda
|
||||
$search = isset($_GET['search']) ? trim($_GET['search']) : '';
|
||||
|
||||
// Obtener conversaciones agregadas por usuario: último mensaje + conteo de no leídos + avatar
|
||||
$filter = isset($_GET['filter']) ? strtolower(trim($_GET['filter'])) : 'all';
|
||||
@@ -47,7 +50,19 @@ try {
|
||||
lm.local_file AS last_local_file,
|
||||
lm.local_thumb AS last_local_thumb,
|
||||
lm.created_at AS last_time,
|
||||
IFNULL(SUM(CASE WHEN c.direction = 'incoming' AND (c.is_read IS NULL OR c.is_read = 0) THEN 1 ELSE 0 END), 0) AS unread_count
|
||||
IFNULL(SUM(CASE WHEN c.direction = 'incoming' AND (c.is_read IS NULL OR c.is_read = 0) THEN 1 ELSE 0 END), 0) AS unread_count";
|
||||
|
||||
// Si hay búsqueda, agregar campo para ordenar por relevancia
|
||||
if (!empty($search)) {
|
||||
$sql .= ",
|
||||
(CASE
|
||||
WHEN u.name LIKE ? THEN 3
|
||||
WHEN u.phone_number LIKE ? THEN 2
|
||||
ELSE 1
|
||||
END) AS relevance";
|
||||
}
|
||||
|
||||
$sql .= "
|
||||
FROM users u
|
||||
LEFT JOIN conversations c ON c.user_id = u.id
|
||||
LEFT JOIN (
|
||||
@@ -55,7 +70,23 @@ try {
|
||||
JOIN (
|
||||
SELECT user_id, MAX(created_at) AS last_time FROM conversations GROUP BY user_id
|
||||
) t2 ON t1.user_id = t2.user_id AND t1.created_at = t2.last_time
|
||||
) lm ON lm.user_id = u.id
|
||||
) lm ON lm.user_id = u.id";
|
||||
|
||||
// Agregar condición de búsqueda si existe
|
||||
if (!empty($search)) {
|
||||
$sql .= "
|
||||
WHERE (
|
||||
u.name LIKE ?
|
||||
OR u.phone_number LIKE ?
|
||||
OR EXISTS (
|
||||
SELECT 1 FROM conversations c2
|
||||
WHERE c2.user_id = u.id
|
||||
AND c2.content LIKE ?
|
||||
)
|
||||
)";
|
||||
}
|
||||
|
||||
$sql .= "
|
||||
GROUP BY u.id";
|
||||
|
||||
// Aplicar filtro 'unread' si se solicita
|
||||
@@ -63,15 +94,63 @@ try {
|
||||
$sql .= " HAVING IFNULL(SUM(CASE WHEN c.direction = 'incoming' AND (c.is_read IS NULL OR c.is_read = 0) THEN 1 ELSE 0 END), 0) > 0";
|
||||
}
|
||||
|
||||
$sql .= "\n ORDER BY lm.created_at DESC\n LIMIT %d OFFSET %d";
|
||||
// Ordenar por relevancia si hay búsqueda, sino por fecha
|
||||
if (!empty($search)) {
|
||||
$sql .= "\n ORDER BY relevance DESC, lm.created_at DESC";
|
||||
} else {
|
||||
$sql .= "\n ORDER BY lm.created_at DESC";
|
||||
}
|
||||
|
||||
$sql .= "\n LIMIT %d OFFSET %d";
|
||||
|
||||
$conversations = $db->fetchAll(sprintf($sql, $limit, $offset));
|
||||
// Preparar parámetros para la consulta
|
||||
$params = [];
|
||||
if (!empty($search)) {
|
||||
$searchParam = '%' . $search . '%';
|
||||
// Para el campo de relevancia
|
||||
$params[] = $searchParam; // name LIKE
|
||||
$params[] = $searchParam; // phone_number LIKE
|
||||
// Para el WHERE
|
||||
$params[] = $searchParam; // name LIKE
|
||||
$params[] = $searchParam; // phone_number LIKE
|
||||
$params[] = $searchParam; // content LIKE
|
||||
}
|
||||
|
||||
$finalSql = sprintf($sql, $limit, $offset);
|
||||
|
||||
if (!empty($params)) {
|
||||
$conversations = $db->fetchAll($finalSql, $params);
|
||||
} else {
|
||||
$conversations = $db->fetchAll($finalSql);
|
||||
}
|
||||
|
||||
// Conteo total de usuarios con al menos una conversación (útil para paginar)
|
||||
if ($filter === 'unread') {
|
||||
$totalRow = $db->fetch("SELECT COUNT(DISTINCT user_id) AS count FROM conversations c WHERE c.direction = 'incoming' AND (c.is_read IS NULL OR c.is_read = 0)");
|
||||
if (!empty($search)) {
|
||||
// Conteo con búsqueda
|
||||
$searchParam = '%' . $search . '%';
|
||||
$countSql = "SELECT COUNT(DISTINCT u.id) AS count
|
||||
FROM users u
|
||||
LEFT JOIN conversations c ON c.user_id = u.id
|
||||
WHERE (
|
||||
u.name LIKE ?
|
||||
OR u.phone_number LIKE ?
|
||||
OR EXISTS (
|
||||
SELECT 1 FROM conversations c2
|
||||
WHERE c2.user_id = u.id
|
||||
AND c2.content LIKE ?
|
||||
)
|
||||
)";
|
||||
if ($filter === 'unread') {
|
||||
$countSql .= " AND c.direction = 'incoming' AND (c.is_read IS NULL OR c.is_read = 0)";
|
||||
}
|
||||
$totalRow = $db->fetch($countSql, [$searchParam, $searchParam, $searchParam]);
|
||||
} else {
|
||||
$totalRow = $db->fetch("SELECT COUNT(DISTINCT user_id) AS count FROM conversations");
|
||||
// Conteo normal sin búsqueda
|
||||
if ($filter === 'unread') {
|
||||
$totalRow = $db->fetch("SELECT COUNT(DISTINCT user_id) AS count FROM conversations c WHERE c.direction = 'incoming' AND (c.is_read IS NULL OR c.is_read = 0)");
|
||||
} else {
|
||||
$totalRow = $db->fetch("SELECT COUNT(DISTINCT user_id) AS count FROM conversations");
|
||||
}
|
||||
}
|
||||
$total = isset($totalRow['count']) ? intval($totalRow['count']) : 0;
|
||||
|
||||
|
||||
@@ -0,0 +1,113 @@
|
||||
<?php
|
||||
/**
|
||||
* API - Obtener mensajes programados
|
||||
*/
|
||||
|
||||
require_once '../config/config.php';
|
||||
|
||||
header('Content-Type: application/json; charset=utf-8');
|
||||
header('Access-Control-Allow-Origin: *');
|
||||
header('Access-Control-Allow-Methods: GET');
|
||||
header('Access-Control-Allow-Headers: Content-Type');
|
||||
|
||||
requireAuthentication();
|
||||
|
||||
try {
|
||||
$db = Database::getInstance();
|
||||
|
||||
// Parámetros de filtro
|
||||
$userId = isset($_GET['user_id']) ? intval($_GET['user_id']) : null;
|
||||
$status = $_GET['status'] ?? null;
|
||||
$dateFrom = $_GET['date_from'] ?? null;
|
||||
$dateTo = $_GET['date_to'] ?? null;
|
||||
$limit = isset($_GET['limit']) ? intval($_GET['limit']) : 100;
|
||||
$offset = isset($_GET['offset']) ? intval($_GET['offset']) : 0;
|
||||
|
||||
// Construir query
|
||||
$sql = "SELECT * FROM scheduled_messages_view WHERE 1=1";
|
||||
$params = [];
|
||||
|
||||
if ($userId) {
|
||||
$sql .= " AND user_id = ?";
|
||||
$params[] = $userId;
|
||||
}
|
||||
|
||||
if ($status) {
|
||||
$sql .= " AND status = ?";
|
||||
$params[] = $status;
|
||||
}
|
||||
|
||||
if ($dateFrom) {
|
||||
$sql .= " AND scheduled_date >= ?";
|
||||
$params[] = $dateFrom;
|
||||
}
|
||||
|
||||
if ($dateTo) {
|
||||
$sql .= " AND scheduled_date <= ?";
|
||||
$params[] = $dateTo;
|
||||
}
|
||||
|
||||
// Ordenar por fecha más cercana primero
|
||||
$sql .= " ORDER BY scheduled_date ASC, scheduled_time ASC";
|
||||
|
||||
// Paginación
|
||||
$sql .= " LIMIT ? OFFSET ?";
|
||||
$params[] = $limit;
|
||||
$params[] = $offset;
|
||||
|
||||
$messages = $db->fetchAll($sql, $params);
|
||||
|
||||
error_log('📋 get_scheduled_messages.php - Encontrados: ' . count($messages) . ' mensajes');
|
||||
error_log('🔍 SQL ejecutado: ' . $sql);
|
||||
error_log('📊 Parámetros: ' . json_encode($params));
|
||||
|
||||
// Decodificar parámetros JSON
|
||||
foreach ($messages as &$message) {
|
||||
if ($message['template_parameters']) {
|
||||
$message['template_parameters'] = json_decode($message['template_parameters'], true);
|
||||
}
|
||||
}
|
||||
|
||||
// Contar total
|
||||
$countSql = "SELECT COUNT(*) as total FROM scheduled_messages WHERE 1=1";
|
||||
$countParams = [];
|
||||
|
||||
if ($userId) {
|
||||
$countSql .= " AND user_id = ?";
|
||||
$countParams[] = $userId;
|
||||
}
|
||||
|
||||
if ($status) {
|
||||
$countSql .= " AND status = ?";
|
||||
$countParams[] = $status;
|
||||
}
|
||||
|
||||
if ($dateFrom) {
|
||||
$countSql .= " AND scheduled_date >= ?";
|
||||
$countParams[] = $dateFrom;
|
||||
}
|
||||
|
||||
if ($dateTo) {
|
||||
$countSql .= " AND scheduled_date <= ?";
|
||||
$countParams[] = $dateTo;
|
||||
}
|
||||
|
||||
$countResult = $db->fetch($countSql, $countParams);
|
||||
$total = $countResult['total'] ?? 0;
|
||||
|
||||
echo json_encode([
|
||||
'success' => true,
|
||||
'data' => $messages,
|
||||
'total' => $total,
|
||||
'limit' => $limit,
|
||||
'offset' => $offset
|
||||
]);
|
||||
|
||||
} catch (Exception $e) {
|
||||
error_log("Error in get_scheduled_messages.php: " . $e->getMessage());
|
||||
http_response_code(500);
|
||||
echo json_encode([
|
||||
'success' => false,
|
||||
'error' => 'Error obteniendo mensajes programados'
|
||||
]);
|
||||
}
|
||||
@@ -0,0 +1,111 @@
|
||||
<?php
|
||||
/**
|
||||
* API - Obtener detalles completos de una plantilla con variables
|
||||
* Devuelve los parámetros que se deben llenar
|
||||
*/
|
||||
|
||||
require_once '../config/config.php';
|
||||
|
||||
header('Content-Type: application/json; charset=utf-8');
|
||||
header('Access-Control-Allow-Origin: *');
|
||||
header('Access-Control-Allow-Methods: GET');
|
||||
header('Access-Control-Allow-Headers: Content-Type');
|
||||
|
||||
requireAuthentication();
|
||||
|
||||
try {
|
||||
$templateId = isset($_GET['id']) ? intval($_GET['id']) : null;
|
||||
$templateName = isset($_GET['name']) ? trim($_GET['name']) : null;
|
||||
$language = isset($_GET['language']) ? trim($_GET['language']) : 'es';
|
||||
|
||||
if (!$templateId && !$templateName) {
|
||||
throw new Exception('Se requiere ID o nombre de plantilla');
|
||||
}
|
||||
|
||||
$db = Database::getInstance();
|
||||
|
||||
if ($templateId) {
|
||||
$template = $db->fetch(
|
||||
"SELECT * FROM message_templates WHERE id = ?",
|
||||
[$templateId]
|
||||
);
|
||||
} else {
|
||||
$template = $db->fetch(
|
||||
"SELECT * FROM message_templates WHERE template_name = ? AND language_code = ?",
|
||||
[$templateName, $language]
|
||||
);
|
||||
}
|
||||
|
||||
if (!$template) {
|
||||
throw new Exception('Plantilla no encontrada');
|
||||
}
|
||||
|
||||
// Extraer variables del body_text o de example_parameters
|
||||
$bodyText = $template['body_text'] ?? '';
|
||||
$variables = [];
|
||||
|
||||
// Primero intentar usar las variables guardadas en example_parameters
|
||||
if (!empty($template['example_parameters'])) {
|
||||
$examples = json_decode($template['example_parameters'], true);
|
||||
if (isset($examples['variables']) && is_array($examples['variables'])) {
|
||||
// Usar las variables ya procesadas durante la sincronización
|
||||
foreach ($examples['variables'] as $var) {
|
||||
$varName = $var['name'] ?? $var['index'];
|
||||
$variables[] = [
|
||||
'index' => $var['index'],
|
||||
'placeholder' => $var['placeholder'],
|
||||
'label' => is_numeric($varName) ? 'Variable ' . $varName : ucfirst(str_replace('_', ' ', $varName)),
|
||||
'required' => true,
|
||||
'example' => $var['example'] ?? null
|
||||
];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Si no hay variables guardadas, extraerlas del body_text (fallback)
|
||||
if (empty($variables) && preg_match_all('/\{\{([^\}]+)\}\}/', $bodyText, $matches)) {
|
||||
$uniqueVars = array_unique($matches[1]);
|
||||
|
||||
$index = 1;
|
||||
foreach ($uniqueVars as $varName) {
|
||||
$varIndex = is_numeric($varName) ? intval($varName) : $index++;
|
||||
$variables[] = [
|
||||
'index' => $varIndex,
|
||||
'placeholder' => '{{' . $varName . '}}',
|
||||
'label' => is_numeric($varName) ? 'Variable ' . $varName : ucfirst(str_replace('_', ' ', $varName)),
|
||||
'required' => true,
|
||||
'example' => null
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
// Preparar respuesta
|
||||
$response = [
|
||||
'success' => true,
|
||||
'template' => [
|
||||
'id' => $template['id'],
|
||||
'name' => $template['name'],
|
||||
'template_name' => $template['template_name'],
|
||||
'language_code' => $template['language_code'],
|
||||
'category' => $template['category'],
|
||||
'status' => $template['status'],
|
||||
'body_text' => $bodyText,
|
||||
'header_text' => $template['header_text'] ?? null,
|
||||
'header_type' => $template['header_type'] ?? null,
|
||||
'footer_text' => $template['footer_text'] ?? null,
|
||||
'has_variables' => count($variables) > 0,
|
||||
'variables_count' => count($variables),
|
||||
'variables' => $variables
|
||||
]
|
||||
];
|
||||
|
||||
echo json_encode($response, JSON_UNESCAPED_UNICODE);
|
||||
|
||||
} catch (Exception $e) {
|
||||
error_log("Error in get_template_details.php: " . $e->getMessage());
|
||||
http_response_code(500);
|
||||
echo json_encode([
|
||||
'success' => false,
|
||||
'error' => $e->getMessage()
|
||||
]);
|
||||
}
|
||||
+16
-2
@@ -27,8 +27,22 @@ try {
|
||||
$approvedOnly = isset($_GET['approved_only']) && $_GET['approved_only'] == '1';
|
||||
$limit = isset($_GET['limit']) ? intval($_GET['limit']) : null;
|
||||
|
||||
// Ajuste: some DBs may not have body_text column; omit it to avoid SQL errors
|
||||
$sql = "SELECT id, name, template_name, language_code, category, status, created_at FROM message_templates";
|
||||
// Incluir todos los campos necesarios para la UI
|
||||
$sql = "SELECT
|
||||
id,
|
||||
name,
|
||||
template_name,
|
||||
language_code,
|
||||
category,
|
||||
status,
|
||||
body_text,
|
||||
header_text,
|
||||
header_type,
|
||||
footer_text,
|
||||
components,
|
||||
example_parameters,
|
||||
created_at
|
||||
FROM message_templates";
|
||||
$params = [];
|
||||
|
||||
if ($approvedOnly) {
|
||||
|
||||
+41
-5
@@ -24,8 +24,30 @@ header('Access-Control-Allow-Headers: Content-Type');
|
||||
try {
|
||||
$db = Database::getInstance();
|
||||
|
||||
$users = $db->fetchAll(
|
||||
"SELECT
|
||||
// Parámetros de paginación
|
||||
$page = isset($_GET['page']) ? max(1, intval($_GET['page'])) : 1;
|
||||
$limit = isset($_GET['limit']) ? max(1, min(100, intval($_GET['limit']))) : 20;
|
||||
$offset = ($page - 1) * $limit;
|
||||
|
||||
// Búsqueda opcional
|
||||
$search = isset($_GET['search']) ? trim($_GET['search']) : '';
|
||||
$whereClause = '';
|
||||
$params = [];
|
||||
|
||||
if (!empty($search)) {
|
||||
$whereClause = "WHERE u.phone_number LIKE ? OR u.name LIKE ? OR u.email LIKE ?";
|
||||
$searchParam = "%{$search}%";
|
||||
$params = [$searchParam, $searchParam, $searchParam];
|
||||
}
|
||||
|
||||
// Contar total de registros
|
||||
$countQuery = "SELECT COUNT(*) as total FROM users u {$whereClause}";
|
||||
$totalResult = $db->fetch($countQuery, $params);
|
||||
$total = $totalResult['total'];
|
||||
$totalPages = ceil($total / $limit);
|
||||
|
||||
// Obtener usuarios paginados
|
||||
$query = "SELECT
|
||||
u.id,
|
||||
u.phone_number,
|
||||
u.name,
|
||||
@@ -38,12 +60,26 @@ try {
|
||||
m.name as current_menu
|
||||
FROM users u
|
||||
LEFT JOIN menus m ON u.current_menu_id = m.id
|
||||
ORDER BY u.created_at DESC"
|
||||
);
|
||||
{$whereClause}
|
||||
ORDER BY u.created_at DESC
|
||||
LIMIT ? OFFSET ?";
|
||||
|
||||
$params[] = $limit;
|
||||
$params[] = $offset;
|
||||
|
||||
$users = $db->fetchAll($query, $params);
|
||||
|
||||
echo json_encode([
|
||||
'success' => true,
|
||||
'data' => $users
|
||||
'data' => $users,
|
||||
'pagination' => [
|
||||
'page' => $page,
|
||||
'limit' => $limit,
|
||||
'total' => $total,
|
||||
'totalPages' => $totalPages,
|
||||
'hasNext' => $page < $totalPages,
|
||||
'hasPrev' => $page > 1
|
||||
]
|
||||
]);
|
||||
|
||||
} catch (Exception $e) {
|
||||
|
||||
@@ -0,0 +1,44 @@
|
||||
<?php
|
||||
/**
|
||||
* API - Listar usuarios administradores del sistema
|
||||
* Fecha: 3 de febrero de 2026
|
||||
*/
|
||||
|
||||
require_once '../config/config.php';
|
||||
|
||||
// Verificar autenticación
|
||||
requireAuthentication();
|
||||
|
||||
header('Content-Type: application/json; charset=utf-8');
|
||||
header('Cache-Control: no-cache, no-store, must-revalidate');
|
||||
|
||||
try {
|
||||
$db = Database::getInstance();
|
||||
|
||||
// Obtener todos los usuarios administradores
|
||||
$users = $db->fetchAll("
|
||||
SELECT
|
||||
id,
|
||||
username,
|
||||
full_name,
|
||||
email,
|
||||
is_active,
|
||||
last_login,
|
||||
created_at
|
||||
FROM admin_users
|
||||
ORDER BY created_at DESC
|
||||
");
|
||||
|
||||
echo json_encode([
|
||||
'success' => true,
|
||||
'data' => $users
|
||||
]);
|
||||
|
||||
} catch (Exception $e) {
|
||||
error_log('Error listing admin users: ' . $e->getMessage());
|
||||
http_response_code(500);
|
||||
echo json_encode([
|
||||
'success' => false,
|
||||
'error' => 'Error al obtener usuarios: ' . $e->getMessage()
|
||||
]);
|
||||
}
|
||||
@@ -0,0 +1,130 @@
|
||||
<?php
|
||||
/**
|
||||
* API - Generar vista previa de plantilla con parámetros
|
||||
* Reemplaza las variables y muestra cómo se verá el mensaje final
|
||||
*/
|
||||
|
||||
require_once '../config/config.php';
|
||||
|
||||
header('Content-Type: application/json; charset=utf-8');
|
||||
header('Access-Control-Allow-Origin: *');
|
||||
header('Access-Control-Allow-Methods: POST');
|
||||
header('Access-Control-Allow-Headers: Content-Type');
|
||||
|
||||
requireAuthentication();
|
||||
|
||||
try {
|
||||
$input = json_decode(file_get_contents('php://input'), true);
|
||||
|
||||
if (!$input) {
|
||||
throw new Exception('Datos inválidos');
|
||||
}
|
||||
|
||||
$templateId = $input['template_id'] ?? null;
|
||||
$parameters = $input['parameters'] ?? [];
|
||||
|
||||
if (!$templateId) {
|
||||
throw new Exception('Se requiere ID de plantilla');
|
||||
}
|
||||
|
||||
$db = Database::getInstance();
|
||||
$template = $db->fetch(
|
||||
"SELECT * FROM message_templates WHERE id = ?",
|
||||
[$templateId]
|
||||
);
|
||||
|
||||
if (!$template) {
|
||||
throw new Exception('Plantilla no encontrada');
|
||||
}
|
||||
|
||||
// Obtener textos de la plantilla
|
||||
$bodyText = $template['body_text'] ?? '';
|
||||
$headerText = $template['header_text'] ?? null;
|
||||
$footerText = $template['footer_text'] ?? null;
|
||||
|
||||
// Obtener variables guardadas para saber sus placeholders
|
||||
$exampleParams = json_decode($template['example_parameters'] ?? '{}', true);
|
||||
$savedVariables = $exampleParams['variables'] ?? [];
|
||||
|
||||
// Reemplazar variables en el body
|
||||
$bodyPreview = $bodyText;
|
||||
if (is_array($parameters)) {
|
||||
// Si tenemos variables guardadas, usar sus placeholders
|
||||
if (!empty($savedVariables)) {
|
||||
foreach ($savedVariables as $var) {
|
||||
$index = $var['index'] - 1; // Convertir a índice de array (0-based)
|
||||
if (isset($parameters[$index])) {
|
||||
$placeholder = $var['placeholder']; // Ejemplo: {{nombre_tema}} o {{1}}
|
||||
$bodyPreview = str_replace($placeholder, $parameters[$index], $bodyPreview);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Fallback: reemplazar variables numéricas estándar
|
||||
foreach ($parameters as $index => $value) {
|
||||
$placeholder = '{{' . ($index + 1) . '}}';
|
||||
$bodyPreview = str_replace($placeholder, $value, $bodyPreview);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Reemplazar variables en el header si existen
|
||||
$headerPreview = $headerText;
|
||||
if ($headerText && is_array($parameters)) {
|
||||
if (!empty($savedVariables)) {
|
||||
foreach ($savedVariables as $var) {
|
||||
$index = $var['index'] - 1;
|
||||
if (isset($parameters[$index])) {
|
||||
$placeholder = $var['placeholder'];
|
||||
$headerPreview = str_replace($placeholder, $parameters[$index], $headerPreview);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
foreach ($parameters as $index => $value) {
|
||||
$placeholder = '{{' . ($index + 1) . '}}';
|
||||
$headerPreview = str_replace($placeholder, $value, $headerPreview);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Verificar si quedan variables sin reemplazar (tanto numéricas como con nombres)
|
||||
$hasUnreplacedVars = preg_match('/\{\{[^\}]+\}\}/', $bodyPreview);
|
||||
|
||||
// Generar vista previa HTML
|
||||
$htmlPreview = '';
|
||||
|
||||
if ($headerPreview) {
|
||||
$htmlPreview .= '<div class="template-header" style="font-weight: bold; margin-bottom: 8px; color: #075e54;">';
|
||||
$htmlPreview .= htmlspecialchars($headerPreview);
|
||||
$htmlPreview .= '</div>';
|
||||
}
|
||||
|
||||
$htmlPreview .= '<div class="template-body" style="margin-bottom: 8px;">';
|
||||
$htmlPreview .= nl2br(htmlspecialchars($bodyPreview));
|
||||
$htmlPreview .= '</div>';
|
||||
|
||||
if ($footerText) {
|
||||
$htmlPreview .= '<div class="template-footer" style="font-size: 0.85em; color: #667781; margin-top: 8px;">';
|
||||
$htmlPreview .= htmlspecialchars($footerText);
|
||||
$htmlPreview .= '</div>';
|
||||
}
|
||||
|
||||
echo json_encode([
|
||||
'success' => true,
|
||||
'preview' => [
|
||||
'body' => $bodyPreview,
|
||||
'header' => $headerPreview,
|
||||
'footer' => $footerText,
|
||||
'html' => $htmlPreview,
|
||||
'has_unreplaced_variables' => $hasUnreplacedVars,
|
||||
'is_complete' => !$hasUnreplacedVars
|
||||
]
|
||||
], JSON_UNESCAPED_UNICODE);
|
||||
|
||||
} catch (Exception $e) {
|
||||
error_log("Error in preview_template.php: " . $e->getMessage());
|
||||
http_response_code(500);
|
||||
echo json_encode([
|
||||
'success' => false,
|
||||
'error' => $e->getMessage()
|
||||
]);
|
||||
}
|
||||
@@ -0,0 +1,150 @@
|
||||
<?php
|
||||
/**
|
||||
* API - Crear mensaje programado / recordatorio
|
||||
*/
|
||||
|
||||
require_once '../config/config.php';
|
||||
|
||||
header('Content-Type: application/json; charset=utf-8');
|
||||
header('Access-Control-Allow-Origin: *');
|
||||
header('Access-Control-Allow-Methods: POST');
|
||||
header('Access-Control-Allow-Headers: Content-Type');
|
||||
|
||||
requireAuthentication();
|
||||
|
||||
try {
|
||||
$input = json_decode(file_get_contents('php://input'), true);
|
||||
|
||||
error_log('📥 schedule_message.php - Input recibido: ' . json_encode($input));
|
||||
|
||||
if (!$input) {
|
||||
throw new Exception('Datos inválidos');
|
||||
}
|
||||
|
||||
// Validar campos requeridos
|
||||
$userId = isset($input['user_id']) ? intval($input['user_id']) : null;
|
||||
$messageType = $input['message_type'] ?? 'template';
|
||||
$scheduledDate = $input['scheduled_date'] ?? null;
|
||||
$scheduledTime = $input['scheduled_time'] ?? '09:00';
|
||||
|
||||
if (!$userId) {
|
||||
throw new Exception('ID de usuario requerido');
|
||||
}
|
||||
|
||||
if (!$scheduledDate) {
|
||||
throw new Exception('Fecha programada requerida');
|
||||
}
|
||||
|
||||
// Validar que la fecha no sea en el pasado
|
||||
$scheduledDateTime = new DateTime($scheduledDate . ' ' . $scheduledTime);
|
||||
$now = new DateTime();
|
||||
|
||||
if ($scheduledDateTime < $now) {
|
||||
throw new Exception('La fecha programada no puede ser en el pasado');
|
||||
}
|
||||
|
||||
$db = Database::getInstance();
|
||||
|
||||
// Verificar que el usuario existe
|
||||
$user = $db->fetch("SELECT * FROM users WHERE id = ?", [$userId]);
|
||||
if (!$user) {
|
||||
throw new Exception('Usuario no encontrado');
|
||||
}
|
||||
|
||||
// Preparar datos según el tipo de mensaje
|
||||
$templateId = null;
|
||||
$templateName = null;
|
||||
$templateLanguage = 'es';
|
||||
$templateParameters = null;
|
||||
$messageContent = null;
|
||||
|
||||
if ($messageType === 'template') {
|
||||
$templateId = isset($input['template_id']) ? intval($input['template_id']) : null;
|
||||
$templateName = $input['template_name'] ?? null;
|
||||
$templateLanguage = $input['template_language'] ?? 'es';
|
||||
$templateParameters = $input['template_parameters'] ?? null;
|
||||
|
||||
error_log('🔍 template_parameters recibido: ' . json_encode($templateParameters));
|
||||
error_log('🔍 Es array? ' . (is_array($templateParameters) ? 'Sí' : 'No'));
|
||||
|
||||
if (!$templateId && !$templateName) {
|
||||
throw new Exception('Se requiere ID o nombre de plantilla');
|
||||
}
|
||||
|
||||
// Si se proporcionaron parámetros, convertir a JSON
|
||||
if ($templateParameters && is_array($templateParameters)) {
|
||||
$templateParameters = json_encode($templateParameters, JSON_UNESCAPED_UNICODE);
|
||||
error_log('✅ Convertido a JSON: ' . $templateParameters);
|
||||
} else {
|
||||
error_log('⚠️ No se convirtió - es null o no es array');
|
||||
}
|
||||
|
||||
} else if ($messageType === 'text') {
|
||||
$messageContent = $input['message_content'] ?? null;
|
||||
|
||||
if (empty($messageContent)) {
|
||||
throw new Exception('Contenido del mensaje requerido');
|
||||
}
|
||||
} else {
|
||||
throw new Exception('Tipo de mensaje no soportado');
|
||||
}
|
||||
|
||||
// Obtener ID del admin actual
|
||||
$createdBy = $_SESSION['admin_user']['id'] ?? $_SESSION['user_id'] ?? null;
|
||||
|
||||
// Insertar mensaje programado
|
||||
$db->execute(
|
||||
"INSERT INTO scheduled_messages (
|
||||
user_id,
|
||||
template_id,
|
||||
template_name,
|
||||
template_language,
|
||||
template_parameters,
|
||||
message_type,
|
||||
message_content,
|
||||
scheduled_date,
|
||||
scheduled_time,
|
||||
created_by,
|
||||
status
|
||||
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, 'pending')",
|
||||
[
|
||||
$userId,
|
||||
$templateId,
|
||||
$templateName,
|
||||
$templateLanguage,
|
||||
$templateParameters,
|
||||
$messageType,
|
||||
$messageContent,
|
||||
$scheduledDate,
|
||||
$scheduledTime,
|
||||
$createdBy
|
||||
]
|
||||
);
|
||||
|
||||
$scheduledId = $db->lastInsertId();
|
||||
|
||||
error_log('✅ Mensaje programado guardado con ID: ' . $scheduledId);
|
||||
|
||||
// Log
|
||||
writeLog('INFO', "Mensaje programado creado: ID={$scheduledId}, Usuario={$userId}, Fecha={$scheduledDate} {$scheduledTime}");
|
||||
|
||||
echo json_encode([
|
||||
'success' => true,
|
||||
'message' => 'Recordatorio programado exitosamente',
|
||||
'data' => [
|
||||
'id' => $scheduledId,
|
||||
'scheduled_date' => $scheduledDate,
|
||||
'scheduled_time' => $scheduledTime,
|
||||
'user_name' => $user['name'],
|
||||
'phone_number' => $user['phone_number']
|
||||
]
|
||||
]);
|
||||
|
||||
} catch (Exception $e) {
|
||||
error_log("Error in schedule_message.php: " . $e->getMessage());
|
||||
http_response_code(500);
|
||||
echo json_encode([
|
||||
'success' => false,
|
||||
'error' => $e->getMessage()
|
||||
]);
|
||||
}
|
||||
+190
-28
@@ -23,55 +23,214 @@ if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
|
||||
try {
|
||||
$input = json_decode(file_get_contents('php://input'), true);
|
||||
|
||||
if (!$input || !isset($input['message'])) {
|
||||
if (!$input) {
|
||||
http_response_code(400);
|
||||
echo json_encode(['error' => 'Mensaje requerido']);
|
||||
echo json_encode(['error' => 'Datos requeridos']);
|
||||
exit;
|
||||
}
|
||||
|
||||
$filter = $input['filter'] ?? 'all';
|
||||
$message = $input['message'];
|
||||
$selectionType = $input['selection_type'] ?? 'filter';
|
||||
$messageType = $input['message_type'] ?? 'text';
|
||||
|
||||
$db = Database::getInstance();
|
||||
$whatsappService = new WhatsAppService();
|
||||
|
||||
// Construir query según filtro
|
||||
$whereClause = "WHERE u.status = 'active'";
|
||||
// Obtener usuarios según tipo de selección
|
||||
$users = [];
|
||||
|
||||
switch ($filter) {
|
||||
case 'active':
|
||||
$whereClause .= " AND EXISTS (
|
||||
SELECT 1 FROM conversations c
|
||||
WHERE c.user_id = u.id
|
||||
AND c.created_at >= DATE_SUB(NOW(), INTERVAL 7 DAY)
|
||||
)";
|
||||
break;
|
||||
|
||||
case 'recent':
|
||||
$whereClause .= " AND u.created_at >= DATE_SUB(NOW(), INTERVAL 30 DAY)";
|
||||
break;
|
||||
if ($selectionType === 'specific') {
|
||||
// Usuarios específicos seleccionados
|
||||
if (!isset($input['user_ids']) || !is_array($input['user_ids']) || empty($input['user_ids'])) {
|
||||
http_response_code(400);
|
||||
echo json_encode(['error' => 'Debes seleccionar al menos un usuario']);
|
||||
exit;
|
||||
}
|
||||
|
||||
$userIds = array_map('intval', $input['user_ids']);
|
||||
$placeholders = implode(',', array_fill(0, count($userIds), '?'));
|
||||
$query = "SELECT id, phone_number, name FROM users WHERE id IN ($placeholders) ORDER BY id ASC";
|
||||
$users = $db->fetchAll($query, $userIds);
|
||||
|
||||
} else {
|
||||
// Por filtro
|
||||
$filter = $input['filter'] ?? 'all';
|
||||
$whereClause = "1=1";
|
||||
|
||||
switch ($filter) {
|
||||
case 'active':
|
||||
$whereClause .= " AND EXISTS (
|
||||
SELECT 1 FROM conversations c
|
||||
WHERE c.user_id = u.id
|
||||
AND c.created_at >= DATE_SUB(NOW(), INTERVAL 7 DAY)
|
||||
)";
|
||||
break;
|
||||
|
||||
case 'recent':
|
||||
$whereClause .= " AND u.created_at >= DATE_SUB(NOW(), INTERVAL 30 DAY)";
|
||||
break;
|
||||
|
||||
case 'all':
|
||||
// Incluir todos los usuarios
|
||||
break;
|
||||
}
|
||||
|
||||
$query = "SELECT id, phone_number, name FROM users u WHERE " . $whereClause . " ORDER BY id ASC";
|
||||
$users = $db->fetchAll($query);
|
||||
}
|
||||
|
||||
// Obtener usuarios según filtro
|
||||
$users = $db->fetchAll(
|
||||
"SELECT phone_number FROM users u " . $whereClause
|
||||
);
|
||||
if (empty($users)) {
|
||||
http_response_code(404);
|
||||
echo json_encode([
|
||||
'error' => 'No se encontraron usuarios para enviar',
|
||||
'selection_type' => $selectionType
|
||||
]);
|
||||
exit;
|
||||
}
|
||||
|
||||
$sentCount = 0;
|
||||
$errorCount = 0;
|
||||
$errors = [];
|
||||
|
||||
foreach ($users as $user) {
|
||||
try {
|
||||
$response = $whatsappService->sendTextMessage($user['phone_number'], $message);
|
||||
if ($response) {
|
||||
$sentCount++;
|
||||
$response = null;
|
||||
$messageContent = '';
|
||||
|
||||
// Enviar según tipo de mensaje
|
||||
if ($messageType === 'template') {
|
||||
// Enviar plantilla
|
||||
if (!isset($input['template_id'])) {
|
||||
throw new Exception('ID de plantilla requerido');
|
||||
}
|
||||
|
||||
$templateId = intval($input['template_id']);
|
||||
$template = $db->fetch("SELECT * FROM message_templates WHERE id = ?", [$templateId]);
|
||||
|
||||
if (!$template) {
|
||||
throw new Exception('Plantilla no encontrada');
|
||||
}
|
||||
|
||||
$status = strtoupper($template['status']);
|
||||
if ($status !== 'APPROVED') {
|
||||
throw new Exception('La plantilla no está aprobada. Status: ' . $template['status']);
|
||||
}
|
||||
|
||||
// Preparar variables
|
||||
$templateVariables = $input['template_variables'] ?? [];
|
||||
|
||||
error_log('📝 Variables recibidas: ' . json_encode($templateVariables));
|
||||
error_log('🔍 Tipo: ' . gettype($templateVariables));
|
||||
|
||||
// Detectar si son variables numéricas o con nombres
|
||||
$isNumericVars = false;
|
||||
$isNamedVars = false;
|
||||
|
||||
if (is_array($templateVariables) && !empty($templateVariables)) {
|
||||
$firstKey = array_key_first($templateVariables);
|
||||
if (is_int($firstKey) && isset($templateVariables[0])) {
|
||||
// Array indexado numéricamente [0 => "val1", 1 => "val2"]
|
||||
$isNumericVars = true;
|
||||
error_log('✅ Variables tipo: Array numérico indexado');
|
||||
} elseif (is_string($firstKey) && !is_numeric($firstKey)) {
|
||||
// Array asociativo con nombres ["nombre_tema" => "val1", "fecha" => "val2"]
|
||||
$isNamedVars = true;
|
||||
error_log('✅ Variables tipo: Objeto con nombres (asociativo)');
|
||||
} elseif (is_numeric($firstKey)) {
|
||||
// Array con claves numéricas [1 => "val1", 2 => "val2"]
|
||||
$isNumericVars = true;
|
||||
error_log('✅ Variables tipo: Array con claves numéricas');
|
||||
}
|
||||
}
|
||||
|
||||
// Procesar según el tipo
|
||||
$processedVariables = [];
|
||||
|
||||
if ($isNamedVars) {
|
||||
// Variables con nombres: pasar como objeto asociativo
|
||||
$processedVariables = $templateVariables;
|
||||
error_log('📦 Enviando como objeto con nombres: ' . json_encode($processedVariables));
|
||||
} elseif ($isNumericVars) {
|
||||
// Variables numéricas: ordenar y convertir a array indexado
|
||||
ksort($templateVariables, SORT_NUMERIC);
|
||||
foreach ($templateVariables as $value) {
|
||||
$processedVariables[] = $value;
|
||||
}
|
||||
error_log('📊 Enviando como array ordenado: ' . json_encode($processedVariables));
|
||||
} else {
|
||||
error_log('⚠️ Sin variables o formato desconocido');
|
||||
}
|
||||
|
||||
error_log('📤 Variables procesadas: ' . json_encode($processedVariables));
|
||||
|
||||
// Verificar variables si la plantilla usa formato numérico
|
||||
$expectedVars = preg_match_all('/\{\{(\d+)\}\}/', $template['body_text'] ?? '', $matches);
|
||||
if ($expectedVars > 0 && $isNumericVars && count($processedVariables) !== $expectedVars) {
|
||||
throw new Exception("La plantilla requiere {$expectedVars} variable(s) numéricas, pero se enviaron " . count($processedVariables));
|
||||
}
|
||||
|
||||
$response = $whatsappService->sendTemplateMessage(
|
||||
$user['phone_number'],
|
||||
$template['template_name'] ?? $template['name'],
|
||||
$template['language_code'] ?? 'es',
|
||||
$processedVariables
|
||||
);
|
||||
|
||||
// Construir contenido para BD (reemplazar variables en body_text)
|
||||
$messageContent = $template['body_text'] ?? $template['name'];
|
||||
if ($isNamedVars) {
|
||||
// Reemplazar por nombres
|
||||
foreach ($templateVariables as $key => $value) {
|
||||
$messageContent = str_replace('{{' . $key . '}}', $value, $messageContent);
|
||||
}
|
||||
} else {
|
||||
// Reemplazar por índice numérico
|
||||
foreach ($processedVariables as $index => $value) {
|
||||
$messageContent = str_replace('{{' . ($index + 1) . '}}', $value, $messageContent);
|
||||
}
|
||||
}
|
||||
|
||||
} else {
|
||||
// Enviar texto simple
|
||||
$message = trim($input['message'] ?? '');
|
||||
if (empty($message)) {
|
||||
throw new Exception('Mensaje vacío');
|
||||
}
|
||||
|
||||
$response = $whatsappService->sendTextMessage($user['phone_number'], $message);
|
||||
$messageContent = $message;
|
||||
}
|
||||
|
||||
// Pequeña pausa para no saturar la API
|
||||
usleep(100000); // 0.1 segundo
|
||||
if ($response && isset($response['success']) && $response['success']) {
|
||||
$sentCount++;
|
||||
|
||||
// Guardar mensaje en BD
|
||||
$db->insert('conversations', [
|
||||
'user_id' => $user['id'],
|
||||
'message_id' => $response['message_id'] ?? null,
|
||||
'content' => $messageContent,
|
||||
'direction' => 'outgoing',
|
||||
'message_type' => $messageType === 'template' ? 'template' : 'text',
|
||||
'status' => 'sent',
|
||||
'created_at' => date('Y-m-d H:i:s')
|
||||
]);
|
||||
} else {
|
||||
$errorCount++;
|
||||
$errors[] = [
|
||||
'phone' => $user['phone_number'],
|
||||
'error' => 'Respuesta inválida de WhatsApp'
|
||||
];
|
||||
}
|
||||
|
||||
// Pausa para no saturar la API (250ms entre mensajes)
|
||||
usleep(250000);
|
||||
|
||||
} catch (Exception $e) {
|
||||
$errorCount++;
|
||||
$errors[] = [
|
||||
'phone' => $user['phone_number'],
|
||||
'name' => $user['name'] ?? 'Sin nombre',
|
||||
'error' => $e->getMessage()
|
||||
];
|
||||
error_log("Error sending broadcast to {$user['phone_number']}: " . $e->getMessage());
|
||||
}
|
||||
}
|
||||
@@ -80,7 +239,10 @@ try {
|
||||
'success' => true,
|
||||
'sent_count' => $sentCount,
|
||||
'error_count' => $errorCount,
|
||||
'total_users' => count($users)
|
||||
'total_users' => count($users),
|
||||
'selection_type' => $selectionType,
|
||||
'message_type' => $messageType,
|
||||
'errors' => $errorCount > 0 ? $errors : null
|
||||
]);
|
||||
|
||||
} catch (Exception $e) {
|
||||
|
||||
@@ -239,3 +239,27 @@
|
||||
[2026-01-29 11:39:41] Send media by id response: {"messaging_product":"whatsapp","contacts":[{"input":"573168950803","wa_id":"573168950803"}],"messages":[{"id":"wamid.HBgMNTczMTY4OTUwODAzFQIAERgSNzAyNEVCRkRGNUYwM0IzQURGAA=="}]}
|
||||
[2026-01-29 11:39:41] WhatsApp response (successful): {"messaging_product":"whatsapp","contacts":[{"input":"573168950803","wa_id":"573168950803"}],"messages":[{"id":"wamid.HBgMNTczMTY4OTUwODAzFQIAERgSNzAyNEVCRkRGNUYwM0IzQURGAA=="}]}
|
||||
[2026-01-29 11:39:41] Saving to DB - content: record_1769704778262.webm, media_url: 906365935662402, local_file: uploads/media_697b8d4b88a6c2.66006027.ogg, local_thumb: null
|
||||
[2026-02-02 21:43:43] Raw input: {"recipient":"573168950803","media_url":"https://bot.u-s.app/uploads/media_698160df42ea78.64430762.ogg","media_type":"audio","caption":null,"filename":"record_1770086620567.webm","is_voice":true}
|
||||
[2026-02-02 21:43:43] Input decoded: {"recipient":"573168950803","media_url":"https:\/\/bot.u-s.app\/uploads\/media_698160df42ea78.64430762.ogg","media_type":"audio","caption":null,"filename":"record_1770086620567.webm","is_voice":true}
|
||||
[2026-02-02 21:43:43] is_voice: true
|
||||
[2026-02-02 21:43:44] Upload result: {"id":"1591364868775705"}
|
||||
[2026-02-02 21:43:44] Media ID obtained: 1591364868775705
|
||||
[2026-02-02 21:43:45] Send media by id response: {"messaging_product":"whatsapp","contacts":[{"input":"573168950803","wa_id":"573168950803"}],"messages":[{"id":"wamid.HBgMNTczMTY4OTUwODAzFQIAERgSMDhGRTNDMUQ5NEFGQTRBQ0FDAA=="}]}
|
||||
[2026-02-02 21:43:45] WhatsApp response (successful): {"messaging_product":"whatsapp","contacts":[{"input":"573168950803","wa_id":"573168950803"}],"messages":[{"id":"wamid.HBgMNTczMTY4OTUwODAzFQIAERgSMDhGRTNDMUQ5NEFGQTRBQ0FDAA=="}]}
|
||||
[2026-02-02 21:43:45] Saving to DB - content: record_1770086620567.webm, media_url: 1591364868775705, local_file: uploads/media_698160df42ea78.64430762.ogg, local_thumb: null
|
||||
[2026-02-03 08:52:11] Raw input: {"recipient":"573168950803","media_url":"https://bot.u-s.app/uploads/media_6981fd8bb34da0.05106104.xlsx","media_type":"document","caption":null,"filename":"20260122213820_CONSOLIDADO FERTILIZACION DESDE EL 2025-01-22 HASTA EL 2026-01-22.xlsx","is_voice":false}
|
||||
[2026-02-03 08:52:11] Input decoded: {"recipient":"573168950803","media_url":"https:\/\/bot.u-s.app\/uploads\/media_6981fd8bb34da0.05106104.xlsx","media_type":"document","caption":null,"filename":"20260122213820_CONSOLIDADO FERTILIZACION DESDE EL 2025-01-22 HASTA EL 2026-01-22.xlsx","is_voice":false}
|
||||
[2026-02-03 08:52:11] is_voice: false
|
||||
[2026-02-03 08:52:12] Upload result: {"id":"817602961329813"}
|
||||
[2026-02-03 08:52:12] Media ID obtained: 817602961329813
|
||||
[2026-02-03 08:52:13] Send media by id response: {"messaging_product":"whatsapp","contacts":[{"input":"573168950803","wa_id":"573168950803"}],"messages":[{"id":"wamid.HBgMNTczMTY4OTUwODAzFQIAERgSMUVGMkUzOTNCNjBENTE4OEI1AA=="}]}
|
||||
[2026-02-03 08:52:13] WhatsApp response (successful): {"messaging_product":"whatsapp","contacts":[{"input":"573168950803","wa_id":"573168950803"}],"messages":[{"id":"wamid.HBgMNTczMTY4OTUwODAzFQIAERgSMUVGMkUzOTNCNjBENTE4OEI1AA=="}]}
|
||||
[2026-02-03 08:52:13] Saving to DB - content: 20260122213820_CONSOLIDADO FERTILIZACION DESDE EL 2025-01-22 HASTA EL 2026-01-22.xlsx, media_url: 817602961329813, local_file: uploads/media_6981fd8bb34da0.05106104.xlsx, local_thumb: null
|
||||
[2026-02-03 16:20:01] Raw input: {"recipient":"573022548060","media_url":"https://bot.u-s.app/uploads/media_69826681178109.48762271.xlsx","media_type":"document","caption":null,"filename":"20260122213820_CONSOLIDADO FERTILIZACION DESDE EL 2025-01-22 HASTA EL 2026-01-22.xlsx","is_voice":false}
|
||||
[2026-02-03 16:20:01] Input decoded: {"recipient":"573022548060","media_url":"https:\/\/bot.u-s.app\/uploads\/media_69826681178109.48762271.xlsx","media_type":"document","caption":null,"filename":"20260122213820_CONSOLIDADO FERTILIZACION DESDE EL 2025-01-22 HASTA EL 2026-01-22.xlsx","is_voice":false}
|
||||
[2026-02-03 16:20:01] is_voice: false
|
||||
[2026-02-03 16:20:02] Upload result: {"id":"876532491851564"}
|
||||
[2026-02-03 16:20:02] Media ID obtained: 876532491851564
|
||||
[2026-02-03 16:20:02] Send media by id response: {"messaging_product":"whatsapp","contacts":[{"input":"573022548060","wa_id":"573022548060"}],"messages":[{"id":"wamid.HBgMNTczMDIyNTQ4MDYwFQIAERgSMjAzNDlFQTVENzA2OEQ5QzkwAA=="}]}
|
||||
[2026-02-03 16:20:02] WhatsApp response (successful): {"messaging_product":"whatsapp","contacts":[{"input":"573022548060","wa_id":"573022548060"}],"messages":[{"id":"wamid.HBgMNTczMDIyNTQ4MDYwFQIAERgSMjAzNDlFQTVENzA2OEQ5QzkwAA=="}]}
|
||||
[2026-02-03 16:20:02] Saving to DB - content: 20260122213820_CONSOLIDADO FERTILIZACION DESDE EL 2025-01-22 HASTA EL 2026-01-22.xlsx, media_url: 876532491851564, local_file: uploads/media_69826681178109.48762271.xlsx, local_thumb: null
|
||||
|
||||
+10
-3
@@ -163,10 +163,17 @@ try {
|
||||
echo json_encode(['error' => 'Mensaje requerido']);
|
||||
exit;
|
||||
}
|
||||
|
||||
// Procesar saltos de línea escapados (\\n o \n) -> convertir a saltos reales
|
||||
$message = $input['message'];
|
||||
$message = str_replace('\\n', "\n", $message); // \n escapado literal
|
||||
$message = preg_replace('/(?<!\\\\)\\\\n/', "\n", $message); // \n en string
|
||||
|
||||
// DEBUG: Mostrar datos de envío de mensaje de texto
|
||||
error_log("=== DEBUG WHATSAPP TEXT MESSAGE ===");
|
||||
error_log("Recipient: " . $recipient);
|
||||
error_log("Message: " . $input['message']);
|
||||
error_log("Message original: " . $input['message']);
|
||||
error_log("Message processed: " . $message);
|
||||
error_log("Method: sendTextMessage()/sendTextReply()");
|
||||
error_log("====================================");
|
||||
|
||||
@@ -175,9 +182,9 @@ try {
|
||||
|
||||
if (!empty($input['reply_to'])) {
|
||||
$replyTo = $input['reply_to'];
|
||||
$response = $whatsappService->sendTextReply($recipient, $replyTo, $input['message'], $operatorId ? ['operator_id' => $operatorId] : null);
|
||||
$response = $whatsappService->sendTextReply($recipient, $replyTo, $message, $operatorId ? ['operator_id' => $operatorId] : null);
|
||||
} else {
|
||||
$response = $whatsappService->sendTextMessage($recipient, $input['message'], $operatorId ? ['operator_id' => $operatorId] : null);
|
||||
$response = $whatsappService->sendTextMessage($recipient, $message, $operatorId ? ['operator_id' => $operatorId] : null);
|
||||
}
|
||||
break;
|
||||
|
||||
|
||||
@@ -0,0 +1,266 @@
|
||||
<?php
|
||||
/**
|
||||
* API - Sincronizar plantillas desde Facebook/WhatsApp Business API
|
||||
* Obtiene las plantillas desde la API de WhatsApp y las guarda en la base de datos
|
||||
*/
|
||||
|
||||
require_once '../config/config.php';
|
||||
|
||||
header('Content-Type: application/json; charset=utf-8');
|
||||
header('Access-Control-Allow-Origin: *');
|
||||
header('Access-Control-Allow-Methods: POST');
|
||||
header('Access-Control-Allow-Headers: Content-Type');
|
||||
|
||||
requireAuthentication();
|
||||
|
||||
try {
|
||||
$db = Database::getInstance();
|
||||
|
||||
// Obtener configuración de WhatsApp
|
||||
$token = getConfigFromDB('whatsapp_token', '');
|
||||
$wabaId = getConfigFromDB('whatsapp_business_account_id', '');
|
||||
|
||||
if (empty($token)) {
|
||||
throw new Exception('Token de WhatsApp no configurado');
|
||||
}
|
||||
|
||||
if (empty($wabaId)) {
|
||||
throw new Exception('Business Account ID no configurado. Configure el WABA ID en la configuración del sistema.');
|
||||
}
|
||||
|
||||
// Hacer petición a la API de WhatsApp para obtener plantillas
|
||||
$url = "https://graph.facebook.com/v21.0/{$wabaId}/message_templates";
|
||||
|
||||
$ch = curl_init();
|
||||
curl_setopt($ch, CURLOPT_URL, $url);
|
||||
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
|
||||
curl_setopt($ch, CURLOPT_HTTPHEADER, [
|
||||
"Authorization: Bearer {$token}",
|
||||
"Content-Type: application/json"
|
||||
]);
|
||||
|
||||
$response = curl_exec($ch);
|
||||
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
|
||||
$error = curl_error($ch);
|
||||
curl_close($ch);
|
||||
|
||||
if ($error) {
|
||||
throw new Exception("Error de conexión: {$error}");
|
||||
}
|
||||
|
||||
if ($httpCode !== 200) {
|
||||
$errorData = json_decode($response, true);
|
||||
$errorMsg = $errorData['error']['message'] ?? 'Error desconocido';
|
||||
throw new Exception("Error de API ({$httpCode}): {$errorMsg}");
|
||||
}
|
||||
|
||||
$data = json_decode($response, true);
|
||||
|
||||
// LOG: Ver respuesta completa de WhatsApp
|
||||
error_log('📥 Respuesta de WhatsApp API: ' . substr($response, 0, 2000));
|
||||
error_log('📊 Total de plantillas recibidas: ' . count($data['data'] ?? []));
|
||||
|
||||
if (!isset($data['data']) || !is_array($data['data'])) {
|
||||
throw new Exception('Respuesta inválida de la API de WhatsApp');
|
||||
}
|
||||
|
||||
$templates = $data['data'];
|
||||
|
||||
// LOG: Ver primera plantilla como ejemplo
|
||||
if (!empty($templates)) {
|
||||
error_log('📋 Ejemplo de plantilla recibida: ' . json_encode($templates[0], JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT));
|
||||
}
|
||||
$syncedCount = 0;
|
||||
$updatedCount = 0;
|
||||
$skippedCount = 0;
|
||||
$errors = [];
|
||||
|
||||
foreach ($templates as $template) {
|
||||
try {
|
||||
$templateName = $template['name'] ?? '';
|
||||
$language = $template['language'] ?? 'es';
|
||||
$status = $template['status'] ?? 'pending';
|
||||
$category = $template['category'] ?? 'UTILITY';
|
||||
$components = $template['components'] ?? [];
|
||||
|
||||
if (empty($templateName)) {
|
||||
$skippedCount++;
|
||||
continue;
|
||||
}
|
||||
|
||||
// Extraer información de los componentes
|
||||
$bodyText = null;
|
||||
$headerText = null;
|
||||
$headerType = null;
|
||||
$footerText = null;
|
||||
$exampleParameters = [];
|
||||
|
||||
foreach ($components as $component) {
|
||||
$type = $component['type'] ?? '';
|
||||
|
||||
if ($type === 'BODY') {
|
||||
$bodyText = $component['text'] ?? null;
|
||||
// Extraer ejemplos de parámetros del body
|
||||
if (isset($component['example']['body_text'])) {
|
||||
$exampleParameters['body'] = $component['example']['body_text'];
|
||||
}
|
||||
} elseif ($type === 'HEADER') {
|
||||
$headerText = $component['text'] ?? null;
|
||||
$headerType = strtolower($component['format'] ?? 'text');
|
||||
// Extraer ejemplos del header
|
||||
if (isset($component['example']['header_text'])) {
|
||||
$exampleParameters['header'] = $component['example']['header_text'];
|
||||
}
|
||||
} elseif ($type === 'FOOTER') {
|
||||
$footerText = $component['text'] ?? null;
|
||||
}
|
||||
}
|
||||
|
||||
// NUEVO: Extraer variables automáticamente del body_text
|
||||
$variables = [];
|
||||
if ($bodyText) {
|
||||
// Buscar tanto variables numéricas {{1}} como con nombres {{nombre_tema}}
|
||||
preg_match_all('/\{\{([^\}]+)\}\}/', $bodyText, $matches);
|
||||
if (!empty($matches[1])) {
|
||||
$uniqueVars = array_unique($matches[1]);
|
||||
|
||||
error_log("🔍 Template '{$templateName}' - Variables encontradas: " . json_encode($matches[1]));
|
||||
|
||||
$index = 1;
|
||||
foreach ($uniqueVars as $varName) {
|
||||
$example = null;
|
||||
|
||||
// Si la variable es numérica, usar su índice
|
||||
if (is_numeric($varName)) {
|
||||
$varIndex = (int)$varName;
|
||||
// Los ejemplos de WhatsApp vienen como array de arrays: [["valor"]]
|
||||
if (isset($exampleParameters['body'][$varIndex - 1])) {
|
||||
$exampleData = $exampleParameters['body'][$varIndex - 1];
|
||||
$example = is_array($exampleData) ? $exampleData[0] : $exampleData;
|
||||
}
|
||||
} else {
|
||||
// Para variables con nombre, usar índice secuencial
|
||||
$varIndex = $index;
|
||||
if (isset($exampleParameters['body'][$index - 1])) {
|
||||
$exampleData = $exampleParameters['body'][$index - 1];
|
||||
$example = is_array($exampleData) ? $exampleData[0] : $exampleData;
|
||||
}
|
||||
$index++;
|
||||
}
|
||||
|
||||
$variables[] = [
|
||||
'index' => $varIndex,
|
||||
'placeholder' => "{{" . $varName . "}}",
|
||||
'name' => $varName,
|
||||
'example' => $example
|
||||
];
|
||||
}
|
||||
|
||||
error_log("✅ Variables procesadas para '{$templateName}': " . json_encode($variables, JSON_UNESCAPED_UNICODE));
|
||||
}
|
||||
}
|
||||
|
||||
// Si encontramos variables, agregarlas a example_parameters
|
||||
if (!empty($variables)) {
|
||||
$exampleParameters['variables'] = $variables;
|
||||
error_log("💾 example_parameters final para '{$templateName}': " . json_encode($exampleParameters, JSON_UNESCAPED_UNICODE));
|
||||
}
|
||||
|
||||
// Preparar datos para guardar
|
||||
$componentsJson = !empty($components) ? json_encode($components, JSON_UNESCAPED_UNICODE) : null;
|
||||
$exampleJson = !empty($exampleParameters) ? json_encode($exampleParameters, JSON_UNESCAPED_UNICODE) : null;
|
||||
|
||||
// Verificar si la plantilla ya existe
|
||||
$existing = $db->fetch(
|
||||
"SELECT id, status FROM message_templates WHERE template_name = ? AND language_code = ?",
|
||||
[$templateName, $language]
|
||||
);
|
||||
|
||||
if ($existing) {
|
||||
// Actualizar plantilla existente con nuevos campos
|
||||
$db->execute(
|
||||
"UPDATE message_templates SET
|
||||
status = ?,
|
||||
category = ?,
|
||||
body_text = ?,
|
||||
header_text = ?,
|
||||
header_type = ?,
|
||||
footer_text = ?,
|
||||
components = ?,
|
||||
example_parameters = ?,
|
||||
updated_at = NOW()
|
||||
WHERE id = ?",
|
||||
[
|
||||
strtolower($status),
|
||||
strtolower($category),
|
||||
$bodyText,
|
||||
$headerText,
|
||||
$headerType,
|
||||
$footerText,
|
||||
$componentsJson,
|
||||
$exampleJson,
|
||||
$existing['id']
|
||||
]
|
||||
);
|
||||
$updatedCount++;
|
||||
} else {
|
||||
// Insertar nueva plantilla con componentes
|
||||
$db->execute(
|
||||
"INSERT INTO message_templates (
|
||||
name,
|
||||
template_name,
|
||||
language_code,
|
||||
category,
|
||||
status,
|
||||
body_text,
|
||||
header_text,
|
||||
header_type,
|
||||
footer_text,
|
||||
components,
|
||||
example_parameters,
|
||||
created_at
|
||||
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, NOW())",
|
||||
[
|
||||
$templateName, // nombre descriptivo igual al nombre técnico
|
||||
$templateName,
|
||||
$language,
|
||||
strtolower($category),
|
||||
strtolower($status),
|
||||
$bodyText,
|
||||
$headerText,
|
||||
$headerType,
|
||||
$footerText,
|
||||
$componentsJson,
|
||||
$exampleJson
|
||||
]
|
||||
);
|
||||
$syncedCount++;
|
||||
}
|
||||
} catch (Exception $e) {
|
||||
$errors[] = "Error procesando plantilla '{$templateName}': " . $e->getMessage();
|
||||
}
|
||||
}
|
||||
|
||||
// Log de la sincronización
|
||||
writeLog('INFO', "Plantillas sincronizadas desde Facebook: {$syncedCount} nuevas, {$updatedCount} actualizadas, {$skippedCount} sin cambios");
|
||||
|
||||
echo json_encode([
|
||||
'success' => true,
|
||||
'message' => 'Plantillas sincronizadas correctamente',
|
||||
'data' => [
|
||||
'total' => count($templates),
|
||||
'synced' => $syncedCount,
|
||||
'updated' => $updatedCount,
|
||||
'skipped' => $skippedCount,
|
||||
'errors' => $errors
|
||||
]
|
||||
]);
|
||||
|
||||
} catch (Exception $e) {
|
||||
error_log("Error in sync_templates_from_facebook.php: " . $e->getMessage());
|
||||
http_response_code(500);
|
||||
echo json_encode([
|
||||
'success' => false,
|
||||
'error' => $e->getMessage()
|
||||
]);
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
<?php
|
||||
/**
|
||||
* API - Activar/Desactivar usuario administrador
|
||||
* Fecha: 3 de febrero de 2026
|
||||
*/
|
||||
|
||||
require_once '../config/config.php';
|
||||
|
||||
// Verificar autenticación
|
||||
requireAuthentication();
|
||||
|
||||
header('Content-Type: application/json; charset=utf-8');
|
||||
header('Cache-Control: no-cache, no-store, must-revalidate');
|
||||
|
||||
try {
|
||||
$db = Database::getInstance();
|
||||
|
||||
// Leer datos del request
|
||||
$input = json_decode(file_get_contents('php://input'), true);
|
||||
$userId = $input['user_id'] ?? null;
|
||||
$isActive = $input['is_active'] ?? null;
|
||||
|
||||
if (!$userId) {
|
||||
throw new Exception('ID de usuario requerido');
|
||||
}
|
||||
|
||||
// No permitir desactivar el propio usuario
|
||||
if ($userId == $_SESSION['user_id']) {
|
||||
throw new Exception('No puedes desactivar tu propio usuario');
|
||||
}
|
||||
|
||||
// Actualizar estado
|
||||
$db->execute(
|
||||
"UPDATE admin_users SET is_active = ? WHERE id = ?",
|
||||
[$isActive ? 1 : 0, $userId]
|
||||
);
|
||||
|
||||
echo json_encode([
|
||||
'success' => true,
|
||||
'message' => 'Estado actualizado correctamente'
|
||||
]);
|
||||
|
||||
} catch (Exception $e) {
|
||||
error_log('Error toggling admin user status: ' . $e->getMessage());
|
||||
http_response_code(500);
|
||||
echo json_encode([
|
||||
'success' => false,
|
||||
'error' => $e->getMessage()
|
||||
]);
|
||||
}
|
||||
@@ -0,0 +1,61 @@
|
||||
<?php
|
||||
/**
|
||||
* API - Actualizar usuario administrador
|
||||
* Fecha: 3 de febrero de 2026
|
||||
*/
|
||||
|
||||
require_once '../config/config.php';
|
||||
|
||||
// Verificar autenticación
|
||||
requireAuthentication();
|
||||
|
||||
header('Content-Type: application/json; charset=utf-8');
|
||||
header('Cache-Control: no-cache, no-store, must-revalidate');
|
||||
|
||||
try {
|
||||
$db = Database::getInstance();
|
||||
|
||||
// Leer datos del request
|
||||
$input = json_decode(file_get_contents('php://input'), true);
|
||||
$userId = $input['user_id'] ?? null;
|
||||
$username = trim($input['username'] ?? '');
|
||||
$fullName = trim($input['full_name'] ?? '');
|
||||
$email = trim($input['email'] ?? '');
|
||||
|
||||
if (!$userId) {
|
||||
throw new Exception('ID de usuario requerido');
|
||||
}
|
||||
|
||||
if (empty($username)) {
|
||||
throw new Exception('El nombre de usuario es requerido');
|
||||
}
|
||||
|
||||
// Verificar si el username ya existe (excepto para el usuario actual)
|
||||
$existing = $db->fetch(
|
||||
"SELECT id FROM admin_users WHERE username = ? AND id != ?",
|
||||
[$username, $userId]
|
||||
);
|
||||
|
||||
if ($existing) {
|
||||
throw new Exception('El nombre de usuario ya está en uso');
|
||||
}
|
||||
|
||||
// Actualizar usuario
|
||||
$db->execute(
|
||||
"UPDATE admin_users SET username = ?, full_name = ?, email = ? WHERE id = ?",
|
||||
[$username, $fullName, $email, $userId]
|
||||
);
|
||||
|
||||
echo json_encode([
|
||||
'success' => true,
|
||||
'message' => 'Usuario actualizado correctamente'
|
||||
]);
|
||||
|
||||
} catch (Exception $e) {
|
||||
error_log('Error updating admin user: ' . $e->getMessage());
|
||||
http_response_code(500);
|
||||
echo json_encode([
|
||||
'success' => false,
|
||||
'error' => $e->getMessage()
|
||||
]);
|
||||
}
|
||||
@@ -0,0 +1,101 @@
|
||||
<?php
|
||||
/**
|
||||
* API - Actualizar mensaje programado
|
||||
*/
|
||||
|
||||
require_once '../config/config.php';
|
||||
|
||||
header('Content-Type: application/json; charset=utf-8');
|
||||
header('Access-Control-Allow-Origin: *');
|
||||
header('Access-Control-Allow-Methods: PUT, POST');
|
||||
header('Access-Control-Allow-Headers: Content-Type');
|
||||
|
||||
requireAuthentication();
|
||||
|
||||
try {
|
||||
$input = json_decode(file_get_contents('php://input'), true);
|
||||
|
||||
if (!$input) {
|
||||
throw new Exception('Datos inválidos');
|
||||
}
|
||||
|
||||
$id = isset($input['id']) ? intval($input['id']) : null;
|
||||
|
||||
if (!$id) {
|
||||
throw new Exception('ID de mensaje programado requerido');
|
||||
}
|
||||
|
||||
$db = Database::getInstance();
|
||||
|
||||
// Verificar que existe
|
||||
$existing = $db->fetch("SELECT * FROM scheduled_messages WHERE id = ?", [$id]);
|
||||
|
||||
if (!$existing) {
|
||||
throw new Exception('Mensaje programado no encontrado');
|
||||
}
|
||||
|
||||
// No permitir editar mensajes ya enviados
|
||||
if ($existing['status'] === 'sent') {
|
||||
throw new Exception('No se puede editar un mensaje ya enviado');
|
||||
}
|
||||
|
||||
// Preparar campos a actualizar
|
||||
$updates = [];
|
||||
$params = [];
|
||||
|
||||
if (isset($input['scheduled_date'])) {
|
||||
$updates[] = "scheduled_date = ?";
|
||||
$params[] = $input['scheduled_date'];
|
||||
}
|
||||
|
||||
if (isset($input['scheduled_time'])) {
|
||||
$updates[] = "scheduled_time = ?";
|
||||
$params[] = $input['scheduled_time'];
|
||||
}
|
||||
|
||||
if (isset($input['template_parameters'])) {
|
||||
$updates[] = "template_parameters = ?";
|
||||
$params[] = is_array($input['template_parameters'])
|
||||
? json_encode($input['template_parameters'], JSON_UNESCAPED_UNICODE)
|
||||
: $input['template_parameters'];
|
||||
}
|
||||
|
||||
if (isset($input['message_content'])) {
|
||||
$updates[] = "message_content = ?";
|
||||
$params[] = $input['message_content'];
|
||||
}
|
||||
|
||||
if (isset($input['status'])) {
|
||||
$updates[] = "status = ?";
|
||||
$params[] = $input['status'];
|
||||
}
|
||||
|
||||
if (empty($updates)) {
|
||||
throw new Exception('No hay campos para actualizar');
|
||||
}
|
||||
|
||||
// Agregar updated_at
|
||||
$updates[] = "updated_at = NOW()";
|
||||
|
||||
// Agregar ID al final de params
|
||||
$params[] = $id;
|
||||
|
||||
// Ejecutar actualización
|
||||
$sql = "UPDATE scheduled_messages SET " . implode(', ', $updates) . " WHERE id = ?";
|
||||
$db->execute($sql, $params);
|
||||
|
||||
writeLog('INFO', "Mensaje programado actualizado: ID={$id}");
|
||||
|
||||
echo json_encode([
|
||||
'success' => true,
|
||||
'message' => 'Mensaje programado actualizado exitosamente'
|
||||
]);
|
||||
|
||||
} catch (Exception $e) {
|
||||
error_log("Error in update_scheduled_message.php: " . $e->getMessage());
|
||||
http_response_code(500);
|
||||
echo json_encode([
|
||||
'success' => false,
|
||||
'error' => $e->getMessage()
|
||||
]);
|
||||
}
|
||||
+12
-4
@@ -163,10 +163,18 @@ try {
|
||||
}
|
||||
}
|
||||
|
||||
// Construir URL pública
|
||||
$protocol = isset($_SERVER['HTTPS']) && $_SERVER['HTTPS'] === 'on' ? 'https' : 'http';
|
||||
$host = $_SERVER['HTTP_HOST'];
|
||||
$publicUrl = $protocol . '://' . $host . '/uploads/' . $uniqueName;
|
||||
// Construir URL pública usando APP_URL del .env si está disponible
|
||||
// Esto asegura que los archivos sean accesibles públicamente desde WhatsApp (datos móviles)
|
||||
$appUrl = getenv('APP_URL');
|
||||
if ($appUrl && $appUrl !== '') {
|
||||
// Usar APP_URL del .env (ej: https://bot.u-s.app/)
|
||||
$publicUrl = rtrim($appUrl, '/') . '/uploads/' . $uniqueName;
|
||||
} else {
|
||||
// Fallback: auto-detectar desde HTTP_HOST (desarrollo local)
|
||||
$protocol = isset($_SERVER['HTTPS']) && $_SERVER['HTTPS'] === 'on' ? 'https' : 'http';
|
||||
$host = $_SERVER['HTTP_HOST'];
|
||||
$publicUrl = $protocol . '://' . $host . '/uploads/' . $uniqueName;
|
||||
}
|
||||
|
||||
// Determinar tipo de medio
|
||||
$mediaType = 'document';
|
||||
|
||||
@@ -279,3 +279,138 @@
|
||||
[2026-01-28 03:19:02] Request: GET /api/version/media-url.php?url=https%3A%2F%2Flookaside.fbsbx.com%2Fwhatsapp_business%2Fattachments%2F%3Fmid%3D2371342336638440%26source%3Dwebhook%26ext%3D1769575796%26hash%3DARndVKdw1p0CCx3_DpTblR2aOVkb_W5mbTqGFPSOK77cjA GET:{"url":"https:\/\/lookaside.fbsbx.com\/whatsapp_business\/attachments\/?mid=2371342336638440&source=webhook&ext=1769575796&hash=ARndVKdw1p0CCx3_DpTblR2aOVkb_W5mbTqGFPSOK77cjA"} POST:[]
|
||||
[2026-01-28 03:19:02] Proxying Facebook media URL with Authorization to https://lookaside.fbsbx.com/whatsapp_business/attachments/?mid=1217156256734654&source=getMedia&ext=1769588642&hash=ARkCJbkmByU2tdLOv2EHpSH0F12MJjM_Oqj-hiGgNVSYAg
|
||||
[2026-01-28 03:19:02] Resolved media URL from Graph for mid 2371342336638440: https://lookaside.fbsbx.com/whatsapp_business/attachments/?mid=2371342336638440&source=getMedia&ext=1769588642&hash=ARl75SVpu4vAnsH1AXoBGSv5sjftMr7JPocagoN5kIdMWQ
|
||||
[2026-02-03 08:52:36] Request: GET /api/version/media-url.php?url=https%3A%2F%2Flookaside.fbsbx.com%2Fwhatsapp_business%2Fattachments%2F%3Fmid%3D2371342336638440%26source%3Dwebhook%26ext%3D1769575796%26hash%3DARndVKdw1p0CCx3_DpTblR2aOVkb_W5mbTqGFPSOK77cjA GET:{"url":"https:\/\/lookaside.fbsbx.com\/whatsapp_business\/attachments\/?mid=2371342336638440&source=webhook&ext=1769575796&hash=ARndVKdw1p0CCx3_DpTblR2aOVkb_W5mbTqGFPSOK77cjA"} POST:[]
|
||||
[2026-02-03 08:52:36] Request: GET /api/version/media-url.php?id=1217156256734654 GET:{"id":"1217156256734654"} POST:[]
|
||||
[2026-02-03 08:52:36] Graph API request to https://graph.facebook.com/v22.0/1217156256734654
|
||||
[2026-02-03 08:52:36] Resolved media URL from Graph for mid 2371342336638440: https://lookaside.fbsbx.com/whatsapp_business/attachments/?mid=2371342336638440&source=getMedia&ext=1770127056&hash=ARmn_DQsFhxynN1KXT-Ge4Xf8WLnjXtDlWoDsUomB4lLKg
|
||||
[2026-02-03 08:52:36] Proxying Facebook media URL with Authorization to https://lookaside.fbsbx.com/whatsapp_business/attachments/?mid=1217156256734654&source=getMedia&ext=1770127056&hash=ARnKjD4rWdn1Lf97robC2d7AyEHpxH6a2_oPLhmgZ0I9pA
|
||||
[2026-02-03 08:52:57] Request: GET /api/version/media-url.php?id=1217156256734654 GET:{"id":"1217156256734654"} POST:[]
|
||||
[2026-02-03 08:52:57] Request: GET /api/version/media-url.php?url=https%3A%2F%2Flookaside.fbsbx.com%2Fwhatsapp_business%2Fattachments%2F%3Fmid%3D2371342336638440%26source%3Dwebhook%26ext%3D1769575796%26hash%3DARndVKdw1p0CCx3_DpTblR2aOVkb_W5mbTqGFPSOK77cjA GET:{"url":"https:\/\/lookaside.fbsbx.com\/whatsapp_business\/attachments\/?mid=2371342336638440&source=webhook&ext=1769575796&hash=ARndVKdw1p0CCx3_DpTblR2aOVkb_W5mbTqGFPSOK77cjA"} POST:[]
|
||||
[2026-02-03 08:52:57] Graph API request to https://graph.facebook.com/v22.0/1217156256734654
|
||||
[2026-02-03 08:52:57] Resolved media URL from Graph for mid 2371342336638440: https://lookaside.fbsbx.com/whatsapp_business/attachments/?mid=2371342336638440&source=getMedia&ext=1770127077&hash=ARldzoeUm09JzYXDKy0ypZS4VLSt5A541gqPHgUu-u9geA
|
||||
[2026-02-03 08:52:57] Proxying Facebook media URL with Authorization to https://lookaside.fbsbx.com/whatsapp_business/attachments/?mid=1217156256734654&source=getMedia&ext=1770127077&hash=ARmAMwa-nPL80hEmwrHHiSHiMhllm6JUl3UG413ZbMkpnA
|
||||
[2026-02-03 08:52:59] Request: GET /api/version/media-url.php?id=1217156256734654 GET:{"id":"1217156256734654"} POST:[]
|
||||
[2026-02-03 08:52:59] Graph API request to https://graph.facebook.com/v22.0/1217156256734654
|
||||
[2026-02-03 08:52:59] Request: GET /api/version/media-url.php?url=https%3A%2F%2Flookaside.fbsbx.com%2Fwhatsapp_business%2Fattachments%2F%3Fmid%3D2371342336638440%26source%3Dwebhook%26ext%3D1769575796%26hash%3DARndVKdw1p0CCx3_DpTblR2aOVkb_W5mbTqGFPSOK77cjA GET:{"url":"https:\/\/lookaside.fbsbx.com\/whatsapp_business\/attachments\/?mid=2371342336638440&source=webhook&ext=1769575796&hash=ARndVKdw1p0CCx3_DpTblR2aOVkb_W5mbTqGFPSOK77cjA"} POST:[]
|
||||
[2026-02-03 08:52:59] Proxying Facebook media URL with Authorization to https://lookaside.fbsbx.com/whatsapp_business/attachments/?mid=1217156256734654&source=getMedia&ext=1770127079&hash=ARmB9s1otyY0Zp-uU577Ezen_iMY_waheMG8EMOMTf4yvA
|
||||
[2026-02-03 08:52:59] Resolved media URL from Graph for mid 2371342336638440: https://lookaside.fbsbx.com/whatsapp_business/attachments/?mid=2371342336638440&source=getMedia&ext=1770127079&hash=ARlMcDHlgltZRpheK41ar-GLM_sSPXV_8yudSsLXa4IOJw
|
||||
[2026-02-03 08:53:52] Request: GET /api/version/media-url.php?id=1217156256734654 GET:{"id":"1217156256734654"} POST:[]
|
||||
[2026-02-03 08:53:52] Graph API request to https://graph.facebook.com/v22.0/1217156256734654
|
||||
[2026-02-03 08:53:52] Request: GET /api/version/media-url.php?url=https%3A%2F%2Flookaside.fbsbx.com%2Fwhatsapp_business%2Fattachments%2F%3Fmid%3D2371342336638440%26source%3Dwebhook%26ext%3D1769575796%26hash%3DARndVKdw1p0CCx3_DpTblR2aOVkb_W5mbTqGFPSOK77cjA GET:{"url":"https:\/\/lookaside.fbsbx.com\/whatsapp_business\/attachments\/?mid=2371342336638440&source=webhook&ext=1769575796&hash=ARndVKdw1p0CCx3_DpTblR2aOVkb_W5mbTqGFPSOK77cjA"} POST:[]
|
||||
[2026-02-03 08:53:53] Resolved media URL from Graph for mid 2371342336638440: https://lookaside.fbsbx.com/whatsapp_business/attachments/?mid=2371342336638440&source=getMedia&ext=1770127133&hash=ARmVgexderjai88OImGBkEGRonyefGBrm1-ow633oVjF5w
|
||||
[2026-02-03 08:53:53] Proxying Facebook media URL with Authorization to https://lookaside.fbsbx.com/whatsapp_business/attachments/?mid=1217156256734654&source=getMedia&ext=1770127133&hash=ARleR9CvdH_ZXgcEKan0BggNv6osT2pUwtC3qumta4gttA
|
||||
[2026-02-03 08:53:53] Request: GET /api/version/media-url.php?id=1217156256734654 GET:{"id":"1217156256734654"} POST:[]
|
||||
[2026-02-03 08:53:53] Graph API request to https://graph.facebook.com/v22.0/1217156256734654
|
||||
[2026-02-03 08:53:53] Request: GET /api/version/media-url.php?url=https%3A%2F%2Flookaside.fbsbx.com%2Fwhatsapp_business%2Fattachments%2F%3Fmid%3D2371342336638440%26source%3Dwebhook%26ext%3D1769575796%26hash%3DARndVKdw1p0CCx3_DpTblR2aOVkb_W5mbTqGFPSOK77cjA GET:{"url":"https:\/\/lookaside.fbsbx.com\/whatsapp_business\/attachments\/?mid=2371342336638440&source=webhook&ext=1769575796&hash=ARndVKdw1p0CCx3_DpTblR2aOVkb_W5mbTqGFPSOK77cjA"} POST:[]
|
||||
[2026-02-03 08:53:53] Resolved media URL from Graph for mid 2371342336638440: https://lookaside.fbsbx.com/whatsapp_business/attachments/?mid=2371342336638440&source=getMedia&ext=1770127133&hash=ARmVgexderjai88OImGBkEGRonyefGBrm1-ow633oVjF5w
|
||||
[2026-02-03 08:53:53] Proxying Facebook media URL with Authorization to https://lookaside.fbsbx.com/whatsapp_business/attachments/?mid=1217156256734654&source=getMedia&ext=1770127133&hash=ARleR9CvdH_ZXgcEKan0BggNv6osT2pUwtC3qumta4gttA
|
||||
[2026-02-03 11:15:58] Request: GET /api/version/media-url.php?id=1217156256734654 GET:{"id":"1217156256734654"} POST:[]
|
||||
[2026-02-03 11:15:58] Graph API request to https://graph.facebook.com/v22.0/1217156256734654
|
||||
[2026-02-03 11:15:58] Request: GET /api/version/media-url.php?url=https%3A%2F%2Flookaside.fbsbx.com%2Fwhatsapp_business%2Fattachments%2F%3Fmid%3D2371342336638440%26source%3Dwebhook%26ext%3D1769575796%26hash%3DARndVKdw1p0CCx3_DpTblR2aOVkb_W5mbTqGFPSOK77cjA GET:{"url":"https:\/\/lookaside.fbsbx.com\/whatsapp_business\/attachments\/?mid=2371342336638440&source=webhook&ext=1769575796&hash=ARndVKdw1p0CCx3_DpTblR2aOVkb_W5mbTqGFPSOK77cjA"} POST:[]
|
||||
[2026-02-03 11:15:59] Proxying Facebook media URL with Authorization to https://lookaside.fbsbx.com/whatsapp_business/attachments/?mid=1217156256734654&source=getMedia&ext=1770135659&hash=ARlKSkH_tfyiy1Ctx7mwxi3aZ2JrsWdWcK9I73EldYmKeg
|
||||
[2026-02-03 11:15:59] Resolved media URL from Graph for mid 2371342336638440: https://lookaside.fbsbx.com/whatsapp_business/attachments/?mid=2371342336638440&source=getMedia&ext=1770135659&hash=ARmOinKL4W0exQVUyOxwpLCkmPghiI0rzBv22kDEYwbePA
|
||||
[2026-02-03 12:00:15] Request: GET /api/version/media-url.php?id=1217156256734654 GET:{"id":"1217156256734654"} POST:[]
|
||||
[2026-02-03 12:00:15] Graph API request to https://graph.facebook.com/v22.0/1217156256734654
|
||||
[2026-02-03 12:00:15] Request: GET /api/version/media-url.php?url=https%3A%2F%2Flookaside.fbsbx.com%2Fwhatsapp_business%2Fattachments%2F%3Fmid%3D2371342336638440%26source%3Dwebhook%26ext%3D1769575796%26hash%3DARndVKdw1p0CCx3_DpTblR2aOVkb_W5mbTqGFPSOK77cjA GET:{"url":"https:\/\/lookaside.fbsbx.com\/whatsapp_business\/attachments\/?mid=2371342336638440&source=webhook&ext=1769575796&hash=ARndVKdw1p0CCx3_DpTblR2aOVkb_W5mbTqGFPSOK77cjA"} POST:[]
|
||||
[2026-02-03 12:00:15] Proxying Facebook media URL with Authorization to https://lookaside.fbsbx.com/whatsapp_business/attachments/?mid=1217156256734654&source=getMedia&ext=1770138315&hash=ARnn0qsPekd9pOK3dw85bzArN5oMXws99mc_zs6cdK0wtQ
|
||||
[2026-02-03 12:00:15] Resolved media URL from Graph for mid 2371342336638440: https://lookaside.fbsbx.com/whatsapp_business/attachments/?mid=2371342336638440&source=getMedia&ext=1770138315&hash=ARlAM-vxY5Y6ST1Lwp8ox6SBOAefCdZ9yWVZKM-04kcgqQ
|
||||
[2026-02-03 12:45:14] Request: GET /api/version/media-url.php?id=1217156256734654 GET:{"id":"1217156256734654"} POST:[]
|
||||
[2026-02-03 12:45:14] Graph API request to https://graph.facebook.com/v22.0/1217156256734654
|
||||
[2026-02-03 12:45:14] Request: GET /api/version/media-url.php?url=https%3A%2F%2Flookaside.fbsbx.com%2Fwhatsapp_business%2Fattachments%2F%3Fmid%3D2371342336638440%26source%3Dwebhook%26ext%3D1769575796%26hash%3DARndVKdw1p0CCx3_DpTblR2aOVkb_W5mbTqGFPSOK77cjA GET:{"url":"https:\/\/lookaside.fbsbx.com\/whatsapp_business\/attachments\/?mid=2371342336638440&source=webhook&ext=1769575796&hash=ARndVKdw1p0CCx3_DpTblR2aOVkb_W5mbTqGFPSOK77cjA"} POST:[]
|
||||
[2026-02-03 12:45:14] Resolved media URL from Graph for mid 2371342336638440: https://lookaside.fbsbx.com/whatsapp_business/attachments/?mid=2371342336638440&source=getMedia&ext=1770141014&hash=ARn79fTfe65Rz38MXO0hhmyLSuVJsGuIzM30zAngMKVSeA
|
||||
[2026-02-03 12:45:14] Proxying Facebook media URL with Authorization to https://lookaside.fbsbx.com/whatsapp_business/attachments/?mid=1217156256734654&source=getMedia&ext=1770141014&hash=ARknEbp3Lf9RYQ2daDdcExwkmvWlMb1F_DLBhGQYJhqXtQ
|
||||
[2026-02-03 14:22:03] Request: GET /api/version/media-url.php?id=1217156256734654 GET:{"id":"1217156256734654"} POST:[]
|
||||
[2026-02-03 14:22:03] Graph API request to https://graph.facebook.com/v22.0/1217156256734654
|
||||
[2026-02-03 14:22:03] Request: GET /api/version/media-url.php?url=https%3A%2F%2Flookaside.fbsbx.com%2Fwhatsapp_business%2Fattachments%2F%3Fmid%3D2371342336638440%26source%3Dwebhook%26ext%3D1769575796%26hash%3DARndVKdw1p0CCx3_DpTblR2aOVkb_W5mbTqGFPSOK77cjA GET:{"url":"https:\/\/lookaside.fbsbx.com\/whatsapp_business\/attachments\/?mid=2371342336638440&source=webhook&ext=1769575796&hash=ARndVKdw1p0CCx3_DpTblR2aOVkb_W5mbTqGFPSOK77cjA"} POST:[]
|
||||
[2026-02-03 14:22:03] Resolved media URL from Graph for mid 2371342336638440: https://lookaside.fbsbx.com/whatsapp_business/attachments/?mid=2371342336638440&source=getMedia&ext=1770146823&hash=ARmkpGGK9nwQCAqwRqTNu8hZdxaLvFe_QqKCZDa60vjQMw
|
||||
[2026-02-03 14:22:03] Proxying Facebook media URL with Authorization to https://lookaside.fbsbx.com/whatsapp_business/attachments/?mid=1217156256734654&source=getMedia&ext=1770146823&hash=ARkmJ5x9KoRtE0e7E2j3ao1uDWPpEzVsJ3mSCUza8UjbKQ
|
||||
[2026-02-03 14:23:56] Request: GET /api/version/media-url.php?id=1217156256734654 GET:{"id":"1217156256734654"} POST:[]
|
||||
[2026-02-03 14:23:56] Graph API request to https://graph.facebook.com/v22.0/1217156256734654
|
||||
[2026-02-03 14:23:56] Request: GET /api/version/media-url.php?url=https%3A%2F%2Flookaside.fbsbx.com%2Fwhatsapp_business%2Fattachments%2F%3Fmid%3D2371342336638440%26source%3Dwebhook%26ext%3D1769575796%26hash%3DARndVKdw1p0CCx3_DpTblR2aOVkb_W5mbTqGFPSOK77cjA GET:{"url":"https:\/\/lookaside.fbsbx.com\/whatsapp_business\/attachments\/?mid=2371342336638440&source=webhook&ext=1769575796&hash=ARndVKdw1p0CCx3_DpTblR2aOVkb_W5mbTqGFPSOK77cjA"} POST:[]
|
||||
[2026-02-03 14:23:57] Resolved media URL from Graph for mid 2371342336638440: https://lookaside.fbsbx.com/whatsapp_business/attachments/?mid=2371342336638440&source=getMedia&ext=1770146937&hash=ARlFlA7KVAre979qNou-DdifbKg_QVenRMQ-ocl3p7vqjg
|
||||
[2026-02-03 14:23:57] Proxying Facebook media URL with Authorization to https://lookaside.fbsbx.com/whatsapp_business/attachments/?mid=1217156256734654&source=getMedia&ext=1770146937&hash=ARnJ0xX1NUbsvypvvoZA6UwXnFioUbFugAFUqwrBpQh23Q
|
||||
[2026-02-03 14:28:39] Request: GET /api/version/media-url.php?id=1217156256734654 GET:{"id":"1217156256734654"} POST:[]
|
||||
[2026-02-03 14:28:39] Request: GET /api/version/media-url.php?url=https%3A%2F%2Flookaside.fbsbx.com%2Fwhatsapp_business%2Fattachments%2F%3Fmid%3D2371342336638440%26source%3Dwebhook%26ext%3D1769575796%26hash%3DARndVKdw1p0CCx3_DpTblR2aOVkb_W5mbTqGFPSOK77cjA GET:{"url":"https:\/\/lookaside.fbsbx.com\/whatsapp_business\/attachments\/?mid=2371342336638440&source=webhook&ext=1769575796&hash=ARndVKdw1p0CCx3_DpTblR2aOVkb_W5mbTqGFPSOK77cjA"} POST:[]
|
||||
[2026-02-03 14:28:39] Graph API request to https://graph.facebook.com/v22.0/1217156256734654
|
||||
[2026-02-03 14:28:39] Proxying Facebook media URL with Authorization to https://lookaside.fbsbx.com/whatsapp_business/attachments/?mid=1217156256734654&source=getMedia&ext=1770147219&hash=ARkHFZc5AuDMwNI48w_-THsJM1CoGx3G8CtP-N7fcdBBtQ
|
||||
[2026-02-03 14:28:39] Resolved media URL from Graph for mid 2371342336638440: https://lookaside.fbsbx.com/whatsapp_business/attachments/?mid=2371342336638440&source=getMedia&ext=1770147219&hash=ARmjcb4sF70-VmiQiXeLl3p8XSaAjjGF9zbh339aUlnFOg
|
||||
[2026-02-03 14:34:25] Request: GET /api/version/media-url.php?id=1217156256734654 GET:{"id":"1217156256734654"} POST:[]
|
||||
[2026-02-03 14:34:25] Graph API request to https://graph.facebook.com/v22.0/1217156256734654
|
||||
[2026-02-03 14:34:25] Request: GET /api/version/media-url.php?url=https%3A%2F%2Flookaside.fbsbx.com%2Fwhatsapp_business%2Fattachments%2F%3Fmid%3D2371342336638440%26source%3Dwebhook%26ext%3D1769575796%26hash%3DARndVKdw1p0CCx3_DpTblR2aOVkb_W5mbTqGFPSOK77cjA GET:{"url":"https:\/\/lookaside.fbsbx.com\/whatsapp_business\/attachments\/?mid=2371342336638440&source=webhook&ext=1769575796&hash=ARndVKdw1p0CCx3_DpTblR2aOVkb_W5mbTqGFPSOK77cjA"} POST:[]
|
||||
[2026-02-03 14:34:25] Resolved media URL from Graph for mid 2371342336638440: https://lookaside.fbsbx.com/whatsapp_business/attachments/?mid=2371342336638440&source=getMedia&ext=1770147565&hash=ARk8Q6oLaHEvxv7WNOGtOwkgB8RDWPs7xk672ACpSuC-3A
|
||||
[2026-02-03 14:34:25] Proxying Facebook media URL with Authorization to https://lookaside.fbsbx.com/whatsapp_business/attachments/?mid=1217156256734654&source=getMedia&ext=1770147565&hash=ARkNv8rIgcXUHoj2i9ZwZQBW88W7p1yab6a_arnqGzbJtw
|
||||
[2026-02-03 14:39:57] Request: GET /api/version/media-url.php?id=1217156256734654 GET:{"id":"1217156256734654"} POST:[]
|
||||
[2026-02-03 14:39:57] Graph API request to https://graph.facebook.com/v22.0/1217156256734654
|
||||
[2026-02-03 14:39:57] Request: GET /api/version/media-url.php?url=https%3A%2F%2Flookaside.fbsbx.com%2Fwhatsapp_business%2Fattachments%2F%3Fmid%3D2371342336638440%26source%3Dwebhook%26ext%3D1769575796%26hash%3DARndVKdw1p0CCx3_DpTblR2aOVkb_W5mbTqGFPSOK77cjA GET:{"url":"https:\/\/lookaside.fbsbx.com\/whatsapp_business\/attachments\/?mid=2371342336638440&source=webhook&ext=1769575796&hash=ARndVKdw1p0CCx3_DpTblR2aOVkb_W5mbTqGFPSOK77cjA"} POST:[]
|
||||
[2026-02-03 14:39:58] Proxying Facebook media URL with Authorization to https://lookaside.fbsbx.com/whatsapp_business/attachments/?mid=1217156256734654&source=getMedia&ext=1770147898&hash=ARnRWSW5FuBObUTXyIRf_5mEV90eLmf4Kx4hft5MAQH7fw
|
||||
[2026-02-03 14:39:58] Resolved media URL from Graph for mid 2371342336638440: https://lookaside.fbsbx.com/whatsapp_business/attachments/?mid=2371342336638440&source=getMedia&ext=1770147898&hash=ARlVjspTqGVhZzOq1D-cMbpj7AXP3bNSacmtV3ZIg_ALqA
|
||||
[2026-02-03 14:49:54] Request: GET /api/version/media-url.php?id=1217156256734654 GET:{"id":"1217156256734654"} POST:[]
|
||||
[2026-02-03 14:49:54] Graph API request to https://graph.facebook.com/v22.0/1217156256734654
|
||||
[2026-02-03 14:49:54] Request: GET /api/version/media-url.php?url=https%3A%2F%2Flookaside.fbsbx.com%2Fwhatsapp_business%2Fattachments%2F%3Fmid%3D2371342336638440%26source%3Dwebhook%26ext%3D1769575796%26hash%3DARndVKdw1p0CCx3_DpTblR2aOVkb_W5mbTqGFPSOK77cjA GET:{"url":"https:\/\/lookaside.fbsbx.com\/whatsapp_business\/attachments\/?mid=2371342336638440&source=webhook&ext=1769575796&hash=ARndVKdw1p0CCx3_DpTblR2aOVkb_W5mbTqGFPSOK77cjA"} POST:[]
|
||||
[2026-02-03 14:49:55] Proxying Facebook media URL with Authorization to https://lookaside.fbsbx.com/whatsapp_business/attachments/?mid=1217156256734654&source=getMedia&ext=1770148494&hash=ARka8CBIIFFl2uNVBdAnHVJldB8vBjgU0NbvGVDabWBfsw
|
||||
[2026-02-03 14:49:55] Resolved media URL from Graph for mid 2371342336638440: https://lookaside.fbsbx.com/whatsapp_business/attachments/?mid=2371342336638440&source=getMedia&ext=1770148494&hash=ARnz6olwrZgd3SpTw-AbpdOo1lcbFr72hX3T6CcVDX9yzw
|
||||
[2026-02-03 14:50:00] Request: GET /api/version/media-url.php?id=1217156256734654 GET:{"id":"1217156256734654"} POST:[]
|
||||
[2026-02-03 14:50:00] Graph API request to https://graph.facebook.com/v22.0/1217156256734654
|
||||
[2026-02-03 14:50:00] Request: GET /api/version/media-url.php?url=https%3A%2F%2Flookaside.fbsbx.com%2Fwhatsapp_business%2Fattachments%2F%3Fmid%3D2371342336638440%26source%3Dwebhook%26ext%3D1769575796%26hash%3DARndVKdw1p0CCx3_DpTblR2aOVkb_W5mbTqGFPSOK77cjA GET:{"url":"https:\/\/lookaside.fbsbx.com\/whatsapp_business\/attachments\/?mid=2371342336638440&source=webhook&ext=1769575796&hash=ARndVKdw1p0CCx3_DpTblR2aOVkb_W5mbTqGFPSOK77cjA"} POST:[]
|
||||
[2026-02-03 14:50:00] Resolved media URL from Graph for mid 2371342336638440: https://lookaside.fbsbx.com/whatsapp_business/attachments/?mid=2371342336638440&source=getMedia&ext=1770148500&hash=ARkBigqY86ltDKoAFz_5B1g5uDgpegUSHtL4OGJeQME8rw
|
||||
[2026-02-03 14:50:00] Proxying Facebook media URL with Authorization to https://lookaside.fbsbx.com/whatsapp_business/attachments/?mid=1217156256734654&source=getMedia&ext=1770148500&hash=ARmtsjCTyftZSmwlVDnK0QCoYEzA8-NtYX6avOCtr-belQ
|
||||
[2026-02-03 15:43:01] Request: GET /api/version/media-url.php?id=1217156256734654 GET:{"id":"1217156256734654"} POST:[]
|
||||
[2026-02-03 15:43:01] Graph API request to https://graph.facebook.com/v22.0/1217156256734654
|
||||
[2026-02-03 15:43:01] Request: GET /api/version/media-url.php?url=https%3A%2F%2Flookaside.fbsbx.com%2Fwhatsapp_business%2Fattachments%2F%3Fmid%3D2371342336638440%26source%3Dwebhook%26ext%3D1769575796%26hash%3DARndVKdw1p0CCx3_DpTblR2aOVkb_W5mbTqGFPSOK77cjA GET:{"url":"https:\/\/lookaside.fbsbx.com\/whatsapp_business\/attachments\/?mid=2371342336638440&source=webhook&ext=1769575796&hash=ARndVKdw1p0CCx3_DpTblR2aOVkb_W5mbTqGFPSOK77cjA"} POST:[]
|
||||
[2026-02-03 15:43:02] Proxying Facebook media URL with Authorization to https://lookaside.fbsbx.com/whatsapp_business/attachments/?mid=1217156256734654&source=getMedia&ext=1770151682&hash=ARnTTvbQWWCa7LYWf9S55Dg6MSLjjFKtI7nWR0bN2zvDQQ
|
||||
[2026-02-03 15:43:02] Resolved media URL from Graph for mid 2371342336638440: https://lookaside.fbsbx.com/whatsapp_business/attachments/?mid=2371342336638440&source=getMedia&ext=1770151682&hash=ARlo5HOK4ztNMJITAnXP2SkjsasuE9VDkgnxXYFUC-t4ng
|
||||
[2026-02-03 15:46:16] Request: GET /api/version/media-url.php?id=1217156256734654 GET:{"id":"1217156256734654"} POST:[]
|
||||
[2026-02-03 15:46:16] Graph API request to https://graph.facebook.com/v22.0/1217156256734654
|
||||
[2026-02-03 15:46:16] Request: GET /api/version/media-url.php?url=https%3A%2F%2Flookaside.fbsbx.com%2Fwhatsapp_business%2Fattachments%2F%3Fmid%3D2371342336638440%26source%3Dwebhook%26ext%3D1769575796%26hash%3DARndVKdw1p0CCx3_DpTblR2aOVkb_W5mbTqGFPSOK77cjA GET:{"url":"https:\/\/lookaside.fbsbx.com\/whatsapp_business\/attachments\/?mid=2371342336638440&source=webhook&ext=1769575796&hash=ARndVKdw1p0CCx3_DpTblR2aOVkb_W5mbTqGFPSOK77cjA"} POST:[]
|
||||
[2026-02-03 15:46:16] Proxying Facebook media URL with Authorization to https://lookaside.fbsbx.com/whatsapp_business/attachments/?mid=1217156256734654&source=getMedia&ext=1770151876&hash=ARkTtYYdsUFAiZwrYv2rgdGGiEc-D59t4NMElrHDHtA_Vw
|
||||
[2026-02-03 15:46:16] Resolved media URL from Graph for mid 2371342336638440: https://lookaside.fbsbx.com/whatsapp_business/attachments/?mid=2371342336638440&source=getMedia&ext=1770151876&hash=ARlQUE5zvswz5tBhKIWtAYtD-E7qr9nTCcpuEoYi3nzDLA
|
||||
[2026-02-03 15:46:33] Request: GET /api/version/media-url.php?id=1217156256734654 GET:{"id":"1217156256734654"} POST:[]
|
||||
[2026-02-03 15:46:33] Graph API request to https://graph.facebook.com/v22.0/1217156256734654
|
||||
[2026-02-03 15:46:33] Request: GET /api/version/media-url.php?url=https%3A%2F%2Flookaside.fbsbx.com%2Fwhatsapp_business%2Fattachments%2F%3Fmid%3D2371342336638440%26source%3Dwebhook%26ext%3D1769575796%26hash%3DARndVKdw1p0CCx3_DpTblR2aOVkb_W5mbTqGFPSOK77cjA GET:{"url":"https:\/\/lookaside.fbsbx.com\/whatsapp_business\/attachments\/?mid=2371342336638440&source=webhook&ext=1769575796&hash=ARndVKdw1p0CCx3_DpTblR2aOVkb_W5mbTqGFPSOK77cjA"} POST:[]
|
||||
[2026-02-03 15:46:34] Resolved media URL from Graph for mid 2371342336638440: https://lookaside.fbsbx.com/whatsapp_business/attachments/?mid=2371342336638440&source=getMedia&ext=1770151894&hash=ARm8Ao4ZAndoLc5sPn6u44DIOtYTW9ck-en0zxlbgOCIRA
|
||||
[2026-02-03 15:46:34] Proxying Facebook media URL with Authorization to https://lookaside.fbsbx.com/whatsapp_business/attachments/?mid=1217156256734654&source=getMedia&ext=1770151894&hash=ARlLvqyHLBLf1wJwmDZZTlqQT0BpMvqj_hqmhijSVdCbxw
|
||||
[2026-02-03 15:47:11] Request: GET /api/version/media-url.php?id=1217156256734654 GET:{"id":"1217156256734654"} POST:[]
|
||||
[2026-02-03 15:47:11] Request: GET /api/version/media-url.php?url=https%3A%2F%2Flookaside.fbsbx.com%2Fwhatsapp_business%2Fattachments%2F%3Fmid%3D2371342336638440%26source%3Dwebhook%26ext%3D1769575796%26hash%3DARndVKdw1p0CCx3_DpTblR2aOVkb_W5mbTqGFPSOK77cjA GET:{"url":"https:\/\/lookaside.fbsbx.com\/whatsapp_business\/attachments\/?mid=2371342336638440&source=webhook&ext=1769575796&hash=ARndVKdw1p0CCx3_DpTblR2aOVkb_W5mbTqGFPSOK77cjA"} POST:[]
|
||||
[2026-02-03 15:47:11] Graph API request to https://graph.facebook.com/v22.0/1217156256734654
|
||||
[2026-02-03 15:47:11] Resolved media URL from Graph for mid 2371342336638440: https://lookaside.fbsbx.com/whatsapp_business/attachments/?mid=2371342336638440&source=getMedia&ext=1770151931&hash=ARmC5M11laF_p8bRXwSW0aUa6BZ5p6HLJ4xFt5i_rhGelg
|
||||
[2026-02-03 15:47:11] Proxying Facebook media URL with Authorization to https://lookaside.fbsbx.com/whatsapp_business/attachments/?mid=1217156256734654&source=getMedia&ext=1770151931&hash=ARklCUN4q5XJ2DcsvgXtl1clURKnFJGZ9FB1VMXZ4JUTlA
|
||||
[2026-02-03 15:52:33] Request: GET /api/version/media-url.php?url=https%3A%2F%2Flookaside.fbsbx.com%2Fwhatsapp_business%2Fattachments%2F%3Fmid%3D2371342336638440%26source%3Dwebhook%26ext%3D1769575796%26hash%3DARndVKdw1p0CCx3_DpTblR2aOVkb_W5mbTqGFPSOK77cjA GET:{"url":"https:\/\/lookaside.fbsbx.com\/whatsapp_business\/attachments\/?mid=2371342336638440&source=webhook&ext=1769575796&hash=ARndVKdw1p0CCx3_DpTblR2aOVkb_W5mbTqGFPSOK77cjA"} POST:[]
|
||||
[2026-02-03 15:52:33] Request: GET /api/version/media-url.php?id=1217156256734654 GET:{"id":"1217156256734654"} POST:[]
|
||||
[2026-02-03 15:52:33] Graph API request to https://graph.facebook.com/v22.0/1217156256734654
|
||||
[2026-02-03 15:52:34] Proxying Facebook media URL with Authorization to https://lookaside.fbsbx.com/whatsapp_business/attachments/?mid=1217156256734654&source=getMedia&ext=1770152254&hash=ARkc8acjikfeqPK2EEA-lsA6To-kLYCbC0G1N5I-sgDklw
|
||||
[2026-02-03 15:52:34] Resolved media URL from Graph for mid 2371342336638440: https://lookaside.fbsbx.com/whatsapp_business/attachments/?mid=2371342336638440&source=getMedia&ext=1770152254&hash=ARkq5Bv1Yx0I8YZVnRPl1GY3mRSjiTZsTeKecOm3WWP6fA
|
||||
[2026-02-03 15:54:38] Request: GET /api/version/media-url.php?id=1217156256734654 GET:{"id":"1217156256734654"} POST:[]
|
||||
[2026-02-03 15:54:38] Graph API request to https://graph.facebook.com/v22.0/1217156256734654
|
||||
[2026-02-03 15:54:38] Request: GET /api/version/media-url.php?url=https%3A%2F%2Flookaside.fbsbx.com%2Fwhatsapp_business%2Fattachments%2F%3Fmid%3D2371342336638440%26source%3Dwebhook%26ext%3D1769575796%26hash%3DARndVKdw1p0CCx3_DpTblR2aOVkb_W5mbTqGFPSOK77cjA GET:{"url":"https:\/\/lookaside.fbsbx.com\/whatsapp_business\/attachments\/?mid=2371342336638440&source=webhook&ext=1769575796&hash=ARndVKdw1p0CCx3_DpTblR2aOVkb_W5mbTqGFPSOK77cjA"} POST:[]
|
||||
[2026-02-03 15:54:38] Proxying Facebook media URL with Authorization to https://lookaside.fbsbx.com/whatsapp_business/attachments/?mid=1217156256734654&source=getMedia&ext=1770152378&hash=ARkd0Smk6IQrA7Z-TtMBMGK080-7tiill2lA697bnJdzQQ
|
||||
[2026-02-03 15:54:38] Resolved media URL from Graph for mid 2371342336638440: https://lookaside.fbsbx.com/whatsapp_business/attachments/?mid=2371342336638440&source=getMedia&ext=1770152378&hash=ARmCvtVqy3cbEBhreSiBlGMz_k0H0C1q3FglE1cAzOMtsA
|
||||
[2026-02-03 15:54:55] Request: GET /api/version/media-url.php?id=1217156256734654 GET:{"id":"1217156256734654"} POST:[]
|
||||
[2026-02-03 15:54:55] Graph API request to https://graph.facebook.com/v22.0/1217156256734654
|
||||
[2026-02-03 15:54:56] Request: GET /api/version/media-url.php?url=https%3A%2F%2Flookaside.fbsbx.com%2Fwhatsapp_business%2Fattachments%2F%3Fmid%3D2371342336638440%26source%3Dwebhook%26ext%3D1769575796%26hash%3DARndVKdw1p0CCx3_DpTblR2aOVkb_W5mbTqGFPSOK77cjA GET:{"url":"https:\/\/lookaside.fbsbx.com\/whatsapp_business\/attachments\/?mid=2371342336638440&source=webhook&ext=1769575796&hash=ARndVKdw1p0CCx3_DpTblR2aOVkb_W5mbTqGFPSOK77cjA"} POST:[]
|
||||
[2026-02-03 15:54:56] Proxying Facebook media URL with Authorization to https://lookaside.fbsbx.com/whatsapp_business/attachments/?mid=1217156256734654&source=getMedia&ext=1770152396&hash=ARm4I-TH2FgoFVdYU6vyaEldYprMRcQMSUZD7-mteyE2bQ
|
||||
[2026-02-03 15:54:56] Resolved media URL from Graph for mid 2371342336638440: https://lookaside.fbsbx.com/whatsapp_business/attachments/?mid=2371342336638440&source=getMedia&ext=1770152396&hash=ARmySCPJWQXfIqv5WakKH2es6o2rlrkmd_QeRWbDRDhMjA
|
||||
[2026-02-03 16:17:56] Request: GET /api/version/media-url.php?id=1217156256734654 GET:{"id":"1217156256734654"} POST:[]
|
||||
[2026-02-03 16:17:56] Graph API request to https://graph.facebook.com/v22.0/1217156256734654
|
||||
[2026-02-03 16:17:56] Request: GET /api/version/media-url.php?url=https%3A%2F%2Flookaside.fbsbx.com%2Fwhatsapp_business%2Fattachments%2F%3Fmid%3D2371342336638440%26source%3Dwebhook%26ext%3D1769575796%26hash%3DARndVKdw1p0CCx3_DpTblR2aOVkb_W5mbTqGFPSOK77cjA GET:{"url":"https:\/\/lookaside.fbsbx.com\/whatsapp_business\/attachments\/?mid=2371342336638440&source=webhook&ext=1769575796&hash=ARndVKdw1p0CCx3_DpTblR2aOVkb_W5mbTqGFPSOK77cjA"} POST:[]
|
||||
[2026-02-03 16:17:56] Resolved media URL from Graph for mid 2371342336638440: https://lookaside.fbsbx.com/whatsapp_business/attachments/?mid=2371342336638440&source=getMedia&ext=1770153776&hash=ARkpL17QRsFrIZ9gL6jP87dZLx9WL80OCkUG2QOBFeE6Iw
|
||||
[2026-02-03 16:17:56] Proxying Facebook media URL with Authorization to https://lookaside.fbsbx.com/whatsapp_business/attachments/?mid=1217156256734654&source=getMedia&ext=1770153776&hash=ARnVTurshL8o0fva9f7ThLQBCNVvOwYex9n-d0Guelq_3w
|
||||
[2026-02-03 16:18:30] Request: GET /api/version/media-url.php?url=https%3A%2F%2Flookaside.fbsbx.com%2Fwhatsapp_business%2Fattachments%2F%3Fmid%3D2371342336638440%26source%3Dwebhook%26ext%3D1769575796%26hash%3DARndVKdw1p0CCx3_DpTblR2aOVkb_W5mbTqGFPSOK77cjA GET:{"url":"https:\/\/lookaside.fbsbx.com\/whatsapp_business\/attachments\/?mid=2371342336638440&source=webhook&ext=1769575796&hash=ARndVKdw1p0CCx3_DpTblR2aOVkb_W5mbTqGFPSOK77cjA"} POST:[]
|
||||
[2026-02-03 16:18:30] Request: GET /api/version/media-url.php?id=1217156256734654 GET:{"id":"1217156256734654"} POST:[]
|
||||
[2026-02-03 16:18:30] Graph API request to https://graph.facebook.com/v22.0/1217156256734654
|
||||
[2026-02-03 16:18:30] Resolved media URL from Graph for mid 2371342336638440: https://lookaside.fbsbx.com/whatsapp_business/attachments/?mid=2371342336638440&source=getMedia&ext=1770153810&hash=ARkFailq-_F7K1gQIj0Tyqh1KJ_mD8HHlC79mPeupj6g7A
|
||||
[2026-02-03 16:18:30] Proxying Facebook media URL with Authorization to https://lookaside.fbsbx.com/whatsapp_business/attachments/?mid=1217156256734654&source=getMedia&ext=1770153810&hash=ARmcNab_e2NsTTxNcuj_VUmj1_G1h2pdSPXQmEF_T7IZ7A
|
||||
[2026-02-03 16:20:02] Request: GET /api/version/media-url.php?id=1217156256734654 GET:{"id":"1217156256734654"} POST:[]
|
||||
[2026-02-03 16:20:02] Graph API request to https://graph.facebook.com/v22.0/1217156256734654
|
||||
[2026-02-03 16:20:02] Request: GET /api/version/media-url.php?url=https%3A%2F%2Flookaside.fbsbx.com%2Fwhatsapp_business%2Fattachments%2F%3Fmid%3D2371342336638440%26source%3Dwebhook%26ext%3D1769575796%26hash%3DARndVKdw1p0CCx3_DpTblR2aOVkb_W5mbTqGFPSOK77cjA GET:{"url":"https:\/\/lookaside.fbsbx.com\/whatsapp_business\/attachments\/?mid=2371342336638440&source=webhook&ext=1769575796&hash=ARndVKdw1p0CCx3_DpTblR2aOVkb_W5mbTqGFPSOK77cjA"} POST:[]
|
||||
[2026-02-03 16:20:03] Proxying Facebook media URL with Authorization to https://lookaside.fbsbx.com/whatsapp_business/attachments/?mid=1217156256734654&source=getMedia&ext=1770153902&hash=ARmkQUNq3ncSrBHcO337pHLH9_Mrv89BMga1M52iHJjAAA
|
||||
[2026-02-03 16:20:03] Resolved media URL from Graph for mid 2371342336638440: https://lookaside.fbsbx.com/whatsapp_business/attachments/?mid=2371342336638440&source=getMedia&ext=1770153903&hash=ARkcsEFiF-VZtOHz8UIqPbUlftOVB3pxOmlyOhHWvHwNUA
|
||||
[2026-02-03 16:20:10] Request: GET /api/version/media-url.php?id=1217156256734654 GET:{"id":"1217156256734654"} POST:[]
|
||||
[2026-02-03 16:20:10] Graph API request to https://graph.facebook.com/v22.0/1217156256734654
|
||||
[2026-02-03 16:20:10] Request: GET /api/version/media-url.php?url=https%3A%2F%2Flookaside.fbsbx.com%2Fwhatsapp_business%2Fattachments%2F%3Fmid%3D2371342336638440%26source%3Dwebhook%26ext%3D1769575796%26hash%3DARndVKdw1p0CCx3_DpTblR2aOVkb_W5mbTqGFPSOK77cjA GET:{"url":"https:\/\/lookaside.fbsbx.com\/whatsapp_business\/attachments\/?mid=2371342336638440&source=webhook&ext=1769575796&hash=ARndVKdw1p0CCx3_DpTblR2aOVkb_W5mbTqGFPSOK77cjA"} POST:[]
|
||||
[2026-02-03 16:20:11] Proxying Facebook media URL with Authorization to https://lookaside.fbsbx.com/whatsapp_business/attachments/?mid=1217156256734654&source=getMedia&ext=1770153911&hash=ARlsB6UeRRokyVUn_K3GY56WCvw4M7IiTI-0YYDgWKtDXA
|
||||
[2026-02-03 16:20:11] Resolved media URL from Graph for mid 2371342336638440: https://lookaside.fbsbx.com/whatsapp_business/attachments/?mid=2371342336638440&source=getMedia&ext=1770153911&hash=ARnhrJuYCnG-1FpfxhUU638YidJDdCp_9Pusp4nmWCCepQ
|
||||
[2026-02-03 20:31:47] Request: GET /api/version/media-url.php?url=https%3A%2F%2Flookaside.fbsbx.com%2Fwhatsapp_business%2Fattachments%2F%3Fmid%3D2371342336638440%26source%3Dwebhook%26ext%3D1769575796%26hash%3DARndVKdw1p0CCx3_DpTblR2aOVkb_W5mbTqGFPSOK77cjA GET:{"url":"https:\/\/lookaside.fbsbx.com\/whatsapp_business\/attachments\/?mid=2371342336638440&source=webhook&ext=1769575796&hash=ARndVKdw1p0CCx3_DpTblR2aOVkb_W5mbTqGFPSOK77cjA"} POST:[]
|
||||
[2026-02-03 20:31:47] Request: GET /api/version/media-url.php?id=1217156256734654 GET:{"id":"1217156256734654"} POST:[]
|
||||
[2026-02-03 20:31:47] Graph API request to https://graph.facebook.com/v22.0/1217156256734654
|
||||
[2026-02-03 20:31:48] Proxying Facebook media URL with Authorization to https://lookaside.fbsbx.com/whatsapp_business/attachments/?mid=1217156256734654&source=getMedia&ext=1770169008&hash=ARmSF2NK4GqmW53yaJd3NCLYub_AGbau7EHf5WOOETVxbw
|
||||
[2026-02-03 20:31:48] Resolved media URL from Graph for mid 2371342336638440: https://lookaside.fbsbx.com/whatsapp_business/attachments/?mid=2371342336638440&source=getMedia&ext=1770169008&hash=ARlAZuDk9UsmFsMvn4qiAOOAk45RwETVe04h9vkJdDruXg
|
||||
|
||||
Reference in New Issue
Block a user