@jjlmoya/utils-developer 1.16.0 → 1.17.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 (34) hide show
  1. package/package.json +1 -1
  2. package/src/category/index.ts +2 -1
  3. package/src/entries.ts +4 -1
  4. package/src/index.ts +2 -0
  5. package/src/tests/locale_completeness.test.ts +2 -2
  6. package/src/tests/tool_validation.test.ts +2 -2
  7. package/src/tool/jwtDecoder/bibliography.astro +14 -0
  8. package/src/tool/jwtDecoder/bibliography.ts +12 -0
  9. package/src/tool/jwtDecoder/component.astro +209 -0
  10. package/src/tool/jwtDecoder/entry.ts +30 -0
  11. package/src/tool/jwtDecoder/i18n/de.ts +248 -0
  12. package/src/tool/jwtDecoder/i18n/en.ts +247 -0
  13. package/src/tool/jwtDecoder/i18n/es.ts +248 -0
  14. package/src/tool/jwtDecoder/i18n/fr.ts +248 -0
  15. package/src/tool/jwtDecoder/i18n/id.ts +248 -0
  16. package/src/tool/jwtDecoder/i18n/it.ts +248 -0
  17. package/src/tool/jwtDecoder/i18n/ja.ts +248 -0
  18. package/src/tool/jwtDecoder/i18n/ko.ts +248 -0
  19. package/src/tool/jwtDecoder/i18n/nl.ts +247 -0
  20. package/src/tool/jwtDecoder/i18n/pl.ts +247 -0
  21. package/src/tool/jwtDecoder/i18n/pt.ts +247 -0
  22. package/src/tool/jwtDecoder/i18n/ru.ts +248 -0
  23. package/src/tool/jwtDecoder/i18n/sv.ts +248 -0
  24. package/src/tool/jwtDecoder/i18n/tr.ts +248 -0
  25. package/src/tool/jwtDecoder/i18n/zh.ts +248 -0
  26. package/src/tool/jwtDecoder/index.ts +11 -0
  27. package/src/tool/jwtDecoder/jwt-decoder-parser-and-claims-inspector.css +358 -0
  28. package/src/tool/jwtDecoder/logic.ts +96 -0
  29. package/src/tool/jwtDecoder/seo.astro +15 -0
  30. package/src/tool/jwtDecoder/ui.ts +38 -0
  31. package/src/tool/serpPixelSimulator/bibliography.astro +11 -4
  32. package/src/tool/serpPixelSimulator/index.ts +0 -4
  33. package/src/tool/serpPixelSimulator/seo.astro +8 -5
  34. package/src/tools.ts +2 -1
