529 lines
12 KiB
Markdown
529 lines
12 KiB
Markdown
# Directiva: Control de Obra
|
|
|
|
## Metadatos
|
|
|
|
| Campo | Valor |
|
|
|-------|-------|
|
|
| **ID** | DIR-CONST-002 |
|
|
| **Versión** | 1.0.0 |
|
|
| **Ámbito** | Vertical Construcción |
|
|
| **Módulos** | MAI-002, MAI-005, MAI-012 |
|
|
| **Estado** | Activa |
|
|
|
|
## Propósito
|
|
|
|
Define las reglas y patrones para el control de obra, gestión de proyectos de construcción, avances y recursos.
|
|
|
|
## Alcance
|
|
|
|
Esta directiva aplica a:
|
|
- Estructura jerárquica de proyectos
|
|
- Control de avances de obra
|
|
- Gestión de recursos y materiales
|
|
- Presupuestos y costos
|
|
- Subcontratos y contratos
|
|
- Estimaciones de obra
|
|
|
|
## Estructura Jerárquica de Proyectos
|
|
|
|
```yaml
|
|
jerarquia:
|
|
nivel_1:
|
|
nombre: Proyecto/Desarrollo
|
|
entidad: Project
|
|
ejemplo: "Fraccionamiento Los Álamos"
|
|
|
|
nivel_2:
|
|
nombre: Etapa/Fase
|
|
entidad: Phase
|
|
ejemplo: "Etapa 3 - 120 viviendas"
|
|
|
|
nivel_3:
|
|
nombre: Manzana/Torre
|
|
entidad: Block
|
|
ejemplo: "Manzana 15"
|
|
|
|
nivel_4:
|
|
nombre: Lote/Vivienda/Departamento
|
|
entidad: Unit
|
|
ejemplo: "Lote 15-A Casa Modelo Roma"
|
|
|
|
tipos_proyecto:
|
|
HORIZONTAL:
|
|
niveles: [Desarrollo, Etapa, Manzana, Lote]
|
|
ejemplo: Fraccionamiento de casas
|
|
|
|
VERTICAL:
|
|
niveles: [Desarrollo, Torre, Piso, Departamento]
|
|
ejemplo: Edificio de departamentos
|
|
|
|
MIXTO:
|
|
niveles: [Desarrollo, Zona, Bloque, Unidad]
|
|
ejemplo: Centro comercial con oficinas
|
|
```
|
|
|
|
## Reglas Obligatorias
|
|
|
|
### 1. Entidades de Proyecto
|
|
|
|
```typescript
|
|
// Proyecto base
|
|
interface ProjectEntity {
|
|
id: string;
|
|
tenantId: string;
|
|
|
|
// Identificación
|
|
code: string; // Código único: PROJ-2025-001
|
|
name: string;
|
|
projectType: ProjectType; // HORIZONTAL | VERTICAL | MIXTO
|
|
|
|
// Ubicación
|
|
address: string;
|
|
city: string;
|
|
state: string;
|
|
coordinates: GeoPoint | null;
|
|
|
|
// Estado y fechas
|
|
status: ProjectStatus;
|
|
plannedStartDate: Date;
|
|
plannedEndDate: Date;
|
|
actualStartDate: Date | null;
|
|
actualEndDate: Date | null;
|
|
|
|
// Métricas
|
|
totalUnits: number;
|
|
completedUnits: number;
|
|
progressPercentage: number;
|
|
|
|
// Presupuesto
|
|
budgetAmount: number;
|
|
actualCost: number;
|
|
costVariance: number;
|
|
|
|
// Auditoría
|
|
createdAt: Date;
|
|
updatedAt: Date;
|
|
createdBy: string;
|
|
updatedBy: string;
|
|
}
|
|
|
|
type ProjectStatus =
|
|
| 'PLANEACION'
|
|
| 'EN_CONSTRUCCION'
|
|
| 'PAUSADO'
|
|
| 'FINALIZADO'
|
|
| 'CANCELADO';
|
|
|
|
type ProjectType = 'HORIZONTAL' | 'VERTICAL' | 'MIXTO';
|
|
```
|
|
|
|
### 2. Control de Avances
|
|
|
|
```yaml
|
|
avances:
|
|
tipos:
|
|
- FISICO: # Porcentaje de obra ejecutada
|
|
- FINANCIERO: # Porcentaje de presupuesto ejercido
|
|
- PROGRAMATICO: # Cumplimiento del programa
|
|
|
|
granularidad:
|
|
- Por unidad (casa/depto)
|
|
- Por partida de presupuesto
|
|
- Por concepto de obra
|
|
- Por subcontrato
|
|
|
|
frecuencia_registro:
|
|
minima: Semanal
|
|
recomendada: Diaria para conceptos críticos
|
|
```
|
|
|
|
```typescript
|
|
// Registro de avance
|
|
interface ProgressEntry {
|
|
id: string;
|
|
tenantId: string;
|
|
projectId: string;
|
|
unitId: string;
|
|
|
|
// Avance
|
|
date: Date;
|
|
conceptId: string; // Concepto de obra
|
|
previousProgress: number; // % anterior
|
|
currentProgress: number; // % actual
|
|
incrementProgress: number; // Incremento
|
|
|
|
// Cantidades
|
|
quantity: number;
|
|
unitOfMeasure: string;
|
|
|
|
// Validación
|
|
registeredBy: string;
|
|
validatedBy: string | null;
|
|
validatedAt: Date | null;
|
|
|
|
// Evidencia
|
|
photos: string[]; // URLs de fotos
|
|
notes: string;
|
|
}
|
|
```
|
|
|
|
### 3. Gestión de Recursos
|
|
|
|
```typescript
|
|
// Asignación de recursos
|
|
interface ResourceAssignment {
|
|
id: string;
|
|
tenantId: string;
|
|
projectId: string;
|
|
|
|
// Recurso
|
|
resourceType: ResourceType;
|
|
resourceId: string;
|
|
|
|
// Asignación
|
|
assignedFrom: Date;
|
|
assignedTo: Date;
|
|
quantity: number;
|
|
|
|
// Costo
|
|
dailyRate: number;
|
|
estimatedCost: number;
|
|
actualCost: number;
|
|
}
|
|
|
|
type ResourceType =
|
|
| 'MANO_OBRA'
|
|
| 'EQUIPO'
|
|
| 'MAQUINARIA'
|
|
| 'MATERIAL';
|
|
```
|
|
|
|
### 4. Presupuesto y Costos
|
|
|
|
```yaml
|
|
estructura_presupuesto:
|
|
niveles:
|
|
- Partida: # Ej: "01 - Preliminares"
|
|
- Subpartida: # Ej: "01.01 - Limpieza"
|
|
- Concepto: # Ej: "01.01.001 - Trazo y nivelación"
|
|
|
|
tipos_costo:
|
|
- MANO_OBRA:
|
|
directa: true
|
|
indirecta: true
|
|
- MATERIALES:
|
|
locales: true
|
|
importados: true
|
|
- EQUIPOS:
|
|
propios: true
|
|
rentados: true
|
|
- SUBCONTRATOS:
|
|
mano_obra: true
|
|
paquete: true
|
|
- INDIRECTOS:
|
|
administracion: true
|
|
financiamiento: true
|
|
```
|
|
|
|
```typescript
|
|
interface BudgetItem {
|
|
id: string;
|
|
tenantId: string;
|
|
projectId: string;
|
|
|
|
// Jerarquía
|
|
partitionCode: string; // "01.01.001"
|
|
parentId: string | null;
|
|
level: number; // 1, 2, 3
|
|
|
|
// Concepto
|
|
description: string;
|
|
unitOfMeasure: string;
|
|
|
|
// Cantidades
|
|
budgetedQuantity: number;
|
|
executedQuantity: number;
|
|
|
|
// Precios
|
|
unitPrice: number;
|
|
budgetedAmount: number;
|
|
executedAmount: number;
|
|
|
|
// Variación
|
|
quantityVariance: number;
|
|
amountVariance: number;
|
|
variancePercentage: number;
|
|
}
|
|
```
|
|
|
|
### 5. Subcontratos
|
|
|
|
```typescript
|
|
interface SubcontractEntity {
|
|
id: string;
|
|
tenantId: string;
|
|
projectId: string;
|
|
|
|
// Contratista
|
|
contractorId: string;
|
|
contractorName: string;
|
|
|
|
// Contrato
|
|
contractNumber: string;
|
|
contractType: SubcontractType;
|
|
description: string;
|
|
|
|
// Alcance
|
|
budgetItems: string[]; // IDs de conceptos
|
|
|
|
// Montos
|
|
contractAmount: number;
|
|
amortizations: number; // Anticipos amortizados
|
|
deductions: number; // Deducciones
|
|
retentions: number; // Retenciones
|
|
netPayable: number;
|
|
|
|
// Estado
|
|
status: SubcontractStatus;
|
|
startDate: Date;
|
|
endDate: Date;
|
|
|
|
// Estimaciones
|
|
estimationsCount: number;
|
|
estimatedAmount: number;
|
|
paidAmount: number;
|
|
}
|
|
|
|
type SubcontractType =
|
|
| 'MANO_OBRA' // Solo mano de obra
|
|
| 'SUMINISTRO' // Solo materiales
|
|
| 'PAQUETE' // Todo incluido
|
|
| 'PRECIO_UNITARIO' // Por unidad ejecutada
|
|
| 'PRECIO_ALZADO'; // Monto fijo
|
|
|
|
type SubcontractStatus =
|
|
| 'BORRADOR'
|
|
| 'ACTIVO'
|
|
| 'PAUSADO'
|
|
| 'FINIQUITADO'
|
|
| 'CANCELADO';
|
|
```
|
|
|
|
## Patrones de Implementación
|
|
|
|
### Schema de Base de Datos
|
|
|
|
```sql
|
|
-- Schema de proyectos
|
|
CREATE SCHEMA IF NOT EXISTS project_management;
|
|
|
|
-- Proyectos principales
|
|
CREATE TABLE project_management.projects (
|
|
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
|
|
tenant_id UUID NOT NULL,
|
|
code VARCHAR(50) NOT NULL,
|
|
name VARCHAR(200) NOT NULL,
|
|
project_type VARCHAR(20) NOT NULL,
|
|
status VARCHAR(30) NOT NULL DEFAULT 'PLANEACION',
|
|
|
|
-- Ubicación
|
|
address TEXT,
|
|
city VARCHAR(100),
|
|
state VARCHAR(100),
|
|
coordinates POINT,
|
|
|
|
-- Fechas
|
|
planned_start_date DATE,
|
|
planned_end_date DATE,
|
|
actual_start_date DATE,
|
|
actual_end_date DATE,
|
|
|
|
-- Métricas
|
|
total_units INTEGER DEFAULT 0,
|
|
completed_units INTEGER DEFAULT 0,
|
|
progress_percentage DECIMAL(5,2) DEFAULT 0,
|
|
|
|
-- Presupuesto
|
|
budget_amount DECIMAL(15,2) DEFAULT 0,
|
|
actual_cost DECIMAL(15,2) DEFAULT 0,
|
|
|
|
-- Auditoría
|
|
created_at TIMESTAMPTZ DEFAULT NOW(),
|
|
updated_at TIMESTAMPTZ DEFAULT NOW(),
|
|
created_by UUID,
|
|
updated_by UUID,
|
|
is_active BOOLEAN DEFAULT TRUE,
|
|
|
|
UNIQUE(tenant_id, code)
|
|
);
|
|
|
|
-- Índices obligatorios
|
|
CREATE INDEX idx_projects_tenant_id ON project_management.projects(tenant_id);
|
|
CREATE INDEX idx_projects_status ON project_management.projects(status);
|
|
CREATE INDEX idx_projects_type ON project_management.projects(project_type);
|
|
|
|
-- RLS
|
|
ALTER TABLE project_management.projects ENABLE ROW LEVEL SECURITY;
|
|
CREATE POLICY "tenant_isolation" ON project_management.projects
|
|
USING (tenant_id = current_setting('app.current_tenant_id')::uuid);
|
|
|
|
-- Fases/Etapas
|
|
CREATE TABLE project_management.phases (
|
|
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
|
|
tenant_id UUID NOT NULL,
|
|
project_id UUID NOT NULL REFERENCES project_management.projects(id),
|
|
code VARCHAR(50) NOT NULL,
|
|
name VARCHAR(200) NOT NULL,
|
|
sequence INTEGER NOT NULL,
|
|
status VARCHAR(30) NOT NULL DEFAULT 'PLANEACION',
|
|
total_units INTEGER DEFAULT 0,
|
|
completed_units INTEGER DEFAULT 0,
|
|
created_at TIMESTAMPTZ DEFAULT NOW(),
|
|
updated_at TIMESTAMPTZ DEFAULT NOW()
|
|
);
|
|
|
|
-- Unidades (viviendas/departamentos)
|
|
CREATE TABLE project_management.units (
|
|
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
|
|
tenant_id UUID NOT NULL,
|
|
project_id UUID NOT NULL REFERENCES project_management.projects(id),
|
|
phase_id UUID REFERENCES project_management.phases(id),
|
|
block_id UUID,
|
|
|
|
code VARCHAR(50) NOT NULL,
|
|
unit_type VARCHAR(50), -- Casa, Depto, Local
|
|
prototype_id UUID, -- Modelo de vivienda
|
|
|
|
status VARCHAR(30) NOT NULL DEFAULT 'PENDIENTE',
|
|
progress_percentage DECIMAL(5,2) DEFAULT 0,
|
|
|
|
-- Venta
|
|
sale_status VARCHAR(30) DEFAULT 'DISPONIBLE',
|
|
client_id UUID,
|
|
|
|
created_at TIMESTAMPTZ DEFAULT NOW(),
|
|
updated_at TIMESTAMPTZ DEFAULT NOW()
|
|
);
|
|
```
|
|
|
|
### Servicio de Avances
|
|
|
|
```typescript
|
|
@Injectable()
|
|
export class ProgressService {
|
|
constructor(
|
|
@InjectRepository(ProgressEntryEntity)
|
|
private progressRepo: Repository<ProgressEntryEntity>,
|
|
@InjectRepository(UnitEntity)
|
|
private unitRepo: Repository<UnitEntity>,
|
|
private eventEmitter: EventEmitter2,
|
|
) {}
|
|
|
|
async registerProgress(
|
|
dto: RegisterProgressDto,
|
|
tenantId: string,
|
|
userId: string
|
|
): Promise<ProgressEntryEntity> {
|
|
// Obtener avance anterior
|
|
const lastProgress = await this.progressRepo.findOne({
|
|
where: {
|
|
unitId: dto.unitId,
|
|
conceptId: dto.conceptId,
|
|
tenantId
|
|
},
|
|
order: { date: 'DESC' }
|
|
});
|
|
|
|
const previousProgress = lastProgress?.currentProgress ?? 0;
|
|
|
|
// Validar que no retroceda
|
|
if (dto.currentProgress < previousProgress) {
|
|
throw new BadRequestException(
|
|
'El avance no puede ser menor al registrado anteriormente'
|
|
);
|
|
}
|
|
|
|
// Crear registro
|
|
const entry = this.progressRepo.create({
|
|
...dto,
|
|
tenantId,
|
|
previousProgress,
|
|
incrementProgress: dto.currentProgress - previousProgress,
|
|
registeredBy: userId,
|
|
});
|
|
|
|
const saved = await this.progressRepo.save(entry);
|
|
|
|
// Actualizar avance de unidad
|
|
await this.updateUnitProgress(dto.unitId, tenantId);
|
|
|
|
// Emitir evento
|
|
this.eventEmitter.emit('progress.registered', {
|
|
tenantId,
|
|
projectId: dto.projectId,
|
|
unitId: dto.unitId,
|
|
progress: dto.currentProgress
|
|
});
|
|
|
|
return saved;
|
|
}
|
|
|
|
private async updateUnitProgress(
|
|
unitId: string,
|
|
tenantId: string
|
|
): Promise<void> {
|
|
// Calcular promedio ponderado de avances por concepto
|
|
const result = await this.progressRepo
|
|
.createQueryBuilder('p')
|
|
.select('AVG(p.current_progress)', 'avgProgress')
|
|
.where('p.unit_id = :unitId', { unitId })
|
|
.andWhere('p.tenant_id = :tenantId', { tenantId })
|
|
.getRawOne();
|
|
|
|
await this.unitRepo.update(
|
|
{ id: unitId, tenantId },
|
|
{ progressPercentage: result.avgProgress ?? 0 }
|
|
);
|
|
}
|
|
}
|
|
```
|
|
|
|
## Nomenclatura de Códigos
|
|
|
|
```yaml
|
|
proyectos:
|
|
formato: "PROJ-{YYYY}-{NNN}"
|
|
ejemplo: "PROJ-2025-001"
|
|
|
|
fases:
|
|
formato: "{PROJ_CODE}-E{NN}"
|
|
ejemplo: "PROJ-2025-001-E03"
|
|
|
|
unidades:
|
|
horizontal: "M{MZ}-L{LT}" # M15-L07
|
|
vertical: "T{TW}-P{PO}-D{DP}" # T01-P05-D02
|
|
|
|
presupuesto:
|
|
partida: "{NN}" # 01
|
|
subpartida: "{NN}.{NN}" # 01.03
|
|
concepto: "{NN}.{NN}.{NNN}" # 01.03.015
|
|
```
|
|
|
|
## Validaciones Pre-Commit
|
|
|
|
- [ ] Jerarquía de proyecto correctamente implementada
|
|
- [ ] Control de avances con validación de retroceso
|
|
- [ ] Presupuesto estructurado por niveles
|
|
- [ ] RLS habilitado en todas las tablas
|
|
- [ ] Eventos emitidos en operaciones críticas
|
|
- [ ] Códigos generados según nomenclatura
|
|
|
|
## Referencias
|
|
|
|
- Documentación proyectos: `/docs/01-fase-alcance-inicial/MAI-002-proyectos-estructura/`
|
|
- Documentación avances: `/docs/01-fase-alcance-inicial/MAI-005-control-obra/`
|
|
- Core directivas: `../../erp-core/orchestration/directivas/`
|
|
|
|
---
|
|
*Directiva específica de Vertical Construcción*
|