hvp-shared 13.26.0 → 13.35.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/dist/constants/google-calendar.constants.d.ts +80 -0
- package/dist/constants/google-calendar.constants.js +133 -6
- package/dist/constants/google-calendar.constants.test.js +151 -0
- package/dist/constants/index.d.ts +1 -0
- package/dist/constants/index.js +1 -0
- package/dist/constants/shift-checklist.enums.d.ts +83 -0
- package/dist/constants/shift-checklist.enums.js +128 -0
- package/dist/contracts/google-calendar/requests.d.ts +12 -0
- package/dist/contracts/index.d.ts +1 -0
- package/dist/contracts/index.js +1 -0
- package/dist/contracts/shift-checklist/index.d.ts +3 -0
- package/dist/contracts/shift-checklist/index.js +19 -0
- package/dist/contracts/shift-checklist/requests.d.ts +145 -0
- package/dist/contracts/shift-checklist/requests.js +11 -0
- package/dist/contracts/shift-checklist/responses.d.ts +100 -0
- package/dist/contracts/shift-checklist/responses.js +7 -0
- package/dist/contracts/shift-checklist/types.d.ts +86 -0
- package/dist/contracts/shift-checklist/types.js +8 -0
- package/dist/validation/index.d.ts +1 -0
- package/dist/validation/index.js +1 -0
- package/dist/validation/shift-checklist.validation.d.ts +12 -0
- package/dist/validation/shift-checklist.validation.js +28 -0
- package/dist/validation/shift-checklist.validation.test.d.ts +1 -0
- package/dist/validation/shift-checklist.validation.test.js +37 -0
- package/package.json +1 -1
|
@@ -166,6 +166,79 @@ export declare const SCHEDULER_WITH_DATE_REGEX: RegExp;
|
|
|
166
166
|
* date.
|
|
167
167
|
*/
|
|
168
168
|
export declare const SCHEDULER_DATE_FALLBACK_REGEX: RegExp;
|
|
169
|
+
/**
|
|
170
|
+
* Version tag stored in `extendedProperties.private.descriptionFormat` on
|
|
171
|
+
* events created with the labeled format. Lets analytics / future migration
|
|
172
|
+
* scripts distinguish labeled from positional events.
|
|
173
|
+
*/
|
|
174
|
+
export declare const LABELED_DESCRIPTION_FORMAT_VERSION = "labeled-v1";
|
|
175
|
+
/**
|
|
176
|
+
* Per-field spec for the labeled description format.
|
|
177
|
+
*
|
|
178
|
+
* Each field has:
|
|
179
|
+
* - `label`: canonical label written when building (e.g., "Tutor").
|
|
180
|
+
* - `regex`: extraction regex with capturing groups. Group 1 = value.
|
|
181
|
+
* For `tutor`, group 2 captures the optional QVET id digits.
|
|
182
|
+
*
|
|
183
|
+
* Order-independent: labels anchor each field, so the format tolerates
|
|
184
|
+
* reordering or manual edits as long as the labels are present.
|
|
185
|
+
*
|
|
186
|
+
* Tolerates: `Tutor:`, `tutor :`, `TUTOR: `, trailing whitespace.
|
|
187
|
+
*/
|
|
188
|
+
export declare const LABELED_DESCRIPTION_FIELDS: {
|
|
189
|
+
readonly tutor: {
|
|
190
|
+
readonly label: "Tutor";
|
|
191
|
+
readonly regex: RegExp;
|
|
192
|
+
};
|
|
193
|
+
readonly phones: {
|
|
194
|
+
readonly label: "Teléfonos";
|
|
195
|
+
readonly regex: RegExp;
|
|
196
|
+
};
|
|
197
|
+
readonly pet: {
|
|
198
|
+
readonly label: "Mascota";
|
|
199
|
+
readonly regex: RegExp;
|
|
200
|
+
};
|
|
201
|
+
readonly breed: {
|
|
202
|
+
readonly label: "Raza";
|
|
203
|
+
readonly regex: RegExp;
|
|
204
|
+
};
|
|
205
|
+
readonly age: {
|
|
206
|
+
readonly label: "Edad";
|
|
207
|
+
readonly regex: RegExp;
|
|
208
|
+
};
|
|
209
|
+
readonly motivo: {
|
|
210
|
+
readonly label: "Motivo";
|
|
211
|
+
readonly regex: RegExp;
|
|
212
|
+
};
|
|
213
|
+
readonly discount: {
|
|
214
|
+
readonly label: "Descuento";
|
|
215
|
+
readonly regex: RegExp;
|
|
216
|
+
};
|
|
217
|
+
};
|
|
218
|
+
export type LabeledDescriptionField = keyof typeof LABELED_DESCRIPTION_FIELDS;
|
|
219
|
+
/**
|
|
220
|
+
* Returns "labeled" if any known field label is present at the start of a line
|
|
221
|
+
* in the description; otherwise "positional" (legacy format).
|
|
222
|
+
*/
|
|
223
|
+
export declare function detectDescriptionFormat(description: string): "labeled" | "positional";
|
|
224
|
+
/**
|
|
225
|
+
* Extract a single-line labeled field. Returns trimmed value or null.
|
|
226
|
+
*
|
|
227
|
+
* For `motivo` (which may span multiple lines), use {@link extractMotivoLabeled}.
|
|
228
|
+
* For the tutor's QVET id, use {@link extractTutorQvetIdLabeled}.
|
|
229
|
+
*/
|
|
230
|
+
export declare function extractLabeledField(description: string, field: Exclude<LabeledDescriptionField, "motivo">): string | null;
|
|
231
|
+
/**
|
|
232
|
+
* Extract the QVET client id from the `Tutor: NAME (Q12345)` line. Returns the
|
|
233
|
+
* numeric portion as a string (e.g., "12345") or null.
|
|
234
|
+
*/
|
|
235
|
+
export declare function extractTutorQvetIdLabeled(description: string): string | null;
|
|
236
|
+
/**
|
|
237
|
+
* Extract the motivo block which may span multiple lines. Starts at the
|
|
238
|
+
* `Motivo:` line and collects subsequent lines until another labeled field,
|
|
239
|
+
* an AGENDÓ/REAGENDÓ line, the HVP marker, or end of description.
|
|
240
|
+
*/
|
|
241
|
+
export declare function extractMotivoLabeled(description: string): string | null;
|
|
169
242
|
/**
|
|
170
243
|
* Captures pet age expressions in Spanish from descriptions.
|
|
171
244
|
*
|
|
@@ -197,4 +270,11 @@ export declare const HVP_EVENT_PROP_KEYS: {
|
|
|
197
270
|
readonly isPreferredVet: "isPreferredVet";
|
|
198
271
|
readonly source: "source";
|
|
199
272
|
readonly standardVersion: "standardVersion";
|
|
273
|
+
/**
|
|
274
|
+
* Marks the description-builder format version used at create/update time
|
|
275
|
+
* (e.g., "labeled-v1"). Informational only — the parser detects format from
|
|
276
|
+
* content, not from this prop. Useful for analytics ("% of events on labeled
|
|
277
|
+
* format") and future migration scripts.
|
|
278
|
+
*/
|
|
279
|
+
readonly descriptionFormat: "descriptionFormat";
|
|
200
280
|
};
|
|
@@ -13,7 +13,11 @@
|
|
|
13
13
|
* runs remain reproducible.
|
|
14
14
|
*/
|
|
15
15
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
16
|
-
exports.HVP_EVENT_PROP_KEYS = exports.KNOWN_BREEDS = exports.PET_AGE_REGEX = exports.SCHEDULER_DATE_FALLBACK_REGEX = exports.SCHEDULER_WITH_DATE_REGEX = exports.SCHEDULER_REGEX = exports.PHONE_REGEX = exports.QVET_ID_REGEX = exports.CANCELLED_TITLE_REGEX = exports.REMINDER_TITLE_REGEX = exports.PREFERRED_VET_REGEX = exports.EXCLUDED_TITLE_PREFIXES = exports.SERVICE_CODE_SYNONYMS = exports.CANONICAL_SERVICE_CODES = exports.EXCLUDED_COLOR_IDS = exports.NO_SHOW_COLOR_ID = exports.CANCELLED_COLOR_ID = exports.COLOR_ID_TO_BRANCH = exports.BRANCH_COLOR_IDS = exports.STANDARD_V1 = exports.STANDARD_V3 = exports.STANDARD_V2 = void 0;
|
|
16
|
+
exports.HVP_EVENT_PROP_KEYS = exports.KNOWN_BREEDS = exports.PET_AGE_REGEX = exports.LABELED_DESCRIPTION_FIELDS = exports.LABELED_DESCRIPTION_FORMAT_VERSION = exports.SCHEDULER_DATE_FALLBACK_REGEX = exports.SCHEDULER_WITH_DATE_REGEX = exports.SCHEDULER_REGEX = exports.PHONE_REGEX = exports.QVET_ID_REGEX = exports.CANCELLED_TITLE_REGEX = exports.REMINDER_TITLE_REGEX = exports.PREFERRED_VET_REGEX = exports.EXCLUDED_TITLE_PREFIXES = exports.SERVICE_CODE_SYNONYMS = exports.CANONICAL_SERVICE_CODES = exports.EXCLUDED_COLOR_IDS = exports.NO_SHOW_COLOR_ID = exports.CANCELLED_COLOR_ID = exports.COLOR_ID_TO_BRANCH = exports.BRANCH_COLOR_IDS = exports.STANDARD_V1 = exports.STANDARD_V3 = exports.STANDARD_V2 = void 0;
|
|
17
|
+
exports.detectDescriptionFormat = detectDescriptionFormat;
|
|
18
|
+
exports.extractLabeledField = extractLabeledField;
|
|
19
|
+
exports.extractTutorQvetIdLabeled = extractTutorQvetIdLabeled;
|
|
20
|
+
exports.extractMotivoLabeled = extractMotivoLabeled;
|
|
17
21
|
// ─── Standard v1 ─────────────────────────────────────────────────────────────
|
|
18
22
|
/**
|
|
19
23
|
* Multi-factor appointment standard v2.
|
|
@@ -147,31 +151,31 @@ exports.STANDARD_V3 = {
|
|
|
147
151
|
id: "context",
|
|
148
152
|
weight: 8,
|
|
149
153
|
label: "Motivo / contexto",
|
|
150
|
-
description: '
|
|
154
|
+
description: 'Eventos en formato labeled: cuenta el bloque "Motivo:" (>20 caracteres). Eventos legacy posicionales: la descripción tiene contexto clínico relevante más allá del nombre y teléfono — mínimo 20 caracteres tras descontar nombre, teléfono y Q{id}.',
|
|
151
155
|
},
|
|
152
156
|
{
|
|
153
157
|
id: "owner_name",
|
|
154
158
|
weight: 8,
|
|
155
159
|
label: "Nombre del tutor",
|
|
156
|
-
description: 'Nombre del cliente/tutor. Auto-pasa si hay Q{id} en la descripción (cliente linkeado a QVET) o si el teléfono matchea un cliente QVET existente.
|
|
160
|
+
description: 'Nombre del cliente/tutor. Auto-pasa si hay Q{id} en la descripción (cliente linkeado a QVET) o si el teléfono matchea un cliente QVET existente. Eventos labeled: lo lee de la línea "Tutor:". Eventos legacy: lo busca en la primera línea con 2+ palabras de letras (o el patrón "NAME | PETS | Q{id}").',
|
|
157
161
|
},
|
|
158
162
|
{
|
|
159
163
|
id: "owner_phone",
|
|
160
164
|
weight: 8,
|
|
161
165
|
label: "Teléfono del tutor",
|
|
162
|
-
description: 'Teléfono del tutor en cualquier formato común (con o sin +52, con o sin separadores). Auto-pasa si hay Q{id} (sabemos el teléfono del registro QVET).
|
|
166
|
+
description: 'Teléfono del tutor en cualquier formato común (con o sin +52, con o sin separadores). Eventos labeled: lo lee de la línea "Teléfonos:". Eventos legacy: lo busca en cualquier línea de la descripción. Auto-pasa si hay Q{id} (sabemos el teléfono del registro QVET).',
|
|
163
167
|
},
|
|
164
168
|
{
|
|
165
169
|
id: "pet_age",
|
|
166
170
|
weight: 4,
|
|
167
171
|
label: "Edad de la mascota",
|
|
168
|
-
description: 'Edad anotada en la descripción: "4 meses", "1 año y 4 meses", "8 semanas", "2 años". Importante para vacunación, dosis y diagnóstico.',
|
|
172
|
+
description: 'Edad anotada en la descripción. Eventos labeled: línea "Edad:". Eventos legacy: detección por patrón ("4 meses", "1 año y 4 meses", "8 semanas", "2 años"). Importante para vacunación, dosis y diagnóstico.',
|
|
169
173
|
},
|
|
170
174
|
{
|
|
171
175
|
id: "pet_breed",
|
|
172
176
|
weight: 4,
|
|
173
177
|
label: "Raza de la mascota",
|
|
174
|
-
description: 'Raza detectada en la descripción contra un diccionario de razas comunes
|
|
178
|
+
description: 'Raza detectada en la descripción. Eventos labeled: línea "Raza:". Eventos legacy: match contra un diccionario de razas comunes (Yorkie, Shih Tzu, Cocker Spaniel, French Bulldog, Chihuahua, Schnauzer, Golden Retriever, Labrador, Persa, Siamés, mestizo, etc.).',
|
|
175
179
|
},
|
|
176
180
|
{
|
|
177
181
|
id: "discount_format",
|
|
@@ -433,6 +437,122 @@ exports.SCHEDULER_WITH_DATE_REGEX = /\b(?:AG[.:]?|AGE[.:]|AGEND[OÓ][.:]?|AGENG[
|
|
|
433
437
|
* date.
|
|
434
438
|
*/
|
|
435
439
|
exports.SCHEDULER_DATE_FALLBACK_REGEX = /\b(\d{1,2}[./\-]\d{1,2}(?:[./\-]\d{2,4})?|\d{1,2}[./\-](?:ENE|FEB|MAR|ABR|MAY|JUN|JUL|AGO|SEP|OCT|NOV|DIC|ENERO|FEBRERO|MARZO|ABRIL|MAYO|JUNIO|JULIO|AGOSTO|SEPTIEMBRE|OCTUBRE|NOVIEMBRE|DICIEMBRE)(?:[./\-]\d{2,4})?)\s+([A-Z]{2,4})\b/i;
|
|
440
|
+
// ─── Labeled description format (06/2026) ───────────────────────────────────
|
|
441
|
+
/**
|
|
442
|
+
* Version tag stored in `extendedProperties.private.descriptionFormat` on
|
|
443
|
+
* events created with the labeled format. Lets analytics / future migration
|
|
444
|
+
* scripts distinguish labeled from positional events.
|
|
445
|
+
*/
|
|
446
|
+
exports.LABELED_DESCRIPTION_FORMAT_VERSION = "labeled-v1";
|
|
447
|
+
/**
|
|
448
|
+
* Per-field spec for the labeled description format.
|
|
449
|
+
*
|
|
450
|
+
* Each field has:
|
|
451
|
+
* - `label`: canonical label written when building (e.g., "Tutor").
|
|
452
|
+
* - `regex`: extraction regex with capturing groups. Group 1 = value.
|
|
453
|
+
* For `tutor`, group 2 captures the optional QVET id digits.
|
|
454
|
+
*
|
|
455
|
+
* Order-independent: labels anchor each field, so the format tolerates
|
|
456
|
+
* reordering or manual edits as long as the labels are present.
|
|
457
|
+
*
|
|
458
|
+
* Tolerates: `Tutor:`, `tutor :`, `TUTOR: `, trailing whitespace.
|
|
459
|
+
*/
|
|
460
|
+
exports.LABELED_DESCRIPTION_FIELDS = {
|
|
461
|
+
tutor: {
|
|
462
|
+
label: "Tutor",
|
|
463
|
+
regex: /^\s*tutor\s*:\s*(.+?)(?:\s*\(\s*Q(\d{4,7})\s*\))?\s*$/im,
|
|
464
|
+
},
|
|
465
|
+
phones: {
|
|
466
|
+
label: "Teléfonos",
|
|
467
|
+
regex: /^\s*tel[eé]fonos?\s*:\s*(.+?)\s*$/im,
|
|
468
|
+
},
|
|
469
|
+
pet: {
|
|
470
|
+
label: "Mascota",
|
|
471
|
+
regex: /^\s*mascota\s*:\s*(.+?)\s*$/im,
|
|
472
|
+
},
|
|
473
|
+
breed: {
|
|
474
|
+
label: "Raza",
|
|
475
|
+
regex: /^\s*raza\s*:\s*(.+?)\s*$/im,
|
|
476
|
+
},
|
|
477
|
+
age: {
|
|
478
|
+
label: "Edad",
|
|
479
|
+
regex: /^\s*edad\s*:\s*(.+?)\s*$/im,
|
|
480
|
+
},
|
|
481
|
+
motivo: {
|
|
482
|
+
label: "Motivo",
|
|
483
|
+
// Motivo is multi-line; extraction handled by extractMotivoLabeled().
|
|
484
|
+
// Regex below only detects presence of the label.
|
|
485
|
+
regex: /^\s*motivo\s*:/im,
|
|
486
|
+
},
|
|
487
|
+
discount: {
|
|
488
|
+
label: "Descuento",
|
|
489
|
+
regex: /^\s*descuento\s*:\s*(.+?)\s*$/im,
|
|
490
|
+
},
|
|
491
|
+
};
|
|
492
|
+
/**
|
|
493
|
+
* Matches a line that starts a new labeled field or the AGENDÓ/REAGENDÓ block
|
|
494
|
+
* or the HVP marker. Used to bound multi-line `Motivo:` capture.
|
|
495
|
+
*/
|
|
496
|
+
const LABELED_BOUNDARY_REGEX = /^\s*(?:tutor|tel[eé]fonos?|mascota|raza|edad|descuento|motivo|agend|reagend|—)/i;
|
|
497
|
+
/**
|
|
498
|
+
* Returns "labeled" if any known field label is present at the start of a line
|
|
499
|
+
* in the description; otherwise "positional" (legacy format).
|
|
500
|
+
*/
|
|
501
|
+
function detectDescriptionFormat(description) {
|
|
502
|
+
if (!description)
|
|
503
|
+
return "positional";
|
|
504
|
+
for (const field of Object.values(exports.LABELED_DESCRIPTION_FIELDS)) {
|
|
505
|
+
if (field.regex.test(description))
|
|
506
|
+
return "labeled";
|
|
507
|
+
}
|
|
508
|
+
return "positional";
|
|
509
|
+
}
|
|
510
|
+
/**
|
|
511
|
+
* Extract a single-line labeled field. Returns trimmed value or null.
|
|
512
|
+
*
|
|
513
|
+
* For `motivo` (which may span multiple lines), use {@link extractMotivoLabeled}.
|
|
514
|
+
* For the tutor's QVET id, use {@link extractTutorQvetIdLabeled}.
|
|
515
|
+
*/
|
|
516
|
+
function extractLabeledField(description, field) {
|
|
517
|
+
if (!description)
|
|
518
|
+
return null;
|
|
519
|
+
const match = description.match(exports.LABELED_DESCRIPTION_FIELDS[field].regex);
|
|
520
|
+
const value = match?.[1]?.trim();
|
|
521
|
+
return value && value.length > 0 ? value : null;
|
|
522
|
+
}
|
|
523
|
+
/**
|
|
524
|
+
* Extract the QVET client id from the `Tutor: NAME (Q12345)` line. Returns the
|
|
525
|
+
* numeric portion as a string (e.g., "12345") or null.
|
|
526
|
+
*/
|
|
527
|
+
function extractTutorQvetIdLabeled(description) {
|
|
528
|
+
if (!description)
|
|
529
|
+
return null;
|
|
530
|
+
const match = description.match(exports.LABELED_DESCRIPTION_FIELDS.tutor.regex);
|
|
531
|
+
const id = match?.[2]?.trim();
|
|
532
|
+
return id && id.length > 0 ? id : null;
|
|
533
|
+
}
|
|
534
|
+
/**
|
|
535
|
+
* Extract the motivo block which may span multiple lines. Starts at the
|
|
536
|
+
* `Motivo:` line and collects subsequent lines until another labeled field,
|
|
537
|
+
* an AGENDÓ/REAGENDÓ line, the HVP marker, or end of description.
|
|
538
|
+
*/
|
|
539
|
+
function extractMotivoLabeled(description) {
|
|
540
|
+
if (!description)
|
|
541
|
+
return null;
|
|
542
|
+
const lines = description.split("\n");
|
|
543
|
+
const startIdx = lines.findIndex((l) => /^\s*motivo\s*:/i.test(l));
|
|
544
|
+
if (startIdx === -1)
|
|
545
|
+
return null;
|
|
546
|
+
const firstLine = lines[startIdx].replace(/^\s*motivo\s*:\s*/i, "");
|
|
547
|
+
const collected = [firstLine];
|
|
548
|
+
for (let i = startIdx + 1; i < lines.length; i++) {
|
|
549
|
+
if (LABELED_BOUNDARY_REGEX.test(lines[i]))
|
|
550
|
+
break;
|
|
551
|
+
collected.push(lines[i]);
|
|
552
|
+
}
|
|
553
|
+
const value = collected.join("\n").trim();
|
|
554
|
+
return value.length > 0 ? value : null;
|
|
555
|
+
}
|
|
436
556
|
// ─── Pet age detection ───────────────────────────────────────────────────────
|
|
437
557
|
/**
|
|
438
558
|
* Captures pet age expressions in Spanish from descriptions.
|
|
@@ -541,4 +661,11 @@ exports.HVP_EVENT_PROP_KEYS = {
|
|
|
541
661
|
isPreferredVet: "isPreferredVet",
|
|
542
662
|
source: "source", // "hvp"
|
|
543
663
|
standardVersion: "standardVersion",
|
|
664
|
+
/**
|
|
665
|
+
* Marks the description-builder format version used at create/update time
|
|
666
|
+
* (e.g., "labeled-v1"). Informational only — the parser detects format from
|
|
667
|
+
* content, not from this prop. Useful for analytics ("% of events on labeled
|
|
668
|
+
* format") and future migration scripts.
|
|
669
|
+
*/
|
|
670
|
+
descriptionFormat: "descriptionFormat",
|
|
544
671
|
};
|
|
@@ -207,6 +207,157 @@ describe("PET_AGE_REGEX", () => {
|
|
|
207
207
|
expect("Cytopoint 30MG".match(google_calendar_constants_1.PET_AGE_REGEX)).toBeNull();
|
|
208
208
|
});
|
|
209
209
|
});
|
|
210
|
+
describe("Labeled description format (06/2026)", () => {
|
|
211
|
+
const labeledSample = [
|
|
212
|
+
"Tutor: OMAR GOMEZ (Q123456)",
|
|
213
|
+
"Teléfonos: 9992920692 / 9991234567",
|
|
214
|
+
"Mascota: MILO",
|
|
215
|
+
"Raza: Shih Tzu",
|
|
216
|
+
"Edad: 1 año",
|
|
217
|
+
"Motivo: Consulta de seguimiento por dermatitis",
|
|
218
|
+
"Descuento: 15% recordatorio",
|
|
219
|
+
"AGENDÓ JG 14/06/2026",
|
|
220
|
+
"— creada desde HVP app · [HVP]",
|
|
221
|
+
].join("\n");
|
|
222
|
+
const positionalSample = [
|
|
223
|
+
"OMAR GOMEZ | MILO | Q123456",
|
|
224
|
+
"9992920692",
|
|
225
|
+
"Consulta de seguimiento por dermatitis",
|
|
226
|
+
"Shih Tzu, 1 año",
|
|
227
|
+
"15% recordatorio",
|
|
228
|
+
"AGENDÓ JG 14/06/2026",
|
|
229
|
+
"— creada desde HVP app · [HVP]",
|
|
230
|
+
].join("\n");
|
|
231
|
+
describe("LABELED_DESCRIPTION_FORMAT_VERSION", () => {
|
|
232
|
+
it("is a stable string", () => {
|
|
233
|
+
expect(google_calendar_constants_1.LABELED_DESCRIPTION_FORMAT_VERSION).toBe("labeled-v1");
|
|
234
|
+
});
|
|
235
|
+
});
|
|
236
|
+
describe("LABELED_DESCRIPTION_FIELDS", () => {
|
|
237
|
+
it("has all expected fields with labels", () => {
|
|
238
|
+
const labels = Object.fromEntries(Object.entries(google_calendar_constants_1.LABELED_DESCRIPTION_FIELDS).map(([k, v]) => [k, v.label]));
|
|
239
|
+
expect(labels).toEqual({
|
|
240
|
+
tutor: "Tutor",
|
|
241
|
+
phones: "Teléfonos",
|
|
242
|
+
pet: "Mascota",
|
|
243
|
+
breed: "Raza",
|
|
244
|
+
age: "Edad",
|
|
245
|
+
motivo: "Motivo",
|
|
246
|
+
discount: "Descuento",
|
|
247
|
+
});
|
|
248
|
+
});
|
|
249
|
+
});
|
|
250
|
+
describe("detectDescriptionFormat", () => {
|
|
251
|
+
it("identifies a labeled description", () => {
|
|
252
|
+
expect((0, google_calendar_constants_1.detectDescriptionFormat)(labeledSample)).toBe("labeled");
|
|
253
|
+
});
|
|
254
|
+
it("identifies a positional (legacy) description", () => {
|
|
255
|
+
expect((0, google_calendar_constants_1.detectDescriptionFormat)(positionalSample)).toBe("positional");
|
|
256
|
+
});
|
|
257
|
+
it("treats empty / undefined as positional", () => {
|
|
258
|
+
expect((0, google_calendar_constants_1.detectDescriptionFormat)("")).toBe("positional");
|
|
259
|
+
expect((0, google_calendar_constants_1.detectDescriptionFormat)(undefined)).toBe("positional");
|
|
260
|
+
});
|
|
261
|
+
it("detects when only one label is present", () => {
|
|
262
|
+
expect((0, google_calendar_constants_1.detectDescriptionFormat)("Tutor: NORA\n9999999999")).toBe("labeled");
|
|
263
|
+
});
|
|
264
|
+
it("is case-insensitive on the label", () => {
|
|
265
|
+
expect((0, google_calendar_constants_1.detectDescriptionFormat)("tutor: NORA\n9999999999")).toBe("labeled");
|
|
266
|
+
expect((0, google_calendar_constants_1.detectDescriptionFormat)("TUTOR: NORA\n9999999999")).toBe("labeled");
|
|
267
|
+
});
|
|
268
|
+
});
|
|
269
|
+
describe("extractLabeledField", () => {
|
|
270
|
+
it("extracts the tutor name (without QVET id)", () => {
|
|
271
|
+
expect((0, google_calendar_constants_1.extractLabeledField)(labeledSample, "tutor")).toBe("OMAR GOMEZ");
|
|
272
|
+
});
|
|
273
|
+
it("extracts the tutor name when no QVET id is present", () => {
|
|
274
|
+
const desc = "Tutor: NORA DIRCIO\nMascota: KIRI";
|
|
275
|
+
expect((0, google_calendar_constants_1.extractLabeledField)(desc, "tutor")).toBe("NORA DIRCIO");
|
|
276
|
+
});
|
|
277
|
+
it("extracts phones, pet, breed, age, discount", () => {
|
|
278
|
+
expect((0, google_calendar_constants_1.extractLabeledField)(labeledSample, "phones")).toBe("9992920692 / 9991234567");
|
|
279
|
+
expect((0, google_calendar_constants_1.extractLabeledField)(labeledSample, "pet")).toBe("MILO");
|
|
280
|
+
expect((0, google_calendar_constants_1.extractLabeledField)(labeledSample, "breed")).toBe("Shih Tzu");
|
|
281
|
+
expect((0, google_calendar_constants_1.extractLabeledField)(labeledSample, "age")).toBe("1 año");
|
|
282
|
+
expect((0, google_calendar_constants_1.extractLabeledField)(labeledSample, "discount")).toBe("15% recordatorio");
|
|
283
|
+
});
|
|
284
|
+
it("returns null when the label is missing", () => {
|
|
285
|
+
const desc = "Tutor: OMAR\nMascota: MILO";
|
|
286
|
+
expect((0, google_calendar_constants_1.extractLabeledField)(desc, "discount")).toBeNull();
|
|
287
|
+
expect((0, google_calendar_constants_1.extractLabeledField)(desc, "breed")).toBeNull();
|
|
288
|
+
});
|
|
289
|
+
it("tolerates trailing whitespace and extra spaces around the colon", () => {
|
|
290
|
+
const desc = "Tutor : OMAR GOMEZ \nMascota:MILO";
|
|
291
|
+
expect((0, google_calendar_constants_1.extractLabeledField)(desc, "tutor")).toBe("OMAR GOMEZ");
|
|
292
|
+
expect((0, google_calendar_constants_1.extractLabeledField)(desc, "pet")).toBe("MILO");
|
|
293
|
+
});
|
|
294
|
+
it("is order-independent", () => {
|
|
295
|
+
const reordered = [
|
|
296
|
+
"Mascota: MILO",
|
|
297
|
+
"Tutor: OMAR GOMEZ (Q123456)",
|
|
298
|
+
"Edad: 1 año",
|
|
299
|
+
].join("\n");
|
|
300
|
+
expect((0, google_calendar_constants_1.extractLabeledField)(reordered, "tutor")).toBe("OMAR GOMEZ");
|
|
301
|
+
expect((0, google_calendar_constants_1.extractLabeledField)(reordered, "pet")).toBe("MILO");
|
|
302
|
+
expect((0, google_calendar_constants_1.extractLabeledField)(reordered, "age")).toBe("1 año");
|
|
303
|
+
});
|
|
304
|
+
it("returns null for empty description", () => {
|
|
305
|
+
expect((0, google_calendar_constants_1.extractLabeledField)("", "tutor")).toBeNull();
|
|
306
|
+
});
|
|
307
|
+
});
|
|
308
|
+
describe("extractTutorQvetIdLabeled", () => {
|
|
309
|
+
it("extracts the QVET id from the tutor line", () => {
|
|
310
|
+
expect((0, google_calendar_constants_1.extractTutorQvetIdLabeled)(labeledSample)).toBe("123456");
|
|
311
|
+
});
|
|
312
|
+
it("returns null when no QVET id is present", () => {
|
|
313
|
+
const desc = "Tutor: NORA DIRCIO\nMascota: KIRI";
|
|
314
|
+
expect((0, google_calendar_constants_1.extractTutorQvetIdLabeled)(desc)).toBeNull();
|
|
315
|
+
});
|
|
316
|
+
it("tolerates extra whitespace inside the parens", () => {
|
|
317
|
+
const desc = "Tutor: NORA DIRCIO ( Q987654 )";
|
|
318
|
+
expect((0, google_calendar_constants_1.extractTutorQvetIdLabeled)(desc)).toBe("987654");
|
|
319
|
+
});
|
|
320
|
+
it("returns null when the tutor line is missing entirely", () => {
|
|
321
|
+
expect((0, google_calendar_constants_1.extractTutorQvetIdLabeled)("Mascota: KIRI")).toBeNull();
|
|
322
|
+
});
|
|
323
|
+
});
|
|
324
|
+
describe("extractMotivoLabeled", () => {
|
|
325
|
+
it("extracts a single-line motivo", () => {
|
|
326
|
+
expect((0, google_calendar_constants_1.extractMotivoLabeled)(labeledSample)).toBe("Consulta de seguimiento por dermatitis");
|
|
327
|
+
});
|
|
328
|
+
it("extracts a multi-line motivo", () => {
|
|
329
|
+
const desc = [
|
|
330
|
+
"Tutor: OMAR",
|
|
331
|
+
"Mascota: MILO",
|
|
332
|
+
"Motivo: Consulta inicial",
|
|
333
|
+
"Antecedentes de alergia",
|
|
334
|
+
"Sospecha de sarna",
|
|
335
|
+
"AGENDÓ JG 14/06/2026",
|
|
336
|
+
].join("\n");
|
|
337
|
+
expect((0, google_calendar_constants_1.extractMotivoLabeled)(desc)).toBe("Consulta inicial\nAntecedentes de alergia\nSospecha de sarna");
|
|
338
|
+
});
|
|
339
|
+
it("stops at the next labeled field even when AGENDÓ is far below", () => {
|
|
340
|
+
const desc = [
|
|
341
|
+
"Tutor: OMAR",
|
|
342
|
+
"Motivo: Solo este motivo",
|
|
343
|
+
"Mascota: MILO",
|
|
344
|
+
"Raza: Shih Tzu",
|
|
345
|
+
"AGENDÓ JG 14/06/2026",
|
|
346
|
+
].join("\n");
|
|
347
|
+
expect((0, google_calendar_constants_1.extractMotivoLabeled)(desc)).toBe("Solo este motivo");
|
|
348
|
+
});
|
|
349
|
+
it("stops at the HVP marker", () => {
|
|
350
|
+
const desc = [
|
|
351
|
+
"Motivo: Consulta",
|
|
352
|
+
"— creada desde HVP app · [HVP]",
|
|
353
|
+
].join("\n");
|
|
354
|
+
expect((0, google_calendar_constants_1.extractMotivoLabeled)(desc)).toBe("Consulta");
|
|
355
|
+
});
|
|
356
|
+
it("returns null when no motivo label is present", () => {
|
|
357
|
+
expect((0, google_calendar_constants_1.extractMotivoLabeled)("Tutor: OMAR\nMascota: MILO")).toBeNull();
|
|
358
|
+
});
|
|
359
|
+
});
|
|
360
|
+
});
|
|
210
361
|
describe("KNOWN_BREEDS", () => {
|
|
211
362
|
it("includes common breeds observed in the discovery data", () => {
|
|
212
363
|
const lower = google_calendar_constants_1.KNOWN_BREEDS.map((b) => b.toLowerCase());
|
|
@@ -24,6 +24,7 @@ export * from './force-majeure.enums';
|
|
|
24
24
|
export * from './hris.constants';
|
|
25
25
|
export * from './payroll-features.constants';
|
|
26
26
|
export * from './inventory-session.enums';
|
|
27
|
+
export * from './shift-checklist.enums';
|
|
27
28
|
export * from './documentation.enums';
|
|
28
29
|
export * from './document.enums';
|
|
29
30
|
export * from './settlement.enums';
|
package/dist/constants/index.js
CHANGED
|
@@ -40,6 +40,7 @@ __exportStar(require("./force-majeure.enums"), exports);
|
|
|
40
40
|
__exportStar(require("./hris.constants"), exports);
|
|
41
41
|
__exportStar(require("./payroll-features.constants"), exports);
|
|
42
42
|
__exportStar(require("./inventory-session.enums"), exports);
|
|
43
|
+
__exportStar(require("./shift-checklist.enums"), exports);
|
|
43
44
|
__exportStar(require("./documentation.enums"), exports);
|
|
44
45
|
__exportStar(require("./document.enums"), exports);
|
|
45
46
|
__exportStar(require("./settlement.enums"), exports);
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shift Checklist Enums
|
|
3
|
+
*
|
|
4
|
+
* Enums for the shift checklist module: a per-occasion ("run") list of recurring
|
|
5
|
+
* tasks a collaborator reviews at check-in and before checkout. A master
|
|
6
|
+
* `ChecklistActivity` defines the recurring task; a `ChecklistRun` is one
|
|
7
|
+
* occasion (collaborator + moment + day); a `ChecklistCompletion` records a task
|
|
8
|
+
* done/not-done within a run.
|
|
9
|
+
*/
|
|
10
|
+
/**
|
|
11
|
+
* When an activity applies (shown as a tag in the day checklist).
|
|
12
|
+
*
|
|
13
|
+
* - `check_in` (Entrada): start-of-shift tasks
|
|
14
|
+
* - `check_out` (Salida): end-of-shift tasks
|
|
15
|
+
* - `both` (Ambos): relevant at both entrada and salida
|
|
16
|
+
* - `any` (Indistinto): do it once, doesn't matter entrada or salida
|
|
17
|
+
*
|
|
18
|
+
* `both` / `any` are valid ONLY on an activity definition. A run/completion
|
|
19
|
+
* occasion is always `check_in` OR `check_out`.
|
|
20
|
+
*/
|
|
21
|
+
export declare enum ChecklistMoment {
|
|
22
|
+
check_in = "check_in",
|
|
23
|
+
check_out = "check_out",
|
|
24
|
+
both = "both",
|
|
25
|
+
any = "any"
|
|
26
|
+
}
|
|
27
|
+
export declare const CHECKLIST_MOMENT_LABELS: Record<ChecklistMoment, string>;
|
|
28
|
+
/** Moments valid for a run/completion occasion (never `both` / `any`). */
|
|
29
|
+
export declare const RUN_MOMENTS: Set<ChecklistMoment>;
|
|
30
|
+
/**
|
|
31
|
+
* Status of a task within a run (three conscious actions; "unmarked" = no action).
|
|
32
|
+
*
|
|
33
|
+
* - `done` (Hecho): the task was performed.
|
|
34
|
+
* - `skipped` (No hacía falta): evaluated and deemed unnecessary (e.g. the
|
|
35
|
+
* sanitary dispensers were already full). Carries a reason.
|
|
36
|
+
* - `not_done` (No se pudo): consciously not done because it couldn't be (e.g.
|
|
37
|
+
* missing material, no time). Carries a reason.
|
|
38
|
+
*/
|
|
39
|
+
export declare enum ChecklistCompletionStatus {
|
|
40
|
+
done = "done",
|
|
41
|
+
not_done = "not_done",
|
|
42
|
+
skipped = "skipped"
|
|
43
|
+
}
|
|
44
|
+
export declare const CHECKLIST_COMPLETION_STATUS_LABELS: Record<ChecklistCompletionStatus, string>;
|
|
45
|
+
/** Tooltip help text per status (for the UI controls). */
|
|
46
|
+
export declare const CHECKLIST_COMPLETION_STATUS_HELP: Record<ChecklistCompletionStatus, string>;
|
|
47
|
+
/** Statuses that reset the overdue clock (i.e. count as "the task was done"). */
|
|
48
|
+
export declare const SATISFYING_COMPLETION_STATUSES: Set<ChecklistCompletionStatus>;
|
|
49
|
+
/**
|
|
50
|
+
* How an activity's doneness is scoped across branches.
|
|
51
|
+
*
|
|
52
|
+
* - `specific` (Sucursales específicas): applies to the listed `branchIds`;
|
|
53
|
+
* done/overdue tracked PER branch.
|
|
54
|
+
* - `each` (Todas — cada una): applies to every branch; each branch does
|
|
55
|
+
* its own; done/overdue tracked PER branch (e.g. "Barrer recepción").
|
|
56
|
+
* - `any` (Cualquier sucursal): done ONCE for the whole org regardless of
|
|
57
|
+
* branch; done/overdue tracked GLOBALLY (e.g. "Enviar recordatorios de vacuna").
|
|
58
|
+
*/
|
|
59
|
+
export declare enum ChecklistBranchMode {
|
|
60
|
+
specific = "specific",
|
|
61
|
+
each = "each",
|
|
62
|
+
any = "any"
|
|
63
|
+
}
|
|
64
|
+
export declare const CHECKLIST_BRANCH_MODE_LABELS: Record<ChecklistBranchMode, string>;
|
|
65
|
+
/** Branch modes whose doneness is tracked per-branch (not global). */
|
|
66
|
+
export declare const PER_BRANCH_MODES: Set<ChecklistBranchMode>;
|
|
67
|
+
export declare enum ChecklistPriority {
|
|
68
|
+
high = "high",
|
|
69
|
+
medium = "medium",
|
|
70
|
+
low = "low"
|
|
71
|
+
}
|
|
72
|
+
export declare const CHECKLIST_PRIORITY_LABELS: Record<ChecklistPriority, string>;
|
|
73
|
+
/**
|
|
74
|
+
* Lifecycle of a per-occasion checklist run.
|
|
75
|
+
*
|
|
76
|
+
* - `open`: created, collaborator filling it
|
|
77
|
+
* - `submitted`: collaborator confirmed/submitted the run
|
|
78
|
+
*/
|
|
79
|
+
export declare enum ChecklistRunStatus {
|
|
80
|
+
open = "open",
|
|
81
|
+
submitted = "submitted"
|
|
82
|
+
}
|
|
83
|
+
export declare const CHECKLIST_RUN_STATUS_LABELS: Record<ChecklistRunStatus, string>;
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Shift Checklist Enums
|
|
4
|
+
*
|
|
5
|
+
* Enums for the shift checklist module: a per-occasion ("run") list of recurring
|
|
6
|
+
* tasks a collaborator reviews at check-in and before checkout. A master
|
|
7
|
+
* `ChecklistActivity` defines the recurring task; a `ChecklistRun` is one
|
|
8
|
+
* occasion (collaborator + moment + day); a `ChecklistCompletion` records a task
|
|
9
|
+
* done/not-done within a run.
|
|
10
|
+
*/
|
|
11
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
12
|
+
exports.CHECKLIST_RUN_STATUS_LABELS = exports.ChecklistRunStatus = exports.CHECKLIST_PRIORITY_LABELS = exports.ChecklistPriority = exports.PER_BRANCH_MODES = exports.CHECKLIST_BRANCH_MODE_LABELS = exports.ChecklistBranchMode = exports.SATISFYING_COMPLETION_STATUSES = exports.CHECKLIST_COMPLETION_STATUS_HELP = exports.CHECKLIST_COMPLETION_STATUS_LABELS = exports.ChecklistCompletionStatus = exports.RUN_MOMENTS = exports.CHECKLIST_MOMENT_LABELS = exports.ChecklistMoment = void 0;
|
|
13
|
+
// --- Moment of shift ---
|
|
14
|
+
/**
|
|
15
|
+
* When an activity applies (shown as a tag in the day checklist).
|
|
16
|
+
*
|
|
17
|
+
* - `check_in` (Entrada): start-of-shift tasks
|
|
18
|
+
* - `check_out` (Salida): end-of-shift tasks
|
|
19
|
+
* - `both` (Ambos): relevant at both entrada and salida
|
|
20
|
+
* - `any` (Indistinto): do it once, doesn't matter entrada or salida
|
|
21
|
+
*
|
|
22
|
+
* `both` / `any` are valid ONLY on an activity definition. A run/completion
|
|
23
|
+
* occasion is always `check_in` OR `check_out`.
|
|
24
|
+
*/
|
|
25
|
+
var ChecklistMoment;
|
|
26
|
+
(function (ChecklistMoment) {
|
|
27
|
+
ChecklistMoment["check_in"] = "check_in";
|
|
28
|
+
ChecklistMoment["check_out"] = "check_out";
|
|
29
|
+
ChecklistMoment["both"] = "both";
|
|
30
|
+
ChecklistMoment["any"] = "any";
|
|
31
|
+
})(ChecklistMoment || (exports.ChecklistMoment = ChecklistMoment = {}));
|
|
32
|
+
exports.CHECKLIST_MOMENT_LABELS = {
|
|
33
|
+
[ChecklistMoment.check_in]: "Entrada",
|
|
34
|
+
[ChecklistMoment.check_out]: "Salida",
|
|
35
|
+
[ChecklistMoment.both]: "Ambos",
|
|
36
|
+
[ChecklistMoment.any]: "Indistinto",
|
|
37
|
+
};
|
|
38
|
+
/** Moments valid for a run/completion occasion (never `both` / `any`). */
|
|
39
|
+
exports.RUN_MOMENTS = new Set([
|
|
40
|
+
ChecklistMoment.check_in,
|
|
41
|
+
ChecklistMoment.check_out,
|
|
42
|
+
]);
|
|
43
|
+
// --- Completion status ---
|
|
44
|
+
/**
|
|
45
|
+
* Status of a task within a run (three conscious actions; "unmarked" = no action).
|
|
46
|
+
*
|
|
47
|
+
* - `done` (Hecho): the task was performed.
|
|
48
|
+
* - `skipped` (No hacía falta): evaluated and deemed unnecessary (e.g. the
|
|
49
|
+
* sanitary dispensers were already full). Carries a reason.
|
|
50
|
+
* - `not_done` (No se pudo): consciously not done because it couldn't be (e.g.
|
|
51
|
+
* missing material, no time). Carries a reason.
|
|
52
|
+
*/
|
|
53
|
+
var ChecklistCompletionStatus;
|
|
54
|
+
(function (ChecklistCompletionStatus) {
|
|
55
|
+
ChecklistCompletionStatus["done"] = "done";
|
|
56
|
+
ChecklistCompletionStatus["not_done"] = "not_done";
|
|
57
|
+
ChecklistCompletionStatus["skipped"] = "skipped";
|
|
58
|
+
})(ChecklistCompletionStatus || (exports.ChecklistCompletionStatus = ChecklistCompletionStatus = {}));
|
|
59
|
+
exports.CHECKLIST_COMPLETION_STATUS_LABELS = {
|
|
60
|
+
[ChecklistCompletionStatus.done]: "Hecho",
|
|
61
|
+
[ChecklistCompletionStatus.skipped]: "No hacía falta",
|
|
62
|
+
[ChecklistCompletionStatus.not_done]: "No se pudo",
|
|
63
|
+
};
|
|
64
|
+
/** Tooltip help text per status (for the UI controls). */
|
|
65
|
+
exports.CHECKLIST_COMPLETION_STATUS_HELP = {
|
|
66
|
+
[ChecklistCompletionStatus.done]: "Se realizó.",
|
|
67
|
+
[ChecklistCompletionStatus.skipped]: "Se evaluó y no era necesario hacerla (ej. las sanitas ya estaban llenas).",
|
|
68
|
+
[ChecklistCompletionStatus.not_done]: "Conscientemente no se hizo porque no se pudo (ej. faltó material, no dio tiempo).",
|
|
69
|
+
};
|
|
70
|
+
/** Statuses that reset the overdue clock (i.e. count as "the task was done"). */
|
|
71
|
+
exports.SATISFYING_COMPLETION_STATUSES = new Set([
|
|
72
|
+
ChecklistCompletionStatus.done,
|
|
73
|
+
]);
|
|
74
|
+
// --- Branch mode (how an activity relates to branches) ---
|
|
75
|
+
/**
|
|
76
|
+
* How an activity's doneness is scoped across branches.
|
|
77
|
+
*
|
|
78
|
+
* - `specific` (Sucursales específicas): applies to the listed `branchIds`;
|
|
79
|
+
* done/overdue tracked PER branch.
|
|
80
|
+
* - `each` (Todas — cada una): applies to every branch; each branch does
|
|
81
|
+
* its own; done/overdue tracked PER branch (e.g. "Barrer recepción").
|
|
82
|
+
* - `any` (Cualquier sucursal): done ONCE for the whole org regardless of
|
|
83
|
+
* branch; done/overdue tracked GLOBALLY (e.g. "Enviar recordatorios de vacuna").
|
|
84
|
+
*/
|
|
85
|
+
var ChecklistBranchMode;
|
|
86
|
+
(function (ChecklistBranchMode) {
|
|
87
|
+
ChecklistBranchMode["specific"] = "specific";
|
|
88
|
+
ChecklistBranchMode["each"] = "each";
|
|
89
|
+
ChecklistBranchMode["any"] = "any";
|
|
90
|
+
})(ChecklistBranchMode || (exports.ChecklistBranchMode = ChecklistBranchMode = {}));
|
|
91
|
+
exports.CHECKLIST_BRANCH_MODE_LABELS = {
|
|
92
|
+
[ChecklistBranchMode.specific]: "Sucursales específicas",
|
|
93
|
+
[ChecklistBranchMode.each]: "Todas (cada una)",
|
|
94
|
+
[ChecklistBranchMode.any]: "Cualquier sucursal",
|
|
95
|
+
};
|
|
96
|
+
/** Branch modes whose doneness is tracked per-branch (not global). */
|
|
97
|
+
exports.PER_BRANCH_MODES = new Set([
|
|
98
|
+
ChecklistBranchMode.specific,
|
|
99
|
+
ChecklistBranchMode.each,
|
|
100
|
+
]);
|
|
101
|
+
// --- Priority ---
|
|
102
|
+
var ChecklistPriority;
|
|
103
|
+
(function (ChecklistPriority) {
|
|
104
|
+
ChecklistPriority["high"] = "high";
|
|
105
|
+
ChecklistPriority["medium"] = "medium";
|
|
106
|
+
ChecklistPriority["low"] = "low";
|
|
107
|
+
})(ChecklistPriority || (exports.ChecklistPriority = ChecklistPriority = {}));
|
|
108
|
+
exports.CHECKLIST_PRIORITY_LABELS = {
|
|
109
|
+
[ChecklistPriority.high]: "Alta",
|
|
110
|
+
[ChecklistPriority.medium]: "Media",
|
|
111
|
+
[ChecklistPriority.low]: "Baja",
|
|
112
|
+
};
|
|
113
|
+
// --- Run status ---
|
|
114
|
+
/**
|
|
115
|
+
* Lifecycle of a per-occasion checklist run.
|
|
116
|
+
*
|
|
117
|
+
* - `open`: created, collaborator filling it
|
|
118
|
+
* - `submitted`: collaborator confirmed/submitted the run
|
|
119
|
+
*/
|
|
120
|
+
var ChecklistRunStatus;
|
|
121
|
+
(function (ChecklistRunStatus) {
|
|
122
|
+
ChecklistRunStatus["open"] = "open";
|
|
123
|
+
ChecklistRunStatus["submitted"] = "submitted";
|
|
124
|
+
})(ChecklistRunStatus || (exports.ChecklistRunStatus = ChecklistRunStatus = {}));
|
|
125
|
+
exports.CHECKLIST_RUN_STATUS_LABELS = {
|
|
126
|
+
[ChecklistRunStatus.open]: "Abierto",
|
|
127
|
+
[ChecklistRunStatus.submitted]: "Enviado",
|
|
128
|
+
};
|
|
@@ -85,4 +85,16 @@ export interface CreateAppointmentRequest {
|
|
|
85
85
|
* collaborators list. If omitted, the auth user's col_code is used.
|
|
86
86
|
*/
|
|
87
87
|
schedulerColCode?: string;
|
|
88
|
+
/**
|
|
89
|
+
* Optional manual override for the description body. When present, the
|
|
90
|
+
* backend uses this verbatim instead of building the description from the
|
|
91
|
+
* structured fields above. The backend still appends the AGENDÓ/REAGENDÓ
|
|
92
|
+
* scheduler line and the HVP marker so attribution and provenance are not
|
|
93
|
+
* lost.
|
|
94
|
+
*
|
|
95
|
+
* Used when the receptionist enables "Editar texto" in the form to fine-tune
|
|
96
|
+
* wording before saving. On PATCH, supplying this also short-circuits the
|
|
97
|
+
* labeled rebuild — useful for surgical edits.
|
|
98
|
+
*/
|
|
99
|
+
descriptionOverride?: string;
|
|
88
100
|
}
|
|
@@ -13,6 +13,7 @@ export * from './time-off-request';
|
|
|
13
13
|
export * from './force-majeure';
|
|
14
14
|
export * from './job';
|
|
15
15
|
export * from './inventory-session';
|
|
16
|
+
export * from './shift-checklist';
|
|
16
17
|
export * from './client-billing';
|
|
17
18
|
export * from './global-invoice';
|
|
18
19
|
export * from './inventory-report';
|
package/dist/contracts/index.js
CHANGED
|
@@ -29,6 +29,7 @@ __exportStar(require("./time-off-request"), exports);
|
|
|
29
29
|
__exportStar(require("./force-majeure"), exports);
|
|
30
30
|
__exportStar(require("./job"), exports);
|
|
31
31
|
__exportStar(require("./inventory-session"), exports);
|
|
32
|
+
__exportStar(require("./shift-checklist"), exports);
|
|
32
33
|
__exportStar(require("./client-billing"), exports);
|
|
33
34
|
__exportStar(require("./global-invoice"), exports);
|
|
34
35
|
__exportStar(require("./inventory-report"), exports);
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __exportStar = (this && this.__exportStar) || function(m, exports) {
|
|
14
|
+
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
|
|
15
|
+
};
|
|
16
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
17
|
+
__exportStar(require("./types"), exports);
|
|
18
|
+
__exportStar(require("./requests"), exports);
|
|
19
|
+
__exportStar(require("./responses"), exports);
|
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shift Checklist Request Types
|
|
3
|
+
*
|
|
4
|
+
* Request DTOs for the shift checklist endpoints.
|
|
5
|
+
*
|
|
6
|
+
* @example POST /api/shift-checklist/activities
|
|
7
|
+
* @example GET /api/shift-checklist/runs/view
|
|
8
|
+
* @example POST /api/shift-checklist/runs/:id/completions/batch
|
|
9
|
+
*/
|
|
10
|
+
import { ChecklistBranchMode, ChecklistCompletionStatus, ChecklistMoment, ChecklistPriority } from '../../constants/shift-checklist.enums';
|
|
11
|
+
/**
|
|
12
|
+
* Create a master checklist activity.
|
|
13
|
+
*
|
|
14
|
+
* @example POST /api/shift-checklist/activities
|
|
15
|
+
*/
|
|
16
|
+
export interface CreateChecklistActivityRequest {
|
|
17
|
+
/** Concrete, verb-first action (location lives in the name when it matters) */
|
|
18
|
+
name: string;
|
|
19
|
+
/** Branch scope: specific (these branchIds) / each (all, per-branch) / any (global) */
|
|
20
|
+
branchMode: ChecklistBranchMode;
|
|
21
|
+
/** Branch ids the activity applies to (required when branchMode is `specific`) */
|
|
22
|
+
branchIds?: string[];
|
|
23
|
+
momentOfShift: ChecklistMoment;
|
|
24
|
+
/** When false, the activity is a visible reminder only (default true). */
|
|
25
|
+
isTracked?: boolean;
|
|
26
|
+
/** Days between occurrences; null = no fixed cadence (never overdue) */
|
|
27
|
+
frequencyDays?: number | null;
|
|
28
|
+
priority?: ChecklistPriority | null;
|
|
29
|
+
estimatedMinutes?: number | null;
|
|
30
|
+
/** How/when to do it: merged procedure + conditional notes (free text, may be multiline). */
|
|
31
|
+
instructions?: string;
|
|
32
|
+
requiresEvidence?: boolean;
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* Update a master checklist activity (partial).
|
|
36
|
+
*
|
|
37
|
+
* @example PATCH /api/shift-checklist/activities/:id
|
|
38
|
+
*/
|
|
39
|
+
export interface UpdateChecklistActivityRequest {
|
|
40
|
+
name?: string;
|
|
41
|
+
branchMode?: ChecklistBranchMode;
|
|
42
|
+
branchIds?: string[];
|
|
43
|
+
momentOfShift?: ChecklistMoment;
|
|
44
|
+
isTracked?: boolean;
|
|
45
|
+
frequencyDays?: number | null;
|
|
46
|
+
priority?: ChecklistPriority | null;
|
|
47
|
+
estimatedMinutes?: number | null;
|
|
48
|
+
/** How/when to do it: merged procedure + conditional notes (free text, may be multiline). */
|
|
49
|
+
instructions?: string;
|
|
50
|
+
requiresEvidence?: boolean;
|
|
51
|
+
isActive?: boolean;
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* Filters for listing master activities.
|
|
55
|
+
*
|
|
56
|
+
* @example GET /api/shift-checklist/activities?branchId=...&momentOfShift=check_out&isActive=true
|
|
57
|
+
*/
|
|
58
|
+
export interface ListChecklistActivitiesFilters {
|
|
59
|
+
branchId?: string;
|
|
60
|
+
momentOfShift?: ChecklistMoment;
|
|
61
|
+
isActive?: boolean;
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* Query for the checkout view (find-or-create a run + its assembled view).
|
|
65
|
+
*
|
|
66
|
+
* @example GET /api/shift-checklist/runs/view?momentOfShift=check_out&branchId=...
|
|
67
|
+
*/
|
|
68
|
+
export interface GetChecklistViewQuery {
|
|
69
|
+
/** check_in or check_out (never both) */
|
|
70
|
+
momentOfShift: ChecklistMoment;
|
|
71
|
+
branchId: string;
|
|
72
|
+
/** YYYY-MM-DD; defaults to today (America/Mexico_City) server-side */
|
|
73
|
+
shiftDate?: string;
|
|
74
|
+
/** link to the realized attendance occasion (TimeShift) when opened from checkout */
|
|
75
|
+
attendanceRecordId?: string;
|
|
76
|
+
}
|
|
77
|
+
/**
|
|
78
|
+
* Update a run (e.g. correct the branch for the whole run, or add notes).
|
|
79
|
+
*
|
|
80
|
+
* @example PATCH /api/shift-checklist/runs/:id
|
|
81
|
+
*/
|
|
82
|
+
export interface UpdateChecklistRunRequest {
|
|
83
|
+
branchId?: string;
|
|
84
|
+
notes?: string;
|
|
85
|
+
}
|
|
86
|
+
/**
|
|
87
|
+
* A single completion to record/update within a run.
|
|
88
|
+
* Either `activityId` (master task) or `adHocTitle` (ad-hoc task) must be set.
|
|
89
|
+
*/
|
|
90
|
+
export interface RecordCompletionItemRequest {
|
|
91
|
+
/** null/omitted = ad-hoc task (requires adHocTitle) */
|
|
92
|
+
activityId?: string | null;
|
|
93
|
+
adHocTitle?: string;
|
|
94
|
+
status: ChecklistCompletionStatus;
|
|
95
|
+
/** who did it; defaults to [current user]. Multiple for shared tasks. */
|
|
96
|
+
performedByIds?: string[];
|
|
97
|
+
/** required when status is "skipped" */
|
|
98
|
+
skipReason?: string;
|
|
99
|
+
notes?: string;
|
|
100
|
+
evidenceUrl?: string | null;
|
|
101
|
+
}
|
|
102
|
+
/**
|
|
103
|
+
* Batch upsert completions in a run (idempotent per activity).
|
|
104
|
+
*
|
|
105
|
+
* @example POST /api/shift-checklist/runs/:id/completions/batch
|
|
106
|
+
*/
|
|
107
|
+
export interface RecordCompletionsBatchRequest {
|
|
108
|
+
items: RecordCompletionItemRequest[];
|
|
109
|
+
}
|
|
110
|
+
/**
|
|
111
|
+
* Query filters for checklist statistics.
|
|
112
|
+
*
|
|
113
|
+
* @example GET /api/shift-checklist/stats?branchId=...&from=2026-06-01&to=2026-06-20
|
|
114
|
+
*/
|
|
115
|
+
export interface ChecklistStatsQuery {
|
|
116
|
+
branchId?: string;
|
|
117
|
+
from?: string;
|
|
118
|
+
to?: string;
|
|
119
|
+
}
|
|
120
|
+
/**
|
|
121
|
+
* Create a one-off pendiente (just a title; stays open until resolved).
|
|
122
|
+
*
|
|
123
|
+
* @example POST /api/shift-checklist/pending-tasks
|
|
124
|
+
*/
|
|
125
|
+
export interface CreateChecklistPendingTaskRequest {
|
|
126
|
+
title: string;
|
|
127
|
+
branchId: string;
|
|
128
|
+
}
|
|
129
|
+
/**
|
|
130
|
+
* Mark a pendiente done, recording who did it.
|
|
131
|
+
*
|
|
132
|
+
* @example PATCH /api/shift-checklist/pending-tasks/:id/resolve
|
|
133
|
+
*/
|
|
134
|
+
export interface ResolveChecklistPendingTaskRequest {
|
|
135
|
+
performedByIds: string[];
|
|
136
|
+
}
|
|
137
|
+
/**
|
|
138
|
+
* Filters for listing pendientes.
|
|
139
|
+
*
|
|
140
|
+
* @example GET /api/shift-checklist/pending-tasks?branchId=...&includeResolved=false
|
|
141
|
+
*/
|
|
142
|
+
export interface ListChecklistPendingTasksFilters {
|
|
143
|
+
branchId: string;
|
|
144
|
+
includeResolved?: boolean;
|
|
145
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Shift Checklist Request Types
|
|
4
|
+
*
|
|
5
|
+
* Request DTOs for the shift checklist endpoints.
|
|
6
|
+
*
|
|
7
|
+
* @example POST /api/shift-checklist/activities
|
|
8
|
+
* @example GET /api/shift-checklist/runs/view
|
|
9
|
+
* @example POST /api/shift-checklist/runs/:id/completions/batch
|
|
10
|
+
*/
|
|
11
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shift Checklist Response Types
|
|
3
|
+
*
|
|
4
|
+
* Response DTOs for the shift checklist endpoints. Dates are ISO 8601 strings.
|
|
5
|
+
*/
|
|
6
|
+
import { ChecklistBranchMode, ChecklistMoment, ChecklistPriority, ChecklistRunStatus } from '../../constants/shift-checklist.enums';
|
|
7
|
+
import { ChecklistAdHocItem, ChecklistCheckoutItem } from './types';
|
|
8
|
+
/**
|
|
9
|
+
* A master checklist activity.
|
|
10
|
+
*
|
|
11
|
+
* @example GET /api/shift-checklist/activities
|
|
12
|
+
*/
|
|
13
|
+
export interface ChecklistActivityResponse {
|
|
14
|
+
id: string;
|
|
15
|
+
name: string;
|
|
16
|
+
branchMode: ChecklistBranchMode;
|
|
17
|
+
branchIds: string[];
|
|
18
|
+
momentOfShift: ChecklistMoment;
|
|
19
|
+
isTracked: boolean;
|
|
20
|
+
frequencyDays: number | null;
|
|
21
|
+
priority: ChecklistPriority | null;
|
|
22
|
+
estimatedMinutes: number | null;
|
|
23
|
+
/** How/when to do it: merged procedure + conditional notes (free text, may be multiline). */
|
|
24
|
+
instructions: string;
|
|
25
|
+
requiresEvidence: boolean;
|
|
26
|
+
isActive: boolean;
|
|
27
|
+
createdAt: string;
|
|
28
|
+
updatedAt: string;
|
|
29
|
+
createdBy: string;
|
|
30
|
+
updatedBy: string;
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* A per-occasion checklist run.
|
|
34
|
+
*
|
|
35
|
+
* @example GET /api/shift-checklist/runs/view
|
|
36
|
+
*/
|
|
37
|
+
export interface ChecklistRunResponse {
|
|
38
|
+
id: string;
|
|
39
|
+
collaboratorId: string;
|
|
40
|
+
branchId: string;
|
|
41
|
+
branchName: string;
|
|
42
|
+
momentOfShift: ChecklistMoment;
|
|
43
|
+
shiftDate: string;
|
|
44
|
+
attendanceRecordId: string | null;
|
|
45
|
+
status: ChecklistRunStatus;
|
|
46
|
+
submittedAt: string | null;
|
|
47
|
+
notes: string;
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* The assembled checkout view: the run, its activity rows (with last-done +
|
|
51
|
+
* overdue), and any ad-hoc items. Powers the checkout modal.
|
|
52
|
+
*
|
|
53
|
+
* @example GET /api/shift-checklist/runs/view
|
|
54
|
+
* @example POST /api/shift-checklist/runs/:id/completions/batch (refreshed view)
|
|
55
|
+
*/
|
|
56
|
+
export interface ChecklistCheckoutViewResponse {
|
|
57
|
+
run: ChecklistRunResponse;
|
|
58
|
+
items: ChecklistCheckoutItem[];
|
|
59
|
+
adHocItems: ChecklistAdHocItem[];
|
|
60
|
+
}
|
|
61
|
+
export interface ChecklistBranchStats {
|
|
62
|
+
branchId: string;
|
|
63
|
+
branchName: string;
|
|
64
|
+
totalCompletions: number;
|
|
65
|
+
doneCount: number;
|
|
66
|
+
notDoneCount: number;
|
|
67
|
+
skippedCount: number;
|
|
68
|
+
/** master activities currently overdue at this branch */
|
|
69
|
+
overdueActivityCount: number;
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* Checklist statistics over a period.
|
|
73
|
+
*
|
|
74
|
+
* @example GET /api/shift-checklist/stats
|
|
75
|
+
*/
|
|
76
|
+
export interface ChecklistStatsResponse {
|
|
77
|
+
from: string | null;
|
|
78
|
+
to: string | null;
|
|
79
|
+
byBranch: ChecklistBranchStats[];
|
|
80
|
+
totalRuns: number;
|
|
81
|
+
/** done completions / total completions, 0..1 */
|
|
82
|
+
completionRate: number;
|
|
83
|
+
}
|
|
84
|
+
/**
|
|
85
|
+
* A one-off "pendiente" — created on the fly, stays open per branch across shifts
|
|
86
|
+
* until someone marks it done (and only then records who did it).
|
|
87
|
+
*
|
|
88
|
+
* @example GET /api/shift-checklist/pending-tasks?branchId=...
|
|
89
|
+
*/
|
|
90
|
+
export interface ChecklistPendingTaskResponse {
|
|
91
|
+
id: string;
|
|
92
|
+
title: string;
|
|
93
|
+
branchId: string;
|
|
94
|
+
isResolved: boolean;
|
|
95
|
+
doneByIds: string[];
|
|
96
|
+
doneByNames: string[];
|
|
97
|
+
doneAt: string | null;
|
|
98
|
+
createdBy: string;
|
|
99
|
+
createdAt: string;
|
|
100
|
+
}
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shift Checklist — shared building-block types
|
|
3
|
+
*
|
|
4
|
+
* Reusable shapes referenced by the endpoint responses (checkout view items,
|
|
5
|
+
* completion shape). Dates are ISO 8601 strings on the wire.
|
|
6
|
+
*/
|
|
7
|
+
import { ChecklistBranchMode, ChecklistCompletionStatus, ChecklistMoment, ChecklistPriority } from '../../constants/shift-checklist.enums';
|
|
8
|
+
/**
|
|
9
|
+
* A recorded completion of a task within a run (or an ad-hoc task).
|
|
10
|
+
*
|
|
11
|
+
* @example POST /api/shift-checklist/runs/:id/completions/batch
|
|
12
|
+
*/
|
|
13
|
+
export interface ChecklistCompletionResponse {
|
|
14
|
+
id: string;
|
|
15
|
+
runId: string;
|
|
16
|
+
/** null = ad-hoc task (not on the master list) */
|
|
17
|
+
activityId: string | null;
|
|
18
|
+
/** present when activityId is null */
|
|
19
|
+
adHocTitle: string | null;
|
|
20
|
+
status: ChecklistCompletionStatus;
|
|
21
|
+
/** present when status is "skipped" */
|
|
22
|
+
skipReason: string | null;
|
|
23
|
+
/** who performed the task (one or more, for shared tasks) */
|
|
24
|
+
performedByIds: string[];
|
|
25
|
+
performedByNames: string[];
|
|
26
|
+
/** who logged the completion */
|
|
27
|
+
reportedById: string;
|
|
28
|
+
evidenceUrl: string | null;
|
|
29
|
+
notes: string;
|
|
30
|
+
completedAt: string | null;
|
|
31
|
+
/** denormalized from the run */
|
|
32
|
+
branchId: string;
|
|
33
|
+
shiftDate: string;
|
|
34
|
+
momentOfShift: ChecklistMoment;
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* One row in the checkout view: a master activity plus its last-done history and
|
|
38
|
+
* this run's completion (if any).
|
|
39
|
+
*/
|
|
40
|
+
export interface ChecklistCheckoutItem {
|
|
41
|
+
activityId: string;
|
|
42
|
+
name: string;
|
|
43
|
+
/** When the activity is meant to be done (shown as a tag in the day list). */
|
|
44
|
+
momentOfShift: ChecklistMoment;
|
|
45
|
+
/** Branch scope — `any` items count globally and are grouped apart. */
|
|
46
|
+
branchMode: ChecklistBranchMode;
|
|
47
|
+
/** When false, the activity is a visible reminder only — not marked/tracked. */
|
|
48
|
+
isTracked: boolean;
|
|
49
|
+
priority: ChecklistPriority | null;
|
|
50
|
+
estimatedMinutes: number | null;
|
|
51
|
+
instructions: string;
|
|
52
|
+
requiresEvidence: boolean;
|
|
53
|
+
frequencyDays: number | null;
|
|
54
|
+
/** last time this activity was DONE at this branch (drives overdue) */
|
|
55
|
+
lastDoneAt: string | null;
|
|
56
|
+
/** who did it last (may be several for a shared task) */
|
|
57
|
+
lastDoneByIds: string[];
|
|
58
|
+
lastDoneByNames: string[];
|
|
59
|
+
/**
|
|
60
|
+
* Most recent action on this activity at this branch, ANY status — for
|
|
61
|
+
* context when the last thing that happened was an omission (e.g. last done
|
|
62
|
+
* Jan 1 but last action was "omitida" yesterday). Null if never acted on.
|
|
63
|
+
*/
|
|
64
|
+
lastAction: {
|
|
65
|
+
status: ChecklistCompletionStatus;
|
|
66
|
+
at: string;
|
|
67
|
+
byIds: string[];
|
|
68
|
+
byNames: string[];
|
|
69
|
+
skipReason: string | null;
|
|
70
|
+
notes: string;
|
|
71
|
+
} | null;
|
|
72
|
+
/** lastDoneAt + frequencyDays < now (false when no cadence or never done) */
|
|
73
|
+
isOverdue: boolean;
|
|
74
|
+
/** this run's completion for the activity, if recorded */
|
|
75
|
+
completion: ChecklistCompletionResponse | null;
|
|
76
|
+
}
|
|
77
|
+
/** An ad-hoc task added during a run (not tied to a master activity). */
|
|
78
|
+
export interface ChecklistAdHocItem {
|
|
79
|
+
id: string;
|
|
80
|
+
adHocTitle: string;
|
|
81
|
+
status: ChecklistCompletionStatus;
|
|
82
|
+
performedByIds: string[];
|
|
83
|
+
performedByNames: string[];
|
|
84
|
+
completedAt: string | null;
|
|
85
|
+
notes: string;
|
|
86
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Shift Checklist — shared building-block types
|
|
4
|
+
*
|
|
5
|
+
* Reusable shapes referenced by the endpoint responses (checkout view items,
|
|
6
|
+
* completion shape). Dates are ISO 8601 strings on the wire.
|
|
7
|
+
*/
|
|
8
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
package/dist/validation/index.js
CHANGED
|
@@ -25,3 +25,4 @@ __exportStar(require("./email.validation"), exports);
|
|
|
25
25
|
__exportStar(require("./phone.validation"), exports);
|
|
26
26
|
__exportStar(require("./address.validation"), exports);
|
|
27
27
|
__exportStar(require("./document.validation"), exports);
|
|
28
|
+
__exportStar(require("./shift-checklist.validation"), exports);
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { ValidationResult } from './rfc.validation';
|
|
2
|
+
/**
|
|
3
|
+
* Validates a checklist activity frequency (in days).
|
|
4
|
+
*
|
|
5
|
+
* The frequency is the number of days between occurrences of a recurring task.
|
|
6
|
+
* - `null`/`undefined` → no fixed cadence (valid; the task is never "overdue").
|
|
7
|
+
* - otherwise → must be an integer ≥ 1.
|
|
8
|
+
*
|
|
9
|
+
* @param value - frequency in days, or null/undefined for "no cadence"
|
|
10
|
+
* @returns ValidationResult with isValid flag and optional error message
|
|
11
|
+
*/
|
|
12
|
+
export declare function validateChecklistFrequency(value: number | null | undefined): ValidationResult;
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.validateChecklistFrequency = validateChecklistFrequency;
|
|
4
|
+
/**
|
|
5
|
+
* Validates a checklist activity frequency (in days).
|
|
6
|
+
*
|
|
7
|
+
* The frequency is the number of days between occurrences of a recurring task.
|
|
8
|
+
* - `null`/`undefined` → no fixed cadence (valid; the task is never "overdue").
|
|
9
|
+
* - otherwise → must be an integer ≥ 1.
|
|
10
|
+
*
|
|
11
|
+
* @param value - frequency in days, or null/undefined for "no cadence"
|
|
12
|
+
* @returns ValidationResult with isValid flag and optional error message
|
|
13
|
+
*/
|
|
14
|
+
function validateChecklistFrequency(value) {
|
|
15
|
+
if (value === null || value === undefined) {
|
|
16
|
+
return { isValid: true };
|
|
17
|
+
}
|
|
18
|
+
if (typeof value !== 'number' || Number.isNaN(value) || !Number.isInteger(value)) {
|
|
19
|
+
return {
|
|
20
|
+
isValid: false,
|
|
21
|
+
error: 'La frecuencia debe ser un número entero de días',
|
|
22
|
+
};
|
|
23
|
+
}
|
|
24
|
+
if (value < 1) {
|
|
25
|
+
return { isValid: false, error: 'La frecuencia debe ser al menos 1 día' };
|
|
26
|
+
}
|
|
27
|
+
return { isValid: true };
|
|
28
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
const shift_checklist_validation_1 = require("./shift-checklist.validation");
|
|
4
|
+
describe('validateChecklistFrequency', () => {
|
|
5
|
+
describe('valid frequencies', () => {
|
|
6
|
+
it('accepts null (no fixed cadence)', () => {
|
|
7
|
+
expect((0, shift_checklist_validation_1.validateChecklistFrequency)(null).isValid).toBe(true);
|
|
8
|
+
});
|
|
9
|
+
it('accepts undefined (no fixed cadence)', () => {
|
|
10
|
+
expect((0, shift_checklist_validation_1.validateChecklistFrequency)(undefined).isValid).toBe(true);
|
|
11
|
+
});
|
|
12
|
+
it('accepts 1 (daily)', () => {
|
|
13
|
+
expect((0, shift_checklist_validation_1.validateChecklistFrequency)(1).isValid).toBe(true);
|
|
14
|
+
});
|
|
15
|
+
it('accepts arbitrary positive integers', () => {
|
|
16
|
+
expect((0, shift_checklist_validation_1.validateChecklistFrequency)(7).isValid).toBe(true);
|
|
17
|
+
expect((0, shift_checklist_validation_1.validateChecklistFrequency)(30).isValid).toBe(true);
|
|
18
|
+
expect((0, shift_checklist_validation_1.validateChecklistFrequency)(45).isValid).toBe(true);
|
|
19
|
+
});
|
|
20
|
+
});
|
|
21
|
+
describe('invalid frequencies', () => {
|
|
22
|
+
it('rejects 0', () => {
|
|
23
|
+
const result = (0, shift_checklist_validation_1.validateChecklistFrequency)(0);
|
|
24
|
+
expect(result.isValid).toBe(false);
|
|
25
|
+
expect(result.error).toBeDefined();
|
|
26
|
+
});
|
|
27
|
+
it('rejects negative numbers', () => {
|
|
28
|
+
expect((0, shift_checklist_validation_1.validateChecklistFrequency)(-1).isValid).toBe(false);
|
|
29
|
+
});
|
|
30
|
+
it('rejects non-integers', () => {
|
|
31
|
+
expect((0, shift_checklist_validation_1.validateChecklistFrequency)(1.5).isValid).toBe(false);
|
|
32
|
+
});
|
|
33
|
+
it('rejects NaN', () => {
|
|
34
|
+
expect((0, shift_checklist_validation_1.validateChecklistFrequency)(Number.NaN).isValid).toBe(false);
|
|
35
|
+
});
|
|
36
|
+
});
|
|
37
|
+
});
|