# ET-HR-BACKEND - Especificacion Tecnica Backend HR

## METADATOS

| Campo | Valor |
|-------|-------|
| **Modulo** | MGN-014 |
| **Nombre** | Human Resources (HR) |
| **Version** | 1.0.0 |
| **Fecha** | 2026-01-10 |
| **Ubicacion** | `backend/src/modules/hr/` |
| **Schema BD** | `hr` |
| **Estado** | Implementado |

---

## SERVICIOS

### Resumen de Servicios (7)

| # | Servicio | Archivo | Descripcion |
|---|----------|---------|-------------|
| 1 | EmployeesService | `employees.service.ts` | Gestion de empleados |
| 2 | DepartmentsService | `departments.service.ts` | Departamentos y puestos |
| 3 | ContractsService | `contracts.service.ts` | Contratos laborales |
| 4 | LeavesService | `leaves.service.ts` | Ausencias y vacaciones |
| 5 | SkillsService | `skills.service.ts` | Competencias y habilidades |
| 6 | ExpensesService | `expenses.service.ts` | Gastos de empleados |
| 7 | PayslipsService | `payslips.service.ts` | Nominas y recibos |

---

### 1. EmployeesService

**Archivo:** `employees.service.ts`

#### Metodos

| Metodo | Parametros | Retorno | Descripcion |
|--------|------------|---------|-------------|
| `findAll` | `tenantId: string, filters: EmployeeFilters` | `Promise<{ data: Employee[]; total: number }>` | Lista empleados con paginacion y filtros |
| `findById` | `id: string, tenantId: string` | `Promise<Employee>` | Obtiene empleado por ID |
| `create` | `dto: CreateEmployeeDto, tenantId: string, userId: string` | `Promise<Employee>` | Crea nuevo empleado |
| `update` | `id: string, dto: UpdateEmployeeDto, tenantId: string, userId: string` | `Promise<Employee>` | Actualiza empleado |
| `terminate` | `id: string, terminationDate: string, tenantId: string, userId: string` | `Promise<Employee>` | Da de baja empleado |
| `reactivate` | `id: string, tenantId: string, userId: string` | `Promise<Employee>` | Reactiva empleado |
| `delete` | `id: string, tenantId: string` | `Promise<void>` | Elimina empleado |
| `getSubordinates` | `id: string, tenantId: string` | `Promise<Employee[]>` | Obtiene subordinados |

#### Reglas de Negocio

- El `employee_number` debe ser unico por tenant
- No se puede eliminar un empleado con contratos asociados
- No se puede eliminar un empleado que es manager de otros
- Al terminar un empleado, se terminan sus contratos activos

---

### 2. DepartmentsService

**Archivo:** `departments.service.ts`

#### Metodos - Departamentos

| Metodo | Parametros | Retorno | Descripcion |
|--------|------------|---------|-------------|
| `findAll` | `tenantId: string, filters: DepartmentFilters` | `Promise<{ data: Department[]; total: number }>` | Lista departamentos |
| `findById` | `id: string, tenantId: string` | `Promise<Department>` | Obtiene departamento por ID |
| `create` | `dto: CreateDepartmentDto, tenantId: string, userId: string` | `Promise<Department>` | Crea departamento |
| `update` | `id: string, dto: UpdateDepartmentDto, tenantId: string` | `Promise<Department>` | Actualiza departamento |
| `delete` | `id: string, tenantId: string` | `Promise<void>` | Elimina departamento |

#### Metodos - Puestos de Trabajo

| Metodo | Parametros | Retorno | Descripcion |
|--------|------------|---------|-------------|
| `getJobPositions` | `tenantId: string, includeInactive?: boolean` | `Promise<JobPosition[]>` | Lista puestos de trabajo |
| `getJobPositionById` | `id: string, tenantId: string` | `Promise<JobPosition>` | Obtiene puesto por ID |
| `createJobPosition` | `dto: CreateJobPositionDto, tenantId: string` | `Promise<JobPosition>` | Crea puesto |
| `updateJobPosition` | `id: string, dto: UpdateJobPositionDto, tenantId: string` | `Promise<JobPosition>` | Actualiza puesto |
| `deleteJobPosition` | `id: string, tenantId: string` | `Promise<void>` | Elimina puesto |

#### Reglas de Negocio

- El nombre del departamento debe ser unico por empresa
- No se puede eliminar un departamento con empleados
- No se puede eliminar un departamento con subdepartamentos
- El nombre del puesto debe ser unico por tenant

---

### 3. ContractsService

**Archivo:** `contracts.service.ts`

#### Metodos

| Metodo | Parametros | Retorno | Descripcion |
|--------|------------|---------|-------------|
| `findAll` | `tenantId: string, filters: ContractFilters` | `Promise<{ data: Contract[]; total: number }>` | Lista contratos |
| `findById` | `id: string, tenantId: string` | `Promise<Contract>` | Obtiene contrato por ID |
| `create` | `dto: CreateContractDto, tenantId: string, userId: string` | `Promise<Contract>` | Crea contrato |
| `update` | `id: string, dto: UpdateContractDto, tenantId: string, userId: string` | `Promise<Contract>` | Actualiza contrato |
| `activate` | `id: string, tenantId: string, userId: string` | `Promise<Contract>` | Activa contrato |
| `terminate` | `id: string, terminationDate: string, tenantId: string, userId: string` | `Promise<Contract>` | Termina contrato |
| `cancel` | `id: string, tenantId: string, userId: string` | `Promise<Contract>` | Cancela contrato |
| `delete` | `id: string, tenantId: string` | `Promise<void>` | Elimina contrato |

#### Flujo de Estados

```
draft -> active -> expired/terminated
draft -> cancelled
```

#### Reglas de Negocio

- Un empleado solo puede tener un contrato activo
- Solo se pueden editar contratos en estado `draft`
- Solo se pueden eliminar contratos en `draft` o `cancelled`
- Al activar, actualiza el departamento y puesto del empleado

