@softdin/shared 0.1.3

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 (40) hide show
  1. package/README.md +48 -0
  2. package/package.json +50 -0
  3. package/src/a11y/announcer.ts +43 -0
  4. package/src/a11y/apply.ts +83 -0
  5. package/src/a11y/index.ts +31 -0
  6. package/src/a11y/state.ts +40 -0
  7. package/src/a11y/storage.ts +106 -0
  8. package/src/a11y/types.ts +187 -0
  9. package/src/api.ts +89 -0
  10. package/src/assets/softdin-logo.png +0 -0
  11. package/src/auth.ts +134 -0
  12. package/src/components/a11y/A11yLiveRegion.vue +11 -0
  13. package/src/components/a11y/AccessibilityMenuControls.vue +426 -0
  14. package/src/components/a11y/AccessibilityPreferencesPanel.vue +331 -0
  15. package/src/components/a11y/ColorSchemeQuickToggle.vue +105 -0
  16. package/src/components/a11y/SkipLink.vue +41 -0
  17. package/src/components/auth/AuthBrandHeader.vue +129 -0
  18. package/src/components/auth/AuthShell.vue +52 -0
  19. package/src/components/auth/DataProcessingConsentCheckbox.vue +303 -0
  20. package/src/components/auth/DataProcessingConsentForm.vue +111 -0
  21. package/src/components/forms/ErrorAlert.vue +170 -0
  22. package/src/components/forms/FormCancelButton.vue +87 -0
  23. package/src/components/forms/FormCloseButton.vue +114 -0
  24. package/src/components/forms/FormHelpButton.vue +109 -0
  25. package/src/components/forms/FormHelpModal.vue +193 -0
  26. package/src/components/forms/FormMaximizeButton.vue +143 -0
  27. package/src/components/forms/FormRefreshButton.vue +152 -0
  28. package/src/components/forms/FormSaveButton.vue +113 -0
  29. package/src/components/forms/PasswordInput.vue +154 -0
  30. package/src/components/navigation/CatalogListPagination.vue +113 -0
  31. package/src/config/apiBase.ts +37 -0
  32. package/src/lib/catalog-pagination.ts +27 -0
  33. package/src/lib/pagination.ts +86 -0
  34. package/src/lib/softdin-libreria.ts +50 -0
  35. package/src/style.css +278 -0
  36. package/src/types/auth.ts +146 -0
  37. package/src/types/softdin-libreria.d.ts +41 -0
  38. package/src/utils/apiError.ts +55 -0
  39. package/src/utils/connectivity.ts +33 -0
  40. package/src/utils/tenantLogo.ts +6 -0
