From e081dc739a666b89f364e4b7e18f573f7b46f7b9 Mon Sep 17 00:00:00 2001 From: Lizandro Guarnizo <77708265+lizandrogd@users.noreply.github.com> Date: Fri, 13 Mar 2026 13:24:22 -0500 Subject: [PATCH] =?UTF-8?q?feat:=20soporte=20de=20imagen=20din=C3=A1mica?= =?UTF-8?q?=20en=20plantillas=20WhatsApp?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 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 --- api/send_broadcast.php | 13 +++++- api/send_message.php | 12 +++++- assets/js/app_simple.js | 95 +++++++++++++++++++++++++++++++++++------ index.php | 52 +++++++++++++++++----- 4 files changed, 148 insertions(+), 24 deletions(-) diff --git a/api/send_broadcast.php b/api/send_broadcast.php index 6af18ab..80c298b 100644 --- a/api/send_broadcast.php +++ b/api/send_broadcast.php @@ -168,11 +168,22 @@ try { 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 + $processedVariables, + $headerParams ); // Construir contenido para BD (reemplazar variables en body_text) diff --git a/api/send_message.php b/api/send_message.php index 2c4da67..27b9180 100644 --- a/api/send_message.php +++ b/api/send_message.php @@ -260,9 +260,19 @@ try { error_log("Method: sendTemplateMessage()"); error_log("========================================================"); + // 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("Template header image URL: " . $imageUrl); + } + } + // Intentar envío y, si falla por traducción no encontrada, probar variantes de idioma try { - $response = $whatsappService->sendTemplateMessage($recipient, $template, $language, $parameters, [], $componentsSimple); + $response = $whatsappService->sendTemplateMessage($recipient, $template, $language, $parameters, $headerParams, $componentsSimple); } catch (Exception $e) { $message = $e->getMessage(); diff --git a/assets/js/app_simple.js b/assets/js/app_simple.js index cbd876c..b95b1cf 100644 --- a/assets/js/app_simple.js +++ b/assets/js/app_simple.js @@ -1558,11 +1558,25 @@ class SimpleWhatsAppManager { } } + // Subir imagen de header si el usuario seleccionó una + let headerImageUrl = null; + const msgImgInput = document.getElementById('message-template-image'); + if (msgImgInput && msgImgInput.files && msgImgInput.files[0]) { + try { + this.showInfo('Subiendo imagen de la plantilla...'); + headerImageUrl = await this._uploadTemplateImage(msgImgInput); + } catch (e) { + this.showError('Error subiendo imagen de la plantilla: ' + e.message); + return; + } + } + messageData = { recipient: recipient, type: 'template', template_name: templateName, // Usar template_name en lugar de ID - parameters: tplParams + parameters: tplParams, + ...(headerImageUrl ? { header_image_url: headerImageUrl } : {}) }; } @@ -1935,6 +1949,18 @@ class SimpleWhatsAppManager { return; } + // Subir imagen de header broadcast si existe + const bcImgInput = document.getElementById('broadcast-template-image'); + if (bcImgInput && bcImgInput.files && bcImgInput.files[0]) { + try { + this.showInfo('Subiendo imagen de la plantilla...'); + payload.header_image_url = await this._uploadTemplateImage(bcImgInput); + } catch (e) { + this.showError('Error subiendo imagen de la plantilla: ' + e.message); + return; + } + } + try { this.showInfo('Enviando mensajes masivos...'); const result = await this.apiCall('send_broadcast.php', { @@ -1956,6 +1982,19 @@ class SimpleWhatsAppManager { } } + // Sube una imagen al servidor y retorna la URL pública + async _uploadTemplateImage(fileInput) { + const file = fileInput.files[0]; + if (!file) return null; + const fd = new FormData(); + fd.append('file', file); + const resp = await fetch('./api/upload_media.php', { method: 'POST', body: fd }); + if (!resp.ok) throw new Error(`HTTP ${resp.status}`); + const data = await resp.json(); + if (data.success && data.public_url) return data.public_url; + throw new Error(data.error || 'No se pudo subir la imagen'); + } + // Gestión de usuarios centralizada en lab_usuarios.php async loadAdminUsers() { // No-op: la UI redirige a lab_usuarios.php @@ -3184,22 +3223,53 @@ window.loadMessageTemplateDetails = async function() { console.log('📋 Variables:', template.variables); const variablesContainer = document.getElementById('message-template-variables'); - + + // Detectar header de imagen + const hasImageHeader = (template.header_type || '').toUpperCase() === 'IMAGE'; + let html = ''; + + if (hasImageHeader) { + html += ` +
+ +

