sabre-client 0.5.1 → 0.7.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/testing.js CHANGED
@@ -1,6 +1,259 @@
1
1
  // src/testing/queue-fake.ts
2
2
  import { XMLParser as XMLParser3 } from "fast-xml-parser";
3
3
 
4
+ // src/testing/booking-fake.ts
5
+ var LOCATOR = /^[A-Z0-9]{6}$/;
6
+ var FULFILL_REASONS = /* @__PURE__ */ new Set([
7
+ "UNABLE_TO_RETRIEVE_TICKETS",
8
+ "FULFILLMENT_NOT_CONFIRMED",
9
+ "INTERNAL_SERVER_TIMEOUT",
10
+ "DOWNLINE_SERVICE_ERROR",
11
+ "PAYMENT_CARD_ISSUE",
12
+ "NOT_COMMITTED",
13
+ "TRANSPORT",
14
+ "GATEWAY_5XX",
15
+ "UNPARSEABLE",
16
+ "ABORTED"
17
+ ]);
18
+ var ISSUES_BY_DEFAULT = /* @__PURE__ */ new Set(["UNABLE_TO_RETRIEVE_TICKETS", "INTERNAL_SERVER_TIMEOUT", "NOT_COMMITTED", "TRANSPORT", "GATEWAY_5XX", "UNPARSEABLE", "ABORTED"]);
19
+ var VOID_REASONS = /* @__PURE__ */ new Set(["END_TRANSACTION_PROBLEM", "TRANSPORT", "GATEWAY_5XX", "UNPARSEABLE", "ABORTED"]);
20
+ var DOWNLINE_TEXT = {
21
+ INVALID_TICKET_STOCK: "The AirTicketRQ service returned an error: (DesignatePrinterLLSRQ: INVALID TICKET STOCK-0096).",
22
+ COMMISSION_REQUIRED: "The AirTicketRQ service returned an error: (AirTicketLLS failed for /Ticketing[1] with Cause: AirTicketLLSRQ: COMMISSION REQUIRED-0260, TicketingDocumentServicesRQ: No new tickets have been issued).",
23
+ HARDCOPY_PRINTER: "The AirTicketRQ service returned an error: (AirTicketLLS failed for /Ticketing[1] with Cause: AirTicketLLSRQ: ASSIGN HARDCOPY PRINTER FOR ETR DOCUMENTS-USE PTR/-0052, TicketingDocumentServicesRQ: No new tickets have been issued).",
24
+ SECURE_FLIGHT: "The AirTicketRQ service returned an error: (AirTicketLLS failed for /Ticketing[1] with Cause: AirTicketLLSRQ: PASSENGER SECURITY DATA REQUIRED PLEASE UPDATE AND RETRY-1829, TicketingDocumentServicesRQ: No new tickets have been issued)."
25
+ };
26
+ var UNCERTAIN_TEXT = {
27
+ UNABLE_TO_RETRIEVE_TICKETS: ["APPLICATION_ERROR", "Tickets might have been issued successfully but application could not retrieve them. Verify booking details and/or audit trail reports (DQB)."],
28
+ FULFILLMENT_NOT_CONFIRMED: ["WARNING", "The fulfillment operation cannot be confirmed. No new tickets were found in the booking."],
29
+ INTERNAL_SERVER_TIMEOUT: ["APPLICATION_ERROR", "The downline ticketing service exceeded its processing time and the request timed out. Please retry the fulfillment attempt."],
30
+ DOWNLINE_SERVICE_ERROR: ["APPLICATION_ERROR", "The AirTicketRQ service returned an error: (AirTicketLLSRQ: UNABLE TO PROCESS)."],
31
+ PAYMENT_CARD_ISSUE: ["APPLICATION_ERROR", "At least one fulfillment operation was not processed correctly. The transaction was declined due to an invalid card number."]
32
+ };
33
+ function fail(message) {
34
+ throw new TypeError(`sabre-client/testing: ${message}`);
35
+ }
36
+ var json = (status, body) => new Response(JSON.stringify(body), { status, headers: { "Content-Type": "application/json" } });
37
+ var appError = (type, description, category = "APPLICATION_ERROR") => ({ category, type, description });
38
+ function toTicket(value, label, today) {
39
+ if (typeof value !== "object" || value === null) fail(`${label} debe ser un objeto`);
40
+ const ticket = value;
41
+ if (typeof ticket.number !== "string" || !/^[0-9A-Z]{13}$/.test(ticket.number)) fail(`${label}.number debe ser un n\xFAmero de boleto de 13 caracteres`);
42
+ if (ticket.ticketStatusCode !== void 0 && ticket.ticketStatusCode !== "TE" && ticket.ticketStatusCode !== "TV") fail(`${label}.ticketStatusCode debe ser TE o TV`);
43
+ return { number: ticket.number, ticketStatusCode: ticket.ticketStatusCode ?? "TE", date: ticket.date ?? today, committed: true };
44
+ }
45
+ function validateFulfillOutcome(value, label) {
46
+ if (value === "issued" || value === "partial") return value;
47
+ if (typeof value !== "object" || value === null) fail(`${label} debe ser 'issued', 'partial' o un objeto`);
48
+ const outcome = value;
49
+ if ("uncertain" in outcome) {
50
+ if (!FULFILL_REASONS.has(String(outcome.uncertain))) fail(`${label}.uncertain no es una reason de tickets.fulfill`);
51
+ } else if ("downline" in outcome) {
52
+ if (!(String(outcome.downline) in DOWNLINE_TEXT)) fail(`${label}.downline debe ser ${Object.keys(DOWNLINE_TEXT).join(", ")}`);
53
+ } else if (typeof outcome.error !== "string" || outcome.error === "") {
54
+ fail(`${label} debe tener uncertain, downline o error`);
55
+ }
56
+ return value;
57
+ }
58
+ function validateVoidOutcome(value, label) {
59
+ if (value === "voided") return value;
60
+ if (typeof value !== "object" || value === null || !VOID_REASONS.has(String(value.uncertain))) {
61
+ fail(`${label} debe ser 'voided' o { uncertain: ${[...VOID_REASONS].join(" | ")} }`);
62
+ }
63
+ return value;
64
+ }
65
+ function createBookingHandler(options, now, record) {
66
+ const today = () => now().toISOString().slice(0, 10);
67
+ const bookings = /* @__PURE__ */ new Map();
68
+ for (const [locator, seed] of Object.entries(options.bookings ?? {})) {
69
+ if (!LOCATOR.test(locator)) fail(`bookings['${locator}']: la clave debe ser un localizador de 6 caracteres alfanum\xE9ricos en may\xFAscula`);
70
+ if (typeof seed !== "object" || seed === null) fail(`bookings['${locator}'] debe ser un objeto`);
71
+ if (seed.flights !== void 0 && (!Number.isInteger(seed.flights) || seed.flights < 0)) fail(`bookings['${locator}'].flights debe ser un entero >= 0`);
72
+ if (seed.tickets !== void 0 && !Array.isArray(seed.tickets)) fail(`bookings['${locator}'].tickets debe ser una lista`);
73
+ bookings.set(locator, {
74
+ flights: seed.flights ?? 1,
75
+ tickets: (seed.tickets ?? []).map((ticket, index) => toTicket(ticket, `bookings['${locator}'].tickets[${index}]`, today()))
76
+ });
77
+ }
78
+ const ticketing = options.ticketing ?? {};
79
+ if (typeof ticketing !== "object" || ticketing === null) fail("ticketing debe ser un objeto");
80
+ if (ticketing.fulfill !== void 0 && !Array.isArray(ticketing.fulfill)) fail("ticketing.fulfill debe ser una lista");
81
+ if (ticketing.void !== void 0 && !Array.isArray(ticketing.void)) fail("ticketing.void debe ser una lista");
82
+ const fulfillQueue = (ticketing.fulfill ?? []).map((outcome, index) => validateFulfillOutcome(outcome, `ticketing.fulfill[${index}]`));
83
+ const voidQueue = (ticketing.void ?? []).map((outcome, index) => validateVoidOutcome(outcome, `ticketing.void[${index}]`));
84
+ let created = 0;
85
+ let issuedCount = 0;
86
+ const nextLocator = () => {
87
+ let locator;
88
+ do {
89
+ created += 1;
90
+ locator = `FAKE${String.fromCharCode(64 + Math.ceil(created / 26))}${String.fromCharCode(64 + ((created - 1) % 26 + 1))}`;
91
+ } while (bookings.has(locator));
92
+ return locator;
93
+ };
94
+ const nextTicket = () => {
95
+ issuedCount += 1;
96
+ return { number: `999${String(issuedCount).padStart(10, "0")}`, ticketStatusCode: "TE", date: today(), committed: true };
97
+ };
98
+ const ticketJson = (ticket) => ({
99
+ number: ticket.number,
100
+ date: ticket.date,
101
+ airlineCode: "XX",
102
+ travelerIndex: 1,
103
+ flightCoupons: [],
104
+ payment: { total: "100.00", currencyCode: "USD" },
105
+ ticketStatusName: ticket.ticketStatusCode === "TE" ? "Issued" : "Voided",
106
+ ticketStatusCode: ticket.ticketStatusCode
107
+ });
108
+ const bookingJson = (locator, booking) => ({
109
+ bookingId: locator,
110
+ startDate: today(),
111
+ isTicketed: booking.tickets.some((ticket) => ticket.committed),
112
+ flights: Array.from({ length: booking.flights }, (_, index) => ({ itemId: String(index + 1), flightNumber: 100 + index, airlineCode: "XX" })),
113
+ travelers: [{ givenName: "TEST", surname: "PASSENGER" }],
114
+ flightTickets: booking.tickets.filter((ticket) => ticket.committed).map(ticketJson)
115
+ });
116
+ const notFound = () => json(200, { errors: [appError("BOOKING_NOT_FOUND", "Booking cannot be found.", "RESOURCE_NOT_FOUND")] });
117
+ const parse = (body) => {
118
+ try {
119
+ const value = JSON.parse(body);
120
+ return typeof value === "object" && value !== null ? value : {};
121
+ } catch {
122
+ return {};
123
+ }
124
+ };
125
+ const fulfill = (request) => {
126
+ const locator = String(request.confirmationId ?? "");
127
+ record("fulfill", locator);
128
+ const booking = bookings.get(locator);
129
+ if (booking === void 0) return notFound();
130
+ const outcome = fulfillQueue.shift() ?? "issued";
131
+ const echo = { ...request };
132
+ const issue = (committed = true) => {
133
+ const ticket = { ...nextTicket(), committed };
134
+ booking.tickets.push(ticket);
135
+ return [ticket];
136
+ };
137
+ const ticketsOut = (tickets2) => tickets2.map((ticket) => ({ ...ticketJson(ticket), isCommitted: ticket.committed, travelerGivenName: "TEST", travelerSurname: "PASSENGER" }));
138
+ if (typeof outcome === "object" && "error" in outcome) {
139
+ return json(200, { request: echo, errors: [appError(outcome.error, outcome.description ?? `No new tickets have been issued (${outcome.error}).`)] });
140
+ }
141
+ if (typeof outcome === "object" && "downline" in outcome) {
142
+ return json(200, { request: echo, errors: [appError("DOWNLINE_SERVICE_ERROR", DOWNLINE_TEXT[outcome.downline])] });
143
+ }
144
+ if (outcome === "issued" || outcome === "partial") {
145
+ if (booking.tickets.some((ticket) => ticket.ticketStatusCode === "TE")) {
146
+ return json(200, { request: echo, errors: [appError("BOOKING_FULFILLED", "The fulfillment operation cannot be processed. The booking is already fulfilled.")] });
147
+ }
148
+ const tickets2 = issue();
149
+ return json(200, {
150
+ timestamp: now().toISOString().slice(0, 19),
151
+ tickets: ticketsOut(tickets2),
152
+ request: echo,
153
+ ...outcome === "partial" ? { warnings: [appError("PARTIAL_FULFILLMENT", "At least one fulfillment operation was not processed correctly. Ticket issuance was unsuccessful due to a missing form of identification (FOID).", "WARNING")] } : {}
154
+ });
155
+ }
156
+ const { uncertain: reason } = outcome;
157
+ const issues = outcome.issued ?? ISSUES_BY_DEFAULT.has(reason);
158
+ const tickets = issues ? issue(reason !== "NOT_COMMITTED") : [];
159
+ switch (reason) {
160
+ case "TRANSPORT":
161
+ return "transport";
162
+ case "ABORTED":
163
+ return "abort";
164
+ case "GATEWAY_5XX":
165
+ return json(503, { errorCode: "ERR.2SG.PROVIDER.ERROR", message: "sabre-client/testing: 503 forzado" });
166
+ case "UNPARSEABLE":
167
+ return new Response("<html>sabre-client/testing: cuerpo ilegible forzado</html>", { status: 200 });
168
+ case "NOT_COMMITTED":
169
+ return json(200, { timestamp: now().toISOString().slice(0, 19), tickets: ticketsOut(tickets), request: echo });
170
+ default: {
171
+ const [category, description] = UNCERTAIN_TEXT[reason];
172
+ const item = appError(reason, description, category);
173
+ return json(200, { request: echo, ...tickets.length > 0 ? { tickets: ticketsOut(tickets) } : {}, ...category === "WARNING" ? { warnings: [item] } : { errors: [item] } });
174
+ }
175
+ }
176
+ };
177
+ const cancel = (request) => {
178
+ const locator = String(request.confirmationId ?? "");
179
+ record("cancelBooking", locator);
180
+ const booking = bookings.get(locator);
181
+ if (booking === void 0) return notFound();
182
+ const echo = { errorHandlingPolicy: "HALT_ON_ERROR", ...request };
183
+ const live = booking.tickets.filter((ticket) => ticket.ticketStatusCode === "TE");
184
+ if (request.flightTicketOperation === "VOID") {
185
+ if (live.length === 0) {
186
+ return json(200, {
187
+ request: echo,
188
+ errors: [
189
+ appError("NO_ITEMS_CANCELLED", "Nothing was cancelled - cancellation was interrupted due to errors", "WARNING"),
190
+ appError("UNABLE_TO_VOID_TICKET", "Unable to void ticket.", "CANCELLATION_ERROR")
191
+ ]
192
+ });
193
+ }
194
+ const outcome = voidQueue.shift() ?? "voided";
195
+ const voids = outcome === "voided" || outcome.voided !== false;
196
+ if (voids) {
197
+ for (const ticket of live) ticket.ticketStatusCode = "TV";
198
+ if (request.cancelAll === true) booking.flights = 0;
199
+ }
200
+ const voidedTickets = voids ? live.map((ticket) => ticket.number) : [];
201
+ if (outcome !== "voided") {
202
+ switch (outcome.uncertain) {
203
+ case "TRANSPORT":
204
+ return "transport";
205
+ case "ABORTED":
206
+ return "abort";
207
+ case "GATEWAY_5XX":
208
+ return json(503, { errorCode: "ERR.2SG.PROVIDER.ERROR", message: "sabre-client/testing: 503 forzado" });
209
+ case "UNPARSEABLE":
210
+ return new Response("<html>sabre-client/testing: cuerpo ilegible forzado</html>", { status: 200 });
211
+ case "END_TRANSACTION_PROBLEM":
212
+ return json(200, { request: echo, voidedTickets, errors: [appError("END_TRANSACTION_PROBLEM", "Confirmation is pending. Redisplay the booking", "WARNING")] });
213
+ }
214
+ }
215
+ return json(200, {
216
+ timestamp: now().toISOString().slice(0, 19),
217
+ request: echo,
218
+ voidedTickets,
219
+ ...request.retrieveBooking === true ? { booking: bookingJson(locator, booking) } : {}
220
+ });
221
+ }
222
+ if (request.cancelAll === true) booking.flights = 0;
223
+ return json(200, { timestamp: now().toISOString().slice(0, 19), request: echo, ...request.retrieveBooking === true ? { booking: bookingJson(locator, booking) } : {} });
224
+ };
225
+ return (url, body) => {
226
+ const path = new URL(url, "https://fake.invalid").pathname;
227
+ if (path.endsWith("/auth/token")) {
228
+ record("token");
229
+ return json(200, { access_token: "FAKE-TOKEN", token_type: "bearer", expires_in: 604800 });
230
+ }
231
+ if (!path.startsWith("/v1/trip/orders/")) return void 0;
232
+ const request = parse(body);
233
+ switch (path.slice("/v1/trip/orders/".length)) {
234
+ case "createBooking": {
235
+ const locator = nextLocator();
236
+ record("createBooking", locator);
237
+ const flights = (request.flightDetails?.flights ?? []).length;
238
+ bookings.set(locator, { flights: flights > 0 ? flights : 1, tickets: [] });
239
+ return json(200, { timestamp: now().toISOString().slice(0, 19), confirmationId: locator, request });
240
+ }
241
+ case "getBooking": {
242
+ const locator = String(request.confirmationId ?? "");
243
+ record("getBooking", locator);
244
+ const booking = bookings.get(locator);
245
+ return booking === void 0 ? notFound() : json(200, bookingJson(locator, booking));
246
+ }
247
+ case "cancelBooking":
248
+ return cancel(request);
249
+ case "fulfillFlightTickets":
250
+ return fulfill(request);
251
+ default:
252
+ return json(404, { errorCode: "ERR.2SG.NOT_FOUND", message: `sabre-client/testing: ${path} no est\xE1 simulado` });
253
+ }
254
+ };
255
+ }
256
+
4
257
  // src/queue/queue-access.ts