@@ -0,0 +1,358 @@
1
+ .jwt-root {
2
+ width: 100%;
3
+ }
4
+
5
+ .jwt-card {
6
+ display: grid;
7
+ gap: 1rem;
8
+ width: 100%;
9
+ padding: 1rem;
10
+ overflow: hidden;
11
+ background:
12
+ linear-gradient(135deg, rgba(14, 165, 233, 0.14), transparent 34%),
13
+ linear-gradient(315deg, rgba(22, 163, 74, 0.13), transparent 36%),
14
+ #fff;
15
+ border: 1px solid rgb(219, 234, 254);
16
+ border-radius: 1.5rem;
17
+ box-shadow: 0 22px 60px rgba(15, 23, 42, 0.08);
18
+ }
19
+
20
+ .theme-dark .jwt-card {
21
+ background:
22
+ linear-gradient(135deg, rgba(14, 165, 233, 0.18), transparent 34%),
23
+ linear-gradient(315deg, rgba(34, 197, 94, 0.15), transparent 36%),
24
+ rgb(9, 12, 22);
25
+ border-color: rgb(30, 41, 59);
26
+ box-shadow: none;
27
+ }
28
+
29
+ .jwt-input-panel,
30
+ .jwt-json-panel,
31
+ .jwt-claims-panel {
32
+ background: rgba(255, 255, 255, 0.82);
33
+ border: 1px solid rgba(148, 163, 184, 0.28);
34
+ border-radius: 1rem;
35
+ backdrop-filter: blur(16px);
36
+ }
37
+
38
+ .theme-dark .jwt-input-panel,
39
+ .theme-dark .jwt-json-panel,
40
+ .theme-dark .jwt-claims-panel {
41
+ background: rgba(15, 23, 42, 0.78);
42
+ border-color: rgba(71, 85, 105, 0.62);
43
+ }
44
+
45
+ .jwt-input-panel {
46
+ display: grid;
47
+ gap: 0.85rem;
48
+ padding: 1rem;
49
+ }
50
+
51
+ .jwt-token-field {
52
+ display: grid;
53
+ gap: 0.55rem;
54
+ }
55
+
56
+ .jwt-label {
57
+ display: flex;
58
+ gap: 0.5rem;
59
+ align-items: center;
60
+ font-size: 0.9rem;
61
+ font-weight: 750;
62
+ color: rgb(15, 23, 42);
63
+ }
64
+
65
+ .theme-dark .jwt-label {
66
+ color: rgb(226, 232, 240);
67
+ }
68
+
69
+ .jwt-label-icon,
70
+ .jwt-button-icon {
71
+ width: 1rem;
72
+ height: 1rem;
73
+ }
74
+
75
+ .jwt-label-icon {
76
+ color: rgb(2, 132, 199);
77
+ }
78
+
79
+ .jwt-token-input {
80
+ box-sizing: border-box;
81
+ width: 100%;
82
+ min-height: 8.5rem;
83
+ padding: 0.9rem;
84
+ font-size: 0.82rem;
85
+ line-height: 1.55;
86
+ color: rgb(15, 23, 42);
87
+ resize: vertical;
88
+ background: rgb(248, 250, 252);
89
+ border: 1px solid rgb(203, 213, 225);
90
+ border-radius: 0.85rem;
91
+ }
92
+
93
+ .jwt-token-input:focus {
94
+ border-color: rgb(14, 165, 233);
95
+ outline: none;
96
+ box-shadow: 0 0 0 3px rgba(14, 165, 233, 0.14);
97
+ }
98
+
99
+ .theme-dark .jwt-token-input {
100
+ color: rgb(226, 232, 240);
101
+ background: rgb(15, 23, 42);
102
+ border-color: rgb(51, 65, 85);
103
+ }
104
+
105
+ .jwt-action-row {
106
+ display: flex;
107
+ flex-wrap: wrap;
108
+ gap: 0.55rem;
109
+ }
110
+
111
+ .jwt-action,
112
+ .jwt-copy {
113
+ display: inline-flex;
114
+ gap: 0.45rem;
115
+ align-items: center;
116
+ justify-content: center;
117
+ min-height: 2.3rem;
118
+ color: rgb(15, 23, 42);
119
+ cursor: pointer;
120
+ background: #fff;
121
+ border: 1px solid rgb(203, 213, 225);
122
+ border-radius: 0.7rem;
123
+ }
124
+
125
+ .jwt-action {
126
+ padding: 0 0.8rem;
127
+ font-weight: 750;
128
+ }
129
+
130
+ .jwt-action-primary {
131
+ color: #fff;
132
+ background: rgb(2, 132, 199);
133
+ border-color: rgb(2, 132, 199);
134
+ }
135
+
136
+ .theme-dark .jwt-action,
137
+ .theme-dark .jwt-copy {
138
+ color: rgb(226, 232, 240);
139
+ background: rgb(15, 23, 42);
140
+ border-color: rgb(51, 65, 85);
141
+ }
142
+
143
+ .theme-dark .jwt-action-primary {
144
+ color: #fff;
145
+ background: rgb(14, 165, 233);
146
+ border-color: rgb(14, 165, 233);
147
+ }
148
+
149
+ .jwt-status {
150
+ padding: 0.7rem 0.85rem;
151
+ margin: 0;
152
+ font-weight: 750;
153
+ border-radius: 0.8rem;
154
+ }
155
+
156
+ .jwt-status-waiting {
157
+ color: rgb(51, 65, 85);
158
+ background: rgb(241, 245, 249);
159
+ }
160
+
161
+ .jwt-status-valid {
162
+ color: rgb(20, 83, 45);
163
+ background: rgb(220, 252, 231);
164
+ }
165
+
166
+ .jwt-status-invalid,
167
+ .jwt-status-expired {
168
+ color: rgb(127, 29, 29);
169
+ background: rgb(254, 226, 226);
170
+ }
171
+
172
+ .jwt-status-unsigned {
173
+ color: rgb(120, 53, 15);
174
+ background: rgb(254, 243, 199);
175
+ }
176
+
177
+ .theme-dark .jwt-status-waiting {
178
+ color: rgb(203, 213, 225);
179
+ background: rgb(30, 41, 59);
180
+ }
181
+
182
+ .jwt-privacy {
183
+ margin: 0;
184
+ font-size: 0.82rem;
185
+ line-height: 1.45;
186
+ color: rgb(71, 85, 105);
187
+ }
188
+
189
+ .theme-dark .jwt-privacy {
190
+ color: rgb(148, 163, 184);
191
+ }
192
+
193
+ .jwt-output-grid {
194
+ display: grid;
195
+ gap: 1rem;
196
+ }
197
+
198
+ .jwt-json-panel {
199
+ min-width: 0;
200
+ overflow: hidden;
201
+ }
202
+
203
+ .jwt-panel-heading {
204
+ display: flex;
205
+ align-items: center;
206
+ justify-content: space-between;
207
+ padding: 0.85rem 0.95rem;
208
+ border-bottom: 1px solid rgba(148, 163, 184, 0.25);
209
+ }
210
+
211
+ .jwt-panel-heading h2,
212
+ .jwt-claims-panel h2 {
213
+ margin: 0;
214
+ font-size: 0.95rem;
215
+ color: rgb(15, 23, 42);
216
+ }
217
+
218
+ .theme-dark .jwt-panel-heading h2,
219
+ .theme-dark .jwt-claims-panel h2 {
220
+ color: rgb(226, 232, 240);
221
+ }
222
+
223
+ .jwt-copy {
224
+ position: relative;
225
+ width: 2.15rem;
226
+ height: 2.15rem;
227
+ }
228
+
229
+ .jwt-copy svg {
230
+ width: 1rem;
231
+ height: 1rem;
232
+ }
233
+
234
+ .jwt-copy[data-copied]::after {
235
+ position: absolute;
236
+ right: 0;
237
+ bottom: calc(100% + 0.3rem);
238
+ padding: 0.25rem 0.45rem;
239
+ font-size: 0.72rem;
240
+ color: #fff;
241
+ white-space: nowrap;
242
+ content: attr(data-copied);
243
+ background: rgb(15, 23, 42);
244
+ border-radius: 0.45rem;
245
+ }
246
+
247
+ .jwt-code {
248
+ min-height: 9rem;
249
+ max-height: 21rem;
250
+ padding: 1rem;
251
+ margin: 0;
252
+ overflow: auto;
253
+ font-size: 0.78rem;
254
+ line-height: 1.55;
255
+ color: rgb(30, 41, 59);
256
+ white-space: pre-wrap;
257
+ overflow-wrap: anywhere;
258
+ }
259
+
260
+ .jwt-signature-code {
261
+ color: rgb(7, 89, 133);
262
+ }
263
+
264
+ .theme-dark .jwt-code {
265
+ color: rgb(203, 213, 225);
266
+ }
267
+
268
+ .theme-dark .jwt-signature-code {
269
+ color: rgb(125, 211, 252);
270
+ }
271
+
272
+ .jwt-claims-panel {
273
+ padding: 1rem;
274
+ }
275
+
276
+ .jwt-claims {
277
+ display: grid;
278
+ grid-template-columns: 1fr;
279
+ gap: 0.55rem;
280
+ margin: 0.85rem 0 0;
281
+ }
282
+
283
+ .jwt-claims div {
284
+ display: grid;
285
+ grid-template-columns: minmax(7rem, 0.55fr) 1fr;
286
+ gap: 0.65rem;
287
+ padding: 0.65rem 0.75rem;
288
+ background: rgba(248, 250, 252, 0.82);
289
+ border-radius: 0.75rem;
290
+ }
291
+
292
+ .theme-dark .jwt-claims div {
293
+ background: rgba(30, 41, 59, 0.78);
294
+ }
295
+
296
+ .jwt-claims dt {
297
+ font-size: 0.76rem;
298
+ font-weight: 800;
299
+ color: rgb(71, 85, 105);
300
+ }
301
+
302
+ .jwt-claims dd {
303
+ min-width: 0;
304
+ margin: 0;
305
+ font-size: 0.82rem;
306
+ color: rgb(15, 23, 42);
307
+ overflow-wrap: anywhere;
308
+ }
309
+
310
+ .theme-dark .jwt-claims dt {
311
+ color: rgb(148, 163, 184);
312
+ }
313
+
314
+ .theme-dark .jwt-claims dd {
315
+ color: rgb(226, 232, 240);
316
+ }
317
+
318
+ @media (min-width: 860px) {
319
+ .jwt-card {
320
+ grid-template-columns: minmax(20rem, 0.92fr) minmax(0, 1.08fr);
321
+ padding: 1.25rem;
322
+ }
323
+
324
+ .jwt-input-panel,
325
+ .jwt-claims-panel {
326
+ grid-column: 1;
327
+ }
328
+
329
+ .jwt-output-grid {
330
+ grid-row: span 2;
331
+ grid-column: 2;
332
+ }
333
+ }
334
+
335
+ @media (min-width: 1180px) {
336
+ .jwt-output-grid {
337
+ grid-template-columns: repeat(2, minmax(0, 1fr));
338
+ }
339
+
340
+ .jwt-json-panel-main {
341
+ grid-column: span 2;
342
+ }
343
+ }
344
+
345
+ @media (max-width: 520px) {
346
+ .jwt-card {
347
+ padding: 0.7rem;
348
+ border-radius: 1rem;
349
+ }
350
+
351
+ .jwt-action {
352
+ flex: 1 1 100%;
353
+ }
354
+
355
+ .jwt-claims div {
356
+ grid-template-columns: 1fr;
357
+ }
358
+ }
@@ -0,0 +1,96 @@
1
+ export interface DecodedJwt {
2
+ header: Record<string, unknown>;
3
+ payload: Record<string, unknown>;
4
+ signature: string;
5
+ valid: boolean;
6
+ unsigned: boolean;
7
+ expired: boolean;
8
+ error?: 'invalid-segments' | 'invalid-json';
9
+ }
10
+
11
+ const textDecoder = new TextDecoder();
12
+
13
+ function decodeBase64Url(segment: string): string {
14
+ const normalized = segment.replace(/-/g, '+').replace(/_/g, '/');
15
+ const padded = normalized.padEnd(Math.ceil(normalized.length / 4) * 4, '=');
16
+ const binary = atob(padded);
17
+ const bytes = Uint8Array.from(binary, (char) => char.charCodeAt(0));
18
+
19
+ return textDecoder.decode(bytes);
20
+ }
21
+
22
+ function parseJsonSegment(segment: string): Record<string, unknown> {
23
+ const decoded = decodeBase64Url(segment);
24
+ const parsed = JSON.parse(decoded) as unknown;
25
+
26
+ if (!parsed || typeof parsed !== 'object' || Array.isArray(parsed)) {
27
+ throw new Error('invalid-json');
28
+ }
29
+
30
+ return parsed as Record<string, unknown>;
31
+ }
32
+
33
+ function invalidJwt(error: DecodedJwt['error']): DecodedJwt {
34
+ return {
35
+ header: {},
36
+ payload: {},
37
+ signature: '',
38
+ valid: false,
39
+ unsigned: false,
40
+ expired: false,
41
+ error,
42
+ };
43
+ }
44
+
45
+ function hasValidSegments(parts: string[]): boolean {
46
+ return parts.length === 3 && Boolean(parts[0]) && Boolean(parts[1]);
47
+ }
48
+
49
+ function buildDecodedJwt(header: Record<string, unknown>, payload: Record<string, unknown>, signature: string): DecodedJwt {
50
+ const unsigned = signature.length === 0 || header['alg'] === 'none';
51
+ const exp = typeof payload['exp'] === 'number' ? payload['exp'] : undefined;
52
+
53
+ return {
54
+ header,
55
+ payload,
56
+ signature,
57
+ valid: true,
58
+ unsigned,
59
+ expired: typeof exp === 'number' && exp * 1000 < Date.now(),
60
+ };
61
+ }
62
+
63
+ export function decodeJwt(token: string): DecodedJwt {
64
+ const parts = token.trim().split('.');
65
+
66
+ if (!hasValidSegments(parts)) return invalidJwt('invalid-segments');
67
+
68
+ try {
69
+ const header = parseJsonSegment(parts[0]);
70
+ const payload = parseJsonSegment(parts[1]);
71
+ return buildDecodedJwt(header, payload, parts[2] ?? '');
72
+ } catch {
73
+ return invalidJwt('invalid-json');
74
+ }
75
+ }
76
+
77
+ export function formatJson(value: Record<string, unknown>): string {
78
+ return JSON.stringify(value, null, 2);
79
+ }
80
+
81
+ export function formatClaimDate(value: unknown): string | null {
82
+ if (typeof value !== 'number') return null;
83
+
84
+ return new Intl.DateTimeFormat(undefined, {
85
+ dateStyle: 'medium',
86
+ timeStyle: 'medium',
87
+ }).format(new Date(value * 1000));
88
+ }
89
+
90
+ export function formatClaimValue(value: unknown): string | null {
91
+ if (value === undefined || value === null || value === '') return null;
92
+ if (Array.isArray(value)) return value.map(String).join(', ');
93
+ if (typeof value === 'object') return JSON.stringify(value);
94
+
95
+ return String(value);
96
+ }
@@ -0,0 +1,15 @@
1
+ ---
2
+ import { SEORenderer } from '@jjlmoya/utils-shared';
3
+ import { jwtDecoder } from './index';
4
+ import type { KnownLocale } from '../../types';
5
+
6
+ interface Props {
7
+ locale?: KnownLocale;
8
+ }
9
+
10
+ const { locale = 'en' } = Astro.props;
11
+ const content = await jwtDecoder.i18n[locale]?.();
12
+ if (!content) return null;
13
+ ---
14
+
15
+ {content.seo?.length > 0 && <SEORenderer content={{ locale, sections: content.seo }} />}
@@ -0,0 +1,38 @@
1
+ export interface JwtDecoderUI extends Record<string, string> {
2
+ tokenLabel: string;
3
+ tokenPlaceholder: string;
4
+ sampleButton: string;
5
+ clearButton: string;
6
+ statusWaiting: string;
7
+ statusValid: string;
8
+ statusInvalid: string;
9
+ statusExpired: string;
10
+ statusUnsigned: string;
11
+ headerTitle: string;
12
+ payloadTitle: string;
13
+ signatureTitle: string;
14
+ claimsTitle: string;
15
+ copyHeader: string;
16
+ copyPayload: string;
17
+ copySignature: string;
18
+ copyAll: string;
19
+ copiedLabel: string;
20
+ invalidTokenTitle: string;
21
+ invalidTokenBody: string;
22
+ invalidSegmentError: string;
23
+ invalidDecodeError: string;
24
+ emptyJson: string;
25
+ signaturePresent: string;
26
+ signatureMissing: string;
27
+ algorithmLabel: string;
28
+ typeLabel: string;
29
+ issuerLabel: string;
30
+ subjectLabel: string;
31
+ audienceLabel: string;
32
+ issuedAtLabel: string;
33
+ notBeforeLabel: string;
34
+ expiresAtLabel: string;
35
+ claimMissing: string;
36
+ privacyNote: string;
37
+ sampleToken: string;
38
+ }
@@ -1,7 +1,14 @@
1
1
  ---
