Reglas por confirmar
Decisiones que Royal Clean todavía no ha cerrado, con el supuesto conservador que se tomó.
Estas decisiones afectan a dinero, tiempo o permisos y no las ha confirmado Royal Clean. En cada caso se implementó la opción más conservadora y se dejó el sistema preparado para cambiarla sin reescribir nada.
Nada de esto bloquea el MVP.
Para preguntárselas a Royal Clean hay un correo redactado y listo para enviar en CORREO_CONSULTAS_CLIENTE.md: las mismas diez decisiones, agrupadas por tema y explicadas sin jerga.
1. Política de redondeo de horas
Pregunta. ¿Se paga el tiempo exacto, o se redondea al cuarto de hora, o a la media hora?
Asumido. EXACT — se paga el tiempo exacto.
Cómo está preparado. Configurable desde Configuración con tres opciones.
La función applyRoundingPolicy() centraliza el cálculo y tiene tests para las
tres. Cada PayrollPayment guarda la política con la que se calculó, así que un
recibo antiguo siempre puede explicarse.
Al confirmarse. Cambiar el valor en Configuración. No modifica los pagos ya registrados: su desglose está congelado.
Nota. El redondeo 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 no está tomada.
2. Horas extra
Pregunta. ¿Existe una tarifa distinta a partir de cierto número de horas?
Asumido. No. Todas las horas se pagan a la misma tarifa.
Cómo está preparado. El cálculo pasa por
calculateServiceLaborCost(), que recibe un desglose por empleado y no un total
opaco. Añadir tramos de horas extra es cambiar esa función y sus tests, sin
tocar a ninguno de sus llamadores.
3. Domingos, feriados y nocturnidad
Pregunta. ¿Se paga distinto un domingo, un feriado o un turno nocturno?
Asumido. No hay recargos.
Cómo está preparado. Igual que el punto anterior. Las sesiones de trabajo guardan instantes absolutos, así que la información necesaria para segmentar por franja horaria o por día de la semana ya está registrada: no habría que recuperar datos perdidos.
4. Umbral de sesión demasiado larga
Pregunta. ¿A partir de cuántas horas abierto un servicio debe considerarse un olvido?
Asumido. 6 horas.
Cómo está preparado. Configurable. Al superarse, el servicio se marca con
hasTimeWarning y aparece destacado en el panel. El sistema nunca cierra la
sesión ni inventa una hora de finalización: sólo avisa para que un
administrador lo corrija con su motivo.
5. Visualización en dólares
Pregunta. ¿Con qué precisión y en qué pantallas debe aparecer el equivalente en USD?
Asumido. Aparece junto a los importes principales del panel y de las fichas, con el tipo de cambio que un administrador fija a mano.
Cómo está preparado. El colón es siempre la moneda oficial de cálculo. No se almacena ningún histórico en USD ni se depende de ningún servicio externo de cotización, para no introducir un coste recurrente ni una dependencia que pueda caerse.
Matizado el 18 de septiembre de 2026. Las cotizaciones sí pueden emitirse en dólares, con el tipo de cambio escrito a mano y congelado en el documento. Es una excepción acotada: una cotización es una propuesta, no entra en las cifras de rentabilidad, que siguen siendo íntegramente en colones.
6. Política de correo electrónico
Pregunta. ¿Debe el sistema enviar correos, y para qué eventos?
RESUELTO PARCIALMENTE el 18 de septiembre de 2026. Royal Clean confirmó el
envío para cotizaciones, y ya funciona: src/modules/notifications/email.ts
habla con Resend y adjunta el PDF. Ver Reglas de negocio §9.
Sigue pendiente para todo lo demás: incidentes, aviso de reporte listo y
recordatorios siguen siendo notificaciones internas, sin correo saliente. La
aplicación funciona íntegramente sin correo: si faltan RESEND_API_KEY o
EMAIL_FROM, lo único que se degrada es el botón de enviar una cotización, que
explica por qué no está disponible.
Al confirmarse el resto. El módulo ya existe; bastaría con que escuche los tipos de notificación correspondientes.
7. Registro de insumos por el personal
Pregunta. ¿Puede el personal de campo registrar insumos, o sólo un administrador?
Asumido. Sí puede, pero sus registros quedan en PENDING_REVIEW y no
cuentan en las cifras hasta que un administrador los aprueba.
Cómo está preparado. Interruptor employeesCanLogSupplies en Configuración.
El personal nunca ve costos internos ni márgenes de los insumos.
Al confirmarse. Si se decide que sólo administración registre insumos, basta con desactivar el interruptor.
8. Límites de vídeo
Pregunta. ¿Se admite vídeo como evidencia? ¿De qué duración y tamaño?
Asumido. El modelo de datos lo soporta (Evidence.mediaType = VIDEO) con un
límite conservador de 60 MB, pero la interfaz sólo ofrece fotografías.
Cómo está preparado. Los límites están en
src/modules/evidence/constants.ts. El MVP no transcodifica: el archivo se
guardaría tal cual llega.
Al confirmarse. Habilitar el tipo de archivo en el componente de captura y revisar los límites. Si se necesitan vídeos largos, habría que evaluar transcodificación, que es un proyecto aparte.
9. Retención posterior a los 12 meses
Pregunta. Al expirar la evidencia, ¿debe archivarse en frío en algún sitio o se elimina definitivamente?
Asumido. Se elimina el objeto y la metadata queda marcada como EXPIRED.
El historial del servicio se conserva íntegro: fechas, tareas, horas,
incidentes y cifras siguen ahí.
Cómo está preparado. El plazo es configurable. El trabajo de retención está
en expireOldEvidence(), con tests que comprueban que borra el objeto pero no el
historial.
10. Estrategia de respaldo externo
Pregunta. ¿Con qué frecuencia se respalda, dónde se guarda y quién verifica las restauraciones?
Asumido. Nada automatizado todavía. La estrategia recomendada está documentada en OPERATIONS.md: volcado diario, comprimido, cifrado y copiado fuera de la VPS.
Importante. Un volcado guardado únicamente en la misma VPS no es un respaldo: si se pierde el servidor, se pierde con él.
11. Aviso de reporte listo (sin correo)
Pregunta. El cliente pidió "generación y envío automático de reportes" cuando un servicio termina.
Asumido. Se genera el aviso, no el correo: al completarse un servicio, todos
los administradores activos reciben una notificación interna
(SERVICE_REPORT_READY) que enlaza al servicio. El PDF sigue generándose al
vuelo en cada descarga y nunca se guarda (DEC-010): pre-generarlo y
almacenarlo automáticamente rompería esa decisión sin necesidad, ya que el
reporte puede cambiar mientras nadie lo ha descargado todavía.
Cómo está preparado. notifyAdmins() se llama desde
completeService()/changeServiceStatus() en
src/modules/services/service.ts, en cuanto el servicio pasa a COMPLETED
por cualquiera de sus dos caminos (finalizar desde el trabajo de campo, o
arrastrarlo en el Kanban). El envío de correo sigue sin activar (§6): el "envío
automático" que pidió el cliente se resuelve con el aviso interno, no con un
correo saliente.
Al confirmarse el envío de correo (§6). Basta con que
notifications/email.ts (aún no existe) escuche el mismo tipo de notificación
y reenvíe su contenido; no hace falta tocar service.ts de nuevo.
12. Qué mide exactamente el «GOP»
Pregunta. Royal Clean pidió un indicador llamado GOP con esta fórmula:
(gasto en salarios + gasto en insumos) ÷ ingresosEsa fórmula mide costo, no ganancia: sube cuando el negocio va peor. Un
GOP en el sentido habitual del sector —Gross Operating Profit— sería el
complemento: (ingresos − costos) ÷ ingresos.
¿Se quiere el ratio de costo tal cual se pidió, o el margen?
Asumido. Se implementó literal, tal y como se pidió. Inventar la política habría sido peor: si alguien decidió llamarlo así, el número que espera ver es ése.
Cómo está preparado. calculateGopRatio en
src/modules/dashboard/calculations.ts, con test. La tarjeta del panel lo
rotula «Salarios + insumos sobre ingresos. Cuanto más bajo, mejor» para que
nadie lo lea al revés, y se colorea en rojo sólo por encima del 100 %, que es el
único umbral que no depende del criterio de nadie: el mes costó más de lo que
ingresó.
Ya no hace falta tocar código (18 de septiembre de 2026). Desde Configuración se elige qué costos entran en el indicador, si se muestra como costo o como margen, y cómo se llama la tarjeta. Cuando Royal Clean confirme cuál de las dos lecturas quería, es marcar una casilla.
13. Denominador del «Productivity index»
Pregunta. El índice se pidió como tiempo trabajado ÷ cantidad de tareas totales. ¿«Totales» son todas las tareas del período o sólo las completadas?
Asumido. Todas las del período, terminadas o no, que es lo que dice la frase literalmente.
Por qué importa. Un mes con mucho trabajo a medias reparte los minutos entre tareas que todavía no se han hecho, y el índice sale más bajo de lo real: da la impresión de que se es más productivo de lo que se fue.
Cómo está preparado. calculateProductivityIndex en
src/modules/dashboard/calculations.ts. La tarjeta del panel muestra al lado
cuántas tareas entraron en el cálculo.
Al confirmarse. Si se quieren sólo las completadas, es filtrar por
status: "COMPLETED" al contar en getDashboardKpis.
14. Envío de cotizaciones por correo
Pregunta. Las cotizaciones guardan «enviar a» y «con copia a». ¿Debe el sistema enviarlas de verdad?
RESUELTO el 18 de septiembre de 2026. Royal Clean confirmó que sí, y el envío por Resend ya está implementado: el correo lleva el PDF adjunto, pide confirmación y queda registrado. Ver Reglas de negocio §9.
Cómo está preparado. Los campos recipientEmail y ccEmails ya existen en
el modelo Quote y se validan. Falta sólo el transporte.
Al confirmarse. Se resuelve junto con la regla #6: el mismo módulo de correo sirve para cotizaciones y para notificaciones.
Cómo tratar estas reglas
Al implementar algo que las toque:
- No inventes una política irreversible.
- Elige el comportamiento conservador.
- Hazlo configurable si es barato.
- Documenta el supuesto aquí y en el código.
- Si afecta a un cálculo, escribe también el test de la política alternativa.