hvp-shared 13.11.0 → 13.13.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.
@@ -29,3 +29,4 @@ export * from './client-billing.enums';
29
29
  export * from './sat-income-invoice';
30
30
  export * from './global-invoice.enums';
31
31
  export * from './google-calendar.constants';
32
+ export * from './performance-metrics.constants';
@@ -45,3 +45,4 @@ __exportStar(require("./client-billing.enums"), exports);
45
45
  __exportStar(require("./sat-income-invoice"), exports);
46
46
  __exportStar(require("./global-invoice.enums"), exports);
47
47
  __exportStar(require("./google-calendar.constants"), exports);
48
+ __exportStar(require("./performance-metrics.constants"), exports);
@@ -0,0 +1,51 @@
1
+ /**
2
+ * Performance dashboard — canonical metric registry (single source of truth).
3
+ *
4
+ * Consumed by: backend (label, lowerIsBetter for the API), frontend (weight, unit),
5
+ * and the catalog generator. DO NOT duplicate this metadata in consumers.
6
+ *
7
+ * AUTO-GENERATED seed (hvp-backend/scripts/gen-performance-registry.ts).
8
+ * 76 metrics · type {"indicator":38,"support":38} · weight {"critical":10,"important":17,"complementary":11} · status {"implemented":70,"proposed":6}
9
+ */
10
+ export declare enum MetricWeight {
11
+ CRITICAL = "critical",
12
+ IMPORTANT = "important",
13
+ COMPLEMENTARY = "complementary"
14
+ }
15
+ export type MetricType = "indicator" | "support";
16
+ export type MetricUnit = "currency" | "percent" | "number";
17
+ export type MetricPillar = "P1" | "P2" | "P3" | "P4";
18
+ export type MetricEndpoint = "growth" | "cooperation" | "fidelity" | "visits" | "others" | "operative" | "sales";
19
+ export type MetricStatus = "implemented" | "proposed";
20
+ export interface PerformanceMetricMeta {
21
+ /** Stable metric key (matches the API response `metrics[].key`). */
22
+ key: string;
23
+ /** Spanish display label (the one the API sends today). */
24
+ label: string;
25
+ /** indicator = tracked KPI · support = numerator/denominator/context. */
26
+ type: MetricType;
27
+ /** Only indicators have a weight. */
28
+ weight?: MetricWeight;
29
+ unit: MetricUnit;
30
+ /** True when a lower value is better (default false). */
31
+ lowerIsBetter?: boolean;
32
+ /** Which dashboard endpoint produces it. */
33
+ endpoint: MetricEndpoint;
34
+ pillar: MetricPillar;
35
+ status: MetricStatus;
36
+ }
37
+ export declare const PERFORMANCE_METRICS: Record<string, PerformanceMetricMeta>;
38
+ /** Labels for a given endpoint (replaces the per-endpoint *_METRIC_LABELS maps). */
39
+ export declare function performanceLabelsByEndpoint(endpoint: MetricEndpoint): Record<string, string>;
40
+ /** Keys where a lower value is better, for a given endpoint. */
41
+ export declare function performanceLowerIsBetter(endpoint: MetricEndpoint): Set<string>;
42
+ export interface MetricDetailView {
43
+ /** In-app route (HashRouter path under /dashboard). */
44
+ route: string;
45
+ /** Short Spanish name of the destination view. */
46
+ label: string;
47
+ }
48
+ /** metric key → its dedicated view (undefined if the metric has no own page). */
49
+ export declare const METRIC_DETAIL_VIEWS: Record<string, MetricDetailView>;
50
+ /** Convenience accessor: the dedicated view for a metric key, or undefined. */
51
+ export declare function metricDetailView(key: string): MetricDetailView | undefined;
@@ -0,0 +1,194 @@
1
+ "use strict";
2
+ /**
3
+ * Performance dashboard — canonical metric registry (single source of truth).
4
+ *
5
+ * Consumed by: backend (label, lowerIsBetter for the API), frontend (weight, unit),
6
+ * and the catalog generator. DO NOT duplicate this metadata in consumers.
7
+ *
8
+ * AUTO-GENERATED seed (hvp-backend/scripts/gen-performance-registry.ts).
9
+ * 76 metrics · type {"indicator":38,"support":38} · weight {"critical":10,"important":17,"complementary":11} · status {"implemented":70,"proposed":6}
10
+ */
11
+ Object.defineProperty(exports, "__esModule", { value: true });
12
+ exports.METRIC_DETAIL_VIEWS = exports.PERFORMANCE_METRICS = exports.MetricWeight = void 0;
13
+ exports.performanceLabelsByEndpoint = performanceLabelsByEndpoint;
14
+ exports.performanceLowerIsBetter = performanceLowerIsBetter;
15
+ exports.metricDetailView = metricDetailView;
16
+ var MetricWeight;
17
+ (function (MetricWeight) {
18
+ MetricWeight["CRITICAL"] = "critical";
19
+ MetricWeight["IMPORTANT"] = "important";
20
+ MetricWeight["COMPLEMENTARY"] = "complementary";
21
+ })(MetricWeight || (exports.MetricWeight = MetricWeight = {}));
22
+ exports.PERFORMANCE_METRICS = {
23
+ "assistancesGiven": { key: "assistancesGiven", label: "Asistencias prestadas", type: "indicator", weight: MetricWeight.CRITICAL, unit: "number", endpoint: "cooperation", pillar: "P1", status: "implemented" },
24
+ "mentorshipsAsMentor": { key: "mentorshipsAsMentor", label: "Mentorías como mentor", type: "indicator", weight: MetricWeight.IMPORTANT, unit: "number", endpoint: "cooperation", pillar: "P1", status: "implemented" },
25
+ "mentorshipsAsMentoree": { key: "mentorshipsAsMentoree", label: "Mentorías como mentoree", type: "indicator", weight: MetricWeight.IMPORTANT, unit: "number", endpoint: "cooperation", pillar: "P1", status: "implemented" },
26
+ "allConsultations": { key: "allConsultations", label: "Consultas", type: "indicator", weight: MetricWeight.CRITICAL, unit: "number", endpoint: "growth", pillar: "P1", status: "implemented" },
27
+ "allHospitalizations": { key: "allHospitalizations", label: "Hospitalizaciones y emergencias", type: "indicator", weight: MetricWeight.COMPLEMENTARY, unit: "number", endpoint: "growth", pillar: "P1", status: "implemented" },
28
+ "allLabWork": { key: "allLabWork", label: "Laboratorio", type: "indicator", weight: MetricWeight.COMPLEMENTARY, unit: "number", endpoint: "growth", pillar: "P1", status: "implemented" },
29
+ "allPreventive": { key: "allPreventive", label: "Vacunas y desparasitaciones", type: "indicator", weight: MetricWeight.IMPORTANT, unit: "number", endpoint: "growth", pillar: "P1", status: "implemented" },
30
+ "allSurgeries": { key: "allSurgeries", label: "Cirugías", type: "indicator", weight: MetricWeight.CRITICAL, unit: "number", endpoint: "growth", pillar: "P1", status: "implemented" },
31
+ "allSurgeryAssistances": { key: "allSurgeryAssistances", label: "Asistencias de cirugía", type: "indicator", weight: MetricWeight.IMPORTANT, unit: "number", endpoint: "growth", pillar: "P1", status: "implemented" },
32
+ "chemistryPanels": { key: "chemistryPanels", label: "Químicas sanguíneas", type: "support", unit: "number", endpoint: "growth", pillar: "P1", status: "implemented" },
33
+ "commissionAmount": { key: "commissionAmount", label: "Monto de comisiones", type: "indicator", weight: MetricWeight.IMPORTANT, unit: "currency", endpoint: "growth", pillar: "P1", status: "implemented" },
34
+ "commissionCount": { key: "commissionCount", label: "Comisiones recibidas", type: "indicator", weight: MetricWeight.IMPORTANT, unit: "number", endpoint: "growth", pillar: "P1", status: "implemented" },
35
+ "egoCommissions": { key: "egoCommissions", label: "Comisiones de Examen General de Orina (EGO)", type: "indicator", weight: MetricWeight.COMPLEMENTARY, unit: "number", endpoint: "growth", pillar: "P4", status: "implemented" },
36
+ "complexSurgeries": { key: "complexSurgeries", label: "Cirugías complejas", type: "support", unit: "number", endpoint: "growth", pillar: "P1", status: "implemented" },
37
+ "consultations": { key: "consultations", label: "Consultas generales", type: "support", unit: "number", endpoint: "growth", pillar: "P1", status: "implemented" },
38
+ "dewormings": { key: "dewormings", label: "Desparasitaciones", type: "support", unit: "number", endpoint: "growth", pillar: "P1", status: "implemented" },
39
+ "diagnosticTests": { key: "diagnosticTests", label: "Tests diagnósticos", type: "support", unit: "number", endpoint: "growth", pillar: "P1", status: "implemented" },
40
+ "emergencies": { key: "emergencies", label: "Emergencias", type: "support", unit: "number", endpoint: "growth", pillar: "P1", status: "implemented" },
41
+ "englishConsultations": { key: "englishConsultations", label: "Consultas en inglés", type: "support", unit: "number", endpoint: "growth", pillar: "P1", status: "implemented" },
42
+ "hemograms": { key: "hemograms", label: "Hemogramas", type: "support", unit: "number", endpoint: "growth", pillar: "P1", status: "implemented" },
43
+ "hospitalizations": { key: "hospitalizations", label: "Hospitalizaciones", type: "support", unit: "number", endpoint: "growth", pillar: "P1", status: "implemented" },
44
+ "revisions": { key: "revisions", label: "Revisiones", type: "support", unit: "number", endpoint: "growth", pillar: "P1", status: "implemented" },
45
+ "specialistConsultations": { key: "specialistConsultations", label: "Consultas de especialista", type: "support", unit: "number", endpoint: "growth", pillar: "P1", status: "implemented" },
46
+ "specialistRevisions": { key: "specialistRevisions", label: "Revisiones de especialista", type: "support", unit: "number", endpoint: "growth", pillar: "P1", status: "implemented" },
47
+ "surgeries": { key: "surgeries", label: "Cirugías estándar", type: "support", unit: "number", endpoint: "growth", pillar: "P1", status: "implemented" },
48
+ "surgeryAssistances": { key: "surgeryAssistances", label: "Asistencias (anestesista/1er ayudante)", type: "support", unit: "number", endpoint: "growth", pillar: "P1", status: "implemented" },
49
+ "totalServices": { key: "totalServices", label: "Total servicios", type: "indicator", weight: MetricWeight.CRITICAL, unit: "number", endpoint: "growth", pillar: "P1", status: "implemented" },
50
+ "vaccines": { key: "vaccines", label: "Vacunas", type: "support", unit: "number", endpoint: "growth", pillar: "P1", status: "implemented" },
51
+ "commissionConsultations": { key: "commissionConsultations", label: "Consultas y vacunas comisionadas", type: "support", unit: "number", endpoint: "others", pillar: "P1", status: "implemented" },
52
+ "matchedConsultations": { key: "matchedConsultations", label: "Consultas y vacunas comisionadas con visita registrada", type: "support", unit: "number", endpoint: "others", pillar: "P1", status: "implemented" },
53
+ "matchRate": { key: "matchRate", label: "% de consultas y vacunas comisionadas con visita registrada", type: "indicator", weight: MetricWeight.IMPORTANT, unit: "percent", endpoint: "others", pillar: "P1", status: "implemented" },
54
+ "avgVisitLength": { key: "avgVisitLength", label: "Extensión promedio (caracteres)", type: "indicator", weight: MetricWeight.IMPORTANT, unit: "number", endpoint: "visits", pillar: "P1", status: "implemented" },
55
+ "visitCount": { key: "visitCount", label: "Visitas registradas", type: "indicator", weight: MetricWeight.IMPORTANT, unit: "number", endpoint: "visits", pillar: "P1", status: "implemented" },
56
+ "newClients": { key: "newClients", label: "Clientes nuevos", type: "indicator", weight: MetricWeight.COMPLEMENTARY, unit: "number", endpoint: "fidelity", pillar: "P2", status: "implemented" },
57
+ "vetRequestedTotal": { key: "vetRequestedTotal", label: "Veterinario solicitado (total)", type: "indicator", weight: MetricWeight.IMPORTANT, unit: "number", endpoint: "fidelity", pillar: "P2", status: "implemented" },
58
+ "avgTicketOverall": { key: "avgTicketOverall", label: "Ticket promedio general", type: "indicator", weight: MetricWeight.COMPLEMENTARY, unit: "currency", endpoint: "sales", pillar: "P2", status: "implemented" },
59
+ "consultations_avgTicket": { key: "consultations_avgTicket", label: "Consultas — prom. ticket", type: "indicator", weight: MetricWeight.CRITICAL, unit: "currency", endpoint: "sales", pillar: "P2", status: "implemented" },
60
+ "consultations_extraPct": { key: "consultations_extraPct", label: "Consultas — extra %", type: "indicator", weight: MetricWeight.COMPLEMENTARY, unit: "percent", endpoint: "sales", pillar: "P2", status: "implemented" },
61
+ "consultations_tickets": { key: "consultations_tickets", label: "Consultas — tickets", type: "support", unit: "number", endpoint: "sales", pillar: "P2", status: "implemented" },
62
+ "emergencies_avgTicket": { key: "emergencies_avgTicket", label: "Emergencias y hospitalizaciones — prom. ticket", type: "support", unit: "currency", endpoint: "sales", pillar: "P2", status: "implemented" },
63
+ "emergencies_extraPct": { key: "emergencies_extraPct", label: "Emergencias y hospitalizaciones — extra %", type: "support", unit: "percent", endpoint: "sales", pillar: "P2", status: "implemented" },
64
+ "emergencies_tickets": { key: "emergencies_tickets", label: "Emergencias y hospitalizaciones — tickets", type: "support", unit: "number", endpoint: "sales", pillar: "P2", status: "implemented" },
65
+ "generatedRevenue": { key: "generatedRevenue", label: "Facturación generada", type: "indicator", weight: MetricWeight.CRITICAL, unit: "currency", endpoint: "sales", pillar: "P2", status: "implemented" },
66
+ "pharmacyProductSales": { key: "pharmacyProductSales", label: "Venta de farmacia / productos", type: "indicator", weight: MetricWeight.IMPORTANT, unit: "currency", endpoint: "sales", pillar: "P2", status: "implemented" },
67
+ "surgeries_avgTicket": { key: "surgeries_avgTicket", label: "Cirugías — prom. ticket", type: "support", unit: "currency", endpoint: "sales", pillar: "P2", status: "implemented" },
68
+ "surgeries_extraPct": { key: "surgeries_extraPct", label: "Cirugías — extra %", type: "support", unit: "percent", endpoint: "sales", pillar: "P2", status: "implemented" },
69
+ "surgeries_tickets": { key: "surgeries_tickets", label: "Cirugías — tickets", type: "support", unit: "number", endpoint: "sales", pillar: "P2", status: "implemented" },
70
+ "vaccines_avgTicket": { key: "vaccines_avgTicket", label: "Vacunas y desparasitaciones — prom. ticket", type: "support", unit: "currency", endpoint: "sales", pillar: "P2", status: "implemented" },
71
+ "vaccines_extraPct": { key: "vaccines_extraPct", label: "Vacunas y desparasitaciones — extra %", type: "support", unit: "percent", endpoint: "sales", pillar: "P2", status: "implemented" },
72
+ "vaccines_tickets": { key: "vaccines_tickets", label: "Vacunas y desparasitaciones — tickets", type: "support", unit: "number", endpoint: "sales", pillar: "P2", status: "implemented" },
73
+ "absenceRate": { key: "absenceRate", label: "% faltas", type: "indicator", weight: MetricWeight.IMPORTANT, unit: "percent", lowerIsBetter: true, endpoint: "operative", pillar: "P3", status: "implemented" },
74
+ "absences": { key: "absences", label: "Faltas", type: "support", unit: "number", lowerIsBetter: true, endpoint: "operative", pillar: "P3", status: "implemented" },
75
+ "attendanceCount": { key: "attendanceCount", label: "Registros de asistencia", type: "support", unit: "number", endpoint: "operative", pillar: "P3", status: "implemented" },
76
+ "cashFlowCount": { key: "cashFlowCount", label: "Entradas/salidas de caja", type: "support", unit: "number", endpoint: "operative", pillar: "P3", status: "implemented" },
77
+ "cashFlowValidRate": { key: "cashFlowValidRate", label: "% E/S caja bien hechas", type: "indicator", weight: MetricWeight.COMPLEMENTARY, unit: "percent", endpoint: "operative", pillar: "P3", status: "implemented" },
78
+ "closingsCount": { key: "closingsCount", label: "Cierres de caja", type: "indicator", weight: MetricWeight.COMPLEMENTARY, unit: "number", endpoint: "operative", pillar: "P3", status: "implemented" },
79
+ "closingSuccessRate": { key: "closingSuccessRate", label: "% cierres bien hechos", type: "indicator", weight: MetricWeight.IMPORTANT, unit: "percent", endpoint: "operative", pillar: "P3", status: "implemented" },
80
+ "collectionsAmount": { key: "collectionsAmount", label: "Monto de cobros", type: "indicator", weight: MetricWeight.COMPLEMENTARY, unit: "currency", endpoint: "operative", pillar: "P3", status: "implemented" },
81
+ "collectionsTickets": { key: "collectionsTickets", label: "Tickets cobrados", type: "support", unit: "number", endpoint: "operative", pillar: "P3", status: "implemented" },
82
+ "cvObligatoryPct": { key: "cvObligatoryPct", label: "% obligatorios a Clientes Varios", type: "indicator", weight: MetricWeight.IMPORTANT, unit: "percent", lowerIsBetter: true, endpoint: "operative", pillar: "P3", status: "implemented" },
83
+ "duplicateClientsCreated": { key: "duplicateClientsCreated", label: "Clientes duplicados creados", type: "indicator", weight: MetricWeight.IMPORTANT, unit: "number", lowerIsBetter: true, endpoint: "operative", pillar: "P3", status: "implemented" },
84
+ "cvGlobalPct": { key: "cvGlobalPct", label: "% por monto (>$500) a Clientes Varios", type: "support", unit: "percent", lowerIsBetter: true, endpoint: "operative", pillar: "P3", status: "implemented" },
85
+ "cvMedicalPct": { key: "cvMedicalPct", label: "% médicos/externos a Clientes Varios", type: "support", unit: "percent", lowerIsBetter: true, endpoint: "operative", pillar: "P3", status: "implemented" },
86
+ "docsReadRate": { key: "docsReadRate", label: "% documentación interna leída", type: "indicator", weight: MetricWeight.IMPORTANT, unit: "percent", endpoint: "operative", pillar: "P3", status: "implemented" },
87
+ "farCheckInRate": { key: "farCheckInRate", label: "% check-ins fuera", type: "indicator", weight: MetricWeight.COMPLEMENTARY, unit: "percent", lowerIsBetter: true, endpoint: "operative", pillar: "P3", status: "implemented" },
88
+ "farCheckIns": { key: "farCheckIns", label: "Check-ins fuera de sucursal", type: "support", unit: "number", lowerIsBetter: true, endpoint: "operative", pillar: "P3", status: "implemented" },
89
+ "goodSchedulingRate": { key: "goodSchedulingRate", label: "Calidad de agenda (puntaje prom.)", type: "indicator", weight: MetricWeight.IMPORTANT, unit: "percent", endpoint: "operative", pillar: "P3", status: "implemented" },
90
+ "scheduledAppointments": { key: "scheduledAppointments", label: "Citas agendadas con nombre", type: "indicator", weight: MetricWeight.COMPLEMENTARY, unit: "number", endpoint: "operative", pillar: "P3", status: "implemented" },
91
+ "correctVisitTypeRate": { key: "correctVisitTypeRate", label: "% tipo de visita correcto", type: "indicator", weight: MetricWeight.IMPORTANT, unit: "percent", endpoint: "visits", pillar: "P1", status: "implemented" },
92
+ "internalLabRate": { key: "internalLabRate", label: "% químicas y hemogramas internos", type: "indicator", weight: MetricWeight.IMPORTANT, unit: "percent", endpoint: "operative", pillar: "P3", status: "implemented" },
93
+ "invalidClosings": { key: "invalidClosings", label: "Cierres inválidos", type: "support", unit: "number", lowerIsBetter: true, endpoint: "operative", pillar: "P3", status: "implemented" },
94
+ "lateCount": { key: "lateCount", label: "Retardos (>10 min)", type: "support", unit: "number", lowerIsBetter: true, endpoint: "operative", pillar: "P3", status: "implemented" },
95
+ "lateRate": { key: "lateRate", label: "% retardos", type: "indicator", weight: MetricWeight.CRITICAL, unit: "percent", lowerIsBetter: true, endpoint: "operative", pillar: "P3", status: "implemented" },
96
+ "memosRead": { key: "memosRead", label: "Memorandums leídos", type: "support", unit: "number", endpoint: "operative", pillar: "P3", status: "implemented" },
97
+ "memosReadRate": { key: "memosReadRate", label: "% lectura de memorandums", type: "indicator", weight: MetricWeight.IMPORTANT, unit: "percent", endpoint: "operative", pillar: "P3", status: "implemented" },
98
+ "salesAmount": { key: "salesAmount", label: "Monto de ventas", type: "indicator", weight: MetricWeight.COMPLEMENTARY, unit: "currency", endpoint: "operative", pillar: "P3", status: "implemented" },
99
+ "salesCount": { key: "salesCount", label: "Tickets vendidos", type: "support", unit: "number", endpoint: "operative", pillar: "P3", status: "implemented" },
100
+ "sellerMatchRate": { key: "sellerMatchRate", label: "% de cobros con el vendedor correcto", type: "indicator", weight: MetricWeight.IMPORTANT, unit: "percent", endpoint: "operative", pillar: "P3", status: "implemented" },
101
+ "verifiedTickets": { key: "verifiedTickets", label: "Tickets médicos verificados", type: "support", unit: "number", endpoint: "operative", pillar: "P3", status: "implemented" },
102
+ "ownSaleConsultations": { key: "ownSaleConsultations", label: "Consultas y vacunas comisionadas en las que aparezco como vendedor", type: "support", unit: "number", endpoint: "others", pillar: "P3", status: "implemented" },
103
+ "ownSaleRate": { key: "ownSaleRate", label: "% de consultas y vacunas comisionadas en las que aparezco como vendedor", type: "indicator", weight: MetricWeight.COMPLEMENTARY, unit: "percent", endpoint: "others", pillar: "P3", status: "implemented" },
104
+ };
105
+ /** Labels for a given endpoint (replaces the per-endpoint *_METRIC_LABELS maps). */
106
+ function performanceLabelsByEndpoint(endpoint) {
107
+ const out = {};
108
+ for (const m of Object.values(exports.PERFORMANCE_METRICS)) {
109
+ if (m.endpoint === endpoint)
110
+ out[m.key] = m.label;
111
+ }
112
+ return out;
113
+ }
114
+ /** Keys where a lower value is better, for a given endpoint. */
115
+ function performanceLowerIsBetter(endpoint) {
116
+ return new Set(Object.values(exports.PERFORMANCE_METRICS)
117
+ .filter((m) => m.endpoint === endpoint && m.lowerIsBetter)
118
+ .map((m) => m.key));
119
+ }
120
+ const DETAIL_VIEW_GROUPS = [
121
+ {
122
+ view: { route: "/dashboard/commissions", label: "Comisiones" },
123
+ keys: [
124
+ "commissionAmount", "commissionCount", "totalServices", "allConsultations",
125
+ "allSurgeries", "allPreventive", "allSurgeryAssistances", "allHospitalizations",
126
+ "allLabWork", "assistancesGiven", "mentorshipsAsMentor", "mentorshipsAsMentoree",
127
+ "vetRequestedTotal", "newClients", "egoCommissions", "commissionConsultations",
128
+ "ownSaleConsultations", "chemistryPanels", "complexSurgeries", "consultations",
129
+ "dewormings", "diagnosticTests", "emergencies", "englishConsultations", "hemograms",
130
+ "hospitalizations", "revisions", "specialistConsultations", "specialistRevisions",
131
+ "surgeries", "surgeryAssistances", "vaccines",
132
+ ],
133
+ },
134
+ {
135
+ view: { route: "/dashboard/qvet/sync/visits", label: "Visitas clínicas" },
136
+ keys: ["visitCount", "avgVisitLength", "matchRate", "correctVisitTypeRate", "matchedConsultations"],
137
+ },
138
+ {
139
+ view: { route: "/dashboard/google-calendar/diagnose", label: "Diagnóstico de agenda" },
140
+ keys: ["goodSchedulingRate", "scheduledAppointments"],
141
+ },
142
+ {
143
+ view: { route: "/dashboard/attendance-records", label: "Asistencia" },
144
+ keys: ["absenceRate", "lateRate", "farCheckInRate", "absences", "attendanceCount", "lateCount", "farCheckIns"],
145
+ },
146
+ {
147
+ view: { route: "/dashboard/documentation/memos", label: "Memorandums" },
148
+ keys: ["memosReadRate", "memosRead"],
149
+ },
150
+ {
151
+ view: { route: "/dashboard/documentation/compliance", label: "Control de lectura" },
152
+ keys: ["docsReadRate"],
153
+ },
154
+ {
155
+ view: { route: "/dashboard/cash-reconciliation", label: "Reconciliación de caja" },
156
+ keys: ["closingSuccessRate", "closingsCount", "invalidClosings"],
157
+ },
158
+ {
159
+ view: { route: "/dashboard/cash-reconciliation", label: "Reconciliación de caja" },
160
+ keys: ["cashFlowValidRate", "cashFlowCount"],
161
+ },
162
+ {
163
+ view: { route: "/dashboard/qvet/sync/collections", label: "Ventas y cobros" },
164
+ keys: ["salesAmount", "collectionsAmount", "salesCount", "collectionsTickets", "ownSaleRate", "sellerMatchRate", "verifiedTickets"],
165
+ },
166
+ {
167
+ view: { route: "/dashboard/qvet/sync/sales", label: "Ventas QVET" },
168
+ keys: [
169
+ "generatedRevenue", "pharmacyProductSales", "avgTicketOverall",
170
+ "consultations_avgTicket", "consultations_extraPct", "consultations_tickets",
171
+ "surgeries_avgTicket", "surgeries_extraPct", "surgeries_tickets",
172
+ "vaccines_avgTicket", "vaccines_extraPct", "vaccines_tickets",
173
+ "emergencies_avgTicket", "emergencies_extraPct", "emergencies_tickets",
174
+ ],
175
+ },
176
+ {
177
+ view: { route: "/dashboard/audit/generic-clients", label: "Auditoría Clientes Varios" },
178
+ keys: ["cvObligatoryPct", "cvGlobalPct", "cvMedicalPct"],
179
+ },
180
+ {
181
+ view: { route: "/dashboard/audit/duplicates", label: "Auditoría duplicados" },
182
+ keys: ["duplicateClientsCreated"],
183
+ },
184
+ {
185
+ view: { route: "/dashboard/performance/lab", label: "Análisis de laboratorio" },
186
+ keys: ["internalLabRate"],
187
+ },
188
+ ];
189
+ /** metric key → its dedicated view (undefined if the metric has no own page). */
190
+ exports.METRIC_DETAIL_VIEWS = Object.fromEntries(DETAIL_VIEW_GROUPS.flatMap((g) => g.keys.map((k) => [k, g.view])));
191
+ /** Convenience accessor: the dedicated view for a metric key, or undefined. */
192
+ function metricDetailView(key) {
193
+ return exports.METRIC_DETAIL_VIEWS[key];
194
+ }
@@ -27,6 +27,12 @@ export interface DuplicateClientInfo {
27
27
  visitCount: number;
28
28
  salesTotalAmount: number;
29
29
  petNames: string[];
30
+ /** QVET staff member who registered the client ("PERSONAL ALTA"). Null for old records QVET never recorded a creator for. */
31
+ createdByStaff: string | null;
32
+ /** When the client was created in QVET ("FECHA ALTA"), ISO 8601. */
33
+ createdAt: string | null;
34
+ /** When the client was deactivated ("FECHA BAJA"), ISO 8601. Null if still active. */
35
+ deactivatedAt: string | null;
30
36
  }
