@jjlmoya/utils-streaming 1.1.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 (46) hide show
  1. package/package.json +60 -0
  2. package/src/category/i18n/en.ts +134 -0
  3. package/src/category/i18n/es.ts +134 -0
  4. package/src/category/i18n/fr.ts +134 -0
  5. package/src/category/index.ts +12 -0
  6. package/src/category/seo.astro +14 -0
  7. package/src/components/PreviewNavSidebar.astro +116 -0
  8. package/src/components/PreviewToolbar.astro +143 -0
  9. package/src/data.ts +11 -0
  10. package/src/env.d.ts +5 -0
  11. package/src/index.ts +24 -0
  12. package/src/layouts/PreviewLayout.astro +117 -0
  13. package/src/pages/[locale]/[slug].astro +148 -0
  14. package/src/pages/[locale].astro +251 -0
  15. package/src/pages/index.astro +4 -0
  16. package/src/tests/faq_count.test.ts +19 -0
  17. package/src/tests/locale_completeness.test.ts +42 -0
  18. package/src/tests/mocks/astro_mock.js +2 -0
  19. package/src/tests/no_h1_in_components.test.ts +48 -0
  20. package/src/tests/schemas_fulfillment.test.ts +23 -0
  21. package/src/tests/seo_length.test.ts +22 -0
  22. package/src/tests/title_quality.test.ts +55 -0
  23. package/src/tests/tool_validation.test.ts +17 -0
  24. package/src/tool/sorteo/bibliography.astro +14 -0
  25. package/src/tool/sorteo/component.astro +1319 -0
  26. package/src/tool/sorteo/engine.ts +143 -0
  27. package/src/tool/sorteo/i18n/en.ts +253 -0
  28. package/src/tool/sorteo/i18n/es.ts +253 -0
  29. package/src/tool/sorteo/i18n/fr.ts +253 -0
  30. package/src/tool/sorteo/index.ts +29 -0
  31. package/src/tool/sorteo/seo.astro +14 -0
  32. package/src/tool/sorteo/ui-manager.ts +81 -0
  33. package/src/tool/sorteo/ui.ts +30 -0
  34. package/src/tool/tebasCheck/bibliography.astro +14 -0
  35. package/src/tool/tebasCheck/component.astro +587 -0
  36. package/src/tool/tebasCheck/engine.ts +116 -0
  37. package/src/tool/tebasCheck/i18n/en.ts +254 -0
  38. package/src/tool/tebasCheck/i18n/es.ts +254 -0
  39. package/src/tool/tebasCheck/i18n/fr.ts +254 -0
  40. package/src/tool/tebasCheck/index.ts +29 -0
  41. package/src/tool/tebasCheck/ips.json +31 -0
  42. package/src/tool/tebasCheck/seo.astro +14 -0
  43. package/src/tool/tebasCheck/types.ts +45 -0
  44. package/src/tool/tebasCheck/ui-manager.ts +31 -0
  45. package/src/tools.ts +8 -0
  46. package/src/types.ts +71 -0
