Files
labre-web/src/pages/api/admin/baneos/[id]/desbanear.ts
T
LakG ac3e7061ac Rediseño visual UABC + Mulish + auditoría de BD (P0/P1)
Rediseño visual:
- Nueva identidad UABC/editorial (DESIGN.md), tipografía Mulish, tokens
  verde/dorado/negro sobre bone-white, patrón de puntos verde en canvas,
  radios 8/12/30/9999, sin sombras, hairline 1px, tarjetas con leve
  transparencia + blur suave + borde verde muy sutil.
- 45 archivos .astro/.tsx alineados al nuevo sistema (Track A/B/C/D/E).
- Limpieza post-critique: eliminado triple CTA en home alumno, .card
  anidados en admin, aria-live decorativo, 26 aliases legacy en global.css,
  border-l verde en nav (patrón AI-slop), classList.remove obsoletos.

Auditoría de llamadas a BD (para 20-50 usuarios concurrentes):
- P0-1: middleware con cache in-memory de profile (30s) + is_banned (30s)
  por uid; exclusión de assets estáticos (/_astro/*, /audio/*, favicon).
  Invalidación en signout, PATCH /profile, baneos.
- P0-2: eliminado count pendientes duplicado en AppLayout; badge se
  hidrata client-side (BadgeSolicitudes con fetch inicial).
- P0-3: migración 0008 REPLICA IDENTITY FULL en solicitudes + delta
  preciso en BadgeSolicitudes (con fallback a refetch).
- P1: admin/index dedupe (4 queries → 2 con recientes.filter), catalogo
  reusa profile del middleware (3 queries → 1), inventario paralelo,
  estadisticas con cache 60s en RAM (stats-cache.ts), migración 0009
  con 3 índices (fecha, vencidos parcial, audit compuesto).

Fixes P0 de UI post-critique:
- estadisticas.astro: ReferenceError DIAS restaurado.
- solicitudes (3 vistas): error rendering sanitizado (no expone mensaje
  Postgres crudo al admin).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014xaxs8cGcQe9NwbMqA1gvn
2026-09-21 09:42:12 -07:00

33 lines
1.0 KiB
TypeScript

import type { APIRoute } from 'astro';
import { invalidateUserCaches } from '@/middleware';
export const prerender = false;
export const POST: APIRoute = async ({ locals, params }) => {
if (locals.profile?.rol !== 'admin') {
return Response.json({ error: 'No autorizado' }, { status: 403 });
}
const id = Number(params.id);
if (!Number.isInteger(id) || id <= 0) {
return Response.json({ error: 'ID inválido' }, { status: 400 });
}
const { data, error } = await locals.supabase
.from('baneos')
.update({ unbanned_at: new Date().toISOString(), unbanned_by: locals.user!.id })
.eq('id', id)
.is('unbanned_at', null)
.select('id, profile_id');
if (error) {
return Response.json({ error: 'No se pudo desbanear' }, { status: 500 });
}
if (!data || data.length === 0) {
return Response.json({ error: 'Baneo no encontrado o ya inactivo' }, { status: 404 });
}
if (data[0]?.profile_id) invalidateUserCaches(data[0].profile_id);
return Response.json({ ok: true });
};