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

@@ -885,7 +885,9 @@ export interface WacrmLead {
created_at: string | null;
}
export type FrontendRole = 'admin' | 'medico' | 'recepcion' | 'enfermeria' | 'cosmetologia' | 'psicologa' | 'farmacia' | 'gerencia' | 'marketing' | 'lectura';
// Los roles son configurables (modelo skeen.role): el código es libre, por eso
// el tipo ya no es una unión cerrada. ROLE_LABELS queda solo como respaldo.
export type FrontendRole = string;
// Acciones de la Agenda (espejo de AGENDA_PERMS en skeen_whatsapp/roles.py).
export type AgendaPerm = 'ver' | 'agendar' | 'editar' | 'eliminar' | 'visitas';
@@ -996,6 +998,8 @@ export interface FrontendUser {
login: string;
name: string;
role: FrontendRole;
// Nombre del rol configurado en skeen.role (para roles personalizados).
role_name?: string;
must_change_password?: boolean;
last_login?: string | null;
active?: boolean;
@@ -1004,11 +1008,54 @@ export interface FrontendUser {
agenda_perms?: AgendaPerm[] | null;
// Lista propia del usuario; null = hereda del rol. La usa solo el editor.
agenda_perms_custom?: AgendaPerm[] | null;
// Permisos por acción vigentes (rol + overrides), resueltos por el backend.
perms?: Record<string, boolean> | null;
// Solo rol 'medico': hr.employee vinculado (null si no hay vínculo)
employee_id?: number | null;
employee_name?: string | null;
}
// ---- Roles configurables (skeen.role) ----
export interface Role {
id: number;
code: string;
name: string;
rank: number;
sequence: number;
active: boolean;
is_system: boolean;
description: string;
}
export interface RoleUserRef {
code: string;
name: string;
rank: number;
active: boolean;
is_system: boolean;
}
// ---- Permisos por acción ----
export interface PermItem {
key: string;
label: string;
}
export interface PermGroup {
grupo: string;
label: string;
permisos: PermItem[];
}
export interface PermsResponse {
status: string;
catalogo: PermGroup[];
por_rol: Record<string, Record<string, boolean>>;
defaults: Record<string, Record<string, boolean>>;
roles: RoleUserRef[];
message?: string;
}
export interface Receta {
id: number;
name: string;
@@ -1552,6 +1599,40 @@ export const odooApi = {
return data;
},
// ---- Roles configurables ----
async listRoles(opts?: { includeInactive?: boolean }): Promise<{ status: string; roles: Role[]; total: number; message?: string }> {
const { data } = await axios.get(`${ODOO_BASE}/skeen/frontend/v1/roles`, {
params: opts?.includeInactive ? { include_inactive: 1 } : undefined,
});
return data;
},
async createRole(payload: { name: string; rank?: number; sequence?: number; description?: string; active?: boolean }): Promise<{ status: string; role: Role; message?: string }> {
const { data } = await axios.post(`${ODOO_BASE}/skeen/frontend/v1/roles`, payload);
return data;
},
async updateRole(id: number, payload: { name?: string; rank?: number; sequence?: number; active?: boolean; description?: string }): Promise<{ status: string; role: Role; message?: string }> {
const { data } = await axios.put(`${ODOO_BASE}/skeen/frontend/v1/roles/${id}`, payload);
return data;
},
async deleteRole(id: number): Promise<{ status: string; role?: Role; en_uso?: number; message?: string }> {
const { data } = await axios.delete(`${ODOO_BASE}/skeen/frontend/v1/roles/${id}`);
return data;
},
// ---- Permisos por acción ----
async getPermisos(): Promise<PermsResponse> {
const { data } = await axios.get(`${ODOO_BASE}/skeen/frontend/v1/permisos`);
return data;
},
async putPermisos(payload: { role_code?: string; permisos?: Record<string, boolean>; por_rol?: Record<string, Record<string, boolean>> }): Promise<{ status: string; por_rol: Record<string, Record<string, boolean>>; guardados: number; message?: string }> {
const { data } = await axios.put(`${ODOO_BASE}/skeen/frontend/v1/permisos`, payload);
return data;
},
// Health
async healthCheck(): Promise<unknown> {
const { data } = await axios.get(`${ODOO_BASE}/skeen/frontend/v1/health`);