Royal Clean CRM · Documentación
Negocio

Nómina

Cómo se calcula el pago del personal y qué se congela al registrarlo.

La aplicación no transfiere dinero. Calcula, desglosa y deja constancia de que un pago se realizó fuera del sistema.

Cómo se calcula

1. Tarifa aplicable

tarifa = empleado.hourlyRateOverride ?? configuración.defaultHourlyRate

Un hourlyRateOverride de cero es un valor legítimo, no una ausencia: el operador ?? sólo cae al valor global cuando el campo es null.

2. Congelado

La tarifa se copia a ServiceAssignment.hourlyRateSnapshot al asignar el empleado. A partir de ahí, ese servicio se paga siempre a esa tarifa.

María cobraba ₡5.000/h en agosto y pasa a ₡5.500/h en octubre. Los servicios de agosto se siguen calculando a ₡5.000/h, para siempre.

3. Tiempo trabajado

Suma de las sesiones de ese empleado en ese servicio:

minutos = Σ (endedAt − startedAt)

Una sesión abierta cuenta hasta el instante actual, para que el panel muestre el tiempo en curso.

4. Redondeo

minutos_facturables = aplicarPolítica(minutos, política)
PolíticaEfecto
EXACT (actual)Sin cambios
ROUND_15_MINUTESAl cuarto de hora más cercano
ROUND_30_MINUTESA la media hora más cercana

Es al múltiplo más cercano, no siempre hacia arriba: redondear siempre hacia arriba favorecería sistemáticamente a una de las partes, y esa decisión Royal Clean aún no la ha tomado (PENDING_BUSINESS_RULES.md #1).

5. Importe

importe = redondeoAlCéntimo(tarifa × minutos_facturables ÷ 60)

Todo en enteros de céntimos. El redondeo ocurre una sola vez, al final.

Ejemplo

3 h 10 min a ₡5.000/h:

500 000 céntimos/h × 190 min ÷ 60 = 1 583 333,33
                                  → 1 583 333 céntimos
                                  = ₡15.833,33

Nómina de un período

El redondeo se aplica por servicio, no sobre el total del período. Así el desglose siempre suma exactamente el total mostrado, sin diferencias de un céntimo que nadie sabe explicar.

Ejemplo de desglose:

FechaPropiedadTiempoTarifaSubtotal
01/09/2026Villa Azul3 h 10 m₡5.000/h₡15.833,33
05/09/2026Casa Tamarindo4 h 05 m₡5.000/h₡20.416,67
10/09/2026Condo Playa2 h 30 m₡5.000/h₡12.500
Total9 h 45 m₡48.750

Registrar un pago

Al marcar un período como pagado, todo ocurre en una transacción:

flowchart TD
    A[Marcar como pagado] --> B{¿Trabajo en curso<br/>en el período?}
    B -->|| C[Rechazar: el monto<br/>seguiría creciendo]
    B -->|No| D[Calcular el desglose]
    D --> E{¿Queda algo<br/>por pagar?}
    E -->|No| F[Rechazar: ya está pagado]
    E -->|| G[Crear PayrollPayment]
    G --> H[Congelar cada línea<br/>en PayrollPaymentItem]
    H --> I[Escribir auditoría]

Qué se congela

Cada PayrollPaymentItem guarda:

  • El nombre de la propiedad como texto, no una referencia. El recibo sobrevive a que la propiedad se renombre o se archive.
  • La fecha del servicio.
  • Los minutos ya redondeados.
  • La tarifa aplicada.
  • El importe.

El PayrollPayment guarda además la política de redondeo vigente, para que un recibo antiguo siempre pueda explicarse.

Por qué congelar

Si el recibo se recalculara al abrirlo, corregir unas horas de septiembre cambiaría retroactivamente un pago de septiembre que ya se hizo. El registro dejaría de ser un registro.

Con el desglose congelado, corregir horas más adelante no altera lo que dice el recibo. El sistema además avisa en pantalla cuando esa corrección afecta a un servicio ya liquidado, y lo anota en auditoría con la acción PAYROLL_AFFECTED_AFTER_PAYMENT.

Hay un test que lo comprueba: se registra un pago, luego se duplican las horas y se sube la tarifa, y se verifica que el recibo sigue diciendo lo mismo (tests/integration/payroll.test.ts).

Qué no se paga

  • Servicios cancelados. Si tenían tiempo trabajado, el servicio queda marcado con requiresAdminReview para que un administrador decida.
  • Trabajo en curso. Hay que cerrar la participación primero.
  • Servicios ya incluidos en un pago liquidado.

Revertir un pago

Con motivo obligatorio. El registro se elimina —no se marca anulado— para que el trabajo vuelva a quedar disponible para futuras liquidaciones. La auditoría conserva el rastro completo: quién lo registró, por cuánto, qué servicios incluía y quién lo revirtió.

Multi-empleado

Cada empleado tiene sus propias sesiones y su propia tarifa congelada. Nunca se reparte una duración global del servicio entre varias personas.

EmpleadoSesionesTarifaImporte
María11:00 – 13:30₡5.000/h₡12.500
Carlos11:10 – 12:40₡5.000/h₡7.500
Costo laboral del servicio₡20.000

Lo que el personal ve

Su historial muestra fecha, propiedad, duración y estado de las tareas.

Nunca ve su tarifa, lo que gana, ni ningún importe. Esto se garantiza en la capa de consultas: los select del espacio del empleado sencillamente no piden esos campos.

Reglas pendientes

Horas extra, recargos por domingo, feriado o nocturnidad, y la política definitiva de redondeo están sin confirmar. El cálculo está centralizado en calculateServiceLaborCost(), que recibe un desglose por empleado y no un total opaco, precisamente para poder segmentarlo más adelante sin reescribir a sus llamadores.

Detalle en PENDING_BUSINESS_RULES.md.

On this page