From 7ed5c5ab45578afe55bce9ee2f7bb0142563053c Mon Sep 17 00:00:00 2001 From: Adrian Flores Cortes Date: Fri, 12 Dec 2025 08:14:03 -0600 Subject: [PATCH] changes on project erp-suite documentation --- .../verticales/construccion/CONTRIBUTING.md | 478 +++++++++ .../verticales/construccion/PROJECT-STATUS.md | 55 +- .../construccion/docs/ARCHITECTURE.md | 647 ++++++++++++ .../verticales/construccion/docs/README.md | 22 +- .../construccion/docs/api/openapi.yaml | 947 +++++++++++++++++ .../docs/backend/API-REFERENCE.md | 978 ++++++++++++++++++ .../construccion/docs/backend/MODULES.md | 744 +++++++++++++ .../PLAN-IMPLEMENTACION-2025-12.md | 559 ++++++++++ 8 files changed, 4418 insertions(+), 12 deletions(-) create mode 100644 projects/erp-suite/apps/verticales/construccion/CONTRIBUTING.md create mode 100644 projects/erp-suite/apps/verticales/construccion/docs/ARCHITECTURE.md create mode 100644 projects/erp-suite/apps/verticales/construccion/docs/api/openapi.yaml create mode 100644 projects/erp-suite/apps/verticales/construccion/docs/backend/API-REFERENCE.md create mode 100644 projects/erp-suite/apps/verticales/construccion/docs/backend/MODULES.md create mode 100644 projects/erp-suite/apps/verticales/mecanicas-diesel/docs/90-transversal/PLAN-IMPLEMENTACION-2025-12.md diff --git a/projects/erp-suite/apps/verticales/construccion/CONTRIBUTING.md b/projects/erp-suite/apps/verticales/construccion/CONTRIBUTING.md new file mode 100644 index 0000000..9247419 --- /dev/null +++ b/projects/erp-suite/apps/verticales/construccion/CONTRIBUTING.md @@ -0,0 +1,478 @@ +# Guia de Contribucion - ERP Construccion + +Esta guia describe las convenciones y procesos para contribuir al proyecto. + +--- + +## Requisitos Previos + +- Node.js 20 LTS +- Docker y Docker Compose +- PostgreSQL 15 (o usar docker-compose) +- Git + +--- + +## Setup Inicial + +```bash +# 1. Clonar repositorio +git clone +cd apps/verticales/construccion + +# 2. Instalar dependencias +cd backend && npm install + +# 3. Configurar variables de entorno +cp ../.env.example .env +# Editar .env con credenciales locales + +# 4. Levantar servicios +docker-compose up -d postgres redis + +# 5. Ejecutar migraciones (si aplica) +npm run migration:run + +# 6. Iniciar en desarrollo +npm run dev +``` + +--- + +## Estructura de Ramas + +| Rama | Proposito | +|------|-----------| +| `main` | Produccion estable | +| `develop` | Integracion de features | +| `feature/*` | Nuevas funcionalidades | +| `fix/*` | Correcciones de bugs | +| `refactor/*` | Refactorizaciones | +| `docs/*` | Documentacion | + +### Flujo de Trabajo + +``` +1. Crear rama desde develop + git checkout develop + git pull origin develop + git checkout -b feature/MAI-XXX-descripcion + +2. Desarrollar y hacer commits + git commit -m "feat(modulo): descripcion del cambio" + +3. Push y crear Pull Request + git push origin feature/MAI-XXX-descripcion + # Crear PR en GitHub hacia develop + +4. Code Review y merge +``` + +--- + +## Convenciones de Codigo + +### Nomenclatura + +| Tipo | Convencion | Ejemplo | +|------|------------|---------| +| Archivos | kebab-case.tipo.ts | `concepto.entity.ts` | +| Clases | PascalCase + sufijo | `ConceptoService` | +| Interfaces | PascalCase + prefijo I | `IConceptoRepository` | +| Variables | camelCase | `totalAmount` | +| Constantes | UPPER_SNAKE_CASE | `DB_SCHEMAS` | +| Metodos | camelCase + verbo | `findByContrato` | +| Enums | PascalCase | `EstimacionStatus` | +| Tablas DB | snake_case plural | `presupuestos` | +| Columnas DB | snake_case | `tenant_id` | + +### Estructura de Archivos por Modulo + +``` +modules/ +└── nombre-modulo/ + ├── entities/ + │ └── entidad.entity.ts + ├── services/ + │ └── entidad.service.ts + ├── controllers/ + │ └── entidad.controller.ts + ├── dto/ + │ └── entidad.dto.ts + └── index.ts +``` + +### Patron Entity + +```typescript +import { Entity, Column, PrimaryGeneratedColumn, Index } from 'typeorm'; +import { DB_SCHEMAS, DB_TABLES } from '@shared/constants'; + +@Entity({ + schema: DB_SCHEMAS.CONSTRUCTION, + name: DB_TABLES.construction.CONCEPTOS, +}) +@Index(['tenantId', 'code'], { unique: true }) +export class Concepto { + @PrimaryGeneratedColumn('uuid') + id: string; + + @Column({ name: 'tenant_id', type: 'uuid' }) + tenantId: string; + + @Column({ name: 'code', length: 20 }) + code: string; + + // Usar snake_case en name: para mapear a DB + @Column({ name: 'created_at', type: 'timestamptz', default: () => 'NOW()' }) + createdAt: Date; + + @Column({ name: 'deleted_at', type: 'timestamptz', nullable: true }) + deletedAt: Date | null; +} +``` + +### Patron Service + +```typescript +import { Repository } from 'typeorm'; +import { BaseService, ServiceContext } from '@shared/services/base.service'; +import { Concepto } from '../entities/concepto.entity'; + +export class ConceptoService extends BaseService { + constructor(repository: Repository) { + super(repository); + } + + // Metodos especificos del dominio + async findByCode(ctx: ServiceContext, code: string): Promise { + return this.findOne(ctx, { code }); + } +} +``` + +### Patron DTO + +```typescript +import { IsString, IsOptional, IsUUID, MinLength, MaxLength } from 'class-validator'; + +export class CreateConceptoDto { + @IsString() + @MinLength(1) + @MaxLength(20) + code: string; + + @IsString() + @MinLength(1) + @MaxLength(200) + name: string; + + @IsOptional() + @IsUUID() + parentId?: string; +} + +export class UpdateConceptoDto { + @IsOptional() + @IsString() + @MaxLength(200) + name?: string; + + @IsOptional() + @IsNumber() + unitPrice?: number; +} +``` + +--- + +## SSOT - Constantes Centralizadas + +### Regla Principal + +**NUNCA** hardcodear: +- Nombres de schemas +- Nombres de tablas +- Rutas de API +- Valores de enums + +### Uso Correcto + +```typescript +// INCORRECTO +@Entity({ schema: 'construction', name: 'conceptos' }) + +// CORRECTO +import { DB_SCHEMAS, DB_TABLES } from '@shared/constants'; + +@Entity({ + schema: DB_SCHEMAS.CONSTRUCTION, + name: DB_TABLES.construction.CONCEPTOS, +}) +``` + +### Validacion Automatica + +```bash +# Detecta hardcoding de constantes +npm run validate:constants + +# Se ejecuta en pre-commit y CI +``` + +--- + +## Commits + +### Formato de Mensaje + +``` +tipo(alcance): descripcion breve + +[cuerpo opcional] + +[footer opcional] +``` + +### Tipos de Commit + +| Tipo | Descripcion | +|------|-------------| +| `feat` | Nueva funcionalidad | +| `fix` | Correccion de bug | +| `refactor` | Refactorizacion sin cambio funcional | +| `docs` | Documentacion | +| `test` | Tests | +| `chore` | Mantenimiento, dependencias | +| `style` | Formato, sin cambio de logica | +| `perf` | Mejora de performance | + +### Ejemplos + +```bash +# Feature +git commit -m "feat(budgets): agregar versionamiento de presupuestos" + +# Fix +git commit -m "fix(estimates): corregir calculo de totales con decimales" + +# Refactor +git commit -m "refactor(auth): simplificar validacion de tokens" + +# Docs +git commit -m "docs(api): actualizar especificacion OpenAPI" +``` + +--- + +## Testing + +### Ejecutar Tests + +```bash +# Todos los tests +npm test + +# Con cobertura +npm run test:coverage + +# Watch mode +npm run test:watch + +# Archivo especifico +npm test -- concepto.service.spec.ts +``` + +### Estructura de Test + +```typescript +describe('ConceptoService', () => { + let service: ConceptoService; + let mockRepo: jest.Mocked>; + + const ctx: ServiceContext = { + tenantId: 'test-tenant-uuid', + userId: 'test-user-uuid', + }; + + beforeEach(() => { + mockRepo = createMockRepository(); + service = new ConceptoService(mockRepo); + }); + + describe('createConcepto', () => { + it('should create concepto with level 0 for root', async () => { + // Arrange + const dto = { code: '001', name: 'Test' }; + mockRepo.save.mockResolvedValue({ id: 'uuid', ...dto, level: 0 }); + + // Act + const result = await service.createConcepto(ctx, dto); + + // Assert + expect(result.level).toBe(0); + }); + + it('should throw on duplicate code', async () => { + // ... + }); + }); +}); +``` + +### Cobertura Minima + +- Statements: 80% +- Branches: 75% +- Functions: 80% +- Lines: 80% + +--- + +## Linting y Formato + +### Ejecutar Linting + +```bash +# Verificar errores +npm run lint + +# Corregir automaticamente +npm run lint:fix +``` + +### Configuracion ESLint + +El proyecto usa ESLint con TypeScript. Reglas principales: + +- No `any` implicito +- No variables no usadas +- Imports ordenados +- Espacios consistentes + +### Pre-commit Hook + +Se ejecuta automaticamente: + +```bash +npm run precommit # lint + validate:constants +``` + +--- + +## Pull Requests + +### Checklist + +Antes de crear un PR, verificar: + +- [ ] Tests pasan: `npm test` +- [ ] Linting pasa: `npm run lint` +- [ ] Constantes validadas: `npm run validate:constants` +- [ ] Build funciona: `npm run build` +- [ ] Documentacion actualizada (si aplica) +- [ ] Commits con formato correcto + +### Template de PR + +```markdown +## Descripcion + +Breve descripcion del cambio. + +## Tipo de Cambio + +- [ ] Feature +- [ ] Bug fix +- [ ] Refactor +- [ ] Docs +- [ ] Tests + +## Modulo Afectado + +- MAI-XXX: Nombre del modulo + +## Testing + +Describir como probar los cambios. + +## Screenshots (si aplica) + +## Checklist + +- [ ] Tests agregados/actualizados +- [ ] Documentacion actualizada +- [ ] Sin breaking changes (o documentados) +``` + +--- + +## Migraciones de Base de Datos + +### Generar Migracion + +```bash +# Despues de modificar entidades +npm run migration:generate -- -n NombreDescriptivo +``` + +### Ejecutar Migraciones + +```bash +# Aplicar pendientes +npm run migration:run + +# Revertir ultima +npm run migration:revert +``` + +### Convenciones + +- Una migracion por feature/fix +- Nombres descriptivos: `AddStatusToEstimaciones` +- Incluir rollback funcional +- No modificar migraciones ya aplicadas en produccion + +--- + +## Debugging + +### VS Code + +```json +// .vscode/launch.json +{ + "version": "0.2.0", + "configurations": [ + { + "type": "node", + "request": "launch", + "name": "Debug Backend", + "runtimeArgs": ["-r", "ts-node/register"], + "args": ["${workspaceFolder}/backend/src/server.ts"], + "env": { + "NODE_ENV": "development", + "LOG_LEVEL": "debug" + } + } + ] +} +``` + +### Variables de Entorno para Debug + +```bash +LOG_LEVEL=debug +TYPEORM_LOGGING=true +``` + +--- + +## Contacto + +- **Issues:** Reportar bugs o sugerir features en GitHub Issues +- **PRs:** Siempre bienvenidos, seguir las convenciones + +--- + +**Ultima actualizacion:** 2025-12-12 diff --git a/projects/erp-suite/apps/verticales/construccion/PROJECT-STATUS.md b/projects/erp-suite/apps/verticales/construccion/PROJECT-STATUS.md index f10189e..fbda004 100644 --- a/projects/erp-suite/apps/verticales/construccion/PROJECT-STATUS.md +++ b/projects/erp-suite/apps/verticales/construccion/PROJECT-STATUS.md @@ -2,14 +2,40 @@ **Proyecto:** ERP Construccion (Proyecto Independiente) **Estado:** 🚧 En desarrollo -**Progreso:** 55% +**Progreso:** 60% **Ultima actualizacion:** 2025-12-12 --- ## 🆕 CAMBIOS RECIENTES (2025-12-12) -### Fase 2: Backend Core Modules - EN PROGRESO +### Documentacion Tecnica - COMPLETADA + +- ✅ **Documentacion de Arquitectura** + - `docs/ARCHITECTURE.md` - Arquitectura tecnica para desarrolladores + - Patrones de codigo (Entity, Service, DTO) + - Configuracion multi-tenant RLS + - Guia de debugging y performance + +- ✅ **Guia de Contribucion** + - `CONTRIBUTING.md` - Guia completa para desarrolladores + - Convenciones de codigo y nomenclatura + - Flujo de trabajo con Git + - Estructura de commits y PRs + +- ✅ **Documentacion de API** + - `docs/api/openapi.yaml` - Especificacion OpenAPI 3.0.3 + - `docs/backend/API-REFERENCE.md` - Referencia de endpoints + - Ejemplos de request/response + - Codigos de error y rate limiting + +- ✅ **Documentacion de Modulos** + - `docs/backend/MODULES.md` - Documentacion detallada + - Entidades y campos de cada modulo + - Metodos de servicios + - Ejemplos de uso + +### Fase 2: Backend Core Modules - COMPLETADA - ✅ **MAI-003 Presupuestos - Entidades y Services** - `Concepto` entity - Catálogo jerárquico de conceptos @@ -90,9 +116,10 @@ | Área | Implementado | Documentado | Estado | |------|-------------|-------------|--------| | **DDL/Schemas** | 7 schemas, 110 tablas | 7 schemas, 110 tablas | 100% | -| **Backend** | 7 módulos, 30 entidades | 18 módulos | 40% | +| **Backend** | 7 módulos, 30 entidades, 8 services | 18 módulos | 45% | | **Frontend** | Estructura base | 18 módulos | 5% | -| **Documentación** | - | 449 archivos MD | 100% | +| **Documentación Técnica** | OpenAPI, ARCHITECTURE, CONTRIBUTING | API, Módulos, Arquitectura | 100% | +| **Documentación Funcional** | - | 449+ archivos MD | 100% | --- @@ -240,21 +267,34 @@ backend/src/modules/ ## 📁 ARCHIVOS CLAVE +### Codigo - **DDL:** `database/schemas/*.sql` - **Backend:** `backend/src/modules/` - **Services:** `backend/src/shared/services/base.service.ts` - **Auth:** `backend/src/modules/auth/` -- **Docs:** `docs/02-definicion-modulos/` -- **Inventario:** `orchestration/inventarios/MASTER_INVENTORY.yml` - **Constants SSOT:** `backend/src/shared/constants/` +### Documentacion Tecnica +- **Arquitectura:** `docs/ARCHITECTURE.md` +- **Contribucion:** `CONTRIBUTING.md` +- **API OpenAPI:** `docs/api/openapi.yaml` +- **API Reference:** `docs/backend/API-REFERENCE.md` +- **Modulos Backend:** `docs/backend/MODULES.md` + +### Documentacion Funcional +- **Modulos:** `docs/02-definicion-modulos/` +- **Vision General:** `docs/00-vision-general/` +- **Arquitectura SaaS:** `docs/00-vision-general/ARQUITECTURA-SAAS.md` +- **Mapa DB:** `database/_MAP.md` + --- ## 📈 MÉTRICAS | Métrica | Valor | |---------|-------| -| Archivos MD | 449 | +| Archivos MD Funcionales | 449+ | +| Archivos MD Tecnicos | 5 (ARCHITECTURE, CONTRIBUTING, API-REF, MODULES, openapi) | | Requerimientos (RF) | 87 | | Especificaciones (ET) | 78 | | User Stories | 149 | @@ -263,6 +303,7 @@ backend/src/modules/ | Entidades TypeORM | 30 | | Services Backend | 8 | | Tablas DDL | 110 | +| Endpoints API Documentados | 35+ | --- diff --git a/projects/erp-suite/apps/verticales/construccion/docs/ARCHITECTURE.md b/projects/erp-suite/apps/verticales/construccion/docs/ARCHITECTURE.md new file mode 100644 index 0000000..62d3e8f --- /dev/null +++ b/projects/erp-suite/apps/verticales/construccion/docs/ARCHITECTURE.md @@ -0,0 +1,647 @@ +# Arquitectura Tecnica - ERP Construccion + +**Version:** 1.0.0 +**Fecha:** 2025-12-12 +**Para:** Desarrolladores + +--- + +## Stack Tecnologico + +| Capa | Tecnologia | Version | +|------|------------|---------| +| **Runtime** | Node.js | 20 LTS | +| **Backend Framework** | Express.js | 4.x | +| **ORM** | TypeORM | 0.3.x | +| **Base de Datos** | PostgreSQL + PostGIS | 15 | +| **Cache** | Redis | 7 | +| **Frontend** | React + Vite | 18.x / 5.x | +| **Lenguaje** | TypeScript | 5.x | +| **Validacion** | class-validator | 0.14.x | +| **Autenticacion** | JWT + Refresh Tokens | - | + +--- + +## Estructura de Directorios + +``` +construccion/ +├── backend/ +│ ├── src/ +│ │ ├── modules/ # Modulos de dominio +│ │ │ ├── auth/ # Autenticacion JWT +│ │ │ ├── budgets/ # MAI-003 Presupuestos +│ │ │ ├── progress/ # MAI-005 Control de Obra +│ │ │ ├── estimates/ # MAI-008 Estimaciones +│ │ │ ├── construction/ # MAI-002 Proyectos +│ │ │ ├── hr/ # MAI-007 RRHH +│ │ │ ├── hse/ # MAA-017 HSE +│ │ │ └── core/ # User, Tenant +│ │ │ +│ │ ├── shared/ +│ │ │ ├── constants/ # SSOT - Constantes centralizadas +│ │ │ │ ├── database.constants.ts +│ │ │ │ ├── api.constants.ts +│ │ │ │ ├── enums.constants.ts +│ │ │ │ └── index.ts +│ │ │ ├── services/ +│ │ │ │ └── base.service.ts +│ │ │ └── database/ +│ │ │ └── typeorm.config.ts +│ │ │ +│ │ ├── config/ # Configuracion de app +│ │ ├── types/ # Tipos globales +│ │ └── server.ts # Entry point +│ │ +│ ├── package.json +│ └── tsconfig.json +│ +├── frontend/ +│ └── web/ # App React +│ +├── database/ +│ └── schemas/ # DDL SQL +│ ├── 01-construction-schema-ddl.sql +│ ├── 02-hr-schema-ddl.sql +│ ├── 03-hse-schema-ddl.sql +│ ├── 04-estimates-schema-ddl.sql +│ ├── 05-infonavit-schema-ddl.sql +│ ├── 06-inventory-ext-schema-ddl.sql +│ └── 07-purchase-ext-schema-ddl.sql +│ +├── docs/ # Documentacion +├── devops/ # Scripts CI/CD +└── docker-compose.yml +``` + +--- + +## Arquitectura Multi-tenant + +### Row Level Security (RLS) + +El sistema utiliza RLS de PostgreSQL para aislamiento de datos por tenant. + +```sql +-- Ejemplo de politica RLS +CREATE POLICY tenant_isolation ON construction.fraccionamientos + FOR ALL USING (tenant_id = current_setting('app.current_tenant_id')::UUID); +``` + +### Contexto de Tenant + +```typescript +// El middleware establece el contexto RLS +await dataSource.query(` + SELECT set_config('app.current_tenant_id', $1, true) +`, [tenantId]); +``` + +### ServiceContext + +Todas las operaciones de servicio requieren un contexto: + +```typescript +interface ServiceContext { + tenantId: string; // UUID del tenant actual + userId: string; // UUID del usuario autenticado +} +``` + +--- + +## Patrones de Codigo + +### 1. Entity Pattern + +```typescript +// Convencion de entidades TypeORM +@Entity({ schema: 'construction', name: 'conceptos' }) +@Index(['tenantId', 'code'], { unique: true }) +export class Concepto { + // Primary key UUID + @PrimaryGeneratedColumn('uuid') + id: string; + + // Tenant discriminador (siempre presente) + @Column({ name: 'tenant_id', type: 'uuid' }) + tenantId: string; + + // Columnas con snake_case en DB + @Column({ name: 'created_at', type: 'timestamptz', default: () => 'NOW()' }) + createdAt: Date; + + // Soft delete + @Column({ name: 'deleted_at', type: 'timestamptz', nullable: true }) + deletedAt: Date | null; + + // Relaciones + @ManyToOne(() => Tenant) + @JoinColumn({ name: 'tenant_id' }) + tenant: Tenant; +} +``` + +### 2. Service Pattern + +```typescript +// Extender BaseService para CRUD automatico +export class ConceptoService extends BaseService { + constructor( + repository: Repository, + private readonly presupuestoRepo: Repository + ) { + super(repository); + } + + // Metodos especificos del dominio + async findRootConceptos(ctx: ServiceContext): Promise { + return this.repository.find({ + where: { + tenantId: ctx.tenantId, + parentId: IsNull(), + deletedAt: IsNull(), + }, + order: { code: 'ASC' }, + }); + } +} +``` + +### 3. BaseService + +```typescript +// Metodos disponibles en BaseService +abstract class BaseService { + // Lectura + findAll(ctx, options?): Promise> + findById(ctx, id): Promise + findOne(ctx, where): Promise + find(ctx, options): Promise + count(ctx, where?): Promise + exists(ctx, where): Promise + + // Escritura + create(ctx, data): Promise + update(ctx, id, data): Promise + softDelete(ctx, id): Promise + hardDelete(ctx, id): Promise +} +``` + +### 4. DTO Pattern + +```typescript +// DTOs con class-validator +export class CreateConceptoDto { + @IsString() + @MinLength(1) + @MaxLength(20) + code: string; + + @IsString() + @MinLength(1) + @MaxLength(200) + name: string; + + @IsOptional() + @IsUUID() + parentId?: string; + + @IsOptional() + @IsNumber({ maxDecimalPlaces: 4 }) + @Min(0) + unitPrice?: number; +} +``` + +--- + +## Workflows de Estado + +### Patron de Workflow + +```typescript +// Entidad con estado y workflow +@Entity({ schema: 'estimates', name: 'estimaciones' }) +export class Estimacion { + @Column({ + type: 'enum', + enum: EstimacionStatus, + default: EstimacionStatus.DRAFT, + }) + status: EstimacionStatus; + + // Timestamps de workflow + @Column({ name: 'submitted_at', type: 'timestamptz', nullable: true }) + submittedAt: Date | null; + + @Column({ name: 'reviewed_at', type: 'timestamptz', nullable: true }) + reviewedAt: Date | null; + + @Column({ name: 'approved_at', type: 'timestamptz', nullable: true }) + approvedAt: Date | null; + + @Column({ name: 'rejected_at', type: 'timestamptz', nullable: true }) + rejectedAt: Date | null; +} +``` + +### Transiciones Validas + +```typescript +// Servicio con metodos de transicion +export class EstimacionService extends BaseService { + async submit(ctx: ServiceContext, id: string): Promise { + const estimacion = await this.findById(ctx, id); + if (!estimacion || estimacion.status !== EstimacionStatus.DRAFT) { + return null; + } + + return this.update(ctx, id, { + status: EstimacionStatus.SUBMITTED, + submittedAt: new Date(), + submittedById: ctx.userId, + }); + } + + async approve(ctx: ServiceContext, id: string): Promise { + const estimacion = await this.findById(ctx, id); + if (!estimacion || estimacion.status !== EstimacionStatus.REVIEWED) { + return null; + } + + return this.update(ctx, id, { + status: EstimacionStatus.APPROVED, + approvedAt: new Date(), + approvedById: ctx.userId, + }); + } +} +``` + +--- + +## Autenticacion y Autorizacion + +### JWT Flow + +``` +1. Cliente envia credenciales → POST /auth/login +2. Backend valida y genera: + - Access Token (15min) + - Refresh Token (7 dias) +3. Cliente almacena tokens +4. Requests incluyen: Authorization: Bearer +5. Al expirar access token: POST /auth/refresh +``` + +### Middleware de Auth + +```typescript +// Uso en rutas +router.get('/conceptos', + AuthMiddleware.authenticate, // Requerido: validar JWT + AuthMiddleware.authorize('admin', 'engineer'), // Opcional: roles + conceptosController.findAll +); + +// Para rutas publicas +router.get('/health', + AuthMiddleware.optionalAuthenticate, + healthController.check +); +``` + +### Roles del Sistema + +| Rol | Permisos | +|-----|----------| +| `super_admin` | Todo el sistema | +| `admin` | Todo dentro del tenant | +| `project_manager` | Proyectos asignados | +| `supervisor` | Control de obra | +| `engineer` | Operaciones de campo | +| `accountant` | Estimaciones y finanzas | +| `viewer` | Solo lectura | + +--- + +## Base de Datos + +### Schemas PostgreSQL + +| Schema | Tablas | Descripcion | +|--------|--------|-------------| +| `auth` | 10 | Usuarios, roles, tokens | +| `construction` | 24 | Proyectos, presupuestos | +| `hr` | 8 | Empleados, asistencias | +| `hse` | 58 | Seguridad industrial | +| `estimates` | 8 | Estimaciones | +| `infonavit` | 8 | Integracion INFONAVIT | +| `inventory` | 4 | Inventarios | + +### Convenciones de Tablas + +- Nombres en **snake_case** plural: `conceptos`, `presupuestos` +- Todas tienen `tenant_id` (excepto auth global) +- Todas tienen `deleted_at` para soft delete +- Indices en `(tenant_id, ...)` para performance + +### Tipos Especiales PostgreSQL + +```sql +-- Columnas generadas (calculated) +total_amount DECIMAL(18,2) GENERATED ALWAYS AS (quantity * unit_price) STORED + +-- Tipos geometricos (PostGIS) +location GEOGRAPHY(POINT, 4326) + +-- Enums +status estimate_status_enum NOT NULL DEFAULT 'draft' +``` + +--- + +## SSOT (Single Source of Truth) + +### database.constants.ts + +```typescript +// Schemas +export const DB_SCHEMAS = { + AUTH: 'auth', + CONSTRUCTION: 'construction', + HR: 'hr', + HSE: 'hse', + ESTIMATES: 'estimates', +} as const; + +// Tablas +export const DB_TABLES = { + construction: { + CONCEPTOS: 'conceptos', + PRESUPUESTOS: 'presupuestos', + FRACCIONAMIENTOS: 'fraccionamientos', + }, + // ... +} as const; + +// Referencias completas +export const TABLE_REFS = { + CONCEPTOS: 'construction.conceptos', + PRESUPUESTOS: 'construction.presupuestos', +} as const; +``` + +### api.constants.ts + +```typescript +export const API_ROUTES = { + AUTH: { + BASE: '/api/v1/auth', + LOGIN: '/api/v1/auth/login', + REGISTER: '/api/v1/auth/register', + REFRESH: '/api/v1/auth/refresh', + }, + PRESUPUESTOS: { + BASE: '/api/v1/presupuestos', + BY_ID: (id: string) => `/api/v1/presupuestos/${id}`, + }, +} as const; +``` + +### enums.constants.ts + +```typescript +export const ROLES = { + SUPER_ADMIN: 'super_admin', + ADMIN: 'admin', + PROJECT_MANAGER: 'project_manager', + // ... +} as const; + +export const ESTIMATE_STATUS = { + DRAFT: 'draft', + SUBMITTED: 'submitted', + REVIEWED: 'reviewed', + APPROVED: 'approved', + REJECTED: 'rejected', +} as const; +``` + +--- + +## Testing + +### Estructura de Tests + +``` +backend/ +├── src/ +│ └── modules/ +│ └── budgets/ +│ └── services/ +│ └── concepto.service.spec.ts +└── test/ + ├── setup.ts # Jest setup + └── fixtures/ # Datos de prueba +``` + +### Ejemplo de Test + +```typescript +describe('ConceptoService', () => { + let service: ConceptoService; + let mockRepo: jest.Mocked>; + const ctx: ServiceContext = { + tenantId: 'tenant-uuid', + userId: 'user-uuid', + }; + + beforeEach(() => { + mockRepo = createMockRepository(); + service = new ConceptoService(mockRepo); + }); + + it('should create concepto with level 0 for root', async () => { + const dto = { code: '001', name: 'Test' }; + mockRepo.save.mockResolvedValue({ + id: 'uuid', + ...dto, + level: 0, + tenantId: ctx.tenantId, + }); + + const result = await service.createConcepto(ctx, dto); + + expect(result.level).toBe(0); + expect(result.tenantId).toBe(ctx.tenantId); + }); +}); +``` + +--- + +## Docker + +### docker-compose.yml + +```yaml +services: + postgres: + image: postgis/postgis:15-3.3 + ports: + - "5432:5432" + environment: + POSTGRES_DB: erp_construccion + POSTGRES_USER: construccion + POSTGRES_PASSWORD: ${DB_PASSWORD} + + redis: + image: redis:7-alpine + ports: + - "6379:6379" + + backend: + build: ./backend + ports: + - "3000:3000" + depends_on: + - postgres + - redis + environment: + NODE_ENV: development + DB_HOST: postgres +``` + +### Comandos Docker + +```bash +# Desarrollo +docker-compose up -d + +# Con herramientas dev (Adminer, Mailhog) +docker-compose --profile dev up -d + +# Logs +docker-compose logs -f backend + +# Rebuild +docker-compose build --no-cache backend +``` + +--- + +## CI/CD + +### GitHub Actions Pipeline + +```yaml +# .github/workflows/ci.yml +jobs: + lint: + - npm run lint + - npm run validate:constants + + test: + - npm run test:coverage + + build: + - npm run build + - docker build +``` + +### Scripts de Validacion + +```bash +# Validar que no hay hardcoding de constantes +npm run validate:constants + +# Sincronizar enums backend → frontend +npm run sync:enums + +# Pre-commit hook +npm run precommit # lint + validate +``` + +--- + +## Performance + +### Indices Recomendados + +```sql +-- Siempre indexar tenant_id +CREATE INDEX idx_conceptos_tenant ON construction.conceptos(tenant_id); + +-- Indices compuestos para queries frecuentes +CREATE INDEX idx_conceptos_tenant_code + ON construction.conceptos(tenant_id, code); + +-- Indices parciales para soft delete +CREATE INDEX idx_conceptos_active + ON construction.conceptos(tenant_id) + WHERE deleted_at IS NULL; +``` + +### Query Optimization + +```typescript +// Eager loading para evitar N+1 +const presupuesto = await this.repository.findOne({ + where: { id, tenantId: ctx.tenantId }, + relations: ['partidas', 'partidas.concepto'], +}); + +// Select especifico +const totals = await this.repository + .createQueryBuilder('p') + .select('SUM(p.totalAmount)', 'total') + .where('p.tenantId = :tenantId', { tenantId: ctx.tenantId }) + .getRawOne(); +``` + +--- + +## Debugging + +### VS Code Launch Config + +```json +{ + "type": "node", + "request": "launch", + "name": "Debug Backend", + "runtimeArgs": ["-r", "ts-node/register"], + "args": ["${workspaceFolder}/backend/src/server.ts"], + "env": { + "NODE_ENV": "development", + "LOG_LEVEL": "debug" + } +} +``` + +### Logs + +```bash +# Variables de entorno +LOG_LEVEL=debug # debug | info | warn | error +LOG_FORMAT=dev # dev | json + +# Ver SQL queries +TYPEORM_LOGGING=true +``` + +--- + +## Referencias + +- [Backend README](../backend/README.md) +- [API OpenAPI Spec](./api/openapi.yaml) +- [Arquitectura SaaS](./00-vision-general/ARQUITECTURA-SAAS.md) +- [Mapa de Base de Datos](../database/_MAP.md) + +--- + +**Ultima actualizacion:** 2025-12-12 diff --git a/projects/erp-suite/apps/verticales/construccion/docs/README.md b/projects/erp-suite/apps/verticales/construccion/docs/README.md index 5536bcb..9bde0b4 100644 --- a/projects/erp-suite/apps/verticales/construccion/docs/README.md +++ b/projects/erp-suite/apps/verticales/construccion/docs/README.md @@ -187,14 +187,26 @@ Ver [MAPEO-MAI-TO-MGN.md](./01-analisis-referencias/MAPEO-MAI-TO-MGN.md) para de --- -## Documentos Clave +## Documentacion Tecnica (Desarrolladores) + +| Documento | Descripcion | +|-----------|-------------| +| [ARCHITECTURE.md](./ARCHITECTURE.md) | Arquitectura tecnica, patrones, multi-tenant | +| [CONTRIBUTING.md](../CONTRIBUTING.md) | Guia de contribucion, convenciones, Git | +| [api/openapi.yaml](./api/openapi.yaml) | Especificacion OpenAPI 3.0.3 | +| [backend/API-REFERENCE.md](./backend/API-REFERENCE.md) | Referencia de endpoints con ejemplos | +| [backend/MODULES.md](./backend/MODULES.md) | Documentacion de modulos backend | + +--- + +## Documentos Clave (Funcionales) | Documento | Descripcion | |-----------|-------------| | [ESTRUCTURA-COMPLETA.md](./ESTRUCTURA-COMPLETA.md) | Estructura detallada de modulos | -| [ARQUITECTURA-SAAS.md](./00-overview/ARQUITECTURA-SAAS.md) | Arquitectura SaaS multi-tenant | -| [MVP-APP.md](./00-overview/MVP-APP.md) | Definicion del MVP | -| [GLOSARIO.md](./00-overview/GLOSARIO.md) | Terminos de construccion | +| [ARQUITECTURA-SAAS.md](./00-vision-general/ARQUITECTURA-SAAS.md) | Arquitectura SaaS multi-tenant | +| [MVP-APP.md](./00-vision-general/MVP-APP.md) | Definicion del MVP | +| [GLOSARIO.md](./00-vision-general/GLOSARIO.md) | Terminos de construccion | | [RLS-POLICIES-TODOS-LOS-MODULOS.md](./RLS-POLICIES-TODOS-LOS-MODULOS.md) | Politicas de seguridad | --- @@ -260,5 +272,5 @@ Ver [MAPEO-MAI-TO-MGN.md](./01-analisis-referencias/MAPEO-MAI-TO-MGN.md) para de --- -**Ultima actualizacion:** 2025-12-05 +**Ultima actualizacion:** 2025-12-12 **Responsable:** Requirements-Analyst diff --git a/projects/erp-suite/apps/verticales/construccion/docs/api/openapi.yaml b/projects/erp-suite/apps/verticales/construccion/docs/api/openapi.yaml new file mode 100644 index 0000000..70c8ecb --- /dev/null +++ b/projects/erp-suite/apps/verticales/construccion/docs/api/openapi.yaml @@ -0,0 +1,947 @@ +openapi: 3.0.3 +info: + title: ERP Construccion API + description: | + API REST para sistema de administracion de obra e INFONAVIT. + + ## Autenticacion + + La API utiliza JWT Bearer tokens. Incluir el header: + ``` + Authorization: Bearer + ``` + + ## Multi-tenancy + + Todas las operaciones estan aisladas por tenant_id. + El tenant se determina automaticamente desde el JWT. + + ## Paginacion + + Los endpoints de listado soportan paginacion: + - `page`: Numero de pagina (default: 1) + - `limit`: Items por pagina (default: 20, max: 100) + + version: 1.0.0 + contact: + name: ERP Construccion Team + email: dev@construccion.local + +servers: + - url: http://localhost:3000/api/v1 + description: Development + - url: https://api.construccion.example.com/api/v1 + description: Production + +tags: + - name: Auth + description: Autenticacion y autorizacion + - name: Conceptos + description: Catalogo de conceptos de obra + - name: Presupuestos + description: Presupuestos de obra + - name: Avances + description: Control de avances fisicos + - name: Bitacora + description: Bitacora de obra + - name: Estimaciones + description: Estimaciones periodicas + +paths: + /auth/login: + post: + tags: [Auth] + summary: Login de usuario + operationId: login + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/LoginRequest' + responses: + '200': + description: Login exitoso + content: + application/json: + schema: + $ref: '#/components/schemas/AuthResponse' + '401': + $ref: '#/components/responses/Unauthorized' + + /auth/register: + post: + tags: [Auth] + summary: Registro de usuario + operationId: register + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/RegisterRequest' + responses: + '201': + description: Usuario registrado + content: + application/json: + schema: + $ref: '#/components/schemas/AuthResponse' + '400': + $ref: '#/components/responses/BadRequest' + + /auth/refresh: + post: + tags: [Auth] + summary: Renovar access token + operationId: refreshToken + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/RefreshTokenRequest' + responses: + '200': + description: Tokens renovados + content: + application/json: + schema: + $ref: '#/components/schemas/AuthResponse' + '401': + $ref: '#/components/responses/Unauthorized' + + /auth/logout: + post: + tags: [Auth] + summary: Logout (revocar refresh token) + operationId: logout + security: + - bearerAuth: [] + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/RefreshTokenRequest' + responses: + '204': + description: Logout exitoso + + /conceptos: + get: + tags: [Conceptos] + summary: Listar conceptos + operationId: listConceptos + security: + - bearerAuth: [] + parameters: + - $ref: '#/components/parameters/PageParam' + - $ref: '#/components/parameters/LimitParam' + responses: + '200': + description: Lista de conceptos + content: + application/json: + schema: + $ref: '#/components/schemas/ConceptoListResponse' + post: + tags: [Conceptos] + summary: Crear concepto + operationId: createConcepto + security: + - bearerAuth: [] + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/CreateConceptoRequest' + responses: + '201': + description: Concepto creado + content: + application/json: + schema: + $ref: '#/components/schemas/Concepto' + + /conceptos/tree: + get: + tags: [Conceptos] + summary: Obtener arbol de conceptos + operationId: getConceptoTree + security: + - bearerAuth: [] + parameters: + - name: rootId + in: query + schema: + type: string + format: uuid + description: ID del concepto raiz (opcional) + responses: + '200': + description: Arbol de conceptos + content: + application/json: + schema: + type: array + items: + $ref: '#/components/schemas/ConceptoTree' + + /conceptos/{id}: + get: + tags: [Conceptos] + summary: Obtener concepto por ID + operationId: getConcepto + security: + - bearerAuth: [] + parameters: + - $ref: '#/components/parameters/IdParam' + responses: + '200': + description: Concepto encontrado + content: + application/json: + schema: + $ref: '#/components/schemas/Concepto' + '404': + $ref: '#/components/responses/NotFound' + + /presupuestos: + get: + tags: [Presupuestos] + summary: Listar presupuestos + operationId: listPresupuestos + security: + - bearerAuth: [] + parameters: + - $ref: '#/components/parameters/PageParam' + - $ref: '#/components/parameters/LimitParam' + - name: fraccionamientoId + in: query + schema: + type: string + format: uuid + responses: + '200': + description: Lista de presupuestos + content: + application/json: + schema: + $ref: '#/components/schemas/PresupuestoListResponse' + post: + tags: [Presupuestos] + summary: Crear presupuesto + operationId: createPresupuesto + security: + - bearerAuth: [] + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/CreatePresupuestoRequest' + responses: + '201': + description: Presupuesto creado + content: + application/json: + schema: + $ref: '#/components/schemas/Presupuesto' + + /presupuestos/{id}: + get: + tags: [Presupuestos] + summary: Obtener presupuesto con partidas + operationId: getPresupuesto + security: + - bearerAuth: [] + parameters: + - $ref: '#/components/parameters/IdParam' + responses: + '200': + description: Presupuesto con partidas + content: + application/json: + schema: + $ref: '#/components/schemas/PresupuestoDetalle' + + /presupuestos/{id}/partidas: + post: + tags: [Presupuestos] + summary: Agregar partida al presupuesto + operationId: addPartida + security: + - bearerAuth: [] + parameters: + - $ref: '#/components/parameters/IdParam' + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/AddPartidaRequest' + responses: + '201': + description: Partida agregada + content: + application/json: + schema: + $ref: '#/components/schemas/PresupuestoPartida' + + /presupuestos/{id}/approve: + post: + tags: [Presupuestos] + summary: Aprobar presupuesto + operationId: approvePresupuesto + security: + - bearerAuth: [] + parameters: + - $ref: '#/components/parameters/IdParam' + responses: + '200': + description: Presupuesto aprobado + content: + application/json: + schema: + $ref: '#/components/schemas/Presupuesto' + + /avances: + get: + tags: [Avances] + summary: Listar avances + operationId: listAvances + security: + - bearerAuth: [] + parameters: + - $ref: '#/components/parameters/PageParam' + - $ref: '#/components/parameters/LimitParam' + - name: loteId + in: query + schema: + type: string + format: uuid + - name: status + in: query + schema: + $ref: '#/components/schemas/AdvanceStatus' + responses: + '200': + description: Lista de avances + content: + application/json: + schema: + $ref: '#/components/schemas/AvanceListResponse' + post: + tags: [Avances] + summary: Crear avance + operationId: createAvance + security: + - bearerAuth: [] + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/CreateAvanceRequest' + responses: + '201': + description: Avance creado + content: + application/json: + schema: + $ref: '#/components/schemas/AvanceObra' + + /avances/{id}/fotos: + post: + tags: [Avances] + summary: Agregar foto al avance + operationId: addFotoAvance + security: + - bearerAuth: [] + parameters: + - $ref: '#/components/parameters/IdParam' + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/AddFotoRequest' + responses: + '201': + description: Foto agregada + content: + application/json: + schema: + $ref: '#/components/schemas/FotoAvance' + + /avances/{id}/approve: + post: + tags: [Avances] + summary: Aprobar avance + operationId: approveAvance + security: + - bearerAuth: [] + parameters: + - $ref: '#/components/parameters/IdParam' + responses: + '200': + description: Avance aprobado + content: + application/json: + schema: + $ref: '#/components/schemas/AvanceObra' + + /estimaciones: + get: + tags: [Estimaciones] + summary: Listar estimaciones + operationId: listEstimaciones + security: + - bearerAuth: [] + parameters: + - $ref: '#/components/parameters/PageParam' + - $ref: '#/components/parameters/LimitParam' + - name: contratoId + in: query + schema: + type: string + format: uuid + - name: status + in: query + schema: + $ref: '#/components/schemas/EstimateStatus' + responses: + '200': + description: Lista de estimaciones + content: + application/json: + schema: + $ref: '#/components/schemas/EstimacionListResponse' + post: + tags: [Estimaciones] + summary: Crear estimacion + operationId: createEstimacion + security: + - bearerAuth: [] + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/CreateEstimacionRequest' + responses: + '201': + description: Estimacion creada + content: + application/json: + schema: + $ref: '#/components/schemas/Estimacion' + + /estimaciones/{id}/submit: + post: + tags: [Estimaciones] + summary: Enviar estimacion para revision + operationId: submitEstimacion + security: + - bearerAuth: [] + parameters: + - $ref: '#/components/parameters/IdParam' + responses: + '200': + description: Estimacion enviada + content: + application/json: + schema: + $ref: '#/components/schemas/Estimacion' + + /estimaciones/{id}/approve: + post: + tags: [Estimaciones] + summary: Aprobar estimacion + operationId: approveEstimacion + security: + - bearerAuth: [] + parameters: + - $ref: '#/components/parameters/IdParam' + responses: + '200': + description: Estimacion aprobada + content: + application/json: + schema: + $ref: '#/components/schemas/Estimacion' + +components: + securitySchemes: + bearerAuth: + type: http + scheme: bearer + bearerFormat: JWT + + parameters: + IdParam: + name: id + in: path + required: true + schema: + type: string + format: uuid + PageParam: + name: page + in: query + schema: + type: integer + minimum: 1 + default: 1 + LimitParam: + name: limit + in: query + schema: + type: integer + minimum: 1 + maximum: 100 + default: 20 + + responses: + BadRequest: + description: Solicitud invalida + content: + application/json: + schema: + $ref: '#/components/schemas/Error' + Unauthorized: + description: No autorizado + content: + application/json: + schema: + $ref: '#/components/schemas/Error' + NotFound: + description: Recurso no encontrado + content: + application/json: + schema: + $ref: '#/components/schemas/Error' + + schemas: + Error: + type: object + properties: + error: + type: string + message: + type: string + + PaginationMeta: + type: object + properties: + total: + type: integer + page: + type: integer + limit: + type: integer + totalPages: + type: integer + + LoginRequest: + type: object + required: [email, password] + properties: + email: + type: string + format: email + password: + type: string + minLength: 8 + tenantId: + type: string + format: uuid + + RegisterRequest: + type: object + required: [email, password, firstName, lastName, tenantId] + properties: + email: + type: string + format: email + password: + type: string + minLength: 8 + firstName: + type: string + lastName: + type: string + tenantId: + type: string + format: uuid + + RefreshTokenRequest: + type: object + required: [refreshToken] + properties: + refreshToken: + type: string + + AuthResponse: + type: object + properties: + accessToken: + type: string + refreshToken: + type: string + expiresIn: + type: integer + user: + type: object + properties: + id: + type: string + format: uuid + email: + type: string + firstName: + type: string + lastName: + type: string + roles: + type: array + items: + type: string + tenant: + type: object + properties: + id: + type: string + format: uuid + name: + type: string + + Concepto: + type: object + properties: + id: + type: string + format: uuid + code: + type: string + name: + type: string + description: + type: string + parentId: + type: string + format: uuid + level: + type: integer + path: + type: string + unitPrice: + type: number + isComposite: + type: boolean + createdAt: + type: string + format: date-time + + ConceptoTree: + allOf: + - $ref: '#/components/schemas/Concepto' + - type: object + properties: + children: + type: array + items: + $ref: '#/components/schemas/ConceptoTree' + + CreateConceptoRequest: + type: object + required: [code, name] + properties: + code: + type: string + maxLength: 50 + name: + type: string + maxLength: 255 + description: + type: string + parentId: + type: string + format: uuid + unitPrice: + type: number + isComposite: + type: boolean + + ConceptoListResponse: + type: object + properties: + data: + type: array + items: + $ref: '#/components/schemas/Concepto' + meta: + $ref: '#/components/schemas/PaginationMeta' + + Presupuesto: + type: object + properties: + id: + type: string + format: uuid + code: + type: string + name: + type: string + version: + type: integer + isActive: + type: boolean + totalAmount: + type: number + approvedAt: + type: string + format: date-time + createdAt: + type: string + format: date-time + + PresupuestoPartida: + type: object + properties: + id: + type: string + format: uuid + conceptoId: + type: string + format: uuid + quantity: + type: number + unitPrice: + type: number + totalAmount: + type: number + sequence: + type: integer + + PresupuestoDetalle: + allOf: + - $ref: '#/components/schemas/Presupuesto' + - type: object + properties: + partidas: + type: array + items: + $ref: '#/components/schemas/PresupuestoPartida' + + CreatePresupuestoRequest: + type: object + required: [code, name] + properties: + code: + type: string + name: + type: string + description: + type: string + fraccionamientoId: + type: string + format: uuid + prototipoId: + type: string + format: uuid + + AddPartidaRequest: + type: object + required: [conceptoId, quantity, unitPrice] + properties: + conceptoId: + type: string + format: uuid + quantity: + type: number + unitPrice: + type: number + sequence: + type: integer + + PresupuestoListResponse: + type: object + properties: + data: + type: array + items: + $ref: '#/components/schemas/Presupuesto' + meta: + $ref: '#/components/schemas/PaginationMeta' + + AdvanceStatus: + type: string + enum: [pending, captured, reviewed, approved, rejected] + + AvanceObra: + type: object + properties: + id: + type: string + format: uuid + loteId: + type: string + format: uuid + conceptoId: + type: string + format: uuid + captureDate: + type: string + format: date + quantityExecuted: + type: number + percentageExecuted: + type: number + status: + $ref: '#/components/schemas/AdvanceStatus' + notes: + type: string + createdAt: + type: string + format: date-time + + FotoAvance: + type: object + properties: + id: + type: string + format: uuid + fileUrl: + type: string + fileName: + type: string + description: + type: string + capturedAt: + type: string + format: date-time + + CreateAvanceRequest: + type: object + required: [conceptoId, captureDate, quantityExecuted] + properties: + loteId: + type: string + format: uuid + departamentoId: + type: string + format: uuid + conceptoId: + type: string + format: uuid + captureDate: + type: string + format: date + quantityExecuted: + type: number + percentageExecuted: + type: number + notes: + type: string + + AddFotoRequest: + type: object + required: [fileUrl] + properties: + fileUrl: + type: string + fileName: + type: string + description: + type: string + location: + type: object + properties: + lat: + type: number + lng: + type: number + + AvanceListResponse: + type: object + properties: + data: + type: array + items: + $ref: '#/components/schemas/AvanceObra' + meta: + $ref: '#/components/schemas/PaginationMeta' + + EstimateStatus: + type: string + enum: [draft, submitted, reviewed, approved, invoiced, paid, rejected, cancelled] + + Estimacion: + type: object + properties: + id: + type: string + format: uuid + estimateNumber: + type: string + contratoId: + type: string + format: uuid + periodStart: + type: string + format: date + periodEnd: + type: string + format: date + sequenceNumber: + type: integer + status: + $ref: '#/components/schemas/EstimateStatus' + subtotal: + type: number + advanceAmount: + type: number + retentionAmount: + type: number + taxAmount: + type: number + totalAmount: + type: number + approvedAt: + type: string + format: date-time + createdAt: + type: string + format: date-time + + CreateEstimacionRequest: + type: object + required: [contratoId, fraccionamientoId, periodStart, periodEnd] + properties: + contratoId: + type: string + format: uuid + fraccionamientoId: + type: string + format: uuid + periodStart: + type: string + format: date + periodEnd: + type: string + format: date + notes: + type: string + + EstimacionListResponse: + type: object + properties: + data: + type: array + items: + $ref: '#/components/schemas/Estimacion' + meta: + $ref: '#/components/schemas/PaginationMeta' diff --git a/projects/erp-suite/apps/verticales/construccion/docs/backend/API-REFERENCE.md b/projects/erp-suite/apps/verticales/construccion/docs/backend/API-REFERENCE.md new file mode 100644 index 0000000..338db3f --- /dev/null +++ b/projects/erp-suite/apps/verticales/construccion/docs/backend/API-REFERENCE.md @@ -0,0 +1,978 @@ +# API Reference - ERP Construccion + +Referencia completa de endpoints REST. + +**Base URL:** `http://localhost:3000/api/v1` + +--- + +## Autenticacion + +Todas las rutas (excepto login/register) requieren header: + +```http +Authorization: Bearer +``` + +--- + +## Auth Endpoints + +### POST /auth/login + +Autenticar usuario. + +**Request:** +```json +{ + "email": "user@example.com", + "password": "securePassword123" +} +``` + +**Response 200:** +```json +{ + "user": { + "id": "uuid", + "email": "user@example.com", + "firstName": "Juan", + "lastName": "Perez", + "role": "admin" + }, + "accessToken": "eyJhbGciOiJIUzI1NiIs...", + "refreshToken": "eyJhbGciOiJIUzI1NiIs...", + "expiresIn": 900 +} +``` + +**Response 401:** +```json +{ + "error": "Unauthorized", + "message": "Invalid credentials" +} +``` + +--- + +### POST /auth/register + +Registrar nuevo usuario. + +**Request:** +```json +{ + "email": "nuevo@example.com", + "password": "securePassword123", + "firstName": "Maria", + "lastName": "Lopez", + "tenantId": "tenant-uuid" +} +``` + +**Response 201:** +```json +{ + "user": { + "id": "new-uuid", + "email": "nuevo@example.com", + "firstName": "Maria", + "lastName": "Lopez", + "role": "viewer" + }, + "accessToken": "...", + "refreshToken": "...", + "expiresIn": 900 +} +``` + +--- + +### POST /auth/refresh + +Renovar access token. + +**Request:** +```json +{ + "refreshToken": "eyJhbGciOiJIUzI1NiIs..." +} +``` + +**Response 200:** +```json +{ + "accessToken": "new-access-token", + "refreshToken": "new-refresh-token", + "expiresIn": 900 +} +``` + +--- + +### POST /auth/logout + +Revocar refresh token. + +**Request:** +```json +{ + "refreshToken": "eyJhbGciOiJIUzI1NiIs..." +} +``` + +**Response 200:** +```json +{ + "success": true +} +``` + +--- + +### POST /auth/change-password + +Cambiar password del usuario autenticado. + +**Headers:** +``` +Authorization: Bearer +``` + +**Request:** +```json +{ + "currentPassword": "oldPassword123", + "newPassword": "newSecurePassword456" +} +``` + +**Response 200:** +```json +{ + "success": true +} +``` + +--- + +## Conceptos Endpoints + +### GET /conceptos + +Listar conceptos con paginacion. + +**Query Parameters:** +| Param | Tipo | Default | Descripcion | +|-------|------|---------|-------------| +| `page` | number | 1 | Pagina actual | +| `limit` | number | 20 | Items por pagina | +| `search` | string | - | Buscar por codigo/nombre | +| `parentId` | uuid | - | Filtrar por padre | + +**Response 200:** +```json +{ + "data": [ + { + "id": "uuid-1", + "code": "01", + "name": "Preliminares", + "unit": "PZA", + "unitPrice": 0, + "level": 0, + "path": "01", + "isActive": true + }, + { + "id": "uuid-2", + "code": "01.01", + "name": "Trazo y nivelacion", + "unit": "M2", + "unitPrice": 25.50, + "level": 1, + "path": "01/01", + "parentId": "uuid-1", + "isActive": true + } + ], + "total": 150, + "page": 1, + "limit": 20, + "totalPages": 8 +} +``` + +--- + +### GET /conceptos/:id + +Obtener concepto por ID. + +**Response 200:** +```json +{ + "id": "uuid-1", + "code": "01", + "name": "Preliminares", + "description": "Trabajos preliminares de obra", + "unit": "PZA", + "unitPrice": 0, + "level": 0, + "path": "01", + "isActive": true, + "createdAt": "2025-01-01T00:00:00Z", + "updatedAt": "2025-01-01T00:00:00Z" +} +``` + +**Response 404:** +```json +{ + "error": "Not Found", + "message": "Concepto not found" +} +``` + +--- + +### POST /conceptos + +Crear nuevo concepto. + +**Request:** +```json +{ + "code": "01.02", + "name": "Demoliciones", + "description": "Trabajos de demolicion", + "unit": "M3", + "unitPrice": 150.00, + "parentId": "uuid-1" +} +``` + +**Response 201:** +```json +{ + "id": "new-uuid", + "code": "01.02", + "name": "Demoliciones", + "description": "Trabajos de demolicion", + "unit": "M3", + "unitPrice": 150.00, + "level": 1, + "path": "01/02", + "parentId": "uuid-1", + "isActive": true, + "createdAt": "2025-01-15T10:30:00Z" +} +``` + +--- + +### GET /conceptos/tree + +Obtener arbol de conceptos. + +**Query Parameters:** +| Param | Tipo | Descripcion | +|-------|------|-------------| +| `rootId` | uuid | ID del nodo raiz (opcional) | + +**Response 200:** +```json +{ + "data": [ + { + "id": "uuid-1", + "code": "01", + "name": "Preliminares", + "level": 0, + "children": [ + { + "id": "uuid-2", + "code": "01.01", + "name": "Trazo y nivelacion", + "level": 1, + "children": [] + }, + { + "id": "uuid-3", + "code": "01.02", + "name": "Demoliciones", + "level": 1, + "children": [] + } + ] + } + ] +} +``` + +--- + +## Presupuestos Endpoints + +### GET /presupuestos + +Listar presupuestos. + +**Query Parameters:** +| Param | Tipo | Descripcion | +|-------|------|-------------| +| `fraccionamientoId` | uuid | Filtrar por proyecto | +| `status` | string | draft/submitted/approved | +| `page` | number | Pagina | +| `limit` | number | Items por pagina | + +**Response 200:** +```json +{ + "data": [ + { + "id": "uuid-1", + "code": "PPTO-2025-001", + "name": "Presupuesto Casa Tipo A", + "version": 1, + "status": "approved", + "totalAmount": 1500000.00, + "fraccionamiento": { + "id": "frac-uuid", + "name": "Residencial Los Pinos" + }, + "approvedAt": "2025-01-10T15:00:00Z" + } + ], + "total": 25, + "page": 1, + "limit": 20, + "totalPages": 2 +} +``` + +--- + +### GET /presupuestos/:id + +Obtener presupuesto con partidas. + +**Response 200:** +```json +{ + "id": "uuid-1", + "code": "PPTO-2025-001", + "name": "Presupuesto Casa Tipo A", + "version": 1, + "status": "approved", + "totalAmount": 1500000.00, + "partidas": [ + { + "id": "partida-1", + "concepto": { + "id": "concepto-uuid", + "code": "01.01", + "name": "Trazo y nivelacion", + "unit": "M2" + }, + "quantity": 120.00, + "unitPrice": 25.50, + "totalAmount": 3060.00 + } + ], + "fraccionamiento": { + "id": "frac-uuid", + "name": "Residencial Los Pinos" + }, + "createdAt": "2025-01-01T00:00:00Z", + "approvedAt": "2025-01-10T15:00:00Z", + "approvedBy": { + "id": "user-uuid", + "firstName": "Juan", + "lastName": "Director" + } +} +``` + +--- + +### POST /presupuestos + +Crear presupuesto. + +**Request:** +```json +{ + "code": "PPTO-2025-002", + "name": "Presupuesto Casa Tipo B", + "fraccionamientoId": "frac-uuid" +} +``` + +**Response 201:** +```json +{ + "id": "new-uuid", + "code": "PPTO-2025-002", + "name": "Presupuesto Casa Tipo B", + "version": 1, + "status": "draft", + "totalAmount": 0, + "fraccionamientoId": "frac-uuid", + "createdAt": "2025-01-15T10:30:00Z" +} +``` + +--- + +### POST /presupuestos/:id/partidas + +Agregar partida a presupuesto. + +**Request:** +```json +{ + "conceptoId": "concepto-uuid", + "quantity": 100.00, + "unitPrice": 25.50 +} +``` + +**Response 201:** +```json +{ + "id": "partida-uuid", + "presupuestoId": "presupuesto-uuid", + "conceptoId": "concepto-uuid", + "quantity": 100.00, + "unitPrice": 25.50, + "totalAmount": 2550.00 +} +``` + +--- + +### POST /presupuestos/:id/approve + +Aprobar presupuesto. + +**Response 200:** +```json +{ + "id": "presupuesto-uuid", + "status": "approved", + "approvedAt": "2025-01-15T12:00:00Z", + "approvedById": "user-uuid" +} +``` + +--- + +### POST /presupuestos/:id/version + +Crear nueva version del presupuesto. + +**Response 201:** +```json +{ + "id": "new-version-uuid", + "code": "PPTO-2025-001", + "name": "Presupuesto Casa Tipo A", + "version": 2, + "status": "draft", + "totalAmount": 1500000.00, + "partidas": [...] +} +``` + +--- + +## Avances Endpoints + +### GET /avances + +Listar avances de obra. + +**Query Parameters:** +| Param | Tipo | Descripcion | +|-------|------|-------------| +| `loteId` | uuid | Filtrar por lote | +| `departamentoId` | uuid | Filtrar por departamento | +| `status` | string | captured/reviewed/approved/rejected | +| `dateFrom` | date | Fecha desde | +| `dateTo` | date | Fecha hasta | + +**Response 200:** +```json +{ + "data": [ + { + "id": "avance-uuid", + "lote": { + "id": "lote-uuid", + "number": "L-001" + }, + "concepto": { + "id": "concepto-uuid", + "code": "02.01", + "name": "Cimentacion", + "unit": "M3" + }, + "quantity": 15.50, + "progressDate": "2025-01-15", + "status": "approved", + "capturedBy": { + "firstName": "Pedro", + "lastName": "Residente" + } + } + ], + "total": 150, + "page": 1, + "limit": 20, + "totalPages": 8 +} +``` + +--- + +### POST /avances + +Registrar avance de obra. + +**Request:** +```json +{ + "loteId": "lote-uuid", + "conceptoId": "concepto-uuid", + "quantity": 15.50, + "progressDate": "2025-01-15", + "notes": "Cimentacion completada" +} +``` + +**Response 201:** +```json +{ + "id": "new-avance-uuid", + "loteId": "lote-uuid", + "conceptoId": "concepto-uuid", + "quantity": 15.50, + "progressDate": "2025-01-15", + "status": "captured", + "capturedById": "user-uuid", + "notes": "Cimentacion completada", + "createdAt": "2025-01-15T14:30:00Z" +} +``` + +--- + +### POST /avances/:id/fotos + +Agregar foto a avance. + +**Request (multipart/form-data):** +``` +file: +description: "Vista frontal de cimentacion" +latitude: 20.123456 +longitude: -99.654321 +``` + +**Response 201:** +```json +{ + "id": "foto-uuid", + "avanceObraId": "avance-uuid", + "fileName": "IMG_20250115_143000.jpg", + "filePath": "/uploads/avances/2025/01/IMG_20250115_143000.jpg", + "fileSize": 2048576, + "mimeType": "image/jpeg", + "latitude": 20.123456, + "longitude": -99.654321, + "description": "Vista frontal de cimentacion", + "takenAt": "2025-01-15T14:30:00Z" +} +``` + +--- + +### POST /avances/:id/review + +Revisar avance (workflow). + +**Response 200:** +```json +{ + "id": "avance-uuid", + "status": "reviewed", + "reviewedAt": "2025-01-15T16:00:00Z", + "reviewedById": "supervisor-uuid" +} +``` + +--- + +### POST /avances/:id/approve + +Aprobar avance (workflow). + +**Response 200:** +```json +{ + "id": "avance-uuid", + "status": "approved", + "approvedAt": "2025-01-15T17:00:00Z", + "approvedById": "manager-uuid" +} +``` + +--- + +### POST /avances/:id/reject + +Rechazar avance (workflow). + +**Request:** +```json +{ + "reason": "Cantidad incorrecta, verificar medicion" +} +``` + +**Response 200:** +```json +{ + "id": "avance-uuid", + "status": "rejected", + "rejectedAt": "2025-01-15T16:30:00Z", + "rejectedById": "supervisor-uuid", + "rejectionReason": "Cantidad incorrecta, verificar medicion" +} +``` + +--- + +## Bitacora Endpoints + +### GET /bitacora/:fraccionamientoId + +Obtener entradas de bitacora. + +**Query Parameters:** +| Param | Tipo | Descripcion | +|-------|------|-------------| +| `dateFrom` | date | Fecha desde | +| `dateTo` | date | Fecha hasta | + +**Response 200:** +```json +{ + "data": [ + { + "id": "bitacora-uuid", + "entryNumber": 125, + "entryDate": "2025-01-15", + "weather": "Soleado", + "temperature": 28.5, + "workersCount": 45, + "activities": "Colado de cimentacion lotes 15-20", + "incidents": "Ninguno", + "observations": "Se requiere mas cemento para manana", + "createdBy": { + "firstName": "Pedro", + "lastName": "Residente" + } + } + ], + "total": 125, + "page": 1, + "limit": 20, + "totalPages": 7 +} +``` + +--- + +### POST /bitacora + +Crear entrada de bitacora. + +**Request:** +```json +{ + "fraccionamientoId": "frac-uuid", + "entryDate": "2025-01-15", + "weather": "Soleado", + "temperature": 28.5, + "workersCount": 45, + "activities": "Colado de cimentacion lotes 15-20", + "incidents": "Ninguno", + "observations": "Se requiere mas cemento para manana" +} +``` + +**Response 201:** +```json +{ + "id": "new-bitacora-uuid", + "entryNumber": 126, + "entryDate": "2025-01-15", + "weather": "Soleado", + "temperature": 28.5, + "workersCount": 45, + "activities": "Colado de cimentacion lotes 15-20", + "createdById": "user-uuid", + "createdAt": "2025-01-15T18:00:00Z" +} +``` + +--- + +## Estimaciones Endpoints + +### GET /estimaciones + +Listar estimaciones. + +**Query Parameters:** +| Param | Tipo | Descripcion | +|-------|------|-------------| +| `contratoId` | uuid | Filtrar por contrato | +| `status` | string | draft/submitted/reviewed/approved/rejected | + +**Response 200:** +```json +{ + "data": [ + { + "id": "est-uuid", + "number": 5, + "periodStart": "2025-01-01", + "periodEnd": "2025-01-15", + "status": "approved", + "subtotal": 500000.00, + "iva": 80000.00, + "retentions": 25000.00, + "amortization": 10000.00, + "totalAmount": 545000.00, + "contrato": { + "id": "contrato-uuid", + "number": "CTR-2025-001" + } + } + ], + "total": 5, + "page": 1, + "limit": 20, + "totalPages": 1 +} +``` + +--- + +### GET /estimaciones/:id + +Obtener estimacion con detalles. + +**Response 200:** +```json +{ + "id": "est-uuid", + "number": 5, + "periodStart": "2025-01-01", + "periodEnd": "2025-01-15", + "status": "approved", + "subtotal": 500000.00, + "iva": 80000.00, + "retentions": 25000.00, + "amortization": 10000.00, + "totalAmount": 545000.00, + "conceptos": [ + { + "id": "est-concepto-uuid", + "concepto": { + "code": "02.01", + "name": "Cimentacion", + "unit": "M3" + }, + "contractQuantity": 1000.00, + "previousQuantity": 400.00, + "currentQuantity": 150.00, + "accumulatedQuantity": 550.00, + "pendingQuantity": 450.00, + "unitPrice": 1200.00, + "currentAmount": 180000.00, + "accumulatedAmount": 660000.00, + "generadores": [ + { + "description": "Lotes 15-20", + "length": 10.00, + "width": 5.00, + "height": 0.30, + "quantity": 6, + "partial": 90.00 + } + ] + } + ], + "workflow": [ + { + "fromStatus": "draft", + "toStatus": "submitted", + "changedAt": "2025-01-16T10:00:00Z", + "changedBy": { + "firstName": "Pedro", + "lastName": "Residente" + } + } + ] +} +``` + +--- + +### POST /estimaciones + +Crear estimacion. + +**Request:** +```json +{ + "contratoId": "contrato-uuid", + "periodStart": "2025-01-16", + "periodEnd": "2025-01-31" +} +``` + +**Response 201:** +```json +{ + "id": "new-est-uuid", + "number": 6, + "contratoId": "contrato-uuid", + "periodStart": "2025-01-16", + "periodEnd": "2025-01-31", + "status": "draft", + "subtotal": 0, + "totalAmount": 0, + "createdAt": "2025-01-16T09:00:00Z" +} +``` + +--- + +### POST /estimaciones/:id/conceptos + +Agregar concepto a estimacion. + +**Request:** +```json +{ + "conceptoId": "concepto-uuid", + "contractQuantity": 1000.00, + "currentQuantity": 150.00, + "unitPrice": 1200.00 +} +``` + +**Response 201:** +```json +{ + "id": "new-est-concepto-uuid", + "estimacionId": "est-uuid", + "conceptoId": "concepto-uuid", + "contractQuantity": 1000.00, + "previousQuantity": 550.00, + "currentQuantity": 150.00, + "accumulatedQuantity": 700.00, + "pendingQuantity": 300.00, + "unitPrice": 1200.00, + "currentAmount": 180000.00, + "accumulatedAmount": 840000.00 +} +``` + +--- + +### POST /estimaciones/:id/submit + +Enviar estimacion para revision. + +**Response 200:** +```json +{ + "id": "est-uuid", + "status": "submitted", + "submittedAt": "2025-01-20T10:00:00Z", + "submittedById": "user-uuid" +} +``` + +--- + +### POST /estimaciones/:id/approve + +Aprobar estimacion. + +**Response 200:** +```json +{ + "id": "est-uuid", + "status": "approved", + "approvedAt": "2025-01-21T15:00:00Z", + "approvedById": "director-uuid" +} +``` + +--- + +## Codigos de Error + +| Codigo | Descripcion | +|--------|-------------| +| 400 | Bad Request - Datos invalidos | +| 401 | Unauthorized - Token invalido o expirado | +| 403 | Forbidden - Sin permisos | +| 404 | Not Found - Recurso no encontrado | +| 409 | Conflict - Conflicto (ej: duplicado) | +| 422 | Unprocessable Entity - Validacion fallida | +| 500 | Internal Server Error | + +### Formato de Error + +```json +{ + "error": "Bad Request", + "message": "Validation failed", + "details": [ + { + "field": "email", + "message": "Invalid email format" + } + ] +} +``` + +--- + +## Rate Limiting + +| Tipo | Limite | +|------|--------| +| Login | 5 requests/min por IP | +| API General | 100 requests/min por usuario | +| Uploads | 10 requests/min por usuario | + +**Headers de respuesta:** +``` +X-RateLimit-Limit: 100 +X-RateLimit-Remaining: 95 +X-RateLimit-Reset: 1610000000 +``` + +--- + +**Ultima actualizacion:** 2025-12-12 diff --git a/projects/erp-suite/apps/verticales/construccion/docs/backend/MODULES.md b/projects/erp-suite/apps/verticales/construccion/docs/backend/MODULES.md new file mode 100644 index 0000000..f436b94 --- /dev/null +++ b/projects/erp-suite/apps/verticales/construccion/docs/backend/MODULES.md @@ -0,0 +1,744 @@ +# Documentacion de Modulos Backend + +Este documento detalla la implementacion de cada modulo del backend. + +--- + +## Indice + +1. [Auth Module](#auth-module) +2. [Budgets Module (MAI-003)](#budgets-module-mai-003) +3. [Progress Module (MAI-005)](#progress-module-mai-005) +4. [Estimates Module (MAI-008)](#estimates-module-mai-008) +5. [Construction Module (MAI-002)](#construction-module-mai-002) +6. [HR Module (MAI-007)](#hr-module-mai-007) +7. [HSE Module (MAA-017)](#hse-module-maa-017) +8. [Core Module](#core-module) + +--- + +## Auth Module + +**Ubicacion:** `backend/src/modules/auth/` + +### Descripcion + +Modulo de autenticacion JWT con refresh tokens y soporte multi-tenant. + +### Estructura + +``` +auth/ +├── dto/ +│ └── auth.dto.ts # DTOs de autenticacion +├── services/ +│ └── auth.service.ts # Logica de autenticacion +├── middleware/ +│ └── auth.middleware.ts # Middleware JWT +└── index.ts # Exports +``` + +### DTOs + +```typescript +// LoginDto +{ + email: string; // Email del usuario + password: string; // Password +} + +// RegisterDto +{ + email: string; + password: string; + firstName: string; + lastName: string; + tenantId: string; // Tenant al que pertenece +} + +// RefreshTokenDto +{ + refreshToken: string; +} + +// AuthResponse +{ + user: UserDto; + accessToken: string; + refreshToken: string; + expiresIn: number; +} +``` + +### AuthService + +| Metodo | Descripcion | Parametros | Retorna | +|--------|-------------|------------|---------| +| `login` | Autentica usuario | `LoginDto` | `AuthResponse` | +| `register` | Registra nuevo usuario | `RegisterDto` | `AuthResponse` | +| `refresh` | Renueva tokens | `RefreshTokenDto` | `AuthResponse` | +| `logout` | Revoca refresh token | `token: string` | `boolean` | +| `changePassword` | Cambia password | `ChangePasswordDto` | `boolean` | +| `validateToken` | Valida JWT | `token: string` | `JwtPayload` | + +### AuthMiddleware + +| Metodo | Descripcion | +|--------|-------------| +| `authenticate` | Valida JWT (requerido) | +| `optionalAuthenticate` | Valida JWT (opcional) | +| `authorize(...roles)` | Autoriza por roles | +| `requireAdmin` | Solo admin/super_admin | +| `requireSupervisor` | Solo supervisores+ | +| `setRLSContext` | Configura tenant en sesion PostgreSQL | + +### Ejemplo de Uso + +```typescript +// Login +const auth = await authService.login({ + email: 'user@example.com', + password: 'securePassword123' +}); + +// Usar token en requests +headers: { + 'Authorization': `Bearer ${auth.accessToken}` +} + +// Refresh cuando expire +const newAuth = await authService.refresh({ + refreshToken: auth.refreshToken +}); +``` + +--- + +## Budgets Module (MAI-003) + +**Ubicacion:** `backend/src/modules/budgets/` + +### Descripcion + +Gestion de catalogos de conceptos y presupuestos de obra. + +### Estructura + +``` +budgets/ +├── entities/ +│ ├── concepto.entity.ts +│ ├── presupuesto.entity.ts +│ └── presupuesto-partida.entity.ts +├── services/ +│ ├── concepto.service.ts +│ └── presupuesto.service.ts +└── index.ts +``` + +### Entidades + +#### Concepto + +Catalogo jerarquico de conceptos de obra. + +| Campo | Tipo | Descripcion | +|-------|------|-------------| +| `id` | UUID | Primary key | +| `tenantId` | UUID | Discriminador multi-tenant | +| `code` | string | Codigo unico (ej: "01.02.003") | +| `name` | string | Nombre del concepto | +| `description` | string | Descripcion detallada | +| `unit` | string | Unidad de medida (M2, ML, PZA) | +| `unitPrice` | decimal | Precio unitario | +| `parentId` | UUID | Concepto padre (null=raiz) | +| `level` | number | Nivel jerarquico (0=raiz) | +| `path` | string | Ruta completa (ej: "01/02/003") | +| `isActive` | boolean | Activo para uso | + +**Relaciones:** +- `parent` → Concepto (auto-referencial) +- `children` → Concepto[] (hijos) + +#### Presupuesto + +Presupuesto versionado de obra. + +| Campo | Tipo | Descripcion | +|-------|------|-------------| +| `id` | UUID | Primary key | +| `tenantId` | UUID | Discriminador | +| `fraccionamientoId` | UUID | Proyecto asociado | +| `code` | string | Codigo del presupuesto | +| `name` | string | Nombre descriptivo | +| `version` | number | Version (1, 2, 3...) | +| `status` | enum | draft/submitted/approved | +| `totalAmount` | decimal | Total calculado | +| `approvedAt` | timestamp | Fecha de aprobacion | +| `approvedById` | UUID | Usuario que aprobo | + +**Relaciones:** +- `fraccionamiento` → Fraccionamiento +- `partidas` → PresupuestoPartida[] + +#### PresupuestoPartida + +Lineas de presupuesto. + +| Campo | Tipo | Descripcion | +|-------|------|-------------| +| `id` | UUID | Primary key | +| `presupuestoId` | UUID | Presupuesto padre | +| `conceptoId` | UUID | Concepto referenciado | +| `quantity` | decimal | Cantidad | +| `unitPrice` | decimal | Precio unitario | +| `totalAmount` | decimal | **GENERADO**: quantity * unitPrice | + +### ConceptoService + +| Metodo | Descripcion | +|--------|-------------| +| `createConcepto(ctx, dto)` | Crea concepto, calcula nivel/path | +| `findRootConceptos(ctx)` | Conceptos raiz (nivel 0) | +| `findChildren(ctx, parentId)` | Hijos de un concepto | +| `getConceptoTree(ctx, rootId?)` | Arbol completo/parcial | +| `search(ctx, term)` | Busqueda por codigo/nombre | + +### PresupuestoService + +| Metodo | Descripcion | +|--------|-------------| +| `createPresupuesto(ctx, dto)` | Crea presupuesto | +| `findByFraccionamiento(ctx, id)` | Por proyecto | +| `findWithPartidas(ctx, id)` | Con lineas cargadas | +| `addPartida(ctx, presupuestoId, dto)` | Agregar linea | +| `updatePartida(ctx, partidaId, dto)` | Modificar linea | +| `removePartida(ctx, partidaId)` | Eliminar linea | +| `recalculateTotal(ctx, presupuestoId)` | Recalcular total | +| `createNewVersion(ctx, presupuestoId)` | Crear version nueva | +| `approve(ctx, presupuestoId)` | Aprobar presupuesto | + +--- + +## Progress Module (MAI-005) + +**Ubicacion:** `backend/src/modules/progress/` + +### Descripcion + +Control de avances fisicos, bitacora y programacion de obra. + +### Estructura + +``` +progress/ +├── entities/ +│ ├── avance-obra.entity.ts +│ ├── foto-avance.entity.ts +│ ├── bitacora-obra.entity.ts +│ ├── programa-obra.entity.ts +│ └── programa-actividad.entity.ts +├── services/ +│ ├── avance-obra.service.ts +│ └── bitacora-obra.service.ts +└── index.ts +``` + +### Entidades + +#### AvanceObra + +Registro de avance fisico con workflow. + +| Campo | Tipo | Descripcion | +|-------|------|-------------| +| `id` | UUID | Primary key | +| `tenantId` | UUID | Discriminador | +| `loteId` | UUID | Lote donde se registro | +| `departamentoId` | UUID | Departamento (opcional) | +| `conceptoId` | UUID | Concepto de catalogo | +| `quantity` | decimal | Cantidad ejecutada | +| `progressDate` | date | Fecha de avance | +| `status` | enum | captured/reviewed/approved/rejected | +| `capturedById` | UUID | Usuario que capturo | +| `reviewedAt` | timestamp | Fecha de revision | +| `approvedAt` | timestamp | Fecha de aprobacion | +| `rejectionReason` | string | Motivo de rechazo | +| `notes` | string | Notas adicionales | + +#### FotoAvance + +Evidencia fotografica con geolocalización. + +| Campo | Tipo | Descripcion | +|-------|------|-------------| +| `id` | UUID | Primary key | +| `avanceObraId` | UUID | Avance asociado | +| `fileName` | string | Nombre del archivo | +| `filePath` | string | Ruta en storage | +| `fileSize` | number | Tamano en bytes | +| `mimeType` | string | Tipo MIME | +| `latitude` | decimal | Latitud GPS | +| `longitude` | decimal | Longitud GPS | +| `takenAt` | timestamp | Fecha de captura | +| `description` | string | Descripcion | + +#### BitacoraObra + +Bitacora diaria de obra. + +| Campo | Tipo | Descripcion | +|-------|------|-------------| +| `id` | UUID | Primary key | +| `tenantId` | UUID | Discriminador | +| `fraccionamientoId` | UUID | Proyecto | +| `entryNumber` | number | Numero secuencial | +| `entryDate` | date | Fecha de entrada | +| `weather` | string | Clima del dia | +| `temperature` | decimal | Temperatura | +| `workersCount` | number | Personal en obra | +| `activities` | text | Actividades realizadas | +| `incidents` | text | Incidentes | +| `observations` | text | Observaciones | +| `createdById` | UUID | Usuario que creo | + +### AvanceObraService + +| Metodo | Descripcion | +|--------|-------------| +| `createAvance(ctx, dto)` | Crear avance en status captured | +| `findByLote(ctx, loteId)` | Avances de un lote | +| `findByDepartamento(ctx, deptoId)` | Avances de departamento | +| `findWithFilters(ctx, filters)` | Busqueda con filtros | +| `findWithFotos(ctx, id)` | Avance con fotos | +| `addFoto(ctx, avanceId, dto)` | Agregar foto | +| `review(ctx, id)` | Workflow: revisar | +| `approve(ctx, id)` | Workflow: aprobar | +| `reject(ctx, id, reason)` | Workflow: rechazar | +| `getAccumulatedProgress(ctx)` | Acumulado por concepto | + +### BitacoraObraService + +| Metodo | Descripcion | +|--------|-------------| +| `createEntry(ctx, dto)` | Crear entrada (numero automatico) | +| `findByFraccionamiento(ctx, id)` | Entradas de proyecto | +| `findWithFilters(ctx, id, filters)` | Con filtros | +| `findByDate(ctx, id, date)` | Por fecha especifica | +| `findLatest(ctx, id)` | Ultima entrada | +| `getStats(ctx, id)` | Estadisticas | + +--- + +## Estimates Module (MAI-008) + +**Ubicacion:** `backend/src/modules/estimates/` + +### Descripcion + +Estimaciones periodicas con workflow de aprobacion completo. + +### Estructura + +``` +estimates/ +├── entities/ +│ ├── estimacion.entity.ts +│ ├── estimacion-concepto.entity.ts +│ ├── generador.entity.ts +│ ├── anticipo.entity.ts +│ ├── amortizacion.entity.ts +│ ├── retencion.entity.ts +│ ├── fondo-garantia.entity.ts +│ └── estimacion-workflow.entity.ts +├── services/ +│ └── estimacion.service.ts +└── index.ts +``` + +### Entidades + +#### Estimacion + +Estimacion periodica principal. + +| Campo | Tipo | Descripcion | +|-------|------|-------------| +| `id` | UUID | Primary key | +| `tenantId` | UUID | Discriminador | +| `contratoId` | UUID | Contrato asociado | +| `number` | number | Numero secuencial | +| `periodStart` | date | Inicio del periodo | +| `periodEnd` | date | Fin del periodo | +| `status` | enum | draft/submitted/reviewed/approved/rejected | +| `subtotal` | decimal | Suma de conceptos | +| `iva` | decimal | IVA calculado | +| `retentions` | decimal | Retenciones | +| `amortization` | decimal | Amortizacion de anticipo | +| `totalAmount` | decimal | Total neto | +| `submittedAt` | timestamp | Fecha de envio | +| `reviewedAt` | timestamp | Fecha de revision | +| `approvedAt` | timestamp | Fecha de aprobacion | +| `rejectionReason` | string | Motivo de rechazo | + +#### EstimacionConcepto + +Lineas de estimacion con acumulados. + +| Campo | Tipo | Descripcion | +|-------|------|-------------| +| `id` | UUID | Primary key | +| `estimacionId` | UUID | Estimacion padre | +| `conceptoId` | UUID | Concepto de catalogo | +| `contractQuantity` | decimal | Cantidad contratada | +| `previousQuantity` | decimal | Cantidad acumulada anterior | +| `currentQuantity` | decimal | Cantidad este periodo | +| `accumulatedQuantity` | decimal | **GENERADO**: previous + current | +| `pendingQuantity` | decimal | **GENERADO**: contract - accumulated | +| `unitPrice` | decimal | Precio unitario | +| `currentAmount` | decimal | **GENERADO**: current * price | +| `accumulatedAmount` | decimal | **GENERADO**: accumulated * price | + +#### Generador + +Numeros generadores (desglose de cantidades). + +| Campo | Tipo | Descripcion | +|-------|------|-------------| +| `id` | UUID | Primary key | +| `estimacionConceptoId` | UUID | Linea de estimacion | +| `description` | string | Descripcion del calculo | +| `length` | decimal | Largo | +| `width` | decimal | Ancho | +| `height` | decimal | Alto | +| `quantity` | decimal | Cantidad/Piezas | +| `partial` | decimal | **GENERADO**: formula | +| `formula` | string | Formula aplicada | + +#### Anticipo + +Anticipos de contrato. + +| Campo | Tipo | Descripcion | +|-------|------|-------------| +| `id` | UUID | Primary key | +| `contratoId` | UUID | Contrato | +| `type` | enum | obra/materiales | +| `percentage` | decimal | Porcentaje del contrato | +| `amount` | decimal | Monto del anticipo | +| `grantedDate` | date | Fecha de otorgamiento | +| `amortizedAmount` | decimal | Monto ya amortizado | +| `pendingAmount` | decimal | **GENERADO** | +| `status` | enum | pending/partial/completed | + +#### FondoGarantia + +Fondo de garantia acumulado. + +| Campo | Tipo | Descripcion | +|-------|------|-------------| +| `id` | UUID | Primary key | +| `contratoId` | UUID | Contrato | +| `percentage` | decimal | Porcentaje retenido | +| `accumulatedAmount` | decimal | Monto acumulado | +| `releasedAmount` | decimal | Monto liberado | +| `pendingAmount` | decimal | **GENERADO** | + +### EstimacionService + +| Metodo | Descripcion | +|--------|-------------| +| `createEstimacion(ctx, dto)` | Crear con numero automatico | +| `findByContrato(ctx, contratoId)` | Estimaciones de contrato | +| `findWithFilters(ctx, filters)` | Busqueda con filtros | +| `findWithDetails(ctx, id)` | Con todas las relaciones | +| `addConcepto(ctx, estimacionId, dto)` | Agregar linea | +| `addGenerador(ctx, conceptoId, dto)` | Agregar generador | +| `recalculateTotals(ctx, id)` | Recalcular totales | +| `submit(ctx, id)` | Workflow: enviar | +| `review(ctx, id)` | Workflow: revisar | +| `approve(ctx, id)` | Workflow: aprobar | +| `reject(ctx, id, reason)` | Workflow: rechazar | +| `getContractSummary(ctx, contratoId)` | Resumen financiero | + +--- + +## Construction Module (MAI-002) + +**Ubicacion:** `backend/src/modules/construction/` + +### Descripcion + +Gestion de proyectos y estructura organizacional. + +### Entidades + +#### Proyecto + +| Campo | Tipo | Descripcion | +|-------|------|-------------| +| `id` | UUID | Primary key | +| `tenantId` | UUID | Discriminador | +| `code` | string | Codigo del proyecto | +| `name` | string | Nombre | +| `description` | text | Descripcion | +| `status` | enum | planning/in_progress/completed/cancelled | +| `startDate` | date | Fecha inicio | +| `endDate` | date | Fecha fin | +| `budget` | decimal | Presupuesto total | +| `location` | geography | Ubicacion GPS | +| `address` | string | Direccion | +| `city` | string | Ciudad | +| `state` | string | Estado | + +#### Fraccionamiento + +| Campo | Tipo | Descripcion | +|-------|------|-------------| +| `id` | UUID | Primary key | +| `tenantId` | UUID | Discriminador | +| `proyectoId` | UUID | Proyecto padre | +| `code` | string | Codigo | +| `name` | string | Nombre | +| `totalLots` | number | Total de lotes | +| `builtLots` | number | Lotes construidos | +| `soldLots` | number | Lotes vendidos | + +--- + +## HR Module (MAI-007) + +**Ubicacion:** `backend/src/modules/hr/` + +### Descripcion + +Gestion de recursos humanos y asignaciones. + +### Entidades + +#### Employee + +| Campo | Tipo | Descripcion | +|-------|------|-------------| +| `id` | UUID | Primary key | +| `tenantId` | UUID | Discriminador | +| `employeeNumber` | string | Numero de empleado | +| `firstName` | string | Nombre | +| `lastName` | string | Apellidos | +| `email` | string | Email | +| `phone` | string | Telefono | +| `hireDate` | date | Fecha de contratacion | +| `puestoId` | UUID | Puesto | +| `status` | enum | active/inactive/terminated | +| `dailyRate` | decimal | Salario diario | +| `imssNumber` | string | Numero IMSS | +| `curp` | string | CURP | +| `rfc` | string | RFC | + +#### Puesto + +| Campo | Tipo | Descripcion | +|-------|------|-------------| +| `id` | UUID | Primary key | +| `tenantId` | UUID | Discriminador | +| `code` | string | Codigo | +| `name` | string | Nombre del puesto | +| `department` | string | Departamento | +| `level` | number | Nivel jerarquico | +| `baseSalary` | decimal | Salario base | + +#### EmployeeFraccionamiento + +Asignacion de empleados a proyectos. + +| Campo | Tipo | Descripcion | +|-------|------|-------------| +| `id` | UUID | Primary key | +| `employeeId` | UUID | Empleado | +| `fraccionamientoId` | UUID | Proyecto | +| `role` | string | Rol en el proyecto | +| `startDate` | date | Fecha inicio | +| `endDate` | date | Fecha fin (null=activo) | + +--- + +## HSE Module (MAA-017) + +**Ubicacion:** `backend/src/modules/hse/` + +### Descripcion + +Seguridad, salud y medio ambiente. + +### Entidades + +#### Incidente + +| Campo | Tipo | Descripcion | +|-------|------|-------------| +| `id` | UUID | Primary key | +| `tenantId` | UUID | Discriminador | +| `fraccionamientoId` | UUID | Proyecto | +| `incidentNumber` | string | Numero de reporte | +| `incidentDate` | timestamp | Fecha y hora | +| `type` | enum | accident/near_miss/incident | +| `severity` | enum | low/medium/high/critical | +| `description` | text | Descripcion | +| `location` | string | Ubicacion | +| `immediateActions` | text | Acciones inmediatas | +| `rootCause` | text | Causa raiz | +| `status` | enum | reported/investigating/closed | +| `reportedById` | UUID | Quien reporto | + +#### IncidenteInvolucrado + +| Campo | Tipo | Descripcion | +|-------|------|-------------| +| `id` | UUID | Primary key | +| `incidenteId` | UUID | Incidente | +| `employeeId` | UUID | Empleado (opcional) | +| `name` | string | Nombre (si no es empleado) | +| `role` | enum | victim/witness/reporter | +| `injuryType` | string | Tipo de lesion | +| `treatmentRequired` | boolean | Requirio tratamiento | + +#### IncidenteAccion + +Acciones correctivas. + +| Campo | Tipo | Descripcion | +|-------|------|-------------| +| `id` | UUID | Primary key | +| `incidenteId` | UUID | Incidente | +| `description` | text | Descripcion de la accion | +| `responsibleId` | UUID | Responsable | +| `dueDate` | date | Fecha limite | +| `completedAt` | timestamp | Fecha de cierre | +| `status` | enum | pending/in_progress/completed | + +#### Capacitacion + +| Campo | Tipo | Descripcion | +|-------|------|-------------| +| `id` | UUID | Primary key | +| `tenantId` | UUID | Discriminador | +| `name` | string | Nombre del curso | +| `type` | enum | induction/specific/recertification | +| `duration` | number | Duracion en horas | +| `validityMonths` | number | Vigencia en meses | +| `instructor` | string | Instructor | +| `scheduledDate` | date | Fecha programada | +| `status` | enum | scheduled/completed/cancelled | + +--- + +## Core Module + +**Ubicacion:** `backend/src/modules/core/` + +### Descripcion + +Entidades base del sistema (usuarios, tenants). + +### Entidades + +#### User + +| Campo | Tipo | Descripcion | +|-------|------|-------------| +| `id` | UUID | Primary key | +| `email` | string | Email unico | +| `passwordHash` | string | Password hasheado | +| `firstName` | string | Nombre | +| `lastName` | string | Apellidos | +| `role` | enum | Rol del sistema | +| `isActive` | boolean | Usuario activo | +| `lastLoginAt` | timestamp | Ultimo acceso | +| `emailVerifiedAt` | timestamp | Email verificado | + +#### Tenant + +| Campo | Tipo | Descripcion | +|-------|------|-------------| +| `id` | UUID | Primary key | +| `code` | string | Codigo unico | +| `name` | string | Nombre de la empresa | +| `subdomain` | string | Subdominio | +| `plan` | enum | basic/professional/enterprise | +| `isActive` | boolean | Tenant activo | +| `settings` | jsonb | Configuraciones | +| `createdAt` | timestamp | Fecha de creacion | + +--- + +## Shared: BaseService + +**Ubicacion:** `backend/src/shared/services/base.service.ts` + +### Descripcion + +Servicio base abstracto que proporciona operaciones CRUD multi-tenant. + +### Interface ServiceContext + +```typescript +interface ServiceContext { + tenantId: string; + userId: string; +} +``` + +### Interface PaginatedResult + +```typescript +interface PaginatedResult { + data: T[]; + total: number; + page: number; + limit: number; + totalPages: number; +} +``` + +### Metodos + +| Metodo | Descripcion | Retorna | +|--------|-------------|---------| +| `findAll(ctx, options?)` | Listado paginado | `PaginatedResult` | +| `findById(ctx, id)` | Por ID | `T \| null` | +| `findOne(ctx, where)` | Por condicion | `T \| null` | +| `find(ctx, options)` | Por opciones | `T[]` | +| `create(ctx, data)` | Crear registro | `T` | +| `update(ctx, id, data)` | Actualizar | `T \| null` | +| `softDelete(ctx, id)` | Borrado logico | `boolean` | +| `hardDelete(ctx, id)` | Borrado fisico | `boolean` | +| `count(ctx, where?)` | Contar registros | `number` | +| `exists(ctx, where)` | Verificar existencia | `boolean` | + +### Ejemplo de Uso + +```typescript +class MiService extends BaseService { + constructor(repository: Repository) { + super(repository); + } + + // Metodos personalizados + async findActive(ctx: ServiceContext): Promise { + return this.find(ctx, { + where: { isActive: true }, + order: { createdAt: 'DESC' }, + }); + } +} + +// Uso +const ctx = { tenantId: 'uuid', userId: 'uuid' }; +const items = await miService.findAll(ctx, { page: 1, limit: 10 }); +const item = await miService.findById(ctx, 'item-uuid'); +const created = await miService.create(ctx, { name: 'Nuevo' }); +``` + +--- + +**Ultima actualizacion:** 2025-12-12 diff --git a/projects/erp-suite/apps/verticales/mecanicas-diesel/docs/90-transversal/PLAN-IMPLEMENTACION-2025-12.md b/projects/erp-suite/apps/verticales/mecanicas-diesel/docs/90-transversal/PLAN-IMPLEMENTACION-2025-12.md new file mode 100644 index 0000000..4b68d8d --- /dev/null +++ b/projects/erp-suite/apps/verticales/mecanicas-diesel/docs/90-transversal/PLAN-IMPLEMENTACION-2025-12.md @@ -0,0 +1,559 @@ +# Plan de Implementacion - Mecanicas Diesel + +**Fecha:** 2025-12-12 +**Version:** 2.0.0 +**Estado:** LISTO PARA DESARROLLO + +--- + +## Resumen Ejecutivo + +Este documento presenta el plan actualizado de implementacion para el proyecto **mecanicas-diesel**, considerando el estado actual del proyecto **erp-core** y las especificaciones tecnicas disponibles. + +### Estado Actual + +| Proyecto | Estado | Avance | +|----------|--------|--------| +| **erp-core** | DDL completo, Backend parcial | 60% | +| **mecanicas-diesel** | Documentacion completa, DDL completo | Listo para desarrollo | + +### Decision Arquitectonica + +**mecanicas-diesel** es un **proyecto independiente** que: +- NO depende de erp-core para ejecutarse +- Implementa sus propios schemas y tablas +- Adapta patrones similares a erp-core para su dominio especifico +- Opera de forma completamente standalone + +--- + +## Analisis de erp-core (Diciembre 2025) + +### Estructura de erp-core + +``` +erp-core/ +├── backend/ # 15 modulos implementados (104 archivos TS) +│ └── src/modules/ +│ ├── auth/ # JWT, API Keys, sessions +│ ├── users/ # CRUD usuarios +│ ├── companies/ # Empresas multi-tenant +│ ├── partners/ # Clientes, proveedores +│ ├── inventory/ # Productos, stock +│ ├── financial/ # Contabilidad +│ ├── purchases/ # Ordenes de compra +│ ├── sales/ # Ordenes de venta +│ └── ... +│ +├── frontend/ # 3 features completas (React + Vite) +│ └── src/features/ +│ ├── companies/ +│ ├── users/ +│ └── partners/ +│ +├── database/ddl/ # 15 archivos SQL (~10,000 lineas) +│ ├── 00-prerequisites.sql # Extensions +│ ├── 01-auth.sql # Autenticacion +│ ├── 01-auth-extensions.sql # Extensiones auth +│ ├── 02-core.sql # Partners, catalogos +│ ├── 03-analytics.sql # Contabilidad analitica +│ ├── 04-financial.sql # Contabilidad financiera +│ ├── 05-inventory.sql # Inventario +│ ├── 06-purchase.sql # Compras +│ ├── 07-sales.sql # Ventas +│ ├── 08-projects.sql # Proyectos +│ ├── 09-system.sql # Sistema, mensajes +│ ├── 10-billing.sql # SaaS billing +│ ├── 11-crm.sql # CRM +│ └── 12-hr.sql # RRHH +│ +└── docs/ # 829 archivos Markdown + └── 04-modelado/especificaciones-tecnicas/transversal/ + └── 30 especificaciones tecnicas +``` + +### Modulos de erp-core (19 totales) + +| Fase | Modulos | Estado | +|------|---------|--------| +| **P0 Foundation** | MGN-001 Auth, MGN-002 Users, MGN-003 Roles, MGN-004 Tenants | En desarrollo | +| **P1 Core Business** | MGN-005 Catalogs, MGN-010 Financial, MGN-011 Inventory, MGN-012 Purchasing, MGN-013 Sales, MGN-006 Settings | Planificado | +| **P2 Extended** | MGN-007 Audit, MGN-008 Notifications, MGN-009 Reports, MGN-014 CRM, MGN-015 Projects | Planificado | +| **P3 SaaS** | MGN-016 Billing, MGN-017 Payments, MGN-018 WhatsApp, MGN-019 AI Agents | Planificado | + +### Especificaciones Transversales Disponibles (30) + +``` +erp-core/docs/04-modelado/especificaciones-tecnicas/transversal/ +├── SPEC-ALERTAS-PRESUPUESTO.md +├── SPEC-BLANKET-ORDERS.md +├── SPEC-CONCILIACION-BANCARIA.md +├── SPEC-CONSOLIDACION-FINANCIERA.md +├── SPEC-CONTABILIDAD-ANALITICA-MULTIDIMENSIONAL.md +├── SPEC-FIRMA-ELECTRONICA-NOM151.md +├── SPEC-GASTOS-EMPLEADOS.md +├── SPEC-IMPUESTOS-AVANZADOS.md +├── SPEC-INTEGRACION-CALENDAR.md +├── SPEC-INVENTARIOS-CICLICOS.md +├── SPEC-LOCALIZACION-PAISES.md +├── SPEC-MAIL-THREAD-TRACKING.md +├── SPEC-NOMINA-BASICA.md +├── SPEC-OAUTH2-SOCIAL-LOGIN.md +├── SPEC-PLANTILLAS-CUENTAS.md +├── SPEC-PORTAL-PROVEEDORES.md +├── SPEC-PRESUPUESTOS-REVISIONES.md +├── SPEC-PRICING-RULES.md +├── SPEC-PROYECTOS-DEPENDENCIAS-BURNDOWN.md +├── SPEC-REPORTES-FINANCIEROS.md +├── SPEC-RRHH-EVALUACIONES-SKILLS.md +├── SPEC-SCHEDULER-REPORTES.md +├── SPEC-SEGURIDAD-API-KEYS-PERMISOS.md +├── SPEC-SISTEMA-SECUENCIAS.md +├── SPEC-TAREAS-RECURRENTES.md +├── SPEC-TASAS-CAMBIO-AUTOMATICAS.md +├── SPEC-TRAZABILIDAD-LOTES-SERIES.md +├── SPEC-TWO-FACTOR-AUTHENTICATION.md +├── SPEC-VALORACION-INVENTARIO.md +└── SPEC-WIZARD-TRANSIENT-MODEL.md +``` + +--- + +## Estado Actual de mecanicas-diesel + +### DDL Implementados (11 archivos) + +``` +mecanicas-diesel/database/init/ +├── 00-extensions.sql # Extensiones PostgreSQL +├── 01-create-schemas.sql # 7 schemas propios +├── 02-rls-functions.sql # Multi-tenant RLS +├── 03-service-management-tables.sql # Ordenes, diagnosticos +├── 04-parts-management-tables.sql # Inventario refacciones +├── 05-vehicle-management-tables.sql # Vehiculos, flotas +├── 06-seed-data.sql # Datos iniciales +├── 07-notifications-schema.sql # Tracking, followers, actividades +├── 08-analytics-schema.sql # Contabilidad analitica +├── 09-purchasing-schema.sql # Compras, proveedores +├── 10-warranty-claims.sql # Garantias +└── 11-quote-signature.sql # Firma electronica +``` + +### Schemas de mecanicas-diesel (7) + +| Schema | Tablas | Equivalente Core | +|--------|--------|------------------| +| workshop_core | 9 | auth + core | +| service_management | 14+ | sales + projects | +| parts_management | 12+ | inventory | +| vehicle_management | 8 | (especifico) | +| notifications | 6 | system (messages) | +| analytics | 4 | analytics | +| purchasing | 5 | purchase | + +### Modulos Documentados (6 MVP) + +| Codigo | Modulo | User Stories | Story Points | +|--------|--------|--------------|--------------| +| MMD-001 | Fundamentos | 9 | 42 | +| MMD-002 | Ordenes de Servicio | 11 | 55 | +| MMD-003 | Diagnosticos | 8 | 42 | +| MMD-004 | Inventario | 10 | 42 | +| MMD-005 | Vehiculos | 8 | 34 | +| MMD-006 | Cotizaciones | 7 | 26 | +| **Total** | **6 modulos** | **55 US** | **241 SP** | + +--- + +## Mapeo: mecanicas-diesel → erp-core + +### Equivalencia de Funcionalidades + +| Funcionalidad mecanicas-diesel | erp-core Equivalente | Spec Aplicable | +|--------------------------------|---------------------|----------------| +| Multi-tenant (taller = tenant) | MGN-004 Tenants | - | +| Autenticacion JWT | MGN-001 Auth | SPEC-TWO-FACTOR-AUTHENTICATION | +| Usuarios y roles | MGN-002/003 Users/Roles | SPEC-SEGURIDAD-API-KEYS-PERMISOS | +| Catalogos (servicios, partes) | MGN-005 Catalogs | - | +| Inventario de refacciones | MGN-011 Inventory | SPEC-VALORACION-INVENTARIO, SPEC-TRAZABILIDAD-LOTES-SERIES | +| Ordenes de compra | MGN-012 Purchasing | SPEC-BLANKET-ORDERS | +| Cotizaciones | MGN-013 Sales | SPEC-PRICING-RULES | +| Sistema de tracking | MGN-008 Notifications | SPEC-MAIL-THREAD-TRACKING | +| Contabilidad analitica | financialanalytics | SPEC-CONTABILIDAD-ANALITICA-MULTIDIMENSIONAL | +| Firma electronica | - | SPEC-FIRMA-ELECTRONICA-NOM151 | +| Garantias | (especifico) | - | +| Vehiculos/flotas | (especifico) | - | +| Diagnosticos diesel | (especifico) | - | + +### Specs de erp-core Aplicables a mecanicas-diesel + +| Spec | Aplicabilidad | Implementado en MMD | +|------|---------------|---------------------| +| SPEC-MAIL-THREAD-TRACKING | Alta | Si - 07-notifications | +| SPEC-CONTABILIDAD-ANALITICA | Alta | Si - 08-analytics | +| SPEC-VALORACION-INVENTARIO | Alta | Parcial | +| SPEC-TRAZABILIDAD-LOTES-SERIES | Media | No | +| SPEC-PRICING-RULES | Media | No | +| SPEC-FIRMA-ELECTRONICA-NOM151 | Media | Basico - 11-quote-signature | +| SPEC-SISTEMA-SECUENCIAS | Alta | No (usar directamente) | +| SPEC-SEGURIDAD-API-KEYS | Alta | No (usar directamente) | +| SPEC-TWO-FACTOR-AUTHENTICATION | Baja | No (Fase 2) | + +--- + +## Plan de Desarrollo + +### Fase 1: Backend Foundation (Semanas 1-2) + +**Objetivo:** Implementar APIs core basadas en patrones de erp-core + +#### Sprint 1.1: Auth y Usuarios + +``` +Tareas: +1. Configurar proyecto NestJS + - Estructura de modulos + - Configuracion de DB + - Middleware de autenticacion + +2. Implementar modulo Auth (basado en erp-core/backend/src/modules/auth/) + - Login/Register endpoints + - JWT tokens (access + refresh) + - Password hashing con bcrypt + +3. Implementar modulo Users + - CRUD de usuarios + - Roles por defecto (admin, jefe_taller, mecanico, recepcion) + +4. Implementar RLS middleware + - Inyeccion de tenant_id + - Validacion de permisos + +Entregables: +- [ ] POST /api/auth/login +- [ ] POST /api/auth/register +- [ ] POST /api/auth/refresh +- [ ] GET /api/users +- [ ] GET /api/users/:id +- [ ] POST /api/users +- [ ] PUT /api/users/:id +- [ ] DELETE /api/users/:id +``` + +#### Sprint 1.2: Catalogos y Configuracion + +``` +Tareas: +1. Implementar modulo workshop_core + - Talleres (tenants) + - Clientes + - Servicios + - Bahias de trabajo + +2. Implementar sistema de secuencias (SPEC-SISTEMA-SECUENCIAS) + - Folios de ordenes + - Folios de cotizaciones + +Entregables: +- [ ] CRUD Talleres +- [ ] CRUD Clientes +- [ ] CRUD Servicios +- [ ] CRUD Bahias +- [ ] Sistema de folios automaticos +``` + +### Fase 2: Modulos de Negocio (Semanas 3-5) + +#### Sprint 2.1: Ordenes de Servicio (MMD-002) + +``` +Tareas: +1. CRUD de ordenes de servicio +2. Estados y transiciones (maquina de estados) +3. Asignacion a mecanicos y bahias +4. Integracion con sistema de tracking (notifications schema) + +Entregables: +- [ ] POST /api/service-orders +- [ ] GET /api/service-orders +- [ ] PUT /api/service-orders/:id/status +- [ ] POST /api/service-orders/:id/assign +- [ ] GET /api/service-orders/:id/history (tracking) +``` + +#### Sprint 2.2: Diagnosticos (MMD-003) + +``` +Tareas: +1. CRUD de diagnosticos +2. Tipos de prueba configurables +3. Asociacion con ordenes de servicio +4. Upload de fotos de evidencia + +Entregables: +- [ ] POST /api/diagnostics +- [ ] GET /api/diagnostics/:id +- [ ] POST /api/diagnostics/:id/tests +- [ ] POST /api/diagnostics/:id/photos +``` + +#### Sprint 2.3: Inventario (MMD-004) + +``` +Tareas: +1. CRUD de refacciones +2. Movimientos de stock (siguiendo SPEC-VALORACION-INVENTARIO) +3. Solicitudes desde ordenes de servicio +4. Alertas de stock minimo + +Entregables: +- [ ] CRUD /api/parts +- [ ] POST /api/stock-moves +- [ ] GET /api/stock-levels +- [ ] POST /api/part-requests +``` + +### Fase 3: Cotizaciones y Compras (Semanas 6-7) + +#### Sprint 3.1: Cotizaciones (MMD-006) + +``` +Tareas: +1. CRUD de cotizaciones +2. Generacion desde diagnosticos +3. Aprobacion con firma electronica (basico) +4. Conversion a orden de servicio + +Entregables: +- [ ] POST /api/quotes +- [ ] GET /api/quotes/:id +- [ ] POST /api/quotes/:id/sign +- [ ] POST /api/quotes/:id/convert +``` + +#### Sprint 3.2: Compras (purchasing schema) + +``` +Tareas: +1. CRUD de proveedores +2. Ordenes de compra +3. Recepciones de mercancia +4. Integracion con inventario + +Entregables: +- [ ] CRUD /api/suppliers +- [ ] CRUD /api/purchase-orders +- [ ] POST /api/purchase-receipts +``` + +### Fase 4: Frontend (Semanas 8-10) + +#### Sprint 4.1: Setup y Layout + +``` +Tareas: +1. Configurar proyecto React + Vite +2. Implementar layout principal +3. Sistema de rutas +4. Autenticacion frontend + +Estructura (basada en erp-core/frontend): +src/ +├── features/ +│ ├── auth/ +│ ├── service-orders/ +│ ├── diagnostics/ +│ ├── inventory/ +│ └── quotes/ +├── components/ +├── hooks/ +├── stores/ +└── services/ +``` + +#### Sprint 4.2: Modulos Core + +``` +Entregables: +- [ ] Login/Dashboard +- [ ] Lista de ordenes (Kanban) +- [ ] Formulario de orden +- [ ] Vista de diagnostico +- [ ] Gestion de inventario +``` + +--- + +## Patrones de erp-core a Reutilizar + +### 1. Estructura de Modulo Backend + +```typescript +// Patron de erp-core/backend/src/modules/{module}/ +{module}/ +├── {module}.controller.ts // Endpoints +├── {module}.service.ts // Logica de negocio +├── {module}.routes.ts // Definicion de rutas +├── {module}.dto.ts // Data Transfer Objects (Zod) +└── index.ts // Exports +``` + +### 2. BaseService Pattern + +```typescript +// Reutilizar de erp-core/backend/src/shared/services/base.service.ts +abstract class BaseService { + constructor(protected tableName: string) {} + + async findAll(tenantId: string, filters?: any): Promise + async findById(tenantId: string, id: string): Promise + async create(tenantId: string, data: Partial): Promise + async update(tenantId: string, id: string, data: Partial): Promise + async delete(tenantId: string, id: string): Promise +} +``` + +### 3. Auth Middleware + +```typescript +// Reutilizar patron de erp-core/backend/src/shared/middleware/auth.middleware.ts +export const authMiddleware = async (req, res, next) => { + const token = req.headers.authorization?.split(' ')[1]; + const payload = verifyJWT(token); + req.user = payload; + req.tenantId = payload.tenantId; + next(); +}; +``` + +### 4. Feature Frontend Pattern + +```typescript +// Patron de erp-core/frontend/src/features/{feature}/ +{feature}/ +├── api/{feature}.api.ts // Llamadas HTTP +├── types/{feature}.types.ts // Interfaces +├── hooks/use{Feature}.ts // Custom hooks +└── components/ + ├── {Feature}Form.tsx + ├── {Feature}List.tsx + └── {Feature}FiltersPanel.tsx +``` + +--- + +## Configuracion de Entorno + +### Variables de Entorno (.env) + +```bash +# Server +NODE_ENV=development +PORT=3021 # Puerto asignado por DEVENV +API_PREFIX=/api/v1 + +# Database +DB_HOST=localhost +DB_PORT=5432 +DB_NAME=mecanicas_diesel_db # Base de datos propia +DB_USER=mecanicas_user +DB_PASSWORD=mecanicas_secret_2024 + +# JWT +JWT_SECRET=mmd-super-secret-jwt-key-change-in-production +JWT_EXPIRES_IN=24h +JWT_REFRESH_EXPIRES_IN=7d + +# Logging +LOG_LEVEL=debug + +# CORS +CORS_ORIGIN=http://localhost:3020 +``` + +### Puertos (Consultar con DEVENV) + +| Servicio | Puerto Sugerido | Descripcion | +|----------|-----------------|-------------| +| Backend API | 3021 | APIs REST NestJS | +| Frontend | 3020 | React + Vite | +| PostgreSQL | 5432 | Compartido (DB propia) | + +**IMPORTANTE:** Antes de confirmar puertos, consultar con perfil DEVENV para evitar conflictos. + +--- + +## Entregables por Sprint + +### Sprint 1 (Semanas 1-2) +- [ ] Proyecto NestJS configurado +- [ ] Modulo Auth funcional +- [ ] Modulo Users funcional +- [ ] RLS implementado +- [ ] Tests unitarios auth + +### Sprint 2 (Semanas 3-4) +- [ ] Modulo ServiceOrders +- [ ] Modulo Diagnostics +- [ ] Sistema de tracking +- [ ] Tests de integracion + +### Sprint 3 (Semanas 5-6) +- [ ] Modulo Inventory +- [ ] Modulo Quotes +- [ ] Firma basica +- [ ] Tests E2E + +### Sprint 4 (Semanas 7-8) +- [ ] Modulo Purchasing +- [ ] Integracion completa +- [ ] Documentacion API (Swagger) + +### Sprint 5 (Semanas 9-10) +- [ ] Frontend React +- [ ] Vistas principales +- [ ] Integracion backend +- [ ] Deploy staging + +--- + +## Metricas de Exito + +| Metrica | Objetivo | +|---------|----------| +| Cobertura de tests | >70% | +| Endpoints documentados | 100% | +| User Stories implementadas | 55/55 | +| Tiempo de respuesta API | <200ms | +| Build sin errores | 100% | + +--- + +## Riesgos y Mitigaciones + +| Riesgo | Probabilidad | Impacto | Mitigacion | +|--------|--------------|---------|------------| +| Complejidad de ordenes de servicio | Media | Alto | Empezar con flujo simplificado | +| Integracion con tracking | Baja | Medio | DDL ya probado | +| Firma electronica avanzada | Media | Bajo | Implementar basico primero | +| Performance inventario | Baja | Medio | Indices optimizados en DDL | + +--- + +## Referencias + +| Documento | Ubicacion | +|-----------|-----------| +| DDL mecanicas-diesel | `database/init/*.sql` | +| Specs transversales | `erp-core/docs/04-modelado/especificaciones-tecnicas/transversal/` | +| Backend patterns | `erp-core/backend/src/` | +| Frontend patterns | `erp-core/frontend/src/` | +| User Stories | `docs/02-definicion-modulos/*/historias-usuario/` | +| Epicas | `docs/08-epicas/` | + +--- + +**Documento creado por:** Architecture-Analyst +**Fecha:** 2025-12-12 +**Version:** 2.0.0 +**Para uso de:** TECH-LEADER