sabre-client 0.5.1 → 0.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -649,12 +649,31 @@ type QueueRef = number | string | {
649
649
  };
650
650
  /** La cola de mensajes del PCC: no tiene PNR y Sabre no la lista (`INVALID ENTRY`). */
651
651
  declare const MESSAGE_QUEUE = "S";
652
+ /**
653
+ * Un rango de colas numéricas, de `start` a `end`, las dos incluidas: `{ start: 1, end: 25 }` es el
654
+ * `QC/<PCC>1-25` del host. Lo usan `count` y `activity`. A diferencia de pedir todo el PCC, Sabre
655
+ * devuelve cada cola del rango, también las vacías, con cuenta `0` (CERT y producción, #213).
656
+ *
657
+ * El design también acepta rangos de colas con nombre (`TEST1-TEST2`), pero ninguna prueba los vio:
658
+ * por ahora solo van números.
659
+ */
660
+ interface QueueRange {
661
+ start: number;
662
+ end: number;
663
+ }
652
664
  /** Si la cola es la de mensajes (`'S'`). */
653
665
  declare function isMessageQueue(queue: QueueRef): boolean;
654
666
 
655
667
  interface QueueActivityRequest {
656
668
  /** PCC cuyas colas se analizan. Por defecto, el de la sesión. */
657
669
  pcc?: string;
670
+ /** Analizar solo esta cola (`QA/<PCC><cola>`). No se combina con `range`. */
671
+ queue?: QueueRef;
672
+ /**
673
+ * Analizar un rango de colas (`QA/<PCC>1-25`). No se combina con `queue`. La respuesta trae cada
674
+ * cola del rango, también las que tienen todos los contadores en 0.
675
+ */
676
+ range?: QueueRange;
658
677
  }
659
678
  type QueueActivityOptions = RequestOptions;
660
679
  /**
@@ -686,9 +705,16 @@ interface QueueActivity extends QueueActivityCounters {
686
705
  queue: QueueRef;
687
706
  }
688
707
  interface QueueActivityResponse {
689
- /** Las colas que trae la respuesta, en el orden de Sabre. */
708
+ /**
709
+ * Las colas que trae la respuesta, en el orden de Sabre. Sin filtros, Sabre omite las que tienen
710
+ * todos los contadores en 0; con `range`, trae todas las del rango, y con `queue`, solo esa
711
+ * (CERT y producción, #213).
712
+ */
690
713
  queues: QueueActivity[];
691
- /** `Totals`: los ocho contadores sumados para el PCC. Sin `Totals`, `undefined`. */
714
+ /**
715
+ * `Totals`: los ocho contadores sumados para el PCC, o para el rango con `range`. Con `queue`,
716
+ * Sabre no manda `Totals` y queda en `undefined` (#213).
717
+ */
692
718
  totals?: QueueActivityCounters;
