Initial commit - erp-construccion
This commit is contained in:
commit
a9703b2727
119
.env
Normal file
119
.env
Normal file
@ -0,0 +1,119 @@
|
||||
# =============================================================================
|
||||
# ERP Construccion - Environment Variables
|
||||
# =============================================================================
|
||||
# Copia este archivo a .env y configura los valores
|
||||
# cp .env.example .env
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
# APPLICATION
|
||||
# -----------------------------------------------------------------------------
|
||||
NODE_ENV=development
|
||||
APP_PORT=3000
|
||||
API_VERSION=v1
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
# DATABASE - PostgreSQL
|
||||
# -----------------------------------------------------------------------------
|
||||
DB_HOST=localhost
|
||||
DB_PORT=5433
|
||||
DB_USER=construccion
|
||||
DB_PASSWORD=construccion_dev_2024
|
||||
DB_NAME=erp_construccion
|
||||
DB_SCHEMA=public
|
||||
|
||||
# Database Pool
|
||||
DB_POOL_MIN=2
|
||||
DB_POOL_MAX=10
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
# REDIS
|
||||
# -----------------------------------------------------------------------------
|
||||
REDIS_HOST=localhost
|
||||
REDIS_PORT=6379
|
||||
REDIS_PASSWORD=redis_dev_2024
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
# JWT & AUTHENTICATION
|
||||
# -----------------------------------------------------------------------------
|
||||
JWT_SECRET=your-super-secret-jwt-key-change-in-production-minimum-32-chars
|
||||
JWT_EXPIRES_IN=1d
|
||||
JWT_REFRESH_EXPIRES_IN=7d
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
# CORS
|
||||
# -----------------------------------------------------------------------------
|
||||
CORS_ORIGIN=http://localhost:5173,http://localhost:3001
|
||||
CORS_CREDENTIALS=true
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
# LOGGING
|
||||
# -----------------------------------------------------------------------------
|
||||
LOG_LEVEL=debug
|
||||
LOG_FORMAT=dev
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
# FILE STORAGE (S3 Compatible)
|
||||
# -----------------------------------------------------------------------------
|
||||
STORAGE_TYPE=local
|
||||
# STORAGE_TYPE=s3
|
||||
# S3_BUCKET=construccion-files
|
||||
# S3_REGION=us-east-1
|
||||
# S3_ACCESS_KEY_ID=your-access-key
|
||||
# S3_SECRET_ACCESS_KEY=your-secret-key
|
||||
# S3_ENDPOINT=https://s3.amazonaws.com
|
||||
|
||||
# Local storage path (when STORAGE_TYPE=local)
|
||||
UPLOAD_PATH=./uploads
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
# EMAIL (SMTP)
|
||||
# -----------------------------------------------------------------------------
|
||||
SMTP_HOST=localhost
|
||||
SMTP_PORT=1025
|
||||
SMTP_USER=
|
||||
SMTP_PASSWORD=
|
||||
SMTP_FROM=noreply@construccion.local
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
# WHATSAPP BUSINESS API (Optional)
|
||||
# -----------------------------------------------------------------------------
|
||||
# WHATSAPP_API_URL=https://graph.facebook.com/v17.0
|
||||
# WHATSAPP_PHONE_NUMBER_ID=your-phone-number-id
|
||||
# WHATSAPP_ACCESS_TOKEN=your-access-token
|
||||
# WHATSAPP_VERIFY_TOKEN=your-verify-token
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
# INTEGRATIONS (Optional)
|
||||
# -----------------------------------------------------------------------------
|
||||
# IMSS
|
||||
# IMSS_API_URL=https://api.imss.gob.mx
|
||||
# IMSS_CERTIFICATE_PATH=/certs/imss.p12
|
||||
# IMSS_CERTIFICATE_PASSWORD=
|
||||
|
||||
# INFONAVIT
|
||||
# INFONAVIT_API_URL=https://api.infonavit.org.mx
|
||||
# INFONAVIT_CLIENT_ID=
|
||||
# INFONAVIT_CLIENT_SECRET=
|
||||
|
||||
# SAT (CFDI)
|
||||
# PAC_URL=https://api.pac.com.mx
|
||||
# PAC_USER=
|
||||
# PAC_PASSWORD=
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
# FEATURE FLAGS
|
||||
# -----------------------------------------------------------------------------
|
||||
FEATURE_HSE_AI=false
|
||||
FEATURE_WHATSAPP_BOT=false
|
||||
FEATURE_BIOMETRIC=false
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
# DOCKER COMPOSE OVERRIDES
|
||||
# -----------------------------------------------------------------------------
|
||||
# Used by docker-compose.yml
|
||||
BACKEND_PORT=3000
|
||||
FRONTEND_PORT=5173
|
||||
ADMINER_PORT=8080
|
||||
MAILHOG_SMTP_PORT=1025
|
||||
MAILHOG_WEB_PORT=8025
|
||||
BUILD_TARGET=development
|
||||
119
.env.example
Normal file
119
.env.example
Normal file
@ -0,0 +1,119 @@
|
||||
# =============================================================================
|
||||
# ERP Construccion - Environment Variables
|
||||
# =============================================================================
|
||||
# Copia este archivo a .env y configura los valores
|
||||
# cp .env.example .env
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
# APPLICATION
|
||||
# -----------------------------------------------------------------------------
|
||||
NODE_ENV=development
|
||||
APP_PORT=3000
|
||||
API_VERSION=v1
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
# DATABASE - PostgreSQL
|
||||
# -----------------------------------------------------------------------------
|
||||
DB_HOST=localhost
|
||||
DB_PORT=5432
|
||||
DB_USER=construccion
|
||||
DB_PASSWORD=construccion_dev_2024
|
||||
DB_NAME=erp_construccion
|
||||
DB_SCHEMA=public
|
||||
|
||||
# Database Pool
|
||||
DB_POOL_MIN=2
|
||||
DB_POOL_MAX=10
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
# REDIS
|
||||
# -----------------------------------------------------------------------------
|
||||
REDIS_HOST=localhost
|
||||
REDIS_PORT=6379
|
||||
REDIS_PASSWORD=redis_dev_2024
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
# JWT & AUTHENTICATION
|
||||
# -----------------------------------------------------------------------------
|
||||
JWT_SECRET=your-super-secret-jwt-key-change-in-production-minimum-32-chars
|
||||
JWT_EXPIRES_IN=1d
|
||||
JWT_REFRESH_EXPIRES_IN=7d
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
# CORS
|
||||
# -----------------------------------------------------------------------------
|
||||
CORS_ORIGIN=http://localhost:5173,http://localhost:3001
|
||||
CORS_CREDENTIALS=true
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
# LOGGING
|
||||
# -----------------------------------------------------------------------------
|
||||
LOG_LEVEL=debug
|
||||
LOG_FORMAT=dev
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
# FILE STORAGE (S3 Compatible)
|
||||
# -----------------------------------------------------------------------------
|
||||
STORAGE_TYPE=local
|
||||
# STORAGE_TYPE=s3
|
||||
# S3_BUCKET=construccion-files
|
||||
# S3_REGION=us-east-1
|
||||
# S3_ACCESS_KEY_ID=your-access-key
|
||||
# S3_SECRET_ACCESS_KEY=your-secret-key
|
||||
# S3_ENDPOINT=https://s3.amazonaws.com
|
||||
|
||||
# Local storage path (when STORAGE_TYPE=local)
|
||||
UPLOAD_PATH=./uploads
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
# EMAIL (SMTP)
|
||||
# -----------------------------------------------------------------------------
|
||||
SMTP_HOST=localhost
|
||||
SMTP_PORT=1025
|
||||
SMTP_USER=
|
||||
SMTP_PASSWORD=
|
||||
SMTP_FROM=noreply@construccion.local
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
# WHATSAPP BUSINESS API (Optional)
|
||||
# -----------------------------------------------------------------------------
|
||||
# WHATSAPP_API_URL=https://graph.facebook.com/v17.0
|
||||
# WHATSAPP_PHONE_NUMBER_ID=your-phone-number-id
|
||||
# WHATSAPP_ACCESS_TOKEN=your-access-token
|
||||
# WHATSAPP_VERIFY_TOKEN=your-verify-token
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
# INTEGRATIONS (Optional)
|
||||
# -----------------------------------------------------------------------------
|
||||
# IMSS
|
||||
# IMSS_API_URL=https://api.imss.gob.mx
|
||||
# IMSS_CERTIFICATE_PATH=/certs/imss.p12
|
||||
# IMSS_CERTIFICATE_PASSWORD=
|
||||
|
||||
# INFONAVIT
|
||||
# INFONAVIT_API_URL=https://api.infonavit.org.mx
|
||||
# INFONAVIT_CLIENT_ID=
|
||||
# INFONAVIT_CLIENT_SECRET=
|
||||
|
||||
# SAT (CFDI)
|
||||
# PAC_URL=https://api.pac.com.mx
|
||||
# PAC_USER=
|
||||
# PAC_PASSWORD=
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
# FEATURE FLAGS
|
||||
# -----------------------------------------------------------------------------
|
||||
FEATURE_HSE_AI=false
|
||||
FEATURE_WHATSAPP_BOT=false
|
||||
FEATURE_BIOMETRIC=false
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
# DOCKER COMPOSE OVERRIDES
|
||||
# -----------------------------------------------------------------------------
|
||||
# Used by docker-compose.yml
|
||||
BACKEND_PORT=3000
|
||||
FRONTEND_PORT=5173
|
||||
ADMINER_PORT=8080
|
||||
MAILHOG_SMTP_PORT=1025
|
||||
MAILHOG_WEB_PORT=8025
|
||||
BUILD_TARGET=development
|
||||
263
.github/workflows/ci.yml
vendored
Normal file
263
.github/workflows/ci.yml
vendored
Normal file
@ -0,0 +1,263 @@
|
||||
# =============================================================================
|
||||
# CI Pipeline - ERP Construccion
|
||||
# Runs on every push and pull request
|
||||
# =============================================================================
|
||||
|
||||
name: CI Pipeline
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [main, develop, 'feature/**']
|
||||
pull_request:
|
||||
branches: [main, develop]
|
||||
|
||||
env:
|
||||
NODE_VERSION: '20'
|
||||
POSTGRES_USER: test_user
|
||||
POSTGRES_PASSWORD: test_password
|
||||
POSTGRES_DB: test_db
|
||||
|
||||
jobs:
|
||||
# ===========================================================================
|
||||
# Lint & Type Check
|
||||
# ===========================================================================
|
||||
lint:
|
||||
name: Lint & Type Check
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: ${{ env.NODE_VERSION }}
|
||||
cache: 'npm'
|
||||
cache-dependency-path: |
|
||||
backend/package-lock.json
|
||||
frontend/web/package-lock.json
|
||||
|
||||
- name: Install Backend Dependencies
|
||||
working-directory: backend
|
||||
run: npm ci
|
||||
|
||||
- name: Install Frontend Dependencies
|
||||
working-directory: frontend/web
|
||||
run: npm ci
|
||||
|
||||
- name: Lint Backend
|
||||
working-directory: backend
|
||||
run: npm run lint
|
||||
|
||||
- name: Lint Frontend
|
||||
working-directory: frontend/web
|
||||
run: npm run lint || true
|
||||
|
||||
- name: Type Check Backend
|
||||
working-directory: backend
|
||||
run: npm run build -- --noEmit || npm run build
|
||||
|
||||
# ===========================================================================
|
||||
# Validate Constants (SSOT)
|
||||
# ===========================================================================
|
||||
validate-constants:
|
||||
name: Validate SSOT Constants
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: ${{ env.NODE_VERSION }}
|
||||
cache: 'npm'
|
||||
cache-dependency-path: backend/package-lock.json
|
||||
|
||||
- name: Install Dependencies
|
||||
working-directory: backend
|
||||
run: npm ci
|
||||
|
||||
- name: Run Constants Validation
|
||||
working-directory: backend
|
||||
run: npm run validate:constants || echo "Validation script not yet implemented"
|
||||
|
||||
# ===========================================================================
|
||||
# Unit Tests - Backend
|
||||
# ===========================================================================
|
||||
test-backend:
|
||||
name: Backend Tests
|
||||
runs-on: ubuntu-latest
|
||||
needs: [lint]
|
||||
services:
|
||||
postgres:
|
||||
image: postgis/postgis:15-3.3-alpine
|
||||
env:
|
||||
POSTGRES_USER: ${{ env.POSTGRES_USER }}
|
||||
POSTGRES_PASSWORD: ${{ env.POSTGRES_PASSWORD }}
|
||||
POSTGRES_DB: ${{ env.POSTGRES_DB }}
|
||||
ports:
|
||||
- 5432:5432
|
||||
options: >-
|
||||
--health-cmd pg_isready
|
||||
--health-interval 10s
|
||||
--health-timeout 5s
|
||||
--health-retries 5
|
||||
|
||||
redis:
|
||||
image: redis:7-alpine
|
||||
ports:
|
||||
- 6379:6379
|
||||
options: >-
|
||||
--health-cmd "redis-cli ping"
|
||||
--health-interval 10s
|
||||
--health-timeout 5s
|
||||
--health-retries 5
|
||||
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: ${{ env.NODE_VERSION }}
|
||||
cache: 'npm'
|
||||
cache-dependency-path: backend/package-lock.json
|
||||
|
||||
- name: Install Dependencies
|
||||
working-directory: backend
|
||||
run: npm ci
|
||||
|
||||
- name: Run Unit Tests
|
||||
working-directory: backend
|
||||
run: npm run test -- --coverage --passWithNoTests
|
||||
env:
|
||||
DB_HOST: localhost
|
||||
DB_PORT: 5432
|
||||
DB_USER: ${{ env.POSTGRES_USER }}
|
||||
DB_PASSWORD: ${{ env.POSTGRES_PASSWORD }}
|
||||
DB_NAME: ${{ env.POSTGRES_DB }}
|
||||
REDIS_HOST: localhost
|
||||
REDIS_PORT: 6379
|
||||
|
||||
- name: Upload Coverage Report
|
||||
uses: codecov/codecov-action@v3
|
||||
if: always()
|
||||
with:
|
||||
files: backend/coverage/lcov.info
|
||||
flags: backend
|
||||
fail_ci_if_error: false
|
||||
|
||||
# ===========================================================================
|
||||
# Unit Tests - Frontend
|
||||
# ===========================================================================
|
||||
test-frontend:
|
||||
name: Frontend Tests
|
||||
runs-on: ubuntu-latest
|
||||
needs: [lint]
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: ${{ env.NODE_VERSION }}
|
||||
cache: 'npm'
|
||||
cache-dependency-path: frontend/web/package-lock.json
|
||||
|
||||
- name: Install Dependencies
|
||||
working-directory: frontend/web
|
||||
run: npm ci
|
||||
|
||||
- name: Run Unit Tests
|
||||
working-directory: frontend/web
|
||||
run: npm run test -- --coverage --passWithNoTests || echo "No tests yet"
|
||||
|
||||
- name: Upload Coverage Report
|
||||
uses: codecov/codecov-action@v3
|
||||
if: always()
|
||||
with:
|
||||
files: frontend/web/coverage/lcov.info
|
||||
flags: frontend
|
||||
fail_ci_if_error: false
|
||||
|
||||
# ===========================================================================
|
||||
# Build Check
|
||||
# ===========================================================================
|
||||
build:
|
||||
name: Build
|
||||
runs-on: ubuntu-latest
|
||||
needs: [test-backend, test-frontend]
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: ${{ env.NODE_VERSION }}
|
||||
|
||||
- name: Build Backend
|
||||
working-directory: backend
|
||||
run: |
|
||||
npm ci
|
||||
npm run build
|
||||
|
||||
- name: Build Frontend
|
||||
working-directory: frontend/web
|
||||
run: |
|
||||
npm ci
|
||||
npm run build
|
||||
|
||||
- name: Upload Backend Artifacts
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: backend-dist
|
||||
path: backend/dist
|
||||
retention-days: 7
|
||||
|
||||
- name: Upload Frontend Artifacts
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: frontend-dist
|
||||
path: frontend/web/dist
|
||||
retention-days: 7
|
||||
|
||||
# ===========================================================================
|
||||
# Docker Build (only on main/develop)
|
||||
# ===========================================================================
|
||||
docker:
|
||||
name: Docker Build
|
||||
runs-on: ubuntu-latest
|
||||
needs: [build]
|
||||
if: github.ref == 'refs/heads/main' || github.ref == 'refs/heads/develop'
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v3
|
||||
|
||||
- name: Build Backend Docker Image
|
||||
uses: docker/build-push-action@v5
|
||||
with:
|
||||
context: ./backend
|
||||
file: ./backend/Dockerfile
|
||||
target: production
|
||||
push: false
|
||||
tags: construccion-backend:${{ github.sha }}
|
||||
cache-from: type=gha
|
||||
cache-to: type=gha,mode=max
|
||||
|
||||
- name: Build Frontend Docker Image
|
||||
uses: docker/build-push-action@v5
|
||||
with:
|
||||
context: ./frontend/web
|
||||
file: ./frontend/web/Dockerfile
|
||||
target: production
|
||||
push: false
|
||||
tags: construccion-frontend:${{ github.sha }}
|
||||
cache-from: type=gha
|
||||
cache-to: type=gha,mode=max
|
||||
478
CONTRIBUTING.md
Normal file
478
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
|
||||
31
INVENTARIO.yml
Normal file
31
INVENTARIO.yml
Normal file
@ -0,0 +1,31 @@
|
||||
# Inventario generado por EPIC-008
|
||||
proyecto: erp-construccion
|
||||
fecha: "2026-01-04"
|
||||
generado_por: "inventory-project.sh v1.0.0"
|
||||
|
||||
inventario:
|
||||
docs:
|
||||
total: 496
|
||||
por_tipo:
|
||||
markdown: 453
|
||||
yaml: 32
|
||||
json: 0
|
||||
orchestration:
|
||||
total: 31
|
||||
por_tipo:
|
||||
markdown: 21
|
||||
yaml: 9
|
||||
json: 1
|
||||
|
||||
problemas:
|
||||
archivos_obsoletos: 0
|
||||
referencias_antiguas: 0
|
||||
simco_faltantes:
|
||||
- _MAP.md en docs/
|
||||
- PROJECT-STATUS.md
|
||||
|
||||
estado_simco:
|
||||
herencia_simco: true
|
||||
contexto_proyecto: true
|
||||
map_docs: false
|
||||
project_status: false
|
||||
310
PROJECT-STATUS.md
Normal file
310
PROJECT-STATUS.md
Normal file
@ -0,0 +1,310 @@
|
||||
# ESTADO DEL PROYECTO - ERP Construccion
|
||||
|
||||
**Proyecto:** ERP Construccion (Proyecto Independiente)
|
||||
**Estado:** 🚧 En desarrollo
|
||||
**Progreso:** 60%
|
||||
**Ultima actualizacion:** 2025-12-12
|
||||
|
||||
---
|
||||
|
||||
## 🆕 CAMBIOS RECIENTES (2025-12-12)
|
||||
|
||||
### 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
|
||||
- `Presupuesto` entity - Presupuestos versionados
|
||||
- `PresupuestoPartida` entity - Líneas de presupuesto
|
||||
- `ConceptoService` - Árbol de conceptos, búsqueda
|
||||
- `PresupuestoService` - CRUD, versionamiento, aprobación
|
||||
|
||||
- ✅ **MAI-005 Control de Obra - Entidades y Services**
|
||||
- `AvanceObra` entity - Avances físicos
|
||||
- `FotoAvance` entity - Evidencias fotográficas con GPS
|
||||
- `BitacoraObra` entity - Bitácora diaria
|
||||
- `ProgramaObra` entity - Programa maestro
|
||||
- `ProgramaActividad` entity - WBS/Actividades
|
||||
- `AvanceObraService` - Workflow captura→revisión→aprobación
|
||||
- `BitacoraObraService` - Entradas secuenciales, estadísticas
|
||||
|
||||
- ✅ **MAI-008 Estimaciones - Entidades y Services**
|
||||
- `Estimacion` entity - Estimaciones periódicas
|
||||
- `EstimacionConcepto` entity - Líneas con acumulados
|
||||
- `Generador` entity - Números generadores
|
||||
- `Anticipo` entity - Anticipos con amortización
|
||||
- `Amortizacion` entity - Descuentos por estimación
|
||||
- `Retencion` entity - Fondo de garantía, impuestos
|
||||
- `FondoGarantia` entity - Acumulado por contrato
|
||||
- `EstimacionWorkflow` entity - Historial de estados
|
||||
- `EstimacionService` - Workflow completo, cálculo de totales
|
||||
|
||||
- ✅ **Módulo Auth - JWT + Refresh Tokens**
|
||||
- `AuthService` - Login, register, refresh, logout
|
||||
- `AuthMiddleware` - Autenticación, autorización por roles
|
||||
- DTOs tipados para todas las operaciones
|
||||
- Configuración de RLS con tenant_id
|
||||
|
||||
- ✅ **Base Service Pattern**
|
||||
- `BaseService<T>` - CRUD multi-tenant genérico
|
||||
- Paginación con metadata
|
||||
- Soft delete con audit columns
|
||||
- Contexto de servicio (tenantId, userId)
|
||||
|
||||
### Fase 1: Fundamentos Arquitectonicos - COMPLETADA
|
||||
|
||||
- ✅ **Sistema SSOT implementado**
|
||||
- `database.constants.ts` - Schemas, tablas, columnas
|
||||
- `api.constants.ts` - Rutas API centralizadas
|
||||
- `enums.constants.ts` - Todos los enums del sistema
|
||||
- Index actualizado con exports centralizados
|
||||
|
||||
- ✅ **Path Aliases configurados** (ya existian)
|
||||
- `@shared/*`, `@modules/*`, `@config/*`, `@types/*`, `@utils/*`
|
||||
|
||||
- ✅ **Docker + docker-compose**
|
||||
- PostgreSQL 15 + PostGIS
|
||||
- Redis 7
|
||||
- Backend Node.js
|
||||
- Frontend React + Vite
|
||||
- Adminer (dev)
|
||||
- Mailhog (dev)
|
||||
|
||||
- ✅ **CI/CD GitHub Actions**
|
||||
- Lint & Type Check
|
||||
- Validate SSOT Constants
|
||||
- Unit Tests (Backend + Frontend)
|
||||
- Build Check
|
||||
- Docker Build
|
||||
|
||||
- ✅ **Scripts de validacion**
|
||||
- `validate-constants-usage.ts` - Detecta hardcoding
|
||||
- `sync-enums.ts` - Sincroniza Backend → Frontend
|
||||
|
||||
- ✅ **Documentacion de DB**
|
||||
- `database/_MAP.md` - Mapa completo de schemas
|
||||
|
||||
---
|
||||
|
||||
## 📊 RESUMEN EJECUTIVO
|
||||
|
||||
| Área | Implementado | Documentado | Estado |
|
||||
|------|-------------|-------------|--------|
|
||||
| **DDL/Schemas** | 7 schemas, 110 tablas | 7 schemas, 110 tablas | 100% |
|
||||
| **Backend** | 7 módulos, 30 entidades, 8 services | 18 módulos | 45% |
|
||||
| **Frontend** | Estructura base | 18 módulos | 5% |
|
||||
| **Documentación Técnica** | OpenAPI, ARCHITECTURE, CONTRIBUTING | API, Módulos, Arquitectura | 100% |
|
||||
| **Documentación Funcional** | - | 449+ archivos MD | 100% |
|
||||
|
||||
---
|
||||
|
||||
## 🗄️ BASE DE DATOS
|
||||
|
||||
### Schemas Implementados (DDL)
|
||||
| Schema | Tablas | ENUMs | Archivo DDL |
|
||||
|--------|--------|-------|-------------|
|
||||
| `construction` | 24 | 7 | `01-construction-schema-ddl.sql` |
|
||||
| `hr` | 8 | - | `02-hr-schema-ddl.sql` |
|
||||
| `hse` | 58 | 67 | `03-hse-schema-ddl.sql` |
|
||||
| `estimates` | 8 | 4 | `04-estimates-schema-ddl.sql` |
|
||||
| `infonavit` | 8 | - | `05-infonavit-schema-ddl.sql` |
|
||||
| `inventory` | 4 | - | `06-inventory-ext-schema-ddl.sql` |
|
||||
| `purchase` | 5 | - | `07-purchase-ext-schema-ddl.sql` |
|
||||
| **Total** | **110** | **78** | |
|
||||
|
||||
### DDL Completo
|
||||
Todos los schemas han sido implementados con:
|
||||
- RLS (Row Level Security) para multi-tenancy
|
||||
- Indices optimizados
|
||||
- Funciones auxiliares (ej: `calculate_estimate_totals`)
|
||||
|
||||
---
|
||||
|
||||
## 💻 BACKEND
|
||||
|
||||
### Módulos con Código
|
||||
```
|
||||
backend/src/modules/
|
||||
├── auth/ ✅ Autenticación JWT completa
|
||||
│ ├── services/auth.service.ts
|
||||
│ ├── middleware/auth.middleware.ts
|
||||
│ └── dto/auth.dto.ts
|
||||
├── budgets/ ✅ Presupuestos (MAI-003)
|
||||
│ ├── entities/concepto.entity.ts
|
||||
│ ├── entities/presupuesto.entity.ts
|
||||
│ ├── entities/presupuesto-partida.entity.ts
|
||||
│ ├── services/concepto.service.ts
|
||||
│ └── services/presupuesto.service.ts
|
||||
├── progress/ ✅ Control de Obra (MAI-005)
|
||||
│ ├── entities/avance-obra.entity.ts
|
||||
│ ├── entities/foto-avance.entity.ts
|
||||
│ ├── entities/bitacora-obra.entity.ts
|
||||
│ ├── entities/programa-obra.entity.ts
|
||||
│ ├── entities/programa-actividad.entity.ts
|
||||
│ ├── services/avance-obra.service.ts
|
||||
│ └── services/bitacora-obra.service.ts
|
||||
├── estimates/ ✅ Estimaciones (MAI-008)
|
||||
│ ├── entities/estimacion.entity.ts
|
||||
│ ├── entities/estimacion-concepto.entity.ts
|
||||
│ ├── entities/generador.entity.ts
|
||||
│ ├── entities/anticipo.entity.ts
|
||||
│ ├── entities/amortizacion.entity.ts
|
||||
│ ├── entities/retencion.entity.ts
|
||||
│ ├── entities/fondo-garantia.entity.ts
|
||||
│ ├── entities/estimacion-workflow.entity.ts
|
||||
│ └── services/estimacion.service.ts
|
||||
├── construction/ ✅ Proyectos (MAI-002)
|
||||
│ ├── entities/proyecto.entity.ts
|
||||
│ └── entities/fraccionamiento.entity.ts
|
||||
├── hr/ ✅ RRHH (MAI-007)
|
||||
│ ├── entities/employee.entity.ts
|
||||
│ ├── entities/puesto.entity.ts
|
||||
│ └── entities/employee-fraccionamiento.entity.ts
|
||||
├── hse/ ✅ HSE (MAA-017)
|
||||
│ ├── entities/incidente.entity.ts
|
||||
│ ├── entities/incidente-involucrado.entity.ts
|
||||
│ ├── entities/incidente-accion.entity.ts
|
||||
│ └── entities/capacitacion.entity.ts
|
||||
├── core/ ✅ Base multi-tenant
|
||||
│ ├── entities/user.entity.ts
|
||||
│ └── entities/tenant.entity.ts
|
||||
└── shared/ ✅ Servicios compartidos
|
||||
└── services/base.service.ts
|
||||
```
|
||||
|
||||
### Pendientes
|
||||
- Controllers REST para módulos nuevos
|
||||
- 8 módulos MAI sin código backend
|
||||
- 3 módulos MAE sin código backend
|
||||
- Frontend integración con API
|
||||
|
||||
---
|
||||
|
||||
## 📋 MÓDULOS (18 Total)
|
||||
|
||||
### Fase 1 - MAI (14 módulos)
|
||||
| Código | Nombre | DDL | Backend | Docs |
|
||||
|--------|--------|:---:|:-------:|:----:|
|
||||
| MAI-001 | Fundamentos | - | ✅ | ✅ |
|
||||
| MAI-002 | Proyectos y Estructura | ✅ | ✅ | ✅ |
|
||||
| MAI-003 | Presupuestos y Costos | ✅ | ✅ | ✅ |
|
||||
| MAI-004 | Compras e Inventarios | ✅ | ⏳ | ✅ |
|
||||
| MAI-005 | Control de Obra | ✅ | ✅ | ✅ |
|
||||
| MAI-006 | Reportes y Analytics | - | ❌ | ✅ |
|
||||
| MAI-007 | RRHH y Asistencias | ✅ | ✅ | ✅ |
|
||||
| MAI-008 | Estimaciones | ✅ | ✅ | ✅ |
|
||||
| MAI-009 | Calidad y Postventa | ✅ | ⏳ | ✅ |
|
||||
| MAI-010 | CRM Derechohabientes | ⏳ | ❌ | ✅ |
|
||||
| MAI-011 | INFONAVIT | ✅ | ⏳ | ✅ |
|
||||
| MAI-012 | Contratos | ✅ | ⏳ | ✅ |
|
||||
| MAI-013 | Administración | - | ❌ | ✅ |
|
||||
| MAI-018 | Preconstrucción | ⏳ | ❌ | ✅ |
|
||||
|
||||
### Fase 2 - MAE (3 módulos)
|
||||
| Código | Nombre | DDL | Backend | Docs |
|
||||
|--------|--------|:---:|:-------:|:----:|
|
||||
| MAE-014 | Finanzas y Controlling | ⏳ | ❌ | ✅ |
|
||||
| MAE-015 | Activos y Maquinaria | ⏳ | ❌ | ✅ |
|
||||
| MAE-016 | Gestión Documental | ⏳ | ❌ | ✅ |
|
||||
|
||||
### Fase 3 - MAA (1 módulo)
|
||||
| Código | Nombre | DDL | Backend | Docs |
|
||||
|--------|--------|:---:|:-------:|:----:|
|
||||
| MAA-017 | Seguridad HSE | ✅ | ✅ | ✅ |
|
||||
|
||||
**Leyenda:** ✅ Implementado | ⏳ En progreso | ❌ No iniciado | - No aplica
|
||||
|
||||
---
|
||||
|
||||
## 🎯 PRÓXIMOS PASOS
|
||||
|
||||
### Inmediato
|
||||
1. ✅ ~~Implementar DDL de `estimates` schema~~ - COMPLETADO
|
||||
2. ✅ ~~Implementar DDL de `infonavit` schema~~ - COMPLETADO
|
||||
3. ✅ ~~Backend MAI-003 Presupuestos~~ - COMPLETADO
|
||||
4. ✅ ~~Backend MAI-005 Control de Obra~~ - COMPLETADO
|
||||
5. ✅ ~~Backend MAI-008 Estimaciones~~ - COMPLETADO
|
||||
6. ✅ ~~Módulo Auth JWT completo~~ - COMPLETADO
|
||||
|
||||
### Corto Plazo
|
||||
1. Crear Controllers REST para módulos nuevos
|
||||
2. Implementar backend de MAI-009 (Calidad y Postventa)
|
||||
3. Implementar backend de MAI-011 (INFONAVIT)
|
||||
4. Implementar backend de MAI-012 (Contratos)
|
||||
5. Testing de módulos existentes
|
||||
|
||||
### Mediano Plazo
|
||||
6. Frontend: Integración con API
|
||||
7. Frontend: Módulos de Presupuestos y Estimaciones
|
||||
8. Implementar Curva S y reportes de avance
|
||||
|
||||
---
|
||||
|
||||
## 📁 ARCHIVOS CLAVE
|
||||
|
||||
### Codigo
|
||||
- **DDL:** `database/schemas/*.sql`
|
||||
- **Backend:** `backend/src/modules/`
|
||||
- **Services:** `backend/src/shared/services/base.service.ts`
|
||||
- **Auth:** `backend/src/modules/auth/`
|
||||
- **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 Funcionales | 449+ |
|
||||
| Archivos MD Tecnicos | 5 (ARCHITECTURE, CONTRIBUTING, API-REF, MODULES, openapi) |
|
||||
| Requerimientos (RF) | 87 |
|
||||
| Especificaciones (ET) | 78 |
|
||||
| User Stories | 149 |
|
||||
| Story Points | 692 |
|
||||
| ADRs | 12 |
|
||||
| Entidades TypeORM | 30 |
|
||||
| Services Backend | 8 |
|
||||
| Tablas DDL | 110 |
|
||||
| Endpoints API Documentados | 35+ |
|
||||
|
||||
---
|
||||
|
||||
**Última actualización:** 2025-12-12
|
||||
364
README.md
Normal file
364
README.md
Normal file
@ -0,0 +1,364 @@
|
||||
# ERP Construccion - Sistema de Administracion de Obra e INFONAVIT
|
||||
|
||||
Sistema ERP especializado para empresas de construccion de vivienda con integracion INFONAVIT. Arquitectura multi-tenant SaaS con Row Level Security (RLS).
|
||||
|
||||
## Estado del Proyecto
|
||||
|
||||
| Campo | Valor |
|
||||
|-------|-------|
|
||||
| **Estado** | 🚧 En desarrollo (55%) |
|
||||
| **Version** | 0.2.0 |
|
||||
| **Modulos** | 18 (14 MAI + 3 MAE + 1 MAA) |
|
||||
| **DDL Schemas** | 7 (110 tablas) |
|
||||
| **Entidades Backend** | 30 |
|
||||
| **Services Backend** | 8 |
|
||||
|
||||
---
|
||||
|
||||
## Quick Start
|
||||
|
||||
### Prerequisitos
|
||||
|
||||
- Node.js >= 18.0.0
|
||||
- Docker & Docker Compose
|
||||
- PostgreSQL 15+ con PostGIS (incluido en docker-compose)
|
||||
|
||||
### Instalacion
|
||||
|
||||
```bash
|
||||
# Clonar repositorio
|
||||
cd apps/verticales/construccion
|
||||
|
||||
# Copiar variables de entorno
|
||||
cp .env.example .env
|
||||
|
||||
# Levantar servicios con Docker
|
||||
docker-compose up -d
|
||||
|
||||
# O desarrollo local
|
||||
cd backend && npm install && npm run dev
|
||||
```
|
||||
|
||||
### URLs de Desarrollo
|
||||
|
||||
| Servicio | URL |
|
||||
|----------|-----|
|
||||
| Backend API | http://localhost:3000 |
|
||||
| Frontend | http://localhost:5173 |
|
||||
| Adminer (DB) | http://localhost:8080 |
|
||||
| Mailhog | http://localhost:8025 |
|
||||
|
||||
---
|
||||
|
||||
## Arquitectura
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────┐
|
||||
│ Frontend (React) │
|
||||
│ ┌─────────┐ ┌─────────┐ ┌─────────┐ ┌─────────┐ │
|
||||
│ │ Budgets │ │Progress │ │Estimates│ │ HSE │ │
|
||||
│ └────┬────┘ └────┬────┘ └────┬────┘ └────┬────┘ │
|
||||
└───────┼────────────┼────────────┼────────────┼──────────┘
|
||||
│ │ │ │
|
||||
┌───────▼────────────▼────────────▼────────────▼──────────┐
|
||||
│ Backend (Express + TypeORM) │
|
||||
│ ┌──────────────────────────────────────────────────┐ │
|
||||
│ │ Auth Middleware (JWT + RLS) │ │
|
||||
│ └──────────────────────────────────────────────────┘ │
|
||||
│ ┌─────────┐ ┌─────────┐ ┌─────────┐ ┌─────────┐ │
|
||||
│ │ Budgets │ │Progress │ │Estimates│ │ Auth │ │
|
||||
│ │ Service │ │ Service │ │ Service │ │ Service │ │
|
||||
│ └────┬────┘ └────┬────┘ └────┬────┘ └────┬────┘ │
|
||||
└───────┼────────────┼────────────┼────────────┼──────────┘
|
||||
│ │ │ │
|
||||
┌───────▼────────────▼────────────▼────────────▼──────────┐
|
||||
│ PostgreSQL 15 + PostGIS │
|
||||
│ ┌──────────────────────────────────────────────────┐ │
|
||||
│ │ Row Level Security (tenant_id) │ │
|
||||
│ └──────────────────────────────────────────────────┘ │
|
||||
│ ┌────────┐ ┌────────┐ ┌────────┐ ┌────────┐ ┌──────┐ │
|
||||
│ │ auth │ │constru.│ │ hr │ │ hse │ │estim.│ │
|
||||
│ └────────┘ └────────┘ └────────┘ └────────┘ └──────┘ │
|
||||
└─────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
### Stack Tecnologico
|
||||
|
||||
| Capa | Tecnologia |
|
||||
|------|------------|
|
||||
| **Backend** | Node.js 20, Express 4, TypeORM 0.3 |
|
||||
| **Frontend** | React 18, Vite 5, TypeScript 5 |
|
||||
| **Database** | PostgreSQL 15 + PostGIS |
|
||||
| **Cache** | Redis 7 |
|
||||
| **Auth** | JWT + Refresh Tokens |
|
||||
| **Multi-tenant** | RLS (Row Level Security) |
|
||||
|
||||
---
|
||||
|
||||
## Estructura del Proyecto
|
||||
|
||||
```
|
||||
construccion/
|
||||
├── backend/
|
||||
│ └── src/
|
||||
│ ├── modules/
|
||||
│ │ ├── 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/ # Entidades base
|
||||
│ └── shared/
|
||||
│ ├── constants/ # SSOT (schemas, rutas, enums)
|
||||
│ └── services/ # BaseService multi-tenant
|
||||
├── frontend/
|
||||
│ ├── web/ # App web React
|
||||
│ └── mobile/ # App movil (futuro)
|
||||
├── database/
|
||||
│ └── schemas/ # DDL por schema
|
||||
│ ├── 01-construction-schema-ddl.sql # 24 tablas
|
||||
│ ├── 02-hr-schema-ddl.sql # 8 tablas
|
||||
│ ├── 03-hse-schema-ddl.sql # 58 tablas
|
||||
│ ├── 04-estimates-schema-ddl.sql # 8 tablas
|
||||
│ ├── 05-infonavit-schema-ddl.sql # 8 tablas
|
||||
│ ├── 06-inventory-ext-schema-ddl.sql # 4 tablas
|
||||
│ └── 07-purchase-ext-schema-ddl.sql # 5 tablas
|
||||
├── docs/ # Documentacion completa
|
||||
├── devops/
|
||||
│ └── scripts/ # Validacion SSOT
|
||||
├── docker-compose.yml
|
||||
└── .env.example
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Modulos Implementados
|
||||
|
||||
### MAI-003: Presupuestos y Costos ✅
|
||||
|
||||
```typescript
|
||||
// Entidades
|
||||
- Concepto // Catalogo jerarquico de conceptos
|
||||
- Presupuesto // Presupuestos versionados
|
||||
- PresupuestoPartida // Lineas con calculo automatico
|
||||
|
||||
// Services
|
||||
- ConceptoService // Arbol, busqueda
|
||||
- PresupuestoService // CRUD, versionamiento, aprobacion
|
||||
```
|
||||
|
||||
### MAI-005: Control de Obra ✅
|
||||
|
||||
```typescript
|
||||
// Entidades
|
||||
- AvanceObra // Avances fisicos con workflow
|
||||
- FotoAvance // Evidencias con GPS
|
||||
- BitacoraObra // Bitacora diaria
|
||||
- ProgramaObra // Programa maestro
|
||||
- ProgramaActividad // WBS/Actividades
|
||||
|
||||
// Services
|
||||
- AvanceObraService // Workflow captura->revision->aprobacion
|
||||
- BitacoraObraService // Entradas secuenciales
|
||||
```
|
||||
|
||||
### MAI-008: Estimaciones ✅
|
||||
|
||||
```typescript
|
||||
// Entidades
|
||||
- Estimacion // Estimaciones periodicas
|
||||
- EstimacionConcepto // Lineas con acumulados
|
||||
- Generador // Numeros generadores
|
||||
- Anticipo // Anticipos con amortizacion
|
||||
- Amortizacion // Descuentos por estimacion
|
||||
- Retencion // Fondo de garantia
|
||||
- FondoGarantia // Acumulado por contrato
|
||||
- EstimacionWorkflow // Historial de estados
|
||||
|
||||
// Services
|
||||
- EstimacionService // Workflow completo, calculo totales
|
||||
```
|
||||
|
||||
### Auth: Autenticacion JWT ✅
|
||||
|
||||
```typescript
|
||||
// Funcionalidades
|
||||
- Login con email/password
|
||||
- Registro de usuarios
|
||||
- Refresh tokens
|
||||
- Logout (revocacion)
|
||||
- Middleware de autorizacion por roles
|
||||
- Configuracion RLS por tenant
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## API Endpoints
|
||||
|
||||
### Autenticacion
|
||||
|
||||
```http
|
||||
POST /api/v1/auth/login
|
||||
POST /api/v1/auth/register
|
||||
POST /api/v1/auth/refresh
|
||||
POST /api/v1/auth/logout
|
||||
POST /api/v1/auth/change-password
|
||||
```
|
||||
|
||||
### Presupuestos
|
||||
|
||||
```http
|
||||
GET /api/v1/conceptos
|
||||
GET /api/v1/conceptos/:id
|
||||
POST /api/v1/conceptos
|
||||
GET /api/v1/conceptos/tree
|
||||
|
||||
GET /api/v1/presupuestos
|
||||
GET /api/v1/presupuestos/:id
|
||||
POST /api/v1/presupuestos
|
||||
POST /api/v1/presupuestos/:id/partidas
|
||||
POST /api/v1/presupuestos/:id/approve
|
||||
POST /api/v1/presupuestos/:id/version
|
||||
```
|
||||
|
||||
### Control de Obra
|
||||
|
||||
```http
|
||||
GET /api/v1/avances
|
||||
GET /api/v1/avances/:id
|
||||
POST /api/v1/avances
|
||||
POST /api/v1/avances/:id/fotos
|
||||
POST /api/v1/avances/:id/review
|
||||
POST /api/v1/avances/:id/approve
|
||||
|
||||
GET /api/v1/bitacora/:fraccionamientoId
|
||||
POST /api/v1/bitacora
|
||||
```
|
||||
|
||||
### Estimaciones
|
||||
|
||||
```http
|
||||
GET /api/v1/estimaciones
|
||||
GET /api/v1/estimaciones/:id
|
||||
POST /api/v1/estimaciones
|
||||
POST /api/v1/estimaciones/:id/conceptos
|
||||
POST /api/v1/estimaciones/:id/submit
|
||||
POST /api/v1/estimaciones/:id/review
|
||||
POST /api/v1/estimaciones/:id/approve
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Base de Datos
|
||||
|
||||
### Schemas (7 total, 110 tablas)
|
||||
|
||||
| Schema | Tablas | Descripcion |
|
||||
|--------|--------|-------------|
|
||||
| `auth` | 10 | Usuarios, roles, permisos, tenants |
|
||||
| `construction` | 24 | Proyectos, lotes, presupuestos, avances |
|
||||
| `hr` | 8 | Empleados, asistencias, cuadrillas |
|
||||
| `hse` | 58 | Incidentes, capacitaciones, EPP, STPS |
|
||||
| `estimates` | 8 | Estimaciones, anticipos, retenciones |
|
||||
| `infonavit` | 8 | Registro RUV, derechohabientes |
|
||||
| `inventory` | 4 | Almacenes, requisiciones |
|
||||
|
||||
### Row Level Security
|
||||
|
||||
```sql
|
||||
-- Todas las tablas tienen RLS activado
|
||||
ALTER TABLE construction.fraccionamientos ENABLE ROW LEVEL SECURITY;
|
||||
|
||||
-- Politica de aislamiento por tenant
|
||||
CREATE POLICY tenant_isolation ON construction.fraccionamientos
|
||||
FOR ALL USING (tenant_id = current_setting('app.current_tenant_id')::UUID);
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Scripts NPM
|
||||
|
||||
```bash
|
||||
# Backend
|
||||
npm run dev # Desarrollo con hot-reload
|
||||
npm run build # Compilar TypeScript
|
||||
npm run start # Produccion
|
||||
npm run lint # ESLint
|
||||
npm run test # Jest tests
|
||||
npm run validate:constants # Validar SSOT
|
||||
npm run sync:enums # Sincronizar enums a frontend
|
||||
|
||||
# Docker
|
||||
docker-compose up -d # Levantar servicios
|
||||
docker-compose --profile dev up # Con Adminer y Mailhog
|
||||
docker-compose down # Detener servicios
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Variables de Entorno
|
||||
|
||||
```bash
|
||||
# Application
|
||||
NODE_ENV=development
|
||||
APP_PORT=3000
|
||||
|
||||
# Database
|
||||
DB_HOST=localhost
|
||||
DB_PORT=5432
|
||||
DB_USER=construccion
|
||||
DB_PASSWORD=construccion_dev_2024
|
||||
DB_NAME=erp_construccion
|
||||
|
||||
# JWT
|
||||
JWT_SECRET=your-secret-key-min-32-chars
|
||||
JWT_EXPIRES_IN=1d
|
||||
JWT_REFRESH_EXPIRES_IN=7d
|
||||
|
||||
# Redis
|
||||
REDIS_HOST=localhost
|
||||
REDIS_PORT=6379
|
||||
```
|
||||
|
||||
Ver `.env.example` para la lista completa.
|
||||
|
||||
---
|
||||
|
||||
## Documentacion
|
||||
|
||||
| Documento | Ubicacion |
|
||||
|-----------|-----------|
|
||||
| Estado del Proyecto | `PROJECT-STATUS.md` |
|
||||
| Mapa de Base de Datos | `database/_MAP.md` |
|
||||
| Constantes SSOT | `backend/src/shared/constants/` |
|
||||
| Modulos (18) | `docs/02-definicion-modulos/` |
|
||||
| Requerimientos (87 RF) | `docs/03-requerimientos/` |
|
||||
| User Stories (149 US) | `docs/05-user-stories/` |
|
||||
| ADRs (12) | `docs/97-adr/` |
|
||||
|
||||
---
|
||||
|
||||
## Proximos Pasos
|
||||
|
||||
1. **Corto Plazo**
|
||||
- Controllers REST para modulos nuevos
|
||||
- Backend MAI-009 (Calidad y Postventa)
|
||||
- Backend MAI-011 (INFONAVIT)
|
||||
- Testing de modulos existentes
|
||||
|
||||
2. **Mediano Plazo**
|
||||
- Frontend: Integracion con API
|
||||
- Modulos de Presupuestos y Estimaciones
|
||||
- Curva S y reportes de avance
|
||||
|
||||
---
|
||||
|
||||
## Licencia
|
||||
|
||||
UNLICENSED - Proyecto privado
|
||||
|
||||
---
|
||||
|
||||
**Ultima actualizacion:** 2025-12-12
|
||||
71
backend/.env.example
Normal file
71
backend/.env.example
Normal file
@ -0,0 +1,71 @@
|
||||
# ============================================================================
|
||||
# BACKEND ENVIRONMENT VARIABLES - ERP Construccion
|
||||
# ============================================================================
|
||||
# Proyecto: construccion
|
||||
# Rango de puertos: 3100 (ver DEVENV-PORTS.md)
|
||||
# Fecha: 2025-12-06
|
||||
# ============================================================================
|
||||
|
||||
# Application
|
||||
NODE_ENV=development
|
||||
APP_PORT=3021
|
||||
APP_HOST=0.0.0.0
|
||||
API_VERSION=v1
|
||||
API_PREFIX=/api/v1
|
||||
|
||||
# Database (Puerto 5433 - diferenciado de erp-core:5432)
|
||||
DATABASE_URL=postgresql://erp_user:erp_dev_password@localhost:5433/erp_construccion
|
||||
DB_HOST=localhost
|
||||
DB_PORT=5433
|
||||
DB_NAME=erp_construccion
|
||||
DB_USER=erp_user
|
||||
DB_PASSWORD=erp_dev_password
|
||||
DB_SYNCHRONIZE=false
|
||||
DB_LOGGING=true
|
||||
|
||||
# Redis (Puerto 6380 - diferenciado de erp-core:6379)
|
||||
REDIS_HOST=localhost
|
||||
REDIS_PORT=6380
|
||||
REDIS_URL=redis://localhost:6380
|
||||
|
||||
# MinIO S3 (Puerto 9100 - diferenciado de erp-core:9000)
|
||||
S3_ENDPOINT=http://localhost:9100
|
||||
S3_ACCESS_KEY=minioadmin
|
||||
S3_SECRET_KEY=minioadmin
|
||||
S3_BUCKET=erp-construccion
|
||||
|
||||
# JWT
|
||||
JWT_SECRET=your-super-secret-jwt-key-change-this-in-production
|
||||
JWT_EXPIRATION=24h
|
||||
JWT_REFRESH_EXPIRATION=7d
|
||||
|
||||
# CORS (Frontend en puerto 5174)
|
||||
CORS_ORIGIN=http://localhost:3020,http://localhost:5174
|
||||
CORS_CREDENTIALS=true
|
||||
|
||||
# Rate Limiting
|
||||
RATE_LIMIT_WINDOW_MS=900000
|
||||
RATE_LIMIT_MAX_REQUESTS=100
|
||||
|
||||
# Logging
|
||||
LOG_LEVEL=debug
|
||||
LOG_FORMAT=dev
|
||||
|
||||
# File Upload
|
||||
MAX_FILE_SIZE=10485760
|
||||
UPLOAD_DIR=./uploads
|
||||
|
||||
# Email (opcional)
|
||||
SMTP_HOST=smtp.gmail.com
|
||||
SMTP_PORT=587
|
||||
SMTP_USER=your-email@example.com
|
||||
SMTP_PASSWORD=your-email-password
|
||||
SMTP_FROM=noreply@example.com
|
||||
|
||||
# Security
|
||||
BCRYPT_ROUNDS=10
|
||||
SESSION_SECRET=your-session-secret-change-this
|
||||
|
||||
# External APIs (futuro)
|
||||
INFONAVIT_API_URL=https://api.infonavit.gob.mx
|
||||
INFONAVIT_API_KEY=your-api-key
|
||||
84
backend/Dockerfile
Normal file
84
backend/Dockerfile
Normal file
@ -0,0 +1,84 @@
|
||||
# =============================================================================
|
||||
# Dockerfile - Backend API
|
||||
# ERP Construccion - Node.js + Express + TypeScript
|
||||
# =============================================================================
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
# Stage 1: Base
|
||||
# -----------------------------------------------------------------------------
|
||||
FROM node:20-alpine AS base
|
||||
|
||||
# Install dependencies for native modules
|
||||
RUN apk add --no-cache \
|
||||
python3 \
|
||||
make \
|
||||
g++ \
|
||||
curl
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
# Copy package files
|
||||
COPY package*.json ./
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
# Stage 2: Development
|
||||
# -----------------------------------------------------------------------------
|
||||
FROM base AS development
|
||||
|
||||
# Install all dependencies (including devDependencies)
|
||||
RUN npm ci
|
||||
|
||||
# Copy source code
|
||||
COPY . .
|
||||
|
||||
# Expose port (standard: 3021 for construccion backend)
|
||||
EXPOSE 3021
|
||||
|
||||
# Development command with hot reload
|
||||
CMD ["npm", "run", "dev"]
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
# Stage 3: Builder
|
||||
# -----------------------------------------------------------------------------
|
||||
FROM base AS builder
|
||||
|
||||
# Install all dependencies
|
||||
RUN npm ci
|
||||
|
||||
# Copy source code
|
||||
COPY . .
|
||||
|
||||
# Build TypeScript
|
||||
RUN npm run build
|
||||
|
||||
# Prune devDependencies
|
||||
RUN npm prune --production
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
# Stage 4: Production
|
||||
# -----------------------------------------------------------------------------
|
||||
FROM node:20-alpine AS production
|
||||
|
||||
# Security: Run as non-root user
|
||||
RUN addgroup -g 1001 -S nodejs && \
|
||||
adduser -S nodejs -u 1001
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
# Copy built application
|
||||
COPY --from=builder --chown=nodejs:nodejs /app/dist ./dist
|
||||
COPY --from=builder --chown=nodejs:nodejs /app/node_modules ./node_modules
|
||||
COPY --from=builder --chown=nodejs:nodejs /app/package*.json ./
|
||||
|
||||
# Set user
|
||||
USER nodejs
|
||||
|
||||
# Expose port (standard: 3021 for construccion backend)
|
||||
EXPOSE 3021
|
||||
|
||||
# Health check
|
||||
HEALTHCHECK --interval=30s --timeout=10s --start-period=40s --retries=3 \
|
||||
CMD curl -f http://localhost:3021/health || exit 1
|
||||
|
||||
# Production command
|
||||
CMD ["node", "dist/server.js"]
|
||||
461
backend/README.md
Normal file
461
backend/README.md
Normal file
@ -0,0 +1,461 @@
|
||||
# Backend - ERP Construccion
|
||||
|
||||
API REST para sistema de administracion de obra e INFONAVIT.
|
||||
|
||||
| Campo | Valor |
|
||||
|-------|-------|
|
||||
| **Stack** | Node.js 20 + Express 4 + TypeScript 5 + TypeORM 0.3 |
|
||||
| **Version** | 1.0.0 |
|
||||
| **Entidades** | 30 |
|
||||
| **Services** | 8 |
|
||||
| **Arquitectura** | Multi-tenant con RLS |
|
||||
|
||||
---
|
||||
|
||||
## Quick Start
|
||||
|
||||
```bash
|
||||
# Instalar dependencias
|
||||
npm install
|
||||
|
||||
# Configurar variables de entorno
|
||||
cp ../.env.example .env
|
||||
|
||||
# Desarrollo con hot-reload
|
||||
npm run dev
|
||||
|
||||
# El servidor estara en http://localhost:3000
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Estructura del Proyecto
|
||||
|
||||
```
|
||||
src/
|
||||
├── modules/
|
||||
│ ├── auth/ # Autenticacion JWT
|
||||
│ │ ├── dto/
|
||||
│ │ │ └── auth.dto.ts # DTOs tipados
|
||||
│ │ ├── middleware/
|
||||
│ │ │ └── auth.middleware.ts
|
||||
│ │ ├── services/
|
||||
│ │ │ └── auth.service.ts
|
||||
│ │ └── index.ts
|
||||
│ │
|
||||
│ ├── budgets/ # MAI-003 Presupuestos
|
||||
│ │ ├── entities/
|
||||
│ │ │ ├── concepto.entity.ts
|
||||
│ │ │ ├── presupuesto.entity.ts
|
||||
│ │ │ └── presupuesto-partida.entity.ts
|
||||
│ │ ├── services/
|
||||
│ │ │ ├── concepto.service.ts
|
||||
│ │ │ └── presupuesto.service.ts
|
||||
│ │ └── index.ts
|
||||
│ │
|
||||
│ ├── progress/ # MAI-005 Control de Obra
|
||||
│ │ ├── 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
|
||||
│ │
|
||||
│ ├── estimates/ # MAI-008 Estimaciones
|
||||
│ │ ├── 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
|
||||
│ │
|
||||
│ ├── construction/ # MAI-002 Proyectos
|
||||
│ │ └── entities/
|
||||
│ │ ├── proyecto.entity.ts
|
||||
│ │ └── fraccionamiento.entity.ts
|
||||
│ │
|
||||
│ ├── hr/ # MAI-007 RRHH
|
||||
│ │ └── entities/
|
||||
│ │ ├── employee.entity.ts
|
||||
│ │ ├── puesto.entity.ts
|
||||
│ │ └── employee-fraccionamiento.entity.ts
|
||||
│ │
|
||||
│ ├── hse/ # MAA-017 Seguridad HSE
|
||||
│ │ └── entities/
|
||||
│ │ ├── incidente.entity.ts
|
||||
│ │ ├── incidente-involucrado.entity.ts
|
||||
│ │ ├── incidente-accion.entity.ts
|
||||
│ │ └── capacitacion.entity.ts
|
||||
│ │
|
||||
│ └── core/ # Entidades base
|
||||
│ └── entities/
|
||||
│ ├── user.entity.ts
|
||||
│ └── tenant.entity.ts
|
||||
│
|
||||
└── shared/
|
||||
├── constants/ # SSOT
|
||||
│ ├── database.constants.ts
|
||||
│ ├── api.constants.ts
|
||||
│ ├── enums.constants.ts
|
||||
│ └── index.ts
|
||||
├── services/
|
||||
│ └── base.service.ts # CRUD multi-tenant
|
||||
└── database/
|
||||
└── typeorm.config.ts
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Modulos Implementados
|
||||
|
||||
### Auth Module
|
||||
|
||||
Autenticacion JWT con refresh tokens y multi-tenancy.
|
||||
|
||||
```typescript
|
||||
// Services
|
||||
AuthService
|
||||
├── login(dto) // Login con email/password
|
||||
├── register(dto) // Registro de usuarios
|
||||
├── refresh(dto) // Renovar tokens
|
||||
├── logout(token) // Revocar refresh token
|
||||
└── changePassword(dto) // Cambiar password
|
||||
|
||||
// Middleware
|
||||
AuthMiddleware
|
||||
├── authenticate // Validar JWT (requerido)
|
||||
├── optionalAuthenticate // Validar JWT (opcional)
|
||||
├── authorize(...roles) // Autorizar por roles
|
||||
├── requireAdmin // Solo admin/super_admin
|
||||
└── requireSupervisor // Solo supervisores+
|
||||
```
|
||||
|
||||
### Budgets Module (MAI-003)
|
||||
|
||||
Catalogo de conceptos y presupuestos de obra.
|
||||
|
||||
```typescript
|
||||
// Entities
|
||||
Concepto // Catalogo jerarquico (arbol)
|
||||
Presupuesto // Presupuestos versionados
|
||||
PresupuestoPartida // Lineas con calculo automatico
|
||||
|
||||
// Services
|
||||
ConceptoService
|
||||
├── createConcepto(ctx, dto) // Crear con nivel/path automatico
|
||||
├── findRootConceptos(ctx) // Conceptos raiz
|
||||
├── findChildren(ctx, parentId) // Hijos de un concepto
|
||||
├── getConceptoTree(ctx, rootId) // Arbol completo
|
||||
└── search(ctx, term) // Busqueda por codigo/nombre
|
||||
|
||||
PresupuestoService
|
||||
├── createPresupuesto(ctx, dto)
|
||||
├── findByFraccionamiento(ctx, id)
|
||||
├── findWithPartidas(ctx, id)
|
||||
├── addPartida(ctx, id, dto)
|
||||
├── updatePartida(ctx, id, dto)
|
||||
├── removePartida(ctx, id)
|
||||
├── recalculateTotal(ctx, id)
|
||||
├── createNewVersion(ctx, id) // Versionamiento
|
||||
└── approve(ctx, id)
|
||||
```
|
||||
|
||||
### Progress Module (MAI-005)
|
||||
|
||||
Control de avances fisicos y bitacora de obra.
|
||||
|
||||
```typescript
|
||||
// Entities
|
||||
AvanceObra // Avances con workflow
|
||||
FotoAvance // Evidencias fotograficas con GPS
|
||||
BitacoraObra // Bitacora diaria
|
||||
ProgramaObra // Programa maestro
|
||||
ProgramaActividad // Actividades WBS
|
||||
|
||||
// Services
|
||||
AvanceObraService
|
||||
├── createAvance(ctx, dto)
|
||||
├── findByLote(ctx, loteId)
|
||||
├── findByDepartamento(ctx, deptoId)
|
||||
├── findWithFilters(ctx, filters)
|
||||
├── findWithFotos(ctx, id)
|
||||
├── addFoto(ctx, id, dto)
|
||||
├── review(ctx, id) // Workflow: revisar
|
||||
├── approve(ctx, id) // Workflow: aprobar
|
||||
├── reject(ctx, id, reason) // Workflow: rechazar
|
||||
└── getAccumulatedProgress(ctx) // Acumulado por concepto
|
||||
|
||||
BitacoraObraService
|
||||
├── createEntry(ctx, dto) // Numero automatico
|
||||
├── findByFraccionamiento(ctx, id)
|
||||
├── findWithFilters(ctx, id, filters)
|
||||
├── findByDate(ctx, id, date)
|
||||
├── findLatest(ctx, id)
|
||||
└── getStats(ctx, id) // Estadisticas
|
||||
```
|
||||
|
||||
### Estimates Module (MAI-008)
|
||||
|
||||
Estimaciones periodicas con workflow de aprobacion.
|
||||
|
||||
```typescript
|
||||
// Entities
|
||||
Estimacion // Estimaciones con workflow
|
||||
EstimacionConcepto // Lineas con acumulados
|
||||
Generador // Numeros generadores
|
||||
Anticipo // Anticipos
|
||||
Amortizacion // Amortizaciones
|
||||
Retencion // Retenciones
|
||||
FondoGarantia // Fondo de garantia
|
||||
EstimacionWorkflow // Historial de estados
|
||||
|
||||
// Services
|
||||
EstimacionService
|
||||
├── createEstimacion(ctx, dto) // Numero automatico
|
||||
├── findByContrato(ctx, contratoId)
|
||||
├── findWithFilters(ctx, filters)
|
||||
├── findWithDetails(ctx, id) // Con relaciones
|
||||
├── addConcepto(ctx, id, dto)
|
||||
├── addGenerador(ctx, conceptoId, dto)
|
||||
├── recalculateTotals(ctx, id) // Llama funcion PG
|
||||
├── submit(ctx, id) // Workflow
|
||||
├── review(ctx, id) // Workflow
|
||||
├── approve(ctx, id) // Workflow
|
||||
├── reject(ctx, id, reason) // Workflow
|
||||
└── getContractSummary(ctx, id) // Resumen financiero
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Base Service
|
||||
|
||||
Servicio base con CRUD multi-tenant.
|
||||
|
||||
```typescript
|
||||
// Uso
|
||||
class MiService extends BaseService<MiEntity> {
|
||||
constructor(repository: Repository<MiEntity>) {
|
||||
super(repository);
|
||||
}
|
||||
}
|
||||
|
||||
// Metodos disponibles
|
||||
BaseService<T>
|
||||
├── findAll(ctx, options?) // Paginado
|
||||
├── findById(ctx, id)
|
||||
├── findOne(ctx, where)
|
||||
├── find(ctx, options)
|
||||
├── create(ctx, data)
|
||||
├── update(ctx, id, data)
|
||||
├── softDelete(ctx, id)
|
||||
├── hardDelete(ctx, id)
|
||||
├── count(ctx, where?)
|
||||
└── exists(ctx, where)
|
||||
|
||||
// ServiceContext
|
||||
interface ServiceContext {
|
||||
tenantId: string;
|
||||
userId: string;
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## SSOT Constants
|
||||
|
||||
Sistema de constantes centralizadas.
|
||||
|
||||
```typescript
|
||||
// database.constants.ts
|
||||
import { DB_SCHEMAS, DB_TABLES, TABLE_REFS } from '@shared/constants';
|
||||
|
||||
DB_SCHEMAS.CONSTRUCTION // 'construction'
|
||||
DB_TABLES.construction.CONCEPTOS // 'conceptos'
|
||||
TABLE_REFS.FRACCIONAMIENTOS // 'construction.fraccionamientos'
|
||||
|
||||
// api.constants.ts
|
||||
import { API_ROUTES } from '@shared/constants';
|
||||
|
||||
API_ROUTES.PRESUPUESTOS.BASE // '/api/v1/presupuestos'
|
||||
API_ROUTES.ESTIMACIONES.BY_ID(id) // '/api/v1/estimaciones/:id'
|
||||
|
||||
// enums.constants.ts
|
||||
import { ROLES, PROJECT_STATUS } from '@shared/constants';
|
||||
|
||||
ROLES.ADMIN // 'admin'
|
||||
PROJECT_STATUS.IN_PROGRESS // 'in_progress'
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Scripts NPM
|
||||
|
||||
```bash
|
||||
# Desarrollo
|
||||
npm run dev # Hot-reload con ts-node-dev
|
||||
npm run build # Compilar TypeScript
|
||||
npm run start # Produccion (dist/)
|
||||
|
||||
# Calidad
|
||||
npm run lint # ESLint
|
||||
npm run lint:fix # ESLint con autofix
|
||||
npm run test # Jest
|
||||
npm run test:watch # Jest watch mode
|
||||
npm run test:coverage # Jest con cobertura
|
||||
|
||||
# Base de datos
|
||||
npm run migration:generate # Generar migracion
|
||||
npm run migration:run # Ejecutar migraciones
|
||||
npm run migration:revert # Revertir ultima
|
||||
|
||||
# SSOT
|
||||
npm run validate:constants # Validar no hardcoding
|
||||
npm run sync:enums # Sincronizar a frontend
|
||||
npm run precommit # lint + validate
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Convenciones
|
||||
|
||||
### Nomenclatura
|
||||
|
||||
| Tipo | Convencion | Ejemplo |
|
||||
|------|------------|---------|
|
||||
| Archivos | kebab-case.tipo.ts | `concepto.entity.ts` |
|
||||
| Clases | PascalCase + sufijo | `ConceptoService` |
|
||||
| Variables | camelCase | `totalAmount` |
|
||||
| Constantes | UPPER_SNAKE_CASE | `DB_SCHEMAS` |
|
||||
| Metodos | camelCase + verbo | `findByContrato` |
|
||||
|
||||
### Entity Pattern
|
||||
|
||||
```typescript
|
||||
@Entity({ schema: 'construction', name: 'conceptos' })
|
||||
@Index(['tenantId', 'code'], { unique: true })
|
||||
export class Concepto {
|
||||
@PrimaryGeneratedColumn('uuid')
|
||||
id: string;
|
||||
|
||||
@Column({ name: 'tenant_id', type: 'uuid' })
|
||||
tenantId: string;
|
||||
|
||||
// ... columnas con name: 'snake_case'
|
||||
|
||||
// Soft delete
|
||||
@Column({ name: 'deleted_at', type: 'timestamptz', nullable: true })
|
||||
deletedAt: Date | null;
|
||||
|
||||
// Relations
|
||||
@ManyToOne(() => Tenant)
|
||||
@JoinColumn({ name: 'tenant_id' })
|
||||
tenant: Tenant;
|
||||
}
|
||||
```
|
||||
|
||||
### Service Pattern
|
||||
|
||||
```typescript
|
||||
export class MiService extends BaseService<MiEntity> {
|
||||
constructor(
|
||||
repository: Repository<MiEntity>,
|
||||
private readonly otroRepo: Repository<OtroEntity>
|
||||
) {
|
||||
super(repository);
|
||||
}
|
||||
|
||||
async miMetodo(ctx: ServiceContext, data: MiDto): Promise<MiEntity> {
|
||||
// ctx tiene tenantId y userId
|
||||
return this.create(ctx, data);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Seguridad
|
||||
|
||||
- Helmet para HTTP security headers
|
||||
- CORS configurado por dominio
|
||||
- Rate limiting por IP
|
||||
- JWT con refresh tokens
|
||||
- Bcrypt (12 rounds) para passwords
|
||||
- class-validator para inputs
|
||||
- RLS para aislamiento de tenants
|
||||
|
||||
---
|
||||
|
||||
## Testing
|
||||
|
||||
```bash
|
||||
# Ejecutar tests
|
||||
npm test
|
||||
|
||||
# Con cobertura
|
||||
npm run test:coverage
|
||||
|
||||
# Watch mode
|
||||
npm run test:watch
|
||||
```
|
||||
|
||||
```typescript
|
||||
// Ejemplo de test
|
||||
describe('ConceptoService', () => {
|
||||
let service: ConceptoService;
|
||||
let mockRepo: jest.Mocked<Repository<Concepto>>;
|
||||
|
||||
beforeEach(() => {
|
||||
mockRepo = createMockRepository();
|
||||
service = new ConceptoService(mockRepo);
|
||||
});
|
||||
|
||||
it('should create concepto with level', async () => {
|
||||
const ctx = { tenantId: 'uuid', userId: 'uuid' };
|
||||
const dto = { code: '001', name: 'Test' };
|
||||
|
||||
mockRepo.save.mockResolvedValue({ ...dto, level: 0 });
|
||||
|
||||
const result = await service.createConcepto(ctx, dto);
|
||||
expect(result.level).toBe(0);
|
||||
});
|
||||
});
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Debugging
|
||||
|
||||
### VS Code
|
||||
|
||||
```json
|
||||
{
|
||||
"type": "node",
|
||||
"request": "launch",
|
||||
"name": "Debug Backend",
|
||||
"runtimeArgs": ["-r", "ts-node/register"],
|
||||
"args": ["${workspaceFolder}/src/server.ts"],
|
||||
"env": { "NODE_ENV": "development" }
|
||||
}
|
||||
```
|
||||
|
||||
### Logs
|
||||
|
||||
```typescript
|
||||
// Configurar en .env
|
||||
LOG_LEVEL=debug
|
||||
LOG_FORMAT=dev
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
**Ultima actualizacion:** 2025-12-12
|
||||
16
backend/dist/modules/auth/controllers/auth.controller.d.ts
vendored
Normal file
16
backend/dist/modules/auth/controllers/auth.controller.d.ts
vendored
Normal file
@ -0,0 +1,16 @@
|
||||
/**
|
||||
* AuthController - Controlador de Autenticación
|
||||
*
|
||||
* Endpoints REST para login, register, refresh y logout.
|
||||
* Implementa validación de datos y manejo de errores.
|
||||
*
|
||||
* @module Auth
|
||||
*/
|
||||
import { Router } from 'express';
|
||||
import { DataSource } from 'typeorm';
|
||||
/**
|
||||
* Crear router de autenticación
|
||||
*/
|
||||
export declare function createAuthController(dataSource: DataSource): Router;
|
||||
export default createAuthController;
|
||||
//# sourceMappingURL=auth.controller.d.ts.map
|
||||
1
backend/dist/modules/auth/controllers/auth.controller.d.ts.map
vendored
Normal file
1
backend/dist/modules/auth/controllers/auth.controller.d.ts.map
vendored
Normal file
@ -0,0 +1 @@
|
||||
{"version":3,"file":"auth.controller.d.ts","sourceRoot":"","sources":["../../../../src/modules/auth/controllers/auth.controller.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,EAAE,MAAM,EAAmC,MAAM,SAAS,CAAC;AAClE,OAAO,EAAE,UAAU,EAAE,MAAM,SAAS,CAAC;AAarC;;GAEG;AACH,wBAAgB,oBAAoB,CAAC,UAAU,EAAE,UAAU,GAAG,MAAM,CA+OnE;AAED,eAAe,oBAAoB,CAAC"}
|
||||
237
backend/dist/modules/auth/controllers/auth.controller.js
vendored
Normal file
237
backend/dist/modules/auth/controllers/auth.controller.js
vendored
Normal file
@ -0,0 +1,237 @@
|
||||
"use strict";
|
||||
/**
|
||||
* AuthController - Controlador de Autenticación
|
||||
*
|
||||
* Endpoints REST para login, register, refresh y logout.
|
||||
* Implementa validación de datos y manejo de errores.
|
||||
*
|
||||
* @module Auth
|
||||
*/
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.createAuthController = createAuthController;
|
||||
const express_1 = require("express");
|
||||
const auth_service_1 = require("../services/auth.service");
|
||||
const auth_middleware_1 = require("../middleware/auth.middleware");
|
||||
const user_entity_1 = require("../../core/entities/user.entity");
|
||||
const tenant_entity_1 = require("../../core/entities/tenant.entity");
|
||||
const refresh_token_entity_1 = require("../entities/refresh-token.entity");
|
||||
/**
|
||||
* Crear router de autenticación
|
||||
*/
|
||||
function createAuthController(dataSource) {
|
||||
const router = (0, express_1.Router)();
|
||||
// Inicializar repositorios
|
||||
const userRepository = dataSource.getRepository(user_entity_1.User);
|
||||
const tenantRepository = dataSource.getRepository(tenant_entity_1.Tenant);
|
||||
const refreshTokenRepository = dataSource.getRepository(refresh_token_entity_1.RefreshToken);
|
||||
// Inicializar servicio
|
||||
const authService = new auth_service_1.AuthService(userRepository, tenantRepository, refreshTokenRepository);
|
||||
// Inicializar middleware
|
||||
const authMiddleware = new auth_middleware_1.AuthMiddleware(authService, dataSource);
|
||||
/**
|
||||
* POST /auth/login
|
||||
* Login de usuario
|
||||
*/
|
||||
router.post('/login', async (req, res, next) => {
|
||||
try {
|
||||
const dto = req.body;
|
||||
if (!dto.email || !dto.password) {
|
||||
res.status(400).json({
|
||||
error: 'Bad Request',
|
||||
message: 'Email and password are required',
|
||||
});
|
||||
return;
|
||||
}
|
||||
const result = await authService.login(dto);
|
||||
res.status(200).json({ success: true, data: result });
|
||||
}
|
||||
catch (error) {
|
||||
if (error instanceof Error) {
|
||||
if (error.message === 'Invalid credentials') {
|
||||
res.status(401).json({ error: 'Unauthorized', message: 'Invalid email or password' });
|
||||
return;
|
||||
}
|
||||
if (error.message === 'User is not active') {
|
||||
res.status(403).json({ error: 'Forbidden', message: 'User account is disabled' });
|
||||
return;
|
||||
}
|
||||
if (error.message === 'No tenant specified' || error.message === 'Tenant not found or inactive') {
|
||||
res.status(400).json({ error: 'Bad Request', message: error.message });
|
||||
return;
|
||||
}
|
||||
}
|
||||
next(error);
|
||||
}
|
||||
});
|
||||
/**
|
||||
* POST /auth/register
|
||||
* Registro de nuevo usuario
|
||||
*/
|
||||
router.post('/register', async (req, res, next) => {
|
||||
try {
|
||||
const dto = req.body;
|
||||
if (!dto.email || !dto.password || !dto.firstName || !dto.lastName || !dto.tenantId) {
|
||||
res.status(400).json({
|
||||
error: 'Bad Request',
|
||||
message: 'Email, password, firstName, lastName and tenantId are required',
|
||||
});
|
||||
return;
|
||||
}
|
||||
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
|
||||
if (!emailRegex.test(dto.email)) {
|
||||
res.status(400).json({ error: 'Bad Request', message: 'Invalid email format' });
|
||||
return;
|
||||
}
|
||||
if (dto.password.length < 8) {
|
||||
res.status(400).json({ error: 'Bad Request', message: 'Password must be at least 8 characters' });
|
||||
return;
|
||||
}
|
||||
const result = await authService.register(dto);
|
||||
res.status(201).json({ success: true, data: result });
|
||||
}
|
||||
catch (error) {
|
||||
if (error instanceof Error) {
|
||||
if (error.message === 'Email already registered') {
|
||||
res.status(409).json({ error: 'Conflict', message: 'Email is already registered' });
|
||||
return;
|
||||
}
|
||||
if (error.message === 'Tenant not found') {
|
||||
res.status(400).json({ error: 'Bad Request', message: 'Invalid tenant ID' });
|
||||
return;
|
||||
}
|
||||
}
|
||||
next(error);
|
||||
}
|
||||
});
|
||||
/**
|
||||
* POST /auth/refresh
|
||||
* Renovar access token usando refresh token
|
||||
*/
|
||||
router.post('/refresh', async (req, res, next) => {
|
||||
try {
|
||||
const dto = req.body;
|
||||
if (!dto.refreshToken) {
|
||||
res.status(400).json({ error: 'Bad Request', message: 'Refresh token is required' });
|
||||
return;
|
||||
}
|
||||
const result = await authService.refresh(dto);
|
||||
res.status(200).json({ success: true, data: result });
|
||||
}
|
||||
catch (error) {
|
||||
if (error instanceof Error) {
|
||||
if (error.message === 'Invalid refresh token' || error.message === 'Refresh token expired or revoked') {
|
||||
res.status(401).json({ error: 'Unauthorized', message: error.message });
|
||||
return;
|
||||
}
|
||||
if (error.message === 'User not found or inactive') {
|
||||
res.status(401).json({ error: 'Unauthorized', message: 'User account is disabled or deleted' });
|
||||
return;
|
||||
}
|
||||
}
|
||||
next(error);
|
||||
}
|
||||
});
|
||||
/**
|
||||
* POST /auth/logout
|
||||
* Cerrar sesión (revocar refresh token)
|
||||
*/
|
||||
router.post('/logout', authMiddleware.authenticate, async (req, res, next) => {
|
||||
try {
|
||||
const { refreshToken } = req.body;
|
||||
if (refreshToken) {
|
||||
await authService.logout(refreshToken);
|
||||
}
|
||||
res.status(200).json({ success: true, message: 'Logged out successfully' });
|
||||
}
|
||||
catch (error) {
|
||||
next(error);
|
||||
}
|
||||
});
|
||||
/**
|
||||
* POST /auth/change-password
|
||||
* Cambiar contraseña (requiere autenticación)
|
||||
*/
|
||||
router.post('/change-password', authMiddleware.authenticate, async (req, res, next) => {
|
||||
try {
|
||||
const dto = req.body;
|
||||
const userId = req.user?.sub;
|
||||
if (!userId) {
|
||||
res.status(401).json({ error: 'Unauthorized', message: 'User not authenticated' });
|
||||
return;
|
||||
}
|
||||
if (!dto.currentPassword || !dto.newPassword) {
|
||||
res.status(400).json({ error: 'Bad Request', message: 'Current password and new password are required' });
|
||||
return;
|
||||
}
|
||||
if (dto.newPassword.length < 8) {
|
||||
res.status(400).json({ error: 'Bad Request', message: 'New password must be at least 8 characters' });
|
||||
return;
|
||||
}
|
||||
await authService.changePassword(userId, dto);
|
||||
res.status(200).json({ success: true, message: 'Password changed successfully' });
|
||||
}
|
||||
catch (error) {
|
||||
if (error instanceof Error && error.message === 'Current password is incorrect') {
|
||||
res.status(400).json({ error: 'Bad Request', message: 'Current password is incorrect' });
|
||||
return;
|
||||
}
|
||||
next(error);
|
||||
}
|
||||
});
|
||||
/**
|
||||
* GET /auth/me
|
||||
* Obtener información del usuario autenticado
|
||||
*/
|
||||
router.get('/me', authMiddleware.authenticate, async (req, res, next) => {
|
||||
try {
|
||||
const userId = req.user?.sub;
|
||||
if (!userId) {
|
||||
res.status(401).json({ error: 'Unauthorized', message: 'User not authenticated' });
|
||||
return;
|
||||
}
|
||||
const user = await userRepository.findOne({
|
||||
where: { id: userId },
|
||||
select: ['id', 'email', 'firstName', 'lastName', 'isActive', 'createdAt'],
|
||||
});
|
||||
if (!user) {
|
||||
res.status(404).json({ error: 'Not Found', message: 'User not found' });
|
||||
return;
|
||||
}
|
||||
res.status(200).json({
|
||||
success: true,
|
||||
data: {
|
||||
id: user.id,
|
||||
email: user.email,
|
||||
firstName: user.firstName,
|
||||
lastName: user.lastName,
|
||||
roles: req.user?.roles || [],
|
||||
tenantId: req.tenantId,
|
||||
},
|
||||
});
|
||||
}
|
||||
catch (error) {
|
||||
next(error);
|
||||
}
|
||||
});
|
||||
/**
|
||||
* GET /auth/verify
|
||||
* Verificar si el token es válido
|
||||
*/
|
||||
router.get('/verify', authMiddleware.authenticate, (req, res) => {
|
||||
res.status(200).json({
|
||||
success: true,
|
||||
data: {
|
||||
valid: true,
|
||||
user: {
|
||||
id: req.user?.sub,
|
||||
email: req.user?.email,
|
||||
roles: req.user?.roles,
|
||||
},
|
||||
tenantId: req.tenantId,
|
||||
},
|
||||
});
|
||||
});
|
||||
return router;
|
||||
}
|
||||
exports.default = createAuthController;
|
||||
//# sourceMappingURL=auth.controller.js.map
|
||||
1
backend/dist/modules/auth/controllers/auth.controller.js.map
vendored
Normal file
1
backend/dist/modules/auth/controllers/auth.controller.js.map
vendored
Normal file
File diff suppressed because one or more lines are too long
5
backend/dist/modules/auth/controllers/index.d.ts
vendored
Normal file
5
backend/dist/modules/auth/controllers/index.d.ts
vendored
Normal file
@ -0,0 +1,5 @@
|
||||
/**
|
||||
* Auth Controllers - Export
|
||||
*/
|
||||
export * from './auth.controller';
|
||||
//# sourceMappingURL=index.d.ts.map
|
||||
1
backend/dist/modules/auth/controllers/index.d.ts.map
vendored
Normal file
1
backend/dist/modules/auth/controllers/index.d.ts.map
vendored
Normal file
@ -0,0 +1 @@
|
||||
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/modules/auth/controllers/index.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,cAAc,mBAAmB,CAAC"}
|
||||
21
backend/dist/modules/auth/controllers/index.js
vendored
Normal file
21
backend/dist/modules/auth/controllers/index.js
vendored
Normal file
@ -0,0 +1,21 @@
|
||||
"use strict";
|
||||
/**
|
||||
* Auth Controllers - Export
|
||||
*/
|
||||
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
||||
if (k2 === undefined) k2 = k;
|
||||
var desc = Object.getOwnPropertyDescriptor(m, k);
|
||||
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
||||
desc = { enumerable: true, get: function() { return m[k]; } };
|
||||
}
|
||||
Object.defineProperty(o, k2, desc);
|
||||
}) : (function(o, m, k, k2) {
|
||||
if (k2 === undefined) k2 = k;
|
||||
o[k2] = m[k];
|
||||
}));
|
||||
var __exportStar = (this && this.__exportStar) || function(m, exports) {
|
||||
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
|
||||
};
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
__exportStar(require("./auth.controller"), exports);
|
||||
//# sourceMappingURL=index.js.map
|
||||
1
backend/dist/modules/auth/controllers/index.js.map
vendored
Normal file
1
backend/dist/modules/auth/controllers/index.js.map
vendored
Normal file
@ -0,0 +1 @@
|
||||
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../../src/modules/auth/controllers/index.ts"],"names":[],"mappings":";AAAA;;GAEG;;;;;;;;;;;;;;;;AAEH,oDAAkC"}
|
||||
62
backend/dist/modules/auth/dto/auth.dto.d.ts
vendored
Normal file
62
backend/dist/modules/auth/dto/auth.dto.d.ts
vendored
Normal file
@ -0,0 +1,62 @@
|
||||
/**
|
||||
* Auth DTOs - Data Transfer Objects para autenticación
|
||||
*
|
||||
* @module Auth
|
||||
*/
|
||||
export interface LoginDto {
|
||||
email: string;
|
||||
password: string;
|
||||
tenantId?: string;
|
||||
}
|
||||
export interface RegisterDto {
|
||||
email: string;
|
||||
password: string;
|
||||
firstName: string;
|
||||
lastName: string;
|
||||
tenantId: string;
|
||||
}
|
||||
export interface RefreshTokenDto {
|
||||
refreshToken: string;
|
||||
}
|
||||
export interface ChangePasswordDto {
|
||||
currentPassword: string;
|
||||
newPassword: string;
|
||||
}
|
||||
export interface ResetPasswordRequestDto {
|
||||
email: string;
|
||||
}
|
||||
export interface ResetPasswordDto {
|
||||
token: string;
|
||||
newPassword: string;
|
||||
}
|
||||
export interface TokenPayload {
|
||||
sub: string;
|
||||
email: string;
|
||||
tenantId: string;
|
||||
roles: string[];
|
||||
type: 'access' | 'refresh';
|
||||
iat?: number;
|
||||
exp?: number;
|
||||
}
|
||||
export interface AuthResponse {
|
||||
accessToken: string;
|
||||
refreshToken: string;
|
||||
expiresIn: number;
|
||||
user: {
|
||||
id: string;
|
||||
email: string;
|
||||
firstName: string;
|
||||
lastName: string;
|
||||
roles: string[];
|
||||
};
|
||||
tenant: {
|
||||
id: string;
|
||||
name: string;
|
||||
};
|
||||
}
|
||||
export interface TokenValidationResult {
|
||||
valid: boolean;
|
||||
payload?: TokenPayload;
|
||||
error?: string;
|
||||
}
|
||||
//# sourceMappingURL=auth.dto.d.ts.map
|
||||
1
backend/dist/modules/auth/dto/auth.dto.d.ts.map
vendored
Normal file
1
backend/dist/modules/auth/dto/auth.dto.d.ts.map
vendored
Normal file
@ -0,0 +1 @@
|
||||
{"version":3,"file":"auth.dto.d.ts","sourceRoot":"","sources":["../../../../src/modules/auth/dto/auth.dto.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,MAAM,WAAW,QAAQ;IACvB,KAAK,EAAE,MAAM,CAAC;IACd,QAAQ,EAAE,MAAM,CAAC;IACjB,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB;AAED,MAAM,WAAW,WAAW;IAC1B,KAAK,EAAE,MAAM,CAAC;IACd,QAAQ,EAAE,MAAM,CAAC;IACjB,SAAS,EAAE,MAAM,CAAC;IAClB,QAAQ,EAAE,MAAM,CAAC;IACjB,QAAQ,EAAE,MAAM,CAAC;CAClB;AAED,MAAM,WAAW,eAAe;IAC9B,YAAY,EAAE,MAAM,CAAC;CACtB;AAED,MAAM,WAAW,iBAAiB;IAChC,eAAe,EAAE,MAAM,CAAC;IACxB,WAAW,EAAE,MAAM,CAAC;CACrB;AAED,MAAM,WAAW,uBAAuB;IACtC,KAAK,EAAE,MAAM,CAAC;CACf;AAED,MAAM,WAAW,gBAAgB;IAC/B,KAAK,EAAE,MAAM,CAAC;IACd,WAAW,EAAE,MAAM,CAAC;CACrB;AAED,MAAM,WAAW,YAAY;IAC3B,GAAG,EAAE,MAAM,CAAC;IACZ,KAAK,EAAE,MAAM,CAAC;IACd,QAAQ,EAAE,MAAM,CAAC;IACjB,KAAK,EAAE,MAAM,EAAE,CAAC;IAChB,IAAI,EAAE,QAAQ,GAAG,SAAS,CAAC;IAC3B,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,GAAG,CAAC,EAAE,MAAM,CAAC;CACd;AAED,MAAM,WAAW,YAAY;IAC3B,WAAW,EAAE,MAAM,CAAC;IACpB,YAAY,EAAE,MAAM,CAAC;IACrB,SAAS,EAAE,MAAM,CAAC;IAClB,IAAI,EAAE;QACJ,EAAE,EAAE,MAAM,CAAC;QACX,KAAK,EAAE,MAAM,CAAC;QACd,SAAS,EAAE,MAAM,CAAC;QAClB,QAAQ,EAAE,MAAM,CAAC;QACjB,KAAK,EAAE,MAAM,EAAE,CAAC;KACjB,CAAC;IACF,MAAM,EAAE;QACN,EAAE,EAAE,MAAM,CAAC;QACX,IAAI,EAAE,MAAM,CAAC;KACd,CAAC;CACH;AAED,MAAM,WAAW,qBAAqB;IACpC,KAAK,EAAE,OAAO,CAAC;IACf,OAAO,CAAC,EAAE,YAAY,CAAC;IACvB,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB"}
|
||||
8
backend/dist/modules/auth/dto/auth.dto.js
vendored
Normal file
8
backend/dist/modules/auth/dto/auth.dto.js
vendored
Normal file
@ -0,0 +1,8 @@
|
||||
"use strict";
|
||||
/**
|
||||
* Auth DTOs - Data Transfer Objects para autenticación
|
||||
*
|
||||
* @module Auth
|
||||
*/
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
//# sourceMappingURL=auth.dto.js.map
|
||||
1
backend/dist/modules/auth/dto/auth.dto.js.map
vendored
Normal file
1
backend/dist/modules/auth/dto/auth.dto.js.map
vendored
Normal file
@ -0,0 +1 @@
|
||||
{"version":3,"file":"auth.dto.js","sourceRoot":"","sources":["../../../../src/modules/auth/dto/auth.dto.ts"],"names":[],"mappings":";AAAA;;;;GAIG"}
|
||||
8
backend/dist/modules/auth/entities/index.d.ts
vendored
Normal file
8
backend/dist/modules/auth/entities/index.d.ts
vendored
Normal file
@ -0,0 +1,8 @@
|
||||
/**
|
||||
* Auth Entities - Export
|
||||
*/
|
||||
export { RefreshToken } from './refresh-token.entity';
|
||||
export { Role } from './role.entity';
|
||||
export { Permission } from './permission.entity';
|
||||
export { UserRole } from './user-role.entity';
|
||||
//# sourceMappingURL=index.d.ts.map
|
||||
1
backend/dist/modules/auth/entities/index.d.ts.map
vendored
Normal file
1
backend/dist/modules/auth/entities/index.d.ts.map
vendored
Normal file
@ -0,0 +1 @@
|
||||
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/modules/auth/entities/index.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,EAAE,YAAY,EAAE,MAAM,wBAAwB,CAAC;AACtD,OAAO,EAAE,IAAI,EAAE,MAAM,eAAe,CAAC;AACrC,OAAO,EAAE,UAAU,EAAE,MAAM,qBAAqB,CAAC;AACjD,OAAO,EAAE,QAAQ,EAAE,MAAM,oBAAoB,CAAC"}
|
||||
15
backend/dist/modules/auth/entities/index.js
vendored
Normal file
15
backend/dist/modules/auth/entities/index.js
vendored
Normal file
@ -0,0 +1,15 @@
|
||||
"use strict";
|
||||
/**
|
||||
* Auth Entities - Export
|
||||
*/
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.UserRole = exports.Permission = exports.Role = exports.RefreshToken = void 0;
|
||||
var refresh_token_entity_1 = require("./refresh-token.entity");
|
||||
Object.defineProperty(exports, "RefreshToken", { enumerable: true, get: function () { return refresh_token_entity_1.RefreshToken; } });
|
||||
var role_entity_1 = require("./role.entity");
|
||||
Object.defineProperty(exports, "Role", { enumerable: true, get: function () { return role_entity_1.Role; } });
|
||||
var permission_entity_1 = require("./permission.entity");
|
||||
Object.defineProperty(exports, "Permission", { enumerable: true, get: function () { return permission_entity_1.Permission; } });
|
||||
var user_role_entity_1 = require("./user-role.entity");
|
||||
Object.defineProperty(exports, "UserRole", { enumerable: true, get: function () { return user_role_entity_1.UserRole; } });
|
||||
//# sourceMappingURL=index.js.map
|
||||
1
backend/dist/modules/auth/entities/index.js.map
vendored
Normal file
1
backend/dist/modules/auth/entities/index.js.map
vendored
Normal file
@ -0,0 +1 @@
|
||||
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../../src/modules/auth/entities/index.ts"],"names":[],"mappings":";AAAA;;GAEG;;;AAEH,+DAAsD;AAA7C,oHAAA,YAAY,OAAA;AACrB,6CAAqC;AAA5B,mGAAA,IAAI,OAAA;AACb,yDAAiD;AAAxC,+GAAA,UAAU,OAAA;AACnB,uDAA8C;AAArC,4GAAA,QAAQ,OAAA"}
|
||||
15
backend/dist/modules/auth/entities/permission.entity.d.ts
vendored
Normal file
15
backend/dist/modules/auth/entities/permission.entity.d.ts
vendored
Normal file
@ -0,0 +1,15 @@
|
||||
/**
|
||||
* Permission Entity
|
||||
* Permisos granulares del sistema
|
||||
*
|
||||
* @module Auth
|
||||
*/
|
||||
export declare class Permission {
|
||||
id: string;
|
||||
code: string;
|
||||
name: string;
|
||||
description: string;
|
||||
module: string;
|
||||
createdAt: Date;
|
||||
}
|
||||
//# sourceMappingURL=permission.entity.d.ts.map
|
||||
1
backend/dist/modules/auth/entities/permission.entity.d.ts.map
vendored
Normal file
1
backend/dist/modules/auth/entities/permission.entity.d.ts.map
vendored
Normal file
@ -0,0 +1 @@
|
||||
{"version":3,"file":"permission.entity.d.ts","sourceRoot":"","sources":["../../../../src/modules/auth/entities/permission.entity.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AASH,qBACa,UAAU;IAErB,EAAE,EAAE,MAAM,CAAC;IAGX,IAAI,EAAE,MAAM,CAAC;IAGb,IAAI,EAAE,MAAM,CAAC;IAGb,WAAW,EAAE,MAAM,CAAC;IAGpB,MAAM,EAAE,MAAM,CAAC;IAGf,SAAS,EAAE,IAAI,CAAC;CACjB"}
|
||||
56
backend/dist/modules/auth/entities/permission.entity.js
vendored
Normal file
56
backend/dist/modules/auth/entities/permission.entity.js
vendored
Normal file
@ -0,0 +1,56 @@
|
||||
"use strict";
|
||||
/**
|
||||
* Permission Entity
|
||||
* Permisos granulares del sistema
|
||||
*
|
||||
* @module Auth
|
||||
*/
|
||||
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
|
||||
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
||||
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
||||
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
||||
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
||||
};
|
||||
var __metadata = (this && this.__metadata) || function (k, v) {
|
||||
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
|
||||
};
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.Permission = void 0;
|
||||
const typeorm_1 = require("typeorm");
|
||||
let Permission = class Permission {
|
||||
id;
|
||||
code;
|
||||
name;
|
||||
description;
|
||||
module;
|
||||
createdAt;
|
||||
};
|
||||
exports.Permission = Permission;
|
||||
__decorate([
|
||||
(0, typeorm_1.PrimaryGeneratedColumn)('uuid'),
|
||||
__metadata("design:type", String)
|
||||
], Permission.prototype, "id", void 0);
|
||||
__decorate([
|
||||
(0, typeorm_1.Column)({ type: 'varchar', length: 100, unique: true }),
|
||||
__metadata("design:type", String)
|
||||
], Permission.prototype, "code", void 0);
|
||||
__decorate([
|
||||
(0, typeorm_1.Column)({ type: 'varchar', length: 200 }),
|
||||
__metadata("design:type", String)
|
||||
], Permission.prototype, "name", void 0);
|
||||
__decorate([
|
||||
(0, typeorm_1.Column)({ type: 'text', nullable: true }),
|
||||
__metadata("design:type", String)
|
||||
], Permission.prototype, "description", void 0);
|
||||
__decorate([
|
||||
(0, typeorm_1.Column)({ type: 'varchar', length: 50 }),
|
||||
__metadata("design:type", String)
|
||||
], Permission.prototype, "module", void 0);
|
||||
__decorate([
|
||||
(0, typeorm_1.CreateDateColumn)({ name: 'created_at', type: 'timestamptz' }),
|
||||
__metadata("design:type", Date)
|
||||
], Permission.prototype, "createdAt", void 0);
|
||||
exports.Permission = Permission = __decorate([
|
||||
(0, typeorm_1.Entity)({ schema: 'auth', name: 'permissions' })
|
||||
], Permission);
|
||||
//# sourceMappingURL=permission.entity.js.map
|
||||
1
backend/dist/modules/auth/entities/permission.entity.js.map
vendored
Normal file
1
backend/dist/modules/auth/entities/permission.entity.js.map
vendored
Normal file
@ -0,0 +1 @@
|
||||
{"version":3,"file":"permission.entity.js","sourceRoot":"","sources":["../../../../src/modules/auth/entities/permission.entity.ts"],"names":[],"mappings":";AAAA;;;;;GAKG;;;;;;;;;;;;AAEH,qCAKiB;AAGV,IAAM,UAAU,GAAhB,MAAM,UAAU;IAErB,EAAE,CAAS;IAGX,IAAI,CAAS;IAGb,IAAI,CAAS;IAGb,WAAW,CAAS;IAGpB,MAAM,CAAS;IAGf,SAAS,CAAO;CACjB,CAAA;AAlBY,gCAAU;AAErB;IADC,IAAA,gCAAsB,EAAC,MAAM,CAAC;;sCACpB;AAGX;IADC,IAAA,gBAAM,EAAC,EAAE,IAAI,EAAE,SAAS,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,EAAE,IAAI,EAAE,CAAC;;wCAC1C;AAGb;IADC,IAAA,gBAAM,EAAC,EAAE,IAAI,EAAE,SAAS,EAAE,MAAM,EAAE,GAAG,EAAE,CAAC;;wCAC5B;AAGb;IADC,IAAA,gBAAM,EAAC,EAAE,IAAI,EAAE,MAAM,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC;;+CACrB;AAGpB;IADC,IAAA,gBAAM,EAAC,EAAE,IAAI,EAAE,SAAS,EAAE,MAAM,EAAE,EAAE,EAAE,CAAC;;0CACzB;AAGf;IADC,IAAA,0BAAgB,EAAC,EAAE,IAAI,EAAE,YAAY,EAAE,IAAI,EAAE,aAAa,EAAE,CAAC;8BACnD,IAAI;6CAAC;qBAjBL,UAAU;IADtB,IAAA,gBAAM,EAAC,EAAE,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,aAAa,EAAE,CAAC;GACnC,UAAU,CAkBtB"}
|
||||
33
backend/dist/modules/auth/entities/refresh-token.entity.d.ts
vendored
Normal file
33
backend/dist/modules/auth/entities/refresh-token.entity.d.ts
vendored
Normal file
@ -0,0 +1,33 @@
|
||||
/**
|
||||
* RefreshToken Entity
|
||||
*
|
||||
* Almacena refresh tokens para autenticación JWT.
|
||||
* Permite revocar tokens y gestionar sesiones.
|
||||
*
|
||||
* @module Auth
|
||||
*/
|
||||
import { User } from '../../core/entities/user.entity';
|
||||
export declare class RefreshToken {
|
||||
id: string;
|
||||
userId: string;
|
||||
user: User;
|
||||
token: string;
|
||||
expiresAt: Date;
|
||||
revokedAt: Date | null;
|
||||
createdAt: Date;
|
||||
userAgent: string | null;
|
||||
ipAddress: string | null;
|
||||
/**
|
||||
* Verificar si el token está expirado
|
||||
*/
|
||||
isExpired(): boolean;
|
||||
/**
|
||||
* Verificar si el token está revocado
|
||||
*/
|
||||
isRevoked(): boolean;
|
||||
/**
|
||||
* Verificar si el token es válido
|
||||
*/
|
||||
isValid(): boolean;
|
||||
}
|
||||
//# sourceMappingURL=refresh-token.entity.d.ts.map
|
||||
1
backend/dist/modules/auth/entities/refresh-token.entity.d.ts.map
vendored
Normal file
1
backend/dist/modules/auth/entities/refresh-token.entity.d.ts.map
vendored
Normal file
@ -0,0 +1 @@
|
||||
{"version":3,"file":"refresh-token.entity.d.ts","sourceRoot":"","sources":["../../../../src/modules/auth/entities/refresh-token.entity.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAWH,OAAO,EAAE,IAAI,EAAE,MAAM,iCAAiC,CAAC;AAEvD,qBAGa,YAAY;IAEvB,EAAE,EAAE,MAAM,CAAC;IAGX,MAAM,EAAE,MAAM,CAAC;IAIf,IAAI,EAAE,IAAI,CAAC;IAGX,KAAK,EAAE,MAAM,CAAC;IAGd,SAAS,EAAE,IAAI,CAAC;IAGhB,SAAS,EAAE,IAAI,GAAG,IAAI,CAAC;IAGvB,SAAS,EAAE,IAAI,CAAC;IAGhB,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;IAGzB,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;IAEzB;;OAEG;IACH,SAAS,IAAI,OAAO;IAIpB;;OAEG;IACH,SAAS,IAAI,OAAO;IAIpB;;OAEG;IACH,OAAO,IAAI,OAAO;CAGnB"}
|
||||
95
backend/dist/modules/auth/entities/refresh-token.entity.js
vendored
Normal file
95
backend/dist/modules/auth/entities/refresh-token.entity.js
vendored
Normal file
@ -0,0 +1,95 @@
|
||||
"use strict";
|
||||
/**
|
||||
* RefreshToken Entity
|
||||
*
|
||||
* Almacena refresh tokens para autenticación JWT.
|
||||
* Permite revocar tokens y gestionar sesiones.
|
||||
*
|
||||
* @module Auth
|
||||
*/
|
||||
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
|
||||
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
||||
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
||||
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
||||
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
||||
};
|
||||
var __metadata = (this && this.__metadata) || function (k, v) {
|
||||
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
|
||||
};
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.RefreshToken = void 0;
|
||||
const typeorm_1 = require("typeorm");
|
||||
const user_entity_1 = require("../../core/entities/user.entity");
|
||||
let RefreshToken = class RefreshToken {
|
||||
id;
|
||||
userId;
|
||||
user;
|
||||
token;
|
||||
expiresAt;
|
||||
revokedAt;
|
||||
createdAt;
|
||||
userAgent;
|
||||
ipAddress;
|
||||
/**
|
||||
* Verificar si el token está expirado
|
||||
*/
|
||||
isExpired() {
|
||||
return this.expiresAt < new Date();
|
||||
}
|
||||
/**
|
||||
* Verificar si el token está revocado
|
||||
*/
|
||||
isRevoked() {
|
||||
return this.revokedAt !== null;
|
||||
}
|
||||
/**
|
||||
* Verificar si el token es válido
|
||||
*/
|
||||
isValid() {
|
||||
return !this.isExpired() && !this.isRevoked();
|
||||
}
|
||||
};
|
||||
exports.RefreshToken = RefreshToken;
|
||||
__decorate([
|
||||
(0, typeorm_1.PrimaryGeneratedColumn)('uuid'),
|
||||
__metadata("design:type", String)
|
||||
], RefreshToken.prototype, "id", void 0);
|
||||
__decorate([
|
||||
(0, typeorm_1.Column)({ name: 'user_id', type: 'uuid' }),
|
||||
__metadata("design:type", String)
|
||||
], RefreshToken.prototype, "userId", void 0);
|
||||
__decorate([
|
||||
(0, typeorm_1.ManyToOne)(() => user_entity_1.User, { onDelete: 'CASCADE' }),
|
||||
(0, typeorm_1.JoinColumn)({ name: 'user_id' }),
|
||||
__metadata("design:type", user_entity_1.User)
|
||||
], RefreshToken.prototype, "user", void 0);
|
||||
__decorate([
|
||||
(0, typeorm_1.Column)({ type: 'text' }),
|
||||
__metadata("design:type", String)
|
||||
], RefreshToken.prototype, "token", void 0);
|
||||
__decorate([
|
||||
(0, typeorm_1.Column)({ name: 'expires_at', type: 'timestamptz' }),
|
||||
__metadata("design:type", Date)
|
||||
], RefreshToken.prototype, "expiresAt", void 0);
|
||||
__decorate([
|
||||
(0, typeorm_1.Column)({ name: 'revoked_at', type: 'timestamptz', nullable: true }),
|
||||
__metadata("design:type", Object)
|
||||
], RefreshToken.prototype, "revokedAt", void 0);
|
||||
__decorate([
|
||||
(0, typeorm_1.CreateDateColumn)({ name: 'created_at', type: 'timestamptz' }),
|
||||
__metadata("design:type", Date)
|
||||
], RefreshToken.prototype, "createdAt", void 0);
|
||||
__decorate([
|
||||
(0, typeorm_1.Column)({ name: 'user_agent', type: 'varchar', length: 500, nullable: true }),
|
||||
__metadata("design:type", Object)
|
||||
], RefreshToken.prototype, "userAgent", void 0);
|
||||
__decorate([
|
||||
(0, typeorm_1.Column)({ name: 'ip_address', type: 'varchar', length: 45, nullable: true }),
|
||||
__metadata("design:type", Object)
|
||||
], RefreshToken.prototype, "ipAddress", void 0);
|
||||
exports.RefreshToken = RefreshToken = __decorate([
|
||||
(0, typeorm_1.Entity)({ name: 'refresh_tokens', schema: 'auth' }),
|
||||
(0, typeorm_1.Index)(['userId', 'revokedAt']),
|
||||
(0, typeorm_1.Index)(['token'])
|
||||
], RefreshToken);
|
||||
//# sourceMappingURL=refresh-token.entity.js.map
|
||||
1
backend/dist/modules/auth/entities/refresh-token.entity.js.map
vendored
Normal file
1
backend/dist/modules/auth/entities/refresh-token.entity.js.map
vendored
Normal file
@ -0,0 +1 @@
|
||||
{"version":3,"file":"refresh-token.entity.js","sourceRoot":"","sources":["../../../../src/modules/auth/entities/refresh-token.entity.ts"],"names":[],"mappings":";AAAA;;;;;;;GAOG;;;;;;;;;;;;AAEH,qCAQiB;AACjB,iEAAuD;AAKhD,IAAM,YAAY,GAAlB,MAAM,YAAY;IAEvB,EAAE,CAAS;IAGX,MAAM,CAAS;IAIf,IAAI,CAAO;IAGX,KAAK,CAAS;IAGd,SAAS,CAAO;IAGhB,SAAS,CAAc;IAGvB,SAAS,CAAO;IAGhB,SAAS,CAAgB;IAGzB,SAAS,CAAgB;IAEzB;;OAEG;IACH,SAAS;QACP,OAAO,IAAI,CAAC,SAAS,GAAG,IAAI,IAAI,EAAE,CAAC;IACrC,CAAC;IAED;;OAEG;IACH,SAAS;QACP,OAAO,IAAI,CAAC,SAAS,KAAK,IAAI,CAAC;IACjC,CAAC;IAED;;OAEG;IACH,OAAO;QACL,OAAO,CAAC,IAAI,CAAC,SAAS,EAAE,IAAI,CAAC,IAAI,CAAC,SAAS,EAAE,CAAC;IAChD,CAAC;CACF,CAAA;AAjDY,oCAAY;AAEvB;IADC,IAAA,gCAAsB,EAAC,MAAM,CAAC;;wCACpB;AAGX;IADC,IAAA,gBAAM,EAAC,EAAE,IAAI,EAAE,SAAS,EAAE,IAAI,EAAE,MAAM,EAAE,CAAC;;4CAC3B;AAIf;IAFC,IAAA,mBAAS,EAAC,GAAG,EAAE,CAAC,kBAAI,EAAE,EAAE,QAAQ,EAAE,SAAS,EAAE,CAAC;IAC9C,IAAA,oBAAU,EAAC,EAAE,IAAI,EAAE,SAAS,EAAE,CAAC;8BAC1B,kBAAI;0CAAC;AAGX;IADC,IAAA,gBAAM,EAAC,EAAE,IAAI,EAAE,MAAM,EAAE,CAAC;;2CACX;AAGd;IADC,IAAA,gBAAM,EAAC,EAAE,IAAI,EAAE,YAAY,EAAE,IAAI,EAAE,aAAa,EAAE,CAAC;8BACzC,IAAI;+CAAC;AAGhB;IADC,IAAA,gBAAM,EAAC,EAAE,IAAI,EAAE,YAAY,EAAE,IAAI,EAAE,aAAa,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC;;+CAC7C;AAGvB;IADC,IAAA,0BAAgB,EAAC,EAAE,IAAI,EAAE,YAAY,EAAE,IAAI,EAAE,aAAa,EAAE,CAAC;8BACnD,IAAI;+CAAC;AAGhB;IADC,IAAA,gBAAM,EAAC,EAAE,IAAI,EAAE,YAAY,EAAE,IAAI,EAAE,SAAS,EAAE,MAAM,EAAE,GAAG,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC;;+CACpD;AAGzB;IADC,IAAA,gBAAM,EAAC,EAAE,IAAI,EAAE,YAAY,EAAE,IAAI,EAAE,SAAS,EAAE,MAAM,EAAE,EAAE,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC;;+CACnD;uBA3Bd,YAAY;IAHxB,IAAA,gBAAM,EAAC,EAAE,IAAI,EAAE,gBAAgB,EAAE,MAAM,EAAE,MAAM,EAAE,CAAC;IAClD,IAAA,eAAK,EAAC,CAAC,QAAQ,EAAE,WAAW,CAAC,CAAC;IAC9B,IAAA,eAAK,EAAC,CAAC,OAAO,CAAC,CAAC;GACJ,YAAY,CAiDxB"}
|
||||
21
backend/dist/modules/auth/entities/role.entity.d.ts
vendored
Normal file
21
backend/dist/modules/auth/entities/role.entity.d.ts
vendored
Normal file
@ -0,0 +1,21 @@
|
||||
/**
|
||||
* Role Entity
|
||||
* Roles del sistema para RBAC
|
||||
*
|
||||
* @module Auth
|
||||
*/
|
||||
import { Permission } from './permission.entity';
|
||||
import { UserRole } from './user-role.entity';
|
||||
export declare class Role {
|
||||
id: string;
|
||||
code: string;
|
||||
name: string;
|
||||
description: string;
|
||||
isSystem: boolean;
|
||||
isActive: boolean;
|
||||
createdAt: Date;
|
||||
updatedAt: Date;
|
||||
permissions: Permission[];
|
||||
userRoles: UserRole[];
|
||||
}
|
||||
//# sourceMappingURL=role.entity.d.ts.map
|
||||
1
backend/dist/modules/auth/entities/role.entity.d.ts.map
vendored
Normal file
1
backend/dist/modules/auth/entities/role.entity.d.ts.map
vendored
Normal file
@ -0,0 +1 @@
|
||||
{"version":3,"file":"role.entity.d.ts","sourceRoot":"","sources":["../../../../src/modules/auth/entities/role.entity.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAYH,OAAO,EAAE,UAAU,EAAE,MAAM,qBAAqB,CAAC;AACjD,OAAO,EAAE,QAAQ,EAAE,MAAM,oBAAoB,CAAC;AAE9C,qBACa,IAAI;IAEf,EAAE,EAAE,MAAM,CAAC;IAGX,IAAI,EAAE,MAAM,CAAC;IAGb,IAAI,EAAE,MAAM,CAAC;IAGb,WAAW,EAAE,MAAM,CAAC;IAGpB,QAAQ,EAAE,OAAO,CAAC;IAGlB,QAAQ,EAAE,OAAO,CAAC;IAGlB,SAAS,EAAE,IAAI,CAAC;IAGhB,SAAS,EAAE,IAAI,CAAC;IAShB,WAAW,EAAE,UAAU,EAAE,CAAC;IAG1B,SAAS,EAAE,QAAQ,EAAE,CAAC;CACvB"}
|
||||
84
backend/dist/modules/auth/entities/role.entity.js
vendored
Normal file
84
backend/dist/modules/auth/entities/role.entity.js
vendored
Normal file
@ -0,0 +1,84 @@
|
||||
"use strict";
|
||||
/**
|
||||
* Role Entity
|
||||
* Roles del sistema para RBAC
|
||||
*
|
||||
* @module Auth
|
||||
*/
|
||||
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
|
||||
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
||||
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
||||
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
||||
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
||||
};
|
||||
var __metadata = (this && this.__metadata) || function (k, v) {
|
||||
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
|
||||
};
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.Role = void 0;
|
||||
const typeorm_1 = require("typeorm");
|
||||
const permission_entity_1 = require("./permission.entity");
|
||||
const user_role_entity_1 = require("./user-role.entity");
|
||||
let Role = class Role {
|
||||
id;
|
||||
code;
|
||||
name;
|
||||
description;
|
||||
isSystem;
|
||||
isActive;
|
||||
createdAt;
|
||||
updatedAt;
|
||||
// Relations
|
||||
permissions;
|
||||
userRoles;
|
||||
};
|
||||
exports.Role = Role;
|
||||
__decorate([
|
||||
(0, typeorm_1.PrimaryGeneratedColumn)('uuid'),
|
||||
__metadata("design:type", String)
|
||||
], Role.prototype, "id", void 0);
|
||||
__decorate([
|
||||
(0, typeorm_1.Column)({ type: 'varchar', length: 50, unique: true }),
|
||||
__metadata("design:type", String)
|
||||
], Role.prototype, "code", void 0);
|
||||
__decorate([
|
||||
(0, typeorm_1.Column)({ type: 'varchar', length: 100 }),
|
||||
__metadata("design:type", String)
|
||||
], Role.prototype, "name", void 0);
|
||||
__decorate([
|
||||
(0, typeorm_1.Column)({ type: 'text', nullable: true }),
|
||||
__metadata("design:type", String)
|
||||
], Role.prototype, "description", void 0);
|
||||
__decorate([
|
||||
(0, typeorm_1.Column)({ name: 'is_system', type: 'boolean', default: false }),
|
||||
__metadata("design:type", Boolean)
|
||||
], Role.prototype, "isSystem", void 0);
|
||||
__decorate([
|
||||
(0, typeorm_1.Column)({ name: 'is_active', type: 'boolean', default: true }),
|
||||
__metadata("design:type", Boolean)
|
||||
], Role.prototype, "isActive", void 0);
|
||||
__decorate([
|
||||
(0, typeorm_1.CreateDateColumn)({ name: 'created_at', type: 'timestamptz' }),
|
||||
__metadata("design:type", Date)
|
||||
], Role.prototype, "createdAt", void 0);
|
||||
__decorate([
|
||||
(0, typeorm_1.UpdateDateColumn)({ name: 'updated_at', type: 'timestamptz' }),
|
||||
__metadata("design:type", Date)
|
||||
], Role.prototype, "updatedAt", void 0);
|
||||
__decorate([
|
||||
(0, typeorm_1.ManyToMany)(() => permission_entity_1.Permission),
|
||||
(0, typeorm_1.JoinTable)({
|
||||
name: 'role_permissions',
|
||||
joinColumn: { name: 'role_id', referencedColumnName: 'id' },
|
||||
inverseJoinColumn: { name: 'permission_id', referencedColumnName: 'id' },
|
||||
}),
|
||||
__metadata("design:type", Array)
|
||||
], Role.prototype, "permissions", void 0);
|
||||
__decorate([
|
||||
(0, typeorm_1.OneToMany)(() => user_role_entity_1.UserRole, (userRole) => userRole.role),
|
||||
__metadata("design:type", Array)
|
||||
], Role.prototype, "userRoles", void 0);
|
||||
exports.Role = Role = __decorate([
|
||||
(0, typeorm_1.Entity)({ schema: 'auth', name: 'roles' })
|
||||
], Role);
|
||||
//# sourceMappingURL=role.entity.js.map
|
||||
1
backend/dist/modules/auth/entities/role.entity.js.map
vendored
Normal file
1
backend/dist/modules/auth/entities/role.entity.js.map
vendored
Normal file
@ -0,0 +1 @@
|
||||
{"version":3,"file":"role.entity.js","sourceRoot":"","sources":["../../../../src/modules/auth/entities/role.entity.ts"],"names":[],"mappings":";AAAA;;;;;GAKG;;;;;;;;;;;;AAEH,qCASiB;AACjB,2DAAiD;AACjD,yDAA8C;AAGvC,IAAM,IAAI,GAAV,MAAM,IAAI;IAEf,EAAE,CAAS;IAGX,IAAI,CAAS;IAGb,IAAI,CAAS;IAGb,WAAW,CAAS;IAGpB,QAAQ,CAAU;IAGlB,QAAQ,CAAU;IAGlB,SAAS,CAAO;IAGhB,SAAS,CAAO;IAEhB,YAAY;IAOZ,WAAW,CAAe;IAG1B,SAAS,CAAa;CACvB,CAAA;AApCY,oBAAI;AAEf;IADC,IAAA,gCAAsB,EAAC,MAAM,CAAC;;gCACpB;AAGX;IADC,IAAA,gBAAM,EAAC,EAAE,IAAI,EAAE,SAAS,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,EAAE,IAAI,EAAE,CAAC;;kCACzC;AAGb;IADC,IAAA,gBAAM,EAAC,EAAE,IAAI,EAAE,SAAS,EAAE,MAAM,EAAE,GAAG,EAAE,CAAC;;kCAC5B;AAGb;IADC,IAAA,gBAAM,EAAC,EAAE,IAAI,EAAE,MAAM,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC;;yCACrB;AAGpB;IADC,IAAA,gBAAM,EAAC,EAAE,IAAI,EAAE,WAAW,EAAE,IAAI,EAAE,SAAS,EAAE,OAAO,EAAE,KAAK,EAAE,CAAC;;sCAC7C;AAGlB;IADC,IAAA,gBAAM,EAAC,EAAE,IAAI,EAAE,WAAW,EAAE,IAAI,EAAE,SAAS,EAAE,OAAO,EAAE,IAAI,EAAE,CAAC;;sCAC5C;AAGlB;IADC,IAAA,0BAAgB,EAAC,EAAE,IAAI,EAAE,YAAY,EAAE,IAAI,EAAE,aAAa,EAAE,CAAC;8BACnD,IAAI;uCAAC;AAGhB;IADC,IAAA,0BAAgB,EAAC,EAAE,IAAI,EAAE,YAAY,EAAE,IAAI,EAAE,aAAa,EAAE,CAAC;8BACnD,IAAI;uCAAC;AAShB;IANC,IAAA,oBAAU,EAAC,GAAG,EAAE,CAAC,8BAAU,CAAC;IAC5B,IAAA,mBAAS,EAAC;QACT,IAAI,EAAE,kBAAkB;QACxB,UAAU,EAAE,EAAE,IAAI,EAAE,SAAS,EAAE,oBAAoB,EAAE,IAAI,EAAE;QAC3D,iBAAiB,EAAE,EAAE,IAAI,EAAE,eAAe,EAAE,oBAAoB,EAAE,IAAI,EAAE;KACzE,CAAC;;yCACwB;AAG1B;IADC,IAAA,mBAAS,EAAC,GAAG,EAAE,CAAC,2BAAQ,EAAE,CAAC,QAAQ,EAAE,EAAE,CAAC,QAAQ,CAAC,IAAI,CAAC;;uCACjC;eAnCX,IAAI;IADhB,IAAA,gBAAM,EAAC,EAAE,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,OAAO,EAAE,CAAC;GAC7B,IAAI,CAoChB"}
|
||||
21
backend/dist/modules/auth/entities/user-role.entity.d.ts
vendored
Normal file
21
backend/dist/modules/auth/entities/user-role.entity.d.ts
vendored
Normal file
@ -0,0 +1,21 @@
|
||||
/**
|
||||
* UserRole Entity
|
||||
* Relación usuarios-roles con soporte multi-tenant
|
||||
*
|
||||
* @module Auth
|
||||
*/
|
||||
import { User } from '../../core/entities/user.entity';
|
||||
import { Role } from './role.entity';
|
||||
import { Tenant } from '../../core/entities/tenant.entity';
|
||||
export declare class UserRole {
|
||||
id: string;
|
||||
userId: string;
|
||||
roleId: string;
|
||||
tenantId: string;
|
||||
assignedBy: string;
|
||||
assignedAt: Date;
|
||||
user: User;
|
||||
role: Role;
|
||||
tenant: Tenant;
|
||||
}
|
||||
//# sourceMappingURL=user-role.entity.d.ts.map
|
||||
1
backend/dist/modules/auth/entities/user-role.entity.d.ts.map
vendored
Normal file
1
backend/dist/modules/auth/entities/user-role.entity.d.ts.map
vendored
Normal file
@ -0,0 +1 @@
|
||||
{"version":3,"file":"user-role.entity.d.ts","sourceRoot":"","sources":["../../../../src/modules/auth/entities/user-role.entity.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAWH,OAAO,EAAE,IAAI,EAAE,MAAM,iCAAiC,CAAC;AACvD,OAAO,EAAE,IAAI,EAAE,MAAM,eAAe,CAAC;AACrC,OAAO,EAAE,MAAM,EAAE,MAAM,mCAAmC,CAAC;AAE3D,qBAEa,QAAQ;IAEnB,EAAE,EAAE,MAAM,CAAC;IAGX,MAAM,EAAE,MAAM,CAAC;IAGf,MAAM,EAAE,MAAM,CAAC;IAGf,QAAQ,EAAE,MAAM,CAAC;IAGjB,UAAU,EAAE,MAAM,CAAC;IAGnB,UAAU,EAAE,IAAI,CAAC;IAKjB,IAAI,EAAE,IAAI,CAAC;IAIX,IAAI,EAAE,IAAI,CAAC;IAIX,MAAM,EAAE,MAAM,CAAC;CAChB"}
|
||||
79
backend/dist/modules/auth/entities/user-role.entity.js
vendored
Normal file
79
backend/dist/modules/auth/entities/user-role.entity.js
vendored
Normal file
@ -0,0 +1,79 @@
|
||||
"use strict";
|
||||
/**
|
||||
* UserRole Entity
|
||||
* Relación usuarios-roles con soporte multi-tenant
|
||||
*
|
||||
* @module Auth
|
||||
*/
|
||||
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
|
||||
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
||||
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
||||
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
||||
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
||||
};
|
||||
var __metadata = (this && this.__metadata) || function (k, v) {
|
||||
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
|
||||
};
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.UserRole = void 0;
|
||||
const typeorm_1 = require("typeorm");
|
||||
const user_entity_1 = require("../../core/entities/user.entity");
|
||||
const role_entity_1 = require("./role.entity");
|
||||
const tenant_entity_1 = require("../../core/entities/tenant.entity");
|
||||
let UserRole = class UserRole {
|
||||
id;
|
||||
userId;
|
||||
roleId;
|
||||
tenantId;
|
||||
assignedBy;
|
||||
assignedAt;
|
||||
// Relations
|
||||
user;
|
||||
role;
|
||||
tenant;
|
||||
};
|
||||
exports.UserRole = UserRole;
|
||||
__decorate([
|
||||
(0, typeorm_1.PrimaryGeneratedColumn)('uuid'),
|
||||
__metadata("design:type", String)
|
||||
], UserRole.prototype, "id", void 0);
|
||||
__decorate([
|
||||
(0, typeorm_1.Column)({ name: 'user_id', type: 'uuid' }),
|
||||
__metadata("design:type", String)
|
||||
], UserRole.prototype, "userId", void 0);
|
||||
__decorate([
|
||||
(0, typeorm_1.Column)({ name: 'role_id', type: 'uuid' }),
|
||||
__metadata("design:type", String)
|
||||
], UserRole.prototype, "roleId", void 0);
|
||||
__decorate([
|
||||
(0, typeorm_1.Column)({ name: 'tenant_id', type: 'uuid', nullable: true }),
|
||||
__metadata("design:type", String)
|
||||
], UserRole.prototype, "tenantId", void 0);
|
||||
__decorate([
|
||||
(0, typeorm_1.Column)({ name: 'assigned_by', type: 'uuid', nullable: true }),
|
||||
__metadata("design:type", String)
|
||||
], UserRole.prototype, "assignedBy", void 0);
|
||||
__decorate([
|
||||
(0, typeorm_1.CreateDateColumn)({ name: 'assigned_at', type: 'timestamptz' }),
|
||||
__metadata("design:type", Date)
|
||||
], UserRole.prototype, "assignedAt", void 0);
|
||||
__decorate([
|
||||
(0, typeorm_1.ManyToOne)(() => user_entity_1.User, { onDelete: 'CASCADE' }),
|
||||
(0, typeorm_1.JoinColumn)({ name: 'user_id' }),
|
||||
__metadata("design:type", user_entity_1.User)
|
||||
], UserRole.prototype, "user", void 0);
|
||||
__decorate([
|
||||
(0, typeorm_1.ManyToOne)(() => role_entity_1.Role, (role) => role.userRoles, { onDelete: 'CASCADE' }),
|
||||
(0, typeorm_1.JoinColumn)({ name: 'role_id' }),
|
||||
__metadata("design:type", role_entity_1.Role)
|
||||
], UserRole.prototype, "role", void 0);
|
||||
__decorate([
|
||||
(0, typeorm_1.ManyToOne)(() => tenant_entity_1.Tenant, { onDelete: 'CASCADE' }),
|
||||
(0, typeorm_1.JoinColumn)({ name: 'tenant_id' }),
|
||||
__metadata("design:type", tenant_entity_1.Tenant)
|
||||
], UserRole.prototype, "tenant", void 0);
|
||||
exports.UserRole = UserRole = __decorate([
|
||||
(0, typeorm_1.Entity)({ schema: 'auth', name: 'user_roles' }),
|
||||
(0, typeorm_1.Index)(['userId', 'roleId', 'tenantId'], { unique: true })
|
||||
], UserRole);
|
||||
//# sourceMappingURL=user-role.entity.js.map
|
||||
1
backend/dist/modules/auth/entities/user-role.entity.js.map
vendored
Normal file
1
backend/dist/modules/auth/entities/user-role.entity.js.map
vendored
Normal file
@ -0,0 +1 @@
|
||||
{"version":3,"file":"user-role.entity.js","sourceRoot":"","sources":["../../../../src/modules/auth/entities/user-role.entity.ts"],"names":[],"mappings":";AAAA;;;;;GAKG;;;;;;;;;;;;AAEH,qCAQiB;AACjB,iEAAuD;AACvD,+CAAqC;AACrC,qEAA2D;AAIpD,IAAM,QAAQ,GAAd,MAAM,QAAQ;IAEnB,EAAE,CAAS;IAGX,MAAM,CAAS;IAGf,MAAM,CAAS;IAGf,QAAQ,CAAS;IAGjB,UAAU,CAAS;IAGnB,UAAU,CAAO;IAEjB,YAAY;IAGZ,IAAI,CAAO;IAIX,IAAI,CAAO;IAIX,MAAM,CAAS;CAChB,CAAA;AA/BY,4BAAQ;AAEnB;IADC,IAAA,gCAAsB,EAAC,MAAM,CAAC;;oCACpB;AAGX;IADC,IAAA,gBAAM,EAAC,EAAE,IAAI,EAAE,SAAS,EAAE,IAAI,EAAE,MAAM,EAAE,CAAC;;wCAC3B;AAGf;IADC,IAAA,gBAAM,EAAC,EAAE,IAAI,EAAE,SAAS,EAAE,IAAI,EAAE,MAAM,EAAE,CAAC;;wCAC3B;AAGf;IADC,IAAA,gBAAM,EAAC,EAAE,IAAI,EAAE,WAAW,EAAE,IAAI,EAAE,MAAM,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC;;0CAC3C;AAGjB;IADC,IAAA,gBAAM,EAAC,EAAE,IAAI,EAAE,aAAa,EAAE,IAAI,EAAE,MAAM,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC;;4CAC3C;AAGnB;IADC,IAAA,0BAAgB,EAAC,EAAE,IAAI,EAAE,aAAa,EAAE,IAAI,EAAE,aAAa,EAAE,CAAC;8BACnD,IAAI;4CAAC;AAKjB;IAFC,IAAA,mBAAS,EAAC,GAAG,EAAE,CAAC,kBAAI,EAAE,EAAE,QAAQ,EAAE,SAAS,EAAE,CAAC;IAC9C,IAAA,oBAAU,EAAC,EAAE,IAAI,EAAE,SAAS,EAAE,CAAC;8BAC1B,kBAAI;sCAAC;AAIX;IAFC,IAAA,mBAAS,EAAC,GAAG,EAAE,CAAC,kBAAI,EAAE,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,SAAS,EAAE,EAAE,QAAQ,EAAE,SAAS,EAAE,CAAC;IACxE,IAAA,oBAAU,EAAC,EAAE,IAAI,EAAE,SAAS,EAAE,CAAC;8BAC1B,kBAAI;sCAAC;AAIX;IAFC,IAAA,mBAAS,EAAC,GAAG,EAAE,CAAC,sBAAM,EAAE,EAAE,QAAQ,EAAE,SAAS,EAAE,CAAC;IAChD,IAAA,oBAAU,EAAC,EAAE,IAAI,EAAE,WAAW,EAAE,CAAC;8BAC1B,sBAAM;wCAAC;mBA9BJ,QAAQ;IAFpB,IAAA,gBAAM,EAAC,EAAE,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,YAAY,EAAE,CAAC;IAC9C,IAAA,eAAK,EAAC,CAAC,QAAQ,EAAE,QAAQ,EAAE,UAAU,CAAC,EAAE,EAAE,MAAM,EAAE,IAAI,EAAE,CAAC;GAC7C,QAAQ,CA+BpB"}
|
||||
14
backend/dist/modules/auth/index.d.ts
vendored
Normal file
14
backend/dist/modules/auth/index.d.ts
vendored
Normal file
@ -0,0 +1,14 @@
|
||||
/**
|
||||
* Auth Module - Main Exports
|
||||
*
|
||||
* Módulo de autenticación con JWT y refresh tokens.
|
||||
* Implementa multi-tenancy con RLS.
|
||||
*
|
||||
* @module Auth
|
||||
*/
|
||||
export * from './dto/auth.dto';
|
||||
export { RefreshToken } from './entities/refresh-token.entity';
|
||||
export { AuthService } from './services/auth.service';
|
||||
export { AuthMiddleware, createAuthMiddleware } from './middleware/auth.middleware';
|
||||
export { createAuthController } from './controllers/auth.controller';
|
||||
//# sourceMappingURL=index.d.ts.map
|
||||
1
backend/dist/modules/auth/index.d.ts.map
vendored
Normal file
1
backend/dist/modules/auth/index.d.ts.map
vendored
Normal file
@ -0,0 +1 @@
|
||||
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/modules/auth/index.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,cAAc,gBAAgB,CAAC;AAC/B,OAAO,EAAE,YAAY,EAAE,MAAM,iCAAiC,CAAC;AAC/D,OAAO,EAAE,WAAW,EAAE,MAAM,yBAAyB,CAAC;AACtD,OAAO,EAAE,cAAc,EAAE,oBAAoB,EAAE,MAAM,8BAA8B,CAAC;AACpF,OAAO,EAAE,oBAAoB,EAAE,MAAM,+BAA+B,CAAC"}
|
||||
36
backend/dist/modules/auth/index.js
vendored
Normal file
36
backend/dist/modules/auth/index.js
vendored
Normal file
@ -0,0 +1,36 @@
|
||||
"use strict";
|
||||
/**
|
||||
* Auth Module - Main Exports
|
||||
*
|
||||
* Módulo de autenticación con JWT y refresh tokens.
|
||||
* Implementa multi-tenancy con RLS.
|
||||
*
|
||||
* @module Auth
|
||||
*/
|
||||
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
||||
if (k2 === undefined) k2 = k;
|
||||
var desc = Object.getOwnPropertyDescriptor(m, k);
|
||||
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
||||
desc = { enumerable: true, get: function() { return m[k]; } };
|
||||
}
|
||||
Object.defineProperty(o, k2, desc);
|
||||
}) : (function(o, m, k, k2) {
|
||||
if (k2 === undefined) k2 = k;
|
||||
o[k2] = m[k];
|
||||
}));
|
||||
var __exportStar = (this && this.__exportStar) || function(m, exports) {
|
||||
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
|
||||
};
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.createAuthController = exports.createAuthMiddleware = exports.AuthMiddleware = exports.AuthService = exports.RefreshToken = void 0;
|
||||
__exportStar(require("./dto/auth.dto"), exports);
|
||||
var refresh_token_entity_1 = require("./entities/refresh-token.entity");
|
||||
Object.defineProperty(exports, "RefreshToken", { enumerable: true, get: function () { return refresh_token_entity_1.RefreshToken; } });
|
||||
var auth_service_1 = require("./services/auth.service");
|
||||
Object.defineProperty(exports, "AuthService", { enumerable: true, get: function () { return auth_service_1.AuthService; } });
|
||||
var auth_middleware_1 = require("./middleware/auth.middleware");
|
||||
Object.defineProperty(exports, "AuthMiddleware", { enumerable: true, get: function () { return auth_middleware_1.AuthMiddleware; } });
|
||||
Object.defineProperty(exports, "createAuthMiddleware", { enumerable: true, get: function () { return auth_middleware_1.createAuthMiddleware; } });
|
||||
var auth_controller_1 = require("./controllers/auth.controller");
|
||||
Object.defineProperty(exports, "createAuthController", { enumerable: true, get: function () { return auth_controller_1.createAuthController; } });
|
||||
//# sourceMappingURL=index.js.map
|
||||
1
backend/dist/modules/auth/index.js.map
vendored
Normal file
1
backend/dist/modules/auth/index.js.map
vendored
Normal file
@ -0,0 +1 @@
|
||||
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/modules/auth/index.ts"],"names":[],"mappings":";AAAA;;;;;;;GAOG;;;;;;;;;;;;;;;;;AAEH,iDAA+B;AAC/B,wEAA+D;AAAtD,oHAAA,YAAY,OAAA;AACrB,wDAAsD;AAA7C,2GAAA,WAAW,OAAA;AACpB,gEAAoF;AAA3E,iHAAA,cAAc,OAAA;AAAE,uHAAA,oBAAoB,OAAA;AAC7C,iEAAqE;AAA5D,uHAAA,oBAAoB,OAAA"}
|
||||
58
backend/dist/modules/auth/middleware/auth.middleware.d.ts
vendored
Normal file
58
backend/dist/modules/auth/middleware/auth.middleware.d.ts
vendored
Normal file
@ -0,0 +1,58 @@
|
||||
/**
|
||||
* Auth Middleware - Middleware de Autenticación
|
||||
*
|
||||
* Middleware para Express que valida JWT y extrae información del usuario.
|
||||
* Configura el tenant_id para RLS en PostgreSQL.
|
||||
*
|
||||
* @module Auth
|
||||
*/
|
||||
import { Request, Response, NextFunction } from 'express';
|
||||
import { DataSource } from 'typeorm';
|
||||
import { AuthService } from '../services/auth.service';
|
||||
import { TokenPayload } from '../dto/auth.dto';
|
||||
declare global {
|
||||
namespace Express {
|
||||
interface Request {
|
||||
user?: TokenPayload;
|
||||
tenantId?: string;
|
||||
}
|
||||
}
|
||||
}
|
||||
export declare class AuthMiddleware {
|
||||
private readonly authService;
|
||||
private readonly dataSource;
|
||||
constructor(authService: AuthService, dataSource: DataSource);
|
||||
/**
|
||||
* Middleware de autenticación requerida
|
||||
*/
|
||||
authenticate: (req: Request, res: Response, next: NextFunction) => Promise<void>;
|
||||
/**
|
||||
* Middleware de autenticación opcional
|
||||
*/
|
||||
optionalAuthenticate: (req: Request, _res: Response, next: NextFunction) => Promise<void>;
|
||||
/**
|
||||
* Middleware de autorización por roles
|
||||
*/
|
||||
authorize: (...allowedRoles: string[]) => (req: Request, res: Response, next: NextFunction) => void;
|
||||
/**
|
||||
* Middleware que requiere rol de admin
|
||||
*/
|
||||
requireAdmin: (req: Request, res: Response, next: NextFunction) => void;
|
||||
/**
|
||||
* Middleware que requiere ser supervisor
|
||||
*/
|
||||
requireSupervisor: (req: Request, res: Response, next: NextFunction) => void;
|
||||
/**
|
||||
* Extraer token del header Authorization
|
||||
*/
|
||||
private extractToken;
|
||||
/**
|
||||
* Configurar contexto de tenant para RLS
|
||||
*/
|
||||
private setTenantContext;
|
||||
}
|
||||
/**
|
||||
* Factory para crear middleware de autenticación
|
||||
*/
|
||||
export declare function createAuthMiddleware(authService: AuthService, dataSource: DataSource): AuthMiddleware;
|
||||
//# sourceMappingURL=auth.middleware.d.ts.map
|
||||
1
backend/dist/modules/auth/middleware/auth.middleware.d.ts.map
vendored
Normal file
1
backend/dist/modules/auth/middleware/auth.middleware.d.ts.map
vendored
Normal file
@ -0,0 +1 @@
|
||||
{"version":3,"file":"auth.middleware.d.ts","sourceRoot":"","sources":["../../../../src/modules/auth/middleware/auth.middleware.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,EAAE,OAAO,EAAE,QAAQ,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AAC1D,OAAO,EAAE,UAAU,EAAE,MAAM,SAAS,CAAC;AACrC,OAAO,EAAE,WAAW,EAAE,MAAM,0BAA0B,CAAC;AACvD,OAAO,EAAE,YAAY,EAAE,MAAM,iBAAiB,CAAC;AAG/C,OAAO,CAAC,MAAM,CAAC;IACb,UAAU,OAAO,CAAC;QAChB,UAAU,OAAO;YACf,IAAI,CAAC,EAAE,YAAY,CAAC;YACpB,QAAQ,CAAC,EAAE,MAAM,CAAC;SACnB;KACF;CACF;AAED,qBAAa,cAAc;IAEvB,OAAO,CAAC,QAAQ,CAAC,WAAW;IAC5B,OAAO,CAAC,QAAQ,CAAC,UAAU;gBADV,WAAW,EAAE,WAAW,EACxB,UAAU,EAAE,UAAU;IAGzC;;OAEG;IACH,YAAY,GAAU,KAAK,OAAO,EAAE,KAAK,QAAQ,EAAE,MAAM,YAAY,KAAG,OAAO,CAAC,IAAI,CAAC,CAoCnF;IAEF;;OAEG;IACH,oBAAoB,GAAU,KAAK,OAAO,EAAE,MAAM,QAAQ,EAAE,MAAM,YAAY,KAAG,OAAO,CAAC,IAAI,CAAC,CAmB5F;IAEF;;OAEG;IACH,SAAS,GAAI,GAAG,cAAc,MAAM,EAAE,MAC5B,KAAK,OAAO,EAAE,KAAK,QAAQ,EAAE,MAAM,YAAY,KAAG,IAAI,CAqB9D;IAEF;;OAEG;IACH,YAAY,GAAI,KAAK,OAAO,EAAE,KAAK,QAAQ,EAAE,MAAM,YAAY,KAAG,IAAI,CAEpE;IAEF;;OAEG;IACH,iBAAiB,GAAI,KAAK,OAAO,EAAE,KAAK,QAAQ,EAAE,MAAM,YAAY,KAAG,IAAI,CAEzE;IAEF;;OAEG;IACH,OAAO,CAAC,YAAY;IAiBpB;;OAEG;YACW,gBAAgB;CAQ/B;AAED;;GAEG;AACH,wBAAgB,oBAAoB,CAClC,WAAW,EAAE,WAAW,EACxB,UAAU,EAAE,UAAU,GACrB,cAAc,CAEhB"}
|
||||
146
backend/dist/modules/auth/middleware/auth.middleware.js
vendored
Normal file
146
backend/dist/modules/auth/middleware/auth.middleware.js
vendored
Normal file
@ -0,0 +1,146 @@
|
||||
"use strict";
|
||||
/**
|
||||
* Auth Middleware - Middleware de Autenticación
|
||||
*
|
||||
* Middleware para Express que valida JWT y extrae información del usuario.
|
||||
* Configura el tenant_id para RLS en PostgreSQL.
|
||||
*
|
||||
* @module Auth
|
||||
*/
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.AuthMiddleware = void 0;
|
||||
exports.createAuthMiddleware = createAuthMiddleware;
|
||||
class AuthMiddleware {
|
||||
authService;
|
||||
dataSource;
|
||||
constructor(authService, dataSource) {
|
||||
this.authService = authService;
|
||||
this.dataSource = dataSource;
|
||||
}
|
||||
/**
|
||||
* Middleware de autenticación requerida
|
||||
*/
|
||||
authenticate = async (req, res, next) => {
|
||||
try {
|
||||
const token = this.extractToken(req);
|
||||
if (!token) {
|
||||
res.status(401).json({
|
||||
error: 'Unauthorized',
|
||||
message: 'No token provided',
|
||||
});
|
||||
return;
|
||||
}
|
||||
const validation = this.authService.validateAccessToken(token);
|
||||
if (!validation.valid || !validation.payload) {
|
||||
res.status(401).json({
|
||||
error: 'Unauthorized',
|
||||
message: validation.error || 'Invalid token',
|
||||
});
|
||||
return;
|
||||
}
|
||||
// Establecer información en el request
|
||||
req.user = validation.payload;
|
||||
req.tenantId = validation.payload.tenantId;
|
||||
// Configurar tenant_id para RLS en PostgreSQL
|
||||
await this.setTenantContext(validation.payload.tenantId);
|
||||
next();
|
||||
}
|
||||
catch (error) {
|
||||
res.status(401).json({
|
||||
error: 'Unauthorized',
|
||||
message: 'Authentication failed',
|
||||
});
|
||||
}
|
||||
};
|
||||
/**
|
||||
* Middleware de autenticación opcional
|
||||
*/
|
||||
optionalAuthenticate = async (req, _res, next) => {
|
||||
try {
|
||||
const token = this.extractToken(req);
|
||||
if (token) {
|
||||
const validation = this.authService.validateAccessToken(token);
|
||||
if (validation.valid && validation.payload) {
|
||||
req.user = validation.payload;
|
||||
req.tenantId = validation.payload.tenantId;
|
||||
await this.setTenantContext(validation.payload.tenantId);
|
||||
}
|
||||
}
|
||||
next();
|
||||
}
|
||||
catch {
|
||||
// Si hay error, continuar sin autenticación
|
||||
next();
|
||||
}
|
||||
};
|
||||
/**
|
||||
* Middleware de autorización por roles
|
||||
*/
|
||||
authorize = (...allowedRoles) => {
|
||||
return (req, res, next) => {
|
||||
if (!req.user) {
|
||||
res.status(401).json({
|
||||
error: 'Unauthorized',
|
||||
message: 'Authentication required',
|
||||
});
|
||||
return;
|
||||
}
|
||||
const hasRole = req.user.roles.some((role) => allowedRoles.includes(role));
|
||||
if (!hasRole) {
|
||||
res.status(403).json({
|
||||
error: 'Forbidden',
|
||||
message: 'Insufficient permissions',
|
||||
});
|
||||
return;
|
||||
}
|
||||
next();
|
||||
};
|
||||
};
|
||||
/**
|
||||
* Middleware que requiere rol de admin
|
||||
*/
|
||||
requireAdmin = (req, res, next) => {
|
||||
return this.authorize('admin', 'super_admin')(req, res, next);
|
||||
};
|
||||
/**
|
||||
* Middleware que requiere ser supervisor
|
||||
*/
|
||||
requireSupervisor = (req, res, next) => {
|
||||
return this.authorize('admin', 'super_admin', 'supervisor_obra', 'supervisor_hse')(req, res, next);
|
||||
};
|
||||
/**
|
||||
* Extraer token del header Authorization
|
||||
*/
|
||||
extractToken(req) {
|
||||
const authHeader = req.headers.authorization;
|
||||
if (!authHeader) {
|
||||
return null;
|
||||
}
|
||||
// Bearer token
|
||||
const [type, token] = authHeader.split(' ');
|
||||
if (type !== 'Bearer' || !token) {
|
||||
return null;
|
||||
}
|
||||
return token;
|
||||
}
|
||||
/**
|
||||
* Configurar contexto de tenant para RLS
|
||||
*/
|
||||
async setTenantContext(tenantId) {
|
||||
try {
|
||||
await this.dataSource.query(`SET app.current_tenant_id = '${tenantId}'`);
|
||||
}
|
||||
catch (error) {
|
||||
console.error('Error setting tenant context:', error);
|
||||
throw new Error('Failed to set tenant context');
|
||||
}
|
||||
}
|
||||
}
|
||||
exports.AuthMiddleware = AuthMiddleware;
|
||||
/**
|
||||
* Factory para crear middleware de autenticación
|
||||
*/
|
||||
function createAuthMiddleware(authService, dataSource) {
|
||||
return new AuthMiddleware(authService, dataSource);
|
||||
}
|
||||
//# sourceMappingURL=auth.middleware.js.map
|
||||
1
backend/dist/modules/auth/middleware/auth.middleware.js.map
vendored
Normal file
1
backend/dist/modules/auth/middleware/auth.middleware.js.map
vendored
Normal file
@ -0,0 +1 @@
|
||||
{"version":3,"file":"auth.middleware.js","sourceRoot":"","sources":["../../../../src/modules/auth/middleware/auth.middleware.ts"],"names":[],"mappings":"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"}
|
||||
69
backend/dist/modules/auth/services/auth.service.d.ts
vendored
Normal file
69
backend/dist/modules/auth/services/auth.service.d.ts
vendored
Normal file
@ -0,0 +1,69 @@
|
||||
/**
|
||||
* AuthService - Servicio de Autenticación
|
||||
*
|
||||
* Gestiona login, logout, refresh tokens y validación de JWT.
|
||||
* Implementa patrón multi-tenant con verificación de tenant_id.
|
||||
*
|
||||
* @module Auth
|
||||
*/
|
||||
import { Repository } from 'typeorm';
|
||||
import { User } from '../../core/entities/user.entity';
|
||||
import { Tenant } from '../../core/entities/tenant.entity';
|
||||
import { LoginDto, RegisterDto, RefreshTokenDto, ChangePasswordDto, AuthResponse, TokenValidationResult } from '../dto/auth.dto';
|
||||
export interface RefreshToken {
|
||||
id: string;
|
||||
userId: string;
|
||||
token: string;
|
||||
expiresAt: Date;
|
||||
revokedAt?: Date;
|
||||
}
|
||||
export declare class AuthService {
|
||||
private readonly userRepository;
|
||||
private readonly tenantRepository;
|
||||
private readonly refreshTokenRepository;
|
||||
private readonly jwtSecret;
|
||||
private readonly jwtExpiresIn;
|
||||
private readonly jwtRefreshExpiresIn;
|
||||
constructor(userRepository: Repository<User>, tenantRepository: Repository<Tenant>, refreshTokenRepository: Repository<RefreshToken>);
|
||||
/**
|
||||
* Login de usuario
|
||||
*/
|
||||
login(dto: LoginDto): Promise<AuthResponse>;
|
||||
/**
|
||||
* Registro de usuario
|
||||
*/
|
||||
register(dto: RegisterDto): Promise<AuthResponse>;
|
||||
/**
|
||||
* Refresh de token
|
||||
*/
|
||||
refresh(dto: RefreshTokenDto): Promise<AuthResponse>;
|
||||
/**
|
||||
* Logout - Revocar refresh token
|
||||
*/
|
||||
logout(refreshToken: string): Promise<void>;
|
||||
/**
|
||||
* Cambiar password
|
||||
*/
|
||||
changePassword(userId: string, dto: ChangePasswordDto): Promise<void>;
|
||||
/**
|
||||
* Validar access token
|
||||
*/
|
||||
validateAccessToken(token: string): TokenValidationResult;
|
||||
/**
|
||||
* Validar token
|
||||
*/
|
||||
private validateToken;
|
||||
/**
|
||||
* Generar access token
|
||||
*/
|
||||
private generateAccessToken;
|
||||
/**
|
||||
* Generar refresh token
|
||||
*/
|
||||
private generateRefreshToken;
|
||||
/**
|
||||
* Convertir expiresIn a segundos
|
||||
*/
|
||||
private getExpiresInSeconds;
|
||||
}
|
||||
//# sourceMappingURL=auth.service.d.ts.map
|
||||
1
backend/dist/modules/auth/services/auth.service.d.ts.map
vendored
Normal file
1
backend/dist/modules/auth/services/auth.service.d.ts.map
vendored
Normal file
@ -0,0 +1 @@
|
||||
{"version":3,"file":"auth.service.d.ts","sourceRoot":"","sources":["../../../../src/modules/auth/services/auth.service.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAIH,OAAO,EAAE,UAAU,EAAE,MAAM,SAAS,CAAC;AACrC,OAAO,EAAE,IAAI,EAAE,MAAM,iCAAiC,CAAC;AACvD,OAAO,EAAE,MAAM,EAAE,MAAM,mCAAmC,CAAC;AAC3D,OAAO,EACL,QAAQ,EACR,WAAW,EACX,eAAe,EACf,iBAAiB,EAEjB,YAAY,EACZ,qBAAqB,EACtB,MAAM,iBAAiB,CAAC;AAEzB,MAAM,WAAW,YAAY;IAC3B,EAAE,EAAE,MAAM,CAAC;IACX,MAAM,EAAE,MAAM,CAAC;IACf,KAAK,EAAE,MAAM,CAAC;IACd,SAAS,EAAE,IAAI,CAAC;IAChB,SAAS,CAAC,EAAE,IAAI,CAAC;CAClB;AAED,qBAAa,WAAW;IAMpB,OAAO,CAAC,QAAQ,CAAC,cAAc;IAC/B,OAAO,CAAC,QAAQ,CAAC,gBAAgB;IACjC,OAAO,CAAC,QAAQ,CAAC,sBAAsB;IAPzC,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAS;IACnC,OAAO,CAAC,QAAQ,CAAC,YAAY,CAAS;IACtC,OAAO,CAAC,QAAQ,CAAC,mBAAmB,CAAS;gBAG1B,cAAc,EAAE,UAAU,CAAC,IAAI,CAAC,EAChC,gBAAgB,EAAE,UAAU,CAAC,MAAM,CAAC,EACpC,sBAAsB,EAAE,UAAU,CAAC,YAAY,CAAC;IAOnE;;OAEG;IACG,KAAK,CAAC,GAAG,EAAE,QAAQ,GAAG,OAAO,CAAC,YAAY,CAAC;IAgEjD;;OAEG;IACG,QAAQ,CAAC,GAAG,EAAE,WAAW,GAAG,OAAO,CAAC,YAAY,CAAC;IAyDvD;;OAEG;IACG,OAAO,CAAC,GAAG,EAAE,eAAe,GAAG,OAAO,CAAC,YAAY,CAAC;IA8D1D;;OAEG;IACG,MAAM,CAAC,YAAY,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAOjD;;OAEG;IACG,cAAc,CAAC,MAAM,EAAE,MAAM,EAAE,GAAG,EAAE,iBAAiB,GAAG,OAAO,CAAC,IAAI,CAAC;IAwB3E;;OAEG;IACH,mBAAmB,CAAC,KAAK,EAAE,MAAM,GAAG,qBAAqB;IAIzD;;OAEG;IACH,OAAO,CAAC,aAAa;IAoBrB;;OAEG;IACH,OAAO,CAAC,mBAAmB;IAc3B;;OAEG;YACW,oBAAoB;IAyBlC;;OAEG;IACH,OAAO,CAAC,mBAAmB;CAe5B"}
|
||||
329
backend/dist/modules/auth/services/auth.service.js
vendored
Normal file
329
backend/dist/modules/auth/services/auth.service.js
vendored
Normal file
@ -0,0 +1,329 @@
|
||||
"use strict";
|
||||
/**
|
||||
* AuthService - Servicio de Autenticación
|
||||
*
|
||||
* Gestiona login, logout, refresh tokens y validación de JWT.
|
||||
* Implementa patrón multi-tenant con verificación de tenant_id.
|
||||
*
|
||||
* @module Auth
|
||||
*/
|
||||
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
||||
if (k2 === undefined) k2 = k;
|
||||
var desc = Object.getOwnPropertyDescriptor(m, k);
|
||||
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
||||
desc = { enumerable: true, get: function() { return m[k]; } };
|
||||
}
|
||||
Object.defineProperty(o, k2, desc);
|
||||
}) : (function(o, m, k, k2) {
|
||||
if (k2 === undefined) k2 = k;
|
||||
o[k2] = m[k];
|
||||
}));
|
||||
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
||||
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
||||
}) : function(o, v) {
|
||||
o["default"] = v;
|
||||
});
|
||||
var __importStar = (this && this.__importStar) || (function () {
|
||||
var ownKeys = function(o) {
|
||||
ownKeys = Object.getOwnPropertyNames || function (o) {
|
||||
var ar = [];
|
||||
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
||||
return ar;
|
||||
};
|
||||
return ownKeys(o);
|
||||
};
|
||||
return function (mod) {
|
||||
if (mod && mod.__esModule) return mod;
|
||||
var result = {};
|
||||
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
||||
__setModuleDefault(result, mod);
|
||||
return result;
|
||||
};
|
||||
})();
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.AuthService = void 0;
|
||||
const jwt = __importStar(require("jsonwebtoken"));
|
||||
const bcrypt = __importStar(require("bcryptjs"));
|
||||
class AuthService {
|
||||
userRepository;
|
||||
tenantRepository;
|
||||
refreshTokenRepository;
|
||||
jwtSecret;
|
||||
jwtExpiresIn;
|
||||
jwtRefreshExpiresIn;
|
||||
constructor(userRepository, tenantRepository, refreshTokenRepository) {
|
||||
this.userRepository = userRepository;
|
||||
this.tenantRepository = tenantRepository;
|
||||
this.refreshTokenRepository = refreshTokenRepository;
|
||||
this.jwtSecret = process.env.JWT_SECRET || 'your-super-secret-jwt-key-change-in-production-minimum-32-chars';
|
||||
this.jwtExpiresIn = process.env.JWT_EXPIRES_IN || '1d';
|
||||
this.jwtRefreshExpiresIn = process.env.JWT_REFRESH_EXPIRES_IN || '7d';
|
||||
}
|
||||
/**
|
||||
* Login de usuario
|
||||
*/
|
||||
async login(dto) {
|
||||
// Buscar usuario por email
|
||||
const user = await this.userRepository.findOne({
|
||||
where: { email: dto.email, deletedAt: null },
|
||||
relations: ['userRoles', 'userRoles.role'],
|
||||
});
|
||||
if (!user) {
|
||||
throw new Error('Invalid credentials');
|
||||
}
|
||||
// Verificar password
|
||||
const isPasswordValid = await bcrypt.compare(dto.password, user.passwordHash);
|
||||
if (!isPasswordValid) {
|
||||
throw new Error('Invalid credentials');
|
||||
}
|
||||
// Verificar que el usuario esté activo
|
||||
if (!user.isActive) {
|
||||
throw new Error('User is not active');
|
||||
}
|
||||
// Obtener tenant
|
||||
const tenantId = dto.tenantId || user.defaultTenantId;
|
||||
if (!tenantId) {
|
||||
throw new Error('No tenant specified');
|
||||
}
|
||||
const tenant = await this.tenantRepository.findOne({
|
||||
where: { id: tenantId, isActive: true, deletedAt: null },
|
||||
});
|
||||
if (!tenant) {
|
||||
throw new Error('Tenant not found or inactive');
|
||||
}
|
||||
// Obtener roles del usuario
|
||||
const roles = user.userRoles?.map((ur) => ur.role.code) || [];
|
||||
// Generar tokens
|
||||
const accessToken = this.generateAccessToken(user, tenantId, roles);
|
||||
const refreshToken = await this.generateRefreshToken(user.id);
|
||||
// Actualizar último login
|
||||
await this.userRepository.update(user.id, { lastLoginAt: new Date() });
|
||||
return {
|
||||
accessToken,
|
||||
refreshToken,
|
||||
expiresIn: this.getExpiresInSeconds(this.jwtExpiresIn),
|
||||
user: {
|
||||
id: user.id,
|
||||
email: user.email,
|
||||
firstName: user.firstName,
|
||||
lastName: user.lastName,
|
||||
roles,
|
||||
},
|
||||
tenant: {
|
||||
id: tenant.id,
|
||||
name: tenant.name,
|
||||
},
|
||||
};
|
||||
}
|
||||
/**
|
||||
* Registro de usuario
|
||||
*/
|
||||
async register(dto) {
|
||||
// Verificar si el email ya existe
|
||||
const existingUser = await this.userRepository.findOne({
|
||||
where: { email: dto.email },
|
||||
});
|
||||
if (existingUser) {
|
||||
throw new Error('Email already registered');
|
||||
}
|
||||
// Verificar que el tenant existe
|
||||
const tenant = await this.tenantRepository.findOne({
|
||||
where: { id: dto.tenantId, isActive: true },
|
||||
});
|
||||
if (!tenant) {
|
||||
throw new Error('Tenant not found');
|
||||
}
|
||||
// Hash del password
|
||||
const passwordHash = await bcrypt.hash(dto.password, 12);
|
||||
// Crear usuario
|
||||
const user = await this.userRepository.save(this.userRepository.create({
|
||||
email: dto.email,
|
||||
passwordHash,
|
||||
firstName: dto.firstName,
|
||||
lastName: dto.lastName,
|
||||
defaultTenantId: dto.tenantId,
|
||||
isActive: true,
|
||||
}));
|
||||
// Generar tokens (rol default: user)
|
||||
const roles = ['user'];
|
||||
const accessToken = this.generateAccessToken(user, dto.tenantId, roles);
|
||||
const refreshToken = await this.generateRefreshToken(user.id);
|
||||
return {
|
||||
accessToken,
|
||||
refreshToken,
|
||||
expiresIn: this.getExpiresInSeconds(this.jwtExpiresIn),
|
||||
user: {
|
||||
id: user.id,
|
||||
email: user.email,
|
||||
firstName: user.firstName,
|
||||
lastName: user.lastName,
|
||||
roles,
|
||||
},
|
||||
tenant: {
|
||||
id: tenant.id,
|
||||
name: tenant.name,
|
||||
},
|
||||
};
|
||||
}
|
||||
/**
|
||||
* Refresh de token
|
||||
*/
|
||||
async refresh(dto) {
|
||||
// Validar refresh token
|
||||
const validation = this.validateToken(dto.refreshToken, 'refresh');
|
||||
if (!validation.valid || !validation.payload) {
|
||||
throw new Error('Invalid refresh token');
|
||||
}
|
||||
// Verificar que el token no está revocado
|
||||
const storedToken = await this.refreshTokenRepository.findOne({
|
||||
where: { token: dto.refreshToken, revokedAt: null },
|
||||
});
|
||||
if (!storedToken || storedToken.expiresAt < new Date()) {
|
||||
throw new Error('Refresh token expired or revoked');
|
||||
}
|
||||
// Obtener usuario
|
||||
const user = await this.userRepository.findOne({
|
||||
where: { id: validation.payload.sub, deletedAt: null },
|
||||
relations: ['userRoles', 'userRoles.role'],
|
||||
});
|
||||
if (!user || !user.isActive) {
|
||||
throw new Error('User not found or inactive');
|
||||
}
|
||||
// Obtener tenant
|
||||
const tenant = await this.tenantRepository.findOne({
|
||||
where: { id: validation.payload.tenantId, isActive: true },
|
||||
});
|
||||
if (!tenant) {
|
||||
throw new Error('Tenant not found or inactive');
|
||||
}
|
||||
const roles = user.userRoles?.map((ur) => ur.role.code) || [];
|
||||
// Revocar token anterior
|
||||
await this.refreshTokenRepository.update(storedToken.id, { revokedAt: new Date() });
|
||||
// Generar nuevos tokens
|
||||
const accessToken = this.generateAccessToken(user, tenant.id, roles);
|
||||
const refreshToken = await this.generateRefreshToken(user.id);
|
||||
return {
|
||||
accessToken,
|
||||
refreshToken,
|
||||
expiresIn: this.getExpiresInSeconds(this.jwtExpiresIn),
|
||||
user: {
|
||||
id: user.id,
|
||||
email: user.email,
|
||||
firstName: user.firstName,
|
||||
lastName: user.lastName,
|
||||
roles,
|
||||
},
|
||||
tenant: {
|
||||
id: tenant.id,
|
||||
name: tenant.name,
|
||||
},
|
||||
};
|
||||
}
|
||||
/**
|
||||
* Logout - Revocar refresh token
|
||||
*/
|
||||
async logout(refreshToken) {
|
||||
await this.refreshTokenRepository.update({ token: refreshToken }, { revokedAt: new Date() });
|
||||
}
|
||||
/**
|
||||
* Cambiar password
|
||||
*/
|
||||
async changePassword(userId, dto) {
|
||||
const user = await this.userRepository.findOne({
|
||||
where: { id: userId },
|
||||
});
|
||||
if (!user) {
|
||||
throw new Error('User not found');
|
||||
}
|
||||
const isCurrentValid = await bcrypt.compare(dto.currentPassword, user.passwordHash);
|
||||
if (!isCurrentValid) {
|
||||
throw new Error('Current password is incorrect');
|
||||
}
|
||||
const newPasswordHash = await bcrypt.hash(dto.newPassword, 12);
|
||||
await this.userRepository.update(userId, { passwordHash: newPasswordHash });
|
||||
// Revocar todos los refresh tokens del usuario
|
||||
await this.refreshTokenRepository.update({ userId }, { revokedAt: new Date() });
|
||||
}
|
||||
/**
|
||||
* Validar access token
|
||||
*/
|
||||
validateAccessToken(token) {
|
||||
return this.validateToken(token, 'access');
|
||||
}
|
||||
/**
|
||||
* Validar token
|
||||
*/
|
||||
validateToken(token, expectedType) {
|
||||
try {
|
||||
const payload = jwt.verify(token, this.jwtSecret);
|
||||
if (payload.type !== expectedType) {
|
||||
return { valid: false, error: 'Invalid token type' };
|
||||
}
|
||||
return { valid: true, payload };
|
||||
}
|
||||
catch (error) {
|
||||
if (error instanceof jwt.TokenExpiredError) {
|
||||
return { valid: false, error: 'Token expired' };
|
||||
}
|
||||
if (error instanceof jwt.JsonWebTokenError) {
|
||||
return { valid: false, error: 'Invalid token' };
|
||||
}
|
||||
return { valid: false, error: 'Token validation failed' };
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Generar access token
|
||||
*/
|
||||
generateAccessToken(user, tenantId, roles) {
|
||||
const payload = {
|
||||
sub: user.id,
|
||||
email: user.email,
|
||||
tenantId,
|
||||
roles,
|
||||
type: 'access',
|
||||
};
|
||||
return jwt.sign(payload, this.jwtSecret, {
|
||||
expiresIn: this.jwtExpiresIn,
|
||||
});
|
||||
}
|
||||
/**
|
||||
* Generar refresh token
|
||||
*/
|
||||
async generateRefreshToken(userId) {
|
||||
const payload = {
|
||||
sub: userId,
|
||||
type: 'refresh',
|
||||
};
|
||||
const token = jwt.sign(payload, this.jwtSecret, {
|
||||
expiresIn: this.jwtRefreshExpiresIn,
|
||||
});
|
||||
// Almacenar en DB
|
||||
const expiresAt = new Date();
|
||||
expiresAt.setDate(expiresAt.getDate() + 7); // 7 días
|
||||
await this.refreshTokenRepository.save(this.refreshTokenRepository.create({
|
||||
userId,
|
||||
token,
|
||||
expiresAt,
|
||||
}));
|
||||
return token;
|
||||
}
|
||||
/**
|
||||
* Convertir expiresIn a segundos
|
||||
*/
|
||||
getExpiresInSeconds(expiresIn) {
|
||||
const match = expiresIn.match(/^(\d+)([dhms])$/);
|
||||
if (!match)
|
||||
return 86400; // default 1 día
|
||||
const value = parseInt(match[1]);
|
||||
const unit = match[2];
|
||||
switch (unit) {
|
||||
case 'd': return value * 86400;
|
||||
case 'h': return value * 3600;
|
||||
case 'm': return value * 60;
|
||||
case 's': return value;
|
||||
default: return 86400;
|
||||
}
|
||||
}
|
||||
}
|
||||
exports.AuthService = AuthService;
|
||||
//# sourceMappingURL=auth.service.js.map
|
||||
1
backend/dist/modules/auth/services/auth.service.js.map
vendored
Normal file
1
backend/dist/modules/auth/services/auth.service.js.map
vendored
Normal file
File diff suppressed because one or more lines are too long
5
backend/dist/modules/auth/services/index.d.ts
vendored
Normal file
5
backend/dist/modules/auth/services/index.d.ts
vendored
Normal file
@ -0,0 +1,5 @@
|
||||
/**
|
||||
* Auth Module - Service Exports
|
||||
*/
|
||||
export * from './auth.service';
|
||||
//# sourceMappingURL=index.d.ts.map
|
||||
1
backend/dist/modules/auth/services/index.d.ts.map
vendored
Normal file
1
backend/dist/modules/auth/services/index.d.ts.map
vendored
Normal file
@ -0,0 +1 @@
|
||||
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/modules/auth/services/index.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,cAAc,gBAAgB,CAAC"}
|
||||
21
backend/dist/modules/auth/services/index.js
vendored
Normal file
21
backend/dist/modules/auth/services/index.js
vendored
Normal file
@ -0,0 +1,21 @@
|
||||
"use strict";
|
||||
/**
|
||||
* Auth Module - Service Exports
|
||||
*/
|
||||
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
||||
if (k2 === undefined) k2 = k;
|
||||
var desc = Object.getOwnPropertyDescriptor(m, k);
|
||||
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
||||
desc = { enumerable: true, get: function() { return m[k]; } };
|
||||
}
|
||||
Object.defineProperty(o, k2, desc);
|
||||
}) : (function(o, m, k, k2) {
|
||||
if (k2 === undefined) k2 = k;
|
||||
o[k2] = m[k];
|
||||
}));
|
||||
var __exportStar = (this && this.__exportStar) || function(m, exports) {
|
||||
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
|
||||
};
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
__exportStar(require("./auth.service"), exports);
|
||||
//# sourceMappingURL=index.js.map
|
||||
1
backend/dist/modules/auth/services/index.js.map
vendored
Normal file
1
backend/dist/modules/auth/services/index.js.map
vendored
Normal file
@ -0,0 +1 @@
|
||||
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../../src/modules/auth/services/index.ts"],"names":[],"mappings":";AAAA;;GAEG;;;;;;;;;;;;;;;;AAEH,iDAA+B"}
|
||||
15
backend/dist/modules/budgets/controllers/concepto.controller.d.ts
vendored
Normal file
15
backend/dist/modules/budgets/controllers/concepto.controller.d.ts
vendored
Normal file
@ -0,0 +1,15 @@
|
||||
/**
|
||||
* ConceptoController - Controller de conceptos de obra
|
||||
*
|
||||
* Endpoints REST para gestión del catálogo de conceptos.
|
||||
*
|
||||
* @module Budgets
|
||||
*/
|
||||
import { Router } from 'express';
|
||||
import { DataSource } from 'typeorm';
|
||||
/**
|
||||
* Crear router de conceptos
|
||||
*/
|
||||
export declare function createConceptoController(dataSource: DataSource): Router;
|
||||
export default createConceptoController;
|
||||
//# sourceMappingURL=concepto.controller.d.ts.map
|
||||
1
backend/dist/modules/budgets/controllers/concepto.controller.d.ts.map
vendored
Normal file
1
backend/dist/modules/budgets/controllers/concepto.controller.d.ts.map
vendored
Normal file
@ -0,0 +1 @@
|
||||
{"version":3,"file":"concepto.controller.d.ts","sourceRoot":"","sources":["../../../../src/modules/budgets/controllers/concepto.controller.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,EAAE,MAAM,EAAmC,MAAM,SAAS,CAAC;AAClE,OAAO,EAAE,UAAU,EAAE,MAAM,SAAS,CAAC;AAUrC;;GAEG;AACH,wBAAgB,wBAAwB,CAAC,UAAU,EAAE,UAAU,GAAG,MAAM,CAmOvE;AAED,eAAe,wBAAwB,CAAC"}
|
||||
227
backend/dist/modules/budgets/controllers/concepto.controller.js
vendored
Normal file
227
backend/dist/modules/budgets/controllers/concepto.controller.js
vendored
Normal file
@ -0,0 +1,227 @@
|
||||
"use strict";
|
||||
/**
|
||||
* ConceptoController - Controller de conceptos de obra
|
||||
*
|
||||
* Endpoints REST para gestión del catálogo de conceptos.
|
||||
*
|
||||
* @module Budgets
|
||||
*/
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.createConceptoController = createConceptoController;
|
||||
const express_1 = require("express");
|
||||
const concepto_service_1 = require("../services/concepto.service");
|
||||
const auth_middleware_1 = require("../../auth/middleware/auth.middleware");
|
||||
const auth_service_1 = require("../../auth/services/auth.service");
|
||||
const concepto_entity_1 = require("../entities/concepto.entity");
|
||||
const user_entity_1 = require("../../core/entities/user.entity");
|
||||
const tenant_entity_1 = require("../../core/entities/tenant.entity");
|
||||
const refresh_token_entity_1 = require("../../auth/entities/refresh-token.entity");
|
||||
/**
|
||||
* Crear router de conceptos
|
||||
*/
|
||||
function createConceptoController(dataSource) {
|
||||
const router = (0, express_1.Router)();
|
||||
// Repositorios
|
||||
const conceptoRepository = dataSource.getRepository(concepto_entity_1.Concepto);
|
||||
const userRepository = dataSource.getRepository(user_entity_1.User);
|
||||
const tenantRepository = dataSource.getRepository(tenant_entity_1.Tenant);
|
||||
const refreshTokenRepository = dataSource.getRepository(refresh_token_entity_1.RefreshToken);
|
||||
// Servicios
|
||||
const conceptoService = new concepto_service_1.ConceptoService(conceptoRepository);
|
||||
const authService = new auth_service_1.AuthService(userRepository, tenantRepository, refreshTokenRepository);
|
||||
const authMiddleware = new auth_middleware_1.AuthMiddleware(authService, dataSource);
|
||||
// Helper para crear contexto de servicio
|
||||
const getContext = (req) => {
|
||||
if (!req.tenantId) {
|
||||
throw new Error('Tenant ID is required');
|
||||
}
|
||||
return {
|
||||
tenantId: req.tenantId,
|
||||
userId: req.user?.sub,
|
||||
};
|
||||
};
|
||||
/**
|
||||
* GET /conceptos
|
||||
* Listar conceptos raíz
|
||||
*/
|
||||
router.get('/', authMiddleware.authenticate, async (req, res, next) => {
|
||||
try {
|
||||
const tenantId = req.tenantId;
|
||||
if (!tenantId) {
|
||||
res.status(400).json({ error: 'Bad Request', message: 'Tenant ID required' });
|
||||
return;
|
||||
}
|
||||
const page = parseInt(req.query.page) || 1;
|
||||
const limit = Math.min(parseInt(req.query.limit) || 50, 100);
|
||||
const result = await conceptoService.findRootConceptos(getContext(req), page, limit);
|
||||
res.status(200).json({
|
||||
success: true,
|
||||
data: result.data,
|
||||
pagination: result.meta,
|
||||
});
|
||||
}
|
||||
catch (error) {
|
||||
next(error);
|
||||
}
|
||||
});
|
||||
/**
|
||||
* GET /conceptos/search
|
||||
* Buscar conceptos por código o nombre
|
||||
*/
|
||||
router.get('/search', authMiddleware.authenticate, async (req, res, next) => {
|
||||
try {
|
||||
const tenantId = req.tenantId;
|
||||
if (!tenantId) {
|
||||
res.status(400).json({ error: 'Bad Request', message: 'Tenant ID required' });
|
||||
return;
|
||||
}
|
||||
const term = req.query.q;
|
||||
if (!term || term.length < 2) {
|
||||
res.status(400).json({ error: 'Bad Request', message: 'Search term must be at least 2 characters' });
|
||||
return;
|
||||
}
|
||||
const limit = Math.min(parseInt(req.query.limit) || 20, 50);
|
||||
const conceptos = await conceptoService.search(getContext(req), term, limit);
|
||||
res.status(200).json({ success: true, data: conceptos });
|
||||
}
|
||||
catch (error) {
|
||||
next(error);
|
||||
}
|
||||
});
|
||||
/**
|
||||
* GET /conceptos/tree
|
||||
* Obtener árbol de conceptos
|
||||
*/
|
||||
router.get('/tree', authMiddleware.authenticate, async (req, res, next) => {
|
||||
try {
|
||||
const tenantId = req.tenantId;
|
||||
if (!tenantId) {
|
||||
res.status(400).json({ error: 'Bad Request', message: 'Tenant ID required' });
|
||||
return;
|
||||
}
|
||||
const rootId = req.query.rootId;
|
||||
const tree = await conceptoService.getConceptoTree(getContext(req), rootId);
|
||||
res.status(200).json({ success: true, data: tree });
|
||||
}
|
||||
catch (error) {
|
||||
next(error);
|
||||
}
|
||||
});
|
||||
/**
|
||||
* GET /conceptos/:id
|
||||
* Obtener concepto por ID
|
||||
*/
|
||||
router.get('/:id', authMiddleware.authenticate, async (req, res, next) => {
|
||||
try {
|
||||
const tenantId = req.tenantId;
|
||||
if (!tenantId) {
|
||||
res.status(400).json({ error: 'Bad Request', message: 'Tenant ID required' });
|
||||
return;
|
||||
}
|
||||
const concepto = await conceptoService.findById(getContext(req), req.params.id);
|
||||
if (!concepto) {
|
||||
res.status(404).json({ error: 'Not Found', message: 'Concept not found' });
|
||||
return;
|
||||
}
|
||||
res.status(200).json({ success: true, data: concepto });
|
||||
}
|
||||
catch (error) {
|
||||
next(error);
|
||||
}
|
||||
});
|
||||
/**
|
||||
* GET /conceptos/:id/children
|
||||
* Obtener hijos de un concepto
|
||||
*/
|
||||
router.get('/:id/children', authMiddleware.authenticate, async (req, res, next) => {
|
||||
try {
|
||||
const tenantId = req.tenantId;
|
||||
if (!tenantId) {
|
||||
res.status(400).json({ error: 'Bad Request', message: 'Tenant ID required' });
|
||||
return;
|
||||
}
|
||||
const children = await conceptoService.findChildren(getContext(req), req.params.id);
|
||||
res.status(200).json({ success: true, data: children });
|
||||
}
|
||||
catch (error) {
|
||||
next(error);
|
||||
}
|
||||
});
|
||||
/**
|
||||
* POST /conceptos
|
||||
* Crear concepto
|
||||
*/
|
||||
router.post('/', authMiddleware.authenticate, authMiddleware.authorize('admin', 'engineer', 'director'), async (req, res, next) => {
|
||||
try {
|
||||
const tenantId = req.tenantId;
|
||||
if (!tenantId) {
|
||||
res.status(400).json({ error: 'Bad Request', message: 'Tenant ID required' });
|
||||
return;
|
||||
}
|
||||
const dto = req.body;
|
||||
if (!dto.code || !dto.name) {
|
||||
res.status(400).json({ error: 'Bad Request', message: 'code and name are required' });
|
||||
return;
|
||||
}
|
||||
// Verificar código único
|
||||
const exists = await conceptoService.codeExists(getContext(req), dto.code);
|
||||
if (exists) {
|
||||
res.status(409).json({ error: 'Conflict', message: 'Concept code already exists' });
|
||||
return;
|
||||
}
|
||||
const concepto = await conceptoService.createConcepto(getContext(req), dto);
|
||||
res.status(201).json({ success: true, data: concepto });
|
||||
}
|
||||
catch (error) {
|
||||
next(error);
|
||||
}
|
||||
});
|
||||
/**
|
||||
* PATCH /conceptos/:id
|
||||
* Actualizar concepto
|
||||
*/
|
||||
router.patch('/:id', authMiddleware.authenticate, authMiddleware.authorize('admin', 'engineer', 'director'), async (req, res, next) => {
|
||||
try {
|
||||
const tenantId = req.tenantId;
|
||||
if (!tenantId) {
|
||||
res.status(400).json({ error: 'Bad Request', message: 'Tenant ID required' });
|
||||
return;
|
||||
}
|
||||
const dto = req.body;
|
||||
const concepto = await conceptoService.update(getContext(req), req.params.id, dto);
|
||||
if (!concepto) {
|
||||
res.status(404).json({ error: 'Not Found', message: 'Concept not found' });
|
||||
return;
|
||||
}
|
||||
res.status(200).json({ success: true, data: concepto });
|
||||
}
|
||||
catch (error) {
|
||||
next(error);
|
||||
}
|
||||
});
|
||||
/**
|
||||
* DELETE /conceptos/:id
|
||||
* Eliminar concepto (soft delete)
|
||||
*/
|
||||
router.delete('/:id', authMiddleware.authenticate, authMiddleware.authorize('admin', 'director'), async (req, res, next) => {
|
||||
try {
|
||||
const tenantId = req.tenantId;
|
||||
if (!tenantId) {
|
||||
res.status(400).json({ error: 'Bad Request', message: 'Tenant ID required' });
|
||||
return;
|
||||
}
|
||||
const deleted = await conceptoService.softDelete(getContext(req), req.params.id);
|
||||
if (!deleted) {
|
||||
res.status(404).json({ error: 'Not Found', message: 'Concept not found' });
|
||||
return;
|
||||
}
|
||||
res.status(200).json({ success: true, message: 'Concept deleted' });
|
||||
}
|
||||
catch (error) {
|
||||
next(error);
|
||||
}
|
||||
});
|
||||
return router;
|
||||
}
|
||||
exports.default = createConceptoController;
|
||||
//# sourceMappingURL=concepto.controller.js.map
|
||||
1
backend/dist/modules/budgets/controllers/concepto.controller.js.map
vendored
Normal file
1
backend/dist/modules/budgets/controllers/concepto.controller.js.map
vendored
Normal file
File diff suppressed because one or more lines are too long
7
backend/dist/modules/budgets/controllers/index.d.ts
vendored
Normal file
7
backend/dist/modules/budgets/controllers/index.d.ts
vendored
Normal file
@ -0,0 +1,7 @@
|
||||
/**
|
||||
* Budgets Controllers Index
|
||||
* @module Budgets
|
||||
*/
|
||||
export { createConceptoController } from './concepto.controller';
|
||||
export { createPresupuestoController } from './presupuesto.controller';
|
||||
//# sourceMappingURL=index.d.ts.map
|
||||
1
backend/dist/modules/budgets/controllers/index.d.ts.map
vendored
Normal file
1
backend/dist/modules/budgets/controllers/index.d.ts.map
vendored
Normal file
@ -0,0 +1 @@
|
||||
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/modules/budgets/controllers/index.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EAAE,wBAAwB,EAAE,MAAM,uBAAuB,CAAC;AACjE,OAAO,EAAE,2BAA2B,EAAE,MAAM,0BAA0B,CAAC"}
|
||||
12
backend/dist/modules/budgets/controllers/index.js
vendored
Normal file
12
backend/dist/modules/budgets/controllers/index.js
vendored
Normal file
@ -0,0 +1,12 @@
|
||||
"use strict";
|
||||
/**
|
||||
* Budgets Controllers Index
|
||||
* @module Budgets
|
||||
*/
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.createPresupuestoController = exports.createConceptoController = void 0;
|
||||
var concepto_controller_1 = require("./concepto.controller");
|
||||
Object.defineProperty(exports, "createConceptoController", { enumerable: true, get: function () { return concepto_controller_1.createConceptoController; } });
|
||||
var presupuesto_controller_1 = require("./presupuesto.controller");
|
||||
Object.defineProperty(exports, "createPresupuestoController", { enumerable: true, get: function () { return presupuesto_controller_1.createPresupuestoController; } });
|
||||
//# sourceMappingURL=index.js.map
|
||||
1
backend/dist/modules/budgets/controllers/index.js.map
vendored
Normal file
1
backend/dist/modules/budgets/controllers/index.js.map
vendored
Normal file
@ -0,0 +1 @@
|
||||
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../../src/modules/budgets/controllers/index.ts"],"names":[],"mappings":";AAAA;;;GAGG;;;AAEH,6DAAiE;AAAxD,+HAAA,wBAAwB,OAAA;AACjC,mEAAuE;AAA9D,qIAAA,2BAA2B,OAAA"}
|
||||
15
backend/dist/modules/budgets/controllers/presupuesto.controller.d.ts
vendored
Normal file
15
backend/dist/modules/budgets/controllers/presupuesto.controller.d.ts
vendored
Normal file
@ -0,0 +1,15 @@
|
||||
/**
|
||||
* PresupuestoController - Controller de presupuestos
|
||||
*
|
||||
* Endpoints REST para gestión de presupuestos de obra.
|
||||
*
|
||||
* @module Budgets
|
||||
*/
|
||||
import { Router } from 'express';
|
||||
import { DataSource } from 'typeorm';
|
||||
/**
|
||||
* Crear router de presupuestos
|
||||
*/
|
||||
export declare function createPresupuestoController(dataSource: DataSource): Router;
|
||||
export default createPresupuestoController;
|
||||
//# sourceMappingURL=presupuesto.controller.d.ts.map
|
||||
1
backend/dist/modules/budgets/controllers/presupuesto.controller.d.ts.map
vendored
Normal file
1
backend/dist/modules/budgets/controllers/presupuesto.controller.d.ts.map
vendored
Normal file
@ -0,0 +1 @@
|
||||
{"version":3,"file":"presupuesto.controller.d.ts","sourceRoot":"","sources":["../../../../src/modules/budgets/controllers/presupuesto.controller.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,EAAE,MAAM,EAAmC,MAAM,SAAS,CAAC;AAClE,OAAO,EAAE,UAAU,EAAE,MAAM,SAAS,CAAC;AAWrC;;GAEG;AACH,wBAAgB,2BAA2B,CAAC,UAAU,EAAE,UAAU,GAAG,MAAM,CAqQ1E;AAED,eAAe,2BAA2B,CAAC"}
|
||||
262
backend/dist/modules/budgets/controllers/presupuesto.controller.js
vendored
Normal file
262
backend/dist/modules/budgets/controllers/presupuesto.controller.js
vendored
Normal file
@ -0,0 +1,262 @@
|
||||
"use strict";
|
||||
/**
|
||||
* PresupuestoController - Controller de presupuestos
|
||||
*
|
||||
* Endpoints REST para gestión de presupuestos de obra.
|
||||
*
|
||||
* @module Budgets
|
||||
*/
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.createPresupuestoController = createPresupuestoController;
|
||||
const express_1 = require("express");
|
||||
const presupuesto_service_1 = require("../services/presupuesto.service");
|
||||
const auth_middleware_1 = require("../../auth/middleware/auth.middleware");
|
||||
const auth_service_1 = require("../../auth/services/auth.service");
|
||||
const presupuesto_entity_1 = require("../entities/presupuesto.entity");
|
||||
const presupuesto_partida_entity_1 = require("../entities/presupuesto-partida.entity");
|
||||
const user_entity_1 = require("../../core/entities/user.entity");
|
||||
const tenant_entity_1 = require("../../core/entities/tenant.entity");
|
||||
const refresh_token_entity_1 = require("../../auth/entities/refresh-token.entity");
|
||||
/**
|
||||
* Crear router de presupuestos
|
||||
*/
|
||||
function createPresupuestoController(dataSource) {
|
||||
const router = (0, express_1.Router)();
|
||||
// Repositorios
|
||||
const presupuestoRepository = dataSource.getRepository(presupuesto_entity_1.Presupuesto);
|
||||
const partidaRepository = dataSource.getRepository(presupuesto_partida_entity_1.PresupuestoPartida);
|
||||
const userRepository = dataSource.getRepository(user_entity_1.User);
|
||||
const tenantRepository = dataSource.getRepository(tenant_entity_1.Tenant);
|
||||
const refreshTokenRepository = dataSource.getRepository(refresh_token_entity_1.RefreshToken);
|
||||
// Servicios
|
||||
const presupuestoService = new presupuesto_service_1.PresupuestoService(presupuestoRepository, partidaRepository);
|
||||
const authService = new auth_service_1.AuthService(userRepository, tenantRepository, refreshTokenRepository);
|
||||
const authMiddleware = new auth_middleware_1.AuthMiddleware(authService, dataSource);
|
||||
// Helper para crear contexto de servicio
|
||||
const getContext = (req) => {
|
||||
if (!req.tenantId) {
|
||||
throw new Error('Tenant ID is required');
|
||||
}
|
||||
return {
|
||||
tenantId: req.tenantId,
|
||||
userId: req.user?.sub,
|
||||
};
|
||||
};
|
||||
/**
|
||||
* GET /presupuestos
|
||||
* Listar presupuestos
|
||||
*/
|
||||
router.get('/', authMiddleware.authenticate, async (req, res, next) => {
|
||||
try {
|
||||
const tenantId = req.tenantId;
|
||||
if (!tenantId) {
|
||||
res.status(400).json({ error: 'Bad Request', message: 'Tenant ID required' });
|
||||
return;
|
||||
}
|
||||
const page = parseInt(req.query.page) || 1;
|
||||
const limit = Math.min(parseInt(req.query.limit) || 20, 100);
|
||||
const fraccionamientoId = req.query.fraccionamientoId;
|
||||
let result;
|
||||
if (fraccionamientoId) {
|
||||
result = await presupuestoService.findByFraccionamiento(getContext(req), fraccionamientoId, page, limit);
|
||||
}
|
||||
else {
|
||||
result = await presupuestoService.findAll(getContext(req), { page, limit });
|
||||
}
|
||||
res.status(200).json({
|
||||
success: true,
|
||||
data: result.data,
|
||||
pagination: result.meta,
|
||||
});
|
||||
}
|
||||
catch (error) {
|
||||
next(error);
|
||||
}
|
||||
});
|
||||
/**
|
||||
* GET /presupuestos/:id
|
||||
* Obtener presupuesto por ID con sus partidas
|
||||
*/
|
||||
router.get('/:id', authMiddleware.authenticate, async (req, res, next) => {
|
||||
try {
|
||||
const tenantId = req.tenantId;
|
||||
if (!tenantId) {
|
||||
res.status(400).json({ error: 'Bad Request', message: 'Tenant ID required' });
|
||||
return;
|
||||
}
|
||||
const presupuesto = await presupuestoService.findWithPartidas(getContext(req), req.params.id);
|
||||
if (!presupuesto) {
|
||||
res.status(404).json({ error: 'Not Found', message: 'Budget not found' });
|
||||
return;
|
||||
}
|
||||
res.status(200).json({ success: true, data: presupuesto });
|
||||
}
|
||||
catch (error) {
|
||||
next(error);
|
||||
}
|
||||
});
|
||||
/**
|
||||
* POST /presupuestos
|
||||
* Crear presupuesto
|
||||
*/
|
||||
router.post('/', authMiddleware.authenticate, authMiddleware.authorize('admin', 'engineer', 'director'), async (req, res, next) => {
|
||||
try {
|
||||
const tenantId = req.tenantId;
|
||||
if (!tenantId) {
|
||||
res.status(400).json({ error: 'Bad Request', message: 'Tenant ID required' });
|
||||
return;
|
||||
}
|
||||
const dto = req.body;
|
||||
if (!dto.code || !dto.name) {
|
||||
res.status(400).json({ error: 'Bad Request', message: 'code and name are required' });
|
||||
return;
|
||||
}
|
||||
const presupuesto = await presupuestoService.createPresupuesto(getContext(req), dto);
|
||||
res.status(201).json({ success: true, data: presupuesto });
|
||||
}
|
||||
catch (error) {
|
||||
next(error);
|
||||
}
|
||||
});
|
||||
/**
|
||||
* POST /presupuestos/:id/partidas
|
||||
* Agregar partida al presupuesto
|
||||
*/
|
||||
router.post('/:id/partidas', authMiddleware.authenticate, authMiddleware.authorize('admin', 'engineer', 'director'), async (req, res, next) => {
|
||||
try {
|
||||
const tenantId = req.tenantId;
|
||||
if (!tenantId) {
|
||||
res.status(400).json({ error: 'Bad Request', message: 'Tenant ID required' });
|
||||
return;
|
||||
}
|
||||
const dto = req.body;
|
||||
if (!dto.conceptoId || dto.quantity === undefined || dto.unitPrice === undefined) {
|
||||
res.status(400).json({ error: 'Bad Request', message: 'conceptoId, quantity and unitPrice are required' });
|
||||
return;
|
||||
}
|
||||
const partida = await presupuestoService.addPartida(getContext(req), req.params.id, dto);
|
||||
res.status(201).json({ success: true, data: partida });
|
||||
}
|
||||
catch (error) {
|
||||
if (error instanceof Error && error.message === 'Presupuesto not found') {
|
||||
res.status(404).json({ error: 'Not Found', message: error.message });
|
||||
return;
|
||||
}
|
||||
next(error);
|
||||
}
|
||||
});
|
||||
/**
|
||||
* PATCH /presupuestos/:id/partidas/:partidaId
|
||||
* Actualizar partida
|
||||
*/
|
||||
router.patch('/:id/partidas/:partidaId', authMiddleware.authenticate, authMiddleware.authorize('admin', 'engineer', 'director'), async (req, res, next) => {
|
||||
try {
|
||||
const tenantId = req.tenantId;
|
||||
if (!tenantId) {
|
||||
res.status(400).json({ error: 'Bad Request', message: 'Tenant ID required' });
|
||||
return;
|
||||
}
|
||||
const dto = req.body;
|
||||
const partida = await presupuestoService.updatePartida(getContext(req), req.params.partidaId, dto);
|
||||
if (!partida) {
|
||||
res.status(404).json({ error: 'Not Found', message: 'Budget item not found' });
|
||||
return;
|
||||
}
|
||||
res.status(200).json({ success: true, data: partida });
|
||||
}
|
||||
catch (error) {
|
||||
next(error);
|
||||
}
|
||||
});
|
||||
/**
|
||||
* DELETE /presupuestos/:id/partidas/:partidaId
|
||||
* Eliminar partida
|
||||
*/
|
||||
router.delete('/:id/partidas/:partidaId', authMiddleware.authenticate, authMiddleware.authorize('admin', 'engineer', 'director'), async (req, res, next) => {
|
||||
try {
|
||||
const tenantId = req.tenantId;
|
||||
if (!tenantId) {
|
||||
res.status(400).json({ error: 'Bad Request', message: 'Tenant ID required' });
|
||||
return;
|
||||
}
|
||||
const deleted = await presupuestoService.removePartida(getContext(req), req.params.partidaId);
|
||||
if (!deleted) {
|
||||
res.status(404).json({ error: 'Not Found', message: 'Budget item not found' });
|
||||
return;
|
||||
}
|
||||
res.status(200).json({ success: true, message: 'Budget item deleted' });
|
||||
}
|
||||
catch (error) {
|
||||
next(error);
|
||||
}
|
||||
});
|
||||
/**
|
||||
* POST /presupuestos/:id/version
|
||||
* Crear nueva versión del presupuesto
|
||||
*/
|
||||
router.post('/:id/version', authMiddleware.authenticate, authMiddleware.authorize('admin', 'director'), async (req, res, next) => {
|
||||
try {
|
||||
const tenantId = req.tenantId;
|
||||
if (!tenantId) {
|
||||
res.status(400).json({ error: 'Bad Request', message: 'Tenant ID required' });
|
||||
return;
|
||||
}
|
||||
const newVersion = await presupuestoService.createNewVersion(getContext(req), req.params.id);
|
||||
res.status(201).json({ success: true, data: newVersion });
|
||||
}
|
||||
catch (error) {
|
||||
if (error instanceof Error && error.message === 'Presupuesto not found') {
|
||||
res.status(404).json({ error: 'Not Found', message: error.message });
|
||||
return;
|
||||
}
|
||||
next(error);
|
||||
}
|
||||
});
|
||||
/**
|
||||
* POST /presupuestos/:id/approve
|
||||
* Aprobar presupuesto
|
||||
*/
|
||||
router.post('/:id/approve', authMiddleware.authenticate, authMiddleware.authorize('admin', 'director'), async (req, res, next) => {
|
||||
try {
|
||||
const tenantId = req.tenantId;
|
||||
if (!tenantId) {
|
||||
res.status(400).json({ error: 'Bad Request', message: 'Tenant ID required' });
|
||||
return;
|
||||
}
|
||||
const presupuesto = await presupuestoService.approve(getContext(req), req.params.id);
|
||||
if (!presupuesto) {
|
||||
res.status(404).json({ error: 'Not Found', message: 'Budget not found' });
|
||||
return;
|
||||
}
|
||||
res.status(200).json({ success: true, data: presupuesto, message: 'Budget approved' });
|
||||
}
|
||||
catch (error) {
|
||||
next(error);
|
||||
}
|
||||
});
|
||||
/**
|
||||
* DELETE /presupuestos/:id
|
||||
* Eliminar presupuesto (soft delete)
|
||||
*/
|
||||
router.delete('/:id', authMiddleware.authenticate, authMiddleware.authorize('admin', 'director'), async (req, res, next) => {
|
||||
try {
|
||||
const tenantId = req.tenantId;
|
||||
if (!tenantId) {
|
||||
res.status(400).json({ error: 'Bad Request', message: 'Tenant ID required' });
|
||||
return;
|
||||
}
|
||||
const deleted = await presupuestoService.softDelete(getContext(req), req.params.id);
|
||||
if (!deleted) {
|
||||
res.status(404).json({ error: 'Not Found', message: 'Budget not found' });
|
||||
return;
|
||||
}
|
||||
res.status(200).json({ success: true, message: 'Budget deleted' });
|
||||
}
|
||||
catch (error) {
|
||||
next(error);
|
||||
}
|
||||
});
|
||||
return router;
|
||||
}
|
||||
exports.default = createPresupuestoController;
|
||||
//# sourceMappingURL=presupuesto.controller.js.map
|
||||
1
backend/dist/modules/budgets/controllers/presupuesto.controller.js.map
vendored
Normal file
1
backend/dist/modules/budgets/controllers/presupuesto.controller.js.map
vendored
Normal file
File diff suppressed because one or more lines are too long
35
backend/dist/modules/budgets/entities/concepto.entity.d.ts
vendored
Normal file
35
backend/dist/modules/budgets/entities/concepto.entity.d.ts
vendored
Normal file
@ -0,0 +1,35 @@
|
||||
/**
|
||||
* Concepto Entity
|
||||
* Catalogo de conceptos de obra (estructura jerarquica)
|
||||
*
|
||||
* @module Budgets
|
||||
* @table construction.conceptos
|
||||
* @ddl schemas/01-construction-schema-ddl.sql
|
||||
*/
|
||||
import { Tenant } from '../../core/entities/tenant.entity';
|
||||
import { User } from '../../core/entities/user.entity';
|
||||
export declare class Concepto {
|
||||
id: string;
|
||||
tenantId: string;
|
||||
parentId: string | null;
|
||||
code: string;
|
||||
name: string;
|
||||
description: string | null;
|
||||
unitId: string | null;
|
||||
unitPrice: number | null;
|
||||
isComposite: boolean;
|
||||
level: number;
|
||||
path: string | null;
|
||||
createdAt: Date;
|
||||
createdById: string | null;
|
||||
updatedAt: Date | null;
|
||||
updatedById: string | null;
|
||||
deletedAt: Date | null;
|
||||
deletedById: string | null;
|
||||
tenant: Tenant;
|
||||
parent: Concepto | null;
|
||||
children: Concepto[];
|
||||
createdBy: User | null;
|
||||
updatedBy: User | null;
|
||||
}
|
||||
//# sourceMappingURL=concepto.entity.d.ts.map
|
||||
1
backend/dist/modules/budgets/entities/concepto.entity.d.ts.map
vendored
Normal file
1
backend/dist/modules/budgets/entities/concepto.entity.d.ts.map
vendored
Normal file
@ -0,0 +1 @@
|
||||
{"version":3,"file":"concepto.entity.d.ts","sourceRoot":"","sources":["../../../../src/modules/budgets/entities/concepto.entity.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAaH,OAAO,EAAE,MAAM,EAAE,MAAM,mCAAmC,CAAC;AAC3D,OAAO,EAAE,IAAI,EAAE,MAAM,iCAAiC,CAAC;AAEvD,qBAKa,QAAQ;IAEnB,EAAE,EAAE,MAAM,CAAC;IAGX,QAAQ,EAAE,MAAM,CAAC;IAGjB,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC;IAGxB,IAAI,EAAE,MAAM,CAAC;IAGb,IAAI,EAAE,MAAM,CAAC;IAGb,WAAW,EAAE,MAAM,GAAG,IAAI,CAAC;IAG3B,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;IAGtB,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;IAGzB,WAAW,EAAE,OAAO,CAAC;IAGrB,KAAK,EAAE,MAAM,CAAC;IAGd,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;IAGpB,SAAS,EAAE,IAAI,CAAC;IAGhB,WAAW,EAAE,MAAM,GAAG,IAAI,CAAC;IAG3B,SAAS,EAAE,IAAI,GAAG,IAAI,CAAC;IAGvB,WAAW,EAAE,MAAM,GAAG,IAAI,CAAC;IAG3B,SAAS,EAAE,IAAI,GAAG,IAAI,CAAC;IAGvB,WAAW,EAAE,MAAM,GAAG,IAAI,CAAC;IAK3B,MAAM,EAAE,MAAM,CAAC;IAIf,MAAM,EAAE,QAAQ,GAAG,IAAI,CAAC;IAGxB,QAAQ,EAAE,QAAQ,EAAE,CAAC;IAIrB,SAAS,EAAE,IAAI,GAAG,IAAI,CAAC;IAIvB,SAAS,EAAE,IAAI,GAAG,IAAI,CAAC;CACxB"}
|
||||
149
backend/dist/modules/budgets/entities/concepto.entity.js
vendored
Normal file
149
backend/dist/modules/budgets/entities/concepto.entity.js
vendored
Normal file
@ -0,0 +1,149 @@
|
||||
"use strict";
|
||||
/**
|
||||
* Concepto Entity
|
||||
* Catalogo de conceptos de obra (estructura jerarquica)
|
||||
*
|
||||
* @module Budgets
|
||||
* @table construction.conceptos
|
||||
* @ddl schemas/01-construction-schema-ddl.sql
|
||||
*/
|
||||
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
|
||||
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
||||
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
||||
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
||||
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
||||
};
|
||||
var __metadata = (this && this.__metadata) || function (k, v) {
|
||||
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
|
||||
};
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.Concepto = void 0;
|
||||
const typeorm_1 = require("typeorm");
|
||||
const tenant_entity_1 = require("../../core/entities/tenant.entity");
|
||||
const user_entity_1 = require("../../core/entities/user.entity");
|
||||
let Concepto = class Concepto {
|
||||
id;
|
||||
tenantId;
|
||||
parentId;
|
||||
code;
|
||||
name;
|
||||
description;
|
||||
unitId;
|
||||
unitPrice;
|
||||
isComposite;
|
||||
level;
|
||||
path;
|
||||
createdAt;
|
||||
createdById;
|
||||
updatedAt;
|
||||
updatedById;
|
||||
deletedAt;
|
||||
deletedById;
|
||||
// Relations
|
||||
tenant;
|
||||
parent;
|
||||
children;
|
||||
createdBy;
|
||||
updatedBy;
|
||||
};
|
||||
exports.Concepto = Concepto;
|
||||
__decorate([
|
||||
(0, typeorm_1.PrimaryGeneratedColumn)('uuid'),
|
||||
__metadata("design:type", String)
|
||||
], Concepto.prototype, "id", void 0);
|
||||
__decorate([
|
||||
(0, typeorm_1.Column)({ name: 'tenant_id', type: 'uuid' }),
|
||||
__metadata("design:type", String)
|
||||
], Concepto.prototype, "tenantId", void 0);
|
||||
__decorate([
|
||||
(0, typeorm_1.Column)({ name: 'parent_id', type: 'uuid', nullable: true }),
|
||||
__metadata("design:type", Object)
|
||||
], Concepto.prototype, "parentId", void 0);
|
||||
__decorate([
|
||||
(0, typeorm_1.Column)({ type: 'varchar', length: 50 }),
|
||||
__metadata("design:type", String)
|
||||
], Concepto.prototype, "code", void 0);
|
||||
__decorate([
|
||||
(0, typeorm_1.Column)({ type: 'varchar', length: 255 }),
|
||||
__metadata("design:type", String)
|
||||
], Concepto.prototype, "name", void 0);
|
||||
__decorate([
|
||||
(0, typeorm_1.Column)({ type: 'text', nullable: true }),
|
||||
__metadata("design:type", Object)
|
||||
], Concepto.prototype, "description", void 0);
|
||||
__decorate([
|
||||
(0, typeorm_1.Column)({ name: 'unit_id', type: 'uuid', nullable: true }),
|
||||
__metadata("design:type", Object)
|
||||
], Concepto.prototype, "unitId", void 0);
|
||||
__decorate([
|
||||
(0, typeorm_1.Column)({ name: 'unit_price', type: 'decimal', precision: 12, scale: 4, nullable: true }),
|
||||
__metadata("design:type", Object)
|
||||
], Concepto.prototype, "unitPrice", void 0);
|
||||
__decorate([
|
||||
(0, typeorm_1.Column)({ name: 'is_composite', type: 'boolean', default: false }),
|
||||
__metadata("design:type", Boolean)
|
||||
], Concepto.prototype, "isComposite", void 0);
|
||||
__decorate([
|
||||
(0, typeorm_1.Column)({ type: 'integer', default: 0 }),
|
||||
__metadata("design:type", Number)
|
||||
], Concepto.prototype, "level", void 0);
|
||||
__decorate([
|
||||
(0, typeorm_1.Column)({ type: 'varchar', length: 500, nullable: true }),
|
||||
__metadata("design:type", Object)
|
||||
], Concepto.prototype, "path", void 0);
|
||||
__decorate([
|
||||
(0, typeorm_1.CreateDateColumn)({ name: 'created_at', type: 'timestamptz' }),
|
||||
__metadata("design:type", Date)
|
||||
], Concepto.prototype, "createdAt", void 0);
|
||||
__decorate([
|
||||
(0, typeorm_1.Column)({ name: 'created_by', type: 'uuid', nullable: true }),
|
||||
__metadata("design:type", Object)
|
||||
], Concepto.prototype, "createdById", void 0);
|
||||
__decorate([
|
||||
(0, typeorm_1.UpdateDateColumn)({ name: 'updated_at', type: 'timestamptz', nullable: true }),
|
||||
__metadata("design:type", Object)
|
||||
], Concepto.prototype, "updatedAt", void 0);
|
||||
__decorate([
|
||||
(0, typeorm_1.Column)({ name: 'updated_by', type: 'uuid', nullable: true }),
|
||||
__metadata("design:type", Object)
|
||||
], Concepto.prototype, "updatedById", void 0);
|
||||
__decorate([
|
||||
(0, typeorm_1.Column)({ name: 'deleted_at', type: 'timestamptz', nullable: true }),
|
||||
__metadata("design:type", Object)
|
||||
], Concepto.prototype, "deletedAt", void 0);
|
||||
__decorate([
|
||||
(0, typeorm_1.Column)({ name: 'deleted_by', type: 'uuid', nullable: true }),
|
||||
__metadata("design:type", Object)
|
||||
], Concepto.prototype, "deletedById", void 0);
|
||||
__decorate([
|
||||
(0, typeorm_1.ManyToOne)(() => tenant_entity_1.Tenant),
|
||||
(0, typeorm_1.JoinColumn)({ name: 'tenant_id' }),
|
||||
__metadata("design:type", tenant_entity_1.Tenant)
|
||||
], Concepto.prototype, "tenant", void 0);
|
||||
__decorate([
|
||||
(0, typeorm_1.ManyToOne)(() => Concepto, (c) => c.children, { nullable: true }),
|
||||
(0, typeorm_1.JoinColumn)({ name: 'parent_id' }),
|
||||
__metadata("design:type", Object)
|
||||
], Concepto.prototype, "parent", void 0);
|
||||
__decorate([
|
||||
(0, typeorm_1.OneToMany)(() => Concepto, (c) => c.parent),
|
||||
__metadata("design:type", Array)
|
||||
], Concepto.prototype, "children", void 0);
|
||||
__decorate([
|
||||
(0, typeorm_1.ManyToOne)(() => user_entity_1.User),
|
||||
(0, typeorm_1.JoinColumn)({ name: 'created_by' }),
|
||||
__metadata("design:type", Object)
|
||||
], Concepto.prototype, "createdBy", void 0);
|
||||
__decorate([
|
||||
(0, typeorm_1.ManyToOne)(() => user_entity_1.User),
|
||||
(0, typeorm_1.JoinColumn)({ name: 'updated_by' }),
|
||||
__metadata("design:type", Object)
|
||||
], Concepto.prototype, "updatedBy", void 0);
|
||||
exports.Concepto = Concepto = __decorate([
|
||||
(0, typeorm_1.Entity)({ schema: 'construction', name: 'conceptos' }),
|
||||
(0, typeorm_1.Index)(['tenantId', 'code'], { unique: true }),
|
||||
(0, typeorm_1.Index)(['tenantId']),
|
||||
(0, typeorm_1.Index)(['parentId']),
|
||||
(0, typeorm_1.Index)(['code'])
|
||||
], Concepto);
|
||||
//# sourceMappingURL=concepto.entity.js.map
|
||||
1
backend/dist/modules/budgets/entities/concepto.entity.js.map
vendored
Normal file
1
backend/dist/modules/budgets/entities/concepto.entity.js.map
vendored
Normal file
@ -0,0 +1 @@
|
||||
{"version":3,"file":"concepto.entity.js","sourceRoot":"","sources":["../../../../src/modules/budgets/entities/concepto.entity.ts"],"names":[],"mappings":"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"}
|
||||
8
backend/dist/modules/budgets/entities/index.d.ts
vendored
Normal file
8
backend/dist/modules/budgets/entities/index.d.ts
vendored
Normal file
@ -0,0 +1,8 @@
|
||||
/**
|
||||
* Budgets Module - Entity Exports
|
||||
* MAI-003: Presupuestos
|
||||
*/
|
||||
export * from './concepto.entity';
|
||||
export * from './presupuesto.entity';
|
||||
export * from './presupuesto-partida.entity';
|
||||
//# sourceMappingURL=index.d.ts.map
|
||||
1
backend/dist/modules/budgets/entities/index.d.ts.map
vendored
Normal file
1
backend/dist/modules/budgets/entities/index.d.ts.map
vendored
Normal file
@ -0,0 +1 @@
|
||||
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/modules/budgets/entities/index.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,cAAc,mBAAmB,CAAC;AAClC,cAAc,sBAAsB,CAAC;AACrC,cAAc,8BAA8B,CAAC"}
|
||||
24
backend/dist/modules/budgets/entities/index.js
vendored
Normal file
24
backend/dist/modules/budgets/entities/index.js
vendored
Normal file
@ -0,0 +1,24 @@
|
||||
"use strict";
|
||||
/**
|
||||
* Budgets Module - Entity Exports
|
||||
* MAI-003: Presupuestos
|
||||
*/
|
||||
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
||||
if (k2 === undefined) k2 = k;
|
||||
var desc = Object.getOwnPropertyDescriptor(m, k);
|
||||
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
||||
desc = { enumerable: true, get: function() { return m[k]; } };
|
||||
}
|
||||
Object.defineProperty(o, k2, desc);
|
||||
}) : (function(o, m, k, k2) {
|
||||
if (k2 === undefined) k2 = k;
|
||||
o[k2] = m[k];
|
||||
}));
|
||||
var __exportStar = (this && this.__exportStar) || function(m, exports) {
|
||||
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
|
||||
};
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
__exportStar(require("./concepto.entity"), exports);
|
||||
__exportStar(require("./presupuesto.entity"), exports);
|
||||
__exportStar(require("./presupuesto-partida.entity"), exports);
|
||||
//# sourceMappingURL=index.js.map
|
||||
1
backend/dist/modules/budgets/entities/index.js.map
vendored
Normal file
1
backend/dist/modules/budgets/entities/index.js.map
vendored
Normal file
@ -0,0 +1 @@
|
||||
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../../src/modules/budgets/entities/index.ts"],"names":[],"mappings":";AAAA;;;GAGG;;;;;;;;;;;;;;;;AAEH,oDAAkC;AAClC,uDAAqC;AACrC,+DAA6C"}
|
||||
33
backend/dist/modules/budgets/entities/presupuesto-partida.entity.d.ts
vendored
Normal file
33
backend/dist/modules/budgets/entities/presupuesto-partida.entity.d.ts
vendored
Normal file
@ -0,0 +1,33 @@
|
||||
/**
|
||||
* PresupuestoPartida Entity
|
||||
* Lineas/partidas de un presupuesto
|
||||
*
|
||||
* @module Budgets
|
||||
* @table construction.presupuesto_partidas
|
||||
* @ddl schemas/01-construction-schema-ddl.sql
|
||||
*/
|
||||
import { Tenant } from '../../core/entities/tenant.entity';
|
||||
import { User } from '../../core/entities/user.entity';
|
||||
import { Presupuesto } from './presupuesto.entity';
|
||||
import { Concepto } from './concepto.entity';
|
||||
export declare class PresupuestoPartida {
|
||||
id: string;
|
||||
tenantId: string;
|
||||
presupuestoId: string;
|
||||
conceptoId: string;
|
||||
sequence: number;
|
||||
quantity: number;
|
||||
unitPrice: number;
|
||||
totalAmount: number;
|
||||
createdAt: Date;
|
||||
createdById: string | null;
|
||||
updatedAt: Date | null;
|
||||
updatedById: string | null;
|
||||
deletedAt: Date | null;
|
||||
deletedById: string | null;
|
||||
tenant: Tenant;
|
||||
presupuesto: Presupuesto;
|
||||
concepto: Concepto;
|
||||
createdBy: User | null;
|
||||
}
|
||||
//# sourceMappingURL=presupuesto-partida.entity.d.ts.map
|
||||
1
backend/dist/modules/budgets/entities/presupuesto-partida.entity.d.ts.map
vendored
Normal file
1
backend/dist/modules/budgets/entities/presupuesto-partida.entity.d.ts.map
vendored
Normal file
@ -0,0 +1 @@
|
||||
{"version":3,"file":"presupuesto-partida.entity.d.ts","sourceRoot":"","sources":["../../../../src/modules/budgets/entities/presupuesto-partida.entity.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAYH,OAAO,EAAE,MAAM,EAAE,MAAM,mCAAmC,CAAC;AAC3D,OAAO,EAAE,IAAI,EAAE,MAAM,iCAAiC,CAAC;AACvD,OAAO,EAAE,WAAW,EAAE,MAAM,sBAAsB,CAAC;AACnD,OAAO,EAAE,QAAQ,EAAE,MAAM,mBAAmB,CAAC;AAE7C,qBAGa,kBAAkB;IAE7B,EAAE,EAAE,MAAM,CAAC;IAGX,QAAQ,EAAE,MAAM,CAAC;IAGjB,aAAa,EAAE,MAAM,CAAC;IAGtB,UAAU,EAAE,MAAM,CAAC;IAGnB,QAAQ,EAAE,MAAM,CAAC;IAGjB,QAAQ,EAAE,MAAM,CAAC;IAGjB,SAAS,EAAE,MAAM,CAAC;IAWlB,WAAW,EAAE,MAAM,CAAC;IAGpB,SAAS,EAAE,IAAI,CAAC;IAGhB,WAAW,EAAE,MAAM,GAAG,IAAI,CAAC;IAG3B,SAAS,EAAE,IAAI,GAAG,IAAI,CAAC;IAGvB,WAAW,EAAE,MAAM,GAAG,IAAI,CAAC;IAG3B,SAAS,EAAE,IAAI,GAAG,IAAI,CAAC;IAGvB,WAAW,EAAE,MAAM,GAAG,IAAI,CAAC;IAK3B,MAAM,EAAE,MAAM,CAAC;IAIf,WAAW,EAAE,WAAW,CAAC;IAIzB,QAAQ,EAAE,QAAQ,CAAC;IAInB,SAAS,EAAE,IAAI,GAAG,IAAI,CAAC;CACxB"}
|
||||
137
backend/dist/modules/budgets/entities/presupuesto-partida.entity.js
vendored
Normal file
137
backend/dist/modules/budgets/entities/presupuesto-partida.entity.js
vendored
Normal file
@ -0,0 +1,137 @@
|
||||
"use strict";
|
||||
/**
|
||||
* PresupuestoPartida Entity
|
||||
* Lineas/partidas de un presupuesto
|
||||
*
|
||||
* @module Budgets
|
||||
* @table construction.presupuesto_partidas
|
||||
* @ddl schemas/01-construction-schema-ddl.sql
|
||||
*/
|
||||
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
|
||||
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
||||
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
||||
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
||||
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
||||
};
|
||||
var __metadata = (this && this.__metadata) || function (k, v) {
|
||||
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
|
||||
};
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.PresupuestoPartida = void 0;
|
||||
const typeorm_1 = require("typeorm");
|
||||
const tenant_entity_1 = require("../../core/entities/tenant.entity");
|
||||
const user_entity_1 = require("../../core/entities/user.entity");
|
||||
const presupuesto_entity_1 = require("./presupuesto.entity");
|
||||
const concepto_entity_1 = require("./concepto.entity");
|
||||
let PresupuestoPartida = class PresupuestoPartida {
|
||||
id;
|
||||
tenantId;
|
||||
presupuestoId;
|
||||
conceptoId;
|
||||
sequence;
|
||||
quantity;
|
||||
unitPrice;
|
||||
// Columna calculada (GENERATED ALWAYS AS) - solo lectura
|
||||
totalAmount;
|
||||
createdAt;
|
||||
createdById;
|
||||
updatedAt;
|
||||
updatedById;
|
||||
deletedAt;
|
||||
deletedById;
|
||||
// Relations
|
||||
tenant;
|
||||
presupuesto;
|
||||
concepto;
|
||||
createdBy;
|
||||
};
|
||||
exports.PresupuestoPartida = PresupuestoPartida;
|
||||
__decorate([
|
||||
(0, typeorm_1.PrimaryGeneratedColumn)('uuid'),
|
||||
__metadata("design:type", String)
|
||||
], PresupuestoPartida.prototype, "id", void 0);
|
||||
__decorate([
|
||||
(0, typeorm_1.Column)({ name: 'tenant_id', type: 'uuid' }),
|
||||
__metadata("design:type", String)
|
||||
], PresupuestoPartida.prototype, "tenantId", void 0);
|
||||
__decorate([
|
||||
(0, typeorm_1.Column)({ name: 'presupuesto_id', type: 'uuid' }),
|
||||
__metadata("design:type", String)
|
||||
], PresupuestoPartida.prototype, "presupuestoId", void 0);
|
||||
__decorate([
|
||||
(0, typeorm_1.Column)({ name: 'concepto_id', type: 'uuid' }),
|
||||
__metadata("design:type", String)
|
||||
], PresupuestoPartida.prototype, "conceptoId", void 0);
|
||||
__decorate([
|
||||
(0, typeorm_1.Column)({ type: 'integer', default: 0 }),
|
||||
__metadata("design:type", Number)
|
||||
], PresupuestoPartida.prototype, "sequence", void 0);
|
||||
__decorate([
|
||||
(0, typeorm_1.Column)({ type: 'decimal', precision: 12, scale: 4, default: 0 }),
|
||||
__metadata("design:type", Number)
|
||||
], PresupuestoPartida.prototype, "quantity", void 0);
|
||||
__decorate([
|
||||
(0, typeorm_1.Column)({ name: 'unit_price', type: 'decimal', precision: 12, scale: 4, default: 0 }),
|
||||
__metadata("design:type", Number)
|
||||
], PresupuestoPartida.prototype, "unitPrice", void 0);
|
||||
__decorate([
|
||||
(0, typeorm_1.Column)({
|
||||
name: 'total_amount',
|
||||
type: 'decimal',
|
||||
precision: 14,
|
||||
scale: 2,
|
||||
insert: false,
|
||||
update: false,
|
||||
}),
|
||||
__metadata("design:type", Number)
|
||||
], PresupuestoPartida.prototype, "totalAmount", void 0);
|
||||
__decorate([
|
||||
(0, typeorm_1.CreateDateColumn)({ name: 'created_at', type: 'timestamptz' }),
|
||||
__metadata("design:type", Date)
|
||||
], PresupuestoPartida.prototype, "createdAt", void 0);
|
||||
__decorate([
|
||||
(0, typeorm_1.Column)({ name: 'created_by', type: 'uuid', nullable: true }),
|
||||
__metadata("design:type", Object)
|
||||
], PresupuestoPartida.prototype, "createdById", void 0);
|
||||
__decorate([
|
||||
(0, typeorm_1.UpdateDateColumn)({ name: 'updated_at', type: 'timestamptz', nullable: true }),
|
||||
__metadata("design:type", Object)
|
||||
], PresupuestoPartida.prototype, "updatedAt", void 0);
|
||||
__decorate([
|
||||
(0, typeorm_1.Column)({ name: 'updated_by', type: 'uuid', nullable: true }),
|
||||
__metadata("design:type", Object)
|
||||
], PresupuestoPartida.prototype, "updatedById", void 0);
|
||||
__decorate([
|
||||
(0, typeorm_1.Column)({ name: 'deleted_at', type: 'timestamptz', nullable: true }),
|
||||
__metadata("design:type", Object)
|
||||
], PresupuestoPartida.prototype, "deletedAt", void 0);
|
||||
__decorate([
|
||||
(0, typeorm_1.Column)({ name: 'deleted_by', type: 'uuid', nullable: true }),
|
||||
__metadata("design:type", Object)
|
||||
], PresupuestoPartida.prototype, "deletedById", void 0);
|
||||
__decorate([
|
||||
(0, typeorm_1.ManyToOne)(() => tenant_entity_1.Tenant),
|
||||
(0, typeorm_1.JoinColumn)({ name: 'tenant_id' }),
|
||||
__metadata("design:type", tenant_entity_1.Tenant)
|
||||
], PresupuestoPartida.prototype, "tenant", void 0);
|
||||
__decorate([
|
||||
(0, typeorm_1.ManyToOne)(() => presupuesto_entity_1.Presupuesto, (p) => p.partidas),
|
||||
(0, typeorm_1.JoinColumn)({ name: 'presupuesto_id' }),
|
||||
__metadata("design:type", presupuesto_entity_1.Presupuesto)
|
||||
], PresupuestoPartida.prototype, "presupuesto", void 0);
|
||||
__decorate([
|
||||
(0, typeorm_1.ManyToOne)(() => concepto_entity_1.Concepto),
|
||||
(0, typeorm_1.JoinColumn)({ name: 'concepto_id' }),
|
||||
__metadata("design:type", concepto_entity_1.Concepto)
|
||||
], PresupuestoPartida.prototype, "concepto", void 0);
|
||||
__decorate([
|
||||
(0, typeorm_1.ManyToOne)(() => user_entity_1.User),
|
||||
(0, typeorm_1.JoinColumn)({ name: 'created_by' }),
|
||||
__metadata("design:type", Object)
|
||||
], PresupuestoPartida.prototype, "createdBy", void 0);
|
||||
exports.PresupuestoPartida = PresupuestoPartida = __decorate([
|
||||
(0, typeorm_1.Entity)({ schema: 'construction', name: 'presupuesto_partidas' }),
|
||||
(0, typeorm_1.Index)(['presupuestoId', 'conceptoId'], { unique: true }),
|
||||
(0, typeorm_1.Index)(['tenantId'])
|
||||
], PresupuestoPartida);
|
||||
//# sourceMappingURL=presupuesto-partida.entity.js.map
|
||||
1
backend/dist/modules/budgets/entities/presupuesto-partida.entity.js.map
vendored
Normal file
1
backend/dist/modules/budgets/entities/presupuesto-partida.entity.js.map
vendored
Normal file
@ -0,0 +1 @@
|
||||
{"version":3,"file":"presupuesto-partida.entity.js","sourceRoot":"","sources":["../../../../src/modules/budgets/entities/presupuesto-partida.entity.ts"],"names":[],"mappings":"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"}
|
||||
39
backend/dist/modules/budgets/entities/presupuesto.entity.d.ts
vendored
Normal file
39
backend/dist/modules/budgets/entities/presupuesto.entity.d.ts
vendored
Normal file
@ -0,0 +1,39 @@
|
||||
/**
|
||||
* Presupuesto Entity
|
||||
* Presupuestos de obra por prototipo o fraccionamiento
|
||||
*
|
||||
* @module Budgets
|
||||
* @table construction.presupuestos
|
||||
* @ddl schemas/01-construction-schema-ddl.sql
|
||||
*/
|
||||
import { Tenant } from '../../core/entities/tenant.entity';
|
||||
import { User } from '../../core/entities/user.entity';
|
||||
import { Fraccionamiento } from '../../construction/entities/fraccionamiento.entity';
|
||||
import { PresupuestoPartida } from './presupuesto-partida.entity';
|
||||
export declare class Presupuesto {
|
||||
id: string;
|
||||
tenantId: string;
|
||||
fraccionamientoId: string | null;
|
||||
prototipoId: string | null;
|
||||
code: string;
|
||||
name: string;
|
||||
description: string | null;
|
||||
version: number;
|
||||
isActive: boolean;
|
||||
totalAmount: number;
|
||||
currencyId: string | null;
|
||||
approvedAt: Date | null;
|
||||
approvedById: string | null;
|
||||
createdAt: Date;
|
||||
createdById: string | null;
|
||||
updatedAt: Date | null;
|
||||
updatedById: string | null;
|
||||
deletedAt: Date | null;
|
||||
deletedById: string | null;
|
||||
tenant: Tenant;
|
||||
fraccionamiento: Fraccionamiento | null;
|
||||
createdBy: User | null;
|
||||
approvedBy: User | null;
|
||||
partidas: PresupuestoPartida[];
|
||||
}
|
||||
//# sourceMappingURL=presupuesto.entity.d.ts.map
|
||||
1
backend/dist/modules/budgets/entities/presupuesto.entity.d.ts.map
vendored
Normal file
1
backend/dist/modules/budgets/entities/presupuesto.entity.d.ts.map
vendored
Normal file
@ -0,0 +1 @@
|
||||
{"version":3,"file":"presupuesto.entity.d.ts","sourceRoot":"","sources":["../../../../src/modules/budgets/entities/presupuesto.entity.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAaH,OAAO,EAAE,MAAM,EAAE,MAAM,mCAAmC,CAAC;AAC3D,OAAO,EAAE,IAAI,EAAE,MAAM,iCAAiC,CAAC;AACvD,OAAO,EAAE,eAAe,EAAE,MAAM,oDAAoD,CAAC;AACrF,OAAO,EAAE,kBAAkB,EAAE,MAAM,8BAA8B,CAAC;AAElE,qBAIa,WAAW;IAEtB,EAAE,EAAE,MAAM,CAAC;IAGX,QAAQ,EAAE,MAAM,CAAC;IAGjB,iBAAiB,EAAE,MAAM,GAAG,IAAI,CAAC;IAGjC,WAAW,EAAE,MAAM,GAAG,IAAI,CAAC;IAG3B,IAAI,EAAE,MAAM,CAAC;IAGb,IAAI,EAAE,MAAM,CAAC;IAGb,WAAW,EAAE,MAAM,GAAG,IAAI,CAAC;IAG3B,OAAO,EAAE,MAAM,CAAC;IAGhB,QAAQ,EAAE,OAAO,CAAC;IAGlB,WAAW,EAAE,MAAM,CAAC;IAGpB,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;IAG1B,UAAU,EAAE,IAAI,GAAG,IAAI,CAAC;IAGxB,YAAY,EAAE,MAAM,GAAG,IAAI,CAAC;IAG5B,SAAS,EAAE,IAAI,CAAC;IAGhB,WAAW,EAAE,MAAM,GAAG,IAAI,CAAC;IAG3B,SAAS,EAAE,IAAI,GAAG,IAAI,CAAC;IAGvB,WAAW,EAAE,MAAM,GAAG,IAAI,CAAC;IAG3B,SAAS,EAAE,IAAI,GAAG,IAAI,CAAC;IAGvB,WAAW,EAAE,MAAM,GAAG,IAAI,CAAC;IAK3B,MAAM,EAAE,MAAM,CAAC;IAIf,eAAe,EAAE,eAAe,GAAG,IAAI,CAAC;IAIxC,SAAS,EAAE,IAAI,GAAG,IAAI,CAAC;IAIvB,UAAU,EAAE,IAAI,GAAG,IAAI,CAAC;IAGxB,QAAQ,EAAE,kBAAkB,EAAE,CAAC;CAChC"}
|
||||
160
backend/dist/modules/budgets/entities/presupuesto.entity.js
vendored
Normal file
160
backend/dist/modules/budgets/entities/presupuesto.entity.js
vendored
Normal file
@ -0,0 +1,160 @@
|
||||
"use strict";
|
||||
/**
|
||||
* Presupuesto Entity
|
||||
* Presupuestos de obra por prototipo o fraccionamiento
|
||||
*
|
||||
* @module Budgets
|
||||
* @table construction.presupuestos
|
||||
* @ddl schemas/01-construction-schema-ddl.sql
|
||||
*/
|
||||
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
|
||||
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
||||
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
||||
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
||||
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
||||
};
|
||||
var __metadata = (this && this.__metadata) || function (k, v) {
|
||||
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
|
||||
};
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.Presupuesto = void 0;
|
||||
const typeorm_1 = require("typeorm");
|
||||
const tenant_entity_1 = require("../../core/entities/tenant.entity");
|
||||
const user_entity_1 = require("../../core/entities/user.entity");
|
||||
const fraccionamiento_entity_1 = require("../../construction/entities/fraccionamiento.entity");
|
||||
const presupuesto_partida_entity_1 = require("./presupuesto-partida.entity");
|
||||
let Presupuesto = class Presupuesto {
|
||||
id;
|
||||
tenantId;
|
||||
fraccionamientoId;
|
||||
prototipoId;
|
||||
code;
|
||||
name;
|
||||
description;
|
||||
version;
|
||||
isActive;
|
||||
totalAmount;
|
||||
currencyId;
|
||||
approvedAt;
|
||||
approvedById;
|
||||
createdAt;
|
||||
createdById;
|
||||
updatedAt;
|
||||
updatedById;
|
||||
deletedAt;
|
||||
deletedById;
|
||||
// Relations
|
||||
tenant;
|
||||
fraccionamiento;
|
||||
createdBy;
|
||||
approvedBy;
|
||||
partidas;
|
||||
};
|
||||
exports.Presupuesto = Presupuesto;
|
||||
__decorate([
|
||||
(0, typeorm_1.PrimaryGeneratedColumn)('uuid'),
|
||||
__metadata("design:type", String)
|
||||
], Presupuesto.prototype, "id", void 0);
|
||||
__decorate([
|
||||
(0, typeorm_1.Column)({ name: 'tenant_id', type: 'uuid' }),
|
||||
__metadata("design:type", String)
|
||||
], Presupuesto.prototype, "tenantId", void 0);
|
||||
__decorate([
|
||||
(0, typeorm_1.Column)({ name: 'fraccionamiento_id', type: 'uuid', nullable: true }),
|
||||
__metadata("design:type", Object)
|
||||
], Presupuesto.prototype, "fraccionamientoId", void 0);
|
||||
__decorate([
|
||||
(0, typeorm_1.Column)({ name: 'prototipo_id', type: 'uuid', nullable: true }),
|
||||
__metadata("design:type", Object)
|
||||
], Presupuesto.prototype, "prototipoId", void 0);
|
||||
__decorate([
|
||||
(0, typeorm_1.Column)({ type: 'varchar', length: 30 }),
|
||||
__metadata("design:type", String)
|
||||
], Presupuesto.prototype, "code", void 0);
|
||||
__decorate([
|
||||
(0, typeorm_1.Column)({ type: 'varchar', length: 255 }),
|
||||
__metadata("design:type", String)
|
||||
], Presupuesto.prototype, "name", void 0);
|
||||
__decorate([
|
||||
(0, typeorm_1.Column)({ type: 'text', nullable: true }),
|
||||
__metadata("design:type", Object)
|
||||
], Presupuesto.prototype, "description", void 0);
|
||||
__decorate([
|
||||
(0, typeorm_1.Column)({ type: 'integer', default: 1 }),
|
||||
__metadata("design:type", Number)
|
||||
], Presupuesto.prototype, "version", void 0);
|
||||
__decorate([
|
||||
(0, typeorm_1.Column)({ name: 'is_active', type: 'boolean', default: true }),
|
||||
__metadata("design:type", Boolean)
|
||||
], Presupuesto.prototype, "isActive", void 0);
|
||||
__decorate([
|
||||
(0, typeorm_1.Column)({ name: 'total_amount', type: 'decimal', precision: 16, scale: 2, default: 0 }),
|
||||
__metadata("design:type", Number)
|
||||
], Presupuesto.prototype, "totalAmount", void 0);
|
||||
__decorate([
|
||||
(0, typeorm_1.Column)({ name: 'currency_id', type: 'uuid', nullable: true }),
|
||||
__metadata("design:type", Object)
|
||||
], Presupuesto.prototype, "currencyId", void 0);
|
||||
__decorate([
|
||||
(0, typeorm_1.Column)({ name: 'approved_at', type: 'timestamptz', nullable: true }),
|
||||
__metadata("design:type", Object)
|
||||
], Presupuesto.prototype, "approvedAt", void 0);
|
||||
__decorate([
|
||||
(0, typeorm_1.Column)({ name: 'approved_by', type: 'uuid', nullable: true }),
|
||||
__metadata("design:type", Object)
|
||||
], Presupuesto.prototype, "approvedById", void 0);
|
||||
__decorate([
|
||||
(0, typeorm_1.CreateDateColumn)({ name: 'created_at', type: 'timestamptz' }),
|
||||
__metadata("design:type", Date)
|
||||
], Presupuesto.prototype, "createdAt", void 0);
|
||||
__decorate([
|
||||
(0, typeorm_1.Column)({ name: 'created_by', type: 'uuid', nullable: true }),
|
||||
__metadata("design:type", Object)
|
||||
], Presupuesto.prototype, "createdById", void 0);
|
||||
__decorate([
|
||||
(0, typeorm_1.UpdateDateColumn)({ name: 'updated_at', type: 'timestamptz', nullable: true }),
|
||||
__metadata("design:type", Object)
|
||||
], Presupuesto.prototype, "updatedAt", void 0);
|
||||
__decorate([
|
||||
(0, typeorm_1.Column)({ name: 'updated_by', type: 'uuid', nullable: true }),
|
||||
__metadata("design:type", Object)
|
||||
], Presupuesto.prototype, "updatedById", void 0);
|
||||
__decorate([
|
||||
(0, typeorm_1.Column)({ name: 'deleted_at', type: 'timestamptz', nullable: true }),
|
||||
__metadata("design:type", Object)
|
||||
], Presupuesto.prototype, "deletedAt", void 0);
|
||||
__decorate([
|
||||
(0, typeorm_1.Column)({ name: 'deleted_by', type: 'uuid', nullable: true }),
|
||||
__metadata("design:type", Object)
|
||||
], Presupuesto.prototype, "deletedById", void 0);
|
||||
__decorate([
|
||||
(0, typeorm_1.ManyToOne)(() => tenant_entity_1.Tenant),
|
||||
(0, typeorm_1.JoinColumn)({ name: 'tenant_id' }),
|
||||
__metadata("design:type", tenant_entity_1.Tenant)
|
||||
], Presupuesto.prototype, "tenant", void 0);
|
||||
__decorate([
|
||||
(0, typeorm_1.ManyToOne)(() => fraccionamiento_entity_1.Fraccionamiento, { nullable: true }),
|
||||
(0, typeorm_1.JoinColumn)({ name: 'fraccionamiento_id' }),
|
||||
__metadata("design:type", Object)
|
||||
], Presupuesto.prototype, "fraccionamiento", void 0);
|
||||
__decorate([
|
||||
(0, typeorm_1.ManyToOne)(() => user_entity_1.User),
|
||||
(0, typeorm_1.JoinColumn)({ name: 'created_by' }),
|
||||
__metadata("design:type", Object)
|
||||
], Presupuesto.prototype, "createdBy", void 0);
|
||||
__decorate([
|
||||
(0, typeorm_1.ManyToOne)(() => user_entity_1.User),
|
||||
(0, typeorm_1.JoinColumn)({ name: 'approved_by' }),
|
||||
__metadata("design:type", Object)
|
||||
], Presupuesto.prototype, "approvedBy", void 0);
|
||||
__decorate([
|
||||
(0, typeorm_1.OneToMany)(() => presupuesto_partida_entity_1.PresupuestoPartida, (p) => p.presupuesto),
|
||||
__metadata("design:type", Array)
|
||||
], Presupuesto.prototype, "partidas", void 0);
|
||||
exports.Presupuesto = Presupuesto = __decorate([
|
||||
(0, typeorm_1.Entity)({ schema: 'construction', name: 'presupuestos' }),
|
||||
(0, typeorm_1.Index)(['tenantId', 'code', 'version'], { unique: true }),
|
||||
(0, typeorm_1.Index)(['tenantId']),
|
||||
(0, typeorm_1.Index)(['fraccionamientoId'])
|
||||
], Presupuesto);
|
||||
//# sourceMappingURL=presupuesto.entity.js.map
|
||||
1
backend/dist/modules/budgets/entities/presupuesto.entity.js.map
vendored
Normal file
1
backend/dist/modules/budgets/entities/presupuesto.entity.js.map
vendored
Normal file
@ -0,0 +1 @@
|
||||
{"version":3,"file":"presupuesto.entity.js","sourceRoot":"","sources":["../../../../src/modules/budgets/entities/presupuesto.entity.ts"],"names":[],"mappings":"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"}
|
||||
60
backend/dist/modules/budgets/services/concepto.service.d.ts
vendored
Normal file
60
backend/dist/modules/budgets/services/concepto.service.d.ts
vendored
Normal file
@ -0,0 +1,60 @@
|
||||
/**
|
||||
* ConceptoService - Catalogo de Conceptos de Obra
|
||||
*
|
||||
* Gestiona el catálogo jerárquico de conceptos de obra.
|
||||
* Los conceptos pueden tener estructura padre-hijo (niveles).
|
||||
*
|
||||
* @module Budgets
|
||||
*/
|
||||
import { Repository } from 'typeorm';
|
||||
import { BaseService, ServiceContext, PaginatedResult } from '../../../shared/services/base.service';
|
||||
import { Concepto } from '../entities/concepto.entity';
|
||||
export interface CreateConceptoDto {
|
||||
code: string;
|
||||
name: string;
|
||||
description?: string;
|
||||
parentId?: string;
|
||||
unitId?: string;
|
||||
unitPrice?: number;
|
||||
isComposite?: boolean;
|
||||
}
|
||||
export interface UpdateConceptoDto {
|
||||
name?: string;
|
||||
description?: string;
|
||||
unitId?: string;
|
||||
unitPrice?: number;
|
||||
isComposite?: boolean;
|
||||
}
|
||||
export declare class ConceptoService extends BaseService<Concepto> {
|
||||
constructor(repository: Repository<Concepto>);
|
||||
/**
|
||||
* Crear un nuevo concepto con cálculo automático de nivel y path
|
||||
*/
|
||||
createConcepto(ctx: ServiceContext, data: CreateConceptoDto): Promise<Concepto>;
|
||||
/**
|
||||
* Obtener conceptos raíz (sin padre)
|
||||
*/
|
||||
findRootConceptos(ctx: ServiceContext, page?: number, limit?: number): Promise<PaginatedResult<Concepto>>;
|
||||
/**
|
||||
* Obtener hijos de un concepto
|
||||
*/
|
||||
findChildren(ctx: ServiceContext, parentId: string): Promise<Concepto[]>;
|
||||
/**
|
||||
* Obtener árbol completo de conceptos
|
||||
*/
|
||||
getConceptoTree(ctx: ServiceContext, rootId?: string): Promise<ConceptoNode[]>;
|
||||
private buildTree;
|
||||
/**
|
||||
* Buscar conceptos por código o nombre
|
||||
*/
|
||||
search(ctx: ServiceContext, term: string, limit?: number): Promise<Concepto[]>;
|
||||
/**
|
||||
* Verificar si un código ya existe
|
||||
*/
|
||||
codeExists(ctx: ServiceContext, code: string): Promise<boolean>;
|
||||
}
|
||||
interface ConceptoNode extends Concepto {
|
||||
children: ConceptoNode[];
|
||||
}
|
||||
export {};
|
||||
//# sourceMappingURL=concepto.service.d.ts.map
|
||||
1
backend/dist/modules/budgets/services/concepto.service.d.ts.map
vendored
Normal file
1
backend/dist/modules/budgets/services/concepto.service.d.ts.map
vendored
Normal file
@ -0,0 +1 @@
|
||||
{"version":3,"file":"concepto.service.d.ts","sourceRoot":"","sources":["../../../../src/modules/budgets/services/concepto.service.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,EAAE,UAAU,EAAU,MAAM,SAAS,CAAC;AAC7C,OAAO,EAAE,WAAW,EAAE,cAAc,EAAE,eAAe,EAAE,MAAM,uCAAuC,CAAC;AACrG,OAAO,EAAE,QAAQ,EAAE,MAAM,6BAA6B,CAAC;AAEvD,MAAM,WAAW,iBAAiB;IAChC,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,WAAW,CAAC,EAAE,OAAO,CAAC;CACvB;AAED,MAAM,WAAW,iBAAiB;IAChC,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,WAAW,CAAC,EAAE,OAAO,CAAC;CACvB;AAED,qBAAa,eAAgB,SAAQ,WAAW,CAAC,QAAQ,CAAC;gBAC5C,UAAU,EAAE,UAAU,CAAC,QAAQ,CAAC;IAI5C;;OAEG;IACG,cAAc,CAClB,GAAG,EAAE,cAAc,EACnB,IAAI,EAAE,iBAAiB,GACtB,OAAO,CAAC,QAAQ,CAAC;IAmBpB;;OAEG;IACG,iBAAiB,CACrB,GAAG,EAAE,cAAc,EACnB,IAAI,SAAI,EACR,KAAK,SAAK,GACT,OAAO,CAAC,eAAe,CAAC,QAAQ,CAAC,CAAC;IAQrC;;OAEG;IACG,YAAY,CAChB,GAAG,EAAE,cAAc,EACnB,QAAQ,EAAE,MAAM,GACf,OAAO,CAAC,QAAQ,EAAE,CAAC;IAOtB;;OAEG;IACG,eAAe,CACnB,GAAG,EAAE,cAAc,EACnB,MAAM,CAAC,EAAE,MAAM,GACd,OAAO,CAAC,YAAY,EAAE,CAAC;YAaZ,SAAS;IAqBvB;;OAEG;IACG,MAAM,CACV,GAAG,EAAE,cAAc,EACnB,IAAI,EAAE,MAAM,EACZ,KAAK,SAAK,GACT,OAAO,CAAC,QAAQ,EAAE,CAAC;IAatB;;OAEG;IACG,UAAU,CAAC,GAAG,EAAE,cAAc,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC;CAGtE;AAED,UAAU,YAAa,SAAQ,QAAQ;IACrC,QAAQ,EAAE,YAAY,EAAE,CAAC;CAC1B"}
|
||||
106
backend/dist/modules/budgets/services/concepto.service.js
vendored
Normal file
106
backend/dist/modules/budgets/services/concepto.service.js
vendored
Normal file
@ -0,0 +1,106 @@
|
||||
"use strict";
|
||||
/**
|
||||
* ConceptoService - Catalogo de Conceptos de Obra
|
||||
*
|
||||
* Gestiona el catálogo jerárquico de conceptos de obra.
|
||||
* Los conceptos pueden tener estructura padre-hijo (niveles).
|
||||
*
|
||||
* @module Budgets
|
||||
*/
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.ConceptoService = void 0;
|
||||
const typeorm_1 = require("typeorm");
|
||||
const base_service_1 = require("../../../shared/services/base.service");
|
||||
class ConceptoService extends base_service_1.BaseService {
|
||||
constructor(repository) {
|
||||
super(repository);
|
||||
}
|
||||
/**
|
||||
* Crear un nuevo concepto con cálculo automático de nivel y path
|
||||
*/
|
||||
async createConcepto(ctx, data) {
|
||||
let level = 0;
|
||||
let path = data.code;
|
||||
if (data.parentId) {
|
||||
const parent = await this.findById(ctx, data.parentId);
|
||||
if (parent) {
|
||||
level = parent.level + 1;
|
||||
path = `${parent.path}/${data.code}`;
|
||||
}
|
||||
}
|
||||
return this.create(ctx, {
|
||||
...data,
|
||||
level,
|
||||
path,
|
||||
});
|
||||
}
|
||||
/**
|
||||
* Obtener conceptos raíz (sin padre)
|
||||
*/
|
||||
async findRootConceptos(ctx, page = 1, limit = 50) {
|
||||
return this.findAll(ctx, {
|
||||
page,
|
||||
limit,
|
||||
where: { parentId: (0, typeorm_1.IsNull)() },
|
||||
});
|
||||
}
|
||||
/**
|
||||
* Obtener hijos de un concepto
|
||||
*/
|
||||
async findChildren(ctx, parentId) {
|
||||
return this.find(ctx, {
|
||||
where: { parentId },
|
||||
order: { code: 'ASC' },
|
||||
});
|
||||
}
|
||||
/**
|
||||
* Obtener árbol completo de conceptos
|
||||
*/
|
||||
async getConceptoTree(ctx, rootId) {
|
||||
const where = rootId
|
||||
? { parentId: rootId }
|
||||
: { parentId: (0, typeorm_1.IsNull)() };
|
||||
const roots = await this.find(ctx, {
|
||||
where: where,
|
||||
order: { code: 'ASC' },
|
||||
});
|
||||
return this.buildTree(ctx, roots);
|
||||
}
|
||||
async buildTree(ctx, conceptos) {
|
||||
const tree = [];
|
||||
for (const concepto of conceptos) {
|
||||
const children = await this.findChildren(ctx, concepto.id);
|
||||
const childNodes = children.length > 0
|
||||
? await this.buildTree(ctx, children)
|
||||
: [];
|
||||
tree.push({
|
||||
...concepto,
|
||||
children: childNodes,
|
||||
});
|
||||
}
|
||||
return tree;
|
||||
}
|
||||
/**
|
||||
* Buscar conceptos por código o nombre
|
||||
*/
|
||||
async search(ctx, term, limit = 20) {
|
||||
return this.repository
|
||||
.createQueryBuilder('c')
|
||||
.where('c.tenant_id = :tenantId', { tenantId: ctx.tenantId })
|
||||
.andWhere('c.deleted_at IS NULL')
|
||||
.andWhere('(c.code ILIKE :term OR c.name ILIKE :term)', {
|
||||
term: `%${term}%`,
|
||||
})
|
||||
.orderBy('c.code', 'ASC')
|
||||
.take(limit)
|
||||
.getMany();
|
||||
}
|
||||
/**
|
||||
* Verificar si un código ya existe
|
||||
*/
|
||||
async codeExists(ctx, code) {
|
||||
return this.exists(ctx, { code });
|
||||
}
|
||||
}
|
||||
exports.ConceptoService = ConceptoService;
|
||||
//# sourceMappingURL=concepto.service.js.map
|
||||
1
backend/dist/modules/budgets/services/concepto.service.js.map
vendored
Normal file
1
backend/dist/modules/budgets/services/concepto.service.js.map
vendored
Normal file
@ -0,0 +1 @@
|
||||
{"version":3,"file":"concepto.service.js","sourceRoot":"","sources":["../../../../src/modules/budgets/services/concepto.service.ts"],"names":[],"mappings":"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"}
|
||||
6
backend/dist/modules/budgets/services/index.d.ts
vendored
Normal file
6
backend/dist/modules/budgets/services/index.d.ts
vendored
Normal file
@ -0,0 +1,6 @@
|
||||
/**
|
||||
* Budgets Module - Service Exports
|
||||
*/
|
||||
export * from './concepto.service';
|
||||
export * from './presupuesto.service';
|
||||
//# sourceMappingURL=index.d.ts.map
|
||||
1
backend/dist/modules/budgets/services/index.d.ts.map
vendored
Normal file
1
backend/dist/modules/budgets/services/index.d.ts.map
vendored
Normal file
@ -0,0 +1 @@
|
||||
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/modules/budgets/services/index.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,cAAc,oBAAoB,CAAC;AACnC,cAAc,uBAAuB,CAAC"}
|
||||
22
backend/dist/modules/budgets/services/index.js
vendored
Normal file
22
backend/dist/modules/budgets/services/index.js
vendored
Normal file
@ -0,0 +1,22 @@
|
||||
"use strict";
|
||||
/**
|
||||
* Budgets Module - Service Exports
|
||||
*/
|
||||
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
||||
if (k2 === undefined) k2 = k;
|
||||
var desc = Object.getOwnPropertyDescriptor(m, k);
|
||||
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
||||
desc = { enumerable: true, get: function() { return m[k]; } };
|
||||
}
|
||||
Object.defineProperty(o, k2, desc);
|
||||
}) : (function(o, m, k, k2) {
|
||||
if (k2 === undefined) k2 = k;
|
||||
o[k2] = m[k];
|
||||
}));
|
||||
var __exportStar = (this && this.__exportStar) || function(m, exports) {
|
||||
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
|
||||
};
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
__exportStar(require("./concepto.service"), exports);
|
||||
__exportStar(require("./presupuesto.service"), exports);
|
||||
//# sourceMappingURL=index.js.map
|
||||
1
backend/dist/modules/budgets/services/index.js.map
vendored
Normal file
1
backend/dist/modules/budgets/services/index.js.map
vendored
Normal file
@ -0,0 +1 @@
|
||||
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../../src/modules/budgets/services/index.ts"],"names":[],"mappings":";AAAA;;GAEG;;;;;;;;;;;;;;;;AAEH,qDAAmC;AACnC,wDAAsC"}
|
||||
72
backend/dist/modules/budgets/services/presupuesto.service.d.ts
vendored
Normal file
72
backend/dist/modules/budgets/services/presupuesto.service.d.ts
vendored
Normal file
@ -0,0 +1,72 @@
|
||||
/**
|
||||
* PresupuestoService - Gestión de Presupuestos de Obra
|
||||
*
|
||||
* Gestiona presupuestos de obra con sus partidas.
|
||||
* Soporta versionamiento y aprobación.
|
||||
*
|
||||
* @module Budgets
|
||||
*/
|
||||
import { Repository } from 'typeorm';
|
||||
import { BaseService, ServiceContext, PaginatedResult } from '../../../shared/services/base.service';
|
||||
import { Presupuesto } from '../entities/presupuesto.entity';
|
||||
import { PresupuestoPartida } from '../entities/presupuesto-partida.entity';
|
||||
export interface CreatePresupuestoDto {
|
||||
code: string;
|
||||
name: string;
|
||||
description?: string;
|
||||
fraccionamientoId?: string;
|
||||
prototipoId?: string;
|
||||
currencyId?: string;
|
||||
}
|
||||
export interface AddPartidaDto {
|
||||
conceptoId: string;
|
||||
quantity: number;
|
||||
unitPrice: number;
|
||||
sequence?: number;
|
||||
}
|
||||
export interface UpdatePartidaDto {
|
||||
quantity?: number;
|
||||
unitPrice?: number;
|
||||
sequence?: number;
|
||||
}
|
||||
export declare class PresupuestoService extends BaseService<Presupuesto> {
|
||||
private readonly partidaRepository;
|
||||
constructor(repository: Repository<Presupuesto>, partidaRepository: Repository<PresupuestoPartida>);
|
||||
/**
|
||||
* Crear nuevo presupuesto
|
||||
*/
|
||||
createPresupuesto(ctx: ServiceContext, data: CreatePresupuestoDto): Promise<Presupuesto>;
|
||||
/**
|
||||
* Obtener presupuestos por fraccionamiento
|
||||
*/
|
||||
findByFraccionamiento(ctx: ServiceContext, fraccionamientoId: string, page?: number, limit?: number): Promise<PaginatedResult<Presupuesto>>;
|
||||
/**
|
||||
* Obtener presupuesto con sus partidas
|
||||
*/
|
||||
findWithPartidas(ctx: ServiceContext, id: string): Promise<Presupuesto | null>;
|
||||
/**
|
||||
* Agregar partida al presupuesto
|
||||
*/
|
||||
addPartida(ctx: ServiceContext, presupuestoId: string, data: AddPartidaDto): Promise<PresupuestoPartida>;
|
||||
/**
|
||||
* Actualizar partida
|
||||
*/
|
||||
updatePartida(ctx: ServiceContext, partidaId: string, data: UpdatePartidaDto): Promise<PresupuestoPartida | null>;
|
||||
/**
|
||||
* Eliminar partida
|
||||
*/
|
||||
removePartida(ctx: ServiceContext, partidaId: string): Promise<boolean>;
|
||||
/**
|
||||
* Recalcular total del presupuesto
|
||||
*/
|
||||
recalculateTotal(ctx: ServiceContext, presupuestoId: string): Promise<void>;
|
||||
/**
|
||||
* Crear nueva versión del presupuesto
|
||||
*/
|
||||
createNewVersion(ctx: ServiceContext, presupuestoId: string): Promise<Presupuesto>;
|
||||
/**
|
||||
* Aprobar presupuesto
|
||||
*/
|
||||
approve(ctx: ServiceContext, presupuestoId: string): Promise<Presupuesto | null>;
|
||||
}
|
||||
//# sourceMappingURL=presupuesto.service.d.ts.map
|
||||
1
backend/dist/modules/budgets/services/presupuesto.service.d.ts.map
vendored
Normal file
1
backend/dist/modules/budgets/services/presupuesto.service.d.ts.map
vendored
Normal file
@ -0,0 +1 @@
|
||||
{"version":3,"file":"presupuesto.service.d.ts","sourceRoot":"","sources":["../../../../src/modules/budgets/services/presupuesto.service.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,EAAE,UAAU,EAAE,MAAM,SAAS,CAAC;AACrC,OAAO,EAAE,WAAW,EAAE,cAAc,EAAE,eAAe,EAAE,MAAM,uCAAuC,CAAC;AACrG,OAAO,EAAE,WAAW,EAAE,MAAM,gCAAgC,CAAC;AAC7D,OAAO,EAAE,kBAAkB,EAAE,MAAM,wCAAwC,CAAC;AAE5E,MAAM,WAAW,oBAAoB;IACnC,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAC3B,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAED,MAAM,WAAW,aAAa;IAC5B,UAAU,EAAE,MAAM,CAAC;IACnB,QAAQ,EAAE,MAAM,CAAC;IACjB,SAAS,EAAE,MAAM,CAAC;IAClB,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB;AAED,MAAM,WAAW,gBAAgB;IAC/B,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB;AAED,qBAAa,kBAAmB,SAAQ,WAAW,CAAC,WAAW,CAAC;IAG5D,OAAO,CAAC,QAAQ,CAAC,iBAAiB;gBADlC,UAAU,EAAE,UAAU,CAAC,WAAW,CAAC,EAClB,iBAAiB,EAAE,UAAU,CAAC,kBAAkB,CAAC;IAKpE;;OAEG;IACG,iBAAiB,CACrB,GAAG,EAAE,cAAc,EACnB,IAAI,EAAE,oBAAoB,GACzB,OAAO,CAAC,WAAW,CAAC;IASvB;;OAEG;IACG,qBAAqB,CACzB,GAAG,EAAE,cAAc,EACnB,iBAAiB,EAAE,MAAM,EACzB,IAAI,SAAI,EACR,KAAK,SAAK,GACT,OAAO,CAAC,eAAe,CAAC,WAAW,CAAC,CAAC;IAQxC;;OAEG;IACG,gBAAgB,CACpB,GAAG,EAAE,cAAc,EACnB,EAAE,EAAE,MAAM,GACT,OAAO,CAAC,WAAW,GAAG,IAAI,CAAC;IAW9B;;OAEG;IACG,UAAU,CACd,GAAG,EAAE,cAAc,EACnB,aAAa,EAAE,MAAM,EACrB,IAAI,EAAE,aAAa,GAClB,OAAO,CAAC,kBAAkB,CAAC;IAsB9B;;OAEG;IACG,aAAa,CACjB,GAAG,EAAE,cAAc,EACnB,SAAS,EAAE,MAAM,EACjB,IAAI,EAAE,gBAAgB,GACrB,OAAO,CAAC,kBAAkB,GAAG,IAAI,CAAC;IAwBrC;;OAEG;IACG,aAAa,CAAC,GAAG,EAAE,cAAc,EAAE,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC;IAwB7E;;OAEG;IACG,gBAAgB,CAAC,GAAG,EAAE,cAAc,EAAE,aAAa,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAgBjF;;OAEG;IACG,gBAAgB,CACpB,GAAG,EAAE,cAAc,EACnB,aAAa,EAAE,MAAM,GACpB,OAAO,CAAC,WAAW,CAAC;IA2CvB;;OAEG;IACG,OAAO,CAAC,GAAG,EAAE,cAAc,EAAE,aAAa,EAAE,MAAM,GAAG,OAAO,CAAC,WAAW,GAAG,IAAI,CAAC;CAWvF"}
|
||||
180
backend/dist/modules/budgets/services/presupuesto.service.js
vendored
Normal file
180
backend/dist/modules/budgets/services/presupuesto.service.js
vendored
Normal file
@ -0,0 +1,180 @@
|
||||
"use strict";
|
||||
/**
|
||||
* PresupuestoService - Gestión de Presupuestos de Obra
|
||||
*
|
||||
* Gestiona presupuestos de obra con sus partidas.
|
||||
* Soporta versionamiento y aprobación.
|
||||
*
|
||||
* @module Budgets
|
||||
*/
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.PresupuestoService = void 0;
|
||||
const base_service_1 = require("../../../shared/services/base.service");
|
||||
class PresupuestoService extends base_service_1.BaseService {
|
||||
partidaRepository;
|
||||
constructor(repository, partidaRepository) {
|
||||
super(repository);
|
||||
this.partidaRepository = partidaRepository;
|
||||
}
|
||||
/**
|
||||
* Crear nuevo presupuesto
|
||||
*/
|
||||
async createPresupuesto(ctx, data) {
|
||||
return this.create(ctx, {
|
||||
...data,
|
||||
version: 1,
|
||||
isActive: true,
|
||||
totalAmount: 0,
|
||||
});
|
||||
}
|
||||
/**
|
||||
* Obtener presupuestos por fraccionamiento
|
||||
*/
|
||||
async findByFraccionamiento(ctx, fraccionamientoId, page = 1, limit = 20) {
|
||||
return this.findAll(ctx, {
|
||||
page,
|
||||
limit,
|
||||
where: { fraccionamientoId, isActive: true },
|
||||
});
|
||||
}
|
||||
/**
|
||||
* Obtener presupuesto con sus partidas
|
||||
*/
|
||||
async findWithPartidas(ctx, id) {
|
||||
return this.repository.findOne({
|
||||
where: {
|
||||
id,
|
||||
tenantId: ctx.tenantId,
|
||||
deletedAt: null,
|
||||
},
|
||||
relations: ['partidas', 'partidas.concepto'],
|
||||
});
|
||||
}
|
||||
/**
|
||||
* Agregar partida al presupuesto
|
||||
*/
|
||||
async addPartida(ctx, presupuestoId, data) {
|
||||
const presupuesto = await this.findById(ctx, presupuestoId);
|
||||
if (!presupuesto) {
|
||||
throw new Error('Presupuesto not found');
|
||||
}
|
||||
const partida = this.partidaRepository.create({
|
||||
tenantId: ctx.tenantId,
|
||||
presupuestoId,
|
||||
conceptoId: data.conceptoId,
|
||||
quantity: data.quantity,
|
||||
unitPrice: data.unitPrice,
|
||||
sequence: data.sequence || 0,
|
||||
createdById: ctx.userId,
|
||||
});
|
||||
const savedPartida = await this.partidaRepository.save(partida);
|
||||
await this.recalculateTotal(ctx, presupuestoId);
|
||||
return savedPartida;
|
||||
}
|
||||
/**
|
||||
* Actualizar partida
|
||||
*/
|
||||
async updatePartida(ctx, partidaId, data) {
|
||||
const partida = await this.partidaRepository.findOne({
|
||||
where: {
|
||||
id: partidaId,
|
||||
tenantId: ctx.tenantId,
|
||||
deletedAt: null,
|
||||
},
|
||||
});
|
||||
if (!partida) {
|
||||
return null;
|
||||
}
|
||||
const updated = this.partidaRepository.merge(partida, {
|
||||
...data,
|
||||
updatedById: ctx.userId,
|
||||
});
|
||||
const saved = await this.partidaRepository.save(updated);
|
||||
await this.recalculateTotal(ctx, partida.presupuestoId);
|
||||
return saved;
|
||||
}
|
||||
/**
|
||||
* Eliminar partida
|
||||
*/
|
||||
async removePartida(ctx, partidaId) {
|
||||
const partida = await this.partidaRepository.findOne({
|
||||
where: {
|
||||
id: partidaId,
|
||||
tenantId: ctx.tenantId,
|
||||
},
|
||||
});
|
||||
if (!partida) {
|
||||
return false;
|
||||
}
|
||||
await this.partidaRepository.update({ id: partidaId }, {
|
||||
deletedAt: new Date(),
|
||||
deletedById: ctx.userId,
|
||||
});
|
||||
await this.recalculateTotal(ctx, partida.presupuestoId);
|
||||
return true;
|
||||
}
|
||||
/**
|
||||
* Recalcular total del presupuesto
|
||||
*/
|
||||
async recalculateTotal(ctx, presupuestoId) {
|
||||
const result = await this.partidaRepository
|
||||
.createQueryBuilder('p')
|
||||
.select('SUM(p.quantity * p.unit_price)', 'total')
|
||||
.where('p.presupuesto_id = :presupuestoId', { presupuestoId })
|
||||
.andWhere('p.deleted_at IS NULL')
|
||||
.getRawOne();
|
||||
const total = parseFloat(result?.total || '0');
|
||||
await this.repository.update({ id: presupuestoId }, { totalAmount: total, updatedById: ctx.userId });
|
||||
}
|
||||
/**
|
||||
* Crear nueva versión del presupuesto
|
||||
*/
|
||||
async createNewVersion(ctx, presupuestoId) {
|
||||
const original = await this.findWithPartidas(ctx, presupuestoId);
|
||||
if (!original) {
|
||||
throw new Error('Presupuesto not found');
|
||||
}
|
||||
// Desactivar versión anterior
|
||||
await this.repository.update({ id: presupuestoId }, { isActive: false, updatedById: ctx.userId });
|
||||
// Crear nueva versión
|
||||
const newVersion = await this.create(ctx, {
|
||||
code: original.code,
|
||||
name: original.name,
|
||||
description: original.description,
|
||||
fraccionamientoId: original.fraccionamientoId,
|
||||
prototipoId: original.prototipoId,
|
||||
currencyId: original.currencyId,
|
||||
version: original.version + 1,
|
||||
isActive: true,
|
||||
totalAmount: original.totalAmount,
|
||||
});
|
||||
// Copiar partidas
|
||||
for (const partida of original.partidas) {
|
||||
await this.partidaRepository.save(this.partidaRepository.create({
|
||||
tenantId: ctx.tenantId,
|
||||
presupuestoId: newVersion.id,
|
||||
conceptoId: partida.conceptoId,
|
||||
quantity: partida.quantity,
|
||||
unitPrice: partida.unitPrice,
|
||||
sequence: partida.sequence,
|
||||
createdById: ctx.userId,
|
||||
}));
|
||||
}
|
||||
return newVersion;
|
||||
}
|
||||
/**
|
||||
* Aprobar presupuesto
|
||||
*/
|
||||
async approve(ctx, presupuestoId) {
|
||||
const presupuesto = await this.findById(ctx, presupuestoId);
|
||||
if (!presupuesto) {
|
||||
return null;
|
||||
}
|
||||
return this.update(ctx, presupuestoId, {
|
||||
approvedAt: new Date(),
|
||||
approvedById: ctx.userId,
|
||||
});
|
||||
}
|
||||
}
|
||||
exports.PresupuestoService = PresupuestoService;
|
||||
//# sourceMappingURL=presupuesto.service.js.map
|
||||
1
backend/dist/modules/budgets/services/presupuesto.service.js.map
vendored
Normal file
1
backend/dist/modules/budgets/services/presupuesto.service.js.map
vendored
Normal file
@ -0,0 +1 @@
|
||||
{"version":3,"file":"presupuesto.service.js","sourceRoot":"","sources":["../../../../src/modules/budgets/services/presupuesto.service.ts"],"names":[],"mappings":"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"}
|
||||
15
backend/dist/modules/construction/controllers/etapa.controller.d.ts
vendored
Normal file
15
backend/dist/modules/construction/controllers/etapa.controller.d.ts
vendored
Normal file
@ -0,0 +1,15 @@
|
||||
/**
|
||||
* EtapaController - Controller de etapas
|
||||
*
|
||||
* Endpoints REST para gestión de etapas de fraccionamientos.
|
||||
*
|
||||
* @module Construction
|
||||
*/
|
||||
import { Router } from 'express';
|
||||
import { DataSource } from 'typeorm';
|
||||
/**
|
||||
* Crear router de etapas
|
||||
*/
|
||||
export declare function createEtapaController(dataSource: DataSource): Router;
|
||||
export default createEtapaController;
|
||||
//# sourceMappingURL=etapa.controller.d.ts.map
|
||||
1
backend/dist/modules/construction/controllers/etapa.controller.d.ts.map
vendored
Normal file
1
backend/dist/modules/construction/controllers/etapa.controller.d.ts.map
vendored
Normal file
@ -0,0 +1 @@
|
||||
{"version":3,"file":"etapa.controller.d.ts","sourceRoot":"","sources":["../../../../src/modules/construction/controllers/etapa.controller.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,EAAE,MAAM,EAAmC,MAAM,SAAS,CAAC;AAClE,OAAO,EAAE,UAAU,EAAE,MAAM,SAAS,CAAC;AASrC;;GAEG;AACH,wBAAgB,qBAAqB,CAAC,UAAU,EAAE,UAAU,GAAG,MAAM,CA6JpE;AAED,eAAe,qBAAqB,CAAC"}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user