@blackcode_sa/metaestetics-api 1.12.41 → 1.12.43

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
@@ -1254,9 +1386,8 @@ var ProductService = class extends BaseService {
1254
1386
  return snapshot.data().count;
1255
1387
  }
1256
1388
  /**
1257
- * Gets counts of active products grouped by technology.
1258
- * NOTE: Only counts top-level collection to avoid duplication during migration.
1259
- * Categories/subcategories not available in top-level structure.
1389
+ * Gets counts of active products grouped by category, subcategory, and technology.
1390
+ * Queries technology subcollections which have the legacy fields synced by Cloud Functions.
1260
1391
  */
1261
1392
  async getProductCounts() {
1262
1393
  const counts = {
@@ -1264,14 +1395,18 @@ var ProductService = class extends BaseService {
1264
1395
  bySubcategory: {},
1265
1396
  byTechnology: {}
1266
1397
  };
1267
- const q = query6(this.getTopLevelProductsRef(), where6("isActive", "==", true));
1398
+ const q = query6(collectionGroup(this.db, PRODUCTS_COLLECTION), where6("isActive", "==", true));
1268
1399
  const snapshot = await getDocs6(q);
1269
1400
  snapshot.docs.forEach((doc11) => {
1270
1401
  const product = doc11.data();
1271
- if (product.assignedTechnologyIds && Array.isArray(product.assignedTechnologyIds)) {
1272
- product.assignedTechnologyIds.forEach((techId) => {
1273
- counts.byTechnology[techId] = (counts.byTechnology[techId] || 0) + 1;
1274
- });
1402
+ if (product.categoryId) {
1403
+ counts.byCategory[product.categoryId] = (counts.byCategory[product.categoryId] || 0) + 1;
1404
+ }
1405
+ if (product.subcategoryId) {
1406
+ counts.bySubcategory[product.subcategoryId] = (counts.bySubcategory[product.subcategoryId] || 0) + 1;
1407
+ }
1408
+ if (product.technologyId) {
1409
+ counts.byTechnology[product.technologyId] = (counts.byTechnology[product.technologyId] || 0) + 1;
1275
1410
  }
1276
1411
  });
1277
1412
  return counts;
@@ -1504,6 +1639,87 @@ var ProductService = class extends BaseService {
1504
1639
  })
1505
1640
  );
1506
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
+ }
1507
1723
  };
1508
1724
 
1509
1725
  // src/backoffice/services/requirement.service.ts
@@ -1515,7 +1731,8 @@ import {
1515
1731
  getDocs as getDocs7,
1516
1732
  query as query7,
1517
1733
  updateDoc as updateDoc7,
1518
- where as where7
1734
+ where as where7,
1735
+ orderBy as orderBy7
1519
1736
  } from "firebase/firestore";
1520
1737
 
1521
1738
  // src/backoffice/types/requirement.types.ts
@@ -1636,6 +1853,65 @@ var RequirementService = class extends BaseService {
1636
1853
  ...docSnap.data()
1637
1854
  };
1638
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
+ }
1639
1915
  };
1640
1916
 
1641
1917
  // src/backoffice/services/subcategory.service.ts
