EmpiezaTuSaaS

Configurar Panel de Admin

Guia paso a paso para configurar y personalizar el panel de administracion

Configurar Panel de Admin

En este tutorial aprenderas a configurar el panel de administracion para gestiónar usuarios y roles de tu SaaS.

Requisitos previos

  • Tener el proyecto configurado con Better Auth
  • Base de datos PostgreSQL funcionando
  • Al menos un usuario registrado

Paso 1: Verificar Better Auth Admin Plugin

El plugin de admin ya está configurado en lib/auth.ts:

lib/auth.ts
import { multiSession, admin } from "better-auth/plugins";

export const auth = betterAuth({
  // ...otras configuraciónes
  plugins: [
    multiSession({
      maximumSessions: 5,
    }),
    admin({
      defaultRole: "user",
      adminRoles: ["admin"],
    }),
  ],
});

Los roles disponibles son: user y admin. Solo los usuarios con rol admin pueden acceder al panel.

Paso 2: Crear tu primer administrador

npx prisma studio
  1. Abre Prisma Studio en http://localhost:5555
  2. Navega a la tabla users
  3. Encuentra tu usuario y cambia el campo role a admin
  4. Guarda los cambios
UPDATE users
SET role = 'admin'
WHERE email = 'tu@email.com';

Crea un script temporal:

scripts/create-admin.ts
import { PrismaClient } from "@prisma/client";

const db = new PrismaClient();

async function createAdmin() {
  const email = process.argv[2];

  if (!email) {
    console.error("Uso: npx tsx scripts/create-admin.ts tu@email.com");
    process.exit(1);
  }

  const user = await db.user.update({
    where: { email },
    data: { role: "admin" },
  });

  console.log(`Usuario ${user.email} ahora es admin`);
  await db.$disconnect();
}

createAdmin();
npx tsx scripts/create-admin.ts tu@email.com

Paso 3: Protección de rutas

El acceso al panel de admin está protegido en dos niveles:

Proxy (proxy.ts)

El proxy usa getSessionCookie de better-auth/cookies para una verificación optimista de la cookie de sesión. Como el proxy en Next.js 16 se ejecuta en Node.js runtime (no en Edge Runtime), no necesita hacer llamadas HTTP internas.

proxy.ts
import { getSessionCookie } from "better-auth/cookies";

// Optimistic cookie check — no HTTP call needed
const sessionCookie = getSessionCookie(request);
const isAuthenticated = !!sessionCookie;

La verificación del rol de admin no se hace en el proxy, ya que getSessionCookie solo verifica la existencia de la cookie, no su contenido (no contiene información de rol). En su lugar, la verificación de rol se hace en el layout del admin como Server Component con auth.api.getSession.

Layout del admin (app/(admin)/layout.tsx)

El layout del panel de admin incluye un sidebar con navegación:

app/(admin)/layout.tsx
import Link from "next/link";
import { Shield, Users, BarChart3, Settings, Zap } from "lucide-react";
import { siteConfig } from "@/config/site";

const adminNavItems = [
  { href: "/admin", label: "Overview", icon: BarChart3 },
  { href: "/admin/users", label: "Usuarios", icon: Users },
  { href: "/admin/settings", label: "Configuración", icon: Settings },
];

export default function AdminLayout({
  children,
}: {
  children: React.ReactNode;
}) {
  return (
    <div className="min-h-screen flex">
      {/* Admin Sidebar */}
      <aside className="w-64 border-r border-border bg-zinc-950 hidden md:flex flex-col">
        <div className="p-6 border-b border-border">
          <Link href="/admin" className="flex items-center gap-2 font-bold">
            <Zap className="h-5 w-5" />
            <span>{siteConfig.name}</span>
            <span className="text-xs bg-primary text-primary-foreground px-1.5 py-0.5 rounded ml-1">
              Admin
            </span>
          </Link>
        </div>

        <nav className="flex-1 p-4 space-y-1">
          {adminNavItems.map((item) => (
            <Link
              key={item.href}
              href={item.href}
              className="flex items-center gap-3 px-3 py-2 rounded-lg text-sm text-muted-foreground hover:text-foreground hover:bg-accent transition-colors"
            >
              <item.icon className="h-4 w-4" />
              {item.label}
            </Link>
          ))}
        </nav>

        <div className="p-4 border-t border-border">
          <Link
            href="/dashboard"
            className="flex items-center gap-2 text-sm text-muted-foreground hover:text-foreground transition-colors"
          >
            <Shield className="h-4 w-4" />
            Volver al dashboard
          </Link>
        </div>
      </aside>

      {/* Main */}
      <main className="flex-1 overflow-auto bg-background">
        <div className="p-8">{children}</div>
      </main>
    </div>
  );
}