693
719
  /**
694
720
  * `QueueInfo@DateTime` tal cual llegó, sin año (`"11-06T11:33"`): la hora del host al analizar.
@@ -789,4 +815,4 @@ interface ReservationHistoryResponse {
789
815
  respondedAt?: string;
790
816
  }
791
817
 
792
- export { type PricingInformation as $, type LegDesc as A, type BargainFinderMaxRequest as B, type CurrencyConversion as C, type LegDescription as D, type LegSchedule as E, type Fare as F, type GroupedItineraryResponse as G, type LegScheduleRef as H, type ItineraryGroup as I, type LowFareSearchFlight as J, type LowFareSearchLocation as K, type Leg as L, type LowFareSearchOriginDestination as M, type LowFareSearchPassengerTypeQuantity as N, type OtaAirLowFareSearchRq as O, type LowFareSearchRequestorId as P, type QueueRef as Q, type ReservationHistoryTransaction as R, SabreError as S, type LowFareSearchSource as T, type LowFareSearchTpaExtensions as U, type LowFareSearchTravelPreferences as V, type LowFareSearchTravelerInfoSummary as W, MESSAGE_QUEUE as X, type ObFeeDesc as Y, type PassengerInfo as Z, type PassengerTotalFare as _, type QueueActivityCounters as a, type QueueActivity as a0, type Ref as a1, type SabreBusinessErrorOptions as a2, type SabreErrorOptions as a3, SabreGatewayError as a4, type SabreGatewayErrorOptions as a5, SabreOrchestrationError as a6, type SabreOrchestrationErrorOptions as a7, type SabreProtocol as a8, type SabreRawRequest as a9, type SabreRawResponse as aa, type SabreServiceErrorOptions as ab, SabreSessionError as ac, type SabreSessionErrorKind as ad, type SabreSessionErrorOptions as ae, type ScheduleCarrier as af, type ScheduleDesc as ag, type ScheduleEndpoint as ah, type ScheduleEquipment as ai, type TaxDesc as aj, type TaxSummaryDesc as ak, type TotalFare as al, type ValidatingCarrierDesc as am, isMessageQueue as an, type ReservationHistoryFlightSegment as b, type ReservationHistoryText as c, type ReservationHistoryAccounting as d, type RetryOptions as e, type SabreRawResponseCallback as f, type SabreRawRequestCallback as g, SabreWarning as h, SabreBusinessError as i, type Itinerary as j, type RequestOptions as k, type BargainFinderMaxResponse as l, type QueueActivityRequest as m, type QueueActivityOptions as n, type QueueActivityResponse as o, type ReservationHistoryOptions as p, type ReservationHistoryResponse as q, type BaggageAllowanceDesc as r, type BaggageInformation as s, type FareComponent as t, type FareComponentDesc as u, type FareComponentDescSegment as v, type FareComponentSegment as w, type FareComponentSegmentDetail as x, type FareMessage as y, type ItineraryMessage as z };
818
+ export { type PassengerTotalFare as $, type ItineraryMessage as A, type BargainFinderMaxRequest as B, type CurrencyConversion as C, type LegDesc as D, type LegDescription as E, type Fare as F, type GroupedItineraryResponse as G, type LegSchedule as H, type ItineraryGroup as I, type LegScheduleRef as J, type LowFareSearchFlight as K, type Leg as L, type LowFareSearchLocation as M, type LowFareSearchOriginDestination as N, type OtaAirLowFareSearchRq as O, type LowFareSearchPassengerTypeQuantity as P, type QueueRef as Q, type ReservationHistoryTransaction as R, SabreError as S, type LowFareSearchRequestorId as T, type LowFareSearchSource as U, type LowFareSearchTpaExtensions as V, type LowFareSearchTravelPreferences as W, type LowFareSearchTravelerInfoSummary as X, MESSAGE_QUEUE as Y, type ObFeeDesc as Z, type PassengerInfo as _, type QueueActivityCounters as a, type PricingInformation as a0, type QueueActivity as a1, type Ref as a2, type SabreBusinessErrorOptions as a3, type SabreErrorOptions as a4, SabreGatewayError as a5, type SabreGatewayErrorOptions as a6, SabreOrchestrationError as a7, type SabreOrchestrationErrorOptions as a8, type SabreProtocol as a9, type SabreRawRequest as aa, type SabreRawResponse as ab, type SabreServiceErrorOptions as ac, SabreSessionError as ad, type SabreSessionErrorKind as ae, type SabreSessionErrorOptions as af, type ScheduleCarrier as ag, type ScheduleDesc as ah, type ScheduleEndpoint as ai, type ScheduleEquipment as aj, type TaxDesc as ak, type TaxSummaryDesc as al, type TotalFare as am, type ValidatingCarrierDesc as an, isMessageQueue as ao, type ReservationHistoryFlightSegment as b, type ReservationHistoryText as c, type ReservationHistoryAccounting as d, type RetryOptions as e, type SabreRawResponseCallback as f, type SabreRawRequestCallback as g, SabreWarning as h, SabreBusinessError as i, type Itinerary as j, type RequestOptions as k, type BargainFinderMaxResponse as l, type QueueRange as m, type QueueActivityRequest as n, type QueueActivityOptions as o, type QueueActivityResponse as p, type ReservationHistoryOptions as q, type ReservationHistoryResponse as r, type BaggageAllowanceDesc as s, type BaggageInformation as t, type FareComponent as u, type FareComponentDesc as v, type FareComponentDescSegment as w, type FareComponentSegment as x, type FareComponentSegmentDetail as y, type FareMessage as z };
package/dist/testing.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { Q as QueueRef, a as QueueActivityCounters, R as ReservationHistoryTransaction, b as ReservationHistoryFlightSegment, c as ReservationHistoryText, d as ReservationHistoryAccounting } from './reservation-history-BKRFlxpA.js';
1
+ import { Q as QueueRef, a as QueueActivityCounters, R as ReservationHistoryTransaction, b as ReservationHistoryFlightSegment, c as ReservationHistoryText, d as ReservationHistoryAccounting } from './reservation-history-C6giSdFd.js';
2
2
  export { Anonymizer, AnonymizerLeak, AnonymizerOptions, createAnonymizer, isSafeHostLine } from './anonymize.js';
3
3
 
4
4
  /** Un PNR del fake, con los campos que Sabre manda en `Line`. */
