workspace/projects/gamilit/docs/95-guias-desarrollo/backend/ESTRUCTURA-SHARED.md
rckrdmrd ea1879f4ad feat: Initial workspace structure with multi-level Git configuration
- Configure workspace Git repository with comprehensive .gitignore
- Add Odoo as submodule for ERP reference code
- Include documentation: SETUP.md, GIT-STRUCTURE.md
- Add gitignore templates for projects (backend, frontend, database)
- Structure supports independent repos per project/subproject level

Workspace includes:
- core/ - Reusable patterns, modules, orchestration system
- projects/ - Active projects (erp-suite, gamilit, trading-platform, etc.)
- knowledge-base/ - Reference code and patterns (includes Odoo submodule)
- devtools/ - Development tools and templates
- customers/ - Client implementations template

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2025-12-08 10:44:23 -06:00

8.3 KiB

Estructura de Código Compartido Backend

Versión: 1.0.0 Última Actualización: 2025-11-28 Aplica a: apps/backend/src/shared/


Resumen

La carpeta shared/ contiene código reutilizable que no pertenece a ningún módulo específico. Incluye decoradores, guards, interceptores, filtros, DTOs comunes y utilidades.


Estructura de Carpetas

shared/
├── config/                    # Configuración de la aplicación
│   ├── database.config.ts
│   ├── jwt.config.ts
│   └── app.config.ts
├── constants/                 # Constantes globales
│   ├── database.constants.ts  # Nombres de tablas, esquemas
│   ├── roles.constants.ts     # Roles del sistema
│   └── error-codes.constants.ts
├── decorators/                # Decoradores personalizados
│   ├── api-paginated-response.decorator.ts
│   ├── current-user.decorator.ts
│   ├── roles.decorator.ts
│   └── tenant.decorator.ts
├── dto/                       # DTOs compartidos
│   ├── auth/
│   │   ├── admin-reset-password.dto.ts
│   │   └── reset-password.dto.ts
│   ├── notifications/
│   │   ├── create-notification.dto.ts
│   │   └── notification-response.dto.ts
│   ├── permissions/
│   │   ├── update-role-permissions.dto.ts
│   │   └── update-student-permissions.dto.ts
│   ├── reports/
│   │   └── generate-report.dto.ts
│   └── pagination.dto.ts
├── filters/                   # Exception filters
│   └── http-exception.filter.ts
├── guards/                    # Guards de autorización
│   ├── jwt-auth.guard.ts
│   ├── roles.guard.ts
│   └── rls.guard.ts
├── interceptors/              # Interceptores
│   ├── performance.interceptor.ts
│   ├── rls.interceptor.ts
│   └── transform.interceptor.ts
├── services/                  # Servicios compartidos
│   └── rate-limiter.service.ts
└── utils/                     # Funciones utilitarias
    ├── logger.util.ts
    ├── progress.util.ts
    ├── string.util.ts
    └── validation.util.ts

Componentes Principales

1. Decoradores (decorators/)

@CurrentUser

Extrae el usuario autenticado del request:

// Definición
export const CurrentUser = createParamDecorator(
  (data: unknown, ctx: ExecutionContext) => {
    const request = ctx.switchToHttp().getRequest();
    return request.user;
  },
);

// Uso
@Get('profile')
async getProfile(@CurrentUser() user: UserEntity) {
  return user;
}

@Roles

Define roles requeridos para acceder a un endpoint:

// Definición
export const Roles = (...roles: string[]) => SetMetadata('roles', roles);

// Uso
@Post()
@Roles('admin', 'teacher')
async createContent() { }

@ApiPaginatedResponse

Documenta respuestas paginadas en Swagger:

@Get()
@ApiPaginatedResponse(UserDto)
async findAll(): Promise<PaginatedResponse<UserDto>> { }

2. Guards (guards/)

JwtAuthGuard

Valida tokens JWT en requests:

@Controller('protected')
@UseGuards(JwtAuthGuard)
export class ProtectedController { }

RolesGuard

Verifica que el usuario tenga los roles requeridos:

@UseGuards(JwtAuthGuard, RolesGuard)
@Roles('admin')
export class AdminController { }

RlsGuard

Aplica Row-Level Security basado en tenant:

@UseGuards(JwtAuthGuard, RlsGuard)
export class TenantScopedController { }

3. Interceptores (interceptors/)

RlsInterceptor

Configura el contexto de RLS antes de cada query:

