feat(scripts): agregar capacidad de eliminar guiones desde el panel

Añade endpoints DELETE para guiones y guiones_generados (Vercel + Express),
métodos en api.js y botón de papelera con confirmación en ScriptsView.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-04-01 15:34:04 -05:00
parent 1953be2da0
commit 5eaa95abf7
5 changed files with 106 additions and 23 deletions

View File

@ -1,16 +1,28 @@
import { supabase } from '../../backend/lib/supabase.js' import { supabase } from '../../backend/lib/supabase.js'
export default async function handler(req, res) { export default async function handler(req, res) {
if (req.method !== 'GET') return res.status(405).json({ error: 'Método no permitido' })
const { id } = req.query const { id } = req.query
const { data, error } = await supabase if (req.method === 'GET') {
.from('guiones_generados') const { data, error } = await supabase
.select('*') .from('guiones_generados')
.eq('id', id) .select('*')
.single() .eq('id', id)
.single()
if (error) return res.status(404).json({ error: 'Guion no encontrado' }) if (error) return res.status(404).json({ error: 'Guion no encontrado' })
res.json({ generado: data }) return res.json({ generado: data })
}
if (req.method === 'DELETE') {
const { error } = await supabase
.from('guiones_generados')
.delete()
.eq('id', id)
if (error) return res.status(500).json({ error: error.message })
return res.json({ ok: true })
}
res.status(405).json({ error: 'Método no permitido' })
} }

View File

@ -1,16 +1,28 @@
import { supabase } from '../../backend/lib/supabase.js' import { supabase } from '../../backend/lib/supabase.js'
export default async function handler(req, res) { export default async function handler(req, res) {
if (req.method !== 'GET') return res.status(405).json({ error: 'Método no permitido' })
const { id } = req.query const { id } = req.query
const { data, error } = await supabase if (req.method === 'GET') {
.from('guiones') const { data, error } = await supabase
.select('*') .from('guiones')
.eq('id', id) .select('*')
.single() .eq('id', id)
.single()
if (error) return res.status(404).json({ error: 'Guion no encontrado' }) if (error) return res.status(404).json({ error: 'Guion no encontrado' })
res.json(data) return res.json(data)
}
if (req.method === 'DELETE') {
const { error } = await supabase
.from('guiones')
.delete()
.eq('id', id)
if (error) return res.status(500).json({ error: error.message })
return res.json({ ok: true })
}
res.status(405).json({ error: 'Método no permitido' })
} }

View File

