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.
This commit is contained in:
DANYDHSV 2025-12-27 23:15:32 -06:00
parent 5425659428
commit b0b56a59ce
13 changed files with 4109 additions and 2823 deletions

0
CHANGELOG.md Normal file → Executable file
View File

File diff suppressed because it is too large Load Diff

View File

@ -1 +1,28 @@
{"ipserver":"172.16.5.134","apitoken":"gvcnIJqXdUjneVSjhl6THLlQcYXJyIFCcwHKVba2bvIrNraanCTb5VeoWuJ0TFZ9","unmsApiToken":"079c28f5-888c-457d-bd7a-0a4202590f75","tokencallbell":"g8thcZkXGd3xBj2g3TtYNYFMH1fuesbJ.b6a940ea7d78cf6c9e42f067b21c8ddf96e9fa2a9e307bfd0c7c7c4d7fa38f79","tokenstripe":"sk_test_51OkG0REFY1WEUtgRH6UxBK5pu80Aq5Iy8EcdPnf0cOWzuVLQTpyLCd7CbPzqMsWMafZOHElCxhEHF7g8boURjWlJ00tBwE0W1M","hostServerFTP":"siip.mx","usernameServerFTP":"siip0001","passServerFTP":"$spGiT,[wa)n","ipPuppeteer":"172.16.5.134","portPuppeteer":"3000","idPaymentAdminCRM":"1180","cashPaymentMethodId":false,"courtesyPaymentMethodId":false,"bankTransferPaymentMethodId":true,"paypalPaymentMethodId":true,"creditCardPaypalPaymentMethodId":true,"creditCardStripePaymentMethodId":true,"stripeSubscriptionCreditCardPaymentMethodId":true,"paypalSubscriptionPaymentMethodId":true,"mercadopagoPaymentMethodId":true,"checkPaymentMethodId":true,"customPaymentMethodId":true,"notificationTypeText":true,"installersDataWhatsApp":"{\r\n \"instaladores\": [\r\n {\r\n \"id\": 1019,\r\n \"nombre\": \"Mucio Robledo\",\r\n \"whatsapp\": \"4181878106\"\r\n },\r\n {\r\n \"id\": 1173,\r\n \"nombre\": \"Ángel Arvizu\",\r\n \"whatsapp\": \"4181878106\"\r\n },\r\n {\r\n \"id\": 1172,\r\n \"nombre\": \"Juan Rostro\",\r\n \"whatsapp\": \"4181878106\"\r\n },\r\n {\r\n \"id\": 1015,\r\n \"nombre\": \"Daniel Humberto\",\r\n \"whatsapp\": \"4181878106\"\r\n },\r\n {\r\n \"id\": 1131,\r\n \"nombre\": \"Gricelda Avalos\",\r\n \"whatsapp\": \"4181817609\"\r\n }\r\n ]\r\n}","debugMode":true,"logging_level":true}
{
"ipserver": "172.16.5.134",
"apitoken": "gvcnIJqXdUjneVSjhl6THLlQcYXJyIFCcwHKVba2bvIrNraanCTb5VeoWuJ0TFZ9",
"unmsApiToken": "079c28f5-888c-457d-bd7a-0a4202590f75",
"tokencallbell": "g8thcZkXGd3xBj2g3TtYNYFMH1fuesbJ.b6a940ea7d78cf6c9e42f067b21c8ddf96e9fa2a9e307bfd0c7c7c4d7fa38f79",
"tokenstripe": "sk_test_51OkG0REFY1WEUtgRH6UxBK5pu80Aq5Iy8EcdPnf0cOWzuVLQTpyLCd7CbPzqMsWMafZOHElCxhEHF7g8boURjWlJ00tBwE0W1M",
"hostServerFTP": "siip.mx",
"usernameServerFTP": "siip0001",
"passServerFTP": "$spGiT,[wa)n",
"ipPuppeteer": "172.16.5.134",
"portPuppeteer": "3000",
"idPaymentAdminCRM": "1180",
"cashPaymentMethodId": false,
"courtesyPaymentMethodId": false,
"bankTransferPaymentMethodId": true,
"paypalPaymentMethodId": true,
"creditCardPaypalPaymentMethodId": true,
"creditCardStripePaymentMethodId": true,
"stripeSubscriptionCreditCardPaymentMethodId": true,
"paypalSubscriptionPaymentMethodId": true,
"mercadopagoPaymentMethodId": true,
"checkPaymentMethodId": true,
"customPaymentMethodId": true,
"notificationTypeText": true,
"installersDataWhatsApp": "{\r\n \"instaladores\": [\r\n {\r\n \"id\": 1019,\r\n \"nombre\": \"Mucio Robledo\",\r\n \"whatsapp\": \"4181878106\"\r\n },\r\n {\r\n \"id\": 1173,\r\n \"nombre\": \"\u00c1ngel Arvizu\",\r\n \"whatsapp\": \"4181878106\"\r\n },\r\n {\r\n \"id\": 1172,\r\n \"nombre\": \"Juan Rostro\",\r\n \"whatsapp\": \"4181878106\"\r\n },\r\n {\r\n \"id\": 1015,\r\n \"nombre\": \"Daniel Humberto\",\r\n \"whatsapp\": \"4181878106\"\r\n },\r\n {\r\n \"id\": 1131,\r\n \"nombre\": \"Gricelda Avalos\",\r\n \"whatsapp\": \"4181817609\"\r\n }\r\n ]\r\n}",
"debugMode": true,
"logging_level": true
}

View File

@ -1 +1 @@
{"twilioAccountSid":null,"twilioAuthToken":null,"twilioSmsNumber":null,"displayedErrors":"Not valid configuration: Twilio Account SID must be configured\nNot valid configuration: Twilio Auth Token must be configured","event_client_add":null,"event_client_archive":null,"event_client_delete":null,"event_client_edit":null,"event_invoice_add":null,"event_invoice_add_draft":null,"event_invoice_draft_approved":null,"event_invoice_delete":null,"event_invoice_edit":null,"event_payment_add":null,"event_payment_delete":null,"event_payment_edit":null,"event_payment_unmatch":null,"event_service_activate":null,"event_service_add":null,"event_service_archive":null,"event_service_end":null,"event_service_postpone":null,"event_service_suspend_cancel":null,"event_service_suspend":null,"event_invoice_near_due":null,"event_invoice_overdue":null,"event_client_message":null}
{"twilioAccountSid":null,"twilioAuthToken":null,"twilioSmsNumber":null,"displayedErrors":null}

File diff suppressed because one or more lines are too long

View File

