fase 2.1: roles editables (modelo skeen.role) y permisos por accion (43 claves, 9 grupos) con enforcement en backend y gating en frontend; editor de roles/permisos/menus en Configuracion

This commit is contained in:
2026-10-11 07:32:29 +00:00
parent 75736967ef
commit 28a690aa0f
24 changed files with 1637 additions and 157 deletions

View File

@@ -20,6 +20,7 @@ import {
} from '../components/ui';
import { odooApi, type Service, type Patient, type InventoryItem, type PosCheckoutResult, type Doctor, type Paquete } from '../services/odoo';
import type { PatientPackage } from '../services/odoo';
import { useAuth } from '../lib/auth';
const fmtMoney = (n: number) =>
(n || 0).toLocaleString('es-MX', { style: 'currency', currency: 'MXN', maximumFractionDigits: 2 });
@@ -52,6 +53,7 @@ interface TicketLine {
const Pos: FC = () => {
const { openPatient } = usePatientDrawer();
const { can } = useAuth();
const [searchParams, setSearchParams] = useSearchParams();
// Catálogo
const [catMode, setCatMode] = useState<'servicios' | 'paquetes' | 'articulos'>('servicios');
@@ -818,7 +820,9 @@ const Pos: FC = () => {
value={discount}
onChange={(e) => setDiscount(e.target.value)}
placeholder="0"
className="w-28 border border-theme-border-strong rounded-lg px-2 py-1 text-sm text-right"
disabled={!can('ventas.descuentos')}
title={can('ventas.descuentos') ? undefined : 'Sin permiso para aplicar descuentos'}
className="w-28 border border-theme-border-strong rounded-lg px-2 py-1 text-sm text-right disabled:opacity-50 disabled:cursor-not-allowed"
/>
</span>
</div>
@@ -840,8 +844,8 @@ const Pos: FC = () => {
<Button
className="w-full mt-1 !py-3 text-base"
onClick={abrirCobro}
disabled={lines.length === 0 || !paciente}
title="Cobrar (F9)"
disabled={lines.length === 0 || !paciente || !can('ventas.vender')}
title={can('ventas.vender') ? 'Cobrar (F9)' : 'Sin permiso para vender'}
>
<ShoppingBag size={18} className="mr-2" />
Cobrar {fmtMoney(total)}