import { NextRequest, NextResponse } from 'next/server'; import { createClient } from '@/lib/supabase/server'; import { resolveActiveTenant } from '@/lib/tenant-context'; import { isAgencyRole, serviceHeaders, supabaseUrl, userRole, } from '@/lib/agency'; import { parseJsonCalendar, sheetsToPlans, type ImportResult, type SheetRows, } from '@/lib/calendar-import'; import { buildOfficialCsv } from '@/lib/calendar-template'; import type { RrssMonthPlan } from '@/components/RrssMonthView'; export const dynamic = 'force-dynamic'; export const runtime = 'nodejs'; const MAX_BYTES = 8 * 1024 * 1024; /** @deprecated Prefer GET /api/calendar/template?format=csv */ export async function GET() { return new NextResponse(buildOfficialCsv('CLIENTE'), { headers: { 'Content-Type': 'text/csv; charset=utf-8', 'Content-Disposition': 'attachment; filename="plantilla-calendario-4ventures.csv"', 'Cache-Control': 'no-store', }, }); } export async function POST(req: NextRequest) { const supabase = await createClient(); const { data: { user }, } = await supabase.auth.getUser(); if (!user) { return NextResponse.json({ error: 'Unauthorized' }, { status: 401 }); } if (!isAgencyRole(userRole(user))) { return NextResponse.json( { error: 'Solo el equipo de 4Ventures puede subir calendarios.' }, { status: 403 } ); } const ctx = await resolveActiveTenant(user); if (!ctx.tenantId || ctx.portfolioWide) { return NextResponse.json( { error: 'Selecciona un cliente en Contexto.' }, { status: 400 } ); } const form = await req.formData().catch(() => null); const file = form?.get('file'); if (!(file instanceof File)) { return NextResponse.json({ error: 'Adjunta un CSV, Excel o JSON.' }, { status: 400 }); } if (file.size > MAX_BYTES) { return NextResponse.json({ error: 'El archivo supera 8 MB.' }, { status: 400 }); } const name = file.name || 'calendario'; const buf = Buffer.from(await file.arrayBuffer()); const text = buf.toString('utf8').replace(/^\uFEFF/, ''); const yearHint = new Date().getFullYear(); let parsed: ImportResult; const lower = name.toLowerCase(); if (lower.endsWith('.json') || text.trim().startsWith('{') || text.trim().startsWith('[')) { const json = parseJsonCalendar(text, ctx.label); if (!json || !json.plans.length) { return NextResponse.json( { error: json?.warnings[0] || 'JSON no vĂ¡lido.' }, { status: 400 } ); } parsed = json; } else { try { const mod = await import('xlsx'); const XLSX = (mod as { default?: typeof import('xlsx') }).default ?? mod; const wb = XLSX.read(buf, { type: 'buffer', cellDates: true, raw: false }); const sheets: { name: string; rows: SheetRows }[] = []; for (const sheetName of wb.SheetNames) { const ws = wb.Sheets[sheetName]; if (!ws) continue; const rows = XLSX.utils.sheet_to_json<(string | number | null)[]>(ws, { header: 1, defval: '', raw: false, blankrows: false, }) as SheetRows; sheets.push({ name: sheetName, rows }); } parsed = sheetsToPlans(sheets, ctx.label, yearHint); } catch (e) { return NextResponse.json( { error: e instanceof Error ? `No se pudo leer el Excel/CSV: ${e.message}` : 'No se pudo leer el Excel/CSV', }, { status: 400 } ); } } if (!parsed.plans.length) { return NextResponse.json( { error: parsed.warnings[0] || 'No se leyeron piezas.', sheets: parsed.sheets, }, { status: 400 } ); } const headers = serviceHeaders(); const base = supabaseUrl(); if (!headers || !base) { return NextResponse.json({ error: 'Service role required' }, { status: 503 }); } const saved: { month: string; pieces: number }[] = []; try { for (const plan of parsed.plans) { await upsertPlan(base, headers, ctx.tenantId, plan); saved.push({ month: plan.month, pieces: plan.pieces.length }); } } catch (e) { return NextResponse.json( { error: e instanceof Error ? e.message : 'Error al guardar' }, { status: 500 } ); } return NextResponse.json({ ok: true, tenant: ctx.label, months: saved, warnings: parsed.warnings, sheets: parsed.sheets, }); } async function upsertPlan( base: string, headers: { apikey: string; Authorization: string }, tenantId: string, plan: RrssMonthPlan ) { const title = `RRSS ${plan.label}`; const existing = await fetch( `${base}/rest/v1/documents?tenant_id=eq.${encodeURIComponent( tenantId )}&type=eq.calendar&select=id,title,content`, { headers, cache: 'no-store' } ); const docs = existing.ok ? ((await existing.json()) as { id: string; title: string; content: unknown; }[]) : []; for (const d of docs) { let c = d.content as { month?: string } | string | null; if (typeof c === 'string') { try { c = JSON.parse(c) as { month?: string }; } catch { c = null; } } const month = c && typeof c === 'object' ? c.month : null; if (month === plan.month || d.title === title) { await fetch(`${base}/rest/v1/documents?id=eq.${d.id}`, { method: 'DELETE', headers: { ...headers, Prefer: 'return=minimal' }, }); } } const row = { tenant_id: tenantId, type: 'calendar', title, content: plan, status: 'approved', }; const ins = await fetch(`${base}/rest/v1/documents`, { method: 'POST', headers: { ...headers, 'Content-Type': 'application/json', Prefer: 'return=representation', }, body: JSON.stringify(row), }); if (!ins.ok) { throw new Error(`No se pudo guardar ${plan.month}: ${(await ins.text()).slice(0, 280)}`); } }