2
- import { bibliography } from './bibliography';
2
+ import { Bibliography as SharedBibliography } from '@jjlmoya/utils-shared';
3
+ import { serpPixelSimulator } from './index';
4
+ import type { KnownLocale } from '../../types';
5
+
6
+ interface Props {
7
+ locale?: KnownLocale;
8
+ }
9
+
10
+ const { locale = 'es' } = Astro.props;
11
+ const content = await serpPixelSimulator.i18n[locale]?.();
3
12
  ---
4
13
 
5
- <ul>
6
- {bibliography.map((item) => <li><a href={item.url} rel="nofollow noopener" target="_blank">{item.name}</a></li>)}
7
- </ul>
14
+ {content && <SharedBibliography links={content.bibliography} />}
@@ -1,9 +1,5 @@
1
- import type { ToolDefinition } from '../../types';
2
- import type { ToolDefinition } from '../../types';
3
1
  import { serpPixelSimulator } from './entry';
4
-
5
2
  export * from './entry';
6
-
7
3
  export const SERP_PIXEL_SIMULATOR_TOOL: ToolDefinition = {
8
4
  entry: serpPixelSimulator,
9
5
  Component: () => import('./component.astro'),
@@ -1,12 +1,15 @@
1
1
  ---
2
- import type { SerpPixelSimulatorUI } from './ui';
2
+ import { SEORenderer } from '@jjlmoya/utils-shared';
3
+ import { serpPixelSimulator } from './index';
4
+ import type { KnownLocale } from '../../types';
3
5
 
4
6
  interface Props {
5
- ui?: Record<string, unknown>;
7
+ locale?: KnownLocale;
6
8
  }
7
9
 
8
- const { ui } = Astro.props;
9
- const t = (ui ?? {}) as SerpPixelSimulatorUI;
10
+ const { locale = 'es' } = Astro.props;
11
+ const content = await serpPixelSimulator.i18n[locale]?.();
12
+ if (!content) return null;
10
13
  ---
11
14
 
12
- <section class="tool-seo" aria-label={t.previewLabel}></section>
15
+ {content.seo?.length > 0 && <SEORenderer content={{ locale, sections: content.seo }} />}
package/src/tools.ts CHANGED
@@ -25,5 +25,6 @@ import { GENERADOR_SECURITY_TXT_TOOL } from './tool/generadorSecurityTxt/index';
25
25
  import { CALCULADORA_TIEMPO_DATOS_TOOL } from './tool/calculadoraTiempoDatos/index';
26
26
  import { CONVERSOR_EXCEL_CSV_HTML_TOOL } from './tool/conversorExcelCsvHtml/index';
27
27
  import { SERP_PIXEL_SIMULATOR_TOOL } from './tool/serpPixelSimulator/index';
28
+ import { JWT_DECODER_TOOL } from './tool/jwtDecoder/index';
28
29
 
29
- export const ALL_TOOLS: ToolDefinition[] = [JSON_FORMATTER_TOOL, SVG_TO_CSS_TOOL, ASPECT_RATIO_TOOL, PLACEHOLDER_GENERATOR_TOOL, URL_ENCODER_DECODER_TOOL, DUPLICATE_CSS_REMOVER_TOOL, CSS_TO_INLINE_CONVERTER_TOOL, CSS_SPECIFICITY_CALCULATOR_TOOL, CRON_GENERATOR_TOOL, KEYCODE_TOOL, LLM_COST_CALCULATOR_TOOL, MUSICAL_TYPOGRAPHY_TOOL, MOBILE_MOCKUP_GENERATOR_TOOL, HASH_GENERATOR_TOOL, PROMPT_LIBRARY_TOOL, COLOR_CONVERTER_TOOL, READABILITY_CALCULATOR_TOOL, SVG_SANITIZER_TOOL, UTM_GENERATOR_TOOL, URL_CLEANER_TOOL, INSPECTOR_CERTIFICADOS_SSL_TOOL, GENERADOR_SECURITY_TXT_TOOL, CALCULADORA_TIEMPO_DATOS_TOOL, CONVERSOR_EXCEL_CSV_HTML_TOOL, SERP_PIXEL_SIMULATOR_TOOL];
30
+ export const ALL_TOOLS: ToolDefinition[] = [JSON_FORMATTER_TOOL, SVG_TO_CSS_TOOL, ASPECT_RATIO_TOOL, PLACEHOLDER_GENERATOR_TOOL, URL_ENCODER_DECODER_TOOL, DUPLICATE_CSS_REMOVER_TOOL, CSS_TO_INLINE_CONVERTER_TOOL, CSS_SPECIFICITY_CALCULATOR_TOOL, CRON_GENERATOR_TOOL, KEYCODE_TOOL, LLM_COST_CALCULATOR_TOOL, MUSICAL_TYPOGRAPHY_TOOL, MOBILE_MOCKUP_GENERATOR_TOOL, HASH_GENERATOR_TOOL, PROMPT_LIBRARY_TOOL, COLOR_CONVERTER_TOOL, READABILITY_CALCULATOR_TOOL, SVG_SANITIZER_TOOL, UTM_GENERATOR_TOOL, URL_CLEANER_TOOL, INSPECTOR_CERTIFICADOS_SSL_TOOL, GENERADOR_SECURITY_TXT_TOOL, CALCULADORA_TIEMPO_DATOS_TOOL, CONVERSOR_EXCEL_CSV_HTML_TOOL, SERP_PIXEL_SIMULATOR_TOOL, JWT_DECODER_TOOL];