@jjlmoya/utils-babies 1.4.0 → 1.6.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.
@@ -23,6 +23,26 @@ export interface VaccinationCalendarUI {
23
23
  labelShare: string;
24
24
  faqTitle: string;
25
25
  bibliographyTitle: string;
26
+ labelMonth: string;
27
+ labelMonths: string;
28
+ labelYear: string;
29
+ labelYears: string;
30
+ labelDay: string;
31
+ labelDays: string;
32
+ labelAnd: string;
33
+ labelVaccination: string;
34
+ labelAppointment: string;
35
+ vac_hexavalente: string;
36
+ vac_neumococo: string;
37
+ vac_meningococo_b: string;
38
+ vac_rotavirus: string;
39
+ vac_meningococo_acwy: string;
40
+ vac_triple_virica: string;
41
+ vac_varicela: string;
42
+ vac_gripe: string;
43
+ vac_vph: string;
44
+ vac_tdpa: string;
45
+ vac_polio_booster: string;
26
46
  }
27
47
 
28
48
  export type VaccinationCalendarLocaleContent = ToolLocaleContent<VaccinationCalendarUI>;
@@ -5,12 +5,13 @@ export interface DoseGroup {
5
5
  vaccines: string[];
6
6
  }
7
7
 
8
- export function buildDoseGroups(): DoseGroup[] {
8
+ export function buildDoseGroups(ui: Record<string, string>): DoseGroup[] {
9
9
  const groups: Record<number, string[]> = {};
10
10
  VACCINES.forEach((vac) => {
11
+ const name = ui[`vac_${vac.id}`]!;
11
12
  vac.doses.forEach((d) => {
12
13
  if (!groups[d]) groups[d] = [];
13
- groups[d].push(vac.name);
14
+ groups[d].push(name);
14
15
  });
15
16
  });
16
17
  return Object.entries(groups)
@@ -18,16 +19,29 @@ export function buildDoseGroups(): DoseGroup[] {
18
19
  .sort((a, b) => a.months - b.months);
19
20
  }
20
21
 
21
- export function getAgeLabel(months: number): string {
22
- if (months < 12) return `${months} meses`;
23
- if (months === 12) return '12 meses (1 año)';
22
+ export function getAgeLabel(months: number, ui: Record<string, string>): string {
23
+ const labelMonth = ui['labelMonth'];
24
+ const labelMonths = ui['labelMonths'];
25
+ const labelYear = ui['labelYear'];
26
+ const labelYears = ui['labelYears'];
27
+
28
+ if (months < 12) return `${months} ${months === 1 ? labelMonth : labelMonths}`;
29
+ if (months === 12) return `12 ${labelMonths} (1 ${labelYear})`;
24
30
  const yrs = Math.floor(months / 12);
25
31
  const rem = months % 12;
26
- if (rem === 0) return `${yrs} ${yrs === 1 ? 'año' : 'años'}`;
27
- return `${months} meses`;
32
+ if (rem === 0) return `${yrs} ${yrs === 1 ? labelYear : labelYears}`;
33
+ return `${months} ${labelMonths}`;
28
34
  }
29
35
 
30
- export function calculateAge(birthDate: Date, today: Date): string {
36
+ export function calculateAge(birthDate: Date, today: Date, ui: Record<string, string>): string {
37
+ const labelMonth = ui['labelMonth'];
38
+ const labelMonths = ui['labelMonths'];
39
+ const labelYear = ui['labelYear'];
40
+ const labelYears = ui['labelYears'];
41
+ const labelDay = ui['labelDay'];
42
+ const labelDays = ui['labelDays'];
43
+ const labelAnd = ui['labelAnd'];
44
+
31
45
  let years = today.getFullYear() - birthDate.getFullYear();
32
46
  let months = today.getMonth() - birthDate.getMonth();
33
47
  let days = today.getDate() - birthDate.getDate();
@@ -40,19 +54,31 @@ export function calculateAge(birthDate: Date, today: Date): string {
40
54
  years -= 1;
41
55
  months += 12;
42
56
  }
43
- if (years === 0) return `${months} meses y ${days} días`;
44
- return `${years} años, ${months} meses y ${days} días`;
57
+ const mStr = `${months} ${months === 1 ? labelMonth : labelMonths}`;
58
+ const dStr = `${days} ${days === 1 ? labelDay : labelDays}`;
59
+ if (years === 0) return `${mStr} ${labelAnd} ${dStr}`;
60
+ const yStr = `${years} ${years === 1 ? labelYear : labelYears}`;
61
+ return `${yStr}, ${mStr} ${labelAnd} ${dStr}`;
45
62
  }
46
63
 
47
- export function buildIcsContent(birthDate: Date, doseGroups: DoseGroup[]): string {
64
+ export function buildIcsContent(birthDate: Date, doseGroups: DoseGroup[], ui: Record<string, string>): string {
65
+ const labelVaccination = ui['labelVaccination'];
66
+ const labelAppointment = ui['labelAppointment'];
67
+ const labelMonth = ui['labelMonth'];
68
+ const labelMonths = ui['labelMonths'];
69
+ const labelYear = ui['labelYear'];
70
+ const labelYears = ui['labelYears'];
71
+
48
72
  let ics = 'BEGIN:VCALENDAR\nVERSION:2.0\nPRODID:-//jjlmoya//VaxCal//ES\n';
49
73
  doseGroups.forEach(({ months, vaccines }) => {
50
74
  const target = new Date(birthDate);
51
75
  target.setMonth(target.getMonth() + months);
52
76
  if (target <= new Date()) return;
53
77
  const dateStr = target.toISOString().replace(/[-:]/g, '').split('.')[0] + 'Z';
54
- const label = months >= 12 ? `${Math.floor(months / 12)} años` : `${months} meses`;
55
- ics += `BEGIN:VEVENT\nDTSTART:${dateStr}\nSUMMARY:Vacunación ${label}: ${vaccines.join(', ')}\nDESCRIPTION:Cita de vacunación infantil.\nEND:VEVENT\n`;
78
+ const label = months >= 12
79
+ ? `${Math.floor(months / 12)} ${Math.floor(months / 12) === 1 ? labelYear : labelYears}`
80
+ : `${months} ${months === 1 ? labelMonth : labelMonths}`;
81
+ ics += `BEGIN:VEVENT\nDTSTART:${dateStr}\nSUMMARY:${labelVaccination} ${label}: ${vaccines.join(', ')}\nDESCRIPTION:${labelAppointment}\nEND:VEVENT\n`;
56
82
  });
57
83
  ics += 'END:VCALENDAR';
58
84
  return ics;
@@ -1,417 +0,0 @@
1
- /* PRINCIPAL CONTAINER */
2
- .bfc-card {
3
- background: #fff;
4
- border: 1px solid #e2e8f0;
5
- border-radius: 28px;
6
- overflow: hidden;
7
- display: flex;
8
- flex-direction: column;
9
- box-shadow: 0 25px 50px -12px rgba(0, 0, 0, 0.08);
10
- }
11
-
12
- .dark .bfc-card {
13
- background: #111827;
14
- border-color: #1f2937;
15
- }
16
-
17
- /* GRID STRUCTURE */
18
- .bfc-main {
19
- display: grid;
20
- grid-template-columns: 1fr 1fr;
21
- }
22
-
23
- .bfc-left {
24
- background: #f8fafc;
25
- padding: 40px;
26
- border-right: 1px solid #e2e8f0;
27
- }
28
-
29
- .dark .bfc-left {
30
- background: #1f2937;
31
- border-right-color: #374151;
32
- }
33
-
34
- .bfc-right {
35
- background: #fff;
36
- padding: 40px;
37
- }
38
-
39
- .dark .bfc-right {
40
- background: #111827;
41
- }
42
-
43
- /* SECTION MARKER */
44
- .bfc-section-marker {
45
- display: block;
46
- font-size: 0.75rem;
47
- font-weight: 800;
48
- text-transform: uppercase;
49
- letter-spacing: 0.15em;
50
- color: #64748b;
51
- margin-bottom: 32px;
52
- }
53
-
54
- /* INPUT LABEL */
55
- .bfc-input-label {
56
- display: block;
57
- font-size: 0.95rem;
58
- font-weight: 700;
59
- color: #334155;
60
- margin-bottom: 12px;
61
- }
62
-
63
- .dark .bfc-input-label {
64
- color: #e2e8f0;
65
- }
66
-
67
- /* INPUT GROUP */
68
- .bfc-input-group {
69
- margin-bottom: 28px;
70
- }
71
-
72
- /* UNIT NAV */
73
- .bfc-unit-nav {
74
- display: flex;
75
- background: #e2e8f0;
76
- padding: 4px;
77
- border-radius: 12px;
78
- margin-bottom: 24px;
79
- }
80
-
81
- .dark .bfc-unit-nav {
82
- background: #374151;
83
- }
84
-
85
- .bfc-unit-tab {
86
- flex: 1;
87
- padding: 8px;
88
- border: none;
89
- background: #f1f5f9;
90
- color: #64748b;
91
- font-size: 0.85rem;
92
- font-weight: 700;
93
- border-radius: 8px;
94
- cursor: pointer;
95
- transition: all 0.2s ease;
96
- margin: 2px;
97
- }
98
-
99
- .dark .bfc-unit-tab {
100
- background: #1f2937;
101
- }
102
-
103
- .bfc-unit-active {
104
- background: #fff;
105
- color: #0d9488;
106
- box-shadow: 0 2px 8px rgba(0, 0, 0, 0.05);
107
- }
108
-
109
- .dark .bfc-unit-active {
110
- background: #4b5563;
111
- color: #2dd4bf;
112
- }
113
-
114
- /* STEPPER */
115
- .bfc-stepper-box {
116
- display: flex;
117
- align-items: center;
118
- justify-content: space-between;
119
- background: #fff;
120
- border: 1px solid #cbd5e1;
121
- border-radius: 16px;
122
- padding: 8px;
123
- margin-bottom: 8px;
124
- }
125
-
126
- .dark .bfc-stepper-box {
127
- background: #111827;
128
- border-color: #4b5563;
129
- }
130
-
131
- .bfc-btn-step {
132
- width: 44px;
133
- height: 44px;
134
- border-radius: 12px;
135
- border: none;
136
- background: #f1f5f9;
137
- color: #334155;
138
- font-size: 1.25rem;
139
- font-weight: 700;
140
- cursor: pointer;
141
- transition: all 0.2s ease;
142
- }
143
-
144
- .dark .bfc-btn-step {
145
- background: #374151;
146
- color: #f9fafb;
147
- }
148
-
149
- .bfc-btn-step:hover {
150
- background: #0d9488;
151
- color: #fff;
152
- }
153
-
154
- .bfc-val-view {
155
- text-align: center;
156
- }
157
-
158
- .bfc-val-big {
159
- display: block;
160
- font-size: 2rem;
161
- font-weight: 800;
162
- color: #0f172a;
163
- }
164
-
165
- .dark .bfc-val-big {
166
- color: #fff;
167
- }
168
-
169
- .bfc-val-sub {
170
- display: block;
171
- font-size: 0.85rem;
172
- color: #64748b;
173
- font-weight: 600;
174
- }
175
-
176
- /* SLIDER */
177
- .bfc-slider-wrap {
178
- padding: 0 11px;
179
- }
180
-
181
- .bfc-slider {
182
- -webkit-appearance: none;
183
- appearance: none;
184
- width: 100%;
185
- margin: 16px 0;
186
- height: 6px;
187
- background: #e2e8f0;
188
- border-radius: 3px;
189
- outline: none;
190
- }
191
-
192
- .dark .bfc-slider {
193
- background: #374151;
194
- }
195
-
196
- .bfc-slider::-webkit-slider-thumb {
197
- -webkit-appearance: none;
198
- appearance: none;
199
- width: 22px;
200
- height: 22px;
201
- border-radius: 50%;
202
- background: #0d9488;
203
- cursor: pointer;
204
- border: 3px solid #fff;
205
- box-shadow: 0 4px 10px rgba(13, 148, 136, 0.3);
206
- }
207
-
208
- /* FEED TYPE */
209
- .bfc-type-rack {
210
- display: grid;
211
- grid-template-columns: repeat(3, 1fr);
212
- gap: 12px;
213
- }
214
-
215
- .bfc-type-tile {
216
- background: #f1f5f9;
217
- border: 1px solid #cbd5e1;
218
- border-radius: 14px;
219
- padding: 14px 4px;
220
- text-align: center;
221
- cursor: pointer;
222
- transition: all 0.2s ease;
223
- font-size: 0.9rem;
224
- font-weight: 700;
225
- color: #64748b;
226
- }
227
-
228
- .dark .bfc-type-tile {
229
- background: #1f2937;
230
- border-color: #4b5563;
231
- }
232
-
233
- .bfc-type-active {
234
- background: #f0fdfa;
235
- border-color: #0d9488;
236
- color: #0f766e;
237
- box-shadow: 0 2px 8px rgba(13, 148, 136, 0.1);
238
- }
239
-
240
- .dark .bfc-type-active {
241
- background: rgba(13, 148, 136, 0.1);
242
- color: #2dd4bf;
243
- border-color: #2dd4bf;
244
- }
245
-
246
- /* GAUGE */
247
- .bfc-gauge-area {
248
- margin-top: 40px;
249
- padding: 30px;
250
- background: #fff;
251
- border-radius: 20px;
252
- border: 1px solid #e2e8f0;
253
- }
254
-
255
- .dark .bfc-gauge-area {
256
- background: #111827;
257
- border-color: #374151;
258
- }
259
-
260
- .bfc-gauge-viz {
261
- display: flex;
262
- flex-direction: column;
263
- align-items: center;
264
- text-align: center;
265
- gap: 16px;
266
- }
267
-
268
- .bfc-stomach-bubble {
269
- width: 60px;
270
- height: 60px;
271
- background: radial-gradient(circle at 30% 30%, #5eead4 0%, #0d9488 100%);
272
- border-radius: 50%;
273
- transition: all 0.6s cubic-bezier(0.34, 1.56, 0.64, 1);
274
- box-shadow: 0 10px 25px rgba(13, 148, 136, 0.2);
275
- }
276
-
277
- .bfc-visual-hint {
278
- font-size: 0.95rem;
279
- font-weight: 600;
280
- color: #0f766e;
281
- margin: 0;
282
- }
283
-
284
- .dark .bfc-visual-hint {
285
- color: #2dd4bf;
286
- }
287
-
288
- /* RESULT BOX */
289
- .bfc-res-card-box {
290
- background: #f0fdfa;
291
- border-radius: 24px;
292
- padding: 32px 16px;
293
- text-align: center;
294
- margin-bottom: 24px;
295
- }
296
-
297
- .dark .bfc-res-card-box {
298
- background: rgba(13, 148, 136, 0.05);
299
- }
300
-
301
- .bfc-res-main-val {
302
- display: block;
303
- font-size: 3.5rem;
304
- font-weight: 950;
305
- color: #0d9488;
306
- letter-spacing: -0.05em;
307
- line-height: 1;
308
- }
309
-
310
- .dark .bfc-res-main-val {
311
- color: #2dd4bf;
312
- }
313
-
314
- .bfc-res-label {
315
- display: block;
316
- margin-top: 8px;
317
- font-size: 1rem;
318
- font-weight: 700;
319
- color: #64748b;
320
- }
321
-
322
- /* STATS */
323
- .bfc-stats-grid {
324
- display: grid;
325
- grid-template-columns: 1fr 1fr;
326
- gap: 20px;
327
- margin-bottom: 32px;
328
- }
329
-
330
- .bfc-stat-item {
331
- padding: 8px;
332
- border-bottom: 2px solid #f1f5f9;
333
- }
334
-
335
- .dark .bfc-stat-item {
336
- border-bottom-color: #374151;
337
- }
338
-
339
- .bfc-stat-label {
340
- display: block;
341
- font-size: 0.7rem;
342
- font-weight: 800;
343
- color: #94a3b8;
344
- text-transform: uppercase;
345
- margin-bottom: 2px;
346
- }
347
-
348
- .bfc-stat-value {
349
- font-size: 1.15rem;
350
- font-weight: 800;
351
- color: #334155;
352
- }
353
-
354
- .dark .bfc-stat-value {
355
- color: #e2e8f0;
356
- }
357
-
358
- /* BEHAVIOR SECTION */
359
- .bfc-behavior-sec {
360
- margin-top: 32px;
361
- }
362
-
363
- .bfc-pills-container {
364
- display: flex;
365
- flex-wrap: wrap;
366
- gap: 8px;
367
- margin-top: 12px;
368
- }
369
-
370
- .bfc-pill {
371
- font-size: 0.8rem;
372
- font-weight: 700;
373
- padding: 6px 12px;
374
- border-radius: 100px;
375
- }
376
-
377
- .bfc-pill-hunger {
378
- background: #fff7ed;
379
- color: #c2410c;
380
- }
381
-
382
- .dark .bfc-pill-hunger {
383
- background: rgba(194, 65, 12, 0.1);
384
- color: #fdba74;
385
- }
386
-
387
- .bfc-pill-fullness {
388
- background: #f0fdf4;
389
- color: #15803d;
390
- }
391
-
392
- .dark .bfc-pill-fullness {
393
- background: rgba(21, 128, 61, 0.1);
394
- color: #4ade80;
395
- }
396
-
397
- /* HIDDEN */
398
- .bfc-hidden {
399
- display: none;
400
- }
401
-
402
- /* RESPONSIVE */
403
- @media (max-width: 800px) {
404
- .bfc-main {
405
- grid-template-columns: 1fr;
406
- }
407
-
408
- .bfc-left {
409
- border-right: none;
410
- border-bottom: 1px solid #e2e8f0;
411
- padding: 30px 20px;
412
- }
413
-
414
- .bfc-right {
415
- padding: 30px 20px;
416
- }
417
- }