feat: perfil WhatsApp (foto, about, datos) y reset+sync de plantillas
This commit is contained in:
@@ -0,0 +1,60 @@
|
|||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* API - Obtener perfil de WhatsApp Business
|
||||||
|
* Consulta la información del perfil desde la API de WhatsApp Cloud
|
||||||
|
*/
|
||||||
|
|
||||||
|
require_once '../config/config.php';
|
||||||
|
|
||||||
|
header('Content-Type: application/json; charset=utf-8');
|
||||||
|
|
||||||
|
requireAuthentication();
|
||||||
|
|
||||||
|
try {
|
||||||
|
$config = getWhatsAppConfigFromDB();
|
||||||
|
$token = $config['token'] ?? '';
|
||||||
|
$phoneId = $config['phone_number_id'] ?? '';
|
||||||
|
$apiUrl = rtrim($config['api_url'] ?: 'https://graph.facebook.com/v22.0/', '/');
|
||||||
|
|
||||||
|
if (empty($token) || empty($phoneId)) {
|
||||||
|
echo json_encode(['success' => false, 'error' => 'Token o Phone Number ID no configurado']);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
$url = "{$apiUrl}/{$phoneId}/whatsapp_business_profile?fields=about,address,description,email,profile_picture_url,websites,vertical";
|
||||||
|
|
||||||
|
$ch = curl_init();
|
||||||
|
curl_setopt_array($ch, [
|
||||||
|
CURLOPT_URL => $url,
|
||||||
|
CURLOPT_RETURNTRANSFER => true,
|
||||||
|
CURLOPT_HTTPHEADER => ["Authorization: Bearer {$token}"],
|
||||||
|
CURLOPT_TIMEOUT => 20,
|
||||||
|
CURLOPT_IPRESOLVE => CURL_IPRESOLVE_V4,
|
||||||
|
CURLOPT_SSLVERSION => CURL_SSLVERSION_TLSv1_2,
|
||||||
|
]);
|
||||||
|
|
||||||
|
$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}");
|
||||||
|
}
|
||||||
|
|
||||||
|
$data = json_decode($response, true);
|
||||||
|
|
||||||
|
if ($httpCode !== 200) {
|
||||||
|
$msg = $data['error']['message'] ?? 'Error desconocido';
|
||||||
|
throw new Exception("API error ({$httpCode}): {$msg}");
|
||||||
|
}
|
||||||
|
|
||||||
|
// La API devuelve { data: [ {...profile} ] }
|
||||||
|
$profile = isset($data['data'][0]) ? $data['data'][0] : $data;
|
||||||
|
|
||||||
|
echo json_encode(['success' => true, 'data' => $profile]);
|
||||||
|
|
||||||
|
} catch (Exception $e) {
|
||||||
|
error_log('[get_whatsapp_profile] ' . $e->getMessage());
|
||||||
|
echo json_encode(['success' => false, 'error' => $e->getMessage()]);
|
||||||
|
}
|
||||||
@@ -0,0 +1,177 @@
|
|||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* API - Eliminar todas las plantillas locales y re-sincronizar desde WhatsApp/Facebook
|
||||||
|
* Borra la tabla local y vuelve a importar todo desde la API de Meta
|
||||||
|
*/
|
||||||
|
|
||||||
|
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();
|
||||||
|
|
||||||
|
if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
|
||||||
|
echo json_encode(['success' => false, 'error' => 'Método no permitido']);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
$db = Database::getInstance();
|
||||||
|
|
||||||
|
// Obtener configuración de WhatsApp
|
||||||
|
$token = getConfigFromDB('whatsapp_token', '');
|
||||||
|
$wabaId = getConfigFromDB('whatsapp_business_account_id', '');
|
||||||
|
$config = getWhatsAppConfigFromDB();
|
||||||
|
$apiUrl = rtrim($config['api_url'] ?: 'https://graph.facebook.com/v22.0/', '/');
|
||||||
|
|
||||||
|
if (empty($token)) {
|
||||||
|
throw new Exception('Token de WhatsApp no configurado');
|
||||||
|
}
|
||||||
|
if (empty($wabaId)) {
|
||||||
|
throw new Exception('Business Account ID no configurado. Configúrelo en Configuración del Sistema.');
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── Paso 1: Obtener plantillas desde Facebook ─────────────────────────────
|
||||||
|
$url = "{$apiUrl}/{$wabaId}/message_templates";
|
||||||
|
|
||||||
|
$ch = curl_init();
|
||||||
|
curl_setopt_array($ch, [
|
||||||
|
CURLOPT_URL => $url,
|
||||||
|
CURLOPT_RETURNTRANSFER => true,
|
||||||
|
CURLOPT_HTTPHEADER => ["Authorization: Bearer {$token}", "Content-Type: application/json"],
|
||||||
|
CURLOPT_TIMEOUT => 30,
|
||||||
|
CURLOPT_IPRESOLVE => CURL_IPRESOLVE_V4,
|
||||||
|
CURLOPT_SSLVERSION => CURL_SSLVERSION_TLSv1_2,
|
||||||
|
]);
|
||||||
|
|
||||||
|
$response = curl_exec($ch);
|
||||||
|
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
|
||||||
|
$curlErr = curl_error($ch);
|
||||||
|
curl_close($ch);
|
||||||
|
|
||||||
|
if ($curlErr) {
|
||||||
|
throw new Exception("Error de conexión: {$curlErr}");
|
||||||
|
}
|
||||||
|
if ($httpCode !== 200) {
|
||||||
|
$errData = json_decode($response, true);
|
||||||
|
$errMsg = $errData['error']['message'] ?? 'Error desconocido';
|
||||||
|
throw new Exception("Error de API ({$httpCode}): {$errMsg}");
|
||||||
|
}
|
||||||
|
|
||||||
|
$data = json_decode($response, true);
|
||||||
|
if (!isset($data['data']) || !is_array($data['data'])) {
|
||||||
|
throw new Exception('Respuesta inválida de la API de WhatsApp');
|
||||||
|
}
|
||||||
|
|
||||||
|
$templates = $data['data'];
|
||||||
|
$totalFromFacebook = count($templates);
|
||||||
|
|
||||||
|
// ── Paso 2: Eliminar TODAS las plantillas locales ─────────────────────────
|
||||||
|
$deletedCount = $db->query("DELETE FROM message_templates");
|
||||||
|
error_log("[reset_and_sync_templates] Eliminadas todas las plantillas locales. Iniciando re-import de {$totalFromFacebook} plantillas.");
|
||||||
|
|
||||||
|
// ── Paso 3: Insertar todas las plantillas desde Facebook ──────────────────
|
||||||
|
$syncedCount = 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)) continue;
|
||||||
|
|
||||||
|
// Extraer campos de componentes
|
||||||
|
$bodyText = null;
|
||||||
|
$headerText = null;
|
||||||
|
$headerType = null;
|
||||||
|
$footerText = null;
|
||||||
|
$exampleParameters = [];
|
||||||
|
|
||||||
|
foreach ($components as $comp) {
|
||||||
|
$type = $comp['type'] ?? '';
|
||||||
|
if ($type === 'BODY') {
|
||||||
|
$bodyText = $comp['text'] ?? null;
|
||||||
|
if (isset($comp['example']['body_text'])) {
|
||||||
|
$exampleParameters['body'] = $comp['example']['body_text'];
|
||||||
|
}
|
||||||
|
} elseif ($type === 'HEADER') {
|
||||||
|
$headerText = $comp['text'] ?? null;
|
||||||
|
$headerType = strtolower($comp['format'] ?? 'text');
|
||||||
|
if (isset($comp['example']['header_text'])) {
|
||||||
|
$exampleParameters['header'] = $comp['example']['header_text'];
|
||||||
|
}
|
||||||
|
} elseif ($type === 'FOOTER') {
|
||||||
|
$footerText = $comp['text'] ?? null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Extraer variables automáticamente del body
|
||||||
|
$variables = [];
|
||||||
|
if ($bodyText) {
|
||||||
|
preg_match_all('/\{\{([^\}]+)\}\}/', $bodyText, $matches);
|
||||||
|
if (!empty($matches[1])) {
|
||||||
|
$index = 1;
|
||||||
|
foreach (array_unique($matches[1]) as $varName) {
|
||||||
|
$varIndex = is_numeric($varName) ? (int)$varName : $index;
|
||||||
|
$exData = $exampleParameters['body'][$varIndex - 1] ?? null;
|
||||||
|
$example = is_array($exData) ? $exData[0] : $exData;
|
||||||
|
$variables[] = [
|
||||||
|
'index' => $varIndex,
|
||||||
|
'placeholder' => "{{" . $varName . "}}",
|
||||||
|
'name' => $varName,
|
||||||
|
'example' => $example,
|
||||||
|
];
|
||||||
|
if (!is_numeric($varName)) $index++;
|
||||||
|
}
|
||||||
|
$exampleParameters['variables'] = $variables;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$componentsJson = !empty($components) ? json_encode($components, JSON_UNESCAPED_UNICODE) : null;
|
||||||
|
$exampleJson = !empty($exampleParameters) ? json_encode($exampleParameters, JSON_UNESCAPED_UNICODE) : null;
|
||||||
|
|
||||||
|
$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, $templateName,
|
||||||
|
$language, strtolower($category), strtolower($status),
|
||||||
|
$bodyText, $headerText, $headerType, $footerText,
|
||||||
|
$componentsJson, $exampleJson,
|
||||||
|
]
|
||||||
|
);
|
||||||
|
|
||||||
|
$syncedCount++;
|
||||||
|
} catch (Exception $e) {
|
||||||
|
$errors[] = "Error con '{$templateName}': " . $e->getMessage();
|
||||||
|
error_log("[reset_and_sync_templates] " . end($errors));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
writeLog('INFO', "Reset+Sync plantillas: {$syncedCount}/{$totalFromFacebook} importadas, " . count($errors) . " errores");
|
||||||
|
|
||||||
|
echo json_encode([
|
||||||
|
'success' => true,
|
||||||
|
'message' => "Re-sincronización completada: {$syncedCount} plantillas importadas",
|
||||||
|
'data' => [
|
||||||
|
'total_facebook' => $totalFromFacebook,
|
||||||
|
'deleted_local' => $deletedCount,
|
||||||
|
'imported' => $syncedCount,
|
||||||
|
'errors' => $errors,
|
||||||
|
],
|
||||||
|
]);
|
||||||
|
|
||||||
|
} catch (Exception $e) {
|
||||||
|
error_log('[reset_and_sync_templates] ' . $e->getMessage());
|
||||||
|
echo json_encode(['success' => false, 'error' => $e->getMessage()]);
|
||||||
|
}
|
||||||
@@ -0,0 +1,87 @@
|
|||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* API - Actualizar perfil de WhatsApp Business
|
||||||
|
* Actualiza about, description, address, email, websites
|
||||||
|
*/
|
||||||
|
|
||||||
|
require_once '../config/config.php';
|
||||||
|
|
||||||
|
header('Content-Type: application/json; charset=utf-8');
|
||||||
|
|
||||||
|
requireAuthentication();
|
||||||
|
|
||||||
|
if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
|
||||||
|
echo json_encode(['success' => false, 'error' => 'Método no permitido']);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
$input = json_decode(file_get_contents('php://input'), true) ?? $_POST;
|
||||||
|
|
||||||
|
try {
|
||||||
|
$config = getWhatsAppConfigFromDB();
|
||||||
|
$token = $config['token'] ?? '';
|
||||||
|
$phoneId = $config['phone_number_id'] ?? '';
|
||||||
|
$apiUrl = rtrim($config['api_url'] ?: 'https://graph.facebook.com/v22.0/', '/');
|
||||||
|
|
||||||
|
if (empty($token) || empty($phoneId)) {
|
||||||
|
echo json_encode(['success' => false, 'error' => 'Token o Phone Number ID no configurado']);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Construir payload solo con campos presentes y no vacíos
|
||||||
|
$payload = ['messaging_product' => 'whatsapp'];
|
||||||
|
|
||||||
|
$allowed = ['about', 'address', 'description', 'email', 'vertical'];
|
||||||
|
foreach ($allowed as $field) {
|
||||||
|
if (isset($input[$field])) {
|
||||||
|
$payload[$field] = trim($input[$field]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// websites es un array
|
||||||
|
if (!empty($input['websites'])) {
|
||||||
|
$websites = array_values(array_filter(array_map('trim', (array)$input['websites'])));
|
||||||
|
if (!empty($websites)) {
|
||||||
|
$payload['websites'] = $websites;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$url = "{$apiUrl}/{$phoneId}/whatsapp_business_profile";
|
||||||
|
|
||||||
|
$ch = curl_init();
|
||||||
|
curl_setopt_array($ch, [
|
||||||
|
CURLOPT_URL => $url,
|
||||||
|
CURLOPT_RETURNTRANSFER => true,
|
||||||
|
CURLOPT_POST => true,
|
||||||
|
CURLOPT_POSTFIELDS => json_encode($payload),
|
||||||
|
CURLOPT_HTTPHEADER => [
|
||||||
|
"Authorization: Bearer {$token}",
|
||||||
|
"Content-Type: application/json",
|
||||||
|
],
|
||||||
|
CURLOPT_TIMEOUT => 20,
|
||||||
|
CURLOPT_IPRESOLVE => CURL_IPRESOLVE_V4,
|
||||||
|
CURLOPT_SSLVERSION => CURL_SSLVERSION_TLSv1_2,
|
||||||
|
]);
|
||||||
|
|
||||||
|
$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}");
|
||||||
|
}
|
||||||
|
|
||||||
|
$data = json_decode($response, true);
|
||||||
|
|
||||||
|
if ($httpCode !== 200) {
|
||||||
|
$msg = $data['error']['message'] ?? 'Error desconocido';
|
||||||
|
throw new Exception("API error ({$httpCode}): {$msg}");
|
||||||
|
}
|
||||||
|
|
||||||
|
echo json_encode(['success' => true, 'message' => 'Perfil actualizado correctamente']);
|
||||||
|
|
||||||
|
} catch (Exception $e) {
|
||||||
|
error_log('[update_whatsapp_profile] ' . $e->getMessage());
|
||||||
|
echo json_encode(['success' => false, 'error' => $e->getMessage()]);
|
||||||
|
}
|
||||||
@@ -0,0 +1,134 @@
|
|||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* API - Subir foto de perfil de WhatsApp Business
|
||||||
|
* 1. Sube la imagen como media a la API de WhatsApp
|
||||||
|
* 2. Usa el media_handle obtenido para actualizar la foto de perfil
|
||||||
|
*/
|
||||||
|
|
||||||
|
require_once '../config/config.php';
|
||||||
|
|
||||||
|
header('Content-Type: application/json; charset=utf-8');
|
||||||
|
|
||||||
|
requireAuthentication();
|
||||||
|
|
||||||
|
if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
|
||||||
|
echo json_encode(['success' => false, 'error' => 'Método no permitido']);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (empty($_FILES['photo']) || $_FILES['photo']['error'] !== UPLOAD_ERR_OK) {
|
||||||
|
$code = $_FILES['photo']['error'] ?? -1;
|
||||||
|
echo json_encode(['success' => false, 'error' => "No se recibió archivo válido (código {$code})"]);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
$file = $_FILES['photo'];
|
||||||
|
|
||||||
|
// Validar tipo MIME
|
||||||
|
$allowedMimes = ['image/jpeg', 'image/png'];
|
||||||
|
$finfo = new finfo(FILEINFO_MIME_TYPE);
|
||||||
|
$mime = $finfo->file($file['tmp_name']);
|
||||||
|
if (!in_array($mime, $allowedMimes, true)) {
|
||||||
|
echo json_encode(['success' => false, 'error' => 'Solo se permiten imágenes JPG o PNG']);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Validar tamaño (máx 5 MB)
|
||||||
|
if ($file['size'] > 5 * 1024 * 1024) {
|
||||||
|
echo json_encode(['success' => false, 'error' => 'La imagen no puede superar 5 MB']);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
$config = getWhatsAppConfigFromDB();
|
||||||
|
$token = $config['token'] ?? '';
|
||||||
|
$phoneId = $config['phone_number_id'] ?? '';
|
||||||
|
$apiUrl = rtrim($config['api_url'] ?: 'https://graph.facebook.com/v22.0/', '/');
|
||||||
|
|
||||||
|
if (empty($token) || empty($phoneId)) {
|
||||||
|
echo json_encode(['success' => false, 'error' => 'Token o Phone Number ID no configurado']);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── Paso 1: subir la imagen como media ───────────────────────────────────
|
||||||
|
$uploadUrl = "{$apiUrl}/{$phoneId}/media";
|
||||||
|
|
||||||
|
$cfile = new CURLFile($file['tmp_name'], $mime, basename($file['name']));
|
||||||
|
|
||||||
|
$ch = curl_init();
|
||||||
|
curl_setopt_array($ch, [
|
||||||
|
CURLOPT_URL => $uploadUrl,
|
||||||
|
CURLOPT_RETURNTRANSFER => true,
|
||||||
|
CURLOPT_POST => true,
|
||||||
|
CURLOPT_POSTFIELDS => [
|
||||||
|
'messaging_product' => 'whatsapp',
|
||||||
|
'file' => $cfile,
|
||||||
|
'type' => $mime,
|
||||||
|
],
|
||||||
|
CURLOPT_HTTPHEADER => ["Authorization: Bearer {$token}"],
|
||||||
|
CURLOPT_TIMEOUT => 30,
|
||||||
|
CURLOPT_IPRESOLVE => CURL_IPRESOLVE_V4,
|
||||||
|
CURLOPT_SSLVERSION => CURL_SSLVERSION_TLSv1_2,
|
||||||
|
]);
|
||||||
|
|
||||||
|
$uploadResponse = curl_exec($ch);
|
||||||
|
$uploadCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
|
||||||
|
$uploadError = curl_error($ch);
|
||||||
|
curl_close($ch);
|
||||||
|
|
||||||
|
if ($uploadError) {
|
||||||
|
throw new Exception("Error al subir imagen: {$uploadError}");
|
||||||
|
}
|
||||||
|
|
||||||
|
$uploadData = json_decode($uploadResponse, true);
|
||||||
|
if ($uploadCode !== 200 || empty($uploadData['id'])) {
|
||||||
|
$msg = $uploadData['error']['message'] ?? 'Error desconocido al subir';
|
||||||
|
throw new Exception("Upload error ({$uploadCode}): {$msg}");
|
||||||
|
}
|
||||||
|
|
||||||
|
$mediaHandle = $uploadData['id'];
|
||||||
|
|
||||||
|
// ── Paso 2: actualizar la foto de perfil con el handle ───────────────────
|
||||||
|
$profileUrl = "{$apiUrl}/{$phoneId}/whatsapp_business_profile";
|
||||||
|
|
||||||
|
$payload = [
|
||||||
|
'messaging_product' => 'whatsapp',
|
||||||
|
'profile_picture_handle' => $mediaHandle,
|
||||||
|
];
|
||||||
|
|
||||||
|
$ch2 = curl_init();
|
||||||
|
curl_setopt_array($ch2, [
|
||||||
|
CURLOPT_URL => $profileUrl,
|
||||||
|
CURLOPT_RETURNTRANSFER => true,
|
||||||
|
CURLOPT_POST => true,
|
||||||
|
CURLOPT_POSTFIELDS => json_encode($payload),
|
||||||
|
CURLOPT_HTTPHEADER => [
|
||||||
|
"Authorization: Bearer {$token}",
|
||||||
|
"Content-Type: application/json",
|
||||||
|
],
|
||||||
|
CURLOPT_TIMEOUT => 20,
|
||||||
|
CURLOPT_IPRESOLVE => CURL_IPRESOLVE_V4,
|
||||||
|
CURLOPT_SSLVERSION => CURL_SSLVERSION_TLSv1_2,
|
||||||
|
]);
|
||||||
|
|
||||||
|
$profileResponse = curl_exec($ch2);
|
||||||
|
$profileCode = curl_getinfo($ch2, CURLINFO_HTTP_CODE);
|
||||||
|
$profileError = curl_error($ch2);
|
||||||
|
curl_close($ch2);
|
||||||
|
|
||||||
|
if ($profileError) {
|
||||||
|
throw new Exception("Error al actualizar foto: {$profileError}");
|
||||||
|
}
|
||||||
|
|
||||||
|
$profileData = json_decode($profileResponse, true);
|
||||||
|
if ($profileCode !== 200) {
|
||||||
|
$msg = $profileData['error']['message'] ?? 'Error desconocido al actualizar foto';
|
||||||
|
throw new Exception("Profile photo error ({$profileCode}): {$msg}");
|
||||||
|
}
|
||||||
|
|
||||||
|
echo json_encode(['success' => true, 'message' => 'Foto de perfil actualizada correctamente']);
|
||||||
|
|
||||||
|
} catch (Exception $e) {
|
||||||
|
error_log('[upload_whatsapp_profile_photo] ' . $e->getMessage());
|
||||||
|
echo json_encode(['success' => false, 'error' => $e->getMessage()]);
|
||||||
|
}
|
||||||
@@ -117,6 +117,11 @@ class SimpleWhatsAppManager {
|
|||||||
case 'templates':
|
case 'templates':
|
||||||
this.loadTemplates();
|
this.loadTemplates();
|
||||||
break;
|
break;
|
||||||
|
case 'whatsapp_profile':
|
||||||
|
if (typeof window.loadWhatsAppProfile === 'function') {
|
||||||
|
window.loadWhatsAppProfile();
|
||||||
|
}
|
||||||
|
break;
|
||||||
case 'broadcast':
|
case 'broadcast':
|
||||||
// Recargar plantillas cuando se abre el tab de broadcast
|
// Recargar plantillas cuando se abre el tab de broadcast
|
||||||
if (typeof loadBroadcastTemplates === 'function') {
|
if (typeof loadBroadcastTemplates === 'function') {
|
||||||
@@ -5098,4 +5103,160 @@ window.toggleAdminUserStatus = async function(userId, isActive) {
|
|||||||
console.error(`Error ${action}ando usuario:`, error);
|
console.error(`Error ${action}ando usuario:`, error);
|
||||||
alert(`Error al ${action} usuario: ` + error.message);
|
alert(`Error al ${action} usuario: ` + error.message);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ─────────────────────────────────────────────────────────────────────────────
|
||||||
|
// PERFIL DE WHATSAPP BUSINESS
|
||||||
|
// ─────────────────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
window.loadWhatsAppProfile = async function() {
|
||||||
|
if (!window.whatsappManager) return;
|
||||||
|
window.whatsappManager.showInfo('Cargando perfil desde WhatsApp...');
|
||||||
|
try {
|
||||||
|
const res = await window.whatsappManager.apiCall('get_whatsapp_profile.php');
|
||||||
|
if (!res || !res.success) {
|
||||||
|
window.whatsappManager.showError(res?.error || 'Error al obtener el perfil');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const p = res.data || {};
|
||||||
|
|
||||||
|
document.getElementById('wp-about').value = p.about || '';
|
||||||
|
document.getElementById('wp-description').value = p.description || '';
|
||||||
|
document.getElementById('wp-address').value = p.address || '';
|
||||||
|
document.getElementById('wp-email').value = p.email || '';
|
||||||
|
document.getElementById('wp-website').value = (p.websites && p.websites[0]) ? p.websites[0] : '';
|
||||||
|
|
||||||
|
const vertSel = document.getElementById('wp-vertical');
|
||||||
|
if (vertSel && p.vertical) {
|
||||||
|
vertSel.value = p.vertical;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Foto de perfil
|
||||||
|
const img = document.getElementById('wp-profile-photo-preview');
|
||||||
|
const placeholder = document.getElementById('wp-profile-photo-placeholder');
|
||||||
|
if (p.profile_picture_url && img) {
|
||||||
|
img.src = p.profile_picture_url;
|
||||||
|
img.style.display = 'inline-block';
|
||||||
|
if (placeholder) placeholder.style.display = 'none';
|
||||||
|
}
|
||||||
|
|
||||||
|
window.whatsappManager.showSuccess('Perfil cargado correctamente');
|
||||||
|
} catch (e) {
|
||||||
|
window.whatsappManager.showError('Error: ' + e.message);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
window.saveWhatsAppProfile = async function() {
|
||||||
|
if (!window.whatsappManager) return;
|
||||||
|
|
||||||
|
const payload = {
|
||||||
|
about: document.getElementById('wp-about')?.value?.trim() || '',
|
||||||
|
description: document.getElementById('wp-description')?.value?.trim() || '',
|
||||||
|
address: document.getElementById('wp-address')?.value?.trim() || '',
|
||||||
|
email: document.getElementById('wp-email')?.value?.trim() || '',
|
||||||
|
vertical: document.getElementById('wp-vertical')?.value || '',
|
||||||
|
websites: [document.getElementById('wp-website')?.value?.trim()].filter(Boolean),
|
||||||
|
};
|
||||||
|
|
||||||
|
try {
|
||||||
|
window.whatsappManager.showInfo('Guardando perfil...');
|
||||||
|
const res = await window.whatsappManager.apiCall('update_whatsapp_profile.php', {
|
||||||
|
method: 'POST',
|
||||||
|
body: payload,
|
||||||
|
});
|
||||||
|
if (res && res.success) {
|
||||||
|
window.whatsappManager.showSuccess(res.message || 'Perfil actualizado correctamente');
|
||||||
|
} else {
|
||||||
|
window.whatsappManager.showError(res?.error || 'Error al guardar el perfil');
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
window.whatsappManager.showError('Error: ' + e.message);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
window.uploadWhatsAppProfilePhoto = async function() {
|
||||||
|
const fileInput = document.getElementById('wp-photo-file');
|
||||||
|
if (!fileInput || !fileInput.files || fileInput.files.length === 0) {
|
||||||
|
alert('Selecciona una foto primero');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const formData = new FormData();
|
||||||
|
formData.append('photo', fileInput.files[0]);
|
||||||
|
|
||||||
|
try {
|
||||||
|
window.whatsappManager.showInfo('Subiendo foto de perfil...');
|
||||||
|
const baseUrl = window.whatsappManager.baseUrl || 'api/';
|
||||||
|
const response = await fetch(baseUrl + 'upload_whatsapp_profile_photo.php', {
|
||||||
|
method: 'POST',
|
||||||
|
body: formData,
|
||||||
|
credentials: 'same-origin',
|
||||||
|
});
|
||||||
|
|
||||||
|
const res = await response.json();
|
||||||
|
if (res && res.success) {
|
||||||
|
window.whatsappManager.showSuccess(res.message || 'Foto actualizada correctamente');
|
||||||
|
// Actualizar preview local
|
||||||
|
const reader = new FileReader();
|
||||||
|
reader.onload = (e) => {
|
||||||
|
const img = document.getElementById('wp-profile-photo-preview');
|
||||||
|
const ph = document.getElementById('wp-profile-photo-placeholder');
|
||||||
|
if (img) { img.src = e.target.result; img.style.display = 'inline-block'; }
|
||||||
|
if (ph) { ph.style.display = 'none'; }
|
||||||
|
};
|
||||||
|
reader.readAsDataURL(fileInput.files[0]);
|
||||||
|
fileInput.value = '';
|
||||||
|
} else {
|
||||||
|
window.whatsappManager.showError(res?.error || 'Error al subir la foto');
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
window.whatsappManager.showError('Error: ' + e.message);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Cargar perfil al abrir la pestaña
|
||||||
|
document.addEventListener('DOMContentLoaded', () => {
|
||||||
|
const profileLinks = document.querySelectorAll('[data-tab="whatsapp_profile"]');
|
||||||
|
profileLinks.forEach(link => {
|
||||||
|
link.addEventListener('click', () => {
|
||||||
|
// Pequeño delay para que el tab esté visible
|
||||||
|
setTimeout(() => window.loadWhatsAppProfile && window.loadWhatsAppProfile(), 100);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// ─────────────────────────────────────────────────────────────────────────────
|
||||||
|
// RESET + RE-SINCRONIZACIÓN DE PLANTILLAS
|
||||||
|
// ─────────────────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
window.resetAndSyncTemplates = async function() {
|
||||||
|
if (!window.whatsappManager) { alert('Error: Sistema no inicializado'); return; }
|
||||||
|
|
||||||
|
const confirmed = confirm(
|
||||||
|
'⚠️ ATENCIÓN: Esta acción eliminará TODAS las plantillas locales y las volverá a importar desde WhatsApp/Facebook.\n\n' +
|
||||||
|
'¿Estás seguro de que deseas continuar?'
|
||||||
|
);
|
||||||
|
if (!confirmed) return;
|
||||||
|
|
||||||
|
const btn = document.getElementById('btn-reset-sync-templates');
|
||||||
|
if (btn) { btn.disabled = true; btn.innerHTML = '<i class="fas fa-spinner fa-spin"></i> Procesando...'; }
|
||||||
|
|
||||||
|
try {
|
||||||
|
window.whatsappManager.showInfo('Eliminando plantillas locales y re-sincronizando desde WhatsApp...');
|
||||||
|
|
||||||
|
const response = await window.whatsappManager.apiCall('reset_and_sync_templates.php', { method: 'POST' });
|
||||||
|
|
||||||
|
if (response && response.success) {
|
||||||
|
const d = response.data || {};
|
||||||
|
const msg = `✅ ${response.message}\n• Recibidas de Facebook: ${d.total_facebook || 0}\n• Importadas: ${d.imported || 0}${d.errors?.length ? '\n⚠️ ' + d.errors.length + ' errores' : ''}`;
|
||||||
|
window.whatsappManager.showSuccess(msg);
|
||||||
|
if (window.whatsappManager.loadTemplates) window.whatsappManager.loadTemplates();
|
||||||
|
} else {
|
||||||
|
window.whatsappManager.showError(response?.error || 'Error desconocido al re-sincronizar');
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
window.whatsappManager.showError('Error de conexión: ' + e.message);
|
||||||
|
} finally {
|
||||||
|
if (btn) { btn.disabled = false; btn.innerHTML = '<i class="fas fa-trash-restore"></i> Limpiar y Re-sincronizar'; }
|
||||||
|
}
|
||||||
|
};
|
||||||
};
|
};
|
||||||
@@ -58,6 +58,7 @@ try {
|
|||||||
<li><a href="scheduled_messages.php" class="nav-link"><i class="fas fa-clock"></i> Mensajes Programados</a></li>
|
<li><a href="scheduled_messages.php" class="nav-link"><i class="fas fa-clock"></i> Mensajes Programados</a></li>
|
||||||
<li><a href="#templates" class="nav-link" data-tab="templates"><i class="fas fa-file-text"></i> Plantillas</a></li>
|
<li><a href="#templates" class="nav-link" data-tab="templates"><i class="fas fa-file-text"></i> Plantillas</a></li>
|
||||||
<li><a href="#autoresponses" class="nav-link" data-tab="autoresponses"><i class="fas fa-robot"></i> Respuestas Auto</a></li>
|
<li><a href="#autoresponses" class="nav-link" data-tab="autoresponses"><i class="fas fa-robot"></i> Respuestas Auto</a></li>
|
||||||
|
<li><a href="#whatsapp_profile" class="nav-link" data-tab="whatsapp_profile"><i class="fab fa-whatsapp"></i> Perfil WhatsApp</a></li>
|
||||||
<li><a href="#system_config" class="nav-link" data-tab="system_config"><i class="fas fa-cog"></i> Configuración</a></li>
|
<li><a href="#system_config" class="nav-link" data-tab="system_config"><i class="fas fa-cog"></i> Configuración</a></li>
|
||||||
<li><a href="#logs" class="nav-link" data-tab="logs"><i class="fas fa-file-alt"></i> Logs</a></li>
|
<li><a href="#logs" class="nav-link" data-tab="logs"><i class="fas fa-file-alt"></i> Logs</a></li>
|
||||||
<li><a href="#terms" class="nav-link" data-tab="terms"><i class="fas fa-file-contract"></i> T&C Aceptaciones</a></li>
|
<li><a href="#terms" class="nav-link" data-tab="terms"><i class="fas fa-file-contract"></i> T&C Aceptaciones</a></li>
|
||||||
@@ -664,6 +665,98 @@ try {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- WhatsApp Profile Tab -->
|
||||||
|
<div id="whatsapp_profile" class="tab-content">
|
||||||
|
<div class="card">
|
||||||
|
<div class="card-header d-flex justify-content-between align-items-center">
|
||||||
|
<h5><i class="fab fa-whatsapp text-success"></i> Perfil de WhatsApp Business</h5>
|
||||||
|
<button class="btn btn-outline-primary btn-sm" onclick="loadWhatsAppProfile()">
|
||||||
|
<i class="fas fa-sync-alt"></i> Recargar desde WhatsApp
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<div class="card-body">
|
||||||
|
<div class="row">
|
||||||
|
<!-- Foto de perfil -->
|
||||||
|
<div class="col-md-3 text-center mb-4">
|
||||||
|
<div class="mb-3">
|
||||||
|
<img id="wp-profile-photo-preview" src="" alt="Foto de perfil"
|
||||||
|
style="width:120px;height:120px;border-radius:50%;object-fit:cover;border:3px solid #25D366;display:none;">
|
||||||
|
<div id="wp-profile-photo-placeholder"
|
||||||
|
style="width:120px;height:120px;border-radius:50%;background:#f0f0f0;border:3px solid #ccc;display:inline-flex;align-items:center;justify-content:center;">
|
||||||
|
<i class="fas fa-user fa-3x text-muted"></i>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="mb-2">
|
||||||
|
<label class="form-label small fw-bold">Cambiar foto de perfil</label>
|
||||||
|
<input type="file" id="wp-photo-file" class="form-control form-control-sm" accept="image/jpeg,image/png">
|
||||||
|
<small class="text-muted">JPG o PNG, máx. 5 MB</small>
|
||||||
|
</div>
|
||||||
|
<button class="btn btn-success btn-sm w-100" onclick="uploadWhatsAppProfilePhoto()">
|
||||||
|
<i class="fas fa-upload"></i> Subir foto
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Datos del perfil -->
|
||||||
|
<div class="col-md-9">
|
||||||
|
<form id="wp-profile-form">
|
||||||
|
<div class="mb-3">
|
||||||
|
<label class="form-label">Descripción (about) <small class="text-muted">— visible en el perfil de WhatsApp</small></label>
|
||||||
|
<textarea class="form-control" id="wp-about" rows="2" maxlength="139" placeholder="Ej: Horario de atención: L-V 8am-6pm"></textarea>
|
||||||
|
<small class="text-muted">Máx. 139 caracteres</small>
|
||||||
|
</div>
|
||||||
|
<div class="mb-3">
|
||||||
|
<label class="form-label">Descripción larga</label>
|
||||||
|
<textarea class="form-control" id="wp-description" rows="3" maxlength="512" placeholder="Descripción detallada del negocio"></textarea>
|
||||||
|
</div>
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-md-6 mb-3">
|
||||||
|
<label class="form-label">Dirección</label>
|
||||||
|
<input type="text" class="form-control" id="wp-address" placeholder="Dirección física del negocio">
|
||||||
|
</div>
|
||||||
|
<div class="col-md-6 mb-3">
|
||||||
|
<label class="form-label">Correo electrónico</label>
|
||||||
|
<input type="email" class="form-control" id="wp-email" placeholder="contacto@empresa.com">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="mb-3">
|
||||||
|
<label class="form-label">Sitio web</label>
|
||||||
|
<input type="url" class="form-control" id="wp-website" placeholder="https://www.empresa.com">
|
||||||
|
</div>
|
||||||
|
<div class="mb-3">
|
||||||
|
<label class="form-label">Categoría de negocio</label>
|
||||||
|
<select class="form-select" id="wp-vertical">
|
||||||
|
<option value="">— Seleccionar —</option>
|
||||||
|
<option value="UNDEFINED">Sin categoría</option>
|
||||||
|
<option value="OTHER">Otro</option>
|
||||||
|
<option value="AUTO">Automotriz</option>
|
||||||
|
<option value="BEAUTY">Belleza / Spa</option>
|
||||||
|
<option value="APPAREL">Ropa / Moda</option>
|
||||||
|
<option value="EDU">Educación</option>
|
||||||
|
<option value="ENTERTAIN">Entretenimiento</option>
|
||||||
|
<option value="EVENT_PLAN">Eventos</option>
|
||||||
|
<option value="FINANCE">Finanzas</option>
|
||||||
|
<option value="GROCERY">Supermercado</option>
|
||||||
|
<option value="GOVT">Gobierno</option>
|
||||||
|
<option value="HOTEL">Hotel</option>
|
||||||
|
<option value="HEALTH">Salud / Medicina</option>
|
||||||
|
<option value="NONPROFIT">Organización sin fines de lucro</option>
|
||||||
|
<option value="PROF_SERVICES">Servicios profesionales</option>
|
||||||
|
<option value="RETAIL">Comercio minorista</option>
|
||||||
|
<option value="TRAVEL">Viajes / Turismo</option>
|
||||||
|
<option value="RESTAURANT">Restaurante</option>
|
||||||
|
<option value="NOT_A_BIZ">No es un negocio</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
<button type="button" class="btn btn-primary" onclick="saveWhatsAppProfile()">
|
||||||
|
<i class="fas fa-save"></i> Guardar cambios de perfil
|
||||||
|
</button>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<!-- Templates Tab -->
|
<!-- Templates Tab -->
|
||||||
<div id="templates" class="tab-content">
|
<div id="templates" class="tab-content">
|
||||||
<div class="card">
|
<div class="card">
|
||||||
@@ -673,6 +766,9 @@ try {
|
|||||||
<button class="btn btn-success" id="btn-sync-templates" onclick="syncTemplatesFromFacebook()">
|
<button class="btn btn-success" id="btn-sync-templates" onclick="syncTemplatesFromFacebook()">
|
||||||
<i class="fas fa-sync-alt"></i> Sincronizar desde Facebook
|
<i class="fas fa-sync-alt"></i> Sincronizar desde Facebook
|
||||||
</button>
|
</button>
|
||||||
|
<button class="btn btn-danger ms-2" id="btn-reset-sync-templates" onclick="resetAndSyncTemplates()">
|
||||||
|
<i class="fas fa-trash-restore"></i> Limpiar y Re-sincronizar
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="card-body">
|
<div class="card-body">
|
||||||
|
|||||||
Reference in New Issue
Block a user