16 KiB
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
// 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
// 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
}
// 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:
-- 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:
// 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:
// 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
// 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');
}
}
// 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
-- 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
// 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 verticalesDIRECTIVA-MULTI-TENANT.md- Aislamiento por tenantDIRECTIVA-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