# CLAUDE.md

This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.

## Project Overview

Novasis is a multi-tenant SaaS invoicing backend (Paraguay market) built with NestJS + Prisma + PostgreSQL. It handles electronic invoicing (SIFEN), subscription management, POS operations, and wholesale sales.

## Commands

```bash
pnpm start:dev          # Dev server with watch mode
pnpm build              # Compile TypeScript to dist/
pnpm start:prod         # Run compiled production build
pnpm lint               # ESLint check
pnpm lint:fix           # Auto-fix lint issues
pnpm test               # Run Jest unit tests
pnpm test:e2e           # End-to-end tests
pnpm format             # Prettier formatting

# Database
npx prisma migrate dev  # Apply migrations in dev
npx prisma generate     # Regenerate Prisma client after schema changes
npx prisma db seed      # Run seed script (prisma/seed.ts)
```

## Architecture

### Entry Point & Global Setup (`src/main.ts`)

- Global prefix: `/api` with URI versioning (`/api/v1`)
- Swagger docs at `/docs`
- BullMQ dashboard at `/queues`
- Global `ValidationPipe` (whitelist, forbidNonWhitelisted, transform)
- Global `BigIntSerializerInterceptor` (BigInt→string, Decimal→number in responses)
- Winston logger with daily rotation (14d app, 30d errors)

### Module Pattern

Every feature follows this structure:

```
src/<feature>/
├── <feature>.module.ts
├── <feature>.controller.ts
├── <feature>.service.ts
└── dto/
```

Controllers use a layered guard/decorator stack:

```typescript
@ApiBearerAuth()
@UseGuards(AuthGuard('jwt'), ModuleGuard)
@RequireModule('MODULO_CODE')
// Per-endpoint:
@RequirePermission('MODULO_CODE', 'CREAR')
```

### Auth & Access Control (multi-layer)

1. **JWT Auth** — Access token (1d) + refresh token (7d, stored in Redis with jti rotation)
2. **AppTokenGuard** — Validates `x-app-key` header for public endpoints
3. **ModuleGuard** — Checks module is active in user's subscription (superAdmin/holding/reseller bypass)
4. **PermissionGuard** — Verifies user has specific privilege on module (superAdmin bypasses)
5. **SuscripcionGuard** — Global guard that blocks POST/PUT/PATCH/DELETE when subscription is expired
6. **@SkipSuscripcionCheck()** — Decorator to exempt endpoints from subscription check

User context is extracted via `@GetUser()` decorator from JWT payload.

### Database (Prisma + PostgreSQL)

- Schema: `prisma/schema.prisma` (large file — 240KB+)
- Migrations: `prisma/migrations/` (53+ migrations)
- UUID primary keys with `uuid_generate_v4()`
- Decimal types for financial values
- Soft deletes via `deleted` boolean flags
- Multi-tenant: data scoped by `empresa_id` / `sucursal_id`

### Background Jobs (BullMQ + Redis)

Queues: `email-queue`, `sifen-queue`, `sifen-sync-queue`, `sifen-nc-queue`, `middleware-empresa-queue`

- SIFEN sync status cron runs every 5 minutes
- Redis also serves as cache (5-min TTL default) and refresh token store

### Schema & Migration Conventions

- **Enums en Prisma**: Siempre declarar un `enum NombreEnum { ... }` en `schema.prisma` para campos de estado/tipo. No usar `String @db.VarChar(20) // comentario con valores`. Ejemplo:
  ```prisma
  enum tes_extracto_estado {
    PENDIENTE
    PARCIAL
    CONCILIADO
  }
  // En el modelo:
  estado tes_extracto_estado @default(PENDIENTE)
  ```

