- Detecta header_type=IMAGE en get_template_details.php
- send_message (individual): muestra input de imagen si la plantilla lo requiere,
sube a upload_media.php y envía header_image_url al backend
- broadcast (masivo): igual, con upload previo al envío
- api/send_message.php: acepta header_image_url y lo pasa como headerParameters
[type:image, image:{link:URL}] a WhatsAppService::sendTemplateMessage()
- api/send_broadcast.php: igual para cada destinatario del masivo
- Preview en tiempo real de la imagen seleccionada en ambos formularios
264 lines
11 KiB
PHP
264 lines
11 KiB
PHP
<?php
|
|
/**
|
|
* API - Envío masivo de mensajes
|
|
* Fecha: 13 de noviembre de 2025
|
|
*/
|
|
|
|
require_once '../config/config.php';
|
|
|
|
// Verificar autenticación
|
|
requireAuthentication();
|
|
|
|
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');
|
|
|
|
if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
|
|
http_response_code(405);
|
|
echo json_encode(['error' => 'Método no permitido']);
|
|
exit;
|
|
}
|
|
|
|
try {
|
|
$input = json_decode(file_get_contents('php://input'), true);
|
|
|
|
if (!$input) {
|
|
http_response_code(400);
|
|
echo json_encode(['error' => 'Datos requeridos']);
|
|
exit;
|
|
}
|
|
|
|
$selectionType = $input['selection_type'] ?? 'filter';
|
|
$messageType = $input['message_type'] ?? 'text';
|
|
|
|
$db = Database::getInstance();
|
|
$whatsappService = new WhatsAppService();
|
|
|
|
// Obtener usuarios según tipo de selección
|
|
$users = [];
|
|
|
|
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);
|
|
}
|
|
|
|
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 = 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));
|
|
}
|
|
|
|
// Preparar header de imagen dinámica si se proporcionó
|
|
$headerParams = [];
|
|
if (!empty($input['header_image_url'])) {
|
|
$imageUrl = filter_var($input['header_image_url'], FILTER_VALIDATE_URL) ? $input['header_image_url'] : null;
|
|
if ($imageUrl) {
|
|
$headerParams = [['type' => 'image', 'image' => ['link' => $imageUrl]]];
|
|
error_log("Broadcast template header image URL: " . $imageUrl);
|
|
}
|
|
}
|
|
|
|
$response = $whatsappService->sendTemplateMessage(
|
|
$user['phone_number'],
|
|
$template['template_name'] ?? $template['name'],
|
|
$template['language_code'] ?? 'es',
|
|
$processedVariables,
|
|
$headerParams
|
|
);
|
|
|
|
// 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;
|
|
}
|
|
|
|
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());
|
|
}
|
|
}
|
|
|
|
echo json_encode([
|
|
'success' => true,
|
|
'sent_count' => $sentCount,
|
|
'error_count' => $errorCount,
|
|
'total_users' => count($users),
|
|
'selection_type' => $selectionType,
|
|
'message_type' => $messageType,
|
|
'errors' => $errorCount > 0 ? $errors : null
|
|
]);
|
|
|
|
} catch (Exception $e) {
|
|
error_log("Error in send_broadcast.php: " . $e->getMessage());
|
|
http_response_code(500);
|
|
echo json_encode(['error' => 'Error interno del servidor: ' . $e->getMessage()]);
|
|
}
|
|
?>
|