---

### 4. LeavesService

**Archivo:** `leaves.service.ts`

#### Metodos - Tipos de Ausencia

| Metodo | Parametros | Retorno | Descripcion |
|--------|------------|---------|-------------|
| `getLeaveTypes` | `tenantId: string, includeInactive?: boolean` | `Promise<LeaveTypeConfig[]>` | Lista tipos de ausencia |
| `getLeaveTypeById` | `id: string, tenantId: string` | `Promise<LeaveTypeConfig>` | Obtiene tipo por ID |
| `createLeaveType` | `dto: CreateLeaveTypeDto, tenantId: string` | `Promise<LeaveTypeConfig>` | Crea tipo de ausencia |
| `updateLeaveType` | `id: string, dto: UpdateLeaveTypeDto, tenantId: string` | `Promise<LeaveTypeConfig>` | Actualiza tipo |
| `deleteLeaveType` | `id: string, tenantId: string` | `Promise<void>` | Elimina tipo |

#### Metodos - Ausencias

| Metodo | Parametros | Retorno | Descripcion |
|--------|------------|---------|-------------|
| `findAll` | `tenantId: string, filters: LeaveFilters` | `Promise<{ data: Leave[]; total: number }>` | Lista ausencias |
| `findById` | `id: string, tenantId: string` | `Promise<Leave>` | Obtiene ausencia por ID |
| `create` | `dto: CreateLeaveDto, tenantId: string, userId: string` | `Promise<Leave>` | Crea ausencia |
| `update` | `id: string, dto: UpdateLeaveDto, tenantId: string, userId: string` | `Promise<Leave>` | Actualiza ausencia |
| `submit` | `id: string, tenantId: string, userId: string` | `Promise<Leave>` | Envia solicitud |
| `approve` | `id: string, tenantId: string, userId: string` | `Promise<Leave>` | Aprueba solicitud |
| `reject` | `id: string, reason: string, tenantId: string, userId: string` | `Promise<Leave>` | Rechaza solicitud |
| `cancel` | `id: string, tenantId: string, userId: string` | `Promise<Leave>` | Cancela solicitud |
| `delete` | `id: string, tenantId: string` | `Promise<void>` | Elimina ausencia |

#### Flujo de Estados

```
draft -> submitted -> approved
                   -> rejected
draft/submitted/approved -> cancelled
```

#### Reglas de Negocio

- Se calcula automaticamente el numero de dias
- No se permiten ausencias solapadas
- Se respeta el maximo de dias por tipo
- Al aprobar, se actualiza el estado del empleado si corresponde

---

### 5. SkillsService

**Archivo:** `skills.service.ts`

#### Metodos - Tipos de Habilidad

| Metodo | Parametros | Retorno | Descripcion |
|--------|------------|---------|-------------|
| `findAllSkillTypes` | `tenantId: string, filters: SkillTypeFilters` | `Promise<{ data: SkillType[]; total: number; page: number; limit: number }>` | Lista tipos |
| `findSkillTypeById` | `id: string, tenantId: string` | `Promise<SkillType \| null>` | Obtiene tipo por ID |
| `createSkillType` | `tenantId: string, dto: CreateSkillTypeDto` | `Promise<SkillType>` | Crea tipo |
| `updateSkillType` | `id: string, tenantId: string, dto: UpdateSkillTypeDto` | `Promise<SkillType \| null>` | Actualiza tipo |
| `deleteSkillType` | `id: string, tenantId: string` | `Promise<boolean>` | Elimina tipo |

#### Metodos - Habilidades

| Metodo | Parametros | Retorno | Descripcion |
|--------|------------|---------|-------------|
| `findAllSkills` | `tenantId: string, filters: SkillFilters` | `Promise<{ data: Skill[]; total: number; page: number; limit: number }>` | Lista habilidades |
| `findSkillById` | `id: string, tenantId: string` | `Promise<Skill \| null>` | Obtiene habilidad por ID |
| `createSkill` | `tenantId: string, dto: CreateSkillDto` | `Promise<Skill>` | Crea habilidad |
| `updateSkill` | `id: string, tenantId: string, dto: UpdateSkillDto` | `Promise<Skill \| null>` | Actualiza habilidad |
| `deleteSkill` | `id: string, tenantId: string` | `Promise<boolean>` | Elimina habilidad |

#### Metodos - Niveles de Habilidad

| Metodo | Parametros | Retorno | Descripcion |
|--------|------------|---------|-------------|
| `findAllSkillLevels` | `tenantId: string, filters: SkillLevelFilters` | `Promise<{ data: SkillLevel[]; total: number; page: number; limit: number }>` | Lista niveles |
| `findSkillLevelById` | `id: string, tenantId: string` | `Promise<SkillLevel \| null>` | Obtiene nivel por ID |
| `createSkillLevel` | `tenantId: string, dto: CreateSkillLevelDto` | `Promise<SkillLevel>` | Crea nivel |
| `updateSkillLevel` | `id: string, tenantId: string, dto: UpdateSkillLevelDto` | `Promise<SkillLevel \| null>` | Actualiza nivel |
| `deleteSkillLevel` | `id: string, tenantId: string` | `Promise<boolean>` | Elimina nivel |

#### Metodos - Habilidades de Empleado

