hvp-shared 13.17.0 → 13.26.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.
@@ -0,0 +1,25 @@
1
+ /**
2
+ * Commission Reconciliation — categories & classification data (hvp-backend#434)
3
+ *
4
+ * Cross-references what QVET SOLD against what was COMMISSIONED, for 4 categories.
5
+ * Detection is by the SALE LINE's section/family (catalog_items is incomplete).
6
+ *
7
+ * @see resources/research/20260609-qvet-section-to-commission-mapping.md
8
+ */
9
+ /** The 4 commissionable categories the reconciliation tracks. */
10
+ export declare enum ReconciliationCategory {
11
+ consulta = "consulta",
12
+ vacuna = "vacuna",
13
+ cirugia = "cirugia",
14
+ emergencia = "emergencia"
15
+ }
16
+ export declare const RECONCILIATION_CATEGORY_LABELS: Record<ReconciliationCategory, string>;
17
+ /** QVET section that holds clinical services. */
18
+ export declare const SERVICIOS_MEDICOS_SECTION = "SERVICIOS MEDICOS";
19
+ /** Surgical families within SERVICIOS MEDICOS (any of these → Cirugía). */
20
+ export declare const SURGERY_FAMILIES: readonly string[];
21
+ /**
22
+ * Maps a stored `commissionAllocations.services[].serviceName` to its category.
23
+ * Service names not listed here are outside the reconciliation scope.
24
+ */
25
+ export declare const COMMISSION_SERVICE_TO_CATEGORY: Record<string, ReconciliationCategory>;
@@ -0,0 +1,57 @@
1
+ "use strict";
2
+ /**
3
+ * Commission Reconciliation — categories & classification data (hvp-backend#434)
4
+ *
5
+ * Cross-references what QVET SOLD against what was COMMISSIONED, for 4 categories.
6
+ * Detection is by the SALE LINE's section/family (catalog_items is incomplete).
7
+ *
8
+ * @see resources/research/20260609-qvet-section-to-commission-mapping.md
9
+ */
10
+ Object.defineProperty(exports, "__esModule", { value: true });
11
+ exports.COMMISSION_SERVICE_TO_CATEGORY = exports.SURGERY_FAMILIES = exports.SERVICIOS_MEDICOS_SECTION = exports.RECONCILIATION_CATEGORY_LABELS = exports.ReconciliationCategory = void 0;
12
+ // ─── Category ──────────────────────────────────────────────────────────────
13
+ /** The 4 commissionable categories the reconciliation tracks. */
14
+ var ReconciliationCategory;
15
+ (function (ReconciliationCategory) {
16
+ ReconciliationCategory["consulta"] = "consulta";
17
+ ReconciliationCategory["vacuna"] = "vacuna";
18
+ ReconciliationCategory["cirugia"] = "cirugia";
19
+ ReconciliationCategory["emergencia"] = "emergencia";
20
+ })(ReconciliationCategory || (exports.ReconciliationCategory = ReconciliationCategory = {}));
21
+ exports.RECONCILIATION_CATEGORY_LABELS = {
22
+ [ReconciliationCategory.consulta]: "Consulta",
23
+ [ReconciliationCategory.vacuna]: "Vacuna",
24
+ [ReconciliationCategory.cirugia]: "Cirugía",
25
+ [ReconciliationCategory.emergencia]: "Emergencia",
26
+ };
27
+ // ─── QVET sale classification (by section/family of the sale line) ───────────
28
+ /** QVET section that holds clinical services. */
29
+ exports.SERVICIOS_MEDICOS_SECTION = "SERVICIOS MEDICOS";
30
+ /** Surgical families within SERVICIOS MEDICOS (any of these → Cirugía). */
31
+ exports.SURGERY_FAMILIES = [
32
+ "CIRUGIA DE TEJIDOS BLANDOS",
33
+ "ASISTENCIA QUIRURGICA",
34
+ "OFTALMOLOGIA",
35
+ "ORTOPEDIA",
36
+ ];
37
+ // ─── Commission service → category ───────────────────────────────────────────
38
+ /**
39
+ * Maps a stored `commissionAllocations.services[].serviceName` to its category.
40
+ * Service names not listed here are outside the reconciliation scope.
41
+ */
42
+ exports.COMMISSION_SERVICE_TO_CATEGORY = {
43
+ // Consulta (cualquier tipo)
44
+ Consulta: ReconciliationCategory.consulta,
45
+ Revisión: ReconciliationCategory.consulta,
46
+ Especialista: ReconciliationCategory.consulta,
47
+ "Revisión especialista": ReconciliationCategory.consulta,
48
+ "Consulta no convencionales": ReconciliationCategory.consulta,
49
+ // Vacuna
50
+ Vacuna: ReconciliationCategory.vacuna,
51
+ // Cirugía (cualquier tipo, incluye roles quirúrgicos)
52
+ Cirugía: ReconciliationCategory.cirugia,
53
+ Anestesista: ReconciliationCategory.cirugia,
54
+ "Primer ayudante": ReconciliationCategory.cirugia,
55
+ // Emergencia
56
+ Emergencia: ReconciliationCategory.emergencia,
57
+ };
@@ -3,6 +3,7 @@
3
3
  */
