ywana-core8 0.1.81 → 0.1.83

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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ywana-core8",
3
- "version": "0.1.81",
3
+ "version": "0.1.83",
4
4
  "description": "ywana-core8",
5
5
  "homepage": "https://ywana.github.io/workspace",
6
6
  "author": "Ernesto Roldan Garcia",
package/src/html/list.css CHANGED
@@ -10,7 +10,6 @@
10
10
  }
11
11
 
12
12
  .list>header {
13
- padding: .5rem 0 0 1.5rem;
14
13
  color: var(--text-color-light);
15
14
  text-transform: capitalize;
16
15
  font-size: .8rem;
@@ -20,10 +19,10 @@
20
19
  overflow: auto;
21
20
  list-style: none;
22
21
  margin: 0;
23
- padding: 0.5rem 0;
24
22
  cursor: pointer;
25
23
  display: flex;
26
24
  flex-direction: column;
25
+ padding: 0;
27
26
  }
28
27
 
29
28
  .list.grouped>ul {
@@ -100,17 +99,25 @@
100
99
  /* Search functionality */
101
100
  .list__search {
102
101
  padding: 1rem;
103
- border-bottom: 1px solid var(--divider-color, #e0e0e0);
104
102
  background-color: var(--background-color-light, #fafafa);
105
103
  }
106
104
 
105
+ /* Outlined variant - search with border */
106
+ .list--outlined .list__search {
107
+ border-bottom: 1px solid var(--divider-color, #e0e0e0);
108
+ }
109
+
107
110
  /* Sort functionality */
108
111
  .list__sort {
109
112
  padding: 0.5rem 1rem;
110
- border-bottom: 1px solid var(--divider-color, #e0e0e0);
111
113
  background-color: var(--background-color-light, #fafafa);
112
114
  }
113
115
 
116
+ /* Outlined variant - sort with border */
117
+ .list--outlined .list__sort {
118
+ border-bottom: 1px solid var(--divider-color, #e0e0e0);
119
+ }
120
+
114
121
  .list__sort-button {
115
122
  display: flex;
116
123
  align-items: center;
@@ -163,25 +170,28 @@
163
170
  align-items: center;
164
171
  padding: 0.75rem;
165
172
  cursor: pointer;
166
- border-bottom: solid 1px var(--divider-color, #e0e0e0);
167
173
  transition: background-color 0.2s ease, transform 0.1s ease;
168
174
  outline: none;
169
175
  position: relative;
170
176
  }
171
177
 
178
+ /* Outlined variant - items with borders */
179
+ .list--outlined .list__item {
180
+ border-bottom: solid 1px var(--divider-color, #e0e0e0);
181
+ }
182
+
172
183
  .list--dense .list__item {
173
184
  padding: 0.5rem 0.75rem;
174
185
  }
175
186
 
176
187
  .list__item:focus {
177
188
  background-color: var(--focus-color, rgba(0, 0, 0, 0.08));
178
- outline: 2px solid var(--primary-color, #2196f3);
179
189
  outline-offset: -2px;
180
190
  }
181
191
 
182
192
  .list__item--selected {
183
193
  background-color: var(--primary-color-lighter, #e3f2fd);
184
- border-left: 4px solid var(--primary-color, #2196f3);
194
+ border-left: .1rem solid var(--primary-color, #2196f3);
185
195
  }
186
196
 
187
197
  .list__item--disabled {
@@ -313,7 +323,6 @@
313
323
  align-items: center;
314
324
  padding: 0.5rem 0.75rem;
315
325
  background-color: var(--background-color-light, #fafafa);
316
- border-bottom: solid 1px var(--divider-color, #e0e0e0);
317
326
  font-weight: 600;
318
327
  color: var(--text-color-light, #666);
319
328
  cursor: pointer;
@@ -321,6 +330,11 @@
321
330
  outline: none;
322
331
  }
323
332
 
333
+ /* Outlined variant - group headers with border */
334
+ .list--outlined .list__group-header {
335
+ border-bottom: solid 1px var(--divider-color, #e0e0e0);
336
+ }
337
+
324
338
  .list__group-header:hover {
325
339
  background-color: var(--hover-color, rgba(0, 0, 0, 0.04));
326
340
  }
@@ -412,7 +426,7 @@
412
426
 
413
427
  /* High Contrast Mode */
414
428
  @media (prefers-contrast: high) {
415
- .list__item {
429
+ .list--outlined .list__item {
416
430
  border-bottom-width: 2px;
417
431
  }
418
432
 
@@ -455,10 +469,13 @@
455
469
 
456
470
  .list__item {
457
471
  break-inside: avoid;
458
- border-bottom: 1px solid black !important;
459
472
  background: white !important;
460
473
  }
461
474
 
475
+ .list--outlined .list__item {
476
+ border-bottom: 1px solid black !important;
477
+ }
478
+
462
479
  .list__item-line1,
463
480
  .list__item-line2,
464
481
  .list__item-meta {
@@ -1,8 +1,8 @@
1
1
  import React, { useState } from 'react'
2
2
  import { List } from './list'
3
3
  import { Button } from './button'
4
- import { Icon } from '
5
- import { ExampleLayout, ExampleSection, ExampleSubsection, CodeSnippet } from './ExampleLayout'./icon'
4
+ import { Icon } from './icon'
5
+ import { ExampleLayout, ExampleSection, ExampleSubsection, CodeSnippet } from './ExampleLayout'
6
6
 
7
7
  /**
8
8
  * Ejemplos del componente List mejorado manteniendo 100% compatibilidad
@@ -13,54 +13,109 @@ export const ListExamples = () => {
13
13
  const [isLoading, setIsLoading] = useState(false)
14
14
  const [showEmpty, setShowEmpty] = useState(false)
15
15
 
16
+ // Función para generar avatares con iniciales y colores aleatorios
17
+ const generateAvatar = (name) => {
18
+ const colors = [
19
+ '#2196F3', '#4CAF50', '#FF9800', '#9C27B0', '#F44336',
20
+ '#00BCD4', '#795548', '#607D8B', '#3F51B5', '#8BC34A',
21
+ '#FFC107', '#E91E63', '#009688', '#673AB7', '#FF5722'
22
+ ]
23
+
24
+ const initials = name
25
+ .split(' ')
26
+ .map(word => word.charAt(0).toUpperCase())
27
+ .slice(0, 2)
28
+ .join('')
29
+
30
+ const colorIndex = name.length % colors.length
31
+ const backgroundColor = colors[colorIndex]
32
+
33
+ return (
34
+ <div style={{
35
+ width: '40px',
36
+ height: '40px',
37
+ borderRadius: '50%',
38
+ backgroundColor,
39
+ color: 'white',
40
+ display: 'flex',
41
+ alignItems: 'center',
42
+ justifyContent: 'center',
43
+ fontSize: '14px',
44
+ fontWeight: 'bold'
45
+ }}>
46
+ {initials}
47
+ </div>
48
+ )
49
+ }
50
+
16
51
  // Datos de ejemplo
17
52
  const basicItems = [
18
- {
19
- id: 1,
20
- line1: 'John Doe',
21
- line2: 'Software Engineer',
22
- icon: 'person',
53
+ {
54
+ id: 1,
55
+ line1: 'John Doe',
56
+ line2: 'Software Engineer',
57
+ avatar: generateAvatar('John Doe'),
23
58
  meta: 'Active',
24
59
  badge: 'New'
25
60
  },
26
- {
27
- id: 2,
28
- line1: 'Jane Smith',
29
- line2: 'Product Manager',
30
- icon: 'business_center',
31
- meta: '2 days ago',
32
- avatar: 'https://via.placeholder.com/40x40/2196f3/ffffff?text=JS'
61
+ {
62
+ id: 2,
63
+ line1: 'Jane Smith',
64
+ line2: 'Product Manager',
65
+ avatar: 'https://images.unsplash.com/photo-1494790108755-2616b612b786?w=40&h=40&fit=crop&crop=face',
66
+ meta: '2 days ago'
33
67
  },
34
- {
35
- id: 3,
36
- line1: 'Mike Johnson',
37
- line2: 'UX Designer',
38
- icon: 'design_services',
39
- meta: 'Offline',
68
+ {
69
+ id: 3,
70
+ line1: 'Mike Johnson',
71
+ line2: 'UX Designer',
72
+ avatar: generateAvatar('Mike Johnson'),
40
73
  actions: (
41
74
  <div style={{ display: 'flex', gap: '0.25rem' }}>
42
75
  <Button icon="edit" size="small" />
43
76
  <Button icon="delete" size="small" />
44
77
  </div>
45
- )
78
+ ),
79
+ meta: 'Offline'
46
80
  },
47
- {
48
- id: 4,
49
- line1: 'Sarah Wilson',
50
- line2: 'Data Scientist',
51
- icon: 'analytics',
81
+ {
82
+ id: 4,
83
+ line1: 'Sarah Wilson',
84
+ line2: 'Data Scientist',
85
+ avatar: generateAvatar('Sarah Wilson'),
52
86
  meta: 'Online',
53
87
  disabled: true
54
88
  }
55
89
  ]
56
90
 
57
91
  const groupedItems = [
58
- { id: 1, line1: 'Apple iPhone 14', line2: '$999', category: 'Electronics', icon: 'phone_iphone' },
59
- { id: 2, line1: 'Samsung Galaxy S23', line2: '$899', category: 'Electronics', icon: 'smartphone' },
60
- { id: 3, line1: 'Nike Air Max', line2: '$120', category: 'Clothing', icon: 'sports_soccer' },
61
- { id: 4, line1: 'Adidas Ultraboost', line2: '$180', category: 'Clothing', icon: 'directions_run' },
62
- { id: 5, line1: 'MacBook Pro', line2: '$2499', category: 'Electronics', icon: 'laptop_mac' },
63
- { id: 6, line1: 'Levi\'s Jeans', line2: '$80', category: 'Clothing', icon: 'checkroom' }
92
+ { id: 1, line1: 'Apple iPhone 14', line2: '$999', category: 'Electronics', avatar: generateAvatar('Apple iPhone 14') },
93
+ { id: 2, line1: 'Samsung Galaxy S23', line2: '$899', category: 'Electronics', avatar: generateAvatar('Samsung Galaxy S23') },
94
+ { id: 3, line1: 'Nike Air Max', line2: '$120', category: 'Clothing', avatar: generateAvatar('Nike Air Max') },
95
+ { id: 4, line1: 'Adidas Ultraboost', line2: '$180', category: 'Clothing', avatar: generateAvatar('Adidas Ultraboost') },
96
+ { id: 5, line1: 'MacBook Pro', line2: '$2499', category: 'Electronics', avatar: generateAvatar('MacBook Pro') },
97
+ { id: 6, line1: 'Levi\'s Jeans', line2: '$80', category: 'Clothing', avatar: generateAvatar('Levi\'s Jeans') }
98
+ ]
99
+
100
+ // Items para lista densa (solo una línea) con iconos
101
+ const denseItems = [
102
+ { id: 1, line1: 'Dashboard', icon: 'dashboard', meta: 'Ctrl+D' },
103
+ { id: 2, line1: 'Analytics', icon: 'analytics', meta: 'Ctrl+A' },
104
+ { id: 3, line1: 'Settings', icon: 'settings', meta: 'Ctrl+S' },
105
+ { id: 4, line1: 'Profile', icon: 'person', meta: 'Ctrl+P' },
106
+ { id: 5, line1: 'Messages', icon: 'message', meta: 'Ctrl+M', badge: '3' },
107
+ { id: 6, line1: 'Notifications', icon: 'notifications', meta: 'Ctrl+N', badge: '12' },
108
+ { id: 7, line1: 'Help', icon: 'help', meta: 'F1' },
109
+ { id: 8, line1: 'Logout', icon: 'logout', meta: 'Ctrl+Q' }
110
+ ]
111
+
112
+ // Items con iconos para otros ejemplos
113
+ const iconItems = [
114
+ { id: 1, line1: 'Home', line2: 'Main dashboard', icon: 'home', meta: 'Active' },
115
+ { id: 2, line1: 'Documents', line2: 'File management', icon: 'folder', meta: '24 files' },
116
+ { id: 3, line1: 'Photos', line2: 'Image gallery', icon: 'photo_library', meta: '156 photos' },
117
+ { id: 4, line1: 'Music', line2: 'Audio player', icon: 'library_music', meta: '89 songs' },
118
+ { id: 5, line1: 'Downloads', line2: 'Downloaded files', icon: 'download', meta: '12 items' }
64
119
  ]
65
120
 
66
121
  const handleSelect = (id) => {
@@ -105,6 +160,11 @@ export const ListExamples = () => {
105
160
  },
106
161
 
107
162
 
163
+ {
164
+ "id": "variants",
165
+ "title": "Variantes",
166
+ "icon": "view_list"
167
+ },
108
168
  {
109
169
 
110
170
 
@@ -233,11 +293,66 @@ export const ListExamples = () => {
233
293
  </div>
234
294
  </section>
235
295
 
296
+ {/* Variantes de Lista */}
297
+ <section style={{ marginBottom: '2rem' }}>
298
+ <h3>Variantes de Lista</h3>
299
+
300
+ <div style={{ marginBottom: '1.5rem' }}>
301
+ <h4>Lista por Defecto (Sin líneas separadoras)</h4>
302
+ <div style={{
303
+ background: '#fff',
304
+ border: '1px solid #ddd',
305
+ borderRadius: '8px',
306
+ overflow: 'hidden'
307
+ }}>
308
+ <List
309
+ items={basicItems.slice(0, 3)}
310
+ onSelect={handleSelect}
311
+ selected={selectedItem}
312
+ />
313
+ </div>
314
+ <CodeSnippet
315
+ language="jsx"
316
+ code={`<List
317
+ items={items}
318
+ onSelect={handleSelect}
319
+ selected={selectedItem}
320
+ />`}
321
+ />
322
+ </div>
323
+
324
+ <div style={{ marginBottom: '1.5rem' }}>
325
+ <h4>Lista Outlined (Con líneas separadoras)</h4>
326
+ <div style={{
327
+ background: '#fff',
328
+ border: '1px solid #ddd',
329
+ borderRadius: '8px',
330
+ overflow: 'hidden'
331
+ }}>
332
+ <List
333
+ items={basicItems.slice(0, 3)}
334
+ onSelect={handleSelect}
335
+ selected={selectedItem}
336
+ outlined
337
+ />
338
+ </div>
339
+ <CodeSnippet
340
+ language="jsx"
341
+ code={`<List
342
+ items={items}
343
+ onSelect={handleSelect}
344
+ selected={selectedItem}
345
+ outlined
346
+ />`}
347
+ />
348
+ </div>
349
+ </section>
350
+
236
351
  {/* Lista básica (compatible con versión original) */}
237
352
  <section style={{ marginBottom: '2rem' }}>
238
353
  <h3>Lista Básica (100% Compatible)</h3>
239
- <div style={{
240
- background: '#fff',
354
+ <div style={{
355
+ background: '#fff',
241
356
  border: '1px solid #ddd',
242
357
  borderRadius: '8px',
243
358
  overflow: 'hidden'
@@ -248,34 +363,99 @@ export const ListExamples = () => {
248
363
  onSelect={handleSelect}
249
364
  />
250
365
  </div>
366
+ <CodeSnippet
367
+ language="jsx"
368
+ code={`const items = [
369
+ {
370
+ id: 1,
371
+ line1: 'John Doe',
372
+ line2: 'Software Engineer',
373
+ avatar: generateAvatar('John Doe'),
374
+ meta: 'Active',
375
+ badge: 'New'
376
+ },
377
+ // ... más items
378
+ ]
379
+
380
+ <List
381
+ items={items}
382
+ selected={selectedItem}
383
+ onSelect={handleSelect}
384
+ />`}
385
+ />
251
386
  </section>
252
387
 
253
388
  {/* Lista con búsqueda */}
254
389
  <section style={{ marginBottom: '2rem' }}>
255
390
  <h3>Lista con Búsqueda Integrada</h3>
256
- <div style={{
257
- background: '#fff',
258
- border: '1px solid #ddd',
259
- borderRadius: '8px',
260
- overflow: 'hidden'
261
- }}>
262
- <List
263
- items={basicItems}
264
- selected={selectedItem}
265
- onSelect={handleSelect}
266
- searchable={true}
267
- searchPlaceholder="Buscar personas..."
268
- searchBy={['line1', 'line2']}
269
- ariaLabel="Lista de personas con búsqueda"
270
- />
391
+
392
+ <div style={{ marginBottom: '1.5rem' }}>
393
+ <h4>Búsqueda Arriba (por defecto)</h4>
394
+ <div style={{
395
+ background: '#fff',
396
+ border: '1px solid #ddd',
397
+ borderRadius: '8px',
398
+ overflow: 'hidden'
399
+ }}>
400
+ <List
401
+ items={basicItems}
402
+ selected={selectedItem}
403
+ onSelect={handleSelect}
404
+ searchable={true}
405
+ searchPlaceholder="Buscar personas..."
406
+ searchBy={['line1', 'line2']}
407
+ ariaLabel="Lista de personas con búsqueda"
408
+ />
409
+ </div>
271
410
  </div>
411
+
412
+ <div style={{ marginBottom: '1.5rem' }}>
413
+ <h4>Búsqueda Abajo</h4>
414
+ <div style={{
415
+ background: '#fff',
416
+ border: '1px solid #ddd',
417
+ borderRadius: '8px',
418
+ overflow: 'hidden'
419
+ }}>
420
+ <List
421
+ items={basicItems}
422
+ selected={selectedItem}
423
+ onSelect={handleSelect}
424
+ searchable={true}
425
+ searchPosition="bottom"
426
+ searchPlaceholder="Buscar personas..."
427
+ searchBy={['line1', 'line2']}
428
+ ariaLabel="Lista de personas con búsqueda abajo"
429
+ />
430
+ </div>
431
+ </div>
432
+
433
+ <CodeSnippet
434
+ language="jsx"
435
+ code={`// Búsqueda arriba (por defecto)
436
+ <List
437
+ items={items}
438
+ searchable={true}
439
+ searchPlaceholder="Buscar personas..."
440
+ searchBy={['line1', 'line2']}
441
+ />
442
+
443
+ // Búsqueda abajo
444
+ <List
445
+ items={items}
446
+ searchable={true}
447
+ searchPosition="bottom"
448
+ searchPlaceholder="Buscar personas..."
449
+ searchBy={['line1', 'line2']}
450
+ />`}
451
+ />
272
452
  </section>
273
453
 
274
454
  {/* Lista con ordenamiento */}
275
455
  <section style={{ marginBottom: '2rem' }}>
276
456
  <h3>Lista con Ordenamiento</h3>
277
- <div style={{
278
- background: '#fff',
457
+ <div style={{
458
+ background: '#fff',
279
459
  border: '1px solid #ddd',
280
460
  borderRadius: '8px',
281
461
  overflow: 'hidden'
@@ -290,13 +470,25 @@ export const ListExamples = () => {
290
470
  ariaLabel="Lista ordenable de personas"
291
471
  />
292
472
  </div>
473
+ <CodeSnippet
474
+ language="jsx"
475
+ code={`<List
476
+ items={items}
477
+ selected={selectedItem}
478
+ onSelect={handleSelect}
479
+ sortable={true}
480
+ sortBy="line1"
481
+ onSort={handleSort}
482
+ ariaLabel="Lista ordenable de personas"
483
+ />`}
484
+ />
293
485
  </section>
294
486
 
295
487
  {/* Lista con selección múltiple */}
296
488
  <section style={{ marginBottom: '2rem' }}>
297
489
  <h3>Lista con Selección Múltiple</h3>
298
- <div style={{
299
- background: '#fff',
490
+ <div style={{
491
+ background: '#fff',
300
492
  border: '1px solid #ddd',
301
493
  borderRadius: '8px',
302
494
  overflow: 'hidden'
@@ -304,19 +496,36 @@ export const ListExamples = () => {
304
496
  <List
305
497
  items={basicItems}
306
498
  selected={selectedItems}
307
- onSelect={handleSelect}
499
+ onSelect={(id) => {
500
+ // Para multiselect, manejar la selección individual también
501
+ const newSelection = selectedItems.includes(id)
502
+ ? selectedItems.filter(item => item !== id)
503
+ : [...selectedItems, id]
504
+ setSelectedItems(newSelection)
505
+ }}
308
506
  multiSelect={true}
309
507
  onMultiSelect={handleMultiSelect}
310
508
  ariaLabel="Lista con selección múltiple"
311
509
  />
312
510
  </div>
511
+ <CodeSnippet
512
+ language="jsx"
513
+ code={`<List
514
+ items={items}
515
+ selected={selectedItems}
516
+ onSelect={handleSelect}
517
+ multiSelect={true}
518
+ onMultiSelect={handleMultiSelect}
519
+ ariaLabel="Lista con selección múltiple"
520
+ />`}
521
+ />
313
522
  </section>
314
523
 
315
524
  {/* Lista agrupada (compatible con versión original) */}
316
525
  <section style={{ marginBottom: '2rem' }}>
317
526
  <h3>Lista Agrupada (100% Compatible)</h3>
318
- <div style={{
319
- background: '#fff',
527
+ <div style={{
528
+ background: '#fff',
320
529
  border: '1px solid #ddd',
321
530
  borderRadius: '8px',
322
531
  overflow: 'hidden'
@@ -330,25 +539,92 @@ export const ListExamples = () => {
330
539
  searchPlaceholder="Buscar productos..."
331
540
  />
332
541
  </div>
542
+ <CodeSnippet
543
+ language="jsx"
544
+ code={`const groupedItems = [
545
+ { id: 1, line1: 'Apple iPhone 14', line2: '$999', category: 'Electronics' },
546
+ { id: 2, line1: 'Samsung Galaxy S23', line2: '$899', category: 'Electronics' },
547
+ { id: 3, line1: 'Nike Air Max', line2: '$120', category: 'Clothing' },
548
+ // ... más items
549
+ ]
550
+
551
+ <List
552
+ items={groupedItems}
553
+ selected={selectedItem}
554
+ onSelect={handleSelect}
555
+ groupBy="category"
556
+ searchable={true}
557
+ searchPlaceholder="Buscar productos..."
558
+ />`}
559
+ />
560
+ </section>
561
+
562
+ {/* Lista con iconos */}
563
+ <section style={{ marginBottom: '2rem' }}>
564
+ <h3>Lista con Iconos</h3>
565
+ <div style={{
566
+ background: '#fff',
567
+ border: '1px solid #ddd',
568
+ borderRadius: '8px',
569
+ overflow: 'hidden'
570
+ }}>
571
+ <List
572
+ items={iconItems}
573
+ selected={selectedItem}
574
+ onSelect={handleSelect}
575
+ ariaLabel="Lista con iconos"
576
+ />
577
+ </div>
578
+ <CodeSnippet
579
+ language="jsx"
580
+ code={`const iconItems = [
581
+ { id: 1, line1: 'Home', line2: 'Main dashboard', icon: 'home', meta: 'Active' },
582
+ { id: 2, line1: 'Documents', line2: 'File management', icon: 'folder', meta: '24 files' },
583
+ // ... más items
584
+ ]
585
+
586
+ <List
587
+ items={iconItems}
588
+ selected={selectedItem}
589
+ onSelect={handleSelect}
590
+ ariaLabel="Lista con iconos"
591
+ />`}
592
+ />
333
593
  </section>
334
594
 
335
595
  {/* Lista densa */}
336
596
  <section style={{ marginBottom: '2rem' }}>
337
- <h3>Lista Densa</h3>
338
- <div style={{
339
- background: '#fff',
597
+ <h3>Lista Densa (Una línea)</h3>
598
+ <div style={{
599
+ background: '#fff',
340
600
  border: '1px solid #ddd',
341
601
  borderRadius: '8px',
342
602
  overflow: 'hidden'
343
603
  }}>
344
604
  <List
345
- items={basicItems}
605
+ items={denseItems}
346
606
  selected={selectedItem}
347
607
  onSelect={handleSelect}
348
608
  dense={true}
349
609
  ariaLabel="Lista densa"
350
610
  />
351
611
  </div>
612
+ <CodeSnippet
613
+ language="jsx"
614
+ code={`const denseItems = [
615
+ { id: 1, line1: 'Dashboard', icon: 'dashboard', meta: 'Ctrl+D' },
616
+ { id: 2, line1: 'Analytics', icon: 'analytics', meta: 'Ctrl+A' },
617
+ // ... más items (solo line1, sin line2)
618
+ ]
619
+
620
+ <List
621
+ items={denseItems}
622
+ selected={selectedItem}
623
+ onSelect={handleSelect}
624
+ dense={true}
625
+ ariaLabel="Lista densa"
626
+ />`}
627
+ />
352
628
  </section>
353
629
 
354
630
  {/* Estados especiales */}