mirror of
https://origin.cursor.com/mrdevmx/panels.git
synced 2026-10-09 13:23:17 +00:00
<!-- CURSOR_AGENT_PR_BODY_BEGIN --> ## Validación de endpoints y SPs Se probaron todos los endpoints nuevos (gastos, almacén, control presupuestal, IAM) y sus RPCs asociados. ### Bugs corregidos en migraciones/SQL 1. **IAM grants en schema core** — `iam-004e` intentaba `GRANT` sobre `core` con rol `iam_owner` (sin permiso). Los grants cruzados se movieron a `core-027-iam-rpc-cross-grants.sql`. 2. **`_cost_settings` ambiguo** — columnas `budget_warn_pct` etc. colisionaban con `RETURNS TABLE` en PL/pgSQL, rompiendo `fn_expense_create` y control presupuestal. Corregido en `core-028-fix-cost-settings-ambiguous.sql`. ### Tests añadidos - `api/cost_modules_test.ts` — CRUD RPC gastos, flujo almacén completo, control presupuestal, IAM permisos - `scripts/crud-smoke-test.sh` — smoke HTTP de todos los endpoints nuevos ### Resultados - `deno test cost_modules_test.ts` — 5/5 OK - `./scripts/crud-smoke-test.sh` — todos los checks OK (gastos CRUD, IVA, cost-settings, almacén, transferencias, cost-control, IAM) <!-- CURSOR_AGENT_PR_BODY_END --> <div><a href="https://cursor.com/agents/bc-eeec3b5c-f789-43e8-a353-0755e4706377?cursor_ref=pr_footer&cursor_cta=open_in_web"><picture><source media="(prefers-color-scheme: dark)" srcset="https://cursor.com/assets/images/open-in-web-dark.png"><source media="(prefers-color-scheme: light)" srcset="https://cursor.com/assets/images/open-in-web-light.png"><img alt="Open in Web" width="114" height="28" src="https://cursor.com/assets/images/open-in-web-dark.png"></picture></a> <a href="https://cursor.com/background-agent?bcId=bc-eeec3b5c-f789-43e8-a353-0755e4706377&cursor_ref=pr_footer&cursor_cta=open_in_cursor"><picture><source media="(prefers-color-scheme: dark)" srcset="https://cursor.com/assets/images/open-in-cursor-dark.png"><source media="(prefers-color-scheme: light)" srcset="https://cursor.com/assets/images/open-in-cursor-light.png"><img alt="Open in Cursor" width="131" height="28" src="https://cursor.com/assets/images/open-in-cursor-dark.png"></picture></a> </div>
78 lines
2.8 KiB
Vue
78 lines
2.8 KiB
Vue
<template>
|
|
<div class="usuarios-page">
|
|
<DeskTableBar v-model="tableQ" placeholder="Buscar usuario">
|
|
<Chip :label="`${users.length} usuarios`" icon="pi pi-users" />
|
|
</DeskTableBar>
|
|
|
|
<DataTable :value="listed" data-key="id" paginator :rows="25" scrollable scroll-height="flex">
|
|
<Column field="username" header="Usuario" style="width: 10rem" />
|
|
<Column field="display_name" header="Nombre" />
|
|
<Column field="role_code" header="Rol" style="width: 9rem" />
|
|
<Column field="email" header="Correo" style="width: 14rem" />
|
|
</DataTable>
|
|
|
|
<Panel header="Permisos del rol operativo (user)" class="mt-4">
|
|
<div class="perm-grid">
|
|
<div v-for="perm in permissions" :key="perm.code" class="perm-item">
|
|
<Checkbox v-model="rolePerms" :input-id="perm.code" :value="perm.code" />
|
|
<label :for="perm.code">{{ perm.label }}</label>
|
|
</div>
|
|
</div>
|
|
<Button label="Guardar permisos" icon="pi pi-save" class="mt-3" @click="saveRolePerms" />
|
|
</Panel>
|
|
</div>
|
|
</template>
|
|
|
|
<script setup lang="ts">
|
|
import { displayErrorMessage } from "~/composables/api-response";
|
|
|
|
type UserRow = { id: number; username: string; display_name: string; role_code: string; email: string };
|
|
type PermRow = { code: string; label: string };
|
|
|
|
const desk = useDesktop();
|
|
const { api } = useApi();
|
|
const toast = useToast();
|
|
|
|
const tableQ = ref("");
|
|
const users = ref<UserRow[]>([]);
|
|
const permissions = ref<PermRow[]>([]);
|
|
const rolePerms = ref<string[]>([]);
|
|
|
|
const listed = computed(() => {
|
|
const q = tableQ.value.toLowerCase();
|
|
return users.value.filter((u) => !q || [u.username, u.display_name, u.email].join(" ").toLowerCase().includes(q));
|
|
});
|
|
|
|
async function load() {
|
|
const [u, p, rp] = await Promise.all([
|
|
api<{ data?: { users?: UserRow[] } }>("/v1/iam/users"),
|
|
api<{ data?: { permissions?: PermRow[] } }>("/v1/iam/permissions"),
|
|
api<{ data?: { permissions?: string[] } }>("/v1/iam/roles/user/permissions"),
|
|
]);
|
|
users.value = u.data?.users ?? [];
|
|
permissions.value = p.data?.permissions ?? [];
|
|
rolePerms.value = rp.data?.permissions ?? [];
|
|
}
|
|
|
|
async function saveRolePerms() {
|
|
try {
|
|
const r = await api<{ message?: string }>("/v1/iam/roles/user/permissions", {
|
|
method: "PUT",
|
|
json: { permissions: rolePerms.value },
|
|
});
|
|
toast.add({ severity: "success", summary: r.message || "Permisos guardados", life: 2500 });
|
|
} catch (e) {
|
|
toast.add({ severity: "error", summary: displayErrorMessage((e as { data?: unknown }).data ?? e), life: 4000 });
|
|
}
|
|
}
|
|
|
|
onMounted(async () => {
|
|
desk.configure({ tab: "Usuarios IAM", ribbon: [] });
|
|
await load();
|
|
});
|
|
</script>
|
|
|
|
<style scoped>
|
|
.perm-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(260px, 1fr)); gap: 8px 16px; }
|
|
.perm-item { display: flex; align-items: center; gap: 8px; font-size: 13px; }
|
|
</style>
|