Initial commit - erp-construccion

This commit is contained in:
rckrdmrd 2026-01-04 06:12:06 -06:00
commit a9703b2727
17701 changed files with 2208306 additions and 0 deletions

119
.env Normal file
View 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
View 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
View 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
View File

@ -0,0 +1,478 @@
# Guia de Contribucion - ERP Construccion
Esta guia describe las convenciones y procesos para contribuir al proyecto.
---
## Requisitos Previos
- Node.js 20 LTS
- Docker y Docker Compose
- PostgreSQL 15 (o usar docker-compose)
- Git
---
## Setup Inicial
```bash
# 1. Clonar repositorio
git clone <repo-url>
cd apps/verticales/construccion
# 2. Instalar dependencias
cd backend && npm install
# 3. Configurar variables de entorno
cp ../.env.example .env
# Editar .env con credenciales locales
# 4. Levantar servicios
docker-compose up -d postgres redis
# 5. Ejecutar migraciones (si aplica)
npm run migration:run
# 6. Iniciar en desarrollo
npm run dev
```
---
## Estructura de Ramas
| Rama | Proposito |
|------|-----------|
| `main` | Produccion estable |
| `develop` | Integracion de features |
| `feature/*` | Nuevas funcionalidades |
| `fix/*` | Correcciones de bugs |
| `refactor/*` | Refactorizaciones |
| `docs/*` | Documentacion |
### Flujo de Trabajo
```
1. Crear rama desde develop
git checkout develop
git pull origin develop
git checkout -b feature/MAI-XXX-descripcion
2. Desarrollar y hacer commits
git commit -m "feat(modulo): descripcion del cambio"
3. Push y crear Pull Request
git push origin feature/MAI-XXX-descripcion
# Crear PR en GitHub hacia develop
4. Code Review y merge
```
---
## Convenciones de Codigo
### Nomenclatura
| Tipo | Convencion | Ejemplo |
|------|------------|---------|
| Archivos | kebab-case.tipo.ts | `concepto.entity.ts` |
| Clases | PascalCase + sufijo | `ConceptoService` |
| Interfaces | PascalCase + prefijo I | `IConceptoRepository` |
| Variables | camelCase | `totalAmount` |
| Constantes | UPPER_SNAKE_CASE | `DB_SCHEMAS` |
| Metodos | camelCase + verbo | `findByContrato` |
| Enums | PascalCase | `EstimacionStatus` |
| Tablas DB | snake_case plural | `presupuestos` |
| Columnas DB | snake_case | `tenant_id` |
### Estructura de Archivos por Modulo
```
modules/
└── nombre-modulo/
├── entities/
│ └── entidad.entity.ts
├── services/
│ └── entidad.service.ts
├── controllers/
│ └── entidad.controller.ts
├── dto/
│ └── entidad.dto.ts
└── index.ts
```
### Patron Entity
```typescript
import { Entity, Column, PrimaryGeneratedColumn, Index } from 'typeorm';
import { DB_SCHEMAS, DB_TABLES } from '@shared/constants';
@Entity({
schema: DB_SCHEMAS.CONSTRUCTION,
name: DB_TABLES.construction.CONCEPTOS,
})
@Index(['tenantId', 'code'], { unique: true })
export class Concepto {
@PrimaryGeneratedColumn('uuid')
id: string;
@Column({ name: 'tenant_id', type: 'uuid' })
tenantId: string;
@Column({ name: 'code', length: 20 })
code: string;
// Usar snake_case en name: para mapear a DB
@Column({ name: 'created_at', type: 'timestamptz', default: () => 'NOW()' })
createdAt: Date;
@Column({ name: 'deleted_at', type: 'timestamptz', nullable: true })
deletedAt: Date | null;
}
```
### Patron Service
```typescript
import { Repository } from 'typeorm';
import { BaseService, ServiceContext } from '@shared/services/base.service';
import { Concepto } from '../entities/concepto.entity';
export class ConceptoService extends BaseService<Concepto> {
constructor(repository: Repository<Concepto>) {
super(repository);
}
// Metodos especificos del dominio
async findByCode(ctx: ServiceContext, code: string): Promise<Concepto | null> {
return this.findOne(ctx, { code });
}
}
```
### Patron DTO
```typescript
import { IsString, IsOptional, IsUUID, MinLength, MaxLength } from 'class-validator';
export class CreateConceptoDto {
@IsString()
@MinLength(1)
@MaxLength(20)
code: string;
@IsString()
@MinLength(1)
@MaxLength(200)
name: string;
@IsOptional()
@IsUUID()
parentId?: string;
}
export class UpdateConceptoDto {
@IsOptional()
@IsString()
@MaxLength(200)
name?: string;
@IsOptional()
@IsNumber()
unitPrice?: number;
}
```
---
## SSOT - Constantes Centralizadas
### Regla Principal
**NUNCA** hardcodear:
- Nombres de schemas
- Nombres de tablas
- Rutas de API
- Valores de enums
### Uso Correcto
```typescript
// INCORRECTO
@Entity({ schema: 'construction', name: 'conceptos' })
// CORRECTO
import { DB_SCHEMAS, DB_TABLES } from '@shared/constants';
@Entity({
schema: DB_SCHEMAS.CONSTRUCTION,
name: DB_TABLES.construction.CONCEPTOS,
})
```
### Validacion Automatica
```bash
# Detecta hardcoding de constantes
npm run validate:constants
# Se ejecuta en pre-commit y CI
```
---
## Commits
### Formato de Mensaje
```
tipo(alcance): descripcion breve
[cuerpo opcional]
[footer opcional]
```
### Tipos de Commit
| Tipo | Descripcion |
|------|-------------|
| `feat` | Nueva funcionalidad |
| `fix` | Correccion de bug |
| `refactor` | Refactorizacion sin cambio funcional |
| `docs` | Documentacion |
| `test` | Tests |
| `chore` | Mantenimiento, dependencias |
| `style` | Formato, sin cambio de logica |
| `perf` | Mejora de performance |
### Ejemplos
```bash
# Feature
git commit -m "feat(budgets): agregar versionamiento de presupuestos"
# Fix
git commit -m "fix(estimates): corregir calculo de totales con decimales"
# Refactor
git commit -m "refactor(auth): simplificar validacion de tokens"
# Docs
git commit -m "docs(api): actualizar especificacion OpenAPI"
```
---
## Testing
### Ejecutar Tests
```bash
# Todos los tests
npm test
# Con cobertura
npm run test:coverage
# Watch mode
npm run test:watch
# Archivo especifico
npm test -- concepto.service.spec.ts
```
### Estructura de Test
```typescript
describe('ConceptoService', () => {
let service: ConceptoService;
let mockRepo: jest.Mocked<Repository<Concepto>>;
const ctx: ServiceContext = {
tenantId: 'test-tenant-uuid',
userId: 'test-user-uuid',
};
beforeEach(() => {
mockRepo = createMockRepository();
service = new ConceptoService(mockRepo);
});
describe('createConcepto', () => {
it('should create concepto with level 0 for root', async () => {
// Arrange
const dto = { code: '001', name: 'Test' };
mockRepo.save.mockResolvedValue({ id: 'uuid', ...dto, level: 0 });
// Act
const result = await service.createConcepto(ctx, dto);
// Assert
expect(result.level).toBe(0);
});
it('should throw on duplicate code', async () => {
// ...
});
});
});
```
### Cobertura Minima
- Statements: 80%
- Branches: 75%
- Functions: 80%
- Lines: 80%
---
## Linting y Formato
### Ejecutar Linting
```bash
# Verificar errores
npm run lint
# Corregir automaticamente
npm run lint:fix
```
### Configuracion ESLint
El proyecto usa ESLint con TypeScript. Reglas principales:
- No `any` implicito
- No variables no usadas
- Imports ordenados
- Espacios consistentes
### Pre-commit Hook
Se ejecuta automaticamente:
```bash
npm run precommit # lint + validate:constants
```
---
## Pull Requests
### Checklist
Antes de crear un PR, verificar:
- [ ] Tests pasan: `npm test`
- [ ] Linting pasa: `npm run lint`
- [ ] Constantes validadas: `npm run validate:constants`
- [ ] Build funciona: `npm run build`
- [ ] Documentacion actualizada (si aplica)
- [ ] Commits con formato correcto
### Template de PR
```markdown
## Descripcion
Breve descripcion del cambio.
## Tipo de Cambio
- [ ] Feature
- [ ] Bug fix
- [ ] Refactor
- [ ] Docs
- [ ] Tests
## Modulo Afectado
- MAI-XXX: Nombre del modulo
## Testing
Describir como probar los cambios.
## Screenshots (si aplica)
## Checklist
- [ ] Tests agregados/actualizados
- [ ] Documentacion actualizada
- [ ] Sin breaking changes (o documentados)
```
---
## Migraciones de Base de Datos
### Generar Migracion
```bash
# Despues de modificar entidades
npm run migration:generate -- -n NombreDescriptivo
```
### Ejecutar Migraciones
```bash
# Aplicar pendientes
npm run migration:run
# Revertir ultima
npm run migration:revert
```
### Convenciones
- Una migracion por feature/fix
- Nombres descriptivos: `AddStatusToEstimaciones`
- Incluir rollback funcional
- No modificar migraciones ya aplicadas en produccion
---
## Debugging
### VS Code
```json
// .vscode/launch.json
{
"version": "0.2.0",
"configurations": [
{
"type": "node",
"request": "launch",
"name": "Debug Backend",
"runtimeArgs": ["-r", "ts-node/register"],
"args": ["${workspaceFolder}/backend/src/server.ts"],
"env": {
"NODE_ENV": "development",
"LOG_LEVEL": "debug"
}
}
]
}
```
### Variables de Entorno para Debug
```bash
LOG_LEVEL=debug
TYPEORM_LOGGING=true
```
---
## Contacto
- **Issues:** Reportar bugs o sugerir features en GitHub Issues
- **PRs:** Siempre bienvenidos, seguir las convenciones
---
**Ultima actualizacion:** 2025-12-12