31
37
  /**
32
38
  * A group of 2+ clients detected as potential duplicates.
@@ -51,6 +57,17 @@ export interface DuplicateClientsSummary {
51
57
  lowConfidence: number;
52
58
  uniqueClientsInvolved: number;
53
59
  }
60
+ /**
61
+ * Per-staff alta summary for the selected period: how many clients each staff
62
+ * registered ("PERSONAL ALTA") and how many of those turned out to be duplicates
63
+ * (score >= 30). Includes staff with 0 duplicates so you can see who does it well.
64
+ * Only present when a period filter (createdFrom/createdTo) is applied.
65
+ */
66
+ export interface DuplicateAltaStaffSummary {
67
+ staff: string;
68
+ created: number;
69
+ duplicates: number;
70
+ }
54
71
  /**
55
72
  * Full response for duplicate client detection endpoint.
56
73
  *
@@ -59,6 +76,7 @@ export interface DuplicateClientsSummary {
59
76
  export interface DuplicateClientsResponse {
60
77
  summary: DuplicateClientsSummary;
61
78
  groups: DuplicateClientGroup[];
79
+ altasSummary?: DuplicateAltaStaffSummary[];
62
80
  }
63
81
  /**
64
82
  * Query parameters for filtering duplicate detection results.
@@ -68,4 +86,12 @@ export interface DuplicateClientsQueryParams {
68
86
  minScore?: number;
69
87
  confidence?: DuplicateClientConfidence;
70
88
  includeInactive?: boolean;
89
+ /**
90
+ * Period filter (YYYY-MM-DD, MX timezone). When set, only groups that contain
91
+ * at least one client CREATED in [createdFrom, createdTo] are returned — i.e.
92
+ * "which clients registered this period duplicate an existing one". Detection
93
+ * still compares against ALL clients (previous ones included).
94
+ */
95
+ createdFrom?: string;
96
+ createdTo?: string;
71
97
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "hvp-shared",
3
- "version": "13.11.0",
3
+ "version": "13.13.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",