| Metodo | Parametros | Retorno | Descripcion |
|--------|------------|---------|-------------|
| `findAllEmployeeSkills` | `tenantId: string, filters: EmployeeSkillFilters` | `Promise<{ data: EmployeeSkill[]; total: number; page: number; limit: number }>` | Lista habilidades de empleados |
| `findEmployeeSkillById` | `id: string, tenantId: string` | `Promise<EmployeeSkill \| null>` | Obtiene por ID |
| `createEmployeeSkill` | `tenantId: string, dto: CreateEmployeeSkillDto` | `Promise<EmployeeSkill>` | Asigna habilidad |
| `updateEmployeeSkill` | `id: string, tenantId: string, dto: UpdateEmployeeSkillDto` | `Promise<EmployeeSkill \| null>` | Actualiza asignacion |
| `deleteEmployeeSkill` | `id: string, tenantId: string` | `Promise<boolean>` | Elimina asignacion |
| `getSkillsByEmployee` | `employeeId: string, tenantId: string` | `Promise<EmployeeSkill[]>` | Obtiene habilidades de un empleado |

---

### 6. ExpensesService

**Archivo:** `expenses.service.ts`

#### Metodos - Hojas de Gastos

| Metodo | Parametros | Retorno | Descripcion |
|--------|------------|---------|-------------|
| `findAllSheets` | `tenantId: string, filters: ExpenseSheetFilters` | `Promise<{ data: ExpenseSheet[]; total: number; page: number; limit: number }>` | Lista hojas |
| `findSheetById` | `id: string, tenantId: string` | `Promise<ExpenseSheet \| null>` | Obtiene hoja por ID |
| `createSheet` | `tenantId: string, dto: CreateExpenseSheetDto, userId?: string` | `Promise<ExpenseSheet>` | Crea hoja |
| `updateSheet` | `id: string, tenantId: string, dto: UpdateExpenseSheetDto` | `Promise<ExpenseSheet \| null>` | Actualiza hoja |
| `submitSheet` | `id: string, tenantId: string` | `Promise<ExpenseSheet \| null>` | Envia hoja |
| `approveSheet` | `id: string, tenantId: string, approvedBy: string` | `Promise<ExpenseSheet \| null>` | Aprueba hoja |
| `rejectSheet` | `id: string, tenantId: string` | `Promise<ExpenseSheet \| null>` | Rechaza hoja |
| `deleteSheet` | `id: string, tenantId: string` | `Promise<boolean>` | Elimina hoja |

#### Metodos - Gastos

| Metodo | Parametros | Retorno | Descripcion |
|--------|------------|---------|-------------|
| `findAllExpenses` | `tenantId: string, filters: ExpenseFilters` | `Promise<{ data: Expense[]; total: number; page: number; limit: number }>` | Lista gastos |
| `findExpenseById` | `id: string, tenantId: string` | `Promise<Expense \| null>` | Obtiene gasto por ID |
| `createExpense` | `tenantId: string, dto: CreateExpenseDto, userId?: string` | `Promise<Expense>` | Crea gasto |
| `updateExpense` | `id: string, tenantId: string, dto: UpdateExpenseDto` | `Promise<Expense \| null>` | Actualiza gasto |
| `deleteExpense` | `id: string, tenantId: string` | `Promise<boolean>` | Elimina gasto |
| `recalculateSheetTotals` | `sheetId: string, tenantId: string` | `Promise<void>` | Recalcula totales |

#### Flujo de Estados

```
draft -> submitted -> approved -> posted -> paid
                   -> rejected
```

#### Reglas de Negocio

- Solo se pueden modificar/eliminar gastos en `draft`
- `total_amount` se calcula como `unit_amount * quantity`
- Los totales de la hoja se recalculan automaticamente

---

### 7. PayslipsService

**Archivo:** `payslips.service.ts`

#### Metodos - Estructuras de Nomina

| Metodo | Parametros | Retorno | Descripcion |
|--------|------------|---------|-------------|
| `findAllStructures` | `tenantId: string, filters: PayslipStructureFilters` | `Promise<{ data: PayslipStructure[]; total: number; page: number; limit: number }>` | Lista estructuras |
| `findStructureById` | `id: string, tenantId: string` | `Promise<PayslipStructure \| null>` | Obtiene estructura por ID |
| `createStructure` | `tenantId: string, dto: CreatePayslipStructureDto` | `Promise<PayslipStructure>` | Crea estructura |
| `updateStructure` | `id: string, tenantId: string, dto: UpdatePayslipStructureDto` | `Promise<PayslipStructure \| null>` | Actualiza estructura |
| `deleteStructure` | `id: string, tenantId: string` | `Promise<boolean>` | Elimina estructura |

#### Metodos - Nominas

| Metodo | Parametros | Retorno | Descripcion |
|--------|------------|---------|-------------|
| `findAllPayslips` | `tenantId: string, filters: PayslipFilters` | `Promise<{ data: Payslip[]; total: number; page: number; limit: number }>` | Lista nominas |
| `findPayslipById` | `id: string, tenantId: string` | `Promise<Payslip \| null>` | Obtiene nomina por ID |
| `createPayslip` | `tenantId: string, dto: CreatePayslipDto, userId?: string` | `Promise<Payslip>` | Crea nomina |
| `updatePayslip` | `id: string, tenantId: string, dto: UpdatePayslipDto` | `Promise<Payslip \| null>` | Actualiza nomina |
| `verifyPayslip` | `id: string, tenantId: string` | `Promise<Payslip \| null>` | Verifica nomina |
| `confirmPayslip` | `id: string, tenantId: string` | `Promise<Payslip \| null>` | Confirma nomina |
| `cancelPayslip` | `id: string, tenantId: string` | `Promise<Payslip \| null>` | Cancela nomina |
| `deletePayslip` | `id: string, tenantId: string` | `Promise<boolean>` | Elimina nomina |

#### Metodos - Lineas de Nomina

| Metodo | Parametros | Retorno | Descripcion |
|--------|------------|---------|-------------|
| `getPayslipLines` | `payslipId: string, tenantId: string` | `Promise<PayslipLine[]>` | Obtiene lineas |
| `addPayslipLine` | `payslipId: string, tenantId: string, dto: CreatePayslipLineDto` | `Promise<PayslipLine \| null>` | Agrega linea |
| `updatePayslipLine` | `payslipId: string, lineId: string, tenantId: string, dto: UpdatePayslipLineDto` | `Promise<PayslipLine \| null>` | Actualiza linea |
| `removePayslipLine` | `payslipId: string, lineId: string, tenantId: string` | `Promise<boolean>` | Elimina linea |

