@blackcode_sa/metaestetics-api 1.12.49 → 1.12.51

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.
@@ -169,6 +169,73 @@ var BrandService = class extends BaseService {
169
169
  ...docSnap.data()
170
170
  };
171
171
  }
172
+ /**
173
+ * Exports brands to CSV string, suitable for Excel/Sheets.
174
+ * Includes headers and optional UTF-8 BOM.
175
+ * By default exports only active brands (set includeInactive to true to export all).
176
+ */
177
+ async exportToCsv(options) {
178
+ var _a, _b;
179
+ const includeInactive = (_a = options == null ? void 0 : options.includeInactive) != null ? _a : false;
180
+ const includeBom = (_b = options == null ? void 0 : options.includeBom) != null ? _b : true;
181
+ const headers = [
182
+ "id",
183
+ "name",
184
+ "manufacturer",
185
+ "website",
186
+ "description",
187
+ "isActive"
188
+ ];
189
+ const rows = [];
190
+ rows.push(headers.map((h) => this.formatCsvValue(h)).join(","));
191
+ const PAGE_SIZE = 1e3;
192
+ let cursor;
193
+ const baseConstraints = [];
194
+ if (!includeInactive) {
195
+ baseConstraints.push(where("isActive", "==", true));
196
+ }
197
+ baseConstraints.push(orderBy("name_lowercase"));
198
+ while (true) {
199
+ const constraints = [...baseConstraints, limit(PAGE_SIZE)];
200
+ if (cursor) constraints.push(startAfter(cursor));
201
+ const q = query(this.getBrandsRef(), ...constraints);
202
+ const snapshot = await getDocs(q);
203
+ if (snapshot.empty) break;
204
+ for (const d of snapshot.docs) {
205
+ const brand = { id: d.id, ...d.data() };
206
+ rows.push(this.brandToCsvRow(brand));
207
+ }
208
+ cursor = snapshot.docs[snapshot.docs.length - 1];
209
+ if (snapshot.size < PAGE_SIZE) break;
210
+ }
211
+ const csvBody = rows.join("\r\n");
212
+ return includeBom ? "\uFEFF" + csvBody : csvBody;
213
+ }
214
+ brandToCsvRow(brand) {
215
+ var _a, _b, _c, _d, _e, _f;
216
+ const values = [
217
+ (_a = brand.id) != null ? _a : "",
218
+ (_b = brand.name) != null ? _b : "",
219
+ (_c = brand.manufacturer) != null ? _c : "",
220
+ (_d = brand.website) != null ? _d : "",
221
+ (_e = brand.description) != null ? _e : "",
222
+ String((_f = brand.isActive) != null ? _f : "")
223
+ ];
224
+ return values.map((v) => this.formatCsvValue(v)).join(",");
225
+ }
226
+ formatDateIso(value) {
227
+ if (value instanceof Date) return value.toISOString();
228
+ if (value && typeof value.toDate === "function") {
229
+ const d = value.toDate();
230
+ return d instanceof Date ? d.toISOString() : String(value);
231
+ }
232
+ return String(value != null ? value : "");
233
+ }
234
+ formatCsvValue(value) {
235
+ const str = value === null || value === void 0 ? "" : String(value);
236
+ const escaped = str.replace(/"/g, '""');
237
+ return `"${escaped}"`;
238
+ }
172
239
  };
173
240
 
174
241
  // src/backoffice/services/category.service.ts
@@ -367,6 +434,71 @@ var CategoryService = class extends BaseService {
367
434
  ...docSnap.data()
368
435
  };
369
436
  }
437
+ /**
438
+ * Exports categories to CSV string, suitable for Excel/Sheets.
439
+ * Includes headers and optional UTF-8 BOM.
440
+ * By default exports only active categories (set includeInactive to true to export all).
441
+ */
442
+ async exportToCsv(options) {
443
+ var _a, _b;
444
+ const includeInactive = (_a = options == null ? void 0 : options.includeInactive) != null ? _a : false;
445
+ const includeBom = (_b = options == null ? void 0 : options.includeBom) != null ? _b : true;
446
+ const headers = [
447
+ "id",
448
+ "name",
449
+ "description",
450
+ "family",
451
+ "isActive"
452
+ ];
453
+ const rows = [];
454
+ rows.push(headers.map((h) => this.formatCsvValue(h)).join(","));
455
+ const PAGE_SIZE = 1e3;
456
+ let cursor;
457
+ const constraints = [];
458
+ if (!includeInactive) {
459
+ constraints.push(where2("isActive", "==", true));
460
+ }
461
+ constraints.push(orderBy2("name"));
462
+ while (true) {
463
+ const queryConstraints = [...constraints, limit2(PAGE_SIZE)];
464
+ if (cursor) queryConstraints.push(startAfter2(cursor));
465
+ const q = query2(this.categoriesRef, ...queryConstraints);
466
+ const snapshot = await getDocs2(q);
467
+ if (snapshot.empty) break;
468
+ for (const d of snapshot.docs) {
469
+ const category = { id: d.id, ...d.data() };
470
+ rows.push(this.categoryToCsvRow(category));
471
+ }
472
+ cursor = snapshot.docs[snapshot.docs.length - 1];
473
+ if (snapshot.size < PAGE_SIZE) break;
474
+ }
475
+ const csvBody = rows.join("\r\n");
476
+ return includeBom ? "\uFEFF" + csvBody : csvBody;
477
+ }
478
+ categoryToCsvRow(category) {
479
+ var _a, _b, _c, _d, _e;
480
+ const values = [
481
+ (_a = category.id) != null ? _a : "",
482
+ (_b = category.name) != null ? _b : "",
483
+ (_c = category.description) != null ? _c : "",
484
+ (_d = category.family) != null ? _d : "",
485
+ String((_e = category.isActive) != null ? _e : "")
486
+ ];
487
+ return values.map((v) => this.formatCsvValue(v)).join(",");
488
+ }
489
+ formatDateIso(value) {
490
+ if (value instanceof Date) return value.toISOString();
491
+ if (value && typeof value.toDate === "function") {
492
+ const d = value.toDate();
493
+ return d instanceof Date ? d.toISOString() : String(value);
494
+ }
495
+ return String(value != null ? value : "");
496
+ }
497
+ formatCsvValue(value) {
498
+ const str = value === null || value === void 0 ? "" : String(value);
499
+ const escaped = str.replace(/"/g, '""');
500
+ return `"${escaped}"`;
501
+ }
370
502
  };
371
503
 
372
504
  // src/services/documentation-templates/documentation-template.service.ts
