Dashboard ERP en tiempo real con Next.js 14 y Supabase PostgreSQL
Aprende a construir un dashboard empresarial que muestra ventas, inventario y pedidos actualizándose en tiempo real. Usamos Next.js 14 App Router, Supabase Realtime (suscripciones PostgreSQL) y TypeScript — el mismo stack con el que construimos los ERPs a medida de Alcanet.
Stack del proyecto
- Framework: Next.js 14 (App Router)
- DB + Realtime: Supabase PostgreSQL
- Auth: Supabase Auth (JWT)
- UI: Tailwind CSS
- Lenguaje: TypeScript
- Deploy: Vercel o Netlify
Por qué Next.js + Supabase para un ERP colombiano
Las PYMEs colombianas tienen un desafío específico: necesitan software robusto a precio accesible. SAP B1 cuesta más de $15.000 USD en licencias. Odoo requiere un equipo técnico interno. La alternativa es construir el ERP con un stack moderno que permita entregar en semanas, no en años.
Next.js 14 con App Router permite mezclar Server Components (datos seguros, sin exponer claves) con Client Components (tiempo real, interactividad). Supabase agrega Realtime sobre PostgreSQL estándar con Row Level Security — el mismo motor de base de datos que usan empresas Fortune 500.
Estructura del proyecto
app/
dashboard/
layout.tsx ← Server Component (verifica sesión Supabase)
page.tsx ← Resumen general (KPIs del día)
ventas/
page.tsx ← Tabla de ventas + gráfico
inventario/
page.tsx ← Stock en tiempo real
pedidos/
page.tsx ← Pipeline de pedidos
components/
dashboard/
KPICard.tsx ← Server Component (dato estático del build)
SalesTable.tsx ← Client Component (suscripción Realtime)
StockAlert.tsx ← Client Component (notificación en vivo)
lib/
supabase/
server.ts ← Cliente Supabase para Server Components
client.ts ← Cliente Supabase para Client ComponentsConfigurar los clientes Supabase (server vs client)
Next.js 14 distingue entre código que corre en el servidor (seguro, puede usar claves secretas) y código que corre en el browser (público). Supabase necesita dos instancias separadas:
// lib/supabase/server.ts — usa SERVICE_ROLE_KEY (solo servidor)
import { createClient } from "@supabase/supabase-js";
export function createServerClient() {
return createClient(
process.env.NEXT_PUBLIC_SUPABASE_URL!,
process.env.SUPABASE_SERVICE_ROLE_KEY!, // ← NUNCA exponer al browser
);
}// lib/supabase/client.ts — usa ANON_KEY (seguro en browser con RLS)
"use client";
import { createBrowserClient } from "@supabase/ssr";
export function createBrowserSupabase() {
return createBrowserClient(
process.env.NEXT_PUBLIC_SUPABASE_URL!,
process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!,
);
}Server Component: KPIs del día (datos en build o ISR)
Los KPIs del dashboard (ventas del día, número de pedidos, stock crítico) se pueden cargar como Server Component con revalidación cada 60 segundos. Esto significa que el HTML ya llega pre-renderizado al usuario — cero tiempo de espera para los datos principales:
// app/dashboard/page.tsx
import { createServerClient } from "@/lib/supabase/server";
export const revalidate = 60; // ISR: revalida cada 60 segundos
export default async function DashboardPage() {
const supabase = createServerClient();
const hoy = new Date().toISOString().split("T")[0];
const [{ data: ventasHoy }, { data: pedidosPendientes }, { data: stockCritico }] =
await Promise.all([
supabase.rpc("total_ventas_hoy", { fecha: hoy }),
supabase.from("pedidos").select("id", { count: "exact" }).eq("estado", "pendiente"),
supabase.from("productos").select("id", { count: "exact" }).lt("stock", 10),
]);
return (
<div className="grid grid-cols-3 gap-6 p-6">
<KPICard titulo="Ventas hoy" valor={ventasHoy ?? 0} prefix="$" />
<KPICard titulo="Pedidos pendientes" valor={pedidosPendientes?.length ?? 0} />
<KPICard titulo="Stock crítico" valor={stockCritico?.length ?? 0} alert />
</div>
);
}Client Component: tabla de pedidos en tiempo real
Para los datos que necesitan actualizarse sin recargar la página (nuevos pedidos, cambios de estado), usamos Supabase Realtime con el método channel. Esto abre una conexión WebSocket a PostgreSQL y recibe cambios en milisegundos:
"use client";
// components/dashboard/PedidosRealtime.tsx
import { useEffect, useState } from "react";
import { createBrowserSupabase } from "@/lib/supabase/client";
type Pedido = { id: string; cliente: string; total: number; estado: string };
export default function PedidosRealtime({ initial }: { initial: Pedido[] }) {
const [pedidos, setPedidos] = useState<Pedido[]>(initial);
useEffect(() => {
const supabase = createBrowserSupabase();
const canal = supabase
.channel("pedidos-live")
.on(
"postgres_changes",
{ event: "*", schema: "public", table: "pedidos" },
(payload) => {
if (payload.eventType === "INSERT") {
setPedidos((prev) => [payload.new as Pedido, ...prev]);
}
if (payload.eventType === "UPDATE") {
setPedidos((prev) =>
prev.map((p) => (p.id === payload.new.id ? (payload.new as Pedido) : p))
);
}
}
)
.subscribe();
return () => { supabase.removeChannel(canal); };
}, []);
return (
<table>
<tbody>
{pedidos.map((p) => (
<tr key={p.id}>
<td>{p.cliente}</td>
<td>${p.total.toLocaleString("es-CO")}</td>
<td>{p.estado}</td>
</tr>
))}
</tbody>
</table>
);
}El patrón clave: el Server Component padre hace el fetch inicial (rápido, seguro, pre-renderizado), y pasa los datos como initial al Client Component que solo se "activa" para las actualizaciones posteriores. El usuario ve datos inmediatamente, sin spinner.
Row Level Security: cada empresa solo ve sus datos
Si el ERP sirve a múltiples empresas (multi-tenant), la seguridad es crítica. Supabase RLS permite definir políticas directamente en PostgreSQL — ningún código de aplicación puede bypassearlas:
-- Activar RLS en la tabla pedidos
ALTER TABLE pedidos ENABLE ROW LEVEL SECURITY;
-- Solo el usuario autenticado ve los pedidos de su empresa
CREATE POLICY "empresa_propia" ON pedidos
FOR ALL
USING (
empresa_id = (
SELECT empresa_id FROM usuarios
WHERE id = auth.uid()
)
);¿Tu empresa necesita un ERP a medida?
Construimos ERPs con este mismo stack en 8–16 semanas. Desde $3.500 USD. Adaptado a la DIAN y tus procesos.
💬 Cotizar ERP a medida
Escrito por
Luis Alcalá
Fundador & CEO — Alcanet
Desarrollador de software especializado en Next.js, automatización con n8n e IA aplicada a negocios. 6+ años construyendo webs, ERP y sistemas para PYMEs en Colombia y Latinoamérica.
ARTÍCULOS RELACIONADOS
→ ¿Cuánto cuesta un ERP en Colombia en 2026? Comparativa real→ Cómo automatizar una empresa en Colombia — Guía 2026→ ERP a medida con IA para PYMEs en Colombia