Reportes
El PDF que se entrega al cliente y qué nunca aparece en él.
Hay dos clases de reporte, con audiencias opuestas.
1. Reportes internos
Sólo para administración. Muestran todo, incluidos costos y márgenes.
| Pantalla | Qué responde |
|---|---|
Panel (/admin) | ¿Cómo va hoy? ¿Y el mes? |
| Detalle de servicio | ¿Qué se hizo, quién, cuánto tiempo, cuánto dejó? |
| Ficha de propiedad | ¿Es rentable esta propiedad? |
| Ficha de cliente | ¿Cuánto factura este cliente? |
| Ficha de empleado | ¿Cuántas horas y cuánto se le debe? |
Reportes (/admin/reportes) | ¿Qué propiedades y clientes dejan más margen? |
| Pagos | ¿Qué hay que pagar y qué ya se pagó? |
Rentabilidad
Ingresos = precio base + insumos cobrados (sólo aprobados)
Costos = costo laboral + costo interno de insumos
Margen = Ingresos − Costos
Margen % = Margen ÷ Ingresos × 100Todo el cálculo está en
src/modules/finance/calculations.ts,
con tests.
Cifras parciales. Cuando falta un costo interno, el resultado se marca como parcial y se explica por qué:
«2 insumos no tienen costo interno registrado.»
Nunca se asume que un costo desconocido vale cero. Presentar un margen inflado como si fuera exacto sería peor que no presentarlo.
El margen es null, no cero, cuando los ingresos son cero: un margen sobre cero
no está definido y mostrar «0 %» sería una afirmación falsa.
Línea de tiempo
El detalle de servicio muestra una línea de tiempo derivada del registro de auditoría, no de una tabla de eventos paralela. Duplicar el historial garantizaría que algún día las dos versiones se contradigan.
09:58 Trabajo iniciado María
10:17 Tarea completada María · Limpiar cocina
10:52 Incidente reportado María
11:40 Servicio completado María
12:15 Horario corregido admin
«La empleada olvidó finalizar el servicio.»2. Reporte PDF para el cliente
Documento profesional pensado para compartir por correo o WhatsApp.
Qué incluye
- Marca, dirección y contacto de Royal Clean
- Cliente, propiedad y dirección
- Fecha, hora programada, horario real y duración
- Personal que atendió
- Cada tarea realizada, con sus fotografías antes y después
- Notas del personal
- Incidentes relevantes
- Insumos repuestos, con lo que se cobra por ellos
- Precio del servicio
- Sello «SERVICIO COMPLETADO»
Qué NO incluye
- Tarifas por hora
- Salarios ni costo laboral
- Costos internos de insumos
- Márgenes ni rentabilidad
- Notas administrativas
- Cualquier dato de otros clientes
Esto no se deja a la disciplina de quien programe: hay un test que lo
comprueba. Se crea un servicio con una tarifa distintiva (₡7.777/h) y un costo
interno distintivo (₡1.804), se generan los datos del reporte y se verifica que
esos números no aparecen por ninguna parte
(tests/integration/service-report.test.ts).
Imágenes incrustadas
El servidor descarga las fotografías de R2 y las incrusta en el PDF como datos.
La alternativa —enlazar a URLs firmadas— produciría un documento que deja de funcionar en cuanto expira la firma. Un reporte que el cliente abre tres días después mostraría huecos. Incrustar hace el documento autosuficiente.
Se incrustan hasta 4 fotografías por tarea para acotar el tamaño. Una imagen que no se puede recuperar se omite en lugar de romper la generación: es preferible un reporte con una foto menos que ningún reporte.
Generación
GET /api/reportes/servicio/{id}Sólo ADMIN. Se genera bajo demanda y no se almacena (ver DECISIONS.md DEC-010).
Cada generación incrementa reportVersion y anota reportGeneratedAt, con una
entrada de auditoría, para poder trazar cuántas veces se emitió un reporte y
cuándo.
El PDF se sirve con Content-Disposition: inline, así que se abre en el
navegador: el administrador lo revisa antes de decidir si lo comparte.
Paginación
Un servicio con muchas tareas ocupa varias páginas. Cada tarea se marca con
wrap={false} para que no se parta por la mitad, y el pie repite «Página X de
Y» en todas.
Hay un test con 25 tareas que verifica que el documento tiene más de una página, es decir, que el contenido se reparte en lugar de recortarse.
Tipografía
Se usan las fuentes estándar de PDF (Helvetica), disponibles en todos los lectores sin incrustar nada. Mantiene el archivo pequeño y elimina cualquier dependencia de red durante la generación.
Filtros por rango de fechas
Pagos y Reportes ofrecen atajos —hoy, esta semana, este mes, últimos 30 días— y un rango libre.
No se asume una quincena fija: Royal Clean todavía no ha confirmado su ciclo de pago (PENDING_BUSINESS_RULES.md #1).
Rendimiento
- Ningún listado descarga imágenes a resolución completa: las miniaturas se cargan de forma diferida y sólo las visibles.
- Todos los listados están paginados.
- Las agregaciones financieras se calculan en el servidor, dentro de un
Suspensepropio, para que la parte operativa de la página aparezca antes.