- **Migraciones — flujo obligatorio** ⚠️ NUNCA crear archivos `.sql` sueltos en `prisma/migrations/`. Prisma los ignora completamente y no los aplica en producción. Siempre usar directorios:

  ```
  prisma/migrations/
  └── 20260423_bancard_vpos/      ← CORRECTO: directorio
      └── migration.sql           ← el SQL va aquí adentro
  
  prisma/migrations/
  └── 20260423_bancard_vpos.sql   ← MAL: archivo suelto, Prisma lo ignora
  ```

  **Flujo para agregar una migración** (cuando `prisma migrate dev` falla por shadow DB):
  1. Actualizar `prisma/schema.prisma`
  2. Crear directorio: `mkdir -p prisma/migrations/YYYYMMDD_nombre/`
  3. Crear `prisma/migrations/YYYYMMDD_nombre/migration.sql` con el SQL (usar `IF NOT EXISTS` y `DO $$ BEGIN...EXCEPTION WHEN duplicate_object THEN NULL; END $$` para idempotencia)
  4. Aplicar localmente: `npx prisma db execute --file prisma/migrations/YYYYMMDD_nombre/migration.sql --schema prisma/schema.prisma`
  5. Marcar como aplicada en el tracker: `npx prisma migrate resolve --applied "YYYYMMDD_nombre"`
  6. Regenerar cliente: `npx prisma generate`

  **En producción**: `npx prisma migrate deploy` aplica automáticamente todos los directorios no registrados en `_prisma_migrations`. Si una migración falló a medias: `npx prisma migrate resolve --rolled-back "nombre"` para limpiar el estado, corregir el SQL y volver a deployar.

### Catálogo de seguridad (módulos/submódulos/privilegios) y scripts one-shot

El catálogo de seguridad se define en `src/seguridad/seeds/seguridad.seed-data.ts` (módulos → submódulos → privilegios) y se sincroniza **solo, en cada arranque** vía `SeguridadSeedBootstrap` (`OnApplicationBootstrap` → `runCatalogoMaestro()` + `sincronizarPerfilesSistema()`). Es idempotente: registra privilegios nuevos, actualiza existentes (incluido `submodulos.modulo_id` — reparenta si cambió el catálogo), backfillea perfiles con selector `*`/`SUBMODULO.*`. **Nunca borra.** Se desactiva con `SEGURIDAD_SEED_ON_BOOT=false`.

Para editar el catálogo: cambiás `seguridad.seed-data.ts` y **reiniciás el backend** — el bootstrap lo aplica. No hace falta migración SQL para movidas de catálogo (las asignaciones referencian por `id`, no por `codigo`/`modulo_id`).

**Aplicar un cambio de catálogo SIN reiniciar** (o en entornos donde no corre el bootstrap): NO uses `seed-cli.ts` para esto — bootstrapea **todo el AppModule** (colas, cron, conexiones) y bajo `ts-node` OOMea o se cuelga varios minutos. En su lugar, escribí un **script one-shot liviano** que instancia **solo `PrismaClient`** (corre en ~2s, sin AppModule):

  ```ts
  import 'dotenv/config';
  import { PrismaClient } from '@prisma/client';
  const prisma = new PrismaClient();
  // ... consultas/updates directos ...
  main().then(() => prisma.$disconnect()).catch(async (e) => { console.error(e); await prisma.$disconnect(); process.exit(1); });
  ```

  Guardalo en `scripts/`, agregá un entry en `package.json` con el patrón estándar (`ts-node --transpile-only -r tsconfig-paths/register --project tsconfig.scripts.json scripts/<nombre>.ts`) y hacelo idempotente + con flag `--dry`. El `--transpile-only` es lo que evita el error `TS5109` (`module: NodeNext`) sin hacks de env.

  Ejemplo ya disponible — **reparentar un submódulo** a otro módulo (usado para mover Gastos de TESORERIA → COMPRAS):
  ```bash
  pnpm reparent:submodulo -- --submodulo=TES_GASTOS --modulo=COMPRAS --dry   # previsualizar
  pnpm reparent:submodulo -- --submodulo=TES_GASTOS --modulo=COMPRAS         # aplicar
  ```
  (`scripts/reparent-submodulo.ts`: actualiza `submodulos.modulo_id` + backfillea `perfiles_privilegios.modulo_id`.)

  Regla general: **cuando un script solo toca datos vía Prisma, usá `PrismaClient` directo, no `NestFactory.createApplicationContext(AppModule)`.** Reservá el bootstrap del AppModule para scripts que realmente necesiten servicios de Nest (colas, providers con lógica).

