changes on project erp-suite documentation
Some checks are pending
CI Pipeline / changes (push) Waiting to run
CI Pipeline / core (push) Blocked by required conditions
CI Pipeline / trading-backend (push) Blocked by required conditions
CI Pipeline / trading-data-service (push) Blocked by required conditions
CI Pipeline / trading-frontend (push) Blocked by required conditions
CI Pipeline / erp-core (push) Blocked by required conditions
CI Pipeline / erp-mecanicas (push) Blocked by required conditions
CI Pipeline / gamilit-backend (push) Blocked by required conditions
CI Pipeline / gamilit-frontend (push) Blocked by required conditions

This commit is contained in:
Adrian Flores Cortes 2025-12-12 08:14:03 -06:00
parent 4ac2b7aa0d
commit 7ed5c5ab45
8 changed files with 4418 additions and 12 deletions

View File

@ -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 <repo-url>
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<Concepto> {
constructor(repository: Repository<Concepto>) {
super(repository);
}
// Metodos especificos del dominio
async findByCode(ctx: ServiceContext, code: string): Promise<Concepto | null> {
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<Repository<Concepto>>;
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

View File

@ -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+ |
---

View File

@ -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<Concepto> {
constructor(
repository: Repository<Concepto>,
private readonly presupuestoRepo: Repository<Presupuesto>
) {
super(repository);
}
// Metodos especificos del dominio
async findRootConceptos(ctx: ServiceContext): Promise<Concepto[]> {
return this.repository.find({
where: {
tenantId: ctx.tenantId,
parentId: IsNull(),
deletedAt: IsNull(),
},
order: { code: 'ASC' },
});
}
}
```
### 3. BaseService
```typescript
// Metodos disponibles en BaseService<T>
abstract class BaseService<T> {
// Lectura
findAll(ctx, options?): Promise<PaginatedResult<T>>
findById(ctx, id): Promise<T | null>
findOne(ctx, where): Promise<T | null>
find(ctx, options): Promise<T[]>
count(ctx, where?): Promise<number>
exists(ctx, where): Promise<boolean>
// Escritura
create(ctx, data): Promise<T>
update(ctx, id, data): Promise<T | null>
softDelete(ctx, id): Promise<boolean>
hardDelete(ctx, id): Promise<boolean>
}
```
### 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<Estimacion> {
async submit(ctx: ServiceContext, id: string): Promise<Estimacion | null> {
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<Estimacion | null> {
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 <access_token>
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<Repository<Concepto>>;
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

View File

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

View File

@ -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 <access_token>
```
## 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'

View File

@ -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 <access_token>
```
---
## 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 <access_token>
```
**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: <archivo imagen>
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

View File

@ -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<T> {
data: T[];
total: number;
page: number;
limit: number;
totalPages: number;
}
```
### Metodos
| Metodo | Descripcion | Retorna |
|--------|-------------|---------|
| `findAll(ctx, options?)` | Listado paginado | `PaginatedResult<T>` |
| `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<MiEntity> {
constructor(repository: Repository<MiEntity>) {
super(repository);
}
// Metodos personalizados
async findActive(ctx: ServiceContext): Promise<MiEntity[]> {
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

View File

@ -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<T> {
constructor(protected tableName: string) {}
async findAll(tenantId: string, filters?: any): Promise<T[]>
async findById(tenantId: string, id: string): Promise<T | null>
async create(tenantId: string, data: Partial<T>): Promise<T>
async update(tenantId: string, id: string, data: Partial<T>): Promise<T>
async delete(tenantId: string, id: string): Promise<boolean>
}
```
### 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