Saltar a contenido

Plan: Milestone 1 — Acceso y Administración

Source PRD: docs/prds/qaudit.prd.md Blueprint: docs/rfcs/RFC-0001-qaudit-arquitectura.md Selected Milestone: 1 — Acceso y administración Complexity: Large

Outcome (PRD): Un auditor inicia sesión con su cuenta corporativa y solo entra si está habilitado; el admin gestiona tiendas y usuarios.

Summary

Primer milestone de un proyecto greenfield. Entrega el outcome de acceso + administración y establece toda la base técnica que los milestones 2–7 reutilizan: scaffolding Next.js + TypeScript estricto, Prisma + Postgres, Docker Compose, arnés de pruebas (Vitest + Playwright, cobertura ≥95% per RFC §11), SSO con Microsoft Entra ID con whitelist y roles, y dos CRUDs de administración (tiendas y usuarios). La app vive en la raíz del repo (no en subcarpeta). No se toca QUICK POS ni el flujo de auditoría todavía.

Naturaleza dual del milestone

Eje Qué incluye
Fundación (se hace una vez, fija convenciones) Scaffolding, tooling, Prisma singleton, capa lib/domain vacía pero estructurada, Docker Compose, arnés de tests
Feature (el outcome de M1) Auth SSO + whitelist + roles, guards de ruta, CRUD de stores, CRUD de users

Patterns to Mirror

No existe código previo — proyecto greenfield. Este milestone crea los patrones. Fuentes de convención a respetar (no inventar otras):

Categoría Fuente Patrón a establecer
Capas RFC §4.2 src/app/ (UI) · src/app/api/ (borde HTTP, valida y delega) · src/lib/domain/ (puro, sin I/O) · src/lib/db.ts (Prisma singleton)
Estilo / naming reglas web globales (coding-style) Componentes PascalCase, hooks use*, organización por feature, archivos <800 líneas
Tests reglas globales (testing) + RFC §11 AAA, nombres descriptivos, TDD (RED→GREEN→REFACTOR), cobertura ≥95%
Errores RFC §9 + global Validación con schema (Zod) en el borde; nunca confiar en input; authz revalidada server-side
Datos RFC §5 Prisma como fuente de verdad; constraints en la BD; migración incremental por milestone

Files to Change

Convención de paquete: pnpm (alineado con las reglas de hooks web). La app vive en la raíz del repo (per RFC §4.2): src/app/ es el App Router, prisma/ y la config quedan arriba. La carpeta de documentación (docs/) convive en la raíz sin interferir con el build de Next.

