reeemplazo de archivos desde el servidor de producción
This commit is contained in:
parent
e113c750b9
commit
d2c6a30ee4
13
CHANGELOG.md
13
CHANGELOG.md
@ -1,5 +1,18 @@
|
|||||||
# CHANGELOG - SIIP WhatsApp Notifications Plugin
|
# CHANGELOG - SIIP WhatsApp Notifications Plugin
|
||||||
|
|
||||||
|
## VERSIÓN 4.5.0 - 13-03-2026
|
||||||
|
|
||||||
|
### ⚠️ Requisito Técnico (Update)
|
||||||
|
- **PHP**: Se requiere **PHP 8.1 o superior** para soportar las nuevas dependencias de AWS. (Habilitado en servidor de pruebas).
|
||||||
|
|
||||||
|
### ✨ Nuevas Características (Features)
|
||||||
|
|
||||||
|
1️⃣ **Gestión de Instaladores**: Módulo para visualizar tareas en curso y realizar el reenvío manual de notificaciones de asignación.
|
||||||
|
|
||||||
|
### 🐛 Correcciones (Bug Fixes)
|
||||||
|
|
||||||
|
1️⃣ **Fix Notificaciones**: Corregido bug que enviaba mensajes de desasignación al cambiar de técnico en tareas activas.
|
||||||
|
|
||||||
## VERSIÓN 4.4.0 - 10-03-2026
|
## VERSIÓN 4.4.0 - 10-03-2026
|
||||||
|
|
||||||
### ✨ Nuevas Características (Features)
|
### ✨ Nuevas Características (Features)
|
||||||
|
|||||||
1438
data/plugin.log
1438
data/plugin.log
File diff suppressed because it is too large
Load Diff
@ -5,13 +5,18 @@
|
|||||||
"displayName": "SIIP - Procesador de Pagos en línea con Stripe, Oxxo y Transferencia, Sincronizador de CallBell y Envío de Notificaciones y comprobantes vía WhatsApp",
|
"displayName": "SIIP - Procesador de Pagos en línea con Stripe, Oxxo y Transferencia, Sincronizador de CallBell y Envío de Notificaciones y comprobantes vía WhatsApp",
|
||||||
"description": "Este plugin sincroniza los clientes del sistema UISP CRM con los contactos de WhatsApp en CallBell, además procesa pagos de Stripe como las trasferencias bancarias y genera referencias de pago vía OXXO, además envía comprobantes de pago en formato imagen PNG o texto vía Whatsapp a los clientes",
|
"description": "Este plugin sincroniza los clientes del sistema UISP CRM con los contactos de WhatsApp en CallBell, además procesa pagos de Stripe como las trasferencias bancarias y genera referencias de pago vía OXXO, además envía comprobantes de pago en formato imagen PNG o texto vía Whatsapp a los clientes",
|
||||||
"url": "https://siip.mx/",
|
"url": "https://siip.mx/",
|
||||||
"version": "4.4.0",
|
"version": "5.0.0",
|
||||||
"unmsVersionCompliancy": {
|
"unmsVersionCompliancy": {
|
||||||
"min": "2.1.0",
|
"min": "2.1.0",
|
||||||
"max": null
|
"max": null
|
||||||
},
|
},
|
||||||
"author": "SIIP INTERNET",
|
"author": "SIIP INTERNET",
|
||||||
"changelog": [
|
"changelog": [
|
||||||
|
{
|
||||||
|
"version": "4.5.0",
|
||||||
|
"date": "2026-03-13",
|
||||||
|
"changes": "Actualización: Implementado reenvío manual de notificaciones de instaladores, fix de lógica de asignación técnica y optimización de AWS SDK (Requiere PHP 8.1+)."
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"version": "4.4.0",
|
"version": "4.4.0",
|
||||||
"date": "2026-03-10",
|
"date": "2026-03-10",
|
||||||
|
|||||||
42
public.php
42
public.php
@ -7,8 +7,7 @@ chdir(__DIR__);
|
|||||||
use Ubnt\UcrmPluginSdk\Service\PluginConfigManager;
|
use Ubnt\UcrmPluginSdk\Service\PluginConfigManager;
|
||||||
use Ubnt\UcrmPluginSdk\Service\UcrmApi;
|
use Ubnt\UcrmPluginSdk\Service\UcrmApi;
|
||||||
use Ubnt\UcrmPluginSdk\Service\UcrmSecurity;
|
use Ubnt\UcrmPluginSdk\Service\UcrmSecurity;
|
||||||
|
use SmsNotifier\Service\PaymentIntentService;
|
||||||
// Eliminado: PaymentIntentService ya no se usa aquí
|
|
||||||
|
|
||||||
if (!file_exists(__DIR__ . '/data/config.json')) {
|
if (!file_exists(__DIR__ . '/data/config.json')) {
|
||||||
die('Acceso denegado o configuración no encontrada.');
|
die('Acceso denegado o configuración no encontrada.');
|
||||||
@ -46,8 +45,7 @@ $httpClient = new \GuzzleHttp\Client([
|
|||||||
]);
|
]);
|
||||||
|
|
||||||
$ucrmApi = new UcrmApi($httpClient, $config['apitoken'] ?? '');
|
$ucrmApi = new UcrmApi($httpClient, $config['apitoken'] ?? '');
|
||||||
|
$paymentIntentService = new PaymentIntentService($ucrmApi, $config['tokenstripe'] ?? '');
|
||||||
$ucrmApi = new UcrmApi($httpClient, $config['apitoken'] ?? '');
|
|
||||||
|
|
||||||
// Admins Logic
|
// Admins Logic
|
||||||
$admins = [];
|
$admins = [];
|
||||||
@ -322,6 +320,42 @@ if (isset($_GET['action'])) {
|
|||||||
exit;
|
exit;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if ($_GET['action'] === 'search_stripe') {
|
||||||
|
$q = $_GET['q'] ?? '';
|
||||||
|
echo json_encode($paymentIntentService->searchClients($q));
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($_GET['action'] === 'get_stripe_details') {
|
||||||
|
$id = $_GET['id'] ?? null;
|
||||||
|
if ($id) {
|
||||||
|
echo json_encode($paymentIntentService->getClientDetails($id));
|
||||||
|
} else {
|
||||||
|
echo json_encode(['error' => 'Missing ID']);
|
||||||
|
}
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($_GET['action'] === 'get_stripe_history') {
|
||||||
|
$stripeCustomerId = $_GET['stripeCustomerId'] ?? null;
|
||||||
|
if ($stripeCustomerId) {
|
||||||
|
echo json_encode(['history' => $paymentIntentService->getLastPayments($stripeCustomerId, 10)]);
|
||||||
|
} else {
|
||||||
|
echo json_encode(['error' => 'Missing customer id']);
|
||||||
|
}
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($_GET['action'] === 'get_oxxo_history') {
|
||||||
|
$stripeCustomerId = $_GET['stripeCustomerId'] ?? null;
|
||||||
|
if ($stripeCustomerId) {
|
||||||
|
echo json_encode(['history' => $paymentIntentService->getLastOxxoPayments($stripeCustomerId, 5)]);
|
||||||
|
} else {
|
||||||
|
echo json_encode(['error' => 'Missing customer id']);
|
||||||
|
}
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
if ($_GET['action'] === 'get_installer_jobs') {
|
if ($_GET['action'] === 'get_installer_jobs') {
|
||||||
$installerId = $_GET['installerId'] ?? null;
|
$installerId = $_GET['installerId'] ?? null;
|
||||||
if (!$installerId) {
|
if (!$installerId) {
|
||||||
|
|||||||
BIN
siip_full_structure/.DS_Store
vendored
Normal file
BIN
siip_full_structure/.DS_Store
vendored
Normal file
Binary file not shown.
310
siip_full_structure/CHANGELOG.md
Normal file
310
siip_full_structure/CHANGELOG.md
Normal file
@ -0,0 +1,310 @@
|
|||||||
|
# CHANGELOG - SIIP WhatsApp Notifications Plugin
|
||||||
|
|
||||||
|
## VERSIÓN 4.5.0 - 13-03-2026
|
||||||
|
|
||||||
|
### ⚠️ Requisito Técnico (Update)
|
||||||
|
- **PHP**: Se requiere **PHP 8.1 o superior** para soportar las nuevas dependencias de AWS. (Habilitado en servidor de pruebas).
|
||||||
|
|
||||||
|
### ✨ Nuevas Características (Features)
|
||||||
|
|
||||||
|
1️⃣ **Gestión de Instaladores**: Módulo para visualizar tareas en curso y realizar el reenvío manual de notificaciones de asignación.
|
||||||
|
|
||||||
|
### 🐛 Correcciones (Bug Fixes)
|
||||||
|
|
||||||
|
1️⃣ **Fix Notificaciones**: Corregido bug que enviaba mensajes de desasignación al cambiar de técnico en tareas activas.
|
||||||
|
|
||||||
|
## VERSIÓN 4.4.0 - 10-03-2026
|
||||||
|
|
||||||
|
### ✨ Nuevas Características (Features)
|
||||||
|
|
||||||
|
1️⃣ **Reenvío manual de notificaciones de tareas**: Nuevo módulo en la sección "Gestión de Instaladores" que permite ver las tareas activas ("En curso") de cada técnico y reenviar manualmente la notificación WhatsApp de asignación con un solo clic.
|
||||||
|
|
||||||
|
- **Nuevo endpoint `GET ?action=get_installer_jobs`**: Consulta la API de UCRM para obtener los jobs activos (`status=1`) filtrados por instalador, enriquecidos con nombre del cliente.
|
||||||
|
- **Nuevo endpoint `POST action=resend_job_notification`**: Simula un webhook `job.edit` (status 0→1) vía loopback curl para disparar el flujo completo de notificación.
|
||||||
|
- **Tabla interactiva**: Card "📋 Tareas Activas del Instalador" con columnas Folio, Cliente, Fecha, Descripción y botón 📨 Reenviar.
|
||||||
|
- **Icono 📋 en tabla de instaladores**: Permite cargar las tareas activas de cualquier técnico con un clic.
|
||||||
|
|
||||||
|
## VERSIÓN 4.3.1 - 10-03-2026
|
||||||
|
|
||||||
|
### 🐛 Correcciones (Bug Fixes)
|
||||||
|
|
||||||
|
1️⃣ **Fix Notificación de Cambio de Instalador**: Corregido bug crítico en `AbstractMessageNotifierFacade.php` donde al cambiar de técnico en una tarea **"En curso"**, el **nuevo** instalador recibía el mensaje de **desasignación** (❌ "se te ha desasignado la tarea...") en lugar de un mensaje de **asignación** con los datos del cliente.
|
||||||
|
|
||||||
|
**Causa raíz**: La bandera `$changeInstaller=true` se pasaba tanto a la notificación del técnico anterior como a la del nuevo, causando que ambos recibieran la plantilla de desasignación. El fix envía `false` al nuevo técnico para que use la plantilla de asignación normal.
|
||||||
|
|
||||||
|
## VERSIÓN 4.3.0 - 23-02-2026
|
||||||
|
|
||||||
|
### 🔐 Seguridad y Acceso (Login)
|
||||||
|
|
||||||
|
1️⃣ **Sistema de Login Integrado**:
|
||||||
|
_ Implementada pantalla de inicio de sesión "Premium" para acceso externo
|
||||||
|
_ Validación estricta con la API de UISP (endpoint `/user/login`)
|
||||||
|
_ Soporte completo para autenticación de dos factores (2FA / TOTP)
|
||||||
|
_ Control de roles: Acceso restringido exclusivamente a administradores y súper administradores
|
||||||
|
\_ Manejo de estado de sesiones con `sessionStorage` y validación híbrida PHP/JS
|
||||||
|
|
||||||
|
2️⃣ **Protección de Rutas (Proxy de Sesión)**:
|
||||||
|
_ Nuevo sistema PHP para detectar el contexto de la URL (Pública vs Privada CRM)
|
||||||
|
_ Auto-login transparente si existe una sesión válida en el navegador desde el portal UCRM
|
||||||
|
\_ Botón condicional de "Cerrar Sesión" exclusivo para accesos desde URL pública
|
||||||
|
|
||||||
|
### 💎 Rediseño Premium de Interfaz (UI/UX)
|
||||||
|
|
||||||
|
1️⃣ **Autenticación Premium (Stitch Glassmorphism)**:
|
||||||
|
_ Pantalla de login elegante usando tendencias de Glassmorphism (`blur`, transparencias RGBA, bordes sutiles iluminados)
|
||||||
|
_ Partículas flotantes decorativas con efecto desenfoque radial en el fondo oscuro
|
||||||
|
_ Inputs "Glow" reactivos y botones con gradientes vibrantes y sombra perimetral flotante
|
||||||
|
_ Interruptor ('Toggle Switch') animado con física de rebote para alternar entre modo Claro y Oscuro
|
||||||
|
\_ Ocultamiento de textos truncados ("Modo Osc...") en favor de una experiencia más visual y limpia con íconos deslizables
|
||||||
|
|
||||||
|
## VERSIÓN 4.2.2 - 10-02-2026
|
||||||
|
|
||||||
|
### ✨ Nuevas Características (Features)
|
||||||
|
|
||||||
|
1️⃣ **Sistema de Navegación por Pestañas**:
|
||||||
|
_ Implementado sistema de pestañas horizontal similar a siip-payments-tools
|
||||||
|
_ Permite cambiar entre módulos sin volver al dashboard principal
|
||||||
|
_ Pestañas sticky que permanecen fijas al hacer scroll
|
||||||
|
_ Navegación fluida: Instaladores ↔ Notificaciones ↔ Pagos SPEI ↔ Pagos OXXO
|
||||||
|
_ Mejora significativa en la UX de navegación
|
||||||
|
_ Inicialización inteligente de módulos (carga solo cuando se accede)
|
||||||
|
|
||||||
|
### 🎨 Mejoras Visuales
|
||||||
|
|
||||||
|
- Pestañas con iconos representativos de cada módulo
|
||||||
|
- Estilo consistente con el diseño del plugin siip-payments-tools
|
||||||
|
- Adaptación completa a modos claro y oscuro
|
||||||
|
- Animaciones suaves en hover y transiciones de pestañas
|
||||||
|
|
||||||
|
## VERSIÓN 4.2.1 - 10-02-2026
|
||||||
|
|
||||||
|
### 🎨 Mejoras Visuales (Visual Enhancements)
|
||||||
|
|
||||||
|
1️⃣ **Footer Sticky (Pegado al Fondo)**:
|
||||||
|
_ Implementado sistema Flexbox en body y container
|
||||||
|
_ El footer ahora siempre se posiciona al fondo de la página \* Resuelve problema de footer flotante en módulos con poco contenido
|
||||||
|
|
||||||
|
2️⃣ **Footer con Versión Dinámica**:
|
||||||
|
_ Agregado footer profesional con branding SIIP Internet
|
||||||
|
_ Copyright dinámico con año actual usando PHP
|
||||||
|
_ Visualización clara de la versión del plugin (4.2.1)
|
||||||
|
_ Border superior prominente con color primario
|
||||||
|
|
||||||
|
### 🔄 Consistencia Visual
|
||||||
|
|
||||||
|
- Alineado con mejoras visuales del plugin `siip-payments-tools` v2.2.11
|
||||||
|
- Mantenida coherencia en diseño entre todos los plugins SIIP
|
||||||
|
|
||||||
|
### 📝 Nota
|
||||||
|
|
||||||
|
- Las esquinas redondeadas en imágenes del menú se consideraron pero se revirtieron
|
||||||
|
debido a que los logos rectangulares (Stripe, OXXO) se distorsionaban con `object-fit: cover`
|
||||||
|
|
||||||
|
## VERSIÓN 4.2.0 - 17-01-2026
|
||||||
|
|
||||||
|
### 🚀 Nuevas Características (Features)
|
||||||
|
|
||||||
|
1. **Visualizador de Pagos Mensuales**:
|
||||||
|
- Nueva sección en el portal administrativo para análisis visual de pagos.
|
||||||
|
- Selector de mes para consultar estadísticas de cualquier período.
|
||||||
|
- Tarjetas estadísticas mostrando: Total de clientes activos, Clientes que pagaron, Clientes pendientes.
|
||||||
|
- Gráfica de dona (doughnut) interactiva con Chart.js mostrando proporción de pagos.
|
||||||
|
- Tabla detallada de clientes pendientes con información de saldo.
|
||||||
|
- Cálculo automático de porcentajes de completitud.
|
||||||
|
- Loader/spinner animado mientras se cargan los datos.
|
||||||
|
|
||||||
|
2. **Integración de Microservicio para Metadata de Stripe**:
|
||||||
|
- Nuevo microservicio Node.js (`vouchers-oxxopay-generator-service`) con acceso directo a la base de datos UCRM.
|
||||||
|
- Endpoint `/stripe-metadata/:id` para obtener metadata de pagos Stripe que no está disponible en la API de UCRM.
|
||||||
|
- Endpoint `/payments/:id/user` para actualizar el `userId` de pagos vía SQL directo (campo no modificable por API).
|
||||||
|
- Configuración mediante archivo `.env` para credenciales de base de datos.
|
||||||
|
|
||||||
|
### 🔵 Mejoras (Enhancements)
|
||||||
|
|
||||||
|
1. **Sincronización Mejorada con CallBell**:
|
||||||
|
- Fix crítico en sincronización de saldo: Ahora se actualiza correctamente en CallBell cuando se agregan facturas.
|
||||||
|
- Logging detallado agregado a `onlyUpdate()` para diagnóstico de sincronización.
|
||||||
|
- Comparación inteligente de campos para evitar PATCH innecesarios.
|
||||||
|
- Sincronización automática al agregar/editar facturas (`invoice.add`, `invoice.edit`).
|
||||||
|
- Sincronización automática al editar servicios (`service.edit`, `service.suspend`, etc.).
|
||||||
|
|
||||||
|
2. **Mejora en Categorización de Pagos Stripe**:
|
||||||
|
- Lógica mejorada en `ensureStripePaymentAttribute()` para asignar correctamente el atributo `tipoPagoStripe`.
|
||||||
|
- Normalización de valores de metadata: "OXXO" → "OXXO Pay" para coincidir con opciones de UCRM.
|
||||||
|
- Prioridad a metadata obtenida del microservicio sobre adivinación por nombre de método.
|
||||||
|
- Fix de validación 422 para pagos OXXO que fallaban por valor de atributo no válido.
|
||||||
|
|
||||||
|
3. **Configuración para Producción**:
|
||||||
|
- Archivo `.env` agregado al microservicio con todas las credenciales de base de datos.
|
||||||
|
- `docker-compose.yml` refactorizado para usar variables de entorno desde `.env`.
|
||||||
|
- Red Docker `unms_internal` configurada para comunicación segura entre servicios.
|
||||||
|
- Puerto 5432 de PostgreSQL expuesto en `docker-compose.yml` principal para acceso del microservicio.
|
||||||
|
|
||||||
|
### 🐛 Correcciones (Bug Fixes)
|
||||||
|
|
||||||
|
1. **Fix Metadata de Pagos Stripe**: Los pagos por transferencia bancaria y OXXO ahora se categorizan correctamente.
|
||||||
|
2. **Fix sincronización CallBell**: Saldo y estado del cliente ahora se actualizan correctamente en tiempo real.
|
||||||
|
3. **Fix networking Docker**: Resueltos problemas de `ECONNREFUSED` entre contenedores mediante configuración de red interna.
|
||||||
|
4. **Fix validación de atributos**: Normalización de valores para evitar errores 422 en la API de UCRM.
|
||||||
|
|
||||||
|
### 📝 Documentación
|
||||||
|
|
||||||
|
1. Logging mejorado en `ClientCallBellAPI.php` para comparaciones de campo.
|
||||||
|
2. Logging agregado en `AbstractMessageNotifierFacade.php` para diagnóstico de sync.
|
||||||
|
3. Documentación completa del flujo de datos del visualizador de pagos.
|
||||||
|
|
||||||
|
## VERSIÓN 4.1.0 - 15-01-2026
|
||||||
|
|
||||||
|
### 🚀 Nuevas Características (Features)
|
||||||
|
|
||||||
|
1. **Microservicio PDF (`pdf-cropper`)**:
|
||||||
|
- Nuevo servicio en Python (FastAPI + Poppler) desplegado en Docker (Puerto 8050) para la conversión de PDF a Imagen.
|
||||||
|
- Reemplaza la librería `Imagick` de PHP, aislando el procesamiento pesado fuera del plugin.
|
||||||
|
- **Modo Full**: Garantiza la conversión del PDF completo respetando su relación de aspecto original (sin recortes ni "square crop").
|
||||||
|
- **Overlay de Texto**: Capacidad nativa en el backend para sobreescribir texto dinámicamente (ej. cambiar "Tarjeta de crédito Stripe" por "OXXO Pay") basado en parámetros.
|
||||||
|
2. **Integración S3 / MinIO**:
|
||||||
|
- Implementación de `MinioStorageService`.
|
||||||
|
- Los comprobantes (JPG) y Vouchers OXXO se suben automáticamente a MinIO en lugar de usar FTP.
|
||||||
|
- Generación de URLs públicas seguras.
|
||||||
|
|
||||||
|
### 🧹 Mantenimiento y Limpieza (Chores/Refactor)
|
||||||
|
|
||||||
|
1. **Limpieza Automática (Cleanup)**:
|
||||||
|
- Se implementó lógica para eliminar archivos temporales locales (PDFs descargados, JPGs generados) inmediatamente después de una subida exitosa a MinIO.
|
||||||
|
2. **Refactor `ClientCallBellAPI`**:
|
||||||
|
- Eliminación de código muerto relacionado con FTP de WordPress.
|
||||||
|
- Implementación de cliente HTTP Guzzle para comunicación con `pdf-cropper`.
|
||||||
|
- **Cache Busting**: Se añade Timestamp al nombre de archivo (`_time()`) para evitar caché agresivo en WhatsApp/Navegadores.
|
||||||
|
|
||||||
|
### 🐛 Correcciones (Bug Fixes)
|
||||||
|
|
||||||
|
1. **Fix Recorte de Imagen**: Se eliminó la restricción de relación de aspecto 1:1 (cuadrado) para evitar recortes.
|
||||||
|
2. **Standardización de Notas**: Actualización de `AbstractStripeOperationsFacade` para incluir explícitamente "OXXO" o "Transferencia" en las notas de pago.
|
||||||
|
|
||||||
|
## VERSIÓN 4.0.0 - 10-01-2026
|
||||||
|
|
||||||
|
### 🟢 Novedades (Re-diseño UI/UX + Integ. Pagos)
|
||||||
|
|
||||||
|
1️⃣ **Portal Administrativo Integrado (Stripe + Oxxo)**: Se ha rediseñado completamente el panel administrativo (`Dashboard`) para incluir una sección dedicada a **Pagos Online**, permitiendo la generación de referencias SPEI y OXXO Pay sin salir del portal.
|
||||||
|
2️⃣ **Visualización Inline de Vouchers OXXO**: Ahora los comprobantes de pago OXXO se generan y visualizan inmediatamente en una vista partida (Datos + Imagen) dentro del dashboard, eliminando la necesidad de abrir enlaces externos para verificación.
|
||||||
|
3️⃣ **Carga FTP Híbrida**: Optimización inteligente que sube los vouchers al servidor FTP solo cuando es necesario (integraciones externas/webhooks) y utiliza visualización local para el administrador, garantizando máxima velocidad de interfaz.
|
||||||
|
4️⃣ **Modo Oscuro & UI Premium**: Nueva capa visual con sidebar expandido, cabecera global "Sticky", y un sistema de colores adaptativo (Modo Sol/Luna) para una mejor experiencia de usuario.
|
||||||
|
5️⃣ **Seguridad Reforzada**: Refactorización completa del manejo de credenciales, eliminando datos hardcodeados en el código y centralizándolos en la configuración segura del plugin.
|
||||||
|
|
||||||
|
### 🔵 Mejoras
|
||||||
|
|
||||||
|
1️⃣ **Navegación Intuitiva**: Menú lateral rediseñado con accesos directos claros a "Pagos Stripe", "Notificaciones" y "Gestión de Instaladores".
|
||||||
|
2️⃣ **Feedback Visual**: Nuevas alertas toast y modales informativos para confirmar acciones (Generación de referencias, errores de API, etc.).
|
||||||
|
3️⃣ **Compatibilidad**: Ajustes en `manifest.json` y estructura de archivos para asegurar compatibilidad total con las últimas versiones de UISP.
|
||||||
|
|
||||||
|
## VERSIÓN 3.1.0 - 07-01-2026
|
||||||
|
|
||||||
|
### 🟢 Novedades
|
||||||
|
|
||||||
|
1️⃣ **Re-envío Manual de Notificaciones de Pago**: Se añadió un nuevo apartado en el Dashboard que permite buscar clientes y re-disparar notificaciones de WhatsApp para pagos específicos de forma manual.
|
||||||
|
2️⃣ **Buscador de Clientes en Dashboard**: Integración de buscador dinámico para localizar clientes y visualizar su historial de los últimos 10 pagos.
|
||||||
|
|
||||||
|
## VERSIÓN 3.0.0 - 02-01-2026
|
||||||
|
|
||||||
|
### 🟢 Novedades
|
||||||
|
|
||||||
|
1️⃣ **Soporte Multi-Servicio para Antenas**: Ahora el plugin gestiona múltiples servicios por cliente, mostrando cada contraseña con el formato `Servicio 1: <pass> Servicio 2: <pass> ...`.
|
||||||
|
2️⃣ **Validación Granular de Provisionamiento**: Se implementó una lógica de detección por etapas (Servicio -> Sitio UISP -> Dispositivo) para evitar generar contraseñas en sitios "Location Inactive".
|
||||||
|
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️⃣ **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️⃣ **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
|
||||||
|
|
||||||
|
1️⃣ Resolución dinámica del ID del método de pago ("Transferencia bancaria") mediante consulta a la API de UISP, mejorando la portabilidad del plugin entre distintos servidores.
|
||||||
|
2️⃣ Implementación de registro de pago automático desde Webhook Stripe para eventos de tipo `customer_cash_balance_transaction.created` (Saldo aplicado).
|
||||||
|
|
||||||
|
### 🟡 Bugs Resueltos
|
||||||
|
|
||||||
|
1️⃣ Se corrigió el error de validación de la API de UCRM (422) mediante el cast explícito de `clientId` a integer y el uso de `methodId` como string.
|
||||||
|
|
||||||
|
## VERSIÓN 2.9.2
|
||||||
|
|
||||||
|
### 🟡 Bugs Resueltos
|
||||||
|
|
||||||
|
1️⃣ Se solucionó un bug que impedía obtener la contraseñas de la bóveda, ya que el response de la API cambió en la última actualización y la esstructura nueva impedía acceder al dato del password
|
||||||
|
|
||||||
|
## VERSIÓN 2.8.8
|
||||||
|
|
||||||
|
### 🟡 Bugs Resueltos
|
||||||
|
|
||||||
|
1️⃣ Se solucionó un bug que impedía al BOT del CallBell mostrar el monto de la referencia de OXXO en el mensaje donde se le entrega el voucher al cliente, para el caso donde el cliente elegía crear su referencia con la CANTIDAD TOTAL.
|
||||||
|
|
||||||
|
## VERSIÓN 2.8.7
|
||||||
|
|
||||||
|
### 🟢 Novedades
|
||||||
|
|
||||||
|
1️⃣ Ahora las referencias de **OXXO Pago** han cambiado, en lugar de enviarse la URL o link de pago al cliente será la imagen del código de barras y la información que aparece en el link directamente en el mensaje, de esta manera será más cómodo para el cliente tener la imagen en su chat a tener que abrir una URL o link externo.
|
||||||
|
|
||||||
|
### 🔵 Mejoras
|
||||||
|
|
||||||
|
1️⃣ Se modificaron nodos del bot **_OXXO_BOT_** para poder adaptar esta actualización correctamente.
|
||||||
|
2️⃣ Mejoras en el código fuente del flujo de trabajo para las referencias de OXXO PAGO.
|
||||||
|
|
||||||
|
## VERSIÓN 2.8.6
|
||||||
|
|
||||||
|
### 🔵 Mejoras
|
||||||
|
|
||||||
|
1️⃣ Se modificó la información para el envío de notificaciones a llos instaladores en el flujo de trabajo para la desasignación de tareas que hacía que no se viera correctamente la información
|
||||||
|
|
||||||
|
## VERSIÓN 2.8.5
|
||||||
|
|
||||||
|
### 🔵 Mejoras
|
||||||
|
|
||||||
|
1️⃣ Se modificó la información para el envío de notificaciones a los clientes para su visita técnica: ahora ya no se envían las horas en que serán las visitas, SOLO LA FECHA.
|
||||||
|
2️⃣ Se crearon nuevas plantillas de tipo utilidad y se adaptaron al flujo de trabajo en el código.
|
||||||
|
|
||||||
|
### 🟡 Bugs Resueltos
|
||||||
|
|
||||||
|
1️⃣ Se soluciono el bug que impedía enviar notificaciones a los instaladores para nuevas tareas.
|
||||||
|
|
||||||
|
## VERSIÓN 2.8.2
|
||||||
|
|
||||||
|
### 🟡 Bugs Resueltos
|
||||||
|
|
||||||
|
1️⃣ No se enviaban las notificaciones de las tareas al instalador. Se cambió la plantilla de CallBell o WhatsApp con 3 variables en lugar de 8.
|
||||||
|
2️⃣ Se agregó un nuevo tipo de pago ("applied_to_payment") en las propiedades de los Webhooks recibidos mediante Stripe por concepto de transferencias bancarias. Ya que sólo se revisaba el tipo de pago "funded" y eso hacía que no enviara los comprobantes de pago a los clientes para todos los casos.
|
||||||
|
|
||||||
|
## VERSIÓN 2.8.1
|
||||||
|
|
||||||
|
### 🟡 Bugs Resueltos
|
||||||
|
|
||||||
|
1️⃣ No se enviaban las notificaciones de las tareas al instalador.
|
||||||
|
|
||||||
|
## VERSIÓN 2.8.0
|
||||||
|
|
||||||
|
### 🟢 Novedades
|
||||||
|
|
||||||
|
1️⃣ Envío de contraseña de antena en el mensaje que se manda al instalador cuando se le asigna una tarea/servicio.
|
||||||
|
2️⃣ Para clientes nuevos o que no tengan el campo personalizado de “Password Antena Cliente” al actualizarlos se les asignará ese campo o cuando se les actualice su servicio.
|
||||||
|
3️⃣ Ahora se puede modificar tantas veces sea necesaria una tarea o servicio como su fecha o el instalador mientras la tarea permanezca en estado “Abiertos” SIN que se envíen notificaciones a los clientes o instaladores.
|
||||||
|
4️⃣ Se agregó un prefijo al título de la tarea/servicio agendado cuando recién se da de alta para identificar que esta no ha sido notificada aún y se pueden realizar ajustes.
|
||||||
|
|
||||||
|
### 🔵 Mejoras
|
||||||
|
|
||||||
|
1️⃣ Se modificó el flujo de trabajo para el envío de notificaciones a los clientes para su visita técnica, ahora son cuatro distintos tipos de flujo, anteriormente tres.
|
||||||
|
2️⃣ Se reemplazaron algunas plantillas de mensajes de CallBell.
|
||||||
|
3️⃣ Se renombró el uso de la etiqueta “CREARCLABESTRIPE” por “CREAR CLABE STRIPE” para un mejor entendimiento de su uso.
|
||||||
|
|
||||||
|
### 🟡 Bugs Resueltos
|
||||||
|
|
||||||
|
1️⃣ Envío de notificaciones a los clientes y a los instaladores al CERRAR una tarea/servicio (Se mandaban mensajes de asignación y de visita del técnico ) haciendo no posible cerrar las tareas.
|
||||||
115
siip_full_structure/README.md
Normal file
115
siip_full_structure/README.md
Normal file
@ -0,0 +1,115 @@
|
|||||||
|
# SIIP - WhatsApp Notifications & Integrated Payment Portal
|
||||||
|
|
||||||
|

|
||||||
|

|
||||||
|

|
||||||
|

|
||||||
|
|
||||||
|
Este plugin es una solución integral que transforma tu UCRM en un **Portal Administrativo de Última Generación**. No solo automatiza la comunicación por WhatsApp, sino que integra un Dashboard completo para la gestión de pagos online (Stripe/OXXO), visualización de comprobantes y coordinación de equipos técnicos.
|
||||||
|
|
||||||
|
## 🚀 Novedades v4.5.0 (Manual Resend & Fixes)
|
||||||
|
|
||||||
|
- **📨 Reenvío Manual**: Nuevo botón dentro de "Gestión de Instaladores" para reenviar notificaciones de tareas asignadas.
|
||||||
|
- **🔧 Fix Notificaciones**: Corregido bug crítico en la lógica de asignación/desasignación de técnicos.
|
||||||
|
- **⚙️ Dependencias**: Optimización de AWS SDK (Requiere PHP 8.1+).
|
||||||
|
|
||||||
|
## 🐛 Hotfix v4.3.1 (Installer Notification Fix)
|
||||||
|
|
||||||
|
- **🔧 Fix Cambio de Instalador**: Corregido bug donde el nuevo técnico recibía mensaje de desasignación en vez de asignación al cambiar instalador en una tarea "En curso".
|
||||||
|
|
||||||
|
## 🔐 Novedades v4.3.0 (Security & Premium UI)
|
||||||
|
|
||||||
|
- **🛡️ Sistema de Acceso Seguro**: Implementada validación híbrida (Server + Client). El plugin ahora protege las URLs públicas mediante una pantalla de inicio de sesión que requiere credenciales de Administrador de UCRM o autenticación 2FA.
|
||||||
|
- **💎 Autenticación Premium (Glassmorphism)**: Nueva pantalla de inicio de sesión con una interfaz elegante y de lujo basada en Glassmorphism interactivo (esferas flotantes, desenfoques profundos, switch animado tipo iOS para modo oscuro).
|
||||||
|
- **🚀 Single-Sign-On Ciego**: Si el administrador ya ingresó desde el portal interno UCRM, la capa de seguridad detecta la sesión local y le otorga acceso sin pedir credenciales, optimizando el flujo de trabajo.
|
||||||
|
|
||||||
|
## 🚀 Novedades v4.2.0 (Analytics & Sync)
|
||||||
|
|
||||||
|
- **📊 Visualizador de Pagos Mensuales**: Nueva herramienta de análisis que permite seleccionar cualquier mes y visualizar gráficamente:
|
||||||
|
- Estadísticas de clientes activos vs clientes que pagaron
|
||||||
|
- Gráfica de dona interactiva con Chart.js
|
||||||
|
- Listado detallado de clientes pendientes con saldos
|
||||||
|
- Porcentajes de cobranza en tiempo real
|
||||||
|
- **🔄 Sincronización Mejorada CallBell**: Fix crítico que garantiza la actualización automática del saldo y estado del cliente en CallBell cuando se agregan facturas o se modifican servicios.
|
||||||
|
- **🎯 Categorización Inteligente de Pagos**: Nuevo microservicio con acceso directo a la base de datos para obtener metadata de Stripe (tipo de pago) y asignar correctamente los atributos incluso cuando la API de UCRM no tiene la información.
|
||||||
|
- **⚙️ Configuración para Producción**: Sistema de `.env` implementado para gestión segura de credenciales de base de datos.
|
||||||
|
|
||||||
|
## 🚀 Novedades v4.1.0 (Performance & Storage)
|
||||||
|
|
||||||
|
- **⚡ Microservicio PDF (`pdf-cropper`)**: Nuevo motor de renderizado externo (Python/FastAPI) que reemplaza librerías legacy, aumentando la velocidad y eliminando problemas de memoria en el servidor principal.
|
||||||
|
- **📦 MinIO / S3 Storage**: Migración completa del almacenamiento de comprobantes. Adiós FTP, hola almacenamiento de objetos seguro y escalable con URLs firmadas y limpieza automática.
|
||||||
|
- **🖼️ Smart Image Processing**: Generación de imágenes "Full Mode" (sin recortes) y capacidad de **Edición Dinámica de Texto** (Overlay) para personalizar métodos de pago en los comprobantes.
|
||||||
|
|
||||||
|
## 🚀 Novedades v4.0.0 (Portal Dashboard)
|
||||||
|
|
||||||
|
- **🖥️ Dashboard de Pagos Integrado**: Nueva interfaz visual dentro de UCRM para gestionar cobros de Stripe y OXXO sin salir de la plataforma.
|
||||||
|
- **🏪 OXXO Pay Híbrido & Visual**: Generación de fichas OXXO con dos modos inteligentes:
|
||||||
|
- **Inline (Admin)**: Visualización inmediata del voucher (local) para máxima velocidad.
|
||||||
|
- **FTP (Webhook)**: Carga automática a servidor externo para compartir links públicos.
|
||||||
|
- **🌑 UI/UX Premium**: Interfaz rediseñada con Sidebar expandible, cabecera global "Sticky", Modo Oscuro automático y componentes responsivos.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## ⚡ Funcionalidades Principales
|
||||||
|
|
||||||
|
### 1. Gestión de Cobranza Avanzada
|
||||||
|
|
||||||
|
- **Pagos SPEI**: Instrucciones inmediatas con CLABE personalizada.
|
||||||
|
- **Vouchers OXXO**: Generación instantánea de códigos de barras con vista previa "Split-Screen" (Datos + Imagen).
|
||||||
|
- **Historial en Tiempo Real**: Consulta los últimos 10 pagos de cualquier cliente al instante.
|
||||||
|
|
||||||
|
### 2. Automatización WhatsApp (CallBell)
|
||||||
|
|
||||||
|
- **Notificaciones Dinámicas**: Envío automático de facturas, recordatorios de pago y avisos de corte.
|
||||||
|
- **Re-envío Manual**: Herramienta para disparar notificaciones específicas desde el Dashboard si el proceso automático falla.
|
||||||
|
- **Sincronización Total**: Mantiene los contactos de CallBell siempre actualizados con los datos del CRM.
|
||||||
|
|
||||||
|
### 3. Coordinación Técnica (Jobs/Tasks)
|
||||||
|
|
||||||
|
- **Agenda Inteligente**: Notifica a instaladores sobre nuevas tareas y reprogramaciones.
|
||||||
|
- **Datos para Técnicos**: Envía coordenadas GPS y contraseñas de equipos (formato impresora térmica) directamente al WhatsApp del técnico.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🏗️ Arquitectura del Sistema
|
||||||
|
|
||||||
|
El plugin utiliza una arquitectura modular basada en **Facades** y **Servicios** para garantizar estabilidad y escalabilidad.
|
||||||
|
|
||||||
|
### Componentes Clave
|
||||||
|
|
||||||
|
- **Frontend (`public.php`)**: Una Single Page Application (SPA) ligera incrustada en UCRM, construida con Vanilla JS y CSS moderno (Variables, Flexbox/Grid).
|
||||||
|
- **Despachador (`Plugin.php`)**: Router de eventos que delega acciones a los controladores específicos.
|
||||||
|
- **Motor de Pagos (`PaymentIntentStripe`)**: Servicio robusto que se comunica con la API de Stripe para crear intenciones de pago y métodos.
|
||||||
|
- **Generador OXXO (`AbstractOxxoOperationsFacade`)**: Orquesta el flujo complejo: Stripe -> Puppeteer (Screenshot) -> FTP/Local Storage -> Respuesta JSON.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🛠️ Requisitos e Integraciones
|
||||||
|
|
||||||
|
Para desbloquear todo el potencial, el plugin requiere:
|
||||||
|
|
||||||
|
1. **UISP/UCRM**: Versión 2.1.0 o superior.
|
||||||
|
2. **PHP**: Versión **8.1 o superior** (Recomendado Ubuntu 22.04 LTS).
|
||||||
|
3. **Stripe API**: Keys de producción/test para procesar pagos.
|
||||||
|
3. **CallBell API**: Token para el envío de mensajes de WhatsApp.
|
||||||
|
4. **Microservicio Puppeteer**: Contenedor Docker para renderizar los vouchers de OXXO a imagen.
|
||||||
|
5. **Servidor FTP (Opcional)**: Requerido solo si se desea generar URLs públicas para compartir los vouchers externamente.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🏷️ Configuración Rápida
|
||||||
|
|
||||||
|
El plugin se configura directamente desde el panel de UCRM (`Ajustes -> Plugins -> siip-whatsapp-notifications`).
|
||||||
|
|
||||||
|
### Parámetros Críticos
|
||||||
|
|
||||||
|
- `ipserver`: Tu dominio UCRM.
|
||||||
|
- `tokenstripe`: Tu Secret Key de Stripe.
|
||||||
|
- `tokencallbell`: Tu API Key de CallBell.
|
||||||
|
- `hostServerFTP` / `user` / `pass`: Credenciales para el almacenamiento de vouchers remotos.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 📝 Registro de Cambios
|
||||||
|
|
||||||
|
Para consultar la evolución detallada del proyecto, ver el archivo [CHANGELOG.md](./CHANGELOG.md).
|
||||||
299
siip_full_structure/manifest.json
Normal file
299
siip_full_structure/manifest.json
Normal file
@ -0,0 +1,299 @@
|
|||||||
|
{
|
||||||
|
"version": "1",
|
||||||
|
"information": {
|
||||||
|
"name": "siip-whatsapp-notifications",
|
||||||
|
"displayName": "SIIP - Procesador de Pagos en línea con Stripe, Oxxo y Transferencia, Sincronizador de CallBell y Envío de Notificaciones y comprobantes vía WhatsApp",
|
||||||
|
"description": "Este plugin sincroniza los clientes del sistema UISP CRM con los contactos de WhatsApp en CallBell, además procesa pagos de Stripe como las trasferencias bancarias y genera referencias de pago vía OXXO, además envía comprobantes de pago en formato imagen PNG o texto vía Whatsapp a los clientes",
|
||||||
|
"url": "https://siip.mx/",
|
||||||
|
"version": "5.0.0",
|
||||||
|
"unmsVersionCompliancy": {
|
||||||
|
"min": "2.1.0",
|
||||||
|
"max": null
|
||||||
|
},
|
||||||
|
"author": "SIIP INTERNET",
|
||||||
|
"changelog": [
|
||||||
|
{
|
||||||
|
"version": "4.5.0",
|
||||||
|
"date": "2026-03-13",
|
||||||
|
"changes": "Actualización: Implementado reenvío manual de notificaciones de instaladores, fix de lógica de asignación técnica y optimización de AWS SDK (Requiere PHP 8.1+)."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"version": "4.4.0",
|
||||||
|
"date": "2026-03-10",
|
||||||
|
"changes": "Nueva funcionalidad: Reenvío manual de notificaciones de tareas a instaladores desde el módulo Gestión de Instaladores. Tabla de jobs activos por técnico con botón de reenvío."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"version": "4.3.1",
|
||||||
|
"date": "2026-03-10",
|
||||||
|
"changes": "Hotfix: Corregido bug donde al cambiar de instalador en una tarea En Curso, el nuevo técnico recibía mensaje de desasignación en vez de asignación."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"version": "4.3.0",
|
||||||
|
"date": "2026-02-23",
|
||||||
|
"changes": "Implementación de Sistema de Login Seguro con validación de sesiones UCRM y Rediseño Premium Glassmorphism UI."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"version": "4.1.0",
|
||||||
|
"date": "2026-01-15",
|
||||||
|
"changes": "Implementación de Microservicio PDF, Integración MinIO, Fix Recorte y Soporte overlay textos."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"version": "4.0.0",
|
||||||
|
"date": "2026-01-10",
|
||||||
|
"changes": "Re-diseño completo de UI, integración total de Stripe (SPEI/OXXO), visualización inline de vouchers, optimización de seguridad FTP y modo oscuro."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"version": "3.1.0",
|
||||||
|
"date": "2026-01-07",
|
||||||
|
"changes": "Añadida funcionalidad de re-envío manual de notificaciones de pago."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"version": "3.0.0",
|
||||||
|
"date": "2026-01-02",
|
||||||
|
"changes": "Soporte multi-servicio y optimizaciones de rendimiento."
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"configuration": [
|
||||||
|
{
|
||||||
|
"key": "ipserver",
|
||||||
|
"label": "Dirección IP o dominio del servidor",
|
||||||
|
"description": "La dirección IP del servidor o dominio donde se ejecuta el sistema UISP CRM. Ejemplo: 192.168.1.120 o sistema.empresa.com",
|
||||||
|
"required": 1,
|
||||||
|
"type": "text"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"key": "apitoken",
|
||||||
|
"label": "Token de la API UCRM",
|
||||||
|
"description": "Token de autenticación necesario para el uso de la API del sistema UISP UCRM, se utiliza para gestionar cualquier información de los clientes. Contiene 64 caracteres y se genera desde el módulo de Ajustes del UISP CRM en la opción de 'Seguridad' y en la sección de 'Claves app'.",
|
||||||
|
"required": 1,
|
||||||
|
"type": "text"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"key": "unmsApiToken",
|
||||||
|
"label": "Token de la API UNMS",
|
||||||
|
"description": "Token de autenticación necesario para el uso de la API del sistema UISP UNMS, se utiliza para gestionar información de antenas u otros dispositivos de red. Contiene 34 caracteres y se genera desde el módulo de Ajustes del UISP Network en la opción de 'Usuarios' y en apartado de 'API tokens'.",
|
||||||
|
"required": 0,
|
||||||
|
"type": "text"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"key": "tokencallbell",
|
||||||
|
"label": "Token de la API de CallBell",
|
||||||
|
"description": "Token de autenticación para el uso de la API de CallBell que maneja las funciones realacionadas con WhatsApp. El token es de tipo 'Bearer' y contiene 97 caracteres, ejemplo: g9thcZkXGd3xBj2g2TtYNYFMH1fuesbJ.b6a947ea7d78cf6c8e42f067a21c8daf91e9fa2a9e310bfd0c7c7c4d7fa36f68",
|
||||||
|
"required": 1,
|
||||||
|
"type": "text"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"key": "tokenstripe",
|
||||||
|
"label": "Token de la API de Stripe",
|
||||||
|
"description": "Token de autenticación para el uso de la API de Stripe que maneja las funciones realacionadas con los pagos en línea (Transferencia y OXXO PAGO). ",
|
||||||
|
"required": 1,
|
||||||
|
"type": "text"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"key": "hostServerFTP",
|
||||||
|
"label": "IP o dominio del servidor FTP",
|
||||||
|
"description": "Dirección IP o dominio del sitio del servidor FTP para la carga de comprobantes de pago del sistema y su posterior envío",
|
||||||
|
"required": 1,
|
||||||
|
"type": "text"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"key": "usernameServerFTP",
|
||||||
|
"label": "Usuario FTP",
|
||||||
|
"description": "Nombre de usuario para inicio de sesión el servidor FTP, necesario para la carga de comprobantes de pago del sistema y su posterior envío",
|
||||||
|
"required": 1,
|
||||||
|
"type": "text"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"key": "passServerFTP",
|
||||||
|
"label": "Password FTP",
|
||||||
|
"description": "Contraseña para inicio de sesión en el servidor FTP, necesario para la carga de comprobantes de pago del sistema y su posterior envío",
|
||||||
|
"required": 1,
|
||||||
|
"type": "text"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"key": "ipPuppeteer",
|
||||||
|
"label": "Dirección IP del servicio de Puppeteer",
|
||||||
|
"description": "Dirección IP del contenedor docker que ejecuta el servicio de Puppeteer para la generación de comprobantes de pago en formato imagen PNG",
|
||||||
|
"required": 1,
|
||||||
|
"type": "text"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"key": "portPuppeteer",
|
||||||
|
"label": "Puerto del servicio de Puppeteer",
|
||||||
|
"description": "Puerto del contenedor docker que ejecuta el servicio de Puppeteer, por defecto es el 3000",
|
||||||
|
"required": 1,
|
||||||
|
"type": "text"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"key": "idPaymentAdminCRM",
|
||||||
|
"label": "ID del usuario para pagos en línea",
|
||||||
|
"description": "ID del usuario administrador del CRM asigando para realizar pagos en línea con Stripe. Todos los pagos que llegan desde Stripe se asignan a este usuario. Se recomienda crear un usuario exclusivo para este fin.",
|
||||||
|
"required": 1,
|
||||||
|
"type": "text"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"key": "cashPaymentMethodId",
|
||||||
|
"label": "Envío de Comprobante por pago en efectivo",
|
||||||
|
"description": "Habilita el envío de comprobantes en formato de imagen por WhatsApp cuando el método de pago es en efectivo",
|
||||||
|
"required": 0,
|
||||||
|
"type": "checkbox"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"key": "courtesyPaymentMethodId",
|
||||||
|
"label": "Envío de Comprobante por pago de cortesía",
|
||||||
|
"description": "Habilita el envío de comprobantes en formato de imagen por WhatsApp cuando el método de pago es por crédito de cortesía",
|
||||||
|
"required": 0,
|
||||||
|
"type": "checkbox"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"key": "bankTransferPaymentMethodId",
|
||||||
|
"label": "Envío de Comprobante por pago con Transferencia Bancaria",
|
||||||
|
"description": "Habilita el envío de comprobantes en formato de imagen por WhatsApp cuando el método de pago es por Transferencia Bancaria",
|
||||||
|
"required": 0,
|
||||||
|
"type": "checkbox"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"key": "paypalPaymentMethodId",
|
||||||
|
"label": "Envío de Comprobante por pago con PayPal",
|
||||||
|
"description": "Habilita el envío de comprobantes en formato de imagen por WhatsApp cuando el método de pago es por PayPal",
|
||||||
|
"required": 0,
|
||||||
|
"type": "checkbox"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"key": "creditCardPaypalPaymentMethodId",
|
||||||
|
"label": "Envío de Comprobante por pago de Tarjeta de Crédito o Débito con PayPal",
|
||||||
|
"description": "Habilita el envío de comprobantes en formato de imagen por WhatsApp cuando el método de pago es con Tarjeta de Credito o Débito con PayPal",
|
||||||
|
"required": 0,
|
||||||
|
"type": "checkbox"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"key": "creditCardStripePaymentMethodId",
|
||||||
|
"label": "Envío de Comprobante por pago de Tarjeta de Crédito o Débito por medio de Stripe",
|
||||||
|
"description": "Habilita el envío de comprobantes en formato de imagen por WhatsApp cuando el método de pago es con Tarjeta de Crédito o Débito por medio de Stripe",
|
||||||
|
"required": 0,
|
||||||
|
"type": "checkbox"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"key": "stripeSubscriptionCreditCardPaymentMethodId",
|
||||||
|
"label": "Envío de Comprobante por pago con una Suscripción de Stripe (tarjeta de crédito)",
|
||||||
|
"description": "Habilita el envío de comprobantes en formato de imagen por WhatsApp cuando el método de pago es por una Suscripción de Stripe (tarjeta de crédito)",
|
||||||
|
"required": 0,
|
||||||
|
"type": "checkbox"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"key": "paypalSubscriptionPaymentMethodId",
|
||||||
|
"label": "Envío de Comprobante por pago con Suscripción de PayPal",
|
||||||
|
"description": "Habilita el envío de comprobantes en formato de imagen por WhatsApp cuando el método de pago es por Suscripción de PayPal",
|
||||||
|
"required": 0,
|
||||||
|
"type": "checkbox"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"key": "mercadopagoPaymentMethodId",
|
||||||
|
"label": "Envío de Comprobante por pago de MercadoPago",
|
||||||
|
"description": "Habilita el envío de comprobantes en formato de imagen por WhatsApp cuando el método de pago es por MercadoPago",
|
||||||
|
"required": 0,
|
||||||
|
"type": "checkbox"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"key": "checkPaymentMethodId",
|
||||||
|
"label": "Envío de Comprobante por pago de Cheque",
|
||||||
|
"description": "Habilita el envío de comprobantes en formato de imagen por WhatsApp cuando el método de pago es por Cheque",
|
||||||
|
"required": 0,
|
||||||
|
"type": "checkbox"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"key": "customPaymentMethodId",
|
||||||
|
"label": "Envío de Comprobante por pago Personalizado",
|
||||||
|
"description": "Habilita el envío de comprobantes en formato de imagen por WhatsApp cuando el método de pago es Personalizado",
|
||||||
|
"required": 0,
|
||||||
|
"type": "checkbox"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"key": "oxxoPayPaymentMethodId",
|
||||||
|
"label": "Envío de Comprobante por pago de OXXO Pay",
|
||||||
|
"description": "Habilita el envío de comprobantes en formato de imagen por WhatsApp cuando el método de pago es OXXO Pay",
|
||||||
|
"required": 0,
|
||||||
|
"type": "checkbox"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"key": "creditDebitCardPaymentMethodId",
|
||||||
|
"label": "Envío de Comprobante por pago de Tarjeta de Crédito/Débito (Genérico)",
|
||||||
|
"description": "Habilita el envío de comprobantes en formato de imagen por WhatsApp cuando el método de pago es Tarjeta de Crédito/Débito (Genérico)",
|
||||||
|
"required": 0,
|
||||||
|
"type": "checkbox"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"key": "notificationTypeText",
|
||||||
|
"label": "Envío de Comprobante por medio de plantilla de texto",
|
||||||
|
"description": "Habilita el envío de comprobantes en formato TEXTO por WhatsApp, si está deshabilitado se utiliza el formato de imagen.",
|
||||||
|
"required": 0,
|
||||||
|
"type": "checkbox"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"key": "installersDataWhatsApp",
|
||||||
|
"label": "Datos de los instaladores en formato JSON",
|
||||||
|
"description": "El número identificador (id) del instalador se obtiene desde la API del CRM en su ednpoint \"/api/v1.0/user\" y se debe de obtener el campo 'id' de cada instalador para agregarlo en este campo en formato JSON",
|
||||||
|
"required": 1,
|
||||||
|
"type": "textarea"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"key": "debugMode",
|
||||||
|
"label": "Debug Mode?",
|
||||||
|
"description": "More detailed log info - Informacion de log mas detallada",
|
||||||
|
"required": 0,
|
||||||
|
"type": "checkbox"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"key": "minioEndpoint",
|
||||||
|
"label": "MinIO Endpoint (Internal)",
|
||||||
|
"description": "URL interna para subir archivos (ej: http://localhost:9002)",
|
||||||
|
"required": 1,
|
||||||
|
"type": "text"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"key": "minioPublicUrl",
|
||||||
|
"label": "MinIO Public URL (External)",
|
||||||
|
"description": "URL externa para generar enlaces públicos (ej: https://aws-venus.siip.mx)",
|
||||||
|
"required": 1,
|
||||||
|
"type": "text"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"key": "minioAccessKey",
|
||||||
|
"label": "MinIO Access Key",
|
||||||
|
"description": "Clave de acceso de MinIO",
|
||||||
|
"required": 1,
|
||||||
|
"type": "text"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"key": "minioSecretKey",
|
||||||
|
"label": "MinIO Secret Key",
|
||||||
|
"description": "Clave secreta de MinIO",
|
||||||
|
"required": 1,
|
||||||
|
"type": "text"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"key": "minioBucket",
|
||||||
|
"label": "MinIO Bucket",
|
||||||
|
"description": "Nombre del bucket (ej: vouchers-oxxo)",
|
||||||
|
"required": 1,
|
||||||
|
"type": "text"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"key": "logging_level",
|
||||||
|
"label": "enable debug logs",
|
||||||
|
"description": "Make the plugin more verbose.",
|
||||||
|
"type": "checkbox",
|
||||||
|
"required": 0
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"menu": [
|
||||||
|
{
|
||||||
|
"key": "Reports",
|
||||||
|
"label": "Portal Administrativo de Pagos de STRIPE y Notificaciones WhatsApp",
|
||||||
|
"type": "admin",
|
||||||
|
"target": "iframe"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"supportsWebhookEvents": true
|
||||||
|
}
|
||||||
3307
siip_full_structure/public.php
Normal file
3307
siip_full_structure/public.php
Normal file
File diff suppressed because it is too large
Load Diff
531
siip_full_structure/src/Facade/AbstractMessageNotifierFacade.php
Normal file
531
siip_full_structure/src/Facade/AbstractMessageNotifierFacade.php
Normal file
@ -0,0 +1,531 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace SmsNotifier\Facade;
|
||||||
|
|
||||||
|
use GuzzleHttp\Client;
|
||||||
|
use SmsNotifier\Data\NotificationData;
|
||||||
|
use SmsNotifier\Facade\ClientCallBellAPI;
|
||||||
|
use SmsNotifier\Factory\MessageTextFactory;
|
||||||
|
use SmsNotifier\Service\Logger;
|
||||||
|
use SmsNotifier\Service\SmsNumberProvider;
|
||||||
|
use Ubnt\UcrmPluginSdk\Service\UcrmApi;
|
||||||
|
use Ubnt\UcrmPluginSdk\Service\PluginConfigManager;
|
||||||
|
use \DateTime;
|
||||||
|
|
||||||
|
abstract class AbstractMessageNotifierFacade
|
||||||
|
{
|
||||||
|
protected $logger;
|
||||||
|
protected $messageTextFactory;
|
||||||
|
protected $clientPhoneNumber;
|
||||||
|
protected $ucrmApi;
|
||||||
|
|
||||||
|
const SUBJECT_OF_INSTALLER_CHANGE = ["se ha cancelado una tarea que tenías asignada con el folio ", "se te ha desasignado❌ la tarea con el folio "];
|
||||||
|
const ADDITIONAL_CHANGE_DATA = ["Ya no es necesario realizar la visita técnica.", "En tu lugar asistirá el técnico 👷🏻♂️➡️ "];
|
||||||
|
|
||||||
|
public function __construct(Logger $logger, MessageTextFactory $messageTextFactory, SmsNumberProvider $clientPhoneNumber)
|
||||||
|
{
|
||||||
|
$this->logger = $logger;
|
||||||
|
$this->messageTextFactory = $messageTextFactory;
|
||||||
|
$this->clientPhoneNumber = $clientPhoneNumber;
|
||||||
|
|
||||||
|
$config = PluginConfigManager::create()->loadConfig();
|
||||||
|
$ipServer = $config['ipserver'] ?? 'localhost';
|
||||||
|
$apiUrl = "https://$ipServer/crm/api/v1.0/";
|
||||||
|
|
||||||
|
$client = new Client([
|
||||||
|
'base_uri' => $apiUrl,
|
||||||
|
'verify' => false,
|
||||||
|
]);
|
||||||
|
|
||||||
|
$this->ucrmApi = new UcrmApi($client, $config['apitoken'] ?? '');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function verifyPaymentActionToDo(NotificationData $notificationData): void
|
||||||
|
{
|
||||||
|
$arrayPhones = $this->clientPhoneNumber->getUcrmClientNumbers($notificationData, null);
|
||||||
|
foreach ($arrayPhones as $type => $phones) {
|
||||||
|
$type = trim(strtolower($type));
|
||||||
|
if (!is_array($phones)) continue;
|
||||||
|
foreach ($phones as $phone) {
|
||||||
|
switch ($type) {
|
||||||
|
case 'whatsapp':
|
||||||
|
$this->notifyAndUpdate($notificationData, $phone);
|
||||||
|
break;
|
||||||
|
case 'whatsnotifica':
|
||||||
|
$this->notify($notificationData, $phone);
|
||||||
|
break;
|
||||||
|
case 'whatsactualiza':
|
||||||
|
$this->onlyUpdate($notificationData, $phone);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public function verifyClientActionToDo(NotificationData $notificationData): void
|
||||||
|
{
|
||||||
|
$arrayPhones = $this->clientPhoneNumber->getUcrmClientNumbers($notificationData, null);
|
||||||
|
foreach ($arrayPhones as $type => $phones) {
|
||||||
|
$type = trim(strtolower($type));
|
||||||
|
if (!is_array($phones)) continue;
|
||||||
|
foreach ($phones as $phone) {
|
||||||
|
if ($type === 'whatsapp' || $type === 'whatsactualiza') $this->onlyUpdate($notificationData, $phone);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public function verifyServiceActionToDo(NotificationData $notificationData): void
|
||||||
|
{
|
||||||
|
$arrayPhones = $this->clientPhoneNumber->getUcrmClientNumbers($notificationData, null);
|
||||||
|
foreach ($arrayPhones as $type => $phones) {
|
||||||
|
$type = trim(strtolower($type));
|
||||||
|
if (!is_array($phones)) continue;
|
||||||
|
foreach ($phones as $phone) {
|
||||||
|
if ($type === 'whatsapp' || $type === 'whatsactualiza') $this->onlyUpdateService($notificationData, $phone);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public function verifyJobActionToDo($jsonNotificationData, $reprogramming = null, $changeInstaller = null): void
|
||||||
|
{
|
||||||
|
$this->logger->info('Iniciando verifyJobActionToDo');
|
||||||
|
$clientId = $jsonNotificationData['extraData']['entity']['clientId'];
|
||||||
|
$installerId = $jsonNotificationData['extraData']['entity']['assignedUserId'];
|
||||||
|
$jobId = $jsonNotificationData['entityId'];
|
||||||
|
|
||||||
|
$dateString = $jsonNotificationData['extraData']['entity']['date'] ?? null;
|
||||||
|
$formattedDate = $dateString ? sprintf("*%s*", (new DateTime($dateString))->format('d/m/Y')) : '';
|
||||||
|
|
||||||
|
$config = PluginConfigManager::create()->loadConfig();
|
||||||
|
$admin = $this->ucrmApi->get("users/admins/$installerId", []);
|
||||||
|
$installerName = trim(($admin['firstName'] ?? '') . ' ' . ($admin['lastName'] ?? ''));
|
||||||
|
|
||||||
|
$installerWhatsApp = '';
|
||||||
|
$installers = json_decode($config['installersDataWhatsApp'] ?? '{"instaladores":[]}', true);
|
||||||
|
foreach ($installers['instaladores'] as $inst) {
|
||||||
|
if ($inst['id'] == $installerId) {
|
||||||
|
$installerWhatsApp = $inst['whatsapp'];
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (empty($installerWhatsApp)) $this->logger->warning("No se encontró número de WhatsApp para el instalador ID: $installerId");
|
||||||
|
|
||||||
|
$clientCRM = $this->ucrmApi->get("clients/$clientId", []);
|
||||||
|
$clientName = trim(($clientCRM['firstName'] ?? '') . ' ' . ($clientCRM['lastName'] ?? ''));
|
||||||
|
$passCRM = '';
|
||||||
|
foreach ($clientCRM['attributes'] as $attr) {
|
||||||
|
if ($attr['key'] === 'passwordAntenaCliente') {
|
||||||
|
$passCRM = $attr['value'];
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$allPhones = $this->clientPhoneNumber->getAllUcrmClientNumbers($clientCRM);
|
||||||
|
$phonesStr = implode(', ', array_map(fn($n) => $this->validarNumeroTelefono($n), $allPhones));
|
||||||
|
|
||||||
|
$api = new ClientCallBellAPI($config['apitoken'], $config['ipserver'], $config['tokencallbell']);
|
||||||
|
|
||||||
|
$title = $jsonNotificationData['extraData']['entity']['title'] ?? '';
|
||||||
|
$isPending = (stripos($title, '[NOTIFICACION-PENDIENTE]') !== false);
|
||||||
|
$isNoWhatsApp = (stripos($title, '[CLIENTE-SIN-WHATSAPP]') !== false);
|
||||||
|
|
||||||
|
$reprogramming = filter_var($reprogramming, FILTER_VALIDATE_BOOLEAN);
|
||||||
|
$changeInstaller = filter_var($changeInstaller, FILTER_VALIDATE_BOOLEAN);
|
||||||
|
|
||||||
|
$clientPhones = $this->clientPhoneNumber->getUcrmClientNumbers(null, $clientCRM);
|
||||||
|
$hasClientWhatsApp = false;
|
||||||
|
foreach ($clientPhones as $type => $phones) {
|
||||||
|
$type = trim(strtolower($type));
|
||||||
|
if (($type === 'whatsapp' || $type === 'whatsnotifica') && !empty($phones)) {
|
||||||
|
$hasClientWhatsApp = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$shouldNotifyTech = ($isPending || $reprogramming || $changeInstaller);
|
||||||
|
$shouldNotifyClient = ($isPending || $isNoWhatsApp || $reprogramming || $changeInstaller);
|
||||||
|
|
||||||
|
// 1. Notificar al Instalador Anterior (Desasignación)
|
||||||
|
if ($changeInstaller) {
|
||||||
|
$prevId = $jsonNotificationData['extraData']['entityBeforeEdit']['assignedUserId'];
|
||||||
|
$prevAdmin = $this->ucrmApi->get("users/admins/$prevId", []);
|
||||||
|
$prevWhatsApp = '';
|
||||||
|
foreach ($installers['instaladores'] as $inst) {
|
||||||
|
if ($inst['id'] == $prevId) {
|
||||||
|
$prevWhatsApp = $inst['whatsapp'];
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if ($prevWhatsApp) {
|
||||||
|
$api->sendJobNotificationWhatsAppToInstaller($this->validarNumeroTelefono($prevWhatsApp), [
|
||||||
|
"installerName" => "👷🏻♂️" . trim(($prevAdmin['firstName'] ?? '') . ' ' . ($prevAdmin['lastName'] ?? '')),
|
||||||
|
"subjectOfChange" => self::SUBJECT_OF_INSTALLER_CHANGE[1],
|
||||||
|
"jobId" => $jobId,
|
||||||
|
"clientFullName" => "[$clientId] $clientName",
|
||||||
|
"additionalChangeData" => self::ADDITIONAL_CHANGE_DATA[1] . ' *' . $installerName . '*',
|
||||||
|
], $reprogramming, $changeInstaller);
|
||||||
|
sleep(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. Notificar al Cliente (si aplica)
|
||||||
|
$clientNotified = false;
|
||||||
|
if ($shouldNotifyClient && $hasClientWhatsApp) {
|
||||||
|
foreach ($clientPhones as $type => $phones) {
|
||||||
|
$type = trim(strtolower($type));
|
||||||
|
if (!is_array($phones) || ($type !== 'whatsapp' && $type !== 'whatsnotifica')) continue;
|
||||||
|
foreach ($phones as $phone) {
|
||||||
|
if ($api->sendJobNotificationWhatsAppToClient($this->validarNumeroTelefono($phone), ["clientFullName" => $clientName, "jobId" => $jobId, "date" => $formattedDate, "installerName" => $installerName], $reprogramming, $changeInstaller)) {
|
||||||
|
$clientNotified = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. Notificar al Técnico (si aplica)
|
||||||
|
if ($shouldNotifyTech) {
|
||||||
|
$passVault = $this->getVaultCredentialsByClientId($clientId);
|
||||||
|
$api->sendJobNotificationWhatsAppToInstaller($this->validarNumeroTelefono($installerWhatsApp), [
|
||||||
|
"installerName" => $installerName,
|
||||||
|
"clientFullName" => "$clientName [ID:$clientId]",
|
||||||
|
"jobId" => $jobId,
|
||||||
|
"clientAddress" => $clientCRM['fullAddress'] ?? 'N/A',
|
||||||
|
"clientWhatsApp" => !empty($phonesStr) ? $phonesStr : 'Sin WhatsApp',
|
||||||
|
"date" => $formattedDate,
|
||||||
|
"jobDescription" => $jsonNotificationData['extraData']['entity']['description'] ?? 'S/D',
|
||||||
|
"gmapsLocation" => ($clientCRM['addressGpsLat'] && $clientCRM['addressGpsLon']) ? "https://www.google.com/maps?q={$clientCRM['addressGpsLat']},{$clientCRM['addressGpsLon']}" : 'N/A',
|
||||||
|
"passwordAntenaCliente" => $this->comparePasswords($passCRM, $passVault)
|
||||||
|
], $reprogramming, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3. Gestión del Título / Prefijos
|
||||||
|
if ($isPending) {
|
||||||
|
if ($clientNotified || $reprogramming || $changeInstaller) {
|
||||||
|
$newTitle = str_ireplace('[NOTIFICACION-PENDIENTE]', '', $title);
|
||||||
|
$this->ucrmApi->patch("scheduling/jobs/$jobId", ['title' => trim($newTitle)]);
|
||||||
|
} else if (!$hasClientWhatsApp) {
|
||||||
|
$newTitle = str_ireplace('[NOTIFICACION-PENDIENTE]', '[CLIENTE-SIN-WHATSAPP]', $title);
|
||||||
|
$this->ucrmApi->patch("scheduling/jobs/$jobId", ['title' => trim($newTitle)]);
|
||||||
|
}
|
||||||
|
} else if ($isNoWhatsApp && ($clientNotified || $reprogramming || $changeInstaller)) {
|
||||||
|
$newTitle = str_ireplace('[CLIENTE-SIN-WHATSAPP]', '', $title);
|
||||||
|
$this->ucrmApi->patch("scheduling/jobs/$jobId", ['title' => trim($newTitle)]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public function verifyInvoiceActionToDo(NotificationData $notificationData): void
|
||||||
|
{
|
||||||
|
$arrayPhones = $this->clientPhoneNumber->getUcrmClientNumbers($notificationData, null);
|
||||||
|
foreach ($arrayPhones as $type => $phones) {
|
||||||
|
$type = trim(strtolower($type));
|
||||||
|
if (!is_array($phones)) continue;
|
||||||
|
foreach ($phones as $phone) {
|
||||||
|
if ($type === 'whatsapp' || $type === 'whatsactualiza') $this->onlyUpdate($notificationData, $phone, false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public function notify(NotificationData $notificationData, $phoneToNotify = null): void
|
||||||
|
{
|
||||||
|
$config = PluginConfigManager::create()->loadConfig();
|
||||||
|
$api = new ClientCallBellAPI($config['apitoken'], $config['ipserver'], $config['tokencallbell']);
|
||||||
|
$phone = $this->validarNumeroTelefono($phoneToNotify);
|
||||||
|
if (!$phone) return;
|
||||||
|
if ($config['notificationTypeText'] ?? false) $api->sendTextPaymentNotificationWhatsApp($phone, $notificationData);
|
||||||
|
else $api->sendPaymentNotificationWhatsApp($phone, $notificationData);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function notifyAndUpdate(NotificationData $notificationData, $phoneToNotifyAndUpdate = null): void
|
||||||
|
{
|
||||||
|
$config = PluginConfigManager::create()->loadConfig();
|
||||||
|
$api = new ClientCallBellAPI($config['apitoken'], $config['ipserver'], $config['tokencallbell']);
|
||||||
|
$phone = $this->validarNumeroTelefono($phoneToNotifyAndUpdate);
|
||||||
|
if (!$phone) return;
|
||||||
|
if ($config['notificationTypeText'] ?? false) {
|
||||||
|
if ($api->sendTextPaymentNotificationWhatsApp($phone, $notificationData)) {
|
||||||
|
$contact = json_decode($api->getContactWhatsapp($phone), true);
|
||||||
|
if ($contact) $api->patchWhatsapp($contact, $notificationData);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if ($api->sendPaymentNotificationWhatsApp($phone, $notificationData)) {
|
||||||
|
$contact = json_decode($api->getContactWhatsapp($phone), true);
|
||||||
|
if ($contact) $api->patchWhatsapp($contact, $notificationData);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public function notifyOverDue(NotificationData $notificationData): void
|
||||||
|
{
|
||||||
|
$config = PluginConfigManager::create()->loadConfig();
|
||||||
|
$api = new ClientCallBellAPI($config['apitoken'], $config['ipserver'], $config['tokencallbell']);
|
||||||
|
$phone = $this->clientPhoneNumber->getUcrmClientNumber($notificationData);
|
||||||
|
if ($phone) $api->sendOverdueNotificationWhatsApp($phone, $notificationData);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function onlyUpdate(NotificationData $notificationData, $phoneToUpdate): void
|
||||||
|
{
|
||||||
|
$this->logger->debug("onlyUpdate: Iniciando actualización para teléfono: $phoneToUpdate");
|
||||||
|
$config = PluginConfigManager::create()->loadConfig();
|
||||||
|
$api = new ClientCallBellAPI($config['apitoken'], $config['ipserver'], $config['tokencallbell']);
|
||||||
|
$phone = $this->validarNumeroTelefono($phoneToUpdate);
|
||||||
|
$this->logger->debug("onlyUpdate: Teléfono validado: $phone");
|
||||||
|
$contact = json_decode($api->getContactWhatsapp($phone), true);
|
||||||
|
$this->logger->debug("onlyUpdate: Contacto obtenido de CallBell: " . json_encode($contact));
|
||||||
|
if ($contact) {
|
||||||
|
$this->logger->info("onlyUpdate: Ejecutando patchWhatsapp para teléfono: $phone");
|
||||||
|
$api->patchWhatsapp($contact, $notificationData);
|
||||||
|
} else {
|
||||||
|
$this->logger->warning("onlyUpdate: No se encontró contacto en CallBell para teléfono: $phone");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public function onlyUpdateService(NotificationData $notificationData, $phoneToUpdate): void
|
||||||
|
{
|
||||||
|
$config = PluginConfigManager::create()->loadConfig();
|
||||||
|
$api = new ClientCallBellAPI($config['apitoken'], $config['ipserver'], $config['tokencallbell']);
|
||||||
|
$phone = $this->validarNumeroTelefono($phoneToUpdate);
|
||||||
|
$contact = json_decode($api->getContactWhatsapp($phone), true);
|
||||||
|
if ($contact) $api->patchServiceStatusWhatsApp($contact, $notificationData);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function getVaultCredentialsByClientId($clientId): string
|
||||||
|
{
|
||||||
|
$config = PluginConfigManager::create()->loadConfig();
|
||||||
|
$ipServer = $config['ipserver'] ?? '';
|
||||||
|
$crm = new Client(['base_uri' => "https://{$ipServer}/crm/api/v1.0/", 'verify' => false]);
|
||||||
|
|
||||||
|
try {
|
||||||
|
// OPT: Lazy Check - Si ya tiene pass válido en CRM, no hace falta procesar nada
|
||||||
|
$respClient = $crm->get("clients/$clientId", ['headers' => ['X-Auth-Token' => $config['apitoken']]]);
|
||||||
|
$clientData = json_decode($respClient->getBody()->getContents(), true);
|
||||||
|
$passCRM = '';
|
||||||
|
if (isset($clientData['attributes'])) {
|
||||||
|
foreach ($clientData['attributes'] as $attr) {
|
||||||
|
if ($attr['key'] === 'passwordAntenaCliente') {
|
||||||
|
$passCRM = $attr['value'] ?? '';
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Si el campo no está vacío y no tiene advertencias, usamos el actual para ahorrar recursos
|
||||||
|
if (!empty($passCRM) && strpos($passCRM, '⚠️') === false) {
|
||||||
|
return $passCRM;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 1. Obtener los servicios del cliente
|
||||||
|
$respSvc = $crm->get('clients/services?clientId=' . $clientId, [
|
||||||
|
'headers' => ['X-Auth-Token' => $config['apitoken']]
|
||||||
|
]);
|
||||||
|
$svcs = json_decode($respSvc->getBody()->getContents(), true);
|
||||||
|
|
||||||
|
if (empty($svcs)) {
|
||||||
|
$msg = '⚠️ Cliente sin servicios/antenas';
|
||||||
|
$this->syncPasswordWithCrm((int)$clientId, $msg);
|
||||||
|
return $msg;
|
||||||
|
}
|
||||||
|
|
||||||
|
$unms = new Client(['base_uri' => "https://{$ipServer}/nms/api/v2.1/", 'verify' => false]);
|
||||||
|
$allServicePasswords = [];
|
||||||
|
$isTestEnv = ($ipServer === '172.16.5.134' || $ipServer === 'pruebas.internet.mx' || $ipServer === 'venus.siip.mx');
|
||||||
|
|
||||||
|
$numServices = count($svcs);
|
||||||
|
foreach ($svcs as $index => $svc) {
|
||||||
|
$label = ($numServices > 1) ? "Servicio " . ($index + 1) . ":" : "";
|
||||||
|
$siteId = $svc['unmsClientSiteId'] ?? null;
|
||||||
|
$passwordValue = "";
|
||||||
|
|
||||||
|
if (!$siteId) {
|
||||||
|
$passwordValue = "⚠️ Sin sitio";
|
||||||
|
} else {
|
||||||
|
if ($isTestEnv) {
|
||||||
|
// 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 {
|
||||||
|
// Lógica de producción
|
||||||
|
try {
|
||||||
|
$respDev = $unms->get("devices?siteId=$siteId", [
|
||||||
|
'headers' => ['X-Auth-Token' => $config['unmsApiToken']]
|
||||||
|
]);
|
||||||
|
$devs = json_decode($respDev->getBody()->getContents(), true);
|
||||||
|
|
||||||
|
if (empty($devs)) {
|
||||||
|
$passwordValue = "⚠️ Sin antena";
|
||||||
|
} else {
|
||||||
|
$passVault = null;
|
||||||
|
$firstDeviceId = null;
|
||||||
|
foreach ($devs as $dev) {
|
||||||
|
$deviceId = $dev['identification']['id'] ?? null;
|
||||||
|
if (!$deviceId) continue;
|
||||||
|
if (!$firstDeviceId) $firstDeviceId = $deviceId;
|
||||||
|
try {
|
||||||
|
$respVault = $unms->get("vault/$deviceId/credentials", [
|
||||||
|
'headers' => ['X-Auth-Token' => $config['unmsApiToken']]
|
||||||
|
]);
|
||||||
|
$vault = json_decode($respVault->getBody()->getContents(), true);
|
||||||
|
if (isset($vault['credentials'][0]['password'])) {
|
||||||
|
$passVault = $vault['credentials'][0]['password'];
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
} catch (\Exception $e) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($passVault) {
|
||||||
|
$passwordValue = $passVault;
|
||||||
|
} else if ($firstDeviceId) {
|
||||||
|
// Regenerar
|
||||||
|
$newPass = $this->generateStrongPassword(16);
|
||||||
|
try {
|
||||||
|
$unms->post("vault/$firstDeviceId/credentials/regenerate", [
|
||||||
|
'headers' => ['X-Auth-Token' => $config['unmsApiToken']],
|
||||||
|
'json' => [['username' => 'ubnt', 'password' => $newPass, 'readOnly' => true]]
|
||||||
|
]);
|
||||||
|
$passwordValue = $newPass;
|
||||||
|
} catch (\Exception $e) {
|
||||||
|
$passwordValue = $newPass;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
$passwordValue = "⚠️ Sin antena";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (\Exception $e) {
|
||||||
|
$passwordValue = "⚠️ Error API";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
$allServicePasswords[] = trim("$label $passwordValue");
|
||||||
|
}
|
||||||
|
|
||||||
|
$finalValue = implode(' ', $allServicePasswords);
|
||||||
|
|
||||||
|
// Evitar sincronización redundante
|
||||||
|
if ($finalValue === $passCRM) {
|
||||||
|
return $finalValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->syncPasswordWithCrm((int)$clientId, $finalValue);
|
||||||
|
return $finalValue;
|
||||||
|
} catch (\Exception $e) {
|
||||||
|
$this->logger->error("Error en getVaultCredentialsByClientId: " . $e->getMessage());
|
||||||
|
return 'Error: ' . $e->getMessage();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private function syncPasswordWithCrm(int $clientId, string $passVault): void
|
||||||
|
{
|
||||||
|
$config = PluginConfigManager::create()->loadConfig();
|
||||||
|
$crm = new Client(['base_uri' => "https://{$config['ipserver']}/crm/api/v1.0/", 'verify' => false]);
|
||||||
|
try {
|
||||||
|
$respClient = $crm->get("clients/$clientId", [
|
||||||
|
'headers' => ['X-Auth-Token' => $config['apitoken']]
|
||||||
|
]);
|
||||||
|
$clientData = json_decode($respClient->getBody()->getContents(), true);
|
||||||
|
$passCRM = '';
|
||||||
|
$attributeId = 17;
|
||||||
|
if (isset($clientData['attributes'])) {
|
||||||
|
foreach ($clientData['attributes'] as $attr) {
|
||||||
|
if ($attr['key'] === 'passwordAntenaCliente') {
|
||||||
|
$passCRM = $attr['value'] ?? '';
|
||||||
|
$attributeId = $attr['customAttributeId'];
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (empty($passCRM) || $passCRM !== $passVault) {
|
||||||
|
$this->logger->info("Sincronizando pass CRM cliente $clientId.");
|
||||||
|
$this->patchClientCustomAttribute($clientId, (int)$attributeId, $passVault);
|
||||||
|
}
|
||||||
|
} catch (\Exception $e) {
|
||||||
|
$this->logger->warning("Fallo sincronización pass CRM: " . $e->getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function generateStrongPassword(int $length = 16): string
|
||||||
|
{
|
||||||
|
$lower = 'abcdefghijkmnopqrstuvwxyz'; // Eliminamos 'l'
|
||||||
|
$upper = 'ABCDEFGHJKLMNPQRSTUVWXYZ'; // Eliminamos 'I', 'O'
|
||||||
|
$digits = '23456789'; // Eliminamos '1', '0'
|
||||||
|
$symbols = '@#'; // Solo símbolos amigables para impresoras térmicas
|
||||||
|
|
||||||
|
$all = $lower . $upper . $digits . $symbols;
|
||||||
|
$pwChars = [];
|
||||||
|
|
||||||
|
// Asegurar que tenga al menos uno de cada tipo si es posible
|
||||||
|
$pwChars[] = $lower[random_int(0, strlen($lower) - 1)];
|
||||||
|
$pwChars[] = $upper[random_int(0, strlen($upper) - 1)];
|
||||||
|
$pwChars[] = $digits[random_int(0, strlen($digits) - 1)];
|
||||||
|
$pwChars[] = $symbols[random_int(0, strlen($symbols) - 1)];
|
||||||
|
|
||||||
|
for ($i = count($pwChars); $i < $length; $i++) {
|
||||||
|
$pwChars[] = $all[random_int(0, strlen($all) - 1)];
|
||||||
|
}
|
||||||
|
|
||||||
|
// Mezclar Fisher-Yates
|
||||||
|
$n = count($pwChars);
|
||||||
|
for ($i = $n - 1; $i > 0; $i--) {
|
||||||
|
$j = random_int(0, $i);
|
||||||
|
$tmp = $pwChars[$i];
|
||||||
|
$pwChars[$i] = $pwChars[$j];
|
||||||
|
$pwChars[$j] = $tmp;
|
||||||
|
}
|
||||||
|
|
||||||
|
return implode('', $pwChars);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function patchClientCustomAttribute(int $clientId, int $attributeId, string $value): bool
|
||||||
|
{
|
||||||
|
$config = PluginConfigManager::create()->loadConfig();
|
||||||
|
$crm = new Client(['base_uri' => "https://{$config['ipserver']}/crm/api/v1.0/", 'verify' => false]);
|
||||||
|
try {
|
||||||
|
$crm->patch("clients/$clientId", [
|
||||||
|
'headers' => ['X-Auth-Token' => $config['apitoken']],
|
||||||
|
'json' => [
|
||||||
|
"attributes" => [['value' => $value, 'customAttributeId' => $attributeId]]
|
||||||
|
]
|
||||||
|
]);
|
||||||
|
return true;
|
||||||
|
} catch (\Exception $e) {
|
||||||
|
$this->logger->error("Error patching custom attribute for client $clientId: " . $e->getMessage());
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function comparePasswords(?string $crm, ?string $vault): string
|
||||||
|
{
|
||||||
|
if ($vault && strpos($vault, 'Error') !== 0) return $vault;
|
||||||
|
if ($crm && strpos($crm, 'Error') !== 0) return $crm;
|
||||||
|
return '⚠️ Probar pass conocida.';
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function validarNumeroTelefono($n): string
|
||||||
|
{
|
||||||
|
if (!$n) return '';
|
||||||
|
$n = preg_replace('/\D/', '', (string)$n);
|
||||||
|
return (strlen($n) === 10) ? '52' . $n : $n;
|
||||||
|
}
|
||||||
|
|
||||||
|
abstract protected function sendWhatsApp(NotificationData $notificationData, string $clientSmsNumber): void;
|
||||||
|
}
|
||||||
1460
siip_full_structure/src/Facade/ClientCallBellAPI.php
Normal file
1460
siip_full_structure/src/Facade/ClientCallBellAPI.php
Normal file
File diff suppressed because it is too large
Load Diff
@ -1001,24 +1001,32 @@ class ClientCallBellAPI
|
|||||||
//ejemplo de $notificationData: {"uuid":"cad4fa25-176e-4823-9f4c-1421d05d2a31","changeType":"edit","entity":"client","entityId":2,"message":null,"clientId":2,"eventName":"client.edit","clientData":{"id":2,"userIdent":null,"previousIsp":null,"isLead":false,"clientType":1,"companyName":null,"companyRegistrationNumber":null,"companyTaxId":null,"companyWebsite":null,"street1":"31 Chiapas","street2":null,"city":"Dolores Hidalgo Cuna de la Independencia Nacional","countryId":173,"stateId":null,"zipCode":"37800","fullAddress":"Chiapas 31, Centro, 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":"Cliente espacial, el m\u00e1s chido","sendInvoiceByPost":null,"invoiceMaturityDays":null,"stopServiceDue":null,"stopServiceDueDays":null,"organizationId":1,"tax1Id":null,"tax2Id":null,"tax3Id":null,"registrationDate":"2024-01-25T00:00:00-0600","leadConvertedAt":null,"companyContactFirstName":null,"companyContactLastName":null,"isActive":true,"firstName":"Daniel Humberto","lastName":"Soto Villegas Pollerias2","username":"danydhsv","contacts":[{"id":2,"clientId":2,"email":"dhsv.141089@gmail.com","phone":"5214181878106","name":"Personal","isBilling":false,"isContact":false,"types":[{"id":1000,"name":"WhatsApp"}]},{"id":170,"clientId":2,"email":null,"phone":"5214181817609","name":"Bussiness","isBilling":false,"isContact":false,"types":[]}],"attributes":[{"id":112,"clientId":2,"customAttributeId":10,"name":"Stripe Customer ID","key":"stripeCustomerId","value":"cus_PetN1dhr4rx0kX","clientZoneVisible":true},{"id":113,"clientId":2,"customAttributeId":11,"name":"Clabe Interbancaria","key":"clabeInterbancaria","value":"0021804341999569810","clientZoneVisible":true},{"id":178,"clientId":2,"customAttributeId":15,"name":"Site","key":"site","value":"0LOCS","clientZoneVisible":false},{"id":179,"clientId":2,"customAttributeId":16,"name":"Antena\/Sectorial","key":"antenaSectorial","value":"Sectorial-4b 172.16.13.16\/24","clientZoneVisible":false}],"accountBalance":1553.33,"accountCredit":1553.33,"accountOutstanding":0,"currencyCode":"MXN","organizationName":"SIIP Pruebas","bankAccounts":[],"tags":[{"id":2,"name":"NS EXENTO","colorBackground":"#42a3df","colorText":"#fff"}],"invitationEmailSentDate":null,"avatarColor":"#f1df43","addressGpsLat":21.1564209,"addressGpsLon":-100.9384185,"isArchived":false,"generateProformaInvoices":null,"usesProforma":false,"hasOverdueInvoice":false,"hasOutage":true,"hasSuspendedService":false,"hasServiceWithoutDevices":false,"referral":null,"hasPaymentSubscription":false,"hasAutopayCreditCard":false},"serviceData":null,"invoiceData":null,"paymentData":null}
|
//ejemplo de $notificationData: {"uuid":"cad4fa25-176e-4823-9f4c-1421d05d2a31","changeType":"edit","entity":"client","entityId":2,"message":null,"clientId":2,"eventName":"client.edit","clientData":{"id":2,"userIdent":null,"previousIsp":null,"isLead":false,"clientType":1,"companyName":null,"companyRegistrationNumber":null,"companyTaxId":null,"companyWebsite":null,"street1":"31 Chiapas","street2":null,"city":"Dolores Hidalgo Cuna de la Independencia Nacional","countryId":173,"stateId":null,"zipCode":"37800","fullAddress":"Chiapas 31, Centro, 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":"Cliente espacial, el m\u00e1s chido","sendInvoiceByPost":null,"invoiceMaturityDays":null,"stopServiceDue":null,"stopServiceDueDays":null,"organizationId":1,"tax1Id":null,"tax2Id":null,"tax3Id":null,"registrationDate":"2024-01-25T00:00:00-0600","leadConvertedAt":null,"companyContactFirstName":null,"companyContactLastName":null,"isActive":true,"firstName":"Daniel Humberto","lastName":"Soto Villegas Pollerias2","username":"danydhsv","contacts":[{"id":2,"clientId":2,"email":"dhsv.141089@gmail.com","phone":"5214181878106","name":"Personal","isBilling":false,"isContact":false,"types":[{"id":1000,"name":"WhatsApp"}]},{"id":170,"clientId":2,"email":null,"phone":"5214181817609","name":"Bussiness","isBilling":false,"isContact":false,"types":[]}],"attributes":[{"id":112,"clientId":2,"customAttributeId":10,"name":"Stripe Customer ID","key":"stripeCustomerId","value":"cus_PetN1dhr4rx0kX","clientZoneVisible":true},{"id":113,"clientId":2,"customAttributeId":11,"name":"Clabe Interbancaria","key":"clabeInterbancaria","value":"0021804341999569810","clientZoneVisible":true},{"id":178,"clientId":2,"customAttributeId":15,"name":"Site","key":"site","value":"0LOCS","clientZoneVisible":false},{"id":179,"clientId":2,"customAttributeId":16,"name":"Antena\/Sectorial","key":"antenaSectorial","value":"Sectorial-4b 172.16.13.16\/24","clientZoneVisible":false}],"accountBalance":1553.33,"accountCredit":1553.33,"accountOutstanding":0,"currencyCode":"MXN","organizationName":"SIIP Pruebas","bankAccounts":[],"tags":[{"id":2,"name":"NS EXENTO","colorBackground":"#42a3df","colorText":"#fff"}],"invitationEmailSentDate":null,"avatarColor":"#f1df43","addressGpsLat":21.1564209,"addressGpsLon":-100.9384185,"isArchived":false,"generateProformaInvoices":null,"usesProforma":false,"hasOverdueInvoice":false,"hasOutage":true,"hasSuspendedService":false,"hasServiceWithoutDevices":false,"referral":null,"hasPaymentSubscription":false,"hasAutopayCreditCard":false},"serviceData":null,"invoiceData":null,"paymentData":null}
|
||||||
|
|
||||||
//buscar el attribute con el key site y antenaSectorial
|
//buscar el attribute con el key site y antenaSectorial
|
||||||
$attributes = $notificationData->clientData['attributes']; //Obtener los atributos del cliente
|
$attributes = $notificationData->clientData['attributes'] ?? []; //Obtener los atributos del cliente
|
||||||
$site = '';
|
$site = '';
|
||||||
$antenaSectorial = '';
|
$antenaSectorial = '';
|
||||||
$passAntenaUCRM = '';
|
$passAntenaUCRM = '';
|
||||||
|
$clabeAttr = '';
|
||||||
|
|
||||||
// Iterar sobre los atributos
|
// Iterar sobre los atributos
|
||||||
foreach ($attributes as $attribute) {
|
foreach ($attributes as $attribute) {
|
||||||
// Verificar si el key es "site"
|
// Verificar si el key es "site"
|
||||||
if ($attribute['key'] === 'site') {
|
if ($attribute['key'] === 'site') {
|
||||||
$site = $attribute['value'];
|
$site = $attribute['value'] ?? '';
|
||||||
}
|
}
|
||||||
// Verificar si el key es "antenaSectorial"
|
// Verificar si el key es "antenaSectorial"
|
||||||
if ($attribute['key'] === 'antenaSectorial') {
|
if ($attribute['key'] === 'antenaSectorial') {
|
||||||
$antenaSectorial = $attribute['value'];
|
$antenaSectorial = $attribute['value'] ?? '';
|
||||||
}
|
}
|
||||||
if ($attribute['key'] === 'passwordAntenaCliente') {
|
if ($attribute['key'] === 'passwordAntenaCliente') {
|
||||||
$passAntenaUCRM = $attribute['value'] ?? '';
|
$passAntenaUCRM = $attribute['value'] ?? '';
|
||||||
}
|
}
|
||||||
|
if ($attribute['key'] === 'clabeInterbancaria') {
|
||||||
|
$clabeAttr = $attribute['value'] ?? '';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (empty($clabeInterbancaria)) {
|
||||||
|
$clabeInterbancaria = $clabeAttr;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Parsear contraseñas multi-servicio a JSON
|
// Parsear contraseñas multi-servicio a JSON
|
||||||
@ -1186,19 +1194,26 @@ class ClientCallBellAPI
|
|||||||
$log->appendLog("DEBUG COMPARACIÓN - CallBell Estado: '" . ($contactCustomFields['Estado'] ?? 'NULL') . "'" . PHP_EOL);
|
$log->appendLog("DEBUG COMPARACIÓN - CallBell Estado: '" . ($contactCustomFields['Estado'] ?? 'NULL') . "'" . PHP_EOL);
|
||||||
$log->appendLog("DEBUG COMPARACIÓN - UCRM Estado: '" . $data_CRM['custom_fields']['Estado'] . "'" . PHP_EOL);
|
$log->appendLog("DEBUG COMPARACIÓN - UCRM Estado: '" . $data_CRM['custom_fields']['Estado'] . "'" . PHP_EOL);
|
||||||
|
|
||||||
if (
|
$hasChanges = false;
|
||||||
($contactCustomFields['Cliente'] ?? '') != $data_CRM['custom_fields']['Cliente']
|
if (($contact['name'] ?? '') != $data_CRM['name']) {
|
||||||
|| ($contactCustomFields['Domicilio'] ?? '') != $data_CRM['custom_fields']['Domicilio']
|
$log->appendLog("DEBUG COMPARACIÓN - Diferencia en name. CallBell: '" . ($contact['name'] ?? 'NULL') . "', UCRM: '" . $data_CRM['name'] . "'" . PHP_EOL);
|
||||||
|| ($contactCustomFields['Nombre'] ?? '') != $data_CRM['custom_fields']['Nombre']
|
$hasChanges = true;
|
||||||
|| ($contactCustomFields['URL'] ?? '') != $data_CRM['custom_fields']['URL']
|
} else {
|
||||||
|| ($contactCustomFields['Saldo Actual'] ?? '') != $data_CRM['custom_fields']['Saldo Actual']
|
foreach ($data_CRM['custom_fields'] as $key => $value) {
|
||||||
|| ($contactCustomFields['Estado'] ?? '') != $data_CRM['custom_fields']['Estado']
|
if ($key === 'Fecha Ultima Actualizacion') {
|
||||||
|| ($contactCustomFields['Fecha Ultimo Pago'] ?? '') != $data_CRM['custom_fields']['Fecha Ultimo Pago']
|
continue; // Skip this as it always changes and shouldn't trigger a patch on its own
|
||||||
|| ($contactCustomFields['Monto Ultimo Pago'] ?? '') != $data_CRM['custom_fields']['Monto Ultimo Pago']
|
}
|
||||||
|| ($contactCustomFields['password-antena'] ?? '') != $data_CRM['custom_fields']['password-antena']
|
|
||||||
|| ($contact['name'] ?? '') != $data_CRM['name']
|
$callBellValue = $contactCustomFields[$key] ?? '';
|
||||||
|
if ((string)$callBellValue !== (string)$value) {
|
||||||
|
$log->appendLog("DEBUG COMPARACIÓN - Diferencia encontrada en $key. CallBell: '$callBellValue', UCRM: '$value'" . PHP_EOL);
|
||||||
|
$hasChanges = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
) {
|
if ($hasChanges) {
|
||||||
$log->appendLog("EJECUTANDO PATCH - Se detectaron cambios" . PHP_EOL);
|
$log->appendLog("EJECUTANDO PATCH - Se detectaron cambios" . PHP_EOL);
|
||||||
curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($data_CRM));
|
curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($data_CRM));
|
||||||
$response = curl_exec($ch);
|
$response = curl_exec($ch);
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user