diff --git a/src/app/api/calendar/month/route.ts b/src/app/api/calendar/month/route.ts index a7288c1..6571550 100644 --- a/src/app/api/calendar/month/route.ts +++ b/src/app/api/calendar/month/route.ts @@ -130,6 +130,16 @@ function normalizePiece( hook: str(raw.hook, 300), body: cleanBody, }); + const slide_copy = Array.isArray(raw.slide_copy) + ? (raw.slide_copy as { n?: unknown; label?: unknown; text?: unknown }[]) + .map((s, i) => ({ + n: Number(s.n) || i + 1, + label: s.label != null ? String(s.label).slice(0, 80) : undefined, + text: String(s.text || '').trim().slice(0, 800), + })) + .filter((s) => s.text) + : undefined; + return { id: str(raw.id, 120) || newPieceId(date, title), date, @@ -151,6 +161,9 @@ function normalizePiece( : /linkedin/i.test(enriched.platform || '') ? '#7C66B8' : '#2071cf', + slide_copy: slide_copy?.length ? slide_copy : undefined, + reel_script: str(raw.reel_script, 6000) || undefined, + interactive_story: str(raw.interactive_story, 40) || undefined, }; } diff --git a/src/app/api/calendar/preview/route.ts b/src/app/api/calendar/preview/route.ts index 45652c6..678bd46 100644 --- a/src/app/api/calendar/preview/route.ts +++ b/src/app/api/calendar/preview/route.ts @@ -44,6 +44,8 @@ export async function POST(req: NextRequest) { const body = (await req.json().catch(() => ({}))) as { month?: string; avoid_titles?: string[]; + gen_options?: Record; + options?: Record; }; const month = String(body.month || '').slice(0, 7); if (!/^\d{4}-\d{2}$/.test(month)) { @@ -55,6 +57,9 @@ export async function POST(req: NextRequest) { .map((t) => t.trim().slice(0, 160)) .slice(0, 40) : []; + const genOptions = (body.gen_options || body.options || null) as + | Record + | null; let slug = 'cliente'; const t = await restGet<{ slug?: string }[]>( @@ -132,6 +137,7 @@ export async function POST(req: NextRequest) { brandbook, publishedPosts, avoidTitles, + genOptions: genOptions || undefined, }); return NextResponse.json({ diff --git a/src/app/dashboard/calendario/page.tsx b/src/app/dashboard/calendario/page.tsx index 269c004..f4b8d6f 100644 --- a/src/app/dashboard/calendario/page.tsx +++ b/src/app/dashboard/calendario/page.tsx @@ -302,7 +302,7 @@ export default async function CalendarioPage({ {agency ? ( -
+
p.month).filter(Boolean)} @@ -345,14 +345,14 @@ export default async function CalendarioPage({ return (
{agency ? ( -
+
+ {active.month ? ( + + ) : null} p.month).filter(Boolean)} /> - {active.month ? ( - - ) : null}
) : null} (null); const [dayZoom, setDayZoom] = useState(null); const [form, setForm] = useState(null); - /** Piezas a conservar (✓) en selección parcial */ - const [keepIds, setKeepIds] = useState>(new Set()); + /** Piezas marcadas para regenerar (✓) */ + const [regenIds, setRegenIds] = useState>(new Set()); + const [genOptions, setGenOptions] = useState({ + ...DEFAULT_GEN_OPTIONS, + }); + const [showGenPanel, setShowGenPanel] = useState(true); const monthChoices = useMemo(() => { const base = ymNow(); @@ -168,7 +178,7 @@ export default function CalendarCreator({ setRationale(''); setSource(''); } - setKeepIds(new Set()); + setRegenIds(new Set()); setMonth(ym); setStep('edit'); } catch (e) { @@ -178,8 +188,8 @@ export default function CalendarCreator({ } } - function toggleKeep(id: string) { - setKeepIds((prev) => { + function toggleRegen(id: string) { + setRegenIds((prev) => { const next = new Set(prev); if (next.has(id)) next.delete(id); else next.add(id); @@ -187,10 +197,10 @@ export default function CalendarCreator({ }); } - function toggleKeepDay(date: string) { + function toggleRegenDay(date: string) { const ids = (byDate.get(date) || []).map((p) => p.id); if (!ids.length) return; - setKeepIds((prev) => { + setRegenIds((prev) => { const next = new Set(prev); const allOn = ids.every((id) => next.has(id)); if (allOn) ids.forEach((id) => next.delete(id)); @@ -199,10 +209,19 @@ export default function CalendarCreator({ }); } - async function runPreview(mode: 'replace' | 'regen_unkept' = 'replace') { + async function runPreview(mode: 'replace' | 'regen_selected' = 'replace') { setBusy(true); setErr(null); try { + const opts = normalizeGenOptions(genOptions); + const keepIds = + mode === 'regen_selected' && plan?.pieces?.length + ? new Set( + (plan.pieces || []) + .filter((p) => !regenIds.has(p.id)) + .map((p) => p.id) + ) + : new Set(); const keptPieces = (plan?.pieces || []).filter((p) => keepIds.has(p.id)); const r = await fetch('/api/calendar/preview', { method: 'POST', @@ -212,29 +231,44 @@ export default function CalendarCreator({ avoid_titles: keptPieces .map((p) => p.title || p.hook || '') .filter(Boolean), + gen_options: { + ...opts, + regenInstructions: + mode === 'regen_selected' + ? opts.regenInstructions || opts.instructions + : opts.instructions, + }, }), }); const j = await r.json(); if (!r.ok) throw new Error(j.error || 'No se pudo generar preview'); - if (mode === 'regen_unkept' && plan?.pieces?.length && keepIds.size > 0) { - if (keepIds.size >= plan.pieces.length) { - throw new Error( - 'Desmarca al menos una pieza (o un día) para regenerar solo las malas.' - ); + if (mode === 'regen_selected' && plan?.pieces?.length) { + if (regenIds.size === 0) { + throw new Error('Marca al menos una pieza (o un día) para regenerar.'); + } + if (regenIds.size >= plan.pieces.length) { + setPlan(j.plan); + setRationale(j.rationale || ''); + setSource(j.source || 'heuristic'); + setRegenIds(new Set()); + } else { + const merged = mergeKeepAndRegenerate(plan, j.plan, keepIds); + setPlan(merged); + setRationale( + `${j.rationale || ''} + +Regeneradas: ${regenIds.size} · Conservadas: ${ + keepIds.size + }.` + ); + setSource(`${j.source || 'heuristic'}+regen`); + setRegenIds(new Set()); } - const merged = mergeKeepAndRegenerate(plan, j.plan, keepIds); - setPlan(merged); - setRationale( - `${j.rationale || ''}\n\nSelección: ${keepIds.size} conservadas · ${ - merged.pieces.length - keepIds.size - } regeneradas.` - ); - setSource(`${j.source || 'heuristic'}+parcial`); } else { setPlan(j.plan); setRationale(j.rationale || ''); setSource(j.source || 'heuristic'); - setKeepIds(new Set()); + setRegenIds(new Set()); } } catch (e) { setErr(e instanceof Error ? e.message : 'Error preview'); @@ -243,20 +277,25 @@ export default function CalendarCreator({ } } - function keepOnlySelected() { + function dropSelected() { if (!plan?.pieces?.length) return; - if (keepIds.size === 0) { - setErr('Marca al menos una pieza o un día para quedarte solo con eso.'); + if (regenIds.size === 0) { + setErr('Marca las piezas que quieres eliminar del preview.'); return; } - setPlan(keepOnlyPieces(plan, keepIds)); + const keep = new Set( + (plan.pieces || []).filter((p) => !regenIds.has(p.id)).map((p) => p.id) + ); + setPlan(keepOnlyPieces(plan, keep)); setRationale( - `Selección manual: te quedas con ${keepIds.size} pieza(s). Guarda el mes cuando quieras.` + `Eliminadas ${regenIds.size} pieza(s) del preview. Guarda el mes cuando quieras.` ); setSource('seleccion'); + setRegenIds(new Set()); setErr(null); } + async function savePlan(force = false) { if (!plan) return; setBusy(true); @@ -415,7 +454,7 @@ export default function CalendarCreator({ setDayZoom(null); setForm(null); }} - className="inline-flex items-center gap-2 rounded-xl bg-gray-900 px-4 py-2.5 text-sm font-semibold text-white hover:bg-gray-800" + className="ml-auto inline-flex items-center gap-2 rounded-xl bg-gray-900 px-4 py-2.5 text-sm font-semibold text-white hover:bg-gray-800" > Crear nuevo calendario @@ -506,21 +545,21 @@ export default function CalendarCreator({
+ {showGenPanel ? ( +
+
+

+ Opciones de generación +

+ +
+
+ {( + [ + ['posts', 'Posts'], + ['stories', 'Stories'], + ['reels', 'Reels'], + ['carruseles', 'Carruseles'], + ] as const + ).map(([key, label]) => ( + + ))} +
+
+

+ Días de la semana +

+
+ {WEEKDAY_LABELS.map(({ key, label }) => { + const on = genOptions.weekdays.includes(key); + return ( + + ); + })} +
+
+ +