antes del cambio de color de los botones
This commit is contained in:
parent
d0430dd891
commit
e113c750b9
11
CHANGELOG.md
11
CHANGELOG.md
@ -1,5 +1,16 @@
|
||||
# CHANGELOG - SIIP WhatsApp Notifications Plugin
|
||||
|
||||
## 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,12 +1,18 @@
|
||||
# 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.4.0 (Resend Job Notifications)
|
||||
|
||||
- **📋 Tabla de Tareas Activas por Instalador**: Nuevo módulo dentro de "Gestión de Instaladores" que muestra los jobs "En curso" de cada técnico con datos de cliente, fecha y descripción.
|
||||
- **📨 Reenvío Manual de Notificaciones**: Botón para reenviar la notificación WhatsApp de asignación de tarea a cualquier instalador desde la interfaz del plugin.
|
||||
- **🔗 Integración con API de Scheduling**: Consulta dinámica de `GET /scheduling/jobs?assignedUserId=X&statuses[]=1` para listar tareas activas en tiempo real.
|
||||
|
||||
## 🐛 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".
|
||||
|
||||
@ -1 +1,35 @@
|
||||
{"ipserver":"venus.siip.mx","apitoken":"gvcnIJqXdUjneVSjhl6THLlQcYXJyIFCcwHKVba2bvIrNraanCTb5VeoWuJ0TFZ9","unmsApiToken":"4f5219de-cc5b-413d-b2fb-5133d02f3b26","tokencallbell":"g8thcZkXGd3xBj2g3TtYNYFMH1fuesbJ.b6a940ea7d78cf6c9e42f067b21c8ddf96e9fa2a9e307bfd0c7c7c4d7fa38f79","tokenstripe":"sk_test_51OkG0REFY1WEUtgRH6UxBK5pu80Aq5Iy8EcdPnf0cOWzuVLQTpyLCd7CbPzqMsWMafZOHElCxhEHF7g8boURjWlJ00tBwE0W1M","hostServerFTP":"siip.mx","usernameServerFTP":"siip0001","passServerFTP":"$spGiT,[wa)n","ipPuppeteer":"172.16.5.134","portPuppeteer":"4100","idPaymentAdminCRM":"1180","cashPaymentMethodId":true,"courtesyPaymentMethodId":true,"bankTransferPaymentMethodId":true,"paypalPaymentMethodId":true,"creditCardPaypalPaymentMethodId":true,"creditCardStripePaymentMethodId":true,"stripeSubscriptionCreditCardPaymentMethodId":true,"paypalSubscriptionPaymentMethodId":true,"mercadopagoPaymentMethodId":true,"checkPaymentMethodId":true,"customPaymentMethodId":true,"oxxoPayPaymentMethodId":true,"creditDebitCardPaymentMethodId":true,"notificationTypeText":false,"installersDataWhatsApp":"{\"instaladores\":[{\"id\":1019,\"nombre\":\"Mucio Robledo\",\"whatsapp\":\"4181878106\"},{\"id\":1173,\"nombre\":\"Ángel Arvizu\",\"whatsapp\":\"4181878106\"},{\"id\":1172,\"nombre\":\"Juan Rostro\",\"whatsapp\":\"4181878106\"},{\"id\":1015,\"nombre\":\"Daniel Humberto\",\"whatsapp\":\"4181878106\"},{\"id\":1131,\"nombre\":\"Gricelda Avalos\",\"whatsapp\":\"4181817609\"}]}","debugMode":true,"minioEndpoint":"http://172.16.5.134:9002","minioPublicUrl":"https://aws-venus.siip.mx","minioAccessKey":"minioadmin","minioSecretKey":"minioadmin","minioBucket":"vouchers-oxxo","logging_level":true}
|
||||
{
|
||||
"ipserver": "venus.siip.mx",
|
||||
"apitoken": "gvcnIJqXdUjneVSjhl6THLlQcYXJyIFCcwHKVba2bvIrNraanCTb5VeoWuJ0TFZ9",
|
||||
"unmsApiToken": "4f5219de-cc5b-413d-b2fb-5133d02f3b26",
|
||||
"tokencallbell": "g8thcZkXGd3xBj2g3TtYNYFMH1fuesbJ.b6a940ea7d78cf6c9e42f067b21c8ddf96e9fa2a9e307bfd0c7c7c4d7fa38f79",
|
||||
"tokenstripe": "sk_test_51OkG0REFY1WEUtgRH6UxBK5pu80Aq5Iy8EcdPnf0cOWzuVLQTpyLCd7CbPzqMsWMafZOHElCxhEHF7g8boURjWlJ00tBwE0W1M",
|
||||
"hostServerFTP": "siip.mx",
|
||||
"usernameServerFTP": "siip0001",
|
||||
"passServerFTP": "$spGiT,[wa)n",
|
||||
"ipPuppeteer": "172.16.5.134",
|
||||
"portPuppeteer": "4100",
|
||||
"idPaymentAdminCRM": "1180",
|
||||
"cashPaymentMethodId": true,
|
||||
"courtesyPaymentMethodId": true,
|
||||
"bankTransferPaymentMethodId": true,
|
||||
"paypalPaymentMethodId": true,
|
||||
"creditCardPaypalPaymentMethodId": true,
|
||||
"creditCardStripePaymentMethodId": true,
|
||||
"stripeSubscriptionCreditCardPaymentMethodId": true,
|
||||
"paypalSubscriptionPaymentMethodId": true,
|
||||
"mercadopagoPaymentMethodId": true,
|
||||
"checkPaymentMethodId": true,
|
||||
"customPaymentMethodId": true,
|
||||
"oxxoPayPaymentMethodId": true,
|
||||
"creditDebitCardPaymentMethodId": true,
|
||||
"notificationTypeText": false,
|
||||
"installersDataWhatsApp": "{\"instaladores\":[{\"id\":1019,\"nombre\":\"Mucio Robledo\",\"whatsapp\":\"4181878106\"},{\"id\":1173,\"nombre\":\"Ángel Arvizu\",\"whatsapp\":\"4181878106\"},{\"id\":1172,\"nombre\":\"Juan Rostro\",\"whatsapp\":\"4181878106\"},{\"id\":1015,\"nombre\":\"Daniel Humberto\",\"whatsapp\":\"4181878106\"},{\"id\":1131,\"nombre\":\"Gricelda Avalos\",\"whatsapp\":\"4181817609\"},{\"id\":\"1184\",\"nombre\":\"José Luis Enrique Sánchez\",\"whatsapp\":\"4181878106\"}]}",
|
||||
"debugMode": true,
|
||||
"minioEndpoint": "http:\/\/172.16.5.134:9002",
|
||||
"minioPublicUrl": "https:\/\/aws-venus.siip.mx",
|
||||
"minioAccessKey": "minioadmin",
|
||||
"minioSecretKey": "minioadmin",
|
||||
"minioBucket": "vouchers-oxxo",
|
||||
"logging_level": true
|
||||
}
|
||||
16138
data/plugin.log
16138
data/plugin.log
File diff suppressed because one or more lines are too long
@ -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",
|
||||
"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": "4.3.1",
|
||||
"version": "4.4.0",
|
||||
"unmsVersionCompliancy": {
|
||||
"min": "2.1.0",
|
||||
"max": null
|
||||
},
|
||||
"author": "SIIP INTERNET",
|
||||
"changelog": [
|
||||
{
|
||||
"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",
|
||||
|
||||
232
public.php
232
public.php
@ -236,6 +236,60 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['action'])) {
|
||||
}
|
||||
exit;
|
||||
}
|
||||
|
||||
if ($_POST['action'] === 'resend_job_notification') {
|
||||
$jobId = $_POST['jobId'] ?? null;
|
||||
if (!$jobId) {
|
||||
echo json_encode(['success' => false, 'message' => 'jobId requerido']);
|
||||
exit;
|
||||
}
|
||||
try {
|
||||
$job = $ucrmApi->get("scheduling/jobs/$jobId");
|
||||
$client = $ucrmApi->get("clients/{$job['clientId']}");
|
||||
|
||||
// Simular webhook job.edit con activación (status 0→1)
|
||||
$entityBeforeEdit = $job;
|
||||
$entityBeforeEdit['status'] = 0; // Simular que estaba en "Abierto"
|
||||
$entity = $job;
|
||||
$entity['status'] = 1; // Estado actual: "En curso"
|
||||
// Agregar prefijo para que verifyJobActionToDo reconozca que debe notificar
|
||||
$entity['title'] = '[NOTIFICACION-PENDIENTE]' . ($entity['title'] ?? '');
|
||||
|
||||
$payload = [
|
||||
'uuid' => 'manual-trigger',
|
||||
'changeType' => 'update',
|
||||
'entity' => 'job',
|
||||
'entityId' => (int)$jobId,
|
||||
'eventName' => 'job.edit',
|
||||
'extraData' => [
|
||||
'entity' => $entity,
|
||||
'entityBeforeEdit' => $entityBeforeEdit
|
||||
]
|
||||
];
|
||||
|
||||
$protocol = (!empty($_SERVER['HTTPS']) && $_SERVER['HTTPS'] !== 'off' || $_SERVER['SERVER_PORT'] == 443) ? "https://" : "http://";
|
||||
$selfUrl = $protocol . $_SERVER['HTTP_HOST'] . $_SERVER['PHP_SELF'];
|
||||
|
||||
$ch = curl_init($selfUrl);
|
||||
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
|
||||
curl_setopt($ch, CURLOPT_POST, true);
|
||||
curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($payload));
|
||||
curl_setopt($ch, CURLOPT_HTTPHEADER, ['Content-Type: application/json']);
|
||||
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
|
||||
curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false);
|
||||
curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true);
|
||||
curl_setopt($ch, CURLOPT_POSTREDIR, 3);
|
||||
curl_setopt($ch, CURLOPT_TIMEOUT, 10);
|
||||
$res = curl_exec($ch);
|
||||
$err = curl_error($ch);
|
||||
curl_close($ch);
|
||||
|
||||
echo json_encode(['success' => true, 'message' => 'Notificación reenviada para job #' . $jobId]);
|
||||
} catch (\Exception $e) {
|
||||
echo json_encode(['success' => false, 'message' => $e->getMessage()]);
|
||||
}
|
||||
exit;
|
||||
}
|
||||
}
|
||||
|
||||
if (isset($_GET['action'])) {
|
||||
@ -268,6 +322,45 @@ if (isset($_GET['action'])) {
|
||||
exit;
|
||||
}
|
||||
|
||||
if ($_GET['action'] === 'get_installer_jobs') {
|
||||
$installerId = $_GET['installerId'] ?? null;
|
||||
if (!$installerId) {
|
||||
echo json_encode([]);
|
||||
exit;
|
||||
}
|
||||
try {
|
||||
$jobs = $ucrmApi->get('scheduling/jobs', [
|
||||
'assignedUserId' => $installerId,
|
||||
'statuses[]' => 1,
|
||||
'limit' => 50
|
||||
]);
|
||||
$result = [];
|
||||
foreach ($jobs as $job) {
|
||||
$clientName = 'Sin cliente';
|
||||
if (!empty($job['clientId'])) {
|
||||
try {
|
||||
$client = $ucrmApi->get("clients/{$job['clientId']}");
|
||||
$clientName = trim(($client['firstName'] ?? '') . ' ' . ($client['lastName'] ?? ''));
|
||||
} catch (\Exception $e) {
|
||||
$clientName = 'Cliente #' . $job['clientId'];
|
||||
}
|
||||
}
|
||||
$result[] = [
|
||||
'id' => $job['id'],
|
||||
'title' => $job['title'] ?? 'Sin título',
|
||||
'date' => $job['date'] ?? '',
|
||||
'clientName' => $clientName,
|
||||
'clientId' => $job['clientId'] ?? null,
|
||||
'description' => mb_substr($job['description'] ?? '', 0, 80)
|
||||
];
|
||||
}
|
||||
echo json_encode($result);
|
||||
} catch (\Exception $e) {
|
||||
echo json_encode([]);
|
||||
}
|
||||
exit;
|
||||
}
|
||||
|
||||
if ($_GET['action'] === 'image' || $_GET['action'] === 'get_image') {
|
||||
// Image Handler
|
||||
if (ob_get_level()) ob_end_clean();
|
||||
@ -1717,6 +1810,46 @@ $installersData = json_decode($config['installersDataWhatsApp'] ?? '{"instalador
|
||||
<p>No se encontraron instaladores</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- JOBS ACTIVOS DEL INSTALADOR -->
|
||||
<div class="card" style="margin-top: 2rem;">
|
||||
<div style="margin-bottom: 1.5rem;">
|
||||
<h2 id="jobsSectionTitle" style="margin: 0; display: flex; align-items: center; gap: 10px;">
|
||||
📋 Tareas Activas del Instalador
|
||||
</h2>
|
||||
<p style="color: var(--text-muted); margin: 5px 0 0 0;">Selecciona un instalador con el botón 📋 para ver sus tareas "En curso" y reenviar notificaciones</p>
|
||||
</div>
|
||||
|
||||
<div id="jobsEmptyState" class="placeholder-state">
|
||||
<span class="placeholder-icon">👷</span>
|
||||
<p>Selecciona un instalador para ver sus tareas activas</p>
|
||||
</div>
|
||||
|
||||
<div id="jobsLoading" style="display: none; text-align: center; padding: 2rem; color: var(--text-muted);">
|
||||
<div class="loader" style="margin: 0 auto 1rem;"></div>
|
||||
<p>Cargando tareas...</p>
|
||||
</div>
|
||||
|
||||
<div id="jobsTableWrapper" style="display: none; overflow-x: auto;">
|
||||
<table id="installerJobsTable">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Folio</th>
|
||||
<th>Cliente</th>
|
||||
<th>Fecha</th>
|
||||
<th>Descripción</th>
|
||||
<th>Acción</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody></tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<div id="jobsNoResults" class="placeholder-state" style="display: none;">
|
||||
<span class="placeholder-icon">✅</span>
|
||||
<p>Este instalador no tiene tareas "En curso"</p>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- MODULE 2: NOTIFICATIONS -->
|
||||
@ -2659,6 +2792,7 @@ $installersData = json_decode($config['installersDataWhatsApp'] ?? '{"instalador
|
||||
<td>${inst.nombre}</td>
|
||||
<td>${inst.whatsapp}</td>
|
||||
<td>
|
||||
<span style="cursor:pointer; font-size:1.3em; margin-right:8px;" onclick="loadInstallerJobs('${inst.id}', '${inst.nombre}')" title="Ver tareas activas">📋</span>
|
||||
<img src="?action=image&file=edit.webp" class="icon-action" onclick="editInstaller(${i})" title="Editar">
|
||||
<img src="?action=image&file=delete.webp" class="icon-action" style="margin-left:10px" onclick="deleteInstaller(${i})" title="Borrar">
|
||||
</td>
|
||||
@ -2703,6 +2837,104 @@ $installersData = json_decode($config['installersDataWhatsApp'] ?? '{"instalador
|
||||
}
|
||||
}
|
||||
|
||||
// --- JOBS ACTIVOS DEL INSTALADOR ---
|
||||
async function loadInstallerJobs(installerId, installerName) {
|
||||
const title = document.getElementById('jobsSectionTitle');
|
||||
const emptyState = document.getElementById('jobsEmptyState');
|
||||
const loading = document.getElementById('jobsLoading');
|
||||
const tableWrapper = document.getElementById('jobsTableWrapper');
|
||||
const noResults = document.getElementById('jobsNoResults');
|
||||
|
||||
title.innerHTML = `📋 Tareas Activas: <strong>${installerName}</strong>`;
|
||||
emptyState.style.display = 'none';
|
||||
loading.style.display = 'block';
|
||||
tableWrapper.style.display = 'none';
|
||||
noResults.style.display = 'none';
|
||||
|
||||
// Scroll suave a la sección de jobs
|
||||
title.scrollIntoView({
|
||||
behavior: 'smooth',
|
||||
block: 'start'
|
||||
});
|
||||
|
||||
try {
|
||||
const resp = await fetch(`?action=get_installer_jobs&installerId=${installerId}`);
|
||||
const jobs = await resp.json();
|
||||
loading.style.display = 'none';
|
||||
|
||||
if (!jobs.length) {
|
||||
noResults.style.display = 'block';
|
||||
return;
|
||||
}
|
||||
|
||||
const tbody = document.querySelector('#installerJobsTable tbody');
|
||||
tbody.innerHTML = jobs.map(job => {
|
||||
const dateFormatted = job.date ? new Date(job.date).toLocaleDateString('es-MX', {
|
||||
day: '2-digit',
|
||||
month: '2-digit',
|
||||
year: 'numeric'
|
||||
}) : 'S/F';
|
||||
const titleClean = job.title.replace('[NOTIFICACION-PENDIENTE]', '').replace('[CLIENTE-SIN-WHATSAPP]', '').trim();
|
||||
return `<tr>
|
||||
<td><strong>#${job.id}</strong></td>
|
||||
<td>[${job.clientId}] ${job.clientName}</td>
|
||||
<td>${dateFormatted}</td>
|
||||
<td style="max-width:200px; overflow:hidden; text-overflow:ellipsis; white-space:nowrap;" title="${job.description}">${titleClean || job.description || 'Sin descripción'}</td>
|
||||
<td>
|
||||
<button class="btn btn-primary" style="padding:6px 14px; font-size:0.85em;" onclick="resendJobNotification(${job.id}, this)">
|
||||
📨 Reenviar
|
||||
</button>
|
||||
</td>
|
||||
</tr>`;
|
||||
}).join('');
|
||||
tableWrapper.style.display = 'block';
|
||||
} catch (e) {
|
||||
loading.style.display = 'none';
|
||||
noResults.style.display = 'block';
|
||||
console.error('Error loading installer jobs:', e);
|
||||
}
|
||||
}
|
||||
|
||||
async function resendJobNotification(jobId, btn) {
|
||||
const originalText = btn.innerHTML;
|
||||
btn.innerHTML = '⏳ Enviando...';
|
||||
btn.disabled = true;
|
||||
|
||||
try {
|
||||
const fd = new FormData();
|
||||
fd.append('action', 'resend_job_notification');
|
||||
fd.append('jobId', jobId);
|
||||
|
||||
const resp = await fetch('', {
|
||||
method: 'POST',
|
||||
body: fd
|
||||
});
|
||||
const result = await resp.json();
|
||||
|
||||
if (result.success) {
|
||||
btn.innerHTML = '✅ Enviado';
|
||||
btn.style.backgroundColor = '#22c55e';
|
||||
showToast('Notificación reenviada para tarea #' + jobId);
|
||||
setTimeout(() => {
|
||||
btn.innerHTML = originalText;
|
||||
btn.style.backgroundColor = '';
|
||||
btn.disabled = false;
|
||||
}, 3000);
|
||||
} else {
|
||||
throw new Error(result.message || 'Error desconocido');
|
||||
}
|
||||
} catch (e) {
|
||||
btn.innerHTML = '❌ Error';
|
||||
btn.style.backgroundColor = '#ef4444';
|
||||
showToast('Error: ' + e.message, true);
|
||||
setTimeout(() => {
|
||||
btn.innerHTML = originalText;
|
||||
btn.style.backgroundColor = '';
|
||||
btn.disabled = false;
|
||||
}, 3000);
|
||||
}
|
||||
}
|
||||
|
||||
function openInstallerModal() {
|
||||
document.getElementById('modalOverlay').style.display = 'flex';
|
||||
document.getElementById('installerForm').reset();
|
||||
|
||||
Loading…
Reference in New Issue
Block a user