Corte Suprema de Justicia del Paraguay — Poder Judicial
Seguimiento del cumplimiento de los 89 indicadores del Plan Estratégico Institucional 2026–2030: captura de mediciones por dependencia, validación por flujo de estados (DGPD) y tableros analíticos con cumplimiento normalizado por sentido y línea base.
- Canónico:
AccLabPY/tableropeipj— todo el desarrollo, ramas y PRs ocurren acá (rama principal:main). - Espejo:
franciscoraguilera/PEI-PJ— copia de respaldo de solo lectura, actualizada manualmente congit push personal --all. Nadie desarrolla contra el espejo.
El despliegue productivo corre on-premise en el Poder Judicial (ver docs/despliegue-pj.md).
- El cumplimiento NUNCA es
valor/meta: se normaliza por sentido (ASC/DESC) y línea base (src/domain/cumplimiento.ts, tests golden ensrc/domain/__tests__). Prueba de oro: 4202 (base 18.87, meta 20, valor 19.5) ⇒ 55,7% Rojo; 3201 (100/95/96 DESC) ⇒ 80% Amarillo. - Solo las mediciones APROBADAS alimentan los tableros oficiales.
- La cobertura de reporte siempre acompaña al cumplimiento.
- Historial append-only: una aprobada no se sobrescribe; rectificar crea versión nueva.
- Umbrales de semáforo configurables con herencia Indicador → AE → OE → Global.
- Jerarquía visible OE → Acción Estratégica → Indicador.
- Nadie valida lo que cargó: quien cargó, editó, adjuntó o envió una versión no puede tomarla en revisión ni resolverla.
- La base de producción es la fuente de verdad desde el go-live: el seed
solo prepara entornos nuevos y
scripts/exportar-dataset.mjsmantiene el JSON del repositorio como respaldo.
Next.js 14 (App Router) · TypeScript estricto · Tailwind · Recharts · Prisma 5 con PostgreSQL 17 (FKs nativas, migraciones versionadas) · Auth.js v5 (credenciales + JWT revalidado contra la base + RBAC) · Vitest · Docker.
| Base | Uso | Mediciones |
|---|---|---|
peipj |
producción (datos oficiales) | las que cargue la institución |
peipj_test |
demo/capacitación | mockup sembrado en todos los estados |
Un ADMIN puede alternar su sesión entre ambas desde Administración
(banner "MODO PRUEBA" siempre visible). Los demás roles ven DB_DEFAULT_ENV.
La autenticación siempre ocurre contra peipj (plano de control).
cp .env.example .env # completar credenciales (ver abajo)
npm install
npm run data:verify # valida el dataset canónico: 6 OE / 39 AE / 89 ind.
npm run db:setup:all # migraciones + seed de ambas bases (solo bases vacías)
npm run dev # http://localhost:3000En dev conviene DB_DEFAULT_ENV=test para no tocar producción por error.
| Rol | |
|---|---|
admin@pj.gov.py |
ADMIN (switch de DB, usuarios, matriz) |
dgpd@pj.gov.py |
DGPD_VALIDADOR (valida mediciones) |
carga.dgch@pj.gov.py |
DEPENDENCIA_CARGA (Capital Humano) |
carga.dgtic@pj.gov.py |
DEPENDENCIA_CARGA (DGTIC) |
carga.estadisticas@pj.gov.py |
DEPENDENCIA_CARGA (Estadísticas) |
autoridad@pj.gov.py |
AUTORIDAD (solo lectura total) |
consulta@pj.gov.py |
CONSULTA (solo lectura) |
⚠ Seguridad: en producción estas cuentas ya no usan la clave demo; mantenerlas desactivadas o con contraseñas fuertes.
| Script | Qué hace |
|---|---|
npm run dev / build / start |
ciclo Next.js |
npm run test · test:domain |
Vitest (motor de cumplimiento + contratos) |
npm run typecheck · lint |
calidad |
npm run data:verify |
valida prisma/data/*.json (89 indicadores) |
npm run db:migrate:prod / db:migrate:test |
prisma migrate deploy (nunca db push) |
npm run db:seed:prod / db:seed:test |
seed de entorno nuevo (test: reinicia la demo) |
npm run db:setup:all |
migraciones + seed de ambas bases |
npm run db:studio:prod / db:studio:test |
Prisma Studio |
node scripts/exportar-dataset.mjs |
exporta la base al JSON del repositorio |
prisma/data/ dataset canónico versionado (fuentes y precedencia: NOTAS.md)
src/domain/ motor de cumplimiento PURO (sin Prisma/Next/React) + tests
src/shared/ DTOs planos + schemas Zod + constantes
src/server/ db (factory + switch) · auth (guards/RBAC) · repositories
(scoping) · services (casos de uso + mappers) · api (envelope)
src/app/ (auth)/login · (dashboard)/{ejecutivo,objetivos,acciones,
indicadores,gobernanza,registro,admin} · api/v1 · api/health
src/ui/ layout · components · charts (client-only) · features · theme
Reglas de módulos: domain no importa nada; server no importa React; la UI
no importa @prisma/client (regla ESLint) — los Decimal/BigInt mueren en
src/server/services/mappers.ts.
GET /api/v1/dashboards/ejecutivo?anio · GET /api/v1/objetivos ·
GET /api/v1/indicadores?oe&estado&q&page · GET /api/v1/indicadores/:codigo
· GET|POST /api/v1/mediciones · POST /api/v1/mediciones/:id/{enviar, validar,rectificar,evidencias} · GET /api/health.
La plataforma corre como una sola instancia y cachea en memoria
(src/server/services/cache.ts):
- Estado del PEI, catálogo, umbrales y fichas con TTL + stale-while-revalidate: las navegaciones en caliente responden en 60–120 ms sin tocar la base.
- Warm-up al arrancar (
src/instrumentation.ts): el servidor precalienta ambas bases en background; el primer visitante no paga el cómputo frío. - Invalidación explícita después del commit: aprobar/observar mediciones, editar la matriz o cambiar umbrales limpian el caché. Cada store lleva una generación: un cálculo que empezó antes de la mutación no se guarda.
Para juzgar la velocidad usar
npm run build && npm start:npm run devcompila cada ruta en el primer acceso y siempre se siente más lento.
Ventana de carga por indicador (src/domain/plazos.ts, dominio puro y testeado;
plazos.service.ts en la capa de aplicación):
- Plazo:
Periodo.fechaLimiteCargafija el vencimiento general del ejercicio (Administración → Plazos de carga). Cada acto administrativo posterior se registra append-only enVentanaCargacon alcance GLOBAL / OE / AE / INDICADOR / DEPENDENCIA, autor y motivo. - Resolución: la fecha efectiva es la más tardía aplicable (una prórroga nunca acorta un plazo); un acto manual posterior (APERTURA/CIERRE) manda sobre el cálculo por fecha. Sin fecha ⇒ carga abierta.
- Cierre automático: al vencer,
exigirCargaHabilitada()bloquea con 409CARGA_CERRADAel guardado, el envío y los adjuntos solo para las dependencias; DGPD y ADMIN siguen operando (son quienes prorrogan). - Avisos: dos por indicador y fecha límite (a 7 días o menos y el último
día) a todas las dependencias de carga, incluido el ejercicio anterior en
enero y febrero (evaluación perezosa con throttle horario desde
GET /api/v1/notificaciones); al entrar a Registro aparece un popup con los indicadores por vencer (una vez por sesión). - SLA: cada envío escribe una fila en
SlaCarga(plazo vigente, días de desvío, en plazo, con prórroga); un atraso de horas ya cuenta como fuera de plazo. El resumen por dependencia se ve en Gobernanza → SLA de carga y en la hoja "SLA de carga" del Excel de mediciones.
Otros cambios pedidos por el Poder Judicial (2026): el rechazo se retiró del
circuito (ValidarInputSchema acepta solo APROBADO/OBSERVADO; el estado
subsiste para el histórico); DGPD/Admin pueden eliminar evidencias mientras
la medición no esté aprobada; ante los roles de carga el historial muestra
"la DGPD" en lugar del nombre del validador; Administración incorpora
Estructura del PEI (alta/baja de OE, AE e indicadores) y Ejecución
presupuestaria (asignado/ejecutado del ejercicio, publicada en el Reporte
ejecutivo con gráfico).
Bandeja in-app por usuario (tabla Notificacion, en ambas bases de datos):
- Generación: cada transición de la máquina de estados emite
notificaciones (
notificaciones.service.ts, enganchado enmedicion.service.ts): las cargas enviadas notifican a DGPD/Admin; las resoluciones (aprobada/observada/rectificada/en revisión) notifican a los usuarios de todas las dependencias de carga del indicador. Al aprobar, si el indicador queda en semáforo ROJO se emite ademásINDICADOR_CRITICOa validadores + dependencia. El emisor nunca se auto-notifica; un fallo de notificación jamás rompe la transición (fire-and-forget). - Campanita (
src/ui/layout/campanita.tsx): badge de no leídas con polling de 60 s contraGET /api/v1/notificaciones; el click marca leída (POST /api/v1/notificaciones/leer) y navega al destino. - Detalle de carga (
/registro/carga/[id]): expediente individual de una medición — valores, evidencias descargables, resoluciones de la DGPD y timeline completo del historial. DGPD/Admin ven cualquiera; una dependencia las de los indicadores donde es responsable (scoping demedicion.repo). - Seed de demo:
node scripts/seed-notificaciones.mjssiembra la bandeja en la BD de prueba desde el historial real (idempotente).
La plataforma tiene dos temas conmutables por usuario desde el AppBar (y anónimamente desde el login):
- Agentes PEI (por defecto): identidad del programa — Poppins, gradiente
azul→magenta, naranja de acción, fondo crema, radios amplios, movimiento
sutil (framer-motion, respeta
prefers-reduced-motion). - Clásico: institucional CSJ — navy, Georgia, radios 4 px.
Cómo funciona:
- Tokens CSS:
src/app/globals.cssdefine:root/[data-theme="clasico"]y[data-theme="agentes"]como tripletas RGB (--c-*), radios (--r*), sombras, fuentes (--font-display/--font-body) y gradientes (--grad-marca/--grad-accion).tailwind.config.tslos expone comobg-azul/40,rounded-pj,font-serif,bg-marca, etc.src/ui/theme/tokens.tsexportargb(var(--c-x))para Recharts y estilos inline. - Regla: nunca un hex literal en componentes. Un color nuevo se agrega como
token en ambos bloques de
globals.css+ entrada entailwind.config.ts. - Variantes
agentes:yclasico:(plugin entailwind.config.ts) para diferencias puntuales (p. ej.agentes:rounded-chip, hero delPageHeader). Diferencias estructurales (AppBar, Sidebar, Footer, login) viven ensrc/ui/layout/agentes/y se eligen en el layout portema. - Resolución (
src/server/tema/tema.ts): cookiepei-tema→ JWT (Usuario.tema, cargado al login) →"agentes". El<html data-theme>se decide en el servidor (sin FOUC ni mismatch). Cambiar tema =setTemaAction(BD de control + cookie 1 año + revalidate). - Reportes imprimibles siempre en Clásico:
(print)/reportes/layout.tsxenvuelve en<div data-theme="clasico">— los PDFs son documentos oficiales CSJ. - Componentes con contexto cliente:
TemaProvider/useTema(),MotionProvider,Aparecer,Contador,BlobsFondo,LogoAgentes.
- Carga compartida: la dependencia principal y las corresponsables editan, adjuntan y envían la misma medición; las dependencias «fuente» solo la ven.
- Rectificación: crea la versión siguiente en borrador con los datos y una copia de las evidencias; la interfaz abre la versión nueva.
- Mediciones rechazadas (histórico): guardar sobre ellas inicia una versión nueva.
- Transacciones: cada transición escribe estado, historial y validación
en una sola transacción con control optimista (409
ESTADO_CAMBIADO). - Sesiones: el JWT se revalida contra la base de control (caché de 60 s):
cambios de roles rigen sin volver a ingresar; desactivar o cambiar la
contraseña cierra las sesiones (
Usuario.sesionVersion). - Auditoría:
AuditoriaAdmin(append-only por trigger) registra cambios de matriz, umbrales, estructura, usuarios, plazos, presupuesto y entorno; consulta en/admin/auditoria(ADMIN y DGPD).
Producción on-premise en el Poder Judicial: contenedor Docker detrás de Apache
y del proxy institucional, PostgreSQL 17 dedicado. Runbook completo en
docs/despliegue-pj.md. render.yaml y
docs/deploy-render.md describen el entorno anterior en la nube (histórico).
Importación ETL de Excel (endpoint + plantilla), e2e Playwright completo, LDAP institucional. El diseño ya los contempla (ver prompt maestro §12/§14).