#### Flujo de Estados

```
draft -> verify -> done
draft/verify -> cancel
```

#### Reglas de Negocio

- Solo se pueden modificar nominas en `draft`
- Solo se pueden eliminar nominas en `draft` o `cancel`
- Los totales se recalculan al modificar lineas
- El numero de nomina se genera al confirmar

---

## ENTIDADES

### Employee

```typescript
interface Employee {
  id: string;
  tenant_id: string;
  company_id: string;
  company_name?: string;
  employee_number: string;
  first_name: string;
  last_name: string;
  middle_name?: string;
  full_name?: string;
  user_id?: string;
  birth_date?: Date;
  gender?: string;
  marital_status?: string;
  nationality?: string;
  identification_id?: string;
  identification_type?: string;
  social_security_number?: string;
  tax_id?: string;
  email?: string;
  work_email?: string;
  phone?: string;
  work_phone?: string;
  mobile?: string;
  emergency_contact?: string;
  emergency_phone?: string;
  street?: string;
  city?: string;
  state?: string;
  zip?: string;
  country?: string;
  department_id?: string;
  department_name?: string;
  job_position_id?: string;
  job_position_name?: string;
  manager_id?: string;
  manager_name?: string;
  hire_date: Date;
  termination_date?: Date;
  status: EmployeeStatus;
  bank_name?: string;
  bank_account?: string;
  bank_clabe?: string;
  photo_url?: string;
  notes?: string;
  created_at: Date;
}

type EmployeeStatus = 'active' | 'inactive' | 'on_leave' | 'terminated';
```

### Department

```typescript
interface Department {
  id: string;
  tenant_id: string;
  company_id: string;
  company_name?: string;
  name: string;
  code?: string;
  parent_id?: string;
  parent_name?: string;
  manager_id?: string;
  manager_name?: string;
  description?: string;
  color?: string;
  active: boolean;
  employee_count?: number;
  created_at: Date;
}
```

### JobPosition

```typescript
interface JobPosition {
  id: string;
  tenant_id: string;
  name: string;
  department_id?: string;
  department_name?: string;
  description?: string;
  requirements?: string;
  responsibilities?: string;
  min_salary?: number;
  max_salary?: number;
  active: boolean;
  employee_count?: number;
  created_at: Date;
}
```

### Contract

```typescript
interface Contract {
  id: string;
  tenant_id: string;
  company_id: string;
  company_name?: string;
  employee_id: string;
  employee_name?: string;
  employee_number?: string;
  name: string;
  reference?: string;
  contract_type: ContractType;
  status: ContractStatus;
  job_position_id?: string;
  job_position_name?: string;
  department_id?: string;
  department_name?: string;
  date_start: Date;
  date_end?: Date;
  trial_date_end?: Date;
  wage: number;
  wage_type: string;
  currency_id?: string;
  currency_code?: string;
  hours_per_week: number;
  vacation_days: number;
  christmas_bonus_days: number;
  document_url?: string;
  notes?: string;
  created_at: Date;
}

type ContractStatus = 'draft' | 'active' | 'expired' | 'terminated' | 'cancelled';
type ContractType = 'permanent' | 'temporary' | 'contractor' | 'internship' | 'part_time';
```

### Leave

```typescript
interface Leave {
  id: string;
  tenant_id: string;
  company_id: string;
  company_name?: string;
  employee_id: string;
  employee_name?: string;
  employee_number?: string;
  leave_type_id: string;
  leave_type_name?: string;
  name?: string;
  date_from: Date;
  date_to: Date;
  number_of_days: number;
  status: LeaveStatus;
  description?: string;
  approved_by?: string;
  approved_by_name?: string;
  approved_at?: Date;
  rejection_reason?: string;
  created_at: Date;
}

type LeaveStatus = 'draft' | 'submitted' | 'approved' | 'rejected' | 'cancelled';
type LeaveType = 'vacation' | 'sick' | 'personal' | 'maternity' | 'paternity' | 'bereavement' | 'unpaid' | 'other';
```

### LeaveTypeConfig

```typescript
interface LeaveTypeConfig {
  id: string;
  tenant_id: string;
  name: string;
  code?: string;
  leave_type: LeaveType;
  requires_approval: boolean;
  max_days?: number;
  is_paid: boolean;
  color?: string;
  active: boolean;
  created_at: Date;
}
```

### Skill, SkillType, SkillLevel, EmployeeSkill

```typescript
interface SkillType {
  id: string;
  tenant_id: string;
  name: string;
  skill_levels: string;
  created_at: Date;
}

interface Skill {
  id: string;
  tenant_id: string;
  skill_type_id: string;
  skill_type_name?: string;
  name: string;
  created_at: Date;
}

interface SkillLevel {
  id: string;
  tenant_id: string;
  skill_type_id: string;
  skill_type_name?: string;
  name: string;
  level: number;
  created_at: Date;
}

interface EmployeeSkill {
  id: string;
  employee_id: string;
  employee_name?: string;
  skill_id: string;
  skill_name?: string;
  skill_level_id?: string;
  skill_level_name?: string;
  skill_type_id?: string;
  skill_type_name?: string;
  created_at: Date;
}
```

### ExpenseSheet, Expense

