Royal Clean CRM · Documentación
Negocio

Reglas de negocio

La fuente oficial de cómo funciona Royal Clean dentro del sistema.

Este documento es la fuente oficial. Si el código y este documento se contradicen, uno de los dos tiene un error y hay que resolverlo, no ignorarlo.

Las reglas sin confirmar están en PENDING_BUSINESS_RULES.md.


1. Propiedades y servicios

Una propiedad es permanente. Un servicio es una limpieza concreta.

Villa Tamarindo #3 existe siempre; sus limpiezas del 1, el 8 y el 15 de septiembre son tres registros históricos independientes. La propiedad acumula cientos de servicios a lo largo del tiempo.

  • Toda propiedad pertenece a un cliente.
  • Todo servicio pertenece a una propiedad.
  • Los servicios se crean manualmente. No hay recurrencias automáticas.

2. Estados del servicio

DRAFT ──→ ASSIGNED ──→ IN_PROGRESS ──→ COMPLETED
  │           │              │              │
  │           │              │              └──→ IN_PROGRESS  (retomar trabajo)
  └───────────┴──────────────┴──→ CANCELLED

Transiciones permitidas y sus condiciones:

DesdeHaciaQuiénCondiciones
DRAFTASSIGNEDADMINAl menos un empleado asignado
ASSIGNEDDRAFTADMIN—
ASSIGNEDIN_PROGRESSEmpleadoTiene empleados y tareas
IN_PROGRESSCOMPLETEDEmpleadoTodas las tareas completas, con evidencia, sin sesiones abiertas
COMPLETEDIN_PROGRESSEmpleado«Retomar trabajo»
Cualquiera activoCANCELLEDADMINCon motivo escrito

CANCELLED es terminal absoluto. Un servicio completado no puede cancelarse: primero habría que retomarlo.

Implementado en src/modules/services/status-machine.ts. Toda transición pasa por ahí, incluido arrastrar una tarjeta en el Kanban.

3. Tareas: la plantilla y el snapshot

Cada propiedad tiene una plantilla de tareas editable. Al crear un servicio, las tareas se copian a registros propios de ese servicio.

La plantilla de Villa Tamarindo dice «Limpiar cocina». Se crea el servicio #422, que copia esa tarea. Mañana la plantilla cambia a «Limpiar y desinfectar cocina». El servicio #422 sigue diciendo «Limpiar cocina».

Esto no es un detalle técnico: es la garantía de que un reporte de hace seis meses describe lo que realmente se pidió aquel día.

  • Al crear el servicio, la plantilla es el punto de partida: aparecen todas las tareas marcadas y el administrador puede quitar las que no aplican esa vez y agregar otras que sólo existen en ese servicio. El servicio necesita al menos una tarea. Una propiedad sin plantilla también admite servicios con tareas propias.
  • Un administrador puede añadir, editar o eliminar tareas de un servicio concreto después de crearlo, sin tocar la plantilla.
  • Quitar una tarea al crear no cambia el precio por sí solo: el precio sugerido se recalcula, pero el campo sigue siendo editable.
  • Una tarea ya completada no se elimina en silencio: hay que reabrirla primero.

4. Evidencia fotográfica

Una tarea no puede completarse sin al menos una fotografía «después».

  • Evidencia BEFORE: 0 o más, opcional.
  • Evidencia AFTER: 1 o más, obligatoria.

La validación es del servidor, dentro de la transacción que completa la tarea. La interfaz deshabilita el botón y explica por qué, pero no es la autoridad.

Sólo cuenta la evidencia en estado CONFIRMED, es decir, aquella cuyo objeto se verificó en el almacenamiento. Una subida a medias deja la tarea incompleta, que es lo correcto.

5. Tiempo de trabajo

No existe ningún contador acumulativo. El tiempo se deriva de las sesiones.

Sesión A:  08:00 – 11:00   = 3 h
Sesión B:  11:10 – 11:30   = 20 min
                             ───────