@ -1,147 +1,577 @@
<?php
require_once __DIR__ . '/vendor/autoload.php';
use Ubnt\UcrmPluginSdk\Service\PluginLogManager;
use Ubnt\UcrmPluginSdk\Service\PluginConfigManager;
use Ubnt\UcrmPluginSdk\Service\UcrmApi;
require_once 'main.php';
// Solo permitir acceso si estamos en un entorno UCRM válido
if (!file_exists(__DIR__ . '/data/config.json')) {
die('Acceso denegado o configuración no encontrada.');
}
// Función para imprimir una tabla
// function imprimirTabla()
// {
// echo '
// <table border="1">
// <tr>
// <th>Archivos PDF</th>
// <th>Resultado</th>
// </tr>
// ';
$configManager = PluginConfigManager::create();
$config = $configManager->loadConfig();
$logger = PluginLogManager::create();
$ucrmApi = UcrmApi::create();
// borrarArchivosPDFWordpress();
// echo '</table>
// ';
// }
// Obtener administradores de UCRM para el selector
$admins = [];
try {
$adminsRaw = $ucrmApi->get('users/admins');
foreach ($adminsRaw as $admin) {
$admins[] = [
'id' => $admin['id'],
'nombre' => trim(($admin['firstName'] ?? '') . ' ' . ($admin['lastName'] ?? ''))
];
}
} catch (\Exception $e) {
$logger->error('Error al obtener administradores de UCRM: ' . $e->getMessage());
}
// function borrarArchivosPDFWordpress()
// {
// $log = PluginLogManager::create(); //Initialize Logger
// $configManager = PluginConfigManager::create();
// $config = $configManager->loadConfig();
// Manejar actualizaciones del JSON de instaladores
if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['action']) && $_POST['action'] === 'save_installers') {
$installersJson = $_POST['installers_data'] ?? '';
// // Configuración de conexión FTP
// $ftp_server = $config['hostServerFTP'];
// $ftp_username = $config['usernameServerFTP'];
// $ftp_password = $config['passServerFTP'];
// $remote_folder = "/public_html/wp/wp-content/uploads/img/";
// Validar que sea un JSON válido
if (json_decode($installersJson) !== null) {
$configPath = __DIR__ . '/data/config.json';
$currentConfig = json_decode(file_get_contents($configPath), true);
$currentConfig['installersDataWhatsApp'] = $installersJson;
// // Conexión FTP
// $ftp_conn = ftp_connect($ftp_server) or die("No se pudo conectar al servidor FTP");
// $login = ftp_login($ftp_conn, $ftp_username, $ftp_password);
// ftp_pasv($ftp_conn, true);
if (file_put_contents($configPath, json_encode($currentConfig, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE))) {
header('Content-Type: application/json');
echo json_encode(['success' => true]);
exit;
}
}
// // Verificar conexión y login
// if ($ftp_conn && $login) {
// $log->appendLog("Conexión FTP exitosa" . PHP_EOL);
header('Content-Type: application/json');
echo json_encode(['success' => false, 'message' => 'Error al guardar los datos.']);
exit;
}
// // Obtener lista de archivos en la carpeta
// $files = ftp_nlist($ftp_conn, $remote_folder);
// if (is_array($files)) {
// // Eliminar la ruta del directorio de los archivos
// $files = array_map(function($file) use ($remote_folder) {
// return str_replace($remote_folder, '', $file);
// }, $files);
// Cargar instaladores actuales
$installersData = json_decode($config['installersDataWhatsApp'] ?? '{"instaladores":[]}', true);
?>
<!DOCTYPE html>
<html lang="es" data-theme="light">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>SIIP - Dashboard Admin</title>
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Outfit:wght@300;400;600;700&display=swap" rel="stylesheet">
<style>
:root {
--primary: #2563eb;
--primary-hover: #1d4ed8;
--bg-body: #f8fafc;
--bg-card: #ffffff;
--text-main: #1e293b;
--text-muted: #64748b;
--border: #e2e8f0;
--sidebar-width: 260px;
--danger: #ef4444;
--success: #22c55e;
--transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
}
// // Obtener fechas de modificación
// $filesWithTime = [];
// foreach ($files as $file) {
// $modifiedTime = ftp_mdtm($ftp_conn, $remote_folder . $file);
// if ($modifiedTime != -1) {
// $filesWithTime[$file] = $modifiedTime;
// }
// }
[data-theme="dark"] {
--bg-body: #0f172a;
--bg-card: #1e293b;
--text-main: #f1f5f9;
--text-muted: #94a3b8;
--border: #334155;
}
// // Ordenar archivos por fecha de modificación, más recientes primero
// arsort($filesWithTime);
* {
margin: 0;
padding: 0;
box-sizing: border-box;
font-family: 'Outfit', sans-serif;
}
// // Obtener los archivos a eliminar (todos menos los 5 más recientes)
// $filesToDelete = array_slice(array_keys($filesWithTime), 5);
body {
background-color: var(--bg-body);
color: var(--text-main);
transition: var(--transition);
min-height: 100vh;
display: flex;
}
// // Eliminar archivos antiguos
// foreach ($filesToDelete as $file) {
// if (ftp_delete($ftp_conn, $remote_folder . $file)) {
// echo '<tr><td>' . $file . '</td><td>Archivo borrado</td></tr>';
// } else {
// echo '<tr><td>' . $file . '</td><td>Error al borrar archivo</td></tr>';
// }
// }
// $log->appendLog("Archivos eliminados" . PHP_EOL);
// } else {
// $log->appendLog("No se pudo obtener la lista de archivos de la carpeta FTP" . PHP_EOL);
// }
// // Cerrar conexión FTP
// ftp_close($ftp_conn);
// } else {
// $log->appendLog("No se pudo conectar o iniciar sesión en el servidor FTP." . PHP_EOL);
// }
// }
/* Sidebar */
.sidebar {
width: var(--sidebar-width);
background: var(--bg-card);
border-right: 1px solid var(--border);
padding: 2rem 1.5rem;
display: flex;
flex-direction: column;
position: fixed;
height: 100vh;
z-index: 100;
}
// // Verificar si se ha enviado una solicitud POST
// if ($_SERVER["REQUEST_METHOD"] == "POST" && isset($_POST["pintar"])) {
// // Llamar a la función para imprimir la tabla
// imprimirTabla();
.logo {
font-size: 1.5rem;
font-weight: 700;
color: var(--primary);
margin-bottom: 3rem;
display: flex;
align-items: center;
gap: 10px;
}
// }
.nav-link {
display: flex;
align-items: center;
gap: 12px;
padding: 12px 16px;
border-radius: 12px;
color: var(--text-muted);
text-decoration: none;
margin-bottom: 8px;
transition: var(--transition);
font-weight: 500;
}
// // Contenido HTML para el formulario con el botón
// $html = '
// <!DOCTYPE html>
// <html lang="es">
// <head>
// <meta charset="UTF-8">
// <meta name="viewport" content="width=device-width, initial-scale=1.0">
// <style>
// body {
// font-family: Arial, sans-serif;
// background-color: #f0f0f0;
// margin: 0;
// padding: 20px;
// }
// h1 {
// text-align: center;
// color: #333;
// }
// table {
// width: 100%;
// border-collapse: collapse;
// border: 2px solid #333;
// margin-bottom: 20px;
// }
// th, td {
// padding: 10px;
// border: 1px solid #999;
// }
// th {
// background-color: #f2f2f2;
// }
// tr:nth-child(even) {
// background-color: #f9f9f9;
// }
.nav-link.active, .nav-link:hover {
background: rgba(37, 99, 235, 0.1);
color: var(--primary);
}
// .container {
// display: flex;
// justify-content: center;
// align-items: center;
/* Main Content */
.main-content {
margin-left: var(--sidebar-width);
flex: 1;
padding: 2rem 3rem;
}
// }
// </style>
// </head>
// <body>
// <h1>Limpieza de archivos PDF de comprobantes de pago subidos a Wordpress para su envío</h1>
// <div class="container">
// <form method="post">
// <button type="submit" name="pintar">Ejecutar borrado de archivos</button>
// </form>
// </div>
.header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 2.5rem;
}
// </body>
// </html>
// ';
// echo $html;
.title-section h1 {
font-size: 1.875rem;
font-weight: 700;
}
.title-section p {
color: var(--text-muted);
}
/* Actions */
.actions {
display: flex;
gap: 1rem;
align-items: center;
}
.btn {
padding: 10px 20px;
border-radius: 10px;
border: none;
font-weight: 600;
cursor: pointer;
transition: var(--transition);
display: flex;
align-items: center;
gap: 8px;
}
.btn-primary {
background: var(--primary);
color: white;
}
.btn-primary:hover {
background: var(--primary-hover);
transform: translateY(-2px);
box-shadow: 0 4px 12px rgba(37, 99, 235, 0.2);
}
.theme-toggle {
background: var(--bg-card);
border: 1px solid var(--border);
color: var(--text-main);
width: 44px;
height: 44px;
display: flex;
align-items: center;
justify-content: center;
border-radius: 12px;
overflow: hidden;
position: relative;
}
.theme-toggle svg {
position: absolute;
transition: transform 0.4s ease;
}
[data-theme="light"] .theme-toggle .moon-icon { transform: translateY(40px); }
[data-theme="dark"] .theme-toggle .sun-icon { transform: translateY(-40px); }
/* Card / Table */
.card {
background: var(--bg-card);
border: 1px solid var(--border);
border-radius: 20px;
padding: 1.5rem;
box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1);
}
.table-container {
overflow-x: auto;
}
table {
width: 100%;
border-collapse: collapse;
}
th {
text-align: left;
padding: 1rem;
color: var(--text-muted);
font-weight: 600;
border-bottom: 2px solid var(--border);
}
td {
padding: 1.25rem 1rem;
border-bottom: 1px solid var(--border);
}
.badge {
padding: 4px 12px;
border-radius: 100px;
font-size: 0.875rem;
font-weight: 600;
background: rgba(37, 99, 235, 0.1);
color: var(--primary);
}
.action-btns {
display: flex;
gap: 8px;
}
.action-btn {
width: 36px;
height: 36px;
border-radius: 8px;
border: none;
cursor: pointer;
display: flex;
align-items: center;
justify-content: center;
transition: var(--transition);
}
.edit-btn { background: rgba(34, 197, 94, 0.1); color: var(--success); }
.delete-btn { background: rgba(239, 68, 68, 0.1); color: var(--danger); }
.edit-btn:hover { background: var(--success); color: white; }
.delete-btn:hover { background: var(--danger); color: white; }
/* Modal */
.modal-overlay {
position: fixed;
inset: 0;
background: rgba(0, 0, 0, 0.5);
backdrop-filter: blur(4px);
display: none;
align-items: center;
justify-content: center;
z-index: 1000;
}
.modal {
background: var(--bg-card);
width: 90%;
max-width: 480px;
padding: 2rem;
border-radius: 24px;
box-shadow: 0 20px 25px -5px rgba(0, 0, 0, 0.1);
}
.form-group {
margin-bottom: 1.25rem;
}
.form-group label {
display: block;
margin-bottom: 6px;
font-weight: 600;
font-size: 0.875rem;
}
.form-control {
width: 100%;
padding: 12px 16px;
border-radius: 10px;
border: 1px solid var(--border);
background: var(--bg-body);
color: var(--text-main);
transition: var(--transition);
}
.form-control:focus {
outline: none;
border-color: var(--primary);
}
.modal-footer {
display: flex;
justify-content: flex-end;
gap: 12px;
margin-top: 2rem;
}
/* Toast */
#toast {
position: fixed;
bottom: 2rem;
left: 50%;
transform: translate(-50%, 150%);
padding: 1rem 2rem;
border-radius: 12px;
background: var(--text-main);
color: var(--bg-body);
box-shadow: 0 10px 15px -3px rgba(0, 0, 0, 0.2);
transition: var(--transition);
z-index: 2000;
}
#toast.show { transform: translate(-50%, 0); }
</style>
</head>
<body>
<aside class="sidebar">
<div class="logo">
<svg width="32" height="32" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><path d="m3 21 1.9-1.9a8.5 8.5 0 1 0-3.4-3.4z"/><path d="M9 13h1"/></svg>
<span>SIIP CRM</span>
</div>
<nav>
<a href="#" class="nav-link active">
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect width="7" height="9" x="3" y="3" rx="1"/><rect width="7" height="5" x="14" y="3" rx="1"/><rect width="7" height="9" x="14" y="12" rx="1"/><rect width="7" height="5" x="3" y="16" rx="1"/></svg>
<span>Instaladores</span>
</a>
</nav>
</aside>
<main class="main-content">
<header class="header">
<div class="title-section">
<h1>Gestión de Equipo</h1>
<p>Administra los técnicos registrados en el sistema</p>
</div>
<div class="actions">
<button class="btn theme-toggle" id="themeBtn" title="Cambiar tema">
<svg class="sun-icon" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="4"/><path d="M12 2v2"/><path d="M12 20v2"/><path d="m4.93 4.93 1.41 1.41"/><path d="m17.66 17.66 1.41 1.41"/><path d="M2 12h2"/><path d="M20 12h2"/><path d="m6.34 17.66-1.41 1.41"/><path d="m19.07 4.93-1.41 1.41"/></svg>
<svg class="moon-icon" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M12 3a6 6 0 0 0 9 9 9 9 0 1 1-9-9Z"/></svg>
</button>
<button class="btn btn-primary" id="addBtn">
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M5 12h14"/><path d="M12 5v14"/></svg>
Nuevo Instalador
</button>
</div>
</header>
<section class="card">
<div class="table-container">
<table id="installersTable">
<thead>
<tr>
<th>ID UCRM</th>
<th>Nombre Completo</th>
<th>WhatsApp</th>
<th>Acciones</th>
</tr>
</thead>
<tbody></tbody>
</table>
</div>
</section>
</main>
<!-- Modal Form -->
<div class="modal-overlay" id="modalOverlay">
<div class="modal">
<div class="modal-header">
<h2 id="modalTitle">Registro de Instalador</h2>
</div>
<form id="installerForm">
<input type="hidden" id="editIndex">
<div class="form-group" id="adminSelectGroup">
<label>Seleccionar Administrador UCRM</label>
<select id="adminSelect" class="form-control">
<option value="">-- Seleccionar de UCRM --</option>
<?php foreach ($admins as $admin): ?>
<option value="<?= $admin['id'] ?>" data-name="<?= $admin['nombre'] ?>"><?= $admin['nombre'] ?> (ID: <?= $admin['id'] ?>)</option>
<?php endforeach; ?>
</select>
</div>
<div class="form-group">
<label>ID de Usuario (Automático)</label>
<input type="number" id="installerId" class="form-control" required readonly>
</div>
<div class="form-group">
<label>Nombre Completo</label>
<input type="text" id="installerName" class="form-control" required readonly>
</div>
<div class="form-group">
<label>WhatsApp (Sin +)</label>
<input type="text" id="installerWhatsApp" class="form-control" required placeholder="Ej. 524181234567">
</div>
<div class="modal-footer">
<button type="button" class="btn" onclick="closeModal()">Cancelar</button>
<button type="submit" class="btn btn-primary">Confirmar</button>
</div>
</form>
</div>
</div>
<div id="toast"></div>
<script>
const store = {
installers: <?php echo json_encode($installersData['instaladores']); ?>,
theme: localStorage.getItem('theme') || 'light'
};
document.documentElement.setAttribute('data-theme', store.theme);
function renderTable() {
const tbody = document.querySelector('#installersTable tbody');
tbody.innerHTML = store.installers.map((inst, index) => `
<tr>
<td><span class="badge">#${inst.id}</span></td>
<td><strong>${inst.nombre}</strong></td>
<td>${inst.whatsapp}</td>
<td>
<div class="action-btns">
<button class="action-btn edit-btn" onclick="editInstaller(${index})" title="Editar">
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M17 3a2.85 2.83 0 1 1 4 4L7.5 20.5 2 22l1.5-5.5Z"/><path d="m15 5 4 4"/></svg>
</button>
<button class="action-btn delete-btn" onclick="deleteInstaller(${index})" title="Eliminar">
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M3 6h18"/><path d="M19 6v14c0 1-1 2-2 2H7c-1 0-2-1-2-2V6"/><path d="M8 6V4c0-1 1-2 2-2h4c1 0 2 1 2 2v2"/><line x1="10" x2="10" y1="11" y2="17"/><line x1="14" x2="14" y1="11" y2="17"/></svg>
</button>
</div>
</td>
</tr>
`).join('');
}
async function saveToServer() {
const formData = new FormData();
formData.append('action', 'save_installers');
formData.append('installers_data', JSON.stringify({ instaladores: store.installers }));
try {
const res = await fetch(window.location.href, { method: 'POST', body: formData });
const data = await res.json();
if (data.success) showToast('Éxito: Cambios guardados en config.json');
else showToast('Error al guardar datos', true);
} catch (e) { showToast('Fallo de conexión', true); }
}
function openModal(index = null) {
const form = document.getElementById('installerForm');
form.reset();
const idxField = document.getElementById('editIndex');
idxField.value = index !== null ? index : '';
document.getElementById('modalTitle').textContent = index !== null ? 'Editar Datos' : 'Registrar desde UCRM';
// Si es edición, ocultamos el selector de admins para evitar cambiar el ID accidentalmente
document.getElementById('adminSelectGroup').style.display = index !== null ? 'none' : 'block';
if (index !== null) {
const inst = store.installers[index];
document.getElementById('installerId').value = inst.id;
document.getElementById('installerName').value = inst.nombre;
document.getElementById('installerWhatsApp').value = inst.whatsapp;
}
document.getElementById('modalOverlay').style.display = 'flex';
}
function closeModal() { document.getElementById('modalOverlay').style.display = 'none'; }
// Manejar selección de admin
document.getElementById('adminSelect').onchange = (e) => {
const opt = e.target.options[e.target.selectedIndex];
if (opt.value) {
document.getElementById('installerId').value = opt.value;
document.getElementById('installerName').value = opt.dataset.name;
} else {
document.getElementById('installerId').value = '';
document.getElementById('installerName').value = '';
}
};
document.getElementById('installerForm').onsubmit = (e) => {
e.preventDefault();
const index = document.getElementById('editIndex').value;
const id = parseInt(document.getElementById('installerId').value);
// Validar existencia si es nuevo
if (index === '' && store.installers.some(inst => inst.id === id)) {
showToast('Este instalador ya está registrado', true);
return;
}
const data = {
id: id,
nombre: document.getElementById('installerName').value,
whatsapp: document.getElementById('installerWhatsApp').value
};
if (index === '') store.installers.push(data);
else store.installers[index] = data;
closeModal();
renderTable();
saveToServer();
};
function editInstaller(index) { openModal(index); }
function deleteInstaller(index) {
if (confirm('¿Eliminar a este instalador?')) {
store.installers.splice(index, 1);
renderTable();
saveToServer();
}
}
document.getElementById('themeBtn').onclick = () => {
store.theme = store.theme === 'light' ? 'dark' : 'light';
document.documentElement.setAttribute('data-theme', store.theme);
localStorage.setItem('theme', store.theme);
};
document.getElementById('addBtn').onclick = () => openModal();
function showToast(msg, isError = false) {
const toast = document.getElementById('toast');
toast.textContent = msg;
toast.style.background = isError ? 'var(--danger)' : '#1e293b';
toast.classList.add('show');
setTimeout(() => toast.classList.remove('show'), 3000);
}
renderTable();
</script>
</body>
</html>

View File

@ -13,140 +13,150 @@ class PluginData extends UcrmData
/**
* @var string
*/
public $twilioAccountSid;
public $ipserver;
/**
* @var string
*/
public $apitoken;
/**
* @var string
*/
public $unmsApiToken;
/**
* @var string
*/
public $tokencallbell;
/**
* @var string
*/
public $tokenstripe;
/**
* @var string
*/
public $hostServerFTP;
/**
* @var string
*/
public $usernameServerFTP;
/**
* @var string
*/
public $passServerFTP;
/**
* @var string
*/
public $ipPuppeteer;
/**
* @var string
*/
public $portPuppeteer;
/**
* @var string
*/
public $idPaymentAdminCRM;
/**
* @var bool
*/
public $cashPaymentMethodId;
/**
* @var bool
*/
public $courtesyPaymentMethodId;
/**
* @var bool
*/
public $bankTransferPaymentMethodId;
/**
* @var bool
*/
public $paypalPaymentMethodId;
/**
* @var bool
*/
public $creditCardPaypalPaymentMethodId;
/**
* @var bool
*/
public $creditCardStripePaymentMethodId;
/**
* @var bool
*/
public $stripeSubscriptionCreditCardPaymentMethodId;
/**
* @var bool
*/
public $paypalSubscriptionPaymentMethodId;
/**
* @var bool
*/
public $mercadopagoPaymentMethodId;
/**
* @var bool
*/
public $checkPaymentMethodId;
/**
* @var bool
*/
public $customPaymentMethodId;
/**
* @var bool
*/
public $notificationTypeText;
/**
* @var string
*/
public $installersDataWhatsApp;
/**
* @var bool
*/
public $debugMode;
/**
* @var bool
*/
public $logging_level;
/**
* @var string|null
*/
public $twilioAccountSid;
/**
* @var string|null
*/
public $twilioAuthToken;
/**
* @var string
* @var string|null
*/
public $twilioSmsNumber;
/**
* @var string
* @var string|null
*/
public $displayedErrors;
/**
* @var string
*/
public $event_client_add;
/**
* @var string
*/
public $event_client_archive;
/**
* @var string
*/
public $event_client_delete;
/**
* @var string
*/
public $event_client_edit;
/**
* @var string
*/
public $event_invoice_add;
/**
* @var string
*/
public $event_invoice_add_draft;
/**
* @var string
*/
public $event_invoice_draft_approved;
/**
* @var string
*/
public $event_invoice_delete;
/**
* @var string
*/
public $event_invoice_edit;
/**
* @var string
*/
public $event_payment_add;
/**
* @var string
*/
public $event_payment_delete;
/**
* @var string
*/
public $event_payment_edit;
/**
* @var string
*/
public $event_payment_unmatch;
/**
* @var string
*/
public $event_service_activate;
/**
* @var string
*/
public $event_service_add;
/**
* @var string
*/
public $event_service_archive;
/**
* @var string
*/
public $event_service_end;
/**
* @var string
*/
public $event_service_postpone;
/**
* @var string
*/
public $event_service_suspend_cancel;
/**
* @var string
*/
public $event_service_suspend;
/**
* @var string
*/
public $event_invoice_near_due;
/**
* @var string
*/
public $event_invoice_overdue;
/**
* @var string
*/
public $event_client_message;
/**
* @var string
*/
public $logging_level;
}

