loadConfig();
// LOG DE EMERGENCIA
$debugLogPath = __DIR__ . '/data/debug_public.log';
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$input = file_get_contents('php://input');
$jsonData = json_decode((string)$input, true);
if ($jsonData && (isset($jsonData['uuid']) || isset($jsonData['eventName']) || isset($jsonData['type']))) {
file_put_contents($debugLogPath, "[" . date('Y-m-d H:i:s') . "] Webhook in public.php. Delegating..." . PHP_EOL, FILE_APPEND);
$builder = new \DI\ContainerBuilder();
$container = $builder->build();
$plugin = $container->get(\SmsNotifier\Plugin::class);
$plugin->run();
exit;
}
}
$ipServer = $config['ipserver'] ?? 'localhost';
$ucrmApiUrl = 'https://' . $ipServer . '/crm/api/v1.0/';
$ucrmPublicUrl = 'https://' . $ipServer . '/crm';
$httpClient = new \GuzzleHttp\Client([
'base_uri' => $ucrmApiUrl,
'verify' => false,
'timeout' => 15
]);
$ucrmApi = new UcrmApi($httpClient, $config['apitoken'] ?? '');
$paymentIntentService = new PaymentIntentService($ucrmApi, $config['tokenstripe'] ?? '');
// Admins Logic
$admins = [];
$defaultStripeAdminId = null;
try {
$adminsRaw = $ucrmApi->get('users/admins');
foreach ($adminsRaw as $admin) {
$nombre = trim(($admin['firstName'] ?? '') . ' ' . ($admin['lastName'] ?? ''));
$admins[] = ['id' => $admin['id'], 'nombre' => $nombre];
if (strtolower($nombre) === 'stripe' || strtolower($admin['username'] ?? '') === 'stripe') {
$defaultStripeAdminId = $admin['id'];
}
}
if ($defaultStripeAdminId === null && !empty($admins)) $defaultStripeAdminId = $admins[0]['id'];
} catch (\Exception $e) {
}
// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
// 0. LOGIN SCREEN SUPPORT & AUTHENTICATION
// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
// Determinar si el usuario necesita login (si no tiene sesión UCRM activa)
$security = UcrmSecurity::create();
$currentUser = $security->getUser();
$needsLogin = !$currentUser;
$nmsBaseUrl = "https://{$ipServer}/nms/api/v2.1";
// 0a. NMS Login (POST username + password)
if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_GET['action']) && $_GET['action'] === 'nms_login') {
header('Content-Type: application/json');
$input = json_decode(file_get_contents('php://input'), true);
$username = $input['username'] ?? '';
$password = $input['password'] ?? '';
if (!$username || !$password) {
http_response_code(400);
echo json_encode(['error' => 'Se requieren usuario y contraseña.']);
exit;
}
try {
$nmsClient = new \GuzzleHttp\Client(['verify' => false, 'http_errors' => false]);
$resp = $nmsClient->post("{$nmsBaseUrl}/user/login", [
'json' => ['username' => $username, 'password' => $password],
'headers' => ['Content-Type' => 'application/json'],
]);
$statusCode = $resp->getStatusCode();
$body = json_decode($resp->getBody()->getContents(), true);
$authToken = $resp->getHeaderLine('x-auth-token');
if ($statusCode === 200 && $authToken) {
echo json_encode(['success' => true, 'token' => $authToken, 'user' => $body]);
} elseif ($statusCode === 201) {
http_response_code(201);
echo json_encode(['requires2FA' => true, 'twoFactorToken' => $body]);
} else {
http_response_code($statusCode ?: 401);
echo json_encode(['error' => $body['message'] ?? 'Credenciales inválidas.', 'statusCode' => $statusCode]);
}
} catch (\Exception $e) {
http_response_code(500);
echo json_encode(['error' => 'Error de conexión con el servidor UISP.']);
}
exit;
}
// 0b. NMS TOTP Login
if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_GET['action']) && $_GET['action'] === 'nms_login_totp') {
header('Content-Type: application/json');
$input = json_decode(file_get_contents('php://input'), true);
try {
$nmsClient = new \GuzzleHttp\Client(['verify' => false, 'http_errors' => false]);
$resp = $nmsClient->post("{$nmsBaseUrl}/user/login/totpauth", [
'json' => $input,
'headers' => ['Content-Type' => 'application/json'],
]);
$statusCode = $resp->getStatusCode();
$body = json_decode($resp->getBody()->getContents(), true);
$authToken = $resp->getHeaderLine('x-auth-token');
if ($statusCode === 200 && $authToken) {
echo json_encode(['success' => true, 'token' => $authToken, 'user' => $body]);
} else {
http_response_code($statusCode ?: 401);
echo json_encode(['error' => $body['message'] ?? 'Código TOTP inválido.']);
}
} catch (\Exception $e) {
http_response_code(500);
echo json_encode(['error' => 'Error de conexión con el servidor UISP.']);
}
exit;
}
// 0c. NMS Verify Session
if ($_SERVER['REQUEST_METHOD'] === 'GET' && isset($_GET['action']) && $_GET['action'] === 'nms_verify_session') {
header('Content-Type: application/json');
$token = $_SERVER['HTTP_X_AUTH_TOKEN'] ?? '';
if (!$token) {
http_response_code(401);
echo json_encode(['error' => 'No token provided.']);
exit;
}
try {
$nmsClient = new \GuzzleHttp\Client(['verify' => false, 'http_errors' => false]);
$resp = $nmsClient->get("{$nmsBaseUrl}/user", [
'headers' => ['x-auth-token' => $token],
]);
if ($resp->getStatusCode() === 200) {
echo json_encode(['success' => true, 'user' => json_decode($resp->getBody()->getContents(), true)]);
} else {
http_response_code(401);
echo json_encode(['error' => 'Sesión inválida o expirada.']);
}
} catch (\Exception $e) {
http_response_code(500);
echo json_encode(['error' => 'Error verificando sesión.']);
}
exit;
}
// API HANDLERS
if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['action'])) {
if ($_POST['action'] === 'save_installers') {
$installersJson = $_POST['installers_data'] ?? '';
if (json_decode($installersJson) !== null) {
$configPath = __DIR__ . '/data/config.json';
$currentConfig = json_decode(file_get_contents($configPath), true);
$currentConfig['installersDataWhatsApp'] = $installersJson;
file_put_contents($configPath, json_encode($currentConfig, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE));
echo json_encode(['success' => true]);
exit;
}
echo json_encode(['success' => false]);
exit;
}
if ($_POST['action'] === 'resend_payment') {
$paymentId = $_POST['paymentId'] ?? null;
try {
$payment = $ucrmApi->get("payments/$paymentId");
$client = $ucrmApi->get("clients/{$payment['clientId']}");
$payload = [
'uuid' => 'manual-trigger',
'changeType' => 'insert',
'entity' => 'payment',
'entityId' => (int)$paymentId,
'eventName' => 'payment.add',
'clientData' => $client,
'paymentData' => $payment
];
// Internal Loopback
$protocol = (!empty($_SERVER['HTTPS']) && $_SERVER['HTTPS'] !== 'off' || $_SERVER['SERVER_PORT'] == 443) ? "https://" : "http://";
$selfUrl = $protocol . $_SERVER['HTTP_HOST'] . $_SERVER['PHP_SELF'];
$debugLogPath = __DIR__ . '/data/debug_public.log';
file_put_contents($debugLogPath, "[" . date('Y-m-d H:i:s') . "] RESEND PAYMENT. URL: $selfUrl" . PHP_EOL, FILE_APPEND);
$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); // Mantener POST y payload en redirects 301/302
curl_setopt($ch, CURLOPT_TIMEOUT, 5);
$res = curl_exec($ch);
$err = curl_error($ch);
$code = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);
file_put_contents($debugLogPath, "[" . date('Y-m-d H:i:s') . "] RESEND CURL RESULT - Code: $code - Error: $err - Body: $res" . PHP_EOL, FILE_APPEND);
echo json_encode(['success' => true, 'message' => 'Notificación disparada.']);
} catch (\Exception $e) {
echo json_encode(['success' => false, 'message' => $e->getMessage()]);
}
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'])) {
header('Content-Type: application/json');
if ($_GET['action'] === 'search_clients') {
$q = $_GET['q'] ?? '';
try {
$clients = $ucrmApi->get('clients', ['query' => $q, 'limit' => 6]);
echo json_encode($clients);
} catch (\Exception $e) {
echo json_encode([]);
}
exit;
}
if ($_GET['action'] === 'get_payments') {
$clientId = $_GET['clientId'] ?? null;
try {
$payments = $ucrmApi->get('payments', ['clientId' => $clientId, 'limit' => 20, 'order' => 'createdDate', 'direction' => 'DESC']);
$methods = $ucrmApi->get('payment-methods');
$methodMap = [];
foreach ($methods as $m) $methodMap[$m['id']] = $m['name'];
$formatted = array_slice($payments, 0, 10);
foreach ($formatted as &$p) $p['methodName'] = $methodMap[$p['methodId']] ?? 'N/A';
echo json_encode($formatted);
} catch (\Exception $e) {
echo json_encode([]);
}
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') {
$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();
$filename = basename($_GET['file'] ?? $_GET['name'] ?? '');
$paths = [__DIR__ . '/img/' . $filename, __DIR__ . '/img/webp/' . $filename, __DIR__ . '/vouchers_oxxo/' . $filename];
$finalPath = null;
foreach ($paths as $p) {
if (file_exists($p)) {
$finalPath = $p;
break;
}
}
if ($finalPath) {
$ext = strtolower(pathinfo($filename, PATHINFO_EXTENSION));
$type = ($ext === 'png') ? 'image/png' : (($ext === 'webp') ? 'image/webp' : 'image/jpeg');
header("Content-Type: $type");
header("Content-Length: " . filesize($finalPath));
readfile($finalPath);
} else {
http_response_code(404);
echo "Not Found";
}
exit;
}
}
// POST Actions for Intents
if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['action'])) {
header('Content-Type: application/json');
if ($_POST['action'] === 'create_oxxo_intent') {
try {
$builder = new \DI\ContainerBuilder();
$container = $builder->build();
$oxxoService = $container->get(\SmsNotifier\Facade\PluginOxxoNotifierFacade::class);
if (ob_get_length()) ob_clean();
$result = $oxxoService->createOxxoPaymentIntent(['client_id' => $_POST['clientId']], $_POST['amount'], false);
if (ob_get_length()) ob_clean();
echo json_encode(['success' => true, 'data' => $result]);
} catch (\Exception $e) {
echo json_encode(['success' => false, 'error' => $e->getMessage()]);
}
exit;
}
}
$installersData = json_decode($config['installersDataWhatsApp'] ?? '{"instaladores":[]}', true);
?>
SIIP - Notificaciones y Pagos
SIIP Notifications
Inicia sesión con tu cuenta de UISP
Gestión de Instaladores
👷 Administra tu equipo de técnicos y asigna instalaciones de manera eficiente
| ID |
Nombre |
WhatsApp |
Acciones |
🔍
No se encontraron instaladores
📋 Tareas Activas del Instalador
Selecciona un instalador con el botón 📋 para ver sus tareas "En curso" y reenviar notificaciones
👷
Selecciona un instalador para ver sus tareas activas
| Folio |
Cliente |
Fecha |
Descripción |
Acción |
✅
Este instalador no tiene tareas "En curso"
Notificaciones WhatsApp
📱 Busca clientes y envía comprobantes de pago directamente a su WhatsApp
👋
Busca un cliente arriba para ver sus pagos y enviar notificaciones
| ID |
Fecha |
Monto |
Método |
Acción |
Referencia Generada