5
258
  import { XMLParser as XMLParser2 } from "fast-xml-parser";
6
259
 
@@ -126,6 +379,304 @@ function lineFields(value) {
126
379
  };
127
380
  }
128
381
 
382
+ // src/queue/host-date.ts
383
+ var MONTHS = ["JAN", "FEB", "MAR", "APR", "MAY", "JUN", "JUL", "AUG", "SEP", "OCT", "NOV", "DEC"];
384
+ var DAY_MS = 864e5;
385
+ var HALF_YEAR_DAYS = 184;
386
+ function resolveHostDate(text2, referenceDate, options = {}) {
387
+ const reference = referenceDay(referenceDate);
388
+ const direction = options.direction ?? "past";
389
+ if (direction !== "past" && direction !== "nearest") {
390
+ throw new TypeError("sabre-client: direction debe ser 'past' o 'nearest'");
391
+ }
392
+ if (typeof text2 !== "string") return void 0;
393
+ const parsed = parseHostDate(text2.trim().toUpperCase());
394
+ if (parsed === void 0) return void 0;
395
+ const year = parsed.year ?? resolveYear(parsed.month, parsed.day, reference, direction);
396
+ if (year === void 0 || !isValidDate(year, parsed.month, parsed.day)) return void 0;
397
+ const date = `${pad(year, 4)}-${pad(parsed.month, 2)}-${pad(parsed.day, 2)}`;
398
+ return parsed.time === void 0 ? date : `${date}T${parsed.time}`;
399
+ }
400
+ function parseHostDate(text2) {
401
+ let match = /^(\d{1,2})([A-Z]{3})(\d{2})?$/.exec(text2);
402
+ if (match !== null) {
403
+ const month2 = MONTHS.indexOf(match[2]) + 1;
404
+ if (month2 === 0) return void 0;
405
+ return {
406
+ ...match[3] !== void 0 && { year: 2e3 + Number(match[3]) },
407
+ month: month2,
408
+ day: Number(match[1])
409
+ };
410
+ }
411
+ match = /^(?:(\d{4})-)?(\d{2})-(\d{2})(?:T(\d{2}):(\d{2}))?$/.exec(text2);
412
+ if (match === null) return void 0;
413
+ const [, year, month, day, hours, minutes] = match;
414
+ if (hours !== void 0 && (Number(hours) > 23 || Number(minutes) > 59)) return void 0;
415
+ return {
416
+ ...year !== void 0 && { year: Number(year) },
417
+ month: Number(month),
418
+ day: Number(day),
419
+ ...hours !== void 0 && { time: `${hours}:${minutes}` }
420
+ };
421
+ }
422
+ function resolveYear(month, day, reference, direction) {
423
+ const year = new Date(reference).getUTCFullYear();
424
+ if (direction === "past") {
425
+ for (let candidate = year; candidate >= year - 8; candidate--) {
426
+ if (isValidDate(candidate, month, day) && dayNumber(candidate, month, day) <= reference) return candidate;
427
+ }
428
+ return void 0;
429
+ }
430
+ let best;
431
+ for (const candidate of [year + 1, year, year - 1]) {
432
+ if (!isValidDate(candidate, month, day)) continue;
433
+ const distance = Math.abs(dayNumber(candidate, month, day) - reference);
434
+ if (best === void 0 || distance < best.distance) best = { year: candidate, distance };
435
+ }
436
+ return best !== void 0 && best.distance <= HALF_YEAR_DAYS * DAY_MS ? best.year : void 0;
437
+ }
438
+ function referenceDay(referenceDate) {
439
+ if (referenceDate instanceof Date) {
440
+ const time = referenceDate.getTime();
441
+ if (Number.isNaN(time)) throw new TypeError("sabre-client: referenceDate es un Date inv\xE1lido");
442
+ return time - (time % DAY_MS + DAY_MS) % DAY_MS;
443
+ }
444
+ if (typeof referenceDate === "string") {
445
+ const match = /^(\d{4})-(\d{2})-(\d{2})(?:T.*)?$/.exec(referenceDate.trim());
446
+ if (match !== null) {
447
+ const [year, month, day] = [Number(match[1]), Number(match[2]), Number(match[3])];
448
+ if (isValidDate(year, month, day)) return dayNumber(year, month, day);
449
+ }
450
+ }
451
+ throw new TypeError("sabre-client: referenceDate es obligatoria, como 'YYYY-MM-DD' o un Date v\xE1lido");
452
+ }
453
+ function dayNumber(year, month, day) {
454
+ const date = /* @__PURE__ */ new Date(0);
455
+ date.setUTCFullYear(year, month - 1, day);
456
+ return date.getTime();
457
+ }
458
+ function isValidDate(year, month, day) {
459
+ if (month < 1 || month > 12 || day < 1) return false;
460
+ const date = /* @__PURE__ */ new Date(0);
461
+ date.setUTCFullYear(year, month - 1, day);
462
+ return date.getUTCMonth() === month - 1 && date.getUTCDate() === day;
463
+ }
464
+ function pad(value, length) {
465
+ return String(value).padStart(length, "0");
466
+ }
467
+
468
+ // src/queue/queue-paragraph.ts
469
+ var MESSAGE = /^\s*(\d{3})\s{2,}(\S.*?)\s*$/;
470
+ var PASSENGER_LINE = /^\s*\d+\.\d+[A-Z]/;
471
+ var PASSENGER_SPLIT = /\s+(?=\d+\.\d+[A-Z])/;
472
+ var PASSENGER = /^(\d+\.\d+)([^/*]+?)\s*(?:\/([^*]*?))?\s*(?:\*(.*?))?\s*$/;
473
+ var AIR_SEGMENT = /^\s*(\d{1,2})\s+([A-Z0-9]{2})\s*(\d{1,4})([A-Z])\s+(\d{2}[A-Z]{3})\s+[A-Z0-9]\s+([A-Z]{3})([A-Z]{3})\s+([A-Z]{2})(\d{1,3})(?=\s|$)(.*)$/;
474
+ var SEGMENT_TIMES = /^\s+(\d{3,4}[APNM]?)\s+(\d{3,4}[APNM]?)(?=\s|$)/;
475
+ var OTHER_SEGMENT = /^\s*(\d{1,2})\s+([A-Z]{3})\s+([A-Z0-9]{2})\s+(\d{2}[A-Z]{3})\s+[A-Z0-9]\s+([A-Z]{2})(\d{1,3})(?=\s|$)/;
476
+ var NO_ITINERARY = /^\s*NO ITIN\s*$/;
477
+ var TICKETING_HEADER = /^\s*TKT\/TIME LIMIT\s*$/;
478
+ var TICKETING_ENTRY = /^\s*(\d+)\.(.*?)\s*$/;
479
+ var TICKETED = /^T-(\d{2}[A-Z]{3})?(?:-([A-Z0-9]{3,4})\*([A-Z0-9]+))?(?:\/.*)?$/;
480
+ var ARRANGEMENT = /^(TA[WCMTX])([A-Z0-9]{3,4})?(\d{2}[A-Z]{3})?\d{0,3}\/.*$/;
481
+ var TICKET = /^(TE|TK)\s+(\d{13})\b/;
482
+ var RECEIVED = /^\s*RECEIVED FROM -\s?(.*?)\s*$/;
483
+ var SIGNATURE = /^\s*([A-Z0-9]{3,4})\.(\S+)\s+(\d{2})(\d{2})\/(\d{2}[A-Z]{3}\d{2})\s+([A-Z0-9]{6})\s+H\s*$/;
484
+ var Phase = { Messages: 0, Passengers: 1, Segments: 2, Body: 3 };
485
+ function parseQueueParagraph(lines, options) {
486
+ if (!Array.isArray(lines)) throw new TypeError("sabre-client: lines debe ser un array de strings");
487
+ if (typeof options !== "object" || options === null) {
488
+ throw new TypeError("sabre-client: referenceDate es obligatoria, como 'YYYY-MM-DD' o un Date v\xE1lido");
489
+ }
490
+ const { referenceDate } = options;
491
+ referenceDay(referenceDate);
492
+ const result = { messages: [], passengers: [], segments: [], unparsed: [] };
493
+ const signatureIndex = lastSignatureIndex(lines);
494
+ let phase = Phase.Messages;
495
+ let inTicketing = false;
496
+ lines.forEach((value, index) => {
497
+ const line = typeof value === "string" ? value : "";
498
+ if (line.trim() === "") return;
499
+ if (index === signatureIndex) {
500
+ result.receivedFrom = { ...result.receivedFrom, ...parseSignature(line, referenceDate) };
501
+ return;
502
+ }
503
+ if (inTicketing) {
504
+ const entry = TICKETING_ENTRY.exec(line);
505
+ if (entry !== null) {
506
+ const parsed = parseTicketingEntry(Number(entry[1]), entry[2], referenceDate);
507
+ if (parsed === void 0) result.unparsed.push(line);
508
+ else result.ticketing.entries.push(parsed);
509
+ return;
510
+ }
511
+ inTicketing = false;
512
+ }
513
+ if (TICKETING_HEADER.test(line)) {
514
+ phase = Phase.Body;
515
+ inTicketing = true;
516
+ result.ticketing ??= { entries: [], issued: false };
517
+ return;
518
+ }
519
+ const received = RECEIVED.exec(line);
520
+ if (received !== null) {
521
+ phase = Phase.Body;
522
+ result.receivedFrom = { ...result.receivedFrom, ...received[1] !== "" && { name: received[1] } };
523
+ return;
524
+ }
525
+ if (phase === Phase.Messages) {
526
+ const message = MESSAGE.exec(line);
527
+ if (message !== null) {
528
+ result.messages.push({ instructionCode: Number(message[1]), text: message[2] });
529
+ return;
530
+ }
531
+ }
532
+ if (phase <= Phase.Passengers && PASSENGER_LINE.test(line)) {
533
+ const passengers = parsePassengers(line);
534
+ if (passengers !== void 0) {
535
+ phase = Phase.Passengers;
536
+ result.passengers.push(...passengers);
537
+ return;
538
+ }
539
+ }
540
+ if (phase <= Phase.Segments) {
541
+ if (NO_ITINERARY.test(line)) {
542
+ phase = Phase.Segments;
543
+ return;
544
+ }
545
+ const segment = parseSegment(line, referenceDate);
546
+ if (segment !== void 0) {
547
+ phase = Phase.Segments;
548
+ result.segments.push(segment);
549
+ return;
550
+ }
551
+ }
552
+ result.unparsed.push(line);
553
+ });
554
+ if (result.ticketing !== void 0) {
555
+ result.ticketing.issued = result.ticketing.entries.some((entry) => entry.ticketNumber !== void 0);
556
+ }
557
+ return result;
558
+ }
559
+ function parsePassengers(line) {
560
+ const passengers = [];
561
+ for (const part of line.trim().split(PASSENGER_SPLIT)) {
562
+ const match = PASSENGER.exec(part);
563
+ if (match === null) return void 0;
564
+ const [, nameNumber, surname, givenName, nameRemark] = match;
565
+ passengers.push({
566
+ nameNumber,
567
+ surname: surname.trim(),
568
+ ...givenName !== void 0 && givenName !== "" && { givenName },
569
+ ...nameRemark !== void 0 && nameRemark !== "" && { nameRemark },
570
+ text: part.slice(nameNumber.length).trim()
571
+ });
572
+ }
573
+ return passengers;
574
+ }
575
+ function parseSegment(line, referenceDate) {
576
+ const air = AIR_SEGMENT.exec(line);
577
+ if (air !== null) {
578
+ const [, number2, airlineCode, flightNumber, bookingClass, date2, from, to, status2, quantity2, rest] = air;
579
+ const times = SEGMENT_TIMES.exec(rest);
580
+ const departureTime = times === null ? void 0 : hostTime(times[1]);
581
+ const arrivalTime = times === null ? void 0 : hostTime(times[2]);
582
+ return {
583
+ kind: "air",
584
+ number: Number(number2),
585
+ ...withDate(date2, referenceDate),
586
+ status: status2,
587
+ quantity: Number(quantity2),
588
+ airlineCode,
589
+ flightNumber: Number(flightNumber),
590
+ bookingClass,
591
+ fromAirportCode: from,
592
+ toAirportCode: to,
593
+ ...departureTime !== void 0 && { departureTime },
594
+ ...arrivalTime !== void 0 && { arrivalTime },
595
+ text: line.trim()
596
+ };
597
+ }
598
+ const other = OTHER_SEGMENT.exec(line);
599
+ if (other === null) return void 0;
600
+ const [, number, segmentType, vendorCode, date, status, quantity] = other;
601
+ return {
602
+ kind: "other",
603
+ number: Number(number),
604
+ ...withDate(date, referenceDate),
605
+ status,
606
+ quantity: Number(quantity),
607
+ segmentType,
608
+ vendorCode,
609
+ text: line.trim()
610
+ };
611
+ }
612
+ function parseTicketingEntry(number, text2, referenceDate) {
613
+ const ticketed = TICKETED.exec(text2);
614
+ if (ticketed !== null) {
615
+ const [, date, pseudoCityCode, agentSine] = ticketed;
616
+ return {
617
+ number,
618
+ code: "T-",
619
+ ...date !== void 0 && withDate(date, referenceDate),
620
+ ...pseudoCityCode !== void 0 && { pseudoCityCode },
621
+ ...agentSine !== void 0 && { agentSine },
622
+ text: text2
623
+ };
624
+ }
625
+ const arrangement = ARRANGEMENT.exec(text2);
626
+ if (arrangement !== null) {
627
+ const [, code, pseudoCityCode, date] = arrangement;
628
+ return {
629
+ number,
630
+ code,
631
+ ...date !== void 0 && withDate(date, referenceDate),
632
+ ...pseudoCityCode !== void 0 && { pseudoCityCode },
633
+ text: text2
634
+ };
635
+ }
636
+ const ticket = TICKET.exec(text2);
637
+ if (ticket !== null) return { number, code: ticket[1], ticketNumber: ticket[2], text: text2 };
638
+ return void 0;
639
+ }
640
+ function lastSignatureIndex(lines) {
641
+ for (let index = lines.length - 1; index >= 0; index--) {
642
+ const line = lines[index];
643
+ if (typeof line === "string" && SIGNATURE.test(line)) return index;
644
+ }
645
+ return -1;
646
+ }
647
+ function parseSignature(line, referenceDate) {
648
+ const [, pseudoCityCode, sign, hours, minutes, date, locator] = SIGNATURE.exec(line);
649
+ const agentSine = /\*([A-Z0-9]+)$/.exec(sign)?.[1];
650
+ const day = Number(hours) <= 23 && Number(minutes) <= 59 ? resolveHostDate(date, referenceDate) : void 0;
651
+ return {
652
+ pseudoCityCode,
653
+ ...agentSine !== void 0 && { agentSine },
654
+ ...day !== void 0 && { receivedAt: `${day}T${hours}:${minutes}` },
655
+ locator
656
+ };
657
+ }
658
+ function withDate(text2, referenceDate) {
659
+ const date = resolveHostDate(text2, referenceDate, { direction: "nearest" });
660
+ return date === void 0 ? {} : { date };
661
+ }
662
+ function hostTime(text2) {
663
+ const match = /^(\d{1,2})(\d{2})([APNM])?$/.exec(text2);
664
+ if (match === null) return void 0;
665
+ let hours = Number(match[1]);
666
+ const minutes = Number(match[2]);
667
+ const suffix = match[3];
668
+ if (minutes > 59) return void 0;
669
+ if (suffix === void 0) {
670
+ if (hours > 23) return void 0;
671
+ } else {
672
+ if (hours < 1 || hours > 12) return void 0;
673
+ if (suffix === "A" || suffix === "M") hours %= 12;
674
+ else if (suffix === "P") hours = hours % 12 + 12;
675
+ else if (hours !== 12) return void 0;
676
+ }
677
+ return `${String(hours).padStart(2, "0")}:${String(minutes).padStart(2, "0")}`;
678
+ }
679
+
129
680
  // src/queue/queue-place.ts
130
681
  var SIGNATURE_LINE = /\s\d{4}\/\d{2}[A-Z]{3}\d{2}\s+([A-Z0-9]{6})\s+H\s*$/;
131
682
  function signatureLocator(lines) {
@@ -140,9 +691,18 @@ function signatureLocator(lines) {
140
691
  var SWS_NAMESPACE = "http://webservices.sabre.com/sabreXML/2011/10";
141
692
  var STL_NAMESPACE = "http://services.sabre.com/STL/v01";
142
693
  var SIGNATURE_DATE = "1200/01JAN26";
143
- var LOCATOR = /^[A-Z0-9]{6}$/;
694
+ var LOCATOR2 = /^[A-Z0-9]{6}$/;
144
695
  var NAVIGATION = { I: "ignore", QR: "remove", QXI: "exit" };
145
- function fail(message) {
696
+ var QUEUE_FAKE_SELECTION_FUNCTIONS = ["QD", "AK", "AL", "PI", "AC"];
697
+ var LIST_SELECTION_FUNCTIONS = ["QD", "AK"];
698
+ var MONTHS2 = ["JAN", "FEB", "MAR", "APR", "MAY", "JUN", "JUL", "AUG", "SEP", "OCT", "NOV", "DEC"];
699
+ function placedOn(placedAt, criteria) {
700
+ const date = /^(\d{1,2})([A-Z]{3})$/.exec(criteria.toUpperCase());
701
+ const placed = /(\d{2})-(\d{2})T/.exec(placedAt ?? "");
702
+ if (date === null || placed === null) return false;
703
+ return Number(placed[1]) === MONTHS2.indexOf(date[2]) + 1 && Number(placed[2]) === Number(date[1]);
704
+ }
705
+ function fail2(message) {
146
706
  throw new TypeError(`sabre-client/testing: ${message}`);
147
707
  }
148
708
  var parser2 = new XMLParser3({
@@ -188,18 +748,18 @@ function refOfKey(key) {
188
748
  if (key.startsWith(NAME_PREFIX)) return { name: key.slice(NAME_PREFIX.length) };
189
749
  return /^\d+$/.test(key) ? Number(key) : key;
190
750
  }
191
- function keyAttribute(key, pad = false) {
751
+ function keyAttribute(key, pad2 = false) {
192
752
  if (key.startsWith(NAME_PREFIX)) return `Name="${escape(key.slice(NAME_PREFIX.length))}"`;
193
- return `Number="${escape(pad && /^\d+$/.test(key) ? key.padStart(4, "0") : key)}"`;
753
+ return `Number="${escape(pad2 && /^\d+$/.test(key) ? key.padStart(4, "0") : key)}"`;
194
754
  }
195
755
  function validateKey(key) {
196
756
  if (/^\d+$/.test(key)) {
197
757
  const number = Number(key);
198
- if (number < MIN_QUEUE_NUMBER || number > MAX_QUEUE_NUMBER) fail(`la cola ${key} est\xE1 fuera de 0\u2013511`);
758
+ if (number < MIN_QUEUE_NUMBER || number > MAX_QUEUE_NUMBER) fail2(`la cola ${key} est\xE1 fuera de 0\u2013511`);
199
759
  return;
200
760
  }
201
761
  if (key.startsWith(NAME_PREFIX) ? key.slice(NAME_PREFIX.length).trim() === "" : key.trim() === "") {
202
- fail("el nombre de una cola no puede estar vac\xEDo");
762
+ fail2("el nombre de una cola no puede estar vac\xEDo");
203
763
  }
204
764
  }
205
765
  function shortDate(date) {
@@ -211,26 +771,26 @@ function longDate(date) {
211
771
  function toMessage(item, index) {
212
772
  const label = `queues['${MESSAGE_QUEUE}'][${index}]`;
213
773
  if (!Array.isArray(item) || item.some((line) => typeof line !== "string")) {
214
- fail(`${label}: la S es la cola de mensajes; cada elemento es el texto de un mensaje (string[])`);
774
+ fail2(`${label}: la S es la cola de mensajes; cada elemento es el texto de un mensaje (string[])`);
215
775
  }
216
776
  const lines = item;
217
- if (!lines.some((line) => line.trim() !== "")) fail(`${label}: el mensaje no puede estar vac\xEDo`);
777
+ if (!lines.some((line) => line.trim() !== "")) fail2(`${label}: el mensaje no puede estar vac\xEDo`);
218
778
  return [...lines];
219
779
  }
220
780
  function toPnr(item, label) {
221
781
  if (Array.isArray(item)) {
222
782
  const lines2 = item;
223
783
  const locator = signatureLocator(lines2);
224
- if (locator === void 0) fail(`${label}: el p\xE1rrafo no tiene l\xEDnea de firma; pasa { locator, paragraph }`);
784
+ if (locator === void 0) fail2(`${label}: el p\xE1rrafo no tiene l\xEDnea de firma; pasa { locator, paragraph }`);
225
785
  return { locator, paragraph: [...lines2], primaryPassenger: void 0, placedAt: void 0, placedBy: void 0 };
226
786
  }
227
787
  const pnr = item;
228
- if (typeof pnr !== "object" || pnr === null || typeof pnr.locator !== "string" || !LOCATOR.test(pnr.locator)) {
229
- fail(`${label}: locator debe ser de 6 caracteres alfanum\xE9ricos en may\xFAscula`);
788
+ if (typeof pnr !== "object" || pnr === null || typeof pnr.locator !== "string" || !LOCATOR2.test(pnr.locator)) {
789
+ fail2(`${label}: locator debe ser de 6 caracteres alfanum\xE9ricos en may\xFAscula`);
230
790
  }
231
791
  const lines = [...pnr.paragraph ?? []];
232
792
  const signed = signatureLocator(lines);
233
- if (signed !== void 0 && signed !== pnr.locator) fail(`${label}: la l\xEDnea de firma es de ${signed}, no de ${pnr.locator}`);
793
+ if (signed !== void 0 && signed !== pnr.locator) fail2(`${label}: la l\xEDnea de firma es de ${signed}, no de ${pnr.locator}`);
234
794
  if (signed === void 0) lines.push(`XXXX.XXXX*AAA ${SIGNATURE_DATE} ${pnr.locator} H`);
235
795
  return {
236
796
  locator: pnr.locator,
@@ -281,22 +841,22 @@ var ACTIVITY_COUNTERS = [
281
841
  ["unableToReach", "UnableToReach"]
282
842
  ];
283
843
  function toActivity(value, label) {
284
- if (typeof value !== "object" || value === null || Array.isArray(value)) fail(`${label} debe ser un objeto de contadores`);
844
+ if (typeof value !== "object" || value === null || Array.isArray(value)) fail2(`${label} debe ser un objeto de contadores`);
285
845
  const known = new Set(ACTIVITY_COUNTERS.map(([key]) => key));
286
846
  const result = {};
287
847
  for (const [key, count] of Object.entries(value)) {
288
- if (key === "numMessages") fail(`${label}.numMessages sale de la cola del fake: no se siembra`);
289
- if (!known.has(key)) fail(`${label}.${key} no es un contador de queue.activity`);
290
- if (!Number.isInteger(count) || count < 0) fail(`${label}.${key} debe ser un entero de 0 en adelante`);
848
+ if (key === "numMessages") fail2(`${label}.numMessages sale de la cola del fake: no se siembra`);
849
+ if (!known.has(key)) fail2(`${label}.${key} no es un contador de queue.activity`);
850
+ if (!Number.isInteger(count) || count < 0) fail2(`${label}.${key} debe ser un entero de 0 en adelante`);
291
851
  result[key] = count;
292
852
  }
293
853
  return result;
294
854
  }
295
855
  function toHistory(value, label) {
296
- if (!Array.isArray(value)) fail(`${label} debe ser una lista de transacciones`);
856
+ if (!Array.isArray(value)) fail2(`${label} debe ser una lista de transacciones`);
297
857
  return value.map((transaction, index) => {
298
858
  const at = `${label}[${index}]`;
299
- if (typeof transaction !== "object" || transaction === null) fail(`${at} debe ser un objeto`);
859
+ if (typeof transaction !== "object" || transaction === null) fail2(`${at} debe ser un objeto`);
300
860
  const t = transaction;
301
861
  for (const [list, name] of [
302
862
  [t.flightSegments, "flightSegments"],
@@ -304,12 +864,12 @@ function toHistory(value, label) {
304
864
  [t.remarks, "remarks"]
305
865
  ]) {
306
866
  if (list === void 0) continue;
307
- if (!Array.isArray(list)) fail(`${at}.${name} debe ser una lista`);
867
+ if (!Array.isArray(list)) fail2(`${at}.${name} debe ser una lista`);
308
868
  list.forEach((entry, position) => {
309
- if (typeof entry?.action !== "string" || entry.action.trim() === "") fail(`${at}.${name}[${position}].action debe ser un string no vac\xEDo`);
869
+ if (typeof entry?.action !== "string" || entry.action.trim() === "") fail2(`${at}.${name}[${position}].action debe ser un string no vac\xEDo`);
310
870
  });
311
871
  }
312
- if (t.accounting !== void 0 && !Array.isArray(t.accounting)) fail(`${at}.accounting debe ser una lista`);
872
+ if (t.accounting !== void 0 && !Array.isArray(t.accounting)) fail2(`${at}.accounting debe ser una lista`);
313
873
  return t;
314
874
  });
315
875
  }
@@ -352,11 +912,57 @@ function historicalInfo(t) {
352
912
  "</HistoricalInfo>"
353
913
  ].join("");
354
914
  }
915
+ var DAILY_SALES_DATE = /^\d{4}-\d{2}-\d{2}$/;
916
+ function toDailySalesDay(value, label) {
917
+ const day = Array.isArray(value) ? { transactions: value } : value;
918
+ if (typeof day !== "object" || day === null || !Array.isArray(day.transactions)) fail2(`${label} debe ser una lista de transacciones o { transactions }`);
919
+ if (day.status !== void 0 && !["complete", "incomplete", "deferred"].includes(day.status)) fail2(`${label}.status debe ser complete, incomplete o deferred`);
920
+ for (const [index, t] of day.transactions.entries()) {
921
+ const at = `${label}.transactions[${index}]`;
922
+ if (typeof t !== "object" || t === null) fail2(`${at} debe ser un objeto`);
923
+ for (const field of ["documentType", "transactionCode", "settlementType", "documentNumber", "agentSine", "transactionDateTime"]) {
924
+ if (typeof t[field] !== "string") fail2(`${at}.${field} debe ser un string`);
925
+ }
926
+ if (typeof t.airline?.code !== "string") fail2(`${at}.airline.code debe ser un string`);
927
+ }
928
+ return day;
929
+ }
930
+ function transactionXml(t) {
931
+ const el = (name, value) => value === void 0 ? "" : `<asr:${name}>${escape(value)}</asr:${name}>`;
932
+ const payments = t.payments === void 0 ? "" : `<asr:Payments>${el("CurrencyCode", t.payments.currencyCode)}<asr:DocumentPayment>${el("PaymentTotal", t.payments.total)}${t.payments.details.map((d) => `<asr:PaymentDetail${attrs([["fop", d.fop], ["category", d.category], ["cardType", d.cardType]])}>${el("PaymentAmount", d.amount)}</asr:PaymentDetail>`).join("")}</asr:DocumentPayment></asr:Payments>`;
933
+ const flags = attrs([
934
+ ["nonInteractive", t.nonInteractive === void 0 ? void 0 : String(t.nonInteractive)],
935
+ ["electronicDocument", t.electronicDocument === void 0 ? void 0 : String(t.electronicDocument)],
936
+ ["itineraryType", t.itineraryType],
937
+ ["automatedRefund", t.automatedRefund === void 0 ? void 0 : String(t.automatedRefund)]
938
+ ]);
939
+ return [
940
+ `<asr:Transaction${flags}>`,
941
+ el("DocumentType", t.documentType),
942
+ el("SettlementType", t.settlementType),
943
+ el("PnrLocator", t.pnrLocator),
944
+ el("PassengerName", t.passengerName),
945
+ "<asr:DocumentDetails>",
946
+ el("DocumentNumber", t.documentNumber),
947
+ el("LastConjunctiveDocumentNumber", t.lastConjunctiveDocumentNumber),
948
+ el("StockType", t.stockType),
949
+ el("DocumentStatusCode", t.documentStatusCode),
950
+ el("TransactionCode", t.transactionCode),
951
+ "</asr:DocumentDetails>",
952
+ `<asr:AirlineCode${attrs([["AirlineNumber", t.airline.number]])}>${escape(t.airline.code)}</asr:AirlineCode>`,
953
+ t.commission === void 0 ? "" : `<asr:Commission>${el("Amount", t.commission.amount)}${el("Percent", t.commission.percent)}</asr:Commission>`,
954
+ payments,
955
+ el("AgentSine", t.agentSine),
956
+ el("AgentPseudoCity", t.agentPseudoCity),
957
+ el("TransactionDateTime", t.transactionDateTime),
958
+ "</asr:Transaction>"
959
+ ].join("");
960
+ }
355
961
  var NOT_ALLOWED_INSIDE_QUEUE = "NAK6 -NOT ALLOWED- FINISH OR IGNORE OTHER ACTIVITY";
356
962
  function createQueueFake(options = {}) {
357
- if (typeof options !== "object" || options === null) fail("options debe ser un objeto");
963
+ if (typeof options !== "object" || options === null) fail2("options debe ser un objeto");
358
964
  const now = options.now ?? (() => /* @__PURE__ */ new Date());
359
- if (typeof now !== "function") fail("now debe ser una funci\xF3n");
965
+ if (typeof now !== "function") fail2("now debe ser una funci\xF3n");
360
966
  const pnrs = /* @__PURE__ */ new Map();
361
967
  const queues = /* @__PURE__ */ new Map();
362
968
  const sessions = /* @__PURE__ */ new Map();
@@ -366,7 +972,7 @@ function createQueueFake(options = {}) {
366
972
  const pnr = toPnr(item, label);
367
973
  const known = pnrs.get(pnr.locator);
368
974
  if (known !== void 0 && JSON.stringify(known.paragraph) !== JSON.stringify(pnr.paragraph)) {
369
- fail(`${label}: el PNR ${pnr.locator} aparece con dos p\xE1rrafos distintos`);
975
+ fail2(`${label}: el PNR ${pnr.locator} aparece con dos p\xE1rrafos distintos`);
370
976
  }
371
977
  if (known === void 0) {
372
978
  pnrs.set(pnr.locator, { locator: pnr.locator, paragraph: pnr.paragraph, primaryPassenger: pnr.primaryPassenger });
@@ -376,7 +982,7 @@ function createQueueFake(options = {}) {
376
982
  for (const [optionName, items] of Object.entries(options.queues ?? {})) {
377
983
  const key = optionKey(optionName);
378
984
  validateKey(key);
379
- if (!Array.isArray(items)) fail(`queues['${key}'] debe ser una lista`);
985
+ if (!Array.isArray(items)) fail2(`queues['${key}'] debe ser una lista`);
380
986
  if (key === MESSAGE_QUEUE) {
381
987
  queues.set(key, items.map((item, index) => ({ locator: "", placedAt: void 0, placedBy: void 0, message: toMessage(item, index) })));
382
988
  continue;
@@ -396,9 +1002,25 @@ function createQueueFake(options = {}) {
396
1002
  validateKey(key);
397
1003
  activity.set(key, toActivity(counters, `activity['${key}']`));
398
1004
  }
1005
+ const dailySalesTimeZone = options.dailySalesTimeZone ?? "UTC";
1006
+ let dailySalesDay;
1007
+ try {
1008
+ dailySalesDay = new Intl.DateTimeFormat("en-CA", { timeZone: dailySalesTimeZone, year: "numeric", month: "2-digit", day: "2-digit" });
1009
+ } catch {
1010
+ fail2(`dailySalesTimeZone: '${dailySalesTimeZone}' no es una zona IANA v\xE1lida`);
1011
+ }
1012
+ const dailySales = /* @__PURE__ */ new Map();
1013
+ for (const [pccKey, days] of Object.entries(options.dailySales ?? {})) {
1014
+ if (!/^[A-Z0-9]{3,4}$/.test(pccKey)) fail2(`dailySales['${pccKey}']: la clave debe ser un PCC de 3 o 4 letras may\xFAsculas o d\xEDgitos`);
1015
+ if (typeof days !== "object" || days === null) fail2(`dailySales['${pccKey}'] debe ser un objeto con las fechas`);
1016
+ for (const [date, day] of Object.entries(days)) {
1017
+ if (!DAILY_SALES_DATE.test(date)) fail2(`dailySales['${pccKey}']['${date}']: la fecha debe tener la forma 'YYYY-MM-DD'`);
1018
+ dailySales.set(`${pccKey} ${date}`, toDailySalesDay(day, `dailySales['${pccKey}']['${date}']`));
1019
+ }
1020
+ }
399
1021
  const histories = /* @__PURE__ */ new Map();
400
1022
  for (const [locator, transactions] of Object.entries(options.histories ?? {})) {
401
- if (!LOCATOR.test(locator)) fail(`histories['${locator}']: la clave debe ser un localizador de 6 caracteres alfanum\xE9ricos en may\xFAscula`);
1023
+ if (!LOCATOR2.test(locator)) fail2(`histories['${locator}']: la clave debe ser un localizador de 6 caracteres alfanum\xE9ricos en may\xFAscula`);
402
1024
  histories.set(locator, toHistory(transactions, `histories['${locator}']`));
403
1025
  }
404
1026
  const queue = (key) => {
@@ -438,7 +1060,43 @@ function createQueueFake(options = {}) {
438
1060
  const placeRS = (inner) => `<QueuePlaceRS xmlns="${SWS_NAMESPACE}" xmlns:stl="${STL_NAMESPACE}" Version="2.0.4">${inner}</QueuePlaceRS>`;
439
1061
  const accessError = (message) => accessRS(results("NotProcessed", hostResult("Error", message)));
440
1062
  const placeError = (message) => placeRS(results("NotProcessed", hostResult("Error", message)));
1063
+ const matchesFilter = (entry, fn, criteria) => {
1064
+ if (entry.message !== void 0) return false;
1065
+ const wanted = criteria.trim().toUpperCase();
1066
+ const paragraph = () => parseQueueParagraph(pnrs.get(entry.locator)?.paragraph ?? [], { referenceDate: now() });
1067
+ switch (fn.toUpperCase()) {
1068
+ case "QD":
1069
+ return placedOn(entry.placedAt, wanted);
1070
+ case "AK":
1071
+ return `${entry.placedBy?.pseudoCityCode ?? ""}${entry.placedBy?.agentSine ?? ""}`.toUpperCase() === wanted;
1072
+ case "AL":
1073
+ return paragraph().segments.some((segment) => segment.kind === "air" && segment.airlineCode === wanted);
1074
+ case "AC":
1075
+ return paragraph().segments.some((segment) => segment.status.startsWith(wanted));
1076
+ case "PI": {
1077
+ const code = Number(wanted);
1078
+ if (entry.instructionCode !== void 0) return entry.instructionCode === code;
1079
+ return paragraph().messages.some((message) => message.instructionCode === code);
1080
+ }
1081
+ default:
1082
+ return void 0;
1083
+ }
1084
+ };
1085
+ const selectionOf = (rq, allowed) => {
1086
+ const filters = nodes(rq.Selection).map((selection) => ({ fn: (attr(selection, "Function") ?? "").toUpperCase(), criteria: attr(selection, "Criteria") ?? "" }));
1087
+ if (filters.length === 0) return {};
1088
+ const unsupported = filters.find((filter) => !allowed.includes(filter.fn));
1089
+ if (unsupported !== void 0) return { error: `sabre-client/testing: selection ${unsupported.fn} no est\xE1 simulado (simula ${allowed.join(", ")})` };
1090
+ return { matches: (entry) => filters.every((filter) => matchesFilter(entry, filter.fn, filter.criteria) === true) };
1091
+ };
1092
+ const advance = (session) => {
1093
+ const inside = session.inside;
1094
+ if (inside?.matches === void 0) return;
1095
+ const entries = queue(inside.key);
1096
+ while (inside.index < entries.length && !inside.matches(entries[inside.index])) inside.index += 1;
1097
+ };
441
1098
  const step = (session) => {
1099
+ advance(session);
442
1100
  const inside = session.inside;
443
1101
  const entry = inside === void 0 ? void 0 : queue(inside.key)[inside.index];
444
1102
  if (entry === void 0) {
@@ -452,19 +1110,51 @@ function createQueueFake(options = {}) {
452
1110
  return accessRS(`${results("Complete", success)}${lineXml(entry)}<Paragraph>${textLines2(pnr.paragraph)}</Paragraph>`);
453
1111
  };
454
1112
  const waiting = (session, key, entries) => entries.length - (session.inside?.key === key && entries[session.inside.index] !== void 0 ? 1 : 0);
1113
+ const countRS = (inner) => `<QueueCountRS xmlns="${SWS_NAMESPACE}" xmlns:stl="${STL_NAMESPACE}" Version="2.2.1">${inner}</QueueCountRS>`;
455
1114
  const count = (session, rq) => {
456
- const pcc = attr(node2(node2(rq.QueueInfo)?.QueueIdentifier), "PseudoCityCode") ?? "";
1115
+ const identifier = node2(node2(rq.QueueInfo)?.QueueIdentifier);
1116
+ const pcc = attr(identifier, "PseudoCityCode") ?? "";
1117
+ const countError = (message) => countRS(results("NotProcessed", hostResult("Error", message)));
1118
+ const range = node2(identifier?.Range);
1119
+ const requested = requestedKey(identifier);
1120
+ const { matches, error } = selectionOf(rq, QUEUE_FAKE_SELECTION_FUNCTIONS);
1121
+ if (matches !== void 0 || error !== void 0) {
1122
+ if (range !== void 0) return countError("KEYS NOT ALLOWED");
1123
+ if (requested === void 0) return countError("QUEUE SELECTED NOT VALID OR CLOSED");
1124
+ if (error !== void 0) return countError(error);
1125
+ }
1126
+ const info = (identifiers2) => `${results("Complete", success)}<QueueInfo DateTime="${shortDate(now())}" PseudoCityCode="${escape(pcc)}">${identifiers2.join("")}</QueueInfo>`;
1127
+ const totalsXml = (messages2, pnrs2) => `<Totals Count="${messages2}" Type="MESSAGES"/><Totals Count="0" Type="SPECIALS"/><Totals Count="${pnrs2}" Type="PNRS"/>`;
1128
+ const waitingIn = (key) => waiting(session, key, queues.get(key) ?? []);
1129
+ if (range !== void 0) {
1130
+ const start = Number(attr(range, "Start"));
1131
+ const end = Number(attr(range, "End"));
1132
+ const identifiers2 = [];
1133
+ let pnrsTotal2 = 0;
1134
+ for (let number = start; number <= end; number++) {
1135
+ const total = waitingIn(String(number));
1136
+ identifiers2.push(`<QueueIdentifier Count="${total}" Number="${number}"/>`);
1137
+ pnrsTotal2 += total;
1138
+ }
1139
+ return countRS(`${info(identifiers2)}${totalsXml(0, pnrsTotal2)}`);
1140
+ }
1141
+ if (requested !== void 0) {
1142
+ if (matches === void 0) return countRS(info([`<QueueIdentifier Count="${waitingIn(requested)}" ${keyAttribute(requested)}/>`]));
1143
+ const onScreen = session.inside?.key === requested ? queue(requested)[session.inside.index] : void 0;
1144
+ const matching = (queues.get(requested) ?? []).filter((entry) => entry !== onScreen && matches(entry)).length;
1145
+ return countRS(`${info([`<QueueIdentifier Count="${matching}" ${keyAttribute(requested)}/>`])}<Totals Count="${waitingIn(requested)}"/>`);
1146
+ }
457
1147
  let pnrsTotal = 0;
458
1148
  let messages = 0;
459
1149
  const identifiers = [];
460
- for (const [key, entries] of queues) {
461
- const total = waiting(session, key, entries);
1150
+ for (const [queueKey2, entries] of queues) {
1151
+ const total = waiting(session, queueKey2, entries);
462
1152
  if (total <= 0) continue;
463
- identifiers.push(`<QueueIdentifier Count="${total}" ${keyAttribute(key)}/>`);
464
- if (ALPHA_KEY.test(key)) messages += total;
1153
+ identifiers.push(`<QueueIdentifier Count="${total}" ${keyAttribute(queueKey2)}/>`);
1154
+ if (ALPHA_KEY.test(queueKey2)) messages += total;
465
1155
  else pnrsTotal += total;
466
1156
  }
467
- return `<QueueCountRS xmlns="${SWS_NAMESPACE}" xmlns:stl="${STL_NAMESPACE}" Version="2.2.1">${results("Complete", success)}<QueueInfo DateTime="${shortDate(now())}" PseudoCityCode="${escape(pcc)}">${identifiers.join("")}</QueueInfo><Totals Count="${messages}" Type="MESSAGES"/><Totals Count="0" Type="SPECIALS"/><Totals Count="${pnrsTotal}" Type="PNRS"/></QueueCountRS>`;
1157
+ return countRS(`${info(identifiers)}${totalsXml(messages, pnrsTotal)}`);
468
1158
  };
469
1159
  const access = (session, rq) => {
470
1160
  const navigation = attr(rq.Navigation, "Action");
@@ -474,15 +1164,20 @@ function createQueueFake(options = {}) {
474
1164
  if (key === void 0) return accessError("sabre-client/testing: QueueIdentifier sin Number ni Name");
475
1165
  const isList = node2(identifier?.List) !== void 0;
476
1166
  calls.push({ command: isList ? "list" : "access", queue: refOfKey(key) });
477
- if (nodes(rq.Selection).length > 0) return accessError("sabre-client/testing: selection no est\xE1 simulado");
1167
+ if (isList && nodes(rq.Selection).some((selection) => !LIST_SELECTION_FUNCTIONS.includes((attr(selection, "Function") ?? "").toUpperCase()))) {
1168
+ return accessError("VALID SELECTION CODE FOR QUEUE LIST ARE QD OR AK");
1169
+ }
1170
+ const { matches, error } = selectionOf(rq, QUEUE_FAKE_SELECTION_FUNCTIONS);
1171
+ if (error !== void 0) return accessError(error);
478
1172
  const entries = queue(key);
479
1173
  if (entries.length === 0) return accessError(QUEUE_EMPTY);
480
1174
  if (isList) {
481
1175
  const passenger = attr(identifier?.List, "PrimaryPassenger") === "true";
482
- const lines = entries.map((entry, index) => lineXml(entry, { number: index + 1, passenger })).join("");
1176
+ const listed = matches === void 0 ? entries : entries.filter(matches);
1177
+ const lines = listed.map((entry, index) => lineXml(entry, { number: index + 1, passenger })).join("");
483
1178
  return accessRS(`${results("Complete", success)}${lines}`);
484
1179
  }
485
- session.inside = { key, index: 0 };
1180
+ session.inside = { key, index: 0, ...matches === void 0 ? {} : { matches } };
486
1181
  return step(session);
487
1182
  };
488
1183
  const navigate = (session, rq, action) => {
@@ -532,10 +1227,12 @@ function createQueueFake(options = {}) {
532
1227
  const add = (locator, target) => {
533
1228
  const entries = queue(target.key);
534
1229
  if (entries.some((entry) => entry.locator === locator)) return;
1230
+ const code = Number(target.instructionCode);
535
1231
  entries.push({
536
1232
  locator,
537
1233
  placedAt: longDate(at),
538
- placedBy: target.pcc === void 0 ? void 0 : { pseudoCityCode: target.pcc }
1234
+ placedBy: target.pcc === void 0 ? void 0 : { pseudoCityCode: target.pcc },
1235
+ ...Number.isInteger(code) ? { instructionCode: code } : {}
539
1236
  });
540
1237
  };
541
1238
  if (moving) {
@@ -543,6 +1240,7 @@ function createQueueFake(options = {}) {
543
1240
  if (current === void 0) return placeError("sabre-client/testing: no hay PNR en pantalla");
544
1241
  queue(inside.key).splice(inside.index, 1);
545
1242
  for (const target of targets) add(current.locator, target);
1243
+ advance(session);
546
1244
  const next = queue(inside.key)[inside.index];
547
1245
  if (next === void 0) {
548
1246
  session.inside = void 0;
@@ -565,22 +1263,37 @@ function createQueueFake(options = {}) {
565
1263
  };
566
1264
  const analyze = (session, rq) => {
567
1265
  calls.push({ command: "activity" });
568
- const pcc = attr(node2(node2(rq.QueueInfo)?.QueueIdentifier), "PseudoCityCode") ?? "";
569
- const totals = { NumMessages: 0 };
570
- for (const [, element2] of ACTIVITY_COUNTERS) totals[element2] = 0;
571
- const identifiers = [];
572
- for (const key of /* @__PURE__ */ new Set([...queues.keys(), ...activity.keys()])) {
1266
+ const identifier = node2(node2(rq.QueueInfo)?.QueueIdentifier);
1267
+ const pcc = attr(identifier, "PseudoCityCode") ?? "";
1268
+ const countersOf = (key) => {
573
1269
  const seeded = activity.get(key) ?? {};
574
1270
  const counters = { NumMessages: waiting(session, key, queues.get(key) ?? []) };
575
1271
  for (const [name, element2] of ACTIVITY_COUNTERS) counters[element2] = seeded[name] ?? 0;
576
- if (Object.values(counters).every((value) => value === 0)) continue;
577
- for (const [element2, value] of Object.entries(counters)) totals[element2] = (totals[element2] ?? 0) + value;
578
- identifiers.push(
579
- `<QueueIdentifier ${keyAttribute(key)}>${Object.entries(counters).map(([element2, value]) => `<${element2} Count="${value}"/>`).join("")}</QueueIdentifier>`
580
- );
1272
+ return counters;
1273
+ };
1274
+ const countersXml = (counters) => Object.entries(counters).map(([element2, value]) => `<${element2} Count="${value}"/>`).join("");
1275
+ let keys;
1276
+ let withTotals = true;
1277
+ const range = node2(identifier?.Range);
1278
+ const requested = requestedKey(identifier);
1279
+ if (range !== void 0) {
1280
+ keys = [];
1281
+ for (let number = Number(attr(range, "Start")); number <= Number(attr(range, "End")); number++) keys.push(String(number));
1282
+ } else if (requested !== void 0) {
1283
+ keys = [requested];
1284
+ withTotals = false;
1285
+ } else {
1286
+ keys = [.../* @__PURE__ */ new Set([...queues.keys(), ...activity.keys()])].filter((key) => Object.values(countersOf(key)).some((value) => value !== 0));
581
1287
  }
582
- const totalsXml = Object.entries(totals).map(([element2, value]) => `<${element2} Count="${value}"/>`).join("");
583
- return `<QueueAnalysisRS xmlns="${SWS_NAMESPACE}" xmlns:stl="${STL_NAMESPACE}" Version="2.0.0">${results("Complete", success)}<QueueInfo DateTime="${shortDate(now())}" PseudoCityCode="${escape(pcc)}">${identifiers.join("")}</QueueInfo><Totals>${totalsXml}</Totals></QueueAnalysisRS>`;
1288
+ const totals = { NumMessages: 0 };
1289
+ for (const [, element2] of ACTIVITY_COUNTERS) totals[element2] = 0;
1290
+ const identifiers = keys.map((key) => {
1291
+ const counters = countersOf(key);
1292
+ for (const [element2, value] of Object.entries(counters)) totals[element2] = (totals[element2] ?? 0) + value;
1293
+ return `<QueueIdentifier ${keyAttribute(key)}>${countersXml(counters)}</QueueIdentifier>`;
1294
+ });
1295
+ const totalsXml = withTotals ? `<Totals>${countersXml(totals)}</Totals>` : "";
1296
+ return `<QueueAnalysisRS xmlns="${SWS_NAMESPACE}" xmlns:stl="${STL_NAMESPACE}" Version="2.0.0">${results("Complete", success)}<QueueInfo DateTime="${shortDate(now())}" PseudoCityCode="${escape(pcc)}">${identifiers.join("")}</QueueInfo>${totalsXml}</QueueAnalysisRS>`;
584
1297
  };
585
1298
  const history = (session, rq) => {
586
1299
  const locator = attr(rq.UniqueID, "ID") ?? "";
@@ -596,6 +1309,40 @@ function createQueueFake(options = {}) {
596
1309
  const created = { rph: 1, createdAt: longDate(now()), agent: "AAA", pcc: "XXXX", receivedFrom: "SABRE-CLIENT/TESTING" };
597
1310
  return historyRS(`${results("Complete", success)}${historicalInfo(created)}`);
598
1311
  };
1312
+ const dailySalesSummary = (rq, sessionPcc) => {
1313
+ const criteria = node2(rq.SelectionCriteria) ?? {};
1314
+ const requestedPcc = text(criteria.PseudoCityCode);
1315
+ const requestedDate = text(criteria.ReportDate);
1316
+ calls.push({ command: "dailySalesSummary", ...requestedPcc === void 0 ? {} : { pcc: requestedPcc }, ...requestedDate === void 0 ? {} : { date: requestedDate } });
1317
+ const pcc = requestedPcc ?? sessionPcc;
1318
+ const date = requestedDate ?? dailySalesDay.format(now());
1319
+ const day = dailySales.get(`${pcc} ${date}`) ?? { transactions: [] };
1320
+ const namespaces = 'xmlns:asr="http://www.sabre.com/ns/Ticketing/AsrServices/1.0" xmlns:stl="http://services.sabre.com/STL/v01"';
1321
+ const resultsXml = (inner) => `<asr:Header><stl:Results>${inner}</stl:Results></asr:Header>`;
1322
+ const errorXml = (status, code, message) => `<stl:Error type="BusinessLogic" status="${escape(status)}"><stl:System>SABRE-CLIENT-TESTING</stl:System><stl:ErrorCode>${escape(code)}</stl:ErrorCode><stl:Message>${escape(message)}</stl:Message></stl:Error>`;
1323
+ if (day.errorCode !== void 0) {
1324
+ return `<asr:DailySalesSummaryRS ${namespaces} version="1.2.2">${resultsXml(errorXml("NotProcessed", day.errorCode, `sabre-client/testing: error ${day.errorCode} forzado`))}</asr:DailySalesSummaryRS>`;
1325
+ }
1326
+ const wanted = text(criteria.DocumentType);
1327
+ const transactions = day.transactions.filter(
1328
+ (t) => wanted === void 0 || t.documentType === wanted || wanted === "RFND" && t.documentType === "RFD"
1329
+ );
1330
+ const deferred = day.status === "deferred" ? { reportId: "FAKE-REPORT", queryStillRunning: true, ...day.deferred } : void 0;
1331
+ const rootAttrs = attrs([
1332
+ ["isQueryStillRunning", String(deferred?.queryStillRunning ?? false)],
1333
+ ["reportId", deferred?.reportId],
1334
+ ["chunkNumber", deferred?.chunkNumber],
1335
+ ["maxChunks", deferred?.maxChunks]
1336
+ ]);
1337
+ const incomplete = day.status === "incomplete" ? errorXml("Incomplete", "0", `The following are the failed transactions : [${(day.failedDocuments ?? []).join(", ")}]`) : "";
1338
+ return [
1339
+ `<asr:DailySalesSummaryRS ${namespaces} version="1.2.2"${rootAttrs}>`,
1340
+ resultsXml(`<stl:Success><stl:System>SABRE-CLIENT-TESTING</stl:System></stl:Success>${incomplete}`),
1341
+ `<asr:DSHeader><asr:PseudoCityCode>${escape(pcc)}</asr:PseudoCityCode>${day.agencyName === void 0 ? "" : `<asr:AgencyName>${escape(day.agencyName)}</asr:AgencyName>`}</asr:DSHeader>`,
1342
+ `<asr:DailySalesReport><asr:ReportDate>${escape(date)}</asr:ReportDate>${transactions.map(transactionXml).join("")}</asr:DailySalesReport>`,
1343
+ "</asr:DailySalesSummaryRS>"
1344
+ ].join("");
1345
+ };
599
1346
  const handle = (raw) => {
600
1347
  let document;
601
1348
  try {
@@ -655,6 +1402,8 @@ function createQueueFake(options = {}) {
655
1402
  return reply("QueueAnalysisLLSRS", session, token, analyze(session, rq));
656
1403
  case "TravelItineraryHistoryLLSRQ":
657
1404
  return reply("TravelItineraryHistoryLLSRS", session, token, history(session, rq));
1405
+ case "TKT_TravelAgencyReportsRQ":
1406
+ return reply("TKT_TravelAgencyReportsRS", session, token, dailySalesSummary(rq, text(messageHeader?.CPAId) ?? ""));
658
1407
  default:
659
1408
  return {
660
1409
  status: 500,
@@ -667,10 +1416,20 @@ function createQueueFake(options = {}) {
667
1416
  };
668
1417
  }
669
1418
  };
1419
+ const rest = createBookingHandler(
1420
+ { ...options.bookings === void 0 ? {} : { bookings: options.bookings }, ...options.ticketing === void 0 ? {} : { ticketing: options.ticketing } },
1421
+ now,
1422
+ (command, locator) => calls.push({ command, ...locator === void 0 ? {} : { locator } })
1423
+ );
670
1424
  const fakeFetch = async (input, init) => {
671
1425
  const signal = init?.signal ?? (input instanceof Request ? input.signal : void 0);
672
1426
  signal?.throwIfAborted();
673
1427
  const raw = typeof init?.body === "string" ? init.body : input instanceof Request ? await input.text() : String(init?.body ?? "");
1428
+ const url = input instanceof Request ? input.url : String(input);
1429
+ const restReply = rest(url, raw);
1430
+ if (restReply === "transport") throw new TypeError("fetch failed (sabre-client/testing: fallo de red forzado)");
1431
+ if (restReply === "abort") throw new DOMException("The operation was aborted. (sabre-client/testing: abort forzado)", "AbortError");
1432
+ if (restReply !== void 0) return restReply;
674
1433
  const { status, body } = handle(raw);
675
1434
  return new Response(body, { status, headers: { "Content-Type": "text/xml;charset=UTF-8" } });
676
1435
  };
@@ -3267,11 +4026,11 @@ var queue_production_walk_default = {
3267
4026
  };
3268
4027
 
3269
4028
  // src/testing/fixtures.ts
3270
- function fixture(json) {
4029
+ function fixture(json2) {
3271
4030
  return Object.freeze({
3272
- recordedAt: json.recordedAt,
4031
+ recordedAt: json2.recordedAt,
3273
4032
  responses: Object.freeze(
3274
- json.responses.map(({ operation, status, body }) => Object.freeze({ operation, status, body }))
4033
+ json2.responses.map(({ operation, status, body }) => Object.freeze({ operation, status, body }))
3275
4034
  )
3276
4035
  });
3277
4036
  }
@@ -3283,6 +4042,7 @@ var queueFixtures = Object.freeze({
3283
4042
  placeWithoutInstructionCodeInside: fixture(queue_production_place_sin_pic_adentro_default)
3284
4043
  });
3285
4044
  export {
4045
+ QUEUE_FAKE_SELECTION_FUNCTIONS,
3286
4046
  createAnonymizer,
3287
4047
  createQueueFake,
3288
4048
  isSafeHostLine,