# Vertical Development Guide ## Overview Este documento explica cómo crear una **nueva vertical** de negocio que extiende el **erp-core** genérico. El patrón sigue el modelo de Odoo ERP: core reutilizable + extensiones especializadas por industria. **Verticales Existentes:** - Construcción (INFONAVIT) - 35% completado - Vidrio Templado - 0% - Mecánicas Diesel - 30% - Retail (POS) - 0% - Clínicas - 0% ## Architecture Pattern ### Core + Vertical Model ``` ┌─────────────────────────────────────┐ │ ERP CORE (60-70%) │ │ Generic modules: auth, inventory, │ │ sales, purchases, financial, etc. │ └────────────┬────────────────────────┘ │ extends ▼ ┌─────────────────────────────────────┐ │ VERTICAL (30-40%) │ │ Industry-specific extensions │ │ - Override methods │ │ - Add new modules │ │ - Extend database schemas │ └─────────────────────────────────────┘ ``` ### Key Principles 1. **Don't modify core** - Core stays generic and reusable 2. **Extend, don't replace** - Vertical extends core modules 3. **Inheritance over duplication** - Use TypeScript class inheritance 4. **Additive database changes** - Add schemas, don't modify core schemas 5. **Separate documentation** - Each vertical has its own docs/ ## Step-by-Step: Create New Vertical ### 1. Project Structure ```bash # Create vertical directory mkdir -p apps/verticales/my-vertical cd apps/verticales/my-vertical # Create standard structure mkdir -p backend/src/{modules,shared} mkdir -p frontend/src/{modules,shared} mkdir -p database/ddl/schemas mkdir -p docs mkdir -p orchestration/{00-guidelines,trazas,estados} ``` **Result:** ``` apps/verticales/my-vertical/ ├── backend/ │ └── src/ │ ├── modules/ # Industry-specific modules │ ├── shared/ # Shared utilities │ ├── routes/ # API routes │ └── index.ts # Entry point ├── frontend/ │ └── src/ │ ├── modules/ # UI modules │ └── shared/ # Shared components ├── database/ │ ├── ddl/ │ │ └── schemas/ # Vertical schemas │ ├── migrations/ # Database migrations │ └── seeds/ # Test data ├── docs/ # Vertical documentation └── orchestration/ # Agent orchestration ├── 00-guidelines/ │ └── CONTEXTO-PROYECTO.md ├── trazas/ ├── estados/ └── PROXIMA-ACCION.md ``` ### 2. Define Vertical Context Create `orchestration/00-guidelines/CONTEXTO-PROYECTO.md`: ```markdown # Contexto del Proyecto: [Vertical Name] ## Descripción [What is this vertical? What industry problem does it solve?] ## Módulos Específicos 1. [Module 1] - [Description] 2. [Module 2] - [Description] ## Dependencias del Core - auth: Authentication & authorization - inventory: Product management (extended) - sales: Sales management (extended) - [List core modules used] ## Schemas de Base de Datos 1. [schema_name] - [Purpose] 2. [schema_name] - [Purpose] ## Estado Actual [Current development status] ``` ### 3. Database Schema Design #### Create Vertical Schema ```bash # Create schema DDL touch database/ddl/schemas/my_vertical_management/schema.sql ``` **Example:** `database/ddl/schemas/my_vertical_management/schema.sql` ```sql -- ============================================ -- SCHEMA: my_vertical_management -- PURPOSE: Industry-specific data for my vertical -- DEPENDS ON: auth, core, inventory (from erp-core) -- ============================================ CREATE SCHEMA IF NOT EXISTS my_vertical_management; -- ============================================ -- TABLE: my_vertical_management.custom_entities -- ============================================ CREATE TABLE my_vertical_management.custom_entities ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), tenant_id UUID NOT NULL REFERENCES auth.tenants(id), -- Link to core entities product_id UUID REFERENCES products.products(id), partner_id UUID REFERENCES core.partners(id), -- Vertical-specific fields industry_code VARCHAR(50) NOT NULL, certification_date DATE, compliance_status VARCHAR(20), -- Standard audit fields created_by UUID REFERENCES auth.users(id), updated_by UUID REFERENCES auth.users(id), created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), deleted_at TIMESTAMPTZ ); -- Indexes CREATE INDEX idx_custom_entities_tenant_id ON my_vertical_management.custom_entities(tenant_id); CREATE INDEX idx_custom_entities_industry_code ON my_vertical_management.custom_entities(tenant_id, industry_code) WHERE deleted_at IS NULL; -- RLS Policy ALTER TABLE my_vertical_management.custom_entities ENABLE ROW LEVEL SECURITY; CREATE POLICY tenant_isolation ON my_vertical_management.custom_entities USING (tenant_id = current_setting('app.current_tenant_id')::uuid); -- Permissions GRANT SELECT, INSERT, UPDATE, DELETE ON my_vertical_management.custom_entities TO erp_app_user; -- Comments COMMENT ON TABLE my_vertical_management.custom_entities IS 'Stores industry-specific entity data for my vertical'; ``` ### 4. Backend Module Structure #### Extend Core Module **Example:** Extending Projects module ```typescript // backend/src/modules/projects/vertical-project.service.ts import { ProjectService } from '@erp-shared/modules/projects/project.service'; import { CreateProjectDto } from '@erp-shared/modules/projects/dto'; interface CreateVerticalProjectDto extends CreateProjectDto { // Add vertical-specific fields industryCode: string; certificationDate?: Date; complianceStatus: string; } export class VerticalProjectService extends ProjectService { /** * Override create method to add vertical logic */ async create( data: CreateVerticalProjectDto, tenantId: string, userId: string ): Promise { const client = await this.pool.connect(); try { await client.query('BEGIN'); // 1. Call parent method (creates core project) const project = await super.create(data, tenantId, userId); // 2. Create vertical-specific data await client.query(` INSERT INTO my_vertical_management.custom_entities ( tenant_id, product_id, industry_code, certification_date, compliance_status, created_by ) VALUES ($1, $2, $3, $4, $5, $6) `, [ tenantId, project.id, data.industryCode, data.certificationDate, data.complianceStatus, userId ]); await client.query('COMMIT'); return project; } catch (error) { await client.query('ROLLBACK'); throw error; } finally { client.release(); } } /** * Add vertical-specific method */ async findByCertificationDate( tenantId: string, startDate: Date, endDate: Date ): Promise { const query = ` SELECT p.*, ce.industry_code, ce.certification_date FROM projects.projects p INNER JOIN my_vertical_management.custom_entities ce ON p.id = ce.product_id WHERE p.tenant_id = $1 AND ce.certification_date BETWEEN $2 AND $3 AND p.deleted_at IS NULL `; const result = await this.pool.query(query, [tenantId, startDate, endDate]); return result.rows; } /** * Override validation */ protected async validateProjectData(data: CreateVerticalProjectDto): Promise { // Call parent validation await super.validateProjectData(data); // Add vertical-specific validation if (!data.industryCode) { throw new Error('Industry code is required for this vertical'); } if (data.complianceStatus && !['pending', 'approved', 'rejected'].includes(data.complianceStatus)) { throw new Error('Invalid compliance status'); } } } ``` #### Create New Module **Example:** Vertical-specific module ```typescript // backend/src/modules/certifications/certification.service.ts import { BaseService } from '@erp-core/shared/services/base.service'; interface Certification { id: string; tenantId: string; entityId: string; certificationNumber: string; issueDate: Date; expiryDate: Date; status: string; } interface CreateCertificationDto { entityId: string; certificationNumber: string; issueDate: Date; expiryDate: Date; } export class CertificationService extends BaseService< Certification, CreateCertificationDto, Partial > { constructor() { super('certifications', 'my_vertical_management'); } /** * Find certifications expiring soon */ async findExpiringSoon( tenantId: string, daysAhead: number = 30 ): Promise { const query = ` SELECT * FROM my_vertical_management.certifications WHERE tenant_id = $1 AND expiry_date <= NOW() + INTERVAL '${daysAhead} days' AND expiry_date >= NOW() AND deleted_at IS NULL ORDER BY expiry_date ASC `; const result = await this.pool.query(query, [tenantId]); return result.rows; } /** * Renew certification */ async renew( certificationId: string, tenantId: string, userId: string, newExpiryDate: Date ): Promise { const query = ` UPDATE my_vertical_management.certifications SET expiry_date = $1, status = 'active', updated_by = $2, updated_at = NOW() WHERE id = $3 AND tenant_id = $4 RETURNING * `; const result = await this.pool.query(query, [ newExpiryDate, userId, certificationId, tenantId ]); return result.rows[0]; } } ``` ### 5. API Routes ```typescript // backend/src/routes/index.ts import express from 'express'; import { VerticalProjectService } from '../modules/projects/vertical-project.service'; import { CertificationService } from '../modules/certifications/certification.service'; import { authenticateJWT } from '@erp-core/middleware/auth.middleware'; const router = express.Router(); const projectService = new VerticalProjectService(); const certificationService = new CertificationService(); // Extend core projects endpoint router.post('/projects', authenticateJWT, async (req, res) => { try { const project = await projectService.create( req.body, req.user.tenantId, req.user.id ); res.status(201).json(project); } catch (error) { res.status(400).json({ error: error.message }); } }); // Vertical-specific endpoint router.get('/certifications/expiring', authenticateJWT, async (req, res) => { try { const daysAhead = parseInt(req.query.days as string) || 30; const certs = await certificationService.findExpiringSoon( req.user.tenantId, daysAhead ); res.json(certs); } catch (error) { res.status(500).json({ error: error.message }); } }); export default router; ``` ### 6. Frontend Module ```tsx // frontend/src/modules/certifications/CertificationList.tsx import React, { useEffect, useState } from 'react'; import { api } from '../../shared/utils/api'; interface Certification { id: string; certificationNumber: string; issueDate: string; expiryDate: string; status: string; } export const CertificationList: React.FC = () => { const [certifications, setCertifications] = useState([]); useEffect(() => { fetchCertifications(); }, []); const fetchCertifications = async () => { const response = await api.get('/certifications/expiring?days=30'); setCertifications(response.data); }; return (

