Auditoria de plataforma: rutas rotas y errores por rol

Recorrido dinamico de las 26 rutas con los 10 roles (260 visitas con
navegador real) mas revision estatica de endpoints FE vs BE. Hallazgos
y correcciones:

- frontend.py: expedientes_recientes no tenia @http.route (el endpoint
  /expedientes-recientes respondia 404). Se expone con GET + lectura.
- auth.py: MEDICO_BLOCKED_PREFIXES hacia match por startswith, asi que
  /sales_goal quedaba bloqueado para medicos (403 en consola en todas
  las pantallas por el widget de meta del mes). Ahora el match exige
  frontera de path (/sales bloquea /sales/x pero no /sales_goal).
- Reportes: getCommissions (endpoint admin-only) iba dentro de un
  Promise.all; el 403 tumbaba todo el tab General para recepcion,
  gerencia, enfermeria, psicologa, farmacia y lectura. Ahora va en
  peticion aislada y solo se llama para admin; la seccion de comisiones
  del tab General solo se renderiza para admin.
- Matriz de menus guardada: medico habia quedado fuera de /pacientes
  y /visitas (los defaults del codigo lo incluyen y los medicos lo
  necesitan para ver clientes e historial). Restaurado via PUT
  /roles/permissions (queda en skeen_config_audit).

Verificado con navegador: medico entra a /pacientes y /visitas sin
403; recepcion y gerencia cargan /reportes sin error; admin sigue
viendo la seccion de comisiones. Usuarios temporales de auditoria
eliminados.
This commit is contained in:
2026-09-28 21:13:38 +00:00
parent a4358ada15
commit 5385fc3a53
3 changed files with 28 additions and 7 deletions

View File

@@ -36,6 +36,7 @@ import { Card, Button, Input, PageHeader, Skeleton, EmptyState, toast } from '..
import { odooApi } from '../services/odoo';
import type { CommissionRow } from '../services/odoo';
import { exportToExcel } from '../lib/exporter';
import { useAuth } from '../lib/auth';
import {
REPORT_PRESETS,
RangeOverrideHint,
@@ -144,6 +145,8 @@ const ReportesContent: FC = () => {
const familiaKey = familiaDe(tab);
const familia = FAMILIAS.find((f) => f.key === familiaKey) || FAMILIAS[0];
const { favs, toggle, isFav } = useReportFavorites();
const { hasRole } = useAuth();
const esAdmin = hasRole('admin');
// Rango global del módulo (persistente, compartido por todas las pestañas)
const { start, end, preset, setRange } = useGlobalReportRange();
const [today, setToday] = useState(() => new Date().toISOString().split('T')[0]);
@@ -164,11 +167,10 @@ const ReportesContent: FC = () => {
try {
setLoading(true);
setError(null);
const [salesRes, aptRes, cashRes, commRes] = await Promise.all([
const [salesRes, aptRes, cashRes] = await Promise.all([
odooApi.getSalesReport(start, end),
odooApi.getAppointmentsReport(start, end),
odooApi.getCashReport(today),
odooApi.getCommissions(start, end),
]);
if (salesRes.status === 'success')
setSalesReport({
@@ -181,9 +183,21 @@ const ReportesContent: FC = () => {
setAppointmentsReport({ total: aptRes.total, by_state: aptRes.by_state });
if (cashRes.status === 'success')
setCashReport({ total: cashRes.total, by_method: cashRes.by_method });
if (commRes.status === 'success') {
setCommissions(commRes.commissions || []);
setCommTotals({ base: commRes.total_base || 0, commission: commRes.total_commission || 0 });
// Comisiones viajan en petición aparte y solo para admin: el endpoint
// exige rol admin y un 403 no debe tumbar (ni ensuciar la consola del)
// resto del tab General para los demás roles.
if (esAdmin) {
odooApi.getCommissions(start, end)
.then((commRes) => {
if (commRes.status === 'success') {
setCommissions(commRes.commissions || []);
setCommTotals({ base: commRes.total_base || 0, commission: commRes.total_commission || 0 });
}
})
.catch(() => { /* sin permiso: sección de comisiones queda vacía */ });
} else {
setCommissions([]);
setCommTotals({ base: 0, commission: 0 });
}
} catch (err) {
setError('Error al cargar reportes');
@@ -194,7 +208,7 @@ const ReportesContent: FC = () => {
}
};
load();
}, [start, end, today, tab]);
}, [start, end, today, tab, esAdmin]);
const aptData = appointmentsReport
? Object.entries(appointmentsReport.by_state).map(([name, value]) => ({ name, value }))
@@ -523,6 +537,7 @@ const ReportesContent: FC = () => {
</Card>
</div>
{esAdmin && (
<Card className="mt-4 sm:mt-6">
<Card.Body>
<div className="flex flex-col sm:flex-row sm:items-center sm:justify-between gap-2 mb-4">
@@ -572,6 +587,7 @@ const ReportesContent: FC = () => {
)}
</Card.Body>
</Card>
)}
</>
)}
</Layout>