# Backend Specifications - ERP Construccion

**Fecha:** 2025-12-05
**Version:** 1.0.0
**Stack:** Node.js 20+ / Express.js / TypeScript 5.3+ / TypeORM 0.3.17

---

## Estructura del Directorio

```
05-backend-specs/
+-- README.md (este archivo)
+-- modules/
|   +-- SPEC-construction.md     [MAI-002, MAI-003, MAI-005]
|   +-- SPEC-compliance.md       [MAI-011]
|   +-- SPEC-finance.md          [MAE-014]
|   +-- SPEC-assets.md           [MAE-015]
|   +-- SPEC-documents.md        [MAE-016]
+-- api/
|   +-- API-construction.yaml    [OpenAPI 3.0]
|   +-- API-compliance.yaml
|   +-- API-finance.yaml
|   +-- API-assets.yaml
|   +-- API-documents.yaml
+-- services/
    +-- SERVICE-patterns.md      [Patrones de servicios]
```

---

## Arquitectura Backend

### Estructura de Modulos

```
apps/backend/src/
+-- modules/
|   +-- construction/
|   |   +-- construction.module.ts
|   |   +-- controllers/
|   |   |   +-- project.controller.ts
|   |   |   +-- budget.controller.ts
|   |   |   +-- progress.controller.ts
|   |   +-- services/
|   |   |   +-- project.service.ts
|   |   |   +-- budget.service.ts
|   |   |   +-- progress.service.ts
|   |   +-- entities/
|   |   +-- dto/
|   |   +-- repositories/
|   +-- compliance/
|   +-- finance/
|   +-- assets/
|   +-- documents/
+-- shared/
|   +-- guards/
|   +-- interceptors/
|   +-- decorators/
|   +-- filters/
+-- config/
```

---

## Patrones de Desarrollo

### 1. Estructura de Controlador

```typescript
@Controller('api/v1/projects')
@UseGuards(AuthGuard, TenantGuard)
@ApiTags('projects')
export class ProjectController {
  constructor(private readonly projectService: ProjectService) {}

  @Get()
  @ApiOperation({ summary: 'List all projects' })
  @ApiResponse({ status: 200, type: [ProjectDto] })
  async findAll(
    @Query() query: ProjectQueryDto,
    @CurrentTenant() tenantId: UUID
  ): Promise<PaginatedResponse<ProjectDto>> {
    return this.projectService.findAll(tenantId, query);
  }

  @Get(':id')
  @ApiOperation({ summary: 'Get project by ID' })
  async findOne(
    @Param('id') id: UUID,
    @CurrentTenant() tenantId: UUID
  ): Promise<ProjectDto> {
    return this.projectService.findOne(tenantId, id);
  }

  @Post()
  @ApiOperation({ summary: 'Create new project' })
  async create(
    @Body() dto: CreateProjectDto,
    @CurrentTenant() tenantId: UUID,
    @CurrentUser() userId: UUID
  ): Promise<ProjectDto> {
    return this.projectService.create(tenantId, userId, dto);
  }
}
```

### 2. Estructura de Servicio

```typescript
@Injectable()
export class ProjectService {
  constructor(
    @InjectRepository(Project)
    private readonly projectRepo: Repository<Project>,
    private readonly eventEmitter: EventEmitter2
  ) {}

  async findAll(tenantId: UUID, query: ProjectQueryDto): Promise<PaginatedResponse<ProjectDto>> {
    const qb = this.projectRepo.createQueryBuilder('p')
      .where('p.tenant_id = :tenantId', { tenantId })
      .andWhere('p.deleted_at IS NULL');

    if (query.status) {
      qb.andWhere('p.status = :status', { status: query.status });
    }

    const [items, total] = await qb
      .skip(query.offset)
      .take(query.limit)
      .getManyAndCount();

    return { items: items.map(toDto), total, ...query };
  }

  async create(tenantId: UUID, userId: UUID, dto: CreateProjectDto): Promise<ProjectDto> {
    const project = this.projectRepo.create({
      ...dto,
      tenantId,
      createdBy: userId
    });

    await this.projectRepo.save(project);

    this.eventEmitter.emit('project.created', new ProjectCreatedEvent(project));

    return toDto(project);
  }
}
```

### 3. Estructura de Entidad

