refactor: migrate backend to pnpm monorepo with Hono module architecture

- Replace Bun with pnpm 9 + Turborepo + tsx; apps/api renamed to apps/backend
- Split backend into http/, modules/, lib/ layers mirroring tai-specguard
- Add use-case + Result pattern, Problem Details RFC 7807, basePath /api/v1
- Mount Better Auth at /api/v1/auth, keep session-auth whitelist
- Split Prisma schema into prisma/models/*, generate into generated/
- Rework packages/shared into lib/ + schemas/ with pagination DTOs
- Implement health, groups, students, payments, waitlist modules
- Add vitest suite with prisma mocks (21 tests), biome lint
- Point web client to /api/v1/auth and /api/v1/groups/from-organization
This commit is contained in:
Jose Selesan
2026-09-14 09:18:40 -03:00
parent b41fffa40a
commit 4b1f356fab
101 changed files with 7152 additions and 1456 deletions

View File

@@ -4,35 +4,49 @@ Monorepo web + API para gestión de cobros grupales. Reglas optimizadas para age
## Stack & ejecutables de verdad
- **Package manager / runtime: Bun** (v1.4+, `package.json` viene con `workspaces` y `packageManager: bun`). Usa `bun` para instalar (`bun install`), scripts y filtros. **No uses npm/yarn/pnpm**.
- **Monorepo**: Bun Workspaces orquestado con **Turborepo** (`turbo.json`). Dependencias entre paquetes usan `workspace:*`.
- Los paquetes son Node `"type": "module"`.
- **Package manager: pnpm 9** (`packageManager` en la raíz). Usa `pnpm` para instalar (`pnpm install`), scripts y filtros. **No uses bun/npm/yarn.**
- **Monorepo**: pnpm workspaces (`pnpm-workspace.yaml`) orquestado con **Turborepo** (`turbo.json`). Dependencias entre paquetes usan `workspace:*`.
- **Runtime: Node** (`"type": "module"`). El backend se ejecuta con **tsx** (`tsx watch src/server.ts`); no es Bun.
- Lockfile: `pnpm-lock.yaml` (se versiona). No existe `bun.lock`/`bun.lockb`.
## Comandos
- `bun run typecheck` — corre `tsc --noEmit` en todos los paquetes vía turborepo. **Es la verificación principal; correlo tras tocar código.**
- `bun run --filter @gruperly/api typecheck` / `... @gruperly/web typecheck` — verificación de un solo paquete.
- `bun run dev` — levanta API (4000) y web (6173) con turborepo.
- **No hay script `lint` en ningún paquete**; `bun run lint` no hace nada útil. No dependas de él.
- Base de datos (API): `bun run db:generate` / `db:migrate` / `db:push` / `db:studio`, o bien `bun --filter @gruperly/api db:*`.
```bash
pnpm install # instala dependencias
pnpm dev # levanta API (4000) y web (6173) vía turborepo
pnpm typecheck # tsc --noEmit en todos los paquetes. Principal verificación; corre tras tocar código
pnpm build # build de backend (prisma generate + tsc) y web (vite build)
pnpm lint # biome check en backend y shared
pnpm lint:fix # biome check --write
pnpm --filter @gruperly/backend typecheck # verificación de un solo paquete
pnpm --filter @gruperly/backend test # vitest (backend)
pnpm --filter @gruperly/backend db:* # db:generate / db:migrate / db:push / db:studio
```
## Estructura y fronteras
- `apps/api` — Backend Bun + Hono. Entry point `src/index.ts` (monta `Bun.serve`, puerto por defecto **4000** vía `PORT`). Rutas modulares en `src/routes/`. Prisma en `prisma/schema.prisma`.
- `apps/web` — Frontend React 19 + Vite + Tailwind v4. Entry `src/main.tsx` → `src/router.tsx`. Puerto **6173** (`vite.config.ts`).
- `packages/shared` — Esquemas Zod + tipos. **Se consume como TS fuente directo** (`exports` apunta a `src/index.ts`, sin build previo). `@gruperly/shared` se resuelve vía `paths` en cada tsconfig de app.
- `packages/config` — `tsconfig.base.json`; todos los tsconfig lo extienden.
- `apps/backend` — Backend Node + Hono. Entry point `src/server.ts` (serve de `@hono/node-server`, puerto **4000** vía `PORT`). `src/app.ts` monta `basePath('/api/v1')` + rutas de módulos + notFound/onError en RFC 7807. Capas:
- `src/http/` — infraestructura HTTP: `env.ts`, `problem-details.ts` (+ `problem-builders.ts`, `problem-domain.ts`), `validate.ts` (wrapper de `@hono/zod-validator`), `request-id.ts`, `request-logger.ts`, `security-headers.ts`, `session-auth.ts` (middleware con whitelist pública `/api/v1/health`, `/api/v1/auth`, `/api/auth`).
- `src/modules/<modulo>/` — por módulo: `index.ts`, `routes.ts` y `features/<accion>/{route,use-case}.ts`. **Patrón**: el route valida (`validate.query/json`) y delega en un use-case que devuelve `Result<T, ProblemDetails>` (`ok`/`err` desde `@gruperly/shared`); el route responde con `resultJson` (o `problemJson`).
- `src/lib/` — `prisma.ts` (`getPrismaClient`, proxy `default` y clase `UnitOfWork`), `pagination.ts` (offset/metadata), `email.ts`, `error-message.ts`.
- `src/logger.ts` — pino + pino-pretty.
- Módulos existentes: `health-check`, `auth` (**Better Auth 1.7.2 público**, montado en `/api/v1/auth` vía `basePath` del server; cookie de sesión con `path: "/"`), `groups`, `students`, `payments`, `waitlist` (listados paginados `{ data, pagination }`).
- `apps/web` — Frontend React 19 + Vite + Tailwind v4. Entry `src/main.tsx` → `src/router.tsx`. Puerto **6173** (`vite.config.ts`). Auth client con `basePath: '/api/v1/auth'` (`src/lib/auth-client.ts`); el backend llama a `/api/v1/groups/from-organization` (`src/routes/organizations.tsx`).
- `packages/shared` — Esquemas Zod (v3.24) + tipos + `Result` + Problem Details. **Se consume como TS fuente directo** (`exports` apunta a `src/index.ts`, sin build previo); se resuelve vía el symlink de pnpm en `node_modules` (`@gruperly/shared` no está en `paths` de los tsconfig). El `paths` de los tsconfig solo mapea `@/*` → `src/*` y `@generated/*` → `generated/*`.
- `packages/config` — `tsconfig.base.json`; tsconfigs lo extienden con `"extends": "@gruperly/config/tsconfig.base.json"` (por eso `@gruperly/config` es devDependency de cada paquete).
## Gotchas operativos
- **Prisma 7**: requiere `apps/api/.env` (con `DATABASE_URL`). El CLI lee `apps/api/prisma.config.ts` (datasource `url` vía `env('DATABASE_URL')`); el schema usa `provider = "prisma-client"` con output en `prisma/generated/prisma` (gitignoreado; regenera con `bun run db:generate`). El `PrismaClient` se importa desde `../../prisma/generated/prisma/client` y se instancia con `@prisma/adapter-pg`. Sin `.env` el CLI falla; copia desde `apps/api/.env.example`.
- **Turborepo solo lee lockfile textual `bun.lock`** (no el binario `bun.lockb`). Si se regenera, usa `bun install --save-text-lockfile`. No versiones `bun.lockb`.
- `.env`, `dist/`, `.turbo/` están gitignoreados (que no te extrañe su ausencia).
- **Prisma 7**: requiere `apps/backend/.env` (con `DATABASE_URL`). El CLI lee `apps/backend/prisma.config.ts` (datasource `url` vía `env('DATABASE_URL')`); el schema es **multi-archivo**: `prisma/schema.prisma` (generator con output `../generated/prisma` + datasource simple) que incluye `prisma/models/*.prisma` (`auth.prisma`, `domain.prisma`). El cliente se regenra con `pnpm --filter @gruperly/backend db:generate` y se importa desde `@generated/prisma/client` (alias → `generated/prisma`, gitignoreado); se instancia con `@prisma/adapter-pg`. Sin `.env` el CLI falla; copia desde `apps/backend/.env.example`.
- **Tests (vitest)**: viven en `apps/backend/test/`. Mockean el módulo `@/lib/prisma` con `vi.mock` (usando `vi.hoisted` si el mock se comparte como `tx`); los use-cases ponen el db mockable en `constructor(deps)`. Los tests NO pasan por `sessionAuthMiddleware`; si el route lee `c.get('user')`, inyecta un usuario con un middleware en el harness.
- **zod en v3.24 (pin exacto)**, no v4: `better-call` (transitiva de better-auth) pide `zod ^4` — ese peer warning es conocido y aceptado.
- `.env`, `dist/`, `.turbo/`, `apps/backend/generated/`, `node_modules/` están gitignoreados (que no te extrañe su ausencia).
## Convenciones repo-específicas
- **Frontend en español**: copy de UI, comentarios y textos en español.
- **Base de datos**: todas las tablas usan **snake_case** vía `@@map` en `prisma/schema.prisma` (p. ej. `User` → `users`, `WaitlistEntry` → `waitlist_entries`). Al agregar un modelo nuevo, incluir siempre `@@map("nombre_tabla")`.
- **Base de datos**: todas las tablas usan **snake_case** vía `@@map` en `prisma/models/*.prisma` (p. ej. `User` → `users`, `WaitlistEntry` → `waitlist_entries`). Al agregar un modelo nuevo, incluir siempre `@@map("nombre_tabla")`.
- **API** responde errores en **Problem Details RFC 7807** (`application/problem+json`) y resultados como `{ data, pagination }`; los "use cases" devuelven `Result` y nunca lanzan excepciones de dominio.
- **Tailwind v4** con tokens en `@theme` dentro de `apps/web/src/index.css` (p. ej. `--color-accent: #1e90ff`). Se usan como clases auto-generadas: `text-accent`, `bg-success-soft`, etc. Radius por defecto `0.75rem` (`rounded-xl`).
- **Router NO es file-based** (aunque `stack.md` lo diga): las rutas se declaran manualmente en `apps/web/src/router.tsx` con `createRoute` + `addChildren` y se registran vía module augmentation. **Cada vista nueva debe añadirse ahí.**
- Nav (Inicio/Grupos/Cobros/Ajustes) vive en `apps/web/src/components/layout/nav-items.ts`; es la fuente única para `BottomNav` (móvil) y `Sidebar` (desktop) — no dupliques la lista.
@@ -42,4 +56,4 @@ Monorepo web + API para gestión de cobros grupales. Reglas optimizadas para age
## Fuentes de contexto
- `stack.md` — arquitectura y spec técnica de referencia (UI tokens exactos, stack por app).
- `README.md` — setup de la base de datos y comandos generales.
- `README.md` — setup de la base de datos y comandos generales.