- 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>
8.3 KiB
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
- No duplicar: Si algo se usa en 2+ módulos, moverlo a shared/
- Documentar decoradores: Incluir JSDoc con ejemplos de uso
- Tests para utilidades: Las funciones en utils/ deben tener tests
- Constantes tipadas: Usar
as constpara type safety - Exports centralizados: Crear
index.tsen cada subcarpeta
Ver También
- ESTRUCTURA-MODULOS.md - Estructura de módulos
- ERROR-HANDLING.md - Manejo de errores
- API-CONVENTIONS.md - Convenciones de API