diff --git a/src/app/api/calendar/month/route.ts b/src/app/api/calendar/month/route.ts index 8a644fd..a7288c1 100644 --- a/src/app/api/calendar/month/route.ts +++ b/src/app/api/calendar/month/route.ts @@ -1,6 +1,6 @@ /** * Create / update editorial month plans and pieces. - * POST { action: 'create'|'upsert_piece'|'delete_piece'|'replace_plan', month, piece?, plan?, syncPipeline? } + * POST { action: 'create'|'upsert_piece'|'delete_piece'|'replace_plan'|'delete_month', month, piece?, plan?, syncPipeline? } */ import { NextRequest, NextResponse } from 'next/server'; import { createClient } from '@/lib/supabase/server'; @@ -364,6 +364,87 @@ export async function POST(req: NextRequest) { }); } + if (action === 'delete_month') { + if (!doc?.id) { + return NextResponse.json( + { error: 'No hay calendario guardado para ese mes' }, + { status: 404 } + ); + } + const pieceIds = (existing?.pieces || []) + .map((p) => p.id) + .filter((id): id is string => Boolean(id)); + + let pipelineDeleted = 0; + let pipelineUnlinked = 0; + + for (const pieceId of pieceIds) { + const list = await fetch( + `${base}/rest/v1/creative_pipeline?tenant_id=eq.${encodeURIComponent( + ctx.tenantId + )}&calendar_piece_id=eq.${encodeURIComponent( + pieceId + )}&select=id,status,publish_status`, + { headers, cache: 'no-store' } + ); + if (!list.ok) continue; + const rows = (await list.json()) as { + id: string; + status?: string; + publish_status?: string; + }[]; + for (const row of rows) { + const published = + row.publish_status === 'published' || + row.status === 'publicada' || + row.status === 'publicado'; + if (published) { + const patch = await fetch( + `${base}/rest/v1/creative_pipeline?id=eq.${encodeURIComponent(row.id)}`, + { + method: 'PATCH', + headers: { + ...headers, + 'Content-Type': 'application/json', + Prefer: 'return=minimal', + }, + body: JSON.stringify({ + calendar_piece_id: null, + updated_at: new Date().toISOString(), + }), + } + ); + if (patch.ok) pipelineUnlinked += 1; + } else { + const del = await fetch( + `${base}/rest/v1/creative_pipeline?id=eq.${encodeURIComponent(row.id)}`, + { method: 'DELETE', headers } + ); + if (del.ok) pipelineDeleted += 1; + } + } + } + + const delDoc = await fetch( + `${base}/rest/v1/documents?id=eq.${encodeURIComponent(doc.id)}&tenant_id=eq.${encodeURIComponent(ctx.tenantId)}`, + { method: 'DELETE', headers } + ); + if (!delDoc.ok) { + throw new Error( + (await delDoc.text()).slice(0, 300) || 'No se pudo borrar el documento' + ); + } + + return NextResponse.json({ + ok: true, + deleted: true, + month, + document_id: doc.id, + pipeline_deleted: pipelineDeleted, + pipeline_unlinked: pipelineUnlinked, + }); + } + return NextResponse.json({ error: 'action desconocida' }, { status: 400 }); } catch (e) { return NextResponse.json( diff --git a/src/app/dashboard/calendario/page.tsx b/src/app/dashboard/calendario/page.tsx index 922bb41..d785915 100644 --- a/src/app/dashboard/calendario/page.tsx +++ b/src/app/dashboard/calendario/page.tsx @@ -12,6 +12,7 @@ import { Instagram, Linkedin } from 'lucide-react'; import { PageHeader } from '@/components/ui'; import CalendarUpload from '@/components/CalendarUpload'; import CalendarCreator from '@/components/CalendarCreator'; +import DeleteCalendarMonthButton from '@/components/DeleteCalendarMonthButton'; import { mergePipelineIntoPlans, type PipelineCalendarRow, @@ -229,6 +230,9 @@ export default async function CalendarioPage({ tenantLabel={ctx.label} existingMonths={plans.map((p) => p.month).filter(Boolean)} /> + {active.month ? ( + + ) : null} ) : null} Guardar mes en portal + {existingMonths.includes(month) || source === 'existing' ? ( + + ) : null} {plan?.pieces?.length || 0} piezas {keepIds.size ? ` · ${keepIds.size} ✓` : ''} diff --git a/src/components/DeleteCalendarMonthButton.tsx b/src/components/DeleteCalendarMonthButton.tsx new file mode 100644 index 0000000..8fb1884 --- /dev/null +++ b/src/components/DeleteCalendarMonthButton.tsx @@ -0,0 +1,75 @@ +'use client'; + +import React, { useState } from 'react'; +import { useRouter } from 'next/navigation'; +import { Loader2, Trash2 } from 'lucide-react'; +import { monthLabel } from '@/lib/calendar-import'; + +/** Agency-only: wipe an editorial calendar month document (+ draft pipeline links). */ +export default function DeleteCalendarMonthButton({ + month, + compact = false, +}: { + month: string; + compact?: boolean; +}) { + const router = useRouter(); + const [busy, setBusy] = useState(false); + const [err, setErr] = useState(null); + + async function onDelete() { + const label = monthLabel(month); + const ok = window.confirm( + `¿Borrar por completo el calendario de ${label}?\n\nSe elimina el mes del portal y las piezas de Pipeline RRSS en borrador vinculadas. Las ya publicadas en Meta no se borran de la red (solo se desvinculan).` + ); + if (!ok) return; + const typed = window.prompt( + `Escribe ${month} para confirmar el borrado definitivo:` + ); + if (typed?.trim() !== month) { + setErr('Confirmación cancelada o incorrecta.'); + return; + } + setBusy(true); + setErr(null); + try { + const r = await fetch('/api/calendar/month', { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ action: 'delete_month', month }), + }); + const j = await r.json(); + if (!r.ok) throw new Error(j.error || 'No se pudo borrar'); + router.push('/dashboard/calendario?view=archive'); + router.refresh(); + } catch (e) { + setErr(e instanceof Error ? e.message : 'Error al borrar'); + } finally { + setBusy(false); + } + } + + return ( +
+ + {err ?

{err}

: null} +
+ ); +} diff --git a/src/components/RrssArchiveYear.tsx b/src/components/RrssArchiveYear.tsx index e01c72a..b943b87 100644 --- a/src/components/RrssArchiveYear.tsx +++ b/src/components/RrssArchiveYear.tsx @@ -3,6 +3,7 @@ import Link from 'next/link'; import type { RrssMonthPlan, RrssPiece } from '@/components/RrssMonthView'; import CalendarCreator from '@/components/CalendarCreator'; import CalendarUpload from '@/components/CalendarUpload'; +import DeleteCalendarMonthButton from '@/components/DeleteCalendarMonthButton'; const MONTH_NAMES = [ 'Enero', @@ -35,10 +36,12 @@ function MiniMonth({ year, monthIndex, plan, + canDelete = false, }: { year: number; monthIndex: number; // 0-11 plan?: RrssMonthPlan; + canDelete?: boolean; }) { const monthKey = `${year}-${String(monthIndex + 1).padStart(2, '0')}`; const first = new Date(Date.UTC(year, monthIndex, 1)); @@ -126,12 +129,17 @@ function MiniMonth({ if (live) { return ( - - {body} - +
+ + {body} + + {canDelete ? ( + + ) : null} +
); } @@ -237,6 +245,7 @@ export default function RrssArchiveYear({ year={year} monthIndex={i} plan={byMonth.get(key)} + canDelete={canUpload} /> ); })}