Total:                       3 h 20 min
  • Iniciar un servicio abre una WorkSession.
  • Finalizar la participación la cierra.
  • Retomar el trabajo abre una sesión NUEVA. El tiempo previo nunca se pierde ni se reinicia. La acción se llama «Retomar trabajo», nunca «Reiniciar».
  • Un empleado no puede tener dos sesiones abiertas a la vez. Lo impide un índice único en la base de datos, no sólo la aplicación.

Servicios olvidados abiertos

Si una sesión supera el umbral configurado (6 horas por defecto), el servicio se marca con hasTimeWarning y aparece destacado en el panel.

El sistema nunca inventa una hora de finalización. Un administrador corrige el horario a mano, con motivo obligatorio, y queda auditado.

Varios empleados

Cada empleado tiene sus propias sesiones y cobra sus horas con su propia tarifa. Nunca se usa una duración global del servicio para pagar a varios.

Para finalizar un servicio, todas las participaciones deben estar cerradas. Cada persona cierra la suya con «Finalizar mi participación»; quien cierre el servicio cierra la propia en el mismo acto.

6. Tarifas y nómina

Resolución de la tarifa

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

Congelado

La tarifa se congela al asignar el empleado al servicio. Un aumento posterior no recalcula el pasado.

En agosto María cobraba ₡5.000/h. En octubre pasa a ₡5.500/h. Los servicios de agosto se siguen pagando a ₡5.000/h.

Cálculo

costo = redondeo(tarifa_congelada × minutos_facturables ÷ 60)

El redondeo se aplica por servicio, no sobre el total del período, para que el desglose siempre sume exactamente el total mostrado.

La política de redondeo por defecto es EXACT (tiempo exacto). Es configurable y está pendiente de confirmación por parte de Royal Clean.

Pagos

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

Al marcar un período como pagado se congela el desglose completo en PayrollPaymentItem: fecha, propiedad, minutos, tarifa e importe de cada servicio. Si más adelante se corrigen horas, el recibo emitido no cambia.

Detalle en PAYROLL.md.

7. Precios y rentabilidad

  • Cada propiedad tiene un defaultServicePrice.
  • Cada servicio copia ese precio como basePrice al crearse.
  • Cambiar el precio de la propiedad no modifica servicios existentes.
  • Un administrador puede ajustar el precio de un servicio concreto; queda auditado aparte.
Ingresos = precio base + insumos cobrados (sólo los aprobados)
Costos   = costo laboral
         + costo interno de insumos
         + costos de operación de las tareas
Margen   = Ingresos − Costos

Cuando falta un costo interno, la rentabilidad se marca como parcial y se dice por qué. Nunca se asume que un costo desconocido vale cero.

8. Insumos

Royal Clean repone productos durante un servicio (café, papel, jabón, amenidades) y puede cobrarlos al cliente.

  • Un administrador registra insumos con costo interno y precio de cobro. Puede importarlos del catálogo o agregarlos sueltos.
  • Si la configuración lo permite, el personal también puede registrarlos, pero quedan en PENDING_REVIEW y no cuentan en las cifras hasta que un administrador los aprueba.
  • El personal nunca ve costos ni márgenes de los insumos.

El catálogo de insumos

Los productos se definen una vez en /admin/insumos —nombre, unidad, costo interno y precio de cobro de referencia— y los servicios los importan, en lugar de volver a escribir «Papel higiénico» y a decidir su precio en cada servicio.

Lo que se importa es una copia congelada (§18): los importes quedan fijados en la fila del servicio y cambiar el catálogo después no reescribe lo que ya se cobró. ServiceSupply.catalogItemId sólo deja el rastro de dónde salió.

Un insumo importado por un administrador entra ya aprobado: lo eligió él de un catálogo que él mismo mantiene, y no hay nada que revisar.

Archivar un insumo no lo borra (§5). Deja de ofrecerse al importar, y los servicios que ya lo llevaban lo conservan con su nombre y su precio.

