En este artículo encontrarás la documentación completa, con datos ficticios, de todos los pasos y archivos necesarios para:
- Renovar automáticamente el token de WhatsApp Business
- Configurar tu helper de envío para usar ese token
- Ajustar el panel de configuración para almacenar Client ID, Client Secret, Phone Number ID y Access Token
- Probar y validar el flujo de notificaciones por WhatsApp
1. Estructura de la Base de Datos
Creamos una tabla configuraciones_panel para centralizar todas las opciones (SMTP, Telegram, WhatsApp, etc.). Ejemplo de DDL:
CREATE TABLE `configuraciones_panel` (
`clave_config` varchar(100) NOT NULL,
`valor_config` text DEFAULT NULL,
`descripcion_config` varchar(255) DEFAULT NULL,
`tipo_dato` enum('string','integer','float','boolean','color','text','json') NOT NULL DEFAULT 'string',
`grupo_config` varchar(50) DEFAULT 'General',
`fecha_modificacion` timestamp NULL DEFAULT CURRENT_TIMESTAMP()
ON UPDATE CURRENT_TIMESTAMP(),
PRIMARY KEY (`clave_config`),
KEY `idx_grupo_config` (`grupo_config`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
Valores ficcticios para ser cambiados por tus datos reales:
INSERT INTO `configuraciones_panel`
(`clave_config`, `valor_config`, `descripcion_config`, `tipo_dato`, `grupo_config`)
VALUES
('whatsapp_client_id', '098765432101234', 'ID de la App de Facebook (Client ID)', 'string', 'Notificaciones'),
('whatsapp_client_secret', 'abc123FakeSecretXYZ', 'Client Secret de la App de Facebook para WhatsApp', 'string', 'Notificaciones'),
('whatsapp_api_instancia_id','123456789012345', 'Phone Number ID para la API de WhatsApp Business', 'string', 'Notificaciones'),
('whatsapp_api_token', 'EAAIACFakeLongTokenForTesting...', 'Access Token inicial de la API de WhatsApp (short-lived).', 'text', 'Notificaciones'),
('whatsapp_api_version', 'v21.0', 'Versión de la Graph API para WhatsApp', 'string', 'Notificaciones'),
('whatsapp_token_last_renew_at','2025-12-01 02:00:00', 'Fecha de última renovación de token.', 'string', 'Notificaciones');
2. Script de Renovación Automática del Token
Para evitar errores de “session invalid” o tokens caducados, creamos un script PHP que:
- Lee el token, Client ID y Client Secret de la BD.
- Llama a Facebook Graph /oauth/access_token con grant_type=fb_exchange_token.
- Almacena el nuevo token y la fecha en la BD.
2.1 Archivo: includes/renew_whatsapp_token.php
<?php
// Muestra errores en pantalla (solo en desarrollo)
ini_set('display_errors', 1);
error_reporting(E_ALL);
// 1) Conexión PDO
require_once __DIR__ . '/db_conexion.php';
// 2) Leer credenciales desde BD
$sql = "
SELECT clave_config, valor_config
FROM configuraciones_panel
WHERE clave_config IN (
'whatsapp_api_token',
'whatsapp_client_id',
'whatsapp_client_secret'
)
";
$stmt = $pdo->query($sql);
$config = $stmt->fetchAll(PDO::FETCH_KEY_PAIR);
$currentToken = trim($config['whatsapp_api_token'] ?? '');
$clientId = trim($config['whatsapp_client_id'] ?? '');
$clientSecret = trim($config['whatsapp_client_secret'] ?? '');
if (!$currentToken || !$clientId || !$clientSecret) {
echo "❌ Faltan credenciales en BD. Verifica whatsapp_api_token, whatsapp_client_id o whatsapp_client_secret.\n";
exit(1);
}
// 3) Construir URL de renovación
$params = http_build_query([
'grant_type' => 'fb_exchange_token',
'client_id' => $clientId,
'client_secret' => $clientSecret,
'fb_exchange_token' => $currentToken
]);
$url = "https://graph.facebook.com/{$config['whatsapp_api_version']}/oauth/access_token?{$params}";
// 4) Ejecutar cURL
$ch = curl_init($url);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_TIMEOUT, 15);
$response = curl_exec($ch);
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);
$data = json_decode($response, true);
// 5) Procesar respuesta
if ($httpCode === 200 && !empty($data['access_token'])) {
$newToken = $data['access_token'];
$now = date('Y-m-d H:i:s');
try {
$pdo->beginTransaction();
// Actualizar token
$stmt1 = $pdo->prepare("
UPDATE configuraciones_panel
SET valor_config = :tok
WHERE clave_config = 'whatsapp_api_token'
");
$stmt1->execute([':tok' => $newToken]);
// Actualizar timestamp
$stmt2 = $pdo->prepare("
UPDATE configuraciones_panel
SET valor_config = :ts
WHERE clave_config = 'whatsapp_token_last_renew_at'
");
$stmt2->execute([':ts' => $now]);
$pdo->commit();
echo "✅ Token renovado y guardado con éxito ({$now}).\n";
exit(0);
} catch (Exception $e) {
$pdo->rollBack();
echo "❌ Error guardando en BD: " . $e->getMessage() . "\n";
exit(1);
}
} else {
$err = $data['error']['message'] ?? $response;
echo "❌ Error al renovar token (HTTP {$httpCode}): {$err}\n";
exit(1);
}
2.2 Programar la tarea en Windows
- Abre Programador de Tareas (taskschd.msc).
- Crear tarea → Nombre: “Renovar Token WhatsApp”.
- Desencadenadores: cada 20 horas.
- Acciones:
- Programa: C:\xampp\php\php.exe
- Argumentos: -f "C:\xampp\htdocs\ejemplo\includes\renew_whatsapp_token.php"
- Iniciar en: C:\xampp\htdocs\ejemplo\includes
Así, el script se ejecutará automáticamente y mantendrá el token siempre vigente.
3. Ajustes en el Helper de Envío (whatsapp_helper.php)
Quitamos la lógica interna de renovación por hora y dejamos que solo lea el token actualizado en la BD.
function obtenerTokenWhatsAppValido(PDO $pdo, array &$config) {
$stmt = $pdo->query("
SELECT clave_config, valor_config
FROM configuraciones_panel
WHERE clave_config LIKE 'whatsapp_%'
");
while ($row = $stmt->fetch(PDO::FETCH_ASSOC)) {
$config[$row['clave_config']] = $row['valor_config'];
}
return $config['whatsapp_api_token'] ?? false;
}
Las funciones de envío (enviarWhatsAppNotificacionDeuda, _enviarPayloadWhatsApp, etc.) permanecen idénticas, usando el token que esta función devuelve.
4. Corrección y Formulario de Configuración
En configuracion/index.php ajustamos:
- Mapa genérico $config_map_guardado:
- Eliminamos la línea de whatsapp_client_secret para que no se machaque con valor vacío.
- Bloque independiente para procesar whatsapp_client_secret solo si el usuario ingresa un valor:
// Después del loop genérico:
if (!empty(trim($_POST['whatsapp_client_secret']))) {
$nuevo = trim($_POST['whatsapp_client_secret']);
$sql = "INSERT INTO configuraciones_panel
(clave_config,valor_config,tipo_dato,grupo_config,descripcion_config)
VALUES
('whatsapp_client_secret',:val,'string','Notificaciones','Client Secret de la App WhatsApp')
ON DUPLICATE KEY UPDATE
valor_config = :val, fecha_modificacion = CURRENT_TIMESTAMP()";
$s = $pdo->prepare($sql);
$s->execute([':val' => $nuevo]);
}
Fragmento HTML
<h6 class="text-info">Configuración API de WhatsApp</h6>
<div class="form-check form-switch mb-3">
<input class="form-check-input" type="checkbox"
id="whatsapp_notificaciones_habilitadas"
name="whatsapp_notificaciones_habilitadas" value="1"
<?= $whatsapp_notificaciones_habilitadas ? 'checked' : '' ?>>
<label class="form-check-label" for="whatsapp_notificaciones_habilitadas">
Habilitar Notificaciones por WhatsApp</label>
</div>
<div class="row">
<div class="col-md-6 mb-3">
<label for="whatsapp_client_id">Client ID (App ID)</label>
<input type="text" id="whatsapp_client_id" name="whatsapp_client_id"
class="form-control"
value="<?= htmlspecialchars($whatsapp_client_id) ?>">
</div>
<div class="col-md-6 mb-3">
<label for="whatsapp_client_secret">Client Secret (App Secret)</label>
<input type="password" id="whatsapp_client_secret"
name="whatsapp_client_secret" class="form-control"
placeholder="Dejar en blanco para no cambiar">
<small class="form-text text-muted">
Ingrese solo para actualizar el Client Secret.</small>
</div>
</div>
<div class="row">
<div class="col-md-6 mb-3">
<label for="whatsapp_api_instancia_id">Phone Number ID</label>
<input type="text" id="whatsapp_api_instancia_id"
name="whatsapp_api_instancia_id" class="form-control"
value="<?= htmlspecialchars($whatsapp_api_instancia_id) ?>">
</div>
<div class="col-md-6 mb-3">
<label for="whatsapp_api_version">Versión API</label>
<input type="text" id="whatsapp_api_version"
name="whatsapp_api_version" class="form-control"
value="<?= htmlspecialchars($whatsapp_api_version) ?>">
</div>
</div>
<div class="mb-3">
<label for="whatsapp_api_token">Token de Acceso Inicial</label>
<textarea id="whatsapp_api_token" name="whatsapp_api_token"
class="form-control" rows="2"
placeholder="Token short-lived inicial"><?= htmlspecialchars($whatsapp_api_token) ?></textarea>
</div>
Con esto, el panel permitirá configurar Client ID, Client Secret (solo si se ingresa), Phone Number ID, API Version y el token inicial.
5. Flujo Completo de Envío de Notificaciones
- El Programador de Tareas renueva el token cada 20 horas.
- Tu helper obtenerTokenWhatsAppValido() lee el token largo almacenado.
- Al invocar:
enviarWhatsAppNotificacionDeuda(
$config_whatsapp,
'5491123456789', // Número de cliente ficticio
['nombre_cliente'=>'ACME','monto_total_adeudado'=>150.75,'monedas_adeudadas'=>'USD']
);
6. Pruebas y Verificación
- Ejecuta manualmente http://localhost/ejemplo/includes/renew_whatsapp_token.php para ver en pantalla:
✅ Token renovado y guardado con éxito (2025-12-08 14:00:02).
SELECT clave_config, valor_config
FROM configuraciones_panel
WHERE clave_config='whatsapp_api_token'
OR clave_config='whatsapp_token_last_renew_at';
En el panel de config, modifica solo el Client Secret y guarda; confirma que en BD cambia solo si hubo texto en el password.
Conclusión
Con esta implementación:
- Garantizas que tu token de WhatsApp Business siempre esté vigente, sin interrupciones.
- Mantienes un helper de envío sencillo, enfocado sólo en construir el payload y llamar a la API.
- Permites al administrador del sistema actualizar Client ID, Client Secret, Phone Number ID, API Version y Token inicial desde un formulario centralizado.
- Usas tecnología estándar (PHP + MySQL + Windows Task Scheduler) sin dependencias externas complejas.
¡Listo para integrar notificaciones de WhatsApp Business de principio a fin en tu proyecto local o en producción!