4
4
  export * from './mexican-states';
5
5
  export * from './sat-catalogs';
6
+ export * from './commission-reconciliation.constants';
6
7
  export * from './collaborator.constants';
7
8
  export * from './catalog-item.constants';
8
9
  export * from './pricing.constants';
@@ -19,6 +19,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
19
19
  */
20
20
  __exportStar(require("./mexican-states"), exports);
21
21
  __exportStar(require("./sat-catalogs"), exports);
22
+ __exportStar(require("./commission-reconciliation.constants"), exports);
22
23
  __exportStar(require("./collaborator.constants"), exports);
23
24
  __exportStar(require("./catalog-item.constants"), exports);
24
25
  __exportStar(require("./pricing.constants"), exports);
@@ -119,4 +119,4 @@ export declare const QVET_CATALOG: {
119
119
  /**
120
120
  * All section values as array
121
121
  */
122
- export declare const QVET_SECTIONS_LIST: ("CONSUMO INTERNO" | "EQUIPAMIENTO" | "FARMACIA" | "FARMACIA INTERNA" | "INSUMOS ESCANDALLO" | "INSUMOS MEDICOS" | "OTROS ARTICULOS" | "OTROS SERVICIOS" | "SERVICIOS EXTERNOS" | "SERVICIOS MEDICOS")[];
122
+ export declare const QVET_SECTIONS_LIST: ("SERVICIOS MEDICOS" | "CONSUMO INTERNO" | "EQUIPAMIENTO" | "FARMACIA" | "FARMACIA INTERNA" | "INSUMOS ESCANDALLO" | "INSUMOS MEDICOS" | "OTROS ARTICULOS" | "OTROS SERVICIOS" | "SERVICIOS EXTERNOS")[];
@@ -0,0 +1,4 @@
1
+ /**
2
+ * Commission Reconciliation API Contracts (hvp-backend#434)
3
+ */
4
+ export * from './responses';
@@ -0,0 +1,20 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __exportStar = (this && this.__exportStar) || function(m, exports) {
14
+ for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
15
+ };
16
+ Object.defineProperty(exports, "__esModule", { value: true });
17
+ /**
18
+ * Commission Reconciliation API Contracts (hvp-backend#434)
19
+ */
20
+ __exportStar(require("./responses"), exports);
@@ -0,0 +1,148 @@
1
+ import { ReconciliationCategory } from '../../constants/commission-reconciliation.constants';
2
+ /**
3
+ * Commission Reconciliation API Contracts (hvp-backend#434)
4
+ *
5
+ * GET /api/commission-reconciliation/summary?startDate=&endDate=&branchId=
6
+ * Cross-references commissioned vs sold (QVET) by invoice folio, jul-2025+.
7
+ */
8
+ /**
9
+ * High-level match quality of a commission (3 states, for review):
10
+ * - `perfecto`: has ticket + category sold + seller IS the commissioner
11
+ * - `parcial`: has ticket + category sold, but seller ≠ commissioner (someone else rang up the sale)
12
+ * - `sin_match`: no corresponding sale (no factura, or the commissioned category wasn't sold)
13
+ */
14
+ export type ReconciliationMatchLevel = 'perfecto' | 'parcial' | 'sin_match';
15
+ /**
16
+ * Per-commission-service validation within a ticket. Each commissioned service is matched
17
+ * to its EXACT sale line by qvetCode (2026+, where qvetCode is populated), falling back to
18
+ * the section/family category when the code isn't available. Then the service's principal
19
+ * commissioner is compared to the seller of that line.
20
+ */
21
+ export interface ReconciliationServiceCheck {
22
+ /** The CommissionableService name (e.g. "Consulta", "Cirugía"). */
23
+ serviceName: string;
24
+ /** Principal commissioner of this service (its SIMPLE / highest-amount earner). */
25
+ principalCode: string | null;
26
+ /** How the sale line was matched: exact product (qvetCode), coarse (category), or not found. */
27
+ matchType: 'qvetCode' | 'category' | 'none';
28
+ /** Exact product matched on the factura (qvetCode match only). */
29
+ productName: string | null;
30
+ qvetCode: number | null;
31
+ /** Seller of the matched sale line. Null when not found. */
32
+ sellerCode: string | null;
33
+ /** principal == line seller (the per-commission verdict). */
34
+ ok: boolean;
35
+ }
36
+ /** Status of a commissioned ticket against QVET sales. */
37
+ export type ReconciliationTicketStatus = 'ok' | 'folio_corregido' | 'sin_venta' | 'categoria_no_vendida';
38
+ /** One commissioned ticket (factura) with its reconciliation against sales. */
39
+ export interface ReconciliationTicketRow {
40
+ /** Raw folio as entered in the commission (commissionAllocations.ticketNumber). */
41
+ ticketNumber: string;
42
+ /** Normalized folio used for the cross-reference. */
43
+ normalizedFolio: string;
44
+ /** Commission date (ISO). */
45
+ date: string;
46
+ /** Branch name (Urban/Harbor/Montejo) or id when unresolved. */
47
+ branch: string;
48
+ /** Raw commission service names on the ticket (e.g. "Consulta", "Hemograma"). */
49
+ commissionedServices: string[];
50
+ /** Tracked categories present in the commission (subset of the 4; may be empty). */
51
+ commissionedCategories: ReconciliationCategory[];
52
+ /** Categories sold on the matched factura (empty when no sale matched). */
53
+ soldCategories: ReconciliationCategory[];
54
+ /** Whether the folio matched a QVET sale (exact or auto-corrected). */
55
+ saleFound: boolean;
56
+ /**
57
+ * Nearest factura folio when the raw folio didn't match exactly.
58
+ * For `folio_corregido` it's the auto-linked folio; for `sin_venta` it's a suggestion to review.
59
+ */
60
+ suggestedFolio: string | null;
61
+ /** Commissioned categories that were NOT sold (the discrepancy). */
62
+ missingCategories: ReconciliationCategory[];
63
+ status: ReconciliationTicketStatus;
64
+ /** Commissioner codes (col_code) on the commission. */
65
+ collaboratorCodes: string[];
66
+ /** Principal commissioner (highest commission amount — the SIMPLE/main vet). Should equal the seller. */
67
+ principalCode: string | null;
68
+ /** 3-state quality of the match (see ReconciliationMatchLevel). */
69
+ matchLevel: ReconciliationMatchLevel;
70
+ /** Per-commission-service checks (exact product/qvetCode validation). */
71
+ serviceChecks: ReconciliationServiceCheck[];
72
+ /** Who created the commission (col_code), or null if unknown. */
73
+ createdByCode: string | null;
74
+ /** Resolved seller of the matched factura (null when no sale / unresolved). */
75
+ sellerCode: string | null;
76
+ /** Resolved collector (cobrador) of the matched factura's payment (null when none). */
77
+ collectorCode: string | null;
78
+ /** Whether the resolved seller equals the PRINCIPAL commissioner (null when unknown). */
79
+ sellerMatchesCommission: boolean | null;
80
+ /** Total commission amount on the ticket (rounded). */
81
+ commissionAmount: number;
82
+ /** Heuristic flag: folio looks mistyped (not `[UHM]\d{4,6}`). */
83
+ suspectFolio: boolean;
84
+ }
85
+ /** A factura that sold a category but has no commission for it (reverse/secondary gap). */
86
+ export interface ReconciliationGapRow {
87
+ invoiceNumber: string;
88
+ date: string;
89
+ branch: string;
90
+ /** Sold categories that were not commissioned. */
91
+ missingCategories: ReconciliationCategory[];
92
+ /** Resolved seller of the factura (null when unresolved). */
93
+ sellerCode: string | null;
94
+ }
95
+ /** Per-collaborator aggregate of sold (as seller) vs commissioned, by category. */
96
+ export interface ReconciliationPersonCategoryStat {
97
+ category: ReconciliationCategory;
98
+ soldAsSeller: number;
99
+ commissioned: number;
100
+ }
101
+ export interface ReconciliationPersonRow {
102
+ collaboratorCode: string;
103
+ stats: ReconciliationPersonCategoryStat[];
104
+ }
105
+ /**
106
+ * Per-person registration quality (matched tickets only). Used from two perspectives:
107
+ * - seller: tickets where this person is the recorded VENDEDOR.
108
+ * - collector: tickets where this person is the COBRADOR.
109
+ * `perfecto` = ticket where vendedor == comisionista principal; `parcial` = it doesn't.
110
+ * High `parcial` from the collector view = "quién está registrando mal el vendedor al cobrar".
111
+ */
112
+ export interface ReconciliationStaffQualityRow {
113
+ code: string;
114
+ total: number;
115
+ perfecto: number;
116
+ parcial: number;
117
+ }
118
+ export interface ReconciliationTotals {
119
+ /** Commissions in scope (with at least one tracked category). */
120
+ commissions: number;
121
+ /** Commissioned but not sold (sin_venta + categoria_no_vendida). */
122
+ commissionedNotSold: number;
123
+ /** Auto-linked via folio correction (unambiguous near match). */
124
+ folioCorrected: number;
125
+ /** Of commissionedNotSold, how many have a suspect/mistyped folio. */
126
+ suspectFolios: number;
127
+ /** Facturas that sold a category without a matching commission (secondary gap). */
128
+ soldNotCommissioned: number;
129
+ }
130
+ export interface ReconciliationSummaryResponse {
131
+ period: {
132
+ startDate: string;
133
+ endDate: string;
134
+ };
135
+ totals: ReconciliationTotals;
136
+ /** PRIMARY view: commissioned tickets with a problem (status !== 'ok'). */
137
+ commissionedNotSold: ReconciliationTicketRow[];
138
+ /** Full list of commissioned tickets in the period with their status. */
139
+ tickets: ReconciliationTicketRow[];
140
+ /** SECONDARY view: sold-but-not-commissioned facturas. */
141
+ soldNotCommissioned: ReconciliationGapRow[];
142
+ /** Per-person sold-vs-commissioned aggregates. */
143
+ byPerson: ReconciliationPersonRow[];
144
+ /** Quality from the VENDEDOR perspective (to whom the sale was wrongly assigned). */
145
+ sellerQuality: ReconciliationStaffQualityRow[];
146
+ /** Quality from the COBRADOR perspective (who assigned it wrongly when collecting). */
147
+ collectorQuality: ReconciliationStaffQualityRow[];
148
+ }
@@ -0,0 +1,2 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
@@ -24,3 +24,4 @@ export * from './external-study';
24
24
  export * from './pending-dashboard';
25
25
  export * from './document';
26
26
  export * from './supplier-orders';
27
+ export * from './commission-reconciliation';
@@ -40,3 +40,4 @@ __exportStar(require("./external-study"), exports);
40
40
  __exportStar(require("./pending-dashboard"), exports);
41
41
  __exportStar(require("./document"), exports);
42
42
  __exportStar(require("./supplier-orders"), exports);
43
+ __exportStar(require("./commission-reconciliation"), exports);
package/dist/index.d.ts CHANGED
@@ -11,3 +11,4 @@ export * from './utils/sync-field.helpers';
11
11
  export * from './utils/qvet-catalog.helpers';
12
12
  export * from './utils/qvet-staff.helpers';
13
13
  export * from './utils/enum.helpers';
14
+ export * from './utils/commission-reconciliation.helpers';
package/dist/index.js CHANGED
@@ -29,3 +29,4 @@ __exportStar(require("./utils/sync-field.helpers"), exports);
29
29
  __exportStar(require("./utils/qvet-catalog.helpers"), exports);
30
30
  __exportStar(require("./utils/qvet-staff.helpers"), exports);
31
31
  __exportStar(require("./utils/enum.helpers"), exports);
32
+ __exportStar(require("./utils/commission-reconciliation.helpers"), exports);
@@ -7,12 +7,14 @@
7
7
  * See GH#425.
8
8
  */
9
9
  import { AdministrationRoute } from '../constants/administration-route.enums';
10
- /** Patient profile for the portal Paso 2 (representative averages). */
10
+ /**
11
+ * Patient profile for the portal Paso 2 (representative averages).
12
+ * NOTE: the male/female split is NOT stored per product — it's meaningless there.
13
+ * It's computed at receta-generation time (half/half over the purchased quantity).
14
+ */
11
15
  export interface ControlledRxPatient {
12
16
  weightKg: number;
13
17
  ageMonths: number;
14
- males: number;
15
- females: number;
16
18
  }
17
19
  /**
18
20
  * Treatment scheme for the portal Paso 3.
@@ -0,0 +1,27 @@
1
+ /**
2
+ * Commission Reconciliation — pure classification & key helpers (hvp-backend#434)
3
+ */
4
+ import { ReconciliationCategory } from "../constants/commission-reconciliation.constants";
5
+ /**
6
+ * Normalize an invoice/folio for cross-referencing commission ↔ sale.
7
+ * Strips spaces, slashes and any non-alphanumeric, uppercases.
8
+ *
9
+ * @example
10
+ * normalizeInvoiceNumber(" H/05355") // "H05355"
11
+ * normalizeInvoiceNumber("H05346") // "H05346"
12
+ */
13
+ export declare function normalizeInvoiceNumber(value: string | null | undefined): string;
14
+ /**
15
+ * Classify a QVET sale line into a reconciliation category by its section/family.
16
+ * Returns null when the line is outside the 4 tracked categories.
17
+ *
18
+ * @example
19
+ * classifySaleLine("SERVICIOS MEDICOS", "CONSULTA") // ReconciliationCategory.consulta
20
+ * classifySaleLine("INSUMOS ESCANDALLO", "VACUNAS") // ReconciliationCategory.vacuna
21
+ */
22
+ export declare function classifySaleLine(section: string | null | undefined, family: string | null | undefined): ReconciliationCategory | null;
23
+ /**
24
+ * Classify a commission service name into a reconciliation category.
25
+ * Returns null when the service is outside the 4 tracked categories.
26
+ */
27
+ export declare function classifyCommissionService(serviceName: string | null | undefined): ReconciliationCategory | null;
@@ -0,0 +1,54 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.normalizeInvoiceNumber = normalizeInvoiceNumber;
4
+ exports.classifySaleLine = classifySaleLine;
5
+ exports.classifyCommissionService = classifyCommissionService;
6
+ /**
7
+ * Commission Reconciliation — pure classification & key helpers (hvp-backend#434)
8
+ */
9
+ const commission_reconciliation_constants_1 = require("../constants/commission-reconciliation.constants");
10
+ /**
11
+ * Normalize an invoice/folio for cross-referencing commission ↔ sale.
12
+ * Strips spaces, slashes and any non-alphanumeric, uppercases.
13
+ *
14
+ * @example
15
+ * normalizeInvoiceNumber(" H/05355") // "H05355"
16
+ * normalizeInvoiceNumber("H05346") // "H05346"
17
+ */
18
+ function normalizeInvoiceNumber(value) {
19
+ return String(value ?? "")
20
+ .replace(/[^A-Za-z0-9]/g, "")
21
+ .toUpperCase();
22
+ }
23
+ /**
24
+ * Classify a QVET sale line into a reconciliation category by its section/family.
25
+ * Returns null when the line is outside the 4 tracked categories.
26
+ *
27
+ * @example
28
+ * classifySaleLine("SERVICIOS MEDICOS", "CONSULTA") // ReconciliationCategory.consulta
29
+ * classifySaleLine("INSUMOS ESCANDALLO", "VACUNAS") // ReconciliationCategory.vacuna
30
+ */
31
+ function classifySaleLine(section, family) {
32
+ const s = String(section ?? "").toUpperCase();
33
+ const f = String(family ?? "").toUpperCase();
34
+ if (f === "VACUNAS")
35
+ return commission_reconciliation_constants_1.ReconciliationCategory.vacuna;
36
+ if (s === commission_reconciliation_constants_1.SERVICIOS_MEDICOS_SECTION) {
37
+ if (f === "CONSULTA")
38
+ return commission_reconciliation_constants_1.ReconciliationCategory.consulta;
39
+ if (f === "EMERGENCIA")
40
+ return commission_reconciliation_constants_1.ReconciliationCategory.emergencia;
41
+ if (commission_reconciliation_constants_1.SURGERY_FAMILIES.includes(f))
42
+ return commission_reconciliation_constants_1.ReconciliationCategory.cirugia;
43
+ }
44
+ return null;
45
+ }
46
+ /**
47
+ * Classify a commission service name into a reconciliation category.
48
+ * Returns null when the service is outside the 4 tracked categories.
49
+ */
50
+ function classifyCommissionService(serviceName) {
51
+ if (!serviceName)
52
+ return null;
53
+ return commission_reconciliation_constants_1.COMMISSION_SERVICE_TO_CATEGORY[serviceName] ?? null;
54
+ }
@@ -0,0 +1,65 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ const commission_reconciliation_helpers_1 = require("./commission-reconciliation.helpers");
4
+ const commission_reconciliation_constants_1 = require("../constants/commission-reconciliation.constants");
5
+ describe('normalizeInvoiceNumber', () => {
6
+ it('strips spaces and slashes, uppercases', () => {
7
+ expect((0, commission_reconciliation_helpers_1.normalizeInvoiceNumber)(' H/05355')).toBe('H05355');
8
+ expect((0, commission_reconciliation_helpers_1.normalizeInvoiceNumber)('H05346')).toBe('H05346');
9
+ expect((0, commission_reconciliation_helpers_1.normalizeInvoiceNumber)(' h05355')).toBe('H05355');
10
+ });
11
+ it('matches the sale-factura form to the commission-folio form', () => {
12
+ expect((0, commission_reconciliation_helpers_1.normalizeInvoiceNumber)(' H/05355')).toBe((0, commission_reconciliation_helpers_1.normalizeInvoiceNumber)('H05355'));
13
+ });
14
+ it('handles null/undefined', () => {
15
+ expect((0, commission_reconciliation_helpers_1.normalizeInvoiceNumber)(null)).toBe('');
16
+ expect((0, commission_reconciliation_helpers_1.normalizeInvoiceNumber)(undefined)).toBe('');
17
+ });
18
+ });
19
+ describe('classifySaleLine', () => {
20
+ it('classifies consulta / emergencia by SERVICIOS MEDICOS section', () => {
21
+ expect((0, commission_reconciliation_helpers_1.classifySaleLine)('SERVICIOS MEDICOS', 'CONSULTA')).toBe(commission_reconciliation_constants_1.ReconciliationCategory.consulta);
22
+ expect((0, commission_reconciliation_helpers_1.classifySaleLine)('SERVICIOS MEDICOS', 'EMERGENCIA')).toBe(commission_reconciliation_constants_1.ReconciliationCategory.emergencia);
23
+ });
24
+ it('classifies vacuna by VACUNAS family in any section', () => {
25
+ expect((0, commission_reconciliation_helpers_1.classifySaleLine)('SERVICIOS MEDICOS', 'VACUNAS')).toBe(commission_reconciliation_constants_1.ReconciliationCategory.vacuna);
26
+ expect((0, commission_reconciliation_helpers_1.classifySaleLine)('INSUMOS ESCANDALLO', 'VACUNAS')).toBe(commission_reconciliation_constants_1.ReconciliationCategory.vacuna);
27
+ });
28
+ it('classifies all surgical families as cirugia (within SERVICIOS MEDICOS)', () => {
29
+ for (const fam of ['CIRUGIA DE TEJIDOS BLANDOS', 'ASISTENCIA QUIRURGICA', 'OFTALMOLOGIA', 'ORTOPEDIA']) {
30
+ expect((0, commission_reconciliation_helpers_1.classifySaleLine)('SERVICIOS MEDICOS', fam)).toBe(commission_reconciliation_constants_1.ReconciliationCategory.cirugia);
31
+ }
32
+ });
33
+ it('does NOT classify surgical-named families outside SERVICIOS MEDICOS (e.g. insumos oftalmología)', () => {
34
+ expect((0, commission_reconciliation_helpers_1.classifySaleLine)('INSUMOS MEDICOS', 'OFTALMOLOGIA')).toBeNull();
35
+ });
36
+ it('returns null for out-of-scope sections (farmacia, insumos, lab)', () => {
37
+ expect((0, commission_reconciliation_helpers_1.classifySaleLine)('FARMACIA', 'ANTIPARASITARIOS')).toBeNull();
38
+ expect((0, commission_reconciliation_helpers_1.classifySaleLine)('SERVICIOS EXTERNOS', 'LABORATORIO')).toBeNull();
39
+ expect((0, commission_reconciliation_helpers_1.classifySaleLine)(null, null)).toBeNull();
40
+ });
41
+ it('is case-insensitive', () => {
42
+ expect((0, commission_reconciliation_helpers_1.classifySaleLine)('servicios medicos', 'consulta')).toBe(commission_reconciliation_constants_1.ReconciliationCategory.consulta);
43
+ });
44
+ });
45
+ describe('classifyCommissionService', () => {
46
+ it('maps consulta-type services to consulta', () => {
47
+ for (const s of ['Consulta', 'Revisión', 'Especialista', 'Revisión especialista', 'Consulta no convencionales']) {
48
+ expect((0, commission_reconciliation_helpers_1.classifyCommissionService)(s)).toBe(commission_reconciliation_constants_1.ReconciliationCategory.consulta);
49
+ }
50
+ });
51
+ it('maps surgical roles to cirugia', () => {
52
+ expect((0, commission_reconciliation_helpers_1.classifyCommissionService)('Cirugía')).toBe(commission_reconciliation_constants_1.ReconciliationCategory.cirugia);
53
+ expect((0, commission_reconciliation_helpers_1.classifyCommissionService)('Anestesista')).toBe(commission_reconciliation_constants_1.ReconciliationCategory.cirugia);
54
+ expect((0, commission_reconciliation_helpers_1.classifyCommissionService)('Primer ayudante')).toBe(commission_reconciliation_constants_1.ReconciliationCategory.cirugia);
55
+ });
56
+ it('maps Vacuna and Emergencia', () => {
57
+ expect((0, commission_reconciliation_helpers_1.classifyCommissionService)('Vacuna')).toBe(commission_reconciliation_constants_1.ReconciliationCategory.vacuna);
58
+ expect((0, commission_reconciliation_helpers_1.classifyCommissionService)('Emergencia')).toBe(commission_reconciliation_constants_1.ReconciliationCategory.emergencia);
59
+ });
60
+ it('returns null for out-of-scope services', () => {
61
+ expect((0, commission_reconciliation_helpers_1.classifyCommissionService)('Hemograma')).toBeNull();
62
+ expect((0, commission_reconciliation_helpers_1.classifyCommissionService)('Corte de uñas')).toBeNull();
63
+ expect((0, commission_reconciliation_helpers_1.classifyCommissionService)(null)).toBeNull();
64
+ });
65
+ });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "hvp-shared",
3
- "version": "13.17.0",
3
+ "version": "13.26.0",
4
4
  "description": "Shared types and utilities for HVP backend and frontend",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",