@@ -1158,7 +1290,9 @@ import {
1158
1290
  limit as limit6,
1159
1291
  orderBy as orderBy6,
1160
1292
  startAfter as startAfter5,
1161
- getCountFromServer as getCountFromServer4
1293
+ getCountFromServer as getCountFromServer4,
1294
+ arrayUnion,
1295
+ arrayRemove
1162
1296
  } from "firebase/firestore";
1163
1297
 
1164
1298
  // src/backoffice/types/product.types.ts
@@ -1170,7 +1304,14 @@ var TECHNOLOGIES_COLLECTION = "technologies";
1170
1304
  // src/backoffice/services/product.service.ts
1171
1305
  var ProductService = class extends BaseService {
1172
1306
  /**
1173
- * Gets reference to products collection under a technology
1307
+ * Gets reference to top-level products collection (source of truth)
1308
+ * @returns Firestore collection reference
1309
+ */
1310
+ getTopLevelProductsRef() {
1311
+ return collection6(this.db, PRODUCTS_COLLECTION);
1312
+ }
1313
+ /**
1314
+ * Gets reference to products collection under a technology (backward compatibility)
1174
1315
  * @param technologyId - ID of the technology
1175
1316
  * @returns Firestore collection reference
1176
1317
  */
@@ -1186,6 +1327,7 @@ var ProductService = class extends BaseService {
1186
1327
  ...product,
1187
1328
  brandId,
1188
1329
  technologyId,
1330
+ // Required for old subcollection structure
1189
1331
  createdAt: now,
1190
1332
  updatedAt: now,
1191
1333
  isActive: true
@@ -1245,30 +1387,26 @@ var ProductService = class extends BaseService {
1245
1387
  }
1246
1388
  /**
1247
1389
  * Gets counts of active products grouped by category, subcategory, and technology.
1248
- * This uses a single collectionGroup query for efficiency.
1390
+ * Queries technology subcollections which have the legacy fields synced by Cloud Functions.
1249
1391
  */
1250
1392
  async getProductCounts() {
1251
- const q = query6(collectionGroup(this.db, PRODUCTS_COLLECTION), where6("isActive", "==", true));
1252
- const snapshot = await getDocs6(q);
1253
1393
  const counts = {
1254
1394
  byCategory: {},
1255
1395
  bySubcategory: {},
1256
1396
  byTechnology: {}
1257
1397
  };
1258
- if (snapshot.empty) {
1259
- return counts;
1260
- }
1398
+ const q = query6(collectionGroup(this.db, PRODUCTS_COLLECTION), where6("isActive", "==", true));
1399
+ const snapshot = await getDocs6(q);
1261
1400
  snapshot.docs.forEach((doc11) => {
1262
1401
  const product = doc11.data();
1263
- const { categoryId, subcategoryId, technologyId } = product;
1264
- if (categoryId) {
1265
- counts.byCategory[categoryId] = (counts.byCategory[categoryId] || 0) + 1;
1402
+ if (product.categoryId) {
1403
+ counts.byCategory[product.categoryId] = (counts.byCategory[product.categoryId] || 0) + 1;
1266
1404
  }
1267
- if (subcategoryId) {
1268
- counts.bySubcategory[subcategoryId] = (counts.bySubcategory[subcategoryId] || 0) + 1;
1405
+ if (product.subcategoryId) {
1406
+ counts.bySubcategory[product.subcategoryId] = (counts.bySubcategory[product.subcategoryId] || 0) + 1;
1269
1407
  }
1270
- if (technologyId) {
1271
- counts.byTechnology[technologyId] = (counts.byTechnology[technologyId] || 0) + 1;
1408
+ if (product.technologyId) {
1409
+ counts.byTechnology[product.technologyId] = (counts.byTechnology[product.technologyId] || 0) + 1;
1272
1410
  }
1273
1411
  });
1274
1412
  return counts;
@@ -1347,6 +1485,241 @@ var ProductService = class extends BaseService {
1347
1485
  ...docSnap.data()
1348
1486
  };
1349
1487
  }
1488
+ // ==========================================
1489
+ // NEW METHODS: Top-level collection (preferred)
1490
+ // ==========================================
1491
+ /**
1492
+ * Creates a new product in the top-level collection
1493
+ */
1494
+ async createTopLevel(brandId, product, technologyIds = []) {
1495
+ const now = /* @__PURE__ */ new Date();
1496
+ const newProduct = {
1497
+ ...product,
1498
+ brandId,
1499
+ assignedTechnologyIds: technologyIds,
1500
+ createdAt: now,
1501
+ updatedAt: now,
1502
+ isActive: true
1503
+ };
1504
+ const productRef = await addDoc3(this.getTopLevelProductsRef(), newProduct);
1505
+ return { id: productRef.id, ...newProduct };
1506
+ }
1507
+ /**
1508
+ * Gets all products from the top-level collection
1509
+ */
1510
+ async getAllTopLevel(options) {
1511
+ const { rowsPerPage, lastVisible, brandId } = options;
1512
+ const constraints = [where6("isActive", "==", true), orderBy6("name")];
1513
+ if (brandId) {
1514
+ constraints.push(where6("brandId", "==", brandId));
1515
+ }
1516
+ if (lastVisible) {
1517
+ constraints.push(startAfter5(lastVisible));
1518
+ }
1519
+ constraints.push(limit6(rowsPerPage));
1520
+ const q = query6(this.getTopLevelProductsRef(), ...constraints);
1521
+ const snapshot = await getDocs6(q);
1522
+ const products = snapshot.docs.map(
1523
+ (doc11) => ({
1524
+ id: doc11.id,
1525
+ ...doc11.data()
1526
+ })
1527
+ );
1528
+ const newLastVisible = snapshot.docs[snapshot.docs.length - 1];
1529
+ return { products, lastVisible: newLastVisible };
1530
+ }
1531
+ /**
1532
+ * Gets a product by ID from the top-level collection
1533
+ */
1534
+ async getByIdTopLevel(productId) {
1535
+ const docRef = doc6(this.getTopLevelProductsRef(), productId);
1536
+ const docSnap = await getDoc6(docRef);
1537
+ if (!docSnap.exists()) return null;
1538
+ return {
1539
+ id: docSnap.id,
1540
+ ...docSnap.data()
1541
+ };
1542
+ }
1543
+ /**
1544
+ * Updates a product in the top-level collection
1545
+ */
1546
+ async updateTopLevel(productId, product) {
1547
+ const updateData = {
1548
+ ...product,
1549
+ updatedAt: /* @__PURE__ */ new Date()
1550
+ };
1551
+ const docRef = doc6(this.getTopLevelProductsRef(), productId);
1552
+ await updateDoc6(docRef, updateData);
1553
+ return this.getByIdTopLevel(productId);
1554
+ }
1555
+ /**
1556
+ * Deletes a product from the top-level collection (soft delete)
1557
+ */
1558
+ async deleteTopLevel(productId) {
1559
+ await this.updateTopLevel(productId, {
1560
+ isActive: false
1561
+ });
1562
+ }
1563
+ /**
1564
+ * Assigns a product to a technology
1565
+ */
1566
+ async assignToTechnology(productId, technologyId) {
1567
+ const docRef = doc6(this.getTopLevelProductsRef(), productId);
1568
+ await updateDoc6(docRef, {
1569
+ assignedTechnologyIds: arrayUnion(technologyId),
1570
+ updatedAt: /* @__PURE__ */ new Date()
1571
+ });
1572
+ }
1573
+ /**
1574
+ * Unassigns a product from a technology
1575
+ */
1576
+ async unassignFromTechnology(productId, technologyId) {
1577
+ const docRef = doc6(this.getTopLevelProductsRef(), productId);
1578
+ await updateDoc6(docRef, {
1579
+ assignedTechnologyIds: arrayRemove(technologyId),
1580
+ updatedAt: /* @__PURE__ */ new Date()
1581
+ });
1582
+ }
1583
+ /**
1584
+ * Gets products assigned to a specific technology
1585
+ */
1586
+ async getAssignedProducts(technologyId) {
1587
+ const q = query6(
1588
+ this.getTopLevelProductsRef(),
1589
+ where6("assignedTechnologyIds", "array-contains", technologyId),
1590
+ where6("isActive", "==", true),
1591
+ orderBy6("name")
1592
+ );
1593
+ const snapshot = await getDocs6(q);
1594
+ return snapshot.docs.map(
1595
+ (doc11) => ({
1596
+ id: doc11.id,
1597
+ ...doc11.data()
1598
+ })
1599
+ );
1600
+ }
1601
+ /**
1602
+ * Gets products NOT assigned to a specific technology
1603
+ */
1604
+ async getUnassignedProducts(technologyId) {
1605
+ const q = query6(
1606
+ this.getTopLevelProductsRef(),
1607
+ where6("isActive", "==", true),
1608
+ orderBy6("name")
1609
+ );
1610
+ const snapshot = await getDocs6(q);
1611
+ const allProducts = snapshot.docs.map(
1612
+ (doc11) => ({
1613
+ id: doc11.id,
1614
+ ...doc11.data()
1615
+ })
1616
+ );
1617
+ return allProducts.filter(
1618
+ (product) => {
1619
+ var _a;
1620
+ return !((_a = product.assignedTechnologyIds) == null ? void 0 : _a.includes(technologyId));
1621
+ }
1622
+ );
1623
+ }
1624
+ /**
1625
+ * Gets all products for a brand (from top-level collection)
1626
+ */
1627
+ async getByBrand(brandId) {
1628
+ const q = query6(
1629
+ this.getTopLevelProductsRef(),
1630
+ where6("brandId", "==", brandId),
1631
+ where6("isActive", "==", true),
1632
+ orderBy6("name")
1633
+ );
1634
+ const snapshot = await getDocs6(q);
1635
+ return snapshot.docs.map(
1636
+ (doc11) => ({
1637
+ id: doc11.id,
1638
+ ...doc11.data()
1639
+ })
1640
+ );
1641
+ }
1642
+ /**
1643
+ * Exports products to CSV string, suitable for Excel/Sheets.
1644
+ * Includes headers and optional UTF-8 BOM.
1645
+ * By default exports only active products (set includeInactive to true to export all).
1646
+ */
1647
+ async exportToCsv(options) {
1648
+ var _a, _b;
1649
+ const includeInactive = (_a = options == null ? void 0 : options.includeInactive) != null ? _a : false;
1650
+ const includeBom = (_b = options == null ? void 0 : options.includeBom) != null ? _b : true;
1651
+ const headers = [
1652
+ "id",
1653
+ "name",
1654
+ "brandId",
1655
+ "brandName",
1656
+ "assignedTechnologyIds",
1657
+ "description",
1658
+ "technicalDetails",
1659
+ "dosage",
1660
+ "composition",
1661
+ "indications",
1662
+ "contraindications",
1663
+ "warnings",
1664
+ "isActive"
1665
+ ];
1666
+ const rows = [];
1667
+ rows.push(headers.map((h) => this.formatCsvValue(h)).join(","));
1668
+ const PAGE_SIZE = 1e3;
1669
+ let cursor;
1670
+ const constraints = [];
1671
+ if (!includeInactive) {
1672
+ constraints.push(where6("isActive", "==", true));
1673
+ }
1674
+ constraints.push(orderBy6("name"));
1675
+ while (true) {
1676
+ const queryConstraints = [...constraints, limit6(PAGE_SIZE)];
1677
+ if (cursor) queryConstraints.push(startAfter5(cursor));
1678
+ const q = query6(this.getTopLevelProductsRef(), ...queryConstraints);
1679
+ const snapshot = await getDocs6(q);
1680
+ if (snapshot.empty) break;
1681
+ for (const d of snapshot.docs) {
1682
+ const product = { id: d.id, ...d.data() };
1683
+ rows.push(this.productToCsvRow(product));
1684
+ }
1685
+ cursor = snapshot.docs[snapshot.docs.length - 1];
1686
+ if (snapshot.size < PAGE_SIZE) break;
1687
+ }
1688
+ const csvBody = rows.join("\r\n");
1689
+ return includeBom ? "\uFEFF" + csvBody : csvBody;
1690
+ }
1691
+ productToCsvRow(product) {
1692
+ var _a, _b, _c, _d, _e, _f, _g, _h, _i, _j, _k, _l, _m, _n, _o, _p, _q;
1693
+ const values = [
1694
+ (_a = product.id) != null ? _a : "",
1695
+ (_b = product.name) != null ? _b : "",
1696
+ (_c = product.brandId) != null ? _c : "",
1697
+ (_d = product.brandName) != null ? _d : "",
1698
+ (_f = (_e = product.assignedTechnologyIds) == null ? void 0 : _e.join(";")) != null ? _f : "",
1699
+ (_g = product.description) != null ? _g : "",
1700
+ (_h = product.technicalDetails) != null ? _h : "",
1701
+ (_i = product.dosage) != null ? _i : "",
1702
+ (_j = product.composition) != null ? _j : "",
1703
+ (_l = (_k = product.indications) == null ? void 0 : _k.join(";")) != null ? _l : "",
1704
+ (_n = (_m = product.contraindications) == null ? void 0 : _m.map((c) => c.name).join(";")) != null ? _n : "",
1705
+ (_p = (_o = product.warnings) == null ? void 0 : _o.join(";")) != null ? _p : "",
1706
+ String((_q = product.isActive) != null ? _q : "")
1707
+ ];
1708
+ return values.map((v) => this.formatCsvValue(v)).join(",");
1709
+ }
1710
+ formatDateIso(value) {
1711
+ if (value instanceof Date) return value.toISOString();
1712
+ if (value && typeof value.toDate === "function") {
1713
+ const d = value.toDate();
1714
+ return d instanceof Date ? d.toISOString() : String(value);
1715
+ }
1716
+ return String(value != null ? value : "");
1717
+ }
1718
+ formatCsvValue(value) {
1719
+ const str = value === null || value === void 0 ? "" : String(value);
1720
+ const escaped = str.replace(/"/g, '""');
1721
+ return `"${escaped}"`;
1722
+ }
1350
1723
  };
1351
1724
 
1352
1725
  // src/backoffice/services/requirement.service.ts
@@ -1358,7 +1731,8 @@ import {
1358
1731
  getDocs as getDocs7,
1359
1732
  query as query7,
1360
1733
  updateDoc as updateDoc7,
1361
- where as where7
1734
+ where as where7,
1735
+ orderBy as orderBy7
1362
1736
  } from "firebase/firestore";
1363
1737
 
1364
1738
  // src/backoffice/types/requirement.types.ts
@@ -1479,6 +1853,65 @@ var RequirementService = class extends BaseService {
1479
1853
  ...docSnap.data()
1480
1854
  };
1481
1855
  }
1856
+ /**
1857
+ * Exports requirements to CSV string, suitable for Excel/Sheets.
1858
+ * Includes headers and optional UTF-8 BOM.
1859
+ * By default exports only active requirements (set includeInactive to true to export all).
1860
+ */
1861
+ async exportToCsv(options) {
1862
+ var _a, _b;
1863
+ const includeInactive = (_a = options == null ? void 0 : options.includeInactive) != null ? _a : false;
1864
+ const includeBom = (_b = options == null ? void 0 : options.includeBom) != null ? _b : true;
1865
+ const headers = [
1866
+ "id",
1867
+ "type",
1868
+ "name",
1869
+ "description",
1870
+ "timeframe_duration",
1871
+ "timeframe_unit",
1872
+ "timeframe_notifyAt",
1873
+ "importance",
1874
+ "isActive"
1875
+ ];
1876
+ const rows = [];
1877
+ rows.push(headers.map((h) => this.formatCsvValue(h)).join(","));
1878
+ const q = includeInactive ? query7(this.requirementsRef, orderBy7("name")) : query7(this.requirementsRef, where7("isActive", "==", true), orderBy7("name"));
1879
+ const snapshot = await getDocs7(q);
1880
+ for (const d of snapshot.docs) {
1881
+ const requirement = { id: d.id, ...d.data() };
1882
+ rows.push(this.requirementToCsvRow(requirement));
1883
+ }
1884
+ const csvBody = rows.join("\r\n");
1885
+ return includeBom ? "\uFEFF" + csvBody : csvBody;
1886
+ }
1887
+ requirementToCsvRow(requirement) {
1888
+ var _a, _b, _c, _d, _e, _f, _g, _h, _i, _j, _k, _l, _m, _n;
1889
+ const values = [
1890
+ (_a = requirement.id) != null ? _a : "",
1891
+ (_b = requirement.type) != null ? _b : "",
1892
+ (_c = requirement.name) != null ? _c : "",
1893
+ (_d = requirement.description) != null ? _d : "",
1894
+ (_g = (_f = (_e = requirement.timeframe) == null ? void 0 : _e.duration) == null ? void 0 : _f.toString()) != null ? _g : "",
1895
+ (_i = (_h = requirement.timeframe) == null ? void 0 : _h.unit) != null ? _i : "",
1896
+ (_l = (_k = (_j = requirement.timeframe) == null ? void 0 : _j.notifyAt) == null ? void 0 : _k.join(";")) != null ? _l : "",
1897
+ (_m = requirement.importance) != null ? _m : "",
1898
+ String((_n = requirement.isActive) != null ? _n : "")
1899
+ ];
1900
+ return values.map((v) => this.formatCsvValue(v)).join(",");
1901
+ }
1902
+ formatDateIso(value) {
1903
+ if (value instanceof Date) return value.toISOString();
1904
+ if (value && typeof value.toDate === "function") {
1905
+ const d = value.toDate();
1906
+ return d instanceof Date ? d.toISOString() : String(value);
1907
+ }
1908
+ return String(value != null ? value : "");
1909
+ }
1910
+ formatCsvValue(value) {
1911
+ const str = value === null || value === void 0 ? "" : String(value);
1912
+ const escaped = str.replace(/"/g, '""');
1913
+ return `"${escaped}"`;
1914
+ }
1482
1915
  };
1483
1916
 
1484
1917
  // src/backoffice/services/subcategory.service.ts
@@ -1492,7 +1925,7 @@ import {
1492
1925
  getDoc as getDoc8,
1493
1926
  getDocs as getDocs8,
1494
1927
  limit as limit7,
1495
- orderBy as orderBy7,
1928
+ orderBy as orderBy8,
1496
1929
  query as query8,
1497
1930
  setDoc as setDoc4,
1498
1931
  startAfter as startAfter6,
@@ -1566,7 +1999,7 @@ var SubcategoryService = class extends BaseService {
1566
1999
  const { active = true, limit: queryLimit = 10, lastVisible } = options;
1567
2000
  const constraints = [
1568
2001
  where8("isActive", "==", active),
1569
- orderBy7("name"),
2002
+ orderBy8("name"),
1570
2003
  queryLimit ? limit7(queryLimit) : void 0,
1571
2004
  lastVisible ? startAfter6(lastVisible) : void 0
1572
2005
  ].filter((c) => !!c);
@@ -1593,7 +2026,7 @@ var SubcategoryService = class extends BaseService {
1593
2026
  const { active = true, limit: queryLimit = 10, lastVisible } = options;
1594
2027
  const constraints = [
1595
2028
  where8("isActive", "==", active),
1596
- orderBy7("name"),
2029
+ orderBy8("name"),
1597
2030
  queryLimit ? limit7(queryLimit) : void 0,
1598
2031
  lastVisible ? startAfter6(lastVisible) : void 0
1599
2032
  ].filter((c) => !!c);
@@ -1722,6 +2155,74 @@ var SubcategoryService = class extends BaseService {
1722
2155
  ...docSnap.data()
1723
2156
  };
1724
2157
  }
2158
+ /**
2159
+ * Exports subcategories to CSV string, suitable for Excel/Sheets.
2160
+ * Includes headers and optional UTF-8 BOM.
2161
+ * By default exports only active subcategories (set includeInactive to true to export all).
2162
+ */
2163
+ async exportToCsv(options) {
2164
+ var _a, _b;
2165
+ const includeInactive = (_a = options == null ? void 0 : options.includeInactive) != null ? _a : false;
2166
+ const includeBom = (_b = options == null ? void 0 : options.includeBom) != null ? _b : true;
2167
+ const headers = [
2168
+ "id",
2169
+ "name",
2170
+ "categoryId",
2171
+ "description",
2172
+ "isActive"
2173
+ ];
2174
+ const rows = [];
2175
+ rows.push(headers.map((h) => this.formatCsvValue(h)).join(","));
2176
+ const PAGE_SIZE = 1e3;
2177
+ let cursor;
2178
+ const constraints = [];
2179
+ if (!includeInactive) {
2180
+ constraints.push(where8("isActive", "==", true));
2181
+ }
2182
+ constraints.push(orderBy8("name"));
2183
+ while (true) {
2184
+ const queryConstraints = [...constraints, limit7(PAGE_SIZE)];
2185
+ if (cursor) queryConstraints.push(startAfter6(cursor));
2186
+ const q = query8(
2187
+ collectionGroup2(this.db, SUBCATEGORIES_COLLECTION),
2188
+ ...queryConstraints
2189
+ );
2190
+ const snapshot = await getDocs8(q);
2191
+ if (snapshot.empty) break;
2192
+ for (const d of snapshot.docs) {
2193
+ const subcategory = { id: d.id, ...d.data() };
2194
+ rows.push(this.subcategoryToCsvRow(subcategory));
2195
+ }
2196
+ cursor = snapshot.docs[snapshot.docs.length - 1];
2197
+ if (snapshot.size < PAGE_SIZE) break;
2198
+ }
2199
+ const csvBody = rows.join("\r\n");
2200
+ return includeBom ? "\uFEFF" + csvBody : csvBody;
2201
+ }
2202
+ subcategoryToCsvRow(subcategory) {
2203
+ var _a, _b, _c, _d, _e;
2204
+ const values = [
2205
+ (_a = subcategory.id) != null ? _a : "",
2206
+ (_b = subcategory.name) != null ? _b : "",
2207
+ (_c = subcategory.categoryId) != null ? _c : "",
2208
+ (_d = subcategory.description) != null ? _d : "",
2209
+ String((_e = subcategory.isActive) != null ? _e : "")
2210
+ ];
2211
+ return values.map((v) => this.formatCsvValue(v)).join(",");
2212
+ }
2213
+ formatDateIso(value) {
2214
+ if (value instanceof Date) return value.toISOString();
2215
+ if (value && typeof value.toDate === "function") {
2216
+ const d = value.toDate();
2217
+ return d instanceof Date ? d.toISOString() : String(value);
2218
+ }
2219
+ return String(value != null ? value : "");
2220
+ }
2221
+ formatCsvValue(value) {
2222
+ const str = value === null || value === void 0 ? "" : String(value);
2223
+ const escaped = str.replace(/"/g, '""');
2224
+ return `"${escaped}"`;
2225
+ }
1725
2226
  };
1726
2227
 
1727
2228
  // src/backoffice/services/technology.service.ts
@@ -1732,13 +2233,14 @@ import {
1732
2233
  getDoc as getDoc9,
1733
2234
  getDocs as getDocs9,
1734
2235
  limit as limit8,
1735
- orderBy as orderBy8,
2236
+ orderBy as orderBy9,
1736
2237
  query as query9,
1737
2238
  startAfter as startAfter7,
1738
2239
  updateDoc as updateDoc9,
1739
2240
  where as where9,
1740
- arrayUnion,
1741
- arrayRemove
2241
+ arrayUnion as arrayUnion2,
2242
+ arrayRemove as arrayRemove2,
2243
+ writeBatch
1742
2244
  } from "firebase/firestore";
1743
2245
 
1744
2246
  // src/backoffice/types/static/certification.types.ts
@@ -1845,7 +2347,7 @@ var TechnologyService = class extends BaseService {
1845
2347
  const { active = true, limit: queryLimit = 10, lastVisible } = options;
1846
2348
  const constraints = [
1847
2349
  where9("isActive", "==", active),
1848
- orderBy8("name"),
2350
+ orderBy9("name"),
1849
2351
  queryLimit ? limit8(queryLimit) : void 0,
1850
2352
  lastVisible ? startAfter7(lastVisible) : void 0
1851
2353
  ].filter((c) => !!c);
@@ -1871,7 +2373,7 @@ var TechnologyService = class extends BaseService {
1871
2373
  const constraints = [
1872
2374
  where9("categoryId", "==", categoryId),
1873
2375
  where9("isActive", "==", active),
1874
- orderBy8("name"),
2376
+ orderBy9("name"),
1875
2377
  queryLimit ? limit8(queryLimit) : void 0,
1876
2378
  lastVisible ? startAfter7(lastVisible) : void 0
1877
2379
  ].filter((c) => !!c);
@@ -1897,7 +2399,7 @@ var TechnologyService = class extends BaseService {
1897
2399
  const constraints = [
1898
2400
  where9("subcategoryId", "==", subcategoryId),
1899
2401
  where9("isActive", "==", active),
1900
- orderBy8("name"),
2402
+ orderBy9("name"),
1901
2403
  queryLimit ? limit8(queryLimit) : void 0,
1902
2404
  lastVisible ? startAfter7(lastVisible) : void 0
1903
2405
  ].filter((c) => !!c);
@@ -1927,7 +2429,18 @@ var TechnologyService = class extends BaseService {
1927
2429
  });
1928
2430
  updateData.updatedAt = /* @__PURE__ */ new Date();
1929
2431
  const docRef = doc9(this.technologiesRef, id);
2432
+ const beforeTech = await this.getById(id);
1930
2433
  await updateDoc9(docRef, updateData);
2434
+ const categoryChanged = beforeTech && updateData.categoryId && beforeTech.categoryId !== updateData.categoryId;
2435
+ const subcategoryChanged = beforeTech && updateData.subcategoryId && beforeTech.subcategoryId !== updateData.subcategoryId;
2436
+ const nameChanged = beforeTech && updateData.name && beforeTech.name !== updateData.name;
2437
+ if (categoryChanged || subcategoryChanged || nameChanged) {
2438
+ await this.updateProductsInSubcollection(id, {
2439
+ categoryId: updateData.categoryId,
2440
+ subcategoryId: updateData.subcategoryId,
2441
+ technologyName: updateData.name
2442
+ });
2443
+ }
1931
2444
  return this.getById(id);
1932
2445
  }
1933
2446
  /**
@@ -1968,7 +2481,7 @@ var TechnologyService = class extends BaseService {
1968
2481
  const docRef = doc9(this.technologiesRef, technologyId);
1969
2482
  const requirementType = requirement.type === "pre" ? "requirements.pre" : "requirements.post";
1970
2483
  await updateDoc9(docRef, {
1971
- [requirementType]: arrayUnion(requirement),
2484
+ [requirementType]: arrayUnion2(requirement),
1972
2485
  updatedAt: /* @__PURE__ */ new Date()
1973
2486
  });
1974
2487
  return this.getById(technologyId);
@@ -1983,7 +2496,7 @@ var TechnologyService = class extends BaseService {
1983
2496
  const docRef = doc9(this.technologiesRef, technologyId);
1984
2497
  const requirementType = requirement.type === "pre" ? "requirements.pre" : "requirements.post";
1985
2498
  await updateDoc9(docRef, {
1986
- [requirementType]: arrayRemove(requirement),
2499
+ [requirementType]: arrayRemove2(requirement),
1987
2500
  updatedAt: /* @__PURE__ */ new Date()
1988
2501
  });
1989
2502
  return this.getById(technologyId);
@@ -2022,7 +2535,7 @@ var TechnologyService = class extends BaseService {
2022
2535
  async addBlockingCondition(technologyId, condition) {
2023
2536
  const docRef = doc9(this.technologiesRef, technologyId);
2024
2537
  await updateDoc9(docRef, {
2025
- blockingConditions: arrayUnion(condition),
2538
+ blockingConditions: arrayUnion2(condition),
2026
2539
  updatedAt: /* @__PURE__ */ new Date()
2027
2540
  });
2028
2541
  return this.getById(technologyId);
@@ -2036,7 +2549,7 @@ var TechnologyService = class extends BaseService {
2036
2549
  async removeBlockingCondition(technologyId, condition) {
2037
2550
  const docRef = doc9(this.technologiesRef, technologyId);
2038
2551
  await updateDoc9(docRef, {
2039
- blockingConditions: arrayRemove(condition),
2552
+ blockingConditions: arrayRemove2(condition),
2040
2553
  updatedAt: /* @__PURE__ */ new Date()
2041
2554
  });
2042
2555
  return this.getById(technologyId);
@@ -2315,7 +2828,7 @@ var TechnologyService = class extends BaseService {
2315
2828
  collection9(this.db, TECHNOLOGIES_COLLECTION),
2316
2829
  where9("isActive", "==", true),
2317
2830
  where9("subcategoryId", "==", subcategoryId),
2318
- orderBy8("name")
2831
+ orderBy9("name")
2319
2832
  );
2320
2833
  const snapshot = await getDocs9(q);
2321
2834
  return snapshot.docs.map(
@@ -2336,7 +2849,7 @@ var TechnologyService = class extends BaseService {
2336
2849
  where9("isActive", "==", true),
2337
2850
  where9("categoryId", "==", categoryId),
2338
2851
  where9("subcategoryId", "==", subcategoryId),
2339
- orderBy8("name")
2852
+ orderBy9("name")
2340
2853
  );
2341
2854
  const snapshot = await getDocs9(q);
2342
2855
  return snapshot.docs.map(
@@ -2353,7 +2866,7 @@ var TechnologyService = class extends BaseService {
2353
2866
  const q = query9(
2354
2867
  collection9(this.db, TECHNOLOGIES_COLLECTION),
2355
2868
  where9("isActive", "==", true),
2356
- orderBy8("name")
2869
+ orderBy9("name")
2357
2870
  );
2358
2871
  const snapshot = await getDocs9(q);
2359
2872
  return snapshot.docs.map(
@@ -2363,12 +2876,231 @@ var TechnologyService = class extends BaseService {
2363
2876
  })
2364
2877
  );
2365
2878
  }
2879
+ // ==========================================
2880
+ // NEW METHODS: Product assignment management
2881
+ // ==========================================
2882
+ /**
2883
+ * Assigns multiple products to a technology
2884
+ * Updates each product's assignedTechnologyIds array
2885
+ */
2886
+ async assignProducts(technologyId, productIds) {
2887
+ const batch = writeBatch(this.db);
2888
+ for (const productId of productIds) {
2889
+ const productRef = doc9(this.db, PRODUCTS_COLLECTION, productId);
2890
+ batch.update(productRef, {
2891
+ assignedTechnologyIds: arrayUnion2(technologyId),
2892
+ updatedAt: /* @__PURE__ */ new Date()
2893
+ });
2894
+ }
2895
+ await batch.commit();
2896
+ }
2897
+ /**
2898
+ * Unassigns multiple products from a technology
2899
+ * Updates each product's assignedTechnologyIds array
2900
+ */
2901
+ async unassignProducts(technologyId, productIds) {
2902
+ const batch = writeBatch(this.db);
2903
+ for (const productId of productIds) {
2904
+ const productRef = doc9(this.db, PRODUCTS_COLLECTION, productId);
2905
+ batch.update(productRef, {
2906
+ assignedTechnologyIds: arrayRemove2(technologyId),
2907
+ updatedAt: /* @__PURE__ */ new Date()
2908
+ });
2909
+ }
2910
+ await batch.commit();
2911
+ }
2912
+ /**
2913
+ * Gets products assigned to a specific technology
2914
+ * Reads from top-level collection for immediate consistency (Cloud Functions may lag)
2915
+ */
2916
+ async getAssignedProducts(technologyId) {
2917
+ const q = query9(
2918
+ collection9(this.db, PRODUCTS_COLLECTION),
2919
+ where9("assignedTechnologyIds", "array-contains", technologyId),
2920
+ where9("isActive", "==", true),
2921
+ orderBy9("name")
2922
+ );
2923
+ const snapshot = await getDocs9(q);
2924
+ return snapshot.docs.map(
2925
+ (doc11) => ({
2926
+ id: doc11.id,
2927
+ ...doc11.data()
2928
+ })
2929
+ );
2930
+ }
2931
+ /**
2932
+ * Gets products NOT assigned to a specific technology
2933
+ */
2934
+ async getUnassignedProducts(technologyId) {
2935
+ const q = query9(
2936
+ collection9(this.db, PRODUCTS_COLLECTION),
2937
+ where9("isActive", "==", true),
2938
+ orderBy9("name")
2939
+ );
2940
+ const snapshot = await getDocs9(q);
2941
+ const allProducts = snapshot.docs.map(
2942
+ (doc11) => ({
2943
+ id: doc11.id,
2944
+ ...doc11.data()
2945
+ })
2946
+ );
2947
+ return allProducts.filter(
2948
+ (product) => {
2949
+ var _a;
2950
+ return !((_a = product.assignedTechnologyIds) == null ? void 0 : _a.includes(technologyId));
2951
+ }
2952
+ );
2953
+ }
2954
+ /**
2955
+ * Gets product assignment statistics for a technology
2956
+ */
2957
+ async getProductStats(technologyId) {
2958
+ const products = await this.getAssignedProducts(technologyId);
2959
+ const byBrand = {};
2960
+ products.forEach((product) => {
2961
+ byBrand[product.brandName] = (byBrand[product.brandName] || 0) + 1;
2962
+ });
2963
+ return {
2964
+ totalAssigned: products.length,
2965
+ byBrand
2966
+ };
2967
+ }
2968
+ /**
2969
+ * Updates products in technology subcollection when technology metadata changes
2970
+ * @param technologyId - ID of the technology
2971
+ * @param updates - Fields to update (categoryId, subcategoryId, technologyName)
2972
+ */
2973
+ async updateProductsInSubcollection(technologyId, updates) {
2974
+ const productsRef = collection9(this.db, TECHNOLOGIES_COLLECTION, technologyId, PRODUCTS_COLLECTION);
2975
+ const productsSnapshot = await getDocs9(productsRef);
2976
+ if (productsSnapshot.empty) {
2977
+ return;
2978
+ }
2979
+ const batch = writeBatch(this.db);
2980
+ for (const productDoc of productsSnapshot.docs) {
2981
+ const productRef = productDoc.ref;
2982
+ const updateFields = {};
2983
+ if (updates.categoryId !== void 0) {
2984
+ updateFields.categoryId = updates.categoryId;
2985
+ }
2986
+ if (updates.subcategoryId !== void 0) {
2987
+ updateFields.subcategoryId = updates.subcategoryId;
2988
+ }
2989
+ if (updates.technologyName !== void 0) {
2990
+ updateFields.technologyName = updates.technologyName;
2991
+ }
2992
+ if (Object.keys(updateFields).length > 0) {
2993
+ batch.update(productRef, updateFields);
2994
+ }
2995
+ }
2996
+ await batch.commit();
2997
+ }
2998
+ /**
2999
+ * Exports technologies to CSV string, suitable for Excel/Sheets.
3000
+ * Includes headers and optional UTF-8 BOM.
3001
+ * By default exports only active technologies (set includeInactive to true to export all).
3002
+ * Includes product names from subcollections.
3003
+ */
3004
+ async exportToCsv(options) {
3005
+ var _a, _b;
3006
+ const includeInactive = (_a = options == null ? void 0 : options.includeInactive) != null ? _a : false;
3007
+ const includeBom = (_b = options == null ? void 0 : options.includeBom) != null ? _b : true;
3008
+ const headers = [
3009
+ "id",
3010
+ "name",
3011
+ "description",
3012
+ "family",
3013
+ "categoryId",
3014
+ "subcategoryId",
3015
+ "technicalDetails",
3016
+ "requirements_pre",
3017
+ "requirements_post",
3018
+ "blockingConditions",
3019
+ "contraindications",
3020
+ "benefits",
3021
+ "certificationMinimumLevel",
3022
+ "certificationRequiredSpecialties",
3023
+ "documentationTemplateIds",
3024
+ "productNames",
3025
+ "isActive"
3026
+ ];
3027
+ const rows = [];
3028
+ rows.push(headers.map((h) => this.formatCsvValue(h)).join(","));
3029
+ const PAGE_SIZE = 1e3;
3030
+ let cursor;
3031
+ const constraints = [];
3032
+ if (!includeInactive) {
3033
+ constraints.push(where9("isActive", "==", true));
3034
+ }
3035
+ constraints.push(orderBy9("name"));
3036
+ while (true) {
3037
+ const queryConstraints = [...constraints, limit8(PAGE_SIZE)];
3038
+ if (cursor) queryConstraints.push(startAfter7(cursor));
3039
+ const q = query9(this.technologiesRef, ...queryConstraints);
3040
+ const snapshot = await getDocs9(q);
3041
+ if (snapshot.empty) break;
3042
+ for (const d of snapshot.docs) {
3043
+ const technology = { id: d.id, ...d.data() };
3044
+ const productNames = await this.getProductNamesForTechnology(technology.id);
3045
+ rows.push(this.technologyToCsvRow(technology, productNames));
3046
+ }
3047
+ cursor = snapshot.docs[snapshot.docs.length - 1];
3048
+ if (snapshot.size < PAGE_SIZE) break;
3049
+ }
3050
+ const csvBody = rows.join("\r\n");
3051
+ return includeBom ? "\uFEFF" + csvBody : csvBody;
3052
+ }
3053
+ /**
3054
+ * Gets product names from the technology's product subcollection
3055
+ */
3056
+ async getProductNamesForTechnology(technologyId) {
3057
+ try {
3058
+ const productsRef = collection9(this.db, TECHNOLOGIES_COLLECTION, technologyId, PRODUCTS_COLLECTION);
3059
+ const q = query9(productsRef, where9("isActive", "==", true));
3060
+ const snapshot = await getDocs9(q);
3061
+ return snapshot.docs.map((doc11) => {
3062
+ const product = doc11.data();
3063
+ return product.name || "";
3064
+ }).filter((name) => name);
3065
+ } catch (error) {
3066
+ console.error(`Error fetching products for technology ${technologyId}:`, error);
3067
+ return [];
3068
+ }
3069
+ }
3070
+ technologyToCsvRow(technology, productNames = []) {
3071
+ var _a, _b, _c, _d, _e, _f, _g, _h, _i, _j, _k, _l, _m, _n, _o, _p, _q, _r, _s, _t, _u, _v, _w, _x, _y, _z, _A;
3072
+ const values = [
3073
+ (_a = technology.id) != null ? _a : "",
3074
+ (_b = technology.name) != null ? _b : "",
3075
+ (_c = technology.description) != null ? _c : "",
3076
+ (_d = technology.family) != null ? _d : "",
3077
+ (_e = technology.categoryId) != null ? _e : "",
3078
+ (_f = technology.subcategoryId) != null ? _f : "",
3079
+ (_g = technology.technicalDetails) != null ? _g : "",
3080
+ (_j = (_i = (_h = technology.requirements) == null ? void 0 : _h.pre) == null ? void 0 : _i.map((r) => r.name).join(";")) != null ? _j : "",
3081
+ (_m = (_l = (_k = technology.requirements) == null ? void 0 : _k.post) == null ? void 0 : _l.map((r) => r.name).join(";")) != null ? _m : "",
3082
+ (_o = (_n = technology.blockingConditions) == null ? void 0 : _n.join(";")) != null ? _o : "",
3083
+ (_q = (_p = technology.contraindications) == null ? void 0 : _p.map((c) => c.name).join(";")) != null ? _q : "",
3084
+ (_s = (_r = technology.benefits) == null ? void 0 : _r.map((b) => b.name).join(";")) != null ? _s : "",
3085
+ (_u = (_t = technology.certificationRequirement) == null ? void 0 : _t.minimumLevel) != null ? _u : "",
3086
+ (_x = (_w = (_v = technology.certificationRequirement) == null ? void 0 : _v.requiredSpecialties) == null ? void 0 : _w.join(";")) != null ? _x : "",
3087
+ (_z = (_y = technology.documentationTemplates) == null ? void 0 : _y.map((t) => t.templateId).join(";")) != null ? _z : "",
3088
+ productNames.join(";"),
3089
+ String((_A = technology.isActive) != null ? _A : "")
3090
+ ];
3091
+ return values.map((v) => this.formatCsvValue(v)).join(",");
3092
+ }
3093
+ formatCsvValue(value) {
3094
+ const str = value === null || value === void 0 ? "" : String(value);
3095
+ const escaped = str.replace(/"/g, '""');
3096
+ return `"${escaped}"`;
3097
+ }
2366
3098
  };
2367
3099
 
2368
3100
  // src/backoffice/services/constants.service.ts
2369
3101
  import {
2370
- arrayRemove as arrayRemove2,
2371
- arrayUnion as arrayUnion2,
3102
+ arrayRemove as arrayRemove3,
3103
+ arrayUnion as arrayUnion3,
2372
3104
  doc as doc10,
2373
3105
  getDoc as getDoc10,
2374
3106
  setDoc as setDoc5,
@@ -2436,7 +3168,7 @@ var ConstantsService = class extends BaseService {
2436
3168
  await setDoc5(this.treatmentBenefitsDocRef, { benefits: [newBenefit] });
2437
3169
  } else {
2438
3170
  await updateDoc10(this.treatmentBenefitsDocRef, {
2439
- benefits: arrayUnion2(newBenefit)
3171
+ benefits: arrayUnion3(newBenefit)
2440
3172
  });
2441
3173
  }
2442
3174
  return newBenefit;
@@ -2490,7 +3222,7 @@ var ConstantsService = class extends BaseService {
2490
3222
  return;
2491
3223
  }
2492
3224
  await updateDoc10(this.treatmentBenefitsDocRef, {
2493
- benefits: arrayRemove2(benefitToRemove)
3225
+ benefits: arrayRemove3(benefitToRemove)
2494
3226
  });
2495
3227
  }
2496
3228
  // =================================================================
@@ -2543,7 +3275,7 @@ var ConstantsService = class extends BaseService {
2543
3275
  });
2544
3276
  } else {
2545
3277
  await updateDoc10(this.contraindicationsDocRef, {
2546
- contraindications: arrayUnion2(newContraindication)
3278
+ contraindications: arrayUnion3(newContraindication)
2547
3279
  });
2548
3280
  }
2549
3281
  return newContraindication;
@@ -2599,9 +3331,69 @@ var ConstantsService = class extends BaseService {
2599
3331
  return;
2600
3332
  }
2601
3333
  await updateDoc10(this.contraindicationsDocRef, {
2602
- contraindications: arrayRemove2(toRemove)
3334
+ contraindications: arrayRemove3(toRemove)
2603
3335
  });
2604
3336
  }
3337
+ // =================================================================
3338
+ // CSV Export Methods
3339
+ // =================================================================
3340
+ /**
3341
+ * Exports treatment benefits to CSV string, suitable for Excel/Sheets.
3342
+ * Includes headers and optional UTF-8 BOM.
3343
+ */
3344
+ async exportBenefitsToCsv(options) {
3345
+ var _a;
3346
+ const includeBom = (_a = options == null ? void 0 : options.includeBom) != null ? _a : true;
3347
+ const headers = ["id", "name", "description"];
3348
+ const rows = [];
3349
+ rows.push(headers.map((h) => this.formatCsvValue(h)).join(","));
3350
+ const benefits = await this.getAllBenefitsForFilter();
3351
+ for (const benefit of benefits) {
3352
+ rows.push(this.benefitToCsvRow(benefit));
3353
+ }
3354
+ const csvBody = rows.join("\r\n");
3355
+ return includeBom ? "\uFEFF" + csvBody : csvBody;
3356
+ }
3357
+ /**
3358
+ * Exports contraindications to CSV string, suitable for Excel/Sheets.
3359
+ * Includes headers and optional UTF-8 BOM.
3360
+ */
3361
+ async exportContraindicationsToCsv(options) {
3362
+ var _a;
3363
+ const includeBom = (_a = options == null ? void 0 : options.includeBom) != null ? _a : true;
3364
+ const headers = ["id", "name", "description"];
3365
+ const rows = [];
3366
+ rows.push(headers.map((h) => this.formatCsvValue(h)).join(","));
3367
+ const contraindications = await this.getAllContraindicationsForFilter();
3368
+ for (const contraindication of contraindications) {
3369
+ rows.push(this.contraindicationToCsvRow(contraindication));
3370
+ }
3371
+ const csvBody = rows.join("\r\n");
3372
+ return includeBom ? "\uFEFF" + csvBody : csvBody;
3373
+ }
3374
+ benefitToCsvRow(benefit) {
3375
+ var _a, _b, _c;
3376
+ const values = [
3377
+ (_a = benefit.id) != null ? _a : "",
3378
+ (_b = benefit.name) != null ? _b : "",
3379
+ (_c = benefit.description) != null ? _c : ""
3380
+ ];
3381
+ return values.map((v) => this.formatCsvValue(v)).join(",");
3382
+ }
3383
+ contraindicationToCsvRow(contraindication) {
3384
+ var _a, _b, _c;
3385
+ const values = [
3386
+ (_a = contraindication.id) != null ? _a : "",
3387
+ (_b = contraindication.name) != null ? _b : "",
3388
+ (_c = contraindication.description) != null ? _c : ""
3389
+ ];
3390
+ return values.map((v) => this.formatCsvValue(v)).join(",");
3391
+ }
3392
+ formatCsvValue(value) {
3393
+ const str = value === null || value === void 0 ? "" : String(value);
3394
+ const escaped = str.replace(/"/g, '""');
3395
+ return `"${escaped}"`;
3396
+ }
2605
3397
  };
2606
3398
 
2607
3399
  // src/backoffice/types/static/blocking-condition.types.ts