package/src/style.css ADDED
@@ -0,0 +1,278 @@
1
+ @import 'tailwindcss';
2
+
3
+ @theme {
4
+ --color-brand-50: #f0fdf4;
5
+ --color-brand-100: #dcfce7;
6
+ --color-brand-500: #22c55e;
7
+ --color-brand-600: #16a34a;
8
+ --color-brand-700: #15803d;
9
+ --color-brand-800: #166534;
10
+ --color-surface: #f8fafc;
11
+ --color-sidebar: #14532d;
12
+ --color-text: #0f172a;
13
+ --color-text-muted: #64748b;
14
+ --color-panel: #ffffff;
15
+ --color-border: #e2e8f0;
16
+ }
17
+
18
+ :root {
19
+ --a11y-text-scale: 1;
20
+ --a11y-font-family: system-ui, 'Segoe UI', Roboto, sans-serif;
21
+ --a11y-line-height: 1.5;
22
+ --a11y-letter-spacing: 0;
23
+ }
24
+
25
+ html {
26
+ font-size: calc(16px * var(--a11y-text-scale));
27
+ }
28
+
29
+ html,
30
+ body,
31
+ #app {
32
+ height: 100%;
33
+ }
34
+
35
+ body {
36
+ margin: 0;
37
+ font-family: var(--a11y-font-family);
38
+ line-height: var(--a11y-line-height);
39
+ letter-spacing: var(--a11y-letter-spacing);
40
+ background: var(--color-surface);
41
+ color: var(--color-text);
42
+ -webkit-font-smoothing: antialiased;
43
+ }
44
+
45
+ .sr-only {
46
+ position: absolute;
47
+ width: 1px;
48
+ height: 1px;
49
+ padding: 0;
50
+ margin: -1px;
51
+ overflow: hidden;
52
+ clip: rect(0, 0, 0, 0);
53
+ white-space: nowrap;
54
+ border-width: 0;
55
+ }
56
+
57
+ /* Controles interactivos: cursor que indica acción */
58
+ button:not(:disabled),
59
+ [type='button']:not(:disabled),
60
+ [type='submit']:not(:disabled),
61
+ [type='reset']:not(:disabled),
62
+ select:not(:disabled),
63
+ input[type='checkbox']:not(:disabled),
64
+ input[type='radio']:not(:disabled),
65
+ input[type='file']:not(:disabled),
66
+ input[type='color']:not(:disabled),
67
+ input[type='range']:not(:disabled),
68
+ label[for]:not([aria-disabled='true']),
69
+ label:has(input:not(:disabled)),
70
+ label:has(select:not(:disabled)),
71
+ a[href],
72
+ summary,
73
+ [role='button']:not([aria-disabled='true']):not(:disabled),
74
+ [role='tab']:not([aria-disabled='true']),
75
+ [role='switch']:not(:disabled),
76
+ [role='menuitem']:not([aria-disabled='true']),
77
+ [role='option']:not([aria-disabled='true']) {
78
+ cursor: pointer;
79
+ }
80
+
81
+ /* Controles deshabilitados o solo lectura */
82
+ button:disabled,
83
+ select:disabled,
84
+ input:disabled,
85
+ textarea:disabled,
86
+ [aria-disabled='true'],
87
+ .input-field-disabled,
88
+ .input-field:disabled,
89
+ .input-field[readonly][disabled] {
90
+ cursor: not-allowed;
91
+ }
92
+
93
+ /* --- Tema oscuro --- */
94
+ html[data-a11y-theme='dark'] {
95
+ --color-brand-50: #052e16;
96
+ --color-brand-100: #14532d;
97
+ --color-brand-500: #4ade80;
98
+ --color-brand-600: #22c55e;
99
+ --color-brand-700: #16a34a;
100
+ --color-brand-800: #15803d;
101
+ --color-surface: #0b1220;
102
+ --color-sidebar: #052e16;
103
+ --color-text: #e2e8f0;
104
+ --color-text-muted: #94a3b8;
105
+ --color-panel: #111827;
106
+ --color-border: #334155;
107
+ color-scheme: dark;
108
+ }
109
+
110
+ html[data-a11y-theme='dark'] body {
111
+ background: var(--color-surface);
112
+ color: var(--color-text);
113
+ }
114
+
115
+ html[data-a11y-theme='dark'] .bg-white {
116
+ background-color: var(--color-panel) !important;
117
+ }
118
+
119
+ html[data-a11y-theme='dark'] .bg-slate-50,
120
+ html[data-a11y-theme='dark'] .bg-gray-50 {
121
+ background-color: #0f172a !important;
122
+ }
123
+
124
+ html[data-a11y-theme='dark'] .bg-slate-100,
125
+ html[data-a11y-theme='dark'] .bg-gray-100 {
126
+ background-color: #1e293b !important;
127
+ }
128
+
129
+ html[data-a11y-theme='dark'] .bg-slate-200,
130
+ html[data-a11y-theme='dark'] .bg-gray-200 {
131
+ background-color: #334155 !important;
132
+ }
133
+
134
+ html[data-a11y-theme='dark'] .text-slate-900,
135
+ html[data-a11y-theme='dark'] .text-gray-900,
136
+ html[data-a11y-theme='dark'] .text-black {
137
+ color: #f8fafc !important;
138
+ }
139
+
140
+ html[data-a11y-theme='dark'] .text-slate-800,
141
+ html[data-a11y-theme='dark'] .text-gray-800 {
142
+ color: #e2e8f0 !important;
143
+ }
144
+
145
+ html[data-a11y-theme='dark'] .text-slate-700,
146
+ html[data-a11y-theme='dark'] .text-gray-700 {
147
+ color: #cbd5e1 !important;
148
+ }
149
+
150
+ html[data-a11y-theme='dark'] .text-slate-600,
151
+ html[data-a11y-theme='dark'] .text-gray-600 {
152
+ color: #94a3b8 !important;
153
+ }
154
+
155
+ html[data-a11y-theme='dark'] .text-slate-500,
156
+ html[data-a11y-theme='dark'] .text-gray-500 {
157
+ color: #94a3b8 !important;
158
+ }
159
+
160
+ html[data-a11y-theme='dark'] .text-slate-400,
161
+ html[data-a11y-theme='dark'] .text-gray-400 {
162
+ color: #64748b !important;
163
+ }
164
+
165
+ html[data-a11y-theme='dark'] .border-slate-100,
166
+ html[data-a11y-theme='dark'] .border-gray-100,
167
+ html[data-a11y-theme='dark'] .border-slate-200,
168
+ html[data-a11y-theme='dark'] .border-gray-200,
169
+ html[data-a11y-theme='dark'] .divide-slate-100 > :not([hidden]) ~ :not([hidden]),
170
+ html[data-a11y-theme='dark'] .divide-slate-200 > :not([hidden]) ~ :not([hidden]) {
171
+ border-color: var(--color-border) !important;
172
+ }
173
+
174
+ html[data-a11y-theme='dark'] .ring-slate-200,
175
+ html[data-a11y-theme='dark'] .ring-gray-200 {
176
+ --tw-ring-color: #334155 !important;
177
+ }
178
+
179
+ html[data-a11y-theme='dark'] .shadow-sm,
180
+ html[data-a11y-theme='dark'] .shadow,
181
+ html[data-a11y-theme='dark'] .shadow-md {
182
+ --tw-shadow-color: rgb(0 0 0 / 0.45);
183
+ }
184
+
185
+ html[data-a11y-theme='dark'] input:not([type='checkbox']):not([type='radio']),
186
+ html[data-a11y-theme='dark'] select,
187
+ html[data-a11y-theme='dark'] textarea {
188
+ background-color: #0f172a;
189
+ color: #e2e8f0;
190
+ border-color: #475569;
191
+ }
192
+
193
+ html[data-a11y-theme='dark'] input:disabled,
194
+ html[data-a11y-theme='dark'] select:disabled,
195
+ html[data-a11y-theme='dark'] textarea:disabled {
196
+ background-color: #1e293b;
197
+ color: #64748b;
198
+ }
199
+
200
+ /* --- Alto contraste --- */
201
+ html[data-a11y-contrast='high'] {
202
+ --color-surface: #ffffff;
203
+ --color-text: #000000;
204
+ --color-text-muted: #111111;
205
+ --color-panel: #ffffff;
206
+ --color-border: #000000;
207
+ --color-sidebar: #000000;
208
+ --color-brand-500: #006600;
209
+ --color-brand-600: #004d00;
210
+ --color-brand-700: #003300;
211
+ }
212
+
213
+ html[data-a11y-theme='dark'][data-a11y-contrast='high'] {
214
+ --color-surface: #000000;
215
+ --color-text: #ffffff;
216
+ --color-text-muted: #ffffff;
217
+ --color-panel: #000000;
218
+ --color-border: #ffffff;
219
+ --color-sidebar: #000000;
220
+ --color-brand-500: #66ff66;
221
+ --color-brand-600: #33ff33;
222
+ --color-brand-700: #00cc00;
223
+ }
224
+
225
+ html[data-a11y-contrast='high'] body {
226
+ background: var(--color-surface);
227
+ color: var(--color-text);
228
+ }
229
+
230
+ html[data-a11y-contrast='high'] .bg-white,
231
+ html[data-a11y-contrast='high'] .bg-slate-50,
232
+ html[data-a11y-contrast='high'] .bg-slate-100 {
233
+ background-color: var(--color-panel) !important;
234
+ }
235
+
236
+ html[data-a11y-contrast='high'] .text-slate-500,
237
+ html[data-a11y-contrast='high'] .text-slate-600,
238
+ html[data-a11y-contrast='high'] .text-gray-500,
239
+ html[data-a11y-contrast='high'] .text-gray-600 {
240
+ color: var(--color-text-muted) !important;
241
+ }
242
+
243
+ html[data-a11y-contrast='high'] .border-slate-100,
244
+ html[data-a11y-contrast='high'] .border-slate-200,
245
+ html[data-a11y-contrast='high'] .ring-slate-200 {
246
+ border-color: var(--color-border) !important;
247
+ --tw-ring-color: var(--color-border) !important;
248
+ }
249
+
250
+ html[data-a11y-contrast='high'] *:focus-visible {
251
+ outline: 3px solid #0000ff !important;
252
+ outline-offset: 2px !important;
253
+ }
254
+
255
+ html[data-a11y-theme='dark'][data-a11y-contrast='high'] *:focus-visible {
256
+ outline-color: #ffff00 !important;
257
+ }
258
+
259
+ /* --- Movimiento reducido --- */
260
+ @media (prefers-reduced-motion: reduce) {
261
+ html:not([data-a11y-motion='full']) *,
262
+ html:not([data-a11y-motion='full']) *::before,
263
+ html:not([data-a11y-motion='full']) *::after {
264
+ animation-duration: 0.01ms !important;
265
+ animation-iteration-count: 1 !important;
266
+ transition-duration: 0.01ms !important;
267
+ scroll-behavior: auto !important;
268
+ }
269
+ }
270
+
271
+ html[data-a11y-motion='reduce'] *,
272
+ html[data-a11y-motion='reduce'] *::before,
273
+ html[data-a11y-motion='reduce'] *::after {
274
+ animation-duration: 0.01ms !important;
275
+ animation-iteration-count: 1 !important;
276
+ transition-duration: 0.01ms !important;
277
+ scroll-behavior: auto !important;
278
+ }
@@ -0,0 +1,146 @@
1
+ export interface Tenant {
2
+ id: string
3
+ name: string
4
+ slug: string
5
+ database: string
6
+ status: string
7
+ has_logo?: boolean
8
+ role?: string
9
+ }
10
+
11
+ export interface User {
12
+ id: number
13
+ name: string
14
+ email: string
15
+ is_admin?: boolean
16
+ is_superuser?: boolean
17
+ is_platform_admin?: boolean
18
+ has_avatar?: boolean
19
+ requires_data_processing_consent?: boolean
20
+ data_processing_consent_accepted_at?: string | null
21
+ data_processing_consent_version?: string | null
22
+ }
23
+
24
+ export interface PublicTenant {
25
+ id: string
26
+ name: string
27
+ slug: string
28
+ has_logo?: boolean
29
+ }
30
+
31
+ export interface RegisterPayload {
32
+ name: string
33
+ email: string
34
+ tenant_mode: 'new' | 'existing'
35
+ tenant_slug: string
36
+ tenant_name?: string
37
+ tenant_domain?: string
38
+ migrate?: boolean
39
+ seed?: boolean
40
+ }
41
+
42
+ export interface PublicTenantsResponse {
43
+ tenants: PublicTenant[]
44
+ }
45
+
46
+ export interface RegisterResponse {
47
+ message: string
48
+ user: User
49
+ mail_sent?: boolean
50
+ debug_activation_url?: string
51
+ debug_mail_error?: string
52
+ }
53
+
54
+ export interface ForgotPasswordResponse {
55
+ message: string
56
+ debug_reset_url?: string
57
+ debug_mail_error?: string
58
+ debug_mail_hint?: string
59
+ }
60
+
61
+ export interface ResetPasswordPayload {
62
+ email: string
63
+ token: string
64
+ password: string
65
+ password_confirmation: string
66
+ }
67
+
68
+ export interface MessageResponse {
69
+ message: string
70
+ }
71
+
72
+ export interface CentralAdminLoginCredentials {
73
+ email: string
74
+ password: string
75
+ data_processing_consent_accepted?: boolean
76
+ }
77
+
78
+ export interface LoginCredentials {
79
+ email: string
80
+ password: string
81
+ tenant: string
82
+ data_processing_consent_accepted?: boolean
83
+ }
84
+
85
+ export interface UpdateProfilePayload {
86
+ name: string
87
+ password?: string
88
+ password_confirmation?: string
89
+ current_password?: string
90
+ }
91
+
92
+ export interface ProfileUpdateResponse {
93
+ user: User
94
+ message: string
95
+ }
96
+
97
+ export interface ProfilePhotoResponse {
98
+ user: User
99
+ message: string
100
+ avatar_size?: number
101
+ }
102
+
103
+ export interface CentralLoginResponse {
104
+ user: User
105
+ tenants: Tenant[]
106
+ tenant_selection_required?: boolean
107
+ central_access_available?: boolean
108
+ }
109
+
110
+ export interface AuthResponse {
111
+ token: string
112
+ user: User
113
+ tenants: Tenant[]
114
+ active_tenant: Tenant | null
115
+ session_mode?: 'tenant' | 'central' | null
116
+ }
117
+
118
+ export interface MeResponse {
119
+ user: User
120
+ tenants: Tenant[]
121
+ active_tenant: Tenant | null
122
+ token_tenant?: Tenant | null
123
+ session_mode?: 'tenant' | 'central' | null
124
+ }
125
+
126
+ export interface TenantLogoResponse {
127
+ tenant: Tenant
128
+ message: string
129
+ logo_size?: number
130
+ }
131
+
132
+ export interface DataProcessingConsentPolicy {
133
+ version: string
134
+ title: string
135
+ summary: string
136
+ paragraphs: string[]
137
+ accepted?: boolean
138
+ accepted_at?: string | null
139
+ accepted_version?: string | null
140
+ }
141
+
142
+ export interface DataProcessingConsentResponse {
143
+ message: string
144
+ accepted: boolean
145
+ user?: User
146
+ }
@@ -0,0 +1,41 @@
1
+ declare module 'softdinlibreriajs/src/EnumContinente.js' {
2
+ export interface EnumContinenteItem {
3
+ id: number
4
+ code: string
5
+ description: string
6
+ }
7
+
8
+ export default class EnumContinente {
9
+ static readonly ASIA: number
10
+ static readonly AFRICA: number
11
+ static readonly EUROPA: number
12
+ static readonly AMERICA_NORTE: number
13
+ static readonly AMERICA_SUR: number
14
+ static readonly OCEANIA: number
15
+
16
+ static getAll(): EnumContinenteItem[]
17
+ static getById(id: number): EnumContinenteItem | null
18
+ static getByDescription(description: string): EnumContinenteItem | null
19
+ }
20
+ }
21
+
22
+ declare module 'softdinlibreriajs/src/EnumTipoContrato.js' {
23
+ export interface EnumTipoContratoItem {
24
+ id: number
25
+ code: string
26
+ description: string
27
+ }
28
+
29
+ export default class EnumTipoContrato {
30
+ static readonly FIJO: number
31
+ static readonly INDEFINIDO: number
32
+ static readonly APRENDIZAJE: number
33
+ static readonly OBRA_LABOR: number
34
+ static readonly PRACTICAS_PASANTIAS: number
35
+ static readonly ASOCIACION: number
36
+
37
+ static getAll(): EnumTipoContratoItem[]
38
+ static getById(id: number): EnumTipoContratoItem | null
39
+ static getByDescription(description: string): EnumTipoContratoItem | null
40
+ }
41
+ }
@@ -0,0 +1,55 @@
1
+ import axios from 'axios'
2
+ import { getConnectivityMessage } from './connectivity'
3
+
4
+ export * from './connectivity'
5
+
6
+ export function extractAxiosErrorMessage(error: unknown, fallback: string): string {
7
+ const connectivityMessage = getConnectivityMessage(error)
8
+ if (connectivityMessage) {
9
+ return connectivityMessage
10
+ }
11
+
12
+ if (!axios.isAxiosError(error) || !error.response) {
13
+ return fallback
14
+ }
15
+
16
+ const status = error.response.status
17
+ const data = error.response.data
18
+
19
+ if (typeof data === 'object' && data !== null) {
20
+ const payload = data as { message?: string; errors?: Record<string, string[]> }
21
+ const firstFieldError = payload.errors
22
+ ? Object.values(payload.errors).flat()[0]
23
+ : undefined
24
+
25
+ if (firstFieldError) {
26
+ return firstFieldError
27
+ }
28
+
29
+ if (typeof payload.message === 'string' && payload.message.trim() !== '') {
30
+ return payload.message
31
+ }
32
+ }
33
+
34
+ if (status === 500) {
35
+ return 'Error interno del servidor. Revise storage/logs/laravel.log en softdin-api.'
36
+ }
37
+
38
+ if (status === 503) {
39
+ return 'El servicio no está disponible en este momento.'
40
+ }
41
+
42
+ if (status === 419) {
43
+ return 'La sesión expiró. Recargue la página e intente de nuevo.'
44
+ }
45
+
46
+ if (typeof data === 'string' && data.trim() !== '') {
47
+ if (data.includes('<!DOCTYPE html>') || data.includes('<html')) {
48
+ return `${fallback} (HTTP ${status}: el servidor respondió HTML, no JSON).`
49
+ }
50
+
51
+ return fallback
52
+ }
53
+
54
+ return `${fallback} (HTTP ${status}).`
55
+ }
@@ -0,0 +1,33 @@
1
+ import axios from 'axios'
2
+
3
+ let backendReachable = false
4
+
5
+ export function markBackendReachable(): void {
6
+ backendReachable = true
7
+ }
8
+
9
+ export function hasReachedBackend(): boolean {
10
+ return backendReachable
11
+ }
12
+
13
+ function isBrowserOffline(): boolean {
14
+ return typeof navigator !== 'undefined' && navigator.onLine === false
15
+ }
16
+
17
+ export function isConnectivityError(error: unknown): boolean {
18
+ if (axios.isAxiosError(error)) {
19
+ return !error.response
20
+ }
21
+
22
+ return isBrowserOffline()
23
+ }
24
+
25
+ export function getConnectivityMessage(error: unknown): string | null {
26
+ if (!isConnectivityError(error)) {
27
+ return null
28
+ }
29
+
30
+ return hasReachedBackend()
31
+ ? 'Se perdió la conexión con el servidor. Verifique su red e intente nuevamente.'
32
+ : 'No hay conexión con el servidor. Verifique su red e intente nuevamente.'
33
+ }
@@ -0,0 +1,6 @@
1
+ import { API_BASE_URL } from '../config/apiBase'
2
+
3
+ export function tenantLogoPublicUrl(slug: string, version = 0): string {
4
+ const query = version > 0 ? `?v=${version}` : ''
5
+ return `${API_BASE_URL}/tenants/${encodeURIComponent(slug)}/logo${query}`
6
+ }