File Action Why
package.json, tsconfig.json, next.config.ts CREATE Scaffolding Next.js App Router + TS estricto
eslint.config.mjs, .prettierrc, .stylelintrc CREATE Tooling de calidad (zero-warning policy)
vitest.config.ts, vitest.setup.ts CREATE Unit/integration + cobertura ≥95%
playwright.config.ts CREATE E2E
prisma/schema.prisma CREATE Datasource Postgres + modelos stores y users (solo M1)
prisma/migrations/** CREATE Primera migración
prisma/seed.ts CREATE Bootstrap del primer admin (Diana) desde env
src/lib/db.ts CREATE Prisma client singleton
src/lib/domain/authorization.ts CREATE Decisión pura de autorización (whitelist + rol), testeable sin I/O
src/lib/validation/store.ts, src/lib/validation/user.ts CREATE Schemas Zod (dominio email, rango puerto, formato codsuc)
auth.ts CREATE Auth.js: provider Entra ID single-tenant, callbacks signIn/jwt/session
src/middleware.ts CREATE Guard de rutas a nivel edge (autenticado / rol)
src/app/layout.tsx, src/app/page.tsx CREATE Shell + redirect por rol
src/app/api/health/route.ts CREATE Endpoint de health para el healthcheck de Docker (Task 10)
src/app/(auth)/login/page.tsx, .../denied/page.tsx CREATE Login y pantalla de "no habilitado"
src/app/(admin)/layout.tsx CREATE Guard server-side rol=admin
src/app/(admin)/stores/page.tsx + componentes CREATE UI CRUD de tiendas
src/app/(admin)/users/page.tsx + componentes CREATE UI CRUD de whitelist
src/app/api/stores/route.ts, .../[id]/route.ts CREATE API tiendas (admin-only)
src/app/api/users/route.ts, .../[id]/route.ts CREATE API usuarios (admin-only)
Dockerfile, docker-compose.yml, docker-compose.prod.yml, .env.example, .gitignore CREATE Postgres de dev (Task 1) + despliegue on-premise (RFC §10)
docs/rfcs/RFC-0001-qaudit-arquitectura.md UPDATE ✅ Hecho: §5 users.ms_oid → nullable (se vincula en primer login)

Tasks

Orden TDD: por cada tarea con lógica, el test va primero (RED), luego implementación (GREEN), luego refactor.

Task 1: Scaffolding, tooling y Postgres de desarrollo

  • Action: Inicializar Next.js (App Router, TS estricto) en la raíz del repo; configurar ESLint/Prettier/stylelint, Vitest + RTL, Playwright; scripts pnpm (lint, typecheck, test, test:e2e, build). Levantar un docker-compose.yml con el servicio postgres:16-alpine (volumen + healthcheck) para que la Task 2 tenga contra qué migrar. El Dockerfile de la app y los overrides de prod se completan en la Task 10.
  • Mirror: reglas globales web (coding-style, testing).
  • Validate: pnpm install && pnpm typecheck && pnpm lint && pnpm test --run (suite vacía pasa, cero warnings); docker compose up -d postgres deja la BD healthy.

Task 2: Base de datos y Prisma

  • Action: prisma/schema.prisma con datasource Postgres y modelos stores y users con los constraints del RFC §5 — excepto ms_oid que va nullable @unique. Generar primera migración. Implementar src/lib/db.ts singleton.
  • Mirror: RFC §5 (constraints en BD); migración incremental (solo tablas de M1).
  • Validate: pnpm prisma migrate dev aplica limpio contra el Postgres levantado en la Task 1; pnpm prisma validate.

Task 3: Lógica de autorización (pura) — TDD

  • Action: lib/domain/authorization.ts: función pura que dado un usuario (o ausencia) decide allow/deny y rol, y la lógica de vinculación de ms_oid en primer login (match por email normalizado, case-insensitive). Tests primero.
  • Mirror: lib/domain sin I/O; AAA.
  • Validate: casos — email habilitado+activo → allow; inactivo → deny; inexistente → deny; primer login vincula oid; logins siguientes matchean por oid.

Task 4: Schemas de validación — TDD

  • Action: Zod para store (puerto 1–65535, codsuc 2 dígitos, codemp int, hostname no vacío) y user (email LIKE %@zavidoro.com.py, rol ∈ {admin,auditor}). Tests primero.
  • Validate: rechaza email de otro dominio, puerto fuera de rango, codsuc inválido; acepta válidos.

Task 5: Auth.js + Entra ID + whitelist

  • Action: auth.ts con provider Microsoft Entra ID single-tenant. Callback signIn consulta users vía lib/domain/authorization → si no habilitado, deniega y redirige a /denied. Callback jwt/session expone rol y userId. Primer login vincula ms_oid.
  • Mirror: RFC §6; secretos solo en env, validados al arranque.
  • Validate: integración con DB de test + provider mockeado: usuario habilitado entra con rol correcto; no habilitado → denegado.

Task 6: Guards de ruta

  • Action: middleware.ts (autenticado) + (admin)/layout.tsx (revalida rol=admin server-side). Cada Route Handler revalida authz, no confía en la UI.
  • Mirror: RFC §6 ("nunca confiar solo en el guard de UI").
  • Validate: E2E — auditor no alcanza (admin); no autenticado → login.

Task 7: API + UI de Tiendas (CRUD) — TDD

  • Action: /api/stores (list/create/update/deactivate), admin-only, valida con Zod. UI lista + formulario. Baja lógica (activa=false), no borrado físico.
  • Validate: integración handlers contra DB de test (201/200/403/422); E2E admin crea/edita/desactiva tienda.

Task 8: API + UI de Usuarios/whitelist (CRUD) — TDD

  • Action: /api/users (list/create/update/deactivate), admin-only, valida dominio de email. UI lista + formulario (email, nombre, rol, activo). ms_oid no editable (lo llena el login).
  • Validate: integración (rechaza dominio ajeno con 422; admin-only 403); E2E alta de auditor habilitado.

Task 9: Seed de bootstrap del primer admin

  • Action: prisma/seed.ts que inserta el admin inicial (email desde BOOTSTRAP_ADMIN_EMAIL) idempotente.
  • Validate: correr seed dos veces no duplica; el admin sembrado puede entrar y ver el panel.

Task 10: Dockerfile de la app y despliegue completo

  • Action: Dockerfile (build Next.js), agregar el servicio app al docker-compose.yml ya existente (con depends_on postgres healthy y healthcheck contra /api/health), .env.example, docker-compose.prod.yml para overrides de producción. El .gitignore ya se creó al inicializar git.
  • Mirror: RFC §10.
  • Validate: docker compose up --build levanta app + postgres; healthcheck verde vía /api/health; la app responde y conecta a la BD.

Validation

# desde la raíz del repo
pnpm install
docker compose up -d postgres                        # BD de dev healthy (Task 1)
pnpm prisma validate && pnpm prisma migrate dev
pnpm typecheck                 # tsc --noEmit, cero errores
pnpm lint                      # ESLint + stylelint, cero warnings (zero-warning policy)
pnpm test --run --coverage     # Vitest, ≥95% (RFC §11)
pnpm test:e2e                  # Playwright (provider Entra mockeado)
docker compose up --build -d && docker compose ps    # app + postgres healthy
curl -fsS localhost:3000/api/health                  # endpoint de health responde

Risks

Risk Likelihood Mitigation
ms_oid NOT NULL (RFC §5) incompatible con whitelist previa al login Alta (ya detectado) ✅ Resuelto: ms_oid nullable, se vincula en primer login; RFC §5 ya actualizado
Bootstrap del primer admin (gallina-huevo del whitelist) Alta Seed idempotente desde BOOTSTRAP_ADMIN_EMAIL
E2E del flujo OIDC real requiere tenant Entra Media Mockear el provider en E2E; testear la decisión de authz en unit/integration
Matching email vs oid (cambios de email, mayúsculas) Media Normalizar email (lower/trim); preferir ms_oid una vez vinculado
Callback URL de Entra apuntando a host on-premise expuesto Media Documentar redirect URI en .env.example; validar AUTH_URL al arranque
Scope creep: meter tablas de auditoría que son de M2+ Media Migración de M1 solo incluye stores y users

Acceptance

  • [ ] Tareas 1–10 completas
  • [ ] Auditor habilitado entra con SSO; no habilitado ve /denied; auditor no accede a (admin)
  • [ ] Admin (sembrado) gestiona altas/bajas de tiendas y usuarios desde el panel
  • [ ] pnpm typecheck, pnpm lint (cero warnings), pnpm test --coverage (≥95%), pnpm test:e2e pasan
  • [ ] docker compose up deja app + postgres healthy; /api/health responde
  • [x] RFC §5 actualizado (ms_oid nullable)
  • [ ] Patrones establecidos y documentados para que M2 los espeje (no reinventar)

Status: DRAFT — esperando confirmación antes de escribir código.