feat: soporte multi-servicio, optimización lazy loading y sincronización avanzada con CallBell
- Implementación de gestión multi-servicio para contraseñas de antena con etiquetas condicionales. - Optimización de rendimiento mediante lazy loading para evitar llamadas redundantes a la API de UISP. - Mejora de sincronización con CallBell: contraseñas en formato JSON, unificación de peticiones PATCH y corrección de comparación de saldo. - Generación de contraseñas printer-friendly (alfanuméricas + @, #) sin caracteres ambiguos. - Validación granular de provisionamiento para evitar errores en sitios inactivos. - Actualización de CHANGELOG.md y README.md.
This commit is contained in:
parent
761cd667b5
commit
f851ea6d7d
14
CHANGELOG.md
14
CHANGELOG.md
@ -7,12 +7,18 @@
|
||||
3️⃣ **Lazy Loading & Optimización de Recursos**: Implementación de un "Lazy Check" que detecta si ya hay una contraseña válida en el CRM para omitir llamadas innecesarias a la API de UISP, mejorando la velocidad y reduciendo el consumo de CPU.
|
||||
|
||||
### 🔵 Mejoras
|
||||
1️⃣ **Contraseñas "Printer-Friendly"**: El generador de contraseñas ahora utiliza un set de caracteres optimizado para mini-impresoras térmicas (Alfanumérico + `@`, `#`), eliminando caracteres ambiguos como `l`, `I`, `0`, `O`.
|
||||
2️⃣ **Mensajes de Estado en CRM**: Se agregaron alertas visuales en el campo de contraseña para indicar estados de provisión: `⚠️ Sin sitio vinculado`, `⚠️ Sin antena vinculada`, `⚠️ Cliente sin servicios`.
|
||||
3️⃣ **Robustez en Entornos de Prueba**: Refinamiento del bypass de desarrollo para mantener la estabilidad de las claves generadas y evitar bucles infinitos de webhooks.
|
||||
1️⃣ **Etiquetado Inteligente de Servicios**: Las etiquetas `Servicio 1:`, `Servicio 2:` ahora solo aparecen si el cliente tiene múltiples servicios; para un solo servicio, la contraseña se muestra directamente.
|
||||
2️⃣ **Sincronización Avanzada con CallBell**:
|
||||
- Nuevo campo `Password Antena` enviado en formato JSON estructurado.
|
||||
- Unificación de peticiones PATCH (Resumen + Campos) en una sola llamada para mayor eficiencia.
|
||||
3️⃣ **Contraseñas "Printer-Friendly"**: El generador de contraseñas ahora utiliza un set de caracteres optimizado para mini-impresoras térmicas (Alfanumérico + `@`, `#`), eliminando caracteres ambiguos como `l`, `I`, `0`, `O`.
|
||||
4️⃣ **Mensajes de Estado en CRM**: Se agregaron alertas visuales en el campo de contraseña para indicar estados de provisión: `⚠️ Sin sitio vinculado`, `⚠️ Sin antena vinculada`, `⚠️ Cliente sin servicios`.
|
||||
5️⃣ **Robustez en Entornos de Prueba**: Refinamiento del bypass de desarrollo para mantener la estabilidad de las claves generadas y evitar bucles infinitos de webhooks.
|
||||
|
||||
### 🟡 Bugs Resueltos
|
||||
1️⃣ Se solucionó el bucle infinito de actualizaciones en el atributo `passwordAntenaCliente` que ocurría al detectar cambios en servicios sin dispositivos vinculados.
|
||||
1️⃣ **Sincronización de Saldo**: Se corrigió la discrepancia de nombres entre `Saldo Actual` y `Saldo` que causaba actualizaciones redundantes infinitas con CallBell.
|
||||
2️⃣ **Parseo de Passwords**: Refinamiento de expresiones regulares para capturar correctamente contraseñas multi-servicio.
|
||||
3️⃣ Se solucionó el bucle infinito de actualizaciones en el atributo `passwordAntenaCliente` que ocurría al detectar cambios en servicios sin dispositivos vinculados.
|
||||
|
||||
## VERSIÓN 2.9.3 - 23-12-2025
|
||||
### 🟢 Novedades
|
||||
|
||||
12
README.md
12
README.md
@ -57,10 +57,14 @@ Es necesario configurar los siguientes atributos en UCRM:
|
||||
|
||||
### 📅 Gestión de Contraseñas y Visitas Técnicas
|
||||
Cuando se asigna o reprograma una tarea:
|
||||
1. **Detección Multi-Servicio**: El plugin identifica todos los servicios del cliente y formatea sus contraseñas como `Servicio 1: <pass> Servicio 2: ...`.
|
||||
2. **Validación de Provisionamiento**: Antes de actuar, verifica en UISP si el sitio tiene dispositivos vinculados (evitando errores en estados "Location Inactive").
|
||||
3. **Lazy Loading**: Si el CRM ya tiene una contraseña válida, el plugin omite llamadas redundantes a la API para ahorrar recursos.
|
||||
4. **Notificación**:
|
||||
1. **Detección Multi-Servicio & Etiquetas Inteligentes**:
|
||||
- El plugin identifica todos los servicios del cliente.
|
||||
- Si hay **más de un servicio**, utiliza el formato: `Servicio 1: <pass> Servicio 2: ...`.
|
||||
- Si hay **solo un servicio**, muestra la contraseña directamente sin etiquetas para mayor claridad.
|
||||
2. **Sincronización CallBell Avanzada**:
|
||||
- Los datos técnicos y financieros se sincronizan en una sola petición optimizada.
|
||||
- Las contraseñas se envían como un objeto JSON estructurado al campo `Password Antena`.
|
||||
3. **Validación de Provisionamiento**: Antes de actuar, verifica en UISP si el sitio tiene dispositivos vinculados (evitando errores en estados "Location Inactive").
|
||||
- Envía un mensaje al cliente con el nombre del técnico y la fecha (sin hora).
|
||||
- Envía un mensaje al técnico con la dirección, ubicación en Google Maps y las contraseñas de las antenas optimizadas para lectura en impresoras térmicas (alfanumérico + `@`, `#`).
|
||||
|
||||
|
||||
File diff suppressed because one or more lines are too long
@ -287,8 +287,9 @@ abstract class AbstractMessageNotifierFacade
|
||||
$allServicePasswords = [];
|
||||
$isTestEnv = ($ipServer === '172.16.5.134' || $ipServer === 'pruebas.internet.mx' || $ipServer === 'venus.siip.mx');
|
||||
|
||||
$numServices = count($svcs);
|
||||
foreach ($svcs as $index => $svc) {
|
||||
$label = "Servicio " . ($index + 1) . ":";
|
||||
$label = ($numServices > 1) ? "Servicio " . ($index + 1) . ":" : "";
|
||||
$siteId = $svc['unmsClientSiteId'] ?? null;
|
||||
$passwordValue = "";
|
||||
|
||||
@ -296,10 +297,24 @@ abstract class AbstractMessageNotifierFacade
|
||||
$passwordValue = "⚠️ Sin sitio";
|
||||
} else {
|
||||
if ($isTestEnv) {
|
||||
// Lógica de bypass: intentar recuperar de la cadena existente si existe y es válida
|
||||
if (!empty($passCRM) && preg_match('/Servicio ' . ($index + 1) . ':\s*([^⚠️\s]+)/', $passCRM, $matches)) {
|
||||
// Lógica de bypass: intentar recuperar de la cadena existente
|
||||
$foundInCRM = false;
|
||||
if (!empty($passCRM)) {
|
||||
if ($numServices > 1) {
|
||||
if (preg_match('/Servicio ' . ($index + 1) . ':\s*([^⚠️\s]+)/', $passCRM, $matches)) {
|
||||
$passwordValue = $matches[1];
|
||||
$foundInCRM = true;
|
||||
}
|
||||
} else {
|
||||
// Caso de un solo servicio: si no tiene advertencias ni etiquetas, la tomamos a secas
|
||||
if (strpos($passCRM, '⚠️') === false && strpos($passCRM, 'Servicio') === false) {
|
||||
$passwordValue = trim($passCRM);
|
||||
$foundInCRM = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!$foundInCRM) {
|
||||
$passwordValue = $this->generateStrongPassword(16);
|
||||
}
|
||||
} else {
|
||||
@ -352,7 +367,7 @@ abstract class AbstractMessageNotifierFacade
|
||||
}
|
||||
}
|
||||
}
|
||||
$allServicePasswords[] = "$label $passwordValue";
|
||||
$allServicePasswords[] = trim("$label $passwordValue");
|
||||
}
|
||||
|
||||
$finalValue = implode(' ', $allServicePasswords);
|
||||
|
||||
@ -195,8 +195,9 @@ abstract class AbstractStripeOperationsFacade
|
||||
$allServicePasswords = [];
|
||||
$isTestEnv = ($ipServer === '172.16.5.134' || $ipServer === 'pruebas.internet.mx' || $ipServer === 'venus.siip.mx');
|
||||
|
||||
$numServices = count($svcs);
|
||||
foreach ($svcs as $index => $svc) {
|
||||
$label = "Servicio " . ($index + 1) . ":";
|
||||
$label = ($numServices > 1) ? "Servicio " . ($index + 1) . ":" : "";
|
||||
$siteId = $svc['unmsClientSiteId'] ?? null;
|
||||
$passwordValue = "";
|
||||
|
||||
@ -204,10 +205,24 @@ abstract class AbstractStripeOperationsFacade
|
||||
$passwordValue = "⚠️ Sin sitio";
|
||||
} else {
|
||||
if ($isTestEnv) {
|
||||
// Lógica de bypass: intentar recuperar de la cadena existente si existe y es válida
|
||||
if (!empty($passCRM) && preg_match('/Servicio ' . ($index + 1) . ':\s*([^⚠️\s]+)/', $passCRM, $matches)) {
|
||||
// Lógica de bypass: intentar recuperar de la cadena existente
|
||||
$foundInCRM = false;
|
||||
if (!empty($passCRM)) {
|
||||
if ($numServices > 1) {
|
||||
if (preg_match('/Servicio ' . ($index + 1) . ':\s*([^⚠️\s]+)/', $passCRM, $matches)) {
|
||||
$passwordValue = $matches[1];
|
||||
$foundInCRM = true;
|
||||
}
|
||||
} else {
|
||||
// Caso de un solo servicio: si no tiene advertencias ni etiquetas, asumimos que es el pass
|
||||
if (strpos($passCRM, '⚠️') === false && strpos($passCRM, 'Servicio') === false) {
|
||||
$passwordValue = trim($passCRM);
|
||||
$foundInCRM = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!$foundInCRM) {
|
||||
$passwordValue = $this->generateStrongPassword(16);
|
||||
}
|
||||
} else {
|
||||
@ -260,7 +275,7 @@ abstract class AbstractStripeOperationsFacade
|
||||
}
|
||||
}
|
||||
}
|
||||
$allServicePasswords[] = "$label $passwordValue";
|
||||
$allServicePasswords[] = trim("$label $passwordValue");
|
||||
}
|
||||
|
||||
$finalValue = implode(' ', $allServicePasswords);
|
||||
|
||||
@ -876,6 +876,7 @@ class ClientCallBellAPI
|
||||
$attributes = $notificationData->clientData['attributes']; //Obtener los atributos del cliente
|
||||
$site = '';
|
||||
$antenaSectorial = '';
|
||||
$passAntenaUCRM = '';
|
||||
|
||||
// Iterar sobre los atributos
|
||||
foreach ($attributes as $attribute) {
|
||||
@ -887,7 +888,28 @@ class ClientCallBellAPI
|
||||
if ($attribute['key'] === 'antenaSectorial') {
|
||||
$antenaSectorial = $attribute['value'];
|
||||
}
|
||||
if ($attribute['key'] === 'passwordAntenaCliente') {
|
||||
$passAntenaUCRM = $attribute['value'] ?? '';
|
||||
}
|
||||
}
|
||||
|
||||
// Parsear contraseñas multi-servicio a JSON
|
||||
$passAntenaJSON = [];
|
||||
if (!empty($passAntenaUCRM)) {
|
||||
if (strpos($passAntenaUCRM, 'Servicio') !== false) {
|
||||
// Buscamos patrones "Servicio X: <valor>"
|
||||
// Usamos una expresión más robusta para capturar hasta el siguiente "Servicio" o el final de la cadena
|
||||
preg_match_all('/Servicio (\d+):\s*(.*?)(?=\s*Servicio \d+:|$)/', $passAntenaUCRM, $matches, PREG_SET_ORDER);
|
||||
foreach ($matches as $match) {
|
||||
$numServicio = "Servicio " . $match[1];
|
||||
$passAntenaJSON[$numServicio] = trim($match[2]);
|
||||
}
|
||||
} else {
|
||||
// Un solo servicio sin etiqueta (o con advertencia ⚠️)
|
||||
$passAntenaJSON["Servicio 1"] = trim($passAntenaUCRM);
|
||||
}
|
||||
}
|
||||
$passAntenaFinal = !empty($passAntenaJSON) ? json_encode($passAntenaJSON) : "";
|
||||
|
||||
$log->appendLog("Dentro del proceso del patch: " . PHP_EOL);
|
||||
$this->ucrmApi = UcrmApi::create();
|
||||
@ -917,16 +939,9 @@ class ClientCallBellAPI
|
||||
'Content-Type: application/json',
|
||||
]);
|
||||
|
||||
$ch2 = curl_init();
|
||||
curl_setopt($ch2, CURLOPT_RETURNTRANSFER, true);
|
||||
curl_setopt($ch2, CURLOPT_CUSTOMREQUEST, 'PATCH');
|
||||
curl_setopt($ch2, CURLOPT_HTTPHEADER, [
|
||||
'Authorization: Bearer ' . $this->CallBellAPIToken,
|
||||
'Content-Type: application/json',
|
||||
]);
|
||||
$UrlChatCallBell = 'https://api.callbell.eu/v1/contacts/' . $uuid;
|
||||
curl_setopt($ch, CURLOPT_URL, $UrlChatCallBell);
|
||||
curl_setopt($ch2, CURLOPT_URL, $UrlChatCallBell);
|
||||
|
||||
$nombre_cliente = sprintf("%s %s", $notificationData->clientData['firstName'], $notificationData->clientData['lastName']);
|
||||
$log->appendLog("Nombre del cliente que se va a actualizar: " . $nombre_cliente . PHP_EOL);
|
||||
$log->appendLog("UUID: " . $uuid . PHP_EOL);
|
||||
@ -978,22 +993,6 @@ class ClientCallBellAPI
|
||||
//$fecha_ultimoPago = $fecha_ultimoPago->modify('-6 hours');
|
||||
$fecha_ultimoPago_ajustada = $fecha_ultimoPago->format("d/m/Y H:i");
|
||||
|
||||
//$log->appendLog("las dos fechas ajustadas : " . $fecha_actual_ajustada.' aqui la otra: '.$fecha_ultimoPago_ajustada. PHP_EOL);
|
||||
|
||||
// $attributes = $notificationData->clientData['attributes']; //Obtener los atributos del cliente
|
||||
|
||||
// // Variable para almacenar los valores de los atributos que comienzan con "clabe"
|
||||
// $clabeInterbancaria = '';
|
||||
|
||||
// // Iterar sobre los atributoss
|
||||
// foreach ($attributes as $attribute) {
|
||||
// // Verificar si la "key" comienza con "clabe"
|
||||
// if (strpos($attribute['key'], 'clabe') === 0) {
|
||||
// // Agregar el valor al array $clabeValues
|
||||
// $clabeInterbancaria = $attribute['value'];
|
||||
// }
|
||||
// }
|
||||
|
||||
$accountBalance = $notificationData->clientData['accountBalance'];
|
||||
$saldoTexto = '';
|
||||
|
||||
@ -1010,8 +1009,6 @@ class ClientCallBellAPI
|
||||
|
||||
$resumenClienteJSON = '{' .
|
||||
'"Cliente": "' . $notificationData->clientData['id'] . '",' .
|
||||
'"Domicilio": "' .
|
||||
//(($notificationData->clientData['fullAddress'] == null) ? 'Sin domicilio' : '' . $notificationData->clientData['fullAddress']) . '",' .
|
||||
'"Nombre": "' . sprintf("%s %s", $notificationData->clientData['firstName'], $notificationData->clientData['lastName']) . '",' .
|
||||
'"URL": "https://sistema.siip.mx/crm/client/' . $notificationData->clientId . '",' .
|
||||
'"Saldo Actual": "' . $saldoTexto . '",' .
|
||||
@ -1022,11 +1019,11 @@ class ClientCallBellAPI
|
||||
'"Fecha Ultima Actualizacion": "' . $fecha_actual_ajustada . '",' .
|
||||
'"Clabe Interbancaria": "' . $clabeInterbancaria . '",' .
|
||||
'"Site": "' . $site . '",' .
|
||||
'"Antena/Sectorial": "' . $antenaSectorial . '"' .
|
||||
'"Antena/Sectorial": "' . $antenaSectorial . '",' .
|
||||
'"Password Antena": ' . (empty($passAntenaFinal) ? '""' : $passAntenaFinal) .
|
||||
'}';
|
||||
|
||||
$data_CRM = [
|
||||
//"uuid" => $json_responseAPI->contact->uuid,
|
||||
"name" => sprintf("%s %s", $notificationData->clientData['firstName'], $notificationData->clientData['lastName']),
|
||||
|
||||
"custom_fields" => [
|
||||
@ -1043,74 +1040,34 @@ class ClientCallBellAPI
|
||||
"Clabe Interbancaria" => $clabeInterbancaria,
|
||||
"Site" => $site,
|
||||
"Antena/Sectorial" => $antenaSectorial,
|
||||
"Password Antena" => $passAntenaFinal,
|
||||
],
|
||||
];
|
||||
|
||||
$log->appendLog("JSON con los datos a actualizar: " . json_encode($data_CRM) . PHP_EOL);
|
||||
|
||||
$data_CRM2 = [
|
||||
"custom_fields" => [
|
||||
"Resumen" => $resumenClienteJSON,
|
||||
],
|
||||
];
|
||||
|
||||
$log->appendLog("JSON con los datos a actualizar del resumen: " . $resumenClienteJSON . PHP_EOL);
|
||||
|
||||
// OPT: Comparación inteligente para evitar patches innecesarios
|
||||
if (
|
||||
$response_getContactCallBell['custom_fields']['Cliente'] != $data_CRM['custom_fields']['Cliente']
|
||||
|| $response_getContactCallBell['custom_fields']['Domicilio'] != $data_CRM['custom_fields']['Domicilio']
|
||||
|| $response_getContactCallBell['custom_fields']['Nombre'] != $data_CRM['custom_fields']['Nombre']
|
||||
|| $response_getContactCallBell['custom_fields']['URL'] != $data_CRM['custom_fields']['URL']
|
||||
|| $response_getContactCallBell['custom_fields']['Saldo'] != $data_CRM['custom_fields']['Saldo']
|
||||
|| $response_getContactCallBell['custom_fields']['Saldo Actual'] != $data_CRM['custom_fields']['Saldo Actual']
|
||||
|| $response_getContactCallBell['custom_fields']['Estado'] != $data_CRM['custom_fields']['Estado']
|
||||
|| $response_getContactCallBell['custom_fields']['Fecha Ultimo Pago'] != $data_CRM['custom_fields']['Fecha Ultimo Pago']
|
||||
|| $response_getContactCallBell['custom_fields']['Monto Ultimo Pago'] != $data_CRM['custom_fields']['Monto Ultimo Pago']
|
||||
|| ($response_getContactCallBell['custom_fields']['Password Antena'] ?? '') != $data_CRM['custom_fields']['Password Antena']
|
||||
|| $response_getContactCallBell['name'] != $data_CRM['name']
|
||||
|
||||
) {
|
||||
curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($data_CRM));
|
||||
curl_setopt($ch2, CURLOPT_POSTFIELDS, json_encode($data_CRM2));
|
||||
$response = curl_exec($ch);
|
||||
$log->appendLog("Response Patch CallBell: " . $response . PHP_EOL);
|
||||
$response2 = curl_exec($ch2);
|
||||
$log->appendLog("Response 2 Patch CallBell: " . $response2 . PHP_EOL);
|
||||
curl_close($ch);
|
||||
curl_close($ch2);
|
||||
|
||||
// if($fileNameComprobante != null){
|
||||
// sleep(3);
|
||||
// $this->deleteFileWordPressAndLocal($fileNameComprobante);
|
||||
// }
|
||||
|
||||
// $json_data_patch = '{
|
||||
// "attributes": [
|
||||
// {
|
||||
// "value": "' . $UrlChatCallBell . '",
|
||||
// "customAttributeId": 21
|
||||
// }
|
||||
// ]
|
||||
|
||||
// }'; //JSON para hacer patch de los custom fields del cliente en el UISCP CRM, Campo para el Stripe Customer ID y la Clabe interbancaria
|
||||
|
||||
// $clientguzz = new Client(); //instancia de cliente GuzzleHttp para consumir API UISP CRM
|
||||
// try {
|
||||
// $responseCRM = $clientguzz->patch($baseUri . 'clients/' . $notificationData->clientData['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,
|
||||
// ]); //aquí se contruye la petición para hacer patch hacia el cliente en sus custom fields con la API del UISP UCRM
|
||||
|
||||
// } catch (GuzzleException $error) {
|
||||
// echo "Error al hacer el patch al CRM: " . $error->getMessage() . PHP_EOL;
|
||||
// //exit();
|
||||
// }
|
||||
// $log->appendLog(json_encode($responseCRM) . PHP_EOL); //imprimir respuesta del patch de CRM con la clabe y Customer ID Stripe
|
||||
|
||||
} else {
|
||||
$log->appendLog("No hay cambios que actualizar " . PHP_EOL);
|
||||
curl_close($ch);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Loading…
Reference in New Issue
Block a user