@ -65,6 +65,17 @@ app.get('/api/guiones/:id', async (req, res) => {
res.json(data) res.json(data)
}) })
// ── DELETE /api/guiones/:id ─────────────────────────────────
app.delete('/api/guiones/:id', async (req, res) => {
const { error } = await supabase
.from('guiones')
.delete()
.eq('id', req.params.id)
if (error) return res.status(500).json({ error: error.message })
res.json({ ok: true })
})
// ── GET /api/nichos ───────────────────────────────────────── // ── GET /api/nichos ─────────────────────────────────────────
// Lista de nichos distintos para el selector del formulario // Lista de nichos distintos para el selector del formulario
app.get('/api/nichos', async (req, res) => { app.get('/api/nichos', async (req, res) => {
@ -348,6 +359,17 @@ app.get('/api/generados/:id', async (req, res) => {
res.json(data) res.json(data)
}) })
// ── DELETE /api/generados/:id ───────────────────────────────
app.delete('/api/generados/:id', async (req, res) => {
const { error } = await supabase
.from('guiones_generados')
.delete()
.eq('id', req.params.id)
if (error) return res.status(500).json({ error: error.message })
res.json({ ok: true })
})
app.listen(PORT, () => console.log(`Backend local corriendo en http://localhost:${PORT}`)) app.listen(PORT, () => console.log(`Backend local corriendo en http://localhost:${PORT}`))
// ── Middleware global de manejo de errores ─────────────────── // ── Middleware global de manejo de errores ───────────────────

View File

@ -28,10 +28,12 @@ export const api = {
listar: (params = {}) => request('/guiones?' + new URLSearchParams(params)), listar: (params = {}) => request('/guiones?' + new URLSearchParams(params)),
listarTodos: (params = {}) => request('/guiones?' + new URLSearchParams({ ...params, todos: '1' })), listarTodos: (params = {}) => request('/guiones?' + new URLSearchParams({ ...params, todos: '1' })),
obtener: (id) => request(`/guiones/${id}`), obtener: (id) => request(`/guiones/${id}`),
eliminar: (id) => request(`/guiones/${id}`, { method: 'DELETE' }),
}, },
generados: { generados: {
listar: (params = {}) => request('/generados?' + new URLSearchParams(params)), listar: (params = {}) => request('/generados?' + new URLSearchParams(params)),
obtener: (id) => request(`/generados/${id}`), obtener: (id) => request(`/generados/${id}`),
eliminar: (id) => request(`/generados/${id}`, { method: 'DELETE' }),
}, },
analizar: (body) => request('/analizar', { method: 'POST', body: JSON.stringify(body) }), analizar: (body) => request('/analizar', { method: 'POST', body: JSON.stringify(body) }),
generar: (body) => request('/generar', { method: 'POST', body: JSON.stringify(body) }), generar: (body) => request('/generar', { method: 'POST', body: JSON.stringify(body) }),

View File

@ -178,9 +178,19 @@
</div> </div>
</template> </template>
<button class="p-1.5 rounded-lg text-ink-3 hover:text-accent hover:bg-accent-subtle transition-all opacity-0 group-hover:opacity-100"> <div class="flex items-center gap-1 opacity-0 group-hover:opacity-100 transition-all">
<span class="material-symbols-outlined text-[16px]">open_in_new</span> <button class="p-1.5 rounded-lg text-ink-3 hover:text-accent hover:bg-accent-subtle transition-all">
</button> <span class="material-symbols-outlined text-[16px]">open_in_new</span>
</button>
<button
@click.stop="confirmarEliminar(g)"
:disabled="eliminandoId === g.id"
class="p-1.5 rounded-lg text-ink-3 hover:text-red-400 hover:bg-red-950/40 transition-all disabled:opacity-50"
>
<span v-if="eliminandoId === g.id" class="material-symbols-outlined text-[16px] animate-spin">progress_activity</span>
<span v-else class="material-symbols-outlined text-[16px]">delete</span>
</button>
</div>
</div> </div>
</div> </div>
</div> </div>
@ -301,6 +311,7 @@ const totalGuiones = ref(0)
const modalGenerado = ref(null) const modalGenerado = ref(null)
const cargandoModal = ref(false) const cargandoModal = ref(false)
const copiadoModal = ref(false) const copiadoModal = ref(false)
const eliminandoId = ref(null)
const filtros = ref({ const filtros = ref({
tipo: 'analizados', tipo: 'analizados',
@ -410,6 +421,30 @@ async function copiarGuionModal() {
setTimeout(() => { copiadoModal.value = false }, 2000) setTimeout(() => { copiadoModal.value = false }, 2000)
} }
async function confirmarEliminar(g) {
const titulo = filtros.value.tipo === 'analizados'
? (g.tema_principal || 'este guion')
: (g.titulo_sugerido || 'este guion')
if (!confirm(`¿Eliminar "${titulo}"? Esta acción no se puede deshacer.`)) return
eliminandoId.value = g.id
try {
if (filtros.value.tipo === 'analizados') {
await api.guiones.eliminar(g.id)
} else {
await api.generados.eliminar(g.id)
}
guiones.value = guiones.value.filter(x => x.id !== g.id)
totalGuiones.value = Math.max(0, totalGuiones.value - 1)
} catch (e) {
console.error(e)
alert('No se pudo eliminar: ' + e.message)
} finally {
eliminandoId.value = null
}
}
function plataformaBadge(p) { function plataformaBadge(p) {
const map = { const map = {
tiktok: 'bg-red-950 text-red-400 border border-red-800', tiktok: 'bg-red-950 text-red-400 border border-red-800',