This commit is contained in:
Lizandro Guarnizo
2026-02-21 07:37:36 -05:00
parent 2f73ee2a1a
commit de4b07c806
14 changed files with 2001 additions and 23 deletions
+327
View File
@@ -0,0 +1,327 @@
<?php
/**
* API: Recibir archivos del cliente (público — sin auth de panel)
*
* El cliente sube archivos desde la página upload.php usando un token.
* Los archivos se validan, guardan en disco e insertan en la BD.
* Además se crea un mensaje en la conversación y una notificación para el operador.
*
* POST /api/file_request_upload.php
* Body: multipart/form-data { token: string, file: File }
*
* Respuesta: { success: bool, file_id: int }
*/
error_reporting(E_ERROR | E_PARSE);
ini_set('display_errors', 0);
// Permitir uploads grandes
ini_set('upload_max_filesize', '55M');
ini_set('post_max_size', '60M');
ini_set('max_execution_time', 120);
require_once __DIR__ . '/../config/config.php';
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');
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']);
exit;
}
try {
$token = $_POST['token'] ?? '';
// Validar token
if (empty($token) || !preg_match('/^[a-zA-Z0-9]{32,64}$/', $token)) {
http_response_code(400);
echo json_encode(['success' => false, 'error' => 'Token no válido']);
exit;
}
// Validar archivo
if (!isset($_FILES['file']) || $_FILES['file']['error'] !== UPLOAD_ERR_OK) {
$errorMsg = 'No se recibió el archivo';
if (isset($_FILES['file'])) {
$uploadErrors = [
UPLOAD_ERR_INI_SIZE => 'El archivo excede el tamaño máximo del servidor',
UPLOAD_ERR_FORM_SIZE => 'El archivo excede el tamaño máximo del formulario',
UPLOAD_ERR_PARTIAL => 'El archivo se subió parcialmente',
UPLOAD_ERR_NO_FILE => 'No se envió ningún archivo',
UPLOAD_ERR_NO_TMP_DIR => 'Falta la carpeta temporal del servidor',
UPLOAD_ERR_CANT_WRITE => 'No se pudo escribir el archivo en disco',
];
$errorMsg = $uploadErrors[$_FILES['file']['error']] ?? 'Error de subida desconocido';
}
http_response_code(400);
echo json_encode(['success' => false, 'error' => $errorMsg]);
exit;
}
$db = Database::getInstance();
// Buscar solicitud por token
$request = $db->fetch(
"SELECT fr.*, u.name as user_name, u.phone_number as user_phone
FROM file_requests fr
LEFT JOIN users u ON fr.user_id = u.id
WHERE fr.token = ?",
[$token]
);
if (!$request) {
http_response_code(404);
echo json_encode(['success' => false, 'error' => 'Solicitud no encontrada']);
exit;
}
// Verificar expiración
if (strtotime($request['expires_at']) < time()) {
http_response_code(410);
echo json_encode(['success' => false, 'error' => 'El enlace ha expirado']);
exit;
}
// Verificar estado
if ($request['status'] === 'cancelled') {
http_response_code(410);
echo json_encode(['success' => false, 'error' => 'La solicitud fue cancelada']);
exit;
}
$file = $_FILES['file'];
$maxSize = intval($request['max_file_size']);
// Validar tamaño
if ($file['size'] > $maxSize) {
$maxMB = round($maxSize / (1024 * 1024));
http_response_code(413);
echo json_encode(['success' => false, 'error' => "El archivo excede el límite de {$maxMB} MB"]);
exit;
}
// Detectar MIME type
$finfo = finfo_open(FILEINFO_MIME_TYPE);
$mimeType = finfo_file($finfo, $file['tmp_name']);
finfo_close($finfo);
if (!$mimeType) {
$mimeType = $file['type'] ?: 'application/octet-stream';
}
// Clasificar tipo de media
$mediaType = 'other';
if (strpos($mimeType, 'image/') === 0) $mediaType = 'image';
elseif (strpos($mimeType, 'video/') === 0) $mediaType = 'video';
elseif (strpos($mimeType, 'audio/') === 0) $mediaType = 'audio';
elseif (strpos($mimeType, 'application/') === 0 || strpos($mimeType, 'text/') === 0) $mediaType = 'document';
// Validar tipo permitido
$allowedTypes = explode(',', $request['allowed_types']);
$typeAllowed = in_array($mediaType, $allowedTypes) || in_array('other', $allowedTypes);
if (!$typeAllowed && $mediaType === 'other') {
// Dar paso a "other" si document está permitido
$typeAllowed = in_array('document', $allowedTypes);
}
if (!$typeAllowed) {
http_response_code(400);
echo json_encode(['success' => false, 'error' => 'Tipo de archivo no permitido']);
exit;
}
// Crear directorio de destino
$uploadDir = __DIR__ . '/../uploads/file_requests/' . date('Y') . '/' . date('m');
if (!is_dir($uploadDir)) {
mkdir($uploadDir, 0755, true);
}
// Generar nombre único
$ext = pathinfo($file['name'], PATHINFO_EXTENSION) ?: 'bin';
$ext = strtolower(preg_replace('/[^a-zA-Z0-9]/', '', $ext));
$storedName = 'fr_' . $request['id'] . '_' . uniqid() . '.' . $ext;
$destPath = $uploadDir . '/' . $storedName;
$relativePath = 'uploads/file_requests/' . date('Y') . '/' . date('m') . '/' . $storedName;
// Mover archivo
if (!move_uploaded_file($file['tmp_name'], $destPath)) {
http_response_code(500);
echo json_encode(['success' => false, 'error' => 'No se pudo guardar el archivo en disco']);
exit;
}
// Generar thumbnail para imágenes
$thumbnailPath = null;
if ($mediaType === 'image' && class_exists('GdImage') || function_exists('imagecreatefromjpeg')) {
try {
$thumbName = 'thumb_' . $storedName;
$thumbDest = $uploadDir . '/' . $thumbName;
if (createThumbnail($destPath, $thumbDest, 200, 200)) {
$thumbnailPath = 'uploads/file_requests/' . date('Y') . '/' . date('m') . '/' . $thumbName;
}
} catch (Exception $e) {
// Thumbnail falla silenciosamente
error_log('Thumbnail error: ' . $e->getMessage());
}
}
// Guardar en BD - tabla file_request_uploads
$uploadId = $db->insert('file_request_uploads', [
'request_id' => $request['id'],
'original_filename' => $file['name'],
'stored_filename' => $storedName,
'file_path' => $relativePath,
'file_size' => $file['size'],
'mime_type' => $mimeType,
'media_type' => $mediaType,
'thumbnail_path' => $thumbnailPath,
]);
// Actualizar estado de la solicitud a "uploaded"
$db->update('file_requests', ['status' => 'uploaded'], 'id = ?', [$request['id']]);
// Generar URL pública del archivo
$protocol = (!empty($_SERVER['HTTPS']) && $_SERVER['HTTPS'] !== 'off') ? 'https' : 'http';
$appUrl = $protocol . '://' . $_SERVER['HTTP_HOST'];
$fileUrl = $appUrl . '/' . $relativePath;
$thumbUrl = $thumbnailPath ? ($appUrl . '/' . $thumbnailPath) : null;
// Insertar como mensaje en la conversación (para que el operador lo vea en el chat)
$db->insert('conversations', [
'user_id' => $request['user_id'],
'direction' => 'incoming',
'message_type' => $mediaType === 'other' ? 'document' : $mediaType,
'content' => '📎 Archivo recibido via enlace: ' . $file['name'],
'media_url' => $fileUrl,
'local_file' => $relativePath,
'local_thumb' => $thumbnailPath,
'status' => 'received',
'is_read' => 0,
'filename' => $file['name'],
'mime_type' => $mimeType,
'created_at' => date('Y-m-d H:i:s'),
]);
// Crear notificación para el operador
$userName = $request['user_name'] ?: $request['phone_number'];
$db->insert('notifications', [
'user_id' => $request['user_id'],
'type' => 'file_uploaded',
'message' => "📎 {$userName} subió un archivo: {$file['name']}",
'data' => json_encode([
'request_id' => $request['id'],
'upload_id' => $uploadId,
'filename' => $file['name'],
'file_url' => $fileUrl,
'media_type' => $mediaType,
'file_size' => $file['size'],
]),
'is_read' => 0,
]);
// Escribir evento SSE para actualización en tiempo real
try {
$eventsFile = __DIR__ . '/../uploads/events_global.json';
$events = [];
if (file_exists($eventsFile)) {
$raw = file_get_contents($eventsFile);
$events = json_decode($raw, true) ?: [];
}
$events[] = [
'type' => 'file_uploaded',
'user_id' => $request['user_id'],
'data' => [
'request_id' => $request['id'],
'upload_id' => $uploadId,
'filename' => $file['name'],
'file_url' => $fileUrl,
'thumb_url' => $thumbUrl,
'media_type' => $mediaType,
'file_size' => $file['size'],
'user_name' => $userName,
],
'timestamp' => time(),
];
file_put_contents($eventsFile, json_encode($events));
} catch (Exception $e) {
error_log('Error escribiendo evento SSE: ' . $e->getMessage());
}
echo json_encode([
'success' => true,
'file_id' => $uploadId,
'filename' => $file['name'],
'file_url' => $fileUrl,
'media_type' => $mediaType,
'file_size' => $file['size'],
]);
} catch (Exception $e) {
$errorDetail = date('[Y-m-d H:i:s] ') . 'file_request_upload ERROR: ' . $e->getMessage() . "\n" . $e->getTraceAsString() . "\n\n";
error_log('Error en file_request_upload: ' . $e->getMessage() . ' | Trace: ' . $e->getTraceAsString());
@file_put_contents(__DIR__ . '/../logs/app_errors.log', $errorDetail, FILE_APPEND);
http_response_code(500);
echo json_encode(['success' => false, 'error' => 'Error interno del servidor']);
}
/**
* Crear thumbnail de una imagen usando GD
*/
function createThumbnail($source, $dest, $maxW = 200, $maxH = 200) {
$info = @getimagesize($source);
if (!$info) return false;
$mime = $info['mime'];
$origW = $info[0];
$origH = $info[1];
switch ($mime) {
case 'image/jpeg': $img = @imagecreatefromjpeg($source); break;
case 'image/png': $img = @imagecreatefrompng($source); break;
case 'image/gif': $img = @imagecreatefromgif($source); break;
case 'image/webp': $img = @imagecreatefromwebp($source); break;
default: return false;
}
if (!$img) return false;
// Calcular dimensiones
$ratio = min($maxW / $origW, $maxH / $origH);
if ($ratio >= 1) {
// La imagen ya es más pequeña que el thumb
imagedestroy($img);
return copy($source, $dest);
}
$newW = intval($origW * $ratio);
$newH = intval($origH * $ratio);
$thumb = imagecreatetruecolor($newW, $newH);
// Preservar transparencia para PNG/GIF
if ($mime === 'image/png' || $mime === 'image/gif') {
imagealphablending($thumb, false);
imagesavealpha($thumb, true);
$transparent = imagecolorallocatealpha($thumb, 0, 0, 0, 127);
imagefilledrectangle($thumb, 0, 0, $newW, $newH, $transparent);
}
imagecopyresampled($thumb, $img, 0, 0, 0, 0, $newW, $newH, $origW, $origH);
// Guardar como JPEG (más ligero para thumbs)
$result = imagejpeg($thumb, $dest, 85);
imagedestroy($img);
imagedestroy($thumb);
return $result;
}
+94
View File
@@ -0,0 +1,94 @@
<?php
/**
* API: Consultar solicitudes de archivos grandes de un usuario
*
* GET /api/get_file_requests.php?user_id=123
* GET /api/get_file_requests.php?user_id=123&status=pending
*
* Respuesta: { success: bool, requests: [...], uploads: [...] }
*/
require_once __DIR__ . '/../config/config.php';
error_reporting(E_ERROR | E_PARSE);
ini_set('display_errors', 0);
requireAuthentication();
header('Content-Type: application/json; charset=utf-8');
header('Access-Control-Allow-Origin: *');
if ($_SERVER['REQUEST_METHOD'] !== 'GET') {
http_response_code(405);
echo json_encode(['success' => false, 'error' => 'Método no permitido']);
exit;
}
try {
$userId = intval($_GET['user_id'] ?? 0);
$status = trim($_GET['status'] ?? '');
if (!$userId) {
http_response_code(400);
echo json_encode(['success' => false, 'error' => 'Se requiere user_id']);
exit;
}
$db = Database::getInstance();
// Obtener solicitudes
$sql = "SELECT fr.*,
CASE WHEN fr.expires_at < NOW() AND fr.status = 'pending' THEN 'expired' ELSE fr.status END as effective_status,
(SELECT COUNT(*) FROM file_request_uploads fru WHERE fru.request_id = fr.id) as upload_count
FROM file_requests fr
WHERE fr.user_id = ?";
$params = [$userId];
if (!empty($status)) {
if ($status === 'active') {
$sql .= " AND fr.status IN ('pending','uploaded') AND fr.expires_at > NOW()";
} else {
$sql .= " AND fr.status = ?";
$params[] = $status;
}
}
$sql .= " ORDER BY fr.created_at DESC LIMIT 20";
$requests = $db->fetchAll($sql, $params);
// Obtener uploads de las solicitudes
$uploads = [];
$requestIds = array_column($requests, 'id');
if (!empty($requestIds)) {
$placeholders = implode(',', array_fill(0, count($requestIds), '?'));
$uploads = $db->fetchAll(
"SELECT fru.*, fr.token as request_token
FROM file_request_uploads fru
JOIN file_requests fr ON fru.request_id = fr.id
WHERE fru.request_id IN ({$placeholders})
ORDER BY fru.created_at DESC",
$requestIds
);
// Agregar URLs públicas
$protocol = (!empty($_SERVER['HTTPS']) && $_SERVER['HTTPS'] !== 'off') ? 'https' : 'http';
$appUrl = $protocol . '://' . $_SERVER['HTTP_HOST'];
foreach ($uploads as &$upload) {
$upload['file_url'] = $appUrl . '/' . $upload['file_path'];
$upload['thumb_url'] = $upload['thumbnail_path']
? $appUrl . '/' . $upload['thumbnail_path']
: null;
}
}
echo json_encode([
'success' => true,
'requests' => $requests,
'uploads' => $uploads,
]);
} catch (Exception $e) {
error_log('Error en get_file_requests: ' . $e->getMessage());
http_response_code(500);
echo json_encode(['success' => false, 'error' => 'Error interno']);
}
+156
View File
@@ -0,0 +1,156 @@
<?php
/**
* API: Solicitar archivo grande
*
* Genera un token único, crea la solicitud en BD y envía el enlace
* al cliente por WhatsApp.
*
* POST /api/request_large_file.php
* Body: { user_id: int, phone_number: string }
*
* Respuesta: { success: bool, request_id: int, token: string, upload_url: string }
*/
require_once __DIR__ . '/../config/config.php';
error_reporting(E_ERROR | E_PARSE);
ini_set('display_errors', 0);
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');
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']);
exit;
}
try {
$input = json_decode(file_get_contents('php://input'), true);
$userId = intval($input['user_id'] ?? 0);
$phoneNumber = trim($input['phone_number'] ?? '');
if (!$userId || empty($phoneNumber)) {
http_response_code(400);
echo json_encode(['success' => false, 'error' => 'Se requiere user_id y phone_number']);
exit;
}
$db = Database::getInstance();
// Verificar que el usuario existe
$user = $db->fetch("SELECT id, phone_number, name FROM users WHERE id = ?", [$userId]);
if (!$user) {
http_response_code(404);
echo json_encode(['success' => false, 'error' => 'Usuario no encontrado']);
exit;
}
// Usar el teléfono de la BD si no se proporcionó
$phone = !empty($phoneNumber) ? $phoneNumber : $user['phone_number'];
// Verificar si hay una solicitud pendiente activa (no expirada)
$existing = $db->fetch(
"SELECT id, token, expires_at FROM file_requests
WHERE user_id = ? AND status = 'pending' AND expires_at > NOW()
ORDER BY created_at DESC LIMIT 1",
[$userId]
);
if ($existing) {
// Reutilizar solicitud existente — reenviar enlace
$token = $existing['token'];
$requestId = $existing['id'];
} else {
// Generar nuevo token único (48 caracteres alfanuméricos)
$token = bin2hex(random_bytes(24));
// Operador que solicita
$requestedBy = $_SESSION['user_id'] ?? null;
// Crear solicitud — expira en 24 horas
$expiresAt = date('Y-m-d H:i:s', strtotime('+24 hours'));
$requestId = $db->insert('file_requests', [
'token' => $token,
'user_id' => $userId,
'phone_number' => $phone,
'requested_by' => $requestedBy,
'status' => 'pending',
'max_file_size' => 52428800, // 50MB
'allowed_types' => 'image,document',
'expires_at' => $expiresAt,
]);
}
// Construir URL de carga
$protocol = (!empty($_SERVER['HTTPS']) && $_SERVER['HTTPS'] !== 'off') ? 'https' : 'http';
$appUrl = $protocol . '://' . $_SERVER['HTTP_HOST'];
$uploadUrl = $appUrl . '/upload.php?token=' . $token;
// Enviar mensaje por WhatsApp con el enlace
$whatsapp = new WhatsAppService();
// Mensaje amigable con el enlace
$message = "📎 *Enlace para enviar archivos grandes*\n\n"
. "Hola" . ($user['name'] ? ", " . $user['name'] : "") . ".\n\n"
. "Le enviamos este enlace seguro para que pueda subir archivos de gran tamaño (hasta 50 MB):\n\n"
. "👉 " . $uploadUrl . "\n\n"
. "✅ Puede subir imágenes o documentos.\n"
. "🔒 El enlace es seguro y exclusivo para usted.\n"
. "⏰ Válido por 24 horas.\n\n"
. "_Si tiene alguna duda, estamos aquí para ayudarle._";
$sendResult = $whatsapp->sendTextMessage($phone, $message, [
'context_type' => 'file_request',
'file_request_id' => $requestId,
]);
// Guardar ID del mensaje enviado
if ($sendResult && isset($sendResult['messages'][0]['id'])) {
$messageId = $sendResult['messages'][0]['id'];
$db->update('file_requests',
['message_sent_id' => $messageId],
'id = ?', [$requestId]
);
}
// Crear notificación para el log
$db->insert('notifications', [
'user_id' => $userId,
'type' => 'file_request_sent',
'message' => 'Se envió enlace de carga de archivos a ' . ($user['name'] ?: $phone),
'data' => json_encode([
'request_id' => $requestId,
'token' => $token,
'upload_url' => $uploadUrl,
]),
'is_read' => 0,
]);
echo json_encode([
'success' => true,
'request_id' => $requestId,
'token' => $token,
'upload_url' => $uploadUrl,
'expires_at' => $existing ? $existing['expires_at'] : $expiresAt,
'message' => 'Enlace enviado al cliente por WhatsApp',
]);
} catch (Exception $e) {
$errorDetail = date('[Y-m-d H:i:s] ') . 'request_large_file ERROR: ' . $e->getMessage() . "\n" . $e->getTraceAsString() . "\n\n";
error_log('Error en request_large_file: ' . $e->getMessage() . ' | Trace: ' . $e->getTraceAsString());
@file_put_contents(__DIR__ . '/../logs/app_errors.log', $errorDetail, FILE_APPEND);
http_response_code(500);
echo json_encode(['success' => false, 'error' => 'Error interno: ' . $e->getMessage()]);
}
+27
View File
@@ -263,3 +263,30 @@
[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
[2026-02-21 07:21:21] Raw input: {"recipient":"573168950803","media_url":"https://bot.u-s.app/uploads/media_6999a341951659.74588611.pdf","media_type":"document","caption":null,"filename":"14672320327.pdf","is_voice":false}
[2026-02-21 07:21:21] Input decoded: {"recipient":"573168950803","media_url":"https:\/\/bot.u-s.app\/uploads\/media_6999a341951659.74588611.pdf","media_type":"document","caption":null,"filename":"14672320327.pdf","is_voice":false}
[2026-02-21 07:21:21] is_voice: false
[2026-02-21 07:21:21] Local file: /var/www/html/api/../uploads/media_6999a341951659.74588611.pdf exists=yes size=215839
[2026-02-21 07:21:22] Upload result: {"id":"4311430285767679"}
[2026-02-21 07:21:22] Media ID obtained: 4311430285767679
[2026-02-21 07:21:23] Send media by id response: {"messaging_product":"whatsapp","contacts":[{"input":"573168950803","wa_id":"573168950803"}],"messages":[{"id":"wamid.HBgMNTczMTY4OTUwODAzFQIAERgSQzg1RDY4RjA0MkEyNjY2N0MxAA=="}]}
[2026-02-21 07:21:23] WhatsApp response (successful): {"messaging_product":"whatsapp","contacts":[{"input":"573168950803","wa_id":"573168950803"}],"messages":[{"id":"wamid.HBgMNTczMTY4OTUwODAzFQIAERgSQzg1RDY4RjA0MkEyNjY2N0MxAA=="}]}
[2026-02-21 07:21:23] Saving to DB - content: 14672320327.pdf, media_url: 4311430285767679, local_file: uploads/media_6999a341951659.74588611.pdf, local_thumb: null
[2026-02-21 07:21:47] Raw input: {"recipient":"573022548060","media_url":"https://bot.u-s.app/uploads/media_6999a35b752f47.93279475.png","media_type":"image","caption":null,"filename":"image.png","is_voice":false}
[2026-02-21 07:21:47] Input decoded: {"recipient":"573022548060","media_url":"https:\/\/bot.u-s.app\/uploads\/media_6999a35b752f47.93279475.png","media_type":"image","caption":null,"filename":"image.png","is_voice":false}
[2026-02-21 07:21:47] is_voice: false
[2026-02-21 07:21:47] Local file: /var/www/html/api/../uploads/media_6999a35b752f47.93279475.png exists=yes size=888005
[2026-02-21 07:21:48] Upload result: {"id":"835705749483344"}
[2026-02-21 07:21:48] Media ID obtained: 835705749483344
[2026-02-21 07:21:49] Send media by id response: {"messaging_product":"whatsapp","contacts":[{"input":"573022548060","wa_id":"573022548060"}],"messages":[{"id":"wamid.HBgMNTczMDIyNTQ4MDYwFQIAERgSMzJFRjFFMzFDNDVDRENDRTAyAA=="}]}
[2026-02-21 07:21:49] WhatsApp response (successful): {"messaging_product":"whatsapp","contacts":[{"input":"573022548060","wa_id":"573022548060"}],"messages":[{"id":"wamid.HBgMNTczMDIyNTQ4MDYwFQIAERgSMzJFRjFFMzFDNDVDRENDRTAyAA=="}]}
[2026-02-21 07:21:49] Saving to DB - content: image.png, media_url: 835705749483344, local_file: uploads/media_6999a35b752f47.93279475.png, local_thumb: uploads/media_6999a35b752f47.93279475.png
[2026-02-21 07:23:25] Raw input: {"recipient":"573022548060","media_url":"https://bot.u-s.app/uploads/media_6999a3bdb40265.38760593.pdf","media_type":"document","caption":null,"filename":"DOCUMENTACI‡N_SITIO_XIMENACAICEDO.pdf","is_voice":false}
[2026-02-21 07:23:25] Input decoded: {"recipient":"573022548060","media_url":"https:\/\/bot.u-s.app\/uploads\/media_6999a3bdb40265.38760593.pdf","media_type":"document","caption":null,"filename":"DOCUMENTACI\u2021N_SITIO_XIMENACAICEDO.pdf","is_voice":false}
[2026-02-21 07:23:25] is_voice: false
[2026-02-21 07:23:25] Local file: /var/www/html/api/../uploads/media_6999a3bdb40265.38760593.pdf exists=yes size=225941
[2026-02-21 07:23:26] Upload result: {"id":"804275938612470"}
[2026-02-21 07:23:26] Media ID obtained: 804275938612470
[2026-02-21 07:23:27] Send media by id response: {"messaging_product":"whatsapp","contacts":[{"input":"573022548060","wa_id":"573022548060"}],"messages":[{"id":"wamid.HBgMNTczMDIyNTQ4MDYwFQIAERgSOEQ4MjMwMUQ2QThCQTcwNjgzAA=="}]}
[2026-02-21 07:23:27] WhatsApp response (successful): {"messaging_product":"whatsapp","contacts":[{"input":"573022548060","wa_id":"573022548060"}],"messages":[{"id":"wamid.HBgMNTczMDIyNTQ4MDYwFQIAERgSOEQ4MjMwMUQ2QThCQTcwNjgzAA=="}]}
[2026-02-21 07:23:27] Saving to DB - content: DOCUMENTACI‡N_SITIO_XIMENACAICEDO.pdf, media_url: 804275938612470, local_file: uploads/media_6999a3bdb40265.38760593.pdf, local_thumb: null
+78
View File
@@ -426,3 +426,81 @@
[2026-02-20 09:05:37] 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-20 09:05:37] Graph API fetch failed for mid 2371342336638440 - http: 400 - err: - resp: {"error":{"message":"Unsupported get request. Object with ID '2371342336638440' does not exist, cannot be loaded due to missing permissions, or does not support this operation. Please read the Graph API documentation at https:\/\/developers.facebook.com\/docs\/graph-api","type":"GraphMethodException","code":100,"error_subcode":33,"fbtrace_id":"Ayy1xE4nFVa4ae3yKVwTCJo"}}
[2026-02-20 09:05:37] Proxying Facebook media URL with Authorization to https://lookaside.fbsbx.com/whatsapp_business/attachments/?mid=2371342336638440&source=webhook&ext=1769575796&hash=ARndVKdw1p0CCx3_DpTblR2aOVkb_W5mbTqGFPSOK77cjA
[2026-02-21 06:51:51] Request: GET /api/version/media-url.php?id=876668735219378 GET:{"id":"876668735219378"} POST:[]
[2026-02-21 06:51:53] Auto-cached media 876668735219378 → uploads/media/2026/02/m_69999c594b0e4.jpg
[2026-02-21 07:16:05] Request: GET /api/version/media-url.php?id=1217156256734654 GET:{"id":"1217156256734654"} POST:[]
[2026-02-21 07:16:05] 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-21 07:16:05] Auto-download failed for media 1217156256734654: Graph API fetch failed:
[2026-02-21 07:16:05] Graph API request to https://graph.facebook.com/v22.0/1217156256734654
[2026-02-21 07:16:05] Graph API fetch failed for mid 2371342336638440 - http: 400 - err: - resp: {"error":{"message":"Unsupported get request. Object with ID '2371342336638440' does not exist, cannot be loaded due to missing permissions, or does not support this operation. Please read the Graph API documentation at https:\/\/developers.facebook.com\/docs\/graph-api","type":"GraphMethodException","code":100,"error_subcode":33,"fbtrace_id":"ANJp7KukCHb4DzuSpzES6L5"}}
[2026-02-21 07:16:05] Streaming Facebook media with Authorization: https://lookaside.fbsbx.com/whatsapp_business/attachments/?mid=2371342336638440&source=webhook&ext=1769575796&hash=ARndVKdw1p0CCx3_DpTblR2aOVkb_W5mbTqGFPSOK77cjA
[2026-02-21 07:16:05] streamRemoteFile failed: url=https://lookaside.fbsbx.com/whatsapp_business/attachments/?mid=2371342336638440&source=webhook&ext=1769575796&hash=ARndVKdw1p0CCx3_DpTblR2aOVkb_W5mbTqGFPSOK77cjA err= http=404
[2026-02-21 07:20:04] Request: GET /api/version/media-url.php?id=1217156256734654 GET:{"id":"1217156256734654"} POST:[]
[2026-02-21 07:20:04] 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-21 07:20:04] Graph API fetch failed for mid 2371342336638440 - http: 400 - err: - resp: {"error":{"message":"Unsupported get request. Object with ID '2371342336638440' does not exist, cannot be loaded due to missing permissions, or does not support this operation. Please read the Graph API documentation at https:\/\/developers.facebook.com\/docs\/graph-api","type":"GraphMethodException","code":100,"error_subcode":33,"fbtrace_id":"A6XVbqqIuOgalslGDeKiVzm"}}
[2026-02-21 07:20:04] Streaming Facebook media with Authorization: https://lookaside.fbsbx.com/whatsapp_business/attachments/?mid=2371342336638440&source=webhook&ext=1769575796&hash=ARndVKdw1p0CCx3_DpTblR2aOVkb_W5mbTqGFPSOK77cjA
[2026-02-21 07:20:04] Auto-download failed for media 1217156256734654: Graph API fetch failed:
[2026-02-21 07:20:04] Graph API request to https://graph.facebook.com/v22.0/1217156256734654
[2026-02-21 07:20:04] streamRemoteFile failed: url=https://lookaside.fbsbx.com/whatsapp_business/attachments/?mid=2371342336638440&source=webhook&ext=1769575796&hash=ARndVKdw1p0CCx3_DpTblR2aOVkb_W5mbTqGFPSOK77cjA err= http=404
[2026-02-21 07:21:34] Request: GET /api/version/media-url.php?id=1217156256734654 GET:{"id":"1217156256734654"} POST:[]
[2026-02-21 07:21:34] 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-21 07:21:34] Graph API fetch failed for mid 2371342336638440 - http: 400 - err: - resp: {"error":{"message":"Unsupported get request. Object with ID '2371342336638440' does not exist, cannot be loaded due to missing permissions, or does not support this operation. Please read the Graph API documentation at https:\/\/developers.facebook.com\/docs\/graph-api","type":"GraphMethodException","code":100,"error_subcode":33,"fbtrace_id":"AGMzefWK6Jm8NVar69sH-jp"}}
[2026-02-21 07:21:34] Auto-download failed for media 1217156256734654: Graph API fetch failed:
[2026-02-21 07:21:34] Streaming Facebook media with Authorization: https://lookaside.fbsbx.com/whatsapp_business/attachments/?mid=2371342336638440&source=webhook&ext=1769575796&hash=ARndVKdw1p0CCx3_DpTblR2aOVkb_W5mbTqGFPSOK77cjA
[2026-02-21 07:21:34] Graph API request to https://graph.facebook.com/v22.0/1217156256734654
[2026-02-21 07:21:35] streamRemoteFile failed: url=https://lookaside.fbsbx.com/whatsapp_business/attachments/?mid=2371342336638440&source=webhook&ext=1769575796&hash=ARndVKdw1p0CCx3_DpTblR2aOVkb_W5mbTqGFPSOK77cjA err= http=404
[2026-02-21 07:21:49] Request: GET /api/version/media-url.php?id=1217156256734654 GET:{"id":"1217156256734654"} POST:[]
[2026-02-21 07:21:49] 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-21 07:21:49] Auto-download failed for media 1217156256734654: Graph API fetch failed:
[2026-02-21 07:21:49] Graph API request to https://graph.facebook.com/v22.0/1217156256734654
[2026-02-21 07:21:49] Graph API fetch failed for mid 2371342336638440 - http: 400 - err: - resp: {"error":{"message":"Unsupported get request. Object with ID '2371342336638440' does not exist, cannot be loaded due to missing permissions, or does not support this operation. Please read the Graph API documentation at https:\/\/developers.facebook.com\/docs\/graph-api","type":"GraphMethodException","code":100,"error_subcode":33,"fbtrace_id":"A4_9s9OJ8bcOZfLlTbHf0Dv"}}
[2026-02-21 07:21:49] Streaming Facebook media with Authorization: https://lookaside.fbsbx.com/whatsapp_business/attachments/?mid=2371342336638440&source=webhook&ext=1769575796&hash=ARndVKdw1p0CCx3_DpTblR2aOVkb_W5mbTqGFPSOK77cjA
[2026-02-21 07:21:49] streamRemoteFile failed: url=https://lookaside.fbsbx.com/whatsapp_business/attachments/?mid=2371342336638440&source=webhook&ext=1769575796&hash=ARndVKdw1p0CCx3_DpTblR2aOVkb_W5mbTqGFPSOK77cjA err= http=404
[2026-02-21 07:21:52] Request: GET /api/version/media-url.php?id=1217156256734654 GET:{"id":"1217156256734654"} POST:[]
[2026-02-21 07:21: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-21 07:21:52] Graph API fetch failed for mid 2371342336638440 - http: 400 - err: - resp: {"error":{"message":"Unsupported get request. Object with ID '2371342336638440' does not exist, cannot be loaded due to missing permissions, or does not support this operation. Please read the Graph API documentation at https:\/\/developers.facebook.com\/docs\/graph-api","type":"GraphMethodException","code":100,"error_subcode":33,"fbtrace_id":"AN5tABn0BsuVQndLVmb32L3"}}
[2026-02-21 07:21:52] Streaming Facebook media with Authorization: https://lookaside.fbsbx.com/whatsapp_business/attachments/?mid=2371342336638440&source=webhook&ext=1769575796&hash=ARndVKdw1p0CCx3_DpTblR2aOVkb_W5mbTqGFPSOK77cjA
[2026-02-21 07:21:52] Auto-download failed for media 1217156256734654: Graph API fetch failed:
[2026-02-21 07:21:52] Graph API request to https://graph.facebook.com/v22.0/1217156256734654
[2026-02-21 07:21:52] streamRemoteFile failed: url=https://lookaside.fbsbx.com/whatsapp_business/attachments/?mid=2371342336638440&source=webhook&ext=1769575796&hash=ARndVKdw1p0CCx3_DpTblR2aOVkb_W5mbTqGFPSOK77cjA err= http=404
[2026-02-21 07:22:25] Request: GET /api/version/media-url.php?id=1217156256734654 GET:{"id":"1217156256734654"} POST:[]
[2026-02-21 07:22: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-21 07:22:26] Auto-download failed for media 1217156256734654: Graph API fetch failed:
[2026-02-21 07:22:26] Graph API request to https://graph.facebook.com/v22.0/1217156256734654
[2026-02-21 07:22:26] Graph API fetch failed for mid 2371342336638440 - http: 400 - err: - resp: {"error":{"message":"Unsupported get request. Object with ID '2371342336638440' does not exist, cannot be loaded due to missing permissions, or does not support this operation. Please read the Graph API documentation at https:\/\/developers.facebook.com\/docs\/graph-api","type":"GraphMethodException","code":100,"error_subcode":33,"fbtrace_id":"AiiaRGgmOStqG0RVrC04Gla"}}
[2026-02-21 07:22:26] Streaming Facebook media with Authorization: https://lookaside.fbsbx.com/whatsapp_business/attachments/?mid=2371342336638440&source=webhook&ext=1769575796&hash=ARndVKdw1p0CCx3_DpTblR2aOVkb_W5mbTqGFPSOK77cjA
[2026-02-21 07:22:26] streamRemoteFile failed: url=https://lookaside.fbsbx.com/whatsapp_business/attachments/?mid=2371342336638440&source=webhook&ext=1769575796&hash=ARndVKdw1p0CCx3_DpTblR2aOVkb_W5mbTqGFPSOK77cjA err= http=404
[2026-02-21 07:23:27] Request: GET /api/version/media-url.php?id=1217156256734654 GET:{"id":"1217156256734654"} POST:[]
[2026-02-21 07:23:27] 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-21 07:23:27] Graph API fetch failed for mid 2371342336638440 - http: 400 - err: - resp: {"error":{"message":"Unsupported get request. Object with ID '2371342336638440' does not exist, cannot be loaded due to missing permissions, or does not support this operation. Please read the Graph API documentation at https:\/\/developers.facebook.com\/docs\/graph-api","type":"GraphMethodException","code":100,"error_subcode":33,"fbtrace_id":"AVFGht18YvnGrGvIZP6Oil9"}}
[2026-02-21 07:23:27] Auto-download failed for media 1217156256734654: Graph API fetch failed:
[2026-02-21 07:23:27] Streaming Facebook media with Authorization: https://lookaside.fbsbx.com/whatsapp_business/attachments/?mid=2371342336638440&source=webhook&ext=1769575796&hash=ARndVKdw1p0CCx3_DpTblR2aOVkb_W5mbTqGFPSOK77cjA
[2026-02-21 07:23:27] Graph API request to https://graph.facebook.com/v22.0/1217156256734654
[2026-02-21 07:23:27] streamRemoteFile failed: url=https://lookaside.fbsbx.com/whatsapp_business/attachments/?mid=2371342336638440&source=webhook&ext=1769575796&hash=ARndVKdw1p0CCx3_DpTblR2aOVkb_W5mbTqGFPSOK77cjA err= http=404
[2026-02-21 07:23:29] Request: GET /api/version/media-url.php?id=804275938612470&download=1 GET:{"id":"804275938612470","download":"1"} POST:[]
[2026-02-21 07:23:29] Serving local file for media ID 804275938612470: /var/www/html/uploads/media_6999a3bdb40265.38760593.pdf
[2026-02-21 07:31: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-21 07:31:16] Request: GET /api/version/media-url.php?id=1217156256734654 GET:{"id":"1217156256734654"} POST:[]
[2026-02-21 07:31:16] Graph API fetch failed for mid 2371342336638440 - http: 400 - err: - resp: {"error":{"message":"Unsupported get request. Object with ID '2371342336638440' does not exist, cannot be loaded due to missing permissions, or does not support this operation. Please read the Graph API documentation at https:\/\/developers.facebook.com\/docs\/graph-api","type":"GraphMethodException","code":100,"error_subcode":33,"fbtrace_id":"AQwxMcsaRiezLVdkdElx2cK"}}
[2026-02-21 07:31:16] Streaming Facebook media with Authorization: https://lookaside.fbsbx.com/whatsapp_business/attachments/?mid=2371342336638440&source=webhook&ext=1769575796&hash=ARndVKdw1p0CCx3_DpTblR2aOVkb_W5mbTqGFPSOK77cjA
[2026-02-21 07:31:16] Auto-download failed for media 1217156256734654: Graph API fetch failed:
[2026-02-21 07:31:16] Graph API request to https://graph.facebook.com/v22.0/1217156256734654
[2026-02-21 07:31:17] streamRemoteFile failed: url=https://lookaside.fbsbx.com/whatsapp_business/attachments/?mid=2371342336638440&source=webhook&ext=1769575796&hash=ARndVKdw1p0CCx3_DpTblR2aOVkb_W5mbTqGFPSOK77cjA err= http=404
[2026-02-21 07:31:28] Request: GET /api/version/media-url.php?url=http%3A%2F%2Flocalhost%3A8080%2Fuploads%2Ffile_requests%2F2026%2F02%2Ffr_2_6999a5935b274.pdf&download=1 GET:{"url":"http:\/\/localhost:8080\/uploads\/file_requests\/2026\/02\/fr_2_6999a5935b274.pdf","download":"1"} POST:[]
[2026-02-21 07:31:28] Serving local file for URL http://localhost:8080/uploads/file_requests/2026/02/fr_2_6999a5935b274.pdf: /var/www/html/uploads/file_requests/2026/02/fr_2_6999a5935b274.pdf
[2026-02-21 07:31:40] Request: GET /api/version/media-url.php?id=1217156256734654 GET:{"id":"1217156256734654"} POST:[]
[2026-02-21 07:31:40] 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-21 07:31:40] Graph API fetch failed for mid 2371342336638440 - http: 400 - err: - resp: {"error":{"message":"Unsupported get request. Object with ID '2371342336638440' does not exist, cannot be loaded due to missing permissions, or does not support this operation. Please read the Graph API documentation at https:\/\/developers.facebook.com\/docs\/graph-api","type":"GraphMethodException","code":100,"error_subcode":33,"fbtrace_id":"AknMoavxvfmPrC4LDQE0C60"}}
[2026-02-21 07:31:40] Streaming Facebook media with Authorization: https://lookaside.fbsbx.com/whatsapp_business/attachments/?mid=2371342336638440&source=webhook&ext=1769575796&hash=ARndVKdw1p0CCx3_DpTblR2aOVkb_W5mbTqGFPSOK77cjA
[2026-02-21 07:31:40] Auto-download failed for media 1217156256734654: Graph API fetch failed:
[2026-02-21 07:31:40] Graph API request to https://graph.facebook.com/v22.0/1217156256734654
[2026-02-21 07:31:41] streamRemoteFile failed: url=https://lookaside.fbsbx.com/whatsapp_business/attachments/?mid=2371342336638440&source=webhook&ext=1769575796&hash=ARndVKdw1p0CCx3_DpTblR2aOVkb_W5mbTqGFPSOK77cjA err= http=404
[2026-02-21 07:31:44] Request: GET /api/version/media-url.php?url=http%3A%2F%2Flocalhost%3A8080%2Fuploads%2Ffile_requests%2F2026%2F02%2Ffr_2_6999a5abc6620.docx&download=1 GET:{"url":"http:\/\/localhost:8080\/uploads\/file_requests\/2026\/02\/fr_2_6999a5abc6620.docx","download":"1"} POST:[]
[2026-02-21 07:31:44] Serving local file for URL http://localhost:8080/uploads/file_requests/2026/02/fr_2_6999a5abc6620.docx: /var/www/html/uploads/file_requests/2026/02/fr_2_6999a5abc6620.docx
[2026-02-21 07:35:57] Request: GET /api/version/media-url.php?id=1217156256734654 GET:{"id":"1217156256734654"} POST:[]
[2026-02-21 07:35: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-21 07:35:57] Graph API fetch failed for mid 2371342336638440 - http: 400 - err: - resp: {"error":{"message":"Unsupported get request. Object with ID '2371342336638440' does not exist, cannot be loaded due to missing permissions, or does not support this operation. Please read the Graph API documentation at https:\/\/developers.facebook.com\/docs\/graph-api","type":"GraphMethodException","code":100,"error_subcode":33,"fbtrace_id":"A3_VKJznLWm0yO9VOYihIll"}}
[2026-02-21 07:35:57] Streaming Facebook media with Authorization: https://lookaside.fbsbx.com/whatsapp_business/attachments/?mid=2371342336638440&source=webhook&ext=1769575796&hash=ARndVKdw1p0CCx3_DpTblR2aOVkb_W5mbTqGFPSOK77cjA
[2026-02-21 07:35:57] Auto-download failed for media 1217156256734654: Graph API fetch failed:
[2026-02-21 07:35:57] Graph API request to https://graph.facebook.com/v22.0/1217156256734654
[2026-02-21 07:35:57] streamRemoteFile failed: url=https://lookaside.fbsbx.com/whatsapp_business/attachments/?mid=2371342336638440&source=webhook&ext=1769575796&hash=ARndVKdw1p0CCx3_DpTblR2aOVkb_W5mbTqGFPSOK77cjA err= http=404
+38 -12
View File
@@ -108,6 +108,18 @@ class WhatsAppWebhook {
return;
}
// Extraer nombres de contactos del payload (contacts[].profile.name)
$contactNames = [];
if (isset($value['contacts']) && is_array($value['contacts'])) {
foreach ($value['contacts'] as $contact) {
$waId = $contact['wa_id'] ?? null;
$name = $contact['profile']['name'] ?? null;
if ($waId && $name) {
$contactNames[$waId] = $name;
}
}
}
foreach ($items as $message) {
// En algunos payloads la estructura key es 'from' y 'id' (mensajes), en otros puede venir distinta; normalizamos
$phoneNumber = $message['from'] ?? ($message['wa_id'] ?? null);
@@ -131,9 +143,29 @@ class WhatsAppWebhook {
// Obtener o crear usuario
$user = $this->getUserByPhone($phoneNumber);
$contactName = $contactNames[$phoneNumber] ?? null;
if (!$user) {
$userId = $this->createUser($phoneNumber);
$user = $this->getUserById($userId);
// Guardar nombre del contacto de WhatsApp al crear usuario
if ($contactName && $userId) {
try {
$this->db->update('users', ['name' => $contactName], 'id = ? AND (name IS NULL OR name = ?)', [$userId, '']);
$user['name'] = $contactName;
} catch (Exception $e) {
error_log('[webhook] Error guardando nombre contacto: ' . $e->getMessage());
}
}
} else {
// Actualizar nombre si el usuario no tiene uno guardado
if ($contactName && (empty($user['name']))) {
try {
$this->db->update('users', ['name' => $contactName], 'id = ? AND (name IS NULL OR name = ?)', [$user['id'], '']);
$user['name'] = $contactName;
} catch (Exception $e) {
error_log('[webhook] Error actualizando nombre contacto: ' . $e->getMessage());
}
}
}
// Procesar diferentes tipos de mensaje
@@ -178,40 +210,34 @@ class WhatsAppWebhook {
} elseif (isset($message['image'])) {
$messageText = $message['image']['caption'] ?? '';
$messageType = 'image';
// Preferir URL directo si viene en el webhook (evita resolver media_id)
$mediaUrl = $message['image']['url'] ?? ($message['image']['id'] ?? null);
// Preferir media ID (estable) sobre URL (expira en minutos)
$mediaUrl = $message['image']['id'] ?? ($message['image']['url'] ?? null);
$mimeType = $message['image']['mime_type'] ?? null;
$filename = $message['image']['filename'] ?? null;
} elseif (isset($message['audio'])) {
$messageType = 'audio';
$mediaUrl = $message['audio']['url'] ?? ($message['audio']['id'] ?? null);
$mediaUrl = $message['audio']['id'] ?? ($message['audio']['url'] ?? null);
$mimeType = $message['audio']['mime_type'] ?? null;
$filename = $message['audio']['filename'] ?? null;
} elseif (isset($message['video'])) {
$messageText = $message['video']['caption'] ?? '';
$messageType = 'video';
$mediaUrl = $message['video']['url'] ?? ($message['video']['id'] ?? null);
$mediaUrl = $message['video']['id'] ?? ($message['video']['url'] ?? null);
$mimeType = $message['video']['mime_type'] ?? null;
$filename = $message['video']['filename'] ?? null;
} elseif (isset($message['document'])) {
$messageText = $message['document']['filename'] ?? '';
$messageType = 'document';
$mediaUrl = $message['document']['url'] ?? ($message['document']['id'] ?? null);
$mediaUrl = $message['document']['id'] ?? ($message['document']['url'] ?? null);
$mimeType = $message['document']['mime_type'] ?? null;
$filename = $message['document']['filename'] ?? null;
} elseif (isset($message['sticker'])) {
$messageType = 'sticker';
$mediaUrl = $message['sticker']['url'] ?? ($message['sticker']['id'] ?? null);
$mediaUrl = $message['sticker']['id'] ?? ($message['sticker']['url'] ?? null);
$mimeType = $message['sticker']['mime_type'] ?? null;
$filename = $message['sticker']['filename'] ?? null;
+38 -6
View File
@@ -127,7 +127,8 @@ class OptimizedWebhook {
foreach ($entry['changes'] as $change) {
if (isset($change['value']['messages'])) {
$this->queueMessages($change['value']['messages']);
$contacts = $change['value']['contacts'] ?? [];
$this->queueMessages($change['value']['messages'], $contacts);
}
if (isset($change['value']['statuses'])) {
@@ -156,7 +157,17 @@ class OptimizedWebhook {
/**
* Encolar mensajes para procesamiento asíncrono
*/
private function queueMessages($messages) {
private function queueMessages($messages, $contacts = []) {
// Extraer nombres de contactos del payload
$contactNames = [];
foreach ($contacts as $contact) {
$waId = $contact['wa_id'] ?? null;
$name = $contact['profile']['name'] ?? null;
if ($waId && $name) {
$contactNames[$waId] = $name;
}
}
foreach ($messages as $message) {
try {
$messageId = $message['id'] ?? null;
@@ -178,9 +189,29 @@ class OptimizedWebhook {
// Obtener o crear usuario (rápido, solo DB lookup)
$user = $this->getUserByPhone($phoneNumber);
$contactName = $contactNames[$phoneNumber] ?? null;
if (!$user) {
$userId = $this->createUser($phoneNumber);
$user = $this->getUserById($userId);
// Guardar nombre del contacto de WhatsApp
if ($contactName && $userId) {
try {
$this->db->update('users', ['name' => $contactName], 'id = ? AND (name IS NULL OR name = ? OR name = ?)', [$userId, '', $phoneNumber]);
$user['name'] = $contactName;
} catch (Exception $e) {
$this->logger->warning('Error guardando nombre contacto: ' . $e->getMessage());
}
}
} else {
// Actualizar nombre si no tiene uno real (solo tiene el teléfono o vacío)
if ($contactName && (empty($user['name']) || $user['name'] === $phoneNumber)) {
try {
$this->db->update('users', ['name' => $contactName], 'id = ? AND (name IS NULL OR name = ? OR name = ?)', [$user['id'], '', $phoneNumber]);
$user['name'] = $contactName;
} catch (Exception $e) {
$this->logger->warning('Error actualizando nombre contacto: ' . $e->getMessage());
}
}
}
// Extraer tipo y contenido del mensaje
@@ -249,7 +280,8 @@ class OptimizedWebhook {
case 'image':
$text = $message['image']['caption'] ?? '';
$mediaUrl = $message['image']['url'] ?? $message['image']['id'] ?? null;
// Preferir media ID (estable) sobre URL (expira en minutos)
$mediaUrl = $message['image']['id'] ?? $message['image']['url'] ?? null;
// Encolar descarga de media
if ($mediaUrl) {
@@ -259,7 +291,7 @@ class OptimizedWebhook {
case 'video':
$text = $message['video']['caption'] ?? '';
$mediaUrl = $message['video']['url'] ?? $message['video']['id'] ?? null;
$mediaUrl = $message['video']['id'] ?? $message['video']['url'] ?? null;
if ($mediaUrl) {
$this->queueMediaDownload($mediaUrl, $message['id'], 'video');
@@ -267,7 +299,7 @@ class OptimizedWebhook {
break;
case 'audio':
$mediaUrl = $message['audio']['url'] ?? $message['audio']['id'] ?? null;
$mediaUrl = $message['audio']['id'] ?? $message['audio']['url'] ?? null;
if ($mediaUrl) {
$this->queueMediaDownload($mediaUrl, $message['id'], 'audio');
@@ -276,7 +308,7 @@ class OptimizedWebhook {
case 'document':
$text = $message['document']['filename'] ?? '';
$mediaUrl = $message['document']['url'] ?? $message['document']['id'] ?? null;
$mediaUrl = $message['document']['id'] ?? $message['document']['url'] ?? null;
if ($mediaUrl) {
$this->queueMediaDownload($mediaUrl, $message['id'], 'document');