```typescript
interface ExpenseSheet {
  id: string;
  tenant_id: string;
  company_id: string;
  employee_id: string;
  employee_name?: string;
  name: string;
  state: ExpenseStatus;
  total_amount: number;
  untaxed_amount: number;
  total_amount_taxes: number;
  journal_id?: string;
  account_move_id?: string;
  user_id?: string;
  user_name?: string;
  approved_by?: string;
  approved_by_name?: string;
  approved_date?: Date;
  accounting_date?: Date;
  created_at: Date;
  created_by?: string;
  updated_at?: Date;
}

interface Expense {
  id: string;
  tenant_id: string;
  company_id: string;
  employee_id: string;
  employee_name?: string;
  name: string;
  sheet_id?: string;
  sheet_name?: string;
  product_id?: string;
  product_name?: string;
  unit_amount: number;
  quantity: number;
  total_amount: number;
  untaxed_amount?: number;
  total_amount_taxes?: number;
  currency_id?: string;
  currency_code?: string;
  tax_ids?: string[];
  date: Date;
  description?: string;
  reference?: string;
  analytic_account_id?: string;
  analytic_account_name?: string;
  state: ExpenseStatus;
  payment_mode: string;
  created_at: Date;
  created_by?: string;
  updated_at?: Date;
}

type ExpenseStatus = 'draft' | 'submitted' | 'approved' | 'posted' | 'paid' | 'rejected';
```

### Payslip, PayslipStructure, PayslipLine

```typescript
interface PayslipStructure {
  id: string;
  tenant_id: string;
  name: string;
  code?: string;
  is_active: boolean;
  note?: string;
  created_at: Date;
}

interface Payslip {
  id: string;
  tenant_id: string;
  company_id: string;
  employee_id: string;
  employee_name?: string;
  employee_number?: string;
  contract_id?: string;
  contract_name?: string;
  name: string;
  number?: string;
  state: PayslipStatus;
  date_from: Date;
  date_to: Date;
  date?: Date;
  structure_id?: string;
  structure_name?: string;
  basic_wage?: number;
  gross_wage?: number;
  net_wage?: number;
  worked_days?: number;
  worked_hours?: number;
  journal_id?: string;
  move_id?: string;
  created_at: Date;
  created_by?: string;
  updated_at?: Date;
}

interface PayslipLine {
  id: string;
  payslip_id: string;
  name: string;
  code: string;
  sequence: number;
  category?: string;
  quantity: number;
  rate: number;
  amount: number;
  total: number;
  appears_on_payslip: boolean;
  created_at: Date;
}

type PayslipStatus = 'draft' | 'verify' | 'done' | 'cancel';
```

---

## DTOs

### Employees DTOs

```typescript
interface CreateEmployeeDto {
  company_id: string;
  employee_number: string;
  first_name: string;
  last_name: string;
  middle_name?: string;
  user_id?: string;
  birth_date?: string;
  gender?: string;
  marital_status?: string;
  nationality?: string;
  identification_id?: string;
  identification_type?: string;
  social_security_number?: string;
  tax_id?: string;
  email?: string;
  work_email?: string;
  phone?: string;
  work_phone?: string;
  mobile?: string;
  emergency_contact?: string;
  emergency_phone?: string;
  street?: string;
  city?: string;
  state?: string;
  zip?: string;
  country?: string;
  department_id?: string;
  job_position_id?: string;
  manager_id?: string;
  hire_date: string;
  bank_name?: string;
  bank_account?: string;
  bank_clabe?: string;
  photo_url?: string;
  notes?: string;
}

interface UpdateEmployeeDto {
  first_name?: string;
  last_name?: string;
  middle_name?: string | null;
  user_id?: string | null;
  birth_date?: string | null;
  gender?: string | null;
  marital_status?: string | null;
  nationality?: string | null;
  identification_id?: string | null;
  identification_type?: string | null;
  social_security_number?: string | null;
  tax_id?: string | null;
  email?: string | null;
  work_email?: string | null;
  phone?: string | null;
  work_phone?: string | null;
  mobile?: string | null;
  emergency_contact?: string | null;
  emergency_phone?: string | null;
  street?: string | null;
  city?: string | null;
  state?: string | null;
  zip?: string | null;
  country?: string | null;
  department_id?: string | null;
  job_position_id?: string | null;
  manager_id?: string | null;
  bank_name?: string | null;
  bank_account?: string | null;
  bank_clabe?: string | null;
  photo_url?: string | null;
  notes?: string | null;
}

interface EmployeeFilters {
  company_id?: string;
  department_id?: string;
  status?: EmployeeStatus;
  manager_id?: string;
  search?: string;
  page?: number;
  limit?: number;
}
```

### Departments DTOs

```typescript
interface CreateDepartmentDto {
  company_id: string;
  name: string;
  code?: string;
  parent_id?: string;
  manager_id?: string;
  description?: string;
  color?: string;
}

interface UpdateDepartmentDto {
  name?: string;
  code?: string | null;
  parent_id?: string | null;
  manager_id?: string | null;
  description?: string | null;
  color?: string | null;
  active?: boolean;
}

interface DepartmentFilters {
  company_id?: string;
  active?: boolean;
  search?: string;
  page?: number;
  limit?: number;
}

interface CreateJobPositionDto {
  name: string;
  department_id?: string;
  description?: string;
  requirements?: string;
  responsibilities?: string;
  min_salary?: number;
  max_salary?: number;
}

interface UpdateJobPositionDto {
  name?: string;
  department_id?: string | null;
  description?: string | null;
  requirements?: string | null;
  responsibilities?: string | null;
  min_salary?: number | null;
  max_salary?: number | null;
  active?: boolean;
}
```

### Contracts DTOs

```typescript
interface CreateContractDto {
  company_id: string;
  employee_id: string;
  name: string;
  reference?: string;
  contract_type: ContractType;
  job_position_id?: string;
  department_id?: string;
  date_start: string;
  date_end?: string;
  trial_date_end?: string;
  wage: number;
  wage_type?: string;
  currency_id?: string;
  hours_per_week?: number;
  vacation_days?: number;
  christmas_bonus_days?: number;
  document_url?: string;
  notes?: string;
}

interface UpdateContractDto {
  reference?: string | null;
  job_position_id?: string | null;
  department_id?: string | null;
  date_end?: string | null;
  trial_date_end?: string | null;
  wage?: number;
  wage_type?: string;
  currency_id?: string | null;
  hours_per_week?: number;
  vacation_days?: number;
  christmas_bonus_days?: number;
  document_url?: string | null;
  notes?: string | null;
}

interface ContractFilters {
  company_id?: string;
  employee_id?: string;
  status?: ContractStatus;
  contract_type?: ContractType;
  search?: string;
  page?: number;
  limit?: number;
}
```