Certifications Expiring Soon

{certifications.map((cert) => ( ))}
Certification Number Issue Date Expiry Date Status
{cert.certificationNumber} {new Date(cert.issueDate).toLocaleDateString()} {new Date(cert.expiryDate).toLocaleDateString()} {cert.status}
); }; ``` ### 7. Documentation Create documentation for your vertical: #### Required Docs 1. **CONTEXTO-PROYECTO.md** - Project overview 2. **REQUERIMIENTOS.md** - Functional requirements 3. **MODELO-DATOS.md** - Database schema documentation 4. **API.md** - API endpoints 5. **GUIA-USUARIO.md** - User guide #### Example: MODELO-DATOS.md ```markdown # Modelo de Datos: [Vertical Name] ## Schemas ### my_vertical_management #### Tablas ##### custom_entities **Propósito:** Almacena datos específicos de la industria | Campo | Tipo | Descripción | |-------|------|-------------| | id | UUID | Primary key | | tenant_id | UUID | Tenant isolation | | product_id | UUID | Link to core product | | industry_code | VARCHAR(50) | Industry classification code | | certification_date | DATE | Date of certification | | compliance_status | VARCHAR(20) | Compliance status | **Índices:** - `idx_custom_entities_tenant_id` - Performance - `idx_custom_entities_industry_code` - Queries by code **RLS:** Enabled (tenant_isolation policy) ``` ### 8. Integration with Core #### Import Core Modules ```typescript // backend/src/modules/projects/vertical-project.service.ts // Option 1: Direct import (if monorepo) import { ProjectService } from '../../../erp-core/backend/src/modules/projects/project.service'; // Option 2: Package import (if separate packages) import { ProjectService } from '@erp-shared/modules/projects'; ``` #### Share Types ```typescript // shared-libs/shared/types/index.ts export interface BaseEntity { id: string; tenantId: string; createdBy: string; updatedBy?: string; createdAt: Date; updatedAt: Date; deletedAt?: Date; } export interface Project extends BaseEntity { name: string; description?: string; status: 'draft' | 'active' | 'completed'; } ``` ## Best Practices ### 1. Follow Naming Conventions **Schemas:** ``` {vertical}_management example: construction_management, clinic_management ``` **Tables:** ``` {vertical}_management.{entity_plural} example: construction_management.phases ``` **Services:** ``` {Vertical}{Entity}Service example: ConstructionProjectService ``` ### 2. Always Use Multi-Tenancy ```sql -- ✅ Good CREATE TABLE my_vertical_management.entities ( id UUID PRIMARY KEY, tenant_id UUID NOT NULL REFERENCES auth.tenants(id), -- ... ); -- ❌ Bad (missing tenant_id) CREATE TABLE my_vertical_management.entities ( id UUID PRIMARY KEY, -- missing tenant_id! ); ``` ### 3. Extend, Don't Duplicate ```typescript // ✅ Good - Extend core service class VerticalProjectService extends ProjectService { async create(...) { const project = await super.create(...); // Add vertical logic return project; } } // ❌ Bad - Duplicate core logic class VerticalProjectService { async create(...) { // Copy-pasted from ProjectService // Now you have duplicated code! } } ``` ### 4. Document Dependencies ```markdown ## Dependencias del Core Este vertical extiende los siguientes módulos del core: - **projects** - Gestión de proyectos (override create, findAll) - **inventory** - Productos (agrega campos custom) - **sales** - Ventas (validación adicional) - **financial** - Contabilidad (reportes específicos) ``` ### 5. Use Transactions ```typescript async create(data: any, tenantId: string, userId: string) { const client = await this.pool.connect(); try { await client.query('BEGIN'); // 1. Core operation const entity = await super.create(data, tenantId, userId); // 2. Vertical operation await this.createVerticalData(client, entity.id, data); await client.query('COMMIT'); return entity; } catch (error) { await client.query('ROLLBACK'); throw error; } finally { client.release(); } } ``` ## Testing Your Vertical ### Unit Tests ```typescript // __tests__/vertical-project.service.test.ts import { VerticalProjectService } from '../modules/projects/vertical-project.service'; describe('VerticalProjectService', () => { let service: VerticalProjectService; beforeEach(() => { service = new VerticalProjectService(); }); it('should create project with vertical data', async () => { const data = { name: 'Test Project', industryCode: 'IND-001', complianceStatus: 'pending' }; const project = await service.create(data, 'tenant-id', 'user-id'); expect(project).toBeDefined(); expect(project.name).toBe('Test Project'); // Verify vertical data was created }); }); ``` ### Integration Tests Test interaction with core modules and database. ## Deployment ### Database Migration ```bash # Run core migrations first cd apps/erp-core/database psql -U erp_user -d erp_db -f ddl/schemas/auth/schema.sql psql -U erp_user -d erp_db -f ddl/schemas/core/schema.sql # ... all core schemas # Then run vertical migrations cd apps/verticales/my-vertical/database psql -U erp_user -d erp_db -f ddl/schemas/my_vertical_management/schema.sql ``` ### Environment Variables ```bash # .env for vertical CORE_API_URL=http://localhost:3000 VERTICAL_NAME=my-vertical VERTICAL_DB_SCHEMA=my_vertical_management ``` ## Examples from Existing Verticals ### Construcción Vertical **Extends:** - Projects → Construction Projects (adds phases, developments) - Partners → Derechohabientes (adds INFONAVIT data) - Financial → Presupuestos (construction budgets) **New Modules:** - Quality Management - INFONAVIT Integration - Construction Control ### Mecánicas Diesel Vertical **Extends:** - Inventory → Vehicle Parts (adds vehicle compatibility) - Sales → Work Orders (service orders) - Partners → Vehicle Owners **New Modules:** - Diagnostics - Maintenance Schedules - Vehicle Registry ## Checklist: Create New Vertical - [ ] Create directory structure - [ ] Write CONTEXTO-PROYECTO.md - [ ] Design database schemas - [ ] Create DDL files with RLS - [ ] Identify core modules to extend - [ ] Create service classes (extend BaseService) - [ ] Implement API routes - [ ] Create frontend modules - [ ] Write documentation - [ ] Write unit tests - [ ] Integration testing - [ ] Deploy database schemas ## References - [Architecture Documentation](./ARCHITECTURE.md) - [Multi-Tenancy Guide](./MULTI-TENANCY.md) - [Core Modules Documentation](../apps/erp-core/docs/) - [Odoo Development Patterns](../../erp-core/orchestration/directivas/DIRECTIVA-PATRONES-ODOO.md)