Auditoria de 260 visitas (26 rutas x 10 roles) sobre el build actual: 0 hallazgos.
Dos defectos reales que salieron del barrido anterior se corrigen aqui.
1) Reportes: la matriz guarda claves '/reportes?tab=x' y puede conceder una
pestana suelta (Horas agenda al medico, Exportar a cosmetologia) sin dar el
modulo. Nadie la respetaba:
- App.tsx: nuevo ReportesRoute que evalua la pestana del URL; si el rol no
tiene '/reportes' pero si alguna pestana, aterriza en la primera permitida.
- Reportes.tsx: familias, favoritos y el boton "Exportar datos" se filtran
por canSee('/reportes?tab=x'). 'exportar' no pertenece a ninguna familia,
por eso se lista en TABS_FUERA_DE_FAMILIA (si no, el guard lo reescribia a
'general' y admin perdia la vista). El query se sincroniza con lo renderizado.
- SkeenLayout/HomeNestLayout: "Horas agenda" y "Exportar datos" aparecen en
Analitica para que el permiso sea alcanzable desde el menu, no solo a
mano. Clasico ya los traia.
- auth.py: _medico_reporte_permitido consulta la matriz antes de vetar
/reports/ al medico. Sin ese puente el menu ofrecia la opcion y la API
respondia 403. Las demas rutas vetadas (sales, payments, wallets, pos)
siguen cerradas: verificado comisiones=403, horas-agenda=200.
2) skeen_chat/_touch: 211 "could not serialize access due to concurrent update"
en un barrido. Bandeja, badge y heartbeat salen en paralelo y cada uno
emitia el UPDATE de last_seen aunque no aplicara cambios, abortando la
transaccion de otra peticion. Se corta antes de tocar la fila cuando el
registro es reciente; el savepoint queda para el caso de empate exacto.
Ráfaga de 128 peticiones concurrentes: todas 200, presencia sigue
actualizando (online=true, last_seen avanza) y el ruido bajo de 211 a 5.