493 lines
16 KiB
Markdown
493 lines
16 KiB
Markdown
# DIRECTIVA: Herencia y Extension de Modulos
|
|
|
|
**Proyecto:** ERP Core - Base Generica Reutilizable
|
|
**Version:** 1.0.0
|
|
**Fecha:** 2025-12-05
|
|
**Aplicable a:** ERP Core y todas las verticales
|
|
**Estado:** OBLIGATORIO
|
|
|
|
---
|
|
|
|
## PRINCIPIO FUNDAMENTAL
|
|
|
|
> **"Las verticales EXTIENDEN el core, nunca lo modifican"**
|
|
|
|
El ERP Core proporciona el 60-70% de funcionalidad comun. Las verticales agregan el 30-40% restante especifico de su giro.
|
|
|
|
---
|
|
|
|
## ARQUITECTURA DE HERENCIA
|
|
|
|
```
|
|
┌─────────────────────────────────────────────────────────────────┐
|
|
│ ERP CORE │
|
|
│ ┌─────────────────────────────────────────────────────────┐ │
|
|
│ │ Modulos Genericos (MGN-*) │ │
|
|
│ │ • Auth, Users, Roles, Tenants │ │
|
|
│ │ • Partners, Products, Catalogs │ │
|
|
│ │ • Sales, Purchases, Inventory │ │
|
|
│ │ • Financial, HR, CRM, Projects │ │
|
|
│ └─────────────────────────────────────────────────────────┘ │
|
|
└────────────────────────────┬────────────────────────────────────┘
|
|
│
|
|
┌────────────────────┼────────────────────┐
|
|
↓ ↓ ↓
|
|
┌───────────────┐ ┌───────────────┐ ┌───────────────┐
|
|
│ CONSTRUCCION │ │VIDRIO TEMPLADO│ │MECANICAS DIESEL│
|
|
│ (MAI-*) │ │ (MVT-*) │ │ (MMD-*) │
|
|
├───────────────┤ ├───────────────┤ ├───────────────┤
|
|
│ +Proyectos │ │ +Produccion │ │ +Ordenes Srv │
|
|
│ +Presupuestos │ │ +Corte Optimo │ │ +Diagnostico │
|
|
│ +Control Obra │ │ +Templado │ │ +Refacciones │
|
|
│ +INFONAVIT │ │ +Instalacion │ │ +Flotillas │
|
|
└───────────────┘ └───────────────┘ └───────────────┘
|
|
```
|
|
|
|
---
|
|
|
|
## TIPOS DE HERENCIA
|
|
|
|
### 1. Uso Directo (Sin Modificacion)
|
|
|
|
La vertical usa el modulo del core tal cual.
|
|
|
|
**Ejemplo:** Modulo de autenticacion
|
|
|
|
```typescript
|
|
// En vertical construccion
|
|
// No se crea nada nuevo, se usa directamente
|
|
import { AuthModule } from '@erp-core/modules/auth';
|
|
|
|
@Module({
|
|
imports: [AuthModule], // Usa directo del core
|
|
})
|
|
export class ConstruccionModule {}
|
|
```
|
|
|
|
**Cuando usar:**
|
|
- Autenticacion (MGN-001)
|
|
- Usuarios base (MGN-002)
|
|
- Roles y permisos (MGN-003)
|
|
- Multi-tenancy (MGN-004)
|
|
- Catalogos maestros (MGN-005)
|
|
- Configuracion sistema (MGN-006)
|
|
- Auditoria (MGN-007)
|
|
|
|
---
|
|
|
|
### 2. Extension de Entidad (Campos Adicionales)
|
|
|
|
La vertical agrega campos a una entidad del core sin modificarla.
|
|
|
|
**Ejemplo:** Partner con campos de construccion
|
|
|
|
```typescript
|
|
// CORE: apps/erp-core/backend/src/modules/partners/entities/partner.entity.ts
|
|
@Entity({ schema: 'core_partners', name: 'partners' })
|
|
export class PartnerEntity extends BaseEntity {
|
|
@Column({ length: 200 })
|
|
name: string;
|
|
|
|
@Column({ name: 'is_company', default: false })
|
|
isCompany: boolean;
|
|
|
|
@Column({ name: 'tax_id', length: 20, nullable: true })
|
|
taxId: string;
|
|
|
|
// ... campos base
|
|
}
|
|
```
|
|
|
|
```typescript
|
|
// VERTICAL: apps/verticales/construccion/backend/src/modules/partners/entities/partner-extension.entity.ts
|
|
@Entity({ schema: 'vertical_construccion', name: 'partner_extensions' })
|
|
export class PartnerConstructionExtEntity {
|
|
@PrimaryColumn('uuid')
|
|
id: string; // Mismo ID que partner del core
|
|
|
|
@OneToOne(() => PartnerEntity)
|
|
@JoinColumn({ name: 'id' })
|
|
partner: PartnerEntity;
|
|
|
|
// Campos especificos de construccion
|
|
@Column({ name: 'contractor_license', length: 50, nullable: true })
|
|
contractorLicense: string;
|
|
|
|
@Column({ name: 'infonavit_number', length: 20, nullable: true })
|
|
infonavitNumber: string;
|
|
|
|
@Column({ name: 'specialty', type: 'enum', enum: ContractorSpecialty, nullable: true })
|
|
specialty: ContractorSpecialty;
|
|
|
|
@Column({ name: 'insurance_policy', length: 50, nullable: true })
|
|
insurancePolicy: string;
|
|
|
|
@Column({ name: 'insurance_expiry', type: 'date', nullable: true })
|
|
insuranceExpiry: Date;
|
|
}
|
|
```
|
|
|
|
**DDL Extension:**
|
|
```sql
|
|
-- Schema de vertical
|
|
CREATE SCHEMA IF NOT EXISTS vertical_construccion;
|
|
|
|
-- Tabla de extension (1:1 con core)
|
|
CREATE TABLE vertical_construccion.partner_extensions (
|
|
id UUID PRIMARY KEY REFERENCES core_partners.partners(id) ON DELETE CASCADE,
|
|
contractor_license VARCHAR(50),
|
|
infonavit_number VARCHAR(20),
|
|
specialty VARCHAR(50),
|
|
insurance_policy VARCHAR(50),
|
|
insurance_expiry DATE,
|
|
created_at TIMESTAMPTZ DEFAULT NOW(),
|
|
updated_at TIMESTAMPTZ DEFAULT NOW()
|
|
);
|
|
|
|
-- Vista unificada para consultas
|
|
CREATE VIEW vertical_construccion.v_partners_full AS
|
|
SELECT
|
|
p.*,
|
|
ext.contractor_license,
|
|
ext.infonavit_number,
|
|
ext.specialty,
|
|
ext.insurance_policy,
|
|
ext.insurance_expiry
|
|
FROM core_partners.partners p
|
|
LEFT JOIN vertical_construccion.partner_extensions ext ON p.id = ext.id;
|
|
```
|
|
|
|
**Service de Extension:**
|
|
```typescript
|
|
// VERTICAL: construccion/backend/src/modules/partners/services/partner-construction.service.ts
|
|
@Injectable()
|
|
export class PartnerConstructionService {
|
|
constructor(
|
|
@InjectRepository(PartnerEntity)
|
|
private partnerRepo: Repository<PartnerEntity>,
|
|
@InjectRepository(PartnerConstructionExtEntity)
|
|
private extRepo: Repository<PartnerConstructionExtEntity>,
|
|
) {}
|
|
|
|
async createWithExtension(dto: CreatePartnerConstructionDto): Promise<PartnerWithExtension> {
|
|
return this.dataSource.transaction(async (manager) => {
|
|
// 1. Crear partner base (core)
|
|
const partner = manager.create(PartnerEntity, {
|
|
name: dto.name,
|
|
isCompany: dto.isCompany,
|
|
taxId: dto.taxId,
|
|
tenantId: dto.tenantId,
|
|
});
|
|
await manager.save(partner);
|
|
|
|
// 2. Crear extension (vertical)
|
|
const extension = manager.create(PartnerConstructionExtEntity, {
|
|
id: partner.id, // Mismo ID
|
|
contractorLicense: dto.contractorLicense,
|
|
infonavitNumber: dto.infonavitNumber,
|
|
specialty: dto.specialty,
|
|
});
|
|
await manager.save(extension);
|
|
|
|
return { ...partner, ...extension };
|
|
});
|
|
}
|
|
|
|
async findWithExtension(id: string): Promise<PartnerWithExtension> {
|
|
const partner = await this.partnerRepo.findOne({ where: { id } });
|
|
const extension = await this.extRepo.findOne({ where: { id } });
|
|
return { ...partner, ...extension };
|
|
}
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
### 3. Modulo Nuevo (Especifico de Vertical)
|
|
|
|
La vertical crea modulos completamente nuevos que no existen en el core.
|
|
|
|
**Ejemplo:** Control de Obra (solo construccion)
|
|
|
|
```
|
|
verticales/construccion/backend/src/modules/control-obra/
|
|
├── control-obra.module.ts
|
|
├── control-obra.controller.ts
|
|
├── control-obra.service.ts
|
|
├── entities/
|
|
│ ├── avance-obra.entity.ts
|
|
│ ├── reporte-diario.entity.ts
|
|
│ └── partida-presupuesto.entity.ts
|
|
├── dto/
|
|
│ ├── create-avance.dto.ts
|
|
│ └── update-avance.dto.ts
|
|
└── __tests__/
|
|
```
|
|
|
|
**Relaciones con Core:**
|
|
```typescript
|
|
// verticales/construccion/backend/src/modules/control-obra/entities/avance-obra.entity.ts
|
|
@Entity({ schema: 'vertical_construccion', name: 'avances_obra' })
|
|
export class AvanceObraEntity extends BaseEntity {
|
|
// FK a proyecto (del core projects o extension)
|
|
@ManyToOne(() => ProjectEntity)
|
|
@JoinColumn({ name: 'project_id' })
|
|
project: ProjectEntity;
|
|
|
|
// FK a usuario del core
|
|
@ManyToOne(() => UserEntity)
|
|
@JoinColumn({ name: 'supervisor_id' })
|
|
supervisor: UserEntity;
|
|
|
|
// Campos especificos de control de obra
|
|
@Column({ type: 'date' })
|
|
fecha: Date;
|
|
|
|
@Column({ name: 'porcentaje_avance', type: 'decimal', precision: 5, scale: 2 })
|
|
porcentajeAvance: number;
|
|
|
|
@Column({ type: 'text', nullable: true })
|
|
observaciones: string;
|
|
|
|
@OneToMany(() => ReporteDiarioEntity, r => r.avance)
|
|
reportes: ReporteDiarioEntity[];
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
### 4. Override de Comportamiento (Herencia de Service)
|
|
|
|
La vertical modifica el comportamiento de un service del core.
|
|
|
|
**Ejemplo:** Validacion adicional en ventas
|
|
|
|
```typescript
|
|
// CORE: erp-core/backend/src/modules/sales/services/sale-order.service.ts
|
|
@Injectable()
|
|
export class SaleOrderService {
|
|
async create(dto: CreateSaleOrderDto): Promise<SaleOrderEntity> {
|
|
// Validaciones base
|
|
await this.validatePartner(dto.partnerId);
|
|
await this.validateProducts(dto.lines);
|
|
|
|
// Crear orden
|
|
return this.repository.save(dto);
|
|
}
|
|
|
|
protected async validatePartner(partnerId: string): Promise<void> {
|
|
const partner = await this.partnerService.findOne(partnerId);
|
|
if (!partner) throw new NotFoundException('Partner no encontrado');
|
|
}
|
|
}
|
|
```
|
|
|
|
```typescript
|
|
// VERTICAL: construccion/backend/src/modules/sales/services/sale-order-construction.service.ts
|
|
@Injectable()
|
|
export class SaleOrderConstructionService extends SaleOrderService {
|
|
constructor(
|
|
// Inyectar dependencias del core
|
|
partnerService: PartnerService,
|
|
// Agregar dependencias de vertical
|
|
private projectService: ProjectService,
|
|
private budgetService: BudgetService,
|
|
) {
|
|
super(partnerService);
|
|
}
|
|
|
|
// Override del metodo create
|
|
async create(dto: CreateSaleOrderConstructionDto): Promise<SaleOrderEntity> {
|
|
// Validaciones adicionales de construccion
|
|
await this.validateProject(dto.projectId);
|
|
await this.validateBudget(dto.projectId, dto.lines);
|
|
|
|
// Llamar al metodo base
|
|
const order = await super.create(dto);
|
|
|
|
// Crear extension
|
|
await this.createExtension(order.id, dto);
|
|
|
|
return order;
|
|
}
|
|
|
|
private async validateProject(projectId: string): Promise<void> {
|
|
const project = await this.projectService.findOne(projectId);
|
|
if (!project) throw new NotFoundException('Proyecto no encontrado');
|
|
if (project.status === 'cancelled') {
|
|
throw new BadRequestException('No se puede vender a proyecto cancelado');
|
|
}
|
|
}
|
|
|
|
private async validateBudget(projectId: string, lines: SaleOrderLineDto[]): Promise<void> {
|
|
const budget = await this.budgetService.getAvailable(projectId);
|
|
const total = lines.reduce((sum, l) => sum + l.quantity * l.priceUnit, 0);
|
|
if (total > budget) {
|
|
throw new BadRequestException('Monto excede presupuesto disponible');
|
|
}
|
|
}
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
## REGLAS DE HERENCIA
|
|
|
|
### PERMITIDO
|
|
|
|
| Accion | Descripcion | Ejemplo |
|
|
|--------|-------------|---------|
|
|
| Usar directo | Importar modulo sin cambios | `import { AuthModule }` |
|
|
| Extender entidad | Agregar campos via tabla 1:1 | `partner_extensions` |
|
|
| Crear modulo nuevo | Modulo especifico de vertical | `control-obra` |
|
|
| Override service | Heredar y agregar logica | `extends SaleOrderService` |
|
|
| Agregar endpoints | Nuevos endpoints en vertical | `POST /api/construccion/avances` |
|
|
| Crear vistas | Vistas que combinan core + vertical | `v_partners_full` |
|
|
|
|
### PROHIBIDO
|
|
|
|
| Accion | Razon | Alternativa |
|
|
|--------|-------|-------------|
|
|
| Modificar entidad core | Rompe otras verticales | Crear tabla extension |
|
|
| Eliminar campos core | Rompe funcionalidad base | Usar `is_active` |
|
|
| Modificar DDL core | Afecta todas las verticales | Crear schema vertical |
|
|
| Fork de modulo core | Duplicacion, mantenimiento | Heredar/extender |
|
|
| Modificar API core | Rompe contratos | Crear endpoints propios |
|
|
|
|
---
|
|
|
|
## ESTRUCTURA DE SCHEMAS
|
|
|
|
```sql
|
|
-- Schemas del CORE (no modificar)
|
|
core_system -- Tenants, sequences, config
|
|
core_auth -- Users, sessions, tokens
|
|
core_partners -- Partners, addresses, contacts
|
|
core_products -- Products, variants, categories
|
|
core_sales -- Sale orders, lines
|
|
core_purchases -- Purchase orders, lines
|
|
core_inventory -- Stock, moves, locations
|
|
core_financial -- Accounts, invoices, payments
|
|
core_hr -- Employees, contracts
|
|
core_crm -- Leads, opportunities
|
|
core_projects -- Projects genericos
|
|
|
|
-- Schemas de VERTICALES (extensiones)
|
|
vertical_construccion -- Extension para construccion
|
|
vertical_vidrio -- Extension para vidrio templado
|
|
vertical_mecanicas -- Extension para mecanicas diesel
|
|
vertical_clinicas -- Extension para clinicas
|
|
vertical_retail -- Extension para punto de venta
|
|
```
|
|
|
|
---
|
|
|
|
## PATRON DE MODULO VERTICAL
|
|
|
|
```typescript
|
|
// verticales/{vertical}/backend/src/modules/{modulo}/{modulo}.module.ts
|
|
import { Module } from '@nestjs/common';
|
|
import { TypeOrmModule } from '@nestjs/typeorm';
|
|
|
|
// Importar del CORE (uso directo)
|
|
import { PartnersModule } from '@erp-core/modules/partners';
|
|
import { ProductsModule } from '@erp-core/modules/products';
|
|
import { SalesModule } from '@erp-core/modules/sales';
|
|
|
|
// Entidades locales (extensiones + nuevas)
|
|
import { PartnerConstructionExtEntity } from './entities/partner-extension.entity';
|
|
import { ProjectObraEntity } from './entities/project-obra.entity';
|
|
import { AvanceObraEntity } from './entities/avance-obra.entity';
|
|
|
|
// Services locales
|
|
import { PartnerConstructionService } from './services/partner-construction.service';
|
|
import { ProjectObraService } from './services/project-obra.service';
|
|
import { ControlObraService } from './services/control-obra.service';
|
|
|
|
// Controllers locales
|
|
import { PartnerConstructionController } from './controllers/partner-construction.controller';
|
|
import { ProjectObraController } from './controllers/project-obra.controller';
|
|
import { ControlObraController } from './controllers/control-obra.controller';
|
|
|
|
@Module({
|
|
imports: [
|
|
// Modulos del core (uso directo)
|
|
PartnersModule,
|
|
ProductsModule,
|
|
SalesModule,
|
|
|
|
// Entidades locales
|
|
TypeOrmModule.forFeature([
|
|
PartnerConstructionExtEntity,
|
|
ProjectObraEntity,
|
|
AvanceObraEntity,
|
|
]),
|
|
],
|
|
controllers: [
|
|
PartnerConstructionController,
|
|
ProjectObraController,
|
|
ControlObraController,
|
|
],
|
|
providers: [
|
|
PartnerConstructionService,
|
|
ProjectObraService,
|
|
ControlObraService,
|
|
],
|
|
exports: [
|
|
PartnerConstructionService,
|
|
ProjectObraService,
|
|
ControlObraService,
|
|
],
|
|
})
|
|
export class ConstruccionModule {}
|
|
```
|
|
|
|
---
|
|
|
|
## CHECKLIST DE EXTENSION
|
|
|
|
Antes de extender un modulo del core, verificar:
|
|
|
|
### Analisis
|
|
- [ ] Identificar que modulo del core se extiende
|
|
- [ ] Documentar campos adicionales requeridos
|
|
- [ ] Identificar relaciones con otros modulos
|
|
- [ ] Verificar que no existe ya una extension similar
|
|
|
|
### Diseno
|
|
- [ ] Crear DDL-SPEC para tabla de extension
|
|
- [ ] Disenar vistas unificadas
|
|
- [ ] Especificar indices necesarios
|
|
- [ ] Definir FK a tablas del core
|
|
|
|
### Implementacion
|
|
- [ ] Crear entidad de extension (1:1 con core)
|
|
- [ ] Crear/heredar service
|
|
- [ ] Crear controller con endpoints propios
|
|
- [ ] Integrar en modulo de vertical
|
|
|
|
### Validacion
|
|
- [ ] Funcionalidad core no afectada
|
|
- [ ] Extension funciona correctamente
|
|
- [ ] Tests de integracion pasan
|
|
- [ ] Documentacion actualizada
|
|
|
|
---
|
|
|
|
## REFERENCIAS
|
|
|
|
### Directivas Relacionadas
|
|
- `DIRECTIVA-EXTENSION-VERTICALES.md` - Como se estructuran verticales
|
|
- `DIRECTIVA-MULTI-TENANT.md` - Aislamiento por tenant
|
|
- `DIRECTIVA-PATRONES-ODOO.md` - Patrones de herencia Odoo
|
|
|
|
### Codigo de Referencia
|
|
- Odoo module inheritance: `knowledge-base/patterns/odoo/inheritance.md`
|
|
- Gamilit modules: `projects/gamilit/apps/backend/src/modules/`
|
|
|
|
---
|
|
|
|
**Version:** 1.0.0
|
|
**Ultima actualizacion:** 2025-12-05
|
|
**Estado:** ACTIVA Y OBLIGATORIA
|