Un insumo sin costo interno —que es como nace el catálogo sembrado— deja la rentabilidad de los servicios que lo usen marcada como parcial. Es deliberado: mejor decir que no se sabe que dar por bueno un cero.

Costo de insumos estimado por tarea

Además del gasto real de un servicio, cada tarea del catálogo puede llevar un defaultSupplyCost: lo que se calcula que cuesta en materiales hacerla una vez.

Son dos cosas distintas y conviene no confundirlas:

Qué esDe dónde salePara qué sirve
ServiceSupplyGasto real de un servicioLo registra quien lo consumeMueve las cifras financieras
defaultSupplyCostCosto estimado de un tipo de tareaLo fija administración en el catálogoCotizar y medir el margen por tarea

El estimado se congela en ServiceTask.supplyCostSnapshot al crear el servicio, como el precio (§18). Cambiar la estimación del catálogo no reescribe lo que ya se hizo.

Vacío significa sin estimar, que no es cero: de una tarea sin estimación el margen es desconocido, y por eso queda fuera del ranking de mayor margen en lugar de aparecer con un 100 %.

Los otros costos de operación

Hacer una tarea cuesta más que sus materiales. El catálogo estima otros tres, todos en céntimos y todos opcionales:

CampoQué cubre
defaultTransportCostTraslado del personal hasta la propiedad
defaultSupervisionCostTiempo de supervisión que exige la tarea
defaultMealCostViáticos de alimentación del personal

Se congelan en la tarea del servicio al crearlo, igual que el precio y el costo de insumos (§18, §122), y suman al costo real: mientras no existían, el margen salía sistemáticamente mejor de lo que era.

Una tarea a la que le falte alguno de los tres deja las cifras del servicio marcadas como parciales, con el motivo OPERATIONAL_COST_MISSING. Lo que sí se conoce se suma igual; no se descarta la tarea entera.

Los servicios creados antes de que estos campos existieran no los tienen y calculan exactamente lo mismo que calculaban antes.

Composición del GOP

El indicador GOP del panel se ajusta desde Configuración, sin tocar código:

  • Qué costos entran en el numerador: salarios, insumos, o ambos.
  • Si se muestra como costo (sube cuando el negocio va peor, que es la fórmula que pidió Royal Clean) o como margen, que es su complemento.
  • Cómo se rotula la tarjeta.

Son interruptores y no una fórmula escrita a mano: una expresión libre hay que interpretarla, y una mal escrita dejaría el panel en blanco. Los otros dos indicadores —labor cost ratio y productivity index— tienen definición fija.

Invertido, el valor puede salir negativo: significa que el mes costó más de lo que ingresó. Es información, no un error.

Horario del personal

El horario muestra, por persona y por día, a qué hora toca cada tarea. Se calcula encadenando la duración estimada de cada una a partir de la hora del servicio.

  • Se ve por día, semana o mes.
  • En cuanto una tarea no tiene duración estimada, el reloj se detiene: las siguientes aparecen sin hora en lugar de con una inventada. Recuperarlo exigiría que alguien confirme cuándo se retomó, y ese dato no existe.
  • Un servicio sin nadie asignado aparece igual, al final: es lo que falta por asignar, y esconderlo escondería el problema.
  • Los servicios cancelados se marcan, no se ocultan.
  • Se descarga como imagen —que es lo que Royal Clean manda al personal por WhatsApp— y como PDF para imprimirlo. Los dos salen de los mismos datos, así que no pueden decir cosas distintas.

9. Cotizaciones

