Enable live Meta caption updates and partial IG/FB deletes from the pipeline.
Facebook edits in place when possible; Instagram deletes and republishes. Trash and delete-meta no longer block when only one network succeeds. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -573,8 +573,9 @@ export default function CreativePipelineBoard() {
|
||||
async function removeItem(id: string, alsoMeta = false) {
|
||||
setBusyId(id);
|
||||
setErr(null);
|
||||
setWarning(null);
|
||||
try {
|
||||
const target = items.find((x) => x.id === id);
|
||||
const target = items.find((x) => x.id === id) || previewItem;
|
||||
if (
|
||||
alsoMeta &&
|
||||
(target?.meta_ig_media_id || target?.meta_fb_post_id)
|
||||
@@ -585,14 +586,31 @@ export default function CreativePipelineBoard() {
|
||||
body: JSON.stringify({ id, action: 'delete_meta' }),
|
||||
});
|
||||
const delData = await del.json();
|
||||
if (!del.ok) {
|
||||
// Partial Meta delete still allows removing the pipeline row.
|
||||
if (!del.ok && !delData.partial && !delData.igDeleted && !delData.fbDeleted) {
|
||||
setErr(delData.error || 'No se pudo borrar en Meta');
|
||||
if (delData.item && previewItem?.id === id) {
|
||||
setPreviewItem({ ...previewItem, ...delData.item });
|
||||
}
|
||||
await load();
|
||||
return;
|
||||
}
|
||||
if (delData.warning || delData.partial) {
|
||||
setWarning(
|
||||
`Borrado en redes parcial: ${delData.warning || delData.error || 'revisa IG/FB'}. Eliminando del pipeline…`
|
||||
);
|
||||
}
|
||||
}
|
||||
const delRow = await fetch(
|
||||
`/api/creative-pipeline?id=${encodeURIComponent(id)}`,
|
||||
{ method: 'DELETE' }
|
||||
);
|
||||
if (!delRow.ok) {
|
||||
const j = await delRow.json().catch(() => ({}));
|
||||
setErr(j.error || 'No se pudo eliminar la pieza del pipeline');
|
||||
await load();
|
||||
return;
|
||||
}
|
||||
await fetch(`/api/creative-pipeline?id=${encodeURIComponent(id)}`, {
|
||||
method: 'DELETE',
|
||||
});
|
||||
if (previewItem?.id === id) setPreviewItem(null);
|
||||
await load();
|
||||
} finally {
|
||||
@@ -632,13 +650,14 @@ export default function CreativePipelineBoard() {
|
||||
if (!isAgency) return;
|
||||
if (
|
||||
!window.confirm(
|
||||
'¿Borrar esta publicación en Instagram/Facebook? La pieza seguirá en el pipeline.'
|
||||
'¿Borrar esta publicación en Instagram y Facebook? La pieza seguirá en el pipeline.'
|
||||
)
|
||||
) {
|
||||
return;
|
||||
}
|
||||
setBusyId(id);
|
||||
setErr(null);
|
||||
setWarning(null);
|
||||
try {
|
||||
const res = await fetch('/api/creative-pipeline/publish', {
|
||||
method: 'POST',
|
||||
@@ -646,10 +665,58 @@ export default function CreativePipelineBoard() {
|
||||
body: JSON.stringify({ id, action: 'delete_meta' }),
|
||||
});
|
||||
const data = await res.json();
|
||||
if (!res.ok) {
|
||||
if (!res.ok && !data.partial) {
|
||||
setErr(data.error || 'No se pudo borrar en Meta');
|
||||
if (data.item && previewItem?.id === id) {
|
||||
setPreviewItem({ ...previewItem, ...data.item });
|
||||
}
|
||||
await load();
|
||||
return;
|
||||
}
|
||||
if (data.warning || data.partial) {
|
||||
setWarning(
|
||||
`Borrado parcial — IG: ${data.igDeleted ? 'ok' : 'falló'} · FB: ${
|
||||
data.fbDeleted ? 'ok' : 'falló'
|
||||
}. ${data.warning || data.error || ''}`
|
||||
);
|
||||
}
|
||||
if (data.item && previewItem?.id === id) {
|
||||
setPreviewItem({ ...previewItem, ...data.item });
|
||||
}
|
||||
await load();
|
||||
} finally {
|
||||
setBusyId(null);
|
||||
}
|
||||
}
|
||||
|
||||
async function updateLiveOnMeta(id: string, caption: string) {
|
||||
if (!isAgency) return;
|
||||
if (
|
||||
!window.confirm(
|
||||
'¿Actualizar en redes?\n\n• Facebook: intenta editar el texto.\n• Instagram: borra y republica (Meta no permite editar captions).'
|
||||
)
|
||||
) {
|
||||
return;
|
||||
}
|
||||
setBusyId(id);
|
||||
setErr(null);
|
||||
setWarning(null);
|
||||
try {
|
||||
const res = await fetch('/api/creative-pipeline/publish', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ id, action: 'update_live', caption }),
|
||||
});
|
||||
const data = await res.json();
|
||||
if (!res.ok) {
|
||||
setErr(data.error || 'No se pudo actualizar en redes');
|
||||
if (data.item && previewItem?.id === id) {
|
||||
setPreviewItem({ ...previewItem, ...data.item });
|
||||
}
|
||||
await load();
|
||||
return;
|
||||
}
|
||||
if (data.detail) setWarning(`Actualizado en redes: ${data.detail}`);
|
||||
if (data.item && previewItem?.id === id) {
|
||||
setPreviewItem({ ...previewItem, ...data.item });
|
||||
}
|
||||
@@ -1160,6 +1227,9 @@ export default function CreativePipelineBoard() {
|
||||
onSaveContent={(payload) => saveContent(previewItem.id, payload)}
|
||||
onPublishNow={() => publishNow(previewItem.id)}
|
||||
onDeleteMeta={() => deleteOnMeta(previewItem.id)}
|
||||
onUpdateLive={(caption) =>
|
||||
updateLiveOnMeta(previewItem.id, caption)
|
||||
}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
@@ -1464,6 +1534,7 @@ function DetailModal({
|
||||
onSaveContent,
|
||||
onPublishNow,
|
||||
onDeleteMeta,
|
||||
onUpdateLive,
|
||||
}: {
|
||||
item: PipelineItem;
|
||||
isAgency: boolean;
|
||||
@@ -1484,6 +1555,7 @@ function DetailModal({
|
||||
}) => void;
|
||||
onPublishNow: () => void;
|
||||
onDeleteMeta: () => void;
|
||||
onUpdateLive: (caption: string) => void;
|
||||
}) {
|
||||
const slides = item.slide_paths || [];
|
||||
const allImages = [item.cover_path, ...slides].filter(Boolean) as string[];
|
||||
@@ -1753,14 +1825,30 @@ function DetailModal({
|
||||
Sin enlace a calendario — solo se guarda en el pipeline.
|
||||
</p>
|
||||
)}
|
||||
<button
|
||||
type="button"
|
||||
disabled={busy || !editTitle.trim()}
|
||||
onClick={submitContent}
|
||||
className="w-full rounded-lg bg-gray-900 px-3 py-2 text-xs font-semibold text-white hover:bg-gray-800 disabled:opacity-60"
|
||||
>
|
||||
Guardar cambios
|
||||
</button>
|
||||
<div className="flex flex-col gap-2">
|
||||
<button
|
||||
type="button"
|
||||
disabled={busy || !editTitle.trim()}
|
||||
onClick={submitContent}
|
||||
className="w-full rounded-lg bg-gray-900 px-3 py-2 text-xs font-semibold text-white hover:bg-gray-800 disabled:opacity-60"
|
||||
>
|
||||
Guardar cambios (portal)
|
||||
</button>
|
||||
{(item.meta_ig_media_id || item.meta_fb_post_id) && (
|
||||
<button
|
||||
type="button"
|
||||
disabled={busy || !editTitle.trim()}
|
||||
onClick={() => onUpdateLive(editCaption)}
|
||||
className="w-full rounded-lg border border-violet-300 bg-violet-50 px-3 py-2 text-xs font-semibold text-violet-900 hover:bg-violet-100 disabled:opacity-60"
|
||||
>
|
||||
Actualizar en Instagram / Facebook
|
||||
</button>
|
||||
)}
|
||||
<p className="text-[10px] text-gray-500 leading-relaxed">
|
||||
En redes: FB edita el texto si puede; IG siempre republica
|
||||
(Meta no deja editar captions publicados).
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
) : item.caption ? (
|
||||
<div className="space-y-1">
|
||||
@@ -1825,7 +1913,7 @@ function DetailModal({
|
||||
busy ||
|
||||
!item.cover_path ||
|
||||
item.publish_status === 'publishing' ||
|
||||
item.publish_status === 'published'
|
||||
Boolean(item.meta_ig_media_id && item.meta_fb_post_id)
|
||||
}
|
||||
onClick={onPublishNow}
|
||||
className="inline-flex w-full items-center justify-center gap-1.5 rounded-lg bg-gradient-to-r from-purple-600 via-pink-600 to-amber-500 px-3 py-2 text-xs font-bold text-white shadow-sm hover:opacity-95 disabled:opacity-50"
|
||||
@@ -1833,9 +1921,11 @@ function DetailModal({
|
||||
<Send className="h-3.5 w-3.5" />
|
||||
{item.publish_status === 'publishing'
|
||||
? 'Publicando…'
|
||||
: item.publish_status === 'published'
|
||||
? 'Ya publicado en redes'
|
||||
: 'Publicar ahora (IG + FB)'}
|
||||
: item.meta_ig_media_id && item.meta_fb_post_id
|
||||
? 'Ya publicado en IG + FB'
|
||||
: item.meta_ig_media_id || item.meta_fb_post_id
|
||||
? 'Completar publicación (red faltante)'
|
||||
: 'Publicar ahora (IG + FB)'}
|
||||
</button>
|
||||
{(item.meta_ig_media_id || item.meta_fb_post_id) && (
|
||||
<button
|
||||
|
||||
Reference in New Issue
Block a user