```typescript
@Entity('projects', { schema: 'construction' })
export class Project {
  @PrimaryGeneratedColumn('uuid')
  id: string;

  @Column({ name: 'tenant_id', type: 'uuid' })
  tenantId: string;

  @Column({ length: 20 })
  code: string;

  @Column({ length: 200 })
  name: string;

  @Column({ type: 'text', nullable: true })
  description: string;

  @Column({
    type: 'enum',
    enum: ProjectStatus,
    default: ProjectStatus.PLANNING
  })
  status: ProjectStatus;

  @Column({ name: 'progress_percentage', type: 'decimal', precision: 5, scale: 2, default: 0 })
  progressPercentage: number;

  @CreateDateColumn({ name: 'created_at' })
  createdAt: Date;

  @Column({ name: 'created_by', type: 'uuid', nullable: true })
  createdBy: string;

  @UpdateDateColumn({ name: 'updated_at', nullable: true })
  updatedAt: Date;

  @Column({ name: 'deleted_at', nullable: true })
  deletedAt: Date;

  // Relations
  @OneToMany(() => Development, d => d.project)
  developments: Development[];

  @OneToMany(() => Budget, b => b.project)
  budgets: Budget[];
}
```

### 4. DTOs

```typescript
// Create DTO
export class CreateProjectDto {
  @IsString()
  @MaxLength(20)
  code: string;

  @IsString()
  @MaxLength(200)
  name: string;

  @IsOptional()
  @IsString()
  description?: string;

  @IsOptional()
  @IsDateString()
  startDate?: string;

  @IsOptional()
  @IsDateString()
  endDate?: string;
}

// Response DTO
export class ProjectDto {
  id: string;
  code: string;
  name: string;
  description?: string;
  status: ProjectStatus;
  progressPercentage: number;
  startDate?: string;
  endDate?: string;
  createdAt: string;
}

// Query DTO
export class ProjectQueryDto extends PaginationDto {
  @IsOptional()
  @IsEnum(ProjectStatus)
  status?: ProjectStatus;

  @IsOptional()
  @IsString()
  search?: string;
}
```

---

## Modulos Implementados

| Modulo | Spec | Endpoints | Entidades | Estado |
|--------|------|-----------|-----------|--------|
| construction | [SPEC-construction.md](modules/SPEC-construction.md) | 25+ | 15 | Documentado |
| compliance | [SPEC-compliance.md](modules/SPEC-compliance.md) | 30+ | 10 | Documentado |
| finance | [SPEC-finance.md](modules/SPEC-finance.md) | 50+ | 15 | Documentado |
| assets | [SPEC-assets.md](modules/SPEC-assets.md) | 45+ | 12 | Documentado |
| documents | [SPEC-documents.md](modules/SPEC-documents.md) | 40+ | 10 | Documentado |

**Resumen de Endpoints:** 190+ endpoints documentados

---

## Seguridad

### Guards

```typescript
// Tenant isolation guard
@Injectable()
export class TenantGuard implements CanActivate {
  canActivate(context: ExecutionContext): boolean {
    const request = context.switchToHttp().getRequest();
    const tenantId = request.headers['x-tenant-id'];

    if (!tenantId) {
      throw new UnauthorizedException('Tenant ID required');
    }

    request.tenantId = tenantId;
    return true;
  }
}
```

### RLS Context

```typescript
// Middleware para establecer contexto RLS
export class RLSMiddleware implements NestMiddleware {
  constructor(private readonly dataSource: DataSource) {}

  async use(req: Request, res: Response, next: NextFunction) {
    const tenantId = req.headers['x-tenant-id'];

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

    next();
  }
}
```

---

## Testing

### Estructura de Tests

```
__tests__/
+-- unit/
|   +-- services/
|   +-- controllers/
+-- integration/
|   +-- api/
|   +-- repositories/
+-- e2e/
    +-- projects.e2e-spec.ts
```

### Ejemplo de Test

```typescript
describe('ProjectService', () => {
  let service: ProjectService;
  let repository: MockType<Repository<Project>>;

  beforeEach(async () => {
    const module = await Test.createTestingModule({
      providers: [
        ProjectService,
        {
          provide: getRepositoryToken(Project),
          useFactory: repositoryMockFactory
        }
      ]
    }).compile();

    service = module.get(ProjectService);
    repository = module.get(getRepositoryToken(Project));
  });

  describe('create', () => {
    it('should create a project', async () => {
      const dto: CreateProjectDto = {
        code: 'PRJ-001',
        name: 'Test Project'
      };

      repository.create.mockReturnValue({ id: 'uuid', ...dto });
      repository.save.mockResolvedValue({ id: 'uuid', ...dto });

      const result = await service.create('tenant-id', 'user-id', dto);

      expect(result.code).toBe('PRJ-001');
      expect(repository.save).toHaveBeenCalled();
    });
  });
});
```

---

## Referencias

- [Domain Models](../04-modelado/domain-models/)
- [DDL Specifications](../04-modelado/database-design/schemas/)
- [Epicas](../08-epicas/)

---

*Ultima actualizacion: 2025-12-05*