@@ -8,12 +8,14 @@ interface QueueFakePnr {
8
8
  /**
9
9
  * Las líneas del PNR en formato de host, como el `Paragraph` de Access Queue. Si no termina en
10
10
  * una línea de firma (`… HHMM/DDMMMYY <localizador> H`), el fake agrega una: `place` dentro de
11
- * una cola saca el localizador de ahí, igual que con Sabre.
11
+ * una cola saca el localizador de ahí, igual que con Sabre. `selection` `AL` y `AC` filtran por
12
+ * los segmentos del párrafo, y `PI` por su aviso de PIC (`011 SEE REMARKS`), como los lee
13
+ * `parseQueueParagraph`.
12
14
  */
13
15
  paragraph?: readonly string[];
14
- /** `Line/DateTime`, tal cual. */
16
+ /** `Line/DateTime`, tal cual. Es la fecha por la que filtra `selection` `QD`. */
15
17
  placedAt?: string;
16
- /** `Line/POS/Source`. */
18
+ /** `Line/POS/Source`. `selection` `AK` filtra por el PCC más la firma (`'ABCDAAT'`). */
17
19
  placedBy?: {
18
20
  agentSine?: string;
19
21
  pseudoCityCode?: string;
@@ -96,6 +98,12 @@ interface QueueFake {
96
98
  */
97
99
  expireSessions(): void;
98
100
  }
101
+ /**
102
+ * Las funciones de `selection` que simula el fake (#202). En `list`, Sabre acepta solo `QD` y `AK`
103
+ * (producción, #155); en `access` y `count`, cualquiera (#213), pero el fake simula solo estas.
104
+ * Las demás dan un error de host marcado como no simulado.
105
+ */
106
+ declare const QUEUE_FAKE_SELECTION_FUNCTIONS: readonly ["QD", "AK", "AL", "PI", "AC"];
99
107
  declare function createQueueFake(options?: QueueFakeOptions): QueueFake;
100
108
 
101
109
  /** Una respuesta grabada: la operación del SDK, el HTTP status y el sobre SOAP tal cual llegó. */
@@ -166,4 +174,4 @@ declare const queueFixtures: Readonly<{
166
174
  */
167
175
  declare function queueFakePnrs(fixture: RecordedFixture): QueueFakePnr[];
168
176
 
169
- export { type QueueFake, type QueueFakeActivity, type QueueFakeCall, type QueueFakeCommand, type QueueFakeHistoryTransaction, type QueueFakeItem, type QueueFakeOptions, type QueueFakePnr, type RecordedFixture, type RecordedResponse, createQueueFake, queueFakePnrs, queueFixtures };
177
+ export { QUEUE_FAKE_SELECTION_FUNCTIONS, type QueueFake, type QueueFakeActivity, type QueueFakeCall, type QueueFakeCommand, type QueueFakeHistoryTransaction, type QueueFakeItem, type QueueFakeOptions, type QueueFakePnr, type RecordedFixture, type RecordedResponse, createQueueFake, queueFakePnrs, queueFixtures };
package/dist/testing.js CHANGED
@@ -126,6 +126,304 @@ function lineFields(value) {
126
126
  };
127
127
  }
128
128
 
129
+ // src/queue/host-date.ts
130
+ var MONTHS = ["JAN", "FEB", "MAR", "APR", "MAY", "JUN", "JUL", "AUG", "SEP", "OCT", "NOV", "DEC"];
131
+ var DAY_MS = 864e5;
132
+ var HALF_YEAR_DAYS = 184;
133
+ function resolveHostDate(text2, referenceDate, options = {}) {
134
+ const reference = referenceDay(referenceDate);
135
+ const direction = options.direction ?? "past";
136
+ if (direction !== "past" && direction !== "nearest") {
137
+ throw new TypeError("sabre-client: direction debe ser 'past' o 'nearest'");
138
+ }
139
+ if (typeof text2 !== "string") return void 0;
140
+ const parsed = parseHostDate(text2.trim().toUpperCase());
141
+ if (parsed === void 0) return void 0;
142
+ const year = parsed.year ?? resolveYear(parsed.month, parsed.day, reference, direction);
143
+ if (year === void 0 || !isValidDate(year, parsed.month, parsed.day)) return void 0;
144
+ const date = `${pad(year, 4)}-${pad(parsed.month, 2)}-${pad(parsed.day, 2)}`;
145
+ return parsed.time === void 0 ? date : `${date}T${parsed.time}`;
146
+ }
147
+ function parseHostDate(text2) {
148
+ let match = /^(\d{1,2})([A-Z]{3})(\d{2})?$/.exec(text2);
149
+ if (match !== null) {
150
+ const month2 = MONTHS.indexOf(match[2]) + 1;
151
+ if (month2 === 0) return void 0;
152
+ return {
153
+ ...match[3] !== void 0 && { year: 2e3 + Number(match[3]) },
154
+ month: month2,
155
+ day: Number(match[1])
156
+ };
157
+ }
158
+ match = /^(?:(\d{4})-)?(\d{2})-(\d{2})(?:T(\d{2}):(\d{2}))?$/.exec(text2);
159
+ if (match === null) return void 0;
160
+ const [, year, month, day, hours, minutes] = match;
161
+ if (hours !== void 0 && (Number(hours) > 23 || Number(minutes) > 59)) return void 0;
162
+ return {
163
+ ...year !== void 0 && { year: Number(year) },
164
+ month: Number(month),
165
+ day: Number(day),
166
+ ...hours !== void 0 && { time: `${hours}:${minutes}` }
167
+ };
168
+ }
169
+ function resolveYear(month, day, reference, direction) {
170
+ const year = new Date(reference).getUTCFullYear();
171
+ if (direction === "past") {
172
+ for (let candidate = year; candidate >= year - 8; candidate--) {
173
+ if (isValidDate(candidate, month, day) && dayNumber(candidate, month, day) <= reference) return candidate;
174
+ }
175
+ return void 0;
176
+ }
177
+ let best;
178
+ for (const candidate of [year + 1, year, year - 1]) {
179
+ if (!isValidDate(candidate, month, day)) continue;
180
+ const distance = Math.abs(dayNumber(candidate, month, day) - reference);
181
+ if (best === void 0 || distance < best.distance) best = { year: candidate, distance };
182
+ }
183
+ return best !== void 0 && best.distance <= HALF_YEAR_DAYS * DAY_MS ? best.year : void 0;
184
+ }
185
+ function referenceDay(referenceDate) {
186
+ if (referenceDate instanceof Date) {
187
+ const time = referenceDate.getTime();
188
+ if (Number.isNaN(time)) throw new TypeError("sabre-client: referenceDate es un Date inv\xE1lido");
189
+ return time - (time % DAY_MS + DAY_MS) % DAY_MS;
190
+ }
191
+ if (typeof referenceDate === "string") {
192
+ const match = /^(\d{4})-(\d{2})-(\d{2})(?:T.*)?$/.exec(referenceDate.trim());
193
+ if (match !== null) {
194
+ const [year, month, day] = [Number(match[1]), Number(match[2]), Number(match[3])];
195
+ if (isValidDate(year, month, day)) return dayNumber(year, month, day);
196
+ }
197
+ }
198
+ throw new TypeError("sabre-client: referenceDate es obligatoria, como 'YYYY-MM-DD' o un Date v\xE1lido");
199
+ }
200
+ function dayNumber(year, month, day) {
201
+ const date = /* @__PURE__ */ new Date(0);
202
+ date.setUTCFullYear(year, month - 1, day);
203
+ return date.getTime();
204
+ }
205
+ function isValidDate(year, month, day) {
206
+ if (month < 1 || month > 12 || day < 1) return false;
207
+ const date = /* @__PURE__ */ new Date(0);
208
+ date.setUTCFullYear(year, month - 1, day);
209
+ return date.getUTCMonth() === month - 1 && date.getUTCDate() === day;
210
+ }
211
+ function pad(value, length) {
212
+ return String(value).padStart(length, "0");
213
+ }
214
+
215
+ // src/queue/queue-paragraph.ts
216
+ var MESSAGE = /^\s*(\d{3})\s{2,}(\S.*?)\s*$/;
217
+ var PASSENGER_LINE = /^\s*\d+\.\d+[A-Z]/;
218
+ var PASSENGER_SPLIT = /\s+(?=\d+\.\d+[A-Z])/;
219
+ var PASSENGER = /^(\d+\.\d+)([^/*]+?)\s*(?:\/([^*]*?))?\s*(?:\*(.*?))?\s*$/;
220
+ 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|$)(.*)$/;
221
+ var SEGMENT_TIMES = /^\s+(\d{3,4}[APNM]?)\s+(\d{3,4}[APNM]?)(?=\s|$)/;
222
+ 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|$)/;
223
+ var NO_ITINERARY = /^\s*NO ITIN\s*$/;
224
+ var TICKETING_HEADER = /^\s*TKT\/TIME LIMIT\s*$/;
225
+ var TICKETING_ENTRY = /^\s*(\d+)\.(.*?)\s*$/;
226
+ var TICKETED = /^T-(\d{2}[A-Z]{3})?(?:-([A-Z0-9]{3,4})\*([A-Z0-9]+))?(?:\/.*)?$/;
227
+ var ARRANGEMENT = /^(TA[WCMTX])([A-Z0-9]{3,4})?(\d{2}[A-Z]{3})?\d{0,3}\/.*$/;
228
+ var TICKET = /^(TE|TK)\s+(\d{13})\b/;
229
+ var RECEIVED = /^\s*RECEIVED FROM -\s?(.*?)\s*$/;
230
+ 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*$/;
231
+ var Phase = { Messages: 0, Passengers: 1, Segments: 2, Body: 3 };
232
+ function parseQueueParagraph(lines, options) {
233
+ if (!Array.isArray(lines)) throw new TypeError("sabre-client: lines debe ser un array de strings");
234
+ if (typeof options !== "object" || options === null) {
235
+ throw new TypeError("sabre-client: referenceDate es obligatoria, como 'YYYY-MM-DD' o un Date v\xE1lido");
236
+ }
237
+ const { referenceDate } = options;
238
+ referenceDay(referenceDate);
239
+ const result = { messages: [], passengers: [], segments: [], unparsed: [] };
240
+ const signatureIndex = lastSignatureIndex(lines);
241
+ let phase = Phase.Messages;
242
+ let inTicketing = false;
243
+ lines.forEach((value, index) => {
244
+ const line = typeof value === "string" ? value : "";
245
+ if (line.trim() === "") return;
246
+ if (index === signatureIndex) {
247
+ result.receivedFrom = { ...result.receivedFrom, ...parseSignature(line, referenceDate) };
248
+ return;
249
+ }
250
+ if (inTicketing) {
251
+ const entry = TICKETING_ENTRY.exec(line);
252
+ if (entry !== null) {
253
+ const parsed = parseTicketingEntry(Number(entry[1]), entry[2], referenceDate);
254
+ if (parsed === void 0) result.unparsed.push(line);
255
+ else result.ticketing.entries.push(parsed);
256
+ return;
257
+ }
258
+ inTicketing = false;
259
+ }
260
+ if (TICKETING_HEADER.test(line)) {
261
+ phase = Phase.Body;
262
+ inTicketing = true;
263
+ result.ticketing ??= { entries: [], issued: false };
264
+ return;
265
+ }
266
+ const received = RECEIVED.exec(line);
267
+ if (received !== null) {
268
+ phase = Phase.Body;
269
+ result.receivedFrom = { ...result.receivedFrom, ...received[1] !== "" && { name: received[1] } };
270
+ return;
271
+ }
272
+ if (phase === Phase.Messages) {
273
+ const message = MESSAGE.exec(line);
274
+ if (message !== null) {
275
+ result.messages.push({ instructionCode: Number(message[1]), text: message[2] });
276
+ return;
277
+ }
278
+ }
279
+ if (phase <= Phase.Passengers && PASSENGER_LINE.test(line)) {
280
+ const passengers = parsePassengers(line);
281
+ if (passengers !== void 0) {
282
+ phase = Phase.Passengers;
283
+ result.passengers.push(...passengers);
284
+ return;
285
+ }
286
+ }
287
+ if (phase <= Phase.Segments) {
288
+ if (NO_ITINERARY.test(line)) {
289
+ phase = Phase.Segments;
290
+ return;
291
+ }
292
+ const segment = parseSegment(line, referenceDate);
293
+ if (segment !== void 0) {
294
+ phase = Phase.Segments;
295
+ result.segments.push(segment);
296
+ return;
297
+ }
298
+ }
299
+ result.unparsed.push(line);
300
+ });
301
+ if (result.ticketing !== void 0) {
302
+ result.ticketing.issued = result.ticketing.entries.some((entry) => entry.ticketNumber !== void 0);
303
+ }
304
+ return result;
305
+ }
306
+ function parsePassengers(line) {
307
+ const passengers = [];
308
+ for (const part of line.trim().split(PASSENGER_SPLIT)) {
309
+ const match = PASSENGER.exec(part);
310
+ if (match === null) return void 0;
311
+ const [, nameNumber, surname, givenName, nameRemark] = match;
312
+ passengers.push({
313
+ nameNumber,
314
+ surname: surname.trim(),
315
+ ...givenName !== void 0 && givenName !== "" && { givenName },
316
+ ...nameRemark !== void 0 && nameRemark !== "" && { nameRemark },
317
+ text: part.slice(nameNumber.length).trim()
318
+ });
319
+ }
320
+ return passengers;
321
+ }
322
+ function parseSegment(line, referenceDate) {
323
+ const air = AIR_SEGMENT.exec(line);
324
+ if (air !== null) {
325
+ const [, number2, airlineCode, flightNumber, bookingClass, date2, from, to, status2, quantity2, rest] = air;
326
+ const times = SEGMENT_TIMES.exec(rest);
327
+ const departureTime = times === null ? void 0 : hostTime(times[1]);
328
+ const arrivalTime = times === null ? void 0 : hostTime(times[2]);
329
+ return {
330
+ kind: "air",
331
+ number: Number(number2),
332
+ ...withDate(date2, referenceDate),
333
+ status: status2,
334
+ quantity: Number(quantity2),
335
+ airlineCode,
336
+ flightNumber: Number(flightNumber),
337
+ bookingClass,
338
+ fromAirportCode: from,
339
+ toAirportCode: to,
340
+ ...departureTime !== void 0 && { departureTime },
341
+ ...arrivalTime !== void 0 && { arrivalTime },
342
+ text: line.trim()
343
+ };
344
+ }
345
+ const other = OTHER_SEGMENT.exec(line);
346
+ if (other === null) return void 0;
347
+ const [, number, segmentType, vendorCode, date, status, quantity] = other;
348
+ return {
349
+ kind: "other",
350
+ number: Number(number),
351
+ ...withDate(date, referenceDate),
352
+ status,
353
+ quantity: Number(quantity),
354
+ segmentType,
355
+ vendorCode,
356
+ text: line.trim()
357
+ };
358
+ }
359
+ function parseTicketingEntry(number, text2, referenceDate) {
360
+ const ticketed = TICKETED.exec(text2);
361
+ if (ticketed !== null) {
362
+ const [, date, pseudoCityCode, agentSine] = ticketed;
363
+ return {
364
+ number,
365
+ code: "T-",
366
+ ...date !== void 0 && withDate(date, referenceDate),
367
+ ...pseudoCityCode !== void 0 && { pseudoCityCode },
368
+ ...agentSine !== void 0 && { agentSine },
369
+ text: text2
370
+ };
371
+ }
372
+ const arrangement = ARRANGEMENT.exec(text2);
373
+ if (arrangement !== null) {
374
+ const [, code, pseudoCityCode, date] = arrangement;
375
+ return {
376
+ number,
377
+ code,
378
+ ...date !== void 0 && withDate(date, referenceDate),
379
+ ...pseudoCityCode !== void 0 && { pseudoCityCode },
380
+ text: text2
381
+ };
382
+ }
383
+ const ticket = TICKET.exec(text2);
384
+ if (ticket !== null) return { number, code: ticket[1], ticketNumber: ticket[2], text: text2 };
385
+ return void 0;
386
+ }
387
+ function lastSignatureIndex(lines) {
388
+ for (let index = lines.length - 1; index >= 0; index--) {
389
+ const line = lines[index];
390
+ if (typeof line === "string" && SIGNATURE.test(line)) return index;
391
+ }
392
+ return -1;
393
+ }
394
+ function parseSignature(line, referenceDate) {
395
+ const [, pseudoCityCode, sign, hours, minutes, date, locator] = SIGNATURE.exec(line);
396
+ const agentSine = /\*([A-Z0-9]+)$/.exec(sign)?.[1];
397
+ const day = Number(hours) <= 23 && Number(minutes) <= 59 ? resolveHostDate(date, referenceDate) : void 0;
398
+ return {
399
+ pseudoCityCode,
400
+ ...agentSine !== void 0 && { agentSine },
401
+ ...day !== void 0 && { receivedAt: `${day}T${hours}:${minutes}` },
402
+ locator
403
+ };
404
+ }
405
+ function withDate(text2, referenceDate) {
406
+ const date = resolveHostDate(text2, referenceDate, { direction: "nearest" });
407
+ return date === void 0 ? {} : { date };
408
+ }
409
+ function hostTime(text2) {
410
+ const match = /^(\d{1,2})(\d{2})([APNM])?$/.exec(text2);
411
+ if (match === null) return void 0;
412
+ let hours = Number(match[1]);
413
+ const minutes = Number(match[2]);
414
+ const suffix = match[3];
415
+ if (minutes > 59) return void 0;
416
+ if (suffix === void 0) {
417
+ if (hours > 23) return void 0;
418
+ } else {
419
+ if (hours < 1 || hours > 12) return void 0;
420
+ if (suffix === "A" || suffix === "M") hours %= 12;
421
+ else if (suffix === "P") hours = hours % 12 + 12;
422
+ else if (hours !== 12) return void 0;
423
+ }
424
+ return `${String(hours).padStart(2, "0")}:${String(minutes).padStart(2, "0")}`;
425
+ }
426
+
129
427
  // src/queue/queue-place.ts
130
428
  var SIGNATURE_LINE = /\s\d{4}\/\d{2}[A-Z]{3}\d{2}\s+([A-Z0-9]{6})\s+H\s*$/;
131
429
  function signatureLocator(lines) {
@@ -142,6 +440,15 @@ var STL_NAMESPACE = "http://services.sabre.com/STL/v01";
142
440
  var SIGNATURE_DATE = "1200/01JAN26";
143
441
  var LOCATOR = /^[A-Z0-9]{6}$/;
144
442
  var NAVIGATION = { I: "ignore", QR: "remove", QXI: "exit" };
443
+ var QUEUE_FAKE_SELECTION_FUNCTIONS = ["QD", "AK", "AL", "PI", "AC"];
444
+ var LIST_SELECTION_FUNCTIONS = ["QD", "AK"];
445
+ var MONTHS2 = ["JAN", "FEB", "MAR", "APR", "MAY", "JUN", "JUL", "AUG", "SEP", "OCT", "NOV", "DEC"];
446
+ function placedOn(placedAt, criteria) {
447
+ const date = /^(\d{1,2})([A-Z]{3})$/.exec(criteria.toUpperCase());
448
+ const placed = /(\d{2})-(\d{2})T/.exec(placedAt ?? "");
449
+ if (date === null || placed === null) return false;
450
+ return Number(placed[1]) === MONTHS2.indexOf(date[2]) + 1 && Number(placed[2]) === Number(date[1]);
451
+ }
145
452
  function fail(message) {
146
453
  throw new TypeError(`sabre-client/testing: ${message}`);
147
454
  }
@@ -188,9 +495,9 @@ function refOfKey(key) {
188
495
  if (key.startsWith(NAME_PREFIX)) return { name: key.slice(NAME_PREFIX.length) };
189
496
  return /^\d+$/.test(key) ? Number(key) : key;
190
497
  }
191
- function keyAttribute(key, pad = false) {
498
+ function keyAttribute(key, pad2 = false) {
192
499
  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)}"`;
500
+ return `Number="${escape(pad2 && /^\d+$/.test(key) ? key.padStart(4, "0") : key)}"`;
194
501
  }
195
502
  function validateKey(key) {
196
503
  if (/^\d+$/.test(key)) {
@@ -438,7 +745,43 @@ function createQueueFake(options = {}) {
438
745
  const placeRS = (inner) => `<QueuePlaceRS xmlns="${SWS_NAMESPACE}" xmlns:stl="${STL_NAMESPACE}" Version="2.0.4">${inner}</QueuePlaceRS>`;
439
746
  const accessError = (message) => accessRS(results("NotProcessed", hostResult("Error", message)));
440
747
  const placeError = (message) => placeRS(results("NotProcessed", hostResult("Error", message)));
748
+ const matchesFilter = (entry, fn, criteria) => {
749
+ if (entry.message !== void 0) return false;
750
+ const wanted = criteria.trim().toUpperCase();
751
+ const paragraph = () => parseQueueParagraph(pnrs.get(entry.locator)?.paragraph ?? [], { referenceDate: now() });
752
+ switch (fn.toUpperCase()) {
753
+ case "QD":
754
+ return placedOn(entry.placedAt, wanted);
755
+ case "AK":
756
+ return `${entry.placedBy?.pseudoCityCode ?? ""}${entry.placedBy?.agentSine ?? ""}`.toUpperCase() === wanted;
757
+ case "AL":
758
+ return paragraph().segments.some((segment) => segment.kind === "air" && segment.airlineCode === wanted);
759
+ case "AC":
760
+ return paragraph().segments.some((segment) => segment.status.startsWith(wanted));
761
+ case "PI": {
762
+ const code = Number(wanted);
763
+ if (entry.instructionCode !== void 0) return entry.instructionCode === code;
764
+ return paragraph().messages.some((message) => message.instructionCode === code);
765
+ }
766
+ default:
767
+ return void 0;
768
+ }
769
+ };
770
+ const selectionOf = (rq, allowed) => {
771
+ const filters = nodes(rq.Selection).map((selection) => ({ fn: (attr(selection, "Function") ?? "").toUpperCase(), criteria: attr(selection, "Criteria") ?? "" }));
772
+ if (filters.length === 0) return {};
773
+ const unsupported = filters.find((filter) => !allowed.includes(filter.fn));
774
+ if (unsupported !== void 0) return { error: `sabre-client/testing: selection ${unsupported.fn} no est\xE1 simulado (simula ${allowed.join(", ")})` };
775
+ return { matches: (entry) => filters.every((filter) => matchesFilter(entry, filter.fn, filter.criteria) === true) };
776
+ };
777
+ const advance = (session) => {
778
+ const inside = session.inside;
779
+ if (inside?.matches === void 0) return;
780
+ const entries = queue(inside.key);
781
+ while (inside.index < entries.length && !inside.matches(entries[inside.index])) inside.index += 1;
782
+ };
441
783
  const step = (session) => {
784
+ advance(session);
442
785
  const inside = session.inside;
443
786
  const entry = inside === void 0 ? void 0 : queue(inside.key)[inside.index];
444
787
  if (entry === void 0) {
@@ -452,19 +795,51 @@ function createQueueFake(options = {}) {
452
795
  return accessRS(`${results("Complete", success)}${lineXml(entry)}<Paragraph>${textLines2(pnr.paragraph)}</Paragraph>`);
453
796
  };
454
797
  const waiting = (session, key, entries) => entries.length - (session.inside?.key === key && entries[session.inside.index] !== void 0 ? 1 : 0);
798
+ const countRS = (inner) => `<QueueCountRS xmlns="${SWS_NAMESPACE}" xmlns:stl="${STL_NAMESPACE}" Version="2.2.1">${inner}</QueueCountRS>`;
455
799
  const count = (session, rq) => {
456
- const pcc = attr(node2(node2(rq.QueueInfo)?.QueueIdentifier), "PseudoCityCode") ?? "";
800
+ const identifier = node2(node2(rq.QueueInfo)?.QueueIdentifier);
801
+ const pcc = attr(identifier, "PseudoCityCode") ?? "";
802
+ const countError = (message) => countRS(results("NotProcessed", hostResult("Error", message)));
803
+ const range = node2(identifier?.Range);
804
+ const requested = requestedKey(identifier);
805
+ const { matches, error } = selectionOf(rq, QUEUE_FAKE_SELECTION_FUNCTIONS);
806
+ if (matches !== void 0 || error !== void 0) {
807
+ if (range !== void 0) return countError("KEYS NOT ALLOWED");
808
+ if (requested === void 0) return countError("QUEUE SELECTED NOT VALID OR CLOSED");
809
+ if (error !== void 0) return countError(error);
810
+ }
811
+ const info = (identifiers2) => `${results("Complete", success)}<QueueInfo DateTime="${shortDate(now())}" PseudoCityCode="${escape(pcc)}">${identifiers2.join("")}</QueueInfo>`;
812
+ const totalsXml = (messages2, pnrs2) => `<Totals Count="${messages2}" Type="MESSAGES"/><Totals Count="0" Type="SPECIALS"/><Totals Count="${pnrs2}" Type="PNRS"/>`;
813
+ const waitingIn = (key) => waiting(session, key, queues.get(key) ?? []);
814
+ if (range !== void 0) {
815
+ const start = Number(attr(range, "Start"));
816
+ const end = Number(attr(range, "End"));
817
+ const identifiers2 = [];
818
+ let pnrsTotal2 = 0;
819
+ for (let number = start; number <= end; number++) {
820
+ const total = waitingIn(String(number));
821
+ identifiers2.push(`<QueueIdentifier Count="${total}" Number="${number}"/>`);
822
+ pnrsTotal2 += total;
823
+ }
824
+ return countRS(`${info(identifiers2)}${totalsXml(0, pnrsTotal2)}`);
825
+ }
826
+ if (requested !== void 0) {
827
+ if (matches === void 0) return countRS(info([`<QueueIdentifier Count="${waitingIn(requested)}" ${keyAttribute(requested)}/>`]));
828
+ const onScreen = session.inside?.key === requested ? queue(requested)[session.inside.index] : void 0;
829
+ const matching = (queues.get(requested) ?? []).filter((entry) => entry !== onScreen && matches(entry)).length;
830
+ return countRS(`${info([`<QueueIdentifier Count="${matching}" ${keyAttribute(requested)}/>`])}<Totals Count="${waitingIn(requested)}"/>`);
831
+ }
457
832
  let pnrsTotal = 0;
458
833
  let messages = 0;
459
834
  const identifiers = [];
460
- for (const [key, entries] of queues) {
461
- const total = waiting(session, key, entries);
835
+ for (const [queueKey2, entries] of queues) {
836
+ const total = waiting(session, queueKey2, entries);
462
837
  if (total <= 0) continue;
463
- identifiers.push(`<QueueIdentifier Count="${total}" ${keyAttribute(key)}/>`);
464
- if (ALPHA_KEY.test(key)) messages += total;
838
+ identifiers.push(`<QueueIdentifier Count="${total}" ${keyAttribute(queueKey2)}/>`);
839
+ if (ALPHA_KEY.test(queueKey2)) messages += total;
465
840
  else pnrsTotal += total;
466
841
  }
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>`;
842
+ return countRS(`${info(identifiers)}${totalsXml(messages, pnrsTotal)}`);
468
843
  };
469
844
  const access = (session, rq) => {
470
845
  const navigation = attr(rq.Navigation, "Action");
@@ -474,15 +849,20 @@ function createQueueFake(options = {}) {
474
849
  if (key === void 0) return accessError("sabre-client/testing: QueueIdentifier sin Number ni Name");
475
850
  const isList = node2(identifier?.List) !== void 0;
476
851
  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");
852
+ if (isList && nodes(rq.Selection).some((selection) => !LIST_SELECTION_FUNCTIONS.includes((attr(selection, "Function") ?? "").toUpperCase()))) {
853
+ return accessError("VALID SELECTION CODE FOR QUEUE LIST ARE QD OR AK");
854
+ }
855
+ const { matches, error } = selectionOf(rq, QUEUE_FAKE_SELECTION_FUNCTIONS);
856
+ if (error !== void 0) return accessError(error);
478
857
  const entries = queue(key);
479
858
  if (entries.length === 0) return accessError(QUEUE_EMPTY);
480
859
  if (isList) {
481
860
  const passenger = attr(identifier?.List, "PrimaryPassenger") === "true";
482
- const lines = entries.map((entry, index) => lineXml(entry, { number: index + 1, passenger })).join("");
861
+ const listed = matches === void 0 ? entries : entries.filter(matches);
862
+ const lines = listed.map((entry, index) => lineXml(entry, { number: index + 1, passenger })).join("");
483
863
  return accessRS(`${results("Complete", success)}${lines}`);
484
864
  }
485
- session.inside = { key, index: 0 };
865
+ session.inside = { key, index: 0, ...matches === void 0 ? {} : { matches } };
486
866
  return step(session);
487
867
  };
488
868
  const navigate = (session, rq, action) => {
@@ -532,10 +912,12 @@ function createQueueFake(options = {}) {
532
912
  const add = (locator, target) => {
533
913
  const entries = queue(target.key);
534
914
  if (entries.some((entry) => entry.locator === locator)) return;
915
+ const code = Number(target.instructionCode);
535
916
  entries.push({
536
917
  locator,
537
918
  placedAt: longDate(at),
538
- placedBy: target.pcc === void 0 ? void 0 : { pseudoCityCode: target.pcc }
919
+ placedBy: target.pcc === void 0 ? void 0 : { pseudoCityCode: target.pcc },
920
+ ...Number.isInteger(code) ? { instructionCode: code } : {}
539
921
  });
540
922
  };
541
923
  if (moving) {
@@ -543,6 +925,7 @@ function createQueueFake(options = {}) {
543
925
  if (current === void 0) return placeError("sabre-client/testing: no hay PNR en pantalla");
544
926
  queue(inside.key).splice(inside.index, 1);
545
927
  for (const target of targets) add(current.locator, target);
928
+ advance(session);
546
929
  const next = queue(inside.key)[inside.index];
547
930
  if (next === void 0) {
548
931
  session.inside = void 0;
@@ -565,22 +948,37 @@ function createQueueFake(options = {}) {
565
948
  };
566
949
  const analyze = (session, rq) => {
567
950
  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()])) {
951
+ const identifier = node2(node2(rq.QueueInfo)?.QueueIdentifier);
952
+ const pcc = attr(identifier, "PseudoCityCode") ?? "";
953
+ const countersOf = (key) => {
573
954
  const seeded = activity.get(key) ?? {};
574
955
  const counters = { NumMessages: waiting(session, key, queues.get(key) ?? []) };
575
956
  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
- );
957
+ return counters;
958
+ };
959
+ const countersXml = (counters) => Object.entries(counters).map(([element2, value]) => `<${element2} Count="${value}"/>`).join("");
960
+ let keys;
961
+ let withTotals = true;
962
+ const range = node2(identifier?.Range);
963
+ const requested = requestedKey(identifier);
964
+ if (range !== void 0) {
965
+ keys = [];
966
+ for (let number = Number(attr(range, "Start")); number <= Number(attr(range, "End")); number++) keys.push(String(number));
967
+ } else if (requested !== void 0) {
968
+ keys = [requested];
969
+ withTotals = false;
970
+ } else {
971
+ keys = [.../* @__PURE__ */ new Set([...queues.keys(), ...activity.keys()])].filter((key) => Object.values(countersOf(key)).some((value) => value !== 0));
581
972
  }
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>`;
973
+ const totals = { NumMessages: 0 };
974
+ for (const [, element2] of ACTIVITY_COUNTERS) totals[element2] = 0;
975
+ const identifiers = keys.map((key) => {
976
+ const counters = countersOf(key);
977
+ for (const [element2, value] of Object.entries(counters)) totals[element2] = (totals[element2] ?? 0) + value;
978
+ return `<QueueIdentifier ${keyAttribute(key)}>${countersXml(counters)}</QueueIdentifier>`;
979
+ });
980
+ const totalsXml = withTotals ? `<Totals>${countersXml(totals)}</Totals>` : "";
981
+ 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
982
  };
585
983
  const history = (session, rq) => {
586
984
  const locator = attr(rq.UniqueID, "ID") ?? "";
@@ -3283,6 +3681,7 @@ var queueFixtures = Object.freeze({
3283
3681
  placeWithoutInstructionCodeInside: fixture(queue_production_place_sin_pic_adentro_default)
3284
3682
  });
3285
3683
  export {
3684
+ QUEUE_FAKE_SELECTION_FUNCTIONS,
3286
3685
  createAnonymizer,
3287
3686
  createQueueFake,
3288
3687
  isSafeHostLine,