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()]);
|
||||
}
|
||||
Reference in New Issue
Block a user