@softdin/portal-admin 0.0.0-stage → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (96) hide show
  1. package/README.md +95 -2
  2. package/dist-lib/chunks/AppLayout-COCPfLWo.js +303 -0
  3. package/dist-lib/chunks/AuditoriaView-Bcf_kOqI.js +643 -0
  4. package/dist-lib/chunks/CarnetView-BsdUtYeu.js +450 -0
  5. package/dist-lib/chunks/CarteleraView-CRnVjt-S.js +996 -0
  6. package/dist-lib/chunks/CertificadosView-DvHDflcN.js +549 -0
  7. package/dist-lib/chunks/ChatBandejaView-HqhdSw7G.js +274 -0
  8. package/dist-lib/chunks/ChatConfiguracionView-BdH5gDdR.js +716 -0
  9. package/dist-lib/chunks/ChatConocimientoView-nKTAKxFa.js +263 -0
  10. package/dist-lib/chunks/ChatConversacionView-Do74V09l.js +477 -0
  11. package/dist-lib/chunks/ChatInformeView-BIiAH9L_.js +280 -0
  12. package/dist-lib/chunks/ChatRespuestasView-QFXbyg8U.js +183 -0
  13. package/dist-lib/chunks/EmpleadosView-BtqrO5-8.js +311 -0
  14. package/dist-lib/chunks/HomologacionView-UCZk4u6R.js +949 -0
  15. package/dist-lib/chunks/IngresoSsoView-BKkcnrYO.js +98 -0
  16. package/dist-lib/chunks/SolicitudesView-CYPD2A1s.js +900 -0
  17. package/dist-lib/chunks/TiposSolicitudView-CrWTVizl.js +168 -0
  18. package/dist-lib/chunks/ToggleSwitch-BYrIyheq.js +55 -0
  19. package/dist-lib/chunks/_plugin-vue_export-helper-BOaGB7Aw.js +8 -0
  20. package/dist-lib/chunks/api-M7D1c3IT.js +97 -0
  21. package/dist-lib/chunks/apiError-BHGbwz97.js +15 -0
  22. package/dist-lib/chunks/auth.store-BrGKZg-7.js +220 -0
  23. package/dist-lib/chunks/chat.service-C7ZQf4w3.js +241 -0
  24. package/dist-lib/chunks/cliente-interno.store-De39VsEE.js +203 -0
  25. package/dist-lib/chunks/homologacion-CoeHD7cB.js +131 -0
  26. package/dist-lib/chunks/portal-empleado-6KQrnsQJ.js +120 -0
  27. package/dist-lib/chunks/tipos-D93bLfFC.js +22 -0
  28. package/dist-lib/portal-admin.js +196 -0
  29. package/dist-lib/style.css +4813 -0
  30. package/dist-lib/types/App.vue.d.ts +3 -0
  31. package/dist-lib/types/components/AppLayout.vue.d.ts +3 -0
  32. package/dist-lib/types/components/CarnetClientesInternos.vue.d.ts +11 -0
  33. package/dist-lib/types/components/InvitacionPortalModal.vue.d.ts +20 -0
  34. package/dist-lib/types/components/MenuUsuario.vue.d.ts +3 -0
  35. package/dist-lib/types/components/VistoBuenoPortalModal.vue.d.ts +23 -0
  36. package/dist-lib/types/components/auditoria/AuditoriaFila.vue.d.ts +16 -0
  37. package/dist-lib/types/components/auditoria/AuditoriaFiltros.vue.d.ts +30 -0
  38. package/dist-lib/types/components/auditoria/AuditoriaPaginador.vue.d.ts +14 -0
  39. package/dist-lib/types/components/cartelera/PaginaCarteleraImagen.vue.d.ts +14 -0
  40. package/dist-lib/types/components/cartelera/PublicacionFormModal.vue.d.ts +27 -0
  41. package/dist-lib/types/components/cartelera/VistaCelularCartelera.vue.d.ts +34 -0
  42. package/dist-lib/types/components/certificados/AtenderCertificadoModal.vue.d.ts +22 -0
  43. package/dist-lib/types/components/certificados/RechazarCertificadoModal.vue.d.ts +18 -0
  44. package/dist-lib/types/components/certificados/ResumenSolicitudCertificado.vue.d.ts +8 -0
  45. package/dist-lib/types/components/chat/ChatClientesInternos.vue.d.ts +21 -0
  46. package/dist-lib/types/components/chat/ChatHorarioCampos.vue.d.ts +21 -0
  47. package/dist-lib/types/components/forms/ExportExcelButton.vue.d.ts +30 -0
  48. package/dist-lib/types/components/forms/ToggleSwitch.vue.d.ts +17 -0
  49. package/dist-lib/types/components/homologacion/HomologacionFila.vue.d.ts +30 -0
  50. package/dist-lib/types/components/homologacion/TipoPermisoFormModal.vue.d.ts +26 -0
  51. package/dist-lib/types/components/homologacion/TipoSolicitudFormModal.vue.d.ts +27 -0
  52. package/dist-lib/types/lib/auditoria.d.ts +25 -0
  53. package/dist-lib/types/lib/cartelera-paginas.d.ts +26 -0
  54. package/dist-lib/types/lib/cartelera-vigencia.d.ts +3 -0
  55. package/dist-lib/types/lib/export-excel-csv.d.ts +4 -0
  56. package/dist-lib/types/lib/export-excel.d.ts +9 -0
  57. package/dist-lib/types/lib/exportar-listado.d.ts +68 -0
  58. package/dist-lib/types/lib/homologacion.d.ts +41 -0
  59. package/dist-lib/types/lib/portal-empleado.d.ts +32 -0
  60. package/dist-lib/types/modulo.d.ts +31 -0
  61. package/dist-lib/types/portal-admin/config.d.ts +17 -0
  62. package/dist-lib/types/portal-admin/rutas.d.ts +17 -0
  63. package/dist-lib/types/portal-admin/tipos.d.ts +68 -0
  64. package/dist-lib/types/router/index.d.ts +7 -0
  65. package/dist-lib/types/services/api.d.ts +29 -0
  66. package/dist-lib/types/services/auditoria.service.d.ts +63 -0
  67. package/dist-lib/types/services/auth.service.d.ts +61 -0
  68. package/dist-lib/types/services/carnet.service.d.ts +56 -0
  69. package/dist-lib/types/services/cartelera.service.d.ts +69 -0
  70. package/dist-lib/types/services/chat.service.d.ts +279 -0
  71. package/dist-lib/types/services/configuracion-cliente-interno.service.d.ts +23 -0
  72. package/dist-lib/types/services/homologacion.service.d.ts +105 -0
  73. package/dist-lib/types/services/portal-empleado.service.d.ts +112 -0
  74. package/dist-lib/types/services/solicitudes-certificado.service.d.ts +59 -0
  75. package/dist-lib/types/stores/auth.store.d.ts +125 -0
  76. package/dist-lib/types/stores/cliente-interno.store.d.ts +65 -0
  77. package/dist-lib/types/utils/apiError.d.ts +4 -0
  78. package/dist-lib/types/views/AuditoriaView.vue.d.ts +3 -0
  79. package/dist-lib/types/views/CarnetView.vue.d.ts +3 -0
  80. package/dist-lib/types/views/CarteleraView.vue.d.ts +3 -0
  81. package/dist-lib/types/views/CertificadosView.vue.d.ts +3 -0
  82. package/dist-lib/types/views/EmpleadosView.vue.d.ts +3 -0
  83. package/dist-lib/types/views/HomologacionView.vue.d.ts +4 -0
  84. package/dist-lib/types/views/IngresoSsoView.vue.d.ts +3 -0
  85. package/dist-lib/types/views/LoginView.vue.d.ts +3 -0
  86. package/dist-lib/types/views/OlvideView.vue.d.ts +3 -0
  87. package/dist-lib/types/views/RestablecerView.vue.d.ts +3 -0
  88. package/dist-lib/types/views/SolicitudesView.vue.d.ts +3 -0
  89. package/dist-lib/types/views/TiposSolicitudView.vue.d.ts +3 -0
  90. package/dist-lib/types/views/chat/ChatBandejaView.vue.d.ts +3 -0
  91. package/dist-lib/types/views/chat/ChatConfiguracionView.vue.d.ts +3 -0
  92. package/dist-lib/types/views/chat/ChatConocimientoView.vue.d.ts +3 -0
  93. package/dist-lib/types/views/chat/ChatConversacionView.vue.d.ts +11 -0
  94. package/dist-lib/types/views/chat/ChatInformeView.vue.d.ts +3 -0
  95. package/dist-lib/types/views/chat/ChatRespuestasView.vue.d.ts +3 -0
  96. package/package.json +66 -4
