Implementación inicial del sistema de automatización de redes sociales

- Estructura completa del proyecto con FastAPI
- Modelos de base de datos (productos, servicios, posts, calendario, interacciones)
- Publishers para X, Threads, Instagram, Facebook
- Generador de contenido con DeepSeek API
- Worker de Celery con tareas programadas
- Dashboard básico con templates HTML
- Docker Compose para despliegue
- Documentación completa

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
2026-01-28 01:11:44 +00:00
commit 049d2133f9
53 changed files with 5876 additions and 0 deletions

View File

@@ -0,0 +1,181 @@
<!DOCTYPE html>
<html lang="es">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Dashboard - Social Media Automation</title>
<link href="https://cdn.jsdelivr.net/npm/tailwindcss@2.2.19/dist/tailwind.min.css" rel="stylesheet">
<style>
body { background-color: #1a1a2e; color: #eee; }
.card { background-color: #16213e; border-radius: 12px; }
.accent { color: #d4a574; }
.btn-primary { background-color: #d4a574; color: #1a1a2e; }
.btn-primary:hover { background-color: #c49564; }
</style>
</head>
<body class="min-h-screen">
<!-- Header -->
<header class="bg-gray-900 border-b border-gray-800 px-6 py-4">
<div class="flex justify-between items-center">
<h1 class="text-2xl font-bold">
<span class="accent">Consultoría AS</span> - Social Media
</h1>
<nav class="flex gap-4">
<a href="/" class="px-4 py-2 rounded hover:bg-gray-800">Home</a>
<a href="/posts" class="px-4 py-2 rounded hover:bg-gray-800">Posts</a>
<a href="/calendar" class="px-4 py-2 rounded hover:bg-gray-800">Calendario</a>
<a href="/interactions" class="px-4 py-2 rounded hover:bg-gray-800">Interacciones</a>
<a href="/products" class="px-4 py-2 rounded hover:bg-gray-800">Productos</a>
</nav>
</div>
</header>
<main class="container mx-auto px-6 py-8">
<!-- Stats -->
<div class="grid grid-cols-5 gap-4 mb-8">
<div class="card p-4 text-center">
<div class="text-3xl font-bold accent">{{ stats.posts_today }}</div>
<div class="text-gray-400 text-sm">Posts Hoy</div>
</div>
<div class="card p-4 text-center">
<div class="text-3xl font-bold accent">{{ stats.posts_week }}</div>
<div class="text-gray-400 text-sm">Posts Semana</div>
</div>
<div class="card p-4 text-center">
<div class="text-3xl font-bold text-yellow-500">{{ stats.pending_approval }}</div>
<div class="text-gray-400 text-sm">Pendientes</div>
</div>
<div class="card p-4 text-center">
<div class="text-3xl font-bold text-blue-500">{{ stats.scheduled }}</div>
<div class="text-gray-400 text-sm">Programados</div>
</div>
<div class="card p-4 text-center">
<div class="text-3xl font-bold text-red-500">{{ stats.interactions_pending }}</div>
<div class="text-gray-400 text-sm">Interacciones</div>
</div>
</div>
<div class="grid grid-cols-2 gap-6">
<!-- Pending Approval -->
<div class="card p-6">
<h2 class="text-xl font-semibold mb-4">Pendientes de Aprobación</h2>
{% if pending_posts %}
{% for post in pending_posts %}
<div class="border-b border-gray-700 py-4 last:border-0">
<div class="flex justify-between items-start mb-2">
<span class="bg-blue-900 text-blue-300 px-2 py-1 rounded text-xs">
{{ post.content_type }}
</span>
<span class="text-gray-500 text-sm">
{{ post.scheduled_at }}
</span>
</div>
<p class="text-gray-300 mb-3">{{ post.content[:200] }}...</p>
<div class="flex gap-2">
<button onclick="approvePost({{ post.id }})"
class="btn-primary px-3 py-1 rounded text-sm">
Aprobar
</button>
<button onclick="rejectPost({{ post.id }})"
class="bg-red-900 text-red-300 px-3 py-1 rounded text-sm">
Rechazar
</button>
<button onclick="editPost({{ post.id }})"
class="bg-gray-700 px-3 py-1 rounded text-sm">
Editar
</button>
</div>
</div>
{% endfor %}
{% else %}
<p class="text-gray-500">No hay posts pendientes</p>
{% endif %}
</div>
<!-- Scheduled -->
<div class="card p-6">
<h2 class="text-xl font-semibold mb-4">Próximas Publicaciones</h2>
{% if scheduled_posts %}
{% for post in scheduled_posts %}
<div class="border-b border-gray-700 py-3 last:border-0 flex items-center gap-4">
<div class="text-center">
<div class="text-accent font-bold">{{ post.scheduled_at }}</div>
</div>
<div class="flex-1">
<span class="text-xs bg-gray-700 px-2 py-1 rounded">
{{ post.content_type }}
</span>
<p class="text-gray-400 text-sm mt-1">
{{ post.content[:100] }}...
</p>
</div>
<div class="flex gap-1">
{% for platform in post.platforms %}
<span class="text-xs bg-gray-800 px-2 py-1 rounded">
{{ platform }}
</span>
{% endfor %}
</div>
</div>
{% endfor %}
{% else %}
<p class="text-gray-500">No hay posts programados</p>
{% endif %}
</div>
</div>
<!-- Recent Interactions -->
<div class="card p-6 mt-6">
<h2 class="text-xl font-semibold mb-4">Interacciones Recientes</h2>
{% if recent_interactions %}
<div class="grid gap-4">
{% for interaction in recent_interactions %}
<div class="bg-gray-800 rounded-lg p-4">
<div class="flex justify-between items-start mb-2">
<div>
<span class="font-semibold">@{{ interaction.author_username }}</span>
<span class="text-gray-500 text-sm ml-2">{{ interaction.platform }}</span>
</div>
<span class="text-gray-500 text-xs">{{ interaction.interaction_at }}</span>
</div>
<p class="text-gray-300">{{ interaction.content }}</p>
<div class="mt-3 flex gap-2">
<button class="bg-accent text-gray-900 px-3 py-1 rounded text-sm">
Responder
</button>
<button class="bg-gray-700 px-3 py-1 rounded text-sm">
Marcar como Lead
</button>
</div>
</div>
{% endfor %}
</div>
{% else %}
<p class="text-gray-500">No hay interacciones pendientes</p>
{% endif %}
</div>
</main>
<script>
async function approvePost(postId) {
const response = await fetch(`/api/posts/${postId}/approve`, { method: 'POST' });
if (response.ok) {
location.reload();
}
}
async function rejectPost(postId) {
if (confirm('¿Seguro que quieres rechazar este post?')) {
const response = await fetch(`/api/posts/${postId}/reject`, { method: 'POST' });
if (response.ok) {
location.reload();
}
}
}
function editPost(postId) {
window.location.href = `/posts/${postId}/edit`;
}
</script>
</body>
</html>