Wizdaa Time Off

Wizdaa Time Off

EmpresaSistema de Gestion de Ausencias
Año2025
TipoFullstack App · GraphQL · Clean Architecture
RolArquitectura · Backend · Frontend
Volver
01

Gestionar licencias, vacaciones y permisos de empleados con planillas o emails genera errores, solicitudes duplicadas, balances desactualizados y cero trazabilidad. Se necesitaba un sistema que automatice el flujo completo: solicitud, aprobacion jerarquica, deduccion de dias y control de solapamientos — todo con reglas de negocio estrictas, acceso basado en roles y una arquitectura backend que sea testeable y mantenible a largo plazo.

02

Disene y desarrolle un monorepo fullstack con NestJS + GraphQL (code-first con Apollo Server) en backend y React + Vite en frontend. La arquitectura aplica Repository Pattern para aislar el acceso a datos via Prisma, tipos de dominio propios desacoplados del ORM para testabilidad, y Single Responsibility por clase. El sistema gestiona 4 tipos de licencia con balances anuales automaticos, flujo de estados PENDING → APPROVED/REJECTED → COMPLETED/CANCELLED, 8 reglas de negocio estrictas validadas en cada operacion, y control de acceso RBAC con 3 roles jerarquicos.

API GraphQL code-first con Apollo Server — queries tipadas: employees (con reportes directos), employee(id), timeOffRequests(filters: status/type/employeeId), leaveBalance(employeeId, year)
Mutations tipadas: createEmployee, createTimeOffRequest, reviewTimeOffRequest(decision: APPROVE/REJECT + reviewNote), cancelTimeOffRequest — cada una con validaciones de negocio antes de ejecutar
GraphQL Playground en /graphql para testing interactivo de queries y mutations en desarrollo
Repository Pattern estricto: los servicios nunca tocan Prisma directamente — EmployeeRepository, TimeOffRequestRepository y LeaveBalanceRepository aislan toda la capa de datos
Tipos de dominio propios (common/types/) desacoplados de los tipos generados por Prisma — el dominio no depende de la infraestructura, se puede cambiar el ORM sin tocar logica de negocio
Single Responsibility aplicado por clase: cada modulo (employees, time-off-requests, leave-balance) con su resolver, service y repository separados
Filtro global de excepciones para GraphQL (common/filters/) — errores de negocio se transforman en respuestas GraphQL tipadas, no en errores 500
Clases de excepciones custom ligeras (common/exceptions/) para cada caso: InsufficientBalanceException, OverlappingRequestException, UnauthorizedReviewException
8 reglas de negocio validadas en cada solicitud: sin fechas pasadas, start <= end, exclusion de fines de semana del computo de dias, sin solapamiento de solicitudes PENDING/APPROVED del mismo empleado
Control de saldo antes de aprobar: verifica dias disponibles por tipo de licencia. UNPAID (sin goce) siempre permitido sin limite
Prohibido auto-aprobarse: un empleado no puede revisar su propia solicitud aunque sea MANAGER
Restauracion automatica de dias al cancelar solicitudes APPROVED — el balance se recalcula y los dias vuelven al saldo disponible
4 tipos de licencia con balances anuales predefinidos: Vacaciones (15 dias), Enfermedad (10 dias), Personal (5 dias), Sin goce (ilimitado)
Calculo de dias habiles con utilidad propia (common/utils/) que excluye sabados y domingos del conteo — una solicitud de lunes a viernes cuenta 5 dias, no 7
Flujo de estados completo: PENDING → APPROVED/REJECTED por manager → COMPLETED (automatico al pasar la fecha) o CANCELLED (por el empleado con restauracion de balance)
Estructura jerarquica de empleados con relaciones de reporte (managerId) — el flujo de aprobacion sigue la cadena de mando
RBAC con 3 roles: EMPLOYEE (solicitar y cancelar propias), MANAGER (revisar solicitudes de sus reportes directos), ADMIN (acceso completo al sistema)
Testing con Jest — unit tests puros sin contenedor DI de NestJS. Servicios testeados con mocks de repositorios, cobertura de todas las reglas de negocio y edge cases
Seed de datos de prueba via Prisma: empleados con relaciones jerarquicas, balances por tipo y solicitudes en distintos estados para testing manual
Frontend React + Vite + TypeScript con Apollo Client para consumir la API GraphQL
Dashboard de manager: lista de solicitudes pendientes de sus reportes con acciones de aprobar/rechazar
Vista de empleado: mis solicitudes, balance disponible por tipo, formulario de nueva solicitud con validacion client-side
Docker Compose para PostgreSQL en desarrollo — un comando para levantar la base de datos
Prisma con migraciones automaticas, schema declarativo con enums (Role, LeaveType, RequestStatus) y foreign keys para integridad referencial
FLUJO — Solicitud de licencia: [Empleado] mutation createTimeOffRequest {type: VACATION, startDate, endDate} → [Resolver] extrae employeeId del contexto → [TimeOffService] ejecuta 8 validaciones: fecha no pasada, start <= end, excluye fines de semana (5 dias habiles, no 7), sin solapamiento con solicitudes PENDING/APPROVED del mismo empleado → calcula dias habiles → crea request con status=PENDING → persiste via TimeOffRequestRepository → response con requestId y status
FLUJO — Aprobacion por manager: [Manager] mutation reviewTimeOffRequest {id, decision: APPROVE, reviewNote} → [Resolver] extrae managerId → [TimeOffService] carga solicitud → verifica que el solicitante es reporte directo del manager (employee.managerId == managerId) → verifica que el manager NO es el solicitante (prohibido auto-aprobarse) → verifica status==PENDING → carga LeaveBalance del empleado para el tipo y ano → verifica dias disponibles >= dias solicitados (UNPAID siempre permitido) → deduce dias del balance → status=APPROVED → persiste balance y solicitud → response con solicitud actualizada
FLUJO — Cancelacion con restauracion de balance: [Empleado] mutation cancelTimeOffRequest {id} → [TimeOffService] carga solicitud → verifica ownership (solicitud.employeeId == userId) → si status==APPROVED: carga balance → suma dias de vuelta al saldo disponible → persiste balance actualizado → status=CANCELLED → si status==PENDING: simplemente status=CANCELLED (sin tocar balance) → persiste → response con solicitud cancelada
FLUJO — Control de acceso RBAC: [Request GraphQL] → [Resolver] extrae role del contexto → EMPLOYEE: solo ve sus solicitudes y balances, puede crear y cancelar propias → MANAGER: ve solicitudes de sus reportes directos (query employees con relacion managerId), puede aprobar/rechazar → ADMIN: acceso completo, ve todos los empleados, todas las solicitudes, puede crear empleados y modificar roles
FLUJO — Calculo de dias habiles: startDate=lunes 2025-06-02, endDate=viernes 2025-06-13 → itera cada dia del rango → lunes=habil, martes=habil, ..., sabado=NO, domingo=NO → cuenta solo lunes a viernes → resultado: 10 dias habiles (no 12 calendario). Este calculo se usa en validacion (verificar balance) y en deduccion (restar del saldo)
FLUJO — Query GraphQL completa: [Apollo Client] query { employees { id, name, role, directReports { id, name }, timeOffRequests(status: PENDING) { id, type, startDate, endDate } } } → [Apollo Server] parsea query → [EmployeeResolver] resuelve employees → [EmployeeService] → [EmployeeRepository] → Prisma query con include de relaciones → resuelve directReports (DataLoader pattern) → resuelve timeOffRequests filtradas → response JSON con estructura exacta pedida por el cliente
03
NestJS
GraphQL
Apollo Server
TypeScript
Prisma
PostgreSQL
React
Vite
Apollo Client
Jest
Docker
Node.js
04
GraphQLAPI code-first con queries y mutations tipadas
4 tiposLicencias con balance anual automatico
8 reglasValidaciones de negocio estrictas
3 rolesRBAC jerarquico (Employee/Manager/Admin)
RepositoryPattern con domain types desacoplados
JestUnit tests puros sin contenedor DI
05
Siguiente proyectoZocoFullStack