@Injectable()
export class RlsInterceptor implements NestInterceptor {
  async intercept(context: ExecutionContext, next: CallHandler) {
    const request = context.switchToHttp().getRequest();
    const tenantId = request.user?.tenantId;

    // SET app.current_tenant_id = tenantId
    await this.dataSource.query(`SET app.current_tenant_id = '${tenantId}'`);

    return next.handle();
  }
}

PerformanceInterceptor

Mide tiempo de ejecución de endpoints:

@Injectable()
export class PerformanceInterceptor implements NestInterceptor {
  intercept(context: ExecutionContext, next: CallHandler) {
    const start = Date.now();
    return next.handle().pipe(
      tap(() => {
        const duration = Date.now() - start;
        this.logger.log(`${method} ${url} - ${duration}ms`);
      }),
    );
  }
}

4. Filtros (filters/)

HttpExceptionFilter

Formatea errores HTTP de manera consistente:

@Catch(HttpException)
export class HttpExceptionFilter implements ExceptionFilter {
  catch(exception: HttpException, host: ArgumentsHost) {
    const response = host.switchToHttp().getResponse();
    const status = exception.getStatus();

    response.status(status).json({
      success: false,
      error: {
        code: status,
        message: exception.message,
        timestamp: new Date().toISOString(),
      },
    });
  }
}

5. Constantes (constants/)

database.constants.ts

export const SCHEMAS = {
  AUTH_MANAGEMENT: 'auth_management',
  EDUCATIONAL_CONTENT: 'educational_content',
  GAMIFICATION_SYSTEM: 'gamification_system',
  USER_PROGRESS: 'user_progress',
  SOCIAL_FEATURES: 'social_features',
  NOTIFICATION_SYSTEM: 'notification_system',
  AUDIT_LOGGING: 'audit_logging',
  ADMIN_DASHBOARD: 'admin_dashboard',
  SYSTEM_CONFIGURATION: 'system_configuration',
} as const;

export const TABLES = {
  USERS: `${SCHEMAS.AUTH_MANAGEMENT}.users`,
  USER_STATS: `${SCHEMAS.GAMIFICATION_SYSTEM}.user_stats`,
  // ...
} as const;

6. DTOs Compartidos (dto/)

PaginationDto

export class PaginationDto {
  @IsOptional()
  @Type(() => Number)
  @IsInt()
  @Min(1)
  page?: number = 1;

  @IsOptional()
  @Type(() => Number)
  @IsInt()
  @Min(1)
  @Max(100)
  limit?: number = 20;
}

PaginatedResponse

export interface PaginatedResponse<T> {
  data: T[];
  meta: {
    total: number;
    page: number;
    limit: number;
    totalPages: number;
  };
}

7. Utilidades (utils/)

logger.util.ts

export const createLogger = (context: string) => {
  return new Logger(context);
};

validation.util.ts

export const isValidUUID = (value: string): boolean => {
  const uuidRegex = /^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;
  return uuidRegex.test(value);
};

string.util.ts

export const slugify = (text: string): string => {
  return text
    .toLowerCase()
    .normalize('NFD')
    .replace(/[\u0300-\u036f]/g, '')
    .replace(/[^a-z0-9]+/g, '-')
    .replace(/(^-|-$)/g, '');
};

8. Servicios Compartidos (services/)

RateLimiterService

@Injectable()
export class RateLimiterService {
  private readonly store = new Map<string, RateLimitEntry>();

  async checkLimit(key: string, limit: number, windowMs: number): Promise<boolean> {
    const entry = this.store.get(key);
    const now = Date.now();

    if (!entry || now - entry.timestamp > windowMs) {
      this.store.set(key, { count: 1, timestamp: now });
      return true;
    }

    if (entry.count >= limit) {
      return false;
    }

    entry.count++;
    return true;
  }
}

Registro Global

Los componentes compartidos se registran globalmente en app.module.ts:

@Module({
  providers: [
    {
      provide: APP_FILTER,
      useClass: HttpExceptionFilter,
    },
    {
      provide: APP_INTERCEPTOR,
      useClass: RlsInterceptor,
    },
    {
      provide: APP_INTERCEPTOR,
      useClass: PerformanceInterceptor,
    },
    RateLimiterService,
  ],
})
export class AppModule {}

Buenas Prácticas

  1. No duplicar: Si algo se usa en 2+ módulos, moverlo a shared/
  2. Documentar decoradores: Incluir JSDoc con ejemplos de uso
  3. Tests para utilidades: Las funciones en utils/ deben tener tests
  4. Constantes tipadas: Usar as const para type safety
  5. Exports centralizados: Crear index.ts en cada subcarpeta

Ver También