Esta plantilla requiere una imagen de encabezado dinámica. Súbela aquí.

+ + +
+ `; + setTimeout(() => { + const imgInput = document.getElementById('message-template-image'); + if (imgInput) imgInput.addEventListener('change', function() { + const file = this.files[0]; + if (file) { + const reader = new FileReader(); + reader.onload = e => { + document.getElementById('msg-image-thumb').src = e.target.result; + document.getElementById('msg-image-preview').style.display = 'block'; + }; + reader.readAsDataURL(file); + } + }); + }, 50); + } + if (template.variables && template.variables.length > 0) { console.log(`✅ Se encontraron ${template.variables.length} variables`); - let html = ''; - + html += ``; + template.variables.forEach((variable, index) => { const varLabel = variable.label || variable.name || `Variable ${variable.index || index + 1}`; const placeholder = variable.placeholder || `{{${variable.index || index + 1}}}`; const example = variable.example || ''; - + html += `
- `; }); - + console.log('📝 HTML generado para variables:', html); variablesContainer.innerHTML = html; variablesContainer.style.display = 'block'; console.log('✅ Variables container mostrado'); } else { - console.log('⚠️ Template sin variables'); - variablesContainer.innerHTML = '

Esta plantilla no tiene variables

'; - variablesContainer.style.display = 'block'; + if (!hasImageHeader) html += '

Esta plantilla no tiene variables de texto

'; + console.log('⚠️ Template sin variables de texto'); + variablesContainer.innerHTML = html; + variablesContainer.style.display = (hasImageHeader || html) ? 'block' : 'none'; } updateMessagePreview(); diff --git a/index.php b/index.php index 17e2833..935bf9d 100644 --- a/index.php +++ b/index.php @@ -1977,22 +1977,53 @@ try { console.log('📋 Variables:', template.variables); const variablesContainer = document.getElementById('broadcast-template-variables'); - + + // Detectar header de imagen + const hasImageHeader = (template.header_type || '').toUpperCase() === 'IMAGE'; + let html = ''; + + if (hasImageHeader) { + html += ` +
+ +

Esta plantilla requiere una imagen de encabezado dinámica. Súbela aquí.

+ + +
+ `; + setTimeout(() => { + const imgInput = document.getElementById('broadcast-template-image'); + if (imgInput) imgInput.addEventListener('change', function() { + const file = this.files[0]; + if (file) { + const reader = new FileReader(); + reader.onload = e => { + document.getElementById('bc-image-thumb').src = e.target.result; + document.getElementById('bc-image-preview').style.display = 'block'; + }; + reader.readAsDataURL(file); + } + }); + }, 50); + } + if (template.variables && template.variables.length > 0) { console.log(`✅ Se encontraron ${template.variables.length} variables`); - let html = ''; - + html += ``; + template.variables.forEach((variable, index) => { console.log(`Variable ${index}:`, variable); const placeholder = variable.example || `Valor para Variable ${variable.index}`; const label = variable.label || `Variable ${variable.index}`; - + html += `
- `; }); - + console.log('📝 HTML generado para variables:', html); variablesContainer.innerHTML = html; variablesContainer.style.display = 'block'; console.log('✅ Variables container mostrado'); } else { + if (!hasImageHeader) html += '

Esta plantilla no tiene variables de texto

'; console.log('⚠️ Template sin variables'); - variablesContainer.innerHTML = '

Esta plantilla no tiene variables

'; - variablesContainer.style.display = 'block'; + variablesContainer.innerHTML = html; + variablesContainer.style.display = (hasImageHeader || html) ? 'block' : 'none'; } updateBroadcastPreview();