erp-suite/docs/VERTICAL-GUIDE.md
rckrdmrd 106adc1de0 feat: Add orchestration context and environment configuration
- Add CONTEXT-MAP.yml and ENVIRONMENT-INVENTORY.yml
- Add propagacion-fase8 directory
- Update project dependencies and context

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-07 05:38:14 -06:00

20 KiB

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

# 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:

# 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

# Create schema DDL
touch database/ddl/schemas/my_vertical_management/schema.sql

Example: database/ddl/schemas/my_vertical_management/schema.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

// 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<Project> {
    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<any[]> {
    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<void> {
    // 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

// 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<CreateCertificationDto>
> {
  constructor() {
    super('certifications', 'my_vertical_management');
  }

  /**
   * Find certifications expiring soon
   */
  async findExpiringSoon(
    tenantId: string,
    daysAhead: number = 30
  ): Promise<Certification[]> {
    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<Certification> {
    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

// 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

// 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<Certification[]>([]);

  useEffect(() => {
    fetchCertifications();
  }, []);

  const fetchCertifications = async () => {
    const response = await api.get('/certifications/expiring?days=30');
    setCertifications(response.data);
  };

  return (
    <div className="p-4">
      <h2 className="text-2xl font-bold mb-4">Certifications Expiring Soon</h2>

      <table className="w-full border-collapse border">
        <thead>
          <tr className="bg-gray-100">
            <th className="border p-2">Certification Number</th>
            <th className="border p-2">Issue Date</th>
            <th className="border p-2">Expiry Date</th>
            <th className="border p-2">Status</th>
          </tr>
        </thead>
        <tbody>
          {certifications.map((cert) => (
            <tr key={cert.id}>
              <td className="border p-2">{cert.certificationNumber}</td>
              <td className="border p-2">{new Date(cert.issueDate).toLocaleDateString()}</td>
              <td className="border p-2">{new Date(cert.expiryDate).toLocaleDateString()}</td>
              <td className="border p-2">
                <span className={`px-2 py-1 rounded ${
                  cert.status === 'active' ? 'bg-green-200' : 'bg-red-200'
                }`}>
                  {cert.status}
                </span>
              </td>
            </tr>
          ))}
        </tbody>
      </table>
    </div>
  );
};

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

# 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

// 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

// 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

-- ✅ 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

// ✅ 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

## 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

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

// __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

# 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

# .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