Una cotización es una propuesta de trabajo para un cliente, con sus importes y su impuesto.

  • Se compone de líneas: un tipo de trabajo (del catálogo o escrito a mano), una cantidad y un precio unitario antes de impuestos.
  • El IVA se aplica sobre el subtotal ya sumado, nunca línea a línea: redondear en cada línea separaría el total de la comprobación que hace cualquiera, que es multiplicar el subtotal por el 13 %.
  • El tipo impositivo se guarda en puntos básicos (1300 = 13,00 %) y se congela en cada cotización. Si el IVA del país cambia, las cotizaciones ya emitidas siguen diciendo lo que dijeron.
  • Los importes los deriva siempre el servidor a partir de las líneas. El formulario calcula los mismos números con la misma función, pero sólo para enseñarlos mientras se escribe.
  • Estados: DRAFT → SENT → ACCEPTED o REJECTED. Una cotización que ya tuvo respuesta no se puede modificar: el cliente respondió a unos números concretos. Para cambiarla se crea otra.
  • Se archivan, no se borran (§11).

Cotizar a un cliente que todavía no existe

La primera cotización suele ser el primer contacto, así que el campo de cliente admite escribir un nombre nuevo además de elegir de la lista.

  • Si el nombre no existe, el cliente se registra al guardar la cotización, dentro de la misma transacción: si la cotización falla, no queda una ficha suelta que nadie pidió.
  • Antes de crear se busca por nombre sin distinguir mayúsculas ni acentos. «Pacific Rentals» y «pacific rentals» son el mismo negocio, y tener su historial partido en dos no se arregla solo.
  • Mientras se escribe se muestran los clientes que ya existen, que es lo que de verdad evita el duplicado: quien iba a crear uno repetido lo ve y lo elige.
  • La propiedad sólo se puede asociar cuando el cliente ya está registrado.

Enviar la cotización

El paso a ENVIADA lo da el sistema al mandar el correo, no una persona marcándolo. Antes no había forma de saber si una cotización había salido; ahora se guarda cuándo salió, a quién y quién la mandó, y queda en auditoría.

  • El correo lleva el PDF adjunto, el mismo que se previsualiza en pantalla.
  • Enviar pide confirmación y enseña antes la lista exacta de destinatarios: es la única acción del módulo que no se puede deshacer.
  • Si el correo no está configurado en el servidor, el botón no aparece y se explica por qué, en vez de fallar al pulsarlo.
  • Se manda primero y se registra después. Si el registro fallara, se avisa de que el correo ya salió, en lugar de dejar creer que no pasó nada.
  • «Ya la envié por fuera» existe para cuando se pasó por WhatsApp o en mano. Cambia el estado pero no escribe registro de envío: nadie puede confundir una cosa con la otra.
  • Reenviar una cotización ya respondida manda otra copia, pero no la devuelve a ENVIADA: la respuesta del cliente no se pisa.
  • Cada envío queda en el historial de la cotización: fecha, destinatarios, copias y quién lo hizo. Una cotización enviada más de una vez se lee «Reenviada · N envíos» en el listado. Marcarla «Ya la envié por fuera» cuenta como envío, pero aparece distinguido y sin destinatarios, porque no se sabe a quién se mandó. El historial sale de la auditoría, así que los envíos anteriores a esta función también aparecen.

Campos de la plantilla de Royal Clean

Royal Clean cotizaba en una hoja de Excel. De esa plantilla se tomaron los campos, no el diseño: el PDF mantiene la maqueta del sistema.

  • Contacto: la persona con la que se coordina, cuando no es la misma a la que va dirigida la cotización.
  • Método de pago: las formas que se aceptan, en texto libre («USD, Colón, SINPE, Efectivo o Transferencia»). Es una frase que lee el cliente, no una lista que el sistema interprete.
  • Descripción por línea: qué incluye cada trabajo. Si la línea sale del catálogo, se copia la descripción de la tarea, para que no acabe diciendo cosas distintas de una cotización a otra.

La aritmética de su plantilla coincide con la del sistema: sobre su propio ejemplo —₡42.500 y ₡23.000— ambos dan subtotal ₡65.500, IVA ₡8.515 y total ₡74.015. Hay un test de integración que lo fija con esas cifras.

Moneda e idioma