@@ -0,0 +1,143 @@
1
+ export class GiveawayEngine {
2
+ private rawParticipants: string[] = [];
3
+ private excludeList: string[] = [];
4
+ private allowDuplicates: boolean = false;
5
+ private weightRegex = /^(.+?)(?:\s+[\*x]\s*(\d+)|\s*\(\s*[x\*]?\s*(\d+)\s*\))$/i;
6
+
7
+ private processedParticipants: string[] = [];
8
+
9
+ constructor() {
10
+ this.process();
11
+ }
12
+
13
+ public setParticipantsFromText(text: string): void {
14
+ this.rawParticipants = text
15
+ .split(/\n/)
16
+ .map((s) => s.trim())
17
+ .filter((s) => s.length > 0);
18
+ this.process();
19
+ }
20
+
21
+ public setBlacklistFromText(text: string): void {
22
+ this.excludeList = text
23
+ .split(/[\n,]/)
24
+ .map((s) => s.trim().toLowerCase())
25
+ .filter((s) => s.length > 0);
26
+ this.process();
27
+ }
28
+
29
+ public setAllowDuplicates(allow: boolean): void {
30
+ this.allowDuplicates = allow;
31
+ this.process();
32
+ }
33
+
34
+ private parseLine(line: string): { name: string; weight: number } {
35
+ let name = line;
36
+ let weight = 1;
37
+ const match = line.match(this.weightRegex);
38
+ if (match && match[1]) {
39
+ name = match[1].trim();
40
+ const wStr = match[2] || match[3] || '1';
41
+ weight = parseInt(wStr, 10);
42
+ if (isNaN(weight) || weight < 1) {
43
+ weight = 1;
44
+ }
45
+ }
46
+ return { name, weight };
47
+ }
48
+
49
+ private process(): void {
50
+ this.processedParticipants = [];
51
+
52
+ if (this.allowDuplicates) {
53
+ this.processDuplicates();
54
+ } else {
55
+ this.processUnique();
56
+ }
57
+ }
58
+
59
+ private processDuplicates(): void {
60
+ for (const line of this.rawParticipants) {
61
+ const { name, weight } = this.parseLine(line);
62
+ if (this.excludeList.includes(name.toLowerCase())) {
63
+ continue;
64
+ }
65
+
66
+ for (let i = 0; i < weight; i++) {
67
+ this.processedParticipants.push(name);
68
+ }
69
+ }
70
+ }
71
+
72
+ private processUnique(): void {
73
+ const tempMap = new Map<string, number>();
74
+
75
+ for (const line of this.rawParticipants) {
76
+ const { name, weight } = this.parseLine(line);
77
+ if (this.excludeList.includes(name.toLowerCase())) {
78
+ continue;
79
+ }
80
+
81
+ const currentW = tempMap.get(name) || 0;
82
+ tempMap.set(name, currentW + weight);
83
+ }
84
+
85
+ tempMap.forEach((w, n) => {
86
+ for (let i = 0; i < w; i++) {
87
+ this.processedParticipants.push(n);
88
+ }
89
+ });
90
+ }
91
+
92
+ public getParticipants(): string[] {
93
+ return this.processedParticipants;
94
+ }
95
+
96
+ public getCount(): number {
97
+ return this.processedParticipants.length;
98
+ }
99
+
100
+ public pickWinners(count: number = 1): string[] {
101
+ if (this.processedParticipants.length === 0) {
102
+ return [];
103
+ }
104
+
105
+ const availableIndices = Array.from(
106
+ { length: this.processedParticipants.length },
107
+ (_, i) => i
108
+ );
109
+ const winners: string[] = [];
110
+ const numToPick = Math.min(count, availableIndices.length);
111
+
112
+ for (let i = 0; i < numToPick; i++) {
113
+ const randomIndex = this.getSecureRandomIndex(availableIndices.length);
114
+ const winningIndex = availableIndices[randomIndex];
115
+ if (winningIndex !== undefined) {
116
+ const winner = this.processedParticipants[winningIndex];
117
+ if (winner) winners.push(winner);
118
+ availableIndices.splice(randomIndex, 1);
119
+ }
120
+ }
121
+
122
+ return winners;
123
+ }
124
+
125
+ private getSecureRandomIndex(limit: number): number {
126
+ if (typeof crypto !== 'undefined' && crypto.getRandomValues) {
127
+ const array = new Uint32Array(1);
128
+ crypto.getRandomValues(array);
129
+ const val = array[0];
130
+ if (val !== undefined) return val % limit;
131
+ }
132
+ return Math.floor(Math.random() * limit);
133
+ }
134
+
135
+ public removeParticipant(name: string): void {
136
+ this.rawParticipants = this.rawParticipants.filter((line) => {
137
+ const { name: extractedName } = this.parseLine(line);
138
+ return extractedName.toLowerCase() !== name.toLowerCase();
139
+ });
140
+
141
+ this.process();
142
+ }
143
+ }
@@ -0,0 +1,253 @@
1
+ import type { WithContext, FAQPage, HowTo, SoftwareApplication } from 'schema-dts';
2
+ import type { ToolLocaleContent } from '../../../types';
3
+ import type { SorteoUI } from '../ui';
4
+
5
+ const slug = 'giveaway';
6
+ const title = 'Random Name Picker for Streaming';
7
+ const description =
8
+ 'Choose a winner at random from a list of names. Free, fast, and visual giveaway tool for Twitch, YouTube, and events.';
9
+
10
+ const faqData = [
11
+ {
12
+ question: 'Is this giveaway truly random?',
13
+ answer:
14
+ 'Yes, we use the browser\'s cryptographic randomness algorithm (Web Crypto API) to ensure that each participant has exactly the same probability of winning, without bias or manipulation.',
15
+ },
16
+ {
17
+ question: 'Can I use this giveaway on Twitch or YouTube?',
18
+ answer:
19
+ 'Absolutely. As a web tool, you can capture the window with OBS or share your screen directly. The clean design and animations are designed so that the audience sees the process with total transparency.',
20
+ },
21
+ {
22
+ question: 'How do I prevent someone from participating twice?',
23
+ answer:
24
+ 'The tool has an automatic "duplicate cleaning" function that detects identical names or those with small spacing variations to ensure each real person counts only once.',
25
+ },
26
+ {
27
+ question: 'Can I draw several winners at once?',
28
+ answer:
29
+ 'Yes, you can configure the number of winners desired before clicking the button. The tool will list the lucky ones clearly so you can mention them in your live stream.',
30
+ },
31
+ {
32
+ question: 'How many names can I add to the list?',
33
+ answer:
34
+ 'There is no strict limit imposed by the tool. We have optimized the engine to handle lists of thousands of participants without performance issues, making it ideal even for massive giveaways.',
35
+ },
36
+ {
37
+ question: 'Are my data or the list of participants saved?',
38
+ answer:
39
+ 'No, never. Your privacy comes first. The entire giveaway process runs locally in your web browser. The names you enter are never sent to our servers or stored in any database.',
40
+ },
41
+ ];
42
+
43
+ const howToData = [
44
+ {
45
+ name: 'Prepare the list of participants',
46
+ text: 'Copy the list of names from your chat, Excel, or social network and paste it into the text box.',
47
+ },
48
+ {
49
+ name: 'Configure giveaway options',
50
+ text: 'Choose how many winners you need and if you want to filter duplicates or empty names.',
51
+ },
52
+ {
53
+ name: 'Launch the innocent hand',
54
+ text: 'Click the giveaway button. A visual animation will maintain the tension before revealing the winner.',
55
+ },
56
+ {
57
+ name: 'Announce results',
58
+ text: 'Copy the names of the winners to share them on your social networks or streaming chat.',
59
+ },
60
+ ];
61
+
62
+ const faqSchema: WithContext<FAQPage> = {
63
+ '@context': 'https://schema.org',
64
+ '@type': 'FAQPage',
65
+ mainEntity: faqData.map((item) => ({
66
+ '@type': 'Question',
67
+ name: item.question,
68
+ acceptedAnswer: { '@type': 'Answer', text: item.answer },
69
+ })),
70
+ };
71
+
72
+ const howToSchema: WithContext<HowTo> = {
73
+ '@context': 'https://schema.org',
74
+ '@type': 'HowTo',
75
+ name: title,
76
+ description,
77
+ step: howToData.map((step, i) => ({
78
+ '@type': 'HowToStep',
79
+ position: i + 1,
80
+ name: step.name,
81
+ text: step.text,
82
+ })),
83
+ };
84
+
85
+ const appSchema: WithContext<SoftwareApplication> = {
86
+ '@context': 'https://schema.org',
87
+ '@type': 'SoftwareApplication',
88
+ name: title,
89
+ description,
90
+ applicationCategory: 'UtilityApplication',
91
+ operatingSystem: 'All',
92
+ offers: { '@type': 'Offer', price: '0', priceCurrency: 'USD' },
93
+ inLanguage: 'en',
94
+ };
95
+
96
+ export const content: ToolLocaleContent<SorteoUI> = {
97
+ slug,
98
+ title,
99
+ description,
100
+ faqTitle: 'Frequently Asked Questions',
101
+ faq: faqData,
102
+ bibliographyTitle: 'Technical References',
103
+ bibliography: [
104
+ {
105
+ name: 'Web Crypto API: getRandomValues()',
106
+ url: 'https://developer.mozilla.org/en-US/docs/Web/API/Crypto/getRandomValues',
107
+ },
108
+ {
109
+ name: 'Fisher-Yates Shuffle Algorithm',
110
+ url: 'https://en.wikipedia.org/wiki/Fisher%E2%80%93Yates_shuffle',
111
+ },
112
+ ],
113
+ howTo: howToData,
114
+ schemas: [faqSchema as any, howToSchema as any, appSchema as any],
115
+ seo: [
116
+ {
117
+ type: 'title',
118
+ text: 'Random Name Picker and Participants List Online',
119
+ level: 2,
120
+ },
121
+ {
122
+ type: 'paragraph',
123
+ html: 'Wondering how to do a random giveaway online quickly, safely, and totally transparently? Our free <strong>Name Picker</strong> tool is the ultimate solution to choose a winner at random in seconds. Designed to be simple, visual, and effective, it is perfect for any scenario where you need a digital "innocent hand".',
124
+ },
125
+ {
126
+ type: 'paragraph',
127
+ html: 'Whether you are managing a contest on social networks, a massive giveaway on your streaming channel, or simply deciding who takes out the trash today, our random selector guarantees total impartiality thanks to modern cryptographic algorithms. <strong>No manipulation, no bias, just pure randomness.</strong>'
128
+ },
129
+ {
130
+ type: 'title',
131
+ text: 'Use Cases',
132
+ level: 3,
133
+ },
134
+ {
135
+ type: 'comparative',
136
+ columns: 2,
137
+ items: [
138
+ {
139
+ title: 'Social Media Giveaways',
140
+ description: 'Ideal for Instagram, Twitter (X), or Facebook contests. Simply copy names from comments and paste them to choose a fair winner. The tool automatically removes duplicates.',
141
+ },
142
+ {
143
+ title: 'Twitch / YouTube Streaming',
144
+ description: 'Thanks to our Studio Mode with smooth animations and integrated sounds, you can share your screen directly in OBS and offer an exciting visual show to your audience while choosing winners live.',
145
+ },
146
+ {
147
+ title: 'Class and Team Dynamics',
148
+ description: 'Teachers and team leaders can use it to form random groups, choose who presents first, or assign tasks at random with total transparency and no favoritism.',
149
+ },
150
+ {
151
+ title: 'Secret Santa and Events',
152
+ description: 'Simplify the organization of family events, office giveaways, or Secret Santa by choosing names at random instantly without the need for papers or complicated logistics.',
153
+ },
154
+ ],
155
+ },
156
+ {
157
+ type: 'title',
158
+ text: 'Why is our tool different?',
159
+ level: 3,
160
+ },
161
+ {
162
+ type: 'list',
163
+ items: [
164
+ '<strong>Real Cryptography:</strong> We use the browser\'s Web Crypto API (W3C standard) instead of weak pseudo-random generators. Every giveaway is mathematically impartial.',
165
+ '<strong>No Storage:</strong> Your data never leaves your browser. We don\'t sell lists, we don\'t profile you, we don\'t store anything. Pure local processing.',
166
+ '<strong>Visual Design:</strong> Cinema mode and animations make every giveaway a memorable event. Perfect for OBS capture or live streaming.',
167
+ '<strong>Duplicate Handling:</strong> Automatically detects repeated names or variants (extra spaces, capitalization, etc.) to ensure each real person counts only once.',
168
+ ],
169
+ },
170
+ {
171
+ type: 'title',
172
+ text: 'How to use the giveaway step by step',
173
+ level: 3,
174
+ },
175
+ {
176
+ type: 'list',
177
+ items: [
178
+ '<strong>Step 1 - Enter participants:</strong> Paste your list of names into the main text box. The tool automatically detects each line break as a different participant. Do you have duplicates? No problem, the tool removes them.',
179
+ '<strong>Step 2 - Customize:</strong> In the settings tab you can enable the countdown to generate tension, the confetti effect to celebrate, or enable the "blacklist" to exclude certain names.',
180
+ '<strong>Step 3 - Draw!</strong> Click the main button and our engine will generate a cryptographically secure random selection. The winners will be displayed clearly and memorably.',
181
+ ],
182
+ },
183
+ {
184
+ type: 'title',
185
+ text: 'Weighted Entries: Give Advantage to Some Participants',
186
+ level: 3,
187
+ },
188
+ {
189
+ type: 'paragraph',
190
+ html: 'Want to reward your most loyal subscribers or give more opportunities to certain participants? Our <strong>Weighted Entries</strong> system is unique and allows you to assign a "weight" or multiplier to any name without having to write it multiple times.',
191
+ },
192
+ {
193
+ type: 'tip',
194
+ title: 'How to assign weights to names',
195
+ html: '<p>Use an asterisk (*) or an "x" followed by the number of participations. Examples:</p><ul><li><strong>"John * 5"</strong> - John competes as if he were 5 people</li><li><strong>"Maria x 10"</strong> - Maria has 10 times more chances</li><li><strong>"Peter"</strong> - No symbol = 1 regular entry</li></ul><p>This is perfect for giveaways where you want to give VIP subscribers or special users an advantage.</p>',
196
+ },
197
+ {
198
+ type: 'title',
199
+ text: 'Total Privacy and Security',
200
+ level: 3,
201
+ },
202
+ {
203
+ type: 'paragraph',
204
+ html: 'Unlike other online tools, <strong>we do not store your data</strong>. All processing of names and execution of the giveaway occurs locally in your own browser. Your participant lists never travel over the network or are saved on any external server.',
205
+ },
206
+ {
207
+ type: 'paragraph',
208
+ html: '<strong>What does this mean?</strong> Your participant list is yours and yours alone. Closing the tab, it disappears. No tracking cookies, no user profiles, no central database. Maximum privacy guaranteed for you and those who participate in your giveaways.',
209
+ },
210
+ {
211
+ type: 'title',
212
+ text: 'Mathematical Transparency',
213
+ level: 3,
214
+ },
215
+ {
216
+ type: 'paragraph',
217
+ html: 'Some might wonder: "What if you manipulate the results?" The answer is simple: <strong>we can\'t</strong>. The giveaway code is deterministic and cryptographic. No hidden variables, no "fingers on the stage".',
218
+ },
219
+ {
220
+ type: 'paragraph',
221
+ html: 'Each winner is the direct result of the Fisher-Yates Shuffle algorithm applied to your exact list, using real cryptographic entropy. If you want to audit the process, the code is available on GitHub for public inspection.',
222
+ },
223
+ ],
224
+ ui: {
225
+ title: 'Random Giveaway',
226
+ totalParticipants: 'Total Unique Participants',
227
+ ready: 'READY',
228
+ participants: 'Participants',
229
+ settings: 'Settings',
230
+ importFile: 'Import File',
231
+ clearAll: 'Clear all',
232
+ placeholder: 'Type or paste names here...\nJohn Doe\nMaria Garcia\n@twitch_user',
233
+ onePerLine: '1 participant per line',
234
+ lines: 'lines',
235
+ filters: 'Filters',
236
+ allowDuplicates: 'Allow Duplicates',
237
+ winnerCount: 'Number of Winners',
238
+ autoRemove: 'Auto-Remove Winner',
239
+ blacklist: 'Blacklist (Exclude)',
240
+ blacklistPlaceholder: 'Prohibited names (one per line)...',
241
+ blacklistInfo: 'These users will not enter the giveaway.',
242
+ sceneEffects: 'Scene Effects',
243
+ countdown: 'Countdown (3s)',
244
+ confetti: 'Victory Confetti',
245
+ zenMode: 'Zen Mode (Hide Panel)',
246
+ waitingParticipants: 'Waiting for participants...',
247
+ winner: 'WINNER',
248
+ reroll: 'Reroll Giveaway',
249
+ history: 'History for this session',
250
+ noWinnersYet: 'No winners yet...',
251
+ startGiveaway: 'Start Giveaway',
252
+ },
253
+ };
@@ -0,0 +1,253 @@
1
+ import type { WithContext, FAQPage, HowTo, SoftwareApplication } from 'schema-dts';
2
+ import type { ToolLocaleContent } from '../../../types';
3
+ import type { SorteoUI } from '../ui';
4
+
5
+ const slug = 'sorteo';
6
+ const title = 'Sorteo Aleatorio de Nombres para Streaming';
7
+ const description =
8
+ 'Elije un ganador al azar de una lista de nombres. Herramienta de sorteos gratuita, rápida y visual para Twitch, YouTube y eventos.';
9
+
10
+ const faqData = [
11
+ {
12
+ question: '¿Es realmente aleatorio este sorteo?',
13
+ answer:
14
+ 'Sí, utilizamos el algoritmo de aleatoriedad criptográfica del navegador (Web Crypto API) para asegurar que cada participante tenga exactamente las mismas probabilidades de ganar, sin sesgos ni manipulaciones.',
15
+ },
16
+ {
17
+ question: '¿Puedo usar este sorteo en Twitch o YouTube?',
18
+ answer:
19
+ 'Totalmente. Al ser una herramienta web, puedes capturar la ventana con OBS o compartir pantalla directamente. El diseño limpio y las animaciones están pensados para que el público vea el proceso con total transparencia.',
20
+ },
21
+ {
22
+ question: '¿Cómo evito que alguien participe dos veces?',
23
+ answer:
24
+ 'La herramienta tiene una función de "limpieza de duplicados" automática que detecta nombres idénticos o con pequeñas variaciones de espacios para asegurar que cada persona real cuente solo una vez.',
25
+ },
26
+ {
27
+ question: '¿Puedo sacar varios ganadores a la vez?',
28
+ answer:
29
+ 'Sí, puedes configurar el número de ganadores deseados antes de pulsar el botón. La herramienta listará a los afortunados de forma clara para que puedas mencionarlos en tu directo.',
30
+ },
31
+ {
32
+ question: '¿Cuántos nombres puedo añadir a la lista?',
33
+ answer:
34
+ 'No hay un límite estricto impuesto por la herramienta. Hemos optimizado el motor para manejar listas de miles de participantes sin problemas de rendimiento, lo que lo hace ideal incluso para sorteos masivos.',
35
+ },
36
+ {
37
+ question: '¿Se guardan mis datos o la lista de participantes?',
38
+ answer:
39
+ 'No, nunca. Tu privacidad es lo primero. Todo el proceso del sorteo se ejecuta localmente en tu navegador web. Los nombres que introduces nunca se envían a nuestros servidores ni se almacenan en ninguna base de datos.',
40
+ },
41
+ ];
42
+
43
+ const howToData = [
44
+ {
45
+ name: 'Preparar la lista de participantes',
46
+ text: 'Copia la lista de nombres desde tu chat, Excel o red social y pégala en el cuadro de texto.',
47
+ },
48
+ {
49
+ name: 'Configurar opciones de sorteo',
50
+ text: 'Elige cuántos ganadores necesitas y si quieres filtrar duplicados o nombres vacíos.',
51
+ },
52
+ {
53
+ name: 'Lanzar la mano inocente',
54
+ text: 'Haz clic en el botón de sorteo. Una animación visual mantendrá la tensión antes de revelar al ganador.',
55
+ },
56
+ {
57
+ name: 'Anunciar resultados',
58
+ text: 'Copia los nombres de los ganadores para compartirlos en tus redes o chat de streaming.',
59
+ },
60
+ ];
61
+
62
+ const faqSchema: WithContext<FAQPage> = {
63
+ '@context': 'https://schema.org',
64
+ '@type': 'FAQPage',
65
+ mainEntity: faqData.map((item) => ({
66
+ '@type': 'Question',
67
+ name: item.question,
68
+ acceptedAnswer: { '@type': 'Answer', text: item.answer },
69
+ })),
70
+ };
71
+
72
+ const howToSchema: WithContext<HowTo> = {
73
+ '@context': 'https://schema.org',
74
+ '@type': 'HowTo',
75
+ name: title,
76
+ description,
77
+ step: howToData.map((step, i) => ({
78
+ '@type': 'HowToStep',
79
+ position: i + 1,
80
+ name: step.name,
81
+ text: step.text,
82
+ })),
83
+ };
84
+
85
+ const appSchema: WithContext<SoftwareApplication> = {
86
+ '@context': 'https://schema.org',
87
+ '@type': 'SoftwareApplication',
88
+ name: title,
89
+ description,
90
+ applicationCategory: 'UtilityApplication',
91
+ operatingSystem: 'All',
92
+ offers: { '@type': 'Offer', price: '0', priceCurrency: 'EUR' },
93
+ inLanguage: 'es',
94
+ };
95
+
96
+ export const content: ToolLocaleContent<SorteoUI> = {
97
+ slug,
98
+ title,
99
+ description,
100
+ faqTitle: 'Preguntas Frecuentes',
101
+ faq: faqData,
102
+ bibliographyTitle: 'Referencias Técnicas',
103
+ bibliography: [
104
+ {
105
+ name: 'Web Crypto API: getRandomValues()',
106
+ url: 'https://developer.mozilla.org/en-US/docs/Web/API/Crypto/getRandomValues',
107
+ },
108
+ {
109
+ name: 'Fisher-Yates Shuffle Algorithm',
110
+ url: 'https://en.wikipedia.org/wiki/Fisher%E2%80%93Yates_shuffle',
111
+ },
112
+ ],
113
+ howTo: howToData,
114
+ schemas: [faqSchema as any, howToSchema as any, appSchema as any],
115
+ seo: [
116
+ {
117
+ type: 'title',
118
+ text: 'Sorteo Aleatorio de Nombres y Lista de Participantes Online',
119
+ level: 2,
120
+ },
121
+ {
122
+ type: 'paragraph',
123
+ html: '¿Te preguntas cómo hacer un sorteo aleatorio online de forma rápida, segura y totalmente transparente? Nuestra herramienta gratuita de <strong>Sorteo de Nombres</strong> es la solución definitiva para elegir un ganador al azar en cuestión de segundos. Diseñada para ser simple, visual y efectiva, es perfecta para cualquier escenario donde necesites una "mano inocente" digital.',
124
+ },
125
+ {
126
+ type: 'paragraph',
127
+ html: 'Ya sea que estés gestionando un concurso en redes sociales, un sorteo masivo en tu canal de streaming o simplemente decidiendo quién saca la basura hoy, nuestro selector aleatorio garantiza una imparcialidad total gracias a algoritmos criptográficos modernos. <strong>Sin manipulaciones, sin sesgo, solo pura aleatoriedad.</strong>'
128
+ },
129
+ {
130
+ type: 'title',
131
+ text: 'Casos de Uso',
132
+ level: 3,
133
+ },
134
+ {
135
+ type: 'comparative',
136
+ columns: 2,
137
+ items: [
138
+ {
139
+ title: 'Sorteos en Redes Sociales',
140
+ description: 'Ideal para concursos de Instagram, Twitter (X) o Facebook. Simplemente copia los nombres de los comentarios y pégalos para elegir un ganador justo. La herramienta limpia duplicados automáticamente.',
141
+ },
142
+ {
143
+ title: 'Streaming en Twitch / YouTube',
144
+ description: 'Gracias a nuestro Modo Estudio con animaciones fluidas y sonidos integrados, puedes compartir tu pantalla directamente en OBS y ofrecer un espectáculo visual emocionante a tu audiencia mientras eliges ganadores en directo.',
145
+ },
146
+ {
147
+ title: 'Dinámicas de Clase y Equipos',
148
+ description: 'Profesores y líderes de equipo pueden usarla para formar grupos aleatorios, elegir quién expone primero o asignar tareas al azar con total transparencia y sin favoritismos.',
149
+ },
150
+ {
151
+ title: 'Amigo Invisible y Eventos',
152
+ description: 'Simplifica la organización de eventos familiares, sorteos de oficina o el Amigo Invisible eligiendo nombres al azar de forma instantánea sin necesidad de papeles ni logística complicada.',
153
+ },
154
+ ],
155
+ },
156
+ {
157
+ type: 'title',
158
+ text: '¿Por qué nuestra herramienta es diferente?',
159
+ level: 3,
160
+ },
161
+ {
162
+ type: 'list',
163
+ items: [
164
+ '<strong>Criptografía Real:</strong> Usamos la Web Crypto API del navegador (estándar W3C) en lugar de generadores pseudoaleatorios débiles. Cada sorteo es matemáticamente imparcial.',
165
+ '<strong>Sin Almacenamiento:</strong> Tus datos nunca abandonan tu navegador. No vendemos listas, no hacemos perfilado, no almacenamos nada. Puro procesamiento local.',
166
+ '<strong>Diseño Visual:</strong> El modo cine y las animaciones hacen que cada sorteo sea un evento memorable. Perfecto para captura en OBS o transmisión directa.',
167
+ '<strong>Manejo de Duplicados:</strong> Detecta automáticamente nombres repetidos o variantes (espacios extras, mayúsculas, etc.) para asegurar que cada persona real cuente una sola vez.',
168
+ ],
169
+ },
170
+ {
171
+ type: 'title',
172
+ text: 'Cómo usar el sorteo paso a paso',
173
+ level: 3,
174
+ },
175
+ {
176
+ type: 'list',
177
+ items: [
178
+ '<strong>Paso 1 - Introduce los participantes:</strong> Pega tu lista de nombres en el cuadro de texto principal. La herramienta detecta automáticamente cada salto de línea como un participante diferente. ¿Tienes duplicados? No hay problema, la herramienta los limpia.',
179
+ '<strong>Paso 2 - Personaliza:</strong> En la pestaña de configuración puedes activar la cuenta atrás para generar tensión, el efecto de confeti para celebrar, o activar la "lista negra" para excluir ciertos nombres.',
180
+ '<strong>Paso 3 - ¡Sortear!</strong> Haz clic en el botón principal y nuestro motor generará una selección aleatoria criptográficamente segura. Los ganadores se mostrarán de forma clara y memorable.',
181
+ ],
182
+ },
183
+ {
184
+ type: 'title',
185
+ text: 'Entradas Ponderadas: Dar Ventaja a Algunos Participantes',
186
+ level: 3,
187
+ },
188
+ {
189
+ type: 'paragraph',
190
+ html: '¿Quieres premiar a tus suscriptores más fieles o dar más oportunidades a ciertos participantes? Nuestro sistema de <strong>Entradas Ponderadas</strong> es único y te permite asignar un "peso" o multiplicador a cualquier nombre sin tener que escribirlo varias veces.',
191
+ },
192
+ {
193
+ type: 'tip',
194
+ title: 'Cómo asignar pesos a los nombres',
195
+ html: '<p>Utiliza un asterisco (*) o una "x" seguida del número de participaciones. Ejemplos:</p><ul><li><strong>"Juan * 5"</strong> - Juan compite como si fuera 5 personas</li><li><strong>"María x 10"</strong> - María tiene 10 veces más probabilidades</li><li><strong>"Pedro"</strong> - Sin símbolo = 1 entrada normal</li></ul><p>Esto es perfecto para sorteos donde quieres dar ventaja a suscriptores VIP o usuarios especiales.</p>',
196
+ },
197
+ {
198
+ type: 'title',
199
+ text: 'Privacidad y Seguridad Total',
200
+ level: 3,
201
+ },
202
+ {
203
+ type: 'paragraph',
204
+ html: 'A diferencia de otras herramientas online, <strong>no almacenamos tus datos</strong>. Todo el procesamiento de los nombres y la ejecución del sorteo ocurre localmente en tu propio navegador. Tus listas de participantes nunca viajan por la red ni se guardan en ningún servidor externo.',
205
+ },
206
+ {
207
+ type: 'paragraph',
208
+ html: '<strong>¿Qué significa esto?</strong> Tu lista de participantes es tuya y solo tuya. Cerrando la pestaña, desaparece. No hay cookies de rastreo, no hay perfiles de usuario, no hay base de datos central. Máxima privacidad garantizada para ti y para quienes participan en tus sorteos.',
209
+ },
210
+ {
211
+ type: 'title',
212
+ text: 'Transparencia Matemática',
213
+ level: 3,
214
+ },
215
+ {
216
+ type: 'paragraph',
217
+ html: 'Algunos se preguntarán: "¿Y si tú manipulas los resultados?" La respuesta es simple: <strong>no podemos</strong>. El código del sorteo es determinista y criptográfico. No hay variables ocultas, no hay "dedos en la escena".',
218
+ },
219
+ {
220
+ type: 'paragraph',
221
+ html: 'Cada ganador es el resultado directo del algoritmo Fisher-Yates Shuffle aplicado a tu lista exacta, usando entropía criptográfica real. Si quieres auditar el proceso, el código está disponible en GitHub para inspección pública.',
222
+ },
223
+ ],
224
+ ui: {
225
+ title: 'Sorteo Aleatorio',
226
+ totalParticipants: 'Total Participantes Únicos',
227
+ ready: 'LISTO',
228
+ participants: 'Participantes',
229
+ settings: 'Configuración',
230
+ importFile: 'Importar Archivo',
231
+ clearAll: 'Borrar todo',
232
+ placeholder: 'Escribe o pega los nombres aquí...\nJuan Pérez\nMaria García\n@usuario_twitch',
233
+ onePerLine: '1 participante por línea',
234
+ lines: 'líneas',
235
+ filters: 'Filtros',
236
+ allowDuplicates: 'Permitir Duplicados',
237
+ winnerCount: 'Número de Ganadores',
238
+ autoRemove: 'Auto-Eliminar Ganador',
239
+ blacklist: 'Lista Negra (Excluir)',
240
+ blacklistPlaceholder: 'Nombres prohibidos (uno por línea)...',
241
+ blacklistInfo: 'Estos usuarios no entrarán en el sorteo.',
242
+ sceneEffects: 'Efectos de Escena',
243
+ countdown: 'Cuenta Atrás (3s)',
244
+ confetti: 'Confeti de Victoria',
245
+ zenMode: 'Modo Cine (Ocultar Panel)',
246
+ waitingParticipants: 'Esperando participantes...',
247
+ winner: 'GANADOR',
248
+ reroll: 'Repetir Sorteo',
249
+ history: 'Historial de esta sesión',
250
+ noWinnersYet: 'Aún no hay ganadores...',
251
+ startGiveaway: 'Iniciar Sorteo',
252
+ },
253
+ };