Files
trimape_web/blog/post.php
T
2026-07-15 19:45:08 -06:00

344 lines
16 KiB
PHP

<?php
require_once __DIR__ . '/config.php';
$slug = isset($_GET['slug']) ? trim($_GET['slug']) : '';
if ($slug === '') {
header('Location: ' . BLOG_URL);
exit;
}
$db = getDB();
// Fetch post
$stmt = $db->prepare("SELECT p.*, c.name AS category_name, c.slug AS category_slug
FROM blog_posts p
LEFT JOIN blog_categories c ON p.category_id = c.id
WHERE p.slug = :slug AND p.status = 'published'
LIMIT 1");
$stmt->execute([':slug' => $slug]);
$post = $stmt->fetch();
if (!$post) {
http_response_code(404);
$pageTitle = 'Artículo no encontrado - Blog TRIMAPE';
$notFound = true;
} else {
$notFound = false;
$pageTitle = e($post['title']) . ' - Blog TRIMAPE';
$pageDesc = $post['excerpt'] ? e($post['excerpt']) : e(truncate($post['content'], 160));
$coverImage = $post['cover_image'] ?: SITE_URL . '/assets/img/trimape-1200.webp';
// Related posts (same category, excluding current)
$relatedStmt = $db->prepare("SELECT p.*, c.name AS category_name, c.slug AS category_slug
FROM blog_posts p
LEFT JOIN blog_categories c ON p.category_id = c.id
WHERE p.status = 'published' AND p.id != :id AND p.category_id = :catid
ORDER BY p.published_at DESC LIMIT 3");
$relatedStmt->execute([':id' => $post['id'], ':catid' => $post['category_id']]);
$related = $relatedStmt->fetchAll();
// If not enough related from same category, fill with recent
if (count($related) < 3) {
$excludeIds = array_merge([$post['id']], array_column($related, 'id'));
$placeholders = implode(',', array_fill(0, count($excludeIds), '?'));
$fillStmt = $db->prepare("SELECT p.*, c.name AS category_name, c.slug AS category_slug
FROM blog_posts p
LEFT JOIN blog_categories c ON p.category_id = c.id
WHERE p.status = 'published' AND p.id NOT IN ($placeholders)
ORDER BY p.published_at DESC LIMIT " . (3 - count($related)));
$fillStmt->execute($excludeIds);
$related = array_merge($related, $fillStmt->fetchAll());
}
}
?>
<!DOCTYPE html>
<html lang="es">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Montserrat:wght@100..900&display=swap" rel="stylesheet">
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-QWTKZyjpPEjISv5WaRU9OFeRpok6YctnYmDr5pNlyT2bRjXh0JMhjY6hW+ALEwIH" crossorigin="anonymous">
<title><?= $pageTitle ?></title>
<?php if (!$notFound): ?>
<meta name="description" content="<?= $pageDesc ?>">
<meta property="og:title" content="<?= e($post['title']) ?>">
<meta property="og:description" content="<?= $pageDesc ?>">
<meta property="og:type" content="article">
<meta property="og:url" content="<?= BLOG_URL ?>/<?= e($post['slug']) ?>">
<meta property="og:image" content="<?= e($coverImage) ?>">
<meta name="twitter:card" content="summary_large_image">
<meta name="twitter:title" content="<?= e($post['title']) ?>">
<meta name="twitter:description" content="<?= $pageDesc ?>">
<meta name="twitter:image" content="<?= e($coverImage) ?>">
<?php endif; ?>
<link rel="icon" href="<?= SITE_URL ?>/assets/img/favico.png">
<link rel="icon" href="<?= SITE_URL ?>/assets/img/trimape-logo-512-150x150.png" sizes="32x32">
<link rel="icon" href="<?= SITE_URL ?>/assets/img/trimape-logo-512-300x300.png" sizes="192x192">
<link rel="apple-touch-icon" href="<?= SITE_URL ?>/assets/img/trimape-logo-512-300x300.png">
<?php include dirname(__DIR__) . '/css-columna.htm'; ?>
<style>
.post-hero {
position: relative;
width: 100%;
height: 45vh;
min-height: 350px;
overflow: hidden;
}
.post-hero img {
position: absolute;
top: 0; left: 0;
width: 100%; height: 100%;
object-fit: cover;
}
.post-hero .overlay {
position: absolute;
top: 0; left: 0;
width: 100%; height: 100%;
background: linear-gradient(to top, rgba(10,29,59,0.85) 0%, rgba(10,29,59,0.3) 60%, transparent 100%);
z-index: 1;
}
.post-hero .hero-content {
position: relative;
z-index: 2;
height: 100%;
display: flex;
flex-direction: column;
justify-content: flex-end;
padding-bottom: 3rem;
}
.post-content {
font-size: 1.05rem;
line-height: 1.85;
color: #333;
}
.post-content h2, .post-content h3, .post-content h4 {
color: #0a1d3b;
font-weight: 700;
margin-top: 2rem;
margin-bottom: 1rem;
}
.post-content p { margin-bottom: 1.25rem; }
.post-content img {
max-width: 100%;
height: auto;
border-radius: 10px;
margin: 1.5rem 0;
}
.post-content blockquote {
border-left: 4px solid #0e55bb;
padding: 1rem 1.5rem;
margin: 1.5rem 0;
background: #f0f7ff;
border-radius: 0 8px 8px 0;
font-style: italic;
color: #444;
}
.post-content ul, .post-content ol {
padding-left: 1.5rem;
margin-bottom: 1.25rem;
}
.post-content li { margin-bottom: 0.5rem; }
.post-meta {
font-size: 0.85rem;
color: rgba(255,255,255,0.8);
}
.post-meta .badge-cat {
background: linear-gradient(135deg, #0e55bb, #1186cc);
font-size: 0.7rem;
letter-spacing: 0.5px;
text-transform: uppercase;
padding: 5px 14px;
border-radius: 20px;
}
.author-box {
display: flex;
align-items: center;
gap: 12px;
padding: 1.25rem;
background: #f8fafd;
border-radius: 12px;
border: 1px solid #e8eef5;
}
.author-avatar {
width: 48px; height: 48px;
border-radius: 50%;
background: linear-gradient(135deg, #0e55bb, #1186cc);
display: flex; align-items: center; justify-content: center;
color: white; font-weight: 700; font-size: 1.2rem;
}
.share-buttons a {
display: inline-flex;
align-items: center;
justify-content: center;
width: 40px; height: 40px;
border-radius: 10px;
background: #f0f4f8;
color: #555;
transition: all 0.2s;
text-decoration: none;
}
.share-buttons a:hover {
background: #0e55bb;
color: white;
transform: translateY(-2px);
}
.related-card {
border: none;
border-radius: 12px;
overflow: hidden;
box-shadow: 0 2px 12px rgba(0,0,0,0.08);
transition: all 0.3s;
}
.related-card:hover {
transform: translateY(-5px);
box-shadow: 0 12px 30px rgba(10,29,59,0.15);
}
.related-card img { height: 180px; object-fit: cover; }
.related-card .card-title {
font-size: 0.95rem; font-weight: 700; color: #0a1d3b;
display: -webkit-box; -webkit-line-clamp: 2;
-webkit-box-orient: vertical; overflow: hidden;
}
a.related-link { text-decoration: none; color: inherit; }
</style>
</head>
<body>
<?php include dirname(__DIR__) . '/menu-general.htm'; ?>
<?php if ($notFound): ?>
<!-- 404 -->
<div class="container py-5 text-center" style="min-height: 60vh; display: flex; flex-direction: column; justify-content: center;">
<h1 class="display-1 fw-bold" style="color: #0a1d3b;">404</h1>
<p class="lead">El artículo que buscas no existe o fue eliminado.</p>
<a href="<?= BLOG_URL ?>" class="btn px-4 py-2 mt-3" style="background: linear-gradient(135deg, #0e55bb, #1186cc); color: white; border-radius: 8px;">Volver al blog</a>
</div>
<?php else: ?>
<!-- Post Hero -->
<div class="post-hero">
<?php if ($post['cover_image']): ?>
<img src="<?= e($post['cover_image']) ?>" alt="<?= e($post['title']) ?>">
<?php else: ?>
<div style="width:100%;height:100%;background: linear-gradient(135deg, #0a1d3b 0%, #0e55bb 50%, #1186cc 100%);"></div>
<?php endif; ?>
<div class="overlay"></div>
<div class="container hero-content">
<div class="post-meta mb-2">
<?php if ($post['category_name']): ?>
<a href="<?= BLOG_URL ?>/categoria/<?= e($post['category_slug']) ?>" class="badge badge-cat text-decoration-none text-white"><?= e($post['category_name']) ?></a>
<?php endif; ?>
</div>
<h1 class="text-white fw-bold mb-2" style="font-size: 2.2rem; line-height: 1.3; max-width: 800px;"><?= e($post['title']) ?></h1>
<div class="post-meta d-flex align-items-center gap-3 flex-wrap">
<span>
<svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" fill="currentColor" class="bi bi-person me-1" viewBox="0 0 16 16">
<path d="M8 8a3 3 0 1 0 0-6 3 3 0 0 0 0 6m2-3a2 2 0 1 1-4 0 2 2 0 0 1 4 0m4 8c0 1-1 1-1 1H3s-1 0-1-1 1-4 6-4 6 3 6 4m-1-.004c-.001-.246-.154-.986-.832-1.664C11.516 10.68 10.289 10 8 10s-3.516.68-4.168 1.332c-.678.678-.83 1.418-.832 1.664z"/>
</svg>
<?= e($post['author'] ?: 'TRIMAPE') ?>
</span>
<span>
<svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" fill="currentColor" class="bi bi-calendar3 me-1" viewBox="0 0 16 16">
<path d="M14 0H2a2 2 0 0 0-2 2v12a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V2a2 2 0 0 0-2-2M1 3.857C1 3.384 1.448 3 2 3h12c.552 0 1 .384 1 .857v10.286c0 .473-.448.857-1 .857H2c-.552 0-1-.384-1-.857z"/>
<path d="M6.5 7a1 1 0 1 0 0-2 1 1 0 0 0 0 2m3 0a1 1 0 1 0 0-2 1 1 0 0 0 0 2m3 0a1 1 0 1 0 0-2 1 1 0 0 0 0 2m-9 3a1 1 0 1 0 0-2 1 1 0 0 0 0 2m3 0a1 1 0 1 0 0-2 1 1 0 0 0 0 2m3 0a1 1 0 1 0 0-2 1 1 0 0 0 0 2m3 0a1 1 0 1 0 0-2 1 1 0 0 0 0 2m-9 3a1 1 0 1 0 0-2 1 1 0 0 0 0 2m3 0a1 1 0 1 0 0-2 1 1 0 0 0 0 2m3 0a1 1 0 1 0 0-2 1 1 0 0 0 0 2"/>
</svg>
<?= date('d \d\e F, Y', strtotime($post['published_at'])) ?>
</span>
</div>
</div>
</div>
<!-- Post Content -->
<div class="container py-5">
<div class="row justify-content-center">
<div class="col-12 col-lg-8">
<!-- Share buttons -->
<div class="share-buttons d-flex gap-2 mb-4">
<a href="https://www.facebook.com/sharer/sharer.php?u=<?= urlencode(BLOG_URL . '/' . $post['slug']) ?>" target="_blank" rel="noopener" title="Compartir en Facebook">
<svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" fill="currentColor" viewBox="0 0 16 16">
<path d="M16 8.049c0-4.446-3.582-8.05-8-8.05C3.58 0-.002 3.603-.002 8.05c0 4.017 2.926 7.347 6.75 7.951v-5.625h-2.03V8.05H6.75V6.275c0-2.017 1.195-3.131 3.022-3.131.876 0 1.791.157 1.791.157v1.98h-1.009c-.993 0-1.303.621-1.303 1.258v1.51h2.218l-.354 2.326H9.25V16c3.824-.604 6.75-3.934 6.75-7.951"/>
</svg>
</a>
<a href="https://twitter.com/intent/tweet?url=<?= urlencode(BLOG_URL . '/' . $post['slug']) ?>&text=<?= urlencode($post['title']) ?>" target="_blank" rel="noopener" title="Compartir en X">
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" viewBox="0 0 16 16">
<path d="M12.6.75h2.454l-5.36 6.142L16 15.25h-4.937l-3.867-5.07-4.425 5.07H.316l5.733-6.57L0 .75h5.063l3.495 4.633L12.601.75Zm-.86 13.028h1.36L4.323 2.145H2.865z"/>
</svg>
</a>
<a href="https://www.linkedin.com/shareArticle?mini=true&url=<?= urlencode(BLOG_URL . '/' . $post['slug']) ?>&title=<?= urlencode($post['title']) ?>" target="_blank" rel="noopener" title="Compartir en LinkedIn">
<svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" fill="currentColor" viewBox="0 0 16 16">
<path d="M0 1.146C0 .513.526 0 1.175 0h13.65C15.474 0 16 .513 16 1.146v13.708c0 .633-.526 1.146-1.175 1.146H1.175C.526 16 0 15.487 0 14.854zm4.943 12.248V6.169H2.542v7.225zm-1.2-8.212c.837 0 1.358-.554 1.358-1.248-.015-.709-.52-1.248-1.342-1.248S2.4 3.226 2.4 3.934c0 .694.521 1.248 1.327 1.248zm4.908 8.212V9.359c0-.216.016-.432.08-.586.173-.431.568-.878 1.232-.878.869 0 1.216.662 1.216 1.634v3.865h2.401V9.25c0-2.22-1.184-3.252-2.764-3.252-1.274 0-1.845.7-2.165 1.193v.025h-.016l.016-.025V6.169h-2.4c.03.678 0 7.225 0 7.225z"/>
</svg>
</a>
<a href="https://wa.me/?text=<?= urlencode($post['title'] . ' ' . BLOG_URL . '/' . $post['slug']) ?>" target="_blank" rel="noopener" title="Compartir en WhatsApp">
<svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" fill="currentColor" viewBox="0 0 16 16">
<path d="M13.601 2.326A7.85 7.85 0 0 0 7.994 0C3.627 0 .068 3.558.064 7.926c0 1.399.366 2.76 1.057 3.965L0 16l4.204-1.102a7.9 7.9 0 0 0 3.79.965h.004c4.368 0 7.926-3.558 7.93-7.93A7.9 7.9 0 0 0 13.6 2.326zM7.994 14.521a6.6 6.6 0 0 1-3.356-.92l-.24-.144-2.494.654.666-2.433-.156-.251a6.56 6.56 0 0 1-1.007-3.505c0-3.626 2.957-6.584 6.591-6.584a6.56 6.56 0 0 1 4.66 1.931 6.56 6.56 0 0 1 1.928 4.66c-.004 3.639-2.961 6.592-6.592 6.592m3.615-4.934c-.197-.099-1.17-.578-1.353-.646-.182-.065-.315-.099-.445.099-.133.197-.513.646-.627.775-.114.133-.232.148-.43.05-.197-.1-.836-.308-1.592-.985-.59-.525-.985-1.175-1.103-1.372-.114-.198-.011-.304.088-.403.087-.088.197-.232.296-.346.1-.114.133-.198.198-.33.065-.134.034-.248-.015-.347-.05-.099-.445-1.076-.612-1.47-.16-.389-.323-.335-.445-.34-.114-.007-.247-.007-.38-.007a.73.73 0 0 0-.529.247c-.182.198-.691.677-.691 1.654s.71 1.916.81 2.049c.098.133 1.394 2.132 3.383 2.992.47.205.84.326 1.129.418.475.152.904.129 1.246.08.38-.058 1.171-.48 1.338-.943.164-.464.164-.86.114-.943-.049-.084-.182-.133-.38-.232"/>
</svg>
</a>
</div>
<!-- Article body -->
<article class="post-content">
<?= $post['content'] ?>
</article>
<!-- Author -->
<div class="author-box mt-5">
<div class="author-avatar"><?= strtoupper(mb_substr($post['author'] ?: 'T', 0, 1)) ?></div>
<div>
<div class="fw-bold" style="color: #0a1d3b;"><?= e($post['author'] ?: 'TRIMAPE') ?></div>
<div style="font-size: 0.8rem; color: #888;">Publicado el <?= date('d \d\e F, Y', strtotime($post['published_at'])) ?></div>
</div>
</div>
<!-- Back to blog -->
<div class="text-center mt-5">
<a href="<?= BLOG_URL ?>" class="btn px-4 py-2 fw-bold" style="background: linear-gradient(135deg, #0e55bb, #1186cc); color: white; border-radius: 8px;">
← Volver al blog
</a>
</div>
</div>
</div>
<!-- Related Posts -->
<?php if (!empty($related)): ?>
<div class="mt-5 pt-4 border-top">
<h4 class="fw-bold mb-4 text-center" style="color: #0a1d3b;">Artículos relacionados</h4>
<div class="row g-4">
<?php foreach ($related as $rel): ?>
<div class="col-12 col-md-4">
<a href="<?= BLOG_URL ?>/<?= e($rel['slug']) ?>" class="related-link">
<div class="card related-card h-100">
<?php if ($rel['cover_image']): ?>
<img src="<?= e($rel['cover_image']) ?>" class="card-img-top" alt="<?= e($rel['title']) ?>">
<?php else: ?>
<div class="card-img-top d-flex align-items-center justify-content-center" style="background: linear-gradient(135deg, #0a1d3b, #1186cc); height: 180px;">
<img src="<?= SITE_URL ?>/assets/img/trimape-logo-512-300x300.png" alt="TRIMAPE" style="width: 60px; opacity: 0.4;">
</div>
<?php endif; ?>
<div class="card-body">
<h6 class="card-title"><?= e($rel['title']) ?></h6>
<small class="text-muted"><?= date('d M Y', strtotime($rel['published_at'])) ?></small>
</div>
</div>
</a>
</div>
<?php endforeach; ?>
</div>
</div>
<?php endif; ?>
</div>
<?php endif; ?>
<?php include dirname(__DIR__) . '/contacto-div.htm'; ?>
<?php include dirname(__DIR__) . '/footer.htm'; ?>
<?php include dirname(__DIR__) . '/footer-scripts.htm'; ?>
</body>
</html>