Una cotización se emite en colones o en dólares, y el PDF se genera en español o en inglés. Royal Clean cotiza a clientes de fuera del país.

  • Los importes se guardan en céntimos de la moneda elegida, sin convertir. Una cotización en dólares dice dólares, y la aritmética sigue siendo entera.
  • El tipo de cambio se escribe a mano y se congela en la cotización (§18). No se lee de la configuración al mostrarla: el cliente puede aceptar tres semanas después y el papel tiene que seguir diciendo lo mismo. La configuración sólo aporta el valor inicial.
  • Cotizar en dólares exige el tipo de cambio: sin él no hay equivalente que enseñar.
  • El equivalente en la otra moneda es informativo. Convertir y volver no da el importe exacto —redondear a la unidad más gruesa cuesta céntimos— y lo que se cobra es siempre la moneda en la que se emitió. Hay un test que fija esa desviación en lugar de fingir que no existe.

El PDF de la cotización

Cada cotización se puede previsualizar y descargar como PDF, con la marca de Royal Clean: logotipo, datos de la empresa, número consecutivo, destinatario, desglose línea a línea, subtotal, IVA y total.

  • Se genera bajo demanda y no se almacena, como el reporte de servicio (DEC-010). Como los importes están congelados en la fila, regenerarlo mañana produce el mismo documento que hoy.
  • Un PDF de una cotización en borrador lleva un aviso visible que lo dice. Si alguien lo reenvía por error, el aviso viaja con él.
  • El documento no calcula nada: recibe los importes ya formateados a partir de lo que está guardado, para que diga exactamente lo mismo que la pantalla.
  • Es exclusivo de administración: lleva precios, y el personal de campo no ve importes (§141). Hay un recorrido que comprueba que recibe 403.

10. Incidentes

El personal reporta lo que encuentra: daños, objetos faltantes, suciedad excesiva, problemas de acceso, problemas con insumos u otros.

  • Puede adjuntar fotografías.
  • Un incidente marca el servicio con hasIncident.
  • Un administrador lo revisa y lo resuelve con una nota.
  • Al resolverlo, quien lo reportó recibe una notificación. Cerrar el círculo es lo que hace que la gente siga reportando.

11. Cancelaciones

Sólo un administrador cancela, y siempre con motivo escrito. Los empleados asignados reciben un aviso.

Si ya había tiempo trabajado, el servicio queda marcado con requiresAdminReview: ese trabajo se hizo y hay que decidir qué hacer con él. El sistema no lo descarta por su cuenta.

12. Archivado

Nada se borra. Archivar es marcar archivedAt.

  • Cliente archivado: se archivan también sus propiedades.
  • Propiedad archivada: no admite servicios nuevos; su historial permanece. No se puede archivar con un servicio en progreso.
  • Empleado desactivado: pierde el acceso de inmediato y no recibe asignaciones nuevas. Conserva servicios, horas, nómina y auditoría. No se puede desactivar con trabajo en curso.

13. Moneda

El colón costarricense es la moneda oficial de cálculo. Todo importe se almacena como entero de céntimos.

El dólar aparece como equivalente informativo, calculado con un tipo de cambio que un administrador fija a mano. No se guarda ningún histórico en USD ni se depende de ningún servicio externo de cotización.

14. Retención de evidencia

Por defecto, 12 meses. Al expirar:

  • Se elimina el objeto del almacenamiento.
  • La metadata se marca como EXPIRED.
  • El historial del servicio se conserva íntegro.

El plazo es configurable.

15. Lo que el personal de campo nunca ve

  • Su tarifa por hora
  • Lo que gana
  • El precio cobrado al cliente
  • Costos internos y márgenes
  • Otros empleados, salvo sus compañeros en un servicio compartido
  • Propiedades a las que no está asignado

16. Reporte para el cliente

El PDF incluye la marca de Royal Clean, los datos del servicio, las tareas realizadas con sus fotografías, los incidentes relevantes, los insumos repuestos y el precio del servicio.

No incluye tarifas, salarios, costo laboral, costos internos ni márgenes. Hay un test que lo comprueba buscando esos valores en los datos del reporte.

On this page