177 lines
4.7 KiB
TypeScript
177 lines
4.7 KiB
TypeScript
import { NextRequest, NextResponse } from 'next/server';
|
|
import { createClient } from '@/lib/supabase/server';
|
|
import { resolveActiveTenant, restGet } from '@/lib/tenant-context';
|
|
import {
|
|
isAgencyRole,
|
|
serviceHeaders,
|
|
supabaseUrl,
|
|
userRole,
|
|
} from '@/lib/agency';
|
|
import { randomUUID } from 'crypto';
|
|
|
|
export const dynamic = 'force-dynamic';
|
|
|
|
export type SeguimientoEntry = {
|
|
id: string;
|
|
tenant_id: string;
|
|
entry_type: string;
|
|
title: string;
|
|
summary?: string | null;
|
|
body_md?: string | null;
|
|
browser_link?: string | null;
|
|
metrics?: Record<string, unknown>;
|
|
occurred_at?: string | null;
|
|
synced_at?: string;
|
|
source?: string;
|
|
};
|
|
|
|
/** GET — notas + histórico Superhuman (sin series ads). */
|
|
export async function GET() {
|
|
const supabase = await createClient();
|
|
const {
|
|
data: { user },
|
|
} = await supabase.auth.getUser();
|
|
if (!user) {
|
|
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });
|
|
}
|
|
|
|
const ctx = await resolveActiveTenant(user);
|
|
const agency = isAgencyRole(userRole(user));
|
|
|
|
if (agency && ctx.portfolioWide) {
|
|
return NextResponse.json({
|
|
label: ctx.label,
|
|
portfolioWide: true,
|
|
entries: [],
|
|
hint: 'Elige un cliente en el selector para ver su seguimiento.',
|
|
});
|
|
}
|
|
|
|
const tenantId = ctx.tenantId;
|
|
if (!tenantId) {
|
|
return NextResponse.json({ error: 'Sin tenant' }, { status: 400 });
|
|
}
|
|
|
|
let entries: SeguimientoEntry[] = [];
|
|
|
|
if (agency && ctx.viaView) {
|
|
const tid = encodeURIComponent(tenantId);
|
|
const e = await restGet<SeguimientoEntry[]>(
|
|
`/rest/v1/seguimiento_entries?tenant_id=eq.${tid}&select=*&order=occurred_at.desc.nullslast&limit=300`
|
|
);
|
|
entries = e.data || [];
|
|
} else {
|
|
const { data: e } = await supabase
|
|
.from('seguimiento_entries')
|
|
.select('*')
|
|
.order('occurred_at', { ascending: false, nullsFirst: false })
|
|
.limit(300);
|
|
entries = (e as SeguimientoEntry[]) || [];
|
|
}
|
|
|
|
return NextResponse.json({
|
|
label: ctx.label,
|
|
portfolioWide: false,
|
|
tenantId,
|
|
entries,
|
|
isAgency: agency,
|
|
});
|
|
}
|
|
|
|
/**
|
|
* POST — add a manual note / comentario to seguimiento.
|
|
* { title?, body, occurredAt? | occurred_at? }
|
|
*/
|
|
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 });
|
|
}
|
|
|
|
const ctx = await resolveActiveTenant(user);
|
|
if (!ctx.tenantId || ctx.portfolioWide) {
|
|
return NextResponse.json(
|
|
{ error: 'Selecciona un cliente en Contexto' },
|
|
{ status: 400 }
|
|
);
|
|
}
|
|
|
|
const body = (await req.json().catch(() => ({}))) as Record<string, unknown>;
|
|
const text =
|
|
typeof body.body === 'string'
|
|
? body.body.trim()
|
|
: typeof body.note === 'string'
|
|
? body.note.trim()
|
|
: '';
|
|
const plain = text
|
|
.replace(/<br\s*\/?>/gi, '\n')
|
|
.replace(/<\/(p|div|li|h[1-6])>/gi, '\n')
|
|
.replace(/<[^>]+>/g, '')
|
|
.replace(/ /gi, ' ')
|
|
.replace(/&/g, '&')
|
|
.replace(/</g, '<')
|
|
.replace(/>/g, '>')
|
|
.replace(/\s+/g, ' ')
|
|
.trim();
|
|
if (!plain) {
|
|
return NextResponse.json({ error: 'Texto de la nota requerido' }, { status: 400 });
|
|
}
|
|
const title =
|
|
typeof body.title === 'string' && body.title.trim()
|
|
? body.title.trim().slice(0, 200)
|
|
: `Nota · ${new Date().toLocaleDateString('es-ES')}`;
|
|
const occurredRaw =
|
|
(typeof body.occurredAt === 'string' && body.occurredAt) ||
|
|
(typeof body.occurred_at === 'string' && body.occurred_at) ||
|
|
new Date().toISOString();
|
|
|
|
const row = {
|
|
tenant_id: ctx.tenantId,
|
|
source: 'manual',
|
|
entry_type: 'note',
|
|
external_id: `manual-${randomUUID()}`,
|
|
title,
|
|
summary: plain.slice(0, 280),
|
|
body_md: text,
|
|
occurred_at: occurredRaw,
|
|
synced_at: new Date().toISOString(),
|
|
metrics: {},
|
|
};
|
|
|
|
const agency = isAgencyRole(userRole(user));
|
|
const headers = serviceHeaders();
|
|
|
|
if (agency && headers) {
|
|
const r = await fetch(`${supabaseUrl()}/rest/v1/seguimiento_entries`, {
|
|
method: 'POST',
|
|
headers: {
|
|
...headers,
|
|
'Content-Type': 'application/json',
|
|
Prefer: 'return=representation',
|
|
},
|
|
body: JSON.stringify(row),
|
|
});
|
|
if (!r.ok) {
|
|
return NextResponse.json({ error: await r.text() }, { status: 500 });
|
|
}
|
|
const data = await r.json();
|
|
return NextResponse.json(
|
|
{ entry: Array.isArray(data) ? data[0] : data },
|
|
{ status: 201 }
|
|
);
|
|
}
|
|
|
|
const { data, error } = await supabase
|
|
.from('seguimiento_entries')
|
|
.insert(row)
|
|
.select('*')
|
|
.single();
|
|
if (error) {
|
|
return NextResponse.json({ error: error.message }, { status: 500 });
|
|
}
|
|
return NextResponse.json({ entry: data }, { status: 201 });
|
|
}
|