Paso 4: Página principal del admin

La página /admin (app/(admin)/admin/page.tsx) muestra una vista general con tarjetas de estadisticas:

  • Total usuarios con porcentaje de crecimiento
  • MRR (Monthly Recurring Revenue)
  • Conversion (tasa de conversion)
  • Activos hoy

Y una tabla de usuarios recientes registrados en el sistema.

Las estadisticas vienen con valores placeholder (0). Conectalas a tus queries de Prisma para mostrar datos reales.

Paso 5: Gestión de usuarios

Página de usuarios (/admin/users)

En app/(admin)/admin/users/page.tsx encontraras una lista de todos los usuarios registrados. Para cada usuario se muestra:

  • Avatar y nombre con badges de rol (Admin) y estado (Baneado)
  • Email del usuario
  • Estado de suscripcion (plan activo si lo tiene)
  • Verificación de email (verificado o sin verificar)
  • Fecha de registro (en formato relativo en español: "Hace 2 dias")
  • Menu de acciones

Acciones disponibles

El componente AdminUserActions (app/(admin)/admin/users/user-actions.tsx) ofrece estas acciones a traves de un dropdown menu:

AccionDescripción
Hacer adminPromover un usuario al rol admin
Quitar adminRevertir un admin al rol user
Banear usuarioBloquear el acceso del usuario
DesbanearRestaurar el acceso de un usuario baneado

Las acciones se ejecutan a traves del plugin admin de Better Auth:

app/(admin)/admin/users/user-actions.tsx
import { authClient } from "@/lib/auth-client";

// Promover a admin
const setAdmin = () =>
  handleAction(
    () => authClient.admin?.setRole?.({ userId, role: "admin" }),
    "Usuario promovido a admin"
  );

// Revertir a usuario
const setUser = () =>
  handleAction(
    () => authClient.admin?.setRole?.({ userId, role: "user" }),
    "Rol revertido a usuario"
  );

// Banear
const banUser = () =>
  handleAction(
    () => authClient.admin?.banUser?.({ userId }),
    "Usuario baneado"
  );

// Desbanear
const unbanUser = () =>
  handleAction(
    () => authClient.admin?.unbanUser?.({ userId }),
    "Usuario desbaneado"
  );

Paso 6: Añadir nuevas páginas al admin

Crea la página dentro de app/(admin)/admin/:

app/(admin)/admin/mi-pagina/page.tsx
import type { Metadata } from "next";
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";

export const metadata: Metadata = {
  title: "Mi Página - Admin",
};

export default function MiPáginaAdmin() {
  return (
    <div className="space-y-6">
      <div>
        <h1 className="text-2xl font-bold">Mi Página</h1>
        <p className="text-muted-foreground mt-1">
          Descripción de la página
        </p>
      </div>

      <Card>
        <CardHeader>
          <CardTitle>Contenido</CardTitle>
        </CardHeader>
        <CardContent>
          {/* Tu contenido */}
        </CardContent>
      </Card>
    </div>
  );
}

Añade el enlace al sidebar editando app/(admin)/layout.tsx:

import { FileText } from "lucide-react";

const adminNavItems = [
  { href: "/admin", label: "Overview", icon: BarChart3 },
  { href: "/admin/users", label: "Usuarios", icon: Users },
  { href: "/admin/mi-pagina", label: "Mi Página", icon: FileText }, // Nueva página
  { href: "/admin/settings", label: "Configuración", icon: Settings },
];

Mejores practicas

Seguridad

  • Nunca expongas acciones de admin sin verificar el rol en el servidor
  • El proxy protege las rutas a nivel de request, pero siempre válida también en tus API routes
  • Las acciones de ban y cambio de rol se ejecutan a traves de la API de Better Auth, que válida permisos internamente

Resumen

Has configurado:

  • Panel de admin con sidebar de navegación y estadisticas
  • Gestión de usuarios con acciones: hacer admin, quitar admin, banear y desbanear
  • Protección de rutas a nivel de proxy y layout
  • Estructura para añadir nuevas páginas fácilmente

Siguiente paso

On this page