File diff suppressed because it is too large Load Diff

View File

@ -4,112 +4,49 @@ declare(strict_types=1);
namespace SmsNotifier\Facade;
use Attribute;
use Exception;
use GuzzleHttp\Exception\GuzzleException;
use SmsNotifier\Data\NotificationData;
use SmsNotifier\Factory\MessageTextFactory;
use SmsNotifier\Service\Logger;
use SmsNotifier\Service\SmsNumberProvider;
use GuzzleHttp\Client;
use Ubnt\UcrmPluginSdk\Service\UcrmApi;
/*
* send message to client's number
*/
use Ubnt\UcrmPluginSdk\Service\PluginConfigManager;
use Stripe\StripeClient;
abstract class AbstractStripeOperationsFacade
{
/**
* @var Logger
*/
protected $logger;
/**
* @var MessageTextFactory
*/
protected $messageTextFactory;
/**
* @var SmsNumberProvider
*/
protected $clientPhoneNumber;
/**
* @var UcrmApi
*/
protected $ucrmApi;
protected $stripeCustomAttributeID;
protected $clabeInterbancariaBanamexID;
public function __construct(
Logger $logger,
MessageTextFactory $messageTextFactory,
SmsNumberProvider $clientPhoneNumber
) {
public function __construct(Logger $logger, MessageTextFactory $messageTextFactory, SmsNumberProvider $clientPhoneNumber) {
$this->logger = $logger;
$this->messageTextFactory = $messageTextFactory;
$this->clientPhoneNumber = $clientPhoneNumber;
$this->ucrmApi = UcrmApi::create();
}
/*
* Creates a PaymentIntent in Stripe for a Customer
*/
public function createPaymentIntent($event_json)
{
$this->logger->info("Iniciando creación de PaymentIntent en Stripe." . PHP_EOL);
public function createPaymentIntent(array $eventJson): void {
$config = PluginConfigManager::create()->loadConfig();
$stripe = new StripeClient($config['tokenstripe']);
$configManager = \Ubnt\UcrmPluginSdk\Service\PluginConfigManager::create();
$config = $configManager->loadConfig();
$StripeToken = $config['tokenstripe'];
$idPaymentAdmin = $config['idPaymentAdminCRM']; //ID del administrador que crea el PaymentIntent
$stripe = new \Stripe\StripeClient($StripeToken); //Token de clave privada para la API de Stripe
$customer = $eventJson['data']['object']['customer'] ?? null;
$amount = $eventJson['data']['object']['net_amount'] ?? 0;
// Asegurarse de que 'customer' esté presente en la estructura del evento
if (!isset($event_json['data']['object']['customer'])) {
$this->logger->info("Error: Invalid event structure. Customer ID not found." . PHP_EOL);
if (!$customer || $amount <= 0) {
$this->logger->warning("Datos inválidos para PaymentIntent: Customer=$customer, Amount=$amount");
return;
}
$customerId = $event_json['data']['object']['customer'];
if (is_null($customerId)) {
$this->logger->info("Error: Customer ID is null." . PHP_EOL);
return;
}
// Validar que net_amount está presente
if (!isset($event_json['data']['object']['net_amount'])) {
$this->logger->info("Error: net_amount not found in event data." . PHP_EOL);
return;
}
$amount = $event_json['data']['object']['net_amount'];
//convertir en positivo
if ($event_json['data']['object']['net_amount'] < 0) {
$this->logger->warning("Error: net_amount es negativo." . PHP_EOL);
return;
}
//imprimir la cantidad del PaymentIntent
$this->logger->info("Cantidad del PaymentIntent: " . $amount . PHP_EOL);
try {
// Obtener información del cliente desde Stripe
$stripeQuery = $stripe->customers->retrieve($customerId, []);
$UCRM_clientID = $stripeQuery['metadata']['ucrm_client_id']; // ID del cliente en Ubiquiti UISP
$stripeCustomer = $stripe->customers->retrieve($customer);
$ucrmClientId = $stripeCustomer->metadata->ucrm_client_id ?? null;
// Obtener información del administrador actual
$this->ucrmApi = UcrmApi::create();
$currentUserAdmin = $this->ucrmApi->get('users/admins', []);
// Crear PaymentIntent
$paymentIntent = $stripe->paymentIntents->create([
'amount' => $amount,
$pi = $stripe->paymentIntents->create([
'amount' => (int)$amount,
'currency' => 'mxn',
'customer' => $customerId,
'customer' => $customer,
'payment_method_types' => ['customer_balance'],
'payment_method_data' => ['type' => 'customer_balance'],
'confirm' => true,
@ -120,423 +57,189 @@ abstract class AbstractStripeOperationsFacade
],
],
'metadata' => [
'clientId' => $UCRM_clientID, // ID del cliente en Ubiquiti
'clientId' => $ucrmClientId,
'createdBy' => 'UCRM',
'paymentType' => 'card.one_time',
'signedInAdminId' => $idPaymentAdmin, // ID del administrador que crea el PaymentIntent
'signedInAdminId' => $config['idPaymentAdminCRM'],
'tipoPago' => 'Transferencia Bancaria'
],
]);
$this->logger->info("PaymentIntent creado: " . $paymentIntent->id . PHP_EOL);
} catch (\Stripe\Exception\ApiErrorException $e) {
$this->logger->info("Error creando PaymentIntent: " . $e->getMessage() . PHP_EOL);
$this->logger->info("PaymentIntent creado: " . $pi->id);
} catch (\Exception $e) {
$this->logger->error("Error creando PaymentIntent: " . $e->getMessage());
}
}
public function registerPaymentFromWebhook($event_json)
{
$this->logger->info("Procesando pago funded desde webhook..." . PHP_EOL);
$configManager = \Ubnt\UcrmPluginSdk\Service\PluginConfigManager::create();
$config = $configManager->loadConfig();
$StripeToken = $config['tokenstripe'];
$stripe = new \Stripe\StripeClient($StripeToken);
public function registerPaymentFromWebhook(array $eventJson): void {
$config = PluginConfigManager::create()->loadConfig();
$stripe = new StripeClient($config['tokenstripe']);
$data = $eventJson['data']['object'];
$data = $event_json['data']['object'];
$piId = $data['applied_to_payment']['payment_intent'] ?? null;
if (!$piId) return;
if (isset($data['applied_to_payment']['payment_intent'])) {
$piId = $data['applied_to_payment']['payment_intent'];
try {
$pi = $stripe->paymentIntents->retrieve($piId);
$clientId = $pi->metadata->clientId ?? null;
$amount = abs($data['net_amount']) / 100;
try {
$pi = $stripe->paymentIntents->retrieve($piId);
$clientId = $pi->metadata->clientId ?? null;
if (!$clientId) return;
if ($clientId) {
$this->ucrmApi = UcrmApi::create();
$methodId = null;
foreach ($this->ucrmApi->get('payment-methods') as $m) {
if ($m['name'] === 'Transferencia bancaria') { $methodId = $m['id']; break; }
}
// Dynamic lookup for payment method ID
$methodId = null;
$methods = $this->ucrmApi->get('payment-methods');
foreach ($methods as $method) {
if ($method['name'] === 'Transferencia bancaria') {
$methodId = $method['id'];
break;
}
}
if (!$methodId) {
$this->logger->error("Error registrando pago: No se encontró el método 'Transferencia bancaria'");
return;
}
$this->ucrmApi->post('payments', [
'clientId' => (int)$clientId,
'amount' => $amount,
'currencyCode' => strtoupper($pi->currency),
'methodId' => $methodId,
'note' => "Pago via Webhook Stripe (Saldo Aplicado) - PI: $piId",
'createdDate' => date('c'),
]);
$this->logger->info("Pago registrado en UCRM para el cliente $clientId");
} else {
$this->logger->warning("No se encontró clientId en metadata del PaymentIntent $piId");
}
} catch (\Exception $e) {
$this->logger->error("Error registrando pago: " . $e->getMessage());
}
if ($methodId) {
$this->ucrmApi->post('payments', [
'clientId' => (int)$clientId,
'amount' => abs($data['net_amount']) / 100,
'currencyCode' => strtoupper($pi->currency),
'methodId' => $methodId,
'note' => "Stripe (Saldo Aplicado) - PI: $piId",
'createdDate' => date('c'),
]);
}
} catch (\Exception $e) {
$this->logger->error("Error en webhook: " . $e->getMessage());
}
}
public function createStripeClient(NotificationData $notificationData, string $tagName = '', bool $generateSpei = false): void {
$config = PluginConfigManager::create()->loadConfig();
$stripe = new StripeClient($config['tokenstripe']);
$clientData = $notificationData->clientData;
$clientId = $clientData['id'];
/*
* Creates the Stripe Customer
*/
public function createStripeClient(NotificationData $notificationData, $tagStripe = false): void
{
$configManager = \Ubnt\UcrmPluginSdk\Service\PluginConfigManager::create();
$config = $configManager->loadConfig();
// the "exportFormat" key must be defined in plugin's manifest file, see the link above
$IPServer = $config['ipserver'];
$UCRMAPIToken = $config['apitoken'];
$StripeToken = $config['tokenstripe'];
$baseUri = 'https://' . $IPServer . '/crm/api/v1.0/'; //endpoint de la API REST del CRM
$this->ucrmApi = UcrmApi::create();
$stripeId = null;
foreach ($clientData['attributes'] as $attr) {
if ($attr['key'] === 'stripeCustomerId') { $stripeId = $attr['value']; break; }
}
$stripe = new \Stripe\StripeClient($StripeToken); //Token de clave privada en modo prueba para la API de Stripe
//$stripe = new \Stripe\StripeClient('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'); //Token de clave privada en modo prodcucción para la API de Stripe
// Si ya tiene StripeID, solo removemos la etiqueta si se especificó una
if ($stripeId) {
if ($tagName) $this->removeStripeTag($clientId, $tagName);
return;
}
//valor de $notificationData en json
$this->logger->info("Valor de notificationData: " . json_encode($notificationData) . PHP_EOL);
if ($this->createCustomerStripe($notificationData, $stripe, $config, $generateSpei)) {
if ($tagName) $this->removeStripeTag($clientId, $tagName);
}
}
//ejemplo de notificationData: {"uuid":"0be6fee6-db1d-4ab5-a52c-2ee87b04315e","changeType":"edit","entity":"client","entityId":170,"message":null,"clientId":170,"eventName":"client.edit","clientData":{"id":170,"userIdent":null,"previousIsp":null,"isLead":false,"clientType":1,"companyName":null,"companyRegistrationNumber":null,"companyTaxId":null,"companyWebsite":null,"street1":"San Luis 34","street2":null,"city":"Dolores Hidalgo","countryId":173,"stateId":null,"zipCode":"37800","fullAddress":"San Luis 34, Dolores Hidalgo, 37800","invoiceStreet1":null,"invoiceStreet2":null,"invoiceCity":null,"invoiceStateId":null,"invoiceCountryId":null,"invoiceZipCode":null,"invoiceAddressSameAsContact":true,"note":null,"sendInvoiceByPost":null,"invoiceMaturityDays":null,"stopServiceDue":null,"stopServiceDueDays":null,"organizationId":1,"tax1Id":null,"tax2Id":null,"tax3Id":null,"registrationDate":"2025-03-24T00:00:00-0600","leadConvertedAt":"2025-03-24T15:01:22-0600","companyContactFirstName":null,"companyContactLastName":null,"isActive":false,"firstName":"Charicuas","lastName":"Quinero","username":null,"contacts":[{"id":176,"clientId":170,"email":null,"phone":null,"name":null,"isBilling":true,"isContact":true,"types":[{"id":1,"name":"Billing"},{"id":2,"name":"General"}]}],"attributes":[{"id":192,"clientId":170,"customAttributeId":10,"name":"Stripe Customer ID","key":"stripeCustomerId","value":"cus_S0T9BJ4dO0p0A3","clientZoneVisible":true},{"id":193,"clientId":170,"customAttributeId":11,"name":"Clabe Interbancaria","key":"clabeInterbancaria","value":"124180302040274015","clientZoneVisible":true}],"accountBalance":0,"accountCredit":0,"accountOutstanding":0,"currencyCode":"MXN","organizationName":"SIIP Pruebas","bankAccounts":[],"tags":[{"id":5,"name":"CREARCLABESTRIPE","colorBackground":"#e30000","colorText":"#fff"}],"invitationEmailSentDate":null,"avatarColor":"#ef5350","addressGpsLat":22.00854045,"addressGpsLon":-99.0272544,"isArchived":false,"generateProformaInvoices":null,"usesProforma":false,"hasOverdueInvoice":false,"hasOutage":false,"hasSuspendedService":false,"hasServiceWithoutDevices":true,"referral":null,"hasPaymentSubscription":false,"hasAutopayCreditCard":false},"serviceData":null,"invoiceData":null,"paymentData":null}
private function removeStripeTag(int $clientId, string $tagName): void {
foreach ($this->ucrmApi->get('client-tags', []) as $tag) {
if ($tag['name'] === $tagName) {
$this->ucrmApi->patch("clients/$clientId/remove-tag/{$tag['id']}");
break;
}
}
}
//obtener el id del cliente
$clientId = $notificationData->clientData['id'];
protected function createCustomerStripe(NotificationData $notificationData, StripeClient $stripe, array $config, bool $generateSpei = false): bool {
$clientData = $notificationData->clientData;
$clientId = $clientData['id'];
$name = trim(($clientData['firstName'] ?? '') . ' ' . ($clientData['lastName'] ?? ''));
//si en attributes del cliente encuentra el custom field de Stripe Customer ID entonces no se vuelve a crear el cliente en Stripe
$attributes = $notificationData->clientData['attributes'];
$this->logger->info("Valor de attributes: " . json_encode($attributes) . PHP_EOL);
$stripeCustomerId = null;
foreach ($attributes as $attribute) {
if ($attribute['key'] === 'stripeCustomerId') {
$stripeCustomerId = $attribute['value'];
$email = '';
foreach ($clientData['contacts'] as $contact) {
if ($contact['email'] && filter_var($contact['email'], FILTER_VALIDATE_EMAIL)) {
$email = $contact['email'];
break;
}
}
$customAttributes = $this->ucrmApi->get('custom-attributes/', ['attributeType' => 'client']);//Obtener los atributos del sistema que estén vinculados a la entidad "cliente"
//$this->logger->info("result del custom Attributes: " . json_encode($customAttributes) . PHP_EOL);
// Verificar si se obtuvieron los atributos
if ($customAttributes && is_array($customAttributes)) {
foreach ($customAttributes as $attribute) {
// Verificar si 'name' contiene la palabra 'Stripe' sin distinguir mayúsculas y minúsculas
if (isset($attribute['name']) && stripos($attribute['name'], 'Stripe') !== false) {
$this->logger->info("ID correspondiente a 'Customer Stripe ID': " . $attribute['id'] . PHP_EOL);
$this->stripeCustomAttributeID = $attribute['id'];
} else if (isset($attribute['name']) && stripos($attribute['name'], 'Clabe') !== false) {
//$this->logger->info("ID correspondiente a 'Clabe Interbancaria Banamex': " . $attribute['id'] .PHP_EOL);
$this->clabeInterbancariaBanamexID = $attribute['id'];
}
}
} else {
$this->logger->info("Error al obtener los atributos personalizados." . PHP_EOL);
}
if ($stripeCustomerId) {
$this->logger->info("El cliente ya tiene un Stripe Customer ID: " . $stripeCustomerId . PHP_EOL);
if ($tagStripe) {
$tagsIds = $this->ucrmApi->get('client-tags', []);
//ejemplo de respuesta $tagsIds: [{"id":4,"name":"EQUIPO A CREDITO","colorBackground":"#fed74a","colorText":"#444"},{"id":5,"name":"CREARCLABESTRIPE","colorBackground":"#e30000","colorText":"#fff"}]
//obtener el ID de la etiqueta o tag llamada "CREAR CLABE STRIPE" y asiganarla a una variable $tagCrearClabeStripe basandose en el ejemplo de respuesta anterior
$tagCrearClabeStripe = null;
foreach ($tagsIds as $tag) {
if ($tag['name'] === 'CREAR CLABE STRIPE') {
$tagCrearClabeStripe = $tag['id'];
// $this->logger->info("ID de la etiqueta 'CREAR CLABE STRIPE': " . $tagCrearClabeStripe . PHP_EOL);
break;
}
}
if (!$tagCrearClabeStripe) {
$this->logger->info("No se encontró la etiqueta 'CREAR CLABE STRIPE'." . PHP_EOL);
return;
}
$this->ucrmApi->patch("clients/$clientId/remove-tag/" . $tagCrearClabeStripe);
$this->logger->info("Se eliminó la etiqueta 'CREAR CLABE STRIPE' del cliente." . PHP_EOL);
}
return;
}
//$this->logger->info("Ya dentro del metodo Create Stripe Client " . PHP_EOL);
//$this->sendWhatsApp('Hola Dany');
if ($tagStripe) {
$tagsIds = $this->ucrmApi->get('client-tags', []);
//ejemplo de respuesta $tagsIds: [{"id":4,"name":"EQUIPO A CREDITO","colorBackground":"#fed74a","colorText":"#444"},{"id":5,"name":"CREARCLABESTRIPE","colorBackground":"#e30000","colorText":"#fff"}]
//obtener el ID de la etiqueta o tag llamada "CREARCLABESTRIPE" y asiganarla a una variable $tagCrearClabeStripe basandose en el ejemplo de respuesta anterior
$tagCrearClabeStripe = null;
foreach ($tagsIds as $tag) { //revisamos los tags del cliente y si encuentra el tag de "CREARCLABESTRIPE" entonces lo asigna a la variable $tagCrearClabeStripe el valor de id
if ($tag['name'] === 'CREAR CLABE STRIPE') {
$tagCrearClabeStripe = $tag['id'];
// $this->logger->info("ID de la etiqueta 'CREAR CLABE STRIPE': " . $tagCrearClabeStripe . PHP_EOL);
break;
$phone = '';
foreach ($clientData['contacts'] as $contact) {
foreach ($contact['types'] as $type) {
if ($type['name'] === 'WhatsApp') {
$phone = $this->validarNumeroTelefono($contact['phone']);
break 2;
}
}
if (!$tagCrearClabeStripe) {
$this->logger->info("No se encontró la etiqueta 'CREAR CLABE STRIPE'." . PHP_EOL);
return;
}
$this->createCustomerStripe($notificationData, $stripe, $baseUri, $UCRMAPIToken);
$this->ucrmApi->patch("clients/$clientId/remove-tag/" . $tagCrearClabeStripe);
$this->logger->info("Se eliminó la etiqueta 'CREAR CLABE STRIPE' del cliente." . PHP_EOL);
return;
$phone = $this->validarNumeroTelefono($contact['phone']);
}
// Verificar si la solicitud fue exitosa (código de estado 200)
//https://172.16.5.120/crm/api/v1.0/payments?limit=1&clientId=1992&order=createdDate&direction=DESC
$notification_client_data = $notificationData->clientData; //array con los datos del cliente
// Asegúrate de trabajar con un objeto PHP
$dataObject = json_decode(json_encode($notification_client_data)); // Convertir a JSON y luego decodificar como objeto
if (!is_object($dataObject)) {
$this->logger->info("Error: Los datos del cliente no pudieron ser procesados." . PHP_EOL);
return;
}
$this->createCustomerStripe($notificationData, $stripe, $baseUri, $UCRMAPIToken);
}
// /**
// * implement in subclass with the specific messaging provider
// * @see TwilioNotifierFacade::sendWhatsApp()
// */
abstract protected function sendWhatsApp(
string $message
): void;
function createCustomerStripe($notificationData, $stripe, $baseUri, $token): bool
{
$this->ucrmApi = UcrmApi::create();
$this->logger->info("Creando el Customer Stripe" . PHP_EOL);
$ucrm_client_id = $notificationData->clientData['id'];
$clientCRMContacts = $notificationData->clientData['contacts'];
$firstName = $notificationData->clientData['firstName']; //obtenemos nombre del cliente y lo almacenamos en una variable
$lastName = $notificationData->clientData['lastName']; //obtenemos apellidos del cliente y lo almacenamos en una variable
$this->logger->info('El cliente a procesar es : ' . $firstName . ' ' . $lastName . PHP_EOL); //impresión de control para ver el nombre del cliente a procesar en consola
$cadenaNotificationData = json_encode($notificationData);
$this->logger->info("Datos notificationData: " . $cadenaNotificationData . PHP_EOL);
foreach ($clientCRMContacts as $contact) { //aquí revisamos los datos de contacto del cliente, como pueden ser uno o varios se hace uso del foreach
$this->logger->info('Ya dentro del FOREACH!!! ' . PHP_EOL);
$phone = ''; //variable para almacenar el número de teléfono del cliente que se mandará a su cuenta de Stripe
foreach ($contact['types'] as $type) { //revisamos el tipo de contacto
$this->logger->info('REVISANDO EL PRIMER CONTACTO!!! ' . PHP_EOL);
if ($type['name'] === "WhatsApp") { //si es de tipo whatsapp..
//print_r("Encontré un tipo de Contacto para WhatsAapp" . PHP_EOL);
$phone = $contact['phone']; //se asigna como número de teléfono
break;
} else {
$phone = $contact['phone']; //Si no encuentra un tipo de contacto como Whatsapp entonces el último número de celular obtenido es el que se envía
}
}
$email = $this->validarEmail($contact['email']); //validamos el email del cliente mediante la función validarEmail para que en caso de que no esté bien formado lo ponga en blanco
}
$this->logger->info('ahora se procede a validar el teléfono!!! ' . PHP_EOL);
//si la variable $phone no esta vacia o null se valida con la funcion validarNumeroTelefono
if ($phone == '') {
$this->logger->info('El número de teléfono está vacío o no es válido.' . PHP_EOL);
//se manda a crear pero sin numero de teléfono
$this->logger->info('AHORA SE MANDA A CREAR EL CLIENTE A STRIPE!!! ' . PHP_EOL);
$result = $stripe->customers->create([
"description" => "Cliente SIIP CRM con client_id: " . $ucrm_client_id,
"name" => $firstName . ' ' . $lastName,
"email" => $email,
'preferred_locales' => ['es-419']
]); //aquí se contruye la petición de creación de un customer en Stripe y se consume la API
} else {
$phone = $this->validarNumeroTelefono($phone); //validamos y procesamos el número de celular del cliente para que se vaya a 10 dígitos ya que Stripe por si mismo les agrega el +52
$this->logger->info('AHORA SE MANDA A CREAR EL CLIENTE A STRIPE!!! ' . PHP_EOL);
$result = $stripe->customers->create([
"description" => "Cliente SIIP CRM con client_id: " . $ucrm_client_id,
"name" => $firstName . ' ' . $lastName,
"phone" => $phone,
"email" => $email,
'preferred_locales' => ['es-419']
]); //aquí se contruye la petición de creación de un customer en Stripe y se consume la API
}
$this->logger->info(json_encode($result) . PHP_EOL); //imprimir respuesta de creación del cliente
//ejemplo de respuesta de stripe cuando se crea el cliente: {"id":"cus_Rkh9CoRTpjlZUu","object":"customer","address":null,"balance":0,"created":1739250682,"currency":null,"default_source":null,"delinquent":false,"description":"Cliente SIIP CRM con client_id: 167","discount":null,"email":null,"invoice_prefix":"5017E9D3","invoice_settings":{"custom_fields":null,"default_payment_method":null,"footer":null,"rendering_options":null},"livemode":false,"metadata":[],"name":"Javier Alatorre","next_invoice_sequence":1,"phone":"4181878106","preferred_locales":["es-419"],"shipping":null,"tax_exempt":"none","test_clock":null}
//validar si se creo el cliente con la API de Stripe $stripe->customers->create
if (!isset($result['id'])) { //si no existe el ID del cliente en la respuesta de Stripe entonces se manda un mensaje de error
$this->logger->info('Error al crear el cliente en Stripe: ' . json_encode($result) . PHP_EOL);
}
$stripe_customer_id = $result['id']; //obtenemos el customer id de Stripe recibido por medio del consumo del API y lo asignamos a una variable $stripe_customer_id
sleep(2);
$result2 = $stripe->customers->update(
$stripe_customer_id,
['metadata' => ['ucrm_client_id' => '' . $ucrm_client_id]]
);//aquí se contruye la petición de actualización de un customer en Stripe para agregarle los metadatos y se consume la API, requiere el ID del Customer, por eso antes lo almacenamos en la variable $stripe_customer_id
$this->logger->info(json_encode($result2) . PHP_EOL); // imprimir respuesta de actualización del metadata del cliente
sleep(2);
$result3 = $stripe->customers->createFundingInstructions(
$stripe_customer_id,
[
'currency' => 'mxn',
'funding_type' => 'bank_transfer',
'bank_transfer' => ['type' => 'mx_bank_transfer'],
]
); //aquí se contruye la petición de creación de Instrucciones de Fondeo de un customer en Stripe (O su metodo de pago como cuenta bancaria para transferencias) y se consume la API
$this->logger->info(json_encode($result3) . PHP_EOL); //imprimir respuesta de asignación de cuenta bancaria de transferencia
// Acceder al valor de "clabe" para BANORTE
$clabeInterbancaria = $result3['bank_transfer']['financial_addresses'][0]['spei']['clabe']; //Asignamos la clabe obtenida con la API de Stripe con la solicitud anterior a la variable $clabe
$stripeID = $this->stripeCustomAttributeID;
$clabeInterbancariaID = $this->clabeInterbancariaBanamexID;
$customer = $stripe->customers->update(
$stripe_customer_id,
[
'metadata' => [
'clabe' => $clabeInterbancaria, // Nueva clabe
],
]
);
$this->logger->info("CLABE guardada en metadata: " . $customer->metadata->clabe . PHP_EOL);
$json_data_patch = '{
"attributes": [
{
"value": "' . $stripe_customer_id . '",
"customAttributeId":' . $stripeID . '
},
{
"value": "' . $clabeInterbancaria . '",
"customAttributeId":' . $clabeInterbancariaID . '
}
]
}'; //JSON para hacer patch de los custom fields del cliente en el UISCP CRM, Campo para el Stripe Customer ID y la Clabe interbancaria
//valor de $json_data_patch en json
//$this->logger->info("Valor de json_data_patch: " . $json_data_patch . PHP_EOL);
// try{
// $responsepatchCRM= $this->ucrmApi->patch("clients/$ucrm_client_id", [
// "attributes" => [
// [
// "value" => $stripe_customer_id,
// "customAttributeId" => $stripeID
// ],
// [
// "value" => $clabeInterbancaria,
// "customAttributeId" => $clabeInterbancariaID
// ]
// ]
// ]); //aquí se contruye la petición para hacer patch hacia el cliente en sus custom fields con la API del UISP UCRM
// $this->logger->info("Se actualizó el cliente en UCRM con el Stripe Customer ID y la Clabe Interbancaria." . PHP_EOL);
// $this->logger->info(json_encode($responsepatchCRM) . PHP_EOL); //imprimir respuesta del patch de CRM con la clabe y Customer ID Stripe
// }catch (GuzzleException $e) {
// $this->logger->info("Error al hacer el patch al cliente en UCRM: " . $e->getMessage() . PHP_EOL);
// return false; // Return false if patch fails
// }
$clientguzz = new Client(); //instancia de cliente GuzzleHttp para consumir API UISP CRM
try {
//aquí se contruye la petición para hacer patch hacia el cliente en sus custom fields con la API del UISP UCRM
$responseCRM = $clientguzz->patch($baseUri . 'clients/' . $ucrm_client_id, [
'json' => json_decode($json_data_patch, true),
'headers' => [
'X-Auth-App-Key' => $token, // Cambia el nombre de la cabecera de autorización
'Accept' => 'application/json', // Indica que esperamos una respuesta en formato JSON
],
'verify' => false,
$customer = $stripe->customers->create([
'name' => $name,
'email' => $email,
'phone' => $phone,
'description' => "Cliente SIIP ID: $clientId",
'metadata' => ['ucrm_client_id' => $clientId],
'preferred_locales' => ['es-419']
]);
$clabe = '';
if ($generateSpei) {
$fi = $stripe->customers->createFundingInstructions($customer->id, [
'currency' => 'mxn',
'funding_type' => 'bank_transfer',
'bank_transfer' => ['type' => 'mx_bank_transfer'],
]);
$clabe = $fi->bank_transfer->financial_addresses[0]->spei->clabe ?? '';
$stripe->customers->update($customer->id, ['metadata' => ['clabe' => $clabe]]);
}
} catch (GuzzleException $error) {
$this->logger->info("Error al hacer el patch al CRM: " . $error->getMessage() . PHP_EOL);
return false; // Return false if patch fails
}
$this->logger->info(json_encode($responseCRM) . PHP_EOL); //imprimir respuesta del patch de CRM con la clabe y Customer ID Stripe
$attrs = $this->ucrmApi->get('custom-attributes', ['attributeType' => 'client']);
$stripeAttrId = null; $clabeAttrId = null;
foreach ($attrs as $a) {
if (stripos($a['name'], 'Stripe') !== false) $stripeAttrId = $a['id'];
if (stripos($a['name'], 'Clabe') !== false) $clabeAttrId = $a['id'];
}
return true; // Return true if all operations succeed
}
function validarNumeroTelefono($telefono)
{
// Eliminar espacios y guiones
$telefono = preg_replace('/\s+|-/', '', $telefono);
// Eliminar caracteres no numéricos
$telefono = preg_replace('/\D/', '', $telefono);
// Verificar si quedan exactamente 10 dígitos
if (strlen($telefono) === 10) {
// Retornar el número de teléfono correctamente formateado
return $telefono;
} elseif (strlen($telefono) > 10) {
// Si el número tiene más de 10 dígitos, quitar los primeros
return substr($telefono, -10);
} else {
// Si el número tiene menos de 10 dígitos, retornar cadena vacía
return '';
if ($stripeAttrId && $clabeAttrId) {
$this->ucrmApi->patch("clients/$clientId", [
'attributes' => [
['value' => $customer->id, 'customAttributeId' => $stripeAttrId],
['value' => $clabe, 'customAttributeId' => $clabeAttrId]
]
]);
}
return true;
} catch (\Exception $e) {
$this->logger->error("Error creating Stripe customer: " . $e->getMessage());
return false;
}
}
function validarEmail($email)
{
$this->logger->info('SE VALIDA EL EMAIL!!! ' . PHP_EOL);
// Utilizar la función filter_var con el filtro FILTER_VALIDATE_EMAIL para validar el email
if (filter_var($email, FILTER_VALIDATE_EMAIL)) {
// Si el email es válido, devolver el email
return $email;
} else {
// Si el email no es válido, devolver una cadena vacía
return '';
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 patchClientCustomAttribute(int $clientId, int $attributeId, string $value): bool {
try {
$this->ucrmApi->patch("clients/$clientId", [
"attributes" => [['value' => $value, 'customAttributeId' => $attributeId]]
]);
return true;
} catch (\Exception $e) {
$this->logger->error("Error patching attribute: " . $e->getMessage());
return false;
}
}
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 {
$n = preg_replace('/\D/', '', (string)$n);
return (strlen($n) >= 10) ? substr($n, -10) : '';
}
abstract protected function sendWhatsApp(NotificationData $notificationData, string $clientPhoneNumber): void;
}

View File

@ -11,6 +11,9 @@ use SmsNotifier\Service\Logger;
use SmsNotifier\Service\OptionsManager;
use SmsNotifier\Service\SmsNumberProvider;
use Twilio\Rest\Client;
use Ubnt\UcrmPluginSdk\Service\UcrmApi;
use GuzzleHttp\Client as GuzzleClient;
use GuzzleHttp\Exception\GuzzleException;
class PluginNotifierFacade extends AbstractStripeOperationsFacade
@ -36,16 +39,49 @@ class PluginNotifierFacade extends AbstractStripeOperationsFacade
$this->pluginData = $optionsManager->load();
}
public function updatePasswordAntenaIfNeeded(int $clientId, array $jsonData): void
{
$this->ucrmApi = UcrmApi::create();
$customAttributes = $this->ucrmApi->get('custom-attributes/', ['attributeType' => 'client']);
$idPasswordAntenaCliente = null;
foreach ($customAttributes as $attribute) {
if (isset($attribute['key']) && stripos($attribute['key'], 'passwordAntenaCliente') !== false) {
$idPasswordAntenaCliente = $attribute['id'];
break;
}
}
if (!$idPasswordAntenaCliente) {
$this->logger->warning("No se encontró el atributo personalizado 'passwordAntenaCliente'");
return;
}
$passwordAntenaValue = null;
$attributes = $jsonData['extraData']['entity']['attributes'] ?? [];
foreach ($attributes as $attribute) {
if ($attribute['customAttributeId'] === $idPasswordAntenaCliente) {
$passwordAntenaValue = $attribute['value'];
break;
}
}
if ($passwordAntenaValue === null || $passwordAntenaValue === '') {
$password = $this->getVaultCredentialsByClientId($clientId);
if ($password && strpos($password, 'Error') !== 0) {
if ($this->patchClientCustomAttribute($clientId, $idPasswordAntenaCliente, $password)) {
$this->logger->info("Se actualizó passwordAntenaCliente para el cliente $clientId");
}
}
}
}
/*
* Send WhatsApp through the CallBell API
* Implementation of abstract method from AbstractStripeOperationsFacade
*/
protected function sendWhatsApp(
string $message,
): void {
$this->logger->debug('Enviando mensaje desde twilio notifier facade: '.$message);
protected function sendWhatsApp(NotificationData $notificationData, string $clientPhoneNumber): void
{
$this->logger->info("PluginNotifierFacade: Enviando WhatsApp (Mock): " . $clientPhoneNumber);
}
/*
@ -53,6 +89,6 @@ class PluginNotifierFacade extends AbstractStripeOperationsFacade
*/
private function getSenderNumber(): string
{
return $this->pluginData->twilioSmsNumber;
return $this->pluginData->twilioSmsNumber ?? '';
}
}

View File

@ -11,18 +11,10 @@ use SmsNotifier\Service\Logger;
use SmsNotifier\Service\OptionsManager;
use SmsNotifier\Service\SmsNumberProvider;
use Twilio\Rest\Client;
use SmsNotifier\Facade\ClientCallBellAPI;
class TwilioNotifierFacade extends AbstractMessageNotifierFacade
{
/**
* @var Client
*/
private $twilioClient;
/**
* @var PluginData
*/
private $pluginData;
public function __construct(
@ -32,62 +24,41 @@ class TwilioNotifierFacade extends AbstractMessageNotifierFacade
OptionsManager $optionsManager
) {
parent::__construct($logger, $messageTextFactory, $smsNumberProvider);
// load config data
$this->pluginData = $optionsManager->load();
}
/*
* Get Twilio SMS API object (unless it's already initialized)
*/
public function getTwilioClient(): Client
{
if (!$this->twilioClient) {
$this->twilioClient = new Client(
$this->pluginData->token_callbell
$this->pluginData->twilioAccountSid ?? '',
$this->pluginData->twilioAuthToken ?? ''
);
}
return $this->twilioClient;
}
/*
* Send WhatsApp through the CallBell API
*/
protected function sendWhatsApp(
string $message,
): void {
$this->logger->debug('Enviando mensaje desde twilio notifier facade: '.$message);
protected function sendWhatsApp(NotificationData $notificationData, string $clientSmsNumber): void
{
$this->logger->info("TwilioNotifierFacade: Enviando WhatsApp a $clientSmsNumber (Mock)");
}
/*
* Send message through the Twilio client
*/
protected function sendMessage(
NotificationData $notificationData,
string $clientSmsNumber,
string $messageBody
): void {
$this->logger->debug(sprintf('Sending: %s', $messageBody));
$messageInstance = $this->getTwilioClient()->messages->create(
$clientSmsNumber,
[
'from' => $this->getSenderNumber(),
'body' => $messageBody,
]
);
$this->logger->debug((string) $messageInstance);
$this->logger->info(sprintf('Twilio status: %s, message id: %s', $messageInstance->status, $messageInstance->sid));
if ($messageInstance->errorCode) {
$this->logger->warning(sprintf('Twilio error: %s %s', $messageInstance->errorCode, $messageInstance->errorMessage));
try {
$message = $this->getTwilioClient()->messages->create(
$clientSmsNumber,
[
'from' => $this->pluginData->twilioSmsNumber ?? '',
'body' => $messageBody,
]
);
$this->logger->info(sprintf('Twilio status: %s, ID: %s', $message->status, $message->sid));
} catch (\Exception $e) {
$this->logger->error("Error en Twilio: " . $e->getMessage());
}
}
/*
* Phone number of sender - required by Twilio. In this plugin, we only load it from config.
*/
private function getSenderNumber(): string
{
return $this->pluginData->twilioSmsNumber;
}
}

View File

@ -71,24 +71,18 @@ class Plugin
$this->pluginNotifierFacade = $pluginNotifierFacade;
$this->pluginOxxoNotifierFacade = $pluginOxxoNotifierFacade;
$this->notificationDataFactory = $notificationDataFactory;
$this->ucrmApi = UcrmApi::create();
}
public function run(): void
{
// $hola = PHP_SAPI;
// $this->logger->info('valor de PHP_SAPI: ' . $hola);
if (PHP_SAPI === 'fpm-fcgi') {
// $this->logger->debug('Whatsapp over HTTP started');
$this->processHttpRequest();
// $this->logger->debug('HTTP request processing ended.');
} elseif (PHP_SAPI === 'cli') {
// $this->logger->debug('Whatsapp over CLI started');
$this->processCli();
// $this->logger->debug('CLI process ended.');
} else {
throw new \UnexpectedValueException('Unknown PHP_SAPI type: ' . PHP_SAPI);
}
}
private function processCli(): void
@ -106,9 +100,8 @@ class Plugin
$this->logger->setLogLevelThreshold(LogLevel::DEBUG);
}
$userInput = file_get_contents('php://input'); //se recibe el json del webhook
//imprimir el json del webhook
$this->logger->debug('valor del webhook: ' . $userInput . PHP_EOL);
$userInput = file_get_contents('php://input');
$this->logger->debug('Payload recibido: ' . $userInput . PHP_EOL);
if (! $userInput) {
$this->logger->warning('no input');
@ -162,36 +155,23 @@ class Plugin
case 'cash_balance.funds_available':
$this->logger->info('Evento de Pago de fondos disponibles recibido: ' . json_encode($jsonData) . PHP_EOL);
break;
case 'energy.alert':
$this->logger->info('Evento de Energía recibido: ' . $jsonData['message'] . PHP_EOL);
break;
case 'oxxo.request':
$this->logger->info('Evento de solicitud de referencia de oxxo recibido' . PHP_EOL);
$this->logger->info('Evento de solicitud de referencia de OXXO recibido');
// Construir la URL basada en el "client_id"
// $url = "https://siip.mx/wp/wp-content/uploads/img/voucher.png";
if (! empty($jsonData['amount'])) {
$this->logger->info('Referencia personalizada, Valor del monto: ' . $jsonData['amount'] . PHP_EOL);
$this->logger->info('Referencia personalizada, Monto: ' . $jsonData['amount']);
$intentos = 0;
do {
// if ($intentos > 1) {
// sleep(2);
// }
$responseOxxo = $this->pluginOxxoNotifierFacade->createOxxoPaymentIntent($jsonData, $jsonData['amount']);
$this->logger->info('Referencia personalizada, Valor de la respuesta: ' . json_encode($responseOxxo) . PHP_EOL);
//El array asociativo $responseOxxo es un array asosiativo con los siguientes campos: oxxo_reference, error, failDescription, clientID, amount
$this->logger->debug('Respuesta OXXO: ' . json_encode($responseOxxo));
$intentos++;
} while (strpos($responseOxxo['url'], 'https') !== 0 && $intentos < 3); //Mientras la url no contenga https y el número de intentos sea menor a 3
} while (strpos($responseOxxo['url'] ?? '', 'https') !== 0 && $intentos < 3);
} else {
$intentos = 0;
do {
// if ($intentos > 1) {
// sleep(2);
// }
$responseOxxo = $this->pluginOxxoNotifierFacade->createOxxoPaymentIntent($jsonData);
//El array asociativo $responseOxxo es un array asosiativo con los siguientes campos: oxxo_reference, error, failDescription, clientID, amount
$intentos++;
} while (strpos($responseOxxo['url'], 'https') !== 0 && $intentos < 3); //Mientras la url no contenga https y el número de intentos sea menor a 3
} while (strpos($responseOxxo['url'] ?? '', 'https') !== 0 && $intentos < 3);
}
//El array asociativo $responseOxxo es un array asosiativo con los siguientes campos: oxxo_reference, error, failDescription, clientID, amount
@ -210,33 +190,14 @@ class Plugin
'"amount": "' . $responseOxxo['amount'] . '"' .
'}';
header('Content-Type: application/json');
echo $response;
break;
} else {
//crear un json con variable $response que contenga las claves y valores del array $responseOxxo los cuales son: oxxo_reference, error, failDescription, clientID, clientFullName, amount
$response = '{' .
'"url": "' . $responseOxxo['url'] . '",' .
'"oxxo_reference": "' . $responseOxxo['oxxo_reference'] . '",' .
'"voucher_image_url": "' . $responseOxxo['voucher_image_url'] . '",' .
'"error": "' . $responseOxxo['error'] . '",' .
'"failDescription": "' . $responseOxxo['failDescription'] . '",' .
'"clientID": "' . $responseOxxo['clientID'] . '",' .
'"clientFullName": "' . $responseOxxo['clientFullName'] . '",' .
'"amount": "' . $responseOxxo['amount'] . '"' .
'}';
$this->logger->debug('Reponse que se envía a CallBell: ' . $response);
// Enviar el encabezado de respuesta como JSON
$response = json_encode($responseOxxo);
$this->logger->debug('Response enviado: ' . $response);
header('Content-Type: application/json');
// Enviar la respuesta en formato JSON
//echo json_encode($response);
echo $response;
break;
}
}
@ -244,13 +205,8 @@ class Plugin
return;
}
// $event_json = json_decode($userInput);
// $webhook_string = json_encode($event_json);
// $this->logger->debug("El valor de webhook_string: " . $webhook_string . PHP_EOL);
$notification = $this->notificationDataFactory->getObject($jsonData);
$this->logger->debug('valor el evento recibido por webhook: ' . $notification->eventName . PHP_EOL);
$this->logger->debug('Valor de JSON: ' . json_encode($jsonData) . PHP_EOL);
$this->logger->debug('Evento recibido: ' . $notification->eventName);
if ($notification->changeType === 'test') {
@ -281,85 +237,79 @@ class Plugin
$result = json_encode($notification);
$this->logger->debug('Notification encodificado en JSON:' . $result . PHP_EOL);
$datos_payment = $notification->paymentData;
$this->logger->debug('valor del payment data: ' . json_encode($datos_payment) . PHP_EOL);
$payment_method_id = $notification->paymentData['methodId'];
//$this->logger->debug('Metodo de pago: ' . $notification->paymentData['methodId'] . PHP_EOL);
$payment_method = '';
switch ($payment_method_id) {
case '11721cdf-a498-48be-903e-daa67552e4f6':
$payment_method = 'Cheque';
if ($config['checkPaymentMethodId']) {
if ($config['checkPaymentMethodId'] ?? false) {
$this->notifierFacade->verifyPaymentActionToDo($notification);
}
break;
case '6efe0fa8-36b2-4dd1-b049-427bffc7d369':
$payment_method = 'Efectivo';
if ($config['cashPaymentMethodId']) {
if ($config['cashPaymentMethodId'] ?? false) {
$this->notifierFacade->verifyPaymentActionToDo($notification);
}
break;
case '4145b5f5-3bbc-45e3-8fc5-9cda970c62fb':
$payment_method = 'Transferencia bancaria';
if ($config['bankTransferPaymentMethodId']) {
if ($config['bankTransferPaymentMethodId'] ?? false) {
$this->notifierFacade->verifyPaymentActionToDo($notification);
}
break;
case '78e84000-9b5b-44a4-8367-da43df86ce34':
$payment_method = 'PayPal';
if ($config['paypalPaymentMethodId']) {
if ($config['paypalPaymentMethodId'] ?? false) {
$this->notifierFacade->verifyPaymentActionToDo($notification);
}
break;
case '6da98bb9-6df7-4c41-8608-5cdd7fde7d5d':
$payment_method = 'Tarjeta de crédito PayPal';
if ($config['creditCardPaypalPaymentMethodId']) {
if ($config['creditCardPaypalPaymentMethodId'] ?? false) {
$this->notifierFacade->verifyPaymentActionToDo($notification);
}
break;
case '1dd098fa-5d63-4c8d-88b7-3c27ffbbb6ae':
$payment_method = 'Tarjeta de crédito Stripe';
if ($config['creditCardStripePaymentMethodId']) {
if ($config['creditCardStripePaymentMethodId'] ?? false) {
$this->notifierFacade->verifyPaymentActionToDo($notification);
}
break;
case 'b9e1e9d1-5c7b-41d2-b6b2-3e568d700290':
$payment_method = 'Suscripción de Stripe (tarjeta de crédito)';
if ($config['stripeSubscriptionCreditCardPaymentMethodId']) {
if ($config['stripeSubscriptionCreditCardPaymentMethodId'] ?? false) {
$this->notifierFacade->verifyPaymentActionToDo($notification);
}
break;
case '939f7701-00b7-4676-9b1e-17afb268c8ba':
$payment_method = 'Suscripción de PayPal';
if ($config['paypalSubscriptionPaymentMethodId']) {
if ($config['paypalSubscriptionPaymentMethodId'] ?? false) {
$this->notifierFacade->verifyPaymentActionToDo($notification);
}
break;
case '1c963e35-df24-444d-95d2-12592d5107e8':
$payment_method = 'MercadoPago';
if ($config['mercadopagoPaymentMethodId']) {
if ($config['mercadopagoPaymentMethodId'] ?? false) {
$this->notifierFacade->verifyPaymentActionToDo($notification);
}
break;
case 'd8c1eae9-d41d-479f-aeaf-38497975d7b3':
$payment_method = 'Personalizado';
if ($config['customPaymentMethodId']) {
if ($config['customPaymentMethodId'] ?? false) {
$this->notifierFacade->verifyPaymentActionToDo($notification);
}
break;
case '72271b72-5c0a-45e2-94d1-cdf4d7cf10e2':
$payment_method = 'Cortesía';
if ($config['courtesyPaymentMethodId']) {
if ($config['courtesyPaymentMethodId'] ?? false) {
$this->notifierFacade->verifyPaymentActionToDo($notification);
}
break;
default:
$payment_method = 'Desconocido, revisar metodos de pago no contemplados';
$payment_method = 'Desconocido';
break;
}
} else if ($notification->eventName === 'client.edit') {
@ -368,124 +318,53 @@ class Plugin
//ejemplo de json_data: {"uuid":"aacaf5c5-2bf4-44ea-864f-a24121b453bb","changeType":"edit","entity":"client","entityId":"171","eventName":"client.edit","extraData":{"entity":{"id":171,"userIdent":null,"previousIsp":null,"isLead":false,"clientType":1,"companyName":null,"companyRegistrationNumber":null,"companyTaxId":null,"companyWebsite":null,"street1":"Campeche 56","street2":null,"city":"Dolores Hidalgo","countryId":173,"stateId":null,"zipCode":"37800","fullAddress":"Campeche 56, Dolores Hidalgo, 37800","invoiceStreet1":null,"invoiceStreet2":null,"invoiceCity":null,"invoiceStateId":null,"invoiceCountryId":null,"invoiceZipCode":null,"invoiceAddressSameAsContact":true,"note":null,"sendInvoiceByPost":null,"invoiceMaturityDays":null,"stopServiceDue":null,"stopServiceDueDays":null,"organizationId":1,"tax1Id":null,"tax2Id":null,"tax3Id":null,"registrationDate":"2025-05-21T00:00:00-0600","leadConvertedAt":null,"companyContactFirstName":null,"companyContactLastName":null,"isActive":false,"firstName":"Archi","lastName":"Isalas","username":"mainstreamm2@gmail.com","contacts":[{"id":177,"clientId":171,"email":"mainstreamm2@gmail.com","phone":"4181878106","name":null,"isBilling":false,"isContact":false,"types":[]}],"attributes":[{"id":198,"clientId":171,"customAttributeId":10,"name":"Stripe Customer ID","key":"stripeCustomerId","value":"cus_SM2zH6IsjTz6ol","clientZoneVisible":true},{"id":199,"clientId":171,"customAttributeId":11,"name":"Clabe Interbancaria","key":"clabeInterbancaria","value":"124180950530868794","clientZoneVisible":true}],"accountBalance":0,"accountCredit":0,"accountOutstanding":0,"currencyCode":"MXN","organizationName":"SIIP Pruebas","bankAccounts":[],"tags":[],"invitationEmailSentDate":null,"avatarColor":"#f1df43","addressGpsLat":21.1572461,"addressGpsLon":-100.9377137,"isArchived":false,"generateProformaInvoices":null,"usesProforma":false,"hasOverdueInvoice":false,"hasOutage":false,"hasSuspendedService":false,"hasServiceWithoutDevices":true,"referral":null,"hasPaymentSubscription":false,"hasAutopayCreditCard":false},"entityBeforeEdit":{"id":171,"userIdent":null,"previousIsp":null,"isLead":false,"clientType":1,"companyName":null,"companyRegistrationNumber":null,"companyTaxId":null,"companyWebsite":null,"street1":"Campeche 56","street2":null,"city":"Dolores Hidalgo","countryId":173,"stateId":null,"zipCode":"37800","fullAddress":"Campeche 56, Dolores Hidalgo, 37800","invoiceStreet1":null,"invoiceStreet2":null,"invoiceCity":null,"invoiceStateId":null,"invoiceCountryId":null,"invoiceZipCode":null,"invoiceAddressSameAsContact":true,"note":null,"sendInvoiceByPost":null,"invoiceMaturityDays":null,"stopServiceDue":null,"stopServiceDueDays":null,"organizationId":1,"tax1Id":null,"tax2Id":null,"tax3Id":null,"registrationDate":"2025-05-21T00:00:00-0600","leadConvertedAt":null,"companyContactFirstName":null,"companyContactLastName":null,"isActive":false,"firstName":"Archi","lastName":"Isalas","username":"mainstreamm2@gmail.com","contacts":[{"id":177,"clientId":171,"email":"mainstreamm2@gmail.com","phone":"4181878106","name":null,"isBilling":false,"isContact":false,"types":[{"id":1003,"name":"WhatsNotifica"}]}],"attributes":[{"id":198,"clientId":171,"customAttributeId":10,"name":"Stripe Customer ID","key":"stripeCustomerId","value":"cus_SM2zH6IsjTz6ol","clientZoneVisible":true},{"id":199,"clientId":171,"customAttributeId":11,"name":"Clabe Interbancaria","key":"clabeInterbancaria","value":"124180950530868794","clientZoneVisible":true}],"accountBalance":0,"accountCredit":0,"accountOutstanding":0,"currencyCode":"MXN","organizationName":"SIIP Pruebas","bankAccounts":[],"tags":[],"invitationEmailSentDate":null,"avatarColor":"#f1df43","addressGpsLat":21.1572461,"addressGpsLon":-100.9377137,"isArchived":false,"generateProformaInvoices":null,"usesProforma":false,"hasOverdueInvoice":false,"hasOutage":false,"hasSuspendedService":false,"hasServiceWithoutDevices":true,"referral":null,"hasPaymentSubscription":false,"hasAutopayCreditCard":false}}}
$clientID = $jsonData['entityId'];
$this->ucrmApi = UcrmApi::create();
$customAttributes = $this->ucrmApi->get('custom-attributes/', ['attributeType' => 'client']); //Obtener los atributos del sistema que estén vinculados a la entidad "cliente"
//$this->logger->info("result del custom Attributes: " . json_encode($customAttributes) . PHP_EOL);
$idPasswordAntenaCliente = null;
$passwordAntenaValue = null;
//ejemplo de $customAttributes: [{"id":1,"name":"ip","key":"ip","attributeType":"client","type":"string","clientZoneVisible":false},{"id":2,"name":"ubntpass","key":"ubntpass","attributeType":"client","type":"string","clientZoneVisible":false},{"id":3,"name":"adminpass","key":"adminpass","attributeType":"client","type":"string","clientZoneVisible":true},{"id":4,"name":"ssid","key":"ssid","attributeType":"client","type":"string","clientZoneVisible":true},{"id":5,"name":"clavessid","key":"clavessid","attributeType":"client","type":"string","clientZoneVisible":true},{"id":6,"name":"clave","key":"clave","attributeType":"client","type":"string","clientZoneVisible":true},{"id":11,"name":"latitud","key":"latitud","attributeType":"client","type":"string","clientZoneVisible":true},{"id":12,"name":"longitud","key":"longitud","attributeType":"client","type":"string","clientZoneVisible":true},{"id":16,"name":"instalador","key":"instalador","attributeType":"client","type":"string","clientZoneVisible":true},{"id":17,"name":"creado por","key":"creadoPor","attributeType":"client","type":"string","clientZoneVisible":true},{"id":21,"name":"Chat de CallBell","key":"chatDeCallbell","attributeType":"client","type":"string","clientZoneVisible":false},{"id":22,"name":"uuid","key":"uuid","attributeType":"client","type":"string","clientZoneVisible":false},{"id":23,"name":"zona","key":"zona","attributeType":"client","type":"enum","clientZoneVisible":true},{"id":29,"name":"Stripe Customer ID","key":"stripeCustomerId","attributeType":"client","type":"string","clientZoneVisible":true},{"id":30,"name":"Clabe Interbancaria","key":"clabeInterbancaria","attributeType":"client","type":"string","clientZoneVisible":true},{"id":31,"name":"RUTA DE COBRANZA","key":"rutaDeCobranza","attributeType":"client","type":"enum","clientZoneVisible":true},{"id":35,"name":"Site","key":"site","attributeType":"client","type":"string","clientZoneVisible":true},{"id":36,"name":"Antena/Sectorial","key":"antenaSectorial","attributeType":"client","type":"string","clientZoneVisible":true},{"id":37,"name":"Password Antena Cliente","key":"passwordAntenaCliente","attributeType":"client","type":"string","clientZoneVisible":false}]
// Verificar si se obtuvieron los atributos
if ($customAttributes && is_array($customAttributes)) {
foreach ($customAttributes as $attribute) {
// Verificar si 'name' contiene la palabra 'passwordAntenaCliente' sin distinguir mayúsculas y minúsculas
if (isset($attribute['key']) && stripos($attribute['key'], 'passwordAntenaCliente') !== false) {
$this->logger->info("ID correspondiente a 'passwordAntenaCliente': " . $attribute['id'] . PHP_EOL);
$idPasswordAntenaCliente = $attribute['id'];
}
}
} else {
$this->logger->info("Error al obtener los atributos personalizados." . PHP_EOL);
}
//buscar en los attributes ($jsonData) del cliente el id del atributo personalizado 'passwordAntenaCliente' si no está o si está pero está en blanco se manda llamar la función $this->notifierFacade->getVaultCredentials($clientID);
foreach ($jsonData['extraData']['entity']['attributes'] as $attribute) {
if ($attribute['customAttributeId'] === $idPasswordAntenaCliente) {
$this->logger->info("El valor de passwordAntenaValue es: " . $attribute['value'] . PHP_EOL);
$passwordAntenaValue = $attribute['value'];
}
}
//si el value de passwordAntenaValue es igual a null o cadena vacía se manda llamar la función $this->notifierFacade->getVaultCredentials($clientID);
if ($passwordAntenaValue === null || $passwordAntenaValue === '') {
$password = $this->notifierFacade->getVaultCredentials($clientID);
$this->logger->info("El valor de passwordAntenaValue es null o cadena vacía, se manda llamar la función getVaultCredentials" . PHP_EOL);
$this->logger->info("El valor de password es: " . $password . PHP_EOL);
if ($this->notifierFacade->patchClientCustomAttribute($clientID, $idPasswordAntenaCliente, $password)) {
$this->logger->info("Se actualizó el atributo personalizado passwordAntenaCliente con el valor: " . $password . PHP_EOL);
} else {
$this->logger->info("No se pudo actualizar el atributo personalizado passwordAntenaCliente" . PHP_EOL);
}
} else {
$this->logger->info("Ya existe un valor de passwordAntenaValue: " . $passwordAntenaValue . PHP_EOL);
}
$clientID = $jsonData['entityId'];
$this->pluginNotifierFacade->updatePasswordAntenaIfNeeded($clientID, $jsonData);
//ejemplo de json_data: {"uuid":"17e043a7-03b5-4312-ab81-a7818124a77e","changeType":"edit","entity":"client","entityId":"158","eventName":"client.edit","extraData":{"entity":{"id":158,"userIdent":null,"previousIsp":null,"isLead":false,"clientType":1,"companyName":null,"companyRegistrationNumber":null,"companyTaxId":null,"companyWebsite":null,"street1":"23 San Luis","street2":null,"city":"Dolores Hidalgo Cuna de la Independencia Nacional","countryId":173,"stateId":null,"zipCode":"37804","fullAddress":"San Luis 23, Guadalupe, Dolores Hidalgo Cuna de la Independencia Nacional, Gto., M\u00e9xico","invoiceStreet1":null,"invoiceStreet2":null,"invoiceCity":null,"invoiceStateId":null,"invoiceCountryId":null,"invoiceZipCode":null,"invoiceAddressSameAsContact":true,"note":null,"sendInvoiceByPost":null,"invoiceMaturityDays":null,"stopServiceDue":null,"stopServiceDueDays":null,"organizationId":1,"tax1Id":null,"tax2Id":null,"tax3Id":null,"registrationDate":"2025-01-06T00:00:00-0600","leadConvertedAt":"2025-02-09T03:15:49-0600","companyContactFirstName":null,"companyContactLastName":null,"isActive":false,"firstName":"Luis","lastName":"Guti\u00e9rrez","username":null,"contacts":[{"id":162,"clientId":158,"email":null,"phone":null,"name":null,"isBilling":true,"isContact":true,"types":[{"id":1,"name":"Billing"},{"id":2,"name":"General"}]}],"attributes":[],"accountBalance":0,"accountCredit":0,"accountOutstanding":0,"currencyCode":"MXN","organizationName":"SIIP Pruebas","bankAccounts":[],"tags":[],"invitationEmailSentDate":null,"avatarColor":"#2196f3","addressGpsLat":21.153272,"addressGpsLon":-100.9134508,"isArchived":false,"generateProformaInvoices":null,"usesProforma":false,"hasOverdueInvoice":false,"hasOutage":false,"hasSuspendedService":false,"hasServiceWithoutDevices":false,"referral":null,"hasPaymentSubscription":false,"hasAutopayCreditCard":false},"entityBeforeEdit":{"id":158,"userIdent":null,"previousIsp":null,"isLead":true,"clientType":1,"companyName":null,"companyRegistrationNumber":null,"companyTaxId":null,"companyWebsite":null,"street1":"23 San Luis","street2":null,"city":"Dolores Hidalgo Cuna de la Independencia Nacional","countryId":173,"stateId":null,"zipCode":"37804","fullAddress":"San Luis 23, Guadalupe, Dolores Hidalgo Cuna de la Independencia Nacional, Gto., M\u00e9xico","invoiceStreet1":null,"invoiceStreet2":null,"invoiceCity":null,"invoiceStateId":null,"invoiceCountryId":null,"invoiceZipCode":null,"invoiceAddressSameAsContact":true,"note":null,"sendInvoiceByPost":null,"invoiceMaturityDays":null,"stopServiceDue":null,"stopServiceDueDays":null,"organizationId":1,"tax1Id":null,"tax2Id":null,"tax3Id":null,"registrationDate":"2025-01-06T00:00:00-0600","leadConvertedAt":null,"companyContactFirstName":null,"companyContactLastName":null,"isActive":false,"firstName":"Luis","lastName":"Guti\u00e9rrez","username":null,"contacts":[{"id":162,"clientId":158,"email":null,"phone":null,"name":null,"isBilling":true,"isContact":true,"types":[{"id":1,"name":"Billing"},{"id":2,"name":"General"}]}],"attributes":[],"accountBalance":0,"accountCredit":0,"accountOutstanding":0,"currencyCode":"MXN","organizationName":"SIIP Pruebas","bankAccounts":[],"tags":[],"invitationEmailSentDate":null,"avatarColor":"#2196f3","addressGpsLat":21.153272,"addressGpsLon":-100.9134508,"isArchived":false,"generateProformaInvoices":null,"usesProforma":false,"hasOverdueInvoice":false,"hasOutage":false,"hasSuspendedService":false,"hasServiceWithoutDevices":false,"referral":null,"hasPaymentSubscription":false,"hasAutopayCreditCard":false}}}
// Validar que 'extraData' existe y contiene las claves necesarias
if (
isset($jsonData['extraData']['entityBeforeEdit']) &&
isset($jsonData['extraData']['entity'])
) {
if (isset($jsonData['extraData']['entityBeforeEdit'], $jsonData['extraData']['entity'])) {
$entityBeforeEdit = $jsonData['extraData']['entityBeforeEdit'];
$entity = $jsonData['extraData']['entity'];
//$this->logger->debug('Validando claves dentro de entityBeforeEdit y entity');
// Validar si 'isLead' esta en true en entityBeforeEdit y en false en entity
if (array_key_exists('isLead', $entityBeforeEdit) && array_key_exists('isLead', $entity)) {
//$this->logger->debug('Los datos entityBeforeEdit y entity contienen el campo isLead');
if (isset($entityBeforeEdit['isLead'], $entity['isLead'])) {
$isLeadBefore = $entityBeforeEdit['isLead'];
$isLeadAfter = $entity['isLead'];
// Comprobar si 'isLead' cambió de true a false
if ($isLeadBefore === true && $isLeadAfter === false) {
$this->logger->debug('El cliente cambió de potencial a cliente');
//$this->pluginNotifierFacade->createStripeClient($notification); //Se comenta esta línea para que no se cree el cliente en Stripe al convertir el lead a cliente, ya que se hará al agregar la etiqueta 'CREAR CLABE STRIPE'
} else {
$this->logger->debug('El cliente no cambió de potencial a cliente');
}
} else {
$this->logger->warning('El campo isLead no existe en entityBeforeEdit o entity');
}
// buscar si existe la etiqueta 'STRIPE' en entity pero no en entityBeforeEdit
$tags = $jsonData['extraData']['entity']['tags'];
$tagsBefore = $jsonData['extraData']['entityBeforeEdit']['tags'];
if (isset($entity['tags'], $entityBeforeEdit['tags'])) {
$tags = $entity['tags'];
$tagsBefore = $entityBeforeEdit['tags'];
$this->logger->debug('Validando claves dentro de entity y entityBeforeEdit');
// Validar que 'tags' existe en ambas entidades
if (array_key_exists('tags', $jsonData['extraData']['entity']) && array_key_exists('tags', $jsonData['extraData']['entityBeforeEdit'])) {
$this->logger->debug('Los datos entity y entityBeforeEdit contienen el campo tags');
$tags = $jsonData['extraData']['entity']['tags'];
$tagsBefore = $jsonData['extraData']['entityBeforeEdit']['tags'];
$this->logger->debug('Validando si la etiqueta STRIPE existe en entity pero no en entityBeforeEdit');
// Comprobar si la etiqueta 'CREAR CLABE STRIPE' existe en 'tags' pero no en 'tagsBefore'
$stripeTagExists = false;
$clabeTagExistsBefore = false;
$stripeTagExistsBefore = false;
foreach ($tags as $tag) {
if ($tag['name'] === 'CREAR CLABE STRIPE') {
$stripeTagExists = true;
break;
}
}
$clabeTagExists = false;
$stripeTagExists = false;
foreach ($tagsBefore as $tag) {
if ($tag['name'] === 'CREAR CLABE STRIPE') {
$stripeTagExistsBefore = true;
break;
}
if ($tag['name'] === 'CREAR CLABE STRIPE') $clabeTagExistsBefore = true;
if ($tag['name'] === 'CREAR CLIENTE STRIPE') $stripeTagExistsBefore = true;
}
// Comprobar si la etiqueta 'STRIPE' existe en 'tags' pero no en 'tagsBefore'
if ($stripeTagExists && ! $stripeTagExistsBefore) {
$this->logger->debug('La etiqueta CREAR CLABE STRIPE se agregará al cliente');
$this->pluginNotifierFacade->createStripeClient($notification, true);
foreach ($tags as $tag) {
if ($tag['name'] === 'CREAR CLABE STRIPE') $clabeTagExists = true;
if ($tag['name'] === 'CREAR CLIENTE STRIPE') $stripeTagExists = true;
}
if ($clabeTagExists && !$clabeTagExistsBefore) {
$this->logger->debug('La etiqueta CREAR CLABE STRIPE se agregó al cliente');
$this->pluginNotifierFacade->createStripeClient($notification, 'CREAR CLABE STRIPE', true);
}
if ($stripeTagExists && !$stripeTagExistsBefore) {
$this->logger->debug('La etiqueta CREAR CLIENTE STRIPE se agregó al cliente');
$this->pluginNotifierFacade->createStripeClient($notification, 'CREAR CLIENTE STRIPE', false);
}
} else {
$this->logger->warning('El campo tags no existe en entity o entityBeforeEdit');
}
} else {
$this->logger->warning('Los datos entityBeforeEdit o entity no están presentes en extraData');
}
@ -493,78 +372,15 @@ class Plugin
$this->notifierFacade->verifyClientActionToDo($notification);
} else if ($notification->eventName === 'client.add') {
$this->logger->debug('Se agregó un nuevo cliente' . PHP_EOL);
$this->logger->debug('Se agregó un nuevo cliente');
$this->logger->debug('Valor de json_data: ' . json_encode($jsonData));
// Verificar que existen tanto 'entityBeforeEdit' como 'entity'
// if (isset($jsonData['extraData']['entity']['isLead'])) {
// $this->logger->debug('El campo isLead existe en los datos del evento');
// $isLead = $jsonData['extraData']['entity']['isLead'];
// // Comprobar si 'isLead' es true
// if ($isLead === true) {
// $this->logger->debug('El cliente es potencial');
// $this->pluginNotifierFacade->createStripeClient($notification);
// } else {
// $this->logger->debug('El cliente no es potencial');
// $this->pluginNotifierFacade->createStripeClient($notification);
// }
// } else {
// $this->logger->warning('El campo isLead no existe en los datos del evento');
// }
} else if ($notification->eventName === 'service.edit') {
$this->logger->debug('Se editó el servicio a un cliente' . PHP_EOL);
$this->notifierFacade->verifyServiceActionToDo($notification);
//ejemplo de json_data: {"uuid":"06d281ca-d78e-4f0a-a282-3a6b77d25da0","changeType":"edit","entity":"service","entityId":"155","eventName":"service.edit","extraData":{"entity":{"id":155,"prepaid":false,"clientId":171,"status":1,"name":"Basico 300","fullAddress":"Campeche 56, Dolores Hidalgo, 37800","street1":"Campeche 56","street2":null,"city":"Dolores Hidalgo","countryId":173,"stateId":null,"zipCode":"37800","note":null,"addressGpsLat":21.1572461,"addressGpsLon":-100.9377137,"servicePlanId":6,"servicePlanPeriodId":26,"price":300,"hasIndividualPrice":false,"totalPrice":300,"currencyCode":"MXN","invoiceLabel":null,"contractId":null,"contractLengthType":1,"minimumContractLengthMonths":null,"activeFrom":"2025-05-21T00:00:00-0600","activeTo":null,"contractEndDate":null,"discountType":0,"discountValue":null,"discountInvoiceLabel":"Descuento","discountFrom":null,"discountTo":null,"tax1Id":null,"tax2Id":null,"tax3Id":null,"invoicingStart":"2025-05-21T00:00:00-0600","invoicingPeriodType":1,"invoicingPeriodStartDay":1,"nextInvoicingDayAdjustment":10,"invoicingProratedSeparately":true,"invoicingSeparately":false,"sendEmailsAutomatically":null,"useCreditAutomatically":true,"servicePlanName":"Basico 300","servicePlanPrice":300,"servicePlanPeriod":1,"servicePlanType":"Internet","downloadSpeed":8,"uploadSpeed":8,"hasOutage":false,"unmsClientSiteStatus":null,"fccBlockId":null,"lastInvoicedDate":null,"unmsClientSiteId":"359cb58d-e64f-453a-890e-23d5abb4f116","attributes":[],"addressData":null,"suspensionReasonId":null,"serviceChangeRequestId":null,"setupFeePrice":null,"earlyTerminationFeePrice":null,"downloadSpeedOverride":null,"uploadSpeedOverride":null,"trafficShapingOverrideEnd":null,"trafficShapingOverrideEnabled":false,"servicePlanGroupId":null,"suspensionPeriods":[],"surcharges":[]},"entityBeforeEdit":{"id":155,"prepaid":false,"clientId":171,"status":1,"name":"Basico 300","fullAddress":"Campeche 56, Dolores Hidalgo, 37800","street1":"Campeche 56","street2":null,"city":"Dolores Hidalgo","countryId":173,"stateId":null,"zipCode":"37800","note":null,"addressGpsLat":21.1572461,"addressGpsLon":-100.9377137,"servicePlanId":6,"servicePlanPeriodId":26,"price":300,"hasIndividualPrice":false,"totalPrice":300,"currencyCode":"MXN","invoiceLabel":null,"contractId":null,"contractLengthType":1,"minimumContractLengthMonths":null,"activeFrom":"2025-05-21T00:00:00-0600","activeTo":null,"contractEndDate":null,"discountType":0,"discountValue":null,"discountInvoiceLabel":"Descuento","discountFrom":null,"discountTo":null,"tax1Id":null,"tax2Id":null,"tax3Id":null,"invoicingStart":"2025-05-21T00:00:00-0600","invoicingPeriodType":1,"invoicingPeriodStartDay":1,"nextInvoicingDayAdjustment":10,"invoicingProratedSeparately":true,"invoicingSeparately":false,"sendEmailsAutomatically":null,"useCreditAutomatically":true,"servicePlanName":"Basico 300","servicePlanPrice":300,"servicePlanPeriod":1,"servicePlanType":"Internet","downloadSpeed":8,"uploadSpeed":8,"hasOutage":false,"unmsClientSiteStatus":null,"fccBlockId":null,"lastInvoicedDate":null,"unmsClientSiteId":"359cb58d-e64f-453a-890e-23d5abb4f116","attributes":[],"addressData":null,"suspensionReasonId":null,"serviceChangeRequestId":null,"setupFeePrice":null,"earlyTerminationFeePrice":null,"downloadSpeedOverride":null,"uploadSpeedOverride":null,"trafficShapingOverrideEnd":null,"trafficShapingOverrideEnabled":false,"servicePlanGroupId":null,"suspensionPeriods":[],"surcharges":[]}}}
//obtener el clientID y asginarlo a la variable $clientID
$clientID = $jsonData['extraData']['entity']['clientId'];
$this->ucrmApi = UcrmApi::create();
$customAttributes = $this->ucrmApi->get('custom-attributes/', ['attributeType' => 'client']); //Obtener los atributos del sistema que estén vinculados a la entidad "cliente"
//$this->logger->info("result del custom Attributes: " . json_encode($customAttributes) . PHP_EOL);
$idPasswordAntenaCliente = null;
$passwordAntenaValue = null;
//ejemplo de $customAttributes: [{"id":1,"name":"ip","key":"ip","attributeType":"client","type":"string","clientZoneVisible":false},{"id":2,"name":"ubntpass","key":"ubntpass","attributeType":"client","type":"string","clientZoneVisible":false},{"id":3,"name":"adminpass","key":"adminpass","attributeType":"client","type":"string","clientZoneVisible":true},{"id":4,"name":"ssid","key":"ssid","attributeType":"client","type":"string","clientZoneVisible":true},{"id":5,"name":"clavessid","key":"clavessid","attributeType":"client","type":"string","clientZoneVisible":true},{"id":6,"name":"clave","key":"clave","attributeType":"client","type":"string","clientZoneVisible":true},{"id":11,"name":"latitud","key":"latitud","attributeType":"client","type":"string","clientZoneVisible":true},{"id":12,"name":"longitud","key":"longitud","attributeType":"client","type":"string","clientZoneVisible":true},{"id":16,"name":"instalador","key":"instalador","attributeType":"client","type":"string","clientZoneVisible":true},{"id":17,"name":"creado por","key":"creadoPor","attributeType":"client","type":"string","clientZoneVisible":true},{"id":21,"name":"Chat de CallBell","key":"chatDeCallbell","attributeType":"client","type":"string","clientZoneVisible":false},{"id":22,"name":"uuid","key":"uuid","attributeType":"client","type":"string","clientZoneVisible":false},{"id":23,"name":"zona","key":"zona","attributeType":"client","type":"enum","clientZoneVisible":true},{"id":29,"name":"Stripe Customer ID","key":"stripeCustomerId","attributeType":"client","type":"string","clientZoneVisible":true},{"id":30,"name":"Clabe Interbancaria","key":"clabeInterbancaria","attributeType":"client","type":"string","clientZoneVisible":true},{"id":31,"name":"RUTA DE COBRANZA","key":"rutaDeCobranza","attributeType":"client","type":"enum","clientZoneVisible":true},{"id":35,"name":"Site","key":"site","attributeType":"client","type":"string","clientZoneVisible":true},{"id":36,"name":"Antena/Sectorial","key":"antenaSectorial","attributeType":"client","type":"string","clientZoneVisible":true},{"id":37,"name":"Password Antena Cliente","key":"passwordAntenaCliente","attributeType":"client","type":"string","clientZoneVisible":false}]
// Verificar si se obtuvieron los atributos
if ($customAttributes && is_array($customAttributes)) {
foreach ($customAttributes as $attribute) {
// Verificar si 'name' contiene la palabra 'passwordAntenaCliente' sin distinguir mayúsculas y minúsculas
if (isset($attribute['key']) && stripos($attribute['key'], 'passwordAntenaCliente') !== false) {
$this->logger->info("ID correspondiente a 'passwordAntenaCliente': " . $attribute['id'] . PHP_EOL);
$idPasswordAntenaCliente = $attribute['id'];
}
}
} else {
$this->logger->info("Error al obtener los atributos personalizados." . PHP_EOL);
}
//buscar en los attributes ($jsonData) del cliente el id del atributo personalizado 'passwordAntenaCliente' si no está o si está pero está en blanco se manda llamar la función $this->notifierFacade->getVaultCredentials($clientID);
foreach ($jsonData['extraData']['entity']['attributes'] as $attribute) {
if ($attribute['customAttributeId'] === $idPasswordAntenaCliente) {
$this->logger->info("El valor de passwordAntenaValue es: " . $attribute['value'] . PHP_EOL);
$passwordAntenaValue = $attribute['value'];
}
}
//si el value de passwordAntenaValue es igual a null o cadena vacía se manda llamar la función $this->notifierFacade->getVaultCredentials($clientID);
if ($passwordAntenaValue === null || $passwordAntenaValue === '') {
$password = $this->notifierFacade->getVaultCredentials($clientID);
$this->logger->info("El valor de passwordAntenaValue es null o cadena vacía, se manda llamar la función getVaultCredentials" . PHP_EOL);
$this->logger->info("El valor de password es: " . $password . PHP_EOL);
if ($this->notifierFacade->patchClientCustomAttribute($clientID, $idPasswordAntenaCliente, $password)) {
$this->logger->info("Se actualizó el atributo personalizado passwordAntenaCliente con el valor: " . $password . PHP_EOL);
} else {
$this->logger->info("No se pudo actualizar el atributo personalizado passwordAntenaCliente" . PHP_EOL);
}
} else {
$this->logger->info("Ya existe un valor de passwordAntenaValue: " . $passwordAntenaValue . PHP_EOL);
}
$clientID = $jsonData['extraData']['entity']['clientId'];
$this->pluginNotifierFacade->updatePasswordAntenaIfNeeded($clientID, $jsonData);
} else if ($notification->eventName === 'service.suspend') {
$this->logger->debug('Se suspendió el servicio a un cliente' . PHP_EOL);
@ -606,18 +422,13 @@ class Plugin
$this->logger->debug('Eliminación de Factura' . PHP_EOL);
$this->notifierFacade->verifyInvoiceActionToDo($notification);
} else if ($notification->eventName === 'job.add') {
$this->logger->debug('Se ha agregado un nuevo trabajo' . PHP_EOL);
$this->logger->debug('Valor de json_data: ' . json_encode($jsonData));
//Ejemplo de json_data: {"uuid":"434b3da0-984a-4358-a1b6-2a4418bacc49","changeType":"insert","entity":"job","entityId":"38","eventName":"job.add","extraData":{"entity":{"id":38,"title":"Servicio","description":"Revisar Router","assignedUserId":null,"clientId":2,"date":null,"duration":60,"status":0,"address":"31 Chiapas, Dolores Hidalgo Cuna de la Independencia Nacional, 37800, Mexico","gpsLat":null,"gpsLon":null,"attachments":[],"tasks":[]},"entityBeforeEdit":null}}
//Extraer el valor de title en una variable y concatenarle como prefijo la cadena "[SINENVIONOTIFICACION]" por ejemplo: "[NOTIFICACION-PENDIENTE]Servicio"
$title = $jsonData['extraData']['entity']['title'];
$this->logger->debug('Se ha agregado un nuevo trabajo');
$title = $jsonData['extraData']['entity']['title'] ?? '';
$title = '[NOTIFICACION-PENDIENTE]' . $title;
$this->ucrmApi = UcrmApi::create();
$responsePatch = $this->ucrmApi->patch('scheduling/jobs/' . $jsonData['entityId'], [
'title' => $title,
]);
$this->logger->debug('Respuesta de la API al agregar el trabajo: ' . json_encode($responsePatch) . PHP_EOL);
$this->logger->debug('Respuesta de la API al agregar el trabajo: ' . json_encode($responsePatch));
} else if ($notification->eventName === 'job.edit') {
$this->logger->debug('Se actualiza un trabajo' . PHP_EOL);
@ -650,32 +461,29 @@ class Plugin
//Valores de status y su significado: 0 = abierto, 1= En curso, 2 = Cerrado
if ($statusAfter == 1) {
// Comprobar si 'assignedUserId' cambió
// if (($assignedUserIdBefore === null && $assignedUserIdAfter != null) || ($statusBefore == 0 && $statusAfter == 1)) { //Si el campo "assignedUserId" cambió de null a un valor
// $this->logger->debug('El instalador cambió de null a un valor');
$isNewActivation = ($statusBefore == 0 && $statusAfter == 1);
$isTechChange = ($assignedUserIdBefore != null && $assignedUserIdAfter != $assignedUserIdBefore && $dateBefore === $dateAfter);
$isReprogramming = ($assignedUserIdBefore != null && $assignedUserIdBefore === $assignedUserIdAfter && $dateBefore != $dateAfter);
$isBothChange = ($assignedUserIdBefore != null && $assignedUserIdAfter != $assignedUserIdBefore && $dateBefore != $dateAfter);
$isDeassignment = ($assignedUserIdBefore != null && $assignedUserIdAfter === null);
// $this->notifierFacade->verifyJobActionToDo($jsonData); // Se envía notificación de trabajo asignado
// }
//Si el campo status cambió de 0 a 1 y title comienza con el prefijo [NOTIFICACION-PENDIENTE]
if (($statusBefore == 0 && $statusAfter == 1) && strpos($currentTitle, $pendingPrefix) !== false) { // Se envía notificación de trabajo asignado
$this->logger->debug('El instalador cambió de null a un valor');
if ($isNewActivation) {
$this->logger->debug('Trabajo iniciado o asignado: Notificando...');
$this->notifierFacade->verifyJobActionToDo($jsonData, false, false);
} else if (($assignedUserIdBefore != null && $assignedUserIdAfter != $assignedUserIdBefore) && ($dateBefore === $dateAfter)) { //Si el campo "assignedUserId" cambió de un valor a otro y la fecha no cambió
$this->logger->debug('No hay reprogramación de trabajo pero si hay cambio de instalador');
$this->notifierFacade->verifyJobActionToDo($jsonData, false, true); // Se envía notificación de trabajo reasignado
} else if (($assignedUserIdBefore != null && $assignedUserIdBefore === $assignedUserIdAfter) && ($dateBefore != $dateAfter)) { //Si el campo "assignedUserId" no cambió y la fecha cambió
$this->logger->debug('Se reprogramó el trabajo pero no hubo cambio de instalador');
$this->notifierFacade->verifyJobActionToDo($jsonData, true, false); // Se envía notificación de reprogramación de trabajo
} else if (($assignedUserIdBefore != null && $assignedUserIdAfter != $assignedUserIdBefore) && ($dateBefore != $dateAfter)) {
$this->logger->debug('Se reprogramó el trabajo y hubo cambio de instalador');
$this->notifierFacade->verifyJobActionToDo($jsonData, true, true); // Se envía notificación de trabajo reasignado
} else if ($assignedUserIdBefore != null && $assignedUserIdAfter === null) { //Si el campo "assignedUserId" cambió de un valor a null
$this->logger->debug('El instalador cambió de un valor a null');
$this->notifierFacade->verifyJobActionToDo($jsonData, null, true); // Se envía notificación de trabajo desasignado
} else if ($isTechChange) {
$this->logger->debug('Cambio de técnico sin reprogramación');
$this->notifierFacade->verifyJobActionToDo($jsonData, false, true);
} else if ($isReprogramming) {
$this->logger->debug('Reprogramación sin cambio de técnico');
$this->notifierFacade->verifyJobActionToDo($jsonData, true, false);
} else if ($isBothChange) {
$this->logger->debug('Reprogramación y cambio de técnico');
$this->notifierFacade->verifyJobActionToDo($jsonData, true, true);
} else if ($isDeassignment) {
$this->logger->debug('Técnico desasignado');
$this->notifierFacade->verifyJobActionToDo($jsonData, null, true);
} else {
$this->logger->debug('No hubo cambio en el instalador ni en la fecha');
//$this->notifierFacade->verifyJobActionToDo($jsonData); // Se envía notificación de trabajo asignado
$this->logger->debug('Edición de trabajo "En curso" sin cambios de fecha o técnico relevantes para notificación');
}
}

View File

@ -31,13 +31,18 @@ class PluginDataValidator
{
$pluginData = $this->optionsManager->load();
$valid = true;
if (empty($pluginData->twilioAccountSid)) {
$this->errors[] = 'Not valid configuration: Twilio Account SID must be configured';
if (empty($pluginData->tokencallbell)) {
$this->errors[] = 'Configuración inválida: Token de CallBell es requerido';
$valid = false;
}
if (empty($pluginData->twilioAuthToken)) {
$this->errors[] = 'Not valid configuration: Twilio Auth Token must be configured';
if (empty($pluginData->apitoken)) {
$this->errors[] = 'Configuración inválida: Token de API de UCRM es requerido';
$valid = false;
}
if (empty($pluginData->ipserver)) {
$this->errors[] = 'Configuración inválida: IP del Servidor es requerida';
$valid = false;
}