Keep editorial calendars private to each tenant.

Stop seeding Agua Miia’s October plan for every client and restrict the portal publish action to that tenant only.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
4Ventures
2026-10-06 12:25:47 +02:00
parent 5b21ffce94
commit ead310e783
3 changed files with 55 additions and 9 deletions

View File

@@ -30,6 +30,20 @@ export async function POST() {
);
}
const t = await restGet<{ slug?: string; name?: string }[]>(
`/rest/v1/tenants?id=eq.${encodeURIComponent(ctx.tenantId)}&select=slug,name`
);
const slug = t.data?.[0]?.slug || '';
if (slug !== 'agua-miia') {
return NextResponse.json(
{
error:
'Este botón solo carga el plan de octubre de Agua Miia en su propio portal. No publica en Instagram ni copia el calendario a otros clientes.',
},
{ status: 400 }
);
}
const headers = serviceHeaders();
if (!headers) {
return NextResponse.json({ error: 'no service role' }, { status: 500 });

View File

@@ -34,8 +34,13 @@ export default function PublishRrssButton() {
className="inline-flex items-center justify-center min-h-[52px] px-6 rounded-xl border border-emerald-500/40 bg-emerald-500/15 text-emerald-300 text-sm font-bold hover:bg-emerald-500/25 disabled:opacity-50"
>
<Upload className="w-4 h-4 mr-2" />
{busy ? 'Publicando…' : 'Publicar mes en portal'}
{busy ? 'Publicando…' : 'Dejar octubre visible para el cliente'}
</button>
<p className="text-[11px] text-gray-500 max-w-xs leading-relaxed">
No publica en Instagram ni Facebook. Solo guarda el plan de este mes en
el portal de <strong>Agua Miia</strong> para que el cliente lo vea en
Calendario.
</p>
{msg ? <p className="text-[11px] text-gray-400">{msg}</p> : null}
</div>
);

View File

@@ -51,6 +51,15 @@ export default async function CalendarioPage({
);
}
let tenantSlug: string | null = null;
if (ctx.tenantId) {
const t = await restGet<{ slug?: string }[]>(
`/rest/v1/tenants?id=eq.${encodeURIComponent(ctx.tenantId)}&select=slug`
);
tenantSlug = t.data?.[0]?.slug || null;
}
const isAguaMiia = tenantSlug === 'agua-miia';
let calendarDocs: Array<{ title?: string; content?: unknown }> = [];
if (agency && ctx.viaView && ctx.tenantId) {
const tid = encodeURIComponent(ctx.tenantId);
@@ -68,7 +77,6 @@ export default async function CalendarioPage({
calendarDocs = data || [];
}
// Prefer structured month plans; fallback to bundled Octubre Miia seed
const plans: RrssMonthPlan[] = [];
for (const doc of calendarDocs) {
const raw =
@@ -82,12 +90,18 @@ export default async function CalendarioPage({
})()
: doc.content;
const plan = asPlan(raw);
if (plan) plans.push(plan);
if (!plan) continue;
const blob = JSON.stringify(plan).toLowerCase();
if (!isAguaMiia && blob.includes('agua miia')) continue;
plans.push(plan);
}
if (!plans.length) {
if (!plans.length && isAguaMiia) {
plans.push(octPlan as unknown as RrssMonthPlan);
}
const canPublishMiia = agency && isAguaMiia;
const publishBtn = canPublishMiia ? <PublishRrssButton /> : null;
const monthParam = sp.month || plans[0]?.month;
const active = plans.find((p) => p.month === monthParam) || plans[0];
// Portada = archivo anual estilo /rrss/ig-fb/
@@ -111,8 +125,8 @@ export default async function CalendarioPage({
{ctx.label}
</h2>
<p className="text-sm text-gray-400 mt-2 max-w-xl">
Calendario Instagram/Facebook y estrategia LinkedIn. Sin publicación
pública: solo portal + doble check agencia/cliente.
Calendario editorial de {ctx.label}. No se publica en redes: solo el
cliente (y la agencia en su contexto) lo ve aquí.
</p>
</div>
<div className="flex flex-wrap gap-3">
@@ -128,7 +142,7 @@ export default async function CalendarioPage({
>
<Linkedin className="w-4 h-4 mr-2" /> LinkedIn
</Link>
{agency ? <PublishRrssButton /> : null}
{publishBtn}
</div>
</div>
);
@@ -142,7 +156,7 @@ export default async function CalendarioPage({
year={year}
plans={plans}
tenantLabel={ctx.label}
agencyPublish={agency ? <PublishRrssButton /> : null}
agencyPublish={publishBtn}
/>
);
}
@@ -159,13 +173,26 @@ export default async function CalendarioPage({
);
}
// Legacy flat posts fallback
const legacy = asLegacyPosts(
typeof calendarDocs[0]?.content === 'string'
? JSON.parse(calendarDocs[0].content as string)
: calendarDocs[0]?.content
);
if (!legacy.length) {
return (
<div className="rounded-xl border border-gray-200 bg-white p-6 space-y-2">
<h2 className="text-lg font-semibold text-gray-900">
Sin calendario editorial
</h2>
<p className="text-sm text-gray-600">
{ctx.label} no tiene un plan RRSS en el portal. Cada cliente solo ve
el suyo; no se muestra el de otros tenants.
</p>
</div>
);
}
return (
<div className="space-y-6">
<PageHeader