- 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>
20 KiB
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
- Don't modify core - Core stays generic and reusable
- Extend, don't replace - Vertical extends core modules
- Inheritance over duplication - Use TypeScript class inheritance
- Additive database changes - Add schemas, don't modify core schemas
- 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
- CONTEXTO-PROYECTO.md - Project overview
- REQUERIMIENTOS.md - Functional requirements
- MODELO-DATOS.md - Database schema documentation
- API.md - API endpoints
- 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