### Leaves DTOs

```typescript
interface CreateLeaveTypeDto {
  name: string;
  code?: string;
  leave_type: LeaveType;
  requires_approval?: boolean;
  max_days?: number;
  is_paid?: boolean;
  color?: string;
}

interface UpdateLeaveTypeDto {
  name?: string;
  code?: string | null;
  requires_approval?: boolean;
  max_days?: number | null;
  is_paid?: boolean;
  color?: string | null;
  active?: boolean;
}

interface CreateLeaveDto {
  company_id: string;
  employee_id: string;
  leave_type_id: string;
  name?: string;
  date_from: string;
  date_to: string;
  description?: string;
}

interface UpdateLeaveDto {
  leave_type_id?: string;
  name?: string | null;
  date_from?: string;
  date_to?: string;
  description?: string | null;
}

interface LeaveFilters {
  company_id?: string;
  employee_id?: string;
  leave_type_id?: string;
  status?: LeaveStatus;
  date_from?: string;
  date_to?: string;
  search?: string;
  page?: number;
  limit?: number;
}
```

### Skills DTOs

```typescript
interface CreateSkillTypeDto {
  name: string;
  skill_levels?: string;
}

interface UpdateSkillTypeDto {
  name?: string;
  skill_levels?: string;
}

interface CreateSkillDto {
  skill_type_id: string;
  name: string;
}

interface UpdateSkillDto {
  name?: string;
  skill_type_id?: string;
}

interface CreateSkillLevelDto {
  skill_type_id: string;
  name: string;
  level: number;
}

interface UpdateSkillLevelDto {
  name?: string;
  level?: number;
}

interface CreateEmployeeSkillDto {
  employee_id: string;
  skill_id: string;
  skill_level_id?: string;
  skill_type_id?: string;
}

interface UpdateEmployeeSkillDto {
  skill_level_id?: string | null;
  skill_type_id?: string | null;
}
```

### Expenses DTOs

```typescript
interface CreateExpenseSheetDto {
  company_id: string;
  employee_id: string;
  name: string;
  user_id?: string;
  accounting_date?: string;
}

interface UpdateExpenseSheetDto {
  name?: string;
  user_id?: string | null;
  accounting_date?: string | null;
}

interface CreateExpenseDto {
  company_id: string;
  employee_id: string;
  name: string;
  sheet_id?: string;
  product_id?: string;
  unit_amount: number;
  quantity?: number;
  currency_id?: string;
  tax_ids?: string[];
  date?: string;
  description?: string;
  reference?: string;
  analytic_account_id?: string;
  payment_mode?: string;
}

interface UpdateExpenseDto {
  name?: string;
  sheet_id?: string | null;
  product_id?: string | null;
  unit_amount?: number;
  quantity?: number;
  currency_id?: string | null;
  tax_ids?: string[];
  date?: string;
  description?: string | null;
  reference?: string | null;
  analytic_account_id?: string | null;
  payment_mode?: string;
}

interface ExpenseSheetFilters {
  company_id?: string;
  employee_id?: string;
  state?: ExpenseStatus;
  date_from?: string;
  date_to?: string;
  search?: string;
  page?: number;
  limit?: number;
}

interface ExpenseFilters {
  company_id?: string;
  employee_id?: string;
  sheet_id?: string;
  state?: ExpenseStatus;
  date_from?: string;
  date_to?: string;
  search?: string;
  page?: number;
  limit?: number;
}
```

### Payslips DTOs

```typescript
interface CreatePayslipStructureDto {
  name: string;
  code?: string;
  is_active?: boolean;
  note?: string;
}

interface UpdatePayslipStructureDto {
  name?: string;
  code?: string | null;
  is_active?: boolean;
  note?: string | null;
}

interface CreatePayslipDto {
  company_id: string;
  employee_id: string;
  contract_id?: string;
  name: string;
  date_from: string;
  date_to: string;
  date?: string;
  structure_id?: string;
  basic_wage?: number;
  worked_days?: number;
  worked_hours?: number;
}

interface UpdatePayslipDto {
  name?: string;
  date?: string | null;
  structure_id?: string | null;
  basic_wage?: number;
  gross_wage?: number;
  net_wage?: number;
  worked_days?: number;
  worked_hours?: number;
}

interface CreatePayslipLineDto {
  name: string;
  code: string;
  sequence?: number;
  category?: string;
  quantity?: number;
  rate?: number;
  amount: number;
  appears_on_payslip?: boolean;
}

interface UpdatePayslipLineDto {
  name?: string;
  code?: string;
  sequence?: number;
  category?: string;
  quantity?: number;
  rate?: number;
  amount?: number;
  appears_on_payslip?: boolean;
}

interface PayslipStructureFilters {
  is_active?: boolean;
  search?: string;
  page?: number;
  limit?: number;
}

interface PayslipFilters {
  company_id?: string;
  employee_id?: string;
  contract_id?: string;
  structure_id?: string;
  state?: PayslipStatus;
  date_from?: string;
  date_to?: string;
  search?: string;
  page?: number;
  limit?: number;
}
```

---

## ENDPOINTS

### Employees

