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
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:
parent
4ac2b7aa0d
commit
7ed5c5ab45
478
projects/erp-suite/apps/verticales/construccion/CONTRIBUTING.md
Normal file
478
projects/erp-suite/apps/verticales/construccion/CONTRIBUTING.md
Normal 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
|
||||
@ -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+ |
|
||||
|
||||
---
|
||||
|
||||
|
||||
@ -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
|
||||
@ -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
|
||||
|
||||
@ -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'
|
||||
@ -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
|
||||
@ -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
|
||||
@ -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
|
||||
Loading…
Reference in New Issue
Block a user