Files
social-media-automation/dashboard/templates/compose.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

349 lines
16 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
{% extends "base.html" %}
{% block title %}Crear Post{% endblock %}
{% block content %}
<div class="animate-fade-in">
<!-- Header -->
<div class="mb-8">
<h1 class="text-3xl font-bold">Crear Publicación</h1>
<p class="text-gray-400 mt-1">Genera y programa contenido para redes sociales</p>
</div>
<div class="grid grid-cols-1 lg:grid-cols-3 gap-6">
<!-- Main Form -->
<div class="lg:col-span-2 space-y-6">
<!-- Platform Selection -->
<div class="card rounded-2xl p-6">
<h3 class="font-semibold mb-4 flex items-center gap-2">
<span>📱</span>
<span>Plataformas</span>
</h3>
<div class="flex flex-wrap gap-3">
<button type="button" onclick="togglePlatform('x')" id="btn-x"
class="platform-btn px-5 py-3 rounded-xl bg-dark-800 hover:bg-dark-700 flex items-center gap-2 transition-all border-2 border-transparent">
<span class="text-xl">𝕏</span>
<span>Twitter/X</span>
</button>
<button type="button" onclick="togglePlatform('facebook')" id="btn-facebook"
class="platform-btn px-5 py-3 rounded-xl bg-dark-800 hover:bg-dark-700 flex items-center gap-2 transition-all border-2 border-transparent">
<span class="text-xl">📘</span>
<span>Facebook</span>
</button>
<button type="button" onclick="togglePlatform('instagram')" id="btn-instagram"
class="platform-btn px-5 py-3 rounded-xl bg-dark-800 hover:bg-dark-700 flex items-center gap-2 transition-all border-2 border-transparent">
<span class="text-xl">📸</span>
<span>Instagram</span>
</button>
<button type="button" onclick="togglePlatform('threads')" id="btn-threads"
class="platform-btn px-5 py-3 rounded-xl bg-dark-800 hover:bg-dark-700 flex items-center gap-2 transition-all border-2 border-transparent">
<span class="text-xl">🧵</span>
<span>Threads</span>
</button>
</div>
</div>
<!-- Content -->
<div class="card rounded-2xl p-6">
<h3 class="font-semibold mb-4 flex items-center gap-2">
<span>✍️</span>
<span>Contenido</span>
</h3>
<textarea
id="content"
rows="6"
class="w-full bg-dark-800 border border-dark-600 rounded-xl px-4 py-3 text-white placeholder-gray-500 focus:border-primary focus:outline-none resize-none transition-colors"
placeholder="Escribe tu publicación aquí..."
oninput="updateCharCount()"
></textarea>
<div class="flex justify-between items-center mt-3">
<div id="char-count" class="text-sm text-gray-500">0 / 280</div>
<div class="flex gap-2">
<button type="button" onclick="generateWithAI('tip')" class="btn-secondary px-3 py-1 rounded-lg text-sm flex items-center gap-1">
<span>💡</span> Generar Tip
</button>
<button type="button" onclick="generateWithAI('promo')" class="btn-secondary px-3 py-1 rounded-lg text-sm flex items-center gap-1">
<span>📢</span> Generar Promo
</button>
</div>
</div>
</div>
<!-- Hashtags -->
<div class="card rounded-2xl p-6">
<h3 class="font-semibold mb-4 flex items-center gap-2">
<span>#️⃣</span>
<span>Hashtags</span>
</h3>
<input
type="text"
id="hashtags"
class="w-full bg-dark-800 border border-dark-600 rounded-xl px-4 py-3 text-white placeholder-gray-500 focus:border-primary focus:outline-none transition-colors"
placeholder="#Tecnología #Automatización #Tijuana"
>
<div class="flex flex-wrap gap-2 mt-3">
<button type="button" onclick="addHashtag('#ConsultoríaAS')" class="text-xs bg-dark-700 px-2 py-1 rounded-full hover:bg-dark-600 transition-colors">#ConsultoríaAS</button>
<button type="button" onclick="addHashtag('#Tijuana')" class="text-xs bg-dark-700 px-2 py-1 rounded-full hover:bg-dark-600 transition-colors">#Tijuana</button>
<button type="button" onclick="addHashtag('#Tecnología')" class="text-xs bg-dark-700 px-2 py-1 rounded-full hover:bg-dark-600 transition-colors">#Tecnología</button>
<button type="button" onclick="addHashtag('#Automatización')" class="text-xs bg-dark-700 px-2 py-1 rounded-full hover:bg-dark-600 transition-colors">#Automatización</button>
</div>
</div>
<!-- Schedule -->
<div class="card rounded-2xl p-6">
<h3 class="font-semibold mb-4 flex items-center gap-2">
<span>📅</span>
<span>Programación</span>
</h3>
<div class="grid grid-cols-2 gap-4">
<div>
<label class="block text-sm text-gray-400 mb-2">Fecha</label>
<input
type="date"
id="schedule-date"
class="w-full bg-dark-800 border border-dark-600 rounded-xl px-4 py-3 text-white focus:border-primary focus:outline-none transition-colors"
>
</div>
<div>
<label class="block text-sm text-gray-400 mb-2">Hora</label>
<input
type="time"
id="schedule-time"
class="w-full bg-dark-800 border border-dark-600 rounded-xl px-4 py-3 text-white focus:border-primary focus:outline-none transition-colors"
>
</div>
</div>
</div>
</div>
<!-- Sidebar -->
<div class="space-y-6">
<!-- Preview -->
<div class="card rounded-2xl p-6">
<h3 class="font-semibold mb-4 flex items-center gap-2">
<span>👁️</span>
<span>Vista Previa</span>
</h3>
<div id="preview" class="bg-dark-800 rounded-xl p-4 min-h-32">
<p class="text-gray-500 text-sm italic">El contenido aparecerá aquí...</p>
</div>
</div>
<!-- Actions -->
<div class="card rounded-2xl p-6">
<h3 class="font-semibold mb-4 flex items-center gap-2">
<span></span>
<span>Acciones</span>
</h3>
<div class="space-y-3">
<button onclick="saveAsDraft()" class="w-full btn-secondary px-4 py-3 rounded-xl flex items-center justify-center gap-2">
<span>💾</span>
<span>Guardar Borrador</span>
</button>
<button onclick="schedulePost()" class="w-full btn-secondary px-4 py-3 rounded-xl flex items-center justify-center gap-2">
<span>📅</span>
<span>Programar</span>
</button>
<button onclick="publishNow()" class="w-full btn-primary px-4 py-3 rounded-xl flex items-center justify-center gap-2 font-medium">
<span>🚀</span>
<span>Publicar Ahora</span>
</button>
</div>
</div>
<!-- AI Status -->
<div class="card rounded-2xl p-6">
<h3 class="font-semibold mb-4 flex items-center gap-2">
<span>🤖</span>
<span>IA</span>
</h3>
<div id="ai-status" class="text-sm text-gray-400">
Verificando...
</div>
</div>
</div>
</div>
</div>
{% endblock %}
{% block extra_scripts %}
<script>
let selectedPlatforms = [];
const charLimits = { x: 280, facebook: 63206, instagram: 2200, threads: 500 };
function togglePlatform(platform) {
const btn = document.getElementById(`btn-${platform}`);
const index = selectedPlatforms.indexOf(platform);
if (index > -1) {
selectedPlatforms.splice(index, 1);
btn.classList.remove('border-primary', 'bg-primary/20');
btn.classList.add('border-transparent');
} else {
selectedPlatforms.push(platform);
btn.classList.add('border-primary', 'bg-primary/20');
btn.classList.remove('border-transparent');
}
updateCharCount();
}
function updateCharCount() {
const content = document.getElementById('content').value;
const countEl = document.getElementById('char-count');
const previewEl = document.getElementById('preview');
const limit = selectedPlatforms.includes('x') ? 280 :
selectedPlatforms.includes('threads') ? 500 : 2200;
countEl.textContent = `${content.length} / ${limit}`;
if (content.length > limit) {
countEl.classList.add('text-red-400');
countEl.classList.remove('text-yellow-400', 'text-gray-500');
} else if (content.length > limit * 0.9) {
countEl.classList.add('text-yellow-400');
countEl.classList.remove('text-red-400', 'text-gray-500');
} else {
countEl.classList.add('text-gray-500');
countEl.classList.remove('text-red-400', 'text-yellow-400');
}
// Update preview
if (content) {
previewEl.innerHTML = `<p class="text-white whitespace-pre-wrap">${content}</p>`;
} else {
previewEl.innerHTML = `<p class="text-gray-500 text-sm italic">El contenido aparecerá aquí...</p>`;
}
}
function addHashtag(tag) {
const input = document.getElementById('hashtags');
if (!input.value.includes(tag)) {
input.value = input.value ? `${input.value} ${tag}` : tag;
}
}
async function generateWithAI(type) {
showModal('Generando contenido con IA...', true);
try {
const response = await fetch(`/api/generate/${type}`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ platforms: selectedPlatforms.length ? selectedPlatforms : ['x'] })
});
const data = await response.json();
if (data.success && data.content) {
document.getElementById('content').value = data.content;
updateCharCount();
closeModal();
} else {
showModal(`<div class="text-center"><span class="text-4xl mb-4 block">❌</span><p>Error generando contenido</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 saveAsDraft() {
const content = document.getElementById('content').value;
if (!content) {
showModal(`<div class="text-center"><span class="text-4xl mb-4 block">⚠️</span><p>Escribe algo primero</p></div>`);
return;
}
await savePost('draft');
}
async function schedulePost() {
const content = document.getElementById('content').value;
const date = document.getElementById('schedule-date').value;
const time = document.getElementById('schedule-time').value;
if (!content) {
showModal(`<div class="text-center"><span class="text-4xl mb-4 block">⚠️</span><p>Escribe algo primero</p></div>`);
return;
}
if (!date || !time) {
showModal(`<div class="text-center"><span class="text-4xl mb-4 block">⚠️</span><p>Selecciona fecha y hora</p></div>`);
return;
}
await savePost('scheduled', `${date}T${time}:00`);
}
async function publishNow() {
const content = document.getElementById('content').value;
if (!content) {
showModal(`<div class="text-center"><span class="text-4xl mb-4 block">⚠️</span><p>Escribe algo primero</p></div>`);
return;
}
if (!selectedPlatforms.length) {
showModal(`<div class="text-center"><span class="text-4xl mb-4 block">⚠️</span><p>Selecciona al menos una plataforma</p></div>`);
return;
}
showModal('Publicando...', true);
try {
const response = await fetch('/api/publish/single', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
content: content,
platforms: selectedPlatforms,
hashtags: document.getElementById('hashtags').value.split(' ').filter(h => h)
})
});
const data = await response.json();
if (data.success) {
showModal(`<div class="text-center"><span class="text-4xl mb-4 block">🎉</span><p class="text-lg">¡Publicado!</p><p class="text-gray-400 mt-2">${data.message || 'Post publicado correctamente'}</p></div>`);
document.getElementById('content').value = '';
updateCharCount();
} else {
showModal(`<div class="text-center"><span class="text-4xl mb-4 block">❌</span><p>Error: ${data.detail || data.error || 'Error al publicar'}</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 savePost(status, scheduledAt = null) {
showModal('Guardando...', true);
try {
const body = {
content: document.getElementById('content').value,
platforms: selectedPlatforms.length ? selectedPlatforms : ['x'],
hashtags: document.getElementById('hashtags').value.split(' ').filter(h => h),
status: status
};
if (scheduledAt) body.scheduled_at = scheduledAt;
const response = await fetch('/api/posts/', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(body)
});
const data = await response.json();
showModal(`<div class="text-center"><span class="text-4xl mb-4 block">✅</span><p class="text-lg">Guardado</p><p class="text-gray-400 mt-2">Post ${status === 'scheduled' ? 'programado' : 'guardado como borrador'}</p></div>`);
} catch (error) {
showModal(`<div class="text-center"><span class="text-4xl mb-4 block">❌</span><p>Error guardando</p></div>`);
}
}
// Check AI status
async function checkAIStatus() {
try {
const response = await fetch('/api/generate/status');
const data = await response.json();
document.getElementById('ai-status').innerHTML = data.configured
? `<span class="text-green-400">✓</span> ${data.provider || 'DeepSeek'} configurado`
: `<span class="text-red-400">✗</span> No configurado`;
} catch (error) {
document.getElementById('ai-status').innerHTML = `<span class="text-red-400">✗</span> Error`;
}
}
checkAIStatus();
</script>
{% endblock %}