| Metodo | Ruta | Descripcion |
|--------|------|-------------|
| GET | `/api/hr/employees` | Listar empleados |
| GET | `/api/hr/employees/:id` | Obtener empleado |
| POST | `/api/hr/employees` | Crear empleado |
| PUT | `/api/hr/employees/:id` | Actualizar empleado |
| DELETE | `/api/hr/employees/:id` | Eliminar empleado |
| POST | `/api/hr/employees/:id/terminate` | Dar de baja |
| POST | `/api/hr/employees/:id/reactivate` | Reactivar |
| GET | `/api/hr/employees/:id/subordinates` | Obtener subordinados |

### Departments

| Metodo | Ruta | Descripcion |
|--------|------|-------------|
| GET | `/api/hr/departments` | Listar departamentos |
| GET | `/api/hr/departments/:id` | Obtener departamento |
| POST | `/api/hr/departments` | Crear departamento |
| PUT | `/api/hr/departments/:id` | Actualizar departamento |
| DELETE | `/api/hr/departments/:id` | Eliminar departamento |

### Job Positions

| Metodo | Ruta | Descripcion |
|--------|------|-------------|
| GET | `/api/hr/job-positions` | Listar puestos |
| GET | `/api/hr/job-positions/:id` | Obtener puesto |
| POST | `/api/hr/job-positions` | Crear puesto |
| PUT | `/api/hr/job-positions/:id` | Actualizar puesto |
| DELETE | `/api/hr/job-positions/:id` | Eliminar puesto |

### Contracts

| Metodo | Ruta | Descripcion |
|--------|------|-------------|
| GET | `/api/hr/contracts` | Listar contratos |
| GET | `/api/hr/contracts/:id` | Obtener contrato |
| POST | `/api/hr/contracts` | Crear contrato |
| PUT | `/api/hr/contracts/:id` | Actualizar contrato |
| DELETE | `/api/hr/contracts/:id` | Eliminar contrato |
| POST | `/api/hr/contracts/:id/activate` | Activar contrato |
| POST | `/api/hr/contracts/:id/terminate` | Terminar contrato |
| POST | `/api/hr/contracts/:id/cancel` | Cancelar contrato |

### Leaves

| Metodo | Ruta | Descripcion |
|--------|------|-------------|
| GET | `/api/hr/leave-types` | Listar tipos de ausencia |
| GET | `/api/hr/leave-types/:id` | Obtener tipo |
| POST | `/api/hr/leave-types` | Crear tipo |
| PUT | `/api/hr/leave-types/:id` | Actualizar tipo |
| DELETE | `/api/hr/leave-types/:id` | Eliminar tipo |
| GET | `/api/hr/leaves` | Listar ausencias |
| GET | `/api/hr/leaves/:id` | Obtener ausencia |
| POST | `/api/hr/leaves` | Crear ausencia |
| PUT | `/api/hr/leaves/:id` | Actualizar ausencia |
| DELETE | `/api/hr/leaves/:id` | Eliminar ausencia |
| POST | `/api/hr/leaves/:id/submit` | Enviar solicitud |
| POST | `/api/hr/leaves/:id/approve` | Aprobar solicitud |
| POST | `/api/hr/leaves/:id/reject` | Rechazar solicitud |
| POST | `/api/hr/leaves/:id/cancel` | Cancelar solicitud |

### Skills

| Metodo | Ruta | Descripcion |
|--------|------|-------------|
| GET | `/api/hr/skill-types` | Listar tipos de habilidad |
| GET | `/api/hr/skill-types/:id` | Obtener tipo |
| POST | `/api/hr/skill-types` | Crear tipo |
| PUT | `/api/hr/skill-types/:id` | Actualizar tipo |
| DELETE | `/api/hr/skill-types/:id` | Eliminar tipo |
| GET | `/api/hr/skills` | Listar habilidades |
| GET | `/api/hr/skills/:id` | Obtener habilidad |
| POST | `/api/hr/skills` | Crear habilidad |
| PUT | `/api/hr/skills/:id` | Actualizar habilidad |
| DELETE | `/api/hr/skills/:id` | Eliminar habilidad |
| GET | `/api/hr/skill-levels` | Listar niveles |
| GET | `/api/hr/skill-levels/:id` | Obtener nivel |
| POST | `/api/hr/skill-levels` | Crear nivel |
| PUT | `/api/hr/skill-levels/:id` | Actualizar nivel |
| DELETE | `/api/hr/skill-levels/:id` | Eliminar nivel |
| GET | `/api/hr/employee-skills` | Listar habilidades de empleados |
| GET | `/api/hr/employee-skills/:id` | Obtener asignacion |
| POST | `/api/hr/employee-skills` | Asignar habilidad |
| PUT | `/api/hr/employee-skills/:id` | Actualizar asignacion |
| DELETE | `/api/hr/employee-skills/:id` | Eliminar asignacion |
| GET | `/api/hr/employees/:id/skills` | Habilidades de empleado |

### Expenses

| Metodo | Ruta | Descripcion |
|--------|------|-------------|
| GET | `/api/hr/expense-sheets` | Listar hojas de gastos |
| GET | `/api/hr/expense-sheets/:id` | Obtener hoja |
| POST | `/api/hr/expense-sheets` | Crear hoja |
| PUT | `/api/hr/expense-sheets/:id` | Actualizar hoja |
| DELETE | `/api/hr/expense-sheets/:id` | Eliminar hoja |
| POST | `/api/hr/expense-sheets/:id/submit` | Enviar hoja |
| POST | `/api/hr/expense-sheets/:id/approve` | Aprobar hoja |
| POST | `/api/hr/expense-sheets/:id/reject` | Rechazar hoja |
| GET | `/api/hr/expenses` | Listar gastos |
| GET | `/api/hr/expenses/:id` | Obtener gasto |
| POST | `/api/hr/expenses` | Crear gasto |
| PUT | `/api/hr/expenses/:id` | Actualizar gasto |
| DELETE | `/api/hr/expenses/:id` | Eliminar gasto |

### Payslips

