siip-whatsapp-notifications.../src/Facade/AbstractMessageNotifierFacade.php
DANYDHSV b0b56a59ce feat: Dashboard administrativo, integración con API UCRM y optimización de Stripe
- Implementación de Dashboard profesional en public.php con CRUD de instaladores.
- Sincronización con la API de UCRM para selección y validación automática de administradores.
- Soporte para creación selectiva de clientes en Stripe (Etiqueta "CREAR CLIENTE STRIPE" vs "CREAR CLABE STRIPE").
- Mejora en la lógica de notificaciones de tareas (nuevo prefijo [CLIENTE-SIN-WHATSAPP]).
- Refactorización integral de fachadas y saneamiento de código muerto.
- Sistema de Modo Oscuro persistente con UI refinada.
2025-12-27 23:15:32 -06:00

290 lines
15 KiB
PHP
Executable File

<?php
declare(strict_types=1);
namespace SmsNotifier\Facade;
use GuzzleHttp\Client;
use SmsNotifier\Data\NotificationData;
use SmsNotifier\Facade\ClientCallBellAPI;
use SmsNotifier\Factory\MessageTextFactory;
use SmsNotifier\Service\Logger;
use SmsNotifier\Service\SmsNumberProvider;
use Ubnt\UcrmPluginSdk\Service\UcrmApi;
use Ubnt\UcrmPluginSdk\Service\PluginConfigManager;
use \DateTime;
abstract class AbstractMessageNotifierFacade
{
protected $logger;
protected $messageTextFactory;
protected $clientPhoneNumber;
protected $ucrmApi;
const SUBJECT_OF_INSTALLER_CHANGE = ["se ha cancelado una tarea que tenías asignada con el folio ", "se te ha desasignado❌ la tarea con el folio "];
const ADDITIONAL_CHANGE_DATA = ["Ya no es necesario realizar la visita técnica.", "En tu lugar asistirá el técnico 👷🏻‍♂️➡️ "];
public function __construct(Logger $logger, MessageTextFactory $messageTextFactory, SmsNumberProvider $clientPhoneNumber) {
$this->logger = $logger;
$this->messageTextFactory = $messageTextFactory;
$this->clientPhoneNumber = $clientPhoneNumber;
$this->ucrmApi = UcrmApi::create();
}
public function verifyPaymentActionToDo(NotificationData $notificationData): void {
$arrayPhones = $this->clientPhoneNumber->getUcrmClientNumbers($notificationData, null);
foreach ($arrayPhones as $type => $phones) {
$type = trim(strtolower($type));
if (!is_array($phones)) continue;
foreach ($phones as $phone) {
switch ($type) {
case 'whatsapp': $this->notifyAndUpdate($notificationData, $phone); break;
case 'whatsnotifica': $this->notify($notificationData, $phone); break;
case 'whatsactualiza': $this->onlyUpdate($notificationData, $phone); break;
}
}
}
}
public function verifyClientActionToDo(NotificationData $notificationData): void {
$arrayPhones = $this->clientPhoneNumber->getUcrmClientNumbers($notificationData, null);
foreach ($arrayPhones as $type => $phones) {
$type = trim(strtolower($type));
if (!is_array($phones)) continue;
foreach ($phones as $phone) {
if ($type === 'whatsapp' || $type === 'whatsactualiza') $this->onlyUpdate($notificationData, $phone);
}
}
}
public function verifyServiceActionToDo(NotificationData $notificationData): void {
$arrayPhones = $this->clientPhoneNumber->getUcrmClientNumbers($notificationData, null);
foreach ($arrayPhones as $type => $phones) {
$type = trim(strtolower($type));
if (!is_array($phones)) continue;
foreach ($phones as $phone) {
if ($type === 'whatsapp' || $type === 'whatsactualiza') $this->onlyUpdateService($notificationData, $phone);
}
}
}
public function verifyJobActionToDo($jsonNotificationData, $reprogramming = null, $changeInstaller = null): void {
$this->logger->info('Iniciando verifyJobActionToDo');
$clientId = $jsonNotificationData['extraData']['entity']['clientId'];
$installerId = $jsonNotificationData['extraData']['entity']['assignedUserId'];
$jobId = $jsonNotificationData['entityId'];
$dateString = $jsonNotificationData['extraData']['entity']['date'] ?? null;
$formattedDate = $dateString ? sprintf("*%s*", (new DateTime($dateString))->format('d/m/Y')) : '';
$config = PluginConfigManager::create()->loadConfig();
$admin = $this->ucrmApi->get("users/admins/$installerId", []);
$installerName = trim(($admin['firstName'] ?? '') . ' ' . ($admin['lastName'] ?? ''));
$installerWhatsApp = '';
$installers = json_decode($config['installersDataWhatsApp'] ?? '{"instaladores":[]}', true);
foreach ($installers['instaladores'] as $inst) {
if ($inst['id'] == $installerId) { $installerWhatsApp = $inst['whatsapp']; break; }
}
if (empty($installerWhatsApp)) {
$this->logger->warning("No se encontró número de WhatsApp para el instalador ID: $installerId");
}
$clientCRM = $this->ucrmApi->get("clients/$clientId", []);
$clientName = trim(($clientCRM['firstName'] ?? '') . ' ' . ($clientCRM['lastName'] ?? ''));
$passCRM = '';
foreach ($clientCRM['attributes'] as $attr) {
if ($attr['key'] === 'passwordAntenaCliente') { $passCRM = $attr['value']; break; }
}
$allPhones = $this->clientPhoneNumber->getAllUcrmClientNumbers($clientCRM);
$phonesStr = implode(', ', array_map(fn($n) => $this->validarNumeroTelefono($n), $allPhones));
$api = new ClientCallBellAPI($config['apitoken'], $config['ipserver'], $config['tokencallbell']);
// --- Nueva Lógica de Prefijos ---
$title = $jsonNotificationData['extraData']['entity']['title'] ?? '';
$isPending = (stripos($title, '[NOTIFICACION-PENDIENTE]') !== false);
$isNoWhatsApp = (stripos($title, '[CLIENTE-SIN-WHATSAPP]') !== false);
$reprogramming = filter_var($reprogramming, FILTER_VALIDATE_BOOLEAN);
$changeInstaller = filter_var($changeInstaller, FILTER_VALIDATE_BOOLEAN);
$clientPhones = $this->clientPhoneNumber->getUcrmClientNumbers(null, $clientCRM);
$hasClientWhatsApp = false;
foreach ($clientPhones as $type => $phones) {
$type = trim(strtolower($type));
if (($type === 'whatsapp' || $type === 'whatsnotifica') && !empty($phones)) {
$hasClientWhatsApp = true;
break;
}
}
$shouldNotifyTech = ($isPending || $reprogramming || $changeInstaller);
$shouldNotifyClient = ($isPending || $isNoWhatsApp || $reprogramming || $changeInstaller);
$this->logger->debug("Estado de notificación - Pending: " . ($isPending?'SI':'NO') . ", NoWhatsApp: " . ($isNoWhatsApp?'SI':'NO') . ", HasWA: " . ($hasClientWhatsApp?'SI':'NO'));
// 1. Notificar al Instalador Anterior (Desasignación)
if ($changeInstaller) {
$prevId = $jsonNotificationData['extraData']['entityBeforeEdit']['assignedUserId'];
$prevAdmin = $this->ucrmApi->get("users/admins/$prevId", []);
$prevWhatsApp = '';
foreach ($installers['instaladores'] as $inst) {
if ($inst['id'] == $prevId) { $prevWhatsApp = $inst['whatsapp']; break; }
}
if ($prevWhatsApp) {
$api->sendJobNotificationWhatsAppToInstaller($this->validarNumeroTelefono($prevWhatsApp), [
"installerName" => "👷🏻‍♂️" . trim(($prevAdmin['firstName'] ?? '') . ' ' . ($prevAdmin['lastName'] ?? '')),
"subjectOfChange" => self::SUBJECT_OF_INSTALLER_CHANGE[1],
"jobId" => $jobId,
"clientFullName" => "[$clientId] $clientName",
"additionalChangeData" => self::ADDITIONAL_CHANGE_DATA[1] . ' *' . $installerName . '*',
], $reprogramming, $changeInstaller);
sleep(1);
}
}
// 2. Notificar al Cliente (si aplica)
$clientNotified = false;
if ($shouldNotifyClient && $hasClientWhatsApp) {
foreach ($clientPhones as $type => $phones) {
$type = trim(strtolower($type));
if (!is_array($phones) || ($type !== 'whatsapp' && $type !== 'whatsnotifica')) continue;
foreach ($phones as $phone) {
if ($api->sendJobNotificationWhatsAppToClient($this->validarNumeroTelefono($phone), ["clientFullName" => $clientName, "jobId" => $jobId, "date" => $formattedDate, "installerName" => $installerName], $reprogramming, $changeInstaller)) {
$clientNotified = true;
}
}
}
}
// 2. Notificar al Técnico (si aplica)
if ($shouldNotifyTech) {
$passVault = $this->getVaultCredentialsByClientId($clientId);
$api->sendJobNotificationWhatsAppToInstaller($this->validarNumeroTelefono($installerWhatsApp), [
"installerName" => $installerName,
"clientFullName" => "$clientName [ID:$clientId]",
"jobId" => $jobId,
"clientAddress" => $clientCRM['fullAddress'] ?? 'N/A',
"clientWhatsApp" => !empty($phonesStr) ? $phonesStr : 'Sin WhatsApp',
"date" => $formattedDate,
"jobDescription" => $jsonNotificationData['extraData']['entity']['description'] ?? 'S/D',
"gmapsLocation" => ($clientCRM['addressGpsLat'] && $clientCRM['addressGpsLon']) ? "https://www.google.com/maps?q={$clientCRM['addressGpsLat']},{$clientCRM['addressGpsLon']}" : 'N/A',
"passwordAntenaCliente" => $this->comparePasswords($passCRM, $passVault)
], $reprogramming, $changeInstaller);
}
// 3. Gestión del Título / Prefijos
if ($isPending) {
if ($clientNotified || $reprogramming || $changeInstaller) {
// Si el cliente fue notificado o es un cambio, quitamos el prefijo
$newTitle = str_ireplace('[NOTIFICACION-PENDIENTE]', '', $title);
$this->ucrmApi->patch("scheduling/jobs/$jobId", ['title' => trim($newTitle)]);
} else if (!$hasClientWhatsApp) {
// Si no tiene WhatsApp, cambiamos a estado "SIN-WHATSAPP" para no volver a notificar al técnico
$newTitle = str_ireplace('[NOTIFICACION-PENDIENTE]', '[CLIENTE-SIN-WHATSAPP]', $title);
$this->ucrmApi->patch("scheduling/jobs/$jobId", ['title' => trim($newTitle)]);
}
} else if ($isNoWhatsApp && ($clientNotified || $reprogramming || $changeInstaller)) {
// Si estaba marcado como sin whatsapp y ahora sí pudimos notificarlo
$newTitle = str_ireplace('[CLIENTE-SIN-WHATSAPP]', '', $title);
$this->ucrmApi->patch("scheduling/jobs/$jobId", ['title' => trim($newTitle)]);
}
}
public function verifyInvoiceActionToDo(NotificationData $notificationData): void {
$arrayPhones = $this->clientPhoneNumber->getUcrmClientNumbers($notificationData, null);
foreach ($arrayPhones as $type => $phones) {
$type = trim(strtolower($type));
if (!is_array($phones)) continue;
foreach ($phones as $phone) {
if ($type === 'whatsapp' || $type === 'whatsactualiza') $this->onlyUpdate($notificationData, $phone, false);
}
}
}
public function notify(NotificationData $notificationData, $phoneToNotify = null): void {
$config = PluginConfigManager::create()->loadConfig();
$api = new ClientCallBellAPI($config['apitoken'], $config['ipserver'], $config['tokencallbell']);
$phone = $this->validarNumeroTelefono($phoneToNotify);
if (!$phone) return;
if ($config['notificationTypeText'] ?? false) $api->sendTextPaymentNotificationWhatsApp($phone, $notificationData);
else $api->sendPaymentNotificationWhatsApp($phone, $notificationData);
}
public function notifyAndUpdate(NotificationData $notificationData, $phoneToNotifyAndUpdate = null): void {
$config = PluginConfigManager::create()->loadConfig();
$api = new ClientCallBellAPI($config['apitoken'], $config['ipserver'], $config['tokencallbell']);
$phone = $this->validarNumeroTelefono($phoneToNotifyAndUpdate);
if (!$phone) return;
if ($config['notificationTypeText'] ?? false) {
if ($api->sendTextPaymentNotificationWhatsApp($phone, $notificationData)) {
$contact = json_decode($api->getContactWhatsapp($phone), true);
$api->patchWhatsapp($contact, $notificationData);
}
} else {
if ($api->sendPaymentNotificationWhatsApp($phone, $notificationData)) {
$contact = json_decode($api->getContactWhatsapp($phone), true);
$api->patchWhatsapp($contact, $notificationData);
}
}
}
public function notifyOverDue(NotificationData $notificationData): void {
$config = PluginConfigManager::create()->loadConfig();
$api = new ClientCallBellAPI($config['apitoken'], $config['ipserver'], $config['tokencallbell']);
$phone = $this->clientPhoneNumber->getUcrmClientNumber($notificationData);
if ($phone) $api->sendOverdueNotificationWhatsApp($phone, $notificationData);
}
public function onlyUpdate(NotificationData $notificationData, $phoneToUpdate): void {
$config = PluginConfigManager::create()->loadConfig();
$api = new ClientCallBellAPI($config['apitoken'], $config['ipserver'], $config['tokencallbell']);
$phone = $this->validarNumeroTelefono($phoneToUpdate);
$contact = json_decode($api->getContactWhatsapp($phone), true);
if ($contact) $api->patchWhatsapp($contact, $notificationData);
}
public function onlyUpdateService(NotificationData $notificationData, $phoneToUpdate): void {
$config = PluginConfigManager::create()->loadConfig();
$api = new ClientCallBellAPI($config['apitoken'], $config['ipserver'], $config['tokencallbell']);
$phone = $this->validarNumeroTelefono($phoneToUpdate);
$contact = json_decode($api->getContactWhatsapp($phone), true);
if ($contact) $api->patchServiceStatusWhatsApp($contact, $notificationData);
}
protected function getVaultCredentialsByClientId($clientId): string {
$config = PluginConfigManager::create()->loadConfig();
if ($config['ipserver'] === '172.16.5.134') return 'gYAIEK:Be}SK*01z5+/V';
$unms = new Client(['base_uri' => "https://{$config['ipserver']}/nms/api/v2.1/", 'verify' => false]);
$crm = new Client(['base_uri' => "https://{$config['ipserver']}/crm/api/v1.0/", 'verify' => false]);
try {
$respSvc = $crm->get('clients/services?clientId=' . $clientId, ['headers' => ['X-Auth-Token' => $config['apitoken']]]);
$svcs = json_decode($respSvc->getBody()->getContents(), true);
if (!isset($svcs[0]['unmsClientSiteId'])) return 'Error: Sin sitio';
$respDev = $unms->get("devices?siteId={$svcs[0]['unmsClientSiteId']}", ['headers' => ['X-Auth-Token' => $config['unmsApiToken']]]);
$devs = json_decode($respDev->getBody()->getContents(), true);
if (!isset($devs[0]['identification']['id'])) return 'Error: Sin equipo';
$respVault = $unms->get("vault/{$devs[0]['identification']['id']}/credentials", ['headers' => ['X-Auth-Token' => $config['unmsApiToken']]]);
$vault = json_decode($respVault->getBody()->getContents(), true);
return $vault['credentials'][0]['password'] ?? 'Error: Sin pass';
} catch (\Exception $e) { return 'Error: ' . $e->getMessage(); }
}
protected function comparePasswords(?string $crm, ?string $vault): string {
if ($crm && strpos($crm, 'Error') !== 0) return $crm;
if ($vault && strpos($vault, 'Error') !== 0) return $vault;
return '⚠️ Probar pass conocida.';
}
protected function validarNumeroTelefono($n): string {
if (!$n) return '';
$n = preg_replace('/\D/', '', (string)$n);
return (strlen($n) === 10) ? '52' . $n : $n;
}
abstract protected function sendWhatsApp(NotificationData $notificationData, string $clientSmsNumber): void;
}