Preparar repositorio para despliegue: código fuente limpio

This commit is contained in:
jlimolina 2026-01-23 02:00:40 +01:00
parent 866f5c432d
commit 3eca832c1a
76 changed files with 5434 additions and 3496 deletions

View file

@ -0,0 +1,345 @@
{% extends "base.html" %}
{% block title %}
{{ dato.titulo_trad or dato.titulo_orig or 'Detalle de Noticia' }} - El Observador
{% endblock %}
{% block content %}
{% set d = dato %}
{% if not d %}
<div class="container">
<div class="card">
<h2>Artículo No Encontrado</h2>
<p>No se encontró la noticia solicitada.</p>
<a href="{{ url_for('home.home') }}" class="btn">← Volver al inicio</a>
</div>
</div>
{% else %}
<div class="container">
<!-- Encabezado del artículo -->
<header class="article-header">
<div class="article-breadcrumb">
<a href="{{ url_for('home.home') }}">Inicio</a>
{% if d.categoria %}
<a href="{{ url_for('home.home', categoria_id=d.categoria_id) }}">{{ d.categoria }}</a>
{% endif %}
Artículo
</div>
<h1 class="article-title">{{ d.titulo_trad or d.titulo_orig }}</h1>
<div class="article-meta">
{% if d.categoria %}
<span class="badge">{{ d.categoria }}</span>
{% endif %}
{% if d.pais %}
<span class="meta-item"><i class="fas fa-globe"></i> {{ d.pais }}</span>
{% endif %}
<span class="meta-item"><i class="fas fa-newspaper"></i> {{ d.fuente_nombre }}</span>
<span class="meta-item"><i class="fas fa-clock"></i>
{% if d.fecha %}
{{ d.fecha.strftime('%d de %B de %Y - %H:%M') }}
{% endif %}
</span>
{% if d.lang_to %}
<span class="meta-item"><i class="fas fa-language"></i> Traducido al {{ d.lang_to|upper }}</span>
{% endif %}
</div>
</header>
<!-- Contenido principal -->
<div class="row">
<div class="col-md-8 main-section">
<article class="article-content">
{% if d.imagen_url %}
<div class="article-image">
<img src="{{ d.imagen_url }}" alt="{{ d.titulo_trad or d.titulo_orig }}"
style="width: 100%; max-height: 400px; object-fit: cover; border: 1px solid var(--border-color);">
{% if d.imagen_credit %}
<p class="image-credit">Foto: {{ d.imagen_credit }}</p>
{% endif %}
</div>
{% endif %}
<div class="article-body">
{% if d.resumen_trad or d.resumen_orig %}
<div class="article-summary">
<strong>Resumen:</strong>
<p>{{ (d.resumen_trad or d.resumen_orig) | safe_html }}</p>
</div>
{% endif %}
{% if d.contenido_trad or d.contenido_orig %}
<div class="article-full-content">
{{ (d.contenido_trad or d.contenido_orig) | safe_html }}
</div>
{% else %}
<div class="article-excerpt">
<p>{{ (d.resumen_trad or d.resumen_orig) | safe_html }}</p>
{% if d.url %}
<div style="margin-top: 20px; padding-top: 20px; border-top: 1px solid var(--border-color);">
<a href="{{ d.url }}" target="_blank" class="btn">
<i class="fas fa-external-link-alt"></i> Leer artículo completo en {{ d.fuente_nombre }}
</a>
</div>
{% endif %}
</div>
{% endif %}
</div>
</article>
<!-- Acciones del artículo -->
<div class="article-actions">
<div class="action-buttons">
<button class="btn" onclick="toggleReadingMode()" title="Modo lectura">
<i class="fas fa-book-reader"></i> Modo Lectura
</button>
<a href="{{ url_for('pdf.export_noticia', noticia_id=d.noticia_id) }}" class="btn" title="Exportar PDF"
target="_blank">
<i class="fas fa-file-pdf"></i> PDF
</a>
<button class="btn" onclick="shareArticle()" title="Compartir">
<i class="fas fa-share-alt"></i> Compartir
</button>
{% if session.get('user_id') %}
<button class="btn" onclick="toggleFavorite()" title="Añadir a favoritos">
<i class="fas fa-star"></i> Favorito
</button>
{% endif %}
</div>
<div class="article-tags">
{% if d.etiquetas %}
<h4>Etiquetas:</h4>
{% for tag in d.etiquetas.split(',') %}
<span class="tag">{{ tag.strip() }}</span>
{% endfor %}
{% endif %}
</div>
</div>
</div>
<!-- Sidebar del artículo -->
<div class="col-md-4 sidebar">
<!-- Información de la fuente -->
<div class="card">
<h3>Información de la Fuente</h3>
<div class="source-info">
<p><strong>Medio:</strong> {{ d.fuente_nombre }}</p>
{% if d.idioma_orig %}
<p><strong>Idioma original:</strong> {{ d.idioma_orig|upper }}</p>
{% endif %}
{% if d.url %}
<p><strong>URL original:</strong></p>
<a href="{{ d.url }}" target="_blank" class="url-original"
style="word-break: break-all; color: var(--accent-color);">
{{ d.url }}
</a>
{% endif %}
</div>
</div>
<!-- Artículos relacionados -->
{% if related_news %}
<div class="card">
<h3>Artículos Relacionados</h3>
<ul>
{% for related in related_news[:5] %}
<li>
{% if related.traduccion_id %}
<a href="{{ url_for('noticia.noticia', tr_id=related.traduccion_id) }}">
{% else %}
<a href="{{ url_for('noticia.noticia', id=related.id) }}">
{% endif %}
{{ related.titulo_trad if related.tiene_traduccion else related.titulo_original }}
</a>
<small style="color: var(--muted-color);">
{{ related.fecha.strftime('%d/%m %H:%M') if related.fecha else '' }}
</small>
</li>
{% endfor %}
</ul>
</div>
{% endif %}
<!-- Categorías populares -->
{% if categorias %}
<div class="card">
<h3>Más Noticias</h3>
<ul>
{% for cat in categorias[:8] %}
<li>
<a href="{{ url_for('home.home', categoria_id=cat.id) }}">{{ cat.nombre }}</a>
</li>
{% endfor %}
</ul>
</div>
{% endif %}
</div>
</div>
</div>
{% endif %}
<script>
function toggleReadingMode() {
document.body.classList.toggle('reading-mode');
}
function shareArticle() {
if (navigator.share) {
navigator.share({
title: '{{ d.titulo_trad or d.titulo_orig }}',
text: '{{ d.resumen_trad or d.resumen_orig }}',
url: window.location.href
});
} else {
// Fallback para navegadores que no soportan Web Share API
navigator.clipboard.writeText(window.location.href);
alert('Enlace copiado al portapapeles');
}
}
function toggleFavorite() {
// Implementar funcionalidad de favoritos
fetch('/api/favorites/toggle', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
noticia_id: '{{ d.noticia_id }}'
})
})
.then(response => response.json())
.then(data => {
if (data.success) {
const btn = event.target.closest('button');
const icon = btn.querySelector('i');
if (data.is_favorite) {
icon.classList.remove('far');
icon.classList.add('fas');
btn.innerHTML = '<i class="fas fa-star"></i> Eliminar de Favoritos';
} else {
icon.classList.remove('fas');
icon.classList.add('far');
btn.innerHTML = '<i class="far fa-star"></i> Añadir a Favoritos';
}
}
})
.catch(error => console.error('Error:', error));
}
// Estilo adicional para modo lectura
const style = document.createElement('style');
style.textContent = `
.reading-mode .container {
max-width: 800px;
margin: 20px auto;
padding: 40px;
background: var(--paper-color);
border: none;
box-shadow: none;
}
.reading-mode .article-content {
column-count: 1;
font-size: 1.2rem;
line-height: 1.9;
}
.reading-mode .sidebar {
display: none;
}
.reading-mode .article-actions {
margin-top: 40px;
padding-top: 30px;
border-top: 2px solid var(--border-color);
}
.reading-mode .article-title {
font-size: 2.5rem;
line-height: 1.2;
margin-bottom: 20px;
}
.reading-mode .article-meta {
margin-bottom: 30px;
}
.article-image {
margin: 30px 0;
}
.image-credit {
font-size: 0.85rem;
color: var(--muted-color);
margin-top: 8px;
font-style: italic;
}
.article-summary {
background: var(--bg-color);
padding: 20px;
border-left: 4px solid var(--accent-color);
margin: 20px 0;
font-size: 1.1rem;
}
.article-breadcrumb {
font-size: 0.9rem;
color: var(--muted-color);
margin-bottom: 15px;
}
.article-breadcrumb a {
color: var(--muted-color);
text-decoration: none;
}
.article-breadcrumb a:hover {
color: var(--accent-color);
}
.action-buttons {
display: flex;
gap: 10px;
flex-wrap: wrap;
margin-bottom: 20px;
}
.article-tags {
margin-top: 20px;
}
.article-tags h4 {
margin-bottom: 10px;
color: var(--accent-color);
}
.source-info p {
margin-bottom: 10px;
}
.url-original {
display: block;
margin-top: 5px;
font-size: 0.9rem;
}
`;
document.head.appendChild(style);
</script>
{% endblock %}