Estado del sistema
Qué está hecho, qué falta y qué encontró la auditoría de calidad.
Última actualización: 31 de agosto de 2026.
Completado
Fundación
- Next.js 16 · React 19 · TypeScript estricto · Tailwind 4
- Prisma 7 con PostgreSQL 17: 18 modelos, migraciones versionadas
- Invariantes de dominio como restricciones CHECK e índices parciales
- Validación de entorno con Zod, perezosa (el build no necesita secretos)
docker composecon PostgreSQL y MinIO para desarrollo- Semilla de desarrollo con datos realistas de Guanacaste
Autenticación y autorización
- Sesiones opacas en base de datos con revocación inmediata
- Argon2id, política de contraseñas, contraseñas temporales
- Rate limiting por usuario y por IP, en base de datos
- Hash señuelo para no filtrar qué usuarios existen
- Guardas centrales; un recurso ajeno devuelve 404
- Cambio forzado de contraseña tras un restablecimiento
Directorio
- Clientes: alta, edición, archivado en cascada
- Propiedades: alta, edición, información de acceso protegida, archivado
- Plantillas de tareas reordenables
- Empleados: alta con contraseña temporal, tarifa propia, desactivación
- Búsqueda, filtros y paginación en todos los listados del directorio, y búsqueda global que cruza clientes, propiedades, personal y servicios
Servicios
- Creación con snapshot de tareas y congelado de precio
- Máquina de estados centralizada
- Asignación múltiple con congelado de tarifa y aviso de cruces de agenda
- Listado con filtros en la URL, calendario mensual y semanal, y tablero Kanban
- Detalle administrativo con tiempos, evidencia, incidentes, insumos y cifras
- Corrección de horarios con motivo obligatorio y auditoría
- Cancelación con motivo y marcado para revisión si había trabajo, por cualquiera de las dos vías (ficha y tablero)
Flujo del personal de campo
- Pantallas mobile-first: hoy, próximos, historial, perfil
- Detalle de servicio con cronómetro en vivo, sin escribir en la base
- Datos de acceso protegidos por estado y tapados hasta pulsar «Mostrar»
- Tareas con evidencia antes y después, notas y reapertura
- Iniciar, finalizar participación, finalizar servicio y retomar trabajo
- Reporte de incidentes
Evidencia
- Compresión y borrado de EXIF en el navegador
- Subida directa a R2 con URL firmada; confirmación verificada contra el bucket
- Visualización mediante ruta autorizada que redirige a URL firmada
- Retención configurable y limpieza de subidas abandonadas
- Progreso, reintento y cancelación por fotografía
Insumos
- Gestión completa desde el detalle de servicio: crear, editar, eliminar
- Aprobación y rechazo de los registrados por el personal
- Pantalla del personal que anota qué y cuánto repuso, sin ver importes
- Los pendientes no cuentan en las cifras hasta aprobarse
Finanzas
- Aritmética monetaria en céntimos enteros
- Rentabilidad por servicio, propiedad, cliente y período
- Cifras parciales explícitas cuando faltan costos
- Nómina con desglose por servicio y tarifa congelada
- Pagos con desglose congelado y reversión auditada
Reportes
- Reporte PDF con marca, evidencia incrustada y paginación real
- Test que verifica que no filtra tarifas ni márgenes
- Reportes internos de rentabilidad por propiedad y cliente
Sistema
- Incidentes: reporte, bandeja, resolución con notificación
- Notificaciones internas con contador
- Auditoría legible campo a campo, con filtros
- Configuración global con advertencias de efecto histórico
- Apariencia clara, oscura o automática, guardada por dispositivo
- Línea de tiempo derivada de la auditoría
Infraestructura
- Dockerfile multi-etapa, usuario sin privilegios, healthcheck (~640 MB)
- Migraciones aplicadas al arrancar; el contenedor no arranca si fallan
- Trabajo de mantenimiento idempotente, por CLI o endpoint con token
- GitHub Actions: typecheck, lint, tests, integración, build y arranque
- PWA instalable con manifiesto e iconos
Pruebas
- 134 tests unitarios sobre los módulos de cálculo
- 74 tests de integración contra PostgreSQL y almacenamiento reales
- 26 recorridos de extremo a extremo con Playwright, sobre la aplicación compilada, en escritorio y en móvil
Auditoría de calidad (31 de agosto de 2026)
Cinco revisiones independientes —seguridad, corrección de negocio, interfaz, cobertura de especificación y calidad de código— sobre el sistema terminado. Resultado: 38 defectos reales corregidos en cuatro entregas.
Lo que encontró
Seguridad. La evidencia se autorizaba contra un servicio que elegía el
cliente: un empleado podía borrar una foto de otro servicio suyo ya cerrado. La
portada del panel era la única página sin guarda propia. x-forwarded-for se
leía por el extremo que escribe el cliente, lo que permitía evadir el límite de
intentos de acceso. El personal podía fijar el precio de cobro de un insumo.
Dinero. Lo pendiente de nómina se contaba por identidad de servicio y no por importe, así que el trabajo añadido con «Retomar trabajo» a un servicio ya pagado era imposible de cobrar. Un servicio compartido perdía del panel al empleado no pagado. Un pago podía registrarse dos veces. Y reasignar a alguien recongelaba su tarifa, reescribiendo el valor de horas ya trabajadas.
Estado. El tablero cancelaba sin motivo, sin marcar para revisión y sin avisar al personal; y reabrir dejaba puesta la fecha de finalización, de la que depende la ventana de acceso a los códigos de la propiedad.
Interfaz. No existía ninguna pantalla de error: un fallo al renderizar mostraba la de Next.js, en inglés. El buscador entraba en un bucle de navegación con un espacio al final. La marca «Requiere revisión» no se podía quitar nunca. El cajón de navegación era el único modal sin trampa de foco.
Cobertura. Faltaba la búsqueda global. Las advertencias de archivado se calculaban y se descartaban. La paginación de la auditoría perdía los filtros.
Lo que confirmó
- Ninguna consulta del personal de campo arrastra tarifas, salarios, precios ni márgenes: se comprobó campo a campo sobre los nueve campos monetarios.
- Las 60 Server Actions siguen el mismo patrón, sin una sola excepción, y ninguna puede propagar un error crudo de Prisma.
- Cero
any, cero@ts-ignore, cero TODO sin clasificar en todo el código escrito a mano. - Cero colores fuera del sistema de tokens y cero textos en inglés.
- El PDF del cliente no selecciona siquiera los campos que no debe mostrar.
Verificación sobre la imagen de producción
Comprobado el 1 de septiembre de 2026 contra la imagen real, con PostgreSQL 17
en su propia red de Docker y NODE_ENV=production.
| Comprobación | Resultado |
|---|---|
| Arranca y aplica migraciones | 6 segundos |
/api/health | {"status":"ok","database":"ok"} |
/admin sin sesión | 307 al inicio de sesión |
| Ruta inventada | 307, no filtra si existe |
POST /api/jobs/run sin token | 401 |
| Con token incorrecto | 401 |
| Con token correcto | 200 con el recuento del mantenimiento |
Sin JOBS_SECRET configurado | 503, no queda abierto |
| Usuario del contenedor | uid=1001(nextjs), no root |
| Cabeceras de seguridad | X-Frame-Options, X-Content-Type-Options, Referrer-Policy |
X-Powered-By | Ausente |
| Secretos en los registros | Ninguno |
| Primer administrador desde el contenedor | Creado, con cambio de contraseña forzado |
Pendiente
Subida real de fotografías desde el navegador
Los recorridos comprueban que no se puede completar una tarea sin evidencia, pero no suben una fotografía de verdad desde el navegador. El ciclo completo —firmar, subir, confirmar— sí está cubierto a nivel de integración contra el almacenamiento real.
Impacto: bajo.
Vista de servicio para el personal con el servicio archivado
Menor: un servicio archivado no aparece en las listas del personal, pero su URL directa muestra la pantalla normal.
Correo electrónico
Sin integración. La aplicación funciona íntegramente sin correo, según lo acordado. Ver PENDING_BUSINESS_RULES.md #6.
Vídeo como evidencia
El modelo lo soporta con límites definidos, pero la interfaz sólo ofrece fotografías. Ver PENDING_BUSINESS_RULES.md #8.
Logotipo real
El logotipo es un SVG propio en src/components/brand/logo.tsx. El repositorio
no contenía assets de marca. Sustituirlo es editar ese archivo y regenerar los
iconos con node scripts/generate-icons.mjs.
Problemas conocidos
La tipografía depende de la plataforma
Decisión deliberada para no depender de Google Fonts al compilar (DECISIONS.md DEC-015). La aplicación se ve ligeramente distinta en Windows, macOS y Android.
La compresión depende del navegador
Navegadores antiguos sin soporte de WebP caen a JPEG. HEIC de iPhone se decodifica
con el respaldo de <img>, que funciona en Safari reciente pero puede fallar en
versiones viejas.
El panel no se actualiza solo
Por diseño (DECISIONS.md DEC-014). Los datos pueden tener unos segundos de antigüedad.
Decisiones registradas
20 decisiones técnicas documentadas en DECISIONS.md, incluidas tres que sólo aparecieron al construir la imagen de producción:
- DEC-016 — El cliente Prisma se creaba al importar el módulo, lo que hacía
que
next buildexigiera unDATABASE_URLreal. - DEC-017 — La CLI de Prisma quedaba sin sus dependencias transitivas por los enlaces simbólicos de pnpm.
- DEC-015 — El build descargaba la tipografía de Google Fonts, lo que rompe cualquier compilación sin salida a Internet.
Y dos que destaparon tests de integración:
- DEC-018 — El SDK de S3 usaba direccionamiento virtual-host, incompatible con el endpoint de R2 y con MinIO.
- El saneado de auditoría recorría campo a campo los
Decimalde Prisma y alcanzaba sus métodos, con lo que Prisma rechazaba la escritura y tumbaba la operación auditada entera. Ahora las instancias de clase se convierten a texto, con un test que lo fija.
Preguntas abiertas para Royal Clean
Detalle en PENDING_BUSINESS_RULES.md.
- ¿Se redondean las horas de la nómina?
- ¿Existen horas extra con tarifa distinta?
- ¿Hay recargo por domingo, feriado o nocturnidad?
- ¿Es correcto el umbral de 6 horas para la alerta de servicio abierto?
- ¿Debe el sistema enviar correos?
- ¿Puede el personal registrar insumos, o sólo administración? (implementado con revisión obligatoria y un interruptor para desactivarlo)
- ¿Se admitirá vídeo como evidencia?
- ¿Qué pasa con la evidencia al expirar: se archiva o se elimina?
- ¿Con qué frecuencia y hacia dónde se respalda?
En todos los casos se implementó la opción más conservadora y el sistema quedó preparado para cambiarla sin reescribir nada.