@@ -1649,7 +1925,7 @@ import {
1649
1925
  getDoc as getDoc8,
1650
1926
  getDocs as getDocs8,
1651
1927
  limit as limit7,
1652
- orderBy as orderBy7,
1928
+ orderBy as orderBy8,
1653
1929
  query as query8,
1654
1930
  setDoc as setDoc4,
1655
1931
  startAfter as startAfter6,
@@ -1723,7 +1999,7 @@ var SubcategoryService = class extends BaseService {
1723
1999
  const { active = true, limit: queryLimit = 10, lastVisible } = options;
1724
2000
  const constraints = [
1725
2001
  where8("isActive", "==", active),
1726
- orderBy7("name"),
2002
+ orderBy8("name"),
1727
2003
  queryLimit ? limit7(queryLimit) : void 0,
1728
2004
  lastVisible ? startAfter6(lastVisible) : void 0
1729
2005
  ].filter((c) => !!c);
@@ -1750,7 +2026,7 @@ var SubcategoryService = class extends BaseService {
1750
2026
  const { active = true, limit: queryLimit = 10, lastVisible } = options;
1751
2027
  const constraints = [
1752
2028
  where8("isActive", "==", active),
1753
- orderBy7("name"),
2029
+ orderBy8("name"),
1754
2030
  queryLimit ? limit7(queryLimit) : void 0,
1755
2031
  lastVisible ? startAfter6(lastVisible) : void 0
1756
2032
  ].filter((c) => !!c);
@@ -1879,6 +2155,74 @@ var SubcategoryService = class extends BaseService {
1879
2155
  ...docSnap.data()
1880
2156
  };
1881
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
+ }
1882
2226
  };
1883
2227
 
1884
2228
  // src/backoffice/services/technology.service.ts
@@ -1889,7 +2233,7 @@ import {
1889
2233
  getDoc as getDoc9,
1890
2234
  getDocs as getDocs9,
1891
2235
  limit as limit8,
1892
- orderBy as orderBy8,
2236
+ orderBy as orderBy9,
1893
2237
  query as query9,
1894
2238
  startAfter as startAfter7,
1895
2239
  updateDoc as updateDoc9,
@@ -2003,7 +2347,7 @@ var TechnologyService = class extends BaseService {
2003
2347
  const { active = true, limit: queryLimit = 10, lastVisible } = options;
2004
2348
  const constraints = [
2005
2349
  where9("isActive", "==", active),
2006
- orderBy8("name"),
2350
+ orderBy9("name"),
2007
2351
  queryLimit ? limit8(queryLimit) : void 0,
2008
2352
  lastVisible ? startAfter7(lastVisible) : void 0
2009
2353
  ].filter((c) => !!c);
@@ -2029,7 +2373,7 @@ var TechnologyService = class extends BaseService {
2029
2373
  const constraints = [
2030
2374
  where9("categoryId", "==", categoryId),
2031
2375
  where9("isActive", "==", active),
2032
- orderBy8("name"),
2376
+ orderBy9("name"),
2033
2377
  queryLimit ? limit8(queryLimit) : void 0,
2034
2378
  lastVisible ? startAfter7(lastVisible) : void 0
2035
2379
  ].filter((c) => !!c);
@@ -2055,7 +2399,7 @@ var TechnologyService = class extends BaseService {
2055
2399
  const constraints = [
2056
2400
  where9("subcategoryId", "==", subcategoryId),
2057
2401
  where9("isActive", "==", active),
2058
- orderBy8("name"),
2402
+ orderBy9("name"),
2059
2403
  queryLimit ? limit8(queryLimit) : void 0,
2060
2404
  lastVisible ? startAfter7(lastVisible) : void 0
2061
2405
  ].filter((c) => !!c);
@@ -2085,7 +2429,18 @@ var TechnologyService = class extends BaseService {
2085
2429
  });
2086
2430
  updateData.updatedAt = /* @__PURE__ */ new Date();
2087
2431
  const docRef = doc9(this.technologiesRef, id);
2432
+ const beforeTech = await this.getById(id);
2088
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
+ }
2089
2444
  return this.getById(id);
2090
2445
  }
2091
2446
  /**
@@ -2473,7 +2828,7 @@ var TechnologyService = class extends BaseService {
2473
2828
  collection9(this.db, TECHNOLOGIES_COLLECTION),
2474
2829
  where9("isActive", "==", true),
2475
2830
  where9("subcategoryId", "==", subcategoryId),
2476
- orderBy8("name")
2831
+ orderBy9("name")
2477
2832
  );
2478
2833
  const snapshot = await getDocs9(q);
2479
2834
  return snapshot.docs.map(
@@ -2494,7 +2849,7 @@ var TechnologyService = class extends BaseService {
2494
2849
  where9("isActive", "==", true),
2495
2850
  where9("categoryId", "==", categoryId),
2496
2851
  where9("subcategoryId", "==", subcategoryId),
2497
- orderBy8("name")
2852
+ orderBy9("name")
2498
2853
  );
2499
2854
  const snapshot = await getDocs9(q);
2500
2855
  return snapshot.docs.map(
@@ -2511,7 +2866,7 @@ var TechnologyService = class extends BaseService {
2511
2866
  const q = query9(
2512
2867
  collection9(this.db, TECHNOLOGIES_COLLECTION),
2513
2868
  where9("isActive", "==", true),
2514
- orderBy8("name")
2869
+ orderBy9("name")
2515
2870
  );
2516
2871
  const snapshot = await getDocs9(q);
2517
2872
  return snapshot.docs.map(
@@ -2563,7 +2918,7 @@ var TechnologyService = class extends BaseService {
2563
2918
  collection9(this.db, PRODUCTS_COLLECTION),
2564
2919
  where9("assignedTechnologyIds", "array-contains", technologyId),
2565
2920
  where9("isActive", "==", true),
2566
- orderBy8("name")
2921
+ orderBy9("name")
2567
2922
  );
2568
2923
  const snapshot = await getDocs9(q);
2569
2924
  return snapshot.docs.map(
@@ -2580,7 +2935,7 @@ var TechnologyService = class extends BaseService {
2580
2935
  const q = query9(
2581
2936
  collection9(this.db, PRODUCTS_COLLECTION),
2582
2937
  where9("isActive", "==", true),
2583
- orderBy8("name")
2938
+ orderBy9("name")
2584
2939
  );
2585
2940
  const snapshot = await getDocs9(q);
2586
2941
  const allProducts = snapshot.docs.map(
@@ -2610,6 +2965,136 @@ var TechnologyService = class extends BaseService {
2610
2965
  byBrand
2611
2966
  };
2612
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
+ }
2613
3098
  };
2614
3099
 
2615
3100
  // src/backoffice/services/constants.service.ts
@@ -2849,6 +3334,66 @@ var ConstantsService = class extends BaseService {
2849
3334
  contraindications: arrayRemove3(toRemove)
2850
3335
  });
2851
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
+ }
2852
3397
  };
2853
3398
 
2854
3399
  // src/backoffice/types/static/blocking-condition.types.ts