1. 1Datos crudos
  2. 2Features
  3. 3Baseline model
  4. 4Infra core
  5. 5Feature store
  6. 6Modelo definitivo
  7. 7API de inferencia
  8. 8Monitoreo
  9. 9Dashboard
  10. 10Drift real

Pipeline · 9 de 10

Dashboard

Zoom sobre el Dashboard consultando a la API, Postgres, MLflow, Prometheus y Evidently
El último nodo del sistema es el que más conexiones tiene — habla con todos los demás. El navegador solo habla con él (ver Arquitectura).

Con todas las piezas ya sirviendo tráfico por separado, el dashboard las junta en un solo lugar para el usuario final. La decisión de arquitectura que lo hace posible — el navegador nunca habla directo con la API, con MLflow, con Prometheus o con Postgres, todo pasa server-side por Next.js — ya está explicada en detalle en Arquitectura; acá el foco es qué se construyó con esa regla ya decidida.

Framework

Next.js — App Router

Server Components y Route Handlers para cada fuente (lib/{mlflow,prometheus,db}.ts), sin exponer ninguna variable NEXT_PUBLIC_* — todo el fetching queda server-only por diseño, no por disciplina. Solo el proxy de /predict y el servido de HTML de Evidently son Route Handlers reales, porque necesitan ser invocables desde el navegador o devolver contenido no-JSON.

Cinco secciones: Predictor (formulario que llama a /api/predict, que a su vez llama server-side a la API real), Modelos (versiones y métricas desde MLflow), Monitoreo (requests y latencia desde Prometheus), Drift (los reportes HTML de Evidently), y Catálogo de features (la tabla feature_metadata de Postgres, consultada directo — mismo criterio que Feast y la API: cada servicio se conecta directo a Postgres para lo que necesita, sin una capa de indirección extra).

Librería

Recharts

Único componente cliente del dashboard ("use client") — Recharts necesita el DOM para dibujar, así que la sección de Monitoreo es la excepción a que todo sea Server Component.

Tres bugs reales, ninguno obvio de antemano

Prerender accidental. next build prerenderiza la página de Catálogo en build time por defecto — una query a pg no participa del heurístico de fetch-cache que usa Next para detectar páginas dinámicas. El HTML generado tenía los valores de la DB horneados de forma estática. Fix: export const dynamic = "force-dynamic".

HOSTNAME de Docker. El servidor standalone de Next hace process.env.HOSTNAME || '0.0.0.0' — pero Docker siempre setea HOSTNAME al ID del contenedor, así que ese fallback nunca se activa dentro de un contenedor. El servidor terminaba escuchando solo en la IP interna del contenedor (confirmado con netstat: 172.21.0.8:3000, no 0.0.0.0:3000). Fix: HOSTNAME=0.0.0.0 explícito en el environment: del servicio.

wget localhost vs. 127.0.0.1. Ya con el bind correcto, el healthcheck seguía fallando — wget en la imagen Alpine resuelve localhost a ::1 (IPv6) primero, y el servidor solo escucha IPv4. Confirmado que wget http://127.0.0.1:3000/ sí funciona pero wget http://localhost:3000/ no, mismo contenedor. Healthcheck cambiado a 127.0.0.1 explícito.

Artefactos de este paso

ArtefactoTipoQué es
dashboard/lib/{mlflow,prometheus,db}.tsCódigoClientes server-side de cada fuente — llamados directo desde Server Components.
dashboard/app/{page,predictor,models,monitoring,drift,features}RutasLas 6 páginas: Inicio, Predictor, Modelos, Monitoreo, Drift, Catálogo de features.
dashboard/app/api/predict/route.tsRuta APIProxy server-side a la API de inferencia — nunca llamado directo desde el navegador.
dashboard/app/api/drift/[operation]/route.tsRuta APISirve el HTML de Evidently ya generado, 404 limpio si no existe.
dashboard/DockerfileInfraBuild multi-stage, output standalone, HOSTNAME=0.0.0.0 explícito.