| Metodo | Ruta | Descripcion |
|--------|------|-------------|
| GET | `/api/hr/payslip-structures` | Listar estructuras |
| GET | `/api/hr/payslip-structures/:id` | Obtener estructura |
| POST | `/api/hr/payslip-structures` | Crear estructura |
| PUT | `/api/hr/payslip-structures/:id` | Actualizar estructura |
| DELETE | `/api/hr/payslip-structures/:id` | Eliminar estructura |
| GET | `/api/hr/payslips` | Listar nominas |
| GET | `/api/hr/payslips/:id` | Obtener nomina |
| POST | `/api/hr/payslips` | Crear nomina |
| PUT | `/api/hr/payslips/:id` | Actualizar nomina |
| DELETE | `/api/hr/payslips/:id` | Eliminar nomina |
| POST | `/api/hr/payslips/:id/verify` | Verificar nomina |
| POST | `/api/hr/payslips/:id/confirm` | Confirmar nomina |
| POST | `/api/hr/payslips/:id/cancel` | Cancelar nomina |
| GET | `/api/hr/payslips/:id/lines` | Obtener lineas |
| POST | `/api/hr/payslips/:id/lines` | Agregar linea |
| PUT | `/api/hr/payslips/:id/lines/:lineId` | Actualizar linea |
| DELETE | `/api/hr/payslips/:id/lines/:lineId` | Eliminar linea |

---

## TESTS

### Ubicacion de Tests

```
backend/src/modules/hr/__tests__/
  employees.test.ts
  departments.test.ts
  contracts.test.ts
  leaves.test.ts
  skills.test.ts
  expenses.test.ts
  payslips.test.ts
```

### Casos de Prueba Requeridos

#### EmployeesService

- [ ] Crear empleado con datos validos
- [ ] Error al crear empleado con numero duplicado
- [ ] Obtener empleado existente
- [ ] Error al obtener empleado inexistente
- [ ] Listar empleados con filtros
- [ ] Actualizar empleado
- [ ] Dar de baja empleado
- [ ] Error al dar de baja empleado ya dado de baja
- [ ] Reactivar empleado
- [ ] Error al eliminar empleado con contratos
- [ ] Error al eliminar empleado que es manager
- [ ] Obtener subordinados

#### DepartmentsService

- [ ] Crear departamento
- [ ] Error al crear departamento con nombre duplicado
- [ ] Listar departamentos
- [ ] Actualizar departamento
- [ ] Error al eliminar departamento con empleados
- [ ] Error al eliminar departamento con subdepartamentos
- [ ] CRUD de puestos de trabajo

#### ContractsService

- [ ] Crear contrato
- [ ] Error al crear contrato con empleado que ya tiene uno activo
- [ ] Activar contrato
- [ ] Terminar contrato
- [ ] Cancelar contrato
- [ ] Error al editar contrato no borrador

#### LeavesService

- [ ] Crear tipo de ausencia
- [ ] Crear solicitud de ausencia
- [ ] Validar dias maximos por tipo
- [ ] Error al crear ausencia solapada
- [ ] Flujo completo: draft -> submitted -> approved
- [ ] Rechazar solicitud con razon
- [ ] Cancelar solicitud

#### SkillsService

- [ ] CRUD de tipos de habilidad
- [ ] CRUD de habilidades
- [ ] CRUD de niveles
- [ ] Asignar habilidad a empleado
- [ ] Obtener habilidades de empleado

#### ExpensesService

- [ ] Crear hoja de gastos
- [ ] Agregar gastos a hoja
- [ ] Recalculo automatico de totales
- [ ] Flujo de aprobacion
- [ ] Error al modificar gasto no borrador

#### PayslipsService

- [ ] Crear estructura de nomina
- [ ] Crear nomina
- [ ] Agregar lineas a nomina
- [ ] Recalculo de gross/net
- [ ] Flujo: draft -> verify -> done
- [ ] Cancelar nomina
- [ ] Error al modificar nomina confirmada

---

## DEPENDENCIAS

### Internas

| Modulo | Descripcion |
|--------|-------------|
| `auth` | Empresas y usuarios |
| `core` | Monedas |
| `inventory` | Productos (para gastos) |
| `analytics` | Cuentas analiticas |

### Tablas Referenciadas

- `auth.companies` - Empresa del empleado/contrato
- `auth.users` - Usuario asociado al empleado
- `core.currencies` - Moneda del contrato/gasto
- `inventory.products` - Producto del gasto
- `analytics.analytic_accounts` - Cuenta analitica

### Externas

```json
{
  "pg": "Base de datos PostgreSQL"
}
```

---

## ERRORES COMUNES

| Codigo | Mensaje | Causa |
|--------|---------|-------|
| `NOT_FOUND` | Empleado no encontrado | ID invalido o no pertenece al tenant |
| `CONFLICT` | Ya existe un empleado con ese numero | `employee_number` duplicado |
| `CONFLICT` | No se puede eliminar un empleado con contratos | Tiene contratos asociados |
| `CONFLICT` | No se puede eliminar un empleado que es manager | Es manager de otros empleados |
| `VALIDATION` | El empleado ya tiene un contrato activo | Intento de crear segundo contrato activo |
| `VALIDATION` | Solo se pueden editar contratos en estado borrador | Contrato no esta en `draft` |
| `VALIDATION` | Ya existe una solicitud de ausencia para estas fechas | Ausencias solapadas |
| `VALIDATION` | Este tipo de ausencia tiene un maximo de X dias | Excede dias permitidos |
| `VALIDATION` | Solo se pueden modificar gastos en estado borrador | Gasto no esta en `draft` |
| `VALIDATION` | Solo se pueden modificar nominas en estado borrador | Nomina no esta en `draft` |

---

## CHANGELOG

| Version | Fecha | Cambios |
|---------|-------|---------|
| 1.0.0 | 2026-01-10 | Especificacion inicial con 7 servicios |
