Backend de erp-construccion - Workspace V2
Parameter 's' in the categoryStats.map() callback on line 404 was implicitly typed as 'any', causing TS7006 error. Added explicit 'any' type annotation to resolve the TypeScript compilation error. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> |
||
|---|---|---|
| scripts | ||
| src | ||
| .env.example | ||
| .gitignore | ||
| Dockerfile | ||
| package-lock.json | ||
| package.json | ||
| README.md | ||
| service.descriptor.yml | ||
| tsconfig.json | ||
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
# 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.
// 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.
// 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.
// 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.
// 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.
// 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.
// 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
# 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
@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
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
# Ejecutar tests
npm test
# Con cobertura
npm run test:coverage
# Watch mode
npm run test:watch
// 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
{
"type": "node",
"request": "launch",
"name": "Debug Backend",
"runtimeArgs": ["-r", "ts-node/register"],
"args": ["${workspaceFolder}/src/server.ts"],
"env": { "NODE_ENV": "development" }
}
Logs
// Configurar en .env
LOG_LEVEL=debug
LOG_FORMAT=dev
Ultima actualizacion: 2025-12-12