@@ -0,0 +1,168 @@
1
+ import { i as portalTiposService, t as useClienteInternoStore } from "./cliente-interno.store-De39VsEE.js";
2
+ import { t as _plugin_vue_export_helper_default } from "./_plugin-vue_export-helper-BOaGB7Aw.js";
3
+ import { t as mensajeError } from "./apiError-BHGbwz97.js";
4
+ import { t as ToggleSwitch_default } from "./ToggleSwitch-BYrIyheq.js";
5
+ import { c as soporteDeTipo, n as conceptoDeTipo } from "./portal-empleado-6KQrnsQJ.js";
6
+ import { l as soporteDeTipoNuevo, s as esTipoNuevo } from "./homologacion-CoeHD7cB.js";
7
+ import { Fragment, computed, createBlock, createCommentVNode, createElementBlock, createElementVNode, createTextVNode, createVNode, defineComponent, onMounted, openBlock, ref, renderList, toDisplayString, unref, watch } from "vue";
8
+ //#region src/views/TiposSolicitudView.vue?vue&type=script&setup=true&lang.ts
9
+ var _hoisted_1 = { class: "page-shell" };
10
+ var _hoisted_2 = {
11
+ class: "selector",
12
+ "data-prueba": "configurando"
13
+ };
14
+ var _hoisted_3 = {
15
+ key: 0,
16
+ class: "panel-muted"
17
+ };
18
+ var _hoisted_4 = {
19
+ key: 0,
20
+ class: "panel-error",
21
+ role: "alert"
22
+ };
23
+ var _hoisted_5 = {
24
+ key: 1,
25
+ class: "panel-muted"
26
+ };
27
+ var _hoisted_6 = {
28
+ key: 2,
29
+ class: "table-wrap"
30
+ };
31
+ var _hoisted_7 = { class: "data-table table-cards" };
32
+ var _hoisted_8 = { key: 0 };
33
+ var _hoisted_9 = {
34
+ class: "cell-primary",
35
+ "data-label": "Tipo"
36
+ };
37
+ var _hoisted_10 = { "data-label": "Novedad" };
38
+ var _hoisted_11 = {
39
+ key: 0,
40
+ class: "sub"
41
+ };
42
+ var _hoisted_12 = { "data-label": "Visto bueno de nómina" };
43
+ var _hoisted_13 = {
44
+ key: 0,
45
+ class: "panel-muted"
46
+ };
47
+ var _hoisted_14 = { "data-label": "Disponible" };
48
+ var _hoisted_15 = {
49
+ key: 0,
50
+ "data-label": "Configuración"
51
+ };
52
+ var _hoisted_16 = ["disabled", "onClick"];
53
+ var _hoisted_17 = {
54
+ key: 1,
55
+ class: "panel-muted"
56
+ };
57
+ //#endregion
58
+ //#region src/views/TiposSolicitudView.vue
59
+ var TiposSolicitudView_default = /*#__PURE__*/ _plugin_vue_export_helper_default(/* @__PURE__ */ defineComponent({
60
+ __name: "TiposSolicitudView",
61
+ setup(__props) {
62
+ const tipos = ref([]);
63
+ const clientesInternos = useClienteInternoStore();
64
+ const clienteInterno = computed(() => clientesInternos.seleccionado);
65
+ const loading = ref(false);
66
+ const error = ref(null);
67
+ const guardando = ref(null);
68
+ async function cargar() {
69
+ loading.value = true;
70
+ error.value = null;
71
+ try {
72
+ tipos.value = await portalTiposService.listar(clienteInterno.value);
73
+ } catch (e) {
74
+ error.value = mensajeError(e, "No se pudieron cargar los tipos de solicitud.");
75
+ } finally {
76
+ loading.value = false;
77
+ }
78
+ }
79
+ async function cambiar(t, campo, valor) {
80
+ error.value = null;
81
+ guardando.value = `${t.id}-${campo}`;
82
+ try {
83
+ const actualizado = await portalTiposService.actualizar(t.id, { [campo]: valor }, clienteInterno.value);
84
+ tipos.value = tipos.value.map((x) => x.id === t.id ? actualizado : x);
85
+ } catch (e) {
86
+ error.value = mensajeError(e, "No se pudo guardar el cambio.");
87
+ } finally {
88
+ guardando.value = null;
89
+ }
90
+ }
91
+ async function heredar(t) {
92
+ if (clienteInterno.value === null) return;
93
+ error.value = null;
94
+ guardando.value = `${t.id}-heredar`;
95
+ try {
96
+ const actualizado = await portalTiposService.heredar(t.id, clienteInterno.value);
97
+ tipos.value = tipos.value.map((x) => x.id === t.id ? actualizado : x);
98
+ } catch (e) {
99
+ error.value = mensajeError(e, "No se pudo guardar el cambio.");
100
+ } finally {
101
+ guardando.value = null;
102
+ }
103
+ }
104
+ /** Los tipos nuevos (`t_…`, creados en «Homologación») crean la novedad con el concepto que se les homologue. */
105
+ function novedadDe(t) {
106
+ if (esTipoNuevo(t.tipo)) return {
107
+ concepto: "Tipo nuevo del portal: la del concepto que se le homologue en «Homologación»",
108
+ soporte: soporteDeTipoNuevo(t)
109
+ };
110
+ return {
111
+ concepto: conceptoDeTipo(t.tipo),
112
+ soporte: soporteDeTipo(t.tipo)
113
+ };
114
+ }
115
+ watch(clienteInterno, () => void cargar());
116
+ onMounted(() => void cargar());
117
+ return (_ctx, _cache) => {
118
+ return openBlock(), createElementBlock("section", _hoisted_1, [
119
+ _cache[7] || (_cache[7] = createElementVNode("header", null, [createElementVNode("h1", { class: "page-title" }, "Tipos de solicitud del portal"), createElementVNode("p", { class: "page-subtitle" }, " Qué puede radicar el empleado en su Portal de autogestión y qué pasa al aprobarse. Con «Visto bueno de nómina» la novedad nace inactiva y nómina la activa desde «Solicitudes»; sin él, entra directo en la liquidación. Los cambios aplican a las solicitudes que se radiquen desde ahora. ")], -1)),
120
+ createElementVNode("p", _hoisted_2, [createElementVNode("span", null, [
121
+ _cache[0] || (_cache[0] = createTextVNode(" Configurando para ", -1)),
122
+ createElementVNode("strong", null, toDisplayString(unref(clientesInternos).nombre(clienteInterno.value)), 1),
123
+ _cache[1] || (_cache[1] = createElementVNode("span", { class: "panel-muted" }, "(se cambia en «Cliente», arriba)", -1))
124
+ ]), clienteInterno.value !== null ? (openBlock(), createElementBlock("span", _hoisted_3, " Lo que cambie aquí aplica solo a los empleados de este cliente y prevalece sobre lo de todo el tenant. ")) : createCommentVNode("", true)]),
125
+ error.value ? (openBlock(), createElementBlock("p", _hoisted_4, toDisplayString(error.value), 1)) : loading.value ? (openBlock(), createElementBlock("p", _hoisted_5, "Cargando…")) : createCommentVNode("", true),
126
+ tipos.value.length ? (openBlock(), createElementBlock("div", _hoisted_6, [createElementVNode("table", _hoisted_7, [createElementVNode("thead", null, [createElementVNode("tr", null, [
127
+ _cache[2] || (_cache[2] = createElementVNode("th", null, "Tipo", -1)),
128
+ _cache[3] || (_cache[3] = createElementVNode("th", null, "Novedad que se crea al aprobar", -1)),
129
+ _cache[4] || (_cache[4] = createElementVNode("th", null, "Visto bueno de nómina", -1)),
130
+ _cache[5] || (_cache[5] = createElementVNode("th", null, "Disponible en el portal", -1)),
131
+ clienteInterno.value !== null ? (openBlock(), createElementBlock("th", _hoisted_8, "Configuración")) : createCommentVNode("", true)
132
+ ])]), createElementVNode("tbody", null, [(openBlock(true), createElementBlock(Fragment, null, renderList(tipos.value, (t) => {
133
+ return openBlock(), createElementBlock("tr", { key: t.id }, [
134
+ createElementVNode("td", _hoisted_9, toDisplayString(t.nombre), 1),
135
+ createElementVNode("td", _hoisted_10, [createTextVNode(toDisplayString(novedadDe(t).concepto) + " ", 1), novedadDe(t).soporte ? (openBlock(), createElementBlock("span", _hoisted_11, toDisplayString(novedadDe(t).soporte), 1)) : createCommentVNode("", true)]),
136
+ createElementVNode("td", _hoisted_12, [t.tipo === "certificado" ? (openBlock(), createElementBlock("span", _hoisted_13, "No aplica")) : (openBlock(), createBlock(ToggleSwitch_default, {
137
+ key: 1,
138
+ "model-value": t.requiere_visto_bueno_nomina,
139
+ disabled: guardando.value !== null,
140
+ "onUpdate:modelValue": ($event) => cambiar(t, "requiere_visto_bueno_nomina", $event)
141
+ }, null, 8, [
142
+ "model-value",
143
+ "disabled",
144
+ "onUpdate:modelValue"
145
+ ]))]),
146
+ createElementVNode("td", _hoisted_14, [createVNode(ToggleSwitch_default, {
147
+ "model-value": t.activo,
148
+ disabled: guardando.value !== null,
149
+ "onUpdate:modelValue": ($event) => cambiar(t, "activo", $event)
150
+ }, null, 8, [
151
+ "model-value",
152
+ "disabled",
153
+ "onUpdate:modelValue"
154
+ ])]),
155
+ clienteInterno.value !== null ? (openBlock(), createElementBlock("td", _hoisted_15, [t.personalizado ? (openBlock(), createElementBlock(Fragment, { key: 0 }, [_cache[6] || (_cache[6] = createTextVNode(" Propia del cliente · ", -1)), createElementVNode("button", {
156
+ type: "button",
157
+ class: "btn-link",
158
+ disabled: guardando.value !== null,
159
+ onClick: ($event) => heredar(t)
160
+ }, " Usar la de todo el tenant ", 8, _hoisted_16)], 64)) : (openBlock(), createElementBlock("span", _hoisted_17, "Como todo el tenant"))])) : createCommentVNode("", true)
161
+ ]);
162
+ }), 128))])])])) : createCommentVNode("", true)
163
+ ]);
164
+ };
165
+ }
166
+ }), [["__scopeId", "data-v-a9228d37"]]);
167
+ //#endregion
168
+ export { TiposSolicitudView_default as default };
@@ -0,0 +1,55 @@
1
+ import { t as _plugin_vue_export_helper_default } from "./_plugin-vue_export-helper-BOaGB7Aw.js";
2
+ import { createCommentVNode, createElementBlock, createElementVNode, defineComponent, mergeModels, normalizeClass, openBlock, toDisplayString, useModel, vModelCheckbox, withDirectives } from "vue";
3
+ //#region src/components/forms/ToggleSwitch.vue?vue&type=script&setup=true&lang.ts
4
+ var _hoisted_1 = {
5
+ key: 0,
6
+ class: "toggle-label"
7
+ };
8
+ var _hoisted_2 = [
9
+ "id",
10
+ "disabled",
11
+ "aria-checked",
12
+ "aria-label"
13
+ ];
14
+ //#endregion
15
+ //#region src/components/forms/ToggleSwitch.vue
16
+ var ToggleSwitch_default = /*#__PURE__*/ _plugin_vue_export_helper_default(/* @__PURE__ */ defineComponent({
17
+ __name: "ToggleSwitch",
18
+ props: /*@__PURE__*/ mergeModels({
19
+ disabled: { type: Boolean },
20
+ label: {},
21
+ ariaLabel: {},
22
+ id: {}
23
+ }, {
24
+ "modelValue": {
25
+ type: Boolean,
26
+ default: false
27
+ },
28
+ "modelModifiers": {}
29
+ }),
30
+ emits: ["update:modelValue"],
31
+ setup(__props) {
32
+ const model = useModel(__props, "modelValue");
33
+ return (_ctx, _cache) => {
34
+ return openBlock(), createElementBlock("label", { class: normalizeClass(["toggle", { "toggle-disabled": __props.disabled }]) }, [
35
+ __props.label ? (openBlock(), createElementBlock("span", _hoisted_1, toDisplayString(__props.label), 1)) : createCommentVNode("", true),
36
+ withDirectives(createElementVNode("input", {
37
+ id: __props.id,
38
+ "onUpdate:modelValue": _cache[0] || (_cache[0] = ($event) => model.value = $event),
39
+ type: "checkbox",
40
+ class: "toggle-input",
41
+ role: "switch",
42
+ disabled: __props.disabled,
43
+ "aria-checked": model.value,
44
+ "aria-label": __props.ariaLabel ?? __props.label
45
+ }, null, 8, _hoisted_2), [[vModelCheckbox, model.value]]),
46
+ _cache[1] || (_cache[1] = createElementVNode("span", {
47
+ class: "toggle-track",
48
+ "aria-hidden": "true"
49
+ }, [createElementVNode("span", { class: "toggle-thumb" })], -1))
50
+ ], 2);
51
+ };
52
+ }
53
+ }), [["__scopeId", "data-v-f2c39227"]]);
54
+ //#endregion
55
+ export { ToggleSwitch_default as t };
@@ -0,0 +1,8 @@
1
+ //#region \0plugin-vue:export-helper
2
+ var _plugin_vue_export_helper_default = (sfc, props) => {
3
+ const target = sfc.__vccOpts || sfc;
4
+ for (const [key, val] of props) target[key] = val;
5
+ return target;
6
+ };
7
+ //#endregion
8
+ export { _plugin_vue_export_helper_default as t };
@@ -0,0 +1,97 @@
1
+ import axios from "axios";
2
+ //#region src/services/api.ts
3
+ /**
4
+ * Cliente HTTP de Gestión del Portal (softdin-portal-api, rutas `/rrhh`). Esta
5
+ * app ya no habla con softdin-api: el usuario, el token y el cliente son del
6
+ * portal.
7
+ *
8
+ * La base no sale de `import.meta.env` aquí: este archivo también se publica en
9
+ * `@softdin/portal-admin` y Vite congelaría en el paquete la URL con la que se compiló.
10
+ * La pone quien instala el módulo (`configurarApiUrl`).
11
+ */
12
+ var api = axios.create({
13
+ headers: { Accept: "application/json" },
14
+ timeout: 3e4
15
+ });
16
+ /** `https://apiportal…/api/v1` → base `…/api/v1/rrhh`. */
17
+ function configurarApiUrl(apiUrl) {
18
+ api.defaults.baseURL = `${apiUrl.replace(/\/+$/, "")}/rrhh`;
19
+ }
20
+ var CLAVE_URL = "softdin_portal_admin_api_url";
21
+ /**
22
+ * Con SSO la base puede venir en la aserción (`portal_api_url`) y no en la configuración de
23
+ * la app: se recuerda para que, al recargar con el token guardado, las peticiones no salgan
24
+ * contra el propio origen antes de la siguiente aserción.
25
+ */
26
+ function recordarApiUrl(apiUrl) {
27
+ configurarApiUrl(apiUrl);
28
+ try {
29
+ localStorage.setItem(CLAVE_URL, apiUrl);
30
+ } catch {}
31
+ }
32
+ function apiUrlRecordada() {
33
+ try {
34
+ return localStorage.getItem(CLAVE_URL);
35
+ } catch {
36
+ return null;
37
+ }
38
+ }
39
+ function apiUrlConfigurada() {
40
+ return typeof api.defaults.baseURL === "string" && api.defaults.baseURL !== "";
41
+ }
42
+ function esFueraDeAlcance(error) {
43
+ if (!axios.isAxiosError(error)) return false;
44
+ const datos = error.response?.data;
45
+ return datos?.codigo === "fuera_de_alcance" || datos?.message === "No tiene acceso a ese cliente.";
46
+ }
47
+ var quitar = null;
48
+ /** Rutas donde un 403 significa «solo el administrador del cliente», no sesión cerrada. */
49
+ var SOLO_ADMIN = [
50
+ "/auditoria",
51
+ "/homologacion",
52
+ "/tipos-permiso",
53
+ "/tipos-solicitud"
54
+ ];
55
+ /**
56
+ * `token`: el de la sesión. `alExpirar`: 401 con sesión abierta (token vencido,
57
+ * usuario desactivado). Un 403 (cliente desactivado o sin acceso) también cierra.
58
+ * `renovar` (SSO): ante un 401 se pide un token nuevo una sola vez y se repite la petición;
59
+ * si no se puede, entonces sí `alExpirar`. Llamarla otra vez reemplaza los interceptores.
60
+ */
61
+ function setupApiInterceptors(token, alExpirar, renovar) {
62
+ quitar?.();
63
+ const pedido = api.interceptors.request.use((config) => {
64
+ const t = token();
65
+ if (t && config.url !== "/auth/sso") config.headers.Authorization = `Bearer ${t}`;
66
+ return config;
67
+ });
68
+ let renovando = null;
69
+ function renovarUnaVez() {
70
+ if (!renovar) return Promise.resolve(false);
71
+ renovando ??= renovar().catch(() => false).finally(() => {
72
+ renovando = null;
73
+ });
74
+ return renovando;
75
+ }
76
+ const respuesta = api.interceptors.response.use(void 0, async (error) => {
77
+ const status = axios.isAxiosError(error) ? error.response?.status : void 0;
78
+ const config = axios.isAxiosError(error) ? error.config : void 0;
79
+ const url = config?.url ?? "";
80
+ const sinRol = status === 403 && (SOLO_ADMIN.some((prefijo) => url.startsWith(prefijo)) || esFueraDeAlcance(error));
81
+ if ((status === 401 || status === 403) && token() && !url.startsWith("/auth/") && !sinRol) {
82
+ if (status === 401 && renovar && config && !config._portalRenovado) {
83
+ config._portalRenovado = true;
84
+ if (await renovarUnaVez()) return api.request(config);
85
+ }
86
+ alExpirar();
87
+ }
88
+ return Promise.reject(error);
89
+ });
90
+ quitar = () => {
91
+ api.interceptors.request.eject(pedido);
92
+ api.interceptors.response.eject(respuesta);
93
+ quitar = null;
94
+ };
95
+ }
96
+ //#endregion
97
+ export { recordarApiUrl as a, configurarApiUrl as i, apiUrlConfigurada as n, setupApiInterceptors as o, apiUrlRecordada as r, api as t };
@@ -0,0 +1,15 @@
1
+ import axios from "axios";
2
+ import { getConnectivityMessage } from "@softdin/shared/utils/apiError";
3
+ //#region src/utils/apiError.ts
4
+ /** Mensaje para el usuario: sin conexión, el primer error de validación o el `message` del servidor. */
5
+ function mensajeError(e, porDefecto) {
6
+ const conexion = getConnectivityMessage(e);
7
+ if (conexion) return conexion;
8
+ if (axios.isAxiosError(e) && e.response) {
9
+ const data = e.response.data;
10
+ return (data?.errors ? Object.values(data.errors).flat()[0] : void 0) ?? data?.message ?? porDefecto;
11
+ }
12
+ return porDefecto;
13
+ }
14
+ //#endregion
15
+ export { mensajeError as t };
@@ -0,0 +1,220 @@
1
+ import { a as recordarApiUrl, n as apiUrlConfigurada, t as api } from "./api-M7D1c3IT.js";
2
+ import { defineStore } from "pinia";
3
+ import { computed, ref } from "vue";
4
+ //#region src/services/auth.service.ts
5
+ var authService = {
6
+ async login(email, password, clienteId) {
7
+ const { data } = await api.post("/auth/login", {
8
+ email,
9
+ password,
10
+ ...clienteId !== void 0 ? { cliente_id: clienteId } : {}
11
+ });
12
+ return data;
13
+ },
14
+ /**
15
+ * Ingreso desde softdin-central (`rrhh/auth/sso`): misma respuesta que `login`. La aserción
16
+ * se gasta solo cuando entrega token.
17
+ */
18
+ async sso(asercion, clienteId) {
19
+ const { data } = await api.post("/auth/sso", {
20
+ asercion,
21
+ ...clienteId !== void 0 ? { cliente_id: clienteId } : {}
22
+ });
23
+ return data;
24
+ },
25
+ async me() {
26
+ const { data } = await api.get("/me");
27
+ return data;
28
+ },
29
+ /** Token nuevo ligado al otro cliente; el actual queda anulado. */
30
+ async cambiarCliente(clienteId) {
31
+ const { data } = await api.post("/auth/cambiar-cliente", { cliente_id: clienteId });
32
+ return data;
33
+ },
34
+ async logout() {
35
+ await api.post("/auth/logout");
36
+ },
37
+ /** Siempre la misma respuesta, exista o no el correo. */
38
+ async olvide(email) {
39
+ const { data } = await api.post("/auth/olvide", { email });
40
+ return data.message;
41
+ },
42
+ async restablecer(datos) {
43
+ const { data } = await api.post("/auth/restablecer", datos);
44
+ return data.message;
45
+ }
46
+ };
47
+ //#endregion
48
+ //#region src/portal-admin/config.ts
49
+ var configPortal = {
50
+ basePath: "/",
51
+ sesion: null,
52
+ alSalir: null,
53
+ cambioDeTenant: true
54
+ };
55
+ /** `'/central/portal/'` → `'/central/portal'`; `''` o `'/'` → `'/'`. */
56
+ function normalizarBasePath(base) {
57
+ const limpio = `/${(base ?? "/").trim().replace(/^\/+|\/+$/g, "")}`;
58
+ return limpio === "/" ? "/" : limpio;
59
+ }
60
+ /** Une la base con un tramo: `('/', 'ingreso')` → `'/ingreso'`. */
61
+ function unirRuta(base, tramo) {
62
+ return base === "/" ? `/${tramo}` : `${base}/${tramo}`;
63
+ }
64
+ //#endregion
65
+ //#region src/stores/auth.store.ts
66
+ /**
67
+ * Sesión de RRHH en Gestión del Portal: usuario propio del portal (no de
68
+ * SoftDIN) y un token ligado a un solo cliente, como en softdin-tenant.
69
+ */
70
+ var CLAVE = "softdin_portal_tenant_sesion";
71
+ function leer() {
72
+ try {
73
+ const crudo = localStorage.getItem(CLAVE);
74
+ return crudo ? JSON.parse(crudo) : null;
75
+ } catch {
76
+ return null;
77
+ }
78
+ }
79
+ var useAuthStore = defineStore("portal-auth", () => {
80
+ const guardada = leer();
81
+ const token = ref(guardada?.token ?? null);
82
+ const usuario = ref(guardada?.usuario ?? null);
83
+ const cliente = ref(guardada?.cliente ?? null);
84
+ const clientes = ref(guardada?.clientes ?? []);
85
+ const rol = ref(guardada?.rol);
86
+ const esAdminServidor = ref(guardada?.es_admin);
87
+ const clientesInternosServidor = ref(guardada?.clientes_internos);
88
+ /** `tenant` = SSO desde softdin-tenant: la sesión es solo de su cliente (sin cambio de cliente). */
89
+ const ambito = ref(guardada?.ambito);
90
+ const verificada = ref(false);
91
+ const isAuthenticated = computed(() => token.value !== null && cliente.value !== null);
92
+ /**
93
+ * Administra el cliente de la sesión. Si el servidor no manda `es_admin` se mira `rol`; si
94
+ * no manda ninguno, no se sabe y se trata como no administrador (la Auditoría queda oculta).
95
+ */
96
+ const esAdmin = computed(() => esAdminServidor.value !== void 0 ? esAdminServidor.value === true : rol.value === "admin");
97
+ /**
98
+ * Clientes internos en los que puede trabajar: la lista que mandó el servidor, o
99
+ ull si no
100
+ * tiene restricción (o el servidor no lo dijo). Las consultas no se limitan; las escrituras
101
+ * fuera de la lista las rechaza el API.
102
+ */
103
+ const clientesInternos = computed(() => clientesInternosServidor.value ?? null);
104
+ function rolDe(d) {
105
+ rol.value = d.rol;
106
+ esAdminServidor.value = typeof d.es_admin === "boolean" ? d.es_admin : void 0;
107
+ const ci = d.clientes_internos;
108
+ clientesInternosServidor.value = ci === null ? null : Array.isArray(ci) ? ci.filter((id) => Number.isInteger(id)) : void 0;
109
+ ambito.value = d.ambito ?? void 0;
110
+ }
111
+ function persistir() {
112
+ try {
113
+ if (token.value && usuario.value && cliente.value) {
114
+ const s = {
115
+ token: token.value,
116
+ usuario: usuario.value,
117
+ cliente: cliente.value,
118
+ clientes: clientes.value,
119
+ rol: rol.value,
120
+ es_admin: esAdminServidor.value,
121
+ clientes_internos: clientesInternosServidor.value,
122
+ ambito: ambito.value
123
+ };
124
+ localStorage.setItem(CLAVE, JSON.stringify(s));
125
+ }
126
+ } catch {}
127
+ }
128
+ function setSession(s) {
129
+ token.value = s.token;
130
+ usuario.value = s.usuario;
131
+ cliente.value = s.cliente;
132
+ clientes.value = s.clientes;
133
+ rolDe(s);
134
+ verificada.value = (s.es_admin !== void 0 || s.rol !== void 0) && (configPortal.sesion === null || s.clientes_internos !== void 0);
135
+ persistir();
136
+ }
137
+ function clearSession() {
138
+ token.value = null;
139
+ usuario.value = null;
140
+ cliente.value = null;
141
+ clientes.value = [];
142
+ rol.value = void 0;
143
+ esAdminServidor.value = void 0;
144
+ clientesInternosServidor.value = void 0;
145
+ ambito.value = void 0;
146
+ verificada.value = false;
147
+ try {
148
+ localStorage.removeItem(CLAVE);
149
+ } catch {}
150
+ }
151
+ /** Devuelve la respuesta tal cual: si pide elegir cliente, la vista muestra la lista. */
152
+ async function login(email, password, clienteId) {
153
+ const r = await authService.login(email, password, clienteId);
154
+ if ("token" in r) setSession(r);
155
+ return r;
156
+ }
157
+ /** Una vez por carga de página: confirma que el token sigue vivo y refresca los clientes. */
158
+ async function verificar() {
159
+ if (verificada.value) return;
160
+ const r = await authService.me();
161
+ usuario.value = r.usuario;
162
+ cliente.value = r.cliente;
163
+ clientes.value = r.clientes;
164
+ rolDe(r);
165
+ verificada.value = true;
166
+ persistir();
167
+ }
168
+ /**
169
+ * Ingreso por SSO (softdin-central): pide una aserción NUEVA al proveedor y la cambia por el
170
+ * token del portal. Sin `clienteId` y con varios clientes responde `requiere_cliente`; el
171
+ * segundo paso vuelve a llamar aquí con el elegido (y otra aserción: son de un solo uso).
172
+ */
173
+ async function ingresarSso(clienteId) {
174
+ const proveedor = configPortal.sesion;
175
+ if (!proveedor) throw new Error("El ingreso por SSO no está configurado.");
176
+ const { asercion, portal_api_url } = await proveedor.obtenerAsercion();
177
+ if (!apiUrlConfigurada()) {
178
+ if (!portal_api_url) throw new Error("No se conoce la dirección del portal (portal_api_url).");
179
+ recordarApiUrl(portal_api_url);
180
+ }
181
+ const r = await authService.sso(asercion, clienteId);
182
+ if ("token" in r) setSession(r);
183
+ return r;
184
+ }
185
+ /** Tras un 401: token nuevo para el mismo cliente, sin que el usuario lo note. */
186
+ async function renovarSso() {
187
+ if (!configPortal.sesion) return false;
188
+ return "token" in await ingresarSso(cliente.value?.id);
189
+ }
190
+ async function cambiarCliente(id) {
191
+ setSession(await authService.cambiarCliente(id));
192
+ }
193
+ async function logout() {
194
+ try {
195
+ await authService.logout();
196
+ } catch {} finally {
197
+ clearSession();
198
+ }
199
+ }
200
+ return {
201
+ token,
202
+ usuario,
203
+ cliente,
204
+ clientes,
205
+ rol,
206
+ esAdmin,
207
+ clientesInternos,
208
+ ambito,
209
+ isAuthenticated,
210
+ login,
211
+ ingresarSso,
212
+ renovarSso,
213
+ verificar,
214
+ cambiarCliente,
215
+ logout,
216
+ clearSession
217
+ };
218
+ });
219
+ //#endregion
220
+ export { unirRuta as i, configPortal as n, normalizarBasePath as r, useAuthStore as t };