31
INVENTARIO.yml Normal file
View 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
View 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
View 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
View 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
View 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
View 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

View 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

View 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"}

View 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

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,5 @@
/**
* Auth Controllers - Export
*/
export * from './auth.controller';
//# sourceMappingURL=index.d.ts.map

View 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"}

View 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

View File

@ -0,0 +1 @@
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../../src/modules/auth/controllers/index.ts"],"names":[],"mappings":";AAAA;;GAEG;;;;;;;;;;;;;;;;AAEH,oDAAkC"}

View 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

View 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"}

View 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

View File

@ -0,0 +1 @@
{"version":3,"file":"auth.dto.js","sourceRoot":"","sources":["../../../../src/modules/auth/dto/auth.dto.ts"],"names":[],"mappings":";AAAA;;;;GAIG"}

View 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

View 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"}

View 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

View 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"}

View 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

View 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"}

View 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

View 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"}

View 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

View 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"}

View 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

View 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"}

View 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

View 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"}

View 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

View 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"}

View 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

View 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"}

View 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

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

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

View 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"}

View 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

View 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"}

View 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

View 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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"}

View 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

View 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"}

View 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

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,5 @@
/**
* Auth Module - Service Exports
*/
export * from './auth.service';
//# sourceMappingURL=index.d.ts.map

View 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"}

View 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

View File

@ -0,0 +1 @@
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../../src/modules/auth/services/index.ts"],"names":[],"mappings":";AAAA;;GAEG;;;;;;;;;;;;;;;;AAEH,iDAA+B"}

View 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

View 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"}

View 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

File diff suppressed because one or more lines are too long

View 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

View 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"}

View 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

View 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"}

View 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

View 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"}

View 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

File diff suppressed because one or more lines are too long

View 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

View 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"}

View 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

View 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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"}

View 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

View 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"}

View 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

View 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"}

View 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

View 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"}

View 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

View 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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"}

View 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

View 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"}

View 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

View 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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"}

View 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

View 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"}

View 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

View 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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"}

View File

@ -0,0 +1,6 @@
/**
* Budgets Module - Service Exports
*/
export * from './concepto.service';
export * from './presupuesto.service';
//# sourceMappingURL=index.d.ts.map

View 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"}

View 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

View 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"}

View 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

View 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"}

View 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

View 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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"}

View 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

View 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