**Gating por submódulo (Capa 1) también aplica a superAdmin**: `/auth/me` filtra `permisos` por submódulos activos del plan y además devuelve `privilegios_bloqueados` (privilegios de módulos activos cuyo submódulo fue excluido del plan). El frontend (`AuthStore.hasPermission`) los bloquea incluso para superAdmin — superAdmin bypasea el **perfil** (Capa 2), NO el **plan** (Capa 1). Holdings/resellers bypasean ambas.

### Key Conventions

- **Audit logging**: Use `@Auditable('entity_type')` decorator + `AuditService` for tracking changes
- **Plan limits**: `0` or `null` means unlimited; checked via `PlanLimitsService`
- **File uploads**: S3-compatible (DigitalOcean Spaces) with Sharp image optimization
- **Roles**: superAdmin, holding, reseller, admin, user — guards check these for bypass logic
- **Config**: Environment validated via Joi schema in `src/config/envs.ts`, accessed via `envs` object

### Convenciones globales (aplican a backend y frontend)

- **Inputs numéricos (precios, montos, totales, tipo de cambio, cantidades monetarias)**: en el frontend usar SIEMPRE el componente reutilizable `MonedaInput.jsx`. Formatea al escribir según la moneda configurada (default PYG, sin decimales; USD/otras con decimales según moneda). No usar `<TextField type="number">` crudo para valores monetarios.

- **Enums en código y base de datos**: declarar enums tipados tanto en TypeScript (`enum` o union literal) como en Prisma (`enum` en `schema.prisma`) para todo lo que sea **estado, tipo, categoría, modo, régimen, etc.** No usar strings sueltos con comentarios. Excepción: estados/categorías **dinámicos** definidos por el usuario en runtime (ej: categorías custom de movimientos de tesorería) — esos van en tabla catálogo, no enum.

- **Filosofía UX por pantalla**: cada nueva pantalla o flujo debe respetar estos principios (validar antes de mergear):
  - Navegación clara y lógica (breadcrumbs, tabs jerárquicos, no callejones sin salida)
  - Minimizar pasos y fricción (preferir 1 pantalla con secciones a 4 wizards si los datos son pocos; defaults inteligentes)
  - Interfaz limpia y visualmente organizada (espaciado consistente, agrupación visual de campos relacionados)
  - Elementos consistentes en todo el sistema (botones, colores de estado, posición de acciones primarias/secundarias — reusar componentes `ui/`)
  - Mensajes y comentarios de ayuda contextual (`helperText`, tooltips con `enterDelay=300`, banners de prerrequisito)
  - Ejemplos prácticos en placeholders y helpers (`Ej: 2.798.309`, `Ej: 01/05/2026`, `Ej: 001-001-0000001`)
  - Feedback visual inmediato (loading states, toasts con `notistack` indicando severidad + acción "Deshacer/Reintentar", validación inline)
  - Diseño responsive (mobile / tablet / desktop — usar breakpoints MUI estándar, checklists colapsan a acordeón en `<600px`)
  - Accesibilidad y legibilidad (`aria-describedby` en helpers, `focus-visible` visible, contraste WCAG AA, tamaños de texto legibles)
  - Optimización de velocidad (TanStack Query con cache, paginar listados largos, evitar re-renders innecesarios, lazy-load de pantallas pesadas)

### External Integrations

- **SIFEN** — Paraguay's electronic invoicing system, via middleware service (MIDDLEWARE_SIFEN_URL)
- **Twilio** — SMS notifications
- **SMTP** — Email via Handlebars templates (`src/mail/templates/*.hbs`, copied to dist)
- **QZ Tray** — Direct printing support (certificate-based signing)
