@jjlmoya/utils-babies 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.
- package/package.json +69 -0
- package/src/category/i18n/en.ts +48 -0
- package/src/category/i18n/es.ts +48 -0
- package/src/category/i18n/fr.ts +48 -0
- package/src/category/index.ts +24 -0
- package/src/category/seo.astro +15 -0
- package/src/components/PreviewNavSidebar.astro +116 -0
- package/src/components/PreviewToolbar.astro +143 -0
- package/src/data.ts +30 -0
- package/src/env.d.ts +5 -0
- package/src/index.ts +19 -0
- package/src/layouts/PreviewLayout.astro +117 -0
- package/src/pages/[locale]/[slug].astro +146 -0
- package/src/pages/[locale].astro +251 -0
- package/src/pages/index.astro +4 -0
- package/src/tests/faq_count.test.ts +19 -0
- package/src/tests/locale_completeness.test.ts +42 -0
- package/src/tests/mocks/astro_mock.js +2 -0
- package/src/tests/no_h1_in_components.test.ts +48 -0
- package/src/tests/seo_length.test.ts +23 -0
- package/src/tests/tool_validation.test.ts +17 -0
- package/src/tool/baby-feeding-calculator/bibliography.astro +7 -0
- package/src/tool/baby-feeding-calculator/component.astro +210 -0
- package/src/tool/baby-feeding-calculator/i18n/en.ts +162 -0
- package/src/tool/baby-feeding-calculator/i18n/es.ts +162 -0
- package/src/tool/baby-feeding-calculator/i18n/fr.ts +162 -0
- package/src/tool/baby-feeding-calculator/index.ts +47 -0
- package/src/tool/baby-feeding-calculator/logic.ts +85 -0
- package/src/tool/baby-feeding-calculator/seo.astro +58 -0
- package/src/tool/baby-feeding-calculator/style.css +329 -0
- package/src/tool/baby-percentile-calculator/bibliography.astro +7 -0
- package/src/tool/baby-percentile-calculator/component.astro +388 -0
- package/src/tool/baby-percentile-calculator/i18n/en.ts +244 -0
- package/src/tool/baby-percentile-calculator/i18n/es.ts +244 -0
- package/src/tool/baby-percentile-calculator/i18n/fr.ts +244 -0
- package/src/tool/baby-percentile-calculator/index.ts +54 -0
- package/src/tool/baby-percentile-calculator/lmsData.ts +80 -0
- package/src/tool/baby-percentile-calculator/logic.ts +85 -0
- package/src/tool/baby-percentile-calculator/seo.astro +36 -0
- package/src/tool/baby-percentile-calculator/style.css +393 -0
- package/src/tool/baby-size-converter/bibliography.astro +7 -0
- package/src/tool/baby-size-converter/component.astro +289 -0
- package/src/tool/baby-size-converter/data.json +11 -0
- package/src/tool/baby-size-converter/i18n/en.ts +203 -0
- package/src/tool/baby-size-converter/i18n/es.ts +203 -0
- package/src/tool/baby-size-converter/i18n/fr.ts +203 -0
- package/src/tool/baby-size-converter/index.ts +53 -0
- package/src/tool/baby-size-converter/logic.ts +44 -0
- package/src/tool/baby-size-converter/seo.astro +36 -0
- package/src/tool/baby-size-converter/style.css +394 -0
- package/src/tool/fertile-days-estimator/bibliography.astro +7 -0
- package/src/tool/fertile-days-estimator/component.astro +265 -0
- package/src/tool/fertile-days-estimator/i18n/en.ts +258 -0
- package/src/tool/fertile-days-estimator/i18n/es.ts +262 -0
- package/src/tool/fertile-days-estimator/i18n/fr.ts +258 -0
- package/src/tool/fertile-days-estimator/index.ts +47 -0
- package/src/tool/fertile-days-estimator/logic.ts +58 -0
- package/src/tool/fertile-days-estimator/seo.astro +36 -0
- package/src/tool/fertile-days-estimator/style.css +419 -0
- package/src/tool/pregnancy-calculator/bibliography.astro +7 -0
- package/src/tool/pregnancy-calculator/calculator.ts +41 -0
- package/src/tool/pregnancy-calculator/component.astro +432 -0
- package/src/tool/pregnancy-calculator/i18n/en.ts +315 -0
- package/src/tool/pregnancy-calculator/i18n/es.ts +319 -0
- package/src/tool/pregnancy-calculator/i18n/fr.ts +315 -0
- package/src/tool/pregnancy-calculator/index.ts +55 -0
- package/src/tool/pregnancy-calculator/milestones.ts +153 -0
- package/src/tool/pregnancy-calculator/seo.astro +36 -0
- package/src/tool/pregnancy-calculator/store.ts +60 -0
- package/src/tool/pregnancy-calculator/style.css +807 -0
- package/src/tool/vaccination-calendar/bibliography.astro +7 -0
- package/src/tool/vaccination-calendar/component.astro +286 -0
- package/src/tool/vaccination-calendar/i18n/en.ts +170 -0
- package/src/tool/vaccination-calendar/i18n/es.ts +174 -0
- package/src/tool/vaccination-calendar/i18n/fr.ts +170 -0
- package/src/tool/vaccination-calendar/index.ts +47 -0
- package/src/tool/vaccination-calendar/logic.ts +59 -0
- package/src/tool/vaccination-calendar/seo.astro +36 -0
- package/src/tool/vaccination-calendar/style.css +316 -0
- package/src/tool/vaccination-calendar/vaccinationData.ts +21 -0
- package/src/tools.ts +17 -0
- package/src/types.ts +72 -0
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
---
|
|
2
|
+
import { Bibliography as BibliographyUI } from '@jjlmoya/utils-shared';
|
|
3
|
+
import type { BibliographyEntry } from '../../types';
|
|
4
|
+
interface Props { links?: BibliographyEntry[]; title: string; }
|
|
5
|
+
const { links = [], title } = Astro.props;
|
|
6
|
+
---
|
|
7
|
+
<BibliographyUI links={links} title={title} />
|
|
@@ -0,0 +1,286 @@
|
|
|
1
|
+
---
|
|
2
|
+
import './style.css';
|
|
3
|
+
import type { VaccinationCalendarUI } from './index';
|
|
4
|
+
interface Props { ui: VaccinationCalendarUI; }
|
|
5
|
+
const { ui } = Astro.props;
|
|
6
|
+
---
|
|
7
|
+
<div id="vaccination-calendar-root" class="vaccination-calendar" data-ui={JSON.stringify(ui)}>
|
|
8
|
+
<div class="vaccination-calendar-header">
|
|
9
|
+
<div class="vaccination-calendar-field">
|
|
10
|
+
<label class="vaccination-calendar-field-label" for="vc-dd" id="vc-birth-label">{ui.labelBirthDate}</label>
|
|
11
|
+
<div class="vaccination-calendar-triple-input">
|
|
12
|
+
<input
|
|
13
|
+
class="vaccination-calendar-segment"
|
|
14
|
+
id="vc-dd"
|
|
15
|
+
type="text"
|
|
16
|
+
inputmode="numeric"
|
|
17
|
+
maxlength="2"
|
|
18
|
+
placeholder={ui.placeholderDD}
|
|
19
|
+
aria-label="Día"
|
|
20
|
+
/>
|
|
21
|
+
<span class="vaccination-calendar-sep">/</span>
|
|
22
|
+
<input
|
|
23
|
+
class="vaccination-calendar-segment"
|
|
24
|
+
id="vc-mm"
|
|
25
|
+
type="text"
|
|
26
|
+
inputmode="numeric"
|
|
27
|
+
maxlength="2"
|
|
28
|
+
placeholder={ui.placeholderMM}
|
|
29
|
+
aria-label="Mes"
|
|
30
|
+
/>
|
|
31
|
+
<span class="vaccination-calendar-sep">/</span>
|
|
32
|
+
<input
|
|
33
|
+
class="vaccination-calendar-segment vaccination-calendar-segment-year"
|
|
34
|
+
id="vc-yyyy"
|
|
35
|
+
type="text"
|
|
36
|
+
inputmode="numeric"
|
|
37
|
+
maxlength="4"
|
|
38
|
+
placeholder={ui.placeholderAAAA}
|
|
39
|
+
aria-label="Año"
|
|
40
|
+
/>
|
|
41
|
+
</div>
|
|
42
|
+
<div class="vaccination-calendar-error" id="vc-error" role="alert" aria-live="polite"></div>
|
|
43
|
+
</div>
|
|
44
|
+
<div class="vaccination-calendar-age-badge" id="vc-age-badge" aria-live="polite"></div>
|
|
45
|
+
</div>
|
|
46
|
+
|
|
47
|
+
<div class="vaccination-calendar-empty" id="vc-empty">
|
|
48
|
+
<p>{ui.emptyMsg}</p>
|
|
49
|
+
</div>
|
|
50
|
+
|
|
51
|
+
<div class="vaccination-calendar-context" id="vc-context">
|
|
52
|
+
<div class="vaccination-calendar-next-title" id="vc-next-label">{ui.labelNextAppointment}</div>
|
|
53
|
+
<div class="vaccination-calendar-next-date" id="vc-next-date"></div>
|
|
54
|
+
<ul class="vaccination-calendar-vac-list" id="vc-next-vaccines"></ul>
|
|
55
|
+
<div class="vaccination-calendar-actions">
|
|
56
|
+
<button class="vaccination-calendar-btn-primary" id="vc-btn-reminder">{ui.btnAddReminder}</button>
|
|
57
|
+
<button class="vaccination-calendar-btn-primary vaccination-calendar-btn-share" id="vc-btn-share">{ui.labelShare}</button>
|
|
58
|
+
</div>
|
|
59
|
+
<div class="vaccination-calendar-footer">
|
|
60
|
+
<span>{ui.labelSource}</span>
|
|
61
|
+
</div>
|
|
62
|
+
</div>
|
|
63
|
+
|
|
64
|
+
<div class="vaccination-calendar-sections" id="vc-sections">
|
|
65
|
+
<details class="vaccination-calendar-accordion-item" id="vc-accordion-past">
|
|
66
|
+
<summary class="vaccination-calendar-accordion-trigger">{ui.labelPassed}</summary>
|
|
67
|
+
<div class="vaccination-calendar-accordion-content">
|
|
68
|
+
<div class="vaccination-calendar-timeline" id="vc-timeline-past"></div>
|
|
69
|
+
</div>
|
|
70
|
+
</details>
|
|
71
|
+
<details class="vaccination-calendar-accordion-item" id="vc-accordion-future" open>
|
|
72
|
+
<summary class="vaccination-calendar-accordion-trigger">{ui.labelFuture}</summary>
|
|
73
|
+
<div class="vaccination-calendar-accordion-content">
|
|
74
|
+
<div class="vaccination-calendar-timeline" id="vc-timeline-future"></div>
|
|
75
|
+
</div>
|
|
76
|
+
</details>
|
|
77
|
+
</div>
|
|
78
|
+
</div>
|
|
79
|
+
|
|
80
|
+
<script>
|
|
81
|
+
import { buildDoseGroups, getAgeLabel, calculateAge, buildIcsContent } from './logic';
|
|
82
|
+
import type { DoseGroup } from './logic';
|
|
83
|
+
|
|
84
|
+
const root = document.getElementById('vaccination-calendar-root') as HTMLElement;
|
|
85
|
+
const ui = JSON.parse(root.dataset.ui as string) as Record<string, string>;
|
|
86
|
+
|
|
87
|
+
const ddInput = root.querySelector('#vc-dd') as HTMLInputElement;
|
|
88
|
+
const mmInput = root.querySelector('#vc-mm') as HTMLInputElement;
|
|
89
|
+
const yyyyInput = root.querySelector('#vc-yyyy') as HTMLInputElement;
|
|
90
|
+
const errorEl = root.querySelector('#vc-error') as HTMLElement;
|
|
91
|
+
const ageBadge = root.querySelector('#vc-age-badge') as HTMLElement;
|
|
92
|
+
const emptyEl = root.querySelector('#vc-empty') as HTMLElement;
|
|
93
|
+
const contextEl = root.querySelector('#vc-context') as HTMLElement;
|
|
94
|
+
const sectionsEl = root.querySelector('#vc-sections') as HTMLElement;
|
|
95
|
+
const nextDateEl = root.querySelector('#vc-next-date') as HTMLElement;
|
|
96
|
+
const nextVaccinesEl = root.querySelector('#vc-next-vaccines') as HTMLElement;
|
|
97
|
+
const timelinePast = root.querySelector('#vc-timeline-past') as HTMLElement;
|
|
98
|
+
const timelineFuture = root.querySelector('#vc-timeline-future') as HTMLElement;
|
|
99
|
+
const btnReminder = root.querySelector('#vc-btn-reminder') as HTMLButtonElement;
|
|
100
|
+
const btnShare = root.querySelector('#vc-btn-share') as HTMLButtonElement;
|
|
101
|
+
|
|
102
|
+
const doseGroups = buildDoseGroups();
|
|
103
|
+
let currentBirthDate: Date | null = null;
|
|
104
|
+
|
|
105
|
+
function parseInputDate(): Date | null {
|
|
106
|
+
const dd = ddInput.value.trim();
|
|
107
|
+
const mm = mmInput.value.trim();
|
|
108
|
+
const yyyy = yyyyInput.value.trim();
|
|
109
|
+
if (!dd || !mm || !yyyy || yyyy.length < 4) return null;
|
|
110
|
+
const d = Number(dd);
|
|
111
|
+
const m = Number(mm);
|
|
112
|
+
const y = Number(yyyy);
|
|
113
|
+
if (isNaN(d) || isNaN(m) || isNaN(y)) return null;
|
|
114
|
+
if (m < 1 || m > 12 || d < 1 || d > 31) return null;
|
|
115
|
+
const date = new Date(y, m - 1, d);
|
|
116
|
+
if (date.getMonth() !== m - 1 || date.getDate() !== d) return null;
|
|
117
|
+
return date;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
function formatDate(date: Date): string {
|
|
121
|
+
return new Intl.DateTimeFormat('es-ES', { day: '2-digit', month: 'long', year: 'numeric' }).format(date);
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
function buildTimelineRow(group: DoseGroup, birthDate: Date, _today: Date, isPast: boolean): HTMLElement {
|
|
125
|
+
const target = new Date(birthDate);
|
|
126
|
+
target.setMonth(target.getMonth() + group.months);
|
|
127
|
+
const row = document.createElement('div');
|
|
128
|
+
row.className = 'vaccination-calendar-timeline-row';
|
|
129
|
+
const ageEl = document.createElement('div');
|
|
130
|
+
ageEl.className = 'vaccination-calendar-timeline-age';
|
|
131
|
+
ageEl.textContent = getAgeLabel(group.months);
|
|
132
|
+
const vacEl = document.createElement('div');
|
|
133
|
+
vacEl.className = 'vaccination-calendar-timeline-vac';
|
|
134
|
+
group.vaccines.forEach((name) => {
|
|
135
|
+
const pill = document.createElement('span');
|
|
136
|
+
pill.className = 'vaccination-calendar-vac-pill';
|
|
137
|
+
pill.textContent = name;
|
|
138
|
+
vacEl.appendChild(pill);
|
|
139
|
+
});
|
|
140
|
+
const statusEl = document.createElement('div');
|
|
141
|
+
statusEl.className = isPast
|
|
142
|
+
? 'vaccination-calendar-timeline-status vaccination-calendar-check'
|
|
143
|
+
: 'vaccination-calendar-timeline-status vaccination-calendar-clock';
|
|
144
|
+
statusEl.textContent = (isPast ? ui['labelStatusOk'] : ui['labelStatusPending']) ?? null;
|
|
145
|
+
row.appendChild(ageEl);
|
|
146
|
+
row.appendChild(vacEl);
|
|
147
|
+
row.appendChild(statusEl);
|
|
148
|
+
return row;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
function renderTimelines(birthDate: Date): void {
|
|
152
|
+
const today = new Date();
|
|
153
|
+
today.setHours(0, 0, 0, 0);
|
|
154
|
+
timelinePast.innerHTML = '';
|
|
155
|
+
timelineFuture.innerHTML = '';
|
|
156
|
+
doseGroups.forEach((group) => {
|
|
157
|
+
const target = new Date(birthDate);
|
|
158
|
+
target.setMonth(target.getMonth() + group.months);
|
|
159
|
+
target.setHours(0, 0, 0, 0);
|
|
160
|
+
const isPast = target <= today;
|
|
161
|
+
const row = buildTimelineRow(group, birthDate, today, isPast);
|
|
162
|
+
if (isPast) {
|
|
163
|
+
timelinePast.appendChild(row);
|
|
164
|
+
} else {
|
|
165
|
+
timelineFuture.appendChild(row);
|
|
166
|
+
}
|
|
167
|
+
});
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
function findNextGroup(birthDate: Date): DoseGroup | null {
|
|
171
|
+
const today = new Date();
|
|
172
|
+
today.setHours(0, 0, 0, 0);
|
|
173
|
+
for (const group of doseGroups) {
|
|
174
|
+
const target = new Date(birthDate);
|
|
175
|
+
target.setMonth(target.getMonth() + group.months);
|
|
176
|
+
target.setHours(0, 0, 0, 0);
|
|
177
|
+
if (target >= today) return group;
|
|
178
|
+
}
|
|
179
|
+
return null;
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
function renderNextAppointment(birthDate: Date): void {
|
|
183
|
+
const today = new Date();
|
|
184
|
+
today.setHours(0, 0, 0, 0);
|
|
185
|
+
const next = findNextGroup(birthDate);
|
|
186
|
+
if (!next) {
|
|
187
|
+
nextDateEl.textContent = 'Calendario completo';
|
|
188
|
+
nextVaccinesEl.innerHTML = '';
|
|
189
|
+
return;
|
|
190
|
+
}
|
|
191
|
+
const target = new Date(birthDate);
|
|
192
|
+
target.setMonth(target.getMonth() + next.months);
|
|
193
|
+
target.setHours(0, 0, 0, 0);
|
|
194
|
+
const isToday = target.getTime() === today.getTime();
|
|
195
|
+
if (isToday) {
|
|
196
|
+
root.classList.add('vaccination-calendar-is-today');
|
|
197
|
+
nextDateEl.textContent = ui['btnToday'] ?? null;
|
|
198
|
+
} else {
|
|
199
|
+
root.classList.remove('vaccination-calendar-is-today');
|
|
200
|
+
nextDateEl.textContent = `${getAgeLabel(next.months)} — ${formatDate(target)}`;
|
|
201
|
+
}
|
|
202
|
+
nextVaccinesEl.innerHTML = '';
|
|
203
|
+
next.vaccines.forEach((name) => {
|
|
204
|
+
const li = document.createElement('li');
|
|
205
|
+
li.className = 'vaccination-calendar-vac-item';
|
|
206
|
+
const nameEl = document.createElement('span');
|
|
207
|
+
nameEl.className = 'vaccination-calendar-vac-name';
|
|
208
|
+
nameEl.textContent = name;
|
|
209
|
+
li.appendChild(nameEl);
|
|
210
|
+
nextVaccinesEl.appendChild(li);
|
|
211
|
+
});
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
function updateDisplay(birthDate: Date): void {
|
|
215
|
+
const today = new Date();
|
|
216
|
+
today.setHours(0, 0, 0, 0);
|
|
217
|
+
ageBadge.textContent = calculateAge(birthDate, today);
|
|
218
|
+
root.classList.add('vaccination-calendar-active');
|
|
219
|
+
emptyEl.style.display = 'none';
|
|
220
|
+
contextEl.style.display = '';
|
|
221
|
+
sectionsEl.style.display = '';
|
|
222
|
+
renderNextAppointment(birthDate);
|
|
223
|
+
renderTimelines(birthDate);
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
function clearDisplay(): void {
|
|
227
|
+
root.classList.remove('vaccination-calendar-active', 'vaccination-calendar-is-today');
|
|
228
|
+
ageBadge.textContent = '';
|
|
229
|
+
emptyEl.style.display = '';
|
|
230
|
+
contextEl.style.display = 'none';
|
|
231
|
+
sectionsEl.style.display = 'none';
|
|
232
|
+
errorEl.textContent = '';
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
function handleInput(): void {
|
|
236
|
+
const date = parseInputDate();
|
|
237
|
+
if (!date) {
|
|
238
|
+
currentBirthDate = null;
|
|
239
|
+
const hasValue = ddInput.value || mmInput.value || yyyyInput.value;
|
|
240
|
+
errorEl.textContent = hasValue ? (ui['invalidMsg'] ?? '') : '';
|
|
241
|
+
clearDisplay();
|
|
242
|
+
return;
|
|
243
|
+
}
|
|
244
|
+
const today = new Date();
|
|
245
|
+
today.setHours(0, 0, 0, 0);
|
|
246
|
+
if (date > today) {
|
|
247
|
+
currentBirthDate = null;
|
|
248
|
+
errorEl.textContent = ui['futureMsg'] ?? null;
|
|
249
|
+
clearDisplay();
|
|
250
|
+
return;
|
|
251
|
+
}
|
|
252
|
+
errorEl.textContent = '';
|
|
253
|
+
currentBirthDate = date;
|
|
254
|
+
updateDisplay(date);
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
function autoAdvance(input: HTMLInputElement, next: HTMLInputElement | null, maxLen: number): void {
|
|
258
|
+
input.addEventListener('input', () => {
|
|
259
|
+
if (input.value.length >= maxLen && next) next.focus();
|
|
260
|
+
handleInput();
|
|
261
|
+
});
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
autoAdvance(ddInput, mmInput, 2);
|
|
265
|
+
autoAdvance(mmInput, yyyyInput, 2);
|
|
266
|
+
yyyyInput.addEventListener('input', handleInput);
|
|
267
|
+
|
|
268
|
+
btnReminder.addEventListener('click', () => {
|
|
269
|
+
if (!currentBirthDate) return;
|
|
270
|
+
const ics = buildIcsContent(currentBirthDate, doseGroups);
|
|
271
|
+
const blob = new Blob([ics], { type: 'text/calendar' });
|
|
272
|
+
const url = URL.createObjectURL(blob);
|
|
273
|
+
const a = document.createElement('a');
|
|
274
|
+
a.href = url;
|
|
275
|
+
a.download = 'vacunacion-bebe.ics';
|
|
276
|
+
a.click();
|
|
277
|
+
URL.revokeObjectURL(url);
|
|
278
|
+
});
|
|
279
|
+
|
|
280
|
+
btnShare.addEventListener('click', () => {
|
|
281
|
+
if (!currentBirthDate || !navigator.share) return;
|
|
282
|
+
navigator.share({ title: 'Calendario de Vacunación', ...(ui['labelSource'] !== undefined ? { text: ui['labelSource'] } : {}), url: window.location.href });
|
|
283
|
+
});
|
|
284
|
+
|
|
285
|
+
clearDisplay();
|
|
286
|
+
</script>
|
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
import type { VaccinationCalendarLocaleContent } from '../index';
|
|
2
|
+
import type { WithContext, FAQPage, HowTo, SoftwareApplication } from 'schema-dts';
|
|
3
|
+
|
|
4
|
+
const slug = 'baby-vaccination-calendar-spain';
|
|
5
|
+
const title = 'Baby Vaccination Calendar in Spain';
|
|
6
|
+
const description = 'Calculate the exact vaccination dates for your baby according to the AEP 2026 schedule.';
|
|
7
|
+
const faq = [
|
|
8
|
+
{
|
|
9
|
+
question: 'What is the AEP 2026 vaccination schedule?',
|
|
10
|
+
answer: 'It is the vaccination schedule recommended by the Spanish Association of Pediatrics for 2026. It includes all systematic and optional vaccines for babies and children from birth to 14 years of age.',
|
|
11
|
+
},
|
|
12
|
+
{
|
|
13
|
+
question: 'Is it mandatory to vaccinate my baby according to the schedule?',
|
|
14
|
+
answer: 'In Spain, vaccination is not legally mandatory, but it is strongly recommended. Vaccines on the systematic schedule are free and administered at public health centres.',
|
|
15
|
+
},
|
|
16
|
+
{
|
|
17
|
+
question: 'What happens if we miss a dose?',
|
|
18
|
+
answer: 'If a dose is delayed, you do not need to start over. Your paediatrician will advise you on how to resume the schedule from where it was left off. The important thing is to complete the course as soon as possible.',
|
|
19
|
+
},
|
|
20
|
+
{
|
|
21
|
+
question: 'Can several vaccines be given on the same day?',
|
|
22
|
+
answer: 'Yes, it is common and safe to administer several vaccines at the same visit. Combined vaccines such as the hexavalent already protect against six diseases in a single injection.',
|
|
23
|
+
},
|
|
24
|
+
];
|
|
25
|
+
const howTo = [
|
|
26
|
+
{
|
|
27
|
+
name: 'Enter the date of birth',
|
|
28
|
+
text: 'Type the day, month and year of your baby\'s birth in the corresponding fields.',
|
|
29
|
+
},
|
|
30
|
+
{
|
|
31
|
+
name: 'Check the next appointment',
|
|
32
|
+
text: 'The calculator automatically shows you when the next vaccination is and which vaccines are due.',
|
|
33
|
+
},
|
|
34
|
+
{
|
|
35
|
+
name: 'Review the full calendar',
|
|
36
|
+
text: 'Expand the past and future appointment sections to see the complete vaccination calendar.',
|
|
37
|
+
},
|
|
38
|
+
{
|
|
39
|
+
name: 'Export reminders',
|
|
40
|
+
text: 'Press the button to download an .ics file with all future appointments and add them to your phone calendar.',
|
|
41
|
+
},
|
|
42
|
+
];
|
|
43
|
+
|
|
44
|
+
const faqSchema: WithContext<FAQPage> = {
|
|
45
|
+
'@context': 'https://schema.org',
|
|
46
|
+
'@type': 'FAQPage',
|
|
47
|
+
mainEntity: faq.map((item) => ({
|
|
48
|
+
'@type': 'Question',
|
|
49
|
+
name: item.question,
|
|
50
|
+
acceptedAnswer: { '@type': 'Answer', text: item.answer },
|
|
51
|
+
})),
|
|
52
|
+
};
|
|
53
|
+
const howToSchema: WithContext<HowTo> = {
|
|
54
|
+
'@context': 'https://schema.org',
|
|
55
|
+
'@type': 'HowTo',
|
|
56
|
+
name: title,
|
|
57
|
+
description,
|
|
58
|
+
step: howTo.map((step) => ({
|
|
59
|
+
'@type': 'HowToStep',
|
|
60
|
+
name: step.name,
|
|
61
|
+
text: step.text,
|
|
62
|
+
})),
|
|
63
|
+
};
|
|
64
|
+
const appSchema: WithContext<SoftwareApplication> = {
|
|
65
|
+
'@context': 'https://schema.org',
|
|
66
|
+
'@type': 'SoftwareApplication',
|
|
67
|
+
name: title,
|
|
68
|
+
description,
|
|
69
|
+
applicationCategory: 'UtilitiesApplication',
|
|
70
|
+
operatingSystem: 'Web',
|
|
71
|
+
offers: { '@type': 'Offer', price: '0', priceCurrency: 'EUR' },
|
|
72
|
+
inLanguage: 'en',
|
|
73
|
+
};
|
|
74
|
+
|
|
75
|
+
export const content: VaccinationCalendarLocaleContent = {
|
|
76
|
+
slug,
|
|
77
|
+
title,
|
|
78
|
+
description,
|
|
79
|
+
ui: {
|
|
80
|
+
labelBirthDate: "Baby's date of birth",
|
|
81
|
+
placeholderDD: 'DD',
|
|
82
|
+
placeholderMM: 'MM',
|
|
83
|
+
placeholderAAAA: 'YYYY',
|
|
84
|
+
emptyMsg: 'Enter the day, month and year of your baby\'s birth to see the vaccination calendar.',
|
|
85
|
+
invalidMsg: 'The date entered is not valid.',
|
|
86
|
+
futureMsg: 'A baby cannot have been born in the future.',
|
|
87
|
+
labelNextAppointment: 'Next calculated appointment',
|
|
88
|
+
btnAddReminder: 'Add reminder to phone',
|
|
89
|
+
btnToday: "It's today! Don't forget the booklet",
|
|
90
|
+
labelPassed: 'Past appointments',
|
|
91
|
+
labelFuture: 'Future calendar',
|
|
92
|
+
labelStatusOk: 'OK',
|
|
93
|
+
labelStatusPending: 'PEND.',
|
|
94
|
+
labelSource: 'Source: AEP 2026',
|
|
95
|
+
labelShare: 'Share these dates',
|
|
96
|
+
faqTitle: 'Frequently asked questions',
|
|
97
|
+
bibliographyTitle: 'References',
|
|
98
|
+
},
|
|
99
|
+
seo: [
|
|
100
|
+
{ type: 'title', text: "Baby Vaccination Calculator: When Is My Child's Next Dose?", level: 2 },
|
|
101
|
+
{ type: 'stats', columns: 4, items: [
|
|
102
|
+
{ value: '+95%', label: 'Effectiveness' },
|
|
103
|
+
{ value: '14', label: 'Total doses' },
|
|
104
|
+
{ value: 'Public', label: 'AEP cost' },
|
|
105
|
+
{ value: 'High', label: 'Safety' },
|
|
106
|
+
]},
|
|
107
|
+
{ type: 'tip', html: 'The AEP 2026 schedule includes important updates such as the extension of the meningococcal B vaccine and revised recommendations for HPV in both sexes from age 12.' },
|
|
108
|
+
{ type: 'title', text: 'What is new in the AEP 2026 Schedule', level: 3 },
|
|
109
|
+
{ type: 'list', items: [
|
|
110
|
+
'Meningococcal B (Bexsero): consolidated 2+1 schedule with doses at 2, 4 and 12 months.',
|
|
111
|
+
'HPV extended to all adolescents from age 12, regardless of sex.',
|
|
112
|
+
'Pneumococcal: updated recommendation with PCV15 or PCV20 depending on regional availability.',
|
|
113
|
+
'Rotavirus: oral vaccine included in the systematic schedule across all regions.',
|
|
114
|
+
'Tdap: booster recommended at ages 6 and 12 to maintain immunity against whooping cough.',
|
|
115
|
+
]},
|
|
116
|
+
{ type: 'title', text: 'Differences between Spanish regions', level: 3 },
|
|
117
|
+
{ type: 'list', items: [
|
|
118
|
+
'Some regions include additional vaccines not covered in the national schedule.',
|
|
119
|
+
'Rotavirus funding varies by region, although the trend is universal coverage.',
|
|
120
|
+
'Meningococcal ACWY may be administered at slightly different ages depending on the regional protocol.',
|
|
121
|
+
'Always check with your paediatrician or local health centre to confirm the current schedule.',
|
|
122
|
+
]},
|
|
123
|
+
{ type: 'title', text: 'How to export the calendar to your phone', level: 3 },
|
|
124
|
+
{ type: 'list', items: [
|
|
125
|
+
"Enter your baby's date of birth in the calculator.",
|
|
126
|
+
'Press the "Add reminder to phone" button to download the .ics file.',
|
|
127
|
+
'Open the file with your calendar app (Google Calendar, Apple Calendar, etc.).',
|
|
128
|
+
'All vaccination events will be saved with the corresponding date and vaccines.',
|
|
129
|
+
]},
|
|
130
|
+
{ type: 'title', text: 'Common side effects', level: 3 },
|
|
131
|
+
{ type: 'list', items: [
|
|
132
|
+
'Redness or swelling at the injection site: disappears within 1–2 days.',
|
|
133
|
+
'Mild fever (37.5–38.5 °C): normal in the first 24–48 hours.',
|
|
134
|
+
'Irritability or crying: common in babies after the first doses.',
|
|
135
|
+
'Temporary drowsiness or loss of appetite: no treatment needed.',
|
|
136
|
+
'Serious reactions such as anaphylaxis are extremely rare (fewer than 1 per million doses).',
|
|
137
|
+
]},
|
|
138
|
+
{ type: 'tip', html: 'Bringing the baby well-fed and in comfortable clothing makes the visit easier. After the vaccine, skin-to-skin contact or breastfeeding helps soothe pain and inflammation naturally.' },
|
|
139
|
+
{ type: 'summary', title: 'What to remember', items: [
|
|
140
|
+
'The AEP 2026 schedule includes 14 doses up to age 12 for complete protection.',
|
|
141
|
+
'First-year vaccines protect against up to 9 serious diseases simultaneously.',
|
|
142
|
+
'Mild side effects are normal and disappear within 1–2 days.',
|
|
143
|
+
'You can export all vaccination appointments to your phone calendar with a single click.',
|
|
144
|
+
'Always consult your paediatrician if you have questions about your region\'s schedule.',
|
|
145
|
+
]},
|
|
146
|
+
],
|
|
147
|
+
faqTitle: "Frequently asked questions",
|
|
148
|
+
faq,
|
|
149
|
+
bibliographyTitle: "References",
|
|
150
|
+
bibliography: [
|
|
151
|
+
{
|
|
152
|
+
name: 'Spanish Association of Pediatrics - Vaccination Schedule 2026',
|
|
153
|
+
url: 'https://www.aeped.es/comite-vacunas/calendario-vacunaciones',
|
|
154
|
+
},
|
|
155
|
+
{
|
|
156
|
+
name: 'Ministry of Health Spain - Vaccination Calendar',
|
|
157
|
+
url: 'https://www.sanidad.gob.es/areas/promocionPrevencion/vacunaciones/calendario/home.htm',
|
|
158
|
+
},
|
|
159
|
+
{
|
|
160
|
+
name: 'WHO - Immunization',
|
|
161
|
+
url: 'https://www.who.int/health-topics/vaccines-and-immunization',
|
|
162
|
+
},
|
|
163
|
+
{
|
|
164
|
+
name: 'CDC - Recommended Child and Adolescent Immunization Schedule',
|
|
165
|
+
url: 'https://www.cdc.gov/vaccines/schedules/hcp/imz/child-adolescent.html',
|
|
166
|
+
},
|
|
167
|
+
],
|
|
168
|
+
howTo,
|
|
169
|
+
schemas: [faqSchema as any, howToSchema as any, appSchema],
|
|
170
|
+
};
|
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
import type { VaccinationCalendarLocaleContent } from '../index';
|
|
2
|
+
import type { WithContext, FAQPage, HowTo, SoftwareApplication } from 'schema-dts';
|
|
3
|
+
|
|
4
|
+
const slug = 'calendario-vacunacion-espana-bebes';
|
|
5
|
+
const title = 'Calendario de Vacunación en España';
|
|
6
|
+
const description = 'Calcula las fechas exactas de vacunación de tu bebé según el calendario AEP 2026.';
|
|
7
|
+
const faq = [
|
|
8
|
+
{
|
|
9
|
+
question: '¿Qué es el calendario de vacunación AEP 2026?',
|
|
10
|
+
answer: 'Es el calendario de vacunaciones recomendado por la Asociación Española de Pediatría para el año 2026. Incluye todas las vacunas sistemáticas y opcionales para bebés y niños desde el nacimiento hasta los 14 años.',
|
|
11
|
+
},
|
|
12
|
+
{
|
|
13
|
+
question: '¿Es obligatorio vacunar a mi bebé según el calendario?',
|
|
14
|
+
answer: 'En España la vacunación no es legalmente obligatoria, pero sí muy recomendada. Las vacunas del calendario sistemático son gratuitas y se administran en los centros de salud públicos.',
|
|
15
|
+
},
|
|
16
|
+
{
|
|
17
|
+
question: '¿Qué pasa si nos saltamos una dosis?',
|
|
18
|
+
answer: 'Si se retrasa una dosis no es necesario empezar de cero. Tu pediatra te indicará cómo retomar el calendario desde donde se dejó. Lo importante es completar la pauta lo antes posible.',
|
|
19
|
+
},
|
|
20
|
+
{
|
|
21
|
+
question: '¿Puedo administrar varias vacunas el mismo día?',
|
|
22
|
+
answer: 'Sí, es habitual y seguro administrar varias vacunas en la misma visita. Las vacunas combinadas como la hexavalente ya protegen contra seis enfermedades en una sola inyección.',
|
|
23
|
+
},
|
|
24
|
+
{
|
|
25
|
+
question: '¿Son seguras las vacunas para los bebés prematuros?',
|
|
26
|
+
answer: 'Los bebés prematuros deben vacunarse según su edad cronológica (desde el nacimiento), no la edad corregida, salvo indicación específica del neonatólogo. Tienen prioridad por ser más vulnerables.',
|
|
27
|
+
},
|
|
28
|
+
];
|
|
29
|
+
const howTo = [
|
|
30
|
+
{
|
|
31
|
+
name: 'Introduce la fecha de nacimiento',
|
|
32
|
+
text: 'Escribe el día, mes y año de nacimiento de tu bebé en los campos correspondientes.',
|
|
33
|
+
},
|
|
34
|
+
{
|
|
35
|
+
name: 'Consulta la próxima cita',
|
|
36
|
+
text: 'La calculadora te muestra automáticamente cuándo es la próxima vacunación y qué vacunas corresponden.',
|
|
37
|
+
},
|
|
38
|
+
{
|
|
39
|
+
name: 'Revisa el calendario completo',
|
|
40
|
+
text: 'Despliega las secciones de citas pasadas y futuras para ver todo el calendario de vacunación.',
|
|
41
|
+
},
|
|
42
|
+
{
|
|
43
|
+
name: 'Exporta los recordatorios',
|
|
44
|
+
text: 'Pulsa el botón para descargar un archivo .ics con todas las citas futuras y añadirlas a tu calendario del móvil.',
|
|
45
|
+
},
|
|
46
|
+
];
|
|
47
|
+
|
|
48
|
+
const faqSchema: WithContext<FAQPage> = {
|
|
49
|
+
'@context': 'https://schema.org',
|
|
50
|
+
'@type': 'FAQPage',
|
|
51
|
+
mainEntity: faq.map((item) => ({
|
|
52
|
+
'@type': 'Question',
|
|
53
|
+
name: item.question,
|
|
54
|
+
acceptedAnswer: { '@type': 'Answer', text: item.answer },
|
|
55
|
+
})),
|
|
56
|
+
};
|
|
57
|
+
const howToSchema: WithContext<HowTo> = {
|
|
58
|
+
'@context': 'https://schema.org',
|
|
59
|
+
'@type': 'HowTo',
|
|
60
|
+
name: title,
|
|
61
|
+
description,
|
|
62
|
+
step: howTo.map((step) => ({
|
|
63
|
+
'@type': 'HowToStep',
|
|
64
|
+
name: step.name,
|
|
65
|
+
text: step.text,
|
|
66
|
+
})),
|
|
67
|
+
};
|
|
68
|
+
const appSchema: WithContext<SoftwareApplication> = {
|
|
69
|
+
'@context': 'https://schema.org',
|
|
70
|
+
'@type': 'SoftwareApplication',
|
|
71
|
+
name: title,
|
|
72
|
+
description,
|
|
73
|
+
applicationCategory: 'UtilitiesApplication',
|
|
74
|
+
operatingSystem: 'Web',
|
|
75
|
+
offers: { '@type': 'Offer', price: '0', priceCurrency: 'EUR' },
|
|
76
|
+
inLanguage: 'es',
|
|
77
|
+
};
|
|
78
|
+
|
|
79
|
+
export const content: VaccinationCalendarLocaleContent = {
|
|
80
|
+
slug,
|
|
81
|
+
title,
|
|
82
|
+
description,
|
|
83
|
+
ui: {
|
|
84
|
+
labelBirthDate: 'Fecha de nacimiento del bebé',
|
|
85
|
+
placeholderDD: 'DD',
|
|
86
|
+
placeholderMM: 'MM',
|
|
87
|
+
placeholderAAAA: 'AAAA',
|
|
88
|
+
emptyMsg: 'Introduce el día, mes y año de nacimiento de tu bebé para ver el calendario de vacunación.',
|
|
89
|
+
invalidMsg: 'La fecha introducida no es válida.',
|
|
90
|
+
futureMsg: 'Un bebé no puede haber nacido en el futuro.',
|
|
91
|
+
labelNextAppointment: 'Próxima cita calculada',
|
|
92
|
+
btnAddReminder: 'Añadir recordatorio al móvil',
|
|
93
|
+
btnToday: '¡Es hoy! No olvides la cartilla',
|
|
94
|
+
labelPassed: 'Citas pasadas',
|
|
95
|
+
labelFuture: 'Calendario futuro',
|
|
96
|
+
labelStatusOk: 'OK',
|
|
97
|
+
labelStatusPending: 'PEND.',
|
|
98
|
+
labelSource: 'Fuente: AEP 2026',
|
|
99
|
+
labelShare: 'Compartir estas fechas',
|
|
100
|
+
faqTitle: 'Preguntas frecuentes',
|
|
101
|
+
bibliographyTitle: 'Referencias',
|
|
102
|
+
},
|
|
103
|
+
seo: [
|
|
104
|
+
{ type: 'title', text: 'Calculadora de Vacunas: ¿Cuándo le toca la próxima a mi hijo?', level: 2 },
|
|
105
|
+
{ type: 'stats', columns: 4, items: [
|
|
106
|
+
{ value: '+95%', label: 'Efectividad' },
|
|
107
|
+
{ value: '14', label: 'Dosis totales' },
|
|
108
|
+
{ value: 'Público', label: 'Costo AEP' },
|
|
109
|
+
{ value: 'Alta', label: 'Seguridad' },
|
|
110
|
+
]},
|
|
111
|
+
{ type: 'tip', html: 'El calendario AEP 2026 incluye novedades importantes como la extensión de la vacuna frente al meningococo B y la actualización de las recomendaciones para el VPH en ambos sexos desde los 12 años.' },
|
|
112
|
+
{ type: 'title', text: 'Novedades del Calendario AEP 2026', level: 3 },
|
|
113
|
+
{ type: 'list', items: [
|
|
114
|
+
'Meningococo B (Bexsero): pauta 2+1 consolidada con dosis a los 2, 4 y 12 meses.',
|
|
115
|
+
'VPH extendido a todos los adolescentes a partir de los 12 años, independientemente del sexo.',
|
|
116
|
+
'Neumococo: recomendación actualizada con VCN15 o VCN20 según disponibilidad regional.',
|
|
117
|
+
'Rotavirus: vacuna oral incluida en el calendario sistemático en todas las comunidades.',
|
|
118
|
+
'Tdpa: refuerzo recomendado a los 6 y 12 años para mantener la inmunidad frente a tosferina.',
|
|
119
|
+
]},
|
|
120
|
+
{ type: 'title', text: 'Diferencias entre comunidades autónomas', level: 3 },
|
|
121
|
+
{ type: 'list', items: [
|
|
122
|
+
'Algunas comunidades incluyen vacunas adicionales no recogidas en el calendario nacional.',
|
|
123
|
+
'La financiación del rotavirus varía según la comunidad, aunque la tendencia es la cobertura universal.',
|
|
124
|
+
'El meningococo ACWY puede administrarse en edades ligeramente distintas según el protocolo autonómico.',
|
|
125
|
+
'Consulta siempre con tu pediatra o el centro de salud de tu comunidad para confirmar el calendario vigente.',
|
|
126
|
+
]},
|
|
127
|
+
{ type: 'title', text: 'Cómo exportar el calendario a tu móvil', level: 3 },
|
|
128
|
+
{ type: 'list', items: [
|
|
129
|
+
'Introduce la fecha de nacimiento de tu bebé en la calculadora.',
|
|
130
|
+
'Pulsa el botón "Añadir recordatorio al móvil" para descargar el archivo .ics.',
|
|
131
|
+
'Abre el archivo con tu aplicación de calendario (Google Calendar, Apple Calendar, etc.).',
|
|
132
|
+
'Todos los eventos de vacunación quedarán guardados con la fecha y vacunas correspondientes.',
|
|
133
|
+
]},
|
|
134
|
+
{ type: 'title', text: 'Efectos secundarios habituales', level: 3 },
|
|
135
|
+
{ type: 'list', items: [
|
|
136
|
+
'Rojez o hinchazón en el lugar de la inyección: desaparece en 1-2 días.',
|
|
137
|
+
'Fiebre leve (37.5–38.5 °C): normal en las primeras 24-48 horas.',
|
|
138
|
+
'Irritabilidad o llanto: frecuente en bebés tras las primeras dosis.',
|
|
139
|
+
'Somnolencia o falta de apetito transitoria: no precisa tratamiento.',
|
|
140
|
+
'Reacciones graves como anafilaxia son extremadamente raras (menos de 1 por millón de dosis).',
|
|
141
|
+
]},
|
|
142
|
+
{ type: 'tip', html: 'Llevar al bebé bien alimentado y con ropa cómoda facilita la visita. Tras la vacuna, el contacto piel con piel o la lactancia materna ayudan a calmar el dolor y la inflamación de forma natural.' },
|
|
143
|
+
{ type: 'summary', title: 'Lo que debes recordar', items: [
|
|
144
|
+
'El calendario AEP 2026 incluye 14 dosis hasta los 12 años para una protección completa.',
|
|
145
|
+
'Las vacunas del primer año protegen contra hasta 9 enfermedades graves de forma simultánea.',
|
|
146
|
+
'Los efectos secundarios leves son normales y desaparecen en 1-2 días.',
|
|
147
|
+
'Puedes exportar todas las citas de vacunación al calendario de tu móvil con un solo clic.',
|
|
148
|
+
'Consulta siempre a tu pediatra si tienes dudas sobre el calendario de tu comunidad.',
|
|
149
|
+
]},
|
|
150
|
+
],
|
|
151
|
+
faqTitle: "Preguntas frecuentes",
|
|
152
|
+
faq,
|
|
153
|
+
bibliographyTitle: "Referencias",
|
|
154
|
+
bibliography: [
|
|
155
|
+
{
|
|
156
|
+
name: 'Asociación Española de Pediatría - Calendario de Vacunaciones 2026',
|
|
157
|
+
url: 'https://www.aeped.es/comite-vacunas/calendario-vacunaciones',
|
|
158
|
+
},
|
|
159
|
+
{
|
|
160
|
+
name: 'Ministerio de Sanidad - Calendario de Vacunación',
|
|
161
|
+
url: 'https://www.sanidad.gob.es/areas/promocionPrevencion/vacunaciones/calendario/home.htm',
|
|
162
|
+
},
|
|
163
|
+
{
|
|
164
|
+
name: 'OMS - Inmunización',
|
|
165
|
+
url: 'https://www.who.int/es/health-topics/vaccines-and-immunization',
|
|
166
|
+
},
|
|
167
|
+
{
|
|
168
|
+
name: 'CDC - Recommended Child and Adolescent Immunization Schedule',
|
|
169
|
+
url: 'https://www.cdc.gov/vaccines/schedules/hcp/imz/child-adolescent.html',
|
|
170
|
+
},
|
|
171
|
+
],
|
|
172
|
+
howTo,
|
|
173
|
+
schemas: [faqSchema as any, howToSchema as any, appSchema],
|
|
174
|
+
};
|