Files
social-media-automation/dashboard/templates/posts.html
Consultoría AS e32885afc5 fix: Fix YAML syntax errors and validator prompt formatting
- Fix YAML files with unquoted strings containing quotes
- Export singleton instances in ai/__init__.py
- Fix validator scoring prompt to use replace() instead of format()
  to avoid conflicts with JSON curly braces

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-28 21:13:58 +00:00

157 lines
7.8 KiB
HTML

{% extends "base.html" %}
{% block title %}Posts{% endblock %}
{% block content %}
<div class="animate-fade-in">
<!-- Header -->
<div class="flex items-center justify-between mb-8">
<div>
<h1 class="text-3xl font-bold">Posts</h1>
<p class="text-gray-400 mt-1">Gestiona todas tus publicaciones</p>
</div>
<a href="/compose" class="btn-primary px-6 py-3 rounded-xl font-medium flex items-center gap-2 transition-all">
<span>✍️</span>
<span>Crear Post</span>
</a>
</div>
<!-- Filters -->
<div class="card rounded-2xl p-4 mb-6">
<div class="flex flex-wrap gap-4 items-center">
<select id="filter-status" onchange="filterPosts()" class="bg-dark-800 border border-dark-600 rounded-xl px-4 py-2 text-white focus:border-primary focus:outline-none">
<option value="">Todos los estados</option>
<option value="published">Publicados</option>
<option value="scheduled">Programados</option>
<option value="pending_approval">Pendientes</option>
<option value="draft">Borradores</option>
<option value="failed">Fallidos</option>
</select>
<select id="filter-platform" onchange="filterPosts()" class="bg-dark-800 border border-dark-600 rounded-xl px-4 py-2 text-white focus:border-primary focus:outline-none">
<option value="">Todas las plataformas</option>
<option value="x">X (Twitter)</option>
<option value="facebook">Facebook</option>
<option value="instagram">Instagram</option>
<option value="threads">Threads</option>
</select>
<input type="text" id="filter-search" placeholder="Buscar..." onkeyup="filterPosts()" class="bg-dark-800 border border-dark-600 rounded-xl px-4 py-2 text-white placeholder-gray-500 focus:border-primary focus:outline-none flex-1 min-w-48">
</div>
</div>
<!-- Posts List -->
<div id="posts-container" class="space-y-4">
{% for post in posts %}
<div class="card rounded-2xl p-6 hover:border-primary/30 transition-all post-item"
data-status="{{ post.status }}"
data-platforms="{{ post.platforms|join(',') if post.platforms else '' }}"
data-content="{{ post.content|lower }}">
<div class="flex items-start justify-between gap-4">
<div class="flex-1">
<p class="text-white mb-3">{{ post.content[:200] }}{% if post.content|length > 200 %}...{% endif %}</p>
<div class="flex flex-wrap items-center gap-2">
{% for platform in post.platforms %}
<span class="text-xs px-2 py-1 rounded-full
{% if platform == 'x' %}bg-gray-700{% endif %}
{% if platform == 'facebook' %}bg-blue-600/30 text-blue-400{% endif %}
{% if platform == 'instagram' %}bg-pink-600/30 text-pink-400{% endif %}
{% if platform == 'threads' %}bg-gray-600{% endif %}
">{{ platform }}</span>
{% endfor %}
<span class="text-xs px-2 py-1 rounded-full
{% if post.status == 'published' %}bg-green-500/20 text-green-400{% endif %}
{% if post.status == 'scheduled' %}bg-blue-500/20 text-blue-400{% endif %}
{% if post.status == 'pending_approval' %}bg-yellow-500/20 text-yellow-400{% endif %}
{% if post.status == 'draft' %}bg-gray-500/20 text-gray-400{% endif %}
{% if post.status == 'failed' %}bg-red-500/20 text-red-400{% endif %}
">{{ post.status }}</span>
</div>
</div>
<div class="flex flex-col items-end gap-2">
<span class="text-xs text-gray-500">{{ post.created_at[:16] if post.created_at else '-' }}</span>
<div class="flex gap-2">
{% if post.status == 'draft' or post.status == 'pending_approval' %}
<button onclick="publishPost({{ post.id }})" class="text-xs bg-green-500/20 text-green-400 px-3 py-1 rounded-lg hover:bg-green-500/30 transition-colors">
Publicar
</button>
{% endif %}
<button onclick="deletePost({{ post.id }})" class="text-xs bg-red-500/20 text-red-400 px-3 py-1 rounded-lg hover:bg-red-500/30 transition-colors">
Eliminar
</button>
</div>
</div>
</div>
</div>
{% else %}
<div class="text-center py-16 text-gray-500">
<span class="text-6xl mb-4 block">📭</span>
<p class="text-xl">No hay posts</p>
<p class="mt-2">Crea tu primera publicación</p>
<a href="/compose" class="btn-primary px-6 py-3 rounded-xl font-medium inline-flex items-center gap-2 mt-4">
<span>✍️</span>
<span>Crear Post</span>
</a>
</div>
{% endfor %}
</div>
</div>
{% endblock %}
{% block extra_scripts %}
<script>
function filterPosts() {
const status = document.getElementById('filter-status').value;
const platform = document.getElementById('filter-platform').value;
const search = document.getElementById('filter-search').value.toLowerCase();
document.querySelectorAll('.post-item').forEach(post => {
const postStatus = post.dataset.status;
const postPlatforms = post.dataset.platforms;
const postContent = post.dataset.content;
const matchesStatus = !status || postStatus === status;
const matchesPlatform = !platform || postPlatforms.includes(platform);
const matchesSearch = !search || postContent.includes(search);
post.style.display = matchesStatus && matchesPlatform && matchesSearch ? 'block' : 'none';
});
}
async function publishPost(id) {
if (!confirm('¿Publicar este post ahora?')) return;
showModal('Publicando...', true);
try {
const response = await fetch(`/api/posts/${id}/publish`, { method: 'POST' });
const data = await response.json();
if (data.success) {
showModal('<div class="text-center"><span class="text-4xl mb-4 block">🎉</span><p>¡Publicado!</p></div>');
setTimeout(() => location.reload(), 1500);
} else {
showModal(`<div class="text-center"><span class="text-4xl mb-4 block">❌</span><p>${data.error || 'Error'}</p></div>`);
}
} catch (error) {
showModal('<div class="text-center"><span class="text-4xl mb-4 block">❌</span><p>Error de conexión</p></div>');
}
}
async function deletePost(id) {
if (!confirm('¿Eliminar este post?')) return;
showModal('Eliminando...', true);
try {
const response = await fetch(`/api/posts/${id}`, { method: 'DELETE' });
if (response.ok) {
showModal('<div class="text-center"><span class="text-4xl mb-4 block">🗑️</span><p>Eliminado</p></div>');
setTimeout(() => location.reload(), 1000);
} else {
showModal('<div class="text-center"><span class="text-4xl mb-4 block">❌</span><p>Error eliminando</p></div>');
}
} catch (error) {
showModal('<div class="text-center"><span class="text-4xl mb-4 block">❌</span><p>